]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'en/ort-readiness'
authorJunio C Hamano <gitster@pobox.com>
Fri, 16 Apr 2021 20:53:34 +0000 (13:53 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 16 Apr 2021 20:53:34 +0000 (13:53 -0700)
Plug the ort merge backend throughout the rest of the system, and
start testing it as a replacement for the recursive backend.

* en/ort-readiness:
  Add testing with merge-ort merge strategy
  t6423: mark remaining expected failure under merge-ort as such
  Revert "merge-ort: ignore the directory rename split conflict for now"
  merge-recursive: add a bunch of FIXME comments documenting known bugs
  merge-ort: write $GIT_DIR/AUTO_MERGE whenever we hit a conflict
  t: mark several submodule merging tests as fixed under merge-ort
  merge-ort: implement CE_SKIP_WORKTREE handling with conflicted entries
  t6428: new test for SKIP_WORKTREE handling and conflicts
  merge-ort: support subtree shifting
  merge-ort: let renormalization change modify/delete into clean delete
  merge-ort: have ll_merge() use a special attr_index for renormalization
  merge-ort: add a special minimal index just for renormalization
  merge-ort: use STABLE_QSORT instead of QSORT where required

16 files changed:
branch.c
builtin/rebase.c
ci/run-build-and-tests.sh
merge-ort.c
merge-recursive.c
path.c
path.h
sequencer.c
t/t3512-cherry-pick-submodule.sh
t/t3513-revert-submodule.sh
t/t5572-pull-submodule.sh
t/t6423-merge-rename-directories.sh
t/t6428-merge-conflicts-sparse.sh [new file with mode: 0755]
t/t6437-submodule-merge.sh
t/t6438-submodule-directory-file-conflicts.sh
t/test-lib.sh

index 9c9dae1eae321c6878607a3b624a187fcb176380..b71a2de29dbed9ca37423f956f0b54f3fdc279d9 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -344,6 +344,7 @@ void remove_merge_branch_state(struct repository *r)
        unlink(git_path_merge_rr(r));
        unlink(git_path_merge_msg(r));
        unlink(git_path_merge_mode(r));
+       unlink(git_path_auto_merge(r));
        save_autostash(git_path_merge_autostash(r));
 }
 
index 783b526f6e758a05a75524d86f91088bb7ba5b87..ed1da1760e4cd87527265c699a790daccd3cf21e 100644 (file)
@@ -738,6 +738,7 @@ static int finish_rebase(struct rebase_options *opts)
        int ret = 0;
 
        delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
+       unlink(git_path_auto_merge(the_repository));
        apply_autostash(state_dir_path("autostash", opts));
        close_object_store(the_repository->objects);
        /*
index a66b5e8c75aec2e4a9ea80227a8b492c8efcdf8d..d19be40544c8fde49b9405ab170e283c1ad8f97c 100755 (executable)
@@ -16,6 +16,7 @@ linux-gcc)
        export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
        make test
        export GIT_TEST_SPLIT_INDEX=yes
+       export GIT_TEST_MERGE_ALGORITHM=recursive
        export GIT_TEST_FULL_IN_PACK_ARRAY=true
        export GIT_TEST_OE_SIZE=10
        export GIT_TEST_OE_DELTA_SIZE=5
index 028d712f5406ff4f17b3834d2c25f7c13118d848..d1f4cf8a1f2ed404c697bcabf36ef63a072a0f7f 100644 (file)
@@ -18,6 +18,7 @@
 #include "merge-ort.h"
 
 #include "alloc.h"
+#include "attr.h"
 #include "blob.h"
 #include "cache-tree.h"
 #include "commit.h"
@@ -25,6 +26,7 @@
 #include "diff.h"
 #include "diffcore.h"
 #include "dir.h"
+#include "entry.h"
 #include "ll-merge.h"
 #include "object-store.h"
 #include "revision.h"
@@ -220,6 +222,16 @@ struct merge_options_internal {
         */
        struct rename_info renames;
 
