return to table of content

I kind of like rebasing

AceJohnny2
111 replies
1d20h

Ah! My people.

I, too, much prefer a rebase-heavy workflow. It allows me to both have a dirty "internal" history and clean up for publication.

As a side-effect, it also makes me comfortable having a mostly linear-history for what I publish, as opposed to a many-branched, merge-heavy one, which I dislike, and makes history confusing.

I reject the argument that a no-rebase, merge-only history "preserves the true history of how commits were created", because I believe that is irrelevant. What is relevant is what the tree looks like once the merge (or rebase) lands.

Should a merge conflict arise, in a rebase workflow the conflict resolution is folded into the rebased commit, so it looks like it was fine all along. In a merge workflow, the fix is in the separate merge commit. In both cases you still have to handle the merge conflict. And in my opinion it is not significant for the merge conflict resolution to be separate from the original commit itself because, again: what's important is the final state of the repo.

quectophoton
37 replies
1d19h

I reject the argument that a no-rebase, merge-only history "preserves the true history of how commits were created", because I believe that is irrelevant. What is relevant is what the tree looks like once the merge (or rebase) lands.

That's kind of the point though: being reasonably sure that a commit contains a tree that the committer had seen at some point, instead of making up history with commits that contain trees that the committer never saw at any point at all.

When someone rebases `n` commits, experience has taught me I can't trust any commits other than `HEAD`; chances are any commit printed by `git log "HEAD~${n}..HEAD^"` was never checked out by anyone, much less tested at all.

CI pipelines also usually run only against HEAD at the moment of push, so if someone pushes `n` commits, then `n-1` are usually ignored by CI pipeline.

Modifying options for compiler or linter or formatter checker; adding a new dependency or updating an existing dependency's version; changing default options for the project. Stuff like that might make those commits useless, and if someone notices a problem in HEAD after rebase, and decides to fix it, even if the fix is moved to the earliest possible point, nobody would bother re-testing all those n-1 commits after the fix was added, leaving broken commits useless for git bisect.

So I agree that rebase is nice. How most people use it, though, not so nice.

Buttons840
13 replies
1d4h

In a merge-based workflow you can have commits like "wip" or "before lunch"; no reason to believe those were ever tested either.

I like rebasing but it's ultimately up to the author. Even tools like Fossil, that don't have official history rewriting tools, don't ensure that history has never been rewritten because people can use external tools to do the rewriting (and I've done this).

martin-t
12 replies
1d3h

Use a temporary branch for those. When you come back, undo the commit (git test -- hard if memory serves but i just have an "uncommit" alias) and commit the fully finished work to the real branch.

Buttons840
11 replies
1d3h

