test-lib: introduce test_commit_bulk
[gitweb.git] / merge-recursive.c
index 2edfa01e43c5b78b200858c9ce1297ce7d68be66..d2e380b7ed845e6fb2cd5616754a3f12a11c7b18 100644 (file)
@@ -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;
 }
@@ -1076,7 +1081,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 +1375,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 +1660,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,
@@ -3090,10 +3105,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 +3208,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 +3232,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 +3282,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 +3655,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);
 }