athul.sure
athul.sure

Reputation: 328

Checking out tags on Git

I was going through some git internals and came across tags and how they're stored internally. In the Git SCM site I found the following:

Checking out Tags

You can’t really checkout a tag in Git, since they can’t be moved around. If you want to put a version of your repository in your working directory that looks like a specific tag, you can create a new branch at a specific tag with git checkout -b [branchname] [tagname]:

git checkout -b version2 v2.0.0

Switched to a new branch 'version2'

Of course if you do this and do a commit, your version2 branch will be slightly different than your v2.0.0 tag since it will move forward with your new changes, so do be careful.

I have the following doubts:

  1. Why can't we check out tags? Can't we traverse the Git TAG to make HEAD point to the commit pointed to by the tag?
  2. Can we create a new commit which will then point to the commit pointed to by tag as the parent?
  3. It is said that if we create a new branch from the tag and commit, it'll be slightly different. What difference are they talking about? Won't the new commit (assuming nothing has changed) refer to the tagged commit as its parent with everything else (trees & blobs) remaining the same?

Thank you.

EDIT:
I'm referring to cases where HEAD and the tag point to different commits.

Upvotes: 4

Views: 2338

Answers (1)

torek
torek

Reputation: 488183

Why can't we check out tags?

We can!

Can't we traverse the Git TAG to make HEAD point to the commit pointed to by the tag?

Yes, we can do just that (though, strictly speaking, we don't even need much if any traversal: commits are snapshots, so we're done once we reach the right one, and tags generally require either zero or one intermediate steps to reach the commit).

The more typical state for the HEAD file—which is an actual file in .git named HEAD—is that it contains the name of a branch, rather than the ID of a commit. This mode does not have much of a formal name, but I call it "being on a branch", to contrast it with the other mode.

The mode we get when .git/HEAD points directly to some commit (by containing its hash ID) is, as CodeWizard mentioned in a comment, called a "detached HEAD". Since .git/HEAD no longer contains the name of a branch, we are now on no branch—or, equivalently, on some sort of unnamed branch, where we can use the @ symbol (since Git version 1.8.5) to name it, or by spelling out the word HEAD. Of course HEAD is a fine name; it's just that Git will overwrite the contents of the .git/HEAD file as soon as we run git checkout master again, for instance, and then we will no longer have the hash ID stored anywhere.

Can we create a new commit which will then point to the commit pointed to by tag as the parent?

English is a bit ambiguous here, but I think a diagram makes it clearer that the answer is "yes":

 initially: on branch `master` with commit `H` as the current commit

 ...--F--G     <-- tag: v2.0.0
          \
           H   <-- master (HEAD)

Here, commit H points back to commit G; commit G points to commit F; and so on. The branch named master points to commit H, and the tag name v2.0.0 points to commit G (either directly, if it's a lightweight tag, or through one annotated tag object, if it's an annotated tag).

 After `git checkout v2.0.0`:

 ...--F--G     <-- HEAD, tag: v2.0.0
          \
           H   <-- master

The .git/HEAD file now contains the hash ID of commit G.

If we make a new commit now:

           I   <-- HEAD
          /
 ...--F--G     <-- tag: v2.0.0
          \
           H   <-- master

New commit I has commit G as its parent, as you suggested. The .git/HEAD file now contains the hash ID of commit I.

It is said that if we create a new branch from the tag and commit, it'll be slightly different. What difference are they talking about? Won't the new commit (assuming nothing has changed) refer to the tagged commit as its parent with everything else (trees & blobs) remaining the same?

The commit contents would be identical, but the drawing of the commit graph would change a bit. Here's the new graph drawings, starting from the same starting point, but using git checkout -b version2 v2.0.0 as the middle step.

 initially: on branch `master` with commit `H` as the current commit

 ...--F--G     <-- tag: v2.0.0
          \
           H   <-- master (HEAD)


 After `git checkout -b version2 v2.0.0`:

 ...--F--G     <-- version2 (HEAD), tag: v2.0.0
          \
           H   <-- master


If we make a new commit now:

           I   <-- version2 (HEAD)
          /
 ...--F--G     <-- tag: v2.0.0
          \
           H   <-- master

Note that in this case—the one where we are "on a branch" named version2the branch name moves along with us as we make new commits. When we are not on any branch, in the "detached HEAD" mode, new commits just update (the commit hash ID stored in) .git/HEAD. But when .git/HEAD contains the name of a branch, those new commits update that branch. The .git/HEAD file itself just continues to contain the name of the branch.


This, in fact, is what it means to be "on a branch": whenever .git/HEAD contains the name of the branch, various operations—specifically including making new commits and using git reset—change the hash ID the branch name remembers, rather than changing .git/HEAD itself. Whenever .git/HEAD contains the hash ID of a commit, these same operations just update .git/HEAD itself.

The git checkout command usually updates the contents of .git/HEAD, and is one of the few commands that can change the branch name stored in .git/HEAD. (I say "usually" here because git checkout is kind of a kitchen-sink command, having various modes that do oddball things just because they're closely related to the main thing that it usually does.) Specifically, if you are on a branch, git checkout can put you on a different branch; if you're not on any branch, git checkout can get you onto one; if you use --detach or a tag name or similar, git checkout can put you into detached HEAD mode; and if you're not on any branch, git checkout can change which commit HEAD points to.

(The git reset command can do some of these things, but it won't take you on or off any branch. Like git checkout, it's a bit "kitchen sinky": it has many things it can do and it is hard to describe these well without many words and several diagrams!)

Upvotes: 4

Related Questions