What are your favorite git features or tricks, or even workflows? Post one feature, trick, or workflow per answer.
My favorite feature is interactive rebase back to the last upstream commit. This allows me to edit, merge(squash), and drop all of the commits I haven't pushed upstream yet, before I do so. Since I work on top of svn, my workflow looks something like:
git rebase -i svn/working_branch
<edit/squash/drop/etc>
This gives me a list of all of the changes I have on this branch that aren't in svn. I repeat the above as necessary, often doing squashes and reordering in multiple steps to reduce the impact of merges and keep myself sane. When I'm satisfied my commits are clear and coherent, then I do one final unit test run before I dcommit.
The Git Magic article is an excellent reference for tips and tricks. I still go back and reread it occasionally to pick up new things that I missed the previous time I read it.
git stash - Great for quickly parking what you're working on, switching to another branch to work on, and then going back to what you were doing. Saves making a commit (not that spurious commits are a problem in git thanks to git rebase -i and squashing)
git commit --amend - for those of us who have a terrible habit of committing before compiling/testing.
My favourite feature, hands down, is the index. Since my initial surprise about it abated and I got used to it, I have wondered why anyone would want to work without this concept. The initial bewilderment about the behaviour of git diff
with regards to the index turned into solid conviction.
Often when I start to do a particular thing in a codebase, I don’t have a clear idea up front about what I will want to do. With git, I just start working and see where the work takes me. I can use git add
to incrementally add things for the next commit to the index, either as I work or afterwards, and use git diff
to review which tentative changes I have not yet evaluated.
In that way, git lets me follow a pretty free-wheeling style of work, while still allowing me to easily render it as a series of small coherent patches once I have found out what made sense to do. I don’t need to plan ahead carefully and follow the plan meticulously.
Yeah, the index is an extra layer of indirection that seems unnecessary at first glance. What I have found is that instead it is liberating.
[Addendum: Over a year after this reply, I wrote In praise of Git’s index on my weblog, wherein I say essentially the same things, at length.]
I tend to work on master optimistically. I'll occasionally need to retroactively create a branch to put my work off to the side. git makes this easy. I don't always have the same recipe, but it'll look something like this:
# Convert the recent work on master to a feature branch
git branch feature-branch
# Drop my master back to the same ref as origin
git reset --hard origin/master
# Switch to the new branch
git checkout feature-branch
To echo @Aristotle Ppagaltzis, the index allows you to do neat stuff like commit partial changes within a single file.
git add --interactive
So in this C project I was working on recently, one of our regression tests began failing. The project was in an in-between state, so I figured something was just temporarily broken, and as we filled things back in it would probably pass again.
A good number of commits went by and things started to come together, but this old test was still failing. Clearly someone had actually introduced a bug along the line, rather than merely introducing a temporary hole in functionality.
So I run git bisect start
, then git bisect bad
on the experimental head of the repository. Then I jump back about thirty commits to find one where the test passes and I run git bisect good
. git then jumps me to a commit halfway between the known good and the known bad commits, I run the test and do git bisect good
or git bisect bad
. Repeat this process about five times and I'm right at the commit where the bug was introduced.
I'd done a fairly innocent seeming cast of a pointer, which screwed up some pointer arithmetic, since you're so curious.
All in all it took just a couple of minutes. However, it turns out I did this the slow way. Since I had a test program that returned 0 on success and something else on failure, I could have simply given git the command to run it, and it could have found the commit in question in seconds. See: git bisect run
git checkout -b some-experiment
# do some work
git commit -m 'some feature'
# do some more work
git commit -m 'some other feature'
# experiment fails
git checkout master
# start working on new thing
git commit -m 'some mundane bugifx'
# realize I need some code from my experimental branch, but not the whole thing
gitk --all
# figure out the sha-1 of the change I want
git cherry-pick the_sha1_of_some_other_feature
git add --patch
(or -p
) and its big brother git add --interactive
(or -i
). They allow you to stage only certain chunks from all the changes you have in your working directory into the index, and you can even edit them, so your commit looks exactly the way you want it to.
Here's a neat trick:
Create a script named git-foo in your $PATH. When you run 'git foo', it'll call your custom script.
This is the easiest way to add commands to git and have them appear to be built-in.
Show Branch Name in Bash Prompt
Great tip for working with git from the command line. Basically, you can set your Bash prompt to display your active git branch, if and only-if you are inside of a git repository. You can do this by updating the PS1
define or adding to the end of your .bashrc
file.
PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
The shell will now display the following prompt:
[user@host workingdir (master)]$
gitready.com is awesome -- lots of tip-of-the-day style tricks.
Adding alias definitions using git config. Here's some that I often use:
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.ci commit
git config --global alias.br branch
git config --global alias.staged 'diff --cached'
Simply knowing that git reflog exists, and will allow me to revert to any previous commit if something has gone wrong. It remembers those commits that was "deleted" by rewriting history, using commands like git rebase, git reset, and git commit --amend.
Show every commit that the master branch has pointed to in the past:
git reflog show master
Revert to the tenth commit in the list:
git reset --hard master@{10}
Start a new branch from the commit that the master pointed to one month back:
git checkout -b branchX master@{'one month ago'}
Maybe not the features that you use most often, but knowing that they exist allows you to use other features without worrying about destroying anything.
Just the basic fast and whenever-you-want branching. Having come from using svn, the ability to separate changes out from each-other and continue coding on two different branches effortlessly is priceless.
I use branches for my feature todo list, for bugfixes, for backups, and I love that the branches don't exist in a certain path, they're just waiting in an alternate dimension to be switched to at any time.
git blame
is too verbose to my taste (although you can customize output format) I recently discovered git gui blame
, which I find much easier to read.
I also love git cherry -v master
, to see the local commits on a branch that haven't been pushed into master yet.
One of my favourite features of git (and mercurial, and probably other DVCS) is that I can just zip up my project's folder and I have a working repo with full history. No need to pull/push whatever (although I know I can do that), I just send a zip and voilá, the repo is there as well as its history.
Add this short script to your .zshrc
autoload -Uz vcs_info
precmd() {
psvar=()
vcs_info
[[ -n $vcs_info_msg_0_ ]] && psvar[1]="$vcs_info_msg_0_"
}
PS1="%m%(1v.%F{red}%1v%f.)%# "
to get your command line prompt to display current branch/tag you're on.
A bit more in-depth look into vsc_info here
Combination of git stash
and git fetch
. This applies for when you have a team of people working with a central repository (like gitorious or gitosis).
If I am working on something and then when I am ready to push the changes instead of just committing them I would do a fetch to see if the remote has changed. If it's changed I would stash, do git merge origin
, then git stash apply
. This avoids an automatic merge message when you pull on top of your commit (like Merge branch 'master' of <server>:project
).
Reusing previous commit message when automatic merge fails. In fact git tells you use -c <SHA1>
when you do merge and it fails.
After you fix up the conflicts all you do is this:
git commit -C <SHA1>
And the original commit message is retained.
The ability to rewrite history with "git rebase -i". One of the things I use it for is to commit my work as a series of small changes, but before I push it upstream, mash all the of the changes that belong together into one rev.
# (work work work)
$ git commit -a -m'WIP: Refactoring ftp fetcher to make bug fix possible'
# (work work work)
$ git commit -a -m'WIP: Added a test for passive mode bug'
# (work work work)
$ git commit -a -m'Fixed passive mode bug in ftp fetcher'
Now I've got three commits that I want to smash together into one before I push them upstream:
$ git rebase -i HEAD~3
Git brings up my editor with a file that starts with these three lines:
pick d56cc97 Fixed passive mode receive bug in ftp fetcher
pick 2c9b044 WIP: Added a test for passive mode bug
pick 5d87764 WIP: Refactoring ftp fetcher to make bug fix possible
I change the second and third "pick" to "s" (for "squash"), save the file, and exit my editor:
pick d56cc97 Fixed passive mode receive bug in ftp fetcher
s 2c9b044 WIP: Added a test for passive mode bug
s 5d87764 WIP: Refactoring ftp fetcher to make bug fix possible
Git smashes the revs together, then presents me with the comments
# This is a combination of 3 commits.
# The first commit's message is:
Refactoring ftp fetcher to make bug fix possible
# This is the 2nd commit message:
Added a test for passive mode bug
# This is the 3rd commit message:
Fixed passive mode receive bug in ftp fetcher
Which I replace with:
Fixed passive mode receive bug in ftp fetcher
And that's all.
Having this ability means I can commit any time I want a good "go back to" point and not worry about "polluting" the upstream tree with all of my nervous little commits.
I rather like git log -g
, which shows the history of where the HEAD
has been, even if you reset
yourself up and down your commit set.
It's a bit like ORIG_HEAD
but goes back for weeks – this has gotten me out of a mess many many times, and gives me confidence to play around with some of git's fiddlier features without having to tie on a safety branch before I start.
Mine favourites are:
- git cherry-pick
- git blame -- a.file (faster than SVN and gives more info)
- git gui and clicking files for staging into commit
Igor, Instead of using git stash, you can use git pull --rebase origin. This will rebase your local branch commits onto the remote changes.
I really use tig quite a bit. It's much quicker to use than git add -i
. I can add files to be committed with 1 key in a single screen instead of adding 1 at a time. Also, it's much easier to see what has changed in your index. It's pure command line and not as clunky as git gui
I'm partial, but the gbranch command in http://github.com/garysweaver/git-scripts allows you to track a remote branch if it already exists, make a remotely tracking git branch if one doesn't exist, or just switch to the local branch by that name:
gbranch (branch_name)
If you give it a second argument, it tracks a remote branch if it already exists, branches from the specified remote branch to make a remotely tracking git branch from it if the new branch doesn't exist, or just switches to the local new branch by that name:
gbranch (branch_name) (existing_remote_branch_to_branch_from)
For those that want to be lazy with Git, it works great, and I've been using it a while.
git lg aliased to
git log --graph --all --pretty=format:'%Cred%h%Creset - %Cgreen(%cr)%Creset %s%C(yellow)%d%Creset' --abbrev-commit --date=relative
GitHub + Schoolwork (branches for every homework assignment that eventually merge into master). 1 repo per class.