For the 5% of engineers that diligently split each PR into nice semantic changes, I suppose that's nice. But the vast majority of engineers don't do this. Individual commits in a PR are testing and iteration. You don't want to read though that.
Unless, of course, you're asking the engineer to squash on their end before making the PR. But what's the value in that ceremony?
Each PR being squashed to 1 commit is nice and easy to reason about. If you truly care about making more semantic history, split the work into multiple PRs.
For that matter, why merge? Rebase it on top. It's so much cleaner. It's atomic and hermetic.
With an explicit merge, you keep two histories, yet mostly care about the "main" one. With rebase, you're effectively forgetting there ever was a separate history, and chose to rewrite the history when "effectively merging" (rebasing).
There's value in both, mostly seems to come down to human preference. As long as the people that will be working with it agrees, I personally don't care either way which one, granted it's consistently applied.
But a MERGE… is a single commit on Master, while keeping the detailed history!
- We just don’t use the merge because they are ugly,
- And they’re only ugly because the visualizers make them ugly.
It’s a tooling problem. The merge is the correct implementation. (and yet I use the rebase-fast-forward).
I like merge commits because they preserve the process of the review.
I appreciate that, but I still can't square it with my world view.
GitHub or whatever tool you use preserves all the comments and feedback and change history if you ever need to go back and reference it. Such instances are limited, and in my experience it's mainly politics and not technical when this happens. The team's PR discussion itself isn't captured in git, so it's lossy to expect this type of artifact to live in git anyway. It's also much less searchable and less first class than just going back to GitHub to access this.
Ultimately, these software development artifacts aren't relevant to the production state of the software you're deploying. It feels muddled to put an incomplete version of it into your tree when the better source of truth lives outside.
> Ultimately, these software development artifacts aren't relevant to the production state of the software you're deploying. It feels muddled to put an incomplete version of it into your tree when the better source of truth lives outside.
You could make the same claim about the entire history. Git is a development tool, production just needs a working cut of the source.
The advice around PRs rings hollow, after all they were invented by the very people that don't care - which is why they show all changes by default and hide the commits away, commit messages buried after 5 clicks. And because this profession is now filled with people that don't care, add the whole JIRA ticket and fix version rigmarole on top - all kinds of things that show up in some PMs report but not in my console fixing an issue that requires history.
1. It makes review much easier, which is both important because core maintainer effort is the most precious resource in open source, and because it increases the likelihood that your PR will be accepted.
2. It makes it easier for people to use the history for analysis, which is especially important when you may not be able to speak directly to the original author.
These reasons also apply in commercial environments of course, but to a lesser extent.
For me, organizing my PRs this way is second nature and only nominal effort, because I'm extremely comfortable with Git, including the following idiom which serves as a more powerful form of `git commit --amend`:
git add -p
git commit --fixup COMMIT_ID
git stash
git rebase -i --autosquash COMMIT_ID~
An additional benefit is that this methodology doesn't work well for huge changesets, so it discourages the anti-pattern of long-lived topic branches. :)> For that matter, why merge? Rebase it on top.
Yes, that works for me although it might not work for people who aren't going to the same lengths to craft a logical history. I have no interest in preserving my original WIP commits — my goal is to create something that is easy to review.
BUT... the PR should ultimately be merged with a merge commit. Then when you have a bug you can run `git bisect` on merges only, which is good enough.
If you did not approach it through literate programming, I just prefer all of the thousands of lines at once.
Each unit of code (PR, commit, CL, whatever you want to call it) you send for review should be able to stand on its own, or at the very least least not break anything because it's not hooked into anything important yet.
(And for me as well — both the individual commits and the PR-level summary are useful.)
So, those of us who prefer commit-sized chunking don't have to do anything special to accommodate your preference.
It doesn't go the other way, of course, if you present one big commit to me. But so long as your code is well-commented (heh) and the PR isn't too huge (heh heh) and you don't intersperse file renamings (heh heh heh) or code formatting changes (heh heh heh heh) which make it extremely difficult to see what you changed... no problem!
One of the "individual commits saved me" cases was when one of these introduced a bug. They tried to cut the number of lines in half by moving conditions around, not intending to make any functional changes, but didn't account for a rare edge case. It was in a company-shared library and we didn't find it until upgrading it on one of our products a year or two after the change.
The first one definitely won't pass CI, the second one might pass CI depending on the changes and whether the repository is configured to consider certain code quality issues CI failures (e.g., in my "green" commits, I have a bias for duplicating code instead of making a new abstraction if I need to do the same thing in a subtly different way), and then the third one definitely passes because it addresses both the test cases in the red commit and any code quality issues in the green commit (such as combining the duplicated code together into a new abstraction that suits both use cases).
I've been on a maintenance team for ~5 years and this has saved me so many times in svn, where you can't squash, for weird edge cases caused by a change a decade or more ago. It's the reason I'm against blind squashes in git.
My favorite was, around 2022, discovering something that everyone believed was released in 2015, but was temporarily reverted in 2016 while dealing with another bug, that the original team forgot to re-release. If the 2016 reversion had been squashed along with the other bug, I might never have learned it was intended to be temporary.
I'm fine with manually squashing "typo" commits, but these individual ones are the kind where you can't know ahead of time if they'll be useful. It's better to keep them, and use "git log --first-parent" if you only want the overview of merges.
However, this is also just a more specific version of the general problem that long-lived, elaborate topic branches are difficult to work with.
git rebase --onto squashed nonsquashed next-pr
Works fine in my experience (except when submodules are involved)T_T
We keep our git use extremely simple, we don't spend much time even thinking about git. The most we do with git is commit, push, and merge (and stash is useful too). Never need to rebase or get any deeper into git. Doing anything complicated with git is wasting development time. Squashing commits isn't useful to us at all. We have too much forward velocity to worry that some commit isn't squashed. If a bug does come up, we move forward and fix it, the git history doesn't really figure into our process much, if at all.
Other teams at my company are obsessive about squashing, and I am glad I don't work on those teams. They are frustrating to work with, they are notoriously slow to ship anything, and their product still breaks even with all their processes and hurdles to getting anything shipped. Teams that demand squashing simply don't impress me at all.
That said, very few people seem to be like me. Most people have no concept of what a clear commit history is. I think it's kind of similar to how most people are terrible written communicators. Few people have any clue how to express themselves clearly. The easiest way to deal with people like this is to just have them squash their PRs. This way you can at least enforce some sanity at review and then the final commit should enforce some standards.
I agree on rebasing instead of straight merging, but even that's too complicated for most people.
This exactly - if your commit history for a PR is interesting enough to split apart, then the original PR was too large and should have been split up to begin with.
This is also a team culture thing - people won't make "clean" commits into a PR if they know people aren't going to be bisecting into them and trying to build. OTOH, having people spend time prepping good commits is potentially time wasted if nobody ever looks at the PR commit history aside from the PR reviewers.
And please do not come back with “you shouldn’t do a refactor as part of a feature change.” We don’t need to add bureaucracy to avoid a problem caused by failure to understand the power of good version control.
There is a reason pull requests and merges operate on branches, not just individual commits. It’s like you’re intentionally hamstringing yourself by saying these things must appear as a single commit.
Do I want every PR to be a long ugly list of trivial half-done commits with messages like “fix typo” or “partial checkpoint of encabulator functionality”? No. Does everything need to be hammered down into a single wad? Also no. There is a middle ground and people should be trusted to find it, and even to have their own opinion on where it is.
IMO `git merge --squash` was perhaps a cultural mistake. Few people use that command but the apparent cultural impact seems to have been great (if that was where it came from).
GitHub squash is a bullet list of all the commit messages. That’s just inviting a ball of mud changelist[1]. But because of the all too convenient "squash" shorthand we now have to explain every time: Yes, there is a middle ground between having one monolithic commit and keeping every little trivial checkpoint and correction.
[1]: The change: the diff. The list: try to correlate each bullet point with something in the diff. (In cases where that applies, some things will have been done and undone and won’t show up in the diff.)
Good habits make good engineers.
You never know which of your commits will cause a future problem so structuring all of them well means that when you need to reach for a tool like git bisect then your history makes it easy to find the cause of the problem.
It takes next to no extra effort.
So you can differentiate the plumbing from the porcelain.
If all the helpers, function expansions, typo corrections, and general renamings are isolated, what remains is the pure additional functional changes on its own. It makes reviewing changes much easier.
Splitting work into multiple PRs is unnecessary ritual.
I have never reasoned about git history or paid attention to most commit messages or found any of it useful compared to the contents of the change.
When I used git bisect with success it was on unknown projects. Known projects are easy to debug.
I don't argue with your point (even if I am obsessive about commits separation), but one needs to keep in mind that the reverse also applies, that is, on other end of the spectrum, there are devs who create kitchen-sink PRs which include, for example, refactorings, which make squashed PRs harder to reasons about.
I think cause and effect are the other way around here. You write and keep work-in-progress commits without caring about changes because the history will be discarded and the team will only look at pull requests as a single unit, and write tidy distinct commits because the history will be kept and individual commits will be reviewed.
I've done both, and getting everyone to do commits properly is much nicer, though GitHub and similar tools don't really support or encourage it. If you work with repository history a lot (for example, you have important repositories that aren't frequently committed to, or maintain many different versions of the project) it's invaluable. Most projects don't really care about the history—only the latest changes—and work with pull-requests, which is why they tend to use the squashed pull request approach.
If you mean stacked PRs, yeah GitHub absolutely sucks. Gerrit a decade ago was a better experience.
I would rather deal with nice and tidy but untested commits rather than a very well-tested but too monolithic squash. If you test the eventual merge then you have done no less testing than the squash case.
Old commits are read more than they are run.
Here's a simple reason: at my company, if you don't do this, you get fired.
This is basic engineering hygiene.
Those 5% shouldn't be approving PRs without it.
Those 95% shouldn't be code owners.
Nice things can be had, you just have to work for them.
git log --first-parent
git bisect --first-parent
The above gives you clean PR history in the main branch while retaining detailed work history in (merged) feature branches.
I really don't understand why would I squash having git merge --no-ff at my disposal...
[1] https://github.com/rectang/dotfiles/blob/master/gitextra#L11...
Merges lie I worse ways than rebase does. Hands down. With rebase I break my own shit. With merge I can break yours. Since your code is already merges into trunk, it has fewer eyes on it now and it’s on me to make sure my code works with yours and not vice versa.
With a merge commit anyone can see each original path, and the merge result (with its author), and even try the three-way merge again. With a rebase, the original context is lost (the original commits were replaced by the rebased versions).
A rebase is a lossy projection of the same operation. A rebase lies about its history, a merge does not.
Are cherry picks involved (which also screw up history)? https://devblogs.microsoft.com/oldnewthing/20180313-00/?p=98...
https://lucasoshiro.github.io/posts-en/2024-04-08-please_don...
Squashes and Rebases delete information entirely out of source control. If a PR adds 100 lines of code, then deletes 50 of them and you squash it, you've lost 50 lines of code. Once your PR tool garbage collects those original commits you can't easily get it back. Maybe that's fine and that was garbage code anyway, but it was part of the process of getting those 50 lines you kept and could be relevant to questions of "why were these 50 lines added?" Were those other 50 lines alternatives that were rejected in testing, perhaps? Worse, were they deleted on accident in a bad inner merge and not caught in time?
I feel a lot less confident in the YAGNI code tossing that Squashes and Rebases do. I've had too many times where I've needed to code dive a fix out of a Merge commit or out of a long winding PR that went through several alternatives.
Git provides some great 2D navigation tools such as `--first-parent`. You can have "I just want a view of PR merges" and the structure of "the commits below that PR are accessible, too". I can't see why you wouldn't want that. That's what source control is for, "save all the minutia in case I need to find it again someday" and it is just a UI problem if you don't want the 2D view by default.
I think the "subway diagrams" that most git UIs show look pretty, especially in screenshots, but are not the UI most people actually want to work with day-to-day. I think a lot fewer people would be so adamant about squash-only or rebase-only workflows if more git UIs took a "1D" first view of the git DAG instead of "subway views". I think more git UIs should start with `--first-parent` and provide drilldown into "lower" commits instead.
"typo fix"