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
* 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");
const char *value;
@ -344,20 +344,7 @@ static enum rebase_type config_get_rebase(void)
if (!git_config_get_value("pull.rebase", &value))
return parse_config_rebase("pull.rebase", value, 1);
if (opt_verbosity >= 0 && !opt_ff) {
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"));
}
*rebase_unspecified = 1;
return REBASE_FALSE;
}
@ -924,6 +911,36 @@ 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)
{
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)
{
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 rebase_fork_point;
int autostash;
int rebase_unspecified = 0;
int can_ff;
if (!getenv("GIT_REFLOG_ACTION"))
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());
if (opt_rebase < 0)
opt_rebase = config_get_rebase();
opt_rebase = config_get_rebase(&rebase_unspecified);
if (read_cache_unmerged())
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)
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) {
int ret = 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))
die(_("cannot rebase with locally recorded submodule modifications"));
if (!autostash) {
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,
&merge_heads.oid[0]);
if (repo_is_descendant_of(the_repository,
merge_head, list)) {
if (can_ff) {
/* we can fast-forward this without invoking rebase */
opt_ff = "--ff-only";
ran_ff = 1;
ret = run_merge();
}
free_commit_list(list);
}
if (!ran_ff)
ret = run_rebase(&newbase, &upstream);

View File

@ -29,11 +29,8 @@ test_expect_success 'setup' '
test_expect_success 'pull.rebase not set' '
git reset --hard c0 &&
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
git pull . c1 2>err &&
test_i18ngrep ! "Pulling without specifying how to reconcile" err
'
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_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' '
git reset --hard c1 &&
test -f c0.c &&