Merge branch 'fc/pull-merge-rebase'

When a user does not tell "git pull" to use rebase or merge, the
command gives a loud message telling a user to choose between
rebase or merge but creates a merge anyway, forcing users who would
want to rebase to redo the operation.  Fix an early part of this
problem by tightening the condition to give the message---there is
no reason to stop or force the user to choose between rebase or
merge if the history fast-forwards.

* fc/pull-merge-rebase:
  pull: display default warning only when non-ff
  pull: correct condition to trigger non-ff advice
  pull: get rid of unnecessary global variable
  pull: give the advice for choosing rebase/merge much later
  pull: refactor fast-forward check
This commit is contained in:
Junio C Hamano 2021-01-06 23:33:44 -08:00
commit d3fa84d528
2 changed files with 104 additions and 32 deletions

View File

@ -324,7 +324,7 @@ static const char *config_get_ff(void)
* looks for the value of "pull.rebase". If both configuration keys do not * looks for the value of "pull.rebase". If both configuration keys do not
* exist, returns REBASE_FALSE. * exist, returns REBASE_FALSE.
*/ */
static enum rebase_type config_get_rebase(void) static enum rebase_type config_get_rebase(int *rebase_unspecified)
{ {
struct branch *curr_branch = branch_get("HEAD"); struct branch *curr_branch = branch_get("HEAD");
const char *value; const char *value;
@ -344,20 +344,7 @@ static enum rebase_type config_get_rebase(void)
if (!git_config_get_value("pull.rebase", &value)) if (!git_config_get_value("pull.rebase", &value))
return parse_config_rebase("pull.rebase", value, 1); return parse_config_rebase("pull.rebase", value, 1);
if (opt_verbosity >= 0 && !opt_ff) { *rebase_unspecified = 1;
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"
"\n"
" git config pull.rebase false # merge (the default strategy)\n"
" git config pull.rebase true # rebase\n"
" git config pull.ff only # fast-forward only\n"
"\n"
"You can replace \"git config\" with \"git config --global\" to set a default\n"
"preference for all repositories. You can also pass --rebase, --no-rebase,\n"
"or --ff-only on the command line to override the configured default per\n"
"invocation.\n"));
}
return REBASE_FALSE; return REBASE_FALSE;
} }
@ -924,6 +911,36 @@ 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)
{
int ret;
struct commit_list *list = NULL;
struct commit *merge_head, *head;
head = lookup_commit_reference(the_repository, orig_head);
commit_list_insert(head, &list);
merge_head = lookup_commit_reference(the_repository, orig_merge_head);
ret = repo_is_descendant_of(the_repository, merge_head, list);
free_commit_list(list);
return ret;
}
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"
"\n"
" git config pull.rebase false # merge (the default strategy)\n"
" git config pull.rebase true # rebase\n"
" git config pull.ff only # fast-forward only\n"
"\n"
"You can replace \"git config\" with \"git config --global\" to set a default\n"
"preference for all repositories. You can also pass --rebase, --no-rebase,\n"
"or --ff-only on the command line to override the configured default per\n"
"invocation.\n"));
}
int cmd_pull(int argc, const char **argv, const char *prefix) int cmd_pull(int argc, const char **argv, const char *prefix)
{ {
const char *repo, **refspecs; const char *repo, **refspecs;
@ -931,6 +948,8 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
struct object_id orig_head, curr_head; struct object_id orig_head, curr_head;
struct object_id rebase_fork_point; struct object_id rebase_fork_point;
int autostash; int autostash;
int rebase_unspecified = 0;
int can_ff;
if (!getenv("GIT_REFLOG_ACTION")) if (!getenv("GIT_REFLOG_ACTION"))
set_reflog_message(argc, argv); set_reflog_message(argc, argv);
@ -952,7 +971,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
opt_ff = xstrdup_or_null(config_get_ff()); opt_ff = xstrdup_or_null(config_get_ff());
if (opt_rebase < 0) if (opt_rebase < 0)
opt_rebase = config_get_rebase(); opt_rebase = config_get_rebase(&rebase_unspecified);
if (read_cache_unmerged()) if (read_cache_unmerged())
die_resolve_conflict("pull"); die_resolve_conflict("pull");
@ -1026,6 +1045,13 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
if (opt_rebase && merge_heads.nr > 1) if (opt_rebase && merge_heads.nr > 1)
die(_("Cannot rebase onto multiple branches.")); die(_("Cannot rebase onto multiple branches."));
can_ff = get_can_ff(&orig_head, &merge_heads.oid[0]);
if (rebase_unspecified && !opt_ff && !can_ff) {
if (opt_verbosity >= 0)
show_advice_pull_non_ff();
}
if (opt_rebase) { if (opt_rebase) {
int ret = 0; int ret = 0;
int ran_ff = 0; int ran_ff = 0;
@ -1040,22 +1066,12 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
submodule_touches_in_range(the_repository, &upstream, &curr_head)) submodule_touches_in_range(the_repository, &upstream, &curr_head))
die(_("cannot rebase with locally recorded submodule modifications")); die(_("cannot rebase with locally recorded submodule modifications"));
if (!autostash) { if (!autostash) {
struct commit_list *list = NULL; if (can_ff) {
struct commit *merge_head, *head;
head = lookup_commit_reference(the_repository,
&orig_head);
commit_list_insert(head, &list);
merge_head = lookup_commit_reference(the_repository,
&merge_heads.oid[0]);
if (repo_is_descendant_of(the_repository,
merge_head, list)) {
/* we can fast-forward this without invoking rebase */ /* we can fast-forward this without invoking rebase */
opt_ff = "--ff-only"; opt_ff = "--ff-only";
ran_ff = 1; ran_ff = 1;
ret = run_merge(); ret = run_merge();
} }
free_commit_list(list);
} }
if (!ran_ff) if (!ran_ff)
ret = run_rebase(&newbase, &upstream); ret = run_rebase(&newbase, &upstream);

View File

@ -29,11 +29,8 @@ test_expect_success 'setup' '
test_expect_success 'pull.rebase not set' ' test_expect_success 'pull.rebase not set' '
git reset --hard c0 && git reset --hard c0 &&
git -c color.advice=always pull . c1 2>err && git pull . c1 2>err &&
test_decode_color <err >decoded && test_i18ngrep ! "Pulling without specifying how to reconcile" err
test_i18ngrep "<YELLOW>hint: " decoded &&
test_i18ngrep "Pulling without specifying how to reconcile" decoded
' '
test_expect_success 'pull.rebase not set and pull.ff=true' ' test_expect_success 'pull.rebase not set and pull.ff=true' '
@ -87,6 +84,65 @@ test_expect_success 'pull.rebase not set and --ff-only given' '
test_i18ngrep ! "Pulling without specifying how to reconcile" err test_i18ngrep ! "Pulling without specifying how to reconcile" 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_decode_color <err >decoded &&
test_i18ngrep "<YELLOW>hint: " decoded &&
test_i18ngrep "Pulling without specifying how to reconcile" 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_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_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_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_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_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_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_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_expect_success 'merge c1 with c2' ' test_expect_success 'merge c1 with c2' '
git reset --hard c1 && git reset --hard c1 &&
test -f c0.c && test -f c0.c &&