Review
The gitrevisions
manual describes all the ways you can reference objects, and ranges of objects, in git
. If you have used git for a while and are comfortable using it, but don’t feel like a master of it; gitrevisions
can and should be your next step. I believe this manual teaches the intermediate git user the language they need for describing the structure of their git repo. The git log --graph
command shows a repository as a graph of objects, blobs(files) and trees(dirs), as it changes over time. It makes sense to me to think of revisions as coordinates in that graph. I enjoyed reading this manual, I hope you will learn something from my review of it.
Revisions
By commit with <SHA-1>
Each commit object has a single unique name within the repository, its SHA-1 hash e.g. 0bde21a
, 0bde21a835dbb052b00895b69cf6e345a2406ed0
. This hash is the ultimate identity of every revision, and can be used from anywhere. The rest of of the gitrevisions
manual is about ways to target commit objects by names other than their 40-character hash, which would be difficult to work with.
Named commits with <refname>
It’s difficult to go far in git without using refnames. These are things like HEAD
, master
, or origin/master
. Behind the scenes git treats these as aliases for refnames like ref/heads/master
, ref/remotes/origin/master
. Curious about these reference names, checkout the contents of your .git/refs
directory.
There are these other special HEAD refnames which stay consistent in their meaning, even as you change or alter the state of your repository.
VARIANT | DESCRIPTION |
---|---|
@ | An alias for HEAD, when used on its own. |
HEAD | commit on which you based changes in the working tree |
FETCH_HEAD | head of last fetched branch using git fetch |
ORIG_HEAD | created internally by git when git is doing an operation that moves HEAD in a drastic way |
MERGE_HEAD | commit which you into your branch with git merge |
CHERRY_PICK_HEAD | commit you are cherry picking when you run git cherry-pick |
Parent selection with <rev>^[<n>]
HEAD^
is the first parent of HEAD
. Some commits, such as merge commits, have multiple parents. Using ^1
will select the first, and ^2
will select the second. What helps me is to think of ^1
as a name for “Mom” and ^2
as a name for “Dad”, and the way you express maternal-Grandma would ^1^1
, parent 1 (Mom) of parent 1 (Mom). Similarly maternal Grandpa would be ^1^2
. Maybe that’s a strange example but I hope it helps with understanding how the caret works in these revisions.
Generational ancestors with <rev>~[<n>]
HEAD~3
means go back three ancestors, and is equivalent to HEAD^^^
or HEAD^1^1^1
. I find this is often a more intuitive way of targeting commits in the past than the ^
approach.
ranges
Commands like git log
operate on a set of commits. When you enter a single commit to git log you’re telling it to show you the commit itself as well as any commits in its ancestry chain. Defining ranges of commits can be used to narrow and focus the information in these commands.
Exclusion
The first, most basic way to narrow a range is to specify a prefix caret, as in ^master
. This means don’t show me anything which is an ancestor of master.
git log ^master HEAD
Shows the commits of your checked out branch since you diverged from master.
Dotted Ranges
The ..
(two-dot) range is an alternative way of excluding commit ancestors. Running git log master..HEAD
is the same as the example above using ^master HEAD
.
The ...
(three-dot) range defines a set of commits that are reachable from either, but not both of the commits.
git log master...HEAD
Shows what has happened on master since we diverged from it AND what happened on HEAD since we diverged from master. This is useful for showing two separate branches of changes I think.
The origin
is implicitly inserted if you leave off one of the commits on either side of the range.
Simple | Implied | Meaning |
---|---|---|
..HEAD | origin..HEAD | What did I do since I diverged from the origin branch? |
HEAD.. | HEAD..origin | What does the origin branch have that I don’t have since I diverged from it |
History by date with <refname>@{<date>}
This tries to get the exact commit for that reference as it was at the specified time. For example:
What was in my local master branch 1 week ago?
git show 'master@{1 week ago}'=
Shortcut to the upstream branch with <branchname>@{upstream}
Using @{upstream}
will reference the commit of the branch configured by branch.<name>.remote
and branch.<name>.merge
Shortcut to the push branch with <branchname>@{push}
Using @{push}
will reference the commit of the branch configured by push.default
. The manual describes this as a reference to “where we would push to if git push
were run while branchname
was checked out.
Refer to a file or subtree of a revision with <rev>:<path>
Targets files or a directory at the given path. For instance:
git show origin/master:README.md
OR
git diff origin/master:blog.org
To show the difference between the blog.org file in the working tree, and the one on origin/master. That first argument origin/master
is a revision. Git diff can take a path revision to show a diff between blog.org in my working tree, and the repositories README.md on origin. Why do this? I have no idea, but it works!
git diff origin/master:./README.md blog.org=
Now, throwing it all together! What’s the diff between origin master blog.org, and origin master blog.org from 1 week ago?
git diff origin/master:blog.org 'origin/master@{1 week ago}:blog.org'
Conclusion
After reading and reviewing this manual, I am planning to commands which leverage the power of revisions like git-log
, git-rebase
, and git-push
. I think I’ll be coming back to this document pretty often as I dig into these other tools. As always, thank you so much for reading this manual review!