+       /*
+        * attr_index: hacky minimal index used for renormalization
+        *
+        * renormalization code _requires_ an index, though it only needs to
+        * find a .gitattributes file within the index.  So, when
+        * renormalization is important, we create a special index with just
+        * that one file.
+        */
+       struct index_state attr_index;
+
        /*
         * current_dir_name, toplevel_dir: temporary vars
         *
@@ -399,6 +411,9 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,
        string_list_clear(&opti->paths_to_free, 0);
        opti->paths_to_free.strdup_strings = 0;
 
+       if (opti->attr_index.cache_nr) /* true iff opt->renormalize */
+               discard_index(&opti->attr_index);
+
        /* Free memory used by various renames maps */
        for (i = MERGE_SIDE1; i <= MERGE_SIDE2; ++i) {
                strintmap_func(&renames->dirs_removed[i]);
@@ -1187,6 +1202,63 @@ static int merge_submodule(struct merge_options *opt,
        return 0;
 }
 
+static void initialize_attr_index(struct merge_options *opt)
+{
+       /*
+        * The renormalize_buffer() functions require attributes, and
+        * annoyingly those can only be read from the working tree or from
+        * an index_state.  merge-ort doesn't have an index_state, so we
+        * generate a fake one containing only attribute information.
+        */
+       struct merged_info *mi;
+       struct index_state *attr_index = &opt->priv->attr_index;
+       struct cache_entry *ce;
+
+       attr_index->initialized = 1;
+
+       if (!opt->renormalize)
+               return;
+
+       mi = strmap_get(&opt->priv->paths, GITATTRIBUTES_FILE);
+       if (!mi)
+               return;
+
+       if (mi->clean) {
+               int len = strlen(GITATTRIBUTES_FILE);
+               ce = make_empty_cache_entry(attr_index, len);
+               ce->ce_mode = create_ce_mode(mi->result.mode);
+               ce->ce_flags = create_ce_flags(0);
+               ce->ce_namelen = len;
+               oidcpy(&ce->oid, &mi->result.oid);
+               memcpy(ce->name, GITATTRIBUTES_FILE, len);
+               add_index_entry(attr_index, ce,
+                               ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
+               get_stream_filter(attr_index, GITATTRIBUTES_FILE, &ce->oid);
+       } else {
+               int stage, len;
+               struct conflict_info *ci;
+
+               ASSIGN_AND_VERIFY_CI(ci, mi);
+               for (stage = 0; stage < 3; stage++) {
+                       unsigned stage_mask = (1 << stage);
+
+                       if (!(ci->filemask & stage_mask))
+                               continue;
+                       len = strlen(GITATTRIBUTES_FILE);
+                       ce = make_empty_cache_entry(attr_index, len);
+                       ce->ce_mode = create_ce_mode(ci->stages[stage].mode);
+                       ce->ce_flags = create_ce_flags(stage);
+                       ce->ce_namelen = len;
+                       oidcpy(&ce->oid, &ci->stages[stage].oid);
+                       memcpy(ce->name, GITATTRIBUTES_FILE, len);
+                       add_index_entry(attr_index, ce,
+                                       ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
+                       get_stream_filter(attr_index, GITATTRIBUTES_FILE,
+                                         &ce->oid);
+               }
+       }
+}
+
 static int merge_3way(struct merge_options *opt,
                      const char *path,
                      const struct object_id *o,
@@ -1201,6 +1273,9 @@ static int merge_3way(struct merge_options *opt,
        char *base, *name1, *name2;
        int merge_status;
 
+       if (!opt->priv->attr_index.initialized)
+               initialize_attr_index(opt);
+
        ll_opts.renormalize = opt->renormalize;
        ll_opts.extra_marker_size = extra_marker_size;
        ll_opts.xdl_opts = opt->xdl_opts;
@@ -1239,7 +1314,7 @@ static int merge_3way(struct merge_options *opt,
 
        merge_status = ll_merge(result_buf, path, &orig, base,
                                &src1, name1, &src2, name2,
-                               opt->repo->index, &ll_opts);
+                               &opt->priv->attr_index, &ll_opts);
 
        free(base);
        free(name1);
@@ -1562,18 +1637,7 @@ static void get_provisional_directory_renames(struct merge_options *opt,
                                 "no destination getting a majority of the "
                                 "files."),
                               source_dir);
-                       /*
-                        * We should mark this as unclean IF something attempts
-                        * to use this rename.  We do not yet have the logic
-                        * in place to detect if this directory rename is being
-                        * used, and optimizations that reduce the number of
-                        * renames cause this to falsely trigger.  For now,
-                        * just disable it, causing t6423 testcase 2a to break.
-                        * We'll later fix the detection, and when we do we
-                        * will re-enable setting *clean to 0 (and thereby fix
-                        * t6423 testcase 2a).
-                        */
-                       /*   *clean = 0;   */
+                       *clean = 0;
                } else {
                        strmap_put(&renames->dir_renames[side],
                                   source_dir, (void*)best);
@@ -2404,7 +2468,7 @@ static int detect_and_process_renames(struct merge_options *opt,
        clean &= collect_renames(opt, &combined, MERGE_SIDE2,
                                 &renames->dir_renames[1],
                                 &renames->dir_renames[2]);
-       QSORT(combined.queue, combined.nr, compare_pairs);
+       STABLE_QSORT(combined.queue, combined.nr, compare_pairs);
        trace2_region_leave("merge", "directory renames", opt->repo);
 
        trace2_region_enter("merge", "process renames", opt->repo);
@@ -2474,6 +2538,61 @@ static int string_list_df_name_compare(const char *one, const char *two)
        return onelen - twolen;
 }
 
+static int read_oid_strbuf(struct merge_options *opt,
+                          const struct object_id *oid,
+                          struct strbuf *dst)
+{
+       void *buf;
+       enum object_type type;
+       unsigned long size;
+       buf = read_object_file(oid, &type, &size);
+       if (!buf)
+               return err(opt, _("cannot read object %s"), oid_to_hex(oid));
+       if (type != OBJ_BLOB) {
+               free(buf);
+               return err(opt, _("object %s is not a blob"), oid_to_hex(oid));
+       }
+       strbuf_attach(dst, buf, size, size + 1);
+       return 0;
+}
+
+static int blob_unchanged(struct merge_options *opt,
+                         const struct version_info *base,
+                         const struct version_info *side,
+                         const char *path)
+{
+       struct strbuf basebuf = STRBUF_INIT;
+       struct strbuf sidebuf = STRBUF_INIT;
+       int ret = 0; /* assume changed for safety */
+       const struct index_state *idx = &opt->priv->attr_index;
+
+       if (!idx->initialized)
+               initialize_attr_index(opt);
+
+       if (base->mode != side->mode)
+               return 0;
+       if (oideq(&base->oid, &side->oid))
+               return 1;
+
+       if (read_oid_strbuf(opt, &base->oid, &basebuf) ||
+           read_oid_strbuf(opt, &side->oid, &sidebuf))
+               goto error_return;
+       /*
+        * Note: binary | is used so that both renormalizations are
+        * performed.  Comparison can be skipped if both files are
+        * unchanged since their sha1s have already been compared.
+        */
+       if (renormalize_buffer(idx, path, basebuf.buf, basebuf.len, &basebuf) |
+           renormalize_buffer(idx, path, sidebuf.buf, sidebuf.len, &sidebuf))
+               ret = (basebuf.len == sidebuf.len &&
+                      !memcmp(basebuf.buf, sidebuf.buf, basebuf.len));
+
+error_return:
+       strbuf_release(&basebuf);
+       strbuf_release(&sidebuf);
+       return ret;
+}
+
 struct directory_versions {
        /*
         * versions: list of (basename -> version_info)
@@ -2549,6 +2668,7 @@ static void write_tree(struct object_id *result_oid,
         */
        relevant_entries.items = versions->items + offset;
        relevant_entries.nr = versions->nr - offset;
+       /* No need for STABLE_QSORT -- filenames must be unique */
        QSORT(relevant_entries.items, relevant_entries.nr, tree_entry_order);
 
        /* Pre-allocate some space in buf */
@@ -3060,8 +3180,13 @@ static void process_entry(struct merge_options *opt,
                modify_branch = (side == 1) ? opt->branch1 : opt->branch2;
                delete_branch = (side == 1) ? opt->branch2 : opt->branch1;
 
-               if (ci->path_conflict &&
-                   oideq(&ci->stages[0].oid, &ci->stages[side].oid)) {
+               if (opt->renormalize &&
+                   blob_unchanged(opt, &ci->stages[0], &ci->stages[side],
+                                  path)) {
+                       ci->merged.is_null = 1;
+                       ci->merged.clean = 1;
+               } else if (ci->path_conflict &&
+                          oideq(&ci->stages[0].oid, &ci->stages[side].oid)) {
                        /*
                         * This came from a rename/delete; no action to take,
                         * but avoid printing "modify/delete" conflict notice
@@ -3233,23 +3358,27 @@ static int checkout(struct merge_options *opt,
        return ret;
 }
 
-static int record_conflicted_index_entries(struct merge_options *opt,
-                                          struct index_state *index,
-                                          struct strmap *paths,
-                                          struct strmap *conflicted)
+static int record_conflicted_index_entries(struct merge_options *opt)
 {
        struct hashmap_iter iter;
        struct strmap_entry *e;
+       struct index_state *index = opt->repo->index;
+       struct checkout state = CHECKOUT_INIT;
        int errs = 0;
        int original_cache_nr;
 
-       if (strmap_empty(conflicted))
+       if (strmap_empty(&opt->priv->conflicted))
                return 0;
 
+       /* If any entries have skip_worktree set, we'll have to check 'em out */
+       state.force = 1;
+       state.quiet = 1;
+       state.refresh_cache = 1;
+       state.istate = index;
        original_cache_nr = index->cache_nr;
 
        /* Put every entry from paths into plist, then sort */
-       strmap_for_each_entry(conflicted, &iter, e) {
+       strmap_for_each_entry(&opt->priv->conflicted, &iter, e) {
                const char *path = e->key;
                struct conflict_info *ci = e->value;
                int pos;
@@ -3290,9 +3419,23 @@ static int record_conflicted_index_entries(struct merge_options *opt,
                         * the higher order stages.  Thus, we need override
                         * the CE_SKIP_WORKTREE bit and manually write those
                         * files to the working disk here.
-                        *
-                        * TODO: Implement this CE_SKIP_WORKTREE fixup.
                         */
+                       if (ce_skip_worktree(ce)) {
+                               struct stat st;
+
+                               if (!lstat(path, &st)) {
+                                       char *new_name = unique_path(&opt->priv->paths,
+                                                                    path,
+                                                                    "cruft");
+
+                                       path_msg(opt, path, 1,
+                                                _("Note: %s not up to date and in way of checking out conflicted version; old copy renamed to %s"),
+                                                path, new_name);
+                                       errs |= rename(path, new_name);
+                                       free(new_name);
+                               }
+                               errs |= checkout_entry(ce, &state, NULL, NULL);
+                       }
 
                        /*
                         * Mark this cache entry for removal and instead add
@@ -3324,6 +3467,11 @@ static int record_conflicted_index_entries(struct merge_options *opt,
         * entries we added to the end into their right locations.
         */
        remove_marked_cache_entries(index, 1);
+       /*
+        * No need for STABLE_QSORT -- cmp_cache_name_compare sorts primarily
+        * on filename and secondarily on stage, and (name, stage #) are a
+        * unique tuple.
+        */
        QSORT(index->cache, index->cache_nr, cmp_cache_name_compare);
 
        return errs;
@@ -3337,7 +3485,8 @@ void merge_switch_to_result(struct merge_options *opt,
 {
        assert(opt->priv == NULL);
        if (result->clean >= 0 && update_worktree_and_index) {
-               struct merge_options_internal *opti = result->priv;
+               const char *filename;
+               FILE *fp;
 
                trace2_region_enter("merge", "checkout", opt->repo);
                if (checkout(opt, head, result->tree)) {
@@ -3348,14 +3497,22 @@ void merge_switch_to_result(struct merge_options *opt,
                trace2_region_leave("merge", "checkout", opt->repo);
 
                trace2_region_enter("merge", "record_conflicted", opt->repo);
-               if (record_conflicted_index_entries(opt, opt->repo->index,
-                                                   &opti->paths,
-                                                   &opti->conflicted)) {
+               opt->priv = result->priv;
+               if (record_conflicted_index_entries(opt)) {
                        /* failure to function */
+                       opt->priv = NULL;
                        result->clean = -1;
                        return;
                }
+               opt->priv = NULL;
                trace2_region_leave("merge", "record_conflicted", opt->repo);
+
+               trace2_region_enter("merge", "write_auto_merge", opt->repo);
+               filename = git_path_auto_merge(opt->repo);
+               fp = xfopen(filename, "w");
+               fprintf(fp, "%s\n", oid_to_hex(&result->tree->object.oid));
+               fclose(fp);
+               trace2_region_leave("merge", "write_auto_merge", opt->repo);
        }
 
        if (display_update_msgs) {
@@ -3400,6 +3557,8 @@ void merge_finalize(struct merge_options *opt,
 {
        struct merge_options_internal *opti = result->priv;
 
+       if (opt->renormalize)
+               git_attr_set_direction(GIT_ATTR_CHECKIN);
        assert(opt->priv == NULL);
 
        clear_or_reinit_internal_opts(opti, 0);
@@ -3408,6 +3567,23 @@ void merge_finalize(struct merge_options *opt,
 
 /*** Function Grouping: helper functions for merge_incore_*() ***/
 
+static struct tree *shift_tree_object(struct repository *repo,
+                                     struct tree *one, struct tree *two,
+                                     const char *subtree_shift)
+{
+       struct object_id shifted;
+
+       if (!*subtree_shift) {
+               shift_tree(repo, &one->object.oid, &two->object.oid, &shifted, 0);
+       } else {
+               shift_tree_by(repo, &one->object.oid, &two->object.oid, &shifted,
+                             subtree_shift);
+       }
+       if (oideq(&two->object.oid, &shifted))
+               return two;
+       return lookup_tree(repo, &shifted);
+}
+
 static inline void set_commit_tree(struct commit *c, struct tree *t)
 {
        c->maybe_tree = t;
@@ -3475,6 +3651,10 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)
        /* Default to histogram diff.  Actually, just hardcode it...for now. */
        opt->xdl_opts = DIFF_WITH_ALG(opt, HISTOGRAM_DIFF);
 
+       /* Handle attr direction stuff for renormalization */
+       if (opt->renormalize)
+               git_attr_set_direction(GIT_ATTR_CHECKOUT);
+
        /* Initialization of opt->priv, our internal merge data */
        trace2_region_enter("merge", "allocate/init", opt->repo);
        if (opt->priv) {
@@ -3533,6 +3713,13 @@ static void merge_ort_nonrecursive_internal(struct merge_options *opt,
 {
        struct object_id working_tree_oid;
 
+       if (opt->subtree_shift) {
+               side2 = shift_tree_object(opt->repo, side1, side2,
+                                         opt->subtree_shift);
+               merge_base = shift_tree_object(opt->repo, side1, merge_base,
+                                              opt->subtree_shift);
+       }
+
        trace2_region_enter("merge", "collect_merge_info", opt->repo);
        if (collect_merge_info(opt, merge_base, side1, side2) != 0) {
                /*
index ed31f9496cbcb2e71f41fde922075dc2684c70f2..7618303f7b425dfdedf90dd1bffca0de78a4166f 100644 (file)
@@ -1075,6 +1075,11 @@ static int merge_3way(struct merge_options *opt,
        read_mmblob(&src1, &a->oid);
        read_mmblob(&src2, &b->oid);
 
+       /*
+        * FIXME: Using a->path for normalization rules in ll_merge could be
+        * wrong if we renamed from a->path to b->path.  We should use the
+        * target path for where the file will be written.
+        */
        merge_status = ll_merge(result_buf, a->path, &orig, base,
                                &src1, name1, &src2, name2,
                                opt->repo->index, &ll_opts);
@@ -1154,6 +1159,8 @@ static void print_commit(struct commit *commit)
        struct strbuf sb = STRBUF_INIT;
        struct pretty_print_context ctx = {0};
        ctx.date_mode.type = DATE_NORMAL;
+       /* FIXME: Merge this with output_commit_title() */
+       assert(!merge_remote_util(commit));
        format_commit_message(commit, " %h: %m %s", &sb, &ctx);
        fprintf(stderr, "%s\n", sb.buf);
        strbuf_release(&sb);
@@ -1177,6 +1184,11 @@ static int merge_submodule(struct merge_options *opt,
        int search = !opt->priv->call_depth;
 
        /* store a in result in case we fail */
+       /* FIXME: This is the WRONG resolution for the recursive case when
+        * we need to be careful to avoid accidentally matching either side.
+        * Should probably use o instead there, much like we do for merging
+        * binaries.
+        */
        oidcpy(result, a);
 
        /* we can not handle deletion conflicts */
@@ -1301,6 +1313,13 @@ static int merge_mode_and_contents(struct merge_options *opt,
 
        if ((S_IFMT & a->mode) != (S_IFMT & b->mode)) {
                result->clean = 0;
+               /*
+                * FIXME: This is a bad resolution for recursive case; for
+                * the recursive case we want something that is unlikely to
+                * accidentally match either side.  Also, while it makes
+                * sense to prefer regular files over symlinks, it doesn't
+                * make sense to prefer regular files over submodules.
+                */
                if (S_ISREG(a->mode)) {
                        result->blob.mode = a->mode;
                        oidcpy(&result->blob.oid, &a->oid);
@@ -1349,6 +1368,7 @@ static int merge_mode_and_contents(struct merge_options *opt,
                        free(result_buf.ptr);
                        if (ret)
                                return ret;
+                       /* FIXME: bug, what if modes didn't match? */
                        result->clean = (merge_status == 0);
                } else if (S_ISGITLINK(a->mode)) {
                        result->clean = merge_submodule(opt, &result->blob.oid,
@@ -2663,6 +2683,14 @@ static int process_renames(struct merge_options *opt,
        struct string_list b_by_dst = STRING_LIST_INIT_NODUP;
        const struct rename *sre;
 
+       /*
+        * FIXME: As string-list.h notes, it's O(n^2) to build a sorted
+        * string_list one-by-one, but O(n log n) to build it unsorted and
+        * then sort it.  Note that as we build the list, we do not need to
+        * check if the existing destination path is already in the list,
+        * because the structure of diffcore_rename guarantees we won't
+        * have duplicates.
+        */
        for (i = 0; i < a_renames->nr; i++) {
                sre = a_renames->items[i].util;
                string_list_insert(&a_by_dst, sre->pair->two->path)->util
@@ -3601,6 +3629,15 @@ static int merge_recursive_internal(struct merge_options *opt,
                        return err(opt, _("merge returned no commit"));
        }
 
+       /*
+        * FIXME: Since merge_recursive_internal() is only ever called by
+        * places that ensure the index is loaded first
+        * (e.g. builtin/merge.c, rebase/sequencer, etc.), in the common
+        * case where the merge base was unique that means when we get here
+        * we immediately discard the index and re-read it, which is a
+        * complete waste of time.  We should only be discarding and
+        * re-reading if we were forced to recurse.
+        */
        discard_index(opt->repo->index);
        if (!opt->priv->call_depth)
                repo_read_index(opt->repo);
diff --git a/path.c b/path.c
index 7b385e5eb282276f5d2e0becde48d5e09434d6b1..9e883eb52446d39573a0291109e4b57d28d56bc9 100644 (file)
--- a/path.c
+++ b/path.c
@@ -1534,5 +1534,6 @@ REPO_GIT_PATH_FUNC(merge_rr, "MERGE_RR")
 REPO_GIT_PATH_FUNC(merge_mode, "MERGE_MODE")
 REPO_GIT_PATH_FUNC(merge_head, "MERGE_HEAD")
 REPO_GIT_PATH_FUNC(merge_autostash, "MERGE_AUTOSTASH")
+REPO_GIT_PATH_FUNC(auto_merge, "AUTO_MERGE")
 REPO_GIT_PATH_FUNC(fetch_head, "FETCH_HEAD")
 REPO_GIT_PATH_FUNC(shallow, "shallow")
diff --git a/path.h b/path.h
index e7e77da6aaa5cf8e4652141ecf785ee18effeaca..251c78d980000af0a40f1388ece5c9d68b354982 100644 (file)
--- a/path.h
+++ b/path.h
@@ -176,6 +176,7 @@ struct path_cache {
        const char *merge_mode;
        const char *merge_head;
        const char *merge_autostash;
+       const char *auto_merge;
        const char *fetch_head;
        const char *shallow;
 };
@@ -191,6 +192,7 @@ const char *git_path_merge_rr(struct repository *r);
 const char *git_path_merge_mode(struct repository *r);
 const char *git_path_merge_head(struct repository *r);
 const char *git_path_merge_autostash(struct repository *r);
+const char *git_path_auto_merge(struct repository *r);
 const char *git_path_fetch_head(struct repository *r);
 const char *git_path_shallow(struct repository *r);
 
index fd183b5593bcc1c3558350d9e9d12118afdf6417..c29a36824cc5891bb5418d4da61c79759d4993c4 100644 (file)
@@ -2281,6 +2281,7 @@ static int do_pick_commit(struct repository *r,
                refs_delete_ref(get_main_ref_store(r), "", "CHERRY_PICK_HEAD",
                                NULL, 0);
                unlink(git_path_merge_msg(r));
+               unlink(git_path_auto_merge(r));
                fprintf(stderr,
                        _("dropping %s %s -- patch contents already upstream\n"),
                        oid_to_hex(&commit->object.oid), msg.subject);
@@ -2644,6 +2645,8 @@ void sequencer_post_commit_cleanup(struct repository *r, int verbose)
                need_cleanup = 1;
        }
 
+       unlink(git_path_auto_merge(r));
+
        if (!need_cleanup)
                return;
 
@@ -4304,6 +4307,7 @@ static int pick_commits(struct repository *r,
                        unlink(rebase_path_stopped_sha());
                        unlink(rebase_path_amend());
                        unlink(git_path_merge_head(r));
+                       unlink(git_path_auto_merge(r));
                        delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
 
                        if (item->command == TODO_BREAK) {
@@ -4717,6 +4721,7 @@ static int commit_staged_changes(struct repository *r,
                return error(_("could not commit staged changes."));
        unlink(rebase_path_amend());
        unlink(git_path_merge_head(r));
+       unlink(git_path_auto_merge(r));
        if (final_fixup) {
                unlink(rebase_path_fixup_msg());
                unlink(rebase_path_squash_msg());
index 822f2d4bfbd5adc7d7dfce8d6f9c022ac2276b41..c657840db33b6e8d4c47ece5c9eb0feba495825a 100755 (executable)
@@ -8,8 +8,11 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/lib-submodule-update.sh
 
-KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
-KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
+if test "$GIT_TEST_MERGE_ALGORITHM" != ort
+then
+       KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
+       KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
+fi
 test_submodule_switch "cherry-pick"
 
 test_expect_success 'unrelated submodule/file conflict is ignored' '
index a759f12cbb1d6b7676d639806cdbe94e1cc201ac..74cd96e582234feeabf6a8055ff98e17e2002c09 100755 (executable)
@@ -30,7 +30,10 @@ git_revert () {
        git revert HEAD
 }
 
-KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
+if test "$GIT_TEST_MERGE_ALGORITHM" != ort
+then
+       KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
+fi
 test_submodule_switch_func "git_revert"
 
 test_done
index 29537f4798ef85845f3b2f267abf227d9bd1fe3c..4f92a116e1f0ec48bdf79f1963da32c8ecdcd8dd 100755 (executable)
@@ -42,8 +42,11 @@ git_pull_noff () {
        $2 git pull --no-ff
 }
 
-KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
-KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
+if test "$GIT_TEST_MERGE_ALGORITHM" != ort
+then
+       KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
+       KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
+fi
 test_submodule_switch_func "git_pull_noff"
 
 test_expect_success 'pull --recurse-submodule setup' '
index 379aac0103ccbdf4131168362b1ba591a349cc42..7134769149fc538d685431e6963c655e9db806fd 100755 (executable)
@@ -4797,7 +4797,7 @@ test_setup_12f () {
        )
 }
 
-test_expect_merge_algorithm failure success '12f: Trivial directory resolve, caching, all kinds of fun' '
+test_expect_merge_algorithm failure failure '12f: Trivial directory resolve, caching, all kinds of fun' '
        test_setup_12f &&
        (
                cd 12f &&
diff --git a/t/t6428-merge-conflicts-sparse.sh b/t/t6428-merge-conflicts-sparse.sh
new file mode 100755 (executable)
index 0000000..7e8bf49
--- /dev/null
@@ -0,0 +1,158 @@
+#!/bin/sh
+
+test_description="merge cases"
+
+# The setup for all of them, pictorially, is:
+#
+#      A
+#      o
+#     / \
+#  O o   ?
+#     \ /
+#      o
+#      B
+#
+# To help make it easier to follow the flow of tests, they have been
+# divided into sections and each test will start with a quick explanation
+# of what commits O, A, and B contain.
+#
+# Notation:
+#    z/{b,c}   means  files z/b and z/c both exist
+#    x/d_1     means  file x/d exists with content d1.  (Purpose of the
+#                     underscore notation is to differentiate different
+#                     files that might be renamed into each other's paths.)
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-merge.sh
+
+
+# Testcase basic, conflicting changes in 'numerals'
+
+test_setup_numerals () {
+       test_create_repo numerals_$1 &&
+       (
+               cd numerals_$1 &&
+
+               >README &&
+               test_write_lines I II III >numerals &&
+               git add README numerals &&
+               test_tick &&
+               git commit -m "O" &&
+
+               git branch O &&
+               git branch A &&
+               git branch B &&
+
+               git checkout A &&
+               test_write_lines I II III IIII >numerals &&
+               git add numerals &&
+               test_tick &&
+               git commit -m "A" &&
+
+               git checkout B &&
+               test_write_lines I II III IV >numerals &&
+               git add numerals &&
+               test_tick &&
+               git commit -m "B" &&
+
+               cat <<-EOF >expected-index &&
+               H README
+               M numerals
+               M numerals
+               M numerals
+               EOF
+
+               cat <<-EOF >expected-merge
+               I
+               II
+               III
+               <<<<<<< HEAD
+               IIII
+               =======
+               IV
+               >>>>>>> B^0
+               EOF
+
+       )
+}
+
+test_expect_success 'conflicting entries written to worktree even if sparse' '
+       test_setup_numerals plain &&
+       (
+               cd numerals_plain &&
+
+               git checkout A^0 &&
+
+               test_path_is_file README &&
+               test_path_is_file numerals &&
+
+               git sparse-checkout init &&
+               git sparse-checkout set README &&
+
+               test_path_is_file README &&
+               test_path_is_missing numerals &&
+
+               test_must_fail git merge -s recursive B^0 &&
+
+               git ls-files -t >index_files &&
+               test_cmp expected-index index_files &&
+
+               test_path_is_file README &&
+               test_path_is_file numerals &&
+
+               test_cmp expected-merge numerals &&
+
+               # 4 other files:
+               #   * expected-merge
+               #   * expected-index
+               #   * index_files
+               #   * others
+               git ls-files -o >others &&
+               test_line_count = 4 others
+       )
+'
+
+test_expect_merge_algorithm failure success 'present-despite-SKIP_WORKTREE handled reasonably' '
+       test_setup_numerals in_the_way &&
+       (
+               cd numerals_in_the_way &&
+
+               git checkout A^0 &&
+
+               test_path_is_file README &&
+               test_path_is_file numerals &&
+
+               git sparse-checkout init &&
+               git sparse-checkout set README &&
+
+               test_path_is_file README &&
+               test_path_is_missing numerals &&
+
+               echo foobar >numerals &&
+
+               test_must_fail git merge -s recursive B^0 &&
+
+               git ls-files -t >index_files &&
+               test_cmp expected-index index_files &&
+
+               test_path_is_file README &&
+               test_path_is_file numerals &&
+
+               test_cmp expected-merge numerals &&
+
+               # There should still be a file with "foobar" in it
+               grep foobar * &&
+
+               # 5 other files:
+               #   * expected-merge
+               #   * expected-index
+               #   * index_files
+               #   * others
+               #   * whatever name was given to the numerals file that had
+               #     "foobar" in it
+               git ls-files -o >others &&
+               test_line_count = 5 others
+       )
+'
+
+test_done
index 0f92bcf326c848407ad78131d28004d45648d95d..e5e89c2045e714aca5e52d7612ba6bed67fa3c5e 100755 (executable)
@@ -6,6 +6,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-merge.sh
 
 #
 # history
@@ -328,7 +329,7 @@ test_expect_success 'setup file/submodule conflict' '
        )
 '
 
-test_expect_failure 'file/submodule conflict' '
+test_expect_merge_algorithm failure success 'file/submodule conflict' '
        test_when_finished "git -C file-submodule reset --hard" &&
        (
                cd file-submodule &&
@@ -437,7 +438,7 @@ test_expect_failure 'directory/submodule conflict; keep submodule clean' '
        )
 '
 
-test_expect_failure !FAIL_PREREQS 'directory/submodule conflict; should not treat submodule files as untracked or in the way' '
+test_expect_merge_algorithm failure success !FAIL_PREREQS 'directory/submodule conflict; should not treat submodule files as untracked or in the way' '
        test_when_finished "git -C directory-submodule/path reset --hard" &&
        test_when_finished "git -C directory-submodule reset --hard" &&
        (
index 04bf4be7d79224cebbe66df806acbe069f200e9e..8df67a0ef99d26357404579ef3dbc102317fc5c6 100755 (executable)
@@ -12,8 +12,11 @@ test_submodule_switch "merge --ff"
 
 test_submodule_switch "merge --ff-only"
 
-KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
-KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
+if test "$GIT_TEST_MERGE_ALGORITHM" != ort
+then
+       KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
+       KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
+fi
 test_submodule_switch "merge --no-ff"
 
 test_done
index d3f6af6a65451cdd7868e5dc355b44fed49a0670..3dec266221cd07a608b58a1ac89449cef2a21cc3 100644 (file)
@@ -448,6 +448,8 @@ export EDITOR
 
 GIT_DEFAULT_HASH="${GIT_TEST_DEFAULT_HASH:-sha1}"
 export GIT_DEFAULT_HASH
+GIT_TEST_MERGE_ALGORITHM="${GIT_TEST_MERGE_ALGORITHM:-ort}"
+export GIT_TEST_MERGE_ALGORITHM
 
 # Tests using GIT_TRACE typically don't want <timestamp> <file>:<line> output
 GIT_TRACE_BARE=1