my git workflow uses rebase a lot. I always fetch upstream changes (the main repo i forked from) and then merge to my branches, and then rebase to remove useless (to me :D) merge commits and tree splits.
one thing on this workflow that annoys me is:
$ git rebase upstream/master
Cannot rebase: You have unstaged changes.
Please commit or stash them.
$ git stash
Saved working directory and index state WIP on cc: abc1234 Merge remote-tracking branch 'upstream/master' into local_branch
HEAD is now at abc1234 Merge remote-tracking branch 'upstream/master' into local_branch
$ git rebase upstream/master
First, rewinding head to replay your work on top of it...
Applying: awesome code change
$ git stash pop
so here we have 4 commands, 1=failed rebase, 2=stash, 3=rebase, 4=stash pop. anything but 3 is just mindless work.
So, the question is: What is the most recommended way of automating it? an alias to run git stash/rebase/pop everytime? some git config that forces rebase to stash or treat it as another commit to reapply afterwards? something else?
Edit: As of Git version 1.8.4, but with an important side bug fixed in Git version 2.0.1,
git rebasenow has--autostash. You can configuregit rebaseto use--autostashby default as well, withgit config --global rebase.autoStash true. Please note the following sentence from the documentation:(I still prefer to just make commits.)
TL;DR answer: just make a commit (then unmake it later)
It may help you to realize that
git stashis really justgit commit(in a more complicated form, which commits the index first, then the work-tree—when you apply a stash, you can maintain the separation of index and work-tree, or combine them into just a work-tree change).What makes a stash special is that the commits it makes—the two or, with
-uor-a, even three commits—are made in an unusual form (as a merge commit that's not really a merge) and not placed on any branch (instead, the specialrefs/stashreference is used to retain and find them).Since they're not on a branch,
rebasedoes not touch them, and in your workflow, it's thegit stash popthat brings the work-tree changes into your new work-tree. However, if you make your own (normal) commit, on a branch, and rebase and include that commit, this normal commit will be rebased along with any others. We'll get to one last problem in a moment; for now, let's draw this up, as a series of commits that do (or don't) get rebased:At this point, here's what you have:
Here,
A,B, andCare your commits (I'll assume you've made 3), all on branchmaster. Thei-whanging off commitCis your stash, which is not on the branch, but is still a two-commit "git stash bag" and is actually attached to your latest commit (C). The@commits (there might be just one) are the new upstream commits.(If you have made no commits, your stash-bag hangs off commit
*, and your current branch points to commit*, so thatgit rebasehas no work to do other than move your current branch pointer forward. Everything works out the same, in this case, but I'll assume there are some commits.)Now you run
git rebase upstream/master. This copies your commits to new commits, with new IDs and new parent IDs, so that they sit atop the last@. The stash-bag does not move, so the result looks like this:You now use
git stash pop, which restores the i/w stuff as work-tree changes, erasing thestashlabel (more precisely, popping it so thatstash@{1}, if it exists, is nowstash, and so on). That releases the last references to the originalA - B - Cchain, and means we don't need thei-wbit either, which lets us redraw this as the much simpler:Now let's draw what happens if, instead of
git stash save, you just do agit commit -a(orgit addandgit commitwithout -a) to create an actual commitD. You start with:Now you
git rebase upstream/master, which copiesAthroughDto place them at the end of the last@, and you have this:The only problem is that you have this one unwanted extra commit
D(well,D'now), instead of uncommitted work-tree changes. But this is trivially undone withgit resetto step back one commit. We can use a--mixedreset—the default—to get the index (staging area) re-set too, so as to "un-add" all the files, or if you want them to staygit add-ed, a--softreset. (Neither affects the resulting commit graph, only the index state is different.)Here's what that looks like:
You might think this is inefficient, but when you use
git stashyou're actually making at least two commits, which you then abandon later when yougit stash popthem. The real difference is that by making temporary, not-for-publication commits, you get those automatically rebased.Don't be afraid of temporary commits
There's a general rule with git: make lots of temporary commits, to save your work as you go. You can always rebase them away later. That is, instead of this:
where
A,B, andCare perfect and final commits atop commit*(from someone else or earlier published stuff), make this:where
a1is an initial stab atA,a2fixes a bug ina1,b1is an initial attempt to makebwork,a3is from realizing thatb1requiresAto be different after all,b2fixes a bug inb1,a4fixes a bug ina3's change toa2, andb3is whatb1should have done; thenc1is an initial attempt atC,b4is another fix tob1,c2is a refinement, and so on.Let's say that after
c3you think it's mostly ready. Now you rungit rebase -i origin/masteror whatever, shuffle thepicklines to geta1througha4into order,b1throughb4into order, andc1throughc3into order, and let the rebase run. Then you fix any conflicts and make sure stuff is still right, then you run anothergit rebase -ito collapse all fouraversions intoA, and so on.When you're all done, it looks like you created a perfect
Athe first time (or maybe witha4or some other one depending on which commits you keep and which ones you drop and whether you re-set any time stamps in things). Other people may not want or need to see your intermediate work—though you can retain it, not combining commits, if that's useful. Meanwhile you never need to have uncommitted stuff that you have to rebase, because you just have commits of partial stuff.It does help to give these commits names, in the one-line commit text, that will guide your later rebase work:
and so on.