2

My local master branch is two commits ahead of origin/master

I want to preserve these two commits and sync local master with origin. In general and specifically, what will these commands acheive?

git checkout -b "branch-to-save-my-two-commits"
git pull origin master --force

2 Answers 2

10

In order from subject line to rest of question:

  • git pull simply runs git fetch followed by a second Git command. The second command defaults to git merge but can be configured to be git rebase instead. Hence, to know what git pull will do, you should read the git pull manual page and see which options are passed to git fetch and which are passed instead to the second command, then read the other corresponding manual page if necessary.

    This is what the git pull manual page says about -f / --force:

    -f, --force
           When git fetch is used with <rbranch>:<lbranch> refspec, it refuses to update the local branch <lbranch> unless the remote branch <rbranch> it fetches is a descendant of <lbranch>. This option overrides that check.

    This documentation is not that great since it talks about refspecs without defining them; but you are not using a full refspec, so the answer is that --force winds up being ignored entirely.

  • git checkout -b "branch-to-save-my-two-commits"

    This—assuming it does not error out—creates a new branch name, pointing to the current (HEAD) commit. It does nothing else, so that the index / staging-area remains unmodified from however it is now, and the work-tree remains unmodified from however it is now. No new commits are created, but your current branch has changed. git rev-parse HEAD produces the same commit ID as before, but git symbolic-ref HEAD produces the name of the newly created branch.

  • git pull origin master --force

    As noted above, since the refspec (master) does not have the full <rbranch>:<lbranch> form, the --force flag has no effect. As before, we'll assume this all runs to completion without error.

    The fetch step therefore fetches from the URL recorded for the remote named origin, bringing over any commits on their master that are not already in your repository. Their master's branch tip commit ID is placed in your Git's FETCH_HEAD file as usual, and if you Git version is 1.8.4 or newer, your Git also updates your origin/master remote-tracking branch.

    After your git fetch completes successfully, your Git runs either git merge or git rebase depending on your prior commands to configure it. If you have used no such configuration commands, it defaults to running git merge. For this answer I will assume you have not configured it to run git rebase instead.

    While the actual arguments to git merge are a bit complicated (the pull code uses data left in FETCH_HEAD rather than simply using the remote-tracking branch directly), the effect, at least in Git versions 1.8.4 and higher, is basically the same as if you ran:

    git merge origin/master
    

    That is, merge into the current branch—the one you just created with your git checkout -b—the commit now identified by the just-updated origin/master. Git will find the merge base between HEAD and origin/master.

    If this merge base is the same commit as origin/master, Git will do nothing at all (except for printing a message that there is nothing to merge). (This is probably going to be the case; see below.)

    Otherwise, if this merge base is not itself the same commit as HEAD, Git will proceed to do a regular merge. First, it will compute two diffs: merge-base to HEAD, and merge-base to origin/master. Then it will combine these diffs to produce a single "our changes plus their changes" version of all the files, and use the resulting tree to make a new merge commit on the current branch. (This is another possible case; see below.)

    If this merge base is the same commit as HEAD, but is not the same commit as origin/master, Git will do a fast-forward pseudo-merge: it will move the current branch label so that it points to the tip commit of origin/master and check that commit out into the index and work-tree. (This is unlikely, given your setup; see below.)

(Note that if the index or work-tree are dirty at the start of a git merge or git pull, the operation itself will fail. So if the git checkout -b left behind a dirty index or work-tree, no merge will occur.)

What you wanted to do instead

My local master branch is two commits ahead of origin/master.

If this is the case and you're on master when you run git checkout -b branch-to-save-my-two-commits, then we can draw the post-new-branch commit graph fragment like this:

...--o--o        <-- origin/master
         \
          o--o   <-- HEAD->branch-to-save-my-two-commits, master

The git fetch step will then do nothing—I'm assuming that when you say that your master is two commits ahead of origin/master, you mean you have also checked that the other Git, the one at origin, has no new commits there. (If it does bring in new commits, they will modify this graph, adding more commits along the top row.)