This is destroying the "real history" though. (Which again, I'm fine with, I like rebasing.)

Two months from now I'm quite likely to say something like "oh yeah, I remember I encountered a bug related to that, I was trying to fix it before lunch". The "wip" and "before lunch" commits are just as likely to be relevant in the future as any other.

It's nice to assume that all commits will compile and pass the tests, but it's sometimes useful to have a snapshot of that weird compiler error you encountered. So much for our nice assumption.

This is why I say it's all up to the author, and if the author likes rebasing, I don't think anyone should have a problem with that. (Don't rewrite public branches, of course.)

martin-t
7 replies
1d

There's levels of granularity that matter. You could just as well record all your edits in realtime. Make a script that makes a commit every second or every time you finish editing a line. It might be interesting later, yet that's usually not how people use git. Those changes wouldn't be meaningful units of work.

If you make a commit "wip" or "before lunch" because you want a backup of your work or want to continue on a different computer, then it's not a meaningful unit either. It's OK to throw away.

Most people prefer less granular commits but not to the point of having 1 commit per issue/PR. For example after inheriting someone else's code written in a hurry and not tested, I often end up dividing my work into several commits - first there's a cleanup of all the things that need renaming for consistency, adding docs/tests, removing redundant/unused code, etc. sometimes this ends up being more commits as i reveal more tech debt. Then, when i am confident i actually understand code and it's up to my standards, I make the actual change. This can be again multiple commits. The first and second group are often mixed.

And it's important when it later turns out i broke something - i can focus on the commits that make functional changes as the issue is usually there and not in the cleanup commits which can be 10x larger.

BTW what git is really missing is a way to mark multiple commits as one unit of work so the granularity stays there but is hidden by default and can be expanded.

rawling
5 replies
21h41m

BTW what git is really missing is a way to mark multiple commits as one unit of work so the granularity stays there but is hidden by default and can be expanded.

Is that not just a non-FF'd, non-squashed merge of a branch?

martin-t
3 replies
19h36m

That's the closest you get today but it means having to make, merge and delete branches all the time. What i propose is something like git squash but that keeps the history internally. It would present as one commit in gitk and other GUIs but could be expanded to see more detail.

sam_bristow
1 replies
19h20m

Does gitk have an equivalent of `git log --first-parent`?

Izkata
0 replies
17h29m

In the View menu dialog, there's a checkbox for "Limit to first parent"

normie3000
0 replies
12h55m

it means having to make, merge and delete branches all the time

Isn't this something that git makes simple?

sam_bristow
0 replies
19h22m

This is my preferred branching model. Most forges seem to call it "semi-linear history". If you have a lot of people working on the repo you'll probably want a merge queue to handle landing PRs but that's pretty straight forward.

It works really well with things like git bisect. It also means history is actually useful.

squeaky-clean
0 replies
18h57m

Make a script that makes a commit every second or every time you finish editing a line. It might be interesting later, yet that's usually not how people use git. Those changes wouldn't be meaningful units of work.

Every Jetbrains IDE does this, and VSCode has it's own equivalent feature. They don't use git, but same thing really. It's one of the most useful features ever IMO.

quectophoton
0 replies
18h17m

If people say "preserve history" as in "literally don't delete anything", then yeah I see where you're coming from.

I'm not against rebase, and even use it myself. But having a repo where every 3rd commit is a dice roll for git bisect just because straight line pretty, is just as annoying as people shipping their reflog.

A rebase of one commit is harmless. A squash is harmless. A rebase of multiple commits where every commit is deliberate (verifying all rebased commits, etc) is harmless.

A rebase that ignores the fact that any commit whose hash changed can now fail, is irresponsible. Shipping `wip` commit messages is irresponsible. A merge commit with the default message is irresponsible (it's no different from a `wip`-style commit). Having a branch with merge commits that could have been cherry-picks[3].

Also, to me the lie is not some aesthetic thing like commit order or some easily forgeable timestamp; the lie is having a commit that (for example) assumes the `p4tcc` driver is being used[1], and you read the diff and indeed it has assumptions that imply that driver is being used[2], but when you actually checkout that commit and see if that driver exists it turns out no it fucking doesn't, and hours were wasted chasing ghosts. Only because when that commit was created, the p4tcc driver was being used, but when you checked out weeks later now that commit magically uses the `est` driver instead.

If you're going to keep straight line, then test every change; if you don't do it, don't complain about broken middle commits.

If you're going to do merge commits, then keep each commit clean[4], even the merge commit[5]; if you don't don't complain about a history that is polluted with weird commits and looks like the timeline of a time-travelling show.

[1]: Because it did when that commit was created.

[2]: Because, again, it did when that commit was created.

[3]: This assumes the branch will later be integrated into main with a merge commit.

[4]: Squash is harmless. It's just omission. If anyone complains about purity, then just keep them happy with `git reset $COMMIT ; git add --all ; git commit -m "This is a new commit from scratch"`

[5]: Write something that helps those who use `git log --first-parent`. If you're on GitHub, at least use PR title and description as default (can be overriden on a case-by-case basis). If not, then even just "${JIRA_ID}: ${JIRA_TITLE}" is more useful than the default merge commit message while still letting you be lazy.

devjab
0 replies
19h4m

I depends on what you view as the real history. If you link each pull request to a work item you’re not going to really need all the commits on a development branch, because the only part of the history which matters is the pull request.

I think people should just use what works for them, if that’s debase who cares? The important part is being able to commit “asd” 9 billion times. If you can’t do that it will tax your developers with needlessly having to come up with reasons why they committed before lunch… that meeting… going to the toilet and so on.

dahart
0 replies
43m

Yeah, exactly, it’s up to the author to determine what’s important to preserve. Note this is always true, because the author is the one who commits, and can do anything before committing. If keeping the “before lunch” commit is useful for the history, rebasing does not prevent that in any way. Personally, I doubt that particular comment really is just as likely to be useful as something describing what the change is, but I’m with you that it’s author’s choice. It seems like squashing “WIP” and “before lunch” and describing the change content & reasoning has quite a bit higher likelihood of usefulness down the road than a comment on when you planned to eat lunch, and that has been true for me in practice for many years.

There is no “real history” in git, and it’s kind of a fictitious idea, even in Fossil or other VCSes that don’t offer rebase. Think about it: commit order of “WIP” ideas in a branch is already arbitrary, and commits only capture what you committed, not what you typed, nor when you ran the build, nor what bugs you fixed before committing, nor what you had for lunch, nor anything you didn’t choose to commit. Taking away rebase only adds extra pressure to plan your commits and be careful before committing, which means that people will do more editing that is not captured by the commit “history” before committing! Having rebase allows you to commit willy-nilly messes as you go and know that nobody has to see it. It seems like rebase might very well be safer in general because it encourages use of the safety net rather than discouraging frequent messes… and we’re all making frequent messes regardless of VCS, all we’re talking about is whether we force the rest of the team to have to be subjected to our messes.

Git provides change dependencies, and does not offer “history” in the sense you’re implying. People overload the word “history”, and git’s sense of history is to show the chain of state dependencies known as commits, and those have editable metadata on them. In other words, git’s “history” is a side-effect, a view of the dependencies. Git’s “history” does usually have loose association with an order of events, but nothing is or ever was guaranteed. It is by design that you can edit them (meaning build a new set of dependencies with rewritten metadata… the old one is still there until garbage collection), therefore there is no “real history”, that’s not a real thing.

osigurdson
8 replies
1d4h

I think most people these days just look at PRs. Everything else is largely noise.

saagarjha
7 replies
20h17m

Not at all. Someone’s got to look at your commits in the future when your code breaks ;)

Izkata
5 replies
17h24m

Yep, this is why I'm mostly against squashing (and completely against blind squash-merges).

Spivak
3 replies
12h30m

I'm not sure I get the advantage. The only thing I know is that the last commit on each PR is the one that is claimed to work. All others might as well be noise at that point since those random intermediates were never HEAD on the main branch, might be broken, incomplete, have failing tests, etc.. Squashing every PR into a single commit is at least an honest history of what's actually going out.

If you squash you have a history where every commit was tested and works (bugs notwithstanding) which to me is way more useful.

saagarjha
1 replies
11h43m

I mean you should be designing your commits such that each individual commit builds. That's the point of using squashes to fix up your history!

osigurdson
0 replies
5h48m

Commit refactoring can be really hard work however. Basically you do something like taking N random commits and convert these into M logical ones - where each one delivers incremental value and builds upon the other.

For some types of work it is easy, N=M: you were able to do high quality value adding atomic commits for the whole PR without rework.

For other types N >> M. This can happen when trying different approaches to a hard problem. I suppose research type work could always be considered a POC and the actual implementation could be a kind of cleanroom re-implementation of the POC but there isn't always time for such things and (again) the PR is far more important than the commits that built up to it - particularly if the resultant code is of equal quality. Note that I am not advocating for long running branches here - trunk based development is generally better provided it doesn't over incentivize teams to avoid hard problems (but that is a topic for another day).

This is why I think git should include the PR as a first class concept. For simple N=M type work, 1 PR should generally be 1 commit. Why not, after all, make the PR small and easy to review when you can? For harder N >> M type work, you get one PR with many commits that one can dig into if necessary.

Izkata
0 replies
11h29m

(bugs notwithstanding)

This is the reason. I've been on a maintenance team for years where almost everything we handle was written by people no longer at the company, and often enough I've seen bugs get introduced during the original work, where the fix ends up being obvious because I can see the original commits and how the code got into its current state. A squash of any sort would've hidden the refactor and made it much more difficult.

My favorite are ones where "linting" and code formatter commits introduce bugs. Keep those separate from your actual work, please.

saagarjha
0 replies
11h43m

Oh I'm for squashing to make the history make sense. Please do not blindly squash-merge though.

watwut
0 replies
9h26m

And messy intermediate states won't help him at all. It won't help him either when related commits are interwinned with unrelated commits in history rather then being together.

saghm
4 replies
23h11m

That's kind of the point though: being reasonably sure that a commit contains a tree that the committer had seen at some point, instead of making up history with commits that contain trees that the committer never saw at any point at all.

I don't really understand why this would be important. If I'm the one committing, I can rebase however I want to rewrite history before merging, so if I'm super adamant that a commit that looks a certain way exists, I can just make that commit and then put commits around it as needed to ensure that it can be merged with by fast-forward to preserve it. If I'm not the one committing, why should I care about what intermediate states that the person who committed them don't even care about enough to preserve?

To me, the issue seems more that the UX for doing this sort of thing is not intuitive to most people, so the amount of effort needed to get the history rebased to what I described above often ends up being higher than people are willing to spend. This isn't a particulary compelling argument to me in favor of merging workflows though because it doesn't end up making the history better; it just removes most of the friction of merging by giving up any semblance of sane commit history.

(edited to add the below)

When someone rebases `n` commits, experience has taught me I can't trust any commits other than `HEAD`; chances are any commit printed by `git log "HEAD~${n}..HEAD^"` was never checked out by anyone, much less tested at all.

I definitely agree that generating broken commits during a rebase is not a good thing for anyone, and I'd be super frustrated if I had teammates doing that. At least personally, I make sure to compile and run unit tests before continuing after each step of a rebase after I've fixed conflicts; there's even the `x` option in an interactive rebase to execute a command on each commit (which will halt and drop into commit and allow you to amend before continuing if it fails), which is unfortunately not super well known.

quectophoton
3 replies
17h19m

I don't really understand why this would be important.

It is important because not everyone does this:

[...] and then put commits around it as needed to ensure that it can be merged with by fast-forward to preserve it.

Good quality rebases like those are more likely to happen on patch-based workflows (not necessarily email), compared to PR-based workflows, because there's more focus on the individual commits themselves being meaningful, with straight line history being mostly a nice side-effect. With "more likely" I mean literally that, more likely; I'm not saying it only happens there.

In PR-based workflows on the other hand, people tend to care only about HEAD. PR color is green? LGTM ship it :rocketemoji:. Most just read blog post by git shaman saying straight line pretty and then go to GitHub and enable the setting for that without thinking more than that; or learn that you can reorder commits to tell pretty story and do it without thinking more than that.

Though it's also true that some repository owners only care about the tagged commits; all untagged commits could be broken and they don't care because "it's supposed to be in progress" and "as long as the most recent commit works, it's fine". They've never needed to checkout any specific commit on any repository (understandable if they never contribute to others' repositories).

---

Also, you probably noticed already because of your edit, but re:

If I'm not the one committing, why should I care about what intermediate states that the person who committed them don't even care about enough to preserve?

With "intermediate states" I don't mean what other people committed; I mean all your own commits that you just rebased (all your own commits whose hash changed) that are not the most recent one.

You are in the minority that fixes those; most people I've met would be like:

* All commits are tested and work fine.

* Create PR.

* See CI fails because branch is outdated.

* Rebase PR onto most recent commit of main branch.

* See CI fails because, idk, let's say it's something easy to fix like a more strict linter config.

* Make a new commit that fixes the linter errors.

* CI passes.

* Everyone LGTM's the PR and it gets fast-forwarded.

* The PR had `n` commits, but now `n-1` of those fail the linter because they contain the new config for the linter, but the committer never bothered to look at those commits, they only cared about HEAD. Those `n-1` commits "contain trees that the committer never saw at any point at all" (copy-pasting that quote from my message). And it doesn't matter that those commits are broken because for those people having pretty straight line is way more important than a working commit.

The recent FreeBSD/Netflix thingy[1] had a successful bisect only because when people rebase stuff in there, they don't YOLO those `n-1` rebased commits. If that had been any of my previous workplaces, or anyone who only rebase because "straight line pretty" without thinking anything more than that, then that whole bisect could have gone way worse.

[1]: https://news.ycombinator.com/item?id=40630699

saghm
2 replies
16h29m

All of your points seem accurate to me, but I don't see how merge workflows fix any of it. It seems like the same thing could happen where each commit along the way is broken until the final one, and then it's merged as-is. I don't think that having those intermediate commits being the exact ones that the person made is a solution because the problem you're describing is social, not technical; people not caring about committing messy intermediate state to the repo isn't going to be fixed by using merging rather than rebasing. The only workflow that would eliminate the problem entirely is to completely remove all intermediate state by squashing to a single commit before any merge, at which point doing a merge versus a rebase won't matter.

quectophoton
1 replies
15h23m

Neither workflow fixes anything. Each strategy helps with some things, but require discipline in other things.

Using merges lets you commit as you go, without needing to go back to repeat a test on a previous commit, and only worry about conflicts at the end of your development. Write code, test, commit. Write more code, test, commit. Cherry-pick, test, commit. Merge into main, fix conflicts, finish merge. There's never a need to go back and re-test, like with rebase, because the commits that were already tested are still there. But they require discipline to not pollute history, and being open to squashing commits that don't add any useful information (you want to avoid having "WIP"-style commits).

Using rebases lets you rewrite commits to take advantage of the most recent changes from the main branch, instead of waiting until you finish with your feature. But they require discipline to go back and repeat tests to ensure that any commit that changed still works as expected (and it's needed because the commits changed, hence their different hash, so they are no longer the commit hashes that were tested), and being open to having some merge commits (you want to avoid rebasing a 10 commit migration of your telemetry library because if 3 months later you find out your costs in production were way higher than what they told you they would be, reverting a single merge commit is more dumbproof compared to reverting a manually provided range of commits).

So yes, choosing one or the other is a social problem. Both are good solutions with good discipline, and both are bad solutions with bad discipline. One of those makes it less likely for people in my bubble to make a mess out of that repo. It might be the same as for your bubble, or it might be different.

But on a good project it doesn't really matter which one is done.

saghm
0 replies
13h41m

So yes, choosing one or the other is a social problem. Both are good solutions with good discipline, and both are bad solutions with bad discipline. One of those makes it less likely for people in my bubble to make a mess out of that repo. It might be the same as for your bubble, or it might be different.

But on a good project it doesn't really matter which one is done.

I appreciate your explanations! I think I understand your point of view now, and I do actually agree with it. In particular, I hadn't fully considered that the problem ultimately being social means that the "best" choice will be mostly dependent on what consensus a group is able to come to.

Thinking about this more, it almost seems like having a preference could become self-reinforcing; it's hard to be a member of a group that reaches a consensus on using merges as someone who prefers rebases (and likewise for the reverse), which over time manifests as more and more anecdotal evidence in favor of the preference working better than the alternative. It's no wonder that debates about this sort of thing become so contentious over time...

ajross
2 replies
17h47m

That's kind of the point though: being reasonably sure that a commit contains a tree that the committer had seen at some point, instead of making up history with commits that contain trees that the committer never saw at any point at all.

I don't see how this follows. Merge-heavy histories in my experience tend to be far less bisectable. They have all sorts of "oops, fixup" nonsense going on, precisely because the author did not take the time to get things right the first time.

Any workflow that happens on a number of patches greater than 1 accepts poor bisectability as a risk. But the only real solution there is Giant Monolithic Commits, which we all agree is even worse, right?

quectophoton
1 replies
16h29m

Yeah if "merge-heavy" means "ship the reflog", I get what you mean.

But if "merge-heavy" means "use merges when it makes sense, use rebase when it makes sense", then you can get a nice history with `git log --first-parent` that groups related commits together, and also a nice history with `git log --cherry` that shows what the "always-rebase-never-merge" dogmatic people want.

If for this particular project it just so happens that merge doesn't make sense because of the specific needs of the project, then so be it, nothing wrong with that. Same with rebases.

Unfortunately this topic is another holy war where the ship-the-reflog dogma fights against the always-rebase-never-merge dogma.

No balance.

I don't see how this follows. Merge-heavy histories in my experience tend to be far less bisectable. They have all sorts of "oops, fixup" nonsense going on, precisely because the author did not take the time to get things right the first time.

That sounds more like merge-only (a.k.a. "ship the reflog"). Doesn't have to be that way.

Evaluate trade-offs and choose based on that evaluation.

Does adding a new commit have any actual advantage (e.g. easily reverting one or the other) compared to just amending/squashing it, or is it just some developer's own subjective sense of purity?

Does re-ordering the commits have any actual advantage (e.g. change has a smaller context and can be more easily reverted that way) compared to just leaving those commits in that order, or is it just some developer's own subjective sense of aesthetics?

Does using merge commits bring any actual advantage (e.g. the project benefits from being able to bisect on PRs or features as a whole) compared to rebasing (not fast-forwarding), or is it just some developer's own subjective sense of purity?

Does rebasing bring any actual advantage (e.g. each commit is already atomic, fully self-contained, and well tested against the new base, so "grouping" them with a merge commit doesn't make sense) compared to doing a merge-commit, or is it just some developer's own subjective sense of aesthetics?

Any workflow that happens on a number of patches greater than 1 accepts poor bisectability as a risk.

Poor bisectability or developers putting actual effort into ensuring commits are atomic and test them.

Bisectability is nice with good rebased commits. Bisectability is nice with good merge commits.

Bisectability is bad when developers don't care about keeping bisectability good.

But the only real solution there is Giant Monolithic Commits, which we all agree is even worse, right?

It depends.

Those commits might not be easy to understand, but they sure as hell are easy to revert (more likely than not) if something goes wrong, because they tend to correspond almost 1:1 to GitHub issues (or Jira tickets, or whatever equivalent). Keyword "almost" because sometimes you can get 2 of those for the same issue/ticket/whatever.

But those 2 unproperly split commits (therefore huge) are still easier to revert compared to a spray of 10 unproperly rebased tiny commits where 9 of them are broken (because of what I mention in other comments where people only test HEAD).

ajross
0 replies
4h0m

Too much text. But what I will say is that the "good" merge workflow you posit really only exists in one place (Linux) and requires a feudal hierarchy of high value maintainers individually enforcing all the rules via personal virtuosity. I've never seen it scale to a "typical" project run by managers and processes.

Where the straightforward "get your stuff rebased into a linear tree" tends to work pretty well in practice. The rules are simpler and easier to audit and enforce.

rednafi
1 replies
1d5h

This assumes the rebasing will be done in a shared branch. Two rules of rebasing:

1. Never rebase a shared branch 2. Never break rule 1

lmz
0 replies
1d5h

No, that describes rebasing and preserving the intermediate commits. Of course, if you squash into one commit at merge time, this won't happen.

zimbu668
0 replies
15h24m

nobody would bother re-testing all those n-1 commits after the fix was added

I do.

kazinator
0 replies
19h49m

If you want the full history of someone's work, you need all the edits. Including all the times they backspaced over the typo. With down-to-the-millisecond timestamps attached!

forrestthewoods
0 replies
7h53m

leaving broken commits useless for git bisect.

Honestly all of this is just a function of Git’s tooling being pretty bad.

There’s no reason that a merge based history can’t be presented as a linear history. That’s purely a matter of how the information is displayed!

Similarly there’s absolutely no reason that git bisect should try to operate on every single commit. We live in a world where CI systems need to land a queue commits at a time. No one can afford to run every test on every commit. Git should have support for tagging commits that had varying levels of tests and then running bisect against only those commits. Easy peasy.

cryptonector
0 replies
19h44m

When I rebase I diff after each rebase to check that the only diffs are the ones I intended. So I, the committer, have seen all my rebased commits.

AceJohnny2
34 replies
1d20h

I'll add what should be obvious to any seasoned git user: rebase is only possible for "private" commits. If the commits were published, then merge is the only solution, because otherwise with a rebase you will "change (git) history" and break people's repos that have yours as upstream.

I recommend reading this 2008 exchange between top Linux developers learning to use git, and Torvalds's... characteristic language when talking about rebasing a "public" repo:

https://www.yarchive.net/comp/linux/git_rebase.html

(I'm putting "private" and "public" in quotes because they're really on a spectrum)

PaulDavisThe1st
24 replies
18h18m

If you work on a branch also worked on by others, then yes, rebase is "only" possible on private commits.

But if you work on a feature branch, you can publish that branch as long as everybody else understands that it is yours and nobody should push to it. Then you can safely git push --force to the feature branch so that others can (a) see what you're up to (b) test the results. When you're done, you can do a final (or only) rebase against the target branch then merge to that branch.

patrickthebold
11 replies
15h47m

What annoys me about this is that every branch ends up being "private" and people don't collaborate (In practice). So you end up with: "I can't start work on X until feature Y is merged, and there's a lot of PR comments left unaddressed so let's just merge Y since X is urgent and fix the PR comments later..."

PaulDavisThe1st
6 replies
15h32m

That's not at all our experience at ardour.org.

Most PRs happen against the main branch, maybe after a private feature branch, maybe not.

Meanwhile, major developments happen in a feature branch, and these are almost always owned by a single person who is responsible for rebasing against the main branch (and eventually merging back there).

But maybe our development team and pace are not good tests for this model.

The_Colonel
3 replies
13h14m

and these are almost always owned by a single person who is responsible for rebasing against the main branch

That still seems very annoying. People have unplanned time-off all the time, live in different timezones, or perhaps are l just busy ATM and can't do the rebase.

randerson
0 replies
6h22m

This can be mostly avoided with short-lived branches that are merged by the end of each day. If that isn't feasible, people can still collaborate on a feature branch as long as a single person is nominated to rebase it at the end.

SOLAR_FIELDS
0 replies
12h15m

In practice I’ve found:

- Unplanned time off means the feature just doesn’t make it in

- Living in different time zones means that you just wait and work slows down

In either event, there is rebase and squash auto merge from major git providers like GitHub which helps with this a ton. With that enabled unless the repo is super high traffic usually this is an afterthought unless there is merge conflicts which is semi rare.

That being said, that’s just the reality of what I’ve seen play out in various organizations. I personally like to work off other people branches a lot more than my peers because I work in platform engineering and very often developers come to me with feature branches that need some CI/infra/other change or optimization and very often it’s easiest for me to just drop a commit into their existing branch. The change falls under their ownership and it gets mainlined along with their feature.

PaulDavisThe1st
0 replies
4h46m

They are the only ones working on the feature branch. If they take a vacation or fall ill or whatever, it causes no issues for anyone else.

ykonstant
0 replies
10h39m

Off topic, but thank you for having a perfectly modern and beautiful skeuomorphic GUI in Ardour. Tactile buttons with descriptive names instead of vague flat squares with incomprehensible icons; I wish more software offered this kind of interface.

kfrzcode
0 replies
14h6m

OT but I love Audour so much. Thanks for you and your team!

watwut
0 replies
9h33m

cooperating on half baked code sux so much, that I happily take the trade off

kalaksi
0 replies
10h3m

If feature Y is incomplete, then sharing a feature branch (which sounds like a road to a messy commit history) doesn't change that. You could also use the unmerged feature branch Y temporarily as a base for X if you need some code from it.

another-dave
0 replies
7h10m

Do you think that works any better if you're using merges Vs rebasing though?

If the problem is that you urgently need some small fix in an otherwise broken branch, you can always just cherry-pick that across to your own fresh pr.

Shog9
0 replies
15h2m

My team ran into this early on when attempting to adopt a rebase-preferred workflow for feature branches...

...then we got used to it. A few workflow changes were necessary:

- configure pull.rebase=true. This is kinda just nice in general, but critical if someone might have rebased the branch you're working on overnight. - get used to pushing up your changes regularly - at very least before you quit for the day. - get used to pulling remote changes regularly - at very least before you start work on a branch each day. Rebase after pulling - especially important if you've branched off another feature branch (which may have been rebased). This way you avoid doing a bunch of work on an out of date base. - *Talk to each other.* If it isn't already obvious what a collaborator is doing... Ask them! And err on the side of over communicating what you're doing.

It turns out, all of these behaviors are kinda just generally useful, and after a bit you forget about rebase being the motivation and just enjoy having a bit less friction when working together.

smcameron
8 replies
16h45m

Then you can safely git push --force

What I hate about this workflow is it teaches you to git push --force.

Then one day, you're in the wrong window and you accidentally git push --force on master.

Much prefer a workflow where you never git push --force

necessary
3 replies
16h2m

Git hosting platforms generally have controls that allow you to prohibit force pushes on protected branches (i.e. master)

The_Colonel
2 replies
13h13m

This doesn't fix the problem since you still can do a lot of damage doing that on a (perhaps wrong) feature branch.

nickkell
0 replies
10h38m

That's where reflog comes in handy

SOLAR_FIELDS
0 replies
12h8m

It certainly diminishes it significantly. Very few people are usually collaborating on the same branch together that isn’t the main branch in most orgs. So the chances of you destroying yours or someone else’s work that way is pretty low. You can also branch protect any arbitrary branch, at least on GitHub, though I’ve never been in an org that protects branches other than the main one, besides an org that used release branches. I’ve also never seen someone clobber someone else’s feature branch, though it probably does happen. Much more often people are accidentally clobbering their own branches.

The ability to rewrite history on feature branches is powerful in a good way and slots right into the way Git is philosophically designed. I would probably not be interested in removing that feature to prevent the rare case that someone footguns themselves or someone else

zimbu668
1 replies
15h27m

You should do this instead:

git push —-force-with-lease repo branch

ajb
0 replies
13h14m

This. --force-with-lease is what --force should have been in the first place. Hopefully they will eventually make it so, and rename --force to something less accessible.

cortesoft
1 replies
13h37m

Any repo worth its salt shouldn't allow a force push to the main branch.

ck45
0 replies
10h21m

I fully agree, and also think that if it's a professional environment pushing to the main branch itself is very questionable for most / code repos. Not only because of the Sarbanes-Oxley Act for public companies.

Aeolun
1 replies
11h37m

Never use git push —force. Alway use git push —force-with-lease.

Then if someone changed your remote branch, git will always let you know :)

arrowsmith
0 replies
7h55m

Been using git every day for ten+ years and I never knew about --force-with-lease.

That's great to have learned, thanks!

e12e
0 replies
6h0m

I think feature branches typically should be "mostly" private - but sometimes a few quick fixer are better to push, rather than just describe as a review comment.

MrDarcy
4 replies
20h17m

In practice it’s fine to rebase and force push to your own published branch, even when it’s an open PR.

Just mark it draft or otherwise communicate when it’s no longer a moving target.

singpolyma3
3 replies
20h1m

Especially when it's an open PR. That's effectively essential in order to, say, address feedback

spankalee
2 replies
17h35m

I try not to rebase and force push for branches under active review though. It makes it much harder for the reviewer to see the changes that have happened since they last reviewed.

everybodyknows
0 replies
4h25m

I version-number PR branches for upstreams on GitHub. If my branch 'for-upstream-13.0' does not apply cleanly on top of upstream 'main', I sync my fork's 'main' to upstream in GitHub's Web GUI, fetch to my desktop, and build a new 'for-upstream-13.1' on top, with the help of git-cherry-pick. Might need a new PR. All history is preserved, both on GH and locally.

argulane
0 replies
8h3m

That's only a problem when using GitHub. GitLab and Gerrit correctly version such force push PR updates and provide a nice view of what has changed.

forrestthewoods
2 replies
7h57m

rebase is only possible for "private" commits.

This is false. Well, it can be false. Git sucks and makes it harder then it needs to be.

It’s relatively easy to have multiple people working on a feature branch that is continuously rebased.

Meta’s Mercurial-like VCS system automagically stores 100% of commits in the cloud. Feature branches are anonymous rather than named. It’s all just a tree of commit hashes.

Having multiple people work on one “branch” requires some basic communication as to what is considered “tip”. But since it’s all anonymous there’s no issues with different people incrementing a named branch to different commits.

Honestly it’s pretty simple and easy. Git makes it harder than it actually is.

rmgk
1 replies
4h19m

As far as I can tell, what is really meant by “rebase is only possible for "private" commits.” is that for public commits, rebasing would require to coordinate with everyone interested in that branch.

This seems to be very similar to what you say with “requires some basic communication as to what is considered ‘tip’”

Requiring coordination (or a constrained model of allowed changes) when modifying things (such as what the tip/branch points to) is a general principle unrelated to git and applies to every workflow.

That said, I do understand that Meta’s centralized tool is more useful for your usecase.

forrestthewoods
0 replies
2h26m

I do understand that Meta’s centralized tool is more useful for your usecase.

One of my chief Git complaints is that 99.9% of projects are de facto centralized on GitHub. Genuinely decentralized projects are vanishingly rare.

The way Git auto-advances branch tags causes a lot of pain. Having lots of people commit to a shared feature branch that has lots of rebasing is quite easy when the “tip” is infrequently updated. Each person thinking they can declare the new tip with every commit is the source of pain.

ruraljuror
0 replies
1d18h

I like your point. The exception is if the project you work on is itself downstream, in which case maintainers will rebase those “published” commits, probably employing the same technique they use to keep their local development branches up-to-date with the downstream project.

stavros
13 replies
1d5h

I've been a developer for twenty four years. I've seen a lot of people have strong opinions about whether to merge or rebase, to squash or not to squash. I haven't seen one single time where it made a difference whether something was merged or squashed, as I've never seen anyone look at commit messages in depth, versus tickets or PRs or bisecting.

klysm
7 replies
1d4h

The problem I run into is when a developer merges the main line branch into their feature branch repeatedly to get new changes. Now your branch is completely tangled up and difficult to change

recursive
6 replies
1d3h

I do this like... all the time. At least weekly for the last 5 years maybe? I can't remember a time where this has caused anything to get tangled or difficult.

singpolyma3
1 replies
19h57m

...why? Isn't this exactly what rebase is for? Just plain, boring, non-interactive rebase.

recursive
0 replies
17h39m

Maybe? But it's also what merge is for.

If I used rebase like this regularly it would become difficult to determine if a given commit is an ancestor of HEAD. Sometimes I like to do that.

recursive
0 replies
1d1h

Merges I create from the CLI don't really look that differennt from the ones through some forge UI PR process. I don't know what Linus is getting at. If I was working under someone who had a process like this, I could ask them to clarify what they wanted me to do. I don't have that. And I need to be able to justify all of my decisions. So this isn't useful to me.

I don't doubt that Linus has good reasons for all this. I just don't know what they are. And I don't know if they're applicable to other repos.

klysm
1 replies
1d3h

Cherry-picking is harder. Splitting up the branch is harder.

recursive
0 replies
1d1h

I cherry pick occasionally. I don't see how it's affected at all? Edit: If another dev and I both cherry pick the same commit concurrently, that might do something weird. Maybe that's it?

I don't know what splitting up a branch means.

dnadler
1 replies
1d5h

Really?

I certainly have used commit messages and seen others do the same. Perhaps this is more an indictment of the quality of the commit messages than anything else.

In my experience, rebase and squash makes it easier to collect work into meaningful groups and thus write more helpful/informative commit messages.

I can think of a few times off the top of my head when I referred back to a detailed commit message in a repo to understand why a change was made.

stavros
0 replies
1d4h

If I'm making a change that people are likely to wonder about, I tend to add comments. Basically, anything that's not obvious tends to get a comment, change or no.

sensanaty
0 replies
10h30m

I would like to do it more, but it's hard to convince people to not produce absolutely useless commit messages, and trust me I've tried.

Looking through my work project git log, it's a sea of random ticket numbers followed by absolutely nothing helpful or descriptive, usually just the title of the ticket if you're lucky.

david_allison
0 replies
1d2h

For what it's worth, I often look at commits/commit messages in-depth.

I wouldn't do so if the history were a mess

citrin_ru
0 replies
1d4h

I read 'git log -p' often, and linear history makes it so much easier. The same with bisecting. I'm yet to see any practical advantage of not allowing rebase/squash in feature branches. Preserving all history of feature branches is not an advantage to me. The history I care about is the history of the main branch and I don't want to see it splint into many tiny commits, especially not into commits looking like add/fix/fix/fix/fix where at all point except the last the code is broken.

rictic
10 replies
1d19h

Sure it looks nice, but it's a fake history. After a rebase you have commits where no one's ever run the tests, no author ever intended to get the repo into that state. Hell, most likely no one knows if the code even compiles.

It's assuming that that merge conflicts will never be too difficult, and people won't make mistakes in resolving them. If so, too bad, the original commits are lost by the rebase process. (They might or might not be kept in any given clone. No guarantees, good luck trawling through the reflog.)

It's corrupting your database because it makes the UI for exploring it nicer.

This is the opposite of what we should be doing! If the truth is messy we should build better UIs for exploring it. Add additional information on top of the true history, allow people to group and describe commits. Add more information to get the UI that you want, rather than destroying information because it makes things look cluttered.

AceJohnny2
3 replies
1d18h

After a rebase you have commits where no one's ever run the tests, no author ever intended to get the repo into that state. Hell, most likely no one knows if the code even compiles.

That is the role of the CI system.

lmz
2 replies
1d5h

CI will usually only run the latest commit. Even if commit a, b, and c were all tested, the resulting commits a', b', and c' after rebase would usually only have the last one tested.

skybrian
1 replies
20h57m

Since you shouldn't publish commits that weren't tested, this suggests you should publish only one commit at a time.

(Unless you think "works on my machine" is good enough testing. Sometimes it is.)

lmz
0 replies
17h14m

So rebase then force push a', wait for CI, push b', wait for CI, push c'? Personally I just squash on merge.

000ooo000
2 replies
1d12h

After a rebase you have commits where no one's ever run the tests, no author ever intended to get the repo into that state. Hell, most likely no one knows if the code even compiles.

Only true if the commit author doesn't do so, and it is trivial to do so, either during or after a rebase using the rebase exec command. So given this is a discipline issue no different from a developer authoring a change without testing it, I fail to see how this is "rebase"'s fault.

it makes the UI for exploring it nicer

Not to imply I accept the "corrupt database" opinion, but I think it's worth saying that aside from the collaborative element of VCS, commits exist for the purpose of exploring past code changes. A practice which improves that seems sound to me.

we should build better UIs for exploring it

Go right ahead :)

Nullabillity
1 replies
20h4m

Only true if the commit author doesn't do so, and it is trivial to do so, either during or after a rebase using the rebase exec command. So given this is a discipline issue no different from a developer authoring a change without testing it, I fail to see how this is "rebase"'s fault.

"Undisciplined enough to use rebase, disciplined enough to put in extra effort to mitigate some of the harms of rebase" is an imaginary intersection.

Go right ahead :)

    git log --first-parent

