mirror of
https://github.com/git/git
synced 2024-08-25 10:45:59 +00:00
![Elijah Newren](/assets/img/avatar_default.png)
This is the bug that just won't die; there always seems to be another form of it somewhere. See the commit message of55f39cf755
("merge: fix misleading pre-merge check documentation", 2018-06-30) for a more detailed explanation), but in short: <quick summary> builtin/merge.c contains this important requirement for merge strategies: ...the index must be in sync with the head commit. The strategies are responsible to ensure this. This condition is important to enforce because there are two likely failure cases when the index isn't in sync with the head commit: * we silently throw away changes the user had staged before the merge * we accidentally (and silently) include changes in the merge that were not part of either of the branches/trees being merged Discarding users' work and mis-merging are both bad outcomes, especially when done silently, so naturally this rule was stated sternly -- but, unfortunately totally ignored in practice unless and until actual bugs were found. But, fear not: the bugs from this were fixed in commitee6566e8d7
("[PATCH] Rewrite read-tree", 2005-09-05) through a rewrite of read-tree (again, commit55f39cf755
has a more detailed explanation of how this affected merge). And it was fixed again in commit160252f816
("git-merge-ours: make sure our index matches HEAD", 2005-11-03) ...and it was fixed again in commit3ec62ad9ff
("merge-octopus: abort if index does not match HEAD", 2016-04-09) ...and again in commit65170c07d4
("merge-recursive: avoid incorporating uncommitted changes in a merge", 2017-12-21) ...and again in commiteddd1a411d
("merge-recursive: enforce rule that index matches head before merging", 2018-06-30) ...with multiple testcases added to the testsuite that could be enumerated in even more commits. Then, finally, in a patch in the same series as the last fix above, the documentation about this requirement was fixed in commit55f39cf755
("merge: fix misleading pre-merge check documentation", 2018-06-30), and we all lived happily ever after... </quick summary> Unfortunately, "ever after" apparently denotes a limited time and it expired today. The merge-recursive rule to enforce that index matches head was at the beginning of merge_trees() and would only trigger when opt->call_depth was 0. Since merge_recursive() doesn't call merge_trees() until after returning from recursing, this meant that the check wasn't triggered by merge_recursive() until it had first finished all the intermediate merges to create virtual merge bases. That is a potentially HUGE amount of computation (and writing of intermediate merge results into the .git/objects directory) before it errors out and says, in effect, "Sorry, I can't do any merging because you have some local changes that would be overwritten." Trying to enforce that all of merge_trees(), merge_recursive(), and merge_recursive_generic() checked the index == head condition earlier resulted in a bunch of broken tests. It turns out that merge_recursive() has code to drop and reload the cache while recursing to create intermediate virtual merge bases, but unfortunately that code runs even when no recursion is necessary. This unconditional dropping and reloading of the cache masked a few bugs: * builtin/merge-recursive.c: didn't even bother loading the index. * builtin/stash.c: feels like a fake 'builtin' because it repeatedly invokes git subprocesses all over the place, mixed with other operations. In particular, invoking "git reset" will reset the index on disk, but the parent process that invoked it won't automatically have its in-memory index updated. * t3030-merge-recursive.h: this test has always been broken in that it didn't make sure to make index match head before running. But, it didn't care about the index or even the merge result, just the verbose output while running. While commiteddd1a411d
("merge-recursive: enforce rule that index matches head before merging", 2018-06-30) should have uncovered this broken test, it used a test_must_fail wrapper around the merge-recursive call because it was known that the merge resulted in a rename/rename conflict. Thus, that fix only made this test fail for a different reason, and since the index == head check didn't happen until after coming all the way back out of the recursion, the testcase had enough information to pass the one check that it did perform. So, load the index in builtin/merge-recursive.c, reload the in-memory index in builtin/stash.c, and modify the t3030 testcase to correctly setup the index and make sure that the test fails in the expected way (meaning it reports a rename/rename conflict). This makes sure that all callers actually make the index match head. The next commit will then enforce the condition that index matches head earlier so this problem doesn't return in the future. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
93 lines
2.4 KiB
C
93 lines
2.4 KiB
C
#include "cache.h"
|
|
#include "builtin.h"
|
|
#include "commit.h"
|
|
#include "tag.h"
|
|
#include "merge-recursive.h"
|
|
#include "xdiff-interface.h"
|
|
|
|
static const char builtin_merge_recursive_usage[] =
|
|
"git %s <base>... -- <head> <remote> ...";
|
|
|
|
static char *better_branch_name(const char *branch)
|
|
{
|
|
static char githead_env[8 + GIT_MAX_HEXSZ + 1];
|
|
char *name;
|
|
|
|
if (strlen(branch) != the_hash_algo->hexsz)
|
|
return xstrdup(branch);
|
|
xsnprintf(githead_env, sizeof(githead_env), "GITHEAD_%s", branch);
|
|
name = getenv(githead_env);
|
|
return xstrdup(name ? name : branch);
|
|
}
|
|
|
|
int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
|
|
{
|
|
const struct object_id *bases[21];
|
|
unsigned bases_count = 0;
|
|
int i, failed;
|
|
struct object_id h1, h2;
|
|
struct merge_options o;
|
|
char *better1, *better2;
|
|
struct commit *result;
|
|
|
|
init_merge_options(&o, the_repository);
|
|
if (argv[0] && ends_with(argv[0], "-subtree"))
|
|
o.subtree_shift = "";
|
|
|
|
if (argc < 4)
|
|
usagef(builtin_merge_recursive_usage, argv[0]);
|
|
|
|
for (i = 1; i < argc; ++i) {
|
|
const char *arg = argv[i];
|
|
|
|
if (starts_with(arg, "--")) {
|
|
if (!arg[2])
|
|
break;
|
|
if (parse_merge_opt(&o, arg + 2))
|
|
die(_("unknown option %s"), arg);
|
|
continue;
|
|
}
|
|
if (bases_count < ARRAY_SIZE(bases)-1) {
|
|
struct object_id *oid = xmalloc(sizeof(struct object_id));
|
|
if (get_oid(argv[i], oid))
|
|
die(_("could not parse object '%s'"), argv[i]);
|
|
bases[bases_count++] = oid;
|
|
}
|
|
else
|
|
warning(Q_("cannot handle more than %d base. "
|
|
"Ignoring %s.",
|
|
"cannot handle more than %d bases. "
|
|
"Ignoring %s.",
|
|
(int)ARRAY_SIZE(bases)-1),
|
|
(int)ARRAY_SIZE(bases)-1, argv[i]);
|
|
}
|
|
if (argc - i != 3) /* "--" "<head>" "<remote>" */
|
|
die(_("not handling anything other than two heads merge."));
|
|
|
|
if (repo_read_index_unmerged(the_repository))
|
|
die_resolve_conflict("merge");
|
|
|
|
o.branch1 = argv[++i];
|
|
o.branch2 = argv[++i];
|
|
|
|
if (get_oid(o.branch1, &h1))
|
|
die(_("could not resolve ref '%s'"), o.branch1);
|
|
if (get_oid(o.branch2, &h2))
|
|
die(_("could not resolve ref '%s'"), o.branch2);
|
|
|
|
o.branch1 = better1 = better_branch_name(o.branch1);
|
|
o.branch2 = better2 = better_branch_name(o.branch2);
|
|
|
|
if (o.verbosity >= 3)
|
|
printf(_("Merging %s with %s\n"), o.branch1, o.branch2);
|
|
|
|
failed = merge_recursive_generic(&o, &h1, &h2, bases_count, bases, &result);
|
|
|
|
free(better1);
|
|
free(better2);
|
|
|
|
if (failed < 0)
|
|
return 128; /* die() error code */
|
|
return failed;
|
|
}
|