Merge branch 'en/pull-conflicting-options'

"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:
Junio C Hamano 2021-08-30 16:06:01 -07:00
Родитель c4203212e3 6f843a3355
Коммит 7d0daf3f12
18 изменённых файлов: 370 добавлений и 82 удалений

Просмотреть файл

@ -61,6 +61,8 @@ merge has resulted in conflicts.
OPTIONS OPTIONS
------- -------
:git-merge: 1
include::merge-options.txt[] include::merge-options.txt[]
-m <msg>:: -m <msg>::

Просмотреть файл

@ -15,14 +15,17 @@ SYNOPSIS
DESCRIPTION DESCRIPTION
----------- -----------
Incorporates changes from a remote repository into the current Incorporates changes from a remote repository into the current branch.
branch. In its default mode, `git pull` is shorthand for If the current branch is behind the remote, then by default it will
`git fetch` followed by `git merge FETCH_HEAD`. 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 More precisely, `git pull` runs `git fetch` with the given parameters
parameters and calls 'git merge' to merge the retrieved branch and then depending on configuration options or command line flags,
heads into the current branch. will call either `git rebase` or `git merge` to reconcile diverging
With `--rebase`, it runs 'git rebase' instead of 'git merge'. branches.
<repository> should be the name of a remote repository as <repository> should be the name of a remote repository as
passed to linkgit:git-fetch[1]. <refspec> can name an 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. unless you have read linkgit:git-rebase[1] carefully.
--no-rebase:: --no-rebase::
Override earlier --rebase. This is shorthand for --rebase=false.
Options related to fetching Options related to fetching
~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~

Просмотреть файл

