Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I have been contributing code for 10+ years, and I have worked on teams that did rebase and others that did not.

Not once have a ever debugged a problem that benefited from rebase vs merge. Fundamentally, I do not debug off git history. Not once has git history helped debug outside of looking at the blame + offending PR and diff.

Can someone tell me when they were fixing a problem and they were glad that they rebased? Bc I can't.





Debugging from git history is a separate question from merge vs rebase. Debugging from history can be done with non-rebased merges, with rebased merges, and with squashed commits, without any noticeable difference. Pass `--first-parent` to git-log and git-bisect in the first two cases and it's virtually identical.

My preference for rebasing comes from delivering stacked PRs: when you're working on a chain of individually reviewable changes, every commit is a clean, atomic, deliverable patch. git-format-patch works well with this model. GitHub is a pain to use this way but you can do it with some extra scripts and setting a custom "base" branch.

The reason in that scenario to prefer rebasing over "merging in master" is that every merge from master into the head of your stack is a stake in the ground: you can't push changes to parent commits anymore. But the whole point of stacked diffs is that I want to be able to identify different issues while I work, which belong to different changes. I want to clean things up as I go, without bothering reviewers with irrelevant changes. "Oh this README could use a rewrite; let me fix that and push it all the way up the chain into its own little commit," or "Actually now that I'm here, let me update dependencies and ensure we're on latest before I apply my changes". IME, an ideal PR is 90% refactors and "prefactors" which don't change semantics, all the way up to "implemented functionality behind a feature flag", and 10% actual changes which change the semantics. Having an editable history that you can "keep bringing with you" is indispensible.

Debugging history isn't really related. Other than that this workflow allows you to create a history of very small, easily testable, easily reviewable, easily revertible commits, which makes debugging easier. But that's a downstream effect.


> Debugging from git history is a separate question from merge vs rebase.

But the main benefit proponents or rebase say its for keeping the history clean which also makes it easier to pinpoint and offending commit.

Personally, a clean commit history was never something that made my job easier.

> Other than that this workflow allows you to create a history of very small, easily testable, easily reviewable, easily revertible commits, which makes debugging easier. But that's a downstream effect.

I would agree that it is important for commits to go from working state to working state as you are working on a task, but this is an argument for atomic commits, not about commit history.


> Personally, a clean commit history was never something that made my job easier.

How do you define "clean"? I've certainly been aided by commit messages that help me identify likely places to investigate further, and hindered by commit messages that lack utility.


> How do you define "clean"?

In the context of merge vs rebase, I think "clean" means linear, without visible parallel lines. Quality of commit messages is orthogonal. I agree with the poster that this particular flavor of "clean" (linear) has never ever helped me one bit.


Agreed, it just means "linear" for most people.

I think the obsession with a linear master/main is a leftover from the time when everyone used a centralized system like svn. Git wasn't designed like that; the Linux kernel project tells contributors to "embrace merges." Your commit history is supposed to look like a branching river, because that's an accurate representation of the activity within your community.

I think having a major platform like github encourages people to treat git as a centralized version control system, and care about the aesthetics of their master/main branches more than they should. The fact the github only shows the commit history as a linear timeline doesn't help, either.


we're in the minority I think. I always find it easier to just debug a problem from first principles instead of assuming that it worked at some point and then someone broke it. often times that assumption is wrong, and often times the search for bad commit is more lengthy and less informative than doing the normal experimental process. I certainly admit that there are ases where the test is easily reproducible and bisect just spits out the answer, but that a seductive win. I certainly wouldn't start by reading the commit log and rewinding history until I at least had a general idea of the source of the problem, and it wasn't immediately obvious what to try next to get more information.

if you look at it as in investment in understanding the code base more than just closing the ticket as soon as possible, then the 'lets see what really going on here' approach makes more sense.


> I certainly wouldn't start by reading the commit log

Me neither, for what is worth. But even if the idea is "when in order to figure out this issue, you have to go to the history", a linear history and a linear log never helped me either. For example, to find where a certain change happened to try to understand what was the intent, what I need is the commit and its neighbors, which works just as well with linear vs branching history because the neighbors are going to still be nearby up and down, not found via visual search.


If you have not already, try Graphite. You will be delighted as it serves that exact purpose.

I use magit which afaik is still undefeated for this workflow. Particularly with this snippet to “pop down” individual ranges of changes from a commit: https://br0g.0brg.net/notes/2026-01-13T09:49:00-0500.html .

