The only time it might make sense if you are following some arbitrary strict style guidelines for commits. Some people care more about the commit history than others, not that either way is necessarily better.
The only time it might make sense if you are following some arbitrary strict style guidelines for commits. Some people care more about the commit history than others, not that either way is necessarily better.
I have a friend, he thought rebasing for linear history was not worth the effort. I told him to do it, because I once had to find a regression over thousands of commits in a merge-heavy code base and it took days. He was not convinced.
Then he had to find a regression. It took over a month.
With git bisect's binary search, it would have taken half a day.
My friend now rebases.
Any given snapshot has a linear history, so it should be as bisectable as the rebased equivalent. What am I missing here?
Not sure what you mean. The key thing of merges is that they... merge... two histories.
A git history graph tool shows that clearly then.
A bisection has to choose whether to go left or right.
It didn't even occur to me that anybody would permit that in their CI.
If you check in commits that don't compile then you can't use automatic bisection effectively (it still does work if that happens rarely, thanks to automatic `git bisect skip`).
Of course every non-building commit will make bisecting a merge history a pain even more, not sure why you think it to be better with merges than with linear history.
> It didn't even occur to me that anybody would permit that in their CI.
How do you enforce it? Are you saying you make your CI compile and run tests for every single commit on a feature branch before allowing it to be merged? That takes a lot of time if you're doing the kind of small commits that make bisection most effective.
> Of course every non-building commit will make bisecting a merge history a pain even more, not sure why you think it to be better with merges than with linear history.
Because with rebase you're much more likely to get a long chain of commits that don't compile. E.g. imagine developer A adds a new feature and starts off by writing some code that calls some function, and meanwhile developer B renames that function in master. Then a while later developer A rebases onto master, fixes their compilation errors, and merges their feature branch in. All of the commits A did in between now don't compile, so you will "git bisect skip" all of them, and if your bisect lands somewhere in that chain of commits you have to do another round of bisection manually or something.
With merge, all of A's commits still compile and you can bisect through to the specific commit that caused the problem. (Maybe one or two isolated commits don't compile because they were never tested on CI, sure, but that's ok - git bisect skip handles them, it's only a problem if you have a long chain of non-compiling commits)
> How do you enforce it?
I don't think you can. You just rely on the the developer to only create compiling commits (if possible). Also, code review might catch these.
> Because with rebase you're much more likely to get a long chain of commits that don't compile
After a rebase you try to compile the code and it will fail due to the renamed function. Then you fix the function name and move this change into the commit that started using this function (perhaps employing a fixup commit). Now, all following commits compile because they have the fixed call site, and previous commits compile as well because the call wasn't there yet.
Right. But there's a natural incentive to create compiling commits as you work (because when you're working on something you at least occasionally compile your code and run tests). There's much less incentive to go back and check after a rebase.
> Also, code review might catch these.
Pretty unlikely - usually people just review the overall diff, not the individual commits, and even if they do, the commits make sense visually whether they compile or not.
> Then you fix the function name and move this change into the commit that started using this function (perhaps employing a fixup commit).
If you are disciplined enough to notice and do this right, sure. But it's extra work that eats into you discipline budget.
Git usage is only one part of a wider engineering org. That like saying "bugless code is objectively better" without considering time to delivery, engineering resources, etc.
Disagree. You can always flatten a commit graph into a linear history if you want, but you can't restore the original commit graph from a linear history. So preserving the original history is objectively better.
Clean history can exist with merges, but I think merging all over the place obviously encourages messy behaviors.
You can't rebase to get back to the original commits, not without knowing what they are.
> Imagine trying to bisect some spaghetti bowl of commits with merges to find the source of a recurring issue.
I do it all the time (well, less so now that I work with a better team where those issues are pretty rare), it's easy, that's the whole point of the git bisect command.
> It would be relatively nonsensical compared to a clean linear history.
Rebased history is much harder to bisect because you often get long chain of commits that don't compile or are otherwise broken.
Git rebase does not destroy history, it just does not link it together. That might be a bad thing. But the individual commits all making an appearance on the destination branch is a good thing.
From those who favor merge, what is bad is that there are no second parent pointers tracking where those changes came from.
This coulid be obtained by reimplementing git rebase as a sequence of merges. Git rebase is a sequence of zero or more cherry picks, not merges. If git rebase merged each commit instead of cherry picking, each commit would have a parent pointing to its original.
In a git bisect, there would be no need to chase those second parents; you would be looking for which merged commit introduced the breakage, and not care about its original, except in some rare situations where you want to analyze more deeply what went wrong (and then that parent pointer would be a bit handy).