Merge branch 'en/pull-conflicting-options' into maint
"git pull" had various corner cases that were not well thought out around its --rebase backend, e.g. "git pull --ff-only" did not stop but went ahead and rebased when the history on other side is not a descendant of our history. The series tries to fix them up. * en/pull-conflicting-options: pull: fix handling of multiple heads pull: update docs & code for option compatibility with rebasing pull: abort by default when fast-forwarding is not possible pull: make --rebase and --no-rebase override pull.ff=only pull: since --ff-only overrides, handle it first pull: abort if --ff-only is given and fast-forwarding is impossible t7601: add tests of interactions with multiple merge heads and config t7601: test interaction of merge/rebase/fast-forward flags and options
This commit is contained in:
commit
b20f67a659
@ -61,6 +61,8 @@ merge has resulted in conflicts.
|
||||
|
||||
OPTIONS
|
||||
-------
|
||||
:git-merge: 1
|
||||
|
||||
include::merge-options.txt[]
|
||||
|
||||
-m <msg>::
|
||||
|
@ -15,14 +15,17 @@ SYNOPSIS
|
||||
DESCRIPTION
|
||||
-----------
|
||||
|
||||
Incorporates changes from a remote repository into the current
|
||||
branch. In its default mode, `git pull` is shorthand for
|
||||
`git fetch` followed by `git merge FETCH_HEAD`.
|
||||
Incorporates changes from a remote repository into the current branch.
|
||||
If the current branch is behind the remote, then by default it will
|
||||
fast-forward the current branch to match the remote. If the current
|
||||
branch and the remote have diverged, the user needs to specify how to
|
||||
reconcile the divergent branches with `--rebase` or `--no-rebase` (or
|
||||
the corresponding configuration option in `pull.rebase`).
|
||||
|
||||
More precisely, 'git pull' runs 'git fetch' with the given
|
||||
parameters and calls 'git merge' to merge the retrieved branch
|
||||
heads into the current branch.
|
||||
With `--rebase`, it runs 'git rebase' instead of 'git merge'.
|
||||
More precisely, `git pull` runs `git fetch` with the given parameters
|
||||
and then depending on configuration options or command line flags,
|
||||
will call either `git rebase` or `git merge` to reconcile diverging
|
||||
branches.
|
||||
|
||||
<repository> should be the name of a remote repository as
|
||||
passed to linkgit:git-fetch[1]. <refspec> can name an
|
||||
@ -132,7 +135,7 @@ published that history already. Do *not* use this option
|
||||
unless you have read linkgit:git-rebase[1] carefully.
|
||||
|
||||
--no-rebase::
|
||||
Override earlier --rebase.
|
||||
This is shorthand for --rebase=false.
|
||||
|
||||
Options related to fetching
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -2,6 +2,9 @@
|
||||
--no-commit::
|
||||
Perform the merge and commit the result. This option can
|
||||
be used to override --no-commit.
|
||||
ifdef::git-pull[]
|
||||
Only useful when merging.
|
||||
endif::git-pull[]
|
||||
+
|
||||
With --no-commit perform the merge and stop just before creating
|
||||
a merge commit, to give the user a chance to inspect and further
|
||||
@ -39,6 +42,7 @@ set to `no` at the beginning of them.
|
||||
to `MERGE_MSG` before being passed on to the commit machinery in the
|
||||
case of a merge conflict.
|
||||
|
||||
ifdef::git-merge[]
|
||||
--ff::
|
||||
--no-ff::
|
||||
--ff-only::
|
||||
@ -47,6 +51,22 @@ set to `no` at the beginning of them.
|
||||
default unless merging an annotated (and possibly signed) tag
|
||||
that is not stored in its natural place in the `refs/tags/`
|
||||
hierarchy, in which case `--no-ff` is assumed.
|
||||
endif::git-merge[]
|
||||
ifdef::git-pull[]
|
||||
--ff-only::
|
||||
Only update to the new history if there is no divergent local
|
||||
history. This is the default when no method for reconciling
|
||||
divergent histories is provided (via the --rebase=* flags).
|
||||
|
||||
--ff::
|
||||
--no-ff::
|
||||
When merging rather than rebasing, specifies how a merge is
|
||||
handled when the merged-in history is already a descendant of
|
||||
the current history. If merging is requested, `--ff` is the
|
||||
default unless merging an annotated (and possibly signed) tag
|
||||
that is not stored in its natural place in the `refs/tags/`
|
||||
hierarchy, in which case `--no-ff` is assumed.
|
||||
endif::git-pull[]
|
||||
+
|
||||
With `--ff`, when possible resolve the merge as a fast-forward (only
|
||||
update the branch pointer to match the merged branch; do not create a
|
||||
@ -55,9 +75,11 @@ descendant of the current history), create a merge commit.
|
||||
+
|
||||
With `--no-ff`, create a merge commit in all cases, even when the merge
|
||||
could instead be resolved as a fast-forward.
|
||||
ifdef::git-merge[]
|
||||
+
|
||||
With `--ff-only`, resolve the merge as a fast-forward when possible.
|
||||
When not possible, refuse to merge and exit with a non-zero status.
|
||||
endif::git-merge[]
|
||||
|
||||
-S[<keyid>]::
|
||||
--gpg-sign[=<keyid>]::
|
||||
@ -73,6 +95,9 @@ When not possible, refuse to merge and exit with a non-zero status.
|
||||
In addition to branch names, populate the log message with
|
||||
one-line descriptions from at most <n> actual commits that are being
|
||||
merged. See also linkgit:git-fmt-merge-msg[1].
|
||||
ifdef::git-pull[]
|
||||
Only useful when merging.
|
||||
endif::git-pull[]
|
||||
+
|
||||
With --no-log do not list one-line descriptions from the
|
||||
actual commits being merged.
|
||||
@ -102,10 +127,17 @@ With --no-squash perform the merge and commit the result. This
|
||||
option can be used to override --squash.
|
||||
+
|
||||
With --squash, --commit is not allowed, and will fail.
|
||||
ifdef::git-pull[]
|
||||
+
|
||||
Only useful when merging.
|
||||
endif::git-pull[]
|
||||
|
||||
--no-verify::
|
||||
This option bypasses the pre-merge and commit-msg hooks.
|
||||
See also linkgit:githooks[5].
|
||||
ifdef::git-pull[]
|
||||
Only useful when merging.
|
||||
endif::git-pull[]
|
||||
|
||||
-s <strategy>::
|
||||
--strategy=<strategy>::
|
||||
@ -127,6 +159,10 @@ With --squash, --commit is not allowed, and will fail.
|
||||
default trust model, this means the signing key has been signed by
|
||||
a trusted key. If the tip commit of the side branch is not signed
|
||||
with a valid key, the merge is aborted.
|
||||
ifdef::git-pull[]
|
||||
+
|
||||
Only useful when merging.
|
||||
endif::git-pull[]
|
||||
|
||||
--summary::
|
||||
--no-summary::
|
||||
@ -167,3 +203,7 @@ endif::git-pull[]
|
||||
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.
|
||||
ifdef::git-pull[]
|
||||
+
|
||||
Only useful when merging.
|
||||
endif::git-pull[]
|
||||
|
5
advice.c
5
advice.c
@ -286,6 +286,11 @@ void NORETURN die_conclude_merge(void)
|
||||
die(_("Exiting because of unfinished merge."));
|
||||
}
|
||||
|
||||
void NORETURN die_ff_impossible(void)
|
||||
{
|
||||
die(_("Not possible to fast-forward, aborting."));
|
||||
}
|
||||
|
||||
void advise_on_updating_sparse_paths(struct string_list *pathspec_list)
|
||||
{
|
||||
struct string_list_item *item;
|
||||
|
1
advice.h
1
advice.h
@ -96,6 +96,7 @@ void advise_if_enabled(enum advice_type type, const char *advice, ...);
|
||||
int error_resolve_conflict(const char *me);
|
||||
void NORETURN die_resolve_conflict(const char *me);
|
||||
void NORETURN die_conclude_merge(void);
|
||||
void NORETURN die_ff_impossible(void);
|
||||
void advise_on_updating_sparse_paths(struct string_list *pathspec_list);
|
||||
void detach_advice(const char *new_name);
|
||||
|
||||
|
@ -1622,7 +1622,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
|
||||
}
|
||||
|
||||
if (fast_forward == FF_ONLY)
|
||||
die(_("Not possible to fast-forward, aborting."));
|
||||
die_ff_impossible();
|
||||
|
||||
if (autostash)
|
||||
create_autostash(the_repository,
|
||||
|
@ -893,6 +893,8 @@ static int run_rebase(const struct object_id *newbase,
|
||||
strvec_pushv(&args, opt_strategy_opts.v);
|
||||
if (opt_gpg_sign)
|
||||
strvec_push(&args, opt_gpg_sign);
|
||||
if (opt_signoff)
|
||||
strvec_push(&args, opt_signoff);
|
||||
if (opt_autostash == 0)
|
||||
strvec_push(&args, "--no-autostash");
|
||||
else if (opt_autostash == 1)
|
||||
@ -911,12 +913,18 @@ static int run_rebase(const struct object_id *newbase,
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int get_can_ff(struct object_id *orig_head, struct object_id *orig_merge_head)
|
||||
static int get_can_ff(struct object_id *orig_head,
|
||||
struct oid_array *merge_heads)
|
||||
{
|
||||
int ret;
|
||||
struct commit_list *list = NULL;
|
||||
struct commit *merge_head, *head;
|
||||
struct object_id *orig_merge_head;
|
||||
|
||||
if (merge_heads->nr > 1)
|
||||
return 0;
|
||||
|
||||
orig_merge_head = &merge_heads->oid[0];
|
||||
head = lookup_commit_reference(the_repository, orig_head);
|
||||
commit_list_insert(head, &list);
|
||||
merge_head = lookup_commit_reference(the_repository, orig_merge_head);
|
||||
@ -927,9 +935,9 @@ static int get_can_ff(struct object_id *orig_head, struct object_id *orig_merge_
|
||||
|
||||
static void show_advice_pull_non_ff(void)
|
||||
{
|
||||
advise(_("Pulling without specifying how to reconcile divergent branches is\n"
|
||||
"discouraged. You can squelch this message by running one of the following\n"
|
||||
"commands sometime before your next pull:\n"
|
||||
advise(_("You have divergent branches and need to specify how to reconcile them.\n"
|
||||
"You can do so by running one of the following commands sometime before\n"
|
||||
"your next pull:\n"
|
||||
"\n"
|
||||
" git config pull.rebase false # merge (the default strategy)\n"
|
||||
" git config pull.rebase true # rebase\n"
|
||||
@ -966,8 +974,22 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
|
||||
|
||||
parse_repo_refspecs(argc, argv, &repo, &refspecs);
|
||||
|
||||
if (!opt_ff)
|
||||
if (!opt_ff) {
|
||||
opt_ff = xstrdup_or_null(config_get_ff());
|
||||
/*
|
||||
* A subtle point: opt_ff was set on the line above via
|
||||
* reading from config. opt_rebase, in contrast, is set
|
||||
* before this point via command line options. The setting
|
||||
* of opt_rebase via reading from config (using
|
||||
* config_get_rebase()) does not happen until later. We
|
||||
* are relying on the next if-condition happening before
|
||||
* the config_get_rebase() call so that an explicit
|
||||
* "--rebase" can override a config setting of
|
||||
* pull.ff=only.
|
||||
*/
|
||||
if (opt_rebase >= 0 && opt_ff && !strcmp(opt_ff, "--ff-only"))
|
||||
opt_ff = "--ff";
|
||||
}
|
||||
|
||||
if (opt_rebase < 0)
|
||||
opt_rebase = config_get_rebase(&rebase_unspecified);
|
||||
@ -1041,14 +1063,25 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
|
||||
die(_("Cannot merge multiple branches into empty head."));
|
||||
return pull_into_void(merge_heads.oid, &curr_head);
|
||||
}
|
||||
if (opt_rebase && merge_heads.nr > 1)
|
||||
die(_("Cannot rebase onto multiple branches."));
|
||||
if (merge_heads.nr > 1) {
|
||||
if (opt_rebase)
|
||||
die(_("Cannot rebase onto multiple branches."));
|
||||
if (opt_ff && !strcmp(opt_ff, "--ff-only"))
|
||||
die(_("Cannot fast-forward to multiple branches."));
|
||||
}
|
||||
|
||||
can_ff = get_can_ff(&orig_head, &merge_heads.oid[0]);
|
||||
can_ff = get_can_ff(&orig_head, &merge_heads);
|
||||
|
||||
if (rebase_unspecified && !opt_ff && !can_ff) {
|
||||
if (opt_verbosity >= 0)
|
||||
show_advice_pull_non_ff();
|
||||
/* ff-only takes precedence over rebase */
|
||||
if (opt_ff && !strcmp(opt_ff, "--ff-only")) {
|
||||
if (!can_ff)
|
||||
die_ff_impossible();
|
||||
opt_rebase = REBASE_FALSE;
|
||||
}
|
||||
/* If no action specified and we can't fast forward, then warn. */
|
||||
if (!opt_ff && rebase_unspecified && !can_ff) {
|
||||
show_advice_pull_non_ff();
|
||||
die(_("Need to specify how to reconcile divergent branches."));
|
||||
}
|
||||
|
||||
if (opt_rebase) {
|
||||
|
@ -65,7 +65,7 @@ test_expect_success setup '
|
||||
export GIT_AUTHOR_DATE GIT_COMMITTER_DATE &&
|
||||
|
||||
git checkout master &&
|
||||
git pull -s ours . side &&
|
||||
git pull -s ours --no-rebase . side &&
|
||||
|
||||
GIT_AUTHOR_DATE="2006-06-26 00:05:00 +0000" &&
|
||||
GIT_COMMITTER_DATE="2006-06-26 00:05:00 +0000" &&
|
||||
|
@ -136,12 +136,12 @@ test_expect_success 'the default remote . should not break explicit pull' '
|
||||
git reset --hard HEAD^ &&
|
||||
echo file >expect &&
|
||||
test_cmp expect file &&
|
||||
git pull . second &&
|
||||
git pull --no-rebase . second &&
|
||||
echo modified >expect &&
|
||||
test_cmp expect file &&
|
||||
git reflog -1 >reflog.actual &&
|
||||
sed "s/^[0-9a-f][0-9a-f]*/OBJID/" reflog.actual >reflog.fuzzy &&
|
||||
echo "OBJID HEAD@{0}: pull . second: Fast-forward" >reflog.expected &&
|
||||
echo "OBJID HEAD@{0}: pull --no-rebase . second: Fast-forward" >reflog.expected &&
|
||||
test_cmp reflog.expected reflog.fuzzy
|
||||
'
|
||||
|
||||
@ -226,7 +226,7 @@ test_expect_success 'fail if the index has unresolved entries' '
|
||||
test_commit modified2 file &&
|
||||
git ls-files -u >unmerged &&
|
||||
test_must_be_empty unmerged &&
|
||||
test_must_fail git pull . second &&
|
||||
test_must_fail git pull --no-rebase . second &&
|
||||
git ls-files -u >unmerged &&
|
||||
test_file_not_empty unmerged &&
|
||||
cp file expected &&
|
||||
@ -409,37 +409,37 @@ test_expect_success 'pull --rebase --no-autostash & rebase.autostash unset' '
|
||||
|
||||
test_expect_success 'pull succeeds with dirty working directory and merge.autostash set' '
|
||||
test_config merge.autostash true &&
|
||||
test_pull_autostash 2
|
||||
test_pull_autostash 2 --no-rebase
|
||||
'
|
||||
|
||||
test_expect_success 'pull --autostash & merge.autostash=true' '
|
||||
test_config merge.autostash true &&
|
||||
test_pull_autostash 2 --autostash
|
||||
test_pull_autostash 2 --autostash --no-rebase
|
||||
'
|
||||
|
||||
test_expect_success 'pull --autostash & merge.autostash=false' '
|
||||
test_config merge.autostash false &&
|
||||
test_pull_autostash 2 --autostash
|
||||
test_pull_autostash 2 --autostash --no-rebase
|
||||
'
|
||||
|
||||
test_expect_success 'pull --autostash & merge.autostash unset' '
|
||||
test_unconfig merge.autostash &&
|
||||
test_pull_autostash 2 --autostash
|
||||
test_pull_autostash 2 --autostash --no-rebase
|
||||
'
|
||||
|
||||
test_expect_success 'pull --no-autostash & merge.autostash=true' '
|
||||
test_config merge.autostash true &&
|
||||
test_pull_autostash_fail --no-autostash
|
||||
test_pull_autostash_fail --no-autostash --no-rebase
|
||||
'
|
||||
|
||||
test_expect_success 'pull --no-autostash & merge.autostash=false' '
|
||||
test_config merge.autostash false &&
|
||||
test_pull_autostash_fail --no-autostash
|
||||
test_pull_autostash_fail --no-autostash --no-rebase
|
||||
'
|
||||
|
||||
test_expect_success 'pull --no-autostash & merge.autostash unset' '
|
||||
test_unconfig merge.autostash &&
|
||||
test_pull_autostash_fail --no-autostash
|
||||
test_pull_autostash_fail --no-autostash --no-rebase
|
||||
'
|
||||
|
||||
test_expect_success 'pull.rebase' '
|
||||
|
@ -113,7 +113,7 @@ test_expect_success 'git pull --force' '
|
||||
git pull two &&
|
||||
test_commit A &&
|
||||
git branch -f origin &&
|
||||
git pull --all --force
|
||||
git pull --no-rebase --all --force
|
||||
)
|
||||
'
|
||||
|
||||
@ -179,7 +179,7 @@ test_expect_success 'git pull --allow-unrelated-histories' '
|
||||
(
|
||||
cd dst &&
|
||||
test_must_fail git pull ../src side &&
|
||||
git pull --allow-unrelated-histories ../src side
|
||||
git pull --no-rebase --allow-unrelated-histories ../src side
|
||||
)
|
||||
'
|
||||
|
||||
|
@ -28,7 +28,7 @@ test_expect_success setup '
|
||||
test_expect_success pull '
|
||||
(
|
||||
cd cloned &&
|
||||
git pull --log &&
|
||||
git pull --no-rebase --log &&
|
||||
git log -2 &&
|
||||
git cat-file commit HEAD >result &&
|
||||
grep Dollar result
|
||||
@ -41,7 +41,7 @@ test_expect_success '--log=1 limits shortlog length' '
|
||||
git reset --hard HEAD^ &&
|
||||
test "$(cat afile)" = original &&
|
||||
test "$(cat bfile)" = added &&
|
||||
git pull --log=1 &&
|
||||
git pull --no-rebase --log=1 &&
|
||||
git log -3 &&
|
||||
git cat-file commit HEAD >result &&
|
||||
grep Dollar result &&
|
||||
|
@ -108,27 +108,27 @@ test_expect_success 'setup commit on main and other pull' '
|
||||
|
||||
test_expect_success 'pull --set-upstream upstream main sets branch main but not other' '
|
||||
clear_config main other &&
|
||||
git pull --set-upstream upstream main &&
|
||||
git pull --no-rebase --set-upstream upstream main &&
|
||||
check_config main upstream refs/heads/main &&
|
||||
check_config_missing other
|
||||
'
|
||||
|
||||
test_expect_success 'pull --set-upstream main:other2 does not set the branch other2' '
|
||||
clear_config other2 &&
|
||||
git pull --set-upstream upstream main:other2 &&
|
||||
git pull --no-rebase --set-upstream upstream main:other2 &&
|
||||
check_config_missing other2
|
||||
'
|
||||
|
||||
test_expect_success 'pull --set-upstream upstream other sets branch main' '
|
||||
clear_config main other &&
|
||||
git pull --set-upstream upstream other &&
|
||||
git pull --no-rebase --set-upstream upstream other &&
|
||||
check_config main upstream refs/heads/other &&
|
||||
check_config_missing other
|
||||
'
|
||||
|
||||
test_expect_success 'pull --set-upstream upstream tag does not set the tag' '
|
||||
clear_config three &&
|
||||
git pull --tags --set-upstream upstream three &&
|
||||
git pull --no-rebase --tags --set-upstream upstream three &&
|
||||
check_config_missing three
|
||||
'
|
||||
|
||||
@ -144,16 +144,16 @@ test_expect_success 'pull --set-upstream http://nosuchdomain.example.com fails w
|
||||
|
||||
test_expect_success 'pull --set-upstream upstream HEAD sets branch HEAD' '
|
||||
clear_config main other &&
|
||||
git pull --set-upstream upstream HEAD &&
|
||||
git pull --no-rebase --set-upstream upstream HEAD &&
|
||||
check_config main upstream HEAD &&
|
||||
git checkout other &&
|
||||
git pull --set-upstream upstream HEAD &&
|
||||
git pull --no-rebase --set-upstream upstream HEAD &&
|
||||
check_config other upstream HEAD
|
||||
'
|
||||
|
||||
test_expect_success 'pull --set-upstream upstream with more than one branch does nothing' '
|
||||
clear_config main three &&
|
||||
git pull --set-upstream upstream main three &&
|
||||
git pull --no-rebase --set-upstream upstream main three &&
|
||||
check_config_missing main &&
|
||||
check_config_missing three
|
||||
'
|
||||
|
@ -87,7 +87,7 @@ test_expect_success 'updating origin' '
|
||||
'
|
||||
|
||||
test_expect_success 'pulling changes from origin' '
|
||||
git -C C pull origin
|
||||
git -C C pull --no-rebase origin
|
||||
'
|
||||
|
||||
# the 2 local objects are commit and tree from the merge
|
||||
@ -96,7 +96,7 @@ test_expect_success 'that alternate to origin gets used' '
|
||||
'
|
||||
|
||||
test_expect_success 'pulling changes from origin' '
|
||||
git -C D pull origin
|
||||
git -C D pull --no-rebase origin
|
||||
'
|
||||
|
||||
# the 5 local objects are expected; file3 blob, commit in A to add it
|
||||
|
@ -103,7 +103,7 @@ test_expect_success 'setup' '
|
||||
test_expect_success 'pull renaming branch into unrenaming one' \
|
||||
'
|
||||
git show-branch &&
|
||||
test_expect_code 1 git pull . white &&
|
||||
test_expect_code 1 git pull --no-rebase . white &&
|
||||
git ls-files -s &&
|
||||
test_stdout_line_count = 3 git ls-files -u B &&
|
||||
test_stdout_line_count = 1 git ls-files -s N &&
|
||||
@ -119,7 +119,7 @@ test_expect_success 'pull renaming branch into another renaming one' \
|
||||
rm -f B &&
|
||||
git reset --hard &&
|
||||
git checkout red &&
|
||||
test_expect_code 1 git pull . white &&
|
||||
test_expect_code 1 git pull --no-rebase . white &&
|
||||
test_stdout_line_count = 3 git ls-files -u B &&
|
||||
test_stdout_line_count = 1 git ls-files -s N &&
|
||||
sed -ne "/^g/{
|
||||
@ -133,7 +133,7 @@ test_expect_success 'pull unrenaming branch into renaming one' \
|
||||
'
|
||||
git reset --hard &&
|
||||
git show-branch &&
|
||||
test_expect_code 1 git pull . main &&
|
||||
test_expect_code 1 git pull --no-rebase . main &&
|
||||
test_stdout_line_count = 3 git ls-files -u B &&
|
||||
test_stdout_line_count = 1 git ls-files -s N &&
|
||||
sed -ne "/^g/{
|
||||
@ -147,7 +147,7 @@ test_expect_success 'pull conflicting renames' \
|
||||
'
|
||||
git reset --hard &&
|
||||
git show-branch &&
|
||||
test_expect_code 1 git pull . blue &&
|
||||
test_expect_code 1 git pull --no-rebase . blue &&
|
||||
test_stdout_line_count = 1 git ls-files -u A &&
|
||||
test_stdout_line_count = 1 git ls-files -u B &&
|
||||
test_stdout_line_count = 1 git ls-files -u C &&
|
||||
@ -163,7 +163,7 @@ test_expect_success 'interference with untracked working tree file' '
|
||||
git reset --hard &&
|
||||
git show-branch &&
|
||||
echo >A this file should not matter &&
|
||||
test_expect_code 1 git pull . white &&
|
||||
test_expect_code 1 git pull --no-rebase . white &&
|
||||
test_path_is_file A
|
||||
'
|
||||
|
||||
@ -173,7 +173,7 @@ test_expect_success 'interference with untracked working tree file' '
|
||||
git show-branch &&
|
||||
rm -f A &&
|
||||
echo >A this file should not matter &&
|
||||
test_expect_code 1 git pull . red &&
|
||||
test_expect_code 1 git pull --no-rebase . red &&
|
||||
test_path_is_file A
|
||||
'
|
||||
|
||||
@ -183,7 +183,7 @@ test_expect_success 'interference with untracked working tree file' '
|
||||
git checkout -f main &&
|
||||
git tag -f anchor &&
|
||||
git show-branch &&
|
||||
git pull . yellow &&
|
||||
git pull --no-rebase . yellow &&
|
||||
test_path_is_missing M &&
|
||||
git reset --hard anchor
|
||||
'
|
||||
@ -210,7 +210,7 @@ test_expect_success 'updated working tree file should prevent the merge' '
|
||||
echo >>M one line addition &&
|
||||
cat M >M.saved &&
|
||||
git update-index M &&
|
||||
test_expect_code 128 git pull . yellow &&
|
||||
test_expect_code 128 git pull --no-rebase . yellow &&
|
||||
test_cmp M M.saved &&
|
||||
rm -f M.saved
|
||||
'
|
||||
@ -222,7 +222,7 @@ test_expect_success 'interference with untracked working tree file' '
|
||||
git tag -f anchor &&
|
||||
git show-branch &&
|
||||
echo >M this file should not matter &&
|
||||
git pull . main &&
|
||||
git pull --no-rebase . main &&
|
||||
test_path_is_file M &&
|
||||
! {
|
||||
git ls-files -s |
|
||||
|
@ -100,7 +100,7 @@ test_expect_success 'merge update' '
|
||||
git checkout -b topic_2 &&
|
||||
git commit -m "update git-gui" &&
|
||||
cd ../git &&
|
||||
git pull -s subtree gui topic_2 &&
|
||||
git pull --no-rebase -s subtree gui topic_2 &&
|
||||
git ls-files -s >actual &&
|
||||
(
|
||||
echo "100644 $o3 0 git-gui/git-gui.sh" &&
|
||||
@ -129,7 +129,7 @@ test_expect_success 'initial ambiguous subtree' '
|
||||
test_expect_success 'merge using explicit' '
|
||||
cd ../git &&
|
||||
git reset --hard topic_2 &&
|
||||
git pull -Xsubtree=git-gui gui topic_2 &&
|
||||
git pull --no-rebase -Xsubtree=git-gui gui topic_2 &&
|
||||
git ls-files -s >actual &&
|
||||
(
|
||||
echo "100644 $o3 0 git-gui/git-gui.sh" &&
|
||||
@ -142,7 +142,7 @@ test_expect_success 'merge using explicit' '
|
||||
test_expect_success 'merge2 using explicit' '
|
||||
cd ../git &&
|
||||
git reset --hard topic_2 &&
|
||||
git pull -Xsubtree=git-gui2 gui topic_2 &&
|
||||
git pull --no-rebase -Xsubtree=git-gui2 gui topic_2 &&
|
||||
git ls-files -s >actual &&
|
||||
(
|
||||
echo "100644 $o1 0 git-gui/git-gui.sh" &&
|
||||
|
@ -69,11 +69,11 @@ test_expect_success 'binary file with -Xours/-Xtheirs' '
|
||||
'
|
||||
|
||||
test_expect_success 'pull passes -X to underlying merge' '
|
||||
git reset --hard main && git pull -s recursive -Xours . side &&
|
||||
git reset --hard main && git pull -s recursive -X ours . side &&
|
||||
git reset --hard main && git pull -s recursive -Xtheirs . side &&
|
||||
git reset --hard main && git pull -s recursive -X theirs . side &&
|
||||
git reset --hard main && test_must_fail git pull -s recursive -X bork . side
|
||||
git reset --hard main && git pull --no-rebase -s recursive -Xours . side &&
|
||||
git reset --hard main && git pull --no-rebase -s recursive -X ours . side &&
|
||||
git reset --hard main && git pull --no-rebase -s recursive -Xtheirs . side &&
|
||||
git reset --hard main && git pull --no-rebase -s recursive -X theirs . side &&
|
||||
git reset --hard main && test_must_fail git pull --no-rebase -s recursive -X bork . side
|
||||
'
|
||||
|
||||
test_expect_success SYMLINKS 'symlink with -Xours/-Xtheirs' '
|
||||
|
@ -27,120 +27,324 @@ test_expect_success 'setup' '
|
||||
git tag c3
|
||||
'
|
||||
|
||||
test_expect_success 'pull.rebase not set' '
|
||||
test_expect_success 'pull.rebase not set, ff possible' '
|
||||
git reset --hard c0 &&
|
||||
git pull . c1 2>err &&
|
||||
test_i18ngrep ! "Pulling without specifying how to reconcile" err
|
||||
test_i18ngrep ! "You have divergent branches" err
|
||||
'
|
||||
|
||||
test_expect_success 'pull.rebase not set and pull.ff=true' '
|
||||
git reset --hard c0 &&
|
||||
test_config pull.ff true &&
|
||||
git pull . c1 2>err &&
|
||||
test_i18ngrep ! "Pulling without specifying how to reconcile" err
|
||||
test_i18ngrep ! "You have divergent branches" err
|
||||
'
|
||||
|
||||
test_expect_success 'pull.rebase not set and pull.ff=false' '
|
||||
git reset --hard c0 &&
|
||||
test_config pull.ff false &&
|
||||
git pull . c1 2>err &&
|
||||
test_i18ngrep ! "Pulling without specifying how to reconcile" err
|
||||
test_i18ngrep ! "You have divergent branches" err
|
||||
'
|
||||
|
||||
test_expect_success 'pull.rebase not set and pull.ff=only' '
|
||||
git reset --hard c0 &&
|
||||
test_config pull.ff only &&
|
||||
git pull . c1 2>err &&
|
||||
test_i18ngrep ! "Pulling without specifying how to reconcile" err
|
||||
test_i18ngrep ! "You have divergent branches" err
|
||||
'
|
||||
|
||||
test_expect_success 'pull.rebase not set and --rebase given' '
|
||||
git reset --hard c0 &&
|
||||
git pull --rebase . c1 2>err &&
|
||||
test_i18ngrep ! "Pulling without specifying how to reconcile" err
|
||||
test_i18ngrep ! "You have divergent branches" err
|
||||
'
|
||||
|
||||
test_expect_success 'pull.rebase not set and --no-rebase given' '
|
||||
git reset --hard c0 &&
|
||||
git pull --no-rebase . c1 2>err &&
|
||||
test_i18ngrep ! "Pulling without specifying how to reconcile" err
|
||||
test_i18ngrep ! "You have divergent branches" err
|
||||
'
|
||||
|
||||
test_expect_success 'pull.rebase not set and --ff given' '
|
||||
git reset --hard c0 &&
|
||||
git pull --ff . c1 2>err &&
|
||||
test_i18ngrep ! "Pulling without specifying how to reconcile" err
|
||||
test_i18ngrep ! "You have divergent branches" err
|
||||
'
|
||||
|
||||
test_expect_success 'pull.rebase not set and --no-ff given' '
|
||||
git reset --hard c0 &&
|
||||
git pull --no-ff . c1 2>err &&
|
||||
test_i18ngrep ! "Pulling without specifying how to reconcile" err
|
||||
test_i18ngrep ! "You have divergent branches" err
|
||||
'
|
||||
|
||||
test_expect_success 'pull.rebase not set and --ff-only given' '
|
||||
git reset --hard c0 &&
|
||||
git pull --ff-only . c1 2>err &&
|
||||
test_i18ngrep ! "Pulling without specifying how to reconcile" err
|
||||
test_i18ngrep ! "You have divergent branches" err
|
||||
'
|
||||
|
||||
test_expect_success 'pull.rebase not set (not-fast-forward)' '
|
||||
git reset --hard c2 &&
|
||||
git -c color.advice=always pull . c1 2>err &&
|
||||
test_must_fail git -c color.advice=always pull . c1 2>err &&
|
||||
test_decode_color <err >decoded &&
|
||||
test_i18ngrep "<YELLOW>hint: " decoded &&
|
||||
test_i18ngrep "Pulling without specifying how to reconcile" decoded
|
||||
test_i18ngrep "You have divergent branches" decoded
|
||||
'
|
||||
|
||||
test_expect_success 'pull.rebase not set and pull.ff=true (not-fast-forward)' '
|
||||
git reset --hard c2 &&
|
||||
test_config pull.ff true &&
|
||||
git pull . c1 2>err &&
|
||||
test_i18ngrep ! "Pulling without specifying how to reconcile" err
|
||||
test_i18ngrep ! "You have divergent branches" err
|
||||
'
|
||||
|
||||
test_expect_success 'pull.rebase not set and pull.ff=false (not-fast-forward)' '
|
||||
git reset --hard c2 &&
|
||||
test_config pull.ff false &&
|
||||
git pull . c1 2>err &&
|
||||
test_i18ngrep ! "Pulling without specifying how to reconcile" err
|
||||
test_i18ngrep ! "You have divergent branches" err
|
||||
'
|
||||
|
||||
test_expect_success 'pull.rebase not set and pull.ff=only (not-fast-forward)' '
|
||||
git reset --hard c2 &&
|
||||
test_config pull.ff only &&
|
||||
test_must_fail git pull . c1 2>err &&
|
||||
test_i18ngrep ! "Pulling without specifying how to reconcile" err
|
||||
test_i18ngrep ! "You have divergent branches" err
|
||||
'
|
||||
|
||||
test_expect_success 'pull.rebase not set and --rebase given (not-fast-forward)' '
|
||||
git reset --hard c2 &&
|
||||
git pull --rebase . c1 2>err &&
|
||||
test_i18ngrep ! "Pulling without specifying how to reconcile" err
|
||||
test_i18ngrep ! "You have divergent branches" err
|
||||
'
|
||||
|
||||
test_expect_success 'pull.rebase not set and --no-rebase given (not-fast-forward)' '
|
||||
git reset --hard c2 &&
|
||||
git pull --no-rebase . c1 2>err &&
|
||||
test_i18ngrep ! "Pulling without specifying how to reconcile" err
|
||||
test_i18ngrep ! "You have divergent branches" err
|
||||
'
|
||||
|
||||
test_expect_success 'pull.rebase not set and --ff given (not-fast-forward)' '
|
||||
git reset --hard c2 &&
|
||||
git pull --ff . c1 2>err &&
|
||||
test_i18ngrep ! "Pulling without specifying how to reconcile" err
|
||||
test_i18ngrep ! "You have divergent branches" err
|
||||
'
|
||||
|
||||
test_expect_success 'pull.rebase not set and --no-ff given (not-fast-forward)' '
|
||||
git reset --hard c2 &&
|
||||
git pull --no-ff . c1 2>err &&
|
||||
test_i18ngrep ! "Pulling without specifying how to reconcile" err
|
||||
test_i18ngrep ! "You have divergent branches" err
|
||||
'
|
||||
|
||||
test_expect_success 'pull.rebase not set and --ff-only given (not-fast-forward)' '
|
||||
git reset --hard c2 &&
|
||||
test_must_fail git pull --ff-only . c1 2>err &&
|
||||
test_i18ngrep ! "Pulling without specifying how to reconcile" err
|
||||
test_i18ngrep ! "You have divergent branches" err
|
||||
'
|
||||
|
||||
test_does_rebase () {
|
||||
git reset --hard c2 &&
|
||||
git "$@" . c1 &&
|
||||
# Check that we actually did a rebase
|
||||
git rev-list --count HEAD >actual &&
|
||||
git rev-list --merges --count HEAD >>actual &&
|
||||
test_write_lines 3 0 >expect &&
|
||||
test_cmp expect actual &&
|
||||
rm actual expect
|
||||
}
|
||||
|
||||
# Prefers merge over fast-forward
|
||||
test_does_merge_when_ff_possible () {
|
||||
git reset --hard c0 &&
|
||||
git "$@" . c1 &&
|
||||
# Check that we actually did a merge
|
||||
git rev-list --count HEAD >actual &&
|
||||
git rev-list --merges --count HEAD >>actual &&
|
||||
test_write_lines 3 1 >expect &&
|
||||
test_cmp expect actual &&
|
||||
rm actual expect
|
||||
}
|
||||
|
||||
# Prefers fast-forward over merge or rebase
|
||||
test_does_fast_forward () {
|
||||
git reset --hard c0 &&
|
||||
git "$@" . c1 &&
|
||||
|
||||
# Check that we did not get any merges
|
||||
git rev-list --count HEAD >actual &&
|
||||
git rev-list --merges --count HEAD >>actual &&
|
||||
test_write_lines 2 0 >expect &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
# Check that we ended up at c1
|
||||
git rev-parse HEAD >actual &&
|
||||
git rev-parse c1^{commit} >expect &&
|
||||
test_cmp actual expect &&
|
||||
|
||||
# Remove temporary files
|
||||
rm actual expect
|
||||
}
|
||||
|
||||
# Doesn't fail when fast-forward not possible; does a merge
|
||||
test_falls_back_to_full_merge () {
|
||||
git reset --hard c2 &&
|
||||
git "$@" . c1 &&
|
||||
# Check that we actually did a merge
|
||||
git rev-list --count HEAD >actual &&
|
||||
git rev-list --merges --count HEAD >>actual &&
|
||||
test_write_lines 4 1 >expect &&
|
||||
test_cmp expect actual &&
|
||||
rm actual expect
|
||||
}
|
||||
|
||||
# Attempts fast forward, which is impossible, and bails
|
||||
test_attempts_fast_forward () {
|
||||
git reset --hard c2 &&
|
||||
test_must_fail git "$@" . c1 2>err &&
|
||||
test_i18ngrep "Not possible to fast-forward, aborting" err
|
||||
}
|
||||
|
||||
#
|
||||
# Group 1: Interaction of --ff-only with --[no-]rebase
|
||||
# (And related interaction of pull.ff=only with pull.rebase)
|
||||
#
|
||||
test_expect_success '--ff-only overrides --rebase' '
|
||||
test_attempts_fast_forward pull --rebase --ff-only
|
||||
'
|
||||
|
||||
test_expect_success '--ff-only overrides --rebase even if first' '
|
||||
test_attempts_fast_forward pull --ff-only --rebase
|
||||
'
|
||||
|
||||
test_expect_success '--ff-only overrides --no-rebase' '
|
||||
test_attempts_fast_forward pull --ff-only --no-rebase
|
||||
'
|
||||
|
||||
test_expect_success 'pull.ff=only overrides pull.rebase=true' '
|
||||
test_attempts_fast_forward -c pull.ff=only -c pull.rebase=true pull
|
||||
'
|
||||
|
||||
test_expect_success 'pull.ff=only overrides pull.rebase=false' '
|
||||
test_attempts_fast_forward -c pull.ff=only -c pull.rebase=false pull
|
||||
'
|
||||
|
||||
# Group 2: --rebase=[!false] overrides --no-ff and --ff
|
||||
# (And related interaction of pull.rebase=!false and pull.ff=!only)
|
||||
test_expect_success '--rebase overrides --no-ff' '
|
||||
test_does_rebase pull --rebase --no-ff
|
||||
'
|
||||
|
||||
test_expect_success '--rebase overrides --ff' '
|
||||
test_does_rebase pull --rebase --ff
|
||||
'
|
||||
|
||||
test_expect_success '--rebase fast-forwards when possible' '
|
||||
test_does_fast_forward pull --rebase --ff
|
||||
'
|
||||
|
||||
test_expect_success 'pull.rebase=true overrides pull.ff=false' '
|
||||
test_does_rebase -c pull.rebase=true -c pull.ff=false pull
|
||||
'
|
||||
|
||||
test_expect_success 'pull.rebase=true overrides pull.ff=true' '
|
||||
test_does_rebase -c pull.rebase=true -c pull.ff=true pull
|
||||
'
|
||||
|
||||
# Group 3: command line flags take precedence over config
|
||||
test_expect_success '--ff-only takes precedence over pull.rebase=true' '
|
||||
test_attempts_fast_forward -c pull.rebase=true pull --ff-only
|
||||
'
|
||||
|
||||
test_expect_success '--ff-only takes precedence over pull.rebase=false' '
|
||||
test_attempts_fast_forward -c pull.rebase=false pull --ff-only
|
||||
'
|
||||
|
||||
test_expect_success '--no-rebase takes precedence over pull.ff=only' '
|
||||
test_falls_back_to_full_merge -c pull.ff=only pull --no-rebase
|
||||
'
|
||||
|
||||
test_expect_success '--rebase takes precedence over pull.ff=only' '
|
||||
test_does_rebase -c pull.ff=only pull --rebase
|
||||
'
|
||||
|
||||
test_expect_success '--rebase overrides pull.ff=true' '
|
||||
test_does_rebase -c pull.ff=true pull --rebase
|
||||
'
|
||||
|
||||
test_expect_success '--rebase overrides pull.ff=false' '
|
||||
test_does_rebase -c pull.ff=false pull --rebase
|
||||
'
|
||||
|
||||
test_expect_success '--rebase overrides pull.ff unset' '
|
||||
test_does_rebase pull --rebase
|
||||
'
|
||||
|
||||
# Group 4: --no-rebase heeds pull.ff=!only or explict --ff or --no-ff
|
||||
|
||||
test_expect_success '--no-rebase works with --no-ff' '
|
||||
test_does_merge_when_ff_possible pull --no-rebase --no-ff
|
||||
'
|
||||
|
||||
test_expect_success '--no-rebase works with --ff' '
|
||||
test_does_fast_forward pull --no-rebase --ff
|
||||
'
|
||||
|
||||
test_expect_success '--no-rebase does ff if pull.ff unset' '
|
||||
test_does_fast_forward pull --no-rebase
|
||||
'
|
||||
|
||||
test_expect_success '--no-rebase heeds pull.ff=true' '
|
||||
test_does_fast_forward -c pull.ff=true pull --no-rebase
|
||||
'
|
||||
|
||||
test_expect_success '--no-rebase heeds pull.ff=false' '
|
||||
test_does_merge_when_ff_possible -c pull.ff=false pull --no-rebase
|
||||
'
|
||||
|
||||
# Group 5: pull.rebase=!false in combination with --no-ff or --ff
|
||||
test_expect_success 'pull.rebase=true and --no-ff' '
|
||||
test_does_rebase -c pull.rebase=true pull --no-ff
|
||||
'
|
||||
|
||||
test_expect_success 'pull.rebase=true and --ff' '
|
||||
test_does_rebase -c pull.rebase=true pull --ff
|
||||
'
|
||||
|
||||
test_expect_success 'pull.rebase=false and --no-ff' '
|
||||
test_does_merge_when_ff_possible -c pull.rebase=false pull --no-ff
|
||||
'
|
||||
|
||||
test_expect_success 'pull.rebase=false and --ff, ff possible' '
|
||||
test_does_fast_forward -c pull.rebase=false pull --ff
|
||||
'
|
||||
|
||||
test_expect_success 'pull.rebase=false and --ff, ff not possible' '
|
||||
test_falls_back_to_full_merge -c pull.rebase=false pull --ff
|
||||
'
|
||||
|
||||
# End of groupings for conflicting merge vs. rebase flags/options
|
||||
|
||||
test_expect_success 'Multiple heads warns about inability to fast forward' '
|
||||
git reset --hard c1 &&
|
||||
test_must_fail git pull . c2 c3 2>err &&
|
||||
test_i18ngrep "You have divergent branches" err
|
||||
'
|
||||
|
||||
test_expect_success 'Multiple can never be fast forwarded' '
|
||||
git reset --hard c0 &&
|
||||
test_must_fail git -c pull.ff=only pull . c1 c2 c3 2>err &&
|
||||
test_i18ngrep ! "You have divergent branches" err &&
|
||||
# In addition to calling out "cannot fast-forward", we very much
|
||||
# want the "multiple branches" piece to be called out to users.
|
||||
test_i18ngrep "Cannot fast-forward to multiple branches" err
|
||||
'
|
||||
|
||||
test_expect_success 'Cannot rebase with multiple heads' '
|
||||
git reset --hard c0 &&
|
||||
test_must_fail git -c pull.rebase=true pull . c1 c2 c3 2>err &&
|
||||
test_i18ngrep ! "You have divergent branches" err &&
|
||||
test_i18ngrep "Cannot rebase onto multiple branches." err
|
||||
'
|
||||
|
||||
test_expect_success 'merge c1 with c2' '
|
||||
|
@ -68,7 +68,7 @@ test_expect_success 'merge c1 with c2, c3, c4, c5' '
|
||||
|
||||
test_expect_success 'pull c2, c3, c4, c5 into c1' '
|
||||
git reset --hard c1 &&
|
||||
git pull . c2 c3 c4 c5 &&
|
||||
git pull --no-rebase . c2 c3 c4 c5 &&
|
||||
test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
|
||||
test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
|
||||
test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
|
||||
|
Loading…
Reference in New Issue
Block a user