I have just created a Git presentation. The presentation is named Git, Practical Tips, and it contains good practices that I have picked up during my four years as a Git user.
The presentation consists of six parts, A quick introduction; History manipulation with merge, rebase and reset; Finding with Git; Configuration; Under the hood; and Interacting with Github.
If you find this interesting and would like to hear a very practical presentation about Git tips and tricks, feel free to contact me :)
In this post I will describe how to configure Git to work well from the command line. It consists of two main parts, Git configuration and Bash configuration.
I will only describe some select samples of my configuration here. If you want to see more, my configuration files are on Github.
Git Configuration
The Git configuration part is just a bunch of aliases I use. Some are simple
and some are more advanced. The aliases are declared in my global git config
file, ~/.gitconfig
under the [alias]
tag. Here are some of most important
ones.
git add --patch
[alias] ap = "add --patch"
git ap
(git add --patch
) is awesome. It lets me add selected parts of the
changes in my working directory, allowing me to create a consistent commit with a
simple clear commit message.
git add --update
au = "add --update"
git au
adds all the changed files to the index. I use it mainly when I forget
to remove a file with git rm
and instead remove it with rm
. In this case
Git will see that the file is missing but not staged for removal. When I run
git au
it will be added to the index as if I had used git rm
in the first
place.
git stash save -u
ss = "stash save -u"
git ss
stashes everything in my working directory, including untracked files
(-u
). The reason I use git stash save
instead of just git stash
is that
it allows me to write a message for the stash, similar to a commit message.
git amend
amend = "commit --amend -c HEAD" amendc = "commit --amend -C HEAD"
git amend
lets me add more changes to the previous commit. It is very useful
when I forget to add a change to the index before I commit it. It amends the
new changes in the index and lets me edit the old commit message. git amendc
does the same thing but reuses the old commit message.
git alias
alias = "!git config -l | grep alias | cut -c 7-"
git alias
shows me all my aliases. Starting with a bang (!) is necessary to
execute arbitrary bash commands. Note that the git
command must be included.
The code in the alias means, list configuration, find aliases, show characters
7 and on.
git log --diff-filter
fa = "log --diff-filter=A --summary" fd = "log --diff-filter=D --summary"
git fa
(find added) and git fd
(find deleted) shows me a log of commits
where files were added and deleted respectively. It is great for finding out
how and when my files get deleted. I use it with a filename git fd
my-missing-file.rb
or with with grep
, git fd | grep -C 3 missing
.
grep -C 3
means shows me 3 lines of context around the matching line.
git log-pretty
l = "!git log-hist" log-hist = "!git log-pretty --graph" log-pretty = "log --pretty='format:%C(blue)%h%C(red)%d%C(yellow) %s %C(green)%an%Creset, %ar'"
git l
is my main logging command it and prints a beautiful compact log. When
I reuse an alias I must use the shell command alias, the bang (!), since Git does
not allow me to reference an alias from another directly.
log = The log command --graph = Text-based graphical representation --pretty='format' = Format according to spec %C(color) = Change color %h = Abbreviated commit hash (6b266c2) %d = Ref names (HEAD, origin/master) %s = Subject (first line of comment) %an = Author name %ar = Author date, relative
git log --simplify-by-decoration
lt = "!git log-hist --simplify-by-decoration"
git lt
(log tagged) uses --simplify-by-decoration
to show a list of
"important" commits. Important in this case means commits that are pointed to
by a branch or tagged. It reuses the log-hist
alias above.
Bash Configuration
I used to have a bunch of aliases, such as ga
, gd
, etc. but, now I use
my Git aliases instead. But I still have configuration for command completion
and a nice informative prompt.
function g()
I use the git
command more than any other command during a days work. git status
is the subcommand I use mostly. I have optimized for this by creating a
function g()
that has status
as its default argument.
# `g` is a shortcut for git, it defaults to `git s` (status) if no argument is given. function g() { local cmd=${1-s} shift git $cmd $@ }
The g()
function gives me a lot of power out of a single character.
$ g ## master M README.md ?? doc.md $ g l * 4f71f8d (HEAD, heroku/master, master) Send 404 for missing ... * ec00879 Added support for options Anders Janmyr, 5 weeks ago * 09c178f (origin/master) id cannot be a number Anders Janmyr, 6 weeks ago * e561d03 Send status and send in one call Anders Janmyr, 6 weeks ago * 9615be5 Added some more logging Anders Janmyr, 6 weeks ago * de4730e Improved the code somewhat Anders Janmyr, 6 weeks ago * 1f3f763 Added allow methods header Anders Janmyr, 6 weeks ago * ca3065c Added filter to documentation Anders Janmyr, 6 we
function gg()
My second (and last) function is gg()
.
# Commit pending changes and quote all arguments as message function gg() { git ci -m "$*" }
gg()
allows me to type a commit message without any quotes.
$ gg Added todo list to the Readme [master 98556af] Added todo list to the Readme 1 file changed, 1 insertion(+)
bash-completion
Installing bash-completion gives me command completion for commands, subcommands and more.
# An example $ git rem<TAB> sh<TAB> o<TAB> # will complete to $ git remote show origin
I use Homebrew to install Git, brew install git
. It gives me a new version of
Git. It also installs git-completion.bash
in /usr/local/etc/bash_completion.d/
.
I use the same configuration on Ubuntu and I check for the file in
/etc/bash-completion.d/
too.
# Prefer /usr/local/etc but fallback to /etc if [ -f /usr/local/etc/bash_completion.d/git-completion.bash ] then source /usr/local/etc/bash_completion.d/git-completion.bash elif [ -f /etc/bash_completion.d/git ]; then source /etc/bash_completion.d/git fi
This is great but, what about my beautiful little g()
function? How do I make
it work with command completion? It turns out to be quite easy. Include the
following little snippet in a configuration file, such as .bashrc
.
# Set up git command completion for g __git_complete g __git_main
The snippet reuses the functions, __git_complete
and __git_main
included with
git-completion.bash
to make completion work with g
too. Lovely!
bash-prompt
In later versions of Git, the prompt functionality has been extracted out into
its own script, git-prompt.sh
. I include it like this.
if [ -f /usr/local/etc/bash_completion.d/git-prompt.sh ] then source /usr/local/etc/bash_completion.d/git-prompt.sh fi
I configure my prompt like this, it contains a little more magic than the plain
Git configuration. I put it in one of my bash configuration files, such as
.bashrc
.
function prompt { # Check exit status of last command if [[ "$?" -eq "0" ]]; then # If it is OK (0) color the prompt ($) green local status="" local sign=$(echo -ne "\[${GREEN}\]\$\[${NO_COLOR}\]") else # If not OK (not 0) color the prompt ($) red and set status to exit code local status=" \[${RED}\]$?\[${NO_COLOR}\] " local sign=$(echo -ne "\[${RED}\]\$\[${NO_COLOR}\]") fi # Get the current SHA of the repository local sha=$(git rev-parse --short HEAD 2>/dev/null) # Set the prompt # \! - history number # : - literal : # \W - Basename of current working directory # $sha - The SHA calculated above # $(__git_ps1 '@%s') - literal @ followed by Git branch, etc. # $status - The exit status calculated above # $sign - The red or green prompt, calculated above export PS1="[\!:${LIGHT_GRAY}\W${NO_COLOR} $sha${GREEN}$(__git_ps1 '@%s')${NO_COLOR}$status]\n$sign " } # Tell bash to invoke the above function when printing the prompt PROMPT_COMMAND='prompt'
The function __git_ps1()
can further be configured with some environment
variables. This is what use.
# Git prompt config export GIT_PS1_SHOWDIRTYSTATE=true export GIT_PS1_SHOWUNTRACKEDFILES=true export GIT_PS1_SHOWUPSTREAM="auto" # export GIT_PS1_SHOWSTASHSTATE=true
The resulting prompt looks like this:
The different signs to the right indicate:
# * - Changed files in working dir # + - Changed files in index # % - Untracked files in working dir # < - The branch is behind upstream # > - The branch is ahead of upstream (Yes, it can be both)
More info can be found in git-prompt.sh
.
Credits
Obviously I have not figured this out all by myself. Here are some of my sources:
2 comments:
In ZSH your g() command has to check the value of $#. Working example in gist: https://gist.github.com/4953414. Great post.
@Jonathan, I'm glad you liked it. I used to have a little ZSH envy until I tried to switch and realized how used to Bash I am.
Post a Comment