merge-recursive: Split was_tracked() out of would_lose_untracked()
[gitweb.git] / merge-recursive.c
index 7757e5f1634c69d20a5537dedeec385d7a44a43b..a30e5a4449afb99a1e364d6b44c575cf784254e5 100644 (file)
@@ -360,20 +360,24 @@ static int string_list_df_name_compare(const void *a, const void *b)
        return onelen - twolen;
 }
 
-
-
-static void make_room_for_directories_of_df_conflicts(struct merge_options *o,
-                                                     struct string_list *entries)
+static void record_df_conflict_files(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;
-        * 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.
+       /* If there is a D/F conflict and the file for such a conflict
+        * currently exist in the working copy, we want to allow it to
+        * be removed to make room for the corresponding directory if
+        * needed.  The files underneath the directories of such D/F
+        * conflicts will be handled in process_entry(), while the
+        * files of such D/F conflicts will be processed later in
+        * process_df_entry().  If the corresponding directory ends up
+        * being removed by the merge, then no additional work needs
+        * to be done by process_df_entry() for the conflicting file.
+        * If the directory needs to be written to the working copy,
+        * then the conflicting file will simply be removed (e.g. in
+        * make_room_for_path).  If the directory is written to the
+        * working copy but the file also has a conflict that needs to
+        * be resolved, then process_df_entry() will reinstate the
+        * file with a new unique name.
         */
        const char *last_file = NULL;
        int last_len = 0;
@@ -390,6 +394,7 @@ static void make_room_for_directories_of_df_conflicts(struct merge_options *o,
        qsort(entries->items, entries->nr, sizeof(*entries->items),
              string_list_df_name_compare);
 
+       string_list_clear(&o->df_conflict_file_set, 1);
        for (i = 0; i < entries->nr; i++) {
                const char *path = entries->items[i].string;
                int len = strlen(path);
@@ -398,14 +403,15 @@ static void make_room_for_directories_of_df_conflicts(struct merge_options *o,
                /*
                 * 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 so, record that it's okay to remove last_file to make
+                * room for path and friends if needed.
                 */
                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);
+                       string_list_insert(&o->df_conflict_file_set, last_file);
                }
 
                /*
@@ -591,7 +597,31 @@ static void flush_buffer(int fd, const char *buf, unsigned long size)
        }
 }
 
-static int would_lose_untracked(const char *path)
+static int dir_in_way(const char *path, int check_working_copy)
+{
+       int pos, pathlen = strlen(path);
+       char *dirpath = xmalloc(pathlen + 2);
+       struct stat st;
+
+       strcpy(dirpath, path);
+       dirpath[pathlen] = '/';
+       dirpath[pathlen+1] = '\0';
+
+       pos = cache_name_pos(dirpath, pathlen+1);
+
+       if (pos < 0)
+               pos = -1 - pos;
+       if (pos < active_nr &&
+           !strncmp(dirpath, active_cache[pos]->name, pathlen+1)) {
+               free(dirpath);
+               return 1;
+       }
+
+       free(dirpath);
+       return check_working_copy && !lstat(path, &st) && S_ISDIR(st.st_mode);
+}
+
+static int was_tracked(const char *path)
 {
        int pos = cache_name_pos(path, strlen(path));
 
@@ -608,11 +638,16 @@ static int would_lose_untracked(const char *path)
                switch (ce_stage(active_cache[pos])) {
                case 0:
                case 2:
-                       return 0;
+                       return 1;
                }
                pos++;
        }
-       return file_exists(path);
+       return 0;
+}
+
+static int would_lose_untracked(const char *path)
+{
+       return !was_tracked(path) && file_exists(path);
 }
 
 static int make_room_for_path(const char *path)
@@ -880,7 +915,6 @@ static void conflict_rename_delete(struct merge_options *o,
 {
        char *dest_name = pair->two->path;
        int df_conflict = 0;
-       struct stat st;
 
        output(o, 1, "CONFLICT (rename/delete): Rename %s->%s in %s "
               "and deleted in %s",
@@ -890,7 +924,7 @@ static void conflict_rename_delete(struct merge_options *o,
                update_stages(dest_name, NULL,
                              rename_branch == o->branch1 ? pair->two : NULL,
                              rename_branch == o->branch1 ? NULL : pair->two);
-       if (lstat(dest_name, &st) == 0 && S_ISDIR(st.st_mode)) {
+       if (dir_in_way(dest_name, !o->call_depth)) {
                dest_name = unique_path(o, dest_name, rename_branch);
                df_conflict = 1;
        }
@@ -912,13 +946,12 @@ static void conflict_rename_rename_1to2(struct merge_options *o,
        const char *ren2_dst = pair2->two->path;
        const char *dst_name1 = ren1_dst;
        const char *dst_name2 = ren2_dst;
-       struct stat st;
-       if (lstat(ren1_dst, &st) == 0 && S_ISDIR(st.st_mode)) {
+       if (dir_in_way(ren1_dst, !o->call_depth)) {
                dst_name1 = del[delp++] = unique_path(o, ren1_dst, branch1);
                output(o, 1, "%s is a directory in %s adding as %s instead",
                       ren1_dst, branch2, dst_name1);
        }
-       if (lstat(ren2_dst, &st) == 0 && S_ISDIR(st.st_mode)) {
+       if (dir_in_way(ren2_dst, !o->call_depth)) {
                dst_name2 = del[delp++] = unique_path(o, ren2_dst, branch2);
                output(o, 1, "%s is a directory in %s adding as %s instead",
                       ren2_dst, branch1, dst_name2);
@@ -1078,7 +1111,7 @@ static int process_renames(struct merge_options *o,
                        try_merge = 0;
 
                        if (sha_eq(src_other.sha1, null_sha1)) {
-                               if (string_list_has_string(&o->current_directory_set, ren1_dst)) {
+                               if (dir_in_way(ren1_dst, 0 /*check_wc*/)) {
                                        ren1->dst_entry->processed = 0;
                                        setup_rename_df_conflict_info(RENAME_DELETE,
                                                                      ren1->pair,
@@ -1157,7 +1190,7 @@ static int process_renames(struct merge_options *o,
                                        a = &src_other;
                                }
                                update_stages_and_entry(ren1_dst, ren1->dst_entry, one, a, b, 1);
-                               if (string_list_has_string(&o->current_directory_set, ren1_dst)) {
+                               if (dir_in_way(ren1_dst, 0 /*check_wc*/)) {
                                        setup_rename_df_conflict_info(RENAME_NORMAL,
                                                                      ren1->pair,
                                                                      NULL,
@@ -1262,7 +1295,6 @@ static int merge_content(struct merge_options *o,
        const char *reason = "content";
        struct merge_file_info mfi;
        struct diff_filespec one, a, b;
-       struct stat st;
        unsigned df_conflict_remains = 0;
 
        if (!o_sha) {
@@ -1279,7 +1311,7 @@ static int merge_content(struct merge_options *o,
 
        mfi = merge_file(o, &one, &a, &b, o->branch1, o->branch2);
        if (df_rename_conflict_branch &&
-           lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
+           dir_in_way(path, !o->call_depth)) {
                df_conflict_remains = 1;
        }
 
@@ -1344,8 +1376,7 @@ static int process_entry(struct merge_options *o,
                                output(o, 2, "Removing %s", path);
                        /* do not touch working file if it did not exist */
                        remove_file(o, 1, path, !a_sha);
-               } else if (string_list_has_string(&o->current_directory_set,
-                                                 path)) {
+               } else if (dir_in_way(path, 0 /*check_wc*/)) {
                        entry->processed = 0;
                        return 1; /* Assume clean until processed */
                } else {
@@ -1368,7 +1399,7 @@ static int process_entry(struct merge_options *o,
                        mode = b_mode;
                        sha = b_sha;
                }
-               if (string_list_has_string(&o->current_directory_set, path)) {
+               if (dir_in_way(path, 0 /*check_wc*/)) {
                        /* Handle D->F conflicts after all subfiles */
                        entry->processed = 0;
                        return 1; /* Assume clean until processed */
@@ -1416,7 +1447,6 @@ static int process_df_entry(struct merge_options *o,
        unsigned char *o_sha = stage_sha(entry->stages[1].sha, o_mode);
        unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
        unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
-       struct stat st;
 
        entry->processed = 1;
        if (entry->rename_df_conflict_info) {
@@ -1461,7 +1491,7 @@ 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 */
                char *renamed = NULL;
-               if (lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
+               if (dir_in_way(path, !o->call_depth)) {
                        renamed = unique_path(o, path, a_sha ? o->branch1 : o->branch2);
                }
                clean_merge = 0;
@@ -1489,7 +1519,7 @@ static int process_df_entry(struct merge_options *o,
                        sha = b_sha;
                        conf = "directory/file";
                }
-               if (lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
+               if (dir_in_way(path, !o->call_depth)) {
                        char *new_path = unique_path(o, path, add_branch);
                        clean_merge = 0;
                        output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. "
@@ -1550,7 +1580,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);
+               record_df_conflict_files(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);
@@ -1776,6 +1806,8 @@ void init_merge_options(struct merge_options *o)
        o->current_file_set.strdup_strings = 1;
        memset(&o->current_directory_set, 0, sizeof(struct string_list));
        o->current_directory_set.strdup_strings = 1;
+       memset(&o->df_conflict_file_set, 0, sizeof(struct string_list));
+       o->df_conflict_file_set.strdup_strings = 1;
 }
 
 int parse_merge_opt(struct merge_options *o, const char *s)