This post is basically a collection of things I think I should remember about git: setup, commands, multiple repos, more obscure options etc.

Configuration

Setting up short aliases for you git commands changes everything.

Two things: enable colors, and make git push push the current branch to a branch of the same name (and not all the branches, which is the default). This means when you do git push, it pushes your working branch but not other branches.

git config --global color.ui true
git config --global push.default current

Maybe worth it:

git config branch.autosetuprebase always
git difftool -t <tool>
git mergetool -t <tool>

Zsh/bash aliases

These are small things, but they make things so easy. I instinctively type gs after changing directory nowadays.

alias ga='git add'
alias gp='git push'
alias gl='git log'
alias gs='git status'
alias gd='git diff'
alias gdc='git diff --cached'
alias g='git'
git config --global alias.lg "log --graph 
  --pretty=format:'%Cred%h%Creset-%C(yellow)%d%Creset %s %Cgreen(%cr)%C(bold blue)<%an>%Creset'
  --abbrev-commit --date=relative"

Apparently, the "git lg" above can be replicated fairly well with:

git log --decorate --graph --oneline

Zsh shell prompt

This one is way simpler than those fancy ones offered elsewhere, but it works well for me:

# get the current branch
parse_git_branch () {
    git branch 2> /dev/null | grep "" | sed -e 's/ (.*)/ @\1/g'
}

# colors
BLACK=$'\033[0m'
RED=$'\033[38;5;167m'
GREEN=$'\033[38;5;71m'
BLUE=$'\033[38;5;111m'
YELLOW=$'\033[38;5;228m'
ORANGE=$'\033[38;5;173m'

function precmd() {
    export PROMPT="%{$fg_bold[white]%}[%1~%{$reset_color%}%{$YELLOW%}$(parse_git_branch)%{$fg_bold[white]%}]%{$BLACK%}%\ "
}

Managing multiple git repositories

A fairly simple way of doing this is to add a function that you can use to run the same command in multiple directories. This one is for zsh:

gr() {
  local cmd="$*"
  for dir in /home/m/repos/{one,two,three}; do
   (
    print "nin $dir"
    cd $dir
    eval "$cmd"
   )
   done
}

alias grs="gr git --no-pager status -sb"
alias grl="gr git --no-pager log --decorate --graph --oneline -n 3"
alias grd="gr git diff"
alias grdc="gr git diff --cached"
alias grn="gr npm ls"

In the example below, I 1) switch all working repos to master, 2) fetch the recent remote changes, 3) check the status of every repository, and 4) after going into those couple of dirs and stashing stuff (not shown in detail), pull all repos to update them

gr git checkout master
gr git fetch
grs
in /home/m/repos/one
## master
in /home/m/repos/two
## master...origin/master [behind 8]
in /home/m/repos/three
## master...origin/master [behind 4]
 M Makefile
gr git pull

Add all changes or all changes to existing files

$ git add -A

Adds all files and file deletes, so you don't need to enter them manually.

$ git add -u

Same, but doesn't add files that didn't exist before.

Log-fu

$ git log -p

.. to show the patches alongside the log

git log --author="git config --get user.name" --pretty=format:'%h %an %Cred%ar %Cgreen%s' v1.0.1..master

git supports friendly dates specs:

git log HEAD@{one.day.ago}

Shows the commits that you've made between "refactor" and "master", e.g. for emailing a changelog with your commits:

6591466 Mikito Takada 7 months ago Port over more code
1f464fb Mikito Takada 7 months ago Refactor window
41a3ec9 Mikito Takada 9 months ago Handle switching from window to root window
$ git log staging -1 -p -- my/subdir

Show the latest commit that affects the my/subdir path on the branch "staging" with the patch.

$ git log --stat

Show the changed files only.

$ git log --author=foo

Only commits by author foo.

$ git log --stat -- subdirectory

Show the commits affecting only "./subdirectory", with a summary of the files (--stat).

For example:

$ git log --stat -- .

Shows the summary of commits affecting the current directory.

$ git log --grep='.foo.'

Grep for a specific log message.

$ git log --since="1 week ago" --until="2 weeks ago"

Filter commits by date.

$ git shortlog -sn

Shows a "top contributors list" by commits.

Selecting things

  • Full SHA1
  • Partial SHA1 - at least 4 characters and unique
  • branch^ first parent
  • branch^2 merge parent
  • branch~5 five first-parents back
  • branch:path/to/file directly get a blob
  • branch@{yesterday} local date based reference
  • branch@{5} the 5th last value of branch (locally)

Diff-fu

Differences between the staged files and the last commit:

$ git diff --cached

I use that a lot just before committing the changes to check what my commit changed.

So git diff is for the files that are unstaged, while git diff --cached is for staged files.

What's different between my branch and some other branch, e.g. master?

$ git diff my_branch..master

What changed in the last three commits?

$ git diff HEAD^3

Ignore whitespace?

$ git diff HEAD^ --word-diff

What's different between my branch and some other branch in subdirectory (or file)?

$ git diff my_branch..master subdirectory

Remember, git diff outputs an applicable patch:

$ git diff > my.patch
$ git apply < my.patch

For example, I once used git diff to generate a patch for a folder that was missing it's .git directory, and then applied that patch to a live environment (patching a test VM):

