merge-recursive: tweak magic band-aid
[gitweb.git] / merge-recursive.c
index a3f986d874e6ee703d989f7ab365b568ba09de7e..59482ffc87f7ae0b2004e3f1bbb467d199a3bcde 100644 (file)
@@ -346,6 +346,71 @@ static struct string_list *get_unmerged(void)
        return unmerged;
 }
 
+static void make_room_for_directories_of_df_conflicts(struct merge_options *o,
+                                                     struct string_list *entries)
+{
+       /* If there are D/F conflicts, and the paths currently exist
+        * in the working copy as a file, we want to remove them to
+        * make room for the corresponding directory.  Such paths will
+        * later be processed in process_df_entry() at the end.  If
+        * the corresponding directory ends up being removed by the
+        * merge, then the file will be reinstated at that time
+        * (albeit with a different timestamp!); 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;
+       struct stage_data *last_e;
+       int i;
+
+       /*
+        * Do not do any of this crazyness during the recursive; we don't
+        * even write anything to the working tree!
+        */
+       if (o->call_depth)
+               return;
+
+       for (i = 0; i < entries->nr; i++) {
+               const char *path = entries->items[i].string;
+               int len = strlen(path);
+               struct stage_data *e = entries->items[i].util;
+
+               /*
+                * Check if last_file & path correspond to a D/F conflict;
+                * i.e. whether path is last_file+'/'+<something>.
+                * If so, remove last_file to make room for path and friends.
+                */
+               if (last_file &&
+                   len > last_len &&
+                   memcmp(path, last_file, last_len) == 0 &&
+                   path[last_len] == '/') {
+                       output(o, 3, "Removing %s to make room for subdirectory; may re-add later.", last_file);
+                       unlink(last_file);
+               }
+
+               /*
+                * Determine whether path could exist as a file in the
+                * working directory as a possible D/F conflict.  This
+                * will only occur when it exists in stage 2 as a
+                * file.
+                */
+               if (S_ISREG(e->stages[2].mode) || S_ISLNK(e->stages[2].mode)) {
+                       last_file = path;
+                       last_len = len;
+                       last_e = e;
+               } else {
+                       last_file = NULL;
+               }
+       }
+}
+
 struct rename
 {
        struct diff_filepair *pair;
@@ -975,8 +1040,6 @@ static int process_renames(struct merge_options *o,
                                                              branch2,
                                                              ren1->dst_entry,
                                                              ren2->dst_entry);
-                               remove_file(o, 0, ren1_dst, 0);
-                               /* ren2_dst not in head, so no need to delete */
                        } else {
                                remove_file(o, 1, ren1_src, 1);
                                update_stages_and_entry(ren1_dst,
@@ -1020,7 +1083,6 @@ static int process_renames(struct merge_options *o,
                                                                      branch2,
                                                                      ren1->dst_entry,
                                                                      NULL);
-                                       remove_file(o, 0, ren1_dst, 0);
                                } else {
                                        clean_merge = 0;
                                        conflict_rename_delete(o, ren1->pair, branch1, branch2);
@@ -1099,7 +1161,6 @@ static int process_renames(struct merge_options *o,
                                                                      NULL,
                                                                      ren1->dst_entry,
                                                                      NULL);
-                                       remove_file(o, 0, ren1_dst, 0);
                                }
                        }
                }
@@ -1164,25 +1225,29 @@ static int blob_unchanged(const unsigned char *o_sha,
 
 static void handle_delete_modify(struct merge_options *o,
                                 const char *path,
+                                const char *new_path,
                                 unsigned char *a_sha, int a_mode,
                                 unsigned char *b_sha, int b_mode)
 {
        if (!a_sha) {
                output(o, 1, "CONFLICT (delete/modify): %s deleted in %s "
-                      "and modified in %s. Version %s of %s left in tree.",
+                      "and modified in %s. Version %s of %s left in tree%s%s.",
                       path, o->branch1,
-                      o->branch2, o->branch2, path);
-               update_file(o, 0, b_sha, b_mode, path);
+                      o->branch2, o->branch2, path,
+                      path == new_path ? "" : " at ",
+                      path == new_path ? "" : new_path);
+               update_file(o, 0, b_sha, b_mode, new_path);
        } else {
                output(o, 1, "CONFLICT (delete/modify): %s deleted in %s "
-                      "and modified in %s. Version %s of %s left in tree.",
+                      "and modified in %s. Version %s of %s left in tree%s%s.",
                       path, o->branch2,
-                      o->branch1, o->branch1, path);
-               update_file(o, 0, a_sha, a_mode, path);
+                      o->branch1, o->branch1, path,
+                      path == new_path ? "" : " at ",
+                      path == new_path ? "" : new_path);
+               update_file(o, 0, a_sha, a_mode, new_path);
        }
 }
 
