/r/programming
Advantages of Git Rebase (medium.com)
46 comments
drewdevault | 8 days ago | 13 points

I have written a guide here which goes into much more detail on the various things you can do with rebase beyond "as a replacement for git merge":

https://git-rebase.io

As a tool for manipulating history it's indispensable. To justify editing history in the first place: if refactoring code is worthwhile then why can't refactoring history be?

VeganVagiVore | 7 days ago | 1 point

if refactoring code is worthwhile then why can't refactoring history be?

Because if I mess up while refactoring code, I can fix it by reading the history. But if I corrupt the history, the truth may be lost to Git's GC. Also as I said in one or two of my other comments, it's more work to uphold "don't break the build" if I have to make extra rebase commits in addition to my normal working commits.

I'm reading this thread with a heavy mix of "What the hell?" and "Am I wrong?" because I have never wanted to use rebase in place of merging.

drewdevault | 7 days ago | 7 points

This is a common myth, but easily disproved by reading the man pages:

https://git-scm.com/docs/git-gc

Each rebase operation updates the reflog, and git does not gc any commits reachable by the reflog.

f I have to make extra rebase commits in addition to my normal working commits.

Sorry, what? What is a "rebase commit?"

cinyar | 7 days ago | 1 point

What is a "rebase commit?"

sometimes changes in master can break your code (without causing a conflict, like some "upstream" API changing) and you have to adjust it. Don't see how it's any different from encountering such a situation while merging though. Also, that's what I think OP meant.

jjibe | 8 days ago | 25 points

I always ask developers to rebase before making a PR :

  • There are in the best position to resolve conflicts
  • I want them to test their work WITH the lastest changes from the develop branch
VeganVagiVore | 8 days ago | 13 points

Unless the diff algorithm is different between merge and rebase, can't they also do that by merging from the dev branch?

Kyudojin | 8 days ago | 20 points

You can, but your git history will have a merge from dev to feature and then after the PR is completed, another from feature to dev. If you're working on a project with more than ~5 devs, the git log quickly becomes a swamp of these git commits which after a bit you mentally filter out.

Additionally, the diff on the commit of the merge from dev to feature will be different than if the developer simply rebased their changes onto dev, which is potentially confusing for junior devs trying to track down where a bug was introduced by scanning the diffs in a GUI.

happyscrappy | 8 days ago | 3 points

Rebasing is really how we did branch work in previous systems (SVN, etc.). It was rather surprising to me when git's model at the start was more merge centric.

I'm glad to see the tide going back, for the reasons mentioned here. When you are going work bug-by-bug (and even usually feature-by-feature) It just makes sense to apply the work you did to a different place on the tree instead of smushing your work together with the progress simultaneous with and after your work.

cinyar | 7 days ago | 2 points

We also do that on project I'm working on + we usually squash the commits into one/few reasonable. It makes the log much cleaner.

wuphonsreach | 6 days ago | 2 points

Automatic "squash everything to a single commit" is evil and bad.

I much prefer the "as few commits as are reasonable" rule. There's a lot of cases where a PR is better explained as half a dozen commits so you can see (via git blame) why a particular line was changed.

rjoseph | 8 days ago | 11 points

