I was recently asked about the “prompt git status trick”, so I thought this might be a good opportunity to dump some git tips and tricks I’ve found useful.
First, the prompt git status trick. This uses a bash function defined in git-prompt.sh. It may be automatically source
d on your system. If not, you can source
it yourself, or I’ve got a basic (i.e., not full-featured) alternative implementation you can just put in your ~/.bashrc
. But the essence is that you just stick $(__git_ps1)
in your definition of PS1
(the shell variable, not the telescope). I like to stick it in with red coloring after the working directory, which is what’s implemented below. It tells you the current branch, and the status of the work on that branch through a series of symbols:
-
<
means you’re behind and cangit merge
additional commits. -
>
means you’re ahead and cangit push
your commits. -
=
means you have all the commits. -
<>
means you’ve diverged, so you may want togit push --force
if you’re on your ticket branch orgit toss
if you’re on master. -
$
means you have work stashed (so you cangit stash pop
). -
+
means you have changes added but not yet committed. -
*
means you have changes that have not yet been added or committed.
# The following two functions provide a basic alternative for git.git/contrib/completion/git-prompt.sh
# in case it's not available
function prompt_git_dirty {
local gitstat=`git status 2> /dev/null`
local charstat=""
[[ -z $(echo $gitstat | grep "nothing to commit") ]] && charstat="\%"
[[ -n $(echo $gitstat | grep "Your branch and '.*' have diverged") ]] && echo "${charstat}\<\>" && return
[[ -n $(echo $gitstat | grep 'Your branch is ahead of') ]] && echo "${charstat}\>" && return
[[ -n $(echo $gitstat | grep 'Your branch is behind') ]] && echo "${charstat}\<" && return
echo $charstat
}
function prompt_git_branch {
git branch --no-color 2> /dev/null | sed -e '/^[^*]/d' -e "s/* \(.*\)/\[\1$(prompt_git_dirty)\]/"
}
# Setup for git.git/contrib/completion/git-prompt.sh
export GIT_PS1_SHOWDIRTYSTATE=1
export GIT_PS1_SHOWSTASHSTATE=1
export GIT_PS1_SHOWUNTRACKEDFILES=1
export GIT_PS1_SHOWUPSTREAM="auto"
type __git_ps1 1>/dev/null 2>&1 || alias __git_ps1=prompt_git_branch
if [[ ${EUID} == 0 ]] ; then
PS1='\[\033[01;31m\]\u@\h\[\033[01;34m\]:\W \#\[\033[00m\] '
else
PS1='\[\e[1;32m\]\u@\h\[\e[0;39m\]:\[\e[1;34m\]\w\[\e[1;31m\]$(__git_ps1)\[\e[0;1m\] \$ \[\e[0;39m\]'
fi
Next, a simple but helpful bash alias for updating lots of repos. Before I go offline, I like to run lsst-fetch
, which gets all the work that’s on the remote servers. It doesn’t actually update any of the branches I’m working on (it’s a fetch
, not a pull
), so it’s the best of all worlds: I’ve got the information but it hasn’t corrupted my working state. And I immediately know the state when I cd
into the repo because it’s right there on my shell prompt.
function git-recursive-fetch {
for g in `find "$1" -name .git`; do
echo Working on $g
git --git-dir $g fetch --all
git --git-dir $g fetch --all --tags
done
}
alias lsst-fetch="git-recursive-fetch $HOME/LSST"
Now, some git aliases from my ~/.gitconfig
. I have more that I’ve stolen from various places, but these are the ones I actually use.
- The first three (
stat
,co
,cp
) are basic aliases that save typing. -
git desc
describes the branch state in the same way that scons does when it chooses a version name on install, so this allows me to compare my working state with eups versions. -
git recent
provides a commit history, with one line per commit, and graphing the merges. This is what I typically use to trace what’s been going on in a repo. -
git hist
provides a fuller commit history, with the full commit messages. If I’m trying to find particular changes, I often usegit hist -p
to also include the actual changes that have been made. -
git fixup
creates a fixup to the most recent commit. You’ll need togit rebase -i
later to squash those commits, so more often I usegit amend
-
git amend
applies work to the last commit without modifying the commit message. So if I’ve discovered something left out of the most recent commit, I fix it and dogit amend -a
(to throw all changes into the commit) orgit add
followed bygit amend
(to be more selective). -
git in
shows commits that will come in from the remote (if I did agit pull
). -
git out
shows commits that will go out to the remote (if I did agit push
). -
git toss
throws away any new commits. If I have made and pushed changes on my laptop and now I need to update my state on the same repo on the cluster I will dogit fetch && git toss
. -
git nuke
cleans up files that don’t belong and throws away any new commits. -
git new
shows new commits created by the last command (e.g.,git pull
). -
git alias
lists the aliases. To find a specific alias you have togrep
the output. -
git oldest-ancestor
finds the oldest ancestor of two commits or branches (based on this). This is useful for finding the base of a branch that we need to rebase against. -
git review
displays commits (the message and patch) for careful review. You can specify the branches involved, or it will default to the current branch againstorigin/master
. This is very helpful for checking your work before sending it off for review. -
git sub
lists the commit messages for work you want to submit for review. I like to run this and paste it into the Jira ticket so the reviewer knows exactly what I’m submitting. -
git sub-patch
is the same, except the patch is included instead of just a stat of files changed. -
git follow
lets you follow the evolution of particular lines of code. The syntax isgit follow <file> <firstLine> [<lastLine>]
. If the<lastLine>
is not specified, just the single line will be followed.
[alias]
stat = status
co = checkout
cp = cherry-pick
desc = describe --tags --always
recent = "log --graph --date-order --pretty=format:'%C(yellow)%h%d%Creset %Cred%an%Creset %Cblue%cr%Creset %s'"
hist = "log --decorate --graph --date-order --stat"
fixup = commit --fixup=HEAD
amend = "!git log -n 1 --pretty=tformat:%s%n%n%b | git commit -F - --amend"
in = "!git remote update -p; git log ..@{u}"
out = "log @{u}.."
toss = "reset --hard @{u}"
nuke = "!git clean -df && git reset --hard @{u}"
new = !sh -c 'git log $1@{1}..$1@{0} "$@"'
alias = "config --get-regexp '^alias.*'"
oldest-ancestor = !bash -c 'diff --old-line-format='' --new-line-format='' <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | head -1' -
review = !sh -c 'git log --patch --reverse "${2:-origin/master}".."${1:-HEAD}"' -
sub = !sh -c 'git --no-pager log --stat --reverse "${2:-origin/master}".."${1:-HEAD}"' -
sub-patch = !sh -c 'git --no-pager log --patch --reverse ${2:-origin/master}..${1:-HEAD}' -
follow = "!sh -c 'git log --topo-order -u -L $2,${3:-$2}:"$1"'" -
Some other things from my ~/.gitconfig
that I find useful:
[color]
ui = true
[help]
autocorrect = 1
[push]
default = tracking
[rebase]
autosquash = true
autostash = true