Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Git branch from tag - internals

Tags:

git

In git, supposing I have a branch master and a tag 0.0.1, if I do the following:

git checkout 0.0.1
git branch -b random-fix
# some changes...
git checkout master
git merge random-fix

The branch from tag, internally, creates a reference to the commit marked by the tag in the branch master or the branch is from the "tag itself", as if the tag was some kind of branch?

I'm asking this because when I checkout to a tag, I will be in a "detached head" or something like that, right? Knowing that made me think about the branch-from-tag.

like image 792
caarlos0 Avatar asked Sep 26 '13 18:09

caarlos0


2 Answers

Tags Reference Commits

Tags are references to commits. Specifically, lightweight tags are just refs that point to a given commit using a user-defined name. For example:

$ git log
commit e0e92bc337b246696aec5c214507321c7526c1e9
Author: John Doe <[email protected]>
Date:   Thu Sep 26 14:38:36 2013 -0400

    Empty initial commit.

$ git tag v0.0.1

$ cat .git/refs/tags/v0.0.1 
e0e92bc337b246696aec5c214507321c7526c1e9

The actual SHA-1 is the same in both cases. Put another way, the tag is just a pointer to the commit.

Use the Tag as a Commit Identifier

You can branch from a tag the exact same way as you do from a commit identifier. For example, the following two commands are effectively the same thing:

  • git checkout -b new_branch v0.0.1
  • git checkout -b new_branch e0e92bc337b246696aec5c214507321c7526c1e9

It makes no difference to Git whether the starting point for the new branch is listed as a tag, SHA-1, or some other form of revision selection.

like image 60
Todd A. Jacobs Avatar answered Oct 14 '22 22:10

Todd A. Jacobs


(This should perhaps be a comment but it's way too too big and needs nice formatting :-) ... also, I appear to be in a quirky mood, if you follow some of the links...)

The key to understanding this—I think you're already there, but let's put this in the SO article to make it clear to the next reader—is this:

All git refs wind up naming (pointing to) a single commit.

This is true for tags (whether lightweight or annotated), branches, "remote branches", git "notes" references, the "stash", and so on. They all name one commit, and that's it. (Well, OK, not quite: technically, tags can name any object in the repo. In fact, that's how annotated tags work: there's a lightweight tag that names a repository object that is the annotated tag, and then the annotated tag names a commit object. That's also how HEAD works: it usually names another ref, which then names the commit. So, sometimes you have to peel a few layers off the onion to hit the commit. Naming a blob or tree object is possible, but normally, nothing actually does this.)

(The "true name" of the commit is, of course, the SHA-1 value.)

What makes a branch reference name "special" is equally simple:

A branch reference is a name that automatically moves to the new branch tip, when new commits are added.

Specifically, a ref of the form refs/heads/branchname points to some commit (by definition, since names point to commits). When you are "on" that branch1 and do some git operation that adds a new commit, like git commit or git merge or git cherry-pick, git sticks the new commit into the repo, then re-points the name to the new commit. That's all it has to do!

What we think of as "the branch" is formed by starting at the tip—the commit to which the name points—and working backwards, using each commit's parent or parents. If the commit has one parent, it's an ordinary commit "on the branch". If it has two or more, it's a "merge", and you can follow all of its parents to find what got merged. If it has no parents at all, it's a root commit (like the initial commit in a new repo—you can actually have more than one root; git "notes" do this, for instance).

If you put seven branch labels onto one commit, you now have seven2 names for "the branch". If you clear this down to one, it's less confusing, of course, but git won't care either way. (Git only cares if you take it down to zero names. Now the branch still exists, but it's really hard to find, and it's eligible for garbage collection.)

Since we're on the topic, let's also make a note about "remote branches". (I have never been quite satisfied with the name "remote branch" but I don't have a better one, so let's just define it.) A "remote branch" is a local ref of the form refs/remotes/rname/bname, where rname is the name of the remote (such as origin) and bname is the branch name on the remote, i.e., the part that comes after refs/heads/ if you log in on that remote and look at the branch over there. You can't get "on" a remote branch—if you git checkout origin/master, git gives you a "detached HEAD"—but these branch names are automatically updated: when you use git fetch to get new commits from the remote, you also pick up the new branch-tips. In other words, instead of you moving the names to the new branch tips, you let someone else do it (on the remote) and then you pick up their latest version all at once, when you fetch from them.


1To be "on a branch", the HEAD ref must be an "indirect" reference, something like ref: refs/heads/master. When HEAD is instead "detached" it contains a raw SHA-1 value. You can still add commits; they're added to an un-labeled branch. The reference in HEAD keeps them from being garbage-collected.

2Or more, if there are tags. Let's assume there are no tags.

3There is no footnote three.

like image 39
torek Avatar answered Oct 14 '22 22:10

torek