Git is hard: amending commit history

If you’re used to subversion and cvs, using git can be mind-bending. It does all the same things (and more), but nothing in quite the same way.

Idiomatic git means creating local branches, doing lots of little commits, then altering your local commit history if needed before pushing to the shared repository.

Today I tried to do that — branched, made a bunch of little commits, branched again, committed one more, then…. oh crap. I created a branch but didn’t check it out, so all those commits went to master.

Fortunately, I haven’t pushed, so all those commits exist only in my local repository. I can screw with them.

To move your x most recent commits from master to a branch that you created a while back, but never checked out:

git checkout
git rebase master // this moves the branch pointer to where master is now

git checkout master
git reset --hard HEAD^x // this moves the master pointer back x commits.

Alternately, you can specify exactly which commit a new branch should include:

git rebase --onto

For instance, I used “git log master” to find the last commit on master that I wanted my branch a236 to include, then did this:

git rebase --onto 8d819fed45753abaf05c59f4caee359804f28b47 master a236

Now branch a236 includes that commit. I’m free to reset master to something earlier, and the commits will be remembered by branch a236.

With all my commits and branches reorganized, I can push master to origin, leaving my personal branches local.

Just in this post I’ve used several concepts — local repository, master, push, rebase, reset, origin — foreign to Subversion users. And worse, I’ve used some concepts — branch, checkout — that sound familiar but subtly different.

Git and dvcs need to be learned from the inside out, at least a little bit, to be used with benefits and without frustration. The tutorials don’t teach it this way. I’ll make a presentation, and find some place to give it.