Given that the fetch does nothing, we can then say what the merge step does: HEAD points to a later commit than origin/master; the merge base is the tip of origin/master; and the merge just says that you're up to date, and does nothing. This leaves you in the same state as you had before.

If the fetch brings in some new commits, though, we get something more like this. Let's assume it brings in just one commit:

...--o--*---o    <-- origin/master
         \
          A--B   <-- HEAD->branch-to-save-my-two-commits, master

This time, the merge base is the same commit as before—which I've marked as * this time—but origin/master points to a later commit, so there is something to merge. Git will attempt to merge the diffs from * to tip-of-origin/master with the diffs from * to HEAD. Assuming it succeeds, Git will make a new commit that contains this merge, and points back to both the new origin/master and the previous HEAD:

...--o--*---o   <-- origin/master
         \   \
          A   o  <-- HEAD->branch-to-save-my-two-commits
           \ /
            B   <-- master

I've also labeled the two "to be preserved" commits A and B here so that it's clearer what happened: we've moved B down a line in the drawing just so that we can keep master pointing to it, even though HEAD and branch-to-save-my-two-commits has moved on to point to the new merge commit.

I want to preserve these two commits and sync local master with origin.

In that case, what you want to run is:1

git checkout -b newbranch
git fetch origin
git branch -f master origin/master

The first command, as before, makes a new branch pointing to the current commit, and puts you on the new branch (so that you are no longer on branch master).

The second command updates your local origin/master, and any other origin/* remote-tracking branches as a nearly-free bonus. (It takes a tiny bit of extra time to fetch everything, but less time to fetch them now than it will take to fetch any such other things later, so you might as well just do it now.)

The last command forcibly moves your master to point to your just-updated origin/master. This would be unsafe had we not just made the new branch pointing to the current commit (which, again, I am assuming was where master was pointing when we started this whole process).


1There are a few other forms you could use, including perhaps what you were thinking of / instructed to use:

git checkout -b newbranch
git fetch --force origin master:master

Note that this uses git fetch, not git pull. The best advice I know about the git pull command is: don't use it. It's meant as a convenience, but in practice it's not very convenient.

6
  • 1
    There are three stages of using git pull: (1) when you just learned it because the tutorial you used (like so many others) encourages it, erroneously, (2) when you start using Git in a very complex project, realize that you don't actually understand the semantics of git pull, and stop using git pull and (3) when you have enough experience with Git to know exactly when using git pull is appropriate.
    – Pockets
    Commented Jan 11, 2017 at 0:53
  • 1
    @Pockets: heh. I will actually use git pull sometimes, e.g., when updating my Git repo for Git (where I never modify master and know the Git folks don't rewind master). Mostly, though, I made an alias, git mff = git merge --ff-only, and run git fetch and then check out branches and git mff when appropriate.
    – torek
    Commented Jan 11, 2017 at 1:05
  • @torek, yep, if I'm in a dev-on-fork model, I use git pull to update master from upstream constantly.
    – Pockets
    Commented Jan 11, 2017 at 5:11
  • Ok so my command 'git pull master' is really only appropriate if master is clean and only contains commits that already part of master otherwise, I could be merging junk into master that I don't want. Commented Jan 11, 2017 at 20:51
  • Thanks for your answer @torek , it was very helpful, especially the part right after 'In that case, what you want to run is:' and letting me know that my -f wasn't going to work unless I specified origin/master. Commented Mar 16, 2018 at 16:14
-1

I'll explain both lines separate:

git checkout -b "branch-to-save-my-two-commits": This will cause git to move the working directory to a branch with that name. It will not commit, you have to do that by yourself.

git pull origin master --force: Will cause to force pull from master (like what it sounds like). I think, it would override all local changes on the actual master and will head it to the content of origin master.

See the official git pull documentation.

Not the answer you're looking for? Browse other questions tagged or ask your own question.