A common workflow is to make a commit on a local branch, push the branch to the remote, check out the remote branch on a second computer, amend the commit on the second computer, force-push back to the remote branch, and finally submit a pull request. However, if the user switches back to the first computer, they must then run the cumbersome command `git fetch && git reset --hard origin`. (Actually, at this point Git novices often try running `git pull --force`, but it doesn't do what they expect.) This patch adds the shortcut `git pull --reset` to serve as a complement to `git push --force`. Signed-off-by: Alex Henrie <alexhenrie24@xxxxxxxxx> --- Documentation/git-pull.txt | 8 ++++++++ builtin/pull.c | 23 +++++++++++++++++++++++ t/t5520-pull.sh | 24 ++++++++++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index 118d9d86f7..032a5c2e34 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -23,6 +23,7 @@ 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'. +With `--reset`, it runs `git reset --hard` instead of 'git merge'. <repository> should be the name of a remote repository as passed to linkgit:git-fetch[1]. <refspec> can name an @@ -141,6 +142,13 @@ unless you have read linkgit:git-rebase[1] carefully. + This option is only valid when "--rebase" is used. +--reset:: + Reset the local branch to be identical to the remote branch, discarding + any local commits or other changes. + +--no-reset:: + Override earlier --reset. + Options related to fetching ~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/builtin/pull.c b/builtin/pull.c index 33db889955..b32134c1f1 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -95,6 +95,7 @@ static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT; /* Options passed to git-merge or git-rebase */ static enum rebase_type opt_rebase = -1; +static char *opt_reset; static char *opt_diffstat; static char *opt_log; static char *opt_signoff; @@ -144,6 +145,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_PASSTHRU(0, "reset", &opt_reset, NULL, + N_("discard all local changes rather than merging"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG), 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), @@ -860,6 +864,16 @@ static int run_rebase(const struct object_id *curr_head, return ret; } +/** + * Runs git-reset, returning its exit status. + */ +static int run_reset(void) +{ + static const char *argv[] = { "reset", "--hard", "FETCH_HEAD", NULL }; + + return run_command_v_opt(argv, RUN_GIT_CMD); +} + int cmd_pull(int argc, const char **argv, const char *prefix) { const char *repo, **refspecs; @@ -892,6 +906,9 @@ int cmd_pull(int argc, const char **argv, const char *prefix) if (get_oid("HEAD", &orig_head)) oidclr(&orig_head); + if (opt_rebase && opt_reset) + die(_("--rebase and --reset are mutually exclusive")); + if (!opt_rebase && opt_autostash != -1) die(_("--[no-]autostash option is only valid with --rebase.")); @@ -986,6 +1003,12 @@ int cmd_pull(int argc, const char **argv, const char *prefix) ret = rebase_submodules(); return ret; + } else if (opt_reset) { + int ret = run_reset(); + if (!ret && (recurse_submodules == RECURSE_SUBMODULES_ON || + recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND)) + ret = update_submodules(); + return ret; } else { int ret = run_merge(); if (!ret && (recurse_submodules == RECURSE_SUBMODULES_ON || diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh index cf4cc32fd0..597f2429d5 100755 --- a/t/t5520-pull.sh +++ b/t/t5520-pull.sh @@ -708,4 +708,28 @@ test_expect_success 'git pull --rebase against local branch' ' test file = "$(cat file2)" ' +test_expect_success 'git pull --rebase --reset is an invalid combination' ' + test_must_fail git pull --rebase --reset . +' + +test_expect_success 'git pull --reset overwrites or deletes all tracked files' ' + git init cloned && + ( + cd cloned && + echo committed > committed && + echo staged > staged && + echo untracked > file && + echo untracked > untracked && + git add committed && + git commit -m original && + git add staged && + git pull --reset .. && + test ! -f committed && + test ! -f staged && + test "$(cat untracked)" = "untracked" && + test "$(git status --porcelain)" = "?? untracked" + ) && + test_cmp file cloned/file +' + test_done -- 2.21.0