lolinder
0 replies
16h33m

This is begging the question (the fallacy, not the colloquial expression). It's only undisciplined to rebase if it's a bad practice, which is the topic under consideration here.

You can't use your preferred answer to the debate as justification for dismissing your opponent's arguments.

watwut
0 replies
9h23m

Same with merge, they are just somewhere in the middle.

singpolyma3
0 replies
19h59m

Nothing prevents people from committing untested code that doesn't even compile without any rebase involved. Even hooks don't help since the incompetent coworkers are just barely smart enough to learn about -n

lolinder
0 replies
16h29m

After a rebase you have commits where no one's ever run the tests, no author ever intended to get the repo into that state.

I take it that you've never reviewed a PR where the commits were a series of "wip" commits that don't even type check, much less pass the tests?

Undisciplined developers will be undisciplined. Forcing a rebase-free workflow mostly makes it less likely that these developers will lose work, it doesn't magically give you a clean commit history from them.

Nullabillity
4 replies
20h8m

I reject the argument that a no-rebase, merge-only history "preserves the true history of how commits were created", because I believe that is irrelevant. What is relevant is what the tree looks like once the merge (or rebase) lands.

Was the bug introduced by the rebase, or was that code always broken?

singpolyma3
3 replies
20h5m

Code doesn't exist until it is merged, so these are equivalent

