Open Source ePortfolios
Create. Share. Engage.

Exposing My Ignorance /
My git cheat sheet

There's an XCKD comic making the rounds, which sums up a lot of peoples' experience with git:


I hear this a lot, and indeed, it's sorta built into our own Mahara developer documentation. Git has an awfully steep learning curve.

But personally, I've never had much of a problem with git. Before I started at Catalyst, I had used CVS and Subversion. I remembered that when I'd switched from CVS to Subversion, all my old CVS knowledge was mostly useless and I'd had to actually read up on how Subversion works in order to avoid problems with it. So, I did the same thing when I switched to git. I spent one or two days in my first week reading the free online Pro Git book, until I understood the concepts behind it and what the commands were doing. I've never really been confused or had any problems since then.

... or have I? As I tweeted a self-congratulatory tweet about my git mastery, I remembered I actually keep an extensive "git cheat sheet" in the notes program on my computer. I consult it frequently, not so much because I find git confusing, but because I'm not great at remembering the specific syntax for commands. I keep similar cheat sheets for a lot of things; a quick search shows 19 of them at present.

Anyway, I thought my git cheat sheet might be handy to someone else, so, in the spirit of exposing my ignorance, here it is. The notes in here have been accrued over the course of nearly 6 years, some ranging from abstract information when I first learned git which I have seldom used since, and some I consult on weekly basis. Mostly the newest stuff is at the top, and the oldest stuff is at the bottom.

Git cheat sheet

Do git diff in meld (or other graphical tool)

git difftool

Uses exactly the same arguments as "git diff", but it displays the diff in the tool of your choice instead of the terminal.

Great for when you really want to just diff one function in lib/view.php.

Undo nearly anything

Accessing the weird gerrit meta/config refs

git fetch gerrit refs/meta/config:refs/remotes/gerrit/meta/config
git checkout meta/config
... make changes
git push gerrit meta/config:meta/config

... gerrit itself gives basically the same suggestion:

git fetch origin refs/meta/config:config
git checkout config
git push origin HEAD:refs/meta/config

    label-Automated-Tests = -1..+1 group Mahara Automated Testers
[label "Automated-Tests"]
    abbreviation = T
    rule = NoBlock
    value = -1 Tests failed
    value = 0 Tests not run
    value = 1 Tests passed

List all tags in a remote

git ls-remote --tags

Delete a remote tag

git tag -d
git push :refs/tags/

How to push a tag
You can push all them with:

    git push --tags

... but in some setups I've used, they've got a stupid thing set up that sends out an email for each tag, and they may complain if you push all of them at once. But it's super easy to just push one tag:

git push repo tagname

Bammo. If you have a tag and a branch with the same name, you can be more specific:

git push repo refs/tags/tagname

And to delete a tag

git push repo :refs/tags/tagname

Cherry-pick --theirs

git cherry-pick --strategy=recursive -Xtheirs

Search all git branches

git log --all

View an earlier version of a file without checking it out

git show REVISION:path/to/file

Make an existing branch track an upstream branch

git checkout localbranch
git branch --set-upstream-to remote/branchname

OR when pushing it, do
git push -u

Do a merge of two branches that were parallel for a long time

This is what I did when merging Moodle 2.2 into Massey's Stream at Moodle version 2.1. It greatly reduced conflicts and problems, but it doesn't really square with my theoretical understanding of how git merges actually work. This is taken from the Moodle forums:

git checkout -b merge_helper_branch MOODLE_[later]_STABLE
git merge --strategy=ours MOODLE_[earlier]_STABLE
git checkout -b my_custom_[later] my_custom_[earlier]
git merge --strategy-option=patience merge_helper_branch

Recover lost git commits

There are plenty of guides to this online. Here's one:

Have multiple working directories sharing the same git repo

git-new-workdir /path/to/repo /path/to/new/workingdir

Useful because I like to have multiple git instances accessible simultaneously on my computer at different locations under /var/www. This way, I only have to have one actual git repo that I keep up to date.

Check out a new remote tracking branch

Usually it's good enough to just do a git checkout of a new branch that has the same name as exactly one remote branch, and then it will automatically set it up for you (apparently this is controlled by the flag branch.autosetupmerge).

But if that doesn't work, this should:

git checkout -t remoteName/remoteBranchName

