A Git survival guide
Roughly orthogonal git commands
To see the current status of all branches in the repository
git log --graph --decorate --all --onelineIt is my most used git command. I run it after almost every other git operation to maintain a clear understanding of the current repository state.
For a slightly better display but much more complicated command line
PAGER="less -S" \ git log \ --graph \ --decorate \ --all \ --pretty=format:"%C(auto)%h %<(7,trunc)%C(auto)%ae%Creset%C(auto)%d %s [%ar]%Creset"If you only want to see the topology of the tree instead of every commit then add
--simplify-by-decoration. (You might like to read Ian Miell’s “git log – The Good Parts”.)To see the difference between different revisions (or my working copy)
git diff --color-movedand it is useful to use
--word-diffor--ignore-whitespace.This is my second most used git command.
To make a commit
git commit --verbose --patchTo see the full history of my current branch
git log --patch --color-movedTo revert an uncommited change
git checkout --patchTo create a new branch from the current one
git checkout -b <new branch>To switch to an existing branch
git checkout <existing branch>To add a new file
git add --intent-to-add <filename>(or
-N). I don’t usegit addbecause it adds the file to the staging area. I don’t want to bother with the staging area.To rebase
git rebase ...(See
merge.conflictStyle=diff3)To be really specific about which sequences of commits you are rebasing where, use
git rebase -i --onto <target> <from> <to>The latter will interactively rebase the collection of patches
<from>..<to>onto<target>(<from>is exclusive,<to>inclusive). It’s always a reasonable idea to rebase interactively so at least you can see the list of commits that will be rebased. It might turn out you got some of the arguments wrong and then you can bail out early (by deleting all the text from the interactive rebase instruction file, saving it, and closing the editor).To split a commit in two use my
git-split.shscript.sh <path>/git-split.sh bash <commit-id-to-split>There are many, many guides on the web about “how to split a git commit”. All the ones that I have seen give some version of the “Splitting Commits” recipe from the
git rebaseman page. That recipe is fiddly for a non-expert and one can easily find oneself in a confused state. On the other hand the recipe can easily be made robust and packaged in a script that gives the user helpful hints and can’t fail – so that’s what I did!Inform my local repository of the state of the remote
git fetchThen I can refer to remote branches via the names
<remote>/<branch-name>, for exampleorigin/master.To see the status
git status -unoBy default
git statusshows untracked, unignored files. I don’t need to ignore files because I usegit commit --patch. On the other hand I don’t want to see untracked, unignored files ingit status. Instead I usegit status -unowhich supresses display of untracked files.grep the repository, without looking at untracked files or the
.gitdirectorygit grep ...
Global config settings
To avoid having to press
<Enter>after each choicegit config --global interactive.singlekey true(in
git commit --patch,git checkout --patch,git stash --patch, etc.)interactive.singleKey=truerequires the Perl moduleTerm::ReadKey. On Debian this is available in thelibterm-readkey-perlpackage.To be able to resolve rebase/merge conflicts
git config --global merge.conflictStyle diff3It is literally impossible to resolve rebase/merge conflicts without using the
diff3conflict style.
Things I don’t do
I never use the staging area. It is a piece of mutable state and I’d rather remove it from my mental model. You do not need to touch the staging area if you use
git commit --patch. (Apparently empirical research has validated the hypothesis that the staging area is a major pain point for users in practice.)I never use
.gitignore. You don’t need it if you usegit commit --patch.I almost never
git merge. If I want to do a fast forward “merge” (which is not really a merge at all) then I dogit merge --ff-onlyand if I want to bring my branch up to date with the remote then I do
git rebase <remote>/<branch>I never
git pull.git pulljust doesgit fetchfollowed bygit merge. I very rarely want to merge and when I do I will be explicit about it. More likely I will do agit fetchfollowed bygit rebase origin/master.I never stash for the long term. Committing to a temporary branch is a much more robust solution but sometimes stashing is too convenient to not use.
If you have “stashed” by committing then you can pop your “stash” by using
git cherry-pickand doinggit reset HEAD^. You probably then want to delete the temporary branch.I never
commit --amend. Interactive rebase (see above) allows you access to the same functionality without needing to remember a different command.I rarely use
cherry-pick. I find that interactive rebase provides a better approach to reaching my goal.
Explicit git
Many git commands make it possible to perform an action on a commit or on a remote without checking out that commit or without adding that remote.
Show a file from a commit without checking that commit out
git show <commit>:<filename>Create a new branch at an existing revision without checking out that revision first
git checkout -b <new branch name> <existing revision>Push a branch to the remote without explicitly adding that remote or explicitly adding a remote tracking branch
git push <url of remote repository> <local revision>:refs/heads/<name of remote branch>The
refs/headsprefix is only necessary if the branch doesn’t already exist on the remote. If the branch does exist then the prefix is redundant but doesn’t do any harm.Instead of
<url of remote repository>you can use a previously-defined remote name, likeorigin, if you want.Fetch a branch from a remote without explicitly adding that remote
git fetch <url of remote repository> <branch>Then you can do whatever you want with
FETCH_HEADand you will not have added any new remote. For example, you cangit checkout -b <new branch name> FETCH_HEAD.As above, instead of
<url of remote repository>you can use a previously-defined remote name, likeorigin, if you want.
What a rebase/merge conflict is
When you are presented with a rebase or merge conflict your job is to merge the semantic content of both patches. In particular, you should not be choosing just “ours” or just “theirs”. See Resolving git rebase conflicts for more information.
Advanced stuff
All the things your branch has ever been
Are you scared that you’ve lost commits because of a rebase gone wrong
or some other git mystery? Try this command with the name of your
branch in the place of <branch>.
BRANCH=$(git branch --show-current); \
PAGER="less -S" \
git log --graph \
--decorate \
--pretty=format:"%C(auto)%h %<(7,trunc)%C(auto)%ae%Creset%C(auto)%d %s [%ar]%Creset" \
$(git log --walk-reflogs $BRANCH --format="%H")
You will be
presented with a tree that contains everything that has even been on
the given branch. You can then reset your branch to one of its
earlier versions and perhaps cherry-pick into it commits from other
versions.
All the changes to files you care about
git fetch && PAGER="less '+/^commit'" git log --patch --since="1 week" --extended-regexp --author='^([^T]|.[^o])' -- $(git rev-list --since="1 month" --author="Tom Ellis" origin/master | while read commit; do git diff-tree --no-commit-id --name-only -r $commit; done)
Darcs translation
darcs whatsnew=git diffdarcs revert=git -c interactive.singlekey=true checkout --patchdarcs rec=git commit --patch --verbosedarcs pull=git fetch && git rebase origin/master && git push --forceThe git version is somewhat different to
darcs pullbecause instead of automatically merging on a pull we explicitly rebase on the target branch. This is a bit saner and gets conflicts out of the way explicitly and sooner.