Nullabillity
2 replies
19h27m

Err, what?

If I'm trying to fix the PR, the distinction is often a critical starting point for finding the root problem.

lolinder
1 replies
16h37m

If your PR is big enough that you're relying on a git bisect to find a bug, that's a problem in its own right.

A workflow like what OP is describing (code doesn't exist until it's merged) typically also assumes that your PR is one atomic change, not a whole feature. You'd use feature flags or something similar to decide when to actually release a feature, not the PR process.

Nullabillity
0 replies
6h3m

If your PR is big enough that you're relying on a git bisect to find a bug, that's a problem in its own right.

Who said anything about bisecting?

But yes, sometimes changes are big and I'd rather land them as one cohesive unit that demonstrates how it actually solves the root issue than get stuck down a wrong path because "this is what we already landed the infra for".

A workflow like what OP is describing (code doesn't exist until it's merged)

That's not a workflow, it's a misunderstanding of reality.

ak217
2 replies
15h6m

I love rebasing, and use it most of the time. At work we also enforce squash-merging, which is the only scalable way to prevent low quality commits from polluting the main branch history.

While rebasing works most of the time, the problem arises when actually collaborating on feature branches - especially when your collaborator is not confident enough with git to realize when a rebase conflict might lose data. Merging works better in these situations. So while standardizing on rebasing is great for productivity across the org, you also have to watch out for this and make sure developers don't lose the ability to collaborate on branches.

The_Colonel
1 replies
13h7m

What's the point of rebasing when you in the end squash-merge?

In my mind the only real benefit of rebasing is that you split your work into a couple of "nice" commits, but if you squash them anyway...

Ok, I've seen people doing this to ease the review process and I guess it makes sense sometimes, but to me it's still a largely "not worth it" effort.

sensanaty
0 replies
10h29m

The squash merge is for the sanity of master/main, whereas rebase is for the sanity of the reviewer. That's how I look at it at least

treflop
0 replies
9h29m

I've always felt the the rebase vs. merge debate was pointless because I never have dirty internal history by the time that I'm ready to make an MR/PR.

It's just that squashing takes 2 seconds. I just shift-select the commits in my Git client and click 'squash'. To me it's just like cleaning up after cooking. And if I want to re-order my commits, I just like drag and drop the order of them.

If I told you right now to squash or re-order your last 5 commits and you think it'd take you longer than 2 seconds, you are honestly using a bad tool.

Yet hundreds of people have spent many human hours writing these long blog posts about how you have to do things a certain way yadda yadda yadda when the real problem is that they use bad tools, squashing is such a pain, they don't bother to clean up their "fix 1" "fix 2" "try again" commits, and everyone has to deal with Git gymnastics at the end.

rurban
0 replies
1d3h

Exactly. The only true history is the clean, properly rebased history with one change per commit, as I want it.

lr4444lr
0 replies
20h37m

Preach, brutha!

eschneider
0 replies
20h20m

I generally prefer a rebase-free workflow (mostly due to my upbringing. Long story...) But other than rebasing shared branches, which (with notable exceptions) is a Wrong Thing, it's mostly up to teams to decide on a workflow that gets things done for them. As long as there's a consensus on how to do things, go with god.

danlugo92
0 replies
1d5h

Honestly I dont even do history anymore for small projects, I bisect twice a year tops and I have never evee have had to look at a 3 year old commit to figure out a bug, not that the code could'nt be 3 years old but the blame is so tangled at that point..

mrinterweb
20 replies
1d20h

Atomic commits can be great. I really prefer to see intentional atomic commits with meaningful messages than 20+ files changed with a commit message: "Feature X".

I am 100% onboard with devs squashing their "lint fix", "test fix", "whatever minor fix to get CI working", "generally meaningless commit to cross the finish line". Also, if devs are working on something they check in a "WIP" commit. It is great if you smash your WIPs into a single meaningful commit. These manual squash strategies require some discipline to clean things up.

I think the "squash branch to a single commit" merge strategy defeats the purpose of atomic commits. Of course devs will be bad at atomic commits if the commits will inevitably smashed to a single commit. IMO squashing branches on merge is a bad version control strategy. I love it when commits are intentional.

One rule I have for any rebasing is, when there may be more than one person using the branch, no more rebasing that branch.

parpfish
10 replies
1d20h

What do you do with the commits that are “incomplete”?

I frequently get halfway through building something, but the. A more urgent project comes up and i need to commit my half-done non-functional work so I can hop onto a different branch

erik_seaberg
5 replies
1d20h

I stash half-baked work to keep it separate from quality commits. When I come back to the stash, I may or may not like where it was going and decide to build on it.

parpfish
4 replies
1d16h

Stashing always makes me nervous.

For some reason it feels ephemeral and I worry that I’ll lose the stashed changes like when you accidentally overwrote the copy/paste clipboard

recursive
0 replies
1d3h

Yes, I generally don't use it much, because it forces me to remember that I have stashed work.

klyrs
0 replies
21h11m

Committing everything to a new (temporary) branch, and returning to your current branch, costs nothing and saves the headache of stash's weirdness.

deredede
0 replies
22h38m

I used to feel this way because I lost work to `git stash pop` in the past, but there is now a nice feature where if you `git stash pop` and it doesn't cleanly applies, git keeps the stash and I use it more often now.

That said for me the stash is usually used either for temporary stuff (eg taking my current work to another branch) or for things that might be useful to reference later but I will rewrite differently anyways; stuff I want to keep goes into a WIP commit.

dahart
0 replies
13h57m

Git stash is indeed more dangerous than branching. Stashes don’t go into the reflog, and so you lose the primary safety net that git offers. I’ve watched people get into a bad state with stash and end up losing work. The stash documentation says “If you mistakenly drop or clear stash entries, they cannot be recovered through the normal safety mechanisms. However, you can try the following incantation to get a list of stash entries that are still in your repository, but not reachable any more: <git fsck ...>” https://git-scm.com/docs/git-stash

I know stash feels easier to a lot of people, and that’s a valid reason, but it’s really no more typing or thinking to use branches instead, it just might require changing habits.

ngai_aku
0 replies
17h48m

Are you familiar with working trees[1]? It may be a nice thing to utilize if you’re frequently jumping around on different branches.

[1] https://www.git-scm.com/docs/git-worktree

mrinterweb
0 replies
1d20h

I create "WIP" commits, and then I squash those. I squash intentionally to clean up commits that should be together.

kutenai
0 replies
1d19h

I take all "WIP" branches and rebase them as needed onto the 'latest' development. Any continued work on those branches will have to be compatible with all released code, so deal with any issues -- i.e. merge issues during a rebase -- now, rather than later when you try and submit a PR.

As a team leader, I prefer to avoid "a lot of" WIP branches, but I just expect developers to rebase their WIP onto dev, etc.

Oh, and I really really dislike "merging" develop into the WIP branch. This accomplishes the same thing as "rebasing" the WIP branch onto develop, but it leaves a horrible mess behind.

Frankly, I don' give a hoot about some "history" of work. In the end, I care about the unit of work encapsulated in that WIP branch, and that unit must always add on top of develop. Rebase just makes that super clear.

kcrwfrd_
0 replies
19h45m

I usually use stash for this stuff. But alternatively you can commit and then come back and squash / rebase to clean up the commit history.

tkiolp4
3 replies
1d20h

Atomic commits can be great. I really prefer to see intentional atomic commits with meaningful messages than 20+ files changed with a commit message: "Feature X".

