Merge branch 'jt/fast-export-copy-modify-fix'

"git fast-export" with -M/-C option issued "copy" instruction on a
path that is simultaneously modified, which was incorrect.

* jt/fast-export-copy-modify-fix:
  fast-export: do not copy from modified file
This commit is contained in:
Junio C Hamano 2017-09-29 11:23:42 +09:00
commit 8096e1d385
2 changed files with 50 additions and 14 deletions

View file

@ -344,6 +344,7 @@ static void show_filemodify(struct diff_queue_struct *q,
struct diff_options *options, void *data) struct diff_options *options, void *data)
{ {
int i; int i;
struct string_list *changed = data;
/* /*
* Handle files below a directory first, in case they are all deleted * Handle files below a directory first, in case they are all deleted
@ -359,20 +360,31 @@ static void show_filemodify(struct diff_queue_struct *q,
case DIFF_STATUS_DELETED: case DIFF_STATUS_DELETED:
printf("D "); printf("D ");
print_path(spec->path); print_path(spec->path);
string_list_insert(changed, spec->path);
putchar('\n'); putchar('\n');
break; break;
case DIFF_STATUS_COPIED: case DIFF_STATUS_COPIED:
case DIFF_STATUS_RENAMED: case DIFF_STATUS_RENAMED:
printf("%c ", q->queue[i]->status); /*
print_path(ospec->path); * If a change in the file corresponding to ospec->path
putchar(' '); * has been observed, we cannot trust its contents
print_path(spec->path); * because the diff is calculated based on the prior
putchar('\n'); * contents, not the current contents. So, declare a
* copy or rename only if there was no change observed.
*/
if (!string_list_has_string(changed, ospec->path)) {
printf("%c ", q->queue[i]->status);
print_path(ospec->path);
putchar(' ');
print_path(spec->path);
string_list_insert(changed, spec->path);
putchar('\n');
if (!oidcmp(&ospec->oid, &spec->oid) && if (!oidcmp(&ospec->oid, &spec->oid) &&
ospec->mode == spec->mode) ospec->mode == spec->mode)
break; break;
}
/* fallthrough */ /* fallthrough */
case DIFF_STATUS_TYPE_CHANGED: case DIFF_STATUS_TYPE_CHANGED:
@ -393,6 +405,7 @@ static void show_filemodify(struct diff_queue_struct *q,
get_object_mark(object)); get_object_mark(object));
} }
print_path(spec->path); print_path(spec->path);
string_list_insert(changed, spec->path);
putchar('\n'); putchar('\n');
break; break;
@ -528,7 +541,8 @@ static void anonymize_ident_line(const char **beg, const char **end)
*end = out->buf + out->len; *end = out->buf + out->len;
} }
static void handle_commit(struct commit *commit, struct rev_info *rev) static void handle_commit(struct commit *commit, struct rev_info *rev,
struct string_list *paths_of_changed_objects)
{ {
int saved_output_format = rev->diffopt.output_format; int saved_output_format = rev->diffopt.output_format;
const char *commit_buffer; const char *commit_buffer;
@ -615,6 +629,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev)
if (full_tree) if (full_tree)
printf("deleteall\n"); printf("deleteall\n");
log_tree_diff_flush(rev); log_tree_diff_flush(rev);
string_list_clear(paths_of_changed_objects, 0);
rev->diffopt.output_format = saved_output_format; rev->diffopt.output_format = saved_output_format;
printf("\n"); printf("\n");
@ -630,14 +645,15 @@ static void *anonymize_tag(const void *old, size_t *len)
return strbuf_detach(&out, len); return strbuf_detach(&out, len);
} }
static void handle_tail(struct object_array *commits, struct rev_info *revs) static void handle_tail(struct object_array *commits, struct rev_info *revs,
struct string_list *paths_of_changed_objects)
{ {
struct commit *commit; struct commit *commit;
while (commits->nr) { while (commits->nr) {
commit = (struct commit *)commits->objects[commits->nr - 1].item; commit = (struct commit *)commits->objects[commits->nr - 1].item;
if (has_unshown_parent(commit)) if (has_unshown_parent(commit))
return; return;
handle_commit(commit, revs); handle_commit(commit, revs, paths_of_changed_objects);
commits->nr--; commits->nr--;
} }
} }
@ -977,6 +993,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
char *export_filename = NULL, *import_filename = NULL; char *export_filename = NULL, *import_filename = NULL;
uint32_t lastimportid; uint32_t lastimportid;
struct string_list refspecs_list = STRING_LIST_INIT_NODUP; struct string_list refspecs_list = STRING_LIST_INIT_NODUP;
struct string_list paths_of_changed_objects = STRING_LIST_INIT_DUP;
struct option options[] = { struct option options[] = {
OPT_INTEGER(0, "progress", &progress, OPT_INTEGER(0, "progress", &progress,
N_("show progress after <n> objects")), N_("show progress after <n> objects")),
@ -1049,14 +1066,15 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
if (prepare_revision_walk(&revs)) if (prepare_revision_walk(&revs))
die("revision walk setup failed"); die("revision walk setup failed");
revs.diffopt.format_callback = show_filemodify; revs.diffopt.format_callback = show_filemodify;
revs.diffopt.format_callback_data = &paths_of_changed_objects;
DIFF_OPT_SET(&revs.diffopt, RECURSIVE); DIFF_OPT_SET(&revs.diffopt, RECURSIVE);
while ((commit = get_revision(&revs))) { while ((commit = get_revision(&revs))) {
if (has_unshown_parent(commit)) { if (has_unshown_parent(commit)) {
add_object_array(&commit->object, NULL, &commits); add_object_array(&commit->object, NULL, &commits);
} }
else { else {
handle_commit(commit, &revs); handle_commit(commit, &revs, &paths_of_changed_objects);
handle_tail(&commits, &revs); handle_tail(&commits, &revs, &paths_of_changed_objects);
} }
} }

View file

@ -234,7 +234,7 @@ test_expect_success 'fast-export -C -C | fast-import' '
mkdir new && mkdir new &&
git --git-dir=new/.git init && git --git-dir=new/.git init &&
git fast-export -C -C --signed-tags=strip --all > output && git fast-export -C -C --signed-tags=strip --all > output &&
grep "^C file6 file7\$" output && grep "^C file2 file4\$" output &&
cat output | cat output |
(cd new && (cd new &&
git fast-import && git fast-import &&
@ -522,4 +522,22 @@ test_expect_success 'delete refspec' '
test_cmp expected actual test_cmp expected actual
' '
test_expect_success 'when using -C, do not declare copy when source of copy is also modified' '
test_create_repo src &&
echo a_line >src/file.txt &&
git -C src add file.txt &&
git -C src commit -m 1st_commit &&
cp src/file.txt src/file2.txt &&
echo another_line >>src/file.txt &&
git -C src add file.txt file2.txt &&
git -C src commit -m 2nd_commit &&
test_create_repo dst &&
git -C src fast-export --all -C | git -C dst fast-import &&
git -C src show >expected &&
git -C dst show >actual &&
test_cmp expected actual
'
test_done test_done