As someone who worked for several years with gerrit and then joined a company which uses GitHub, I want to fucking put a bullet in my head each time I review a PR. Someone who says ”ehm actually it’s not that bad” must have never used gerrit
As someone who’s used a normal PR flow and now has to use Gerrit, I cannot stand it. I like having feature branches with small commits as I work on a feature. Gerrit does not let me do this as each commit has to have its own change id.
Honestly I agree, I feel like I've entered some crazy land with so many people here praising Gerrit when most of the people I work with absolutely loathe that most of our repos are hosted using it. The UI is absolute ass, it enforces a workflow that doesn't generalize well to all teams/projects/dev workstyles because it's extremely opinionated, our team specifically has had tons of problems with seemingly basic functionality just straight-up not working (probably about 1/3 of the time we clone a repo and get it set up for reviews, it manages to botch the commit hooks for creating change IDs so it creates the ID but actually it's invalid and the server rejects it and we have to go pull the proper commit hook from somewhere else where it's correct), its permission/access control model is super clunky, and the list goes on. I can probably count on one hand the things I like about it over other solutions (changelist descriptions just being the commit message is a big one, and I guess its search/query system is pretty powerful).
It's probably great if it matches your preferred workflow and you get it set up exactly the way you want/need, but it is not a general-purpose git-based code forge. As garbage as Github is, it thrives because it is general-purpose and it can be adapted to many workflows. Gitlab and Bitbucket are similar, finding themselves as mainstays in the corporate world because they can fit most workflows without huge amounts of effort.
I was confused by this as well - I suppose I didn't work with Gerrit for long but it was a nightmare IMO.
Though one funny story... I had just had it with Gerrit at one point - I was in some management training with some devs I didn't work with often. For some reason I got going on a rant about how much I hate Gerrit...
It completely slipped my mind that we also worked with someone named Gerrit who was extremely nice. My rant was vague enough that the person I was talking to didn't realize I was talking about software, not the person. (I hate Gerrit, Gerrit ruins my day regularly, that sort of thing). Had to do a bit of damage control on that one.
Every time project requires to do anything with Gerrit I stop trying to contribute anything. I'm sure it's nice when you set it all up but for someone just wanting to fix a bug I found it's some kind of nightmare.
Each ~~PR~~ commit should represent a reviewable unit of code ~~while~~ and commits should represent a complete (as in not partial modification or code)
IMO "stacked commits" should just be the same thing as PR with a clean chain of commits. Just add a setting that runs CI against every commit in the PR and BOOM, you got stacked commits
So is the reviewer now tracking for themselves where the “units of work” are? If a second reviewer comes in after the first change has happened how do they know they’re to review the first two commits as a single unit?
I haven't used github PRs extensively but on azure devops the PR review UI doesn't let you review each commit of a change request. You can view the changes of a commit but no way of leave feedback.
For devs not used to using git rebase, having separate PRs also makes it easier to apply any changes to one of the PRs.
If you squash PRs, it also maintains history better.
Within my team at Microsoft, I'm currently building a tool to help manage stacked PRs, similar to this new github tooling. I think this would be even more useful for azure devops for the reason you mentioned. Do you think this is something you would find useful? how do you currently manage PR stacks and/or bloated PRs?
Having something built into azure devops would definitely help.
My current workflow when using stacked PRs is:
open first PR with its target set to the main branch
open second PR with its target set to the first PR
wait for reviewers to review each PR
address feedback in each PR
push the changes to each PR
once approved, complete first PR
merge main branch into second PR
change target branch of second PR to the main branch
manually delete branch of first PR
once approved, complete second PR
I would say the main pain points are with juggling the different branches of all the stacked PRs. If you need to address some feedback on one of the bottom PRs, you have to manually update the whole stack. Stacked PRs are analogous to commits on a branch, so maybe we need an equivalent of "git rebase --updated-refs" when managing stacked PRs.
The other pain is closing stacked PRs. You need to close them individually rather than having something automatic. In our case, we only validate code that's targeted to the main branch so for each PR in the stack we need to make sure to run the pipelines and then we get to merge them.
Let's say you're working on a large complex change. There's likely a bunch of easy non controversial prerequisite stuff that you can do up front to help prepare for the actual core of your change.
With stacked PRs, you'd put that early work up in their own PRs. Then immediately start building the more complex changes on top in a PR stack.
This way as your working on the later stuff, you can start landing the earlier stuff. You get to pick all three of:
Don't need to block reviews on the early PRs merging
Say you have a stack of branches, then you have to make a change in the lowest one, or rebase on main. It's a pain in the ass to then propagate that change through the whole stack. This new feature allows you to do that pretty seamlessly.
Right, stacked branches handled part of this before, but stacked PRs are what let you review and land the boring prerequisite work while the harder piece is still in flight.
That really has nothing to do with github. It is git itself that makes it hard to work with stacked branches (especially if you squash commits when merging).
Why does squashing commits when merging complicate the process? I was looking at the documentation, and thought squashing each stacked branches commits would look cleanest?
Because if you create branch B from branch A and then squash/merge branch A all the commits common to branch A and B (i.e. commits made to A before creating branch B) will conflict if you just try to merge in B.
To fix you can use the the newer git rebase --update-refs. There is an older method using the --onto flag of rebase as well.
git rebase —update-refs is not so helpful for when you’re collaborating with others still. I find it would be a nice git feature if squashed commits could keep their place and you don’t need to rebase a stacked PR at all. Not sure if this is somehow feasible in the git model.
One of the issues is that FAR too many developers lack anything beyond the most basic understanding of what Git actually does under the hood. They learn the bare minimum commands they need to use it day to day and then defer to whoever their nearest expert is if something goes wrong. I've seen this at every level from associate up to staff, people that have simply never taken the time to learn what git actually is.
This means that every repository I have ever worked on at every company essentially becomes this vast mix of merge requests, rebases and cross branch merges.
_jj is really worth a try, it makes this so much easier.
You can assign a bookmark (branch) to each commit (revision) in a series, then open a PR for each bookmark. Whenever you modify an old commit, all future bookmarks become dirty of course. Doing jj git push updates all the branches at once, so all your PRs get the latest changes.
Of course there’s nothing you couldn’t do with git, but it’s much nicer to have an easy way to fix up a single commit in history and then update everything that’s dependent at once.
Github's commit list is an unstructured linear list of space-consuming panels. That makes anything but squashing/rebasing into a linear history awkward when compared to, e.g. gitk's visualization that shows the actual DAG structure. Given stacked PRs necessarily interact with the underlying graph relations, it's always going to be more awkward when viewed through an inferior UI, and doubly so if your choice for merging constantly rewrites parts of the graph.
It has everything to do with Github - they have very obviously built an interface that matches their internal workflow with fairly small allowances to other workflows.
It is especially obvious in how the entire UI is built around pull requests, whereas the fundamental unit in git is the commit. So for example, you can still, a billion years after github was created, not comment on commit message content.
Why wouldn't I just do a single PR with multiple commits? Reviewers can review each commit one at a time and then I can rebase and merge when I'm done.
It’s a UI difference. GitHub has no way to really review individual commits and follow how they change. If you force push, the UI gives zero indication that the first commit abc123 turned into def456 and second commit a1b2c3 turned into d4e5f6.
Also there’s no way to review individual commit messages, but there is a way to review/edit PR descriptions. Sometimes that’s as important as the content.
Github absolutely does have a way to review individual commits and view their descriptions. This PR only has one commit, but you get the idea. You can select multiple commits or just one, and it will show you the commit description and only the changes from that commit.
https://i.imgur.com/wfMkW1i.png
Force pushing does mess things up, but you shouldn't be rewriting history after you've opened a PR.
Comments on specific commits will not show up in PR view so thaths the biggest issue, reviewing commits on github does not really work, also it requires developer to create proper, independent commits
"when you have selected to view the specific commit" so they are not shown in PR view :D Thats the problem, PR author will not even notice them unless tagged
Edit: After hours of people arguing with me, someone finally pointed out the actual reason why this has an advantage over plain commits: The PRs don't have to all be merged at the same time.
..but you can do that with multiple commits in single PR.
Problem is just people don't know Git very well...
You can certainly do that with git, but you probably can't do it at work and certainly not via GitHub UI. Pretty sure to do what you're suggesting, I'd need permission to commit to main branch. We only have permission to modify main in anyway via an approved PR.
I guess I'm the only one here who habitually rebases my commits into nice individually reviewable commits before creating a PR.
If I were to use this stacked PR feature, I would be taking my existing workflow and then creating separate PRs for each of my commits. And then, as it says on the linked page "merge them all in one click".
So I don't really see a difference between the two approaches, except that the PR stack seems more difficult to set up.
You’re approach is the proper way to use git. The whole point of being able to do interactive rebases is so people can restructure commits into a set of logical, manageable changes that should be individually compileable and reviewable.
Unfortunately most people don’t know or are too lazy to learn how to properly use git so you get a lot of people who throw up PRs with a million “fixed bugs” type commits, and since it’s so common these people think that’s the ‘right’ way to do it at and therefore can’t fathom the idea of reviewing PRs per commit or taking the time to organize their own commits into something meaningful instead of an unreviewable pile of garbage.
I work with devs that create the PR branch when they start and then push as they go. It's like a stream of consciousness. I've tried to counsel them to use a WIP branch without creating a PR, and to rebase and clean up where it makes sense... but getting people to change their behavior is tough it seems.
OTOH, that approach works well when you’re amending meaningful commits because the PR tracks the previous versions of the branch as you push changes that rewrite history.
I'd rather not waste time making history look clean when the only thing that really matters is the final code. And if I review a PR and they rewrite the history, I now have to re-review every single file because GitHub loses track of which files have already been reviewed since the history has been rewritten
Bro, it takes like 3 seconds to run git rebase -i to fixup your commits to a clean history.
If you don't care to split things up, then you don't need to git rebase or create a PR stack. You can leave your history messy, create a single PR, and then just squash merge when you're ready.
If you do want to split things up to make things easier for the reviewer, then its easier to git rebase -i than it is to create a pr stack.
Say you have these commits:
1. Some Thing
2. garbage
3. asdf
4. Some Other Thing
5. asdf
Then you would git rebase -i them in seconds to make:
1. Some Thing
2. Some Other Thing
Or you could try to figure out how to make a PR stack out of those same commits, assigning 1-3 to the first pr and 4-5 to the second pr. And looking at the docs, creating the stack is way more typing and difficult than a simple interactive rebase.
You clean up the garbage before opening a PR. Any new commits after review as started are cleaned up in the same way before pushing them to the branch. History is never edited from the PRs point of view.
Use fixup commits (git commit --fixup ) for any changes created after review and once all approved, git rebase --autosquash --interactive to automatically order the fixup commits to right place. Easy and you don't lose history while the PR is being reviewed. Rebase only when finished before merging.
then creating separate PRs for each of my commits.
Why each? What's wrong with having multiple commits in a PR if they're related? In particular, you can review commit by commit. Instead of a massive list of changed files. So implementation, tests, docs etc. It's merged as one commit still.
I tried stacked PRs and then I realized my pipeline is 4 hours so I can 4 PRs and wait 16 hours. So I decided to do what you're doing, just rewrite the commit history when it's ready for review
I've done my best to fight the good fight but I think people worry rebase is a bit of a footgun and don't want to put in the time to learn reflog. It reminds me of those dev teams that "don't need rollbacks because we just roll forward." >.<
I just cannot see any difference whatsoever in UX for the reviewer between a PR stack like they show in their example and a PR where each of those PRs in the stack is an individual commit.
In either case, the reviewer reviews each commit/PR individually, and then when the stack of commits/PRs is approved, the entire thing is merged with one click.
It means that you could have different people review each piece of the stack. (I'm assuming each item is a new PR, so nothing needs to change there).
If you get blocked on PR #4, you could still merge PRs #1-3. You're also merging in smaller commits at a time and could mean you're deploying smaller changes at a time. If your commit breaks, it's less code to read through and debug.
The difference comes when you’re addressing PR feedback. Let’s say you had commits A, B, C and I had feedback on each of them. Ideally for me as a reviewer when you make charges it looks like A, D, B, E, C, F and I can look at either D (changes since my last review), or AD the new “unit”
Did you also rewrite history so that D is between A & B? Because if you didn’t I don’t think github would let me see just A & D when B and C are in between them.
Yeah, I will commit when a PR is in a compilable state but not necessarily bug-free, and especially so at the end of the day just in case my local machine kicks the bucket before I get back to it.
When reviewing, I also don't want them rewriting history just to have nice looking commits. That completely breaks GitHub's review system. Every time they force push a rewrite of commits, it no longer tells you which files you've already reviewed
While I definitely would prefer it to not have to squash every PR on merge since you can lose some context for individual changes, that's much better than fucking up the review workflow entirely. I definitely prefer multiple PRs over a single PR with a nice commit history, because that nice history doesn't come for free. You just push the annoyance to every reviewer
One advantage to stacked PRs (outside of not having to constantly rebase) is that you can reduce risk by deploying smaller changes if you deploy on every merge to main.
My understanding of stacked PRs is that the entire stack is merged at once because the docs say "merge them all in one click".
If the entire stack must be merged at once, then it seems like a pointless feature when you can just use commits. If you can merge them one at a time, then it does seem like an improvement/automated way of creating a series of PRs like (main <- A), (A <-B), etc
Thanks, I think this was a key piece of information I was missing. I can now see a possible use case for creating a PR stack, though I still think plain commits will do just fine most of the time.
If they should be merged separately, they are PRs. If they're merged together, they are commits. The PR stack merges as a single unit, so to me makes more sense as commits.
Why wouldn't I just do a single PR with multiple commits?
IME, due to cross-depedencies between PRs.
I need this new piece of code from Henry to build upon, but it has a corner-case where it fails. So while Henry has a PR in which he puts the finishing touches to his code, if I stack my PR on his, I can still build on top of his code and have my own independent review cycle.
Totally agreed. Honestly feels like devs want to see "changed the inputs on handleFoo" as its own commit with its own tests etc. I think I would blow my brains out. I'm way faster at the comprehensive PRs since I can gauge it in one shot.
I'm way faster at the comprehensive PRs since I can gauge it in one shot.
In my experience large PRs get nowhere near as much scrutiny. People’s eyes glaze over and the PR gets waved in when there could be shitloads of issues.
Reviewing commits is the correct approach. Are you seriously suggesting to look at a final diff only? If there are orthogonal changes (large refactoring + 3-line bug fix) you either miss the important part in a sea of unimportant changes or burn yourself out going through each change carefully. If the intermediate commits are partial ramblings then i reject the review asking commits to be reordered/partially squashed to each individual commit makes sense. That's what ends up when merged and it should have quality. Full-squashing PR-s on merge is another retardation i always ban in my projects.
Most PRs I see are just a single commit, or a bunch of commits that aren't intended to be viewed individually and will be squashed together when merging.
However, for larger changes, it is common for PR authors to take a little bit of time to organize the commits before creating the PR to make things easier for the author to see whats going on.
A really common pattern for me is refactoring something in one commit, and then mass updating all usages of it in a second commit. That would be hell to review as a single commit (trying to find the real changes in a sea of automated ones), but looking at the commits individually its very clear what happened.
Before GitHub really took off, this used to be the standard recommended practice. Facebook in particular I feel played a large part in the squash + rebase ideology, but I'm also going off vibes and not facts so don't quote me on that.
You can't review individual commits in a PR -- you can only get feedback on them, but they can't be approved and merged individually. That is what a PR is -- it's a single code review.
There are people who work fast and have good coding hygiene. So they are able to push up clean, fully tested changes 5-10 times a day while working on a larger feature. This stuff can get reviewed and merged even as they are working on their next PR.
It's nice if you need to do something like an API change and want to get that approved and merged quickly because it's heavily used and you don't want to keep getting clobbered by other changes.
The issue with this is when using squashed merges on each PR. Git loses the commit identity in this process so you end up with merge conflicts trying to catch branches up.
Only squash fixup/wip commits in each feature/PR branch, but keep conceptually different changes in separate commits even if they are in the same overarching PR.
Because rebasing can introduce bugs, and makes it very difficult to identify their source.
Suppose the main branch has some utility_func, which is used by your feature development. A refactor/cleanup lands on main, which updates the behavior of utility_func and updates all callsites of utility_func to match. However, the additional calls to utility_func introduced on your feature branch haven't been updated. If you rebase your feature branch onto the main branch, your feature branch is now broken, as it relied on the old semantics of utility_func. If you're lucky, this will be a compile-time error, but there's no guarantee of that. Reverting to an earlier commit on your feature branch doesn't help, because those earlier commits have also been rebased. Unless you check through the git reflog (and pray that git's garbage collection hasn't occurred since then), it will look as though the feature branch had always been buggy.
A better solution is to merge from main into develop/main. That way, the error is clearly introduced in the merge commit. You can revert to an earlier commit in the feature branch for testing, because those versions are still part of the git history.
That's not how merge or rebase work. Both would trigger conflicts if there are any. But if you have just added a use of a function, that won't be a conflict because nobody else is changing that call site. Merge won't save you there.
But yes, if you need your old branch back, you'll need to use reflog, but reflog should be fine for a month, and if you are still wary of rebase, just create a backup branch.
The two changes conflict, just not in a way that git can recognize\
Merge won't save you there.
I agree, using a merge won't cause git to recognize a semantic conflict between branches, as it can only recognize text conflicts. However, merge also won't make the problem worse. Rebase hides the evidence that you ever had a working version.
if you are still wary of rebase, just create a backup branch.
The problem isn't that the rebase requires a backup branch, but that it isn't obvious that you'll need a backup until after the point when you lost it. Rebase destroys your history.
Why do you need evidence that you ever had a working version?
I work with compiled code it is immediately clear what the issue is. Rebase lets me fix the issue and there's no evidence that there ever was a problem.
I'm struggling to understand why a merge commit would fail in that case, while a rebase would succeed.
I do get your point being able to track which merge commit introduced the behavior, that is pretty smart and (as a career rebaser) I have been in that exact scenario before and been totally lost as to what broke what
It's not that the merge commit will resolve the semantic conflict between the branches on its own. That still needs to be sorted out, regardless of whether you use rebase or merge. The key is that using git merge won't make the conflict become any worse. When using git rebase, you need to sort out the semantic conflict while also having scrubbed the history of any clue as to the source of the bug.
merge commit would work but pollute main branch. squash merge/rebase are great but create conflict when "stacking" branches or trying to catch up. Native stacking fixes all of these to my understanding.
I'd argue that this isn't "pollution" at all, and is just a display issue.
Want a clean linear history of the changes to the main branch? Use git log --first-parent
Want a branching history with clearly marked dependencies? Use git log --graph
Want a linearized history that interleaves work on multiple branches while sorting by date? No, nobody ever wants that, but that's what git log provides.
The whole goal of squash/rebase is to make the output of git log look like git log --first-parent. The easier way to achieve this is just to use git log --first-parent.
I don't really see the issue and how this is directly related to stacked PRs. Rebase feature branches on main if you have conflicts. Squash your own commits how it makes sense conceptually. If your team enforces squashes on merge, then send each commit that you want separate as a separate PR. Merge feature branches into main when ready, or force rebase and fast forward to avoid merge commits.
I wish, wish, wish that github had a better display of the history of merges. The source of all the pain from squashed merges is from people trying to solve a display issue by introducing data integrity pitfalls. In any other context, this would be a complete non-starter, but somehow "clean linear git history" overrules that.
My personal theory is this is caused by github's use of git log instead of git log --first-parent when showing changes. If all merges are done with git merge --no-ff, then the main branch would only ever contain merge commits. The clean history that proponents of squash/rebase want would be available, and without introducing conflicts in the process.
If your commits aren’t each an atomic piece of work they also cost time if you need to revert just one feature.
If your commits aren’t each an atomic piece of work they also cost time if you need to revert just one feature.
I interpreted this to mean “If a merged feature is made up of several commits, then reverting that feature is more time consuming than reverting a squashed commit would be”.
Plus, what kicked this whole thing off was the comment:
The issue with this is when using squashed merges on each PR.
So I don't think it's unreasonable of me to be talking about squashed merges.
The benefit of squashing is getting rid of all the "fixed", "wip", etc commits that a lot of devs leave. If a PR/branch has commits with messages that all make sense, then there's no need to use squashing.
Except my first job, every place I have tried to push for this I have been met with resistance. People just CBA to work with PRs on a commit level and would rather squash and forget.
My first job made me understand how valuable having independent, working and split commits can be when debugging. I fear I'll never work in a codebase that can fullfill this again.
You can do that just by branching from whichever commit was used for PR A. This is just extra UI fluff on top of (what should be) a commonly-known Git branching pattern.
Yes, but then PR B contains all the commits from PR A, so there's no way to review all commits from just branch B at once. You can review commit by commit, but that requires knowing which commit belongs to which and there's still no way to see a squash diff.
I do agree that this is simply a Github UI fix and there's no new fundamental branching model being introduced here.. But UI is very important. My org and I are very excited for this change.
Read my comment again. B is based on A, yes, but is being merged to A, so the diff is only what was added in B. Each PR is reviewed independently, so even if you have a series like D->C->B->A->main, each PR is only the diff between the current branch and the parent. Since all changes are related, all PRs are reviewed independently. Only once ALL PRs are approved are they merged from right-to-left.
This is almost certainly what GitHub is doing under the hub with their PR stacks.
Ah I see! That works if you're pushing to the same repo that you're opening a PR against, but not if you're using a fork. That won't be possible for most (in my experience) cases. Even when I do have push access to a repo, I prefer opening PRs from my fork to keep the branch pollution down.
Also the UX of setting the bases for each PR is quite bad. Afaik, it's not possible to do from the cli (but maybe I'm wrong).
But you I do take your point that it's technically possible in some cases. Though, not in a way that I would consider usable.
Yes. It's different because you're not buddy-fucking a hundred of your coworkers when they find out that the files they've been writing a PR against have been deleted when your months-long epic got merged after its months-long code review.
They're orthogonal concerns. Let's agree that reviewing large PR's is sub-optimal. The what you want to do is code PR 1,2,3,4. Then you can have your team approve them as they get them and merge them to a central branch branch as they're approved with minimal toil.
Now that central branch could be master or it could be feature-foobar. The same concern applies.
Now... some say "That's ok I'll just skip review on feature-foobar and do review when it merges to master". However that breaks our invariant above about not relying on large PR reviews.
Finally... you're right that feature branches directionally simplify this b/c you're less likely to have merge conflicts on a feature branch (OTOH making a rule that only 1 dev working serially on 1 feature branch is limiting).
Here's some AI assisted options to make you feel better.
Concise:
Large PRs are hard to review — break work into small PRs (1, 2, 3, 4) regardless of whether they target master or a feature branch. Skipping review on a feature branch just defers the large-review problem. Feature branches reduce merge conflicts but can bottleneck if limited to one developer.
Concise for a 5 yo:
Imagine you have a big pile of LEGOs to check. Checking the whole pile at once is really hard! It's better to check a few LEGOs at a time. And you should always check them — don't say "I'll check them all later," because then you're back to the big pile problem.
Well to be honest that's the first time I've ever heard of that approach. Pull requests to branches that are not master? I suppose that works. It sounds a bit like some agile koolaid stuff though, calling it an epic.
Stacked PR don't necessarily have anything to with agile or product management.
The way i use them is to split a piece of work into multiple stages. Let's say you're changing an old part of the code base and you determine that you should refactor some code. You do the refactor and open a PR with those changes. Then, you do the actual code change that you were supposed to do and open a PR but target it to the first PR.
This way it's easier for the reviewer to review each change individually instead of having the refactor and the change request all together.
Think of it like being able to review the individual commits of a PR branch.
It's just what I've always heard them called. I think it's because of how Jira organises things as tasks under an epic task. Works well in that workflow.
trunk based development and CI remove the need for any of this coordination overhead, along with actually trusting the people who work for you and helping them improve
stacked PRs are an orthogonal concept to trunk based development and CI.
to me this seems like a way to split a single PR into multiple more digestible PRs for the reviewer, and giving commits a tighter conceptual scope for the developer.
I built a better one for agents!! Built-in worktree support, a CLI designed for agents (no interactive flows), and designed for parallel agents to work in parallel
From what I hear it doesn't work between forks, only between branches from the same repo. We prevent users from creating branches and require them to fork instead, so until they fix that it's still unusable for us :/
It's not my decision, but the main reasons I'm aware of are:
- in a big code base with thousands of contributors, everyone pushing their personal branches to the main repo would degrade the time it takes to fetch
- in cases where you have a downstream part and an upstream part, and you accidentally push some sensitive stuff to upstream, people are less likely to be watching everyone's forks than the main repo, so if you discover it quickly you may be able to prevent the leak by just deleting the branch (and contacting Github to have them wipe every reference to those commits)
Or, you know, iterate. With planning and purpose instead of just YOLO'ing until it works.
And BTW, legitimately large merges aren't gonna benefit from this anyway because the system won't work at each step without all of the dependencies from later commits in place. So you can read the code but can't prove each step anyway.
Jeez guys, this really needed to be a thing? We invent better duct tape to keep the stovepipe going.
Can someone explain how/why this workflow makes CI run against the final branch for all the PRs? Normally CI would run against the changes up until the given point in the stack, but the docs say this new feature triggers CI from the ref of the final PR in the stack, how is that safe?
This is great, is there info about how rebases after merges are done?
Having to rebase all of my PR's every time a pr on my manual stack was merged was always a big pain for me
I have first hated rebases as unnecessary as merges do merge the code well enough. Then I learned `git rebase -i` and rebase plans in-depth and used it all the time for cleanup, reorders, repairs, updates and all, while still preferring merges once the code and history was OK to publish. But merges do not play well with rebases, even with --update-refs.
Then I learned `jj` and basic language for `jj rebase`. Plus `jj undo` xD
and I'm not moving back, unless someone pays me 2x :P
nah, but really, it just handles the tree reordering so well and saves so much time, it's simply no point in not-using it, considering everything is still git-based. Things like git seeing weird things during jj conflict resolutions are minor and almost unperceivable after a bit of getting used.
and yeah. conflict resolutions. It's probably even bigger game changer than "current commit" idea and "rebase on steroids". Remember how when in git a conflict shows up, you're stuck untill you reset or solve it? In JJ you can just continue. Yes, files are broken. But you can do your merge/rebase/edit/reorder/everything as you like, and go beck and fix the conflicts once you (and shape of the repo/file/history/etc) is ready for that. Conflicts may even solve themselves if you finish moving or commits around or squashing them.
So far, the ONLY real irritating issue I found is handling \r\n on Windows. In pure Git, so far, it was always the best idea to use autocrlf=true or input. With JJ side by side, setting it 'true' is asking for issues. Or so it was for me, maybe I configured somethign wrong. Anyways. I'd suggest either using "input" and be vigilant for occasional mixups, or "false" and set (or teach:)) your IDE to not emit \r. In '90s and '00s that was quite a challenge, but today most editors and IDEs now handle that well.
nah, but really, it just handles the tree reordering so well and saves so much time
I toyed around with it a little and found that it's really antagonistic to how I work. The biggest bummer is that it simply has no upstaged changes. I frequently find myself floating changes so I can very quickly switch branches with those changes still in effect. For instance I wrote a benchmark for a library I was contributing too and had to change the build files of the library to make the benchmark work. And then I ran that benchmark on 3 different branches.
You know of a way to work that way without having to jj undo and jj redo or cherry-picking commits and stuff like that?
> (git checkout b/c/...) You know of a way to work that way without having to jj undo and jj redo or cherry-picking commits and stuff like that?
in short: not yet, but maybe
I do that too all the time! The more I used git, the more "floating edits" I tend to have, either to be able to switch like you said, or to selectively commit them here and there. It gets progressively harder to manage and sort out as the pool of edit grows.
Even with git alone, this 'taught' me to make a lot of small, named, independent commits. Or even unnamed. But 'independent' is the key. Do them in a way I can easily rebase/cherrypick/cut/paste them around at will into any place or order. Your example of a benchmark is a great example. If you can switch to/from a branch while having it floating with no conflicts, it sounds perfect for such temporary disposable commit.
Having lots of commits, with content to be preserved and pushed out, or content to be eventually removed, is a hassle. That's why I eventually mastered git-rebase-i and probably heavily overused it, and that's why I love JJ now.
In terms of pure git, if the 'benchmark' files are totally separate there is a way to create a commit IIRC 'orphaned' that has no parent. Drop the files there, and then merge it wherever you want with no conflicts. But merging and splitting them is a hassle. JJ should be able to work with that as well the same way and make it much easier, but still some hassle. But it obviously won't work if you need to have some parent files. Then you have to find "common base" between all branches you are going to move between, and create the "common benchmark commit" on top of that, like any other feature branch, and them merge/unmerge from that point. More hassle.
That's moreless how I'd keep it without JJ, and I did it a few times, and .... because I did not like "the hassle" I stopped doing it, and started having "floating uncommited changes", which are cool, but only as long as you have, say, up to 2-3 such parallel "bags" to tag along. Then it usually gets tangled mixed and no longer independent enough.
Anyways. Having JJ and better rebases, I'd just commit it. Or let JJ commit it for me as it does. Then, instead of `git checkout B` (or `switch` as they name it now), I would `jj rebase -s @ -d B` or something like that. Meaning, "move 'currentcommit' onto B". Since "I had changes", when I run any command, JJ will automatically commit the changes as "@" on top of HEAD, then execute the command - and will move the changes to where I wanted to go, to B. And since it encompassed @, after the command you'll end up sitting at '@', with your changes, placed on 'B'.
Of course `jj rebase -s @ -d B` is much more verbose, but since it has only 1 actual parameter (B) and it can be at the end, it can be easily aliased into something shorter.
I also often start just poking the codebase and see what falls out, then sort my working set into commits / branches after the fact. With JJ i'd have to rewrite history every time I do this or just commit "Various fixes and improvements". And yes JJ makes rewriting history way easier, but it's still more work and less convenient.
To be honest, I do not see any difference to how I work.
If I have a lot of current changes to sort out, I still tend to use my old git client, since I'm used to it and I find file/chunk/line staging&committing rather fast, and I do not like that tool provided with JJ (or didn't learn it enough to like it :P). In most cases, I can quicky go over changes and sort them into set of commits with old git tools. But after that, I sort and reorder those commits into proper branches with JJ, since it's easier/quicker/more expressive.
For that matter, I rarely use `jj new` in the way the authors wanted (so do changes, describe, new, do changes, describe, new, ...). But I use it a lot to move around (a.k.a. git checkout/switch). Also I found myself using `jj edit` quite often - it's great for splitting large commits or fixing some typos (while 'canonical' way would be to `jj new X ; then edit typo ; then jj squash`).
In addition to that, JJ adds some more tools for various cases, like `jj parallelize` (turn string of commits into a fan of 'branches') - which sounds odd, and "there's no problem doing it manually" with rebase, but when you know upfront that you need to something like that, it's just awsome to toss a single command instead of, say, 5 rebases :D
But! for poking-around-the-codebase, there's one command I'm, still learning, called `jj absorb` and "mega merge" workflow. In theory you sit on top of octopus merge, edit whatever, then use `absorb` to 'send changes to correct branches' to 'already modified files' + occasional new commit and small rebase to 'add totally new changes to specific branch, so absorb knows where to send more of them' sounds like ultimate way of 'poking the codebase' and feature branches but I'm still too new to that to say if there are any pitfalls etc.
Anyways. Having JJ and better rebases, I'd just commit it. Or let JJ commit it for me as it does. Then, instead of git checkout B (or switch as they name it now), I would jj rebase -s @ -d B or something like that. Meaning, "move 'currentcommit' onto B". Since "I had changes", when I run any command, JJ will automatically commit the changes as "@" on top of HEAD, then execute the command - and will move the changes to where I wanted to go, to B. And since it encompassed @, after the command you'll end up sitting at '@', with your changes, placed on 'B'.
Wouldn't that commit the benchmark-related changes into my feature branches, so I'd have to remove them from the branch later when I actually want to submit the review?
If I have a lot of current changes to sort out, I still tend to use my old git client, since I'm used to it and I find file/chunk/line staging&committing rather fast, and I do not like that tool provided with JJ (or didn't learn it enough to like it :P). In most cases, I can quickly go over changes and sort them into set of commits with old git tools. But after that, I sort and reorder those commits into proper branches with JJ, since it's easier/quicker/more expressive.
That's not quite what I mean. I mean that after a couple hours of randomly poking and refactoring, I have 10, 20, 30 unstaged files. I then git checkout -b Feature_A, stage the changes for that branch, commit, publish branch, git stash; git switch -; git checkout -b Feature_b;, repeat.
Or I just create individual commits out of the unstaged changes one at a time and simply push, if I'm already on a relevant feature branch.
I don't need to rewrite history or rebase, because I have no history to change (yet) and no commits to rebase.
> Wouldn't that commit the benchmark-related changes into my feature branches, so I'd have to remove them from the branch later when I actually want to submit the review?
Uh,.. no, but yes, but no? :)
JJ does not update branches tips until asked to. At least mine didn't do that. Maybe there is an option for it.
When I issued sth like `jj rebase -r Foo -A Bar` it moved the 'foo' commit onto Bar, but didn't update any branches labels/bookmars that were sitting on Bar.
So if you were on branch1 B with your benchmark-unstaged state, and C was the tip of your "branch2", and then `jj rebase -r @ -A C`, then after that command:
- you'd totally remove the benchmark from branch1
- you'd have the benchmark 'pasted' on top of C as a commit '@', but "branch2" would still point to C
- your working copy state would branch2, plus 'benchmark' as current @, visible to JJ like that, but from git's point of view you'd be on C with 'benchmark unstaged' state
Of course if you added new commits on top of that state, then you MIGHT need to clean up before pushing. But I think that's kinda obvious, right? The cleanup might be as easy as moving 1 commit (the benchmark) to the tip, to cut it away when moving to branch3. It all depends on how you commit.
For example, if in this state you'd add new commits via git - then it just works as it did - git doesn't see @, it sees C as your current commit, and adds new commits on top of that. @ detoriates. On next scan, JJ synces with git, updates @ to reflect where you really are with git, one-to-one, perfect image of your current state, but the "old @" is left dangling and `jj log` WILL show that "old @" to you as dangling commit. You can `jj abandon` it later. That's the only cleanup needed.
For another example, if you added new commits via JJ - then JJ adds them on top of @(benchmark) moving @ to new tip. Everything shifts, but if you know which commit was the benchmark, you can `jj rebase -r X -A @` to have it again at the tip, and to make it again the 'movable unstaged changes'. Again, that one command is all the 'cleanups' needed.
And I bet there are couple more options that I can't think of right now.
But! I know, I say and write a lot now, but I am not trying to convince you it's the best way :D it seems possible, but certainly doesn't mean best.
What I really do love in JJ the most, is that I can actually seamlessly switch between GIT and JJ. I think I dove into details but didn't stress it enough. Yeah, I just dumped a wall of text about how to reproduce "floating changes" from GIT into JJ - but the truth is, I didn't need it and you don't needed either, because even having JJ active in your repo, you could just do the same thing as you always did. JJ would just sync with new git state afterwards, and there would be some 'dangling commits', exactly like in first "For example" above, and you could then just `jj abandon` them anytime.
So sorry for realizing that just now :) The easy way eluded me, "it's doable in JJ directly" sounded too tempting
re: new language for referencing commits - yeah, I'm still learning it. Harder, but has much much more potential. All the times when I wanted to find something via git and it was hardly possible without heavy scripting - often in JJ it's just one lookup. But yeah, tailoring it right means sitting with the docs for haf an hour usually :)
I think the main problem I have is that I have no good feeling for what model jj uses.
Git I understand to be a graph of labelled diffs. Then you have your pending changes and a subset of those are the staged changes earmarked for a new node on the graph. And all git operations are just manipulating that graph.
With JJ, I'm not entirely sure what the abstraction is or how to reason about it.
heh.. I totally agree here. The docs weren't really helpful. I mean, they were, but.. the things they wrote were explained rather well, but it didn't "ring" to me. Automatic commits and `jj new` sounded absolutely weird to my mind used to git. I could basically write exactly the same what you wrote here :) including reflog :)
What made me try JJ was the note about "different handling of conflicts", and description of how "jj absorb" works. First week was hard. After that I focused on 3 commands: jj rebase, jj squash and jj new (didn't notice that "edit" exists yet), for everything else - plain git. And after next week, something "clicked" and I just "got it". Frankly, even if I tried, I can't explain how the JJ "patch/change" model differs from Git "commit/content" model. In most cases, it's almost the same. In the few cases it differs - I still perceive it as "like in git but...", and to this extent I think I understand why the docs were written that way..
I think the greatest hill to climb on for me was the autocommit, which I eventually got when it occured to me as meta-idea that it's like "everything is a file" in linux, but here: everything is commit ; yes, it's new changes and unstaged, but let's say that diff is extra commit '@' to just be able to treat everything uniformly. >!Technically it's not like that, @ is a physical real commit, and git state is intentionally kept at "1 commit behind", and git doesn't like it sometimes, but heck, "mentally" this image helped me a bit.!<
I'm tempted to write more, but I think it would end up huge again if I start on "how I perceive JJ's model" geesh.. I don't know. From files and folders and versioning -- it's the same as Git. Commands change, semantics change (sometimes), but the model for file/diffs is, I think, the same.
If I were to describe what JJ really is internally, ignoring that whole porcelain layer of commands, similar to describing Git as "graph of labelled diffs (of files/folders/content)", I'd say that JJ is all of that, plus an extra Git-like layer for that "graph of labelled diffs".
After all, in Git, you have a certain state of "graph of labelled diffs", and you work on it, and by committing and by setting tags and moving branches, you alter it into new state of "graph of labelled diffs". The graph changes. Nodes are added/deleted/altered, labels are added/deleted/altered. So you can version the graph (which is used to version your files). For me, this is what happens in JJ, and it pretty much immediately explains things like `jj oplog` and `jj evolog`.
I hope I didn't waste your time :) If you ever try JJ again, I hope you'll find it as useful as I did.
re: (snip) above.. ....because I got too talkative and it grew WAY to loong. I you feel like reading more, here it goes. But I think I'm talking about the same things all the time. Not sure if there's something valuable here. I might be tired and there might be little value below.
Anyways!! it was awesome to exchange ideas/issues/observations with you, thank you very much! and sorry for another wall of text. I could probably compress it with an LLM or something. Feel free to do as needed :D
------
For me, the growth/getting used to - was rather slow. After few days of using, I almost dropped it completely. It sounds funny, but I think I was simply too good at doing complex things via Git, and the gap between what-I-can-do-in-Git vs in-JJ was just to jarring.
After that "few days", I happened to screw up complex rebase. All via Git, because, I was already in "screw JJ" mode. Naturally I just resetted/backtracked, by collecting SHAs, restoring all screwed up local branches and making them point to what they were before, etc. But then, it occured to me - ok, let's try screwing it up with JJ. I tried making the same parts of that complex rebase with JJ, and ... yeah, screwed it up as well. It was even worse actually. But I learned about `jj oplog` and `jj undo` (*). and .... and I just saw its "power". I mean, it reverted my whole screwed up repo, restored all branches, and even allowed me to jump back and forth between "screwed up" and "all good" states of the whole local git repo. So... there had to be something in JJ that vanilla Git didn't have. So I found my motivation to 'dreadfully' work with JJ for a few weeks more, learning new commands and switches from time to time, until I got more fluent over time and the perceived "jarring gap" (mostly) disappeared. It wasn't easy, but I consider it well worth ;)
(*) I wanted to say that this extra versioning layer added by JJ's (with lots of info and handy ready to use tools), compared to reflog (you get some descriptions and SHAs, but doesn't get any tools) feels a bit like comparing CVS (reflog) to Git (JJ's graph versioning), but it's ... wrong? It sometimes does feel like that, but describing it that way simply over-advertises and promises too much. I think the `oplog` is conceptually mostly like reflog. Only `evolog` is a new beast, it's like "show commit history of single file" in Git, but (since JJ versions the commitgraph's evolution) 'evolog' shows you "how a commit changed" (it started being on branch A, then it was rebased to branch B, then it was edited/amended, then it was...). Anything like that in Git? No. Is it useful? Sometimes. As with any new thing, it's not necessary, but when need arises, it's presence is godsent. I use JJ for about a year now. I used `evolog` twice. Quite .. not jaw-dropping, right? But I'd waste a lot of time if it didn't exist. Like with having-backups or not-having them.
But honestly, from everydays work point of view, that extra layer matters little. What matters more is command syntax and porcelain. After some time of using it, JJ's commands' parameters are a bit more "uniform" in their design than Git's. Also JJ's things like "autocommits and @" and "non-blocking conflicts" matter MUCH more (than oplog or evolog), and the way how JJ handles them (@&conflicts) is mostly irrelevant to "JJ model" or "what JJ really technically provides" - Git could handle it the same way, it's just Git's "(design, ux) choice" to not do it that way. Similarly, JJ's "choice" to not allow to easily stage&commit just a line or two from current changes - sucks a lot - I love that Git feature...
but the versioning how the whole commit/branch graph evolves.. that's just wild. I love reflog in Git, even if I use it 2-3 times a year. Similarly I love to have JJ's graph-versioning, even if it is, in fact, rarely used directly :D
You know of a way to work that way without having to jj undo and jj redo or cherry-picking commits and stuff like that?
You can create a merge, so use jj new a b, test, then jj new a c, and then test, assuming a is where you created the benchmark. Then abandon those merges when you're done with jj abandon foo.
Suppose I have branch A with commits 1,2,3. The I have branch B coming off a with commits 4,5.
I merge branch A, then I have to rebase branch B on top of master.
But hold on, looking at the log of branch B, it's got commits 1,2, 3,4,5 and I have to remember to rebase starting from commit 4.
And if branch A was squashed and I happened to forget that branch B starts on commit 4, now I have to find out what was the last commit in branch A in order to find what commit to rebase from in B.
Now each branch has dozens of commits, and my stack has 5 different branches that need rebasing every time one of them is merged, you can see why it's not a pleasant experience.
Don't pedal with your hands. I don't know what gives you the idea that rebasing means you can have the same 5 commits applied in random order to 5 different branches, with some of the commits squashed, and also merged out of order onto the remote main branch. You're describing a problem with your local setup that you created -- no one else created it for you. The remote master is still fine. There is no problem, except you. So don't create the problem for yourself. Do it differently.
You can still unfuck yourself if you just slow down and go by the numbers. Create a clean branch off the remote master and cherry pick the local commits that you want to exist on your new branch.
That's the problem. Having proper commits before and after a merge will avoid any headaches during rebase.
Reading code changes doesn't stop after it's merged. Weeks/months/years later when discovering a bug it's easier to bisect through smaller commits to figure out what was done.
My PR had branch B pointing to branch A so that reviewers see only the commits in branch B (4,5).
Otherwise I'd have 1 pr (A to master with commits 1,2,3), and another pr (B to master with commits 1,2,3,4,5, which defeats the purpose of having a separate branch).
As soon as the PR for branch A gets merged into master, my PR for branch B that was pointing to A will get automatically redirected onto branch master and very likely have conflicts, at least that's the case in Github.
Moreover the commits from branch A were squashed into a single commit in master, but B is branching off from a previous state and its most common ancestor to master includes the pre-merge commits of A, so reviewers will see that the PR for branch B includes commits 1,2,3,4,5, while in practice commits 1-3 are already in master, in only want commits 4,5.
So I'd have to run git rebase --onto master 3 to ensure that I'm carrying over only the commits that started on branch B
I feel like most complexity here comes from complex dev or PR strategy than git being git.
If you weren't using squash PRs, after merging PR A, commits 1-3 would disappear from PR B, as they would be recognized as already merged on main.
If your goal is to squash commits 1-3, why not squash them before creating branch B?
If this is a scenario where you work in parallel on A and B, but branch out B from A, then go back to tinker with A, isn't that more of a problem with the workflow or PR strategy than git?
There's a lot of folks arguing that stacked PRs aren't adding anything you couldn't already do before. But that's not the case. Currently you need to pick two out of the following three benefits:
Have small easy to review diffs
Avoid early PRs bit rotting
Avoid reviews on later PRs being blocked on early PRs merging
With stacked PRs, it will now be possible to obtain all three of these benefits at the same time. No previously existing native Github workflow can claim this.
How big are these PRs? Especially in the agentic coding era, people are regularly dropping thousand line PRs and it only takes me maybe 5-10 minutes to review the thing. It's a skill to develop like any other. You can also have Claude help you review and speed up the resolution process. I think I would go crazy if I had to break apart PRs in into multiple commits and review each one.
We also use microservices, so maybe this is a monolith thing only?
Is this really a problem for people in general? we deal with massive prs all the time, maybe a conflict once every 3 months and its usually something caused by an errant pipeline integation. This sounds like a solution for orgs that have bigger problems in process and sdlc impl.
Apparently yes, there a page in the doc on that. You have to target FeatureBranchA from FeatureBranchB's PR to get the checkbox to create the stacked PR
What's interesting is how long stacked diffs have been "solved" in adjacent tools while GitHub's PR model stayed static.
Phabricator had proper stacked diff support back when Facebook was using it. Gerrit has had this since the beginning. Graphite built an entire company around making this workflow ergonomic on top of GitHub. GitHub implementing it natively is good, but it's years behind.
The core issue is GitHub's mental model: "a PR = a change" rather than "a commit = a change." Stacking implies each commit is independently reviewable, which maps badly onto branch comparison. Gerrit works around this by making commits the primary unit of review. GitHub is retrofitting that concept onto a paradigm not designed for it.
Still: better late than never. The main incentive to use Graphite specifically gets weaker if GitHub does this natively.
The biggest win here isn't even the branching model - it's that reviewers can finally look at a 2000 line feature in digestible chunks without losing context between separate unrelated PRs. Half my review fatigue comes from opening a massive PR and just going "LGTM" because ain't nobody got time for that. If this gets people to split work into smaller pieces because the tooling finally supports it, that alone is worth it.
At a previous company, we had a lot of pain around PR chaining in our TBD workflow, and I ended up building a stacked PR tool internally because we needed it badly.
Seeing GitHub ship official support is pretty satisfying. This has been a real problem for a long time, so it's good to see it become a first-class workflow.
The biggest practical win of stacked PRs is that your CI pipeline runs against something closer to what will actually land in main. With the old "chain PRs against each other" approach, your CI on PR C was testing against B's branch, not against main + A + B together. So you could pass CI on each individual PR and still break main on merge.
The squash merge problem someone mentioned is real though. If you're squashing on merge (which most teams do), the commit SHAs change and every subsequent PR in the stack suddenly has conflicts. GitHub would need to automatically rebase the rest of the stack after each merge for this to feel smooth. Otherwise you're manually rebasing 3 PRs every time the first one lands.
The other thing I keep running into: reviewers treat each PR in the stack like an isolated change and leave comments that only make sense if they could see the full picture. "Why are you adding this interface with only one implementation?" Because the second implementation is in the next PR. You end up spending half your review time explaining the stack structure instead of discussing the actual code.
Off topic but I really REALLY hate websites (and products) that:
1. Pretend a cookie banner is mandatory by GDPR, it isn’t if you don’t use tracking cookies.
2. Make a snarky claim of not using, but then proceed to place a Cloudflare cookie.
I actually did this completely on purpose. Yeah the site is hosted on cloudflare, it is what it is - waiting for a server in the mail so i can move out of cloudflare and host it myself actually 😅 due to this.
But Critiq the product I’ve tried to go out of my way of making privacy focused, which is why that cookie banner is there. Not vibing, just marketing 💁
Chances are your privacy-focused target audience actually understands and generally likes GDPR, CPRA, and the likes. Not sure if posting "Ha ha, I don't care about regulations around privacy and think it's stupid" on your website underlines your stance on privacy as you think it does.
Not really the target audience, its more of an FU to products such as sourcetree and gitkraken, but I see where you are coming from. It’s about building a product that solves the problem without the need to capitalize on users data. The privacy and terms underlines the commitment, quite extensively, i think.
But sure “cookie banner for no cookies bad, apparently you hate gdpr”. There are more pitchforks in the cellar, torches are extra.
Has graphite done any value adds that are over and above hacking a better workflow on top of GitHub? I know GitHub is slow to meaningful improve nowadays but graphite’s core feature has no moat.
Their merge queue is better than GitHub’s merge queue. It supports enqueueing an entire stack of PRs with one click (like what GitHub says it will provide in this announcement), and supports partitioning the queue so changes can merge unblocked by other unrelated changes (which in a monorepo with high throughput and complex ci is very valuable, it means what used to take 3 hours for a readme PR to merge waiting behind a bunch of backend and front end PRs can now merge nearly instantly).
We never used their dashboard but used the cli extensively to do PR stacking.
i.e. db schema changes at bottom
then a PR each for each microservice change
Frontend on top.
So yes it was just improvements on top of github and you could likely achieve the same without it, but it made code reviews so much more pleasant than a blank LGTM.
Yup, heh. Hopefully this can be adopted for use by JJ. jj-spr handles stacking PRs, but it would be nice to have native support for PR diffbase/children in the UI.
WoodyTheWorker@reddit
Just make it work like Gerrit
kevin7254@reddit
As someone who worked for several years with gerrit and then joined a company which uses GitHub, I want to fucking put a bullet in my head each time I review a PR. Someone who says ”ehm actually it’s not that bad” must have never used gerrit
EveryQuantityEver@reddit
As someone who’s used a normal PR flow and now has to use Gerrit, I cannot stand it. I like having feature branches with small commits as I work on a feature. Gerrit does not let me do this as each commit has to have its own change id.
silverslayer33@reddit
Honestly I agree, I feel like I've entered some crazy land with so many people here praising Gerrit when most of the people I work with absolutely loathe that most of our repos are hosted using it. The UI is absolute ass, it enforces a workflow that doesn't generalize well to all teams/projects/dev workstyles because it's extremely opinionated, our team specifically has had tons of problems with seemingly basic functionality just straight-up not working (probably about 1/3 of the time we clone a repo and get it set up for reviews, it manages to botch the commit hooks for creating change IDs so it creates the ID but actually it's invalid and the server rejects it and we have to go pull the proper commit hook from somewhere else where it's correct), its permission/access control model is super clunky, and the list goes on. I can probably count on one hand the things I like about it over other solutions (changelist descriptions just being the commit message is a big one, and I guess its search/query system is pretty powerful).
It's probably great if it matches your preferred workflow and you get it set up exactly the way you want/need, but it is not a general-purpose git-based code forge. As garbage as Github is, it thrives because it is general-purpose and it can be adapted to many workflows. Gitlab and Bitbucket are similar, finding themselves as mainstays in the corporate world because they can fit most workflows without huge amounts of effort.
apocalypse910@reddit
I was confused by this as well - I suppose I didn't work with Gerrit for long but it was a nightmare IMO.
Though one funny story... I had just had it with Gerrit at one point - I was in some management training with some devs I didn't work with often. For some reason I got going on a rant about how much I hate Gerrit...
It completely slipped my mind that we also worked with someone named Gerrit who was extremely nice. My rant was vague enough that the person I was talking to didn't realize I was talking about software, not the person. (I hate Gerrit, Gerrit ruins my day regularly, that sort of thing). Had to do a bit of damage control on that one.
Blueson@reddit
I can't understand how people are happy with reviewing in Github. It is genuinly such a pain.
Gerrit ruined me...
edsonboldrini@reddit
Funny never heard about Gerrit
blind_ninja_guy@reddit
I dread using GitHub every time I have to. Such a clunky user interface, and really crappy compared to what the rest of the industry could be using.
Familiar-Level-261@reddit
Every time project requires to do anything with Gerrit I stop trying to contribute anything. I'm sure it's nice when you set it all up but for someone just wanting to fix a bug I found it's some kind of nightmare.
Hooxen@reddit
what’s the difference between just constructing it as a chain of commits in a single PR to master?
Sopel97@reddit
each PR should represent a reviewable unit of code while commits should represent a complete change in code
Kache@reddit
Each ~~PR~~ commit should represent a reviewable unit of code ~~while~~ and commits should represent a complete (as in not partial modification or code)
IMO "stacked commits" should just be the same thing as PR with a clean chain of commits. Just add a setting that runs CI against every commit in the PR and BOOM, you got stacked commits
AmosIsFamous@reddit
In this scenario what happens when some code review feedback leads to a change in the first commit in the stack (closest to main)?
Kache@reddit
Same thing that would happen in a stacked PR scenario, that leads to a change in the first PR in the stack
AmosIsFamous@reddit
So is the reviewer now tracking for themselves where the “units of work” are? If a second reviewer comes in after the first change has happened how do they know they’re to review the first two commits as a single unit?
ugh_my_@reddit
Sure until you get anal reviewers who want one PR per change
topMarksForNotTrying@reddit
Makes it possible to review each PR individually.
I haven't used github PRs extensively but on azure devops the PR review UI doesn't let you review each commit of a change request. You can view the changes of a commit but no way of leave feedback.
For devs not used to using git rebase, having separate PRs also makes it easier to apply any changes to one of the PRs.
If you squash PRs, it also maintains history better.
DavidTej@reddit
Within my team at Microsoft, I'm currently building a tool to help manage stacked PRs, similar to this new github tooling. I think this would be even more useful for azure devops for the reason you mentioned. Do you think this is something you would find useful? how do you currently manage PR stacks and/or bloated PRs?
topMarksForNotTrying@reddit
Having something built into azure devops would definitely help.
My current workflow when using stacked PRs is:
I would say the main pain points are with juggling the different branches of all the stacked PRs. If you need to address some feedback on one of the bottom PRs, you have to manually update the whole stack. Stacked PRs are analogous to commits on a branch, so maybe we need an equivalent of "git rebase --updated-refs" when managing stacked PRs.
The other pain is closing stacked PRs. You need to close them individually rather than having something automatic. In our case, we only validate code that's targeted to the main branch so for each PR in the stack we need to make sure to run the pipelines and then we get to merge them.
ahal@reddit
Let's say you're working on a large complex change. There's likely a bunch of easy non controversial prerequisite stuff that you can do up front to help prepare for the actual core of your change.
With stacked PRs, you'd put that early work up in their own PRs. Then immediately start building the more complex changes on top in a PR stack.
This way as your working on the later stuff, you can start landing the earlier stuff. You get to pick all three of:
rdtsc@reddit
Don't need stacked PRs for this, only multiple stacked branches. Then put the lowest one up for review.
mrcarruthers@reddit
Say you have a stack of branches, then you have to make a change in the lowest one, or rebase on main. It's a pain in the ass to then propagate that change through the whole stack. This new feature allows you to do that pretty seamlessly.
rdtsc@reddit
git rebase -i --update-refsthen push the branch(es)ahal@reddit
Then you're missing benefit 1. Before stacked PRs you had to pick two of the three benefits. This is the first time you'll be able to get all three.
Bush-Men209@reddit
Right, stacked branches handled part of this before, but stacked PRs are what let you review and land the boring prerequisite work while the harder piece is still in flight.
Farados55@reddit
Usually commits are amendments to the state of the PR.
Blueson@reddit
One of my largest complaints about Github is how tedious it was to work with stacked PRs. I am happy they are investing into tooling to simplify it.
wildjokers@reddit
That really has nothing to do with github. It is git itself that makes it hard to work with stacked branches (especially if you squash commits when merging).
Living_male@reddit
Why does squashing commits when merging complicate the process? I was looking at the documentation, and thought squashing each stacked branches commits would look cleanest?
wildjokers@reddit
Because if you create branch B from branch A and then squash/merge branch A all the commits common to branch A and B (i.e. commits made to A before creating branch B) will conflict if you just try to merge in B.
To fix you can use the the newer
git rebase --update-refs. There is an older method using the--ontoflag of rebase as well.Living_male@reddit
Thanks, will keep that in mind from now on.
Proper-Ape@reddit
git rebase —update-refsis not so helpful for when you’re collaborating with others still. I find it would be a nice git feature if squashed commits could keep their place and you don’t need to rebase a stacked PR at all. Not sure if this is somehow feasible in the git model.Blueson@reddit
I think there are features lacking in both git and Github to enable an environment where it's pleasant to work with stacking change-requests.
But tools like Gerrit has shown that you can have workflows adapted to stacking change-requests enforced while still using git.
OMGItsCheezWTF@reddit
One of the issues is that FAR too many developers lack anything beyond the most basic understanding of what Git actually does under the hood. They learn the bare minimum commands they need to use it day to day and then defer to whoever their nearest expert is if something goes wrong. I've seen this at every level from associate up to staff, people that have simply never taken the time to learn what git actually is.
This means that every repository I have ever worked on at every company essentially becomes this vast mix of merge requests, rebases and cross branch merges.
Blueson@reddit
Totally with you. I have ended up becoming the go-to git guy because I bother to know more about git than just pull/push.
I am continously surprised by how little time people invest into learning a tool that is so integral to most workplaces.
ZorbaTHut@reddit
In Git's defense, this behavior is not even remotely limited to Git.
hdjddjiieeshs@reddit
That wasn't really an indictment of git, just many of the developers I have worked with over the years.
cosmic-parsley@reddit
_jj is really worth a try, it makes this so much easier.
You can assign a bookmark (branch) to each commit (revision) in a series, then open a PR for each bookmark. Whenever you modify an old commit, all future bookmarks become dirty of course. Doing
jj git pushupdates all the branches at once, so all your PRs get the latest changes.Of course there’s nothing you couldn’t do with git, but it’s much nicer to have an easy way to fix up a single commit in history and then update everything that’s dependent at once.
Uristqwerty@reddit
Github's commit list is an unstructured linear list of space-consuming panels. That makes anything but squashing/rebasing into a linear history awkward when compared to, e.g.
gitk's visualization that shows the actual DAG structure. Given stacked PRs necessarily interact with the underlying graph relations, it's always going to be more awkward when viewed through an inferior UI, and doubly so if your choice for merging constantly rewrites parts of the graph.yxhuvud@reddit
It has everything to do with Github - they have very obviously built an interface that matches their internal workflow with fairly small allowances to other workflows.
It is especially obvious in how the entire UI is built around pull requests, whereas the fundamental unit in git is the commit. So for example, you can still, a billion years after github was created, not comment on commit message content.
GhostPilotdev@reddit
Finally. The fact that we needed third party tools like graphite just to do something GitLab handled natively years ago was embarrassing.
toadi@reddit
I use git town and it works just fine for stacked PRs: https://www.git-town.com/
Not sure what the benefit for github specific tool would be. Also I saw they are integrating AI in again a place where I don't need it.
araujoms@reddit
My largest complaint about Github is that it has achieved zero 9s uptime.
orygin@reddit
Aren't they at three nines now? 89.99%
dbenc@reddit
80.099999
dkarlovi@reddit
Nonsense, as long you count the non leading 9s.
LemmyUserOnReddit@reddit
You typically do though right?
Edgar_Allan_Thoreau@reddit
Only if they follow leading 9s. 99.999% is five 9s. 18.9% is not one 9
LemmyUserOnReddit@reddit
Oh wow I assumed GitHub was achieving 99%. They actually have zero nines lol
mpinnegar@reddit
The important part is to count all the nines! No matter where they appear.
crespire@reddit
Copilot, how many 9's are in 79.7382%?
omgFWTbear@reddit
We’ve been using a German and he insists it’s the most neins he’s ever seen for uptime.
chat-lu@reddit
Set your github settings to German then.
Is GitHub up? Nein!
gimpwiz@reddit
I'm still laughing about this.
89% uptime means it's down for more than one full month per year.
araujoms@reddit
I'm not laughing because I'm forced to use this crap.
Uristqwerty@reddit
If they keep at it, perhaps one day they'll finally attain the dream of 9 5s!
iamapizza@reddit
We need a way of being notified whenever Github is up
Omnipresent_Walrus@reddit
Can someone tell me how this is different to doing reasonably sized PRs into an epic branch and only ever merging epics into main?
chuch1234@reddit
Stacked PRs are useful if each new PR depends on changes that were introduced in the previous one.
MintySkyhawk@reddit
Why wouldn't I just do a single PR with multiple commits? Reviewers can review each commit one at a time and then I can rebase and merge when I'm done.
cosmic-parsley@reddit
It’s a UI difference. GitHub has no way to really review individual commits and follow how they change. If you force push, the UI gives zero indication that the first commit abc123 turned into def456 and second commit a1b2c3 turned into d4e5f6.
Also there’s no way to review individual commit messages, but there is a way to review/edit PR descriptions. Sometimes that’s as important as the content.
MintySkyhawk@reddit
Github absolutely does have a way to review individual commits and view their descriptions. This PR only has one commit, but you get the idea. You can select multiple commits or just one, and it will show you the commit description and only the changes from that commit.
https://i.imgur.com/wfMkW1i.png
Force pushing does mess things up, but you shouldn't be rewriting history after you've opened a PR.
Eddyi0202@reddit
Comments on specific commits will not show up in PR view so thaths the biggest issue, reviewing commits on github does not really work, also it requires developer to create proper, independent commits
MintySkyhawk@reddit
Comments on specific commits do show up in the PR view when you have selected to view a specific commit from the PR.
Eddyi0202@reddit
"when you have selected to view the specific commit" so they are not shown in PR view :D Thats the problem, PR author will not even notice them unless tagged
ham_plane@reddit
Yea, but you can't review, as is approve/request changes, on individual commits
Familiar-Level-261@reddit
..but you can do that with multiple commits in single PR.
Problem is just people don't know Git very well...
Eddyi0202@reddit
Reviewing commits in PR on github just sucks
MintySkyhawk@reddit
You can certainly do that with git, but you probably can't do it at work and certainly not via GitHub UI. Pretty sure to do what you're suggesting, I'd need permission to commit to main branch. We only have permission to modify main in anyway via an approved PR.
Familiar-Level-261@reddit
Well if you only allow merges from pull request probably not but it's a silly option
stumblinbear@reddit
I'm not gonna review a dozen bug fix and refactoring commits in every PR, I only care about its final state
MintySkyhawk@reddit
I guess I'm the only one here who habitually rebases my commits into nice individually reviewable commits before creating a PR.
If I were to use this stacked PR feature, I would be taking my existing workflow and then creating separate PRs for each of my commits. And then, as it says on the linked page "merge them all in one click".
So I don't really see a difference between the two approaches, except that the PR stack seems more difficult to set up.
gSidez@reddit
You’re approach is the proper way to use git. The whole point of being able to do interactive rebases is so people can restructure commits into a set of logical, manageable changes that should be individually compileable and reviewable.
Unfortunately most people don’t know or are too lazy to learn how to properly use git so you get a lot of people who throw up PRs with a million “fixed bugs” type commits, and since it’s so common these people think that’s the ‘right’ way to do it at and therefore can’t fathom the idea of reviewing PRs per commit or taking the time to organize their own commits into something meaningful instead of an unreviewable pile of garbage.
aoeudhtns@reddit
I work with devs that create the PR branch when they start and then push as they go. It's like a stream of consciousness. I've tried to counsel them to use a WIP branch without creating a PR, and to rebase and clean up where it makes sense... but getting people to change their behavior is tough it seems.
m00fster@reddit
That’s what draft pr’s are for in github
aoeudhtns@reddit
Can't get them to use those either.
max123246@reddit
Don't ask them to not make a PR. Just tell them to rewrite their git history when it's ready for review into reviewable commit chunks
aaulia@reddit
Well the CI run would be fun to watch lol.
max123246@reddit
Draft MRs
Manbeardo@reddit
OTOH, that approach works well when you’re amending meaningful commits because the PR tracks the previous versions of the branch as you push changes that rewrite history.
stumblinbear@reddit
I'd rather not waste time making history look clean when the only thing that really matters is the final code. And if I review a PR and they rewrite the history, I now have to re-review every single file because GitHub loses track of which files have already been reviewed since the history has been rewritten
MintySkyhawk@reddit
Bro, it takes like 3 seconds to run
git rebase -ito fixup your commits to a clean history.If you don't care to split things up, then you don't need to git rebase or create a PR stack. You can leave your history messy, create a single PR, and then just squash merge when you're ready.
If you do want to split things up to make things easier for the reviewer, then its easier to
git rebase -ithan it is to create a pr stack.Say you have these commits:
1. Some Thing
2. garbage
3. asdf
4. Some Other Thing
5. asdf
Then you would
git rebase -ithem in seconds to make:1. Some Thing
2. Some Other Thing
Or you could try to figure out how to make a PR stack out of those same commits, assigning 1-3 to the first pr and 4-5 to the second pr. And looking at the docs, creating the stack is way more typing and difficult than a simple interactive rebase.
stumblinbear@reddit
So you're rewriting history, and therefore invalidating any previous review that was completed? You didn't even address my actual complaint
gmes78@reddit
Please read up on
git range-diff.MintySkyhawk@reddit
You clean up the garbage before opening a PR. Any new commits after review as started are cleaned up in the same way before pushing them to the branch. History is never edited from the PRs point of view.
stumblinbear@reddit
I'd rather not simply hope that my machine doesn't kick the bucket and risk losing work if I had to do a significant refactor due to a review comment
Wires77@reddit
You can push a branch without opening a PR
Cazmar@reddit
Use fixup commits (git commit --fixup) for any changes created after review and once all approved, git rebase --autosquash --interactive to automatically order the fixup commits to right place. Easy and you don't lose history while the PR is being reviewed. Rebase only when finished before merging.
Verdeckter@reddit
Why each? What's wrong with having multiple commits in a PR if they're related? In particular, you can review commit by commit. Instead of a massive list of changed files. So implementation, tests, docs etc. It's merged as one commit still.
max123246@reddit
I tried stacked PRs and then I realized my pipeline is 4 hours so I can 4 PRs and wait 16 hours. So I decided to do what you're doing, just rewrite the commit history when it's ready for review
stumblinbear@reddit
What in the goddamn
And here I was just a couple of weeks ago spending a few days to cut our 25 minute builds down to 8
chuch1234@reddit
Four hour pipeline?! I hope improving that is on the roadmap?
max123246@reddit
It's gotten better. Nightly pipelines used to take over 24 hours. Helps that we're in a JIT compile world now instead of AOT
eggdropsoop@reddit
There's dozens of us!
I've done my best to fight the good fight but I think people worry
rebaseis a bit of a footgun and don't want to put in the time to learnreflog. It reminds me of those dev teams that "don't need rollbacks because we just roll forward." >.<aaulia@reddit
Now I know why this stacked PR doesn't click with me, your workflow is closely resembles mine.
rdmty@reddit
You’re just thinking about yourself and not your team
MintySkyhawk@reddit
I just cannot see any difference whatsoever in UX for the reviewer between a PR stack like they show in their example and a PR where each of those PRs in the stack is an individual commit.
In either case, the reviewer reviews each commit/PR individually, and then when the stack of commits/PRs is approved, the entire thing is merged with one click.
https://github.github.com/gh-stack/_astro/stack-navigator.DbHWHwGH_Z14GiHR.webp
rdmty@reddit
It means that you could have different people review each piece of the stack. (I'm assuming each item is a new PR, so nothing needs to change there).
If you get blocked on PR #4, you could still merge PRs #1-3. You're also merging in smaller commits at a time and could mean you're deploying smaller changes at a time. If your commit breaks, it's less code to read through and debug.
AmosIsFamous@reddit
The difference comes when you’re addressing PR feedback. Let’s say you had commits A, B, C and I had feedback on each of them. Ideally for me as a reviewer when you make charges it looks like A, D, B, E, C, F and I can look at either D (changes since my last review), or AD the new “unit”
MintySkyhawk@reddit
You can do that. GitHub lets you select multiple commits to review. So you can say "show me only change from A and D"
AmosIsFamous@reddit
Did you also rewrite history so that D is between A & B? Because if you didn’t I don’t think github would let me see just A & D when B and C are in between them.
stumblinbear@reddit
Yeah, I will commit when a PR is in a compilable state but not necessarily bug-free, and especially so at the end of the day just in case my local machine kicks the bucket before I get back to it.
When reviewing, I also don't want them rewriting history just to have nice looking commits. That completely breaks GitHub's review system. Every time they force push a rewrite of commits, it no longer tells you which files you've already reviewed
While I definitely would prefer it to not have to squash every PR on merge since you can lose some context for individual changes, that's much better than fucking up the review workflow entirely. I definitely prefer multiple PRs over a single PR with a nice commit history, because that nice history doesn't come for free. You just push the annoyance to every reviewer
IanSan5653@reddit
One advantage to stacked PRs (outside of not having to constantly rebase) is that you can reduce risk by deploying smaller changes if you deploy on every merge to main.
MintySkyhawk@reddit
My understanding of stacked PRs is that the entire stack is merged at once because the docs say "merge them all in one click".
If the entire stack must be merged at once, then it seems like a pointless feature when you can just use commits. If you can merge them one at a time, then it does seem like an improvement/automated way of creating a series of PRs like (main <- A), (A <-B), etc
IanSan5653@reddit
It doesn't necessarily have to be merged all at once. That's one way to do them but not the only way.
MintySkyhawk@reddit
Thanks, I think this was a key piece of information I was missing. I can now see a possible use case for creating a PR stack, though I still think plain commits will do just fine most of the time.
Farados55@reddit
Why wouldn’t each commit just be a PR? That’s essentially what you’re doing
MintySkyhawk@reddit
If they should be merged separately, they are PRs. If they're merged together, they are commits. The PR stack merges as a single unit, so to me makes more sense as commits.
DivideSensitive@reddit
IME, due to cross-depedencies between PRs.
I need this new piece of code from Henry to build upon, but it has a corner-case where it fails. So while Henry has a PR in which he puts the finishing touches to his code, if I stack my PR on his, I can still build on top of his code and have my own independent review cycle.
coworker@reddit
Reviewing commits is silly as none represent the final merge. Just wasting time
lechatsportif@reddit
Totally agreed. Honestly feels like devs want to see "changed the inputs on handleFoo" as its own commit with its own tests etc. I think I would blow my brains out. I'm way faster at the comprehensive PRs since I can gauge it in one shot.
Matthew94@reddit
In my experience large PRs get nowhere near as much scrutiny. People’s eyes glaze over and the PR gets waved in when there could be shitloads of issues.
DualWieldMage@reddit
Reviewing commits is the correct approach. Are you seriously suggesting to look at a final diff only? If there are orthogonal changes (large refactoring + 3-line bug fix) you either miss the important part in a sea of unimportant changes or burn yourself out going through each change carefully. If the intermediate commits are partial ramblings then i reject the review asking commits to be reordered/partially squashed to each individual commit makes sense. That's what ends up when merged and it should have quality. Full-squashing PR-s on merge is another retardation i always ban in my projects.
Matthew94@reddit
If you have orthogonal changes then do multiple PRs. The 3 line bug fix should be a separate PR if you’re worried about it being missed.
coworker@reddit
Yes. Don't be lazy and make a PR per commit if you expect each one to be reviewed. It's like you almost understand how PRs work lol
cosmic-parsley@reddit
Depends: if your repo use squash and merge, then yes. That’s kind of what GitHub’s UI is designed for.
But if you use rebase or merge commits then all of it becomes part of history and you need to look through each one.
coworker@reddit
Or, hear me out, you make a PR per commit since you want each one to be reviewed, discussed, and approved.
You guys like to make things way too complicated lol
chuch1234@reddit
Well this has been a very interesting discussion!
chuch1234@reddit
I've never heard of doing prs that way, do you do that?
MintySkyhawk@reddit
Most PRs I see are just a single commit, or a bunch of commits that aren't intended to be viewed individually and will be squashed together when merging.
However, for larger changes, it is common for PR authors to take a little bit of time to organize the commits before creating the PR to make things easier for the author to see whats going on.
A really common pattern for me is refactoring something in one commit, and then mass updating all usages of it in a second commit. That would be hell to review as a single commit (trying to find the real changes in a sea of automated ones), but looking at the commits individually its very clear what happened.
chuch1234@reddit
Neat, thanks! I have often wondered if there's a better way to deal with such things.
Hofstee@reddit
Before GitHub really took off, this used to be the standard recommended practice. Facebook in particular I feel played a large part in the squash + rebase ideology, but I'm also going off vibes and not facts so don't quote me on that.
teerre@reddit
Because GitHub is branch centric not commit centric
CherryLongjump1989@reddit
You can't review individual commits in a PR -- you can only get feedback on them, but they can't be approved and merged individually. That is what a PR is -- it's a single code review.
There are people who work fast and have good coding hygiene. So they are able to push up clean, fully tested changes 5-10 times a day while working on a larger feature. This stuff can get reviewed and merged even as they are working on their next PR.
Eric848448@reddit
It's nice if you need to do something like an API change and want to get that approved and merged quickly because it's heavily used and you don't want to keep getting clobbered by other changes.
ROFLLOLSTER@reddit
Generally the approach I've taken with stacked PRs is:
Stack: c -> b -> a -> main.
Merges are a-> main, b -> main, c -> main
Instead of c -> b, b -> a, a -> main.
This whole thing is just a bit of ui over what you can do already though, so no need to change if you like your workflow.
ekroys@reddit
The issue with this is when using squashed merges on each PR. Git loses the commit identity in this process so you end up with merge conflicts trying to catch branches up.
NineThreeFour1@reddit
Then don't use squashed merges on each PR?!
Only squash fixup/wip commits in each feature/PR branch, but keep conceptually different changes in separate commits even if they are in the same overarching PR.
ekroys@reddit
This is an angle, but ultimately it's not each developers decision sometimes, and it can be a repo wide rule.
double-you@reddit
Why wouldn't you rebase? That's a very normal thing to do.
MereInterest@reddit
Because rebasing can introduce bugs, and makes it very difficult to identify their source.
Suppose the
mainbranch has someutility_func, which is used by your feature development. A refactor/cleanup lands onmain, which updates the behavior ofutility_funcand updates all callsites ofutility_functo match. However, the additional calls toutility_funcintroduced on your feature branch haven't been updated. If you rebase your feature branch onto the main branch, your feature branch is now broken, as it relied on the old semantics ofutility_func. If you're lucky, this will be a compile-time error, but there's no guarantee of that. Reverting to an earlier commit on your feature branch doesn't help, because those earlier commits have also been rebased. Unless you check through thegit reflog(and pray that git's garbage collection hasn't occurred since then), it will look as though the feature branch had always been buggy.A better solution is to merge from
mainintodevelop/main. That way, the error is clearly introduced in the merge commit. You can revert to an earlier commit in the feature branch for testing, because those versions are still part of the git history.double-you@reddit
That's not how merge or rebase work. Both would trigger conflicts if there are any. But if you have just added a use of a function, that won't be a conflict because nobody else is changing that call site. Merge won't save you there.
But yes, if you need your old branch back, you'll need to use reflog, but reflog should be fine for a month, and if you are still wary of rebase, just create a backup branch.
MereInterest@reddit
The two changes conflict, just not in a way that git can recognize\
I agree, using a merge won't cause git to recognize a semantic conflict between branches, as it can only recognize text conflicts. However, merge also won't make the problem worse. Rebase hides the evidence that you ever had a working version.
The problem isn't that the rebase requires a backup branch, but that it isn't obvious that you'll need a backup until after the point when you lost it. Rebase destroys your history.
NineThreeFour1@reddit
What evidence? You can simply put the current status and open issues into the commit message.
Stacked PRs are unrelated to the problem of semantic conflicts. You do need to test your changes regardless.
double-you@reddit
Why do you need evidence that you ever had a working version?
I work with compiled code it is immediately clear what the issue is. Rebase lets me fix the issue and there's no evidence that there ever was a problem.
ham_plane@reddit
I'm struggling to understand why a merge commit would fail in that case, while a rebase would succeed.
I do get your point being able to track which merge commit introduced the behavior, that is pretty smart and (as a career rebaser) I have been in that exact scenario before and been totally lost as to what broke what
MereInterest@reddit
It's not that the merge commit will resolve the semantic conflict between the branches on its own. That still needs to be sorted out, regardless of whether you use rebase or merge. The key is that using
git mergewon't make the conflict become any worse. When usinggit rebase, you need to sort out the semantic conflict while also having scrubbed the history of any clue as to the source of the bug.SanJJ_1@reddit
merge commit would work but pollute main branch. squash merge/rebase are great but create conflict when "stacking" branches or trying to catch up. Native stacking fixes all of these to my understanding.
MereInterest@reddit
I'd argue that this isn't "pollution" at all, and is just a display issue.
Want a clean linear history of the changes to the main branch? Use
git log --first-parentWant a branching history with clearly marked dependencies? Use
git log --graphWant a linearized history that interleaves work on multiple branches while sorting by date? No, nobody ever wants that, but that's what
git logprovides.The whole goal of squash/rebase is to make the output of
git loglook likegit log --first-parent. The easier way to achieve this is just to usegit log --first-parent.NineThreeFour1@reddit
I don't really see the issue and how this is directly related to stacked PRs. Rebase feature branches on main if you have conflicts. Squash your own commits how it makes sense conceptually. If your team enforces squashes on merge, then send each commit that you want separate as a separate PR. Merge feature branches into main when ready, or force rebase and fast forward to avoid merge commits.
MereInterest@reddit
I wish, wish, wish that github had a better display of the history of merges. The source of all the pain from squashed merges is from people trying to solve a display issue by introducing data integrity pitfalls. In any other context, this would be a complete non-starter, but somehow "clean linear git history" overrules that.
My personal theory is this is caused by github's use of
git loginstead ofgit log --first-parentwhen showing changes. If all merges are done withgit merge --no-ff, then the main branch would only ever contain merge commits. The clean history that proponents of squash/rebase want would be available, and without introducing conflicts in the process.gredr@reddit
Quit squashing. You don't pay per commit, you know.
lupercalpainting@reddit
You absolutely pay per commit. Each commit costs a bit of cognitive load for anyone looking at the git history.
If your commits aren’t each an atomic piece of work they also cost time if you need to revert just one feature.
wldmr@reddit
You can revert merge commits like any other. I don't see a problem.
lupercalpainting@reddit
No one said you couldn’t. You’re conflating merging with squashing
wldmr@reddit
I interpreted this to mean “If a merged feature is made up of several commits, then reverting that feature is more time consuming than reverting a squashed commit would be”.
Plus, what kicked this whole thing off was the comment:
So I don't think it's unreasonable of me to be talking about squashed merges.
topMarksForNotTrying@reddit
The benefit of squashing is getting rid of all the "fixed", "wip", etc commits that a lot of devs leave. If a PR/branch has commits with messages that all make sense, then there's no need to use squashing.
alexrobinson@reddit
This is where I suggest people should be rewriting the commit history on their feature branch to fixup those useless commits before merging into main.
lupercalpainting@reddit
And rewriting history frequently includes squashing.
topMarksForNotTrying@reddit
That's what i personally do but, honestly, it got tiring policing the commits of the rest of the team so we settled on targeted PR that get squashed.
gredr@reddit
You could've just... not policed people's commits, you know. Let go, be free, live in the moment!
NineThreeFour1@reddit
I can shoot myself in the foot to experience free will, but I choose not to.
KarasuPat@reddit
…which achieves the same thing as squashing, but with more work
Blueson@reddit
Except my first job, every place I have tried to push for this I have been met with resistance. People just CBA to work with PRs on a commit level and would rather squash and forget.
My first job made me understand how valuable having independent, working and split commits can be when debugging. I fear I'll never work in a codebase that can fullfill this again.
Omnipresent_Walrus@reddit
If your Devs are leaving those commits, they need retraining
Leihd@reddit
So which are you
LucasVanOstrea@reddit
You can push once a day you know? So it's very easy to use amend and git rebase locally during the day
ekroys@reddit
In small teams sure. But larger teams there are many other benefits in squashing. So much more readable
CherryLongjump1989@reddit
It's literally not an issue with stacked commits because you merge them one at a time. There are no two commits to squash as you merge.
maxhaton@reddit
Merge commits, usually.
kintar1900@reddit
Doesn't appear to be any difference at all.
ahal@reddit
The difference is that pr B can depend on changes from pr A without the commits in pr A polluting the diff.
The proposed epic workflow doesn't address this. It's just an integration branch.
kintar1900@reddit
You can do that just by branching from whichever commit was used for PR A. This is just extra UI fluff on top of (what should be) a commonly-known Git branching pattern.
ahal@reddit
Yes, but then PR B contains all the commits from PR A, so there's no way to review all commits from just branch B at once. You can review commit by commit, but that requires knowing which commit belongs to which and there's still no way to see a squash diff.
I do agree that this is simply a Github UI fix and there's no new fundamental branching model being introduced here.. But UI is very important. My org and I are very excited for this change.
kintar1900@reddit
Only if you're merging A to main first, or merging B to main instead of A.
Change A happens and creates a PR. Change B happens, based off A, and PR goes B->A. PRs are reviewed individually, then merged in reverse order.
It's literally the same thing, just without some UI hand-holding.
ahal@reddit
But then B has A's commits in it. You have to review commit by commit and need to know which commits are exclusive to B.
kintar1900@reddit
Read my comment again. B is based on A, yes, but is being merged to A, so the diff is only what was added in B. Each PR is reviewed independently, so even if you have a series like D->C->B->A->main, each PR is only the diff between the current branch and the parent. Since all changes are related, all PRs are reviewed independently. Only once ALL PRs are approved are they merged from right-to-left.
This is almost certainly what GitHub is doing under the hub with their PR stacks.
ahal@reddit
Ah I see! That works if you're pushing to the same repo that you're opening a PR against, but not if you're using a fork. That won't be possible for most (in my experience) cases. Even when I do have push access to a repo, I prefer opening PRs from my fork to keep the branch pollution down.
Also the UX of setting the bases for each PR is quite bad. Afaik, it's not possible to do from the cli (but maybe I'm wrong).
But you I do take your point that it's technically possible in some cases. Though, not in a way that I would consider usable.
Farados55@reddit
LLVM lives on main
CherryLongjump1989@reddit
Yes. It's different because you're not buddy-fucking a hundred of your coworkers when they find out that the files they've been writing a PR against have been deleted when your months-long epic got merged after its months-long code review.
dweezil22@reddit
They're orthogonal concerns. Let's agree that reviewing large PR's is sub-optimal. The what you want to do is code PR 1,2,3,4. Then you can have your team approve them as they get them and merge them to a central branch branch as they're approved with minimal toil.
Now that central branch could be
masteror it could befeature-foobar. The same concern applies.Now... some say "That's ok I'll just skip review on
feature-foobarand do review when it merges tomaster". However that breaks our invariant above about not relying on large PR reviews.Finally... you're right that feature branches directionally simplify this b/c you're less likely to have merge conflicts on a feature branch (OTOH making a rule that only 1 dev working serially on 1 feature branch is limiting).
Omnipresent_Walrus@reddit
Brother do you get paid by the syllable
dweezil22@reddit
Here's some AI assisted options to make you feel better.
Concise:
Concise for a 5 yo:
Omnipresent_Walrus@reddit
That's double points towards the "guys I'd never hire" award so good job I guess
dweezil22@reddit
I apologize for answering your question.
ReallySuperName@reddit
Well to be honest that's the first time I've ever heard of that approach. Pull requests to branches that are not master? I suppose that works. It sounds a bit like some agile koolaid stuff though, calling it an epic.
topMarksForNotTrying@reddit
Stacked PR don't necessarily have anything to with agile or product management.
The way i use them is to split a piece of work into multiple stages. Let's say you're changing an old part of the code base and you determine that you should refactor some code. You do the refactor and open a PR with those changes. Then, you do the actual code change that you were supposed to do and open a PR but target it to the first PR.
This way it's easier for the reviewer to review each change individually instead of having the refactor and the change request all together.
Think of it like being able to review the individual commits of a PR branch.
Omnipresent_Walrus@reddit
It's just what I've always heard them called. I think it's because of how Jira organises things as tasks under an epic task. Works well in that workflow.
Lewke@reddit
github makes another feature enabling terrible development practices to continue, more at 11
teapotrick@reddit
Why is this bad, and what is good?
Lewke@reddit
trunk based development and CI remove the need for any of this coordination overhead, along with actually trusting the people who work for you and helping them improve
teapotrick@reddit
stacked PRs are an orthogonal concept to trunk based development and CI.
to me this seems like a way to split a single PR into multiple more digestible PRs for the reviewer, and giving commits a tighter conceptual scope for the developer.
Lewke@reddit
if you have to use stack based PR's then you arent doing CI, as stacks cant develop in such short time without serious lack of care
ReceptionBrave91@reddit
I built a better one for agents!! Built-in worktree support, a CLI designed for agents (no interactive flows), and designed for parallel agents to work in parallel
https://github.com/rohoswagger/ez-stack
teerre@reddit
Unironically the best update ever. If the implementation doesnt suck ofc
thehenkan@reddit
From what I hear it doesn't work between forks, only between branches from the same repo. We prevent users from creating branches and require them to fork instead, so until they fix that it's still unusable for us :/
Rodr500@reddit
Just out of curiosity, why do you fork instead of branching?
thehenkan@reddit
It's not my decision, but the main reasons I'm aware of are: - in a big code base with thousands of contributors, everyone pushing their personal branches to the main repo would degrade the time it takes to fetch - in cases where you have a downstream part and an upstream part, and you accidentally push some sensitive stuff to upstream, people are less likely to be watching everyone's forks than the main repo, so if you discover it quickly you may be able to prevent the leak by just deleting the branch (and contacting Github to have them wipe every reference to those commits)
mtutty@reddit
Or, you know, iterate. With planning and purpose instead of just YOLO'ing until it works.
And BTW, legitimately large merges aren't gonna benefit from this anyway because the system won't work at each step without all of the dependencies from later commits in place. So you can read the code but can't prove each step anyway.
Jeez guys, this really needed to be a thing? We invent better duct tape to keep the stovepipe going.
AMartin223@reddit
Can someone explain how/why this workflow makes CI run against the final branch for all the PRs? Normally CI would run against the changes up until the given point in the stack, but the docs say this new feature triggers CI from the ref of the final PR in the stack, how is that safe?
Marble_Wraith@reddit
Even if they introduced it at this point... it's too late.
https://www.neowin.net/news/microsoft-copilot-is-now-injecting-ads-into-pull-requests-on-github-gitlab/
Rodger_Ramjet@reddit
This kinda feels like one of those things that has legitimate usages but ends up misused - encouraging anti patterns…
Surely as a profession, we should be moving away from long-lived branches and shipping faster incremental changes - dark launching etc?
sailing67@reddit
finally. been waiting for this forever lol
narsilouu@reddit
Still can't believe we put up with Github at this point...
Gerrit was much better 10 years ago that it is now...
Eric848448@reddit
It's about goddamn time! We wrote a tool at work to simulate this YEARS ago.
Urik88@reddit
This is great, is there info about how rebases after merges are done? Having to rebase all of my PR's every time a pr on my manual stack was merged was always a big pain for me
quetzalcoatl-pl@reddit
if you do not like rebases, try using jujutsu over your current local git repo/wc
https://v5.chriskrycho.com/essays/jj-init/
https://steveklabnik.github.io/jujutsu-tutorial/introduction/introduction.html
I have first hated rebases as unnecessary as merges do merge the code well enough. Then I learned `git rebase -i` and rebase plans in-depth and used it all the time for cleanup, reorders, repairs, updates and all, while still preferring merges once the code and history was OK to publish. But merges do not play well with rebases, even with --update-refs.
Then I learned `jj` and basic language for `jj rebase`. Plus `jj undo` xD
and I'm not moving back, unless someone pays me 2x :P
nah, but really, it just handles the tree reordering so well and saves so much time, it's simply no point in not-using it, considering everything is still git-based. Things like git seeing weird things during jj conflict resolutions are minor and almost unperceivable after a bit of getting used.
and yeah. conflict resolutions. It's probably even bigger game changer than "current commit" idea and "rebase on steroids". Remember how when in git a conflict shows up, you're stuck untill you reset or solve it? In JJ you can just continue. Yes, files are broken. But you can do your merge/rebase/edit/reorder/everything as you like, and go beck and fix the conflicts once you (and shape of the repo/file/history/etc) is ready for that. Conflicts may even solve themselves if you finish moving or commits around or squashing them.
So far, the ONLY real irritating issue I found is handling \r\n on Windows. In pure Git, so far, it was always the best idea to use autocrlf=true or input. With JJ side by side, setting it 'true' is asking for issues. Or so it was for me, maybe I configured somethign wrong. Anyways. I'd suggest either using "input" and be vigilant for occasional mixups, or "false" and set (or teach:)) your IDE to not emit \r. In '90s and '00s that was quite a challenge, but today most editors and IDEs now handle that well.
lotgd-archivist@reddit
I toyed around with it a little and found that it's really antagonistic to how I work. The biggest bummer is that it simply has no upstaged changes. I frequently find myself floating changes so I can very quickly switch branches with those changes still in effect. For instance I wrote a benchmark for a library I was contributing too and had to change the build files of the library to make the benchmark work. And then I ran that benchmark on 3 different branches.
You know of a way to work that way without having to
jj undoandjj redoor cherry-picking commits and stuff like that?quetzalcoatl-pl@reddit
Yeah, I think I understand you well!
in short: not yet, but maybe
I do that too all the time! The more I used git, the more "floating edits" I tend to have, either to be able to switch like you said, or to selectively commit them here and there. It gets progressively harder to manage and sort out as the pool of edit grows.
Even with git alone, this 'taught' me to make a lot of small, named, independent commits. Or even unnamed. But 'independent' is the key. Do them in a way I can easily rebase/cherrypick/cut/paste them around at will into any place or order. Your example of a benchmark is a great example. If you can switch to/from a branch while having it floating with no conflicts, it sounds perfect for such temporary disposable commit.
Having lots of commits, with content to be preserved and pushed out, or content to be eventually removed, is a hassle. That's why I eventually mastered git-rebase-i and probably heavily overused it, and that's why I love JJ now.
In terms of pure git, if the 'benchmark' files are totally separate there is a way to create a commit IIRC 'orphaned' that has no parent. Drop the files there, and then merge it wherever you want with no conflicts. But merging and splitting them is a hassle. JJ should be able to work with that as well the same way and make it much easier, but still some hassle. But it obviously won't work if you need to have some parent files. Then you have to find "common base" between all branches you are going to move between, and create the "common benchmark commit" on top of that, like any other feature branch, and them merge/unmerge from that point. More hassle.
That's moreless how I'd keep it without JJ, and I did it a few times, and .... because I did not like "the hassle" I stopped doing it, and started having "floating uncommited changes", which are cool, but only as long as you have, say, up to 2-3 such parallel "bags" to tag along. Then it usually gets tangled mixed and no longer independent enough.
Anyways. Having JJ and better rebases, I'd just commit it. Or let JJ commit it for me as it does. Then, instead of `git checkout B` (or `switch` as they name it now), I would `jj rebase -s @ -d B` or something like that. Meaning, "move 'currentcommit' onto B". Since "I had changes", when I run any command, JJ will automatically commit the changes as "@" on top of HEAD, then execute the command - and will move the changes to where I wanted to go, to B. And since it encompassed @, after the command you'll end up sitting at '@', with your changes, placed on 'B'.
Of course `jj rebase -s @ -d B` is much more verbose, but since it has only 1 actual parameter (B) and it can be at the end, it can be easily aliased into something shorter.
To be honest, I do not see any difference to how I work.
If I have a lot of current changes to sort out, I still tend to use my old git client, since I'm used to it and I find file/chunk/line staging&committing rather fast, and I do not like that tool provided with JJ (or didn't learn it enough to like it :P). In most cases, I can quicky go over changes and sort them into set of commits with old git tools. But after that, I sort and reorder those commits into proper branches with JJ, since it's easier/quicker/more expressive.
For that matter, I rarely use `jj new` in the way the authors wanted (so do changes, describe, new, do changes, describe, new, ...). But I use it a lot to move around (a.k.a. git checkout/switch). Also I found myself using `jj edit` quite often - it's great for splitting large commits or fixing some typos (while 'canonical' way would be to `jj new X ; then edit typo ; then jj squash`).
In addition to that, JJ adds some more tools for various cases, like `jj parallelize` (turn string of commits into a fan of 'branches') - which sounds odd, and "there's no problem doing it manually" with rebase, but when you know upfront that you need to something like that, it's just awsome to toss a single command instead of, say, 5 rebases :D
But! for poking-around-the-codebase, there's one command I'm, still learning, called `jj absorb` and "mega merge" workflow. In theory you sit on top of octopus merge, edit whatever, then use `absorb` to 'send changes to correct branches' to 'already modified files' + occasional new commit and small rebase to 'add totally new changes to specific branch, so absorb knows where to send more of them' sounds like ultimate way of 'poking the codebase' and feature branches but I'm still too new to that to say if there are any pitfalls etc.
lotgd-archivist@reddit
Wouldn't that commit the benchmark-related changes into my feature branches, so I'd have to remove them from the branch later when I actually want to submit the review?
That's not quite what I mean. I mean that after a couple hours of randomly poking and refactoring, I have 10, 20, 30 unstaged files. I then
git checkout -b Feature_A, stage the changes for that branch, commit, publish branch,git stash; git switch -; git checkout -b Feature_b;, repeat. Or I just create individual commits out of the unstaged changes one at a time and simplypush, if I'm already on a relevant feature branch.I don't need to rewrite history or rebase, because I have no history to change (yet) and no commits to rebase.
quetzalcoatl-pl@reddit
> Wouldn't that commit the benchmark-related changes into my feature branches, so I'd have to remove them from the branch later when I actually want to submit the review?
Uh,.. no, but yes, but no? :)
JJ does not update branches tips until asked to. At least mine didn't do that. Maybe there is an option for it.
When I issued sth like `jj rebase -r Foo -A Bar` it moved the 'foo' commit onto Bar, but didn't update any branches labels/bookmars that were sitting on Bar.
So if you were on branch1 B with your benchmark-unstaged state, and C was the tip of your "branch2", and then `jj rebase -r @ -A C`, then after that command:
- you'd totally remove the benchmark from branch1
- you'd have the benchmark 'pasted' on top of C as a commit '@', but "branch2" would still point to C
- your working copy state would branch2, plus 'benchmark' as current @, visible to JJ like that, but from git's point of view you'd be on C with 'benchmark unstaged' state
Of course if you added new commits on top of that state, then you MIGHT need to clean up before pushing. But I think that's kinda obvious, right? The cleanup might be as easy as moving 1 commit (the benchmark) to the tip, to cut it away when moving to branch3. It all depends on how you commit.
For example, if in this state you'd add new commits via git - then it just works as it did - git doesn't see @, it sees C as your current commit, and adds new commits on top of that. @ detoriates. On next scan, JJ synces with git, updates @ to reflect where you really are with git, one-to-one, perfect image of your current state, but the "old @" is left dangling and `jj log` WILL show that "old @" to you as dangling commit. You can `jj abandon` it later. That's the only cleanup needed.
For another example, if you added new commits via JJ - then JJ adds them on top of @(benchmark) moving @ to new tip. Everything shifts, but if you know which commit was the benchmark, you can `jj rebase -r X -A @` to have it again at the tip, and to make it again the 'movable unstaged changes'. Again, that one command is all the 'cleanups' needed.
And I bet there are couple more options that I can't think of right now.
But! I know, I say and write a lot now, but I am not trying to convince you it's the best way :D it seems possible, but certainly doesn't mean best.
What I really do love in JJ the most, is that I can actually seamlessly switch between GIT and JJ. I think I dove into details but didn't stress it enough. Yeah, I just dumped a wall of text about how to reproduce "floating changes" from GIT into JJ - but the truth is, I didn't need it and you don't needed either, because even having JJ active in your repo, you could just do the same thing as you always did. JJ would just sync with new git state afterwards, and there would be some 'dangling commits', exactly like in first "For example" above, and you could then just `jj abandon` them anytime.
So sorry for realizing that just now :) The easy way eluded me, "it's doable in JJ directly" sounded too tempting
re: new language for referencing commits - yeah, I'm still learning it. Harder, but has much much more potential. All the times when I wanted to find something via git and it was hardly possible without heavy scripting - often in JJ it's just one lookup. But yeah, tailoring it right means sitting with the docs for haf an hour usually :)
lotgd-archivist@reddit
I think the main problem I have is that I have no good feeling for what model jj uses.
Git I understand to be a graph of labelled diffs. Then you have your pending changes and a subset of those are the staged changes earmarked for a new node on the graph. And all git operations are just manipulating that graph.
With JJ, I'm not entirely sure what the abstraction is or how to reason about it.
quetzalcoatl-pl@reddit
heh.. I totally agree here. The docs weren't really helpful. I mean, they were, but.. the things they wrote were explained rather well, but it didn't "ring" to me. Automatic commits and `jj new` sounded absolutely weird to my mind used to git. I could basically write exactly the same what you wrote here :) including reflog :)
What made me try JJ was the note about "different handling of conflicts", and description of how "jj absorb" works. First week was hard. After that I focused on 3 commands: jj rebase, jj squash and jj new (didn't notice that "edit" exists yet), for everything else - plain git. And after next week, something "clicked" and I just "got it". Frankly, even if I tried, I can't explain how the JJ "patch/change" model differs from Git "commit/content" model. In most cases, it's almost the same. In the few cases it differs - I still perceive it as "like in git but...", and to this extent I think I understand why the docs were written that way..
I think the greatest hill to climb on for me was the autocommit, which I eventually got when it occured to me as meta-idea that it's like "everything is a file" in linux, but here: everything is commit ; yes, it's new changes and unstaged, but let's say that diff is extra commit '@' to just be able to treat everything uniformly. >!Technically it's not like that, @ is a physical real commit, and git state is intentionally kept at "1 commit behind", and git doesn't like it sometimes, but heck, "mentally" this image helped me a bit.!<
I'm tempted to write more, but I think it would end up huge again if I start on "how I perceive JJ's model" geesh.. I don't know. From files and folders and versioning -- it's the same as Git. Commands change, semantics change (sometimes), but the model for file/diffs is, I think, the same.
If I were to describe what JJ really is internally, ignoring that whole porcelain layer of commands, similar to describing Git as "graph of labelled diffs (of files/folders/content)", I'd say that JJ is all of that, plus an extra Git-like layer for that "graph of labelled diffs".
After all, in Git, you have a certain state of "graph of labelled diffs", and you work on it, and by committing and by setting tags and moving branches, you alter it into new state of "graph of labelled diffs". The graph changes. Nodes are added/deleted/altered, labels are added/deleted/altered. So you can version the graph (which is used to version your files). For me, this is what happens in JJ, and it pretty much immediately explains things like `jj oplog` and `jj evolog`.
I hope I didn't waste your time :) If you ever try JJ again, I hope you'll find it as useful as I did.
(snip)
quetzalcoatl-pl@reddit
re: (snip) above.. ....because I got too talkative and it grew WAY to loong. I you feel like reading more, here it goes. But I think I'm talking about the same things all the time. Not sure if there's something valuable here. I might be tired and there might be little value below.
Anyways!! it was awesome to exchange ideas/issues/observations with you, thank you very much! and sorry for another wall of text. I could probably compress it with an LLM or something. Feel free to do as needed :D
------
For me, the growth/getting used to - was rather slow. After few days of using, I almost dropped it completely. It sounds funny, but I think I was simply too good at doing complex things via Git, and the gap between what-I-can-do-in-Git vs in-JJ was just to jarring.
After that "few days", I happened to screw up complex rebase. All via Git, because, I was already in "screw JJ" mode. Naturally I just resetted/backtracked, by collecting SHAs, restoring all screwed up local branches and making them point to what they were before, etc. But then, it occured to me - ok, let's try screwing it up with JJ. I tried making the same parts of that complex rebase with JJ, and ... yeah, screwed it up as well. It was even worse actually. But I learned about `jj oplog` and `jj undo` (*). and .... and I just saw its "power". I mean, it reverted my whole screwed up repo, restored all branches, and even allowed me to jump back and forth between "screwed up" and "all good" states of the whole local git repo. So... there had to be something in JJ that vanilla Git didn't have. So I found my motivation to 'dreadfully' work with JJ for a few weeks more, learning new commands and switches from time to time, until I got more fluent over time and the perceived "jarring gap" (mostly) disappeared. It wasn't easy, but I consider it well worth ;)
(*) I wanted to say that this extra versioning layer added by JJ's (with lots of info and handy ready to use tools), compared to reflog (you get some descriptions and SHAs, but doesn't get any tools) feels a bit like comparing CVS (reflog) to Git (JJ's graph versioning), but it's ... wrong? It sometimes does feel like that, but describing it that way simply over-advertises and promises too much. I think the `oplog` is conceptually mostly like reflog. Only `evolog` is a new beast, it's like "show commit history of single file" in Git, but (since JJ versions the commitgraph's evolution) 'evolog' shows you "how a commit changed" (it started being on branch A, then it was rebased to branch B, then it was edited/amended, then it was...). Anything like that in Git? No. Is it useful? Sometimes. As with any new thing, it's not necessary, but when need arises, it's presence is godsent. I use JJ for about a year now. I used `evolog` twice. Quite .. not jaw-dropping, right? But I'd waste a lot of time if it didn't exist. Like with having-backups or not-having them.
But honestly, from everydays work point of view, that extra layer matters little. What matters more is command syntax and porcelain. After some time of using it, JJ's commands' parameters are a bit more "uniform" in their design than Git's. Also JJ's things like "autocommits and @" and "non-blocking conflicts" matter MUCH more (than oplog or evolog), and the way how JJ handles them (@&conflicts) is mostly irrelevant to "JJ model" or "what JJ really technically provides" - Git could handle it the same way, it's just Git's "(design, ux) choice" to not do it that way. Similarly, JJ's "choice" to not allow to easily stage&commit just a line or two from current changes - sucks a lot - I love that Git feature...
but the versioning how the whole commit/branch graph evolves.. that's just wild. I love reflog in Git, even if I use it 2-3 times a year. Similarly I love to have JJ's graph-versioning, even if it is, in fact, rarely used directly :D
steveklabnik1@reddit
You can create a merge, so use
jj new a b, test, thenjj new a c, and then test, assumingais where you created the benchmark. Then abandon those merges when you're done withjj abandon foo.lotgd-archivist@reddit
The benchmark was outside the repository, because it was just for my own testing.
awas the upstream main branch,bandcfeature branches.steveklabnik1@reddit
Oh, then you should just be able to
jj new bandjj new cand when you move away, they'll be automatically abandoned, since they're empty.CondiMesmer@reddit
Sounds like a fighting move
CherryLongjump1989@reddit
I could never figure out why some people have such a hard time with rebasing.
Urik88@reddit
Because sometimes the experience sucks.
Suppose I have branch A with commits 1,2,3. The I have branch B coming off a with commits 4,5.
I merge branch A, then I have to rebase branch B on top of master.
But hold on, looking at the log of branch B, it's got commits 1,2, 3,4,5 and I have to remember to rebase starting from commit 4.
And if branch A was squashed and I happened to forget that branch B starts on commit 4, now I have to find out what was the last commit in branch A in order to find what commit to rebase from in B.
Now each branch has dozens of commits, and my stack has 5 different branches that need rebasing every time one of them is merged, you can see why it's not a pleasant experience.
CherryLongjump1989@reddit
Yeah I can't figure it out. It's like riding a bicycle.
murkaje@reddit
People squashing code on merge is the proverbial putting a stick in the spokes of your bicycle. Some just want to hurt themselves.
Urik88@reddit
Well, how'd you avoid big PR's on big initiatives where you need PR's building on top of each other if you wanted to avoid this scenario?
Believe me I'd love nothing more than to stop pedaling with my hands
CherryLongjump1989@reddit
Don't pedal with your hands. I don't know what gives you the idea that rebasing means you can have the same 5 commits applied in random order to 5 different branches, with some of the commits squashed, and also merged out of order onto the remote main branch. You're describing a problem with your local setup that you created -- no one else created it for you. The remote master is still fine. There is no problem, except you. So don't create the problem for yourself. Do it differently.
You can still unfuck yourself if you just slow down and go by the numbers. Create a clean branch off the remote master and cherry pick the local commits that you want to exist on your new branch.
murkaje@reddit
That's the problem. Having proper commits before and after a merge will avoid any headaches during rebase.
Reading code changes doesn't stop after it's merged. Weeks/months/years later when discovering a bug it's easier to bisect through smaller commits to figure out what was done.
steveklabnik1@reddit
jj really helps with this, because it:
raskinimiugovor@reddit
Why do you have to rebase B on top of master after merging A? Just so that B appears to branch out of your PR commit instead of 3rd one?
Urik88@reddit
My PR had branch B pointing to branch A so that reviewers see only the commits in branch B (4,5).
Otherwise I'd have 1 pr (A to master with commits 1,2,3), and another pr (B to master with commits 1,2,3,4,5, which defeats the purpose of having a separate branch).
As soon as the PR for branch A gets merged into master, my PR for branch B that was pointing to A will get automatically redirected onto branch master and very likely have conflicts, at least that's the case in Github.
Moreover the commits from branch A were squashed into a single commit in master, but B is branching off from a previous state and its most common ancestor to master includes the pre-merge commits of A, so reviewers will see that the PR for branch B includes commits 1,2,3,4,5, while in practice commits 1-3 are already in master, in only want commits 4,5.
So I'd have to run
git rebase --onto master 3to ensure that I'm carrying over only the commits that started on branch Braskinimiugovor@reddit
I feel like most complexity here comes from complex dev or PR strategy than git being git.
If you weren't using squash PRs, after merging PR A, commits 1-3 would disappear from PR B, as they would be recognized as already merged on main.
If your goal is to squash commits 1-3, why not squash them before creating branch B?
If this is a scenario where you work in parallel on A and B, but branch out B from A, then go back to tinker with A, isn't that more of a problem with the workflow or PR strategy than git?
raskinimiugovor@reddit
You could rebase B on top of A instead, before merging A?
chuch1234@reddit
Don't edit existing PRs, open new ones to address feedback.
When you say "rebase ... when merged", what specific merge are you talking about?
ahal@reddit
There's a lot of folks arguing that stacked PRs aren't adding anything you couldn't already do before. But that's not the case. Currently you need to pick two out of the following three benefits:
With stacked PRs, it will now be possible to obtain all three of these benefits at the same time. No previously existing native Github workflow can claim this.
lechatsportif@reddit
How big are these PRs? Especially in the agentic coding era, people are regularly dropping thousand line PRs and it only takes me maybe 5-10 minutes to review the thing. It's a skill to develop like any other. You can also have Claude help you review and speed up the resolution process. I think I would go crazy if I had to break apart PRs in into multiple commits and review each one.
We also use microservices, so maybe this is a monolith thing only?
lechatsportif@reddit
Is this really a problem for people in general? we deal with massive prs all the time, maybe a conflict once every 3 months and its usually something caused by an errant pipeline integation. This sounds like a solution for orgs that have bigger problems in process and sdlc impl.
peterprank@reddit
what was wrong with rebase --update-refs ?
masklinn@reddit
Nothing. In fact they work nicely together.
Forbizzle@reddit
Can you do this without the CLI? If not, why?
masklinn@reddit
Yes, you can do it via the web UI.
desmaraisp@reddit
Apparently yes, there a page in the doc on that. You have to target FeatureBranchA from FeatureBranchB's PR to get the checkbox to create the stacked PR
Feeling_Ad_2729@reddit
What's interesting is how long stacked diffs have been "solved" in adjacent tools while GitHub's PR model stayed static.
Phabricator had proper stacked diff support back when Facebook was using it. Gerrit has had this since the beginning. Graphite built an entire company around making this workflow ergonomic on top of GitHub. GitHub implementing it natively is good, but it's years behind.
The core issue is GitHub's mental model: "a PR = a change" rather than "a commit = a change." Stacking implies each commit is independently reviewable, which maps badly onto branch comparison. Gerrit works around this by making commits the primary unit of review. GitHub is retrofitting that concept onto a paradigm not designed for it.
Still: better late than never. The main incentive to use Graphite specifically gets weaker if GitHub does this natively.
paperlantern-ai@reddit
The biggest win here isn't even the branching model - it's that reviewers can finally look at a 2000 line feature in digestible chunks without losing context between separate unrelated PRs. Half my review fatigue comes from opening a massive PR and just going "LGTM" because ain't nobody got time for that. If this gets people to split work into smaller pieces because the tooling finally supports it, that alone is worth it.
piljoong@reddit
Really glad to see this.
At a previous company, we had a lot of pain around PR chaining in our TBD workflow, and I ended up building a stacked PR tool internally because we needed it badly.
Seeing GitHub ship official support is pretty satisfying. This has been a real problem for a long time, so it's good to see it become a first-class workflow.
jedi4545@reddit
I hate to be a nerd but this is technically a queue of PRs.
SharkBaitDLS@reddit
Being able to do this with a proper UI was the biggest thing I missed after quitting Amazon. Immediately requested access for my tram.
hipsterdad_sf@reddit
The biggest practical win of stacked PRs is that your CI pipeline runs against something closer to what will actually land in main. With the old "chain PRs against each other" approach, your CI on PR C was testing against B's branch, not against main + A + B together. So you could pass CI on each individual PR and still break main on merge.
The squash merge problem someone mentioned is real though. If you're squashing on merge (which most teams do), the commit SHAs change and every subsequent PR in the stack suddenly has conflicts. GitHub would need to automatically rebase the rest of the stack after each merge for this to feel smooth. Otherwise you're manually rebasing 3 PRs every time the first one lands.
The other thing I keep running into: reviewers treat each PR in the stack like an isolated change and leave comments that only make sense if they could see the full picture. "Why are you adding this interface with only one implementation?" Because the second implementation is in the next PR. You end up spending half your review time explaining the stack structure instead of discussing the actual code.
bobyn123@reddit
I see we're into the extend phase of embrace, extend, extinguish. The murder of all open source projects.
DigThatData@reddit
finally
iiiiiiiiitsAlex@reddit
Oof. I implemented this in https://getcritiq.dev not 2 weeks ago. Gg GitHub!
RustOnTheEdge@reddit
Off topic but I really REALLY hate websites (and products) that: 1. Pretend a cookie banner is mandatory by GDPR, it isn’t if you don’t use tracking cookies. 2. Make a snarky claim of not using, but then proceed to place a Cloudflare cookie.
That is bad vibecoding.
iiiiiiiiitsAlex@reddit
I actually did this completely on purpose. Yeah the site is hosted on cloudflare, it is what it is - waiting for a server in the mail so i can move out of cloudflare and host it myself actually 😅 due to this.
But Critiq the product I’ve tried to go out of my way of making privacy focused, which is why that cookie banner is there. Not vibing, just marketing 💁
Alternative-Walk9643@reddit
Chances are your privacy-focused target audience actually understands and generally likes GDPR, CPRA, and the likes. Not sure if posting "Ha ha, I don't care about regulations around privacy and think it's stupid" on your website underlines your stance on privacy as you think it does.
iiiiiiiiitsAlex@reddit
Not really the target audience, its more of an FU to products such as sourcetree and gitkraken, but I see where you are coming from. It’s about building a product that solves the problem without the need to capitalize on users data. The privacy and terms underlines the commitment, quite extensively, i think.
But sure “cookie banner for no cookies bad, apparently you hate gdpr”. There are more pitchforks in the cellar, torches are extra.
deepakmardi@reddit
Feels like a small feature, but it could save a lot time
coolbaluk1@reddit
Might be a good alternative to graphite. It was always a bit too complicated and they changed their cli too often.
Spitfire1900@reddit
Has graphite done any value adds that are over and above hacking a better workflow on top of GitHub? I know GitHub is slow to meaningful improve nowadays but graphite’s core feature has no moat.
Edgar_Allan_Thoreau@reddit
Their merge queue is better than GitHub’s merge queue. It supports enqueueing an entire stack of PRs with one click (like what GitHub says it will provide in this announcement), and supports partitioning the queue so changes can merge unblocked by other unrelated changes (which in a monorepo with high throughput and complex ci is very valuable, it means what used to take 3 hours for a readme PR to merge waiting behind a bunch of backend and front end PRs can now merge nearly instantly).
coolbaluk1@reddit
We never used their dashboard but used the cli extensively to do PR stacking.
i.e. db schema changes at bottom then a PR each for each microservice change Frontend on top.
So yes it was just improvements on top of github and you could likely achieve the same without it, but it made code reviews so much more pleasant than a blank LGTM.
HommeMusical@reddit
This is a blatant, blatant rip-off of
ghstack: https://github.com/ezyang/ghstackI'd like to see how different they are, but Github doesn't even mention the prior art.
The
ghstackworkflow is extremely good, particularly for projects with a high velocity of commits accepted.I assume this will be just as useful, but I can't get the taste of theft out of my mouth.
DerelictMan@reddit
There's more stacked PRs tools out there than you can shake a stick at, including
sprand my own reimplementation ofsprcalledjaspr:https://github.com/ejoffe/spr https://github.com/MichaelSims/git-jaspr
Gerrit was originally released in 2009. This is a very old idea.
wildjokers@reddit
ghstack is doing nothing that you can't already do with
git --update-refs.dkarlovi@reddit
Stacking itself has prior art, the tool you link didn't invent it, no?
ZukowskiHardware@reddit
Just another way to put way too much into production at one time
boysitisover@reddit
Looks good to me
spelunker@reddit
Ship it!
StrangelyBrown@reddit
~~TLDR~~ LGTM
ROFLLOLSTER@reddit
Agreed. I do kind of wish this was automatic if you made stacked PRs against main. Maybe that'll come later once it's been tested.
jingo04@reddit
Has this not been historically solved by having several commits stacked into one PR?
Ladsome@reddit
Damn, gimme gimme!
This would make my workflows at work so much better, I hope I can sneak into the preview...
rismay@reddit
lol. What Google has had for 20 years
shoffing@reddit
Yup, heh. Hopefully this can be adopted for use by JJ. jj-spr handles stacking PRs, but it would be nice to have native support for PR diffbase/children in the UI.
kintar1900@reddit
So...a tool that does what any decent developer and PR process should already be doing? ... Yay.