The most valuable “feature” of git for me is the “single commit with 20+ files changes”. I explain: I have landed in a new codebase, and now I need to add a new feature that would include perhaps a migration and adding a “usecase” and perhaps a “controller” and the corresponding tests. As usual, things are never clear in new codebases (depending on the library/framework used, one might need to add routes to a main routing file, or perhaps touch some configuration yaml file to whitelist the newly introduced endpoint, or perhaps a changelog needs to be kept updated whenever a migration is introduced, etc.). My point is: if I can git blame a line of code of the codebase that’s doing already more or less what I want to introduce, I want to see ALL the files that were touched as part of that change. It’s a lifesaver.

mrinterweb
1 replies
1d20h

How many years have you worked in the software industry?

Professionally, 21 years. Seems like you're implying that someone with an opinion that is different than your own is not experienced.

tkiolp4
0 replies
1d20h

I deleted that part because it sounded the wrong way (it wasn’t my intention to question your experience, but a genuine curiosity)

mrinterweb
0 replies
1d17h

Git has great tools. It sounds like you'd want to see everything associated with a merge commit. If what you're looking for is a certain way to view changes, git's built in tooling usually has you covered regardless of commit strategy.

So if you know a commit sha and you'd like to see all of the merged branch changes associated with that commit as a single patch, I cooked up a script that takes a commit sha and finds the merge commit and show all the changes in a consolidated patch.

#!/bin/sh

merge_commit=$(git log -n1 --merges --pretty=format:"%H" $(git rev-list --ancestry-path $1..HEAD --merges -n1))

git show -m -p --stat --format="" $merge_commit

zikohh
0 replies
1d2h

I agree 100%. I also like using FF only. Reviewing commits is part of the code review process - their to title, description and code. If the commits are not atomic and clear I don't approve the merge request (MR); I believe the MR is a full snapshot of what will go to main.

It's so frustrating to see 7 commits for one MR and it's a 10 line change.

tmtvl
0 replies
1d6h

Atomic commits are also really nice for when some stealthy tricky bug appears and you need to git bisect to find out where it was introduced. Ending up with 'the bug got introduced somewhere in this massive commit that touches nearly every part of the system' is not very helpful.

deredede
0 replies
22h46m

I generally prefer PRs that have multiple atomic commits to be split up in separate PRs. If the atomic commits can't be reviewed separately/introduce non-working states, they are probably too small.

One rule I have for any rebasing is, when there may be more than one person using the branch, no more rebasing that branch.

That is the one rule to obey for rebasing to be useable at all.

arwhatever
0 replies
18h59m

One rule I have for any rebasing is, when there may be more than one person using the branch, no more rebasing that branch.

Is it just me, or is it genuinely funny that whenever someone does this I think of Deep Thoughts by Jack Handey: “When you're going up the stairs and you take a step, kick the other leg up high behind you to keep people from following too close.”

GauntletWizard
0 replies
23h26m

Branches should be small enough and short lived enough that they are atomic commits. Merge early, merge often, don't break things. Work on small pieces that don't change functionality, The missing part of this is that this causes you to need merge-trains earlier - You'll want to start "stacked" commits that are based on the pieces that are atomic but not merged. That sucks, because the tooling isn't yet great at handling that - It should be possible to mark and auto-rebase all your local branches in that dependency chain,

cocoto
13 replies
1d20h

In my opinion squashing is all you need. You keep the organic history on the original branch and the clean and easy to follow history on the master branch. Ideal would be to have a “collapsing” commit instead of squashing for ergonomy.

pizza234
9 replies
1d20h

Highly disciplined/structured PRs (with a high rate of atomic commits) merged via merge commit have significant benefits:

- easier to read/analyze

- have better documentation (more isolated git metadata/history)

