GitHub Stacked PRs
Posted by ezekg 7 days ago
Comments
Comment by adamwk 7 days ago
It’s not just nice for monorepos. It makes both reviewing and working on long-running feature projects so much nicer. It encourages smaller PRs or diffs so that reviews are quick and easy to do in between builds (whereas long pull requests take a big chunk of time).
Comment by smallmancontrov 7 days ago
Comment by alwillis 7 days ago
It wasn't the Mercurial team saying it was faster than Git; that was Facebook after contributing a bunch of patches after testing Mercurial on their very large mono-repo in 2014 [1]:
For our repository, enabling Watchman integration has made Mercurial’s status command more than 5x faster than Git’s status command. Other commands that look for changed files–like diff, update, and commit—also became faster.
In fact they liked Mercurial so much they essentially cloned it to create their own dvcs, Sapling [2]. (An aside: Facebook did all of this because it was taking too long getting new engineers up to speed with Git. Shocker.)
Today, most of the core of Mercurial has been rewritten in Rust; when Facebook did their testing, Mercurial was nearly 100% Python. That's where the "Mercurial is slow" thing came from; launching a large Python 2.x app took a while back in the day.
I was messing with an old Mercurial repo recently… it was like a breath of fresh air. If I can push to GitHub using Mercurial… sign me up.
[1]: https://engineering.fb.com/2014/01/07/core-infra/scaling-mer...
Comment by Gabrys1 7 days ago
Comment by Ericson2314 6 days ago
Comment by alwillis 6 days ago
I was immediately intrigued when I learned that JJ has revsets [1], just like Mercurial.
Comment by withinboredom 7 days ago
Comment by itsdesmond 6 days ago
They don’t sound like a very good teacher.
Comment by withinboredom 6 days ago
Comment by smallmancontrov 6 days ago
I'm glad BigCo made tools to serve their needs, but their needs aren't my needs or most peoples' needs.
> Mercurial has been rewritten in Rust
I'm glad they saw the light eventually! Ditto for the rest of the Rust Tooling Renaissance.
Comment by steveklabnik 7 days ago
I was never a fan of hg either, but now I can use jj, and get some of those benefits without actually using it directly.
Comment by landr0id 7 days ago
Fun story: I don't really know what Microsoft's server-side infra looked like when they migrated the OS repo to git (which, contrary to the name, contains more than just stuff related to the Windows OS), but after a few years they started to hit some object scaling limitations where the easiest solution was to just freeze the "os" repo and roll everyone over to "os2".
Comment by MASNeo 7 days ago
The IBM crowd may feel vindicated at last.
Comment by miki123211 7 days ago
I guess what's old is new again.
Comment by w0m 7 days ago
I have fuzzy memories on reading about it.
Comment by landr0id 7 days ago
The problem was I think something to do with like the number of git objects that it was scaling to causing crazy server load or something. I don't remember the technical details, but definitely something involving the scale of git objects.
Comment by kritr 6 days ago
Changes branches took an eternity, and people resorted to a more workspaces style solution.
If you’re planning on starting a big tech company, I wouldn’t recommend the approach.
Comment by jamesfinlayson 7 days ago
Comment by kqr 7 days ago
Comment by mike_hearn 6 days ago
google1 = code written by Larry, Sergey and employee number 1 (Craig). A hacky pile of Python scripts, dumped fairly quickly.
google2 = the first properly engineered C++ codebase. Protobufs etc were in google2. But the build system was some jungle of custom Makefiles, or something like that. I never saw it directly.
google3 = the same code as google2 but with a new custom build system that used Python scripts to generate Makefiles. I suppose it required a new repository so they could port everything over in parallel with code being worked on in google2. P4 was apparently not that great at branches and google3 didn't use them. Later the same syntax for the build files was kept but turned into a new languages called Starlark and the Makefile generator went away in favor of Blaze, which directly interpreted them.
At least, that's the story I vaguely recall.
Comment by ongy 7 days ago
But not the 3rd mono repo on the same technology to avoid some scaling limit.
Comment by dijit 7 days ago
I think what happened is Google bought a license for source code and customised it.
Comment by steveklabnik 7 days ago
Comment by surajrmal 7 days ago
Comment by unmole 6 days ago
That makes sense because vanilla Perforce is unbearably slow and impossible to scale.
Last I checked, it was bought by Private Equity firms and actual product development had more or less stopped.
Comment by smallmancontrov 7 days ago
The efforts to sell priest robes to fruit vendors were a little silly, but I'm glad they didn't catch on because if they had caught on they no longer would have been silly.
Comment by dwattttt 7 days ago
Comment by hypeatei 6 days ago
0: https://en.wikipedia.org/wiki/The_Cathedral_and_the_Bazaar
Comment by bonzini 6 days ago
Comment by eqvinox 7 days ago
Comment by riffraff 7 days ago
It worked just fine 99% of the time and then 1% it became completely unusable.
Comment by dented42 7 days ago
Comment by littlecranky67 6 days ago
The only case I can imagine is when doing a full checkout of a big repo, but even there, there is --depth which is quite practical.
Comment by windward 6 days ago
Comment by ak217 6 days ago
This whole time (the past 15 years) git has been getting faster without most of us noticing, because big companies have been investing in speeding it up. The reason you don't notice or care is that they work on a very different scale. Thousands of users, thousands of PRs per day, millions of CI/CD jobs all hitting the repo.
Now the cycle is repeating again because these numbers are shooting through the roof because of agentic coding.
Comment by raincole 7 days ago
Comment by forrestthewoods 7 days ago
Git is super mid. It’s a shame that Git and GitHub are so dominant that VCS tooling has stagnated. It could be so so so much better!
Comment by windward 6 days ago
Comment by qsera 6 days ago
Rebase does not make sense in Mercurial because it has the concept of fixed branches. A commit is permanently linked to the branch on which it was made. So you are supposed to use merges.
Same with force-pushing.
Comment by windward 6 days ago
Believe me, I tried to have an open mind about it. Then one day I was getting ready to go on a work trip with a half-finished feature on my work laptop, and realised there was simply no in-model way for backing that wip up to the repo. If I lost my laptop, I lost the progress. mercurial-scm fails at SCM.
Comment by qsera 6 days ago
That is because you have this notion of a "clean history", (which IIUC prevented you from making this permanent wip commit) which in reality does not have a lot of use. For most project, "useful history" or "real history" is better than a "clean" history.
That is what mercurial caters to.
Comment by ezst 6 days ago
not sure what you mean to say, but for thoroughness' sake, no: git and mercurial concepts are not interchangeable, with git having mostly an inferior model.
To give examples: git has no concept of branching (in the way every VCS but Git uses the term). A branch in git is merely a tag on the tip of a series meant to signify that all ancestors belong to the same lineage. This comes with the implication that this lineage information is totally lost when two branches merge (you can't tell which side of the merge corresponded to which lineage). The ugly and generalised workaround is to abuse commit message (e.g. "merge feat-ABC into main") to store an essential piece of the repository history that the VCS cannot take.
Another example is phasing: mercurial records at commit level whether it was exchanged with others or not. That draws a clean line between the history that's always safe to rewrite, and which that is subject to conflicting merges if the person you shared those commits with also happened to rewrite them on their end.
> Then one day I was getting ready to go on a work trip with a half-finished feature on my work laptop, and realised there was simply no in-model way for backing that wip up to the repo. If I lost my laptop, I lost the progress. mercurial-scm fails at SCM.
Sorry to be blunt, but that's a skill issue: hg is no different than every other VCS in that regard. If you want your WIP changes to leave your laptop, you've got to push them somewhere, just like you would in git.
Comment by windward 4 days ago
Permanently, to a single branch in un-buildable form. So useful.
Comment by ezst 6 days ago
- rebasing in Mercurial simply means chopping a subtree off of the history and re-attaching it to a different parent commit. In that sense, rebasing is a very useful and common history-rewriting operation. In fact, it's even simpler and more powerful/versatile than in git, because mercurial couldn't care less if the sub-tree you are rebasing belongs to a branch or not: it's just a DAG. It gets transplanted from A to B. A may or may not be your checked commit, or be the tip of a branch, doesn't matter.
- that mercurial requires a configuration toggle before rebasing can be used (i.e. that the user need to enable the extension explicitly) is a way to encourage interested users to learn their tool, and grow its capabilities together with their knowledge. It's opinionated, it may be too much hand-holding for some, but there is an elegant simplicity in keeping the help pages and autocomplete commands just as complex as the user can take it.
Comment by qsera 6 days ago
Sure, but since commits have a branch attribute attached to them, "rebasing" does not appear to be "first class". It is something that has to be bolted on with an extension.
> because mercurial couldn't care less if the sub-tree you are rebasing belongs to a branch or not
IIUC Git also does not care much about the rebase target being a "branch".
I agree that Mercurial provides more value out of the box than git because it preserves branch info in commits.
I can live with Git because Git is "enough" if used carefully and after coming to terms with the non-intutive UI.
Comment by ezst 4 days ago
Again, that's orthogonal: you may or may not use "named branches" (the kind of which persists at commit level), rebasing works either way consistently and predictably.
> It is something that has to be bolted on with an extension.
The extension ships in core, UX is why it's not enabled by default.
> IIUC Git also does not care much about the rebase target being a "branch".
Indeed, it's just that things likely get weird (for no good reason) when you don't (detached head, "unreachable" commits)
> I can live with Git because Git is "enough" if used carefully and after coming to terms with the non-intutive UI.
That's our sad state of affairs. JJ helps a bit, though.
Comment by saagarjha 6 days ago
Comment by qsera 6 days ago
Comment by worldsayshi 7 days ago
Comment by ptx 6 days ago
Comment by PeterStuer 7 days ago
Comment by worldsayshi 7 days ago
Comment by awesome_dude 7 days ago
IOW, what do you know that nobody else does?
Comment by jorams 7 days ago
You can visit any resource about git and branches will have a prominent role. Git is very good at branches. Mercurial fans will counter by explaining one of the several different branching options it has available and how it is better than the one git has. They may very well be right. It also doesn't matter, because the fact that there's a discussion about what branching method to use really just means Mercurial doesn't solve branches. For close to 20 years the Mercurial website contained a guide that explained only how to have "branches" by having multiple copies of the repository on your system. It looks like the website has now been updated: it doesn't have any explanation about branches at all that I can find. Instead it links to several different external resources that don't focus on branches either. One of them mentions "topic", introduced in 2015. Maybe that's the answer to Git's branching model. I don't care enough to look into it. By 2015 Git had long since won.
Mercurial is a cool toolbox of stuff. Some of them are almost certainly better than git. It's not a better product.
Comment by LordDragonfang 7 days ago
Comment by fc417fc802 7 days ago
I think there are (or perhaps were) some product issues regarding the specifics of various workflows. But at least some of that is simply the inertia of entrenched workflows and where there are actual downsides the (IMO substantial) advantages need to be properly weighed against them.
Personally I think it just comes down to the status quo. Git is popular because it's popular, not because it's noticably superior.
Comment by ezst 6 days ago
I love jumping in discussions about git branching, because that's a very objective and practical area where git made the playing field worse. Less and less people feel it, because people old-enough to have used branch-powered VCSes have long forgotten about them, and those who didn't forget are under-represented in comparison to the newcomers who never have experienced anything else since git became a monopoly.
Anyhow, let's pick django as a project that was using a VCS with branches before moving to git/github, and have a look at the repo history: https://github.com/django/django/commits/stable/6.0.x
Yes, every commit is prefixed with the branch name. Because, unlike mercurial, git is incapable of storing this in its commit metadata. That's ridiculous, that's obscene, but that's the easiest way to do it with git.
Comment by jstimpfle 4 days ago
You can just not move branches. But once you can do it, you will like it. And you are going to use
git branch --contains COMMIT
which will tell you ALL the branches a commit is part of.Git's model is clean and simple, and makes a whole lot of sense. IMHO.
Comment by jstimpfle 4 days ago
I'm old enough to have used SVN (and some CVS) and let me tell you branching was no fun, so much that we didn't really do it.
Comment by Tarq0n 7 days ago
Comment by qsera 6 days ago
Git does not have such concept. That is a trade off and that trade off works great for projects managed like Linux kernel. But for smaller projects where there is a limited number of people working, the information preserved by mercurial could be very valuable.
It also had some really interesting ideas like change set evolution, which enabled history re-writing after a branch has been published. Don't know its current status and how well it turned out to be..
Comment by awesome_dude 6 days ago
If you rebase the feature branch into the main branch THEN follow it up with the merge commit that records the branch name you store the branches (that have been made a part of main) and can see where they are in your log
Mercurial's notes can become cumbersome if there are a large number in the repository, but, obviously, humans can sort that out if it gets out of hand
Comment by xmcqdpt2 6 days ago
Maybe branching was an important reason to adopt git but now we'd probably be ok with a vcs that doesn't even support them.
Comment by awesome_dude 6 days ago
Comment by krick 6 days ago
Comment by awesome_dude 6 days ago
A middle ground is small PRs where people are constantly rebasing to the tip of main to keep conflicts to a minimum
Comment by forrestthewoods 7 days ago
Google and Meta don’t use Git and GitHub. Sapling and Phabricator much much better (when supported by a massive internal team)
Comment by aaronbrethorst 7 days ago
Comment by mi_lk 7 days ago
Comment by CrimsonRain 7 days ago
Comment by corndoge 7 days ago
Comment by codethief 6 days ago
Comment by guelo 7 days ago
Comment by awesome_dude 7 days ago
I personally went from .latest.latest.latest.use.this (naming versions as latest) to tortoise SVN (which I struggled with) to Git (which I also was one of those "walk around with a few memorised commands" people that don't actually know how to use it) to reading the fine manual (well 2.5 chapters of it) to being an evangalist.
I've tried Mercurial, and, frankly, it was just as black magic as Git was to me.
That's network effects.
But my counter is - I've not found Mercurial to be any better, not at all.
I have made multiple attempts to use it, but it's just not doing what I want.
And that's why I'm asking, is it any better, or not.
Comment by WolfeReader 7 days ago
Comment by awesome_dude 7 days ago
But what I will point out, for better or worse, people are now looking at LLMs as Git masters, which is effectively making the LLM the UI which is going to have the effect of removing any assumed advantage of whichever is the "superior" UX
I do wish to make absolutely clear that I personally am not yet ready to completely delegate VCS work to LLMs - as I have pointed out I have what I like to think of as an advanced understanding of the tools, which affords me the luxury of not having an LLM shoot me in the foot, that is soley reserved as my own doing :)
Comment by arw0n 7 days ago
The thing is, to understand which one is actually better, you would have to give the same amount of investment in the second tool, which is not something most people are willing to do if the first tool is "good enough". That's how Python became the default programming language; people don't miss features they do not understand.
Comment by Izkata 6 days ago
So at least for me, git was clearly better.
Comment by kasey_junk 6 days ago
That’s it. That’s why git won, you could put up open source libs with one for free and not the other.
Which is extra funny as the centralized service was the most important part of decentralized version control.
Comment by awesome_dude 2 days ago
So git /did/ have something better than Mercurial after all, it was a 3rd party, but it still meant that it was massively better than Mercurial.
Comment by seniorThrowaway 6 days ago
I've often thought this about github
Comment by dugmartin 7 days ago
Comment by esafak 7 days ago
Comment by dwattttt 7 days ago
Comment by jrochkind1 7 days ago
Comment by Per_Bothner 7 days ago
Comment by jrochkind1 6 days ago
Of course, products also can win market dominance for reasons external to the product's quality itself (marketing, monopoly lock-in, other network effects, consumer preferences on something other than product quality itself, etc).
Comment by outworlder 7 days ago
Are we back to "programming language X is slow" assertions? I thought those had died long ago.
Better algorithms win over 'better' programming languages every single time. Git is really simple and efficient. You could reimplement it in Python and I doubt it would see any significant slowness. Heck, git was originally implemented as a handful of low level binaries stitched together with shell scripts.
Comment by jmalicki 7 days ago
Python is absurdly slow - every method call is a string dict lookup (slots are way underused), everything is all dicts all the time, the bytecode doesn't specialize at all to observed types, it is a uniquely horrible slow language.
I love it, but python is almost uniquely a slow language.
Algorithms matter, but if you have good algorithms, or you're already linear time and just have a ton of data, rewriting something from a single-threaded Python program to a multithreaded rust program I've seen 500x speedups, where the algorithms were not improved at all.
It's the difference between a program running overnight vs. in 30 seconds. And if there are problems, the iteration speed from that is huge.
Comment by eru 7 days ago
To be fair, Python as implement today is horribly slow. You could leave the language the same but apply all the tricks and heroic efforts they used to make JavaScript fast. The language would be the same, but the implementations would be faster.
Of course, in practice the available implementations are very much part of the language and its ecosystems; especially for a language like Python which is so defined by its dominant implementation of CPython.
Comment by jmalicki 7 days ago
But a lot of the monkey-patching kind of things and dynamism of python also means a lot of those sorts of things have to be re-checked often for correctness, so it does take a ton of optimizations off the table. (Of course, those are rare corner cases, so compilers like pypy have been able to optimize for the "happy case" and have a slow fall-back path - but pypy had a ton of incompatibility issues and now seems to be dying).
Comment by mike_hearn 6 days ago
Comment by xmcqdpt2 6 days ago
The real reason is that it is a deliberate choice by the CPython project to prefer extensibility and maintainability to performance. The result is that python is a much more hackable language, with much better C interop than V8 or JVM.
Comment by byroot 7 days ago
Doesn't the Python VM have inline caches? [0]
Comment by jmalicki 7 days ago
It didn't used to.
EDIT: python 3.11+: https://peps.python.org/pep-0659/
Comment by kuschku 7 days ago
Later on I also changed some of the algorithms to faster ones, but their impact was much lower than the language change.
Comment by bonesss 7 days ago
Which is only to say: that rewrite away from python story can also work to show python doing its job. Risk reduction, scaffolding, MVP validation.
Comment by Diggsey 7 days ago
A bunch of low level binaries stitched together with shell scripts is a lot faster than python, so not really sure what the point of this comparison is.
Python is an extremely versatile language, but if what you're doing is computing hashes and diffs, and generally doing entirely CPU-bound work, then it's objectively the wrong tool, unless you can delegate that to a fast, native kernel, in which case you're not actually using Python anymore.
Comment by eru 7 days ago
Comment by eru 7 days ago
That's often true, but not "every single time".
Comment by 20k 7 days ago
One of the reason mercurial lost the dvcs battle is because of its performance - even the mercurial folks admitted that was at least in part because of python
Comment by ragall 7 days ago
No, it's always been true. It's just that at some point people got bored and tired of pointing it out.
Comment by bmitc 7 days ago
Comment by ezst 6 days ago
Yes we are? The slow paths of mercurial have been rewritten in C (and more recently in Rust) and improved the perf story substantially, without taking away from the wild modularity and extensibility hg always had.
Comment by saghm 7 days ago
I doubt it wouldn't be significantly slower. I can't disprove it's possible to do this but it's totally possible for you to prove your claim, so I'd argue that the ball is in your court.
Comment by surajrmal 7 days ago
The reason that some more modern tools, like jj, really blow git out of the water in terms of performance is because they make good choices, such as doing a lot of transformations entirely in memory rather than via the filesystem. It's also because it's written in a language that can execute efficiently. Luckily, it's clear that modern tools like jj are heavily inspired by mercurial so we're not doomed to the ux and performance git binds us with.
Comment by inejge 7 days ago
Apparently I belong to the same club -- when I'm writing AWK scripts. (Arrays are hashmaps in a trenchcoat there.) Using hashmaps is not necessarily an indictment you apparently think it is, if the access pattern fits the problem and other constraints are not in play.
> It's amazing how much we've brainwashed folks to focus on algorithms and lose sight of how to actually properly optimize code. Being aware of how your code interacts with cache is incredibly important.
By the time you start worrying about cache locality you have left general algorithmic concerns far behind. Yes, it's important to recognize the problem, but for most programs, most of the time, that kind of problem simply doesn't appear.
It also doesn't pay to be dogmatic about rules, which is probably the core of your complaint, although unstated. You need to know them, and then you need to know when to break them.
Comment by jstimpfle 6 days ago
I'll take it back a little bit, because there _is_ in fact a lot of algorithmically inefficient code out there, which slows down everything a lot. But after getting the most obvious algorithmic problems out of the way -- even a log-n algorithm isn't much of an improvement to a linear scan, if n < 1000. It's much more important to get that 100+x speedup by implementing the algorithm in a straightforward and cache friendly way.
Comment by surajrmal 6 days ago
Comment by forrestthewoods 7 days ago
It’s amusing you call Git fast. It’s notoriously problematic for large repos such that virtually every BigTech company has made a custom rewrite at some point or another!
Comment by jstimpfle 7 days ago
For everything I've ever done, git was practically instant (except network IO of course). It's one of the fastest and most reliable tools I know. If it isn't fast for you, chances are you are on a slow Windows filesysrem additionally impeded by a Virus scanner.
Comment by forrestthewoods 7 days ago
The mere fact that Git is unable to handle large binary files makes it an unusable tool for literally every project I have ever worked on in my entire career.
Comment by jstimpfle 6 days ago
Takes 21 seconds on my work laptop, indeed a corporate Windows laptop with antivirus installed. Majority of that time is simply network I/O. The cloned repository is 276 MB large.
Actually checking the kernel out takes 90 seconds. This amounts to creating 99195 individual files, totaling 2 GB of data. Expect this to be ~10 times faster on a Linux file system.
So what's your problem?
Comment by forrestthewoods 5 days ago
If you’d like to argue that version control should be centralized, shallow, and sparse by default then I agree.
Comment by jstimpfle 3 days ago
I get your sentiment, but I know how working with e.g. SVN feels. Just doing "svn log" was a pain when I had to do it. The "distributed" aspect of DVCS doesn't prevent you from keeping central what you need central. E.g. you can have github or your own hosting server that your team is exchanging through.
The main point of distributed is speed and self-sufficiency which is a huge plus. E.g. occasional network outages and general lack of bandwidth are still a thing in 2026 (and remain so to some extent for the foreseeable future).
Now, could git improve and allow some things to be staged/tiered/transparently cached better? Probably, and that's where some things like LFS come in. I don't have a large amount of experience in this field though, because what I work with is adequately served by the out-of-the-box git experience.
Comment by jstimpfle 4 days ago
Comment by spockz 7 days ago
Comment by forrestthewoods 7 days ago
Comment by spockz 6 days ago
Comment by pabs3 7 days ago
Comment by bmitc 7 days ago
Comment by Cthulhu_ 6 days ago
Comment by ezst 6 days ago
Among the tricks being used was remotefilelogs, which is a way to "hydrate" content locally on-demand, which was mimicked in git many years later with Microsoft's git-vfs. Same goes with binary/large files that git eventually got as git-lfs.
It's funny to think that a big reason for git to be "fast" today is by playing catch-up with mercurial, which carries this "forever stigma" of being slow.
Comment by Leynos 7 days ago
Comment by kardianos 7 days ago
Comment by tcoff91 7 days ago
Comment by nine_k 7 days ago
Phabricator and even Gerrit are significantly nicer.
Comment by dathanb82 7 days ago
Comment by riffraff 7 days ago
Like, you can do a change that introduced a new API and one that updates all usages.
It's just easier to review those independently.
Or, you may have workflows where you have different versions of schemas and you always keep the old ones. Then you can do two commits (copy X to X+1; update X+1) where the change is obvious, rather than seeing a single diff which is just a huge new file.
I'm sure there's more cases. It's not super common but it is convenient.
Comment by strokirk 5 days ago
Comment by steveklabnik 7 days ago
Comment by verst 7 days ago
Comment by steveklabnik 7 days ago
Comment by Sebb767 6 days ago
Security. Imagine commit #1 introduces a security vulnerability (backdoor) and the features. Then #2 introduces a non-obvious, harmless bug and closes the vulnerability introduced in #1 [0]. At some point, the bug will surface and rolling back commit #2 will be an easy fix, re-introducing your bug.
Alternatively, one of the earlier commits might, for example, contain credential dumping code. Once that commit is mainlined, CI might either automatically run on it or will be able to be run on it since it's no longer marked as unsafe PR.
[0] Think something like #1 introduces array access and #2 adds a bounds-check in a function a layer above - a reviewer with the whole context will see the bounds check and (possibly) consider it fine, but to someone rolling back a commit the necessity will not be obvious.
Comment by adityaathalye 7 days ago
Rant incoming...
Boy do I hate Github/Lab/Bucket style code reviews with a burning passion. Who the hell loses code review history? A record of the very thing that made my code better? The "why" of it all, that I am guaranteed to forget tomorrow morning.
Nobody would be using `--force` or `--force-with-lease` as a normal part of development workflow, of their own volition, if they had read that part of the git-push manpage and been horrified (as one should be).
The magit key sequence for this abominable operation is `P "f-u"`. And every single time I am forced to do it, I read "f-u" as it ought to be read.
Rebase-push is the way to do it (patch sets in Gerrit).
Rebase-force-push is absolutely not.
You see, any development workflow inevitably has to integrate changes from at least one other branch (typically latest develop or master), without destroying change history, nor review history. Gerrit makes this trivial.
It's a bit difficult to convey exactly why I'm so rah-rah Gerrit, because it is a matter of day-to-day experience of
- Well, a single commit of a few lines to maybe a hundred lines *is* the correct unit of code review, rebase, revert etc. Manually "Sizing PRs" to that review context size is utter BS. I have better things to do in life than to book-keep PR sizes. Make a single well-contained, revertible commit. Then keep making those. And now you have a commit history that is clean, that you can merge, bisect, and bulk-revert at will. Octopus merges are a good thing. `git-log` is *designed* to let us view changes in any sequence we wish, *including* the so-called "linear" history. `git log --online`.
- Trivial for committer to send up reviews-preserving rebase-push responses to commit reviews (NO force-push, ever --- that's an "admin" action to *evict* / permanently wipe out disaster scenarios such as when someone accidentally commits and pushes out a plaintext secret or a giant blob of the executable of the source code etc.).
- Fast-for-the-reviewer, per-commit, diff-based, inline-commenting code reviews.
- The years-apart experience of being able to dig into any part of one's (immutable) software change history to offer a teaching moment to someone new to the team.
... to name a few key ones.(edit: add point about review size)
Comment by adityaathalye 7 days ago
Comment by calebio 7 days ago
Comment by treefry 7 days ago
Comment by sam_bristow 7 days ago
Comment by ivantop 7 days ago
Comment by sam_bristow 7 days ago
Comment by montag 7 days ago
Nothing since (Gerrit, Reviewboard, Github, Critique) has measured up...
Comment by Rodeoclash 7 days ago
Comment by surajrmal 6 days ago
Comment by Redoubts 6 days ago
Comment by surajrmal 6 days ago
Comment by nerdypepper 7 days ago
Comment by choi0330 6 days ago
Comment by eru 7 days ago
See https://stackoverflow.com/questions/20756320/how-to-prevent-...
Comment by KwanEsq 7 days ago
Comment by eru 7 days ago
Comment by illamint 7 days ago
Comment by eru 7 days ago
I hope they fixed phabricator in the meantime.
Comment by dbetteridge 7 days ago
One merged pr is a unit of change, at the end of the day the steps you took to produce it aren't relevant to others.
My opinion of course, I'm open to understanding why preserving individual commits is beneficial
Comment by eru 7 days ago
See how the Linux kernel handles git history to see a good example of non-linear history and where it helps. They use merge commits, ie commits with more than one ancestor, all the time.
Comment by saagarjha 6 days ago
Comment by jenadine 7 days ago
- merge some commits independently when partial work is ready.
- mark some commit as reviewed.
- UI to do interactive rebase and and squash and edit individual commits. (I can do that well from the command line, but not when using the GitHub interface, and somehow not everyone from my team is familiar with that)
- ability to attach a comment to a specific commit, or to the commit message.
- better way to visualize what change over time in each forced push/revision (diff of diff)
Git itself already has the concept of commit. Why put this "stacked PR" abstraction on top of it?
Or is there a difference I don't see?
Comment by tcoff91 7 days ago
The idea is that it allows you to better handle working on top of stuff that's not merged yet, and makes it easier for reviewers to review pieces of a larger stack of work independently.
It's really useful in larger corporate environments.
I've used stacked PRs when doing things like upgrading react-native in a monorepo. It required a massive amount of changes, and would be really hard to review as a single pull request. It has to be landed all at once, it's all or nothing. But being able to review it as smaller independent PRs is helpful.
Stacking PRs is also useful even when you don't need to merge the entire stack at once.
Comment by js2 7 days ago
Ahem, pioneered by gerrit. But actually, I'm almost certain even that wasn't original art. I think gerrit just brought it to git.
Comment by sunshowers 7 days ago
Comment by js2 7 days ago
Comment by Izkata 6 days ago
Possibly from github. It got popularized there at least, encouraging forking code, and is why so many people say "pull request" when they mean "merge request".
Comment by js2 6 days ago
https://git-scm.com/docs/git-request-pull
The command is so old it's still written in shell:
https://github.com/git/git/blob/master/git-request-pull.sh
It was first added July 27, 2005:
https://github.com/git/git/commit/ab421d2c7886341c246544bc8d...
https://lore.kernel.org/git/20050726073036.GJ6098@mythryan2....
But even then, it simply codified existing terminology.
Ah, someone else did the research, so minimally BitKeeper had the "pull" command first and the term "pull request" falls naturally from that:
Comment by hokumguru 7 days ago
Comment by p-e-w 7 days ago
Comment by sunshowers 7 days ago
Comment by monster_truck 7 days ago
Comment by pabs3 7 days ago
https://github.com/rietveld-codereview/rietveld https://en.wikipedia.org/wiki/Rietveld_(software) https://codereview.appspot.com/
Comment by jrochkind1 7 days ago
Comment by marktani 5 days ago
haha, I'm sitting in a very crusty corporate environment right now and your comment made me chuckle. I get where you're coming from though, of course!
Comment by jrochkind1 1 day ago
Comment by ttoinou 6 days ago
Comment by eptcyka 7 days ago
Comment by adregan 7 days ago
1: https://git-scm.com/docs/git-rebase#Documentation/git-rebase...
Comment by eptcyka 4 days ago
Comment by jrochkind1 7 days ago
Comment by tcoff91 7 days ago
Comment by mh2266 7 days ago
I don't use Github but I do work at one of the companies that popularized this workflows and it is extremely not a big deal. Pull, rebase, resolve conflicts if necessary, resubmit.
Comment by mikeocool 7 days ago
Especially since you get all of the same advantages with plain old stream on consciousness commits and merges using:
git merge --no-ff
git log --first-parent
git bisect --first-parent
Comment by MrJohz 7 days ago
I've switched over pretty much entirely to Jujutsu (or JJ), which is an alternative VCS that can use Git as its backend so it's still compatible with Github and other git repos. My colleagues can all use git, and I can use JJ without them noticing or needing to care. JJ has merges, and I still use them when I merge a set of changes into the main branch once I've finished working on it, but it also makes rebases really simple and eliminates most of the footguns. So while I'm working on my branch, I can iteratively make a change, and then squash it into the commit I'm working on. If I refactor something, I can split the refactor out so it's in a separate commit and therefore easiest to review and test. When I get review feedback, I can squash it directly into the relevant commit rather than create a new commit for it, which means git blame tends to be much more accurate and helpful - the commit I see in the git blame readout is always the commit that did the change I'm interested in, rather than maybe the commit that was fixing some minor review details, or the commit that had some typo in it that was fixed in a later commit after review but that relationship isn't clear any more.
And while I'm working on a branch, I still have access to the full history of each commit and how it's changed over time, so I can easily make a change and then undo it, or see how a particular commit has evolved and maybe restore a previous state. It's just that the end result that gets merged doesn't contain all those details once they're no longer relevant.
Comment by muti 7 days ago
What's funny is how much better I understand git now, and despite using jj full time, I have been explaining concepts like rebasing, squashing, and stacked PRs to colleagues who exclusively use git tooling
Comment by skydhash 7 days ago
> So while I'm working on my branch, I can iteratively make a[...]which means git blame tends to be much more accurate and helpful
Everything here I can do easily with Magit with a few keystroke. And magit sits directly on top of git, just with interactivity. Which means if I wanted to I could write a few scripts with fzf (to helps with selection) and they would be quite short.
> And while I'm working on a branch, I still have access to the full history of each commit...
Not sure why I would want the history for a specific commit. But there's the reflog in git which is the ultimate undo tool. My transient workspace is only a few branches (a single one in most cases). And that's the few commits I worry about. Rebase and Revert has always been all I needed to alter them.
Comment by MrJohz 6 days ago
That said, there are additional features in jj that I believe aren't possible in magit (such as evolog/interdiffing, or checked-in conflicts), whereas magit-like UIs exist for jj.
You want the history of a specific commit because if you, say, fixup that commit, you want to know how the commit has changed exactly over time. This is especially useful for code review. Let's say you've got a PR containing a refactor commit and a fix commit. You get a review the says you should consider changing the refactor slightly, so you make that change and squash it into the existing refactor commit. You then push the result - how can the reviewer see only the changes you've made to only the refactor commit? That is an interdiff.
In this case, because you've not added any new commits, it's trivial to figure out which commit in the old branch maps to which commit in the new, fixed branch. But this isn't possible in general (consider adding new commits, or reordering something, or updating the commit message somewhere). In jj, each commit also has a change ID, and if multiple commits share the same change ID, then they must be different versions of the same changeset.
You want the history of the repository which includes the history of each commit, because it's a lot easier to type `jj undo` to revert an operation you just did than it is to find the old state of the repository in the reflog and revert to it, including updating all the branch references to point at their original locations. The op log in jj truly is the ultimate undo tool - it contains every state the repository has every been in, including changes to tags and branches that aren't recorded in the reflog, and is much easier to navigate. It is strictly more powerful than the reflog, while being simpler to understand.
Comment by rstuart4133 6 days ago
That one sentence outs you as someone who isn't familiar with JJ.
Here is something to ponder. Despite claims to the contrary, there are many git commands that can destroy work, like `git reset --hard`. The reflog won't save you. However there is literally no JJ command that can't be undone. So no JJ command will destroy your work irretrievably.
Comment by skydhash 6 days ago
Comment by rstuart4133 6 days ago
Comment by saagarjha 6 days ago
Comment by OptionOfT 7 days ago
And I don't rebase or squash because I need provenance in my job.
Comment by OJFord 7 days ago
Comment by sheept 7 days ago
Comment by OJFord 7 days ago
Comment by d0mine 7 days ago
Comment by xixixao 7 days ago
PR/MR is an "atomic" change (ideally the smallest change that can be landed separately - smallest makes it easier to review, bisect and revert)
Individual commits (or what "versions" are in Phabricator) are used for the evolution of the PR/MR to achieve that change.
But really I have 2 use cases for the commits:
1. the PR/MR is still too big, so I split it into individual commits (I know they will land together)
2. I keep the history of the evolution of the PR/MR in the commits ("changed foo to bar cause its a better approach")
Comment by ahmadyan 7 days ago
Surprisingly it never gained the adoption it deserved.
Comment by icy 7 days ago
Comment by rtpg 7 days ago
Perhaps a future iteration of this feature will at least allow us to do something like merge just steps of it if they can be reordered.
Comment by trashburger 6 days ago
Comment by LuttelBurchtje 7 days ago
Comment by ctdinjeu3 5 days ago
Comment by akersten 7 days ago
Right now I manually do "stacked PRs" like this:
main <- PR A <- PR B (PR B's merge target branch is PR A) <- PR C, etc.
If PR B merges first, PR A can merge to main no problems. If PR A merges to main first, fixing PR B is a nightmare. The GitHub UI automatically changes the "target" branch of the PR to main, but instantly conflicts spawn from nowhere. Try to rebase it and you're going to be manually looking at every non-conflicting change that ever happened on that branch, for no apparent reason (yes, the reason is that PR A merging to main created a new merge commit at the head of main, and git just can't handle that or whatever).
So I don't really need a new UI for this, I need the tool to Just Work in a way that makes sense to anyone who wasn't Linus in 1998 when the gospel of rebase was delivered from On High to us unwashed Gentry through his fingertips..
Comment by sameenkarim 7 days ago
git rebase --onto <new_commit_sha_generated_by_squash> <original_commit_sha_from_tip_of_merged_branch> <branch_name>
So for ex in this scenario: PR1: main <- A, B (branch1)
PR2: main <- A, B, C, D (branch2)
PR3: main <- A, B, C, D, E, F (branch3)
When PR 1 and 2 are squash merged, main now looks like: S1 (squash of A+B), S2 (squash of C+D)
Then we run the following: git rebase --onto S2 D branch3
Which rewrites branch3 to: S1, S2, E, F
This operation moves the unique commits from the unmerged branch and replays them on top of the newly squashed commits on the base branch, avoiding any merge conflicts.Comment by puelocesar 7 days ago
I’m conflicted about it, seems like a good convenience, but I wouldn’t want my team to get dependent on an exclusive feature of a single provider
Comment by steveklabnik 6 days ago
Comment by jd3 6 days ago
Comment by xixixao 7 days ago
No idea if this feature fixes this.
Edit: Hopefully `gh stack sync` does the rebasing correctly (rebase --onto with the PR A's last commit as base)
Comment by akersten 7 days ago
Yeah, and I kind of see how git gets confused because the squashed commits essentially disappear. But I don't know why the rebase can't be smart when it sees that file content between the eventual destination commit (the squash) is the same as the tip of the branch (instead of rebasing one commit at a time).
Comment by skydhash 7 days ago
main <- PR A <- PR B
Then you'll have main, squashed A
\
\-> PR A -> PR B
The tip of B is the list of changes of both A and B, while the tip of main is now the squashed version of the changes of A. Unless a branch tracks the end of A in the PR B, It looks like more you want to apply A and B on top of A again.A quick analogy to math
main is X
A is 3
B is 5
Before you have X + 3 + 5 which was equivalent to X + 8, but then when you squash A on on X, it looks like (X + 3) + (3 + 5) from `main`'s point of view, while from B, it should be X + (3 + 5). So you need to rebase B to remove its 3 so that it can be (X + 3) + 5.Branches only store the commits at the top. The rest is found using the parent metadata in each commits (a linked list. Squashing A does not remove its commits. It creates a new one, and the tip of `main` as its parent and set the new commit as the tip of `main`. But the list of commits in B still refer to the old tip of `main` as their ancestor and still includes the old commits of A. Which is why you can't merge the PR because it would have applies the commits of A twice.
Comment by SkiFire13 6 days ago
Comment by jiveturkey 7 days ago
The github UI may change the target to main but your local working branch doesn't, and that's where you `rebase --onto` to fix it, before push to origin.
It's appropriate for github to automatically change the target branch, because you want the diff in the ui to be representative. IIRC gitlab does a much better job of this but this is already achievable.
What is actually useful with natively supported stacks is if you can land the entire stack together and only do 1 CI/actions run. I didn't read the announcement to see if it does that. You typically can't do that even if you merge PR B,C,D first because each merge would normally trigger CI.
EDIT: i see from another comment (apparently from a github person) that the feature does in fact let you land the entire stack and only needs 1 CI run. wunderbar!
Comment by SkiFire13 6 days ago
My "fix" is to do an interactive rebase of PR B on main and drop all of PR A's commits from PR B in the process.
I remember seeing a way to do this automatically, but it requires an option that I never remember. IMO this is kind of the issue with git: a lot of improved workflows sit behind some flags that most people never learn. Interactive rebases work for me because they are one primitive, always working in the same way.
Comment by pastel8739 7 days ago
All you need to do is pull main, then do an interactive rebase with the next branch in your stack with ‘git rebase -i main’, then drop all the commits that are from the branch you just merged.
Comment by claytonjy 6 days ago
Comment by adregan 7 days ago
Comment by Phlogistique 6 days ago
It does some merge magic so that PR B shows the correct diff; and does so without needing to force push, so on your side you can just "git pull" and continue working.
Of course I expect this repo to become obsolete when GitHub makes their native stacking public.
Comment by patrickthebold 7 days ago
That said, after the squash merge of A and git fetch origin, you want something like git rebase --update-refs --onto origin/main A C (or whatever the tip of the chain of branches is)
The --update-refs will make sure pr B is in the right spot. Of course, you need to (force) push the updated branches. AFAICT the gh command line tool makes this a bit smoother.
Comment by gregoryl 7 days ago
I don't see how there is any other way to achieve this cleanly, it's not a git thing, it's a logic thing right?
Comment by akersten 7 days ago
The update branch button works normally when I don't stack the PRs, so I don't know. It just feels like a half baked feature that GitHub automatically changes the PR target branch in this scenario but doesn't automatically do whatever it takes for a 'git merge origin/main' to work.
Comment by skydhash 7 days ago
Those are not hallucinated. PR B still contains all the old commits of A which means merging would apply them twice. The changes in PR B are computed according to the oldest commits belonging to PR B and main which is the parent of squashed A. That would essentially means applying A twice which is not good.
As for updating PR B, PR B doesn't know where PR A (that are also in PR B) ends because PR A is not in main. Squashed A is a new commit and its diff corresponds to the diff of a range of commits in PR B (the old commits of PR A), not the whole B. There's a lot of metadata you'd need to store to be able to update PR B.
Comment by akersten 7 days ago
I don't think we need to store any additional metadata to make the rebase just slightly more smarter and able to skip over the "obvious" commits in this way, but I'm also just a code monkey, so I'm sure there are Reasons.
Comment by skydhash 6 days ago
Git store all its information as a directed acyclic graph (a tree) of commits. The leaves of that tree have names, and are what we called branches. Each commit points to a tree (also a tree data structure) where the nodes are blobs (files) and sub trees. But that tree only stores the files that has been changed since the last commit. Git does not store diffs. Diffs are computed as needed.
This why the common ancestor commit is important. From there, a version of the working directory is computed for each branch (main-with-squashed-A and PR B). Files that have not been changed since PR A are ok, but everything else will be different, especially if you’ve modified the same lines.
Squashed A is a brand new commit with a new tree that PR B does not know about. You need to recompute PR B on top of Squashed A, (which will create new commits for PR B).
Comment by Smaug123 7 days ago
Comment by heldrida 7 days ago
Comment by mckn1ght 7 days ago
Comment by contravariant 7 days ago
I mean if you've got a feature set to merge into dev, and it suddenly merges into main after someone merged dev into main then that's very annoying.
Comment by bsimpson 7 days ago
I never understood the PR=branch model GitHub defaulted to. Stacked commits (ala Phabricator/Gerrit) always jived more with how my brain reasons about changes.
Glad to see this option. I guess I'll have to install their CLI thing now.
Comment by ezekg 7 days ago
Comment by ameliaquining 7 days ago
Comment by ezekg 7 days ago
Comment by NooneAtAll3 7 days ago
presenting only cli commands in announcement wasn't a good choice
Comment by sameenkarim 7 days ago
You can also run a combination of these. For ex, use another tool like jj to develop locally, push up the branches, and use the gh CLI to batch create a stack of n PRs, without touching local state.
Comment by ezekg 7 days ago
Comment by ZeWaka 7 days ago
Probably relies on some internal metadata.
Comment by SamuelAdams 7 days ago
Wait 10 minutes and you’re done.
Comment by sameenkarim 7 days ago
Everyone will have their own way of structuring stacks, but I've found it great for the agent to plan a stack structure that mirrors the work to be done.
Comment by bmitc 7 days ago
Comment by ezekg 6 days ago
Comment by bmitc 6 days ago
My point is that Git is just a component of the GitHub tool, and the GitHub CLI is quite good and helps automate many things in GitHub. For example, even just using `gh browse` and `gh pr create --web` and `gh pr view --web` are fantastic tools.
Comment by ezekg 6 days ago
Comment by bmitc 6 days ago
Comment by contravariant 7 days ago
I mean a branch is just jamming a flag into a commit with a polite note to move the flag along if you're working on it. You make a long trail, leave several flags and merge the whole thing back.
Of course leaving multiple waypoints only makes sense if merging the earlier parts makes any sense, and if the way you continue actually depends on the previous work.
If you can split it into several small changes made to a central branch it's a lot easier to merge things. Otherwise you risk making a new feature codependent on another even if there was no need to.
Comment by nathas 6 days ago
And it doesn't even rebase and merge correctly with fast-forward if there it's a clean set of commits! https://github.com/orgs/community/discussions/5524
Comment by charles_f 6 days ago
Comment by tele_ski 6 days ago
Comment by surajrmal 6 days ago
Comment by cleverdash 7 days ago
Curious whether this changes anything for the AI-assisted workflow. Right now I let Claude Code work on a feature branch and it naturally produces one big diff. Stacked PRs could be interesting if agents learned to split their own work into logical chunks.
Comment by jillesvangurp 7 days ago
To me, stacked PRs seems overly complicated. It seems to boil down to propagating git rebases through stacks of interdependent branches.
I'm fine with that as long as I don't have to deal with people force pushing changes and routinely rewriting upstream history. It's something you probably should do in your own private fork of a repository that you aren't sharing with anyone. Or if you are, you need to communicate clearly. But if the goal is to produce a stack of PRs that in the end merge cleanly, stacked PRs might be a good thing.
As soon as you have multiple collaborators working on a feature branch force pushing can become a problem and you need to impose some rules. Because otherwise you might end up breaking people's local branches and create work for them. The core issue here is that in many teams, people don't actually fork the main repository and have push access to the main repository. Which emulates the central repository model that people were used to twenty years ago. Having push access is not normal in most OSS projects. I've actually gotten the request from some rookie developers that apparently don't get forking to "please give me access to your repository" on some of my OSS projects.
A proper pull request (whether stacked or not) to an OSS project needs to be clean. If you want to work on some feature for weeks you of course need mechanisms to stay on top of up stream changes. OSS maintainers will probably reject anything that looks overly messy to merge. That's their job.
Comment by recursivegirth 6 days ago
I do the obvious checks like tests and spin up a dev instance to make sure the feature works like I want it too, but very rarely am I reviewing every line of code these days.
Comment by steveklabnik 7 days ago
Comment by 4b11b4 7 days ago
Comment by steveklabnik 7 days ago
Comment by ameliaquining 7 days ago
Comment by Arainach 7 days ago
* It amounts to doing N code reviews at once rather than a few small reviews which can be done individually
* Github doesn't have any good UI to move between commits or to look at multiple at once. I have to find them, open them in separate tabs, etc.
* Github's overall UX for reviewing changes, quickly seeing a list of all comments, etc. is just awful. Gerrit is miles ahead. Microsoft's internal tooling was better 16 years ago.
* The more commits you have to read through at once the harder it is to keep track of the state of things.
Comment by Hamuko 7 days ago
I truly do not comprehend this view. How is reviewing N commits different from/having to do less reviews reviewing N separate pull requests? It's the same constant.
Comment by Arainach 7 days ago
A chain of commits:
* Does not go out for review until the author has written all of them
* Cannot be submitted even in partial form until the reviewer has read all of them
Reviewing a chain of commits, as the reviewer I have to review them all. For 10 commits, this means setting aside an hour or whatever - something I will put off until there's a gap in my schedule.
For stacked commits, they can go out for review when each commit is ready. I can review a small CL very quick and will generally do so almost as soon as I get the notification. The author is immediately unblocked. Any feedback I have can be addressed immediately before the author keeps building on top of it.
Comment by tcoff91 7 days ago
Single PR with commits A, B, C: You must merge all commits or no commits. If you don't approve of all the commits, then none of the commits are approved.
3 stacked PRs: I approve PR A and B, and request changes on PR C. The developer of this stack is on vacation. We can incrementally deliver value by merging PRs A and B since those particular changes are blocking some other engineer's work, and we can wait until dev is back to fix PR C.
Comment by mike_hearn 6 days ago
This seems to be the root of the problem. Nothing stops a reviewer merging some commits of a PR, except a desire to avoid the git CLI tooling (or your IDE's support, or....). The central model used in a lot of companies requires the reviewee to do the final merge, but this has never been how git was meant to be used and it doesn't have to be used that way. The reviewer can also do merges. Merge (of whichever commits) = approval, in that model.
Comment by tcoff91 6 days ago
This feature helps improve GitHub so it's useful for companies that do this this way.
At our company, only admin users can actually directly git push to main/master. Everything else HAS to be merged via github and pass through the merge queue.
So this stacked PRs feature will be very helpful for us.
Comment by tcoff91 7 days ago
This isn't reddit people. You're not supposed to downvote just because you disagree. Downvotes are for people who are being assholes, spamming, etc...
If you disagree with a take, reply with a rebuttal. Don't just click downvote.
Comment by steveklabnik 7 days ago
That said, while he hasn't posted here for a long time, this is still in the guidelines:
> Please don't post comments saying that HN is turning into Reddit. It's a semi-noob illusion, as old as the hills.
Comment by tcoff91 7 days ago
Comment by adamwk 7 days ago
Comment by leleat 7 days ago
[1] https://andrewlock.net/working-with-stacked-branches-in-git-...
Comment by flyingcircus3 7 days ago
Until you can make it effortless, maintaining a substantial commit structure and constantly rebasing to add changes to the proper commit quickly turns into more effort than just waiting to the end and manually editing a monster diff into multiple sensible commits. But we take the challenge and tell ourselves we can do better if we're proactive.
Comment by adamwk 7 days ago
Comment by flyingcircus3 7 days ago
Stacked PRs in my experience has primarily been a request to merge in a particular order. If you're the only merger, as in GP's case, there's no need to request this of yourself.
Comment by skydhash 7 days ago
Comment by KptMarchewa 6 days ago
Comment by dbbk 7 days ago
Comment by WhyNotHugo 7 days ago
Just using git, you'd send a set of patches, which can be reviewed, tested and applied individually.
The PR workflow makes a patch series an undivisible set of changes, which must be reviewed, tested and applied in unison.
And stacked PRs tries to work around this issue, but the issue is how PRs are implemented in the first place.
What you really want is the ability to review individual commits/patches again, rather than work on entire bundles at once. Stacked PRs seems like a second layer of abstraction to work around issues with the first layer of abstractions.
Comment by pierrekin 7 days ago
Then the commits in the PR are not held to the standard of being acceptable to apply, and they are squashed together when the PR is merged.
This allows for a work flow in which up until the PR is merged the “history of developing the PR” is preserved but once it is merged, the entire PR is applied as one change to the main branch.
This workflow combined with stacked PRs allows developers to think in terms of the “smallest reviewable and applicable change” without needing to ensure that during development their intermediate states are safe to apply to main.
Comment by philwelch 6 days ago
“What if there’s feedback and you need to make changes after the code review?” Then I do the same thing I did before I posted the code review: make separate “fixup” commits and do an interactive rebase to squash them into my commits. (And yes, I do validate that the intermediate commits build cleanly.)
There’s nothing you get from stacked PR’s that you don’t also get from saying “please review my feature branch commit by commit”.
Comment by pierrekin 6 days ago
Some examples of compromises:
You can’t merge partially merge a large “review commit by commit” PR so you are forced to wait until it is all ready to merge.
Comment by philwelch 5 days ago
These are two different use cases. I thought we were talking about the one where a set of changes is more readable commit by commit but you still want to merge the whole set of changes, not the one where the change is too big to review and merge at once so you have to break it up into multiple reviews. The latter use case is more rare—frankly, it’s a bit of a red flag otherwise—and wasn’t difficult anyway.
Microsoft didn’t need to build anything because it was already built into Git. The only problem is, if people knew how to use Git, Microsoft couldn’t lock them into a proprietary version control platform.
Comment by WhyNotHugo 6 days ago
Comment by pierrekin 6 days ago
Comment by gorgoiler 7 days ago
The traditional tools (mailing-lists, git branches, Phabricator) represented each change as a difference between an old version of the code and the proposed new version. I believe Phabricator literally stored the diff. They were called “diffs” and you could make a new one by copying and pasting into a <textarea> before pressing save*.
The new fangled stuff (GitHub and its clones) recorded your change as being between branches A and B, showed you the difference on the fly, and let you modify branch B. After fifteen years of this we are now seeing the option for branch A to be something other than main, or at least for this to be a well supported workflow.
In traditional git land, having your change as a first class object — an email or printout or ph/D1234 with the patch included — was the default workflow!
*Or some other verb meaning save.
Comment by jiveturkey 7 days ago
Stacked PRs are not breaking up a set of commits into divisible units. Like you said, you can already do that yourself. They let you continue to work off of a PR as your new base. This lets you continue to iterate asynchronously to a review of the earlier PRs, and build on top of them.
You often, very often, need to stage your work into reviewer-consumable units. Those units are the stack.
Comment by cush 7 days ago
Comment by Gigachad 7 days ago
Comment by thamer 6 days ago
No, not necessarily.
I work on a large repo and new features often involve changes to 3 different services: 2 from the backend, and the frontend UI. Sending a single PR with changes to all 3 services is really not ideal: the total diff size in a feature I added recently was maybe 600+ lines, and the reviewers for frontend and backend changes are different people. The changes in the 2 backend services can be thought of as business logic on one side and interactions with external platforms on the other. The business logic can't work without integrating calls to external APIs, and the UI can't work without the business logic.
These days I open 3 separate PRs and the software only works once all 3 are merged and built. It would be great to have all of them as a single package that's still testable and reviewable as 3 distinct parts. The UI reviewer can check out the whole stacked PR and see it running locally with a functional backend, something that's not possible without a lot of manual work when we have 3 PRs.
Comment by pertymcpert 6 days ago
Each of these changes are dependent on the last. Without stacked PRs you have o only one PR and reviewing this is huge. Maybe thousands of lines of complex code. Worse, some reviewers only need to see some parts of it and not the rest.
Stacked diffs were a godsend and the LLVM community's number one complaint about moving to GitHub was losing this feature.
Comment by mh2266 7 days ago
Comment by robertwt7 7 days ago
Comment by acjohnson55 6 days ago
Comment by thcipriani 7 days ago
One part that seems like it's going to feel a little weird is how merging is set up[1].
That is, if I merge the bottom of the stack, it'll rebase the others in the stack, which will probably trigger a CI test run. So, if I have three patches in the stack, and I want to merge the bottom two, I'd merge one, wait for tests to run on the other, merge the second vs. merge just those two in one step (though, without having used it, can't be sure about how this'd work in practice—maybe there's some way to work around this with restacking?)
[0]: <https://docs.gitlab.com/cli/stack/>
[1]: <https://github.github.com/gh-stack/guides/stacked-prs/#mergi...>
Comment by sameenkarim 7 days ago
As we have it designed currently, you would have to wait for CI to pass on the bottom two and then you can merge the bottom two in one step. The top of the stack would then get rebased, which will likely trigger another CI run.
Thanks for the callout - we'll update those docs to make it clear multiple PRs can be merged at once.
Comment by fphilipe 7 days ago
My biggest gripe with GitHub when working with stacks – and something that's not clarified in these docs – is whether fast-forward merges are possible. Its "Merge with rebase" button always rewrites the commit. They do mention that the stack needs to be rebased in order to merge it. My workaround has been `git merge --ff-only top-branch-of-stack` to merge the entire stack locally into main (or anything in between actually) and then push. GitHub neatly recognizes that each PR in the stack is now in main and marks them all as merged. If there are subsequent PRs that weren't merged it updates the base branch.
Having said that, it's great to see GitHub getting a proper UI for this. It's also great that it understands the intent that branch B that goes on top of branch A is a stack and thus CI runs against. I just hope that it's not mandatory to use their CLI in order to create stacks. They do cover this briefly in the FAQ[3], but it might be necessary to use `gh stack init --adopt branch-a branch-b branch-c`. On the other hand, if that removes the need to manually create the N PRs for my stack, that's nice.
[1]: https://git-scm.com/docs/git-rebase#Documentation/git-rebase...
[2]: https://github.com/tummychow/git-absorb
[3]: https://github.github.com/gh-stack/faq/#will-this-work-with-...
Comment by pksunkara 7 days ago
Comment by fphilipe 7 days ago
git --config push.default=matching push --force-with-lease --force-if-includes
In other words, I force push all branches that have a matching upstream by changing my config on the fly.Comment by locknitpicker 7 days ago
...or you don't bother with all that and simply do:
- gh stack init
- gh stack push
- gh stack submit
Comment by fphilipe 7 days ago
The point is that I want to use Git, a tool and skill that is portable to other platforms.
Comment by locknitpicker 7 days ago
You want to use git.
Most people around you want to get things done.
Comment by soledades 6 days ago
Comment by KptMarchewa 6 days ago
Comment by philwelch 6 days ago
Also, my current workflow actually has hooks to block agents from creating or changing commits. I know at some point this will be a limit to scaling, but I think that will result in me spending more rather than less time in git.
Comment by boomlinde 7 days ago
This will help some since you can more easily split PRs into units that make sense to squash at the end, but it still seems like not doing this on a per-commit basis is a disadvantage compared to Gerrit. With Gerrit I can use all the built-in Git rebase/squash/fixup tools to manage the commit stack and push everything in one go. I don't think there's a nearly as convenient a way to work with stacked branches in Git.
Comment by dminik 7 days ago
I notice a lot of examples just vaguely mention "oh, you can have others review your previous changes while you continue working", but this one doesnt make sense to me. Often times, the first set of commits doesn't even make it to the end result. I'm working on a feature using lexical, and at this point I had to rewrite the damn thing 3 times. The time of other devs is quite valuable and I can't imagine wasting it by having them review something that doesn't even make it in.
Now, I have been in situations where I have some ready changes and I need to build something on top. But it's not something just making another branch on top + rebase once the original is merged wouldn't solve.
Is this really worth so much hype?
Comment by pierrekin 7 days ago
Imagine you have some task you are working on, and you wish to share your progress with people in bite sized chunks that they can review one at a time, but you also don’t want to wait for their reviews before you continue working on your task.
Using a stacked set of PRs you can continue producing new work, which depends on the work you’ve already completed, without waiting for the work you’ve already completed to be merged, and without putting all your work into one large PR.
Comment by Gigachad 7 days ago
Comment by steveklabnik 7 days ago
Comment by literallyroy 7 days ago
Comment by philwelch 6 days ago
Comment by pierrekin 6 days ago
Comment by mh2266 7 days ago
> The time of other devs is quite valuable and I can't imagine wasting it by having them review something that doesn't even make it in.
this is now what stacked diffs are for. stacked diffs doesn't mean putting up code that isn't ready. for example you are updating some library that needs an API migration, or compiler version that adds additional stricter errors. you need to touch hundreds of files around the repository to do this. rather than putting up one big diff (or PR) you stack up hundreds of them that are trivial to review on their own, they land immediately (mitigating the risk of merge conflicts as you keep going) then one final one that completes the migration.
Comment by heldrida 7 days ago
So, when I saw this announcement seemed interesting but don’t see the point of it yet.
Comment by metafeather 7 days ago
I hope the Gitub CLI will include syncing[3] 'stacks' locally with upstream in a similar way.
[1]: https://www.git-town.com/stacked-changes.html
[2]: https://github.com/marketplace/actions/git-town-github-actio...
Comment by herpdyderp 7 days ago
Comment by pastel8739 7 days ago
Comment by herpdyderp 7 days ago
Comment by sameenkarim 7 days ago
Also the rationale for having a chain of branches pointing to each other was so the diff in a PR shows just the relevant changes from the specific branch, not the entire set of changes going back to the parent/trunk.
Curious how you're thinking about it?
Comment by herpdyderp 6 days ago
That's exactly right.
> you can create stacked PRs purely via the UI
How?
I see from the docs https://github.github.com/gh-stack/introduction/overview:
> When a pull request is part of a stack
How does GitHub determine if a PR is part of a stack? Is it automatically detected so that I don't need to adjust my tooling that already creates chained PRs?
Comment by sameenkarim 6 days ago
Stacks require users to explicitly indicate that they are opening a PR that should be part of a stack.
Comment by herpdyderp 5 days ago
Comment by godzillafarts 7 days ago
Comment by sroussey 7 days ago
Comment by fweimer 7 days ago
There is already an option to enable review comments on individual commits (see the API endpoint here: https://docs.github.com/en/rest/guides/working-with-comments...). Self-stacking PRs seem redundant.
Comment by jannes 7 days ago
Graphite (which they seem to be inspired by) has frozen branches exactly for that use case:
Comment by CharlieDigital 7 days ago
Comment by AJRF 7 days ago
Comment by avita1 6 days ago
Comment by gugagore 7 days ago
Comment by Liskni_si 6 days ago
Assuming you're currently on the most recent branch (furthest from the trunk), `git rebase -i --update-refs trunk` will rebase all the intermediate branches. If you need to make a change to the first branch nearest the trunk, either use `edit` in the interactive rebase, or make a fixup commit and enable autosquash for the rebase. The `--update-refs` flag makes sure that all the intermediate branches get updated during the rebase.
Then, to push them all, something like `git push origin 'refs/heads/yourname/*'` will push all branches prefixed with `yourname/`. It's a bit stupid that one can't just do `git push 'yourname/*'` though.
Comment by hambes 7 days ago
Comment by scaryclam 7 days ago
Comment by dontlikeyoueith 6 days ago
Comment by ninkendo 6 days ago
Because the most natural way of saying “these changes need to land atomically” is called a branch, and landing it atomically is called a “merge”. But I guess GH’s UI sucks for reviewing large changes, so we’re stuck having to make each change independently mergeable and pass tests (likely disabling dead-code lints, etc) just to work around this limitation. Sigh.
At least when I actually do want changes to be mergeable in a stack, I now have a better UX for having folks review them.
Comment by hambes 5 days ago
Comment by poszlem 7 days ago
Comment by AJRF 6 days ago
> "Sometimes you just need to land changes all together".
Smell. What is that won't land? A UI change needed to go with a Database migration? That just tells me you don't flag your changes which you _should_ be doing. If you have code in your hot path that can break because of a DB or API change and you _don't_ flag that you have made your releases much more dangerous. Fix that before installing a new cli tool
If you can't do a database migration, you have put non optional new fields in somewhere, which doesn't accurately model your data domain - because it didn't exist before, it is tautologically optional.
> "How do I update a bunch of small PRs"
Smell. You should be merging small, flagged commits to main and rebase other branches. The workflow described is the same as feature branches - you start a branch, then branch from that, then branch from that - you shouldn't do that. That is not a git problem in the same way throwing every file on your desktop isn't an OS problem - you are just making a mess.
Comment by j3g6t 7 days ago
Comment by mattstir 6 days ago
Comment by ZeWaka 7 days ago
Comment by YesThatTom2 7 days ago
Comment by ZeWaka 7 days ago
As far as splitting work into different PRs that need coordinated merging, I've only ever encountered that when it's a long lived refactor / feature.
Comment by Hamuko 7 days ago
Comment by topaztee 7 days ago
Comment by nickcw 7 days ago
Comment by simplyluke 7 days ago
Comment by masklinn 7 days ago
They also allow reviewing commits individually, which is very frustrating to do without dedicated support (unless you devolve back to mailing list patch stacks).
Comment by dboreham 7 days ago
Comment by IshKebab 7 days ago
It's a big improvement (assuming they've done it right).
Comment by Macha 7 days ago
Comment by CharlieDigital 7 days ago
Comment by 4b11b4 7 days ago
Comment by stephbook 7 days ago
I'm not a huge fan, since stacked PRs mean the underlying issues don't get addressed (reviews clearly taking too long, too much content in there), but it seems they want something that works for their customers, right now, as they work in real life.
Comment by normie3000 7 days ago
I guess this is why you're getting downvoted. Commits can be edited.
Comment by steveklabnik 7 days ago
If I had to guess a reason they were downvoted (and I didn't downvote, to be clear), it's probably because people see stacked diffs as specifically solving "reviews clearly taking too long, too much content in there", and so it feels contradictory. Then again, as I said, I didn't downvote!
Comment by mortar 6 days ago
Comment by normie3000 6 days ago
Comment by quibono 7 days ago
Comment by eqvinox 7 days ago
> The gh stack CLI handles the local workflow […]
That's not "how it works", that's "how you['re supposed to] use it"… for "how it works" I would've expected something like "the git branches are named foo1 foo2 and foo3 and we recognize that lorem ipsum dolor sit amet…"
…which, if you click the overview link, it says "The CLI is not required to use Stacked PRs — the underlying git operations are standard. But it makes the workflow simpler, and you can create Stacked PRs from the CLI instead of the UI." … erm … how about actually explaining what the git ops are? A link, maybe? Is it just the PRs having common history?
…ffs…
(In case it's not obvious: I couldn't care less for using a GH specific CLI tool.)
Comment by rs545837 7 days ago
One thing I keep thinking about in this same direction: even within a single layer of a stack, line-level diffs are still noisy. You rename a function and update x call sites, the diff shows y changed lines. A reviewer has to mentally reconstruct "oh this is just a rename" from raw red/green text.
Semantic diffing (showing which functions, classes, methods were added/modified/deleted/moved) would pair really well with stacks. Each layer of the stack becomes even easier to review when the diff tells you "modified function X, added function Y" instead of just showing changed lines.
I've been researching something in this direction, https://ataraxy-labs.github.io/sem/. It does entity-level diffs, blame, and impact analysis. Would love to see forges like GitHub move in this direction natively. Stacked PRs solve the too much at once problem. Semantic diffs solve the "what actually changed" problem. Together they'd make code review dramatically better.
Comment by fmbb 7 days ago
OK, yeah, I’m with you.
> Stacked PRs solve this by breaking big changes into a chain of small, focused pull requests that build on each other — each one independently reviewable.
I don’t get this part. It seems like you are just wasting your own time building on top of unreviewed code in branches that have not been integrated in trunk. If your reviews are slow, fix that instead of running ahead faster than your team can actually work.
Comment by altano 7 days ago
Plus there's no review that's instant. Being able to continue working is always better.
Comment by fmbb 6 days ago
Stacking PRs are not a way to make changes smaller and therefore not making reviews easier.
Comment by inerte 7 days ago
Here's something that would be useful: To break down an already big PR into multiples that make up a stack. So people can create a stack and add layers, but somehow re-order them (including adding something new at the first position).
Comment by mattstir 6 days ago
Comment by tcoff91 7 days ago
I use jj to stack branches so i'll just be using the UI to do github pr stacks.
Comment by jrochkind1 7 days ago
Every time I try to do it manually, I wind up screwing everthing up.
Very interested ot check it out.
Comment by netheril96 7 days ago
----
OK, I found this from official docs, so this feature is now quite useless to me:
> Can stacks be created across forks?
> No, Stacked PRs currently require all branches to be in the same repository. Cross-fork stacks are not supported.
Comment by siva7 7 days ago
I'm old enough to have worked with SVN and young enough to have taught engineers to avoid stacking PR in Git. All wisdom has been lost and will probably be rediscovered in another time by another generation.
Comment by jollyllama 7 days ago
Comment by alkonaut 7 days ago
Usually when you develop a "full stack" thing you continuously massage the backend into place while developing frontend stuff. If you have 10 commits for frontend and 10 for backend, they might start with 5 for backend, then 5 commits to each branch to iron out the interface and communication, and finally 5 commits on the frontend. Let's call these commits B1 through B10 and F1 through F10. Initially I have a backend branch based on main wuth commits B1 through B5.
Then I have a frontend branch based on B5 with commits F1 through F5. But now I need to adjust the backend again and I make change B6. Now I need to rebase my frontend branch to sit on B6? And then I make F6 there (And so on)?
And wouldn't this separation normally be obvious e.g. by paths? If I have a regular non-stack PR with 20 commits and 50 changed files, then 25 files will be in /backend and 25 in /frontend.
Sure, the reviewers who only review /frontend/* might now see half the commits being empty of relevant changes. But is that so bad?
Comment by steveklabnik 7 days ago
In this model, you tend to want to amend, rather than add more commits. And so:
> they might start with 5 for backend, then 5 commits to each branch to iron out the interface and communication,
You don't add more commits here, you modify the commits in your stack instead.
> Now I need to rebase my frontend branch to sit on B6?
Yes, when you change something lower in the stack, the things on top need to be rebased. Because your forge understands that they're stacked, it can do this for you. And if there's conflicts, let you know that you need to resolve them, of course.
But in general, because you are amending the commits in the stack rather than adding to it, you don't need to move anything around.
> And wouldn't this separation normally be obvious e.g. by paths?
In the simplest case, sure. But for more complex work, that might not be the case. Furthermore, you said you have five commits for each; within those sets of five, this separation won't exist.
Comment by conor_f 7 days ago
Not for me, but I'm glad it fits other people's workflows. I just hope it doesn't encourage people to try make poorly reasoned changes!
Comment by gpm 7 days ago
I've just written those smaller PRs at once, or in quick enough succession that the previous PRs weren't merged before the later ones were ready. And the later ones relied on the previous ones because that's how working on a feature works.
The earlier PRs are absolutely reviewable and testable without relying on the later ones. The later ones are just treating the earlier ones as part of the codebase. I.e. everything here looks like two different PRs except the timing.
An obvious example would be "implement API for a feature" and then "implement UI that uses that API". Two different PRs. The second fundamentally relies on the first.
Comment by conor_f 6 days ago
1) API implementation - Including tests and docs this should be perfectly acceptable to merge and review independently 2) UX implementation - Feature flagged, dummy API responses, easy to merge + review 3) One quick "glue" PR where the feature can be integration tested etc
This prevents awful merge conflicts, multiple rounds of increasingly complex stacked reviews, and a host of other annoyances.
Is there any reason that the stacked PR workflow is better that I'm ignoring or overlooking?
Comment by gpm 6 days ago
Moreover you haven't even eliminated the dependency. The UI PR requires knowing that the dummy API responses you've created fit the right format - i.e. approval of the API PR up to small nits.
Just test against the actual implementation from the start. Even without stacked PRs just leave the second as a draft with both sets of commits until the first is merged then rebase and make it.
Stacked PRs are superior here because they eliminate that extra work of the draft PR and parallelize the review process slightly better.
Comment by scaryclam 7 days ago
It's not a simple problem to solve, we can't all just jump because someone finished some work after all. But if the PRs are OK to rubber stamp, and merge, and they're safely behind a feature flag, then it could just be as simple as letting the submitter merge without the need for an extra review. That can of course be contentious, but then we can ask "why not?" and figure out what non-human gateways need to be added to help make it possible etc.
I'm finding myself increasingly interested in understanding what friction can be removed from the software review, merge and release process, without sacrificing safe, well tested, understandable code that follows good standards.
Comment by ninkendo 7 days ago
I have never understood what this even means.
Either changes are orthogonal (and can be merged independently), or they’re not. If they are, they can each be their own PR. If they’re not, why do you want to review them independently?
If you reject change A and approve change B, nothing can merge, because B needs A to proceed. If you approve change A and reject change B, then the feature is only half done.
Is it just about people wanting to separate logical chunks of a change so they can avoid get distracted by other changes? Because that seems like something you can already do by just breaking a PR into commits and letting people look at one of those at a time.
I’ve tried my best to give stacked-diff proponents the benefit of the doubt but none of it actually makes sense to me.
Comment by steveklabnik 7 days ago
> If they’re not, why do you want to review them independently?
For this example, you may want review from both a backend engineer and a frontend engineer. That said, see this too though:
> that seems like something you can already do by just breaking a PR into commits and letting people look at one of those at a time.
If you do this in a PR, both get assigned to review the whole thing. Each person sees the code that they don't care about, because they're grouped together. Notifications go to all parties instead of the parties who care about each section. Both reviews can proceed independently in a stack, whereas they happen concurrently in a PR.
> If you approve change A and reject change B, then the feature is only half done.
It depends on what you mean by "the feature." Seen as one huge feature, then yes, it's true that it's not finished until both land. But seen as two separate but related features, it's fine to land the independent change before the dependent one: one feature is finished, but the other is not.
Comment by Phelinofist 7 days ago
Comment by steveklabnik 7 days ago
Comment by ninkendo 7 days ago
There are two separate issues you’re bringing up:
- Both groups being “assigned” the PR: fixable with code owners files. It’s more elegant than assigning diffs to people: groups of people have ownership over segments of the codebase and are responsible for approving changes to it. Solves the problem way better IMO.
- Both groups “seeing” all the changes: I already said GitHub lets you view single commits during PR review. That is already a solved problem.
And I didn’t even bring up the fact that you can just open a second PR for the frontend change that has the backend commit as the parent. Yes, the second PR is a superset of the first, but we’ve already established that (1) the second change isn’t orthogonal to the first one and can’t be merged independently anyway, and (2) reviewers can select only the commits that are in the frontend range. Generally you just mark the second PR as draft until the first one merges (or do what Gitlab does and mark it as “depends on” the first, which prevents it from merging until the first one is done.) The first PR being merged will instantly make the second PR’s diff collapse to just the unique changes once you rebase/merge in the latest main, too.
All of this is to explain how we can already do pretty much all of this. But in reality, it’s silly to have people review change B if change A hasn’t landed yet. A reviewer from A may completely throw the whole thing out and tell you to start over, or everything could otherwise go back to the drawing board. Making reviewers look at change B before this is done, is a potential for a huge waste of time. But then you may think reviewers from change B may opt to make the whole plan go back to the drawing board too, so what makes A so special? And the answer is it’s both a bad approach: just make the whole thing in one PR, and discuss it holistically. Code owners files are for assigning ownership, and breaking things into separate commits is to help people look at a subset of the changes. (Or just, like, have them click on the folder in the source tree they care about. This is not a problem that needs a whole new code review paradigm.)
Comment by steveklabnik 7 days ago
Code owners automatically assigns reviewers. You still end up in the state where many groups are assigned to the same PR, rather than having independent reviews.
> I already said GitHub lets you view single commits during PR review.
Yes, you can look at them, but your review is still in the context of the full PR.
> And I didn’t even bring up the fact that you can just open a second PR for the frontend change that has the backend commit as the parent.
The feature being discussed here is making this a first-class feature of the platform, much nicer to use. The second PR is "stacked" on top of the first.
Comment by ninkendo 7 days ago
> Yes, you can look at them, but your review is still in the context of the full PR.
Why is this a bad thing? I don’t get it. This has literally never been a problem once in my career. Is the issue that people can’t possibly scroll past another discussion? Or… I seriously am racking my brain trying to imagine why it’s a bad thing to have more than one stakeholder in a discussion.
I can think of a lot of reasons why doing the opposite, and siloing off discussions, leads to disaster. That is something I’ve encountered constantly in my career. We start out running an idea past group A, they iterate, then once we reach a consensus we bring the conclusion to group B and they have concerns. But oh, group A already agreed to this so you need to get on board. So group B feels railroaded. Then more meetings are called and we finally bring all the stakeholders together to discuss, and suddenly hey, group A and B both only had a partial view of the big picture, and why didn’t we all discuss this together in the first place? That’s happened more times in my career than I can count. The number of times group B is mad that they have to move their finger to scroll past what group A is talking about? Exactly zero.
Comment by steveklabnik 7 days ago
This isn't about siloing discussions: it's about focus. You can always see the full stack if you want to go look at the other parts, the key is that you don't have to.
The goal is to get thoroughly reviewed changes. It's much easier to review five 100 line changes than one 500 line one, and it's easier to review five 500 line changes than it is a 2500 line one. Keeping commits small and tightly reviewed leads to better outcomes in the end. Massive PRs lead to rubber stamps of +1.
I agree that that scenario sounds like a nightmare. But I don't think that a PR is the right place to solve that problem: it sounds like something that should have been sorted before any of the code was written in the first place.
Comment by ninkendo 7 days ago
This is true if the changes are orthogonal and are truly independent. One should always favor small independent changes if one can.
But when changes are all actually part of the same unit, and aren’t separable (apart from maybe the first of N of them which may be mergeable independently), proponents always seem to advocate that stacked diffs can somehow change this fact. “Oh if only we had stacked diffs we could break this into smaller changes”, ignoring the fact that no, they’d still be ordered and dependent on one another.
Stacked diffs seem like a UI convenience for reviewers… that’s fine I guess. GitHub is basically what you get when you ask the question “how can we make code review as tedious and unhelpful as possible”, and literally anything would be better than what we have (seriously I could fill a book with how bad GitHub is. I don’t think I could design a worse experience if I tried.) So, maybe I should just be happy they’re trying anything.
Comment by steveklabnik 7 days ago
This is the model that the kernel uses, as well as tons of other projects (any Gerrit user, for example), and so it has gotten real-world use and at scale. That said, everyone is also entitled to their preferences :)
Comment by ninkendo 7 days ago
Nah.
The kernel uses a mailing list, and a “review” means a mailing list thread. With some nice CLI tools to integrate with git when you want to actually apply the patch (or start a review thread.)
In that world, “[PATCH 2/5]” (or whatever) in the subject title, and a different CC list for each patch, is a nice way to be able to ensure different subsets of the patch series have different discussions. That’s great.
But if you’re going to compare this to a GitHub UI, you have to choose the basis for comparison, because the two are so utterly different. Choosing one aspect (can we make sure discussions are kept separate), and saying “therefore the kernel uses stacked diffs” is a huge misrepresentation of how different GitHub’s approach is.
Because the kernel approach is the platonic ideal of a code review: it’s a simple threaded discussion between stakeholders, centered around a topic (the patch, which is inlined right in the email.) I would wager close zero kernel maintainer actually look at the diffs exclusively via their email client. They probably just check out the changes locally and look at them, and the purpose of the mailing list is to facilitate focused discussion on parts of the change (which is all we really want, in the end.)
GitHub has so thoroughly shit the bed on actually developing a good model of “threaded discussion about a change”, that you have to change the way you think about git’s model to fix how awful GitHub is at allowing review discussion to stay focused. You shouldn’t need to think about stacked diffs and multiple PR’s. You should use git branches as intended, multiple commits representing changes, and a merge meaning “this branch makes it or not.” That GitHub’s UI for discussing subsets of a change is so abysmal, does not mean the model is wrong. It means their discussion system is so abysmal that a mailing list TUI can run circles around it. Fixing this is GitHub’s problem, and doesn’t require any changes to how PR’s should be split up.
If you have a 2500-line PR with 5 500-line commits, GitHub should not require you to split things up further in any way, just to unfuck their discussion system.
Random idea I spent 10 seconds thinking about: let me start a “here’s a thread discussing the UI changes” and add folks to it, and “here’s a thread discussing the backend changes”, and add folks to that. I can then say “let’s not merge this until both threads are green”. You still see the whole change in the UI. (You can click directories to drill into the changes, that solves the “but the diff is too big” issue.) Discussion on a chunk of the diff is scoped to a discussion thread, which you select when sending the message. Thus, all discussion on any part of the diff is still scoped to a “discussion thread” of arbitrary subsets of stakeholders.
None of this needs me to change how I split up my git branches, an entire logical change is still either “merged” or “not-merged” (seriously who cares about the Pyrrhic victory of merging only change 1/N), and if we want to limit scopes of discussion to subsets of a change, we can just… do that.
Comment by steveklabnik 7 days ago
All of the advantages, like "it’s a simple threaded discussion between stakeholders, centered around a topic", is exactly why people like stacked diffs over PRs.
GitHub is doing "stacked PRs", which is like stacked diffs but more like PRs in the sense that they're stacked branches rather than stacked diffs. I agree that this seems less ideal, but they also are putting it into an existing project, rather than rebuilding everything around it. There's pros and cons to both approaches, but I agree that I'd prefer a native system built for this, personally. I'm still glad they're going to be popularizing the general concept.
Comment by ninkendo 7 days ago
My point is that the LKML and what GitHub do is so different that the definition of “stacked diffs in general” can only describe a tiny aspect of each, if you want to call both of their approaches by the same name. From where I sit, the only common element between them is “they offer a way to keep discussion separated.”
If that’s all people are actually complaining about, there are a thousand better ways to “keep discussion separated” that don’t require me to pretend that it’s ok that only a subset of my branch is ok to merge.
In git, a branch is the thing you either merge or don’t. You merge multiple commits at once, or you don’t. It’s a great model. Breaking up the branch into smaller pieces, and giving people the impression it’s ok to merge the first commit but not the rest, just to unfuck the discussion UX, is putting the cart before the horse. I make a branch strictly because I want it to either all merge or none of it merge. It’s the only sensible approach in my book. If a discussion system is so bad that this is unworkable, it means the discussion system is bad, it doesn’t mean the conceptual model of a merge is bad.
Comment by steveklabnik 7 days ago
That's fine, what I mean is, when we started this convo, I thought you were asking about the general concept of stacked diffs, not the specifics of what GitHub is releasing here. That's my mistake for misunderstanding, sorry about that.
This is also (assumedly, anyway) why they're calling this "stacked PRs" and not "stacked diffs," because what they're doing is slightly different than Gerrit, Phabricator, Critique, etc.
Comment by ninkendo 7 days ago
After thinking about the whole thing I think I can summarize my opinion a lot better now:
Stacked diffs are a category error. Units of discussion, and units of integration, should not be conflated.
A branch is my unit of intended integration: merge all of it or none of it. The fact that reviewers need smaller slices to discuss does not imply those slices should become independently landable history objects. That’s a UX concern for the review tool, not something I should have to encode into Git history.
The ideal system would let me seed discussion however I want (by commit, by path, by subsystem, by semantic region of the diff, etc) without forcing me to pretend those are separate merge units.
Github nails the "merge unit" (CI runs against the whole branch, the branch either merges or doesn't, etc), but absolutely fumbles in the discussion part. I hate that I'd have to change the merge unit just to fix their discussion UX.
Comment by nerdypepper 7 days ago
for example, this stack adds a search bar: https://tangled.org/tangled.org/core/pulls/1287
- the first PR in the stack creates a search index.
- the second one adds a search API handler.
- the last few do the UI.
these are all related. you are right that you can do this by breaking a change into commits, and effectively that is what i do with jujutsu. when i submit my commits to the UI, they form a PR stack. the commits are individually reviewable and updatable in this stacking model.
gh's model is inherently different in that they want you to create a new branch for every new change, which can be quite a nuisance.
have written more about the model here: https://blog.tangled.org/stacking/
Comment by ninkendo 7 days ago
> - the second one adds a search API handler.
> - the last few do the UI.
So you're saying you're going to merge (and continuously integrate, perhaps to production) a dangling, unused search index, consuming resources with no code using it, just to make your review process easier?
It's very depressing that review UX is so abysmal that you have to merge features before they're done just to un-fuck it.
Why can't the change still be a big branch that is either all merged or not... and people can review it in chunks? Why do we require that the unit of integration equals the unit of review?
The perverse logic always goes something like this:
"This PR is too big, break it up into several"
Why?
"It's easier to review small, focused changes"
Why can't we do that in one PR?
"Because... well, you see GitHub's UI makes it really hard to ..."
And that ends up being the root-cause answer. I should be able to make a 10,000 line change in a single commit if I want, and reviewers should be able to view subsets of it however they want: A thread of discussion for the diffs within the `backend` folder. A thread of discussion for the diffs within the `frontend` folder, etc etc. Or at the very least I should be able to make a single branch with multiple commits based on topic (and under no obligation for any of them to even compile, let alone be merge-able) and it should feel natural to review each commit independently. None of this should require me to contort the change into allowing integration partially-completed work, just to allow the review UX to be manageable.
Comment by matharmin 7 days ago
Just covering the review process:
Yes, you can structure your PR into 3 commits to be reviewed separately. I occasionally structure my PRs like this - it does help in some cases. But if those separate parts are large, you really want more structure around it than just a commit.
For example, let's say you have parts A, B and C, with B depending on A, and C depending on B.
1. I may want to open a PR for A while still working on B. Someone may review A soon, in which case I can merge immediately. Or perhaps it will only be reviewed after I finished C, in which case I'll use a stacked PR. 2. The PR(s) may need follow up changes after initial review. By using stacked PRs instead of just separate commits, I can add more commits to the individual PRs. That makes it clear what parts those commits are relevant to, and makes it easy to re-review the individual parts with updated changes. Separate commits don't give you that.
Stacked PRs is not a workflow I'd use often, but there are cases where it's a valuable tool.
Then apart from the review process, there are lots of advantages to keeping changes small. Typically, the larger a change, the longer it lives in a separate branch. That gives more time for merge conflicts to build up. That gives more time for underlying assumptions to change. That makes it more difficult to keep a mental map of all the changes that will be merged.
There are also advantages to deploying small changes at a time, that I won't go into here. But the parent's process of potentially merging and deploying the search index first makes a lot of sense. The extra overhead of managing the index while it's "unused" for a couple of days is not going to hurt you. It allows early testing of the index maintenance in production, seeing the performance overhead and other effects. If there's an issue, it's easy to revert without affecting users.
The overall point is that as features become large, the entire lifecycle becomes easier to manage if you can split it into smaller parts. Sometimes the smaller parts may be user-visible, sometimes not. For features developed in a day or two, there's no need to split it further. But if it will span multiple weeks, in a project with many other developers working on, then splitting into smaller changes helps a lot.
Stacked PRs is not some magical solution here, but it is one tool that helps manage this.
Comment by ninkendo 6 days ago
Why? I reject the notion that large commits should be intrinsically hard to review.
GitHub already has the concept of "code owners", which are people who have ownership/review responsibility over slices of the codebase, based on globs/pattern matching. But they don't implement the other half of that, which is that a reviewer should be able to see a projection of a given PR, which matches the part of the repo they're the owner of.
There. That solves the entire problem of "this is too big, I can't look at all of it" (because your code ownership says this is the chunk of codebase you say you care about), and if that still isn't sufficient, there's a zillion UI features GitHub could add that they simply don't. Why can't I "focus" on a subset of the changes during review, in a way that helps me ignore unrelated discussions/changes? That is, even if I'm not code owner of the `frontend/` folder, why isn't there a UI affordance that says "let me focus on changes inside `frontend/` and ignore discussions/etc for the rest"?
> By using stacked PRs instead of just separate commits, I can add more commits to the individual PRs
Or you could just add commits to the PR, and if GitHub got the damned UI right, it would be natural to see the "slice" you care about, for all the new commits. Having to rearrange commits into separate PR's and slice-and-dice followup changes to file them into the right PR unit, is (to me) a workaround for how shitty GitHub's review UX is. It really shouldn't be this way.
> Then apart from the review process, there are lots of advantages to keeping changes small [...]
I agree with you on most of these points, but the decision to land smaller changes earlier should be made based on things like "let's get early feedback behind a feature flag" or "let's see how this chunk behaves in production so we can validate assumptions", or "let's merge what we have now so to cut back on conflicts", etc. That's all fine. But I'm vehemently opposed to being required to slice up my changes this way, just to work around a terrible review UI.
Personally, I review code in my development environment GitHub's UI is nonsensically terrible to read code. I could go on for hours about this[0], but when looking in my IDE I can drill into a subfolder and look at the diffs there. I can click and follow symbols. I can look at the individual diff history for any wildcarded subset of the repo, and see how the change was broken into commits. If I'm typing up some feedback to say "try doing it this way instead", I can actually try it myself first to make sure I'm not suggesting that someone do something that doesn't even compile.
And GH's discussion UX is by far the worst part of all of it. If you have a thread of discussions around a line of code, then wake up the next morning and want to see what new comments have been added? Good luck. Your best bet is to check your email inbox, because the comments are actually shown to you there. Using GitHub's "inbox" feature? All that is is a link to PR's you have to look at, with no hints at "why" (it could be a CI run finished for all you know.) Good luck figuring out "why" a PR is on your list. Did someone @-mention you? Who knows. So, find the blue dot next to the PR, click it, and then figure out for yourself what changed since the last time you looked. No, you can't just scroll and find it because GitHub hides half the discussions by default. So you have to go and expand all the collapsed sections to hopefully find that conversation you were having yesterday. But oh, you can only find it in the diff tab. So you click that, but the relevant file is collapsed by default ("Large diffs are not rendered blah blah"), so then click that. Then you may find that discussion.
Contrast this to a mailing list. The discussions are... discussion threads. You pick up where you left off. People's comments are right there in your inbox, newest one on top (or whatever your preference is.) You get notified when there's a new message, and when you tap the notification, it's the actual message, not some link to the PR that makes you click 6 more things to maybe find the message that just happened.
[0] like how the first thing you have to do when opening up the changes tab is ctrl+f search for "Large diffs are not rendered by default" to find the actually-important diffs that are not shown to you because GitHub's backend can't scale to rendering large diffs without friction. Countless times I've been burned by approving a PR because I don't see it making a change to some functionality, only to find out it actually did make said change, but GitHub just decided not to show me it. Seriously, the "large diffs" are the most important ones, and those are the ones you don't see without extra clicks. The mind boggles.)
Comment by whereistejas 7 days ago
PS: I love the concept of tangled. I currently use `sourcehut` but may soon move to tangled.
Comment by whereistejas 7 days ago
Comment by nerdypepper 6 days ago
Comment by charcircuit 7 days ago
The feature is also half done in this case. The author can fix up the concerns the reviewer had in A and then both can be merged at the same time.
Comment by ninkendo 7 days ago
Comment by charcircuit 6 days ago
As a counter example. Why use multiple PRs when you can always just merge them into a single one. It's possible to make huge PRs with a bunch of different changes all included, but then the GitHub tools with managing stuff don't really work that well and you have to just do everything as comments instead of being about to actually accept a single accepted change for example.
Comment by mh2266 7 days ago
you have hundreds or thousands of files to fix. that is unreviewable as a single commit, but as a per-file, per-library, per-oncall, etc. commit it is not that bad.
Comment by ninkendo 7 days ago
Why is it intrinsically unreviewable as a single commit? Why can't the discussion/review system allow scoping discussions to a single folder of the change, or a single library, or a particular code-owner's "slice" of the repo, etc? The answer to this question is always unsatisfactory to me. It always ends up being "because GitHub's UI makes it hard to <foo>" and it's just taken as an immutable law of the universe that we're stuck with that UI's limitations.
If a change is huge, find some basis by which to discuss it in smaller chunks. That basis doesn't have to be the PR itself (such that you have to make smaller PR's to make discussion manageable.) It can be a subdirectory of the diff. A wildcard-match over the source files. Whatever the case needs to be, the idea is still that the discussion UX shouldn't make reviewing large changes painful.
Why do we tolerate the fact that GitHub doesn't let you say "approved for changes in `frontend/*`" or "approved for the changes I'm a code-owner of", and have the PR check system mark the PR as approved once all slices have been approved? Why do we tolerate that a thousand-file change is "unreviewable"? Instead we have to change our unit of integration, allowing partially-complete work to be merged, just because the review UX sucks.
Comment by dontlikeyoueith 6 days ago
It's weird.
> Why do we tolerate the fact that GitHub doesn't let you say "approved for changes in `frontend/*`
That's literally what stacked PRs are adding.
Comment by ninkendo 6 days ago
Because it’s not functionally equivalent.
Stacked PR’S only facilitate easier reviews by forcing you to make the layers of the stack mergeable in chunks. It forces the “unit of review” to equal the “unit of integration”, which is completely unnecessary. It forces unfinished code into production to accommodate a broken review UX.
Maybe you want to land a subset of a change early, maybe you don’t, but wouldn’t it be nice to make that decision independently of worrying about the faults of your review tool?
Comment by esafak 7 days ago
Comment by fmbb 7 days ago
Why would you waste time faffing about building B on top of a fantasy version of A? Your time is probably better spent reviewing your colleague’s feature X so they can look at your A.
Comment by sajithdilshan 6 days ago
The only annoying part is that I have to keep on merging the base branch to the feature branch constantly to keep it up-to date. If Github can provide a feature to do that automatically, then that would be perfect. Other than that, I don't see any advantage on this stacked PR approach they are proposing.
Comment by sailorganymede 7 days ago
Comment by mhh__ 7 days ago
A PR is basically a cyberspatial concept saying "I, as a dog on the internet, am asking you to accept my patches" like a mailing list - this encourages trying to see the truth in the whole. A complete feature. More code in one go because you haven't pre-agreed the work.
Stacks are for the opposite social model. You have already agreed what you'll all be working on but you want to add a reviewer in a harmonious way. This gives you the option to make many small changes, and merge from the bottom
Comment by pbrowne011 7 days ago
Comment by whalesalad 7 days ago
Comment by ameliaquining 7 days ago
Comment by varun_ch 7 days ago
There’s a special case where certain official orgs can continue to use github.com instead of github.io for their Pages domain, and that’s how you end up with:
https://github.github.com/gh-stack/
from the code:
Should Pages owned by this user be regarded as “Official GitHub properties”?
def github_owned_pages? GitHub.github_owned_pages.include?(login) end
# Orgs/users that are owned by GitHub and should be allowed to use # `github.com` URLs. # # Returns an Array of String User/Organization logins. ...
Comment by ZeWaka 7 days ago
Comment by atq2119 7 days ago
There seems to be a native stack navigation widget on the PR page, which is certainly a welcome addition.
The most important question though is whether they finally fixed or are going to fix the issues that prevent submitting stacked PRs from forks. I don't see any indication about that on the linked page.
Comment by Liskni_si 6 days ago
Comment by boisterousness 6 days ago
Why are stacked commits useful? Multiple patches can be developed concurrently and efficiently, with each patch focused on a single concern, for a clean Git commit history and improved productivity. The tutorial [2] says:
> One common use of StGit is to “polish” a Git branch before publishing it to another public repository. The kinds of polish that StGit can help with include:
Complete and correct commit messages.
Each patch limited to one coherent topic.
Each patch standing on its own: passing tests, etc.
Considerate patch (commit) order
> Careful curation of Git commit history, as enabled by StGit, can be of high value to those reviewing pull requests or trying to understand why or how code came to be the way it is. ...> As a concrete example, consider a situation where several Git commits have been made in a repository with commit messages such as:
“Improve the snarfle cache”
“Remove debug printout”
“New snarfle cache test”
“Oops, spell function name correctly”
“Fix documentation error”
“More snarfle cache”
> While the above may be the “true” history of commits to the repository, it may not be the history that is most helpful to code reviewers or the developer who needs to understand what happened in this area of the code six months after the fact. Using StGit, this history can be revised to be higher quality and higher value.Originally written in Python (2005, pre-GitHub) by Catalin Marinas, the current version is in Rust. StGit is free and open source [3]. It was inspired by Quilt [4], an earlier system credited to Andrew Morton and Andreas Grünbacher.
[1] https://stacked-git.github.io/ [2] https://stacked-git.github.io/guides/tutorial/#development-b... [3] https://github.com/stacked-git/stgit/ [4] https://en.wikipedia.org/wiki/Quilt_(software)
Comment by zeafoamrun 7 days ago
Comment by K0IN 7 days ago
Comment by cadamsdotcom 7 days ago
Is it?
Comment by steveklabnik 7 days ago
Comment by zzyzxd 7 days ago
Sure, your application has a dependency on that database, but it doesn't necessarily mean you can't deploy the application before having a database. If possible, make it acceptable for your application to stay in a crashloop until your database is online.
Comment by devmor 7 days ago
Comment by sbinnee 7 days ago
Comment by chao- 7 days ago
Comment by jamietanna 7 days ago
If this works as smoothly as it sounds, that'll significantly reduce the overhead!
Comment by choi0330 6 days ago
All in all, pilegit works with Github, Gitlab, Phabricator, Gitea, and custom.
How about https://github.com/hokwangchoi/pilegit?
Comment by altano 7 days ago
Comment by steveklabnik 7 days ago
Comment by ChrisArchitect 7 days ago
> This is a docs site that was made to share the spec and CLI for private preview customers that ended up getting picked up. This will move to GitHub docs once it’s in public preview.
Comment by jwpapi 7 days ago
Honestly I don’t see the benefit of smaller prs, except driving vanity scores?
Like I’m not saying you should
Comment by samsin 6 days ago
Comment by dontlikeyoueith 6 days ago
I typically generate stacks of 3-5 PRs in 1-2 days now (in a gen-AI world).
Comment by meric_ 7 days ago
Only downside is that Phabricator is not open source so viewing it in most things sucks. Hoping now I can get a much better experience
Comment by mhh__ 7 days ago
Comment by meric_ 6 days ago
Seems that company shutdown though a while ago and it got forked into Phorge, but either way I assume there's some divergence from what's internal at this point.
Still I'll look into it, it does look neat and might suit my preferences still. Thanks for the headsup :)
Comment by jen20 7 days ago
Comment by zmmmmm 7 days ago
Comment by mattstir 6 days ago
So it tries to replay commits in the stack and will stop halfway through that individual stack (layer?) to let you fix it if there's a conflict.
Comment by enraged_camel 7 days ago
Comment by masklinn 7 days ago
Comment by teaearlgraycold 7 days ago
Comment by zaps 7 days ago
Comment by solaire_oa 7 days ago
Comment by throwatdem12311 7 days ago
I’ve been trying to convince my boss to buy Graphite for this, seems like Github is getting their a* in gear after Cursor bought them.
If Jetbrains ever implements support for them in IntelliJ I will be in Heaven.
Comment by mhh__ 7 days ago
2. I'm not a huge fan of having to use a secondary tool that isn't formally a layer around git / like jj as opposed to github
Comment by prakashn27 7 days ago
Comment by MASNeo 7 days ago
Comment by baq 7 days ago
Comment by sylware 6 days ago
Since I can still login, is there a web API (using CURL and some identifying session token I could retrieve from my login) I could use to actually do "something". For instance, be involved in the issues of some project?
(for me, that would be mostly valve stuff on linux based OSes)
Any pointers?
Comment by vedant_awasthi 6 days ago
Comment by DesiLurker 7 days ago
Comment by baalimago 7 days ago
Comment by lopsotronic 7 days ago
Comment by silverwind 7 days ago
Comment by steveklabnik 7 days ago
Comment by balamatom 6 days ago
Comment by ghighi7878 7 days ago
Comment by masklinn 7 days ago
Stacked PRs are a development method, for managing changes which are separate but dependent on one another (stacked).
The two are orthogonal they can be used together or independently (or not at all).
Comment by ghighi7878 7 days ago
Comment by IshKebab 7 days ago
I can't remember if Gitlab has the same limitations but I do remember trying to use Gitlab's stacked diffs and finding them to not work very well. Can't remember why tbh.
Comment by Pxtl 7 days ago
Comment by inetknght 7 days ago
Comment by whalesalad 7 days ago
Comment by ZeWaka 7 days ago
Comment by ameliaquining 7 days ago
Comment by inetknght 7 days ago
In practical terms: I manually write a list of PRs, and maintain that list in the description of each of the PRs. Massive duplication. But it clearly shows the merge train.
Comment by throwaway9980 7 days ago
Comment by dpcx 7 days ago
Comment by simplyluke 7 days ago
Comment by Arbortheus 7 days ago
Comment by Yokohiii 7 days ago
Comment by nonoesp 7 days ago
Comment by elAhmo 6 days ago
Comment by iknownthing 6 days ago
Comment by vedant_awasthi 6 days ago
Comment by srvaroa 7 days ago
Comment by ruined 7 days ago
Comment by scottfits 7 days ago
Comment by godzillafarts 7 days ago
Huh? Some stacks need to land all at once and need to be reviewed (and merged) from the top down. It’s not uncommon, in my org at least, to review an entire stack and merge 3 into 2 and then 2 into 1 and then 1 into main. If 2 merges before 3, you just rebase 3 onto 1.
Comment by latentdream 7 days ago
Comment by noident 7 days ago
Comment by landr0id 7 days ago
A stacked PR allows you to construct a sequence of PRs in a way that allows you to iterate on and merge the isolated commits, but blocks merging items higher in the stack until the foundational changes are merged.
Comment by noident 7 days ago
Comment by steveklabnik 7 days ago
What they do that the single branch cannot is things like "have a disjoint set of reviewers where some people only review some commits", and that property is exactly why it encourages more well-organized commits, because you are reviewing them individually, rather than as a massive whole.
They also encourage amending existing commits rather than throwing fixup commits onto the end of a branch, which makes the original commit better rather than splitting it into multiple that aren't semantically useful on their own.
Comment by a_e_k 7 days ago
(FWIW, I'm dealing with this sort of thing at work right now - working on a complex branch, rewriting history to keep it as a sequence of clean testable and reviewable commits, with a plan to split them out to individual PRs when I finish.)
Comment by steveklabnik 6 days ago
That's what this feature is, conceptually. In practice, it does seem slightly more cumbersome due to the fact that they're building it on top of the existing, branch-based PR system, but if you want to keep it to one commit, you can (and that's how I've been working with PRs for a while now regardless, honestly).
They confirmed in other comments here that you don't have to use the CLI, just like you don't have to use gh in general to make pull requests, it's just that they think the experience is nicer with it. This is largely a forge-side UI change.
Comment by dontlikeyoueith 6 days ago
So the point he's trying to make is that Gituhub UI should support Stacked PRs but call them something else because he doesn't like the name?
Comment by Hamuko 7 days ago
Comment by benatkin 7 days ago
Comment by ezekg 7 days ago
Comment by aunderscored 7 days ago
Comment by ezekg 7 days ago
Comment by jaredsohn 7 days ago
I've done this manually by building a big feature branch and asking an LLM to extract out functionality for a portion of it.
For the former, it would seem to split based on frontend/backend, etc. rather than what semantically makes the most sense and for the latter it would include changes I don't want and forget some I do want. But I haven't tried this a lot.
Comment by bombcar 7 days ago
Comment by steveklabnik 7 days ago
Comment by pertymcpert 7 days ago
Comment by jamesfisher 6 days ago
Comment by lpeancovschi 7 days ago
Comment by Liskni_si 6 days ago
∙ `git checkout -b feature-branch-xyz`
∙ make a few commits, perhaps some fixups, rebase, whatever
∙ start tig, look at the history, decide at which points I want to break the branch into stacked PRs, and mark those points using shift-s (which calls my own `git gh-stack branch create $commit` and creates a specially named branch there)
∙ `git gh-stack sync` — collects all the specially named branches, builds a graph of how they're stacked on one another, pushes them, opens stacked PRs
GitHub has had some "support" for stacked PRs for a while, so merging the first one to main will automatically change the target branch of the second to main.
If I need to change anything, I can just `git rebase --interactive --update-refs`, amend commits, split commits, rearrange commits, and then running `git gh-stack sync` will update the PRs for me. If I split a commit in the middle and shift-s to mark it, it will open an extra PR and restack everything to update the order.
Furthermore, the "PR stack" doesn't actually need to be a stack (linear chain), it can be a tree. If I know that some commits are independent of the rest, I don't need to create a separate stack, I just create another local branch, mark PR-ready commits with shift-s, and `git gh-stack sync` will do the right thing. If I need to rebase the whole tree on top of current main, then `git rebase -i --rebase-merges --update-refs` does the job.
I guess what I'm saying is that as someone who's been using git since its inception, it feels much more natural to just do everything in git, and then have a single command that pushes my work to GitHub. And I think this might work even better with jujutsu — just point `git gh-stack sync` at the branches jj makes and it'll make a stack/tree of PRs out of them. :-)
https://github.com/liskin/dotfiles/blob/home/bin/git-gh-stac... if anyone's curious. It's just a few hundred lines of code. Building the graph is done by `git log --simplify-by-decoration`. Opening PRs is shelled out to `gh pr create`.
¹) I mean, I'd much rather they added a UI for reviewing PRs commit-by-commit, with the option to approve/request-changes on each, and the possibility to merge the first few approved ones while continuing work on the rest… But in a world of almost every $dayjob insisting on squash-merging, a UI for stacked PRs is a total game changer, positively.
Comment by the_gipsy 7 days ago
Comment by sparin9 7 days ago
Comment by sameenkarim 7 days ago
Comment by dastbe 7 days ago
Comment by explodes 7 days ago
Also if someone could help me understand: Are these so-called stacked commits not possible with multiple commits on a single branch? I prefer to write my commits as atomic, independent, related changes, on a single branch, with both Git and Mercurial. I am apparently missing something: why can't a better UI simply show a multi-change PR?
Comment by sameenkarim 7 days ago
Comment by jlebar 7 days ago
In the tool I wrote, you have a single branch with linear history. PRs in the chain are demarcated via commit messages. You then don't need any special rebase / sync commands -- you can use regular `git rebase -i` to reorder commits or edit a commit in the middle of a stack. Literally the only special command I need is "push this branch to github as multiple PRs".
Anyway I hope that alongside the branch-based you've built tool in `gh` that there will be an API that I can target.
Comment by sameenkarim 7 days ago
Comment by calebio 7 days ago
Comment by sameenkarim 7 days ago
Comment by jiusanzhou 7 days ago
Comment by mc-serious 7 days ago
Comment by jollife 7 days ago
Comment by TZubiri 7 days ago
I think they have a culture of circumventing 'official' channels and whoever is in charge of a thing is whoever publishes the thing.
I think it's a great way to train users to get phished by github impostors, if tomorrow we see an official download from official.github.com or even official-downloads.github.io, sure it's phishy, but it's also something that github does.
It's also 100% the kind of issues that, if it happens, the user will be blamed.
I would recommend github to stop doing this stuff and have a centralized domain to publish official communications and downloads from. Github.github.com? Come on, get serious.
TL;DR: DO NOT DOWNLOAD ANYTHING from this site, (especially not npm/npx/pnpm/bun/npjndsa) stuff. It's a Github Pages site, just on a subdomain that looks official, theoretically it might be no different from an attacker to obtain access to dksabdkshab.github.com than github.github.com. Even if it is official, would you trust the intern or whoever managed to get a subdomain to not get supply chained? github.github.com just think about it.
Comment by varun_ch 7 days ago
Comment by TZubiri 7 days ago
The quoted microsoft examples are way worse. I see this with outbound email systems a lot, which is especially dangerous because email is a major surface of attack.
Comment by bob1029 7 days ago
Comment by Yokohiii 7 days ago