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 optionsmaint
						commit
						7d0daf3f12
					
				|  | @ -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
	
	 Junio C Hamano
						Junio C Hamano