@@ -2746,31 +2746,63 @@ static int detect_and_process_renames(struct merge_options *opt,
/*** Function Grouping: functions related to process_entries() ***/
-static int string_list_df_name_compare(const char *one, const char *two)
+static int sort_dirs_next_to_their_children(const char *one, const char *two)
{
- int onelen = strlen(one);
- int twolen = strlen(two);
+ unsigned char c1, c2;
+
/*
- * 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.
+ * Here we only care that entries for directories appear adjacent
+ * to and before files underneath the directory. We can achieve
+ * that by pretending to add a trailing slash to every file and
+ * then sorting. In other words, we do not want the natural
+ * sorting of
+ * foo
+ * foo.txt
+ * foo/bar
+ * Instead, we want "foo" to sort as though it were "foo/", so that
+ * we instead get
+ * foo.txt
+ * foo
+ * foo/bar
+ * To achieve this, we basically implement our own strcmp, except that
+ * if we get to the end of either string instead of comparing NUL to
+ * another character, we compare '/' to it.
+ *
+ * If this unusual "sort as though '/' were appended" perplexes
+ * you, perhaps it will help to note that this is not the final
+ * sort. write_tree() will sort again without the trailing slash
+ * magic, but just on paths immediately under a given tree.
*
- * 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.
+ * The reason to not use df_name_compare directly was that it was
+ * just too expensive (we don't have the string lengths handy), so
+ * I had to reimplement it.
*/
- int cmp = df_name_compare(one, onelen, S_IFDIR,
- two, twolen, S_IFDIR);
+
/*
- * Now that 'foo' and 'foo/bar' compare equal, we have to make sure
- * that 'foo' comes before 'foo/bar'.
+ * NOTE: This function will never be called with two equal strings,
+ * because it is used to sort the keys of a strmap, and strmaps have
+ * unique keys by construction. That simplifies our c1==c2 handling
+ * below.
*/
- if (cmp)
- return cmp;
- return onelen - twolen;
+
+ while (*one && (*one == *two)) {
+ one++;
+ two++;
+ }
+
+ c1 = *one;
+ if (!c1)
+ c1 = '/';
+
+ c2 = *two;
+ if (!c2)
+ c2 = '/';
+
+ if (c1 == c2) {
+ /* Getting here means one is a leading directory of the other */
+ return (*one) ? 1 : -1;
+ } else
+ return c1-c2;
}
static int read_oid_strbuf(struct merge_options *opt,
@@ -3481,7 +3513,7 @@ static void process_entries(struct merge_options *opt,
trace2_region_leave("merge", "plist copy", opt->repo);
trace2_region_enter("merge", "plist special sort", opt->repo);
- plist.cmp = string_list_df_name_compare;
+ plist.cmp = sort_dirs_next_to_their_children;
string_list_sort(&plist);
trace2_region_leave("merge", "plist special sort", opt->repo);