0

If I edit some files in my local repo without committing them, and try to git pull, it fails for the obvious reason that git doesn't know how I intend to reconcile newly pulled changes to these files with the uncommitted versions I have.

I know the solution is git pull --rebase --autostash. But why is --rebase necessary?

The --rebase option is explained as:

When true, rebase the current branch on top of the upstream branch after fetching.

And git rebase is described as:

git-rebase - Reapply commits on top of another base tip

But in the above case, there is no local commit that needs to be reapplied on top of what's pulled, only uncommitted changes. Is there some kind of logic for why git decided to require --rebase to do --autostash or is this just accidental?

Donentolon
  • 1,363
  • 1
  • 10
  • 17
  • 1
    Since Git 2.27, `git merge --autostash` exists, see https://stackoverflow.com/a/30209750. It also works for plain `git pull` – philb Jul 02 '20 at 03:22

1 Answers1

4

TL;DR: this is something of a historical accident.

Remember that git pull means:

  1. Run git fetch to obtain new commits from some other Git repository. Anyone can do this safely any time so this step basically always works (unless for some reason your network is down or whatever).

  2. Then, assuming step 1 worked, run a second Git command that messes with the current branch in some way.

Step 2 is the problematic one. The actual command run is normally git merge; the other option is git rebase.

Now, you can't run either git merge or git rebase if you have uncommitted work (with one exception that I'll leave for later). Step 1 is fine but step 2 is not, in this case.

The autostash option basically runs git stash push for you, and git stash push means commit what I have now, but on no branch, then do a git reset --hard (which discards the work you did, but it's safely stored in some commits now). Having done that, step 2 is now always possible. If step 2 completes successfully—though it doesn't always, regardless of which second command you use—Git will then run git stash pop for you.

There are a couple of reasonable questions you can ask here. One is: why not allow this before a git merge? You can stash, rebase, and pop, but you can also stash, merge, and pop; and if you can stash, merge, and pop, why not have that as an option? The only real answer is: the --autostash option was added to rebase back when rebase was a fancy shell script and it was easier to add it there. The merge command was not a fancy shell script and it was harder to add it there.

Another question is the one I think you did ask: if you have no commits of your own, and have done a git stash push if needed, both git merge—which by default does a fast-forward operation for this case, which means it does not actually merge anything—and git rebase wind up doing the same thing: directly checking out the other Git's latest commit (and not failing in step 2 of pull). Again, one might wonder why not allow --autostash to handle both of these cases. Again, the only real answer is "because it was easier to add to the rebase code path".

We should note that even if git merge can do a fast-forward, it doesn't always do one. In particular, you can force Git to do a real merge instead of a fast-forward operation, using git merge --no-ff. You can use git config to set this up to happen on pulls (pull.ff) or always (merge.ff), too. So pull might do a merge even if it could have done a fast-forward instead. Still, if it can fast-forward, the merge it will do will succeed, and we're back to the same situation as before.

It's now time to mention the one special case. When git merge can and does choose to do a fast-forward operation—essentially, a git checkout that drags the branch name forward in the commit-graph—instead of a real merge, it's possible for Git to do this operation even if you have uncommitted work, as long as the uncommitted work is in files that are not updated by the checkout. So there's one case of a git pull that uses git merge that doesn't require autostash. Nonetheless, actually doing a push/fast-forward/pop sequence would be fine, and again we're really just back to the historical accident.

If you feel strongly about this sort of thing, you can always write a patch that teaches Git how to do a git pull and/or git merge with auto-stashing. The source for Git is available; see, e.g., the publish-only clone at https://github.com/git/git.

torek
  • 448,244
  • 59
  • 642
  • 775
  • Thank you for the explanation! It doesn't bother me enough to patch git (aliases are easier in my case, and the patch might bother some people who rely on the old method) but I like to understand the reason (if any) behind the decisions. – Donentolon Jul 08 '20 at 23:15