merge-recursive: fix overwriting dirty files involved in renames
[gitweb.git] / merge-recursive.c
index 786990e27750e5ce3ff3138f674c58dec66a215e..0bd3f37c37bba6d7f40346372122131a4405e577 100644 (file)
@@ -337,32 +337,37 @@ static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree)
        init_tree_desc(desc, tree->buffer, tree->size);
 }
 
-static int git_merge_trees(int index_only,
+static int git_merge_trees(struct merge_options *o,
                           struct tree *common,
                           struct tree *head,
                           struct tree *merge)
 {
        int rc;
        struct tree_desc t[3];
-       struct unpack_trees_options opts;
 
-       memset(&opts, 0, sizeof(opts));
-       if (index_only)
-               opts.index_only = 1;
+       memset(&o->unpack_opts, 0, sizeof(o->unpack_opts));
+       if (o->call_depth)
+               o->unpack_opts.index_only = 1;
        else
-               opts.update = 1;
-       opts.merge = 1;
-       opts.head_idx = 2;
-       opts.fn = threeway_merge;
-       opts.src_index = &the_index;
-       opts.dst_index = &the_index;
-       setup_unpack_trees_porcelain(&opts, "merge");
+               o->unpack_opts.update = 1;
+       o->unpack_opts.merge = 1;
+       o->unpack_opts.head_idx = 2;
+       o->unpack_opts.fn = threeway_merge;
+       o->unpack_opts.src_index = &the_index;
+       o->unpack_opts.dst_index = &the_index;
+       setup_unpack_trees_porcelain(&o->unpack_opts, "merge");
 
        init_tree_desc_from_tree(t+0, common);
        init_tree_desc_from_tree(t+1, head);
        init_tree_desc_from_tree(t+2, merge);
 
-       rc = unpack_trees(3, t, &opts);
+       rc = unpack_trees(3, t, &o->unpack_opts);
+       /*
+        * unpack_trees NULLifies src_index, but it's used in verify_uptodate,
+        * so set to the new index which will usually have modification
+        * timestamp info copied over.
+        */
+       o->unpack_opts.src_index = &the_index;
        cache_tree_free(&active_cache_tree);
        return rc;
 }
@@ -795,6 +800,20 @@ static int would_lose_untracked(const char *path)
        return !was_tracked(path) && file_exists(path);
 }
 
+static int was_dirty(struct merge_options *o, const char *path)
+{
+       struct cache_entry *ce;
+       int dirty = 1;
+
+       if (o->call_depth || !was_tracked(path))
+               return !dirty;
+
+       ce = cache_file_exists(path, strlen(path), ignore_case);
+       dirty = (ce->ce_stat_data.sd_mtime.sec > 0 &&
+                verify_uptodate(ce, &o->unpack_opts) != 0);
+       return dirty;
+}
+
 static int make_room_for_path(struct merge_options *o, const char *path)
 {
        int status, i;
@@ -2677,6 +2696,7 @@ static int handle_modify_delete(struct merge_options *o,
 
 static int merge_content(struct merge_options *o,
                         const char *path,
+                        int file_in_way,
                         struct object_id *o_oid, int o_mode,
                         struct object_id *a_oid, int a_mode,
                         struct object_id *b_oid, int b_mode,
@@ -2751,7 +2771,7 @@ static int merge_content(struct merge_options *o,
                                return -1;
        }
 
-       if (df_conflict_remains) {
+       if (df_conflict_remains || file_in_way) {
                char *new_path;
                if (o->call_depth) {
                        remove_file_from_cache(path);
@@ -2785,6 +2805,30 @@ static int merge_content(struct merge_options *o,
        return mfi.clean;
 }
 
+static int conflict_rename_normal(struct merge_options *o,
+                                 const char *path,
+                                 struct object_id *o_oid, unsigned int o_mode,
+                                 struct object_id *a_oid, unsigned int a_mode,
+                                 struct object_id *b_oid, unsigned int b_mode,
+                                 struct rename_conflict_info *ci)
+{
+       int clean_merge;
+       int file_in_the_way = 0;
+
+       if (was_dirty(o, path)) {
+               file_in_the_way = 1;
+               output(o, 1, _("Refusing to lose dirty file at %s"), path);
+       }
+
+       /* Merge the content and write it out */
+       clean_merge = merge_content(o, path, file_in_the_way,
+                                   o_oid, o_mode, a_oid, a_mode, b_oid, b_mode,
+                                   ci);
+       if (clean_merge > 0 && file_in_the_way)
+               clean_merge = 0;
+       return clean_merge;
+}
+
 /* Per entry merge function */
 static int process_entry(struct merge_options *o,
                         const char *path, struct stage_data *entry)
@@ -2804,9 +2848,12 @@ static int process_entry(struct merge_options *o,
                switch (conflict_info->rename_type) {
                case RENAME_NORMAL:
                case RENAME_ONE_FILE_TO_ONE:
-                       clean_merge = merge_content(o, path,
-                                                   o_oid, o_mode, a_oid, a_mode, b_oid, b_mode,
-                                                   conflict_info);
+                       clean_merge = conflict_rename_normal(o,
+                                                            path,
+                                                            o_oid, o_mode,
+                                                            a_oid, a_mode,
+                                                            b_oid, b_mode,
+                                                            conflict_info);
                        break;
                case RENAME_DIR:
                        clean_merge = 1;
@@ -2902,7 +2949,7 @@ static int process_entry(struct merge_options *o,
        } else if (a_oid && b_oid) {
                /* Case C: Added in both (check for same permissions) and */
                /* case D: Modified in both, but differently. */
-               clean_merge = merge_content(o, path,
+               clean_merge = merge_content(o, path, 0 /* file_in_way */,
                                            o_oid, o_mode, a_oid, a_mode, b_oid, b_mode,
                                            NULL);
        } else if (!o_oid && !a_oid && !b_oid) {
@@ -2943,7 +2990,7 @@ int merge_trees(struct merge_options *o,
                return 1;
        }
 
-       code = git_merge_trees(o->call_depth, common, head, merge);
+       code = git_merge_trees(o, common, head, merge);
 
        if (code != 0) {
                if (show(o, 4) || o->call_depth)