Merge branch 'dl/complete-cherry-pick-revert-skip'
[gitweb.git] / merge-recursive.c
index 2edfa01e43c5b78b200858c9ce1297ce7d68be66..6b812d67e3ecd54ece21c2465ede3e28d6fba325 100644 (file)
@@ -153,9 +153,9 @@ static struct tree *shift_tree_object(struct repository *repo,
        struct object_id shifted;
 
        if (!*subtree_shift) {
-               shift_tree(&one->object.oid, &two->object.oid, &shifted, 0);
+               shift_tree(repo, &one->object.oid, &two->object.oid, &shifted, 0);
        } else {
-               shift_tree_by(&one->object.oid, &two->object.oid, &shifted,
+               shift_tree_by(repo, &one->object.oid, &two->object.oid, &shifted,
                              subtree_shift);
        }
        if (oideq(&two->object.oid, &shifted))
@@ -163,6 +163,11 @@ static struct tree *shift_tree_object(struct repository *repo,
        return lookup_tree(repo, &shifted);
 }
 
+static inline void set_commit_tree(struct commit *c, struct tree *t)
+{
+       c->maybe_tree = t;
+}
+
 static struct commit *make_virtual_commit(struct repository *repo,
                                          struct tree *tree,
                                          const char *comment)
@@ -170,7 +175,7 @@ static struct commit *make_virtual_commit(struct repository *repo,
        struct commit *commit = alloc_commit_node(repo);
 
        set_merge_remote_desc(commit, comment, (struct object *)commit);
-       commit->maybe_tree = tree;
+       set_commit_tree(commit, tree);
        commit->object.parsed = 1;
        return commit;
 }
@@ -460,17 +465,18 @@ static void get_files_dirs(struct merge_options *opt, struct tree *tree)
 {
        struct pathspec match_all;
        memset(&match_all, 0, sizeof(match_all));
-       read_tree_recursive(the_repository, tree, "", 0, 0,
+       read_tree_recursive(opt->repo, tree, "", 0, 0,
                            &match_all, save_files_dirs, opt);
 }
 
-static int get_tree_entry_if_blob(const struct object_id *tree,
+static int get_tree_entry_if_blob(struct repository *r,
+                                 const struct object_id *tree,
                                  const char *path,
                                  struct diff_filespec *dfs)
 {
        int ret;
 
-       ret = get_tree_entry(tree, path, &dfs->oid, &dfs->mode);
+       ret = get_tree_entry(r, tree, path, &dfs->oid, &dfs->mode);
        if (S_ISDIR(dfs->mode)) {
                oidcpy(&dfs->oid, &null_oid);
                dfs->mode = 0;
@@ -482,15 +488,16 @@ static int get_tree_entry_if_blob(const struct object_id *tree,
  * Returns an index_entry instance which doesn't have to correspond to
  * a real cache entry in Git's index.
  */
-static struct stage_data *insert_stage_data(const char *path,
+static struct stage_data *insert_stage_data(struct repository *r,
+               const char *path,
                struct tree *o, struct tree *a, struct tree *b,
                struct string_list *entries)
 {
        struct string_list_item *item;
        struct stage_data *e = xcalloc(1, sizeof(struct stage_data));
-       get_tree_entry_if_blob(&o->object.oid, path, &e->stages[1]);
-       get_tree_entry_if_blob(&a->object.oid, path, &e->stages[2]);
-       get_tree_entry_if_blob(&b->object.oid, path, &e->stages[3]);
+       get_tree_entry_if_blob(r, &o->object.oid, path, &e->stages[1]);
+       get_tree_entry_if_blob(r, &a->object.oid, path, &e->stages[2]);
+       get_tree_entry_if_blob(r, &b->object.oid, path, &e->stages[3]);
        item = string_list_insert(entries, path);
        item->util = e;
        return e;
@@ -1076,7 +1083,7 @@ static int find_first_merges(struct repository *repo,
        struct commit *commit;
        int contains_another;
 
-       char merged_revision[42];
+       char merged_revision[GIT_MAX_HEXSZ + 2];
        const char *rev_args[] = { "rev-list", "--merges", "--ancestry-path",
                                   "--all", merged_revision, NULL };
        struct rev_info revs;
@@ -1370,30 +1377,39 @@ static int handle_rename_via_dir(struct merge_options *opt,
         */
        const struct rename *ren = ci->ren1;
        const struct diff_filespec *dest = ren->pair->two;
+       char *file_path = dest->path;
+       int mark_conflicted = (opt->detect_directory_renames == 1);
+       assert(ren->dir_rename_original_dest);
 
        if (!opt->call_depth && would_lose_untracked(opt, dest->path)) {
-               char *alt_path = unique_path(opt, dest->path, ren->branch);
-
+               mark_conflicted = 1;
+               file_path = unique_path(opt, dest->path, ren->branch);
                output(opt, 1, _("Error: Refusing to lose untracked file at %s; "
-                              "writing to %s instead."),
-                      dest->path, alt_path);
+                                "writing to %s instead."),
+                      dest->path, file_path);
+       }
+
+       if (mark_conflicted) {
                /*
-                * Write the file in worktree at alt_path, but not in the
-                * index.  Instead, write to dest->path for the index but
-                * only at the higher appropriate stage.
+                * Write the file in worktree at file_path.  In the index,
+                * only record the file at dest->path in the appropriate
+                * higher stage.
                 */
-               if (update_file(opt, 0, dest, alt_path))
+               if (update_file(opt, 0, dest, file_path))
                        return -1;
-               free(alt_path);
-               return update_stages(opt, dest->path, NULL,
-                                    ren->branch == opt->branch1 ? dest : NULL,
-                                    ren->branch == opt->branch1 ? NULL : dest);
+               if (file_path != dest->path)
+                       free(file_path);
+               if (update_stages(opt, dest->path, NULL,
+                                 ren->branch == opt->branch1 ? dest : NULL,
+                                 ren->branch == opt->branch1 ? NULL : dest))
+                       return -1;
+               return 0; /* not clean, but conflicted */
+       } else {
+               /* Update dest->path both in index and in worktree */
+               if (update_file(opt, 1, dest, dest->path))
+                       return -1;
+               return 1; /* clean */
        }
-
-       /* Update dest->path both in index and in worktree */
-       if (update_file(opt, 1, dest, dest->path))
-               return -1;
-       return 0;
 }
 
 static int handle_change_delete(struct merge_options *opt,
@@ -1646,6 +1662,7 @@ static int handle_rename_add(struct merge_options *opt,
               c->path, add_branch);
 
        prev_path_desc = xstrfmt("version of %s from %s", path, a->path);
+       ci->ren1->src_entry->stages[other_stage].path = a->path;
        if (merge_mode_and_contents(opt, a, c,
                                    &ci->ren1->src_entry->stages[other_stage],
                                    prev_path_desc,
@@ -1885,12 +1902,14 @@ static struct diff_queue_struct *get_diffpairs(struct merge_options *opt,
        return ret;
 }
 
-static int tree_has_path(struct tree *tree, const char *path)
+static int tree_has_path(struct repository *r, struct tree *tree,
+                        const char *path)
 {
        struct object_id hashy;
        unsigned short mode_o;
 
-       return !get_tree_entry(&tree->object.oid, path,
+       return !get_tree_entry(r,
+                              &tree->object.oid, path,
                               &hashy, &mode_o);
 }
 
@@ -2041,7 +2060,7 @@ static char *handle_path_level_conflicts(struct merge_options *opt,
         */
        if (collision_ent->reported_already) {
                clean = 0;
-       } else if (tree_has_path(tree, new_path)) {
+       } else if (tree_has_path(opt->repo, tree, new_path)) {
                collision_ent->reported_already = 1;
                strbuf_add_separated_string_list(&collision_paths, ", ",
                                                 &collision_ent->source_files);
@@ -2119,7 +2138,7 @@ static void handle_directory_level_conflicts(struct merge_options *opt,
                        string_list_append(&remove_from_merge,
                                           merge_ent->dir)->util = merge_ent;
                        strbuf_release(&merge_ent->new_dir);
-               } else if (tree_has_path(head, head_ent->dir)) {
+               } else if (tree_has_path(opt->repo, head, head_ent->dir)) {
                        /* 2. This wasn't a directory rename after all */
                        string_list_append(&remove_from_head,
                                           head_ent->dir)->util = head_ent;
@@ -2133,7 +2152,7 @@ static void handle_directory_level_conflicts(struct merge_options *opt,
        hashmap_iter_init(dir_re_merge, &iter);
        while ((merge_ent = hashmap_iter_next(&iter))) {
                head_ent = dir_rename_find_entry(dir_re_head, merge_ent->dir);
-               if (tree_has_path(merge, merge_ent->dir)) {
+               if (tree_has_path(opt->repo, merge, merge_ent->dir)) {
                        /* 2. This wasn't a directory rename after all */
                        string_list_append(&remove_from_merge,
                                           merge_ent->dir)->util = merge_ent;
@@ -2462,7 +2481,7 @@ static void apply_directory_rename_modifications(struct merge_options *opt,
                if (pair->status == 'R')
                        re->dst_entry->processed = 1;
 
-               re->dst_entry = insert_stage_data(new_path,
+               re->dst_entry = insert_stage_data(opt->repo, new_path,
                                                  o_tree, a_tree, b_tree,
                                                  entries);
                item = string_list_insert(entries, new_path);
@@ -2485,7 +2504,8 @@ static void apply_directory_rename_modifications(struct merge_options *opt,
         * the various handle_rename_*() functions update the index
         * explicitly rather than relying on unpack_trees() to have done it.
         */
-       get_tree_entry(&tree->object.oid,
+       get_tree_entry(opt->repo,
+                      &tree->object.oid,
                       pair->two->path,
                       &re->dst_entry->stages[stage].oid,
                       &re->dst_entry->stages[stage].mode);
@@ -2570,14 +2590,16 @@ static struct string_list *get_renames(struct merge_options *opt,
                re->dir_rename_original_dest = NULL;
                item = string_list_lookup(entries, re->pair->one->path);
                if (!item)
-                       re->src_entry = insert_stage_data(re->pair->one->path,
+                       re->src_entry = insert_stage_data(opt->repo,
+                                       re->pair->one->path,
                                        o_tree, a_tree, b_tree, entries);
                else
                        re->src_entry = item->util;
 
                item = string_list_lookup(entries, re->pair->two->path);
                if (!item)
-                       re->dst_entry = insert_stage_data(re->pair->two->path,
+                       re->dst_entry = insert_stage_data(opt->repo,
+                                       re->pair->two->path,
                                        o_tree, a_tree, b_tree, entries);
                else
                        re->dst_entry = item->util;
@@ -2841,7 +2863,8 @@ static int detect_and_process_renames(struct merge_options *opt,
        head_pairs = get_diffpairs(opt, common, head);
        merge_pairs = get_diffpairs(opt, common, merge);
 
-       if (opt->detect_directory_renames) {
+       if ((opt->detect_directory_renames == 2) ||
+           (opt->detect_directory_renames == 1 && !opt->call_depth)) {
                dir_re_head = get_directory_renames(head_pairs);
                dir_re_merge = get_directory_renames(merge_pairs);
 
@@ -3090,10 +3113,88 @@ static int handle_rename_normal(struct merge_options *opt,
                                const struct diff_filespec *b,
                                struct rename_conflict_info *ci)
 {
-       /* Merge the content and write it out */
+       struct rename *ren = ci->ren1;
        struct merge_file_info mfi;
-       return handle_content_merge(&mfi, opt, path, was_dirty(opt, path),
-                                   o, a, b, ci);
+       int clean;
+       int side = (ren->branch == opt->branch1 ? 2 : 3);
+
+       /* Merge the content and write it out */
+       clean = handle_content_merge(&mfi, opt, path, was_dirty(opt, path),
+                                    o, a, b, ci);
+
+       if (clean && opt->detect_directory_renames == 1 &&
+           ren->dir_rename_original_dest) {
+               if (update_stages(opt, path,
+                                 NULL,
+                                 side == 2 ? &mfi.blob : NULL,
+                                 side == 2 ? NULL : &mfi.blob))
+                       return -1;
+               clean = 0; /* not clean, but conflicted */
+       }
+       return clean;
+}
+
+static void dir_rename_warning(const char *msg,
+                              int is_add,
+                              int clean,
+                              struct merge_options *opt,
+                              struct rename *ren)
+{
+       const char *other_branch;
+       other_branch = (ren->branch == opt->branch1 ?
+                       opt->branch2 : opt->branch1);
+       if (is_add) {
+               output(opt, clean ? 2 : 1, msg,
+                      ren->pair->one->path, ren->branch,
+                      other_branch, ren->pair->two->path);
+               return;
+       }
+       output(opt, clean ? 2 : 1, msg,
+              ren->pair->one->path, ren->dir_rename_original_dest, ren->branch,
+              other_branch, ren->pair->two->path);
+}
+static int warn_about_dir_renamed_entries(struct merge_options *opt,
+                                         struct rename *ren)
+{
+       const char *msg;
+       int clean = 1, is_add;
+
+       if (!ren)
+               return clean;
+
+       /* Return early if ren was not affected/created by a directory rename */
+       if (!ren->dir_rename_original_dest)
+               return clean;
+
+       /* Sanity checks */
+       assert(opt->detect_directory_renames > 0);
+       assert(ren->dir_rename_original_type == 'A' ||
+              ren->dir_rename_original_type == 'R');
+
+       /* Check whether to treat directory renames as a conflict */
+       clean = (opt->detect_directory_renames == 2);
+
+       is_add = (ren->dir_rename_original_type == 'A');
+       if (ren->dir_rename_original_type == 'A' && clean) {
+               msg = _("Path updated: %s added in %s inside a "
+                       "directory that was renamed in %s; moving it to %s.");
+       } else if (ren->dir_rename_original_type == 'A' && !clean) {
+               msg = _("CONFLICT (file location): %s added in %s "
+                       "inside a directory that was renamed in %s, "
+                       "suggesting it should perhaps be moved to %s.");
+       } else if (ren->dir_rename_original_type == 'R' && clean) {
+               msg = _("Path updated: %s renamed to %s in %s, inside a "
+                       "directory that was renamed in %s; moving it to %s.");
+       } else if (ren->dir_rename_original_type == 'R' && !clean) {
+               msg = _("CONFLICT (file location): %s renamed to %s in %s, "
+                       "inside a directory that was renamed in %s, "
+                       "suggesting it should perhaps be moved to %s.");
+       } else {
+               BUG("Impossible dir_rename_original_type/clean combination");
+       }
+       dir_rename_warning(msg, is_add, clean, opt, ren);
+
+       return clean;
 }
 
 /* Per entry merge function */
@@ -3115,6 +3216,10 @@ static int process_entry(struct merge_options *opt,
        if (entry->rename_conflict_info) {
                struct rename_conflict_info *ci = entry->rename_conflict_info;
                struct diff_filespec *temp;
+               int path_clean;
+
+               path_clean = warn_about_dir_renamed_entries(opt, ci->ren1);
+               path_clean &= warn_about_dir_renamed_entries(opt, ci->ren2);
 
                /*
                 * For cases with a single rename, {o,a,b}->path have all been
@@ -3135,9 +3240,7 @@ static int process_entry(struct merge_options *opt,
                                                           ci);
                        break;
                case RENAME_VIA_DIR:
-                       clean_merge = 1;
-                       if (handle_rename_via_dir(opt, ci))
-                               clean_merge = -1;
+                       clean_merge = handle_rename_via_dir(opt, ci);
                        break;
                case RENAME_ADD:
                        /*
@@ -3187,6 +3290,8 @@ static int process_entry(struct merge_options *opt,
                        entry->processed = 0;
                        break;
                }
+               if (path_clean < clean_merge)
+                       clean_merge = path_clean;
        } else if (o_valid && (!a_valid || !b_valid)) {
                /* Case A: Deleted in one */
                if ((!a_valid && !b_valid) ||
@@ -3558,6 +3663,15 @@ static void merge_recursive_config(struct merge_options *opt)
                opt->merge_detect_rename = git_config_rename("merge.renames", value);
                free(value);
        }
+       if (!git_config_get_string("merge.directoryrenames", &value)) {
+               int boolval = git_parse_maybe_bool(value);
+               if (0 <= boolval) {
+                       opt->detect_directory_renames = boolval ? 2 : 0;
+               } else if (!strcasecmp(value, "conflict")) {
+                       opt->detect_directory_renames = 1;
+               } /* avoid erroring on values from future versions of git */
+               free(value);
+       }
        git_config(git_xmerge_config, NULL);
 }