@@ -27,8 +27,6 @@
#include "commit-reach.h"
#include "sequencer.h"
-static int default_mode;
-
/**
* Parses the value of --rebase. If value is a false value, returns
* REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is
@@ -76,7 +74,7 @@ static char *opt_progress;
static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
/* Options passed to git-merge or git-rebase */
-static enum rebase_type opt_rebase = -1;
+static enum rebase_type opt_rebase;
static char *opt_diffstat;
static char *opt_log;
static char *opt_signoff;
@@ -114,6 +112,24 @@ static int opt_show_forced_updates = -1;
static char *set_upstream;
static struct strvec opt_fetch = STRVEC_INIT;
+static int parse_opt_ff_only(const struct option *opt, const char *arg, int unset)
+{
+ char **value = opt->value;
+ opt_rebase = REBASE_DEFAULT;
+ free(*value);
+ *value = xstrdup_or_null("--ff-only");
+ return 0;
+}
+
+static int parse_opt_merge(const struct option *opt, const char *arg, int unset)
+{
+ enum rebase_type *value = opt->value;
+ free(opt_ff);
+ opt_ff = NULL;
+ *value = REBASE_FALSE;
+ return 0;
+}
+
static struct option pull_options[] = {
/* Shared options */
OPT__VERBOSITY(&opt_verbosity),
@@ -131,8 +147,9 @@ static struct option pull_options[] = {
"(false|true|merges|preserve|interactive)",
N_("incorporate changes by rebasing rather than merging"),
PARSE_OPT_OPTARG, parse_opt_rebase),
- OPT_SET_INT('m', "merge", &opt_rebase,
- N_("incorporate changes by merging"), 0),
+ OPT_CALLBACK_F('m', "merge", &opt_rebase, NULL,
+ N_("incorporate changes by merging"),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, parse_opt_merge),
OPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,
N_("do not show a diffstat at the end of the merge"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG),
@@ -161,9 +178,9 @@ static struct option pull_options[] = {
OPT_PASSTHRU(0, "ff", &opt_ff, NULL,
N_("allow fast-forward"),
PARSE_OPT_NOARG),
- OPT_PASSTHRU(0, "ff-only", &opt_ff, NULL,
+ OPT_CALLBACK_F(0, "ff-only", &opt_ff, NULL,
N_("abort if fast-forward is not possible"),
- PARSE_OPT_NOARG | PARSE_OPT_NONEG),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, parse_opt_ff_only),
OPT_PASSTHRU(0, "verify-signatures", &opt_verify_signatures, NULL,
N_("verify that the named commit has a valid GPG signature"),
PARSE_OPT_NOARG),
@@ -348,9 +365,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);
- default_mode = 1;
-
- return REBASE_FALSE;
+ return REBASE_DEFAULT;
}
/**
@@ -445,7 +460,7 @@ static void NORETURN die_no_merge_candidates(const char *repo, const char **refs
const char *remote = curr_branch ? curr_branch->remote_name : NULL;
if (*refspecs) {
- if (opt_rebase)
+ if (opt_rebase >= REBASE_TRUE)
fprintf_ln(stderr, _("There is no candidate for rebasing against among the refs that you just fetched."));
else
fprintf_ln(stderr, _("There are no candidates for merging among the refs that you just fetched."));
@@ -458,7 +473,7 @@ static void NORETURN die_no_merge_candidates(const char *repo, const char **refs
repo);
} else if (!curr_branch) {
fprintf_ln(stderr, _("You are not currently on a branch."));
- if (opt_rebase)
+ if (opt_rebase >= REBASE_TRUE)
fprintf_ln(stderr, _("Please specify which branch you want to rebase against."));
else
fprintf_ln(stderr, _("Please specify which branch you want to merge with."));
@@ -473,7 +488,7 @@ static void NORETURN die_no_merge_candidates(const char *repo, const char **refs
remote_name = _("<remote>");
fprintf_ln(stderr, _("There is no tracking information for the current branch."));
- if (opt_rebase)
+ if (opt_rebase >= REBASE_TRUE)
fprintf_ln(stderr, _("Please specify which branch you want to rebase against."));
else
fprintf_ln(stderr, _("Please specify which branch you want to merge with."));
@@ -919,6 +934,9 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
struct object_id rebase_fork_point;
int can_ff;
+ opt_ff = xstrdup_or_null(config_get_ff());
+ opt_rebase = config_get_rebase();
+
if (!getenv("GIT_REFLOG_ACTION"))
set_reflog_message(argc, argv);
@@ -935,12 +953,6 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
parse_repo_refspecs(argc, argv, &repo, &refspecs);
- if (!opt_ff)
- opt_ff = xstrdup_or_null(config_get_ff());
-
- if (opt_rebase < 0)
- opt_rebase = config_get_rebase();
-
if (read_cache_unmerged())
die_resolve_conflict("pull");
@@ -950,7 +962,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
if (get_oid("HEAD", &orig_head))
oidclr(&orig_head);
- if (opt_rebase) {
+ if (opt_rebase >= REBASE_TRUE) {
int autostash = config_autostash;
if (opt_autostash != -1)
autostash = opt_autostash;
@@ -1010,19 +1022,18 @@ 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)
+ if (opt_rebase >= REBASE_TRUE && merge_heads.nr > 1)
die(_("Cannot rebase onto multiple branches."));
can_ff = get_can_ff(&orig_head, &merge_heads.oid[0]);
- if (!can_ff && default_mode) {
- if (opt_ff && !strcmp(opt_ff, "--ff-only")) {
- die(_("The pull was not fast-forward, please either merge or rebase.\n"
- "If unsure, run \"git pull --merge\"."));
- }
+ if (!can_ff && !opt_rebase) {
+ if (opt_ff && !strcmp(opt_ff, "--ff-only"))
+ die(_("The pull was not fast-forward, please either merge or rebase."));
+
if (opt_verbosity >= 0 && (!opt_ff || !strcmp(opt_ff, "--ff"))) {
- advise(_("Pulling without specifying how to reconcile divergent branches is\n"
- "discouraged; you need to specify if you want a merge, or a rebase.\n"
+ advise(_("Pulling without specifying how to reconcile divergent branches is discouraged;\n"
+ "you need to specify if you want a merge, a rebase, or a fast-forward.\n"
"You can squelch this message by running one of the following commands:\n"
"\n"
" git config pull.rebase false # merge (the default strategy)\n"
@@ -1037,11 +1048,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
}
}
- /* Disable --ff-only when --merge is specified */
- if (!can_ff && !default_mode && !opt_rebase && opt_ff && !strcmp(opt_ff, "--ff-only"))
- opt_ff = NULL;
-
- if (opt_rebase) {
+ if (opt_rebase >= REBASE_TRUE) {
int ret = 0;
if ((recurse_submodules == RECURSE_SUBMODULES_ON ||
recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) &&
@@ -1050,7 +1057,8 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
if (can_ff) {
/* we can fast-forward this without invoking rebase */
- opt_ff = "--ff-only";
+ free(opt_ff);
+ opt_ff = xstrdup_or_null("--ff-only");
ret = run_merge();
} else {
ret = run_rebase(&curr_head, merge_heads.oid, &rebase_fork_point);
@@ -3,7 +3,8 @@
enum rebase_type {
REBASE_INVALID = -1,
- REBASE_FALSE = 0,
+ REBASE_DEFAULT = 0,
+ REBASE_FALSE,
REBASE_TRUE,
REBASE_PRESERVE,
REBASE_MERGES,
@@ -819,66 +819,89 @@ test_expect_success 'git pull --rebase against local branch' '
test_cmp expect file2
'
-test_expect_success 'git pull fast-forward (ff-only)' '
+setup_other () {
test_when_finished "git checkout master && git branch -D other test" &&
- test_config pull.ff only &&
- git checkout -b other master &&
+ git checkout -b other $1 &&
>new &&
git add new &&
git commit -m new &&
git checkout -b test -t other &&
- git reset --hard master &&
+ git reset --hard master
+}
+
+setup_ff () {
+ setup_other master
+}
+
+setup_non_ff () {
+ setup_other master^
+}
+
+test_expect_success 'fast-forward (ff-only)' '
+ test_config pull.ff only &&
+ setup_ff &&
git pull
'
-test_expect_success 'git pull non-fast-forward (ff-only)' '
- test_when_finished "git checkout master && git branch -D other test" &&
+test_expect_success 'non-fast-forward (ff-only)' '
test_config pull.ff only &&
- git checkout -b other master^ &&
- >new &&
- git add new &&
- git commit -m new &&
- git checkout -b test -t other &&
- git reset --hard master &&
+ setup_non_ff &&
test_must_fail git pull
'
-test_expect_success 'git pull non-fast-forward with merge (ff-only)' '
- test_when_finished "git checkout master && git branch -D other test" &&
+test_expect_success 'non-fast-forward with merge (ff-only)' '
test_config pull.ff only &&
- git checkout -b other master^ &&
- >new &&
- git add new &&
- git commit -m new &&
- git checkout -b test -t other &&
- git reset --hard master &&
- git pull --no-rebase
+ setup_non_ff &&
+ git pull --merge
'
-test_expect_success 'git pull non-fast-forward with rebase (ff-only)' '
- test_when_finished "git checkout master && git branch -D other test" &&
+test_expect_success 'non-fast-forward with rebase (ff-only)' '
test_config pull.ff only &&
- git checkout -b other master^ &&
- >new &&
- git add new &&
- git commit -m new &&
- git checkout -b test -t other &&
- git reset --hard master &&
+ setup_non_ff &&
git pull --rebase
'
-test_expect_success 'git pull non-fast-forward error message' '
- test_when_finished "git checkout master && git branch -D other test" &&
+test_expect_success 'non-fast-forward error message (ff-only)' '
test_config pull.ff only &&
- git checkout -b other master^ &&
- >new &&
- git add new &&
- git commit -m new &&
- git checkout -b test -t other &&
- git reset --hard master &&
+ setup_non_ff &&
test_must_fail git pull 2> error &&
cat error &&
grep -q "The pull was not fast-forward" error
'
+test_expect_success '--merge overrides --ff-only' '
+ setup_non_ff &&
+ git pull --ff-only --merge
+'
+
+test_expect_success '--rebase overrides --ff-only' '
+ setup_non_ff &&
+ git pull --ff-only --rebase
+'
+
+test_expect_success '--ff-only overrides --merge' '
+ setup_non_ff &&
+ test_must_fail git pull --merge --ff-only
+'
+
+test_expect_success '--ff-only overrides pull.rebase=false' '
+ test_config pull.rebase false &&
+ setup_non_ff &&
+ test_must_fail git pull --ff-only
+'
+
+test_expect_success 'pull.rebase=true overrides pull.ff=only' '
+ test_config pull.ff only &&
+ test_config pull.rebase true &&
+ setup_non_ff &&
+ git pull
+'
+
+test_expect_success 'pull.rebase=false overrides pull.ff=only' '
+ test_config pull.ff only &&
+ test_config pull.rebase false &&
+ setup_non_ff &&
+ test_must_fail git pull
+'
+
test_done