diff options
author | Elijah Newren <newren@gmail.com> | 2011-08-11 23:19:56 -0600 |
---|---|---|
committer | Junio C Hamano <gitster@pobox.com> | 2011-08-14 14:19:34 -0700 |
commit | f0fd4d05e8a17fe5ccdd4d3edd686bc6702b8144 (patch) | |
tree | a0f6aa600a85b460c96cd8d6984c1bb857bc3703 | |
parent | merge-recursive: Fix recursive case with D/F conflict via add/add conflict (diff) | |
download | tgif-f0fd4d05e8a17fe5ccdd4d3edd686bc6702b8144.tar.xz |
merge-recursive: Fix sorting order and directory change assumptions
We cannot assume that directory/file conflicts will appear in sorted
order; for example, 'letters.txt' comes between 'letters' and
'letters/file'.
Thanks to Johannes for a pointer about qsort stability issues with
Windows and suggested code change.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Johannes Sixt <j6t@kdbg.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
-rw-r--r-- | merge-recursive.c | 40 | ||||
-rwxr-xr-x | t/t6020-merge-df.sh | 26 |
2 files changed, 53 insertions, 13 deletions
diff --git a/merge-recursive.c b/merge-recursive.c index 418246376b..7757e5f163 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -331,6 +331,37 @@ static struct string_list *get_unmerged(void) return unmerged; } +static int string_list_df_name_compare(const void *a, const void *b) +{ + const struct string_list_item *one = a; + const struct string_list_item *two = b; + int onelen = strlen(one->string); + int twolen = strlen(two->string); + /* + * Here we only care that entries for D/F conflicts are + * adjacent, in particular with the file of the D/F conflict + * appearing before files below the corresponding directory. + * The order of the rest of the list is irrelevant for us. + * + * To achieve this, we sort with df_name_compare and provide + * the mode S_IFDIR so that D/F conflicts will sort correctly. + * We use the mode S_IFDIR for everything else for simplicity, + * since in other cases any changes in their order due to + * sorting cause no problems for us. + */ + int cmp = df_name_compare(one->string, onelen, S_IFDIR, + two->string, twolen, S_IFDIR); + /* + * Now that 'foo' and 'foo/bar' compare equal, we have to make sure + * that 'foo' comes before 'foo/bar'. + */ + if (cmp) + return cmp; + return onelen - twolen; +} + + + static void make_room_for_directories_of_df_conflicts(struct merge_options *o, struct string_list *entries) { @@ -343,11 +374,6 @@ static void make_room_for_directories_of_df_conflicts(struct merge_options *o, * otherwise, if the file is not supposed to be removed by the * merge, the contents of the file will be placed in another * unique filename. - * - * NOTE: This function relies on the fact that entries for a - * D/F conflict will appear adjacent in the index, with the - * entries for the file appearing before entries for paths - * below the corresponding directory. */ const char *last_file = NULL; int last_len = 0; @@ -360,6 +386,10 @@ static void make_room_for_directories_of_df_conflicts(struct merge_options *o, if (o->call_depth) return; + /* Ensure D/F conflicts are adjacent in the entries list. */ + qsort(entries->items, entries->nr, sizeof(*entries->items), + string_list_df_name_compare); + for (i = 0; i < entries->nr; i++) { const char *path = entries->items[i].string; int len = strlen(path); diff --git a/t/t6020-merge-df.sh b/t/t6020-merge-df.sh index eec8f4e3ed..27c3d73961 100755 --- a/t/t6020-merge-df.sh +++ b/t/t6020-merge-df.sh @@ -59,15 +59,19 @@ test_expect_success 'setup modify/delete + directory/file conflict' ' git add letters && git commit -m initial && + # Throw in letters.txt for sorting order fun + # ("letters.txt" sorts between "letters" and "letters/file") echo i >>letters && - git add letters && + echo "version 2" >letters.txt && + git add letters letters.txt && git commit -m modified && git checkout -b delete HEAD^ && git rm letters && mkdir letters && >letters/file && - git add letters && + echo "version 1" >letters.txt && + git add letters letters.txt && git commit -m deleted ' @@ -75,25 +79,31 @@ test_expect_success 'modify/delete + directory/file conflict' ' git checkout delete^0 && test_must_fail git merge modify && - test 3 = $(git ls-files -s | wc -l) && - test 2 = $(git ls-files -u | wc -l) && - test 1 = $(git ls-files -o | wc -l) && + test 5 -eq $(git ls-files -s | wc -l) && + test 4 -eq $(git ls-files -u | wc -l) && + test 1 -eq $(git ls-files -o | wc -l) && test -f letters/file && + test -f letters.txt && test -f letters~modify ' test_expect_success 'modify/delete + directory/file conflict; other way' ' + # Yes, we really need the double reset since "letters" appears as + # both a file and a directory. + git reset --hard && git reset --hard && git clean -f && git checkout modify^0 && + test_must_fail git merge delete && - test 3 = $(git ls-files -s | wc -l) && - test 2 = $(git ls-files -u | wc -l) && - test 1 = $(git ls-files -o | wc -l) && + test 5 -eq $(git ls-files -s | wc -l) && + test 4 -eq $(git ls-files -u | wc -l) && + test 1 -eq $(git ls-files -o | wc -l) && test -f letters/file && + test -f letters.txt && test -f letters~HEAD ' |