The magical -- and not harmful -- rebase (Git)
jeffkreeftmeijer.com
jeffkreeftmeijer.com
Everyone who's worked with one knows that long-lived branches are a bad idea - the performance-improvements branch that the intern started last year and has been running on beta for a while but hasn't really kept up to date with the new features we added on production. The time required to merge one of these seems to grow faster than linearly with the time since it diverged, and a year-old branch is basically impossible to merge.
However, long-lived branches happen (features get shelved and then picked up again, refactoring gets postponed due to deadlines...). Rebase doesn't make long-lived branches a good idea, but it makes them much easier to deal with:
* you can rebase once a week and keep your branch up to date (to avoid the superlinear growth of merging effort). You can just about do this with frequent "latest changes from master" merges, but it clutters the branch history, and merge commits complicate things if you ever need to rebase. (Also, anyone who tried to do this with Subversion probably got shivers down their spine at the thought of merging the branch back in after all those merges from master.)
* when the branch inevitably conflicts with master, it's much easier and less risky to resolve the conflicts a commit at a time, rather than resolving them all at once as a merge would force you to.
If the merge conflicts really grow faster than linear, divide-and-conquer will save you time.
Unfortunately, often the reason you have a long-lived branch is because it's some stop-the-world change (e.g. "refactor data model") which is pretty much all-or-nothing.
Of course it's always possible with enough discipline to turn a stop-the-world change into a series of incremental steps, and usually it's well worth that effort (for risk management etc) - but sometimes that's not feasible.
Just keep rebasing it forward week by week of last year. (Of course this should not happen in real time, since you have to catch up.)
So a stop the world change is still doable with this model---your commit history will just pretend that you finished your branch within a week.
As usual, the Pro Git book does a great job explaining: http://progit.org/book/ch3-6.html
In short, if your team develops with an old-school, SVN type model, where there is one reference repo (the remote) that all developers pull from and push to, rebase to your hearts content. But if your team pulls from each other, you really want to be careful with rebasing. I believe this is how the Linux kernel is developed, which explains all of the warnings about git rebasing. After all, it's logical to assume that you should do exactly as the Linux kernel developers, for whom git was created, do!
git config --global rerere.enabled 1I'm genuinely not sure how I feel about git rebase. Two questions. First, what is so nasty about such a merge commit? People complain about these merges a lot, but is the objection just aesthetic? Why is such a merge bad? Second, from a historical point of view, you did "start working in the feature/login branch before the commits you pulled in were made." So why would you want the history to look otherwise?
I completely understand the use of rebase or --amend to fix a typo or a forgotten file commit, but in a case like this, I'm not sure what to think. I'm inclined to prefer the history to show what actually happened, rather than a tidied up version of the past.
What exactly is the complexity you have in mind here? (I'm not trying to be contentious: I honestly don't see it.)
git log --graphFrom the article:
Merge can be used when you want to merge a feature branch back into your development branch. That way, you’ll be able to see when you merged in what in the future because you have that merge commit I called “nasty” before. It isn’t, really.
I've come to prefer merges for this case. It reflects reality - you really were developing on a separate history for a while, you're not interweaving bugfixes from master with feature work, etc. And it visually (and logically) groups the commits for the feature, so it's very easy to review or revert the feature as a whole, instead of having to figure out exactly which commits in the big linear history are relevant (or having to religiously tag every time you do anything interesting). The current git visualisation tools (gitk, gitg, gitx) aren't very good at displaying histories with interesting merge structures, but I confidently predict someone will release an awesome dotviz-based history visualiser soon which makes it easy to navigate forests of branches and merges.
That said, as the article describes, rebase does handle conflicts (syntactic and semantic) better than merge does. Because it rebases a commit at a time, you have to think about how the upstream change affects each change that you made in the feature branch, which means you're much less likely to resolve the conflict in a way which breaks some subtle assumption you baked into the code two weeks ago.
I've sometimes taken the approach of first rebasing my feature branch onto the latest master to resolve any conflicts, and then doing a 'merge --no-ff' to explicitly create a merge commit to get the visual history marker.
Reading the Oreilly git book (the one with the bat) was immensely useful for me to understand rebasing (and merging and cherry picking). It makes it clear when and where it's harmful, when it might cause problems, and when it's perfectly safe.
It's taken me several years, but I've come to appreciate rebasing, and I recommend it to others now
git push --force remote local-branch-name:remote-branch-name
(or if local-branch-name and remote-branch-name are the same:) git push --force remote branch-name
(N.B. if there's a chance that anyone else is working on the same branch, then think twice before doing --force, because you may spoil their afternoon. Read 'git help push' for more info.)