2

Are the following commands equivalent? If not, what are the differences?

git checkout -B a_branch

and

git branch -f a_branch HEAD
git symbolic-ref HEAD refs/heads/a_branch


See also this related post.

Community
  • 1
  • 1
Evan Aad
  • 5,699
  • 6
  • 25
  • 36

1 Answers1

2

Yes, they are extremely close, so much so that they might as well be identical.

You can stop here, the rest is because the above is a special case

They become much less close if you change one of them slightly. Consider that you can use this as:

git checkout -B name commit-specifier

as well as just:

git checkout -B name

which effectively means:

git checkout -B name HEAD

As the documentation says, the -b and -B flags are the "transactional equivalent" of some alternative sequence of commands. If those commands would (or do) fail somewhere along the way, the -b or -B action is suppressed. And git checkout commit-specifier can in fact fail, when you have uncommitted changes that would be overwritten by checkout.

However, git checkout HEAD should never fail. Given that it won't actually fail, the transactional nature of the -B operation becomes unimportant. So now we look at what the documentation says this is the transactional-equivalent-of:

$ git branch -f <branch> [<start point>]
$ git checkout <branch>

We know that the start-point is HEAD, so:

git branch -f a_branch HEAD

is correct: this matches the first command. And, we know that git checkout <the commit we are already on> is essentially a no-op (does not change the index and work-tree) and git checkout a_branch ends up doing:

git symbolic-ref HEAD refs/heads/a_branch

as its final operation, so:

git checkout -B a_branch

"means":

  1. don't do anything to the index and work-tree (which succeeds);
  2. if that succeeds (which it does), reset a_branch to the current commit; and
  3. if that succeeds (which it does), make HEAD refer to a_branch.

If we add the starting point, though, step 1 may fail, and step 2, if run, does something different.

torek
  • 448,244
  • 59
  • 642
  • 775
  • Thanks. You wrote: "we know that git checkout is essentially a no-op (does not change the index and work-tree)". How do we know that? – Evan Aad Apr 12 '17 at 20:59
  • 2
    We know it because `git checkout`'s first step in actually checking something out is to assess the up-to-three-way comparison of three items: "current commit" *C*, "new commit" *N*, and "index state" *I*. The index—that which is to go into the *next* commit—must be updated to go from *C* to *N*, so wherever those are different, something has to change. But if *N = C*, *nothing* has to change in *I*. That, in turn, means nothing has to change in the work-tree. – torek Apr 12 '17 at 22:28
  • 2
    By the way, the rules for moving from *C* to *N* with *C ≠ N* then have to factor in a lot about *I*, including *I*-vs-work-tree; the exact rules are detailed in [the `git read-tree` documentation](https://www.kernel.org/pub/software/scm/git/docs/git-read-tree.html), where *C* and *N* are called *H* and *M* instead. – torek Apr 12 '17 at 22:40