2.5 refs
So far we have:
- Created some hash objects.
- Created some tree objects that associate file pathname and mode with one or more hash objects
- Created some commit objects that associate metadata with tree objects and allows us to relate tree objects in a graph which is typically interpreted as a version graph where each parent is an earlier version (it should be noted that Git itself is completely unaware of this interpretation though).
So far, so good but it is still a bit cumbersome to use. For one thing we have to remember which commit object we last created so that we can use it as the parent for our next commit. We have seen this problem above, not only when using commit-tree but also using the log command where we needed to know the hash of the most recent commit object in our history.
refs (or ‘references’) to the rescue. A ref is a more human readable way to refer a commit object hash.
1tree -a .git/refs
1.git/refs 2├── heads 3└── tags 4 52 directories, 0 files
The refs directory contains two sub-directories:
- heads—contains references to the head, or latest, commit object we want to name.
- tags—contains references to any object we want to give a human readable name.
We can set the head of our master branch (the default branch5 on which Git works, more on branches later) to our latest commit object:
1echo "e27aaa8c158e6f261f4c03aaaf173a149ad61d81" > .git/refs/heads/master 2git log --stat
We have to use the full hash when writing to the .git/refs/heads/master file.
1commit e27aaa8c158e6f261f4c03aaaf173a149ad61d81 (HEAD -> master) 2Author: vagrant <vagrant@debian-10.7-amd64> 3Date: Wed Mar 10 18:13:55 2021 +0000 4 5 Third commit 6 7file1.txt | 2 +- 81 file changed, 1 insertion(+), 1 deletion(-) 9 10commit 0715e707b906d30c9e395448ddc9e96acd89d5f7 11Author: vagrant <vagrant@debian-10.7-amd64> 12Date: Wed Mar 10 18:11:12 2021 +0000 13 14 Second commit 15 16 another_file.txt | 1 + 17 dir1/file11.txt | 1 + 18 file1.txt | 2 +- 19 3 files changed, 3 insertions(+), 1 deletion(-) 20 21commit f871b58596491e15ee1da91eaf0a4a6c1da3e573 22Author: vagrant <vagrant@debian-10.7-amd64> 23Date: Wed Mar 10 18:07:13 2021 +0000 24 25 First commit 26 27 file1.txt | 1 + 28 1 file changed, 1 insertion(+)
Issuing the log command without specifying the exact commit we are interested in causes Git to look up the refs entry of our current branch (actually it looks in .git/HEAD for the ‘latest’ commit and since we have not moved from the default branch this will be master, we look at .git/HEAD again in §2.6.1.)
1cat .git/HEAD 2cat .git/refs/heads/master
1ref: refs/heads/master
1e27aaa8c158e6f261f4c03aaaf173a149ad61d81
The master file in the .git/refs/heads directory contains the hash of the commit object we want to call the ‘head’ of our master branch.
Editing refs files directly is not ideal (not least because we can’t abbreviate the hash) so Git provides the update-ref command.
1git update-ref refs/heads/master 0715e7 2git log --stat
1commit 0715e707b906d30c9e395448ddc9e96acd89d5f7 2Author: vagrant <vagrant@debian-10.7-amd64> 3Date: Wed Mar 10 18:11:12 2021 +0000 4 5 Second commit 6 7 another_file.txt | 1 + 8 dir1/file11.txt | 1 + 9 file1.txt | 2 +- 10 3 files changed, 3 insertions(+), 1 deletion(-) 11 12commit f871b58596491e15ee1da91eaf0a4a6c1da3e573 13Author: vagrant <vagrant@debian-10.7-amd64> 14Date: Wed Mar 10 18:07:13 2021 +0000 15 16 First commit 17 18 file1.txt | 1 + 19 1 file changed, 1 insertion(+)
After we update the reference to the penultimate commit in our repository (0715e7) we curtail the log at that point and Git will treat that commit as the latest on the master branch.
We can easily restore the head of master using the update-ref command.
1git update-ref refs/heads/master e27aaa 2git log --stat
1commit e27aaa8c158e6f261f4c03aaaf173a149ad61d81 (HEAD -> master) 2Author: vagrant <vagrant@debian-10.7-amd64> 3Date: Wed Mar 10 18:13:55 2021 +0000 4 5 Third commit 6 7file1.txt | 2 +- 81 file changed, 1 insertion(+), 1 deletion(-) 9 10commit 0715e707b906d30c9e395448ddc9e96acd89d5f7 11Author: vagrant <vagrant@debian-10.7-amd64> 12Date: Wed Mar 10 18:11:12 2021 +0000 13 14 Second commit 15 16 another_file.txt | 1 + 17 dir1/file11.txt | 1 + 18 file1.txt | 2 +- 19 3 files changed, 3 insertions(+), 1 deletion(-) 20 21commit f871b58596491e15ee1da91eaf0a4a6c1da3e573 22Author: vagrant <vagrant@debian-10.7-amd64> 23Date: Wed Mar 10 18:07:13 2021 +0000 24 25 First commit 26 27 file1.txt | 1 + 28 1 file changed, 1 insertion(+)
The update-ref command is doing several things for us. Firstly, it allows us to use short hashes (phew!), it checks that the hash we provide is valid too. Secondly you may have notices new directories and files appearing in the repository.
1tree -a
1└── .git 2 ├── branches 3 ├── config 4 ├── description 5 ├── HEAD 6 ├── hooks 7 ├── index 8 ├── info 9 │ └── exclude 10 ├── logs 11 │ ├── HEAD 12 │ └── refs 13 │ └── heads 14 │ └── master
The new logs directory contains two new files; HEAD and refs/heads/master. These contain a record of each time we modify a reference using update-ref. Each log entry records the old hash, the new hash, the user who made the change, and a time stamp for when the change was made. These ‘logs of ref changes’ can be viewed (and manipulated) using the reflog command.
1git reflog
1e27aaa8 (HEAD -> master) HEAD@{0}: 20715e70 HEAD@{1}:
This shows the history of changes we made to the master branch refs/heads/master using update-ref. As with the log command, by default, more recent changes are shown first (reverse chronological order).
The output may look like gibberish but it’s actually simple enough. Let’s break down the first line.
- e27aaa8—This is the new hash we set.
- (HEAD -> master)—This tells us that this entry is about the HEAD reference and this currently refers to the master branch. (Actually, HEAD is an indirect reference that points us to the ‘latest commit on the active branch’, more on this shortly.)
- HEAD@{0}—This is a commit reference, specifically is says that we are referring to the ‘zeroth’ (latest) change relative to the ‘HEAD’ reference.
Okay, we should now be able to read the simpler second line with ease.
- 0715e70—The hash we set when this change was made.
- HEAD@{1}—This line refers to the ‘first’ change to HEAD, counting back from the current value (HEAD@{0}).
2.5.1 Remote refs
There is one more type of ref we need to discuss, the ‘remote refs’. These are read only refs in the sense that one does not manipulate them directly but they are maintained through interaction with remote repositories. As these have such a specialised use I’m going to leave a complete discussion to §5.1, after we have discussed working with multiple repositories in Chapter 5.
5I tend to freely use ‘default branch’ to mean the ‘current default branch’ and ‘the branch Git will use absent any other branches’. This is lazy but I think context makes clear which is implied.