-
 static int merge_content(struct merge_options *o,
                         const char *path,
                         unsigned char *o_sha, int o_mode,
@@ -1215,9 +1280,13 @@ static int merge_content(struct merge_options *o,
        }
 
        if (mfi.clean && !df_conflict_remains &&
-           sha_eq(mfi.sha, a_sha) && mfi.mode == a.mode)
+           sha_eq(mfi.sha, a_sha) && mfi.mode == a.mode &&
+           !o->call_depth && !lstat(path, &st)) {
                output(o, 3, "Skipped %s (merged same as existing)", path);
-       else
+               add_cacheinfo(mfi.mode, mfi.sha, path,
+                             0 /*stage*/, 1 /*refresh*/, 0 /*options*/);
+               return mfi.clean;
+       } else
                output(o, 2, "Auto-merging %s", path);
 
        if (!mfi.clean) {
@@ -1277,11 +1346,11 @@ static int process_entry(struct merge_options *o,
                } else if (string_list_has_string(&o->current_directory_set,
                                                  path)) {
                        entry->processed = 0;
-                       return 1; /* Assume clean till processed */
+                       return 1; /* Assume clean until processed */
                } else {
                        /* Deleted in one and changed in the other */
                        clean_merge = 0;
-                       handle_delete_modify(o, path,
+                       handle_delete_modify(o, path, path,
                                             a_sha, a_mode, b_sha, b_mode);
                }
 
@@ -1301,15 +1370,7 @@ static int process_entry(struct merge_options *o,
                if (string_list_has_string(&o->current_directory_set, path)) {
                        /* Handle D->F conflicts after all subfiles */
                        entry->processed = 0;
-                       /* But get any file out of the way now, so conflicted
-                        * entries below the directory of the same name can
-                        * be put in the working directory.
-                        */
-                       if (a_sha)
-                               output(o, 2, "Removing %s", path);
-                       /* do not touch working file if it did not exist */
-                       remove_file(o, 0, path, !a_sha);
-                       return 1; /* Assume clean till processed */
+                       return 1; /* Assume clean until processed */
                } else {
                        output(o, 2, "Adding %s", path);
                        update_file(o, 1, sha, mode, path);
@@ -1398,8 +1459,11 @@ static int process_df_entry(struct merge_options *o,
                }
        } else if (o_sha && (!a_sha || !b_sha)) {
                /* Modify/delete; deleted side may have put a directory in the way */
+               const char *new_path = path;
+               if (lstat(path, &st) == 0 && S_ISDIR(st.st_mode))
+                       new_path = unique_path(o, path, a_sha ? o->branch1 : o->branch2);
                clean_merge = 0;
-               handle_delete_modify(o, path,
+               handle_delete_modify(o, path, new_path,
                                     a_sha, a_mode, b_sha, b_mode);
        } else if (!o_sha && !!a_sha != !!b_sha) {
                /* directory -> (directory, file) */
@@ -1428,7 +1492,6 @@ static int process_df_entry(struct merge_options *o,
                        output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. "
                               "Adding %s as %s",
                               conf, path, other_branch, path, new_path);
-                       remove_file(o, 0, path, 0);
                        update_file(o, 0, sha, mode, new_path);
                } else {
                        output(o, 2, "Adding %s", path);
@@ -1481,6 +1544,7 @@ int merge_trees(struct merge_options *o,
                get_files_dirs(o, merge);
 
                entries = get_unmerged();
+               make_room_for_directories_of_df_conflicts(o, entries);
                re_head  = get_renames(o, head, common, head, merge, entries);
                re_merge = get_renames(o, merge, common, head, merge, entries);
                clean = process_renames(o, re_head, re_merge);