I have worked on several codebases where it was enforced that the commit be rebased off of whatever the main branch was, all units of work squashed to a single commit, and only "working" code be checked into the main branch. This gives you a really good linear history, and when you're disciplined about writing good final commit messages and tagging them to a ticket, it means bisecting to find challenging bugs later becomes tractable, as each commit nominally should work and should be ready to deploy for testing. I've personally solved a number of challenging regressions this way.

I think one should be allowed to push commits that don't build or pass tests provided a) they are marked as such (so you can skip them sooner when bisecting) and b) the HEAD commit after each push does build and pass tests.

>each commit nominally should work

Except it can be the result of 10 squashed commits.


Which is the entire point of it. Why should I look at ten commits when I can look at one and get the same exact data? Why should I pollute my production history for what a is likely a bunch of debugging commits? The branch is a scratchpad, you should feel empowered within your own branch, rebase allows you to be lazy in the development cycle while presenting a nice clean set of changes at the end of it.

Yes! When you are deep in the code, your brain operates in a non-linear way. You try a solution, it breaks a test. You patch the test. You realize the variable name is wrong. You fix a typo.

Without Squash, the main branch history becomes a timeline of your mental struggle.

With Squash, the main branch becomes a catalog of features delivered.

No body needs to take a trip on the struggle bus with me...


You can split your work in multiple commits and at the same time drop/squash debugging or wip changes. The result allows you to go into much better detail than a PR description.

>Why should I look at ten commits when I can look at one and get the same exact data?

For the same reason you have your production history instead of zip file with code)

>while presenting a nice clean set of changes at the end of it

The set, yes, not a single squashed commit.

>The branch is a scratchpad, you should feel empowered within your own branch, rebase

Yes, amend, fixup, rebase. Make it a nice set of small commits.


Most of those 10 squashed commits likely had commit comments like: "Cleanup based on PR feedback." etc.

That's what --amend and --fixup are for.

Which wreaks havoc with githubs shitty code review tool.

Which is an argument against GitHub, not clean commit history


Yep. Rebase rewrites history and all the PR review comments vanish.

Gitlab seems fine with it

I can give you an example of when I am glad I rebased. There have been many times I have been working on a feature that was going to take some time to finish. In that case my general workflow is to rebase against main every day or two. It lets me keep track of changes and handle conflicts early and makes the eventual merge much simpler. As for debugging I’ve never personally had to do this, but I imagine git bisect would probably work better with rebased, squashed commits.

> I can give you an example of when I am glad I rebased

I think the question was about situations where you were glad to rebase, when you could have merged instead


They kind of spoke to it. Rebasing to bring in changes from main to a feature branch which is a bit longer running keeps all your changes together.

All the commits for your feature get popped on top the commits you brought in from main. When you are putting together your PR you can more easily squash your commits together and fix up your commit history before putting it out for review.

It is a preference thing for sure but I fall into the atomic, self contained, commits camp and rebase workflows make that much cleaner in my opinion. I have worked with both on large teams and I like rebase more but each have their own tradeoffs


You can bring in changes and address conflicts early with merge too, I believe that's GP's point.

Yes but specifically with a rebase merge the commits aren’t interleaved with the commits brought in from mainline like they are with a merge commit.

EDIT: I may have read more into GPs post but on teams that I have been on that used merge commits we did this flow as well where we merged from main before a PR. Resolving conflicts in the feature branch. So that workflow isn’t unique to using rebase.

But using rebase to do this lets you later more easily rewrite history to cleanup the commits for the feature development.


So use --merges when browsing main.

You'll still get interleaved commits. If I work on a branch for a week, committing daily and merging daily from main, when I merge to main, git log will show one commit of mine, then 3 from someone else, then another of mine, etc. The real history of the main branch is that all my commits went in at the same time, after seven days, even if some of them were much older. Rebase tells the real story in this case, merge does not.

  git log --oneline --graph --first-parent

i don't think i have ever even looked at what order the commits are, i only care about the diff vs the target branch when reviewing

especially since every developer has a different idea of what a commit should be, with there being no clear right answer


That's hard to answer because I only rebase.

I used hg (mercurial) before git. Every time I see someone make an argument like yours I think "only because git's merge/branch model is bad and so you need hacks to make it acceptable".

Git won, which is why I've been using it for more than 10 years, but that doesn't mean it was ever best, it was just most popular and so the rest of the eco system makes it worth it accepting the flaws (code review tools and CI system both have much better git support - these are two critical things that if you use anything else will work against you).


Not only is git not the best, but one of the central value props of coding agents and chatbots used for programming is not having to use git in order to interact with free code.