git diff --no-index --diff-filter=MRC -- ./lib ../myapp/lib > my.patch
cd ../myapp/
git apply ../myapp-git/my.patch

Grep-fu

$ git grep -e foo --or -e bar --not -e baz

Grep for foo or bar, but exclude the expression baz.

$ git grep --untracked foo

Include untracked files in grep.

$ git grep --cached foo

Include cached (staged) files in grep.

$ git grep --no-index foo /var/bar

Use "git grep" instead of regular grep, grep for foo in /var/bar. Compared to regular grep, git grep is nicer due to the colors and paging defaults.

List all require('foo') statements content (sed to filter out just the string).

git grep -h -e "require[(]'[a-zA-Z]" | sed -E "s/.require(.([a-zA-Z][^']+).)./\1/" | sort | uniq

Creating a feature branch and merging it back

Checkout the branch you want to base your work on:

$ git checkout master

Then create a branch and check it out:

$ git checkout -b local_name

Push the branch to the remote

$ git push -u origin local_name

Later on, merge it:

$ git checkout master
$ git merge local_name

Look at the unmerged branches:

$ git branch --no-merged

Look at branches that contain a particular commit (e.g. when cherrypicking):

$ git branch --contains 1234abcd

Git hooks

Installing:

cp pre-commit.sh .git/hooks/pre-commit
chmod a+x .git/hooks/pre-commit

Examples:

  • deploying a static website
  • checking commits and doing cleanup
  • integration with external systems
  • email notifications

Deploying over git, with a post-update checkout hook

Git works over ssh

If all you need is a place for storage, you just need sshd and disk space.

Setting up a remote repository

  1. Make local repo, commit stuff, etc.
  2. ssh to the server: GIT_DIR=/path/to/repo git init --shared
  3. Next, tell the local repo about the server: git remote add origin git+ssh://hostname/path/to/repo
  4. Push to the server from the local repo: git push origin master
  5. Clean up the local repo so that you can pull from the remote server: git config branch.master.remote origin git config branch.master.merge refs/heads/master

Excluding files

~/.gitignore for globally excluded files:

npm-debug.log
.DS_Store
.DS_Store?
.Spotlight-V100
.Trashes
Icon?
ehthumbs.db
Thumbs.db

.git/info/exclude for files you want to exclude without changing the gitignore file:

conf/*

.gitignore for a checked in list of excluded paths:

node_modules/

Whitelisting a directory (note: needs the rest of the path):

static/mixu.net/blog/*
!static/mixu.net/blog/assets/

Stashing changes

$ git stash
$ git stash list
$ git stash apply

You can specify a specific stash item from the git stash list output to apply or remove:

$ git stash apply stash@{0}
$ git stash drop stash@{0}

Blaming

$ git blame path/to/file

See who changed what.

$ git blame abce1234^ path/to/file

See the blame, starting one commit before abce1234. E.g. when you need to trace back a particular line of code in history.

Working with remotes

Changing from Github to Bitbucket

$ git remote -v # look at remotes
$ git remove rm origin # delete
$ git remote add origin git://addr/to/repo # add new remote
$ git push -u origin master # push to new remote

Adding a upstream repo to pull in new changes from a forked project

$ git remote add upstream git://addr/to/repo
$ git checkout master &amp;&amp; git pull upstream master

Keeping the number of merges lower on small commits

For small commits made on top of a frequently changing branch like master, you might want to rebase your local changes on top of the current remote before you merge a feature branch (more in depth):

$ git pull --rebase
$ git push

Branching

List all branches:

$ git branch -a

Unmerged branches:

$ git branch --no-merged

Checkout a branch (protip, zsh does git branch completion):

$ git checkout name

If there is a branch called origin/name and you checkout name, then name will be set to track origin/name automatically (that means that git pull works, for example).

Create a feature branch based on the current branch:

$ git checkout -b name

Create a orphan branch (no history) for storing documentation or resources:

$ git checkout --orphan

Merge branches:

$ git merge

Apply a commit from another branch:

$ git cherry-pick sha1_of_commit

Reset the branch to the current head:

$ git reset --hard HEAD

When a branch tracking a remote has become outdated (e.g. you are on staging now but your commits have diverged):

$ git reset --hard origin/staging

Reset a single file to HEAD:

$ git checkout -- path/to/file

Reverting (unapplying) a bad commit:

$ git revert sha1_of_commit

Taking the current branch and creating a version that's squashed into one commit:

$ git checkout -b new-branch
$ git rebase -i

During the rebase, only pick one commit and squash the rest.

Total number of remote branches

First, remove local branches that do not exist on origin (--dry-run if you want to preview):

$ git remote prune origin
$ git branch -r | wc -l
 123

Get the latest commit of all remote branches

[gist id=2313880]

Get the latest commit of all remote branches, and summary, ordered by age

Use this Node script:

https://gist.github.com/91ba46dee32b221b3a84

Delete a local or remote branch

Delete a local branch:

git branch -d some_branch_name

Really delete a local branch, even if git complains about it in the previous command:

git branch -D some_branch_name

Delete a remote branch, e.g. in Github:

git push origin :some_branch_name

The reason for that syntax is that you can do git push origin local_branch_name:remote_branch_name so what that line above is doing is essentially pushing NULL to the remote branch, thereby killing it off.

In git >= v1.7.0:

git push origin --delete <branchName>

Things I should still try out

git add -i
git reflog