.3 Git Internals - Git References
You can run something like
git log 1a410e to look through your whole history, but you still have to remember that
1a410e is the last commit in order to walk that history to find all those objects. You need a file in which you can store the SHA-1 value under a simple name so you can use that pointer rather than the raw SHA-1 value.
In Git, these are called "references" or "refs"; you can find the files that contain the SHA-1 values in the
.git/refs directory. In the current project, this directory contains no files, but it does contain a simple structure:
$ find .git/refs .git/refs .git/refs/heads .git/refs/tags $ find .git/refs -type f $
To create a new reference that will help you remember where your latest commit is, you can technically do something as simple as this:
$ echo "1a410efbd13591db07496601ebc7a059dd55cfe9" > .git/refs/heads/master
Now, you can use the head reference you just created instead of the SHA-1 value in your Git commands:
$ git log --pretty=oneline master 1a410efbd13591db07496601ebc7a059dd55cfe9 third commit cac0cab538b970a37ea1e769cbbde608743bc96d second commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit
You aren’t encouraged to directly edit the reference files. Git provides a safer command to do this if you want to update a reference called
$ git update-ref refs/heads/master 1a410efbd13591db07496601ebc7a059dd55cfe9
That’s basically what a branch in Git is: a simple pointer or reference to the head of a line of work. To create a branch back at the second commit, you can do this:
$ git update-ref refs/heads/test cac0ca
Your branch will contain only work from that commit down:
$ git log --pretty=oneline test cac0cab538b970a37ea1e769cbbde608743bc96d second commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit
Now, your Git database conceptually looks something like Figure 9-4.
Figure 9-4. Git directory objects with branch head references included.
When you run commands like
git branch (branchname), Git basically runs that
update-ref command to add the SHA-1 of the last commit of the branch you’re on into whatever new reference you want to create.
The question now is, when you run
git branch (branchname), how does Git know the SHA-1 of the last commit? The answer is the HEAD file. The HEAD file is a symbolic reference to the branch you’re currently on. By symbolic reference, I mean that unlike a normal reference, it doesn’t generally contain a SHA-1 value but rather a pointer to another reference. If you look at the file, you’ll normally see something like this:
$ cat .git/HEAD ref: refs/heads/master
If you run
git checkout test, Git updates the file to look like this:
$ cat .git/HEAD ref: refs/heads/test
When you run
git commit, it creates the commit object, specifying the parent of that commit object to be whatever SHA-1 value the reference in HEAD points to.
You can also manually edit this file, but again a safer command exists to do so:
symbolic-ref. You can read the value of your HEAD via this command:
$ git symbolic-ref HEAD refs/heads/master
You can also set the value of HEAD:
$ git symbolic-ref HEAD refs/heads/test $ cat .git/HEAD ref: refs/heads/test
You can’t set a symbolic reference outside of the refs style:
$ git symbolic-ref HEAD test fatal: Refusing to point HEAD outside of refs/
You’ve just gone over Git’s three main object types, but there is a fourth. The tag object is very much like a commit object — it contains a tagger, a date, a message, and a pointer. The main difference is that a tag object points to a commit rather than a tree. It’s like a branch reference, but it never moves — it always points to the same commit but gives it a friendlier name.
As discussed in Chapter 2, there are two types of tags: annotated and lightweight. You can make a lightweight tag by running something like this:
$ git update-ref refs/tags/v1.0 cac0cab538b970a37ea1e769cbbde608743bc96d
That is all a lightweight tag is — a branch that never moves. An annotated tag is more complex, however. If you create an annotated tag, Git creates a tag object and then writes a reference to point to it rather than directly to the commit. You can see this by creating an annotated tag (
-a specifies that it’s an annotated tag):
$ git tag -a v1.1 1a410efbd13591db07496601ebc7a059dd55cfe9 -m 'test tag'
Here’s the object SHA-1 value it created:
$ cat .git/refs/tags/v1.1 9585191f37f7b0fb9444f35a9bf50de191beadc2
Now, run the
cat-file command on that SHA-1 value:
$ git cat-file -p 9585191f37f7b0fb9444f35a9bf50de191beadc2 object 1a410efbd13591db07496601ebc7a059dd55cfe9 type commit tag v1.1 tagger Scott Chacon <email@example.com> Sat May 23 16:48:58 2009 -0700 test tag
Notice that the object entry points to the commit SHA-1 value that you tagged. Also notice that it doesn’t need to point to a commit; you can tag any Git object. In the Git source code, for example, the maintainer has added their GPG public key as a blob object and then tagged it. You can view the public key by running
$ git cat-file blob junio-gpg-pub
in the Git source code repository. The Linux kernel repository also has a non-commit-pointing tag object — the first tag created points to the initial tree of the import of the source code.
The third type of reference that you’ll see is a remote reference. If you add a remote and push to it, Git stores the value you last pushed to that remote for each branch in the
refs/remotes directory. For instance, you can add a remote called
origin and push your
master branch to it:
$ git remote add origin firstname.lastname@example.org:schacon/simplegit-progit.git $ git push origin master Counting objects: 11, done. Compressing objects: 100% (5/5), done. Writing objects: 100% (7/7), 716 bytes, done. Total 7 (delta 2), reused 4 (delta 1) To email@example.com:schacon/simplegit-progit.git a11bef0..ca82a6d master -> master
Then, you can see what the
master branch on the
origin remote was the last time you communicated with the server, by checking the
$ cat .git/refs/remotes/origin/master ca82a6dff817ec66f44342007202690a93763949
Remote references differ from branches (
refs/heads references) mainly in that they can’t be checked out. Git moves them around as bookmarks to the last known state of where those branches were on those servers.