Hacker News new | past | comments | ask | show | jobs | submit login

You squash merge when stuff gets added to master (or any other shared/long-lived branch) and delete the development branch, nobody has absolutely any interest in what happened on your development branch. There you go, clean commit history if you care about that sort of thing.

Rebase workflows are awful and unintuitive. Leave the rebasing to the git wizards who actually know what they're doing, in no circumstances should this be part of your day-to-day work.




> Leave the rebasing to the git wizards who actually know what they're doing, in no circumstances should this be part of your day-to-day work.

This sounds like the refrain of someone who doesn't want to actually learn how one of the most fundamental tools of their profession works. I realize that git wasn't the optimal choice for the industry to settle on, but it's what we picked, and simply avoiding a feature that a majority (65%) of your peers use to at least some degree [0] will hamper your professional development.

Learn git. It's not pretty, but it's what we've got, and it's not going anywhere anytime soon.

[0] https://jvns.ca/blog/2024/03/28/git-poll-results/


That poll shows exactly the problem with rebase workflows. 41% say they mostly rebase but 48% do not know that merge/rebase conflicts have swapped order of local/incoming changes. Granted, the percentages are exactly right that those 41% might not at all overlap with the 48% but how likely is that?

The reality is that most people work on their private branches, alone. In that case it makes almost no difference if you rebase or merge. In almost any other scenario, merging still works as expected, while rebasing without understanding git will almost definitely lead to losing work and spending an absurd amount of time resolving conflicts. Why would you want to inflict that on yourself? Just use the approach that always works instead.

Learning Git really isn't high on the priority list for most developers, as they know what they need to use to get stuff done. The complexity gets really high really fast, so it's quite understandable why most people treat Git like DNS or other infrastructure - it's there, I know the basics to get stuff done and if anything goes wrong I ask an expert to take a look. And guess what? There is NOTHING wrong with that.


> 48% do not know that merge/rebase conflicts have swapped order of local/incoming changes

I'm well aware about the differing meaning of HEAD in merge and rebase, and if I had to think about it, would probably realize that it makes sense to always display HEAD first. And that as a consequence, the order would be swapped.

But I would definitely have answered "no" to the question as written.


> nobody has absolutely any interest in what happened on your development branch

I absolutely do. Every lead I've ever worked with absolutely does. If you merge an unnecessarily big commit to master, you are potentially making life very difficult in the future.

If you make a meandering series of commits during development, squash them for the PR. But please, please do not squash the entire feature upon merge.

Also, I'm not sure how rebase could be confusing except in the case where there are multiple commits with big conflicts, but that's rare, and you can make exceptions if the developer is really that unsure with git in those rare cases (or ask for help).


I disagree. It's a nice property that every commit in master is (conceptually) fully buildable & all tests passed. I've not found much benefit from having heaps of tiny commits and trying to work out their state; also cherry-picking (if needed) is simple with squash commits, but extremely difficult without.


That's why they said this:

> If you make a meandering series of commits during development, squash them for the PR. But please, please do not squash the entire feature upon merge.

I typically turn my PRs into a series of buildable and testable commits. I also put a lot of work into making those commits tell a useful story to the reviewer and to anyone doing git blame later, and squashing them all into one commit undoes that work.


Buildable and testable, but notably sometimes not formatted or linted, when (atypically but not rarely) the semantic change is much easier to understand separated from the superficial changes it motivates in the context of the formatting/linting rules of the project.


Yep. And in a similar vein, not squashing allows you to separate a file move from changes to it so that git can track the move.


Of course all commits should be buildable and pass tests. There’s a scale between ”heaps of tiny commits” and ”squash everything”.


All commits that will be shared. There's nothing wrong with a local WIP commit when you want to plant a flag to compare your next changes against.


Why are you interested in looking at individual commits? What can you possibly learn from that that you can't get from looking at the entire diff the PR is introducing?

Does your CI test each individual commit? Afaik most of them only test the top commit. How do you know/enforce that all the inbetween commits also build/pass tests?

How do you know how many commits to revert, if you need to revert the feature? Instead of reverting 1, now you have to revert N where N is not recorded anywhere.


> Why are you interested in looking at individual commits?

Because they can explain their individual rationales, while still making the most sense to merge all together.

> What can you possibly learn from that that you can't get from looking at the entire diff the PR is introducing?

Ease of review (both before merge, and in the future when wondering why something was done a certain way). Saves me as a reviewer from having to guess which parts of the commit are meant to do what.

This, of course, means that every commit needs to be a reasonable change in itself -- fixup commits done while developing should be squashed into the original change with a local rebase (these are the "meandering" commits your parent post mentioned).

> How do you know/enforce that all the inbetween commits also build/pass tests?

I'm no CI expert, but I would hope that most systems allow this as an option.

> How do you know how many commits to revert, if you need to revert the feature? Instead of reverting 1, now you have to revert N where N is not recorded anywhere.

It's recorded in the merge, assuming you always make a merge commit.

Otherwise, since each commit is actually its own logical change, you figure it out the same way as you would figure it out in the "squash PR" model -- bisect to find it, then see if reverting it helps.


A well-crafted commit history for a merge conveys useful information and makes reviewers’ jobs easier by splitting a large change set into smaller logical chunks that are easier to reason about.

If the extra “noise” bothers you you can use —-first parent with git log.


This wouldn't affect what the reviewer sees because the squash commit only happens after the PR is complete and is being merged to master


For more than 15 years of Git usage I've never seen a well crafted commit history for a single PR. It either a series of well crafted multiple PRs with separate reviews or a brain dumpster fire of multiple commits trying to make it work.


Well I have seen them. It’s your anecdata vs my anecdata :)


If you have public GH contributions you could show examples of PRs with stories told by commits and my anecdata would be invalid :)


Why would you squash merge if you have two different atomic commits ? Makes bisecting + reverting a pain... I'd avocate for a rebase instead


> Rebase workflows are awful and unintuitive. Leave the rebasing to the git wizards who actually know what they're doing, in no circumstances should this be part of your day-to-day work.

Hard disagree. I hardly consider myself a "rebase wizard," but I've been a near-exclusive practitioner of rebase workflows since I can remember. I find rebasing much more intuitive than workflows with merge commits. Squash merges are fine, but with proper intuition, they appear like a special case of rebase.

In my experience, the resistance to rebasing comes down to fears about "rewriting history" and false intuitions about how git works. I usually allay the former by pointing out that squash merges - which almost everyone approves of - also rewrite history. The latter issue seems to arise from arrows in popular git visualizations pointing in the wrong direction, e.g. in Gitflow.[0] In git, the child commit points to its parent, because each node is immutable. The git data structures are extremely simple (hence why git is so named), consisting of blobs, trees, commits, tags and references. Once you understand how these work in practice, rebase becomes intuitive.[1][2]

IMO, the only thing unintuitive about git is the CLI. Translating the graph operation I want into the commands is sometimes a challenge. Maybe that makes me a (frustrated) wizard after all?

[0] https://nvie.com/img/git-model@2x.png

[1] https://speakerdeck.com/pbhogan/power-your-workflow-with-git...

[2] https://eagain.net/articles/git-for-computer-scientists/


> In my experience, the resistance to rebasing comes down to fears about "rewriting history" [...]

There is 'git bisect' which only gives you detailed answers if your commit history is as fine-grained as possible while still being compileable and testable. Also, changing commit IDs on branches that are visible to others are a problem.

Which is why my approach is as follows: Work on private branches, one per feature/fix/..., then do interactive rebase to create a compilable and testable patch series out of those onto a for-review branch. While developing, rebasing onto whatever public branch you want to merge with next is of course OK and necessary. When you think the feature in the for-review branch is ready, do a merge-request into the public target (mostly devel) branch as usual, and either merge or rebase, whatever you like best.

But: Whatever branches are public are only ever merged. Never squash-merged, never rebased, never force-pushed, never filter-branched (except maybe after a court order). Because all commits on those branches are necessary to trace what people were doing with the code. All those commits and their relations are necessary for git-blame, bisect, sloccount and other things. Any commit ID there could wind up in some test binary, release ID or stuff, and you absolutely truly need those later on. And while a simple rebase might keep some of the necessary details intact (the other branch will still be there after all), only a plain merge will also preserve all the relations between the branches properly.


Squash and merge is another source for beginners confusion. Now they have two different histories (main and local feature branch) with the same content. It's often case they want to base following work on feature branch (staled history) not start from main and get potential conflicts.


The beauty of this is that the rebase wizards are free to rebase all they want in their feature branches, but everything is squash-merged ito main and no one has to know.


This is terrible for "rebase wizards" because about 80% of the reason why I rebase is to make my commits useful when someone inevitably needs to do a git blame in 5 years to understand the history of this code. A squashed commit can tell them "this was part of this feature", but a well-crafted history plus a merge commit with the PR name and number can tell them that and show what specific code changes were related to one another in one atomic step towards a feature.


But one reason that rebase wizards might curate a a rebased branch of small commits is exactly so that the small commit structure remains on merge to main. That way they can track down any problems with it more easily in the future.

(Ironically, I've found that this style of development makes it less likely for bugs to be introduced in the first place.)




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

Search: