So far we’ve worked exclusively in the
.git/objects folder. What about
If we look, we see it just contains two empty directories:
$ ls .git/refs heads tags $ find .git/refs -type f
We can use the update-ref command to create a named reference to a commit. Like so:
$ git update-ref refs/heads/master fd9274dbef2276ba8dc501be85a48fbfe6fc3e31
If you have another look inside
.git/refs, you’ll see a new file has been created:
$ find .git/refs -type f .git/refs/heads/master $ cat .git/refs/heads/master fd9274dbef2276ba8dc501be85a48fbfe6fc3e31
master is a pointer to the commit
fd9...e31. Anywhere we might use the commit ID, we can use
master as a more convenient shortcut. This is what it looks like:
Let’s use the ref in an example:
$ git cat-file -p master tree f999222f82d1ffe7233a8d86d72f27d5b92478ac parent 65b080f1fe43e6e39b72dd79bda4953f7213658b author Alex Chan <firstname.lastname@example.org> 1520806875 +0000 committer Alex Chan <email@example.com> 1520806875 +0000 Adding c_creatures.txt
We can check the value of a ref with the rev-parse command:
$ git rev-parse master fd9274dbef2276ba8dc501be85a48fbfe6fc3e31
A ref in the
heads folder is more commonly called a branch. So with this command, we’ve created our first Git branch!
Now let’s create another commit, and see what happens to our branch. Notice that I can pass the parent commit with our named ref:
$ echo "Flying foxes feel fantastic but frightening" > foxes.txt $ git update-index --add foxes.txt $ git write-tree c08523d153f6415cda07ea27948830407f243a37 $ echo "Add foxes.txt" | git commit-tree c08523d153f6415cda07ea27948830407f243a37 -p master b023d92829d5d076dc31de5cca92cf0bd5ae8f8e
So we have a new commit, and we can see it if we run
$ git log --oneline b023d92829d5d076dc31de5cca92cf0bd5ae8f8e b023d92 Add foxes.txt fd9274d Adding c_creatures.txt 65b080f initial commit
But has this commit been added to our
master branch? Let’s have a look:
$ git log --oneline master fd9274d Adding c_creatures.txt 65b080f initial commit
Unlike in the world of porcelain commands, branches/refs aren’t automatically advanced to point at new commits. If we create new commits, we need to update the ref manually. Like so:
$ git update-ref refs/heads/master b023d92829d5d076dc31de5cca92cf0bd5ae8f8e $ git log --oneline master b023d92 Add foxes.txt fd9274d Adding c_creatures.txt 65b080f initial commit
Let’s create a second branch in our repository:
$ git update-ref refs/heads/dev 65b080f1fe43e6e39b72dd79bda4953f7213658b
dev is a reference to the initial commit in the repository. We can see the list of references we’ve created either by inspecting the filesystem, or by using a porcelain branch:
$ find .git/refs -type f .git/refs/heads/dev .git/refs/heads/master $ git branch dev * master
Notice that there’s a
* next to the name of the
master branch – this is the current branch, and if we used porcelain commands, it’s the branch where new commits would be added.
The current branch is determined by the contents of the HEAD file:
$ cat .git/HEAD ref: refs/heads/master
We can use symbolic-ref to tell Git we’re on the
dev branch instead:
$ git symbolic-ref HEAD refs/heads/dev $ git branch * dev master
Here HEAD is a reference that has a special meaning. We can use it as a shortcut for commit hashes just as we did with our manually created references above. For example:
$ git cat-file -p HEAD tree 11e2f923d36175b185cfa9dcc34ea068dc2a363c author Alex Chan <firstname.lastname@example.org> 1520806168 +0000 committer Alex Chan <email@example.com> 1520806168 +0000 initial commit
Here’s a quick diagram that summarises what we’ve learnt: refs point to commits (and you could create two refs that point to the same commit). HEAD is a special reference that points to another ref.
Let’s wrap up with a final set of exercises.
.git/refsdirectory. Check it only contains two empty directories.
git logto look at all the commits on this branch.
.git/refsfolder. What do you see?
Repeat this a couple of times, so you get really comfortable creating branches. Use a
git branch to check your work.
To finish off, here are a couple of bonus exercises:
.git/refs, can you think how you might delete a branch? Try it, and use
git branchto check you were successful.
tags. What if you create a ref that points to someting in that folder? What porcelain command can you use for a list of refs in this folder?
git update-ref refs/heads/<branch> <commit ID>>
git rev-parse <branch>
git log <branch>
git symbolic-ref HEAD refs/heads/<branch>
Exercises 1–5 are repeating the steps in the theory section above.
In exercise 6, you can delete a branch by deleting the file in the refs folder. That’s all a branch is – a pointer file.
$ rm .git/refs/heads/master $ git branch * dev
In exercise 7, the
tags folder is used to store Git tags – lightweight pointers to specific commits in the history. These are often used to indicate versioned releases, and generally don’t move with new commits. Here’s a short example:
$ git update-ref refs/tags/v0.1 dev $ find .git/refs/tags -type f .git/refs/tags/v0.1 $ git tag v0.1 $ git rev-parse tags/v0.1 65b080f1fe43e6e39b72dd79bda4953f7213658b
This is why people sometimes say “branches are cheap” in Git – they’re just tiny pointers to commits, which take up almost no disk space.
This is the final part of the workshop. There’s a short recap and conclusion which reviews everything you’ve learnt.