- are easier to debug (and possible to bisect; it's not possible to bisect a squashed commit).

The problem is that it requires a significant amount of discipline (of course, 100% rate of atomic commits is not possible, but high rate is).

mrinterweb
4 replies
1d20h

Another benefit of not squashing is it encourages devs to think about their commits and is a great opportunity to document what you are doing when they are doing it. If you are squashing 10 commits with junk commit messages (because devs know there is little value in having meaningful commits in what will be a squashed branch), trying to summarize all the changes into a meaningful commit message is hard. It can be valuable to see the intention of atomic commits in git history. The squash branch on merge strategy is just lazy and is counterproductive to having meaningful git history.

jaredsohn
1 replies
1d20h

I prefer to add commentary in the PR on the final changes - what does it do, how does it work, what are some pieces of code I'm less sure of, etc. It is accessible since the squashed commit gets linked to it and that provides a place for people to ask questions.

Also is a lot faster to write and easier to communicate compared to messing around with moving code across commits, ensuring tests pass across them, etc.

Basically I think people care more about what was built and how it works rather than how to split it up step by step (although if the latter is important I can add a comment for that on the PR - although ideally it would have been separate PRs.)

000ooo000
0 replies
1d13h

I prefer to add commentary in the PR on the final changes

That vanishes when you shift forges

sjburt
0 replies
1d20h

I think the problem is that often the order you want it in a clean history is not exactly the chronological order it was developed. Eg you may build out a feature vertically, tweaking the interfaces between the components as you go along. But a clean, atomic git history would probably introduce each component in a finished state in separate commits.

recursive
0 replies
1d19h

This depends on the assumption that going to the trouble of making a carefully curated commit history well documented is even worth the trouble. Sometimes it might be, but I don't think it's a given.

The squash branch on merge strategy is just lazy

"Lazy" is the negative way of saying "easy" or even "more efficient". "Lazy" implies that the other way is better. Is it? Maybe sometimes.

Am I "lazy" if I walk on my feet instead of my hands? It really is a lot easier.

jaredsohn
3 replies
1d20h

- are easier to debug (and possible to bisect; it's not possible to bisect a squashed commit).

In practice, if bisecting indicates a commit caused the problem you can narrow it down further by checking out that branch and investigating further within that branch (which ideally isn't too large). Also, if you have a highly structured PR then you may need to be careful to ensure that each individual commit passes CI.

At that point you might as well ship each commit via a separate PR. (While in development you can temporarily set the branch for Part 1 as the base for Part 2.)

fouc
1 replies
1d20h

You're assuming it is not an old bug and that the branch is somehow kept around for other developers to re-open months or years later.

A PR for each commit seems overkill. Usually a PR is for a feature, but code-wise, a feature might require several atomic changes. For example: prepare configuration, move files around, add tests, add new code, delete old code, refactor. Each of those could be a separate commit, bringing you to a polished feature with test coverage.

jaredsohn
0 replies
1d19h

Might depend on how companies use git but I just verified I can see commits for PRs from 2017 in my work codebase; could bisect that if needed (although code likely is no longer relevant.)

I think splitting PRs into multiple commits can make sense when there are only 2-3 commits but you're right that it doesn't when there are more than that.

pizza234
0 replies
21h30m

At that point you might as well ship each commit via a separate PR. (While in development you can temporarily set the branch for Part 1 as the base for Part 2.)

Absolutely. Indeed, this (independent commit) is a very positive side effect (typical example: separating refactoring commits).

simongr3dal
1 replies
1d20h

You sort of get that if you're consistent in working on a separate branch, rebasing, and keeping the merge commit when you finally merge.

recursive
0 replies
1d20h

But you can also get it with much less discipline if just go wild merging and doing whatever. And then resolve your master PRs using squash.

erik_seaberg
0 replies
1d20h

git log --first-parent shows the merges but not the complete history of each merged branch. git log --first-parent --patch displays all the changes that came with a merge as if it were one big squash commit (in general git log --patch diffs trees, not single commits).

switchbak
12 replies
1d20h

I had a colleague (smart guy, lots of impressive Ivy League creds after his name) who just _insisted_ that rebasing was evil. There was simply no way to communicate that:

- I can rebase all I want in my own private repo

- Rebasing is fine if no one else thinks they can depend on your branch's history

- You obviously don't rebase master (!) or a branch that you'll collaborate on (not without proper warning at least).

I've seen this attitude more than a few times, and I think it's fear born of ignorance. I just don't get the inability to consider a different perspective.

Edit: formatting.

recursive
4 replies
1d20h

Not only ignorance. I don't generally use rebase myself. This is after having some experience with it. I recently had to help a junior un-wedge his local repo after an attempted rebase went haywire. I reset to the previous commit and replaced the rebase with a merge. The rest of the lifetime of the branch was uneventful. The problems happened because rebasing changes the identity of commits. When merging that result, commits that have previously been merged create conflicts as they now need to be re-merged under the alias of a different commit hash.

If rebasing works for you, I'm not going to try to sell you something. But there are other ways.

YZF
3 replies
19h19m

`git reflog`

recursive
2 replies
17h42m

I use this regularly. Mostly to answer one question. What was the name of that branch I was working on yesterday?

Or maybe you're suggesting it as a way to unravel a screwed up rebase. I don't know how to do that. But luckily I do know to abort, reset, and merge.

recursive
0 replies
16h2m

Yes. That's literally what I did.

Nullabillity
2 replies
19h34m

You can rebase, but you're still:

- Creating a false history that will be harder for you to understand in the future

- Creating a history that tools will have a harder time dealing with in the future

- Doing so for no actual benefit (in 99% of cases)

dahart
0 replies
4h14m

There’s no such thing as false history in git. Git was not designed to provide an audit trail of keystrokes, and as you can see from the larger comment thread here: nobody needs that. Git provides a mechanism for establishing and rearranging commit dependencies. The order of commit dependencies can be changed by design.

You are factually incorrect about rebase making things harder for people or tools to understand. The primary use case for rebase is making things easy to understand, in private branches after liberal use of the ‘commit early; commit often’ rule. If you take rebase away from git, and ask people to be okay with commits becoming immediately permanent, you increase the chances that people will make mistakes or lose work before they commit, and you remove some flexibility of being able to work on multiple separate topics in a single branch. Like any and all tools, rebase can be used incorrectly, but it is not used incorrectly most of the time, and basing your argument on the rare accidents means the argument is straw man.

000ooo000
0 replies
18h18m

I ranted about this in another comment in this thread, but what do you mean by rebase here? Reworking commits? Squashing commits?

dahart
1 replies
13h27m

I had a colleague (smart guy, lots of impressive Ivy League creds after his name) who just _insisted_ that rebasing was evil.

There is an historical reason for this. It is a hyperbolic marketing point of view invented by Richard Hipp, the author of SQLite, and of Fossil, in order to try to sell Fossil as being superior to Git. “Rebase is a lie” has been a meme ever since he published an article titled “Rebase Considered Harmful”, which has been posted to HN many times. Because he has contributed a lot to open source software development and because SQLite is so widely used, a lot of people respect everything he says and so this unfortunate idea has spread and gets repeated, even by otherwise very smart people.

Dr. Hipp has made appearances on HN arguing that rebase is bad, and even told me it should be likened to criminal activity. (https://news.ycombinator.com/item?id=29133188) The Fossil documentation still has remnants of it, but it has been softened over the years.

Luckily it seems to be dying, and I think the Fossil team might be coming around to the realization that this negative attack smear hyperbole is ultimately not helping Fossil grow. This is the primary sticking point for me and the reason I won’t use Fossil. I’m actually quite interested in trying it, but not until the (ironically) dishonest claims about Git and it’s goals are taken down.

000ooo000
0 replies
11h31m

[From the linked HN comment] To erase an entry in the financial ledger of a company is fraud. It is a felony. [..] I believe that VCSes should be treated similarly. [..] once you check-in the change - it then becomes part of the permanent record. To alter that transaction after the fact is akin to felony fraud.

Yikes. To give a charitable interpretation, that's quite an extreme view. Financial ledgers and VCS histories are not equal and they serve different purposes; the primary purpose of VCS history being to aid in the understanding of the current state of the codebase. In some cases, the "true" activity may provide that understanding, and in others, it may hinder it. Squashing a PR-requested change like "rename new variable based on PR feedback" seems sensible as it reduces noise in the history - definitely not what I'd call fraudulent. To me commits are like documentation and I see no reason why I shouldn't be able to refine that documentation if I think I can improve it for a future reader.

latentsea
0 replies
1d20h

As someone in the "merge-only" camp, for me it simply boils down to I don't think rebasing solves anything I view as a problem and as such I just don't care to know anything about it. You can rebase if you want, but don't bother trying to talk to me about it.

YZF
0 replies
19h12m

Smart people with strong opinions. What can you do.

I've come from a merge workflow into a rebase (git + gerrit) workflow in a huge software project with hundreds of people. Rebase workflows are fine. Merge workflows can be fine.

Personal preferences + the way you're used to doing things.

There's some advantages to being able to see how conflicts were resolved when you're looking for something that went wrong but if you have code reviews and tests then the probability of needing that in a rebase workflow is smaller. In a large team "how we got to a change" is IMO less interesting than "this is the change".

Have seen occasional breaks from things that were incorrectly rebased (generally from auto-rebase situations), but those are rare enough and easy to address. Same things can happen with merges.

An IMO much larger question is branching strategy. Normally if you're following a rebase workflow you're doing trunk based development. Many teams that use a merge workflow use feature branches. If you use feature branches you're pushed towards merge workflows. That's a bigger debate maybe (and I'd vote for trunk based).

osigurdson
9 replies
1d4h

The problem with rebase is it creates a lot of friction when sharing with others. Even if a single dev has two checkouts, it can get confusing ("can I just pull the changes or do I need to reset the branch? - dammit what did I do again? I guess I'll have to review both checkouts history in detail"). Merge based git push / git pull in the other hand, have none of this, thus conceptually much simpler.

Furthermore, since pushes must necessarily overwrite (--force), you actually risk loosing work.

This is all fine and doable but you have to be "on the ball" / paying close attention all the time thus introducing higher cognitive load.

The bottom line is getting work done and making history look good are competing objectives to some extent. There are all kinds of reasons to commit code (I got something working, I want to share something, I need to try this on another machine, etc.). Thus, the only way for commits to appear logical / atomic is to review the current state and refactor it to a logical looking but artificial series of commits. I can't imagine such efforts being useful enough to actually do in most cases. Perhaps even a task better suited to AI.

In reality, the PR is the only commit that anyone cares about. Everything else is mostly just noise. Therefore it would be best if the PR was a first class concept in git vs something only available outside of it (i.e. github).

rednafi
6 replies
1d4h

Two rules of rebasing that I follow strictly:

1. Never rebase a shared branch 2. Never break rule 1

rurban
2 replies
1d2h

Always rebase and push --force. Devs who cannot pull --rebase or reset --hard to latest should quit their jobs, and stop whining.

osigurdson
1 replies
1d2h

True Scotsman. Does it or does it not increase cognitive load?

rurban
0 replies
4h13m

No cognitive load. Just just magit and a set of git aliases

osigurdson
2 replies
1d2h

What is your definition of a shared branch? Is it shared the moment you push it (assuming a github like workflow). Is it shared if you check it out on two separate machines just for yourself?

rednafi
1 replies
1d1h

My definition of a shared branch is the one where only one person is working. It can be your local branch and the same one after you push it to remote. It's still not shared in the sense that only 1 person is supposed to work on that branch.

In this case, you can easily perform rebase and run `git push origin HEAD --force-with-lease` without causing any headache for anyone else.

osigurdson
0 replies
5h43m

Say you have a checkout on Linux and one on Windows and need to do some bespoke development work on each. I personally would just do git push / pull to avoid any potential for lost work. Maybe after the work is done, I would refactor the commits and rebase them before pushing to the authoritative server (e.g. github, azure dev ops, etc.).

klysm
1 replies
1d4h

Always use --force-with-lease.

rednafi
0 replies
1d4h

This will only save you if you haven't taken a pull right before pushing your changes to the remote branch. I almost always do this:

``` git pull --rebase main ```

And then push the changes with `git push origin HEAD`. Pushing with `--force-with-rebase` won't save me from this.

I usually never rebase a shared branch.

Carrok
9 replies
1d20h

Some like to rebase, while others prefer to keep the disorganized records.

Keeping disorganized records is the only thing you can say in favor of merge commits, and I'm unconvinced that's a positive thing to begin with.

joe_fishfish
5 replies
1d20h

You can’t do stacked PRs without merge commits, not unless you really like conflicts anyway.

seadan83
2 replies
1d20h

I have never seen stacked PRs work that well in practice. Two reasons:

- Namely there would be review comments on the first PRs that then cause a cascade of merge conflicts in the follow-on PRs

- Somehow reviewers never seem to like the stack of PRs, my experience is they always react with disdain and/or confusion.

There is the counter-question too, why not stack commits cleanly?

A third reason against, not a good reason, but for many developers Git is super difficult (IMO largely a skill issue, not taking the time to learn basic tools that they need everyday; otherwise I have no clue why software developers do not learn their IDEs and VCS tools very well). Stacking PRs requires some Git skills, a simple feature-branch workflow can be a challenge for many..

Ultimately, I think the solution to stacked PRs is to change review policy to "ship/show/ask": https://martinfowler.com/articles/ship-show-ask.html

In other words, if someone is skilled enough to do a set of stacked PRs, the team likely benefits by letting that person merge the stack on their own when each bit is ready and do a post-merge review instead of pre-merge.

(Side-note, my unsolicited perspective: I'm personally convinced that the benefits of linear history is a magnitude more important than all the other peeves & nits combined between merge vs rebase.)

edfletcher_t137
1 replies
1d20h

- Somehow reviewers never seem to like the stack of PRs, my experience is they always react with disdain and/or confusion.

Are people sending multiple branches of the stack for review at once? It should only ever be the "bottom" branch out-for-review at any time.

- Namely there would be review comments on the first PRs that then cause a cascade of merge conflicts in the follow-on PRs

This can still happen in the model above of course as you need to make changes to the bottom branch in response to review comments/requests.

However, as I noted elsewhere, `--update-refs` is an absolute god-send in those situations: https://andrewlock.net/working-with-stacked-branches-in-git-...

It reduces a ton of manual work (scaled by how many branches you have stacked!) to one operation.

seadan83
0 replies
1d18h

Multiple at once. Good pointer on `--update-refs`!

I can think of quite a few additional concerns. Overall I think it comes down to how the team wants to handle code reviews.

Personally, I do think if the team is at the level to coordinate and execute on a stack list of PRs, there is little need to incur extra round-trip times for "reviewing" precursor changes and instead focus review time where it is explicitly wanted.

Though, I do indeed like stacked PRs over commit-list because there is more incremental progress, but it does come with some costs. For example, perhaps the last reviewer does not like the overall direction that the cumulative work has led to.

My experience is that at that rate, it's best to let teams decide how they want to operate, formalize somewhat how things are shipped, and bias towards shipping. On the other end of the spectrum, a person quickly glancing at refactoring updates, not having good context on how a given PR fits in - it can almost put into question whether CR itself is entirely a best practice. Hence, I'm a fan of "ship/show/ask". I think it mostly does away with the need for stacking PRs with very little downside (and upside of greater efficiency, CR is spent time reviewing more important code, things that benefit from CR and use the reviewers time well, and makes for a better flow for the author since they can readily merge).

kqr
0 replies
11h49m

There's tooling that helps with some of that. I have evaluated StGit for a month or so now, and while it does require that one unlearns some git in the process, it has been handling that sort of situation well.

jaredsohn
1 replies
1d20h

I think the argument against rebasing is spending time polishing a PR to have a cleaner history for one feature that will maybe get looked at during review (rather than the final output) vs also making a PR for a second feature during that same time.

And you can eventually have github squash for you anyway.

yencabulator
0 replies
1d3h

Rebase work resulting in atomic commits != squash

lesuorac
0 replies
1d20h

Well disorganized records can still be very useful.

If you're trying to figure out where a bug came from it can be helpful to bisect through all the commits. If you bisect down to 40 file change that's going to be a pain to continue bisecting. However, if you can bisect down to say 5 changes where your bisection fails and 4 of them are because the app fails to build and the 5th is because that's when the bug was introduced that can be very useful.

peter_l_downs
7 replies
1d4h

Enforce squash merging to main and move on with your life. Linear history on main, individual contributors can rebase and merge or format-patch or do whatever they want on their PR branches and it doesn’t matter. There are zero downsides to this approach.

000ooo000
4 replies
18h14m

There are zero downsides to this approach.

Squash merge.. zero downsides.. hard to take you seriously.

peter_l_downs
3 replies
15h51m

What are the downsides of enforcing squash-merge to main?

lolinder
0 replies
14h13m

I use interactive rebase to construct a useful commit history that will communicate intent to future developers. Things like "move a file first, refactor it in a separate commit" make a huge difference when someone's coming around to view the file history later, and a squash merge removes all that information that I've meticulously encoded in the commit history.

My preferred workflow on a team project is to have well-crafted commits with merge commits. The merge commits signify the wider intent and link back to the PR in GitHub, while the interactively rebased commits tell the story of the steps to get from point A to point B.

dahart
0 replies
13h9m

Losing granularity, obviously, no? Of course it depends on how big branches get before they’re squash-merged, but squashing can make things harder to bisect, harder to track independent semantic changes that overlap, harder to read comments & history, harder to cherry-pick small fixes… lots of things. You don’t notice the downsides until you have to manage release branches or spelunk through the history to find bugs or figure out who wrote some line of code from long back.

000ooo000
0 replies
13h46m

Loss of utility of tools like git bisect, conventional commits, etc.

Git blame is less useful when what were individual commits associated with lines are instead rolled up into a massive commit, such that each affected line is now described by a more general commit.

It can encourage lazy developers to submit shit commit history in PRs knowing it's going to be squashed anyway, making PR harder.

rednafi
0 replies
1d3h

Yep. Neither the rebase nor merge workflow negates the benefits of squash-merging to main.

Snild
0 replies
8h3m

There are some downsides.

https://github.com/libexpat/libexpat/pull/789/commits is a PR of 16 related commits. Many of them make sense individually, but are depended on by subsequent commits.

Reviewing them separately makes sense. It's easier on the reviewer (and future readers) to handle multiple smaller changes, especially since each is more tightly coupled to a rationale in the commit message. Unfortunately, github's PR-focused UI doesn't really make this per-commit review as convenient as Gerrit does.

Additionally, the last two commits are authored by another contributor. This metadata would be lost in a squash.

Of course, each intermediate commit must build and pass tests. WIP and cleanup commits are squashed locally before final review/merge.

You could argue that all of these could be separate PRs, but I think there's value in grouping them up with that final merge commit, showing what one was trying to do at a larger scale.

nailer
7 replies
1d20h

Interactive rebase is indeed great, but:

    # Commands:
    # p, pick <commit> = use commit
    # r, reword <commit> = use commit, but edit the commit message
    # e, edit <commit> = use commit, but stop for amending
    # s, squash <commit> = use commit, but meld into previous commit
    # f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
...the CLI git text-document rebase UI is awful. Better command-line rebase UIs are available, or in Fork, I just select a bunch of tweets, right-click, and squash into parent (or use the https://git-fork.com/images/interactiveRebase.jpg where necessary).

If someone complains that CLI git is somehow more pure than using a decent GUI, ask them to rename a stash and time them.

pluies
3 replies
1d20h

Is it that awful? Each option is nicely explained, and the documentation is exactly where you need it, when you need it. I personally found it extremely helpful during my first interactive rebase, and still give it a glance from time to time when I forget the difference between squash and fixup.

quibono
2 replies
1d20h

Provided rewording, reordering and squashing (or fixing up, same thing really) are all you need I think you could argue this is true. Trying to do anything else tends to be a bit of a pain.

On another note, is it possible not to lose the commit description when trying to split a commit? I'm talking about 1) marking commit as `e` to edit it 2) git reset HEAD~ to move back 3) split the change however you want 4) commit again whenever I do it I always lose the original commit message. I'm not sure if it's me being stupid or if there is just no simple way to do it.

skirmish
0 replies
1d19h

"edit" is useful for splitting a single commit into multiple. You couldn't do it with operations you just mentioned.

000ooo000
0 replies
1d11h

is it possible not to lose the commit description when trying to split a commit?

There's 2 cases: 1) extract changes into a commit that comes before the original commit, and 2) extract changes into a commit that comes after the original commit.

In both, instead of editing the commit you want to split, `break` before that commit, and then use `git checkout <hash_of_commit_to_edit> <path_to_files_of_interest>` to pull the changes of interest out into an new, earlier commit. `git checkout -p` is worth a look here. Alternatively if the changes are simple enough, you could use exec instead of break before the target commit.

For 1) you can commit those extracted changes with a new message and then `git rebase --continue`. The original commit will then lack the extracted changes, and have the original commit message. If you did want to adjust it, reword that commit.

e.g.

    pick c62dfe67 1
    pick 63dcd748 2
    exec git checkout eea68bb8 three.txt && git commit -m "3"
    reword eea68bb8 3+4
For 2) reference the target commit's message as the new, earlier commit's message. Keep in mind that the git invocation in this exec here still supports git aliases so if this was something you do often, you could create an alias for retrieving the next commit message and that last part of the exec could just be `.. && git getnextcommitmessage`

e.g.

    pick c62dfe67 1
    pick 63dcd748 2
    exec git checkout eea68bb8 four.txt && git commit -m "$(git log -1 eea68bb8 --format="%B")"
    reword eea68bb8 3+4

baq
1 replies
1d20h

My only gripe is after 15 or so years with git I still sometimes forget the direction of the time arrow in the interactive rebase.

formerly_proven
0 replies
1d20h

I like that and use it a lot. It's very easy to rearrange stuff.

quibono
6 replies
1d20h

Personally I am squarely in the rebase team although I'm not completely anti-merge. I find the interactive rebase to be so useful that I pretty much never rebase non-interactively. I wonder if part of the reason the merge flow is more popular (or at least it seems more popular) is because native Git tooling around rebasing isn't the best. In fact doing anything more than the simple reword/squash/reorder (like commit splitting or moving chunks between commits) is abysmal. Magit really shines here and makes it a breeze.

switchbak
4 replies
1d20h

I think Git's roots show in its lack of DX. But I'll take it over the horror that was SVN and prior tooling.

Question: how does Magit compare to GitUp? The latter is my benchmark for complex rebases, but I'd love to know if I'm stuck in a local optima.

bigstrat2003
1 replies
17h58m

I think Git's roots show in its lack of DX. But I'll take it over the horror that was SVN and prior tooling.

This statement really confuses me. SVN was so much easier to use than git. It was incredibly straightforward: make changes, commit, done.

Izkata
0 replies
11h14m

Merging branches was roughly the equivalent of git cherry-pick. There's tracking to prevent you from pulling the same commit twice, but not much else.

--reintegrate was introduced later, and even with that it was easy to get wrong.

quibono
0 replies
1d20h

I've not used GitUp so I'm not sure how much help this will be to you since I can't objectively compare the two. As an example, let's say I want to remove some changes from a commit (and potentially move those into one or more different commits). If I go into the commit and select the chunks I want to revert, I can click `u` and right away I get the revert chunk added into my staging area AND the original chunk added to my working tree. Then I just fix-up the original commit (c F) to remove the unwanted change, and commit the original change - either in a few different commits or possibly just one.

joshuanapoli
0 replies
1d20h

I haven't used Magit or GitUp, but I really appreciate JetBrains IDE automatic conflict resolver.

sjburt
0 replies
1d20h

The main issue is that on a longer branch, you have to fix all the rebase conflicts on the oldest commit. Then, if you changed something in the conflict zone, you fix it again on the next commit, because the next commit used to be based on something else. Sometimes git rerere can help with this, but I find it fairly unreliable (could be user error!) If you do a merge, you only need to resolve it all once.

Otherwise I tend to prefer rebase.

PUSH_AX
5 replies
9h50m

I haven’t found meaningful use for history in years. Neither has anyone on any of the teams I’ve worked on. Nitpicking git practices really feels like a waste of time. Pick something that works and roll with it.

oopsallmagic
3 replies
9h13m

Then why are you even using a VCS?

PUSH_AX
2 replies
9h8m

To work on the same code base as the rest of my team? Like everyone else. Certainly not to nitpick about how history is written, in what order or what is omitted.

ImPleadThe5th
1 replies
7h53m

I'm very surprised you've never gone hunting for bugs in your git history.

"Hey something went wrong with X service"

Find commit like "add y to x service'

It's a time saver to me. Plus if the bug is extra tricky, you can ~easily~ revert or pick it out.

PUSH_AX
0 replies
7h8m

I did try when I was a junior. I learned this relies on good commit messages, and the information can be lost during squash. Honestly if I’m playing the role of bug detective I’ve become much faster using other techniques.

RangerScience
0 replies
9h37m

I regularly look up the git blame on lines of code; I’m hoping to get anything from “documentation” on magic numbers, to seeing what else that line went with, although usually to find out who to go ask when I find a WTF.

Before I started regularly looking up that ‘blame’, I also didn’t care too much about got history. Now, I put some effort into making my own history useful to everyone (including future me).

IshKebab
4 replies
1d20h

Rebasing is definitely the better option if you can do it. Sometimes it is just too much of a pain resolving conflicts if you are rebasing a branch with lots of commits and there are lots of conflicts. In that case I normally squash and then rebase. Branches where there's enough history to be worth maintaining multiple commits should - 99% of the time - be separate PRs.

The rare exception is when you have multiple people contributing to a branch before it is merged into master, but for obvious reasons you should avoid that when at all possible.

ycombobreaker
1 replies
1d19h

Branches where there's enough history to be worth maintaining multiple commits should - 99% of the time - be separate PRs.

I appreciate that anyone saying this is probably already on the same wavelength as me, but I don't find this to be true for myself. Many times, complex application features end up represented as a series of related, but atomic/meaningful changesets. I want the pull request to make sense as a whole, but expect that code review or audits are easier to accomplish diff-by-diff.

I see it like building a recipe. I want to hide all of the false starts, sloppy mistakes, do-overs, and checkpoints, because they are useless from a historical standpoint. But I still may want to publish a sequence of changes that accomplishes something.

IshKebab
0 replies
22h35m

Yeah that's fine, you can just push the series of commits and then squash them when they are merged. Both Gitlab and Github can do squash merges.

recursive
0 replies
1d20h

Rebasing is definitely the better option if you can do it.

A bold claim.

but for obvious reasons you should avoid that when at all possible.

Obvious? I do this somewhat regularly and haven't had a problem. I don't even know what the problem is supposed to be.

Also, I never rebase. Maybe that's why I don't have a problem? When you rebase, commit A becomes commit A'. This becomes a problem when you need to determine whether a particular commit is an ancestor or not. A and A' have different commit hashes, so the identity of the commit becomes somewhat ambiguous.

My workflow is do all development using merge only. When the code review is complete, squash to master. I have found no down sides to this. But I'm not trying to sell it to you either.

pseudoramble
2 replies
20h57m

Not too interested into diving into the debate itself, but one minor point I wanted to add to the article where they count the commits to squash and then do `git rebase -i HEAD~n` is that you can replace this strategy with using the branch you're targeting. So if you're working on a feature branch to merge into `main` you can update the local main branch first, then punch in `git rebase -i main` and it'll handle finding all the commits for you.

I'm sure there's even more clever ways to do this, as it always seems like there's more when it comes to git. This is just the most intuitive way I've seen so far, and so it sticks in my mind.

hansvm
1 replies
20h31m

And a fairly quick way to do the same sort of thing is `git fetch && git rebase -i origin/main`. You never bother updating `main` because you kind of don't care for the task at hand.

pseudoramble
0 replies
20h26m

True, good point. Makes sense! Thanks for the improvement!

notJim
2 replies
1d20h

Step 3 can be simplified slightly be doing git fetch && git rebase origin/main. No need to check out the local main.

blcArmadillo
1 replies
1d20h

Or: git pull --rebase origin main

rednafi
0 replies
1d4h

TIL. Thank you.

juancn
2 replies
1d19h

The only issue with rebasing is if someone else has checked out the branch and now they have diverged. It's usually a simple fix, but it can get annoying.

I tend to checkout branches to do code reviews, when changes are complex a diff is not enough, I want an IDE to help me reason about the code (I sometimes even refactor and try different things just so I can understand the code better, so I can make good suggestions).

When I do this, and comment, and the other dev rebases, it's an extra step for me on the next CR cycle.

rednafi
0 replies
1d4h

Never rebase shared branches.

kutenai
0 replies
1d19h

I train my team on how to resolve this. Every team member is able to resolve a force push onto a branch they have local.

Also, we tend to avoid having multiple people work on the same branch.. so, that's also a thing.

If there was a multi-person development effort, then each of those people would have to have a sub-branch of a main feature, and then they would be rebasing their work onto the 'main' feature branch.. which would ultimately be rebased on to dev.. etc.

elijahbenizzy
2 replies
1d20h

100% team rebase -- I think the fear comes from "rewriting history", which sounds scary until you realize that git is entirely immutable data store (through the API at least). Blockchain for the win!

I find merging to be extremely tough to work with -- I don't think I actually got good at git until I learned to rebase entirely.

Also, cherry-picking (effectively an atomic rebase-type) is underrated.

pdonis
0 replies
1d20h

> git is entirely immutable data store

Not entirely, since git can automatically garbage collect commits that aren't reachable.

edfletcher_t137
0 replies
1d20h

I find merging to be extremely tough to work with -- I don't think I actually got good at git until I learned to rebase entirely.

Also, cherry-picking (effectively an atomic rebase-type) is underrated.

This this this, all of this.

I never really "got git" until I finally committed to learning how rebase works.

+1 to cherry-picking. Also patch-add (`add -p`).

dham
2 replies
1d4h

I don't know why anyone cares about this stuff. You can literally do whatever you want on your private branch. If you want to make your life harder, that's cool. Just squash into the main and call it a day. None of this stuff matters anymore, like it did when I started my career.

klysm
0 replies
1d4h

I agree you can do whatever you want on your private branch. Unfortunately I think a lot of developers don’t have a solid grasp of what’s possible in private, and more importantly what you should do in private vs. on public branches.

000ooo000
0 replies
18h13m

None of this stuff matters anymore, like it did when I started my career.

Explain?

xyzzy4747
1 replies
1d20h

If you're using complicated git commands, you're probably doing the wrong thing.

quibono
0 replies
1d20h

I guess it depends on what you mean by a complicated command.

000ooo000
1 replies
1d13h

Everyone's got an opinion on "rebase", and IMO you can safely ignore those that conflate:

* the rebase command, as a means of adjusting commits

* the practice of squashing all commits into a single commit (typically, but not always, via rebase)

* the "rewriting of history", whether on public or private branches

* rebasing on merge vs. creating a merge commit

This article starts by saying they "like rebasing" only to then say

Git rebase allows me to squash my disordered commits into a neat little one

Rebase allows this practice but you could just as easily `git reset @~` a few times and then `git add .; git commit -m "My big commit"`. Alternatively the author could just `git add .; git commit --amend --no-edit` throughout the task and end up with the same result. To be fair to the author, they later add that they don't always squash to a single commit, but here I'm talking about "rebasing" vs. commit practices. Rebase is just a tool which allows various practices and it's tiresome seeing the same arguments against this nebulous "rebase", when the arguments are actually directed at practices.

taftster
0 replies
15h40m

I really appreciate the distinction you're making here. That is, rebase itself is not necessarily either the hero or bane of the process. There are several ways to the "squash my disordered commits" problem, which you've described nicely.

I personally use rebase similar to the author, to squash all my noise down to my final intention. But this could be achieved in other ways, just the same.

swiftcoder
0 replies
7h47m

One of the biggest barriers to rebasing these days is that GitHub's UX for pull requests doesn't keep track of comments across a rebase-and-force-push. Frustrating for those of us who prefer this style

stevebmark
0 replies
18h43m

I find merges more confusing than rebasing. At the end of rebasing you just have commits. Merges are commits with multiple parents, which is a confusing concept.

plasticeagle
0 replies
8h7m

Develop on a branch. Do whatever you like on that branch, no-one cares.

Squash merge to main always.

Problem solved.

phendrenad2
0 replies
1d5h

Great, just don't force it on me, or grumble when not everyone on the team does it.

overgard
0 replies
14h2m

I feel like squash-merge accomplishes what I want (easy revertibility and simpler history). I'll admit I need to learn it better, but every time I've tried to use rebase I feel like I'm playing with fire and I end up doing silly things like making a backup of the whole folder. I just think the process is unintuitive and somewhat scary, and I don't get the net gain. Like, outside of looking at the last couple days is anyone really spelunking into history that much anyway?

oopsallmagic
0 replies
9h11m

People need to get comfortable with git being a DAG, and not the degenerate case of just a straight line. Merge commits aren't scary, folks.

oftenwrong
0 replies
5h36m

The debate about whether to preserve the true history or clean it up is based on the limitations of git log. There is no reason we need to choose. Imagine you could `git summarise` a range of commits. Anyone who wanted a clean, straightforward history could read through the highest level of summarisation. If one wanted to dig deeper, they could peel away the summary and look at the log items below it.

mnahkies
0 replies
1d20h

This may be rose-tinted glasses, but I was fine with merges with Mercurial / https://tortoisehg.bitbucket.io/ but subsequently using git (with gitlab/GitHub ) I've been rebase + `--force-with-lease` every time. I'm happy with my current git workflow, but I can't help but feel I'm missing a trick through finding git merge commits so difficult to understand

micimize
0 replies
13h43m

Rebasing potentially allows for clean, clear documentation of changeset intent in a way that integrates easily with our existing tooling, at the expense of a potential footgun in highly collaborative scenarios. Ie, if someone forked off your branch or referenced a commit you might break a reference accidentally. So it can be nice, but really depends on the environment you're working in.

Gerrit solves the referencing issue by using git notes and maintaining its own ID for changesets across rebases. Without such a system, some seeming benefits of the rebase workflow, like atomically reviewable stacked commits, become fairly awkward. IE, if we want to review commits themselves, but their references get clobbered due to a rebase, that's no good (this was the case a few years ago with GH's rebasing support, maybe improved since).

IMO, that we have to make the "rebase or merge" tradeoff at all, or accept the deep limitations of pure commit-based history, is fairly sub-optimal. It'd be nice if more tooling/workflows were built more around notes. I envision someone/some bot going back and annotatinb a range of commits with a note that associates them into some coherent code documentation system, or amends some faulty assertion, links artifacts, etc. That way blame could take us to salient docs or surface behavior snapshot gifs or w/e. From directly within IDEs

kwakubiney
0 replies
21h15m

This is exactly what I do at work everytime. The only issue with this sometimes for me is when i rebase interactively and i have my commit message with all squashed commits. So I do a `git commit —-amend` on the squashed commit and tidy up the mess in the message.

kutenai
0 replies
1d19h

I've looked at the "arguments" for not rebasing and reject them. I've never once thought 'oh shit, I wish I had not rebased that'.

Keeping things neat in the repo has gained me far more than I ever theoretically lost by some conceptual "loss" of history.

krick
0 replies
15h2m

That's pretty weak defense of rebase. It almost seems like an attempt to promote the opposite. To "squash my disordered commits into a neat little one" is like the only bad and questionable practice involving the word "rebase" which people can rightfully object to. And rejecting that they will usually (unjustly) reject a dozen of other practices involving "rebase". "rebase" does not equal "squash". Using "squash" checkbox in Gitlab is not synonymous to "rebasing".

If you actually care about organizing you disordered commits, you can do it in a sane way and use git rebase -i master. That's basically how I work all the time, I don't even try to write meaningful commits first, I even commit stuff I know I must drop later and I commit every little incremental change file-by-file (to make re-ordering commits easier). Then I'll rebase them interactively to review what I did and make several (most of the time) clean atomic commits.

Then, rebase is simply superior to making merge commits all over the place, because it... well, "does nothing" as opposed to "makes your commit tree into a complete mess". There can be reasons why you would rather not bother rebasing on a particular project, but if you don't have them (i.e. you just don't have a strong justifiable opinion on that matter), just set [pull] rebase = true, and use git merge --ff whenever possible. Most of the time it doesn't even feel any different and just works.

kovac
0 replies
2h6m

The man who designed git advises against rebasing as a default merge strategy, but the comment section is filled with comments promoting rebasing and coming up with wildly elaborate schemes to deal with its problems without addressing any of the points raised by Torvalds.

Git workflows imposed by those that don't understand git well enough is one of the most annoying things.

knallfrosch
0 replies
9h43m

It’s interesting how much devs - including me! - care about git history, yet rarely do we use it in a way that makes the rebase-merge-squash differences meaningful.

Usually I just look at the file history, find the work item from the commit message and then understand _why_ the code is the way it is. That is the functionality I need to have in mind when changing the file. Who cares how it came to be?

kkfx
0 replies
1d20h

Rebasing is a way to cleanup when a project have a useless history because of too many small commits, badly described and so on. A dangerous way, but still a way.

The issue is another: git does next to nothing to handle forks. We can cherry pick some commits, but there is no easy way to state "we have a project that a certain point in time diverge, keeping some common base, that need to be kept in sync, ignoring the fork specific changes". This led to long and manual three-way merges. No extra help.

joshuanapoli
0 replies
1d20h

I like rebasing too. When I have any significant change, I usually end up making a mix of clean-up and preparation around my new feature. I like to move the separable parts into separate branches, so that there is less to review per request.

jaza
0 replies
1d20h

I just git commit --amend and then git push -f origin feat_branch all my incremental changes. Saves me having to think of even one word commit messages like "typo" or "renaming". And saves me having to do an interactive rebase later (which basically has the same end result as git commit --amend anyway). Nobody on my team cares about those incremental changes, including me.

jauntywundrkind
0 replies
1d18h

GitHub uses the committee date, and the committee date changes with rebase. So I almost always --committer-date-is-author-date. Most people don't and it kind of makes their rebases ugly in GitHub, appear to all have happened at time of rebase, which is obviously no good & useless.

Rebasing is excellent. It's a whole suite of tools, unlike merge, which is just merge, for managing history. Rebasing is how I build a sensible history.

I should see if there's some way to set --committer-date-is-author-date by default. Also haven't found a way to make git pull --rebase do c-d-i-a-d.

jaredsohn
0 replies
12h19m

I look forward to the day that I can run a local LLM (for confidentiality reasons) to automatically reorganize commits within my PR. Should be very safe compared to generating code or merging/rebasing other code since it is just changing the grouping of commits and the final code should be unchanged.

This would free up developers to spend more time solving problems for the business instead of tidying up code.

Could also delay doing this to when somebody is trying to understand the code (during review or later) or doing a bisect.

jacobr
0 replies
12h0m

Any article about rebase is incomplete without the mention of `git commit --fixup fb2f677`. When committing you usually already know which commit you’re “fixing” so you specify that.

Then you run `git rebase -i --autosquash origin/main` instead and the commits are already in the right order.

ivolimmen
0 replies
8h12m

I have been working with git for only 10 years. I know how to fix things when stuff goes wrong. I have no strong opinion on merging strategy. Git is now what my clients are concerned with. I am not getting paid for solving git issues (well not directly, I was never hired for it)

iamhamm
0 replies
19h18m

I read that as “I kind of like freebasing” at first and thought it was a bit racy.

fleekonpoint
0 replies
1d20h

I also like rebasing. Sometimes my squashing cannot be completed without manual intervention, so I'll do a squash merge into a temporary branch instead:

   git checkout main
   git checkout -b squash-branch 
   git merge --squash [branch-to-rebase]
At this point I usually git diff the two branches as a sanity check before merging back into main:

    git diff [branch-to-rebase]
    git checkout main
    git merge squash-branch
I am normally able to squash rebase 99% of the time using git rebase -i main, but doing a git merge --squash into a temp branch has saved me a lot of hassle over the years.

fastball
0 replies
13h41m

The best solution is to squash messy commits into one atomic commit, and then use rebasing for a clean history on top of that. But I find that squash-merging all the commits from a PR or branch is generally not what you want.

And if you have atomic commits, then merge commits generally aren't helpful. As others have pointed out, what matters is when the code was added and what it does, which becomes very clear if you have a linear history without merge commits (but atomic commits as much as possible).

If during the development process your commits aren't atomic, that is fine, but then you should make them atomic with squashing before they actually get rebased into main.

So for example if I'm in a personal dev branch and I have "messy fix feat A", "fix feat A but better", "even better feat A", and "add feat B", I should just squash the first three into "Improve feat A" and leave the fourth alone as "Add feat B". Then after a PR review or tests or whatever you just rebase into main. Now there is still a clear delineation between my fixes to A and my addition of B, without forcing me to make separate PRs for highly related features. I end up with:

- clean history

- atomic commits

- limited mental overhead when doing quick and dirty commits

- not forcing atomic PRs which can be overwhelming

dudeinjapan
0 replies
10h6m

Do not like rebasing. For me, it's squash-merge all the way, and I compare the actual diff of the code on my new branch.

Diffs are what matter--not commits.

danjc
0 replies
12h9m

Your commit history is part of your product, not scaffolding. Well designed commits make a codebase a pleasure to work on and reduce the accrual rate of technical debt.

captainbland
0 replies
1h0m

The only rule about rebasing I care about is the golden rule of rebasing: "Never do it on a public branch". In general I'm not going to waste my team's time by messing about with it otherwise one way or the other, and personally use either rebase or amend depending on context to tidy things up locally, and use merge when interacting with public branches.

breatheoften
0 replies
1d2h

Strongly pro rebase here.

I sometimes wish I could require squash merging to main so that history on main is fully linear -- however the fatal flaw with doing that is that it becomes impossible to know from git history whether a given branch has actually been merged to main or whether it was abandoned and left to dangle and rot forever. The inability to observe merge state for a given commit increases the effort required to know whether some given piece of work was merged (or at least requires using mechanisms that are not native to git to make such a determination) and complicates cleaning up old local branches after they are merged. If you use a graphical git client then seeing a brunch of old local branches that are already merged everywhere is noisy and distracting. When real merges are done then can easily write a script to remove all the branches that are already merged to main which helps to reduce noise and maintain better focus and faster navigation.

anordal
0 replies
8h19m

Why haven't more people heard of git revise? Unless you actually want to transplant your changes onto a new base, honestly, do yourself a favor and use this instead:

https://github.com/mystor/git-revise

Its inability to change your worktree (operates in memory instead) is a big speed and safety feature: It won't invalidate your build, and you can't screw up the end state.

It also has features that regular rebase lacks, like splitting a commit and editing all commits at once. I'm more than a big fan of it.

Taylor_OD
0 replies
1d1h

I love rebasing. I know a few people who have disliked it when they first started doing it and most of the time its because they would not rebase into their branches often while working. This made for one big rebase at the end which can be a nightmare.

NathanFlurry
0 replies
19h12m

Our team switched over to Graphite [1] 8 months ago now. Graphite involves a lot of "restacking" (i.e., rebasing for every commit/branch in a "stack").

No one on our team had used rebasing extensively before this, but it's hard to go back. Similar to the article, the benefits we see are:

- Fast iteration with `gt m` (`git commit --amend`) and `gt s` (`git push --force`) - Using `gt restack` (`git rebase` on each parent commit) helps make merge conflicts more transparent in what happened - Commit histories are much more legible

I highly recommend giving it a go.

[1] https://graphite.dev/

Leszek
0 replies
8h28m

Use `git log --first-parent` and if you merge, write useful merge commit messages. There, I've resolved the difference between merge and rebase forever.