@ -2,6 +2,9 @@
--no-commit:: --no-commit::
Perform the merge and commit the result. This option can Perform the merge and commit the result. This option can
be used to override --no-commit. 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 With --no-commit perform the merge and stop just before creating
a merge commit, to give the user a chance to inspect and further 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 to `MERGE_MSG` before being passed on to the commit machinery in the
case of a merge conflict. case of a merge conflict.
ifdef::git-merge[]
--ff:: --ff::
--no-ff:: --no-ff::
--ff-only:: --ff-only::
@ -47,6 +51,22 @@ set to `no` at the beginning of them.
default unless merging an annotated (and possibly signed) tag default unless merging an annotated (and possibly signed) tag
that is not stored in its natural place in the `refs/tags/` that is not stored in its natural place in the `refs/tags/`
hierarchy, in which case `--no-ff` is assumed. 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 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 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 With `--no-ff`, create a merge commit in all cases, even when the merge
could instead be resolved as a fast-forward. could instead be resolved as a fast-forward.
ifdef::git-merge[]
+ +
With `--ff-only`, resolve the merge as a fast-forward when possible. 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. When not possible, refuse to merge and exit with a non-zero status.
endif::git-merge[]
-S[<keyid>]:: -S[<keyid>]::
--gpg-sign[=<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 In addition to branch names, populate the log message with
one-line descriptions from at most <n> actual commits that are being one-line descriptions from at most <n> actual commits that are being
merged. See also linkgit:git-fmt-merge-msg[1]. 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 With --no-log do not list one-line descriptions from the
actual commits being merged. 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. option can be used to override --squash.
+ +
With --squash, --commit is not allowed, and will fail. With --squash, --commit is not allowed, and will fail.
ifdef::git-pull[]
+
Only useful when merging.
endif::git-pull[]
--no-verify:: --no-verify::
This option bypasses the pre-merge and commit-msg hooks. This option bypasses the pre-merge and commit-msg hooks.
See also linkgit:githooks[5]. See also linkgit:githooks[5].
ifdef::git-pull[]
Only useful when merging.
endif::git-pull[]
-s <strategy>:: -s <strategy>::
--strategy=<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 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 a trusted key. If the tip commit of the side branch is not signed
with a valid key, the merge is aborted. with a valid key, the merge is aborted.
ifdef::git-pull[]
+
Only useful when merging.
endif::git-pull[]
--summary:: --summary::
--no-summary:: --no-summary::
@ -167,3 +203,7 @@ endif::git-pull[]
projects that started their lives independently. As that is projects that started their lives independently. As that is
a very rare occasion, no configuration variable to enable a very rare occasion, no configuration variable to enable
this by default exists and will not be added. this by default exists and will not be added.
ifdef::git-pull[]
+
Only useful when merging.
endif::git-pull[]

Просмотреть файл

@ -286,6 +286,11 @@ void NORETURN die_conclude_merge(void)
die(_("Exiting because of unfinished merge.")); 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) void advise_on_updating_sparse_paths(struct string_list *pathspec_list)
{ {
struct string_list_item *item; struct string_list_item *item;

Просмотреть файл

@ -96,6 +96,7 @@ void advise_if_enabled(enum advice_type type, const char *advice, ...);
int error_resolve_conflict(const char *me); int error_resolve_conflict(const char *me);
void NORETURN die_resolve_conflict(const char *me); void NORETURN die_resolve_conflict(const char *me);
void NORETURN die_conclude_merge(void); void NORETURN die_conclude_merge(void);
void NORETURN die_ff_impossible(void);
void advise_on_updating_sparse_paths(struct string_list *pathspec_list); void advise_on_updating_sparse_paths(struct string_list *pathspec_list);
void detach_advice(const char *new_name); 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) if (fast_forward == FF_ONLY)
die(_("Not possible to fast-forward, aborting.")); die_ff_impossible();
if (autostash) if (autostash)
create_autostash(the_repository, create_autostash(the_repository,

Просмотреть файл

@ -893,6 +893,8 @@ static int run_rebase(const struct object_id *newbase,
strvec_pushv(&args, opt_strategy_opts.v); strvec_pushv(&args, opt_strategy_opts.v);
if (opt_gpg_sign) if (opt_gpg_sign)
strvec_push(&args, opt_gpg_sign); strvec_push(&args, opt_gpg_sign);
if (opt_signoff)
strvec_push(&args, opt_signoff);
if (opt_autostash == 0) if (opt_autostash == 0)
strvec_push(&args, "--no-autostash"); strvec_push(&args, "--no-autostash");
else if (opt_autostash == 1) else if (opt_autostash == 1)
@ -911,12 +913,18 @@ static int run_rebase(const struct object_id *newbase,
return ret; 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; int ret;
struct commit_list *list = NULL; struct commit_list *list = NULL;
struct commit *merge_head, *head; 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); head = lookup_commit_reference(the_repository, orig_head);
commit_list_insert(head, &list); commit_list_insert(head, &list);
merge_head = lookup_commit_reference(the_repository, orig_merge_head); 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) static void show_advice_pull_non_ff(void)
{ {
advise(_("Pulling without specifying how to reconcile divergent branches is\n" advise(_("You have divergent branches and need to specify how to reconcile them.\n"
"discouraged. You can squelch this message by running one of the following\n" "You can do so by running one of the following commands sometime before\n"
"commands sometime before your next pull:\n" "your next pull:\n"
"\n" "\n"
" git config pull.rebase false # merge (the default strategy)\n" " git config pull.rebase false # merge (the default strategy)\n"
" git config pull.rebase true # rebase\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); parse_repo_refspecs(argc, argv, &repo, &refspecs);
if (!opt_ff) if (!opt_ff) {
opt_ff = xstrdup_or_null(config_get_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) if (opt_rebase < 0)
opt_rebase = config_get_rebase(&rebase_unspecified); 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.")); die(_("Cannot merge multiple branches into empty head."));
return pull_into_void(merge_heads.oid, &curr_head); return pull_into_void(merge_heads.oid, &curr_head);
} }
if (opt_rebase && merge_heads.nr > 1) if (merge_heads.nr > 1) {
die(_("Cannot rebase onto multiple branches.")); 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) { /* ff-only takes precedence over rebase */
if (opt_verbosity >= 0) if (opt_ff && !strcmp(opt_ff, "--ff-only")) {
show_advice_pull_non_ff(); 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) { if (opt_rebase) {

Просмотреть файл

@ -65,7 +65,7 @@ test_expect_success setup '
export GIT_AUTHOR_DATE GIT_COMMITTER_DATE && export GIT_AUTHOR_DATE GIT_COMMITTER_DATE &&
git checkout master && 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_AUTHOR_DATE="2006-06-26 00:05:00 +0000" &&
GIT_COMMITTER_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^ && git reset --hard HEAD^ &&
echo file >expect && echo file >expect &&
test_cmp expect file && test_cmp expect file &&
git pull . second && git pull --no-rebase . second &&
echo modified >expect && echo modified >expect &&
test_cmp expect file && test_cmp expect file &&
git reflog -1 >reflog.actual && git reflog -1 >reflog.actual &&
sed "s/^[0-9a-f][0-9a-f]*/OBJID/" reflog.actual >reflog.fuzzy && 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 test_cmp reflog.expected reflog.fuzzy
' '
@ -226,7 +226,7 @@ test_expect_success 'fail if the index has unresolved entries' '
test_commit modified2 file && test_commit modified2 file &&
git ls-files -u >unmerged && git ls-files -u >unmerged &&
test_must_be_empty 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 && git ls-files -u >unmerged &&
test_file_not_empty unmerged && test_file_not_empty unmerged &&
cp file expected && 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_expect_success 'pull succeeds with dirty working directory and merge.autostash set' '
test_config merge.autostash true && test_config merge.autostash true &&
test_pull_autostash 2 test_pull_autostash 2 --no-rebase
' '
test_expect_success 'pull --autostash & merge.autostash=true' ' test_expect_success 'pull --autostash & merge.autostash=true' '
test_config 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_expect_success 'pull --autostash & merge.autostash=false' '
test_config 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_expect_success 'pull --autostash & merge.autostash unset' '
test_unconfig merge.autostash && 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_expect_success 'pull --no-autostash & merge.autostash=true' '
test_config 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_expect_success 'pull --no-autostash & merge.autostash=false' '
test_config 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_expect_success 'pull --no-autostash & merge.autostash unset' '
test_unconfig merge.autostash && test_unconfig merge.autostash &&
test_pull_autostash_fail --no-autostash test_pull_autostash_fail --no-autostash --no-rebase
' '
test_expect_success 'pull.rebase' ' test_expect_success 'pull.rebase' '

Просмотреть файл

@ -113,7 +113,7 @@ test_expect_success 'git pull --force' '
git pull two && git pull two &&
test_commit A && test_commit A &&
git branch -f origin && 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 && cd dst &&
test_must_fail git pull ../src side && 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 ' test_expect_success pull '
( (
cd cloned && cd cloned &&
git pull --log && git pull --no-rebase --log &&
git log -2 && git log -2 &&
git cat-file commit HEAD >result && git cat-file commit HEAD >result &&
grep Dollar result grep Dollar result
@ -41,7 +41,7 @@ test_expect_success '--log=1 limits shortlog length' '
git reset --hard HEAD^ && git reset --hard HEAD^ &&
test "$(cat afile)" = original && test "$(cat afile)" = original &&
test "$(cat bfile)" = added && test "$(cat bfile)" = added &&
git pull --log=1 && git pull --no-rebase --log=1 &&
git log -3 && git log -3 &&
git cat-file commit HEAD >result && git cat-file commit HEAD >result &&
grep Dollar 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' ' test_expect_success 'pull --set-upstream upstream main sets branch main but not other' '
clear_config main 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 main upstream refs/heads/main &&
check_config_missing other check_config_missing other
' '
test_expect_success 'pull --set-upstream main:other2 does not set the branch other2' ' test_expect_success 'pull --set-upstream main:other2 does not set the branch other2' '
clear_config other2 && clear_config other2 &&
git pull --set-upstream upstream main:other2 && git pull --no-rebase --set-upstream upstream main:other2 &&
check_config_missing other2 check_config_missing other2
' '
test_expect_success 'pull --set-upstream upstream other sets branch main' ' test_expect_success 'pull --set-upstream upstream other sets branch main' '
clear_config main other && 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 main upstream refs/heads/other &&
check_config_missing other check_config_missing other
' '
test_expect_success 'pull --set-upstream upstream tag does not set the tag' ' test_expect_success 'pull --set-upstream upstream tag does not set the tag' '
clear_config three && clear_config three &&
git pull --tags --set-upstream upstream three && git pull --no-rebase --tags --set-upstream upstream three &&
check_config_missing 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' ' test_expect_success 'pull --set-upstream upstream HEAD sets branch HEAD' '
clear_config main other && clear_config main other &&
git pull --set-upstream upstream HEAD && git pull --no-rebase --set-upstream upstream HEAD &&
check_config main upstream HEAD && check_config main upstream HEAD &&
git checkout other && git checkout other &&
git pull --set-upstream upstream HEAD && git pull --no-rebase --set-upstream upstream HEAD &&
check_config other upstream HEAD check_config other upstream HEAD
' '
test_expect_success 'pull --set-upstream upstream with more than one branch does nothing' ' test_expect_success 'pull --set-upstream upstream with more than one branch does nothing' '
clear_config main three && 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 main &&
check_config_missing three check_config_missing three
' '

Просмотреть файл

@ -87,7 +87,7 @@ test_expect_success 'updating origin' '
' '
test_expect_success 'pulling changes from 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 # 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' ' 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 # 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' \ test_expect_success 'pull renaming branch into unrenaming one' \
' '
git show-branch && git show-branch &&
test_expect_code 1 git pull . white && test_expect_code 1 git pull --no-rebase . white &&
git ls-files -s && git ls-files -s &&
test_stdout_line_count = 3 git ls-files -u B && test_stdout_line_count = 3 git ls-files -u B &&
test_stdout_line_count = 1 git ls-files -s N && 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 && rm -f B &&
git reset --hard && git reset --hard &&
git checkout red && 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 = 3 git ls-files -u B &&
test_stdout_line_count = 1 git ls-files -s N && test_stdout_line_count = 1 git ls-files -s N &&
sed -ne "/^g/{ sed -ne "/^g/{
@ -133,7 +133,7 @@ test_expect_success 'pull unrenaming branch into renaming one' \
' '
git reset --hard && git reset --hard &&
git show-branch && 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 = 3 git ls-files -u B &&
test_stdout_line_count = 1 git ls-files -s N && test_stdout_line_count = 1 git ls-files -s N &&
sed -ne "/^g/{ sed -ne "/^g/{
@ -147,7 +147,7 @@ test_expect_success 'pull conflicting renames' \
' '
git reset --hard && git reset --hard &&
git show-branch && 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 A &&
test_stdout_line_count = 1 git ls-files -u B && test_stdout_line_count = 1 git ls-files -u B &&
test_stdout_line_count = 1 git ls-files -u C && 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 reset --hard &&
git show-branch && git show-branch &&
echo >A this file should not matter && 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 test_path_is_file A
' '
@ -173,7 +173,7 @@ test_expect_success 'interference with untracked working tree file' '
git show-branch && git show-branch &&
rm -f A && rm -f A &&
echo >A this file should not matter && 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 test_path_is_file A
' '
@ -183,7 +183,7 @@ test_expect_success 'interference with untracked working tree file' '
git checkout -f main && git checkout -f main &&
git tag -f anchor && git tag -f anchor &&
git show-branch && git show-branch &&
git pull . yellow && git pull --no-rebase . yellow &&
test_path_is_missing M && test_path_is_missing M &&
git reset --hard anchor git reset --hard anchor
' '
@ -210,7 +210,7 @@ test_expect_success 'updated working tree file should prevent the merge' '
echo >>M one line addition && echo >>M one line addition &&
cat M >M.saved && cat M >M.saved &&
git update-index M && 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 && test_cmp M M.saved &&
rm -f M.saved rm -f M.saved
' '
@ -222,7 +222,7 @@ test_expect_success 'interference with untracked working tree file' '
git tag -f anchor && git tag -f anchor &&
git show-branch && git show-branch &&
echo >M this file should not matter && echo >M this file should not matter &&
git pull . main && git pull --no-rebase . main &&
test_path_is_file M && test_path_is_file M &&
! { ! {
git ls-files -s | git ls-files -s |

Просмотреть файл

@ -100,7 +100,7 @@ test_expect_success 'merge update' '
git checkout -b topic_2 && git checkout -b topic_2 &&
git commit -m "update git-gui" && git commit -m "update git-gui" &&
cd ../git && cd ../git &&
git pull -s subtree gui topic_2 && git pull --no-rebase -s subtree gui topic_2 &&
git ls-files -s >actual && git ls-files -s >actual &&
( (
echo "100644 $o3 0 git-gui/git-gui.sh" && 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' ' test_expect_success 'merge using explicit' '
cd ../git && cd ../git &&
git reset --hard topic_2 && 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 && git ls-files -s >actual &&
( (
echo "100644 $o3 0 git-gui/git-gui.sh" && 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' ' test_expect_success 'merge2 using explicit' '
cd ../git && cd ../git &&
git reset --hard topic_2 && 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 && git ls-files -s >actual &&
( (
echo "100644 $o1 0 git-gui/git-gui.sh" && 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' ' 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 --no-rebase -s recursive -Xours . side &&
git reset --hard main && git pull -s recursive -X ours . side && git reset --hard main && git pull --no-rebase -s recursive -X ours . side &&
git reset --hard main && git pull -s recursive -Xtheirs . side && git reset --hard main && git pull --no-rebase -s recursive -Xtheirs . side &&
git reset --hard main && git pull -s recursive -X theirs . side && git reset --hard main && git pull --no-rebase -s recursive -X theirs . side &&
git reset --hard main && test_must_fail git pull -s recursive -X bork . 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' ' test_expect_success SYMLINKS 'symlink with -Xours/-Xtheirs' '

Просмотреть файл

@ -27,120 +27,324 @@ test_expect_success 'setup' '
git tag c3 git tag c3
' '
test_expect_success 'pull.rebase not set' ' test_expect_success 'pull.rebase not set, ff possible' '
git reset --hard c0 && git reset --hard c0 &&
git pull . c1 2>err && 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' ' test_expect_success 'pull.rebase not set and pull.ff=true' '
git reset --hard c0 && git reset --hard c0 &&
test_config pull.ff true && test_config pull.ff true &&
git pull . c1 2>err && 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' ' test_expect_success 'pull.rebase not set and pull.ff=false' '
git reset --hard c0 && git reset --hard c0 &&
test_config pull.ff false && test_config pull.ff false &&
git pull . c1 2>err && 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' ' test_expect_success 'pull.rebase not set and pull.ff=only' '
git reset --hard c0 && git reset --hard c0 &&
test_config pull.ff only && test_config pull.ff only &&
git pull . c1 2>err && 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' ' test_expect_success 'pull.rebase not set and --rebase given' '
git reset --hard c0 && git reset --hard c0 &&
git pull --rebase . c1 2>err && 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' ' test_expect_success 'pull.rebase not set and --no-rebase given' '
git reset --hard c0 && git reset --hard c0 &&
git pull --no-rebase . c1 2>err && 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' ' test_expect_success 'pull.rebase not set and --ff given' '
git reset --hard c0 && git reset --hard c0 &&
git pull --ff . c1 2>err && 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' ' test_expect_success 'pull.rebase not set and --no-ff given' '
git reset --hard c0 && git reset --hard c0 &&
git pull --no-ff . c1 2>err && 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' ' test_expect_success 'pull.rebase not set and --ff-only given' '
git reset --hard c0 && git reset --hard c0 &&
git pull --ff-only . c1 2>err && 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)' ' test_expect_success 'pull.rebase not set (not-fast-forward)' '
git reset --hard c2 && 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_decode_color <err >decoded &&
test_i18ngrep "<YELLOW>hint: " 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)' ' test_expect_success 'pull.rebase not set and pull.ff=true (not-fast-forward)' '
git reset --hard c2 && git reset --hard c2 &&
test_config pull.ff true && test_config pull.ff true &&
git pull . c1 2>err && 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)' ' test_expect_success 'pull.rebase not set and pull.ff=false (not-fast-forward)' '
git reset --hard c2 && git reset --hard c2 &&
test_config pull.ff false && test_config pull.ff false &&
git pull . c1 2>err && 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)' ' test_expect_success 'pull.rebase not set and pull.ff=only (not-fast-forward)' '
git reset --hard c2 && git reset --hard c2 &&
test_config pull.ff only && test_config pull.ff only &&
test_must_fail git pull . c1 2>err && 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)' ' test_expect_success 'pull.rebase not set and --rebase given (not-fast-forward)' '
git reset --hard c2 && git reset --hard c2 &&
git pull --rebase . c1 2>err && 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)' ' test_expect_success 'pull.rebase not set and --no-rebase given (not-fast-forward)' '
git reset --hard c2 && git reset --hard c2 &&
git pull --no-rebase . c1 2>err && 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)' ' test_expect_success 'pull.rebase not set and --ff given (not-fast-forward)' '
git reset --hard c2 && git reset --hard c2 &&
git pull --ff . c1 2>err && 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)' ' test_expect_success 'pull.rebase not set and --no-ff given (not-fast-forward)' '
git reset --hard c2 && git reset --hard c2 &&
git pull --no-ff . c1 2>err && 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)' ' test_expect_success 'pull.rebase not set and --ff-only given (not-fast-forward)' '
git reset --hard c2 && git reset --hard c2 &&
test_must_fail git pull --ff-only . c1 2>err && 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' ' 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' ' test_expect_success 'pull c2, c3, c4, c5 into c1' '
git reset --hard 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)" &&
test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" && test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" && test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&