(The reason the syntax is like this, it's because "-t" works on its own, and "remoteName/remoteBranchName" is the starting point commit, and the new branch name is automatically generated)

Name of current branch's tracking remote

git config branch.`git name-rev --name-only HEAD`.remote

More generally...
LOCAL_BRANCH=`git name-rev --name-only HEAD`
TRACKING_BRANCH=`git config branch.$LOCAL_BRANCH.merge`
TRACKING_REMOTE=`git config branch.$LOCAL_BRANCH.remote`
REMOTE_URL=`git config remote.$TRACKING_REMOTE.url`

Minimize the size of a repository

This is the process I do when I'm bringing code down to a locked-down client on a USB stick, and I only need to bring info about two branches, and I need to upload the repo over a slow connection so minimizing its file size is important.

1. Remove the original remote (this will also delete all its remote tracking branches)

git remote rm origin

2. If you don't have the git remote command (because you're on a client machine with a really old git implementation) you can delete the remote branches individually using the "-r" and "-D" flags to to git branch

git branch -Dr origin/branch

3. Garbage collection. (Takes around 6 minutes)

git gc

4. You can also do aggressive garbage collection. But this takes about 15 minutes and, in testing was only able to reduce my 396MB repo to 353MB, so probably not worth it (it'd be quicker to just upload/download those extra 40MB at the client site).

git gc --aggressive

Merge in their version:
git merge -s recursive -X theirs (branch)

Abort a merge: git reset --hard HEAD

Create a new remote branch:
git checkout parentbranch
git checkout -b newbranchname
git push remotename refs/heads/newbranchname
git checkout --track -b newbranchname remotename/newbranchname

-- or the short version, to make a new branch based on your local version of a particular branch:

git push remotename yourlocalbranch:newremotebranch

Delete a remote branch
git push remotename :refs/heads/branchname

Create a new branch from the existing workspace's head (keeping any existing local changes, so you can commit them to the new branch if you want)

git checkout -b newbranchname

Then when you're done with the new branch, you can do git checkout oldbranch to go back to the old branch, and git merge newbranch to merge in the changes

Commit ranges:
start..end (In a linear history, this is "after...through". In a more complex history it's "exclude-from-here-earlier..include-from-here-earlier", or [end-and-its-parents] - [start-and-its-parents])
• means the set of commits reachable from [end] that are not reachable from [start] (by traveling along the directed parent links of [start].
• In other words, it's the set [end] and all its ancestors minus [start] and all its ancestors.
• In still other words, everything leading up to [end], minus everything leading up to [start]
• or "Give me all commits that are reachable from [end], and don't give me any commit leading up to and including [start]".

In git log you can say git log ^X Y to do the log of Y minus X and all its parents, same as git log X..Y

[start] and [end] can be: (from man git-rev-parse)
• The full SHA1 object name or a unique substring of it.
• An output from git-describe optionally follewed by a dash and a number of commits, followed by a dash, a g, and an abbreviated object name
• A symbolic ref name.
? "master" typically means commit object referenced by refs/heads/master. Also "heads/master" or "tags/master"
? "HEAD" names the commit  your changes in the working tree are based on
? FETCH_HEAD records the branch you fetched from a remote repository with your last git-fetch invocation
• a ref followed by the suffix @ with a det spec in a brace pair a879uo9087@{yesterday}, to specify the value of the ref at a prior point in time. That's your local ref at the given time.
• ref, @, and an ordinal number in braces a la {1} {5} etc, to specify nth-prior value of that ref. master@{1} is the immediate prior value of master. HEAD@{1}, likewise.
• @ with an empty ref applies to the current branch, so if you've got mdl19-stable checked out, @{1} = mdl19-stable@{1}
• @{-} means the nth branch checked out before the current one
• Suffix ^ to get the first parent of that commit (^2 for second parent, etc, would be if it's a merge with multiple parents). This can be nested so ^^ is parent of parent.
• Suffix ~ to get parent, ~2 to get parent's parent, ~3 for parent's parent, etc. ~2 = ^^. ~3 = ^^^, etc.
• :/'some text' - youngest matching commit whose message starts with specified text and is reachable from any ref.
• ref:/some/path - blob or tree at given path in the tree-ish object named by the part before the colon
• and more!
start...end (notice the three dots)
    - means give me the commits reachable from start and end, but not both. It's the XOR of their history.

You can actually list as many commits as you want. Put them without a caret prefix to include their ancestors, with a caret prefix to exclude:

^G D
^D B
^D B C
F^! D

If you mix careted and uncareted ones, it does a union of the uncareted first, then minuses all the careted ones.

Maps branch names in the remote repository to branch names in the local repository.




1. Generate a patch:
git format-patch -1

2. Apply patch:
    git am

or if that fails,

    git apply --reject

A git patch to be applied without git

    cd /var/www/source
    git diff --no-prefix HEAD~1 HEAD > patch.txt
... and then
    cd /var/www/dest
    patch --dry-run -p0

... and if that's successful
    patch -p0

Check out just a subdirectory of a git repo

Checking out just a subdirectory is a key SVN task, but git has piss-poor support for it. The recommended course is that if the subdirectory is meant to be on its own, you split it off into a separate repo, and then recombine it as a submodule. Other options are these:

1. If the repo in question is actually an SVN repo (which is why it was made with the assumption you'd check out only a subdirectory of it), you can do this by telling it that one directory is the trunk:
    git svn clone -s -r52272:HEAD -T trunk/phase3

If you don't mind never communicating with the parent repo ever again, you can clone the remote repo and then use git filter-branch to kill all the parts not relating to the subdirectory you're concerned with
    git filter-branch --subdirectory-filter path/to/dir -- --all
    git reset --hard
    git gc --aggressive
    git prune

Check out just some of the history of an svn rgit epo

git svn has a -Rn option, which lets you check out only certain revisions, a la
git svn clone -T path/to/trunk -R52272:HEAD

Look at the other versions during a merge
git show :1:filename - the common ancestor
git show :2:filename - the HEAD
git show :3:filename - the remote version
git checkout --ours path/to/file  shows you the version from HEAD
git checkout --theirs path/to/file  shows you the version you're merging to

The instructions it gives you when a rebase fails
(mediawiki-local-1-16-0beta2)aaronw@aaronw:~/www/moodle-itms-wiki$ git rebase rel1_16_0beta2
First, rewinding head to replay your work on top of it...
Applying: hack to allow db setup when access already configured
error: patch failed: config/index.php:224
error: config/index.php: patch does not apply
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
Auto-merging config/index.php
CONFLICT (content): Merge conflict in config/index.php
Failed to merge in the changes.
Patch failed at 0001 hack to allow db setup when access already configured

When you have resolved this problem run "git rebase --continue".
If you would prefer to skip this patch, instead run "git rebase --skip".
To restore the original branch and stop rebasing run "git rebase --abort".

The instructions it gives you when a cherry-pick fails
(mdl19-hnzc)aaronw@aaronw:~/www/moodle-housingnz$ git cherry-pick d7cb528c94
Automatic cherry-pick failed.  After resolving the conflicts,
mark the corrected paths with 'git add ' or 'git rm ' and commit the result.
When commiting, use the option '-c d7cb528' to retain authorship and message.

How git merging works:
The actual details vary depending on what merge strategy you tell it to use, but the default is as follows.
1. You checkout branch A
2. You issue the command "git merge branchB"
3. Git uses "git merge-base" on the backend to find the most recent common ancestor of A & B (or if A & B have a complicated history with two most recent common ancestors that are tied, it finds the most recent common ancestor of those two). Call this ancestor C
4. Git calculates the diff of C -> B
5. Git applies this diff to A
6. If the diff cannot be applied cleanly, then you have a conflict.

How git cherrypicking works
1. You checkout branch A
2. You issue the command git cherrypick B
3. Git calculates the diff between B and B~ (which is tricky if B has multiple parents)
4. Git applies this diff to A
5. If the diff cannot be applied cleanly, you have a conflict.

The files git mergetool generates:

If you are merging branch A into branch B:

• LOCAL: The version on the local branch (A) before the merge
• BASE: The most recent common ancestor of (A) and (B). The normal git merge functionality is to calculate the diff that transforms BASE into REMOTE and apply it to LOCAL.
• REMOTE: The version on the branch (B) that's being merged in
• BACKUP: The original conflict file generated by the merge operation

If you are doing a cherrypick of commit X from branch B
• LOCAL: The version currently in HEAD
• BASE: How B looks at X~
• REMOTE: How B looks at X

If you are doing a revert of commit X from branch B
• LOCAL: The version currently in HEAD
• BASE: How B looks at X
• REMOTE: How B looks at X~

Three-way merge:


So the instructive thing is to look at the difference of Base and Remote and see how that might apply to Local

Check git logs for a REVERSE grep match:

git log --oneline 73c5bebd..HEAD | grep -vP '^[0123456789abcdef]+ Updated the 19 build version to [0-9]{8}$' | grep -oP '^[0123456789abcdef]+\b' | while read LINE; do git log --stat -n 1 ${LINE}; echo " "; done > ~/Documents/WR71594/upstream-changes-july.txt

breaking it down:
git log --oneline 73c5bebd..HEAD
? Prints the git logs in the range we're interested. The output for each one is its SHA id and subject line
grep -vP '^[0123456789abcdef]+ Updated the 19 build version to [0-9]{8}$'
? "grep -v" to remove the ones that match a particular pattern.
grep -oP '^[0123456789abcdef]+\b'
? "grep -o" to just print the SHA id for each line
while read LINE; do
? Execute a command for each line of stdin (which will have one SHA id on each line)git
git log --stat -n 1 {line}; echo " ";
? Print out each log file, with one empty line between them