| ▲ | akersten 8 hours ago |
| Does it fix the current UX issue with Squash & Merge? Right now I manually do "stacked PRs" like this: main <- PR A <- PR B (PR B's merge target branch is PR A) <- PR C, etc. If PR B merges first, PR A can merge to main no problems. If PR A merges to main first, fixing PR B is a nightmare. The GitHub UI automatically changes the "target" branch of the PR to main, but instantly conflicts spawn from nowhere. Try to rebase it and you're going to be manually looking at every non-conflicting change that ever happened on that branch, for no apparent reason (yes, the reason is that PR A merging to main created a new merge commit at the head of main, and git just can't handle that or whatever). So I don't really need a new UI for this, I need the tool to Just Work in a way that makes sense to anyone who wasn't Linus in 1998 when the gospel of rebase was delivered from On High to us unwashed Gentry through his fingertips.. |
|
| ▲ | sameenkarim 6 hours ago | parent | next [-] |
| Yes, we handle this both in the CLI and server using git rebase --onto git rebase --onto <new_commit_sha_generated_by_squash> <original_commit_sha_from_tip_of_merged_branch> <branch_name>
So for ex in this scenario: PR1: main <- A, B (branch1)
PR2: main <- A, B, C, D (branch2)
PR3: main <- A, B, C, D, E, F (branch3)
When PR 1 and 2 are squash merged, main now looks like: S1 (squash of A+B), S2 (squash of C+D)
Then we run the following: git rebase --onto S2 D branch3
Which rewrites branch3 to: S1, S2, E, F
This operation moves the unique commits from the unmerged branch and replays them on top of the newly squashed commits on the base branch, avoiding any merge conflicts. |
|
| ▲ | xixixao 8 hours ago | parent | prev | next [-] |
| Conflicts spawn most likely because PR A was squashed, and once you squash Git doesn't know that PR B's ancestors commits are the same thing as the squashed commit on main. No idea if this feature fixes this. Edit: Hopefully `gh stack sync` does the rebasing correctly (rebase --onto with the PR A's last commit as base) |
| |
| ▲ | akersten 8 hours ago | parent [-] | | > Conflicts spawn most likely because PR A was squashed, and once you squash Git doesn't know that PR B's ancestors commits are the same thing as the squashed commit on main. Yeah, and I kind of see how git gets confused because the squashed commits essentially disappear. But I don't know why the rebase can't be smart when it sees that file content between the eventual destination commit (the squash) is the same as the tip of the branch (instead of rebasing one commit at a time). | | |
| ▲ | skydhash 3 hours ago | parent [-] | | Because at first your have this main <- PR A <- PR B
Then you'll have main, squashed A
\
\-> PR A -> PR B
The tip of B is the list of changes of both A and B, while the tip of main is now the squashed version of the changes of A. Unless a branch tracks the end of A in the PR B, It looks like more you want to apply A and B on top of A again.A quick analogy to math main is X
A is 3
B is 5
Before you have X + 3 + 5 which was equivalent to X + 8, but then when you squash A on on X, it looks like (X + 3) + (3 + 5) from `main`'s point of view, while from B, it should be X + (3 + 5). So you need to rebase B to remove its 3 so that it can be (X + 3) + 5.Branches only store the commits at the top. The rest is found using the parent metadata in each commits (a linked list. Squashing A does not remove its commits. It creates a new one, and the tip of `main` as its parent and set the new commit as the tip of `main`. But the list of commits in B still refer to the old tip of `main` as their ancestor and still includes the old commits of A. Which is why you can't merge the PR because it would have applies the commits of A twice. |
|
|
|
| ▲ | patrickthebold 6 hours ago | parent | prev | next [-] |
| I'm not sure I follow your workflow exactly. If PR B is merged, then I'd expect PR A to already be merged (I'd normally branch off of A to make B.) That said, after the squash merge of A and git fetch origin, you want something like git rebase --update-refs --onto origin/main A C (or whatever the tip of the chain of branches is) The --update-refs will make sure pr B is in the right spot. Of course, you need to (force) push the updated branches. AFAICT the gh command line tool makes this a bit smoother. |
|
| ▲ | pastel8739 7 hours ago | parent | prev | next [-] |
| I agree that this is annoying and unintuitive. But I don’t see the simplest solution here, so: All you need to do is pull main, then do an interactive rebase with the next branch in your stack with ‘git rebase -i main’, then drop all the commits that are from the branch you just merged. |
| |
| ▲ | adregan 5 hours ago | parent [-] | | I typically prefix my commit messages with the ticket number to make it easier to spot the commits to drop. |
|
|
| ▲ | gregoryl 8 hours ago | parent | prev | next [-] |
| If I'm following correctly, the conflicts arise from other commits made to main already - you've implicitly caught branch A up to main, and now you need catch branch B up to main, for a clean merge. I don't see how there is any other way to achieve this cleanly, it's not a git thing, it's a logic thing right? |
| |
| ▲ | akersten 8 hours ago | parent | next [-] | | I've no issue with the logic of needing to update feature branches before merging, that's pretty bread and butter. The specific issue with this workflow is that the "update branch" button for PR B is grayed out because there are these hallucinated conflicts due to the new squash commit. The update branch button works normally when I don't stack the PRs, so I don't know. It just feels like a half baked feature that GitHub automatically changes the PR target branch in this scenario but doesn't automatically do whatever it takes for a 'git merge origin/main' to work. | | |
| ▲ | skydhash 2 hours ago | parent [-] | | > the "update branch" button for PR B is grayed out because there are these hallucinated conflicts due to the new squash commit Those are not hallucinated. PR B still contains all the old commits of A which means merging would apply them twice. The changes in PR B are computed according to the oldest commits belonging to PR B and main which is the parent of squashed A. That would essentially means applying A twice which is not good. As for updating PR B, PR B doesn't know where PR A (that are also in PR B) ends because PR A is not in main. Squashed A is a new commit and its diff corresponds to the diff of a range of commits in PR B (the old commits of PR A), not the whole B. There's a lot of metadata you'd need to store to be able to update PR B. | | |
| ▲ | akersten 13 minutes ago | parent [-] | | I guess to me, I'm looking at it from the perspective of diffing the repo between the squashed commit on main and the tip of the incoming PR. If there are merge conflicts during the rebase in files that don't appear in that diff, I consider that a hallucination, because those changes must already in the target branch and no matter what happened to those files along the way to get there, it will always be a waste of my time to see them during an interactive rebase. I don't think we need to store any additional metadata to make the rebase just slightly more smarter and able to skip over the "obvious" commits in this way, but I'm also just a code monkey, so I'm sure there are Reasons. |
|
| |
| ▲ | Smaug123 8 hours ago | parent | prev [-] | | No, it's a Git thing arising from squash commits. There are workflows to make it work (I've linked the cleanest one I know that works without force pushing), but ultimately they're basically all hacks. https://www.patrickstevens.co.uk/posts/2023-10-18-squash-sta... | | |
| ▲ | heldrida 7 hours ago | parent [-] | | This is actually a reasonable workflow. Although requires some preparation. I’ll try it out! | | |
| ▲ | mckn1ght 4 hours ago | parent [-] | | Yep that's how I do it if I have to deal with stacked PRs. I also just never use rebase once anything has happened in a PR review that incurs historical state, like reviews or other people checking out the branch (that I know of, anyways). I'll rebase while it's local to keep my branch histories tidy, but I'll merge from upstream once shared things are happening. There are a bunch of tools out there for merging/rebasing entire branch stacks, I use https://github.com/dashed/git-chain. |
|
|
|
|
| ▲ | 8 hours ago | parent | prev | next [-] |
| [deleted] |
|
| ▲ | contravariant 6 hours ago | parent | prev | next [-] |
| Oh that's annoying, seems to me there wouldn't have been an issue if you just merged B into A after merging A into main, or the other way around but that already works fine as you pointed out. I mean if you've got a feature set to merge into dev, and it suddenly merges into main after someone merged dev into main then that's very annoying. |
|
| ▲ | jiveturkey 5 hours ago | parent | prev [-] |
| You "just" need to know the original merge-base of PR B to fix this. github support is not really required for that. To me that's the least valuable part of support for stacked PRs since that is already doable yourself. The github UI may change the target to main but your local working branch doesn't, and that's where you `rebase --onto` to fix it, before push to origin. It's appropriate for github to automatically change the target branch, because you want the diff in the ui to be representative. IIRC gitlab does a much better job of this but this is already achievable. What is actually useful with natively supported stacks is if you can land the entire stack together and only do 1 CI/actions run. I didn't read the announcement to see if it does that. You typically can't do that even if you merge PR B,C,D first because each merge would normally trigger CI. EDIT: i see from another comment (apparently from a github person) that the feature does in fact let you land the entire stack and only needs 1 CI run. wunderbar! |