8

Say, if from the master branch, we create a foo branch and a bar branch.

git checkout -b foo master
git checkout -b bar master

Now let's say, if we create a branch wah based on foo, bar, or master:

git checkout -b wah foo

or

git checkout -b wah bar

or

git checkout -b wah master

Then are there any differences at all whether wah is based on foo, bar, or master? Are there any Git commands that can tell it was based on foo, bar, or master?

nonopolarity
  • 146,324
  • 131
  • 460
  • 740

4 Answers4

6

There is no difference. Branches are just references that point to commits. I haven't checked the source code, but I think it's fair to assume that the references are ultimately dereferenced into commit objects whenever you create a branch.

Thus, when you do

git branch foo master
git branch bar master

the branches foo, bar, and master all refer to the same commit object.

Thus, the following will also be equivalent, since wah will also end up referring to the same commit object:

git branch wah master
git branch wah foo
git branch wah bar

Verifying That Branch References Are Just Labels On Commits

You can easily verify this by simply getting the first log entry for each branch (note the commit sha ID for each one):

git log --pretty=oneline -1 master
952e133ff1d1616f068ca524af9f323f6e7f8b7a Add Bash alias for `pbcopy` (OS X)

Another way you can verify this is to just look at the reference files themselves. Each one will contain a single line that contains the commit sha that they currently refer to:

cat .git/refs/heads/master
952e133ff1d1616f068ca524af9f323f6e7f8b7a

cat .git/refs/heads/foo
952e133ff1d1616f068ca524af9f323f6e7f8b7a

Additional Reading

  • "Branches are just references that point to commits." -- That sounds like a description of tags rather than branches. – Keith Thompson May 10 '14 at 21:42
  • 1
    branches are mutable; tags are not (supposed to be) :) – Eevee May 10 '14 at 21:43
  • @KeithThompson both branches and tags are references (of a sort) that ultimately refer to a commit object, so they basically function identically in that regard. –  May 10 '14 at 21:52
  • Never mind, I didn't read the question thoroughly enough. The branches `foo` and `bar` are distinct, but for a new branch `wah` it odesn't matter whehter it's created from `master`, `foo`, or `bar` *when all three branches' tips are the same commit at the time `wah` is created*. – Keith Thompson May 10 '14 at 21:56
  • @KeithThompson exactly `:P` –  May 10 '14 at 22:01
  • But you still describe branches in a manner that would apply more accurately to tags. See [my updated answer](http://stackoverflow.com/a/23586759/827263). A branch name refers to a particular commit at a particular time, but the branch itself is more than that. – Keith Thompson May 10 '14 at 22:13
2

No, in the sense that wah points to the same commit no matter which of the three branches it was created from in this case.

But yes, because git keeps track of which branches are upstream from which others. So when it comes time to synchronise the wah branch with its 'upstream' branch, git will 'want' to synchronise (send) commits to whichever branch is considered to be the upstream. With the following command:

git push . # '.' means the local repo

... if wah was branched off master, the push will want to send commits to (i.e., merge with) master; if branched off foo, then foo; and so on.

Of course, whether or not this is allowed is controlled by another configuration setting: push.default. If push.default is set to upstream, then it will work as I described above. If it's set to simple or matching, the push will fail saying you can implicitly push branches only to other branches with the same name.

A simple way you can get git to show you which branches are based on which others, is with the following command:

git branch -avv

This shows you each branch and its upstream branch next to it in brackets.

So the upshot is that yes, internally git does keep track of which specific branch you branched off, and applies that knowledge in a very specific situation as described above.

Yawar
  • 11,272
  • 4
  • 48
  • 80
1

To expand a bit on the underlying question: commits do not know what branch they were made on. It's not even necessary that a commit was made on any branch at all.

Branches are nothing but cheap labels for the leaves of the history graph — and branches only point at commits, not the other way around. If you're on master and make a new commit, that moves master to the new commit, and nothing associates the prior commit with master except that it happens to be the parent of the new commit.*

(* I'm lying a bit; the reflog will remember the last few commits master used to point to, which is useful in the face of e.g. a rebase gone terribly awry. That's what the @{1} syntax is for. But this is transient and not shared with anyone else.)

Eevee
  • 47,412
  • 11
  • 95
  • 127
  • so how do you make a commit based on another commit? And how can you tell a commit's parent commit? – nonopolarity May 10 '14 at 19:56
  • @動靜能量 what do you mean by making a commit "based on another commit"? What are you trying to accomplish? –  May 10 '14 at 19:58
  • 1
    @動靜能量 The commit's parents are stored as part of the commit. You can run `git show --format=raw` to see (sort of) what a commit looks like. Unless you're looking at a commit without any parents (probably the first commit), you'll see a line starting with `parent`, containing the commit ID of the parent. –  May 10 '14 at 19:59
  • @動靜能量 another simple way to tell a commit's parent is to simply look at the commit graph using `git log --oneline --graph --decorate`. –  May 10 '14 at 20:02
  • I disagree with the assertion that branches can "only go one way." You can put branch labels wherever you want using hard resets. Overall though, it's not a bad answer. –  May 10 '14 at 20:02
  • @Cupcake since Eevee said that "It's not even necessary that a commit was made on any branch at all", so I assume you can commit based on another commit, not base on the HEAD (the last commit) of the current branch – nonopolarity May 10 '14 at 21:15
  • 1
    @動靜能量 `HEAD` is a symbolic reference that points to your current working directory tree, it's independent of branch references. If you do `git checkout `, that will checkout the state of your code at that commit, leaving any branch references behind. It's called "detached HEAD" state, since `HEAD` is then detached from a branch reference. You can make commits from this state, but it's still based off of the currently checked out commit. –  May 10 '14 at 21:41
  • right; committing will generally use `HEAD` as the parent, but `HEAD` doesn't have to be a branch. of course, you'll then have a commit not reachable from any symbolic name, and it'll eventually be reaped by `git gc` unless you fix that. – Eevee May 10 '14 at 21:42
  • also, sorry, by "go one way" i meant you can only _directly_ resolve a branch name to a commit, not the other way around. – Eevee May 10 '14 at 21:44
1

The command:

git checkout -b new-branch-name existing-commit

creates a new branch starting at the specified commit. It doesn't matter whether the existing-commit is specified via a branch name, a tag name, or a sha1 hash; all that matters is which commit it refers to.

You previously created two branches named foo and bar. A branch name refers to the current tip of the specified branch (unlike a tag name, which refers to a particular commit and doesn't change unless you redefine the tag). Since you haven't added any new commits to foo or to bar, both names refer to the same commit, and any of the three commands:

git checkout -b wah master
git checkout -b wah foo
git checkout -b wah bar

does exactly the same thing. (You could also refer to the same commit by its SHA1 hash.)

(On the other hand, if you had added a new commit to the foo branch, then creating a new branch on top of foo would make wah a different branch than if it were created on top of bar.)

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
  • There's nothing wrong with your answer, but I don't think my own answer implied that branch references were somehow immutable. I guess I could edit it to make that more clear though (I'll do that later). –  May 10 '14 at 22:38
  • 1
    @Cupcake: My point is that `git checkout -b wah foo` and `git checkout -b bar` are identical only because `foo` and `bar` happen to have the same tip at the moment the command is executed. The two commands differ once `foo` and `bar` diverge. – Keith Thompson May 10 '14 at 22:51