Sigh. I will forever hate Atlassian for killing Bitbucket hg hosting.

What code review tools do you prefer?


FWIW I have used git bisect with merged commits and it works just as well (unless the commit is enormous... nothing like settling on a sprawling 100 file change commit as the culprit... good argument for discrete commits, but then it wouldn't matter if it were rebased or merged)

I do the same except with merge. I don't see how rebase makes it any better.

It avoids adding merge commits to your history.

I see no reason to avoid that.

> Fundamentally, I do not debug off git history.

Are you saying that you've never used git bisect? If that's the case, I think you're missing out.


Bisect is one of those things where if you're on a certain kind of project, it's really useful, and if you're not on that kind of project you never need it.

If the contributor count is high enough (or you're otherwise in a role for which "contribution" is primarily adjusting others' code), or the behaviors that get reported in bugs are specific and testable, then bisect is invaluable.

If you're in a project where buggy behavior wasn't introduced so much as grew (e.g. the behavior evolved A -> B -> C -> D -> E over time and a bug is reported due to undesirable interactions between released/valuable features in A, C, and E), then bisecting to find "when did this start" won't tell you that much useful. If you often have to write bespoke test scripts to run in bisect (e.g. because "test for presence of bug" is a process that involves restarting/orchestrating lots of services and/or debugging by interacting with a GUI), then you have to balance the time spent writing those with the time it'd take for you to figure out the causal commit by hand. If you're in a project where you're personally familiar with roughly what was released when, or where the release process/community is well-connected, it's often better to promote practices like "ask in Slack/the mailing list whether anyone has made changes to ___ recently, whoever pipes up will help you debug" rather than "everyone should be really good at bisect". Those aren't mutually exclusive, but they both do take work to install in a community and thus have an opportunity cost.

This and many other perennial discussions about Git (including TFA) have a common cause: people assume that criticisms/recommendations for how to use Git as a release coordinator/member of a disconnected team of volunteers apply to people who use Git who are members of small, tightly-coupled teams of collaborators (e.g. working on closed-source software).


> If you're in a project where buggy behavior wasn't introduced so much as grew (e.g. the behavior evolved A -> B -> C -> D -> E over time and a bug is reported due to undesirable interactions between released/valuable features in A, C, and E), then bisecting to find "when did this start" won't tell you that much useful.

I actually think that is the most useful time to use bisect. Since this is a situation where the cause isn't immediately obvious, looking through code can make those issues harder to find.


I'm glad it works for you! I may not have described the situation super clearly: most bugs I triage are either very causally shallow (i.e. they line up exactly with a release or merge, or have an otherwise very well-known cause like "negative input in this form field causes ISE on submit"), or else they're causally well understood but not immediately solvable.

For example, take a made up messaging app. Let's call it ButtsApp. Three big ButtsApp releases releases happened in order that add the features: 1) "send messages"; 2) "oops/undo send"; and 3) "accounts can have multiple users operating on them simultaneously". All of these were deemed to be necessary features and released over successive months.

Most of the bugs that I've spent lots of time diagnosing in my career are of the interacting-known-features variety. In that example, it would be "user A logs in and sends a message, but user B logs in and can undo the sends of user A" or similar. I don't need bisect to tell me that the issue only became problematic when multi-user support was released, but that release isn't getting rolled back. The code triggering the bug is in the undo-send feature that was released months ago, and the offending/buggy action is from the original send-message feature.

Which commit is at fault? Some combination of "none of them" and "all of them". More importantly: is it useful to know commit specifics if we already know that the bug is caused by the interaction of a bunch of separately-released features? In many cases, the "ballistics" of where a bug was added to the codebase are less important.

Again, there are some projects where bisect is solid gold--projects where the bug triage/queue person is more of a traffic cop than a feature/area owner--but in a lot of other projects, bugs are usually some combination of trivially easy to root-cause and/or difficult to fix regardless of whether the causal commit is identified.


From what I can tell the vast majority of developers don't use git bisect and never will.

FWIW, having squashed merge commits also reduces the relevance of bisect. It can still be useful but it’s not necessarily as critical of a tool.

This. This is why small commits are nice. If you do that you might as well rebase. If you squash you lose.

Git bisect is a wonder, especially combined with its ability to potentially do the success/fail testing on its own (with the help of some command you provide).

It is a tragedy that more people don't know about it.


This may be outdated because git’s defaults have improved a lot over the years. When I first used git on a team was in 2011. As I recall, there were various commands like git log -p that would show nothing for a merge commit. So without extra knowledge of the git flags you would not find what you were looking for if it was in a side path of the merge history. This caused a lot of confusion at times. We switched to a rebase approach because linear history is easier for people to use.

To answer your question directly, if somewhat glibly, I’m glad I rebased every time I go looking for something in the history because I don’t have to think about the history as a graph. It’s easier.

More to your point, there are times when blame on a line does not show the culprit. If you move code, or do anything else to that line, then you have to keep searching. Sometimes it’s easier to look at the entire patch history of a file. If there is a way to repeatedly/recursively blame on a line, that’s cool and I’d love to know about it.

I now manage two junior engineers and I insist that they squash and rebase their work. I’ve seen what happens if they don’t. The merges get tangled and crazy, they include stuff from other branches they didn’t mean to, etc. the squash/rebase flow has been a way to make them responsible for what they put into the history, in a way that is simple enough that they got up to speed and own it.


I manage a maintained fork and periodically rebase our changes on top of upstream.

In this case, rebasing is nice because our changes stay in a contiguous block at the top (vs merging which would interleave them), so it's easy for me and others to see exactly where our fork diverges.


Doesn’t that mean you have to fix all the merge conflicts introduced by your commits on every rebase though?

Git has gotten pretty smart about that recently: once you resolve a conflict, if you get the same conflict again it automatically resolves it the same way. Works for both rebase and merge.

if you don't have a merge from main into the branch further down, then git only bothers you about the most recently introduced conflicts conflicts --- the ones you'd have to resolve anyhow, and it remembers how you've resolved those.

You'd have merge conflicts whether you merge or rebase.

rerere can help, but OP probably has their own workflow

If you haven't used git bisect to find a regression, you should try it.

You can write a test (outside of source control) and run `git bisect good` on a good commit and `git bisect bad` on bad one and it'll do a binary search (it's up to you to rerun your test each time and tell git whether that's a good or a bad commit). Rather quickly, it'll point you to the commit that caused the regression.

If you rebase, that commit will be part of a block of commits all from the same author, all correlated with the same feature (and likely in the same PR). Now you know who you need to talk to about it. If you merge, you can still start with the author of the commit that git bisect found, but it's more likely to be interleaved with nearby commits in such a way that when it was under development, it had a different predecessor. That's a recipe for bugs that get found later than they otherwise would've.

If you're not using git history to debug, you're probably not really aware of which problems would've turned out differently if the history was handled differently. If you do, you'll catch cases where one author or another would've caught the bug before merging to main, had they bothered to rebase, but instead the bug was only visible after both authors thought they were done.


> it's up to you to rerun your test each time and tell git whether that's a good or a bad commit

not true. You can use

   git bisect run script-command arguments
where script-command is a ... script that will test the result of the build.

Once we had a slowdown in our application that went unadressed for a couple of months. Using git bisect to binary search across a bunch of different commits and run a perf test, every commit being a "good" historical commit allowed that to be much easier, and I found the offending commit fast.

Ok, I see. This is a use case I did not think about. Worthy of a blog post, I think.

Besides testing for a perf slow down, any other use cases for git bisect + rebase?


The main benefit I've found is when there is work happening concurrently in multiple feature branches at once (e.g. by different people). Rebase-merging greatly simplifies dealing with merge conflicts as you only have a simple diff against a single branch to deal with. The more work you have in progress at once the more important this becomes.

Do you ever use git bisect?

I like to keep a linear history mainly so I don't have to think very hard about tools like that.


--first-parent with bisect really helps when the history is messy.

I've used git bisect on a repo whose commit graph is at least 20-wide at some points. In the two cases I used it, it identified the individual commit. I didn't think very hard about it. It was the first time I used bisect. Maybe I got lucky.

In fact, for searching how a file got to the state it is I prefer that when PRs are merged, they are merged and not rebased. I want the commit shas to be the same.

Rebasing on main loses provenance.

If you want a clean history doing it in the PR, before merging it. That way the PR is the single unit of work.


Merging a PR with rebase doesn't lose provenance. You can just keep all the commits in the PR branch. But even if you squash the branch into a single commit and merge (which these tools automate and many people do), it still doesn't lose provenance. The provenance is the PR itself. The PR is connected to a work item in the ticketing system. The git history preserves all the relevant info.

The provenance that is lost is the original base.

No, the original base is in the commit history. It's just not relevant any more after rebase. It's like your individual keystrokes before a commit are not relevant any more after a commit. They're not lost provenance.

> That way the PR is the single unit of work.

Well if I have a diff of the PR with just the changes, then the PR is already a "unit of work," regardless of merge or rebase, right?


I’ve worked on a code base that was about 15 years old and had gone through many team changes. It was a tricky domain with lots of complicated business logic. When making changes to the code, the commit history was often the only way to figure out if certain behavior was intended and why it was implemented this way. Documentation about how the product should behave often lacked the level of detail. I was certainly always thankful when a dev that was long gone from the team had written commit messages that communicated the intent behind a change.

I use `git rebase` all the time, along with `git add -p`, `git diff` and other tools. It helps me to maintain logical commit history.

- Reshuffle commits into a more logical order. - Edit commit subjects if I notice a mistake. - Squash (merge) commits. Often, for whatever reason pieces of a fix end up in separate commits and it's useful to collect and merge them.

I'd like to make every commit perfect the first time but I haven't managed to do that yet. git-rebase really helps me clean things up before pushing or merging a branch.


I'd be willing to bet most devs do something like this, or wish they could be doing it, but don't know about rebase or are scared of it. However, that might be because they're only thinking about rebase as OP's article uses it: only as an alternative to merge for get changes from another branch.

Interactive rebasing to write local history on your working branch is incredibly useful, but also doesn't have anything to do with the "rebase vs merge" conundrum, and as long as you're not pushing to a shared branch, it doesn't have much to do with "erasing other's history".*

If you can look at a working branch (with more than a trivial addition or fix) and not feel the need to do a interactive rebase (once you know how) before making a PR, then you're either a magical 100x unicorn dev that makes every commit the perfect commit, or you cheated and made a new branch and cherry-pick-squashed your way to a clean history.


What is the argument here? Does it still hold if you s/rebase/comments/g ?

Of course a readable code history aids in debugging. Just as comments and indentation do. None of these are technically necessary, but still a good idea.

Of course running the rebase command doesn't guarantee a readable commit history, but it's hard to craft commits without it. Each and every commit on linux-kernel has been rebased probably a dozen times.


I have often been happy to have a clean linear history when asking myself things like "does build X.Y.Z include this buggy change I found in commit abcdefg?". With a history full of merges, where a commit from 1st of January might be merged only on the 20th of July, this gets MUCH harder to answer.

This is especially true if you have multiple repos and builds from each one, such that you can't just checkout the commit for build X.Y.Z and easily check if the code contains that or not (you'd have to track through dependency builds, checkout those other dependencies, possibly repeat for multiple levels). If the date of a commit always reflects the date it made it into the common branch, a quick git log can tell you the basic info a lot of the time.


