Git Tricks

Always remember the difference between “log” and “diff”. Log lists commits (optionally showing code changes using -p/--patch), while diff shows the code changes. Log can help you understand changes over time, while diff can help you get a holistic understanding of those changes.

Learning to learn

The first thing to know about git is how to read the manpages. The man pages can be accessed by running these from the command line: “man git”, “man git-diff”, “man git-log”, etc. Note that there is a top-level “git” command (one that doesn’t have “diff”/”log”/etc following it), and that it does have its own man page. It introduces all the git commands and the terminology/syntax.

Git Diff

For example, if you know someone implemented a feature but it took them multiple commits, you can use git-log to find the commit just before the first commit to implement the feature (call it commit hash “A” where “A” actually looks something more like “d34b9ec…”), and the last commit that was required to implement the feature (call it “B”), and run:

git diff A..B

The “A..B” syntax appears in git manpages as “<commit>..<commit>”.

That shows you all the code changes from all the commits from one commit (“A”) to the other commit (“B”), joined together (aggregated) as a single unified diff (e.g. so all code changes to a single file show up as a single diff for that file, rather than a list of commits with diffs for each commit).

I don’t think that I ever use any of the additional options of git-diff. Perhaps --name-status, which lists only the filenames and type of modification (added/deleted/moved/modified/type modification [e.g. permissions, from real file to symbolic link, etc]) instead of the actual code changes.

HEAD and “@”

“@” is a relative pointer that refers to “HEAD”, where “HEAD” is the relative position of your current pointer. If you’re on a branch, that’s the most recent commit on that branch. If you check out a specific commit, then it’s that commit. “@” can usually be used in place of a commit hash, for example, from the example above, you could write “git diff A..@”, which shows all the code changes from commit “A” to wherever you are right now (assuming “A” is an ancestor of where you are right now).

@~1 (“~” is a tilde) means “the direct parent of HEAD”. @~2 means “the direct parent of the direct parent of HEAD”, etc. There are more fancy tricks using caret (“^”), which is for dealing with a second (or nth) parent, e.g. in merge commits. These commands can be strung together like this: “git diff A~4^2~3..A” (from A, go to the direct first parent 4 times, then go to the second parent from that commit, then from there go to the first direct parent 3 times) – see the diagram below. If you try to use that syntax to reference a commit that doesn’t exist, you get a message “fatal: ambiguous argument ‘A~1^2~1..A’: unknown revision or path not in the working tree.” Note that you can also use “~” and “^” on commit hashes, for example “A~2”.

...
|
* < commit "A"
|
* < each "*" refers to a commit. this
|   is "A"s first direct parent.
*
|
*
|
* < 4th direct parent of A,
|\  i.e. A~4
* * < 2nd parent from there, i.e. A~4^2,
| |   the one on the right, instead of
* *   the 1st parent (which is the one
| |   on the left)
* *
|/
* < 3rd direct parent from there
|   i.e. A~4^2~3 (note that this didn't
|   need to be in a merged commit -
|   that's just a coincidence.
...

Notice that there could be a lot more commits tangled in with the ones that are shown here. This is just the simplified graph to make it easier to read/understand.

So in the above “git diff” example, these could be useful commands: “git diff @~1..@” – show only the changes from the most recent commit to now; “git diff @~2..@~1” – show the changes from 2 commits ago to 1 commit ago.

Git Log

“git log” shows you all the commits from one commit hash to the next:

git log A..B [PATH ..]

Where [PATH ..] is an optional list of specific files or directories to list the changes of; by default, show all changed files/directories from the specified commits.

Additional useful flags here:
--name-status lists files that were changed in each commit
--author=AUTHOR_GREP show commits only from specific authors
-p/--patch also show the code changes in each commit – these are like individual git-diff commands between each pair of commits.
--graph on the left of the commits, show a colorized illustration of the relationships of the branches to each other, e.g. where branches split and re-join. In busy repositories with lots of branches and developers, these can become pretty intricate and interesting, but ideally they’d stay simple – a complicated graph can hint at a problem (like use of too many branches, or not merging master back into branches often enough).

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.