summaryrefslogtreecommitdiff
path: root/fast-import.c
diff options
context:
space:
mode:
authorShawn O. Pearce <spearce@spearce.org>2007-07-15 05:40:37 (GMT)
committerShawn O. Pearce <spearce@spearce.org>2007-07-15 05:41:23 (GMT)
commitb6f3481bb456acbbb990a1045344bb06e5a40283 (patch)
tree75457994dbd8f190b5f5887b816481e936a7e7d8 /fast-import.c
parent48b4c3d5ab1610c6dc0198fe94334d78e8a82e16 (diff)
downloadgit-b6f3481bb456acbbb990a1045344bb06e5a40283.zip
git-b6f3481bb456acbbb990a1045344bb06e5a40283.tar.gz
git-b6f3481bb456acbbb990a1045344bb06e5a40283.tar.bz2
Teach fast-import to recursively copy files/directories
Some source material (e.g. Subversion dump files) perform directory renames by telling us the directory was copied, then deleted in the same revision. This makes it difficult for a frontend to convert such data formats to a fast-import stream, as all the frontend has on hand is "Copy a/ to b/; Delete a/" with no details about what files are in a/, unless the frontend also kept track of all files. The new 'C' subcommand within a commit allows the frontend to make a recursive copy of one path to another path within the branch, without needing to keep track of the individual file paths. The metadata copy is performed in memory efficiently, but is implemented as a copy-immediately operation, rather than copy-on-write. With this new 'C' subcommand frontends could obviously implement an 'R' (rename) on their own as a combination of 'C' and 'D' (delete), but since we have already offered up 'R' in the past and it is a trivial thing to keep implemented I'm not going to deprecate it. Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
Diffstat (limited to 'fast-import.c')
-rw-r--r--fast-import.c81
1 files changed, 77 insertions, 4 deletions
diff --git a/fast-import.c b/fast-import.c
index a1cb13f..99a19d8 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -26,10 +26,16 @@ Format of STDIN stream:
lf;
commit_msg ::= data;
- file_change ::= file_clr | file_del | file_rnm | file_obm | file_inm;
+ file_change ::= file_clr
+ | file_del
+ | file_rnm
+ | file_cpy
+ | file_obm
+ | file_inm;
file_clr ::= 'deleteall' lf;
file_del ::= 'D' sp path_str lf;
file_rnm ::= 'R' sp path_str sp path_str lf;
+ file_cpy ::= 'C' sp path_str sp path_str lf;
file_obm ::= 'M' sp mode sp (hexsha1 | idnum) sp path_str lf;
file_inm ::= 'M' sp mode sp 'inline' sp path_str lf
data;
@@ -623,6 +629,31 @@ static void release_tree_entry(struct tree_entry *e)
avail_tree_entry = e;
}
+static struct tree_content *dup_tree_content(struct tree_content *s)
+{
+ struct tree_content *d;
+ struct tree_entry *a, *b;
+ unsigned int i;
+
+ if (!s)
+ return NULL;
+ d = new_tree_content(s->entry_count);
+ for (i = 0; i < s->entry_count; i++) {
+ a = s->entries[i];
+ b = new_tree_entry();
+ memcpy(b, a, sizeof(*a));
+ if (a->tree && is_null_sha1(b->versions[1].sha1))
+ b->tree = dup_tree_content(a->tree);
+ else
+ b->tree = NULL;
+ d->entries[i] = b;
+ }
+ d->entry_count = s->entry_count;
+ d->delta_depth = s->delta_depth;
+
+ return d;
+}
+
static void start_packfile(void)
{
static char tmpfile[PATH_MAX];
@@ -1273,6 +1304,43 @@ del_entry:
return 1;
}
+static int tree_content_get(
+ struct tree_entry *root,
+ const char *p,
+ struct tree_entry *leaf)
+{
+ struct tree_content *t = root->tree;
+ const char *slash1;
+ unsigned int i, n;
+ struct tree_entry *e;
+
+ slash1 = strchr(p, '/');
+ if (slash1)
+ n = slash1 - p;
+ else
+ n = strlen(p);
+
+ for (i = 0; i < t->entry_count; i++) {
+ e = t->entries[i];
+ if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) {
+ if (!slash1) {
+ memcpy(leaf, e, sizeof(*leaf));
+ if (e->tree && is_null_sha1(e->versions[1].sha1))
+ leaf->tree = dup_tree_content(e->tree);
+ else
+ leaf->tree = NULL;
+ return 1;
+ }
+ if (!S_ISDIR(e->versions[1].mode))
+ return 0;
+ if (!e->tree)
+ load_tree(e);
+ return tree_content_get(e, slash1 + 1, leaf);
+ }
+ }
+ return 0;
+}
+
static int update_branch(struct branch *b)
{
static const char *msg = "fast-import";
@@ -1658,7 +1726,7 @@ static void file_change_d(struct branch *b)
free(p_uq);
}
-static void file_change_r(struct branch *b)
+static void file_change_cr(struct branch *b, int rename)
{
const char *s, *d;
char *s_uq, *d_uq;
@@ -1694,7 +1762,10 @@ static void file_change_r(struct branch *b)
}
memset(&leaf, 0, sizeof(leaf));
- tree_content_remove(&b->branch_tree, s, &leaf);
+ if (rename)
+ tree_content_remove(&b->branch_tree, s, &leaf);
+ else
+ tree_content_get(&b->branch_tree, s, &leaf);
if (!leaf.versions[1].mode)
die("Path %s not in branch", s);
tree_content_set(&b->branch_tree, d,
@@ -1874,7 +1945,9 @@ static void cmd_new_commit(void)
else if (!prefixcmp(command_buf.buf, "D "))
file_change_d(b);
else if (!prefixcmp(command_buf.buf, "R "))
- file_change_r(b);
+ file_change_cr(b, 1);
+ else if (!prefixcmp(command_buf.buf, "C "))
+ file_change_cr(b, 0);
else if (!strcmp("deleteall", command_buf.buf))
file_change_deleteall(b);
else