Merge branch 'en/ort-readiness'

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
This commit is contained in:
Junio C Hamano 2021-04-16 13:53:34 -07:00
commit 7bec8e7fa6
16 changed files with 446 additions and 38 deletions

View File

@ -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));
}

View 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);
/*

View File

@ -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

View 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) {
/*

View 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);

1
path.c
View File

@ -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")

2
path.h
View File

@ -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);

View 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());

View File

@ -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' '

View File

@ -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

View File

@ -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' '

View File

@ -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 &&

158
t/t6428-merge-conflicts-sparse.sh Executable file
View File

@ -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

View File

@ -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" &&
(

View File

@ -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

View 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