Git tips and tricks
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 parentbranch^2
merge parentbranch~5
five first-parents backbranch:path/to/file
directly get a blobbranch@{yesterday}
local date based referencebranch@{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
- Make local repo, commit stuff, etc.
- ssh to the server: GIT_DIR=/path/to/repo git init --shared
- Next, tell the local repo about the server: git remote add origin git+ssh://hostname/path/to/repo
- Push to the server from the local repo: git push origin master
- 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 && 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