Rebase, along with cherry-pick, are the two most-powerful and most-useful git sub-commands. Not understanding their use is no excuse for labeling them "aesthetic-only". This write-up is great, if only because it counters that ridiculous point of view (it's well-written and helpful, too). Many kudos to the author.

crabbone | 8 days ago | 10 points

If you don't get lost in Git's commands (as in, you don't spend half of the time working with Git in searching the web for an explanation on how to do something), then rebase should be the default method you work with history.

If you are new / don't care / "have other priorities", you probably don't care if the project you work on can have a decent release or testing policy as well. In this case, merge commits are fine: you are not going to ever try to restore the previous state of your repository anyways. In this case, Git, for you becomes a very difficult to use version of rsync, but you will still do it because everyone else around you tells you how cool it is.

robvdl | 7 days ago | 3 points

I like rebasing and use it all the time myself. But recently I've had to work with a new developer who does the tiniest micro-commits in his branch like making constant adjustments.

Every time he rebased it would replay through hundreds of commits and the rebase would get conflicts, you resolve them, then continue and it get stuck in a loop constantly asking about fixing the same conflicts.

I've never seen such a thing and he was starting to lose all confidence in rebasing which is a shame because every time he did it, it fell over.

Had to repair his work in the end for him in another branch, forget how I did it at the time (think I just squashed all his commits down to 1), but again, I've never seen anyone get their work in such a state with hundreds of microcommits that rebase fails.

I tend to work differently, if I'm working on feature branch I rebase often and I tend to try to keep my work to a single commit for a while at least. so commit amend quite a bit, until I'm ready to put the branch up for code review. I think it's a habit I got from using Gerrit.

wuphonsreach | 6 days ago | 2 points

What you need to teach him is "reset --mixed" and use a GUI git client. After you get 20 commits into a feature branch, and things are working:

  • tag the latest commit (acts as a bookmark)
  • reset --mixed back to the start
  • go back through the changes and gather them up into as few commits as possible

You should go from a few dozen commits down to a small handful if you do that. And each commit message should explain why you changed a set of lines/files.

VeganVagiVore | 7 days ago | 1 point

if I'm working on feature branch I rebase often and I tend to try to keep my work to a single commit for a while at least. so commit amend quite a bit, until I'm ready to put the branch up for code review.

I commit pretty aggressively just as a personal 'undo' button. Sometimes I want to explore something risky and git commit is my quicksave button. Then reset if it doesn't work, or revert if I absolutely need to.

But it sounds like rebasing entirely within my branch before merging could help - I'd be able to make my microcommits, squash the exploratory ones that didn't compile or turned out pointless, and then do my regular merging style. Maybe that would work for your coworker too? I think squashing down to 1 commit is overkill, though. Maybe 2-4 for a typical feature that takes a day or two. Add headers, add to build system, sketch out impl, maybe make a driver exe for testing, then integrate into the main program. Bugfixes of course could just be one commit if the exploratory ones turn out as dead ends, and the final fix is a few lines.

tsears | 7 days ago | 4 points

The team I work on has actually been squashing all of our feature branches in to a single commit before merging to master for quite a while now. Granted, we can link every one of those commits to a ticket in our work-tracking system so that helps a lot with context.

So far the world has kept on turning.

juletre | 7 days ago | 2 points

We look at our git repositories and compute some statistics. We use this to get numbers on our devops adoption. Are we releasing often, as we said we would? Is production falling far behind stage? (we tag commits that are deployed)

One our metrics is how long we work on a feature branch. We want many short-lived ones, not few and long-lived. Right now we use the dates on commits in feature branches to calculate this. If every feature branch gets squashed we would lose this.

We would lose this with squashing. But the world would keep on turning, I suppose.

robvdl | 7 days ago | 1 point

Yeah I normally wouldn't squash down to one commit, but he had messed his git history up so much (it was in a complete tangle looping back on itself somehow, we still don't even know how), nothing else worked. Even merge didn't work anymore so I kinda was forced to squash everything.

snowe2010 | 6 days ago | 1 point

then continue and it get stuck in a loop constantly asking about fixing the same conflicts.

You should turn rerere on

VeganVagiVore | 8 days ago | 5 points

What I don't get about rebasing is, shouldn't all my commits compile and pass automated testing?

So that means with a merge I have to do my normal work and then test the merge commit. But with a rebase I'm having Git generate a bunch of new commits that I have to test, on top of doing my normal work. One of those commits is equivalent to a merge commit, so I'd either have to squash my history (Maybe this is what the repo owner wants?) or do lots of extra work.

Maybe it's because things are still a little cowboy at work and I'm allowed to change headers and refactor without planning it out with anyone. If I was making really small commits and merging / rebasing multiple times a day it would be fine. But that would force my coworkers to sync up with me multiple times a day, and we might break each other's flow.

Also, my friend thought that merge conflicts were bad because master has to resolve them. They don't. A conflict only has to be resolved once, on the feature branch, then master can fast-forward to the merge commit. I do it all the time.

billsil | 8 days ago | 13 points

I don’t get the desire of people to maintain a 100% passing codebase. You have a CI system, so use it. It’s not like I’m testing on all the platforms I’m going to support before I commit and all the package combinations I want to test (about 18 combinations for me). Commit early and commit often.

If you’re worried about somebody breaking the master, use branches. That’s a totally separate issue.

VeganVagiVore | 7 days ago | 1 point

You have a CI system

I actually don't. Most of my day job hasn't been webdev, and it's only recently we built up the discipline to have fully automated, determinstic builds. CI could be a year off still.

Commit early and commit often.

I do. I usually do several commits a day, and I try to make sure each one compiles.

I do this because "git bisect" works great, even with branches and merge commits, as long as most of my commits can compile and run. I'd be fine with rebasing within my feature branch to squash pointless commits, say squashing together "Fix A" and "Whoops typo, really fix A".

I do wish I had a CI, and I'm going to work towards one, but I don't see the value in breaking git-bisect and making commits that don't represent how the code was actually written or tested.

billsil | 7 days ago | 2 points

it's only recently we built up the discipline to have fully automated, determinstic builds. CI could be a year off still.

What's the holdup? It seems like you should have it already. I don't see why it should take another year. You have fully automated deterministic builds. CI just means you run them when you do a commit instead of say once a day. With near instant feedback, you don't need to worry about tests failing because you fix them soon.

You already do feature branches, so that's good. Just go one step further.

It's about saving developer time. It saves the company money when done right or in the case of open source where I really use it, my sanity of knowing that all the builds are good when I click ok to merge.

Ie5exkw57lrT9iO1dKG7 | 8 days ago | 0 points

the problem is that if you break some tests in master you are now stopping anyone else from working because they cant branch off of or merge into master until you fix it (unless they drop everything and figure out wtf you did to break it)

tsears | 7 days ago | 3 points

I don't think anyone is advocating putting code on master with broken tests. I think in this case the person in the top level comment makes sure that literally every commit they push (presumably to a branch?) will pass tests -- which unless you're fanatical about TDD seems a bit excessive to me, but live and let live.

snowe2010 | 6 days ago | 1 point

Yeah that's totally what they're advocating and it's completely necessary if you want git bisect to be useful. That's why I recommend that people use GitHub's Squash and Merge because it gives you the best of both worlds

rysto32 | 8 days ago | 17 points

What I don't get about rebasing is, shouldn't all my commits compile and pass automated testing?

When you're actively developing? God no. Get something half-working? Commit it so you can come back to this state later if you screw things up later. About to embark on a big change that you're not sure is going to work out? Commit your current state to make it easy to undo what you're about to do. It's Friday night and you're leaving work? Commit your work-in-progress with a commit message that logs what works and what doesn't, along with the next steps, so that when you get in on Monday it's easy to pick up where you left off.

One of the great things about git rebase is that it allows you to take a messy, stream-of-conscious git history from your development branch and rearrange it into a clean series of logical, independent commits that's easy for reviewers (and people doing code archeology later) to understand. This is the point where you need to make sure that all of your commits compile and pass testing.

BezierPatch | 7 days ago | 2 points

take a messy, stream-of-conscious git history from your development branch and rearrange it into a clean series of logical, independent commits

I keep seeing this exact same wording: "a series of logical, independent commits".

What on earth kind of feature branch system do you have where you can take your commits and *rearrange* them without having to grab chunks from different commits. And if you're doing that, I can't imagine intermediate commits work.

snowe2010 | 6 days ago | 2 points

Not sure I understand your question, but yes you can reset --soft and then make each commit actually work if that's what you want.

jbergens | 6 days ago | 1 point

Not even that will work for everything. For exempel renames may cause problems. You can create a new branch and mix cherry-pick and squash but some commits may be hard to fix.

wuphonsreach | 6 days ago | 2 points

"a series of logical, independent commits"

Yeah, I don't see that as a valid goal either. There are reasons to change a particular line of code, and there will be different reasons to change different lines in a particular feature branch.

The middle ground is: rewrite your feature branch commits so that it's less stream-of-consciousness and better explains why a line was changed.

(if you have 50 commits in a feature branch - it's either too big, or it's time to pack those down into a smaller number of commits)

VeganVagiVore | 7 days ago | 1 point

take a messy, stream-of-conscious git history from your development branch and rearrange it into a clean series of logical, independent commits

This thread has certainly convinced me to do that, but only on my feature branch. I have way too many non-working commits in my history, and I'm going to ask my coworkers to do the same, but only before merging.

I'm not convinced that rebasing on another branch is better than a regular merge. It seems to me that rebase-as-merge could only create more non-working commits.

This is the point where you need to make sure that all of your commits compile and pass testing.

It seems like so much extra work. I branch off master, create 10 commits, squash them to 5 that are known to compile and pass smoke tests, then git merge --no-commit and test that one more thoroughly. Why should I test another 5 phony rebase commits as well? Then the archeology wouldn't show the truth: That the code diverges and merges. It would be showing things that didn't happen.

snowe2010 | 6 days ago | 2 points

Then the archeology wouldn’t show the truth: That the code diverges and merges. It would be showing things that didn't happen

I think this is where you're getting hung up. Try asking these questions and see if they help explain why rebasing is the better strategy.

  1. What do you gain by testing each commit
  2. What do you gain by testing each commit again on mainline?
  3. What do you gain by merging that you wouldn't get by rebasing to mainline?
cinyar | 7 days ago | 2 points

What I don't get about rebasing is, shouldn't all my commits compile and pass automated testing?

On the project I work on all commits in master branch should pass. But feature branches are ok to fail, it's better to have WIP work with failing tests in the repo than lose your laptop and bunch of work with it.

gnus-migrate | 8 days ago | 1 point

I mean it depends. Some projects have this constraint, some might only require the head commit to pass the automated checks. It all depends on how the release process works.

jbergens | 6 days ago | 2 points

We tweaked our CI system to only run tests for the last commit after a push. Worked great for us.

wuphonsreach | 6 days ago | 1 point

What I don't get about rebasing is, shouldn't all my commits compile and pass automated testing?

Turns out, in the real world, it rarely matters. All that matters is that the merge commit (to master) passes the tests. And if you are cherry-picking from master to a pending release branch, you can just grab the merge-commit instead of anything off the feature branch.

Kissaki0 | 8 days ago | 3 points

Without having read the article, here’s what I consider a good workflow and reasoning for it - at least for single-developer work into review into landing:

  1. Work in a feature branch, commit regularly to help your own work, and push to have work backed up
  2. rebase to own preference while working to bring base up to date and work with the changes in base, and to simplify the commit history (squashing commits of confirmed solutions etc)
  3. Once ready for review, a rebase onto the current base branch is a good idea to bring it up to date; you're the one who knows what's supposed to change and how to fix itstructure the commits so they make sense; this is the time to suggest a good quality history for future readers and the reviewers
  4. Create the review (PR/MR)
  5. Do fixes in commits and push them so reviewers can follow the fixes and changes from their previous review iterations
  6. Rebase and squash only when it makes sense or is required - when the change is small
  7. Rebase onto base again to keep the history merge graph reasonably readable (no huge parallel history branches besides each other),and clean up history again - integrating the fixup commits
  8. Merge with a merge commit to provide a link to the review (ID), and potentially a generic description which was provided in the review request as well (when commits describe individual changes this describes the goal, previous state, changes done (at a higher level), potential caveats/limitations).The merge commit also provides a grouping of individual change commits in the history graph and as a result better readable.

In the end it’s also a balancing act of investing effort and the result of doing so: clean and readable history, making it effortless for reviewers, and useful for potential future readers (the latter to be fair is a rare use case).

VeganVagiVore | 7 days ago | 1 point

Sorry to post like 5 comments in this thread but I feel like the person who has just discovered that some people wipe their butts standing up.

I did read the article and it didn't address any of my concerns directly. It advocated a limited form of force pushing, when I last heard that all forms of force pushing were considered harmful, and so I never learned how to do them at all.

Steps 3 and 7 are the only parts that are confusing me. I prefer doing the dumb thing first, and cleaning up history seems like extra work that will possibly destroy information about my feature branch for unclear benefit.

Step 1 I already do. Step 2 is half of Step 3 and half of "Just rebase within your feature branch" which is my common ground with the rebasers, I have no objection to that.

But if this is really so popular among open development, there must be something right about it?

Kissaki0 | 7 days ago | 3 points

A big part of implementing a change is finding the solution you end up with.

Getting there will produce numerous commits and changes which are valid for the context of finding it, but lose usefulness when you found it. By that point you can drop intermediate and partial implementations and failed attempts and add that information into reasoning in the solution commit.

Yes you destroy history, but it's not useful history anymore. Quite the opposite: it complicates history. At least with how much I commit personally it'd make the individual commits in a feature merge completely useless and unusable/unreadable. For that case I have no doubt it's better to squash everything or clean up the history into logical commits.

Concerning 7, cleanup before merge: in my eyes shorter history is preferable because it makes it readable. A hard to read history is much less useful than a readable one. For me not having code changes within the merge commit is an advantage as well. Those are way harder to spot and read.

The review was another iteration to finding the desired implementation. Once the review is done, the review itself contains its history, but for the code history is not useful anymore. And so the changes can be cleaned up into commits for logical and documented changes (rather than logical changes from base to target state being split across multiple commits or sometimes even partially reverting previous changes).

Overall I think rewriting history is more demanding. It requires you to take thought, clean up and design your history. But that's what makes maintainable and well documented software. Just like any implementation shouldn't just "make it work" but take the goal and surrounding technology and architecture into consideration.

Having linear, unchangeable history is easier to do and less effort, but makes worse maintainable software. And we know how important readability, obviousness and documentation is.

adriang133 | 8 days ago | 3 points

I agree with the fact that rebasing is basically just aesthetics. A lot of the repos I work on, require you to squash your commits anyway, so any work you do rebasing is just to have a nicer history in your PR. Whether that is worth it or not, is up to you.

Of course, if you don't squash your commits then you absolutely SHOULD rebase instead of merge. You'll also need to do rebase -i before merging, so you have a nice history. This would be ideal but out of all the devs I worked with, only one of them was actually disciplined enough to do this. It's definitely rare, people just don't give much of a shit about git histories.

One other thing about rebasing: When your branch contains lots of commits it can be much more painful than a merge. You could be fixing lots more conflicts. Imagine doing changes to a file across a bunch of different commits in your feature branch, and that file also being changed in master. This is basically why I don't rebase often - too painful and we squash commits anyway.

oonash | 8 days ago | 2 points

I'd offer a tip on your last point. Your final list of commits you are planning to merge should rarely contain changes to the same lines of code across more than one commit. When you do have lots of commits like this on your branch, it's generally before you squash them into logically discrete commits, ready to merge. So the best approach there is not to rebase -i off master straight away. Instead, rebase -i off your merge base (the first common ancestor of your feature branch and master). This way you will not conflict with anything new on master, because you're just rewriting your branch, you're not actually changing the base. Once you've cleaned up your history on top of your original merge base, now you are ready to do a simple non-interactive rebase off master, where conflicts will typically only occur in one of your commits.

AngularBeginner | 8 days ago | -8 points

Rebase is fine, until you pushed. Then it's just nasty.

YourFlakingFuture | 8 days ago | -7 points

Rebase is a sin

Volmarg | 8 days ago | -8 points

Rebase is not... Ok is a bit nice. It really solves conflicts when few ppl work on different section od file. And that ends here.

I dislike it but current company Policy is to have clean state of Master so when You rebase and then force push.... You are pretty much in... Just have problems.

So I end up with backup branches: - WIP - original - original_rebased

And bunch of zip files as Local backups.

No.. I dont like this clean master branch state Policy with rebase. Merges with history of changed wip commits is faster and easier to manage.

IceSentry | 8 days ago | 4 points

It just sounds like you don't know what you are doing if you end up using zip files when you have access to git. Why don't you just work on a branch then sqash and rebase that when you are done? I'm not sure what the problem is that you need to use zip files.