Isn't it more of a style decision where if your team rebases, and practices clean code discipline, and excellence is a habit rather than an enforcement — the signals visibly emit in `status`.

Most committers don't really understand remotes, much less rebasing.


Any time I'm doing anything remotely to do with merging, I use 'git diff' or 'git difftool'.

If I diff against master, I see changes in 300+ files, when I've only changed 5 (because other people have changed 300+ files.)

> Fundamentally, I do not debug off git history.

Neither. The usual argument I hear against rebase is that it destroys history. Since I don't debug off git history, I'm quite happy to destroy it, and get back to diffing my 5-file changes against (current) master.


I love rebase locally, especially when I have a few non-urgent branches that sit around for a little while. I hate rebase after pushing. The rule of thumb that has worked for me is "don't rewrite someone else's history". Rewriting origin's history is not so bad, but if there's even a chance that a team member has pulled, or based work off your pushed branch (ugh), rebase is horrible.

This is the post that made jj click for me, and not coincidentally it is about a rebase operation that feels complicated in git but trivial in jj.

https://lottia.net/notes/0013-git-jujutsu-miniature.html


Whenever I go spelunking in the history I always find clean, linear history, with small commits (where possible) to be much easier to understand and search than merges.

> Fundamentally, I do not debug off git history.

I'm really sorry. Using bisect and log -S saved hours of code debugging


This "debate" is just insane.

Any workflow that has a review process uses rebase. FULL STOP.

If you don't have your code reviewed and you push code to a shared repo, fine, don't use rebase if you don't want to.


Two very useful use cases for rebase: 1) rewrite history to organize the change list clearly. 2) stacked pull requests of arbitrary depth.

You’ve never run a bisect to identify which commit introduced a specific behavior?

This is when I’ve found it most useful. Having commits merged instead of squashed narrows down and highlights the root problem.

It’s a rare enough situation I don’t push for merge commits over squashed rebases because it’s not worth it, but when I have had to bisect and the commits are merged instead of squashed it is very very useful.

Those commit authors are who I noted as clear thinkers and have tracked over my career to great benefit.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: