What I think I see these days is squash merges being used lazily to avoid having to do anything to build a clean history with clearly semantically delineated commits. Squash merges are good compared to an alternative where people check in super messy noisy branches, but they unfortunately have a big downside because squash merges can make bisecting and history spelunking more difficult, when the branches that are squash merged were big.
Not parent: there are technical commits, such as "fix review", "fix jenkins", "fix typo" etc. Those don't delineate a particular feature but a fix for a problem that arose from the workflow. This ends up with a history of "big feature commit that is wrong in three trivial ways" + "fix 1" + "fix 2" + "fix 3". Of those, "big feature commit" is the important one, but "fix 3" is the only working one. This is clearly silly; you should pretend you were perfect from the start and squash "fix 1" through "fix 3" into "big feature commit". Your typos and brainfarts are not of historical relevance.
Perhaps I'm missing something, but I don't see how your comment answers my questions. Do you mean that a "clean history" is one without "fix 1", "fix 2" and "fix 3"? Or is that a "semantically delineated commit"?
A clean history is one where there is a single commit, "big feature commit", that produces a worktree that is the same as the one produced by "fix 3" in the "unclean" history.
How is this possible, while sharing code? Doesn't this require that pushed code is perfect? What about everyone else working on the same code? Do they wait until you've reached perfection? Or, do you squash the branch once it's complete, with the assumption that there's no other development on/from that temporary branch (I envy you if so)?
(I ask these questions fully assuming I'm doing it wrong.)
And that's the one--and only--reasonable use of rebasing, to squash commits from a branch before merging into main. If engineers find themselves using rebase in any other context than squashing a merge, it's time to re-evaluate the processes/culture around workflow.
When working with published/shared branches with other people, the advice with git has always been that history is history and not to be changed after publishing, unless there is an emergency like a security incident.
Aside from that we need might need to clarify what the question is. With shared code & git, it’s nice to use a branch & merge workflow, and it’s nice to make incoming merges as clean / nice as you can do the resulting history is as smooth as it can be while capturing what happened at a reasonable granularity. These are today’s conventions though, and it’s really up to the team to decide how to balance shared work, and what people feel are the most important workflows and tools.
You fix your local tree before sharing it. Alternatively, you can communicate with your team and tell them they'll need to run git fetch && git rebase -i origin/main to drop your erroneously merged commits.
They are. Or at least can be. Typos I probably agree with, but I've seen plenty of logic bugs introduced in those "fix" commits and keeping them separate from the big one is useful when figuring out what was supposed to happen.
A bunch of wip wip2 wip3 commits don't add any value, and make the log harder to read. But if you break a bigger PR down into "added feature x", "tests for feature x", "refactored y to support x" -- the commits are easier to read and provide valuable "why" history when you're trying to figure out what happened two years later.
That's more about the contents of the merged commits than anything else. Modifying the commit message(s) fixes that, as long as that's what the commits actually did.
Aside from that, how are "a fix for a bug" style commits not "clean"? If merge 123 into master contains a bug that is fixed in a future merge 1234, it doesn't seem "dirty" to me; quite the opposite actually, as it tracks what actually happened.
Now, "wip" style commits shouldn't be on whatever main branch everyone is working on: that's what branches are for. And if everyone is just working off the main branch and committing directly to it, that's an organizational deficiency; not one that VCS can solve.
Modifying commit messages is rewriting history, right?
> “wip” style commits shouldn’t be on whatever branch everyone is working on
Agreed! We aren’t talking about rewriting shared branch history, we are talking about removing the “wip” commits made hastily and locally before pushing them. Sounds like we agree!
> they unfortunately have a big downside because squash merges can make bisecting and history spelunking more difficult, when the branches that are squash merged were big
can you help me understand this? It is the exact opposite of my experience. The flow I see is: bug reported, write a git bisect test, identify the feature that introduced it, reach out to that developer/team.
This is allowed by squash merges. When I've seen these more "clean" histories, they have commit points that wont even compile or have runnable tests causing git bisect to fail.
> branches that are squash merged were big
it must be this - how big are your merges? All the projects I've worked on strive for smaller PRs. Large PRs are usually broken up into smaller pieces. Large PRs are an anti-pattern.
I maybe don’t know what you mean about “clean histories”. Speaking for myself, I always expect a history that’s called “clean” to compile error-free at every commit, unless otherwise noted; one of my personal criteria for calling history ‘clean’ is that efforts are made to keep the main branch up and running for every commit.
> how big are your merges? […] Large PRs are an anti-pattern.
Depends, but they sometimes on occasion can get pretty big, if there’s a bit refactor and/or multiple people in the branch. Small enough PRs are a nice goal - it’s a goal that might agree with and exist in part because squash merges on large PRs lose too much. It’s just the real world routinely gets in the way. It’s very easy for someone who needs to do an ‘atomic’ refactor to touch a ton of files. It’s very easy for a planned feature to end up way bigger than intended. You can’t always keep PRs small or enforce it on other people. Sometimes stuff happens, and when it does, sometimes squash merging feels less good than merging a branch with multiple commits. The good news is that it’s always optional. The bad news is that I can’t necessarily babysit or dictate what others do, and some people prefer squash-merging to spending any time doing cleanup on a messy branch.