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 currentMaybe 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 --onelineZsh 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 pullAdd all changes or all changes to existing files
$ git add -AAdds all files and file deletes, so you don't need to enter them manually.
$ git add -uSame, 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..mastergit 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/subdirShow the latest commit that affects the my/subdir path on the branch "staging" with the patch.
$ git log --statShow the changed files only.
$ git log --author=fooOnly commits by author foo.
$ git log --stat -- subdirectoryShow 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 -snShows a "top contributors list" by commits.
Selecting things
- Full SHA1
- Partial SHA1 - at least 4 characters and unique
branch^first parentbranch^2merge parentbranch~5five first-parents backbranch:path/to/filedirectly 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 --cachedI 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..masterWhat changed in the last three commits?
$ git diff HEAD^3Ignore whitespace?
$ git diff HEAD^ --word-diffWhat's different between my branch and some other branch in subdirectory (or file)?
$ git diff my_branch..master subdirectoryRemember, git diff outputs an applicable patch:
$ git diff > my.patch
$ git apply < my.patchFor 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.patchGrep-fu
$ git grep -e foo --or -e bar --not -e bazGrep for foo or bar, but exclude the expression baz.
$ git grep --untracked fooInclude untracked files in grep.
$ git grep --cached fooInclude cached (staged) files in grep.
$ git grep --no-index foo /var/barUse "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 masterThen create a branch and check it out:
$ git checkout -b local_namePush the branch to the remote
$ git push -u origin local_nameLater on, merge it:
$ git checkout master$ git merge local_nameLook at the unmerged branches:
$ git branch --no-mergedLook at branches that contain a particular commit (e.g. when cherrypicking):
$ git branch --contains 1234abcdGit hooks
Installing:
cp pre-commit.sh .git/hooks/pre-commit
chmod a+x .git/hooks/pre-commitExamples:
- 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 applyYou 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/fileSee who changed what.
$ git blame abce1234^ path/to/fileSee 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 remoteAdding 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 masterKeeping 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 pushBranching
List all branches:
$ git branch -aUnmerged branches:
$ git branch --no-mergedCheckout a branch (protip, zsh does git branch completion):
$ git checkout nameIf 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 nameCreate a orphan branch (no history) for storing documentation or resources:
$ git checkout --orphanMerge branches:
$ git mergeApply a commit from another branch:
$ git cherry-pick sha1_of_commitReset the branch to the current head:
$ git reset --hard HEADWhen a branch tracking a remote has become outdated (e.g. you are on staging now but your commits have diverged):
$ git reset --hard origin/stagingReset a single file to HEAD:
$ git checkout -- path/to/fileReverting (unapplying) a bad commit:
$ git revert sha1_of_commitTaking the current branch and creating a version that's squashed into one commit:
$ git checkout -b new-branch
$ git rebase -iDuring 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
123Get 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_nameReally delete a local branch, even if git complains about it in the previous command:
git branch -D some_branch_nameDelete a remote branch, e.g. in Github:
git push origin :some_branch_nameThe 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