Merge branch 'jc/merge-refuse-new-root'
"git merge" used to allow merging two branches that have no common base by default, which led to a brand new history of an existing project created and then get pulled by an unsuspecting maintainer, which allowed an unnecessary parallel history merged into the existing project. The command has been taught not to allow this by default, with an escape hatch "--allow-unrelated-histories" option to be used in a rare event that merges histories of two projects that started their lives independently. * jc/merge-refuse-new-root: merge: refuse to create too cool a merge by default
This commit is contained in:
commit
d04aa7ec47
@ -98,6 +98,19 @@ commit or stash your changes before running 'git merge'.
|
|||||||
'git merge --abort' is equivalent to 'git reset --merge' when
|
'git merge --abort' is equivalent to 'git reset --merge' when
|
||||||
`MERGE_HEAD` is present.
|
`MERGE_HEAD` is present.
|
||||||
|
|
||||||
|
--allow-unrelated-histories::
|
||||||
|
By default, `git merge` command refuses to merge histories
|
||||||
|
that do not share a common ancestor. This option can be
|
||||||
|
used to override this safety when merging histories of two
|
||||||
|
projects that started their lives independently. As that is
|
||||||
|
a very rare occasion, no configuration variable to enable
|
||||||
|
this by default exists and will not be added, and the list
|
||||||
|
of options at the top of this documentation does not mention
|
||||||
|
this option. Also `git pull` does not pass this option down
|
||||||
|
to `git merge` (instead, you `git fetch` first, examine what
|
||||||
|
you will be merging and then `git merge` locally with this
|
||||||
|
option).
|
||||||
|
|
||||||
<commit>...::
|
<commit>...::
|
||||||
Commits, usually other branch heads, to merge into our branch.
|
Commits, usually other branch heads, to merge into our branch.
|
||||||
Specifying more than one commit will create a merge with
|
Specifying more than one commit will create a merge with
|
||||||
|
@ -64,6 +64,7 @@ static int option_renormalize;
|
|||||||
static int verbosity;
|
static int verbosity;
|
||||||
static int allow_rerere_auto;
|
static int allow_rerere_auto;
|
||||||
static int abort_current_merge;
|
static int abort_current_merge;
|
||||||
|
static int allow_unrelated_histories;
|
||||||
static int show_progress = -1;
|
static int show_progress = -1;
|
||||||
static int default_to_upstream = 1;
|
static int default_to_upstream = 1;
|
||||||
static const char *sign_commit;
|
static const char *sign_commit;
|
||||||
@ -221,6 +222,8 @@ static struct option builtin_merge_options[] = {
|
|||||||
OPT__VERBOSITY(&verbosity),
|
OPT__VERBOSITY(&verbosity),
|
||||||
OPT_BOOL(0, "abort", &abort_current_merge,
|
OPT_BOOL(0, "abort", &abort_current_merge,
|
||||||
N_("abort the current in-progress merge")),
|
N_("abort the current in-progress merge")),
|
||||||
|
OPT_BOOL(0, "allow-unrelated-histories", &allow_unrelated_histories,
|
||||||
|
N_("allow merging unrelated histories")),
|
||||||
OPT_SET_INT(0, "progress", &show_progress, N_("force progress reporting"), 1),
|
OPT_SET_INT(0, "progress", &show_progress, N_("force progress reporting"), 1),
|
||||||
{ OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"),
|
{ OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"),
|
||||||
N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
|
N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
|
||||||
@ -1398,9 +1401,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
|
|||||||
update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.oid.hash,
|
update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.oid.hash,
|
||||||
NULL, 0, UPDATE_REFS_DIE_ON_ERR);
|
NULL, 0, UPDATE_REFS_DIE_ON_ERR);
|
||||||
|
|
||||||
if (remoteheads && !common)
|
if (remoteheads && !common) {
|
||||||
; /* No common ancestors found. We need a real merge. */
|
/* No common ancestors found. */
|
||||||
else if (!remoteheads ||
|
if (!allow_unrelated_histories)
|
||||||
|
die(_("refusing to merge unrelated histories"));
|
||||||
|
/* otherwise, we need a real merge. */
|
||||||
|
} else if (!remoteheads ||
|
||||||
(!remoteheads->next && !common->next &&
|
(!remoteheads->next && !common->next &&
|
||||||
common->item == remoteheads->item)) {
|
common->item == remoteheads->item)) {
|
||||||
/*
|
/*
|
||||||
|
@ -19,6 +19,8 @@ test_expect_success setup '
|
|||||||
test_commit three &&
|
test_commit three &&
|
||||||
git checkout right &&
|
git checkout right &&
|
||||||
test_commit four &&
|
test_commit four &&
|
||||||
|
git checkout --orphan five &&
|
||||||
|
test_commit five &&
|
||||||
git checkout master
|
git checkout master
|
||||||
'
|
'
|
||||||
|
|
||||||
@ -133,4 +135,18 @@ test_expect_success 'merge FETCH_HEAD octopus non-fast-forward' '
|
|||||||
test_cmp expect actual
|
test_cmp expect actual
|
||||||
'
|
'
|
||||||
|
|
||||||
|
# two-project merge
|
||||||
|
test_expect_success 'refuse two-project merge by default' '
|
||||||
|
t3033_reset &&
|
||||||
|
git reset --hard four &&
|
||||||
|
test_must_fail git merge five
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'two-project merge with --allow-unrelated-histories' '
|
||||||
|
t3033_reset &&
|
||||||
|
git reset --hard four &&
|
||||||
|
git merge --allow-unrelated-histories five &&
|
||||||
|
git diff --exit-code five
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
@ -133,7 +133,7 @@ test_expect_success 'set up second root and merge' '
|
|||||||
rm A B C &&
|
rm A B C &&
|
||||||
test_commit 6 D &&
|
test_commit 6 D &&
|
||||||
git checkout other &&
|
git checkout other &&
|
||||||
git merge third
|
git merge --allow-unrelated-histories third
|
||||||
'
|
'
|
||||||
|
|
||||||
cat > expect-third <<'EOF'
|
cat > expect-third <<'EOF'
|
||||||
|
@ -259,7 +259,8 @@ test_expect_success 'clone shallow object count' '
|
|||||||
test_expect_success 'pull in shallow repo with missing merge base' '
|
test_expect_success 'pull in shallow repo with missing merge base' '
|
||||||
(
|
(
|
||||||
cd shallow &&
|
cd shallow &&
|
||||||
test_must_fail git pull --depth 4 .. A
|
git fetch --depth 4 .. A
|
||||||
|
test_must_fail git merge --allow-unrelated-histories FETCH_HEAD
|
||||||
)
|
)
|
||||||
'
|
'
|
||||||
|
|
||||||
@ -279,9 +280,10 @@ test_expect_success 'clone shallow depth count' '
|
|||||||
test_expect_success 'clone shallow object count' '
|
test_expect_success 'clone shallow object count' '
|
||||||
(
|
(
|
||||||
cd shallow &&
|
cd shallow &&
|
||||||
|
git prune &&
|
||||||
git count-objects -v
|
git count-objects -v
|
||||||
) > count.shallow &&
|
) > count.shallow &&
|
||||||
grep "^count: 55" count.shallow
|
grep "^count: 54" count.shallow
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 'fetch --no-shallow on full repo' '
|
test_expect_success 'fetch --no-shallow on full repo' '
|
||||||
|
@ -47,7 +47,9 @@ test_expect_success 'setup roots, merges and octopuses' '
|
|||||||
git checkout -b yetanotherbranch four &&
|
git checkout -b yetanotherbranch four &&
|
||||||
test_commit eight &&
|
test_commit eight &&
|
||||||
git checkout master &&
|
git checkout master &&
|
||||||
test_merge normalmerge newroot &&
|
test_tick &&
|
||||||
|
git merge --allow-unrelated-histories -m normalmerge newroot &&
|
||||||
|
git tag normalmerge &&
|
||||||
test_tick &&
|
test_tick &&
|
||||||
git merge -m tripus sidebranch anotherbranch &&
|
git merge -m tripus sidebranch anotherbranch &&
|
||||||
git tag tripus &&
|
git tag tripus &&
|
||||||
|
@ -215,11 +215,13 @@ test_expect_success 'criss-cross merge-base for octopus-step' '
|
|||||||
git reset --hard E &&
|
git reset --hard E &&
|
||||||
test_commit CC2 &&
|
test_commit CC2 &&
|
||||||
test_tick &&
|
test_tick &&
|
||||||
git merge -s ours CC1 &&
|
# E is a root commit unrelated to MMR root on which CC1 is based
|
||||||
|
git merge -s ours --allow-unrelated-histories CC1 &&
|
||||||
test_commit CC-o &&
|
test_commit CC-o &&
|
||||||
test_commit CCB &&
|
test_commit CCB &&
|
||||||
git reset --hard CC1 &&
|
git reset --hard CC1 &&
|
||||||
git merge -s ours CC2 &&
|
# E is a root commit unrelated to MMR root on which CC1 is based
|
||||||
|
git merge -s ours --allow-unrelated-histories CC2 &&
|
||||||
test_commit CCA &&
|
test_commit CCA &&
|
||||||
|
|
||||||
git rev-parse CC1 CC2 >expected &&
|
git rev-parse CC1 CC2 >expected &&
|
||||||
|
@ -71,7 +71,7 @@ test_expect_success setup '
|
|||||||
note J &&
|
note J &&
|
||||||
|
|
||||||
git checkout master &&
|
git checkout master &&
|
||||||
test_tick && git merge -m "Coolest" unrelated &&
|
test_tick && git merge --allow-unrelated-histories -m "Coolest" unrelated &&
|
||||||
note K &&
|
note K &&
|
||||||
|
|
||||||
echo "Immaterial" >elif &&
|
echo "Immaterial" >elif &&
|
||||||
|
@ -176,7 +176,8 @@ test_expect_success 'up-to-date merge without common ancestor' '
|
|||||||
test_tick &&
|
test_tick &&
|
||||||
(
|
(
|
||||||
cd repo1 &&
|
cd repo1 &&
|
||||||
git pull ../repo2 master
|
git fetch ../repo2 master &&
|
||||||
|
git merge --allow-unrelated-histories FETCH_HEAD
|
||||||
)
|
)
|
||||||
'
|
'
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ test_expect_success 'setup' '
|
|||||||
|
|
||||||
test_expect_success 'initial merge' '
|
test_expect_success 'initial merge' '
|
||||||
git remote add -f gui ../git-gui &&
|
git remote add -f gui ../git-gui &&
|
||||||
git merge -s ours --no-commit gui/master &&
|
git merge -s ours --no-commit --allow-unrelated-histories gui/master &&
|
||||||
git read-tree --prefix=git-gui/ -u gui/master &&
|
git read-tree --prefix=git-gui/ -u gui/master &&
|
||||||
git commit -m "Merge git-gui as our subdirectory" &&
|
git commit -m "Merge git-gui as our subdirectory" &&
|
||||||
git checkout -b work &&
|
git checkout -b work &&
|
||||||
|
@ -19,7 +19,7 @@ test_expect_success 'setup' '
|
|||||||
git checkout --orphan tmp &&
|
git checkout --orphan tmp &&
|
||||||
test_commit start2 &&
|
test_commit start2 &&
|
||||||
git checkout master &&
|
git checkout master &&
|
||||||
git merge -m next start2 &&
|
git merge -m next --allow-unrelated-histories start2 &&
|
||||||
test_commit final &&
|
test_commit final &&
|
||||||
|
|
||||||
test_seq 40 |
|
test_seq 40 |
|
||||||
|
@ -45,7 +45,8 @@ test_expect_success 'setup' '
|
|||||||
touch secondrootfile &&
|
touch secondrootfile &&
|
||||||
git add secondrootfile &&
|
git add secondrootfile &&
|
||||||
git commit -m "second root") &&
|
git commit -m "second root") &&
|
||||||
git pull secondroot master &&
|
git fetch secondroot master &&
|
||||||
|
git merge --allow-unrelated-histories FETCH_HEAD &&
|
||||||
git clone -q --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 &&
|
git clone -q --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 &&
|
||||||
GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true &&
|
GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true &&
|
||||||
GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" &&
|
GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" &&
|
||||||
|
Loading…
Reference in New Issue
Block a user