What Did I Just Do?
The one consistent thing about mistakes is that realization happens about 100ms too late. Whether it’s committing to the wrong branch, merging to the wrong branch, or some other problem, “messing up” your repository is the worst feeling.
The most important thing with Git is when this happens, don’t panic, and don’t push. Anything can be fixed, but it’ll be fixed a lot easier if it hasn’t been pushed yet.
Committing to the Wrong Branch
When you’re switching around between master
and feature branches, it’s easy
to commit to the wrong branch. Environments like Eclipse will tell you what
branch a given repository is on, but that doesn’t mean it doesn’t happen.
If you just need to redo a commit, with a different branch as target, it’s pretty easy.
cd harry
git checkout -b much-ado
echo "Were she other than as she is, she were unhandsome" >> spear02
git add .
git commit -m "Benedick"
git checkout master
echo "But being no other but as she is, I do not like her" >> spear03
git commit -am "Benedick continues"
We switched back to master
probably for some good reason. Then we forgot
we switched and went back to making commits that belong on our feature
branch.
In this case, we don’t want that commit to apply to master
at all. We need
to rewind master
to the point before that commit, but in a way that keeps
the change in our working copy so we can apply it to the branch.
git reset --soft HEAD^
We used reset
rather than checkout
this time. Last time we were content to
just make a new commit after undoing the bad stuff. This time we want that commit
to have never happened, because it would be confusing to people to see a
“Much Ado” commit on master
before that feature branch got merged in.
We also used --soft
, which wasn’t strictly necessary, but it’s a nice touch
because it leaves our “staged” changes. This means we can redo the commit without
having to worry about doing git add
or git commit -a
. Don’t worry about
this kind of touch while you’re learning Git; it’s the kind of thing that
comes naturally over time. The more mistakes you make, the faster you get to
learn the different ways to fix them.
One other thing: here I used yet another way to refer to “the commit just
before the last one”. It’s exactly the same as HEAD@{1}
but I wanted to show
you both because you’ll see them both.
Now that we’ve backed out the commit, we can switch branches and commit it where it belongs.
git checkout much-ado
git commit -m "Benedick continues"
Merging in Traffic
You probably don’t care, but what happened to the original “Benedick continues”
commit, the one that we committed to master
and then backed out? It didn’t go
anywhere. Really, all we did was just change the history for master
so it no
longer included that commit. That commit is still floating out there but it
no longer belongs to any branch.
That’s another one of those “architecture versus effect” questions. In my opinion, we should behave as if that commit is gone, because while we could use some esoteric command to retrieve it, there’s just no value in doing so.
Similarly, what happens if we merge a bunch of commits when we didn’t mean to? We do exactly the same thing to fix it.
Let’s say that Harry thinks he wants to merge his feature branch into master
:
git checkout master
git merge much-ado
At this point the marketing guy shows up and tells him that the feature needs to
wait until version 2.0 because they plan a price increase then and need to justify
it. Harry needs to get that feature out of master
. He doesn’t remember how many
commits were in that feature branch, so he does git log
and finds the last
“good” commit. In my repository, that’s 391590ed0605807042eb0dbd0eb9054396a5ec1a
;
you’ll have to look up your own.
git reset --hard 391590
It makes sense to use --hard
here because we want Git to also update the working
copy. It’s safe because those commits are stil available on the feature branch. In
fact, if the marketing guy were to show up and say he just remembered that he promised
that feature in the next release after all, Harry could just git merge much-ado
again and everything would be back where it was a moment ago.
What If I Pushed?
That example worked because Harry had not pushed the change to shared
yet. But that’s
not very realistic. I still maintain that the best solution is just to get the files
back to the right state and make a new commit. But in some particular, probably rare
situations, that might not be preferred. What about cases where someone committed
personal information to the repository? It’s not OK to just leave that sitting around
in an old version.
Let’s re-merge in much-ado
so we have something to back out:
git merge much-ado
Same as before, we start by fixing our own repository first.
git reset --hard 391590
Now we need to push this change to shared
, but it’s not a regular fast-forward
any more, so Git will reject it. This is one case where it’s justified to do a
“forced” push:
git push -f
You can believe me that the right thing happened, or you can cd
over to Isabelle’s
repository, make sure master
is checked out, and git pull
. The latest “Much Ado”
commits won’t be brought in and they won’t appear in the log.
Wrapping Up
The stuff in this chapter is the Git equivalent of surgery, and it should be as rare as surgery. Even though the commands are short, these changes were relatively complicated to envision. However, these kinds of fixes are complicated in any version control software, and they’d be practically impossible in some tools I’ve used. Git lets you do this, but as I said before, it doesn’t mean you should.
Those familiar with Git will notice that I stayed away from rebase
in this whole
discussion. Rebase and its merge companion cherry-pick
would have let us choose
to keep some commits from history (or some commits from a merge) and skip others. They
also allow editing commits way back in time, or editing commits as they’re merged in.
However, I also recognize that most teams using Git have that one person who gets into
the details of the tool and learns the magic. On some teams, with some tools, I’ve been
that person. I also know that most people are not that person, and it seems silly to
me to pretend that someone should learn how to rebase
or cherry-pick
to use Git.