How to Git out of trouble (part 1)
2020-11-10 08:30 pmHopefully, this post will become the first of a series about solving various common problems with Git. Note that the grouping in that phrase is intentionally ambiguous – it could be either “(solving various common problems) with Git”, or “solving (various common problems with Git)”, and I expect to cover both meanings. Often there are aspects of both: Git got you into trouble, and you need to use Git to get yourself out of it.
“It is easy to shoot your foot off with git, but also easy to revert to a previous foot and merge it with your current leg.” —Jack William Bell
In many cases, though, this will involve git rebase
rather than merge
, and
I think “rebase it onto your current leg” reads better.
Overcoming your fear of git rebase
Many introductions to Git leave out rebase
, either because the author
considers it an “advanced technique”, or because “it changes history” and the
author thinks that it’s undesirable to do so. The latter is undermined by the
fact that they usually do talk about git commit --amend
. But, like amend,
rebase lets you correct mistakes that you would otherwise simply have to live
with, and avoid some situations that you would have a lot of trouble backing
out of.
In order to rebase fearlessly, you only need to follow these simple rules:
- Always commit your changes before you pull, merge, rebase, or check out
another branch! If you have your changes committed, you can always back out
with
git reset
if something goes wrong. Stashing also works, becausegit stash
commits your work in progress before resetting back to the last commit. - Never rebase or amend a commit that’s already been pushed to a shared
branch! You can undo changes that were pushed by mistake with
git revert
. (There are a few cases where you really have to force-push changes, for example if you foolishly commit a configuration file that has passwords in it. It’s a huge hassle, and everyone else on your team will be annoyed at you. If you’re working on a personal project, you’ll be annoyed at yourself, which might be even worse.) - If you’re collaborating, do your work on a feature branch. You can use amend and rebase to clean it up before you merge it. You can even share it with a teammate (although it might be simpler to email a patch set).
That last rule is a lot less important if you’re working by yourself, but it’s still a good idea if you want to keep your history clean and understandable – see Why and How To Keep Your Master Happy. And remember that you’re effectively collaborating if your project is on GitHub or GitLab, even if nobody’s forked it yet.
Push rejected (not fast forward)
One common situation where you may want to rebase is when you try to push a
commit and it gets rejected because there’s another commit on the remote repo.
You can detect this situation without actually trying to push – just use git
fetch
followed by git status
.
I get into this situation all the time with my to-do file, because I make my updates on the master branch and I have one laptop on my desk and a different one in my bedroom, and sometimes I make and commit some changes without pulling first to sync up. This usually happens before I’ve had my first cup of coffee.
The quick fix is git pull --rebase
. Now all of the changes you made are
sitting on top of the commit you just pulled, and it’s safe for you to push.
If you’re developing software, be sure to run all your tests first, and take a
close look at the files that were merged. Just because Git is happy with your
rebase or merge, that doesn’t mean that something didn’t go subtly wrong.
Pull before pushing changes
I get into a similar situation at bedtime if I try to pull the day’s updates
and discover that I hadn’t pushed the changes I made the previous night,
resulting in either a merge commit that I didn’t want, or merge conflicts
that I really didn’t want. You can avoid this problem by always using
git pull --rebase
(and you can set the config variable pull.rebase
to
true
to make that the default, but it’sa little risky). But you can also
fix the problem.
If you have a conflict, you can back get out of it with git merge --abort
.
(Remember that pull is just shorthand for fetch followed by merge.) If the
merge succeeded and made an unwanted merge commit, you can use git reset
--hard HEAD^
.
Another possibility in this situation is that you have some uncommitted
changes. In most cases Git will either go ahead with the merge, or warn you
that a locally-modified file will be overwritten by the merge. In the first
case, you may have merge conflicts to resolve. In the second, you can stash
your changes with git stash
, and after the pull has finished, merge them
back in with git stash pop
. (This combination is almost exactly the same as
committing your changes and then rebasing on top of the pulled commit – stash
actually makes two hidden commits, one to preserve the working tree, and the
other to preserve the index. You can see it in action with gitk --all
.
… and I’m going to stop here, because this has been sitting in my drafts folder, almost completely finished, since the middle of January.
Resources
- Man page for git-merge
- Man page for git-pull
- Man page for git-rebase
- Man page for git-stash
NaBloPoMo stats: 5524 words in 11 posts this month (average 502/post) 967 words in 1 post today
Another fine post from
The Computer Curmudgeon (also at
computer-curmudgeon.com).
Donation buttons in profile.