Every time I see one of these nifty git tricks or workarounds I find myself wondering, “why not just use jj?”
You get a nicer, significantly simpler interface. You don’t need any tricks. You don’t have to google how to work yourself out of a bad state, ever. And you get near-perfect git compatibility (ie you can use jj on a shared git repo, doing all the same things, and your teammates won’t know the difference).
I’ve wondered if there is a psychological thing here: someone who spent time memorizing all the git nonsense may have some pride in that (which is earned, certainly), that introduces some mental friction in walking away???
It's not a trick or workaround. It's a very straightforward use of a command flag on one of the most used git commands. It's even conceptually very simple, you're just rebasing a subset of commits of a branch onto a different parent. Anyone who has ever rebased already has a working mental model of this operation. Framing this issue where knowing a single flag on a command every git user uses every day to perform an operation they already broadly understand as some arcane knowledge and 'nonsense' is ridiculous.
> introduces some mental friction in walking away???
I don't think it's just mental friction. Suppose you've learned git well enough that everything you do in it is automatic and fast, and the things which aren't fast by default you've built aliases and tooling for over the years. Yes, starting from ground zero you might want something like jj, but at the current point in your life you're not starting from ground zero. Switching to jj means learning another tool to achieve similar outcomes on your workflows.
There is also mental friction with learning an entirely new tool. `jj` is different enough from `git` that one can't transfer knowledge. Currently the documentation is not good enough to assuage that issue.
Oh, there's another stgit user! ^5
Coming from darcs, I couldn't use git until stgit came along, and today, it's one of those few tools I can't imagine working without. Nothing else matches my way of code hacking.
So often, I watch people making a big mess with git, and I always recommend stgit to them, so they can post proper and reviewable branches for merging. But in all these years, I could never convince anybody.
i went from being a "jj cli power user" to relying on jjui for all of my complex rebase needs so quickly that i now have to read the man page to recall basic commands
1. It's very new; I haven't had time to learn it properly yet.
2. It's very new and tooling doesn't support it well, e.g. VSCode. There aren't many GUIs yet.
3. I tried it once co-locating with Git and you definitely can't use both at the same time, even if it can use a `.git` directory. It ended up in a huge mess.
I'm definitely in favour of better-than-Git alternatives but I don't think it's reasonable to expect everyone to switch to JJ right now. It isn't so much better that abandoning the de facto standard is obviously worth it yet. (In contrast to things like the iPhone, Rust, SSDs, etc.).
Also I really wish they would focus on some of the bigger pain points of Git. I can deal with rebasing and whatnot. Sometimes it's painful but it's usually not that bad.
What I can't deal with are submodules and LFS. Both are awful and fixing them properly requires fundamental changes to the VCS which aren't going to happen in Git. JJ has an opportunity to do that. Imagine if JJ could say "we have submodules but they aren't awful!" or "you can check in large files!". Those are the sort of huge advantages that would mean you can say "why not just use JJ".
It’s a bit like qwertz. Sure, it is not optimal, there are better alternatives available. But it is good enough, and it is universal. That trumps a 5% typing improvement on my own custom keyboard layout at the cost of not being able to use my coworkers keyboard.
Also, I dislike all of the alternate git frontends I tried, because they are opinionated in a way they clash with my workflow.
Moreover, I don’t think the git CLI is that bad. Once you learn some basic concepts, it makes a lot of sense and is pretty consistent.
Most problems people report stem from a refusal to learn the underlying structure and models. That is on them. And when using a different frontend, they don’t disappear either. They are just abstracted, to allow you to avoid learning them. But they are still there, and you will probably still need to know them at some point.
> Most problems people report stem from a refusal to learn the underlying structure and models.
It's very easy to fall into the trap of believing this: git's implementation fits together neatly enough that it feels like the best you could do. Like, yes it's complex, but surely that's just intrinsic complexity of the problem? (Also, I think we all sometimes feel like someone with a different view must just not know as much as us.)
But if you have used other version control systems (I'm thinking particularly Mercurial here) you realise that actually some of that complexity is just totally made up by git.
Wish i could remember my issues with jj. I tried it, i wanted to stick with it because i loved the fact that i could reorder commits while deferring the actual conflicts.. but something eventually prevented me from switching. Searching my slack history where i talked about this with a coworker who actually used jj:
1. I had quite a bit of trouble figuring out a workflow for branches. Since my companies unit of work is the branch, with specifically named branches, my `jj ls` was confusing as hell.
`jj st` might have helped a bit, but there were scenarios where creating an commit would abandon the branch... if i'm reading my post history correctly. My coworker who was more familiar explained my jj problems away with "definitely pre-release software", so at the time neither of us were aware of a workflow which considered branches more core.
Fwiw, I don't even remember when the jj workflow had branches come into play.. but i was not happy with the UX around branches.
2. iirc i didn't like how it auto stashed/committed things. I found random `dbg!` statements could slip in more easily and i had to be on guard about what is committed, since everything just auto pushed. My normal workflow has me purposefully stashing chunks when i'm satisfied with them, and i use that as the visual metric. That felt less solid with jj.
Please take this with a huge grain of salt, this is 10 month old memory i scavenged from slack history. Plus as my coworker was saying, jj was changing a lot.. so maybe my issues are less relevant now? Or just flat out wrong, but nonetheless i bounced off of jj despite wanting to stick with it.
I more or less use the method described [here](https://steveklabnik.github.io/jujutsu-tutorial/advanced/sim...) for branches. One thing I do change is that I set the bookmark to an empty commit that serves as the head of each branch. When I am satisfied with a commit on head and want to move it to a branch I just `jj rebase -r @ -B branch`. When I want to create a new branch it's just `jj new -A main -B head` and `jj bookmark set branch_name -r @`
Half my team switched to JJ this year, and I do find stacking PRs to be much more pleasant now. We had previously tried out Graphite but it didn't really stick.
I wrote up a little way to use JJ's revsets to make it easy to push an entire stack of branches in one command:
The main issue I kept having when trying to do this with just git is then managing all the branch names to be attached to the right moved commits, so that my stack could be reviewable on github's open PRs.
Does jj help with that at all?
I've experimented a bit with git-town.com (OSS) and now everyone at $DAYJOB uses graphite.com (SaaS) which does that part very well.
It’s one of the core features that rebases, including branch names (bookmarks in jj) work ‘correctly’. You can rebase whole dags, including merges, with multiple named heads with just one jj rebase -b.
note that bookmarks don't float, unlike git branches, so if your pattern is to produce a lot of commits, you'll want something to keep your jj bookmarks pointing to the top of your pile of commits.
this is less of a problem if you're more into the 1 change == 1 commit workflow.
This looks like any other git arcane incantation. If this is a common pattern and jj aims to make things easier, should probably be part of the core commands, no?
I came into the comments specifically to ask if this flag existed. I feel bad that the author developed this whole flow just because they didn't know about this, but that's pretty common with git.
discoverability is a big problem, especially for CLI tools which can't afford to show small hints or "what's new" popups. I myself learned it from someone else, not docs.
update-refs works only in a narrow case when every branch starts form the tip of a previous. Your helper might still be useful if it properly "replants" whole tree keeping its structure.
I've settled on a workflow that reverses the situation. I simply commit all my work to the main branch and cherry pick commits into temporary feature branches only when submitting PRs.
This way I only need to worry about maintaining a single consistent lineage of commits. I've been using this workflow for about a year now and find it to be much easier than juggling and rebasing feature branches.
Interesting, I'll be sure to check it out. It sounds pretty similar to the tool I built which lets you edit a "plan" in a text editor to assign commits to feature branches - the plan is saved so it can be amended continuously.
What I would really like is a Git equivalent to Mercurial's "fold" operation. I usually make a bunch of commits as I work, just as checkpoints, which I then want to turn into a single final commit when it's done, which could be quite some time later, e.g. "started on thing", "broke it", "broke it more", "Add thing to improve foo of bar".
Mercurial's "histedit" command offers two operations to combine the commits: "fold" and "roll", where "fold" brings the earlier commit into the later commit and "roll" does the reverse. The "fold" operation does exactly what I want in this case: It brings all the changes into the final commit from when I actually finished it, using the commit date of that commit.
Git's "rebase -i" command offers "squash" and "fixup" and "fixup -c" and "fixup -C", but they all do the same thing as "roll", i.e. they keep the earliest commit's date, the date when I started working on the thing and not the date when I finished working on it. (So I then cut and paste the correct date and update the commit afterwards. This may not be the best way to do it.)
Running a git command on one branch and multiple branches being affected is really unusual for me! This really does look like it is designed for just this problem, though. Simple overview: https://blog.hot-coffee.dev/en/blog/git_update_refs/
How? I tried recreating the scenario from the article (the section "First rebase –onto") and ran the first rebase with "--update-refs":
$ git checkout feature-1
$ git rebase --update-refs main
Successfully rebased and updated refs/heads/feature-1.
Updated the following refs with --update-refs:
refs/heads/feature-2-base
But all it did was update feature-2-base. It still left feature-2 pointing to the old commits. So I guess it automates "git branch -f feature-2-base feature-1" (step 3), but it doesn't seem to automate "git rebase --onto feature-1 feature-2-base feature-2" (step 2).
Yeah, you need to rebase the tip of the feature branch stack. git will then update all the refs that point to ancestor commits that are moved. So in this case
Stacked commits is awesome, but it sucks that with git you need all these workarounds, and that the servers (GitHub etc) are not designed with that workflow in mind.
I left Google a few months back to work on another project. I missed the internal version control so much that I dropped that other project and am now focused http://twigg.vc
It's very similar to what's used at Meta and Google. Atomic commits, linear history, auto rebase, code review compatible with stacked commits.
I usually just `git rebase origin/main -i` after the base branch has been merged there, and this means I need to explicitly drop the merged commits, but I can inspect what's happening.
This made good sense, though by the time I got to bits saying "that last step is critical. Without it, your next sync will break" and "Don't forget!" I was laughing out loud. I love git :-)
I’ve been meaning to write this article for a long time @flexdinesh . Thanks for taking the time to share this technique for managing stacked diffs using vanilla git rebase!
Even if `--update-refs` didn't exist, my experience is that git can identify duplicate commits produced by rebase, and knows to skip them when rebasing the same commits to the same place again. Am I imagining that?
I'm a heavy user of git-spice: https://abhinav.github.io/git-spice (created by a former coworker) and can't really go back to a time without it. While still not nearly as good as Facebook's Phabricator, it's probably the best workflow for small, focused stacked PRs you can achieve in a Github / Gitlab based repository.
Stacking commits lets you do that without having to wait for each change to be reviewed/merged to the main branch before you iterate on top of those changes.
True. I find I rarely need it: standard rebase or merge do the trick. If they don't the review cycle or PR size may be too high. Super rare I need onto. So rare I look up how to do it when I do.
It's such a complicated way to work though, you start another set of changes then you go back addressing comments then you go back updating the stacked branch and you might need to do that few times...
Teams should focus on getting stuff merged in and not create massive PRs that live forever, life becomes so much easier.
This marker branch step feels like a workaround to a missing capability. It's something I can easily see one forgetting especially if they haven't been doing stacked diff workflows regularly.
I agree it seems error prone. I'm not sure if I'm misunderstanding something, but I use `git cherry-pick` when I know I need to move commits around that might have conflicts. The problem with rebase can be that the user doesn't fully understand all the options being applied and end up with a "bad" merge.
I don't usually want to rewrite history. I just want the target branch with all my commits on top (I usually squash the feature branch into one commit anyway). I have yet to run into a situation where this isn't good enough.
If the branch diverges so much and has so many commits that this simpler approach doesn't work, that might not be a git problem, but a project management one. It's still always nice to know git has tools to get me out of a jam.
Is there any reason besides merge commits ending up in history to not do this with merges instead? ie merge main into feature-1, then feature-1 into feature-2.
Sounds like using --update-refs would let you do all that in a single operation, but you still need to force-push and don't maintain an explicit merge/conflict resolution history, both of which could be considered sub-optimal for collaborative scenarios.
This problem isn't specific to cases where you are rebasing "again and again". Just needing to do a single rebase (e.g. prior to opening a PR) for a stacked feature is enough to need a solution.
And, as others have pointed out, the modern solution is `--update-refs`, so there's no need for complicated workflows any more anyway.
If you mean rebasing as each PR in the stack is merged, most Git platforms have the ability to do that automatically.
No, I mean small chunks that are actually merged instead of having a stack of them floating around.
A rebase before a merge can always happen, of course. But there are not stacked commits then it is just a standard rebase of your small chunk and that's it. And this will also have a shorter life than a stack so rebases will be rarer and simpler.
Oh, yes - but this is more about your situation than your style. Sometimes a feature is large and varied enough to beg multiple PRs, yet singular enough that you are not developing them serially (i.e. as you work on later parts, you are changing earlier parts). Most of the time this isn't the case.
It is entirely viable to never have more than 1 or 2 open pull requests on any particular code repository, and to use continuous delivery practices to keep deploying small changes to production 1 at a time.
That's exactly how I've worked for the past decade or so.
Eh I just use `git rebase -i` and delete the commits I don't want. Much easier to think about.
But the real problem with this workflow is that neither Github nor Gitlab support it at all. Not even Forgejo does. Which blows my mind because is such an obvious way to work.
As far as I know only Tangled actually supports it, but unfortunately Tangled is tangled up in its own weird federation ideas. There's no way to privately host it at all.
Every time I see one of these nifty git tricks or workarounds I find myself wondering, “why not just use jj?”
You get a nicer, significantly simpler interface. You don’t need any tricks. You don’t have to google how to work yourself out of a bad state, ever. And you get near-perfect git compatibility (ie you can use jj on a shared git repo, doing all the same things, and your teammates won’t know the difference).
I’ve wondered if there is a psychological thing here: someone who spent time memorizing all the git nonsense may have some pride in that (which is earned, certainly), that introduces some mental friction in walking away???
It's not a trick or workaround. It's a very straightforward use of a command flag on one of the most used git commands. It's even conceptually very simple, you're just rebasing a subset of commits of a branch onto a different parent. Anyone who has ever rebased already has a working mental model of this operation. Framing this issue where knowing a single flag on a command every git user uses every day to perform an operation they already broadly understand as some arcane knowledge and 'nonsense' is ridiculous.
> introduces some mental friction in walking away???
I don't think it's just mental friction. Suppose you've learned git well enough that everything you do in it is automatic and fast, and the things which aren't fast by default you've built aliases and tooling for over the years. Yes, starting from ground zero you might want something like jj, but at the current point in your life you're not starting from ground zero. Switching to jj means learning another tool to achieve similar outcomes on your workflows.
But with jj there are better workflows that aren’t really doable with git.
There is also mental friction with learning an entirely new tool. `jj` is different enough from `git` that one can't transfer knowledge. Currently the documentation is not good enough to assuage that issue.
I have one and a half decades of muscle memory burned in with inoremap jj <Esc>`^
It's not something I can just shift away from.
For me, the answer is stgit. https://stacked-git.github.io/
Oh, there's another stgit user! ^5 Coming from darcs, I couldn't use git until stgit came along, and today, it's one of those few tools I can't imagine working without. Nothing else matches my way of code hacking. So often, I watch people making a big mess with git, and I always recommend stgit to them, so they can post proper and reviewable branches for merging. But in all these years, I could never convince anybody.
That's really neat.
For me the answer is lazygit. I rarely use the git cli. I don't want to learn the jj cli and the TUI wrappers for jj seem less polished.
jjui is great, give it a try!
i went from being a "jj cli power user" to relying on jjui for all of my complex rebase needs so quickly that i now have to read the man page to recall basic commands
can confirm jjui is super nice, except I've never been a jj cli power user ;)
jj does not support git submodules, this precludes even a casual use on my own personal repo.
> why not just use jj
1. It's very new; I haven't had time to learn it properly yet.
2. It's very new and tooling doesn't support it well, e.g. VSCode. There aren't many GUIs yet.
3. I tried it once co-locating with Git and you definitely can't use both at the same time, even if it can use a `.git` directory. It ended up in a huge mess.
I'm definitely in favour of better-than-Git alternatives but I don't think it's reasonable to expect everyone to switch to JJ right now. It isn't so much better that abandoning the de facto standard is obviously worth it yet. (In contrast to things like the iPhone, Rust, SSDs, etc.).
Also I really wish they would focus on some of the bigger pain points of Git. I can deal with rebasing and whatnot. Sometimes it's painful but it's usually not that bad.
What I can't deal with are submodules and LFS. Both are awful and fixing them properly requires fundamental changes to the VCS which aren't going to happen in Git. JJ has an opportunity to do that. Imagine if JJ could say "we have submodules but they aren't awful!" or "you can check in large files!". Those are the sort of huge advantages that would mean you can say "why not just use JJ".
There is a vscode jj gui extension
It’s a bit like qwertz. Sure, it is not optimal, there are better alternatives available. But it is good enough, and it is universal. That trumps a 5% typing improvement on my own custom keyboard layout at the cost of not being able to use my coworkers keyboard.
Also, I dislike all of the alternate git frontends I tried, because they are opinionated in a way they clash with my workflow.
Moreover, I don’t think the git CLI is that bad. Once you learn some basic concepts, it makes a lot of sense and is pretty consistent.
Most problems people report stem from a refusal to learn the underlying structure and models. That is on them. And when using a different frontend, they don’t disappear either. They are just abstracted, to allow you to avoid learning them. But they are still there, and you will probably still need to know them at some point.
> Most problems people report stem from a refusal to learn the underlying structure and models.
It's very easy to fall into the trap of believing this: git's implementation fits together neatly enough that it feels like the best you could do. Like, yes it's complex, but surely that's just intrinsic complexity of the problem? (Also, I think we all sometimes feel like someone with a different view must just not know as much as us.)
But if you have used other version control systems (I'm thinking particularly Mercurial here) you realise that actually some of that complexity is just totally made up by git.
> It's very easy to fall into the trap of believing this: git's implementation fits together neatly enough that it feels like the best you could do.
I explicitly said that git IS NOT the best we can do. But it is universal and good enough. Not nearly as bad as some people make it out to be.
Wish i could remember my issues with jj. I tried it, i wanted to stick with it because i loved the fact that i could reorder commits while deferring the actual conflicts.. but something eventually prevented me from switching. Searching my slack history where i talked about this with a coworker who actually used jj:
1. I had quite a bit of trouble figuring out a workflow for branches. Since my companies unit of work is the branch, with specifically named branches, my `jj ls` was confusing as hell.
`jj st` might have helped a bit, but there were scenarios where creating an commit would abandon the branch... if i'm reading my post history correctly. My coworker who was more familiar explained my jj problems away with "definitely pre-release software", so at the time neither of us were aware of a workflow which considered branches more core.
Fwiw, I don't even remember when the jj workflow had branches come into play.. but i was not happy with the UX around branches.
2. iirc i didn't like how it auto stashed/committed things. I found random `dbg!` statements could slip in more easily and i had to be on guard about what is committed, since everything just auto pushed. My normal workflow has me purposefully stashing chunks when i'm satisfied with them, and i use that as the visual metric. That felt less solid with jj.
Please take this with a huge grain of salt, this is 10 month old memory i scavenged from slack history. Plus as my coworker was saying, jj was changing a lot.. so maybe my issues are less relevant now? Or just flat out wrong, but nonetheless i bounced off of jj despite wanting to stick with it.
I more or less use the method described [here](https://steveklabnik.github.io/jujutsu-tutorial/advanced/sim...) for branches. One thing I do change is that I set the bookmark to an empty commit that serves as the head of each branch. When I am satisfied with a commit on head and want to move it to a branch I just `jj rebase -r @ -B branch`. When I want to create a new branch it's just `jj new -A main -B head` and `jj bookmark set branch_name -r @`
Jujutsu comes in handy here for the same usecase:
https://github.com/jj-vcs/jj
https://www.stavros.io/posts/switch-to-jujutsu-already-a-tut...
Also found https://github.com/gitbutlerapp/gitbutler
Half my team switched to JJ this year, and I do find stacking PRs to be much more pleasant now. We had previously tried out Graphite but it didn't really stick.
I wrote up a little way to use JJ's revsets to make it easy to push an entire stack of branches in one command:
https://crabmusket.net/2025/jj-bough-a-useful-alias-for-stac...
The main issue I kept having when trying to do this with just git is then managing all the branch names to be attached to the right moved commits, so that my stack could be reviewable on github's open PRs.
Does jj help with that at all?
I've experimented a bit with git-town.com (OSS) and now everyone at $DAYJOB uses graphite.com (SaaS) which does that part very well.
It’s one of the core features that rebases, including branch names (bookmarks in jj) work ‘correctly’. You can rebase whole dags, including merges, with multiple named heads with just one jj rebase -b.
To expand: In jj, bookmarks point to "changes," not commits. Rebases, history manipulations, etc. preserve change ID, so this "just works."
note that bookmarks don't float, unlike git branches, so if your pattern is to produce a lot of commits, you'll want something to keep your jj bookmarks pointing to the top of your pile of commits.
this is less of a problem if you're more into the 1 change == 1 commit workflow.
There's a very common alias `jj tug` for this case:
It moves the nearest bookmark to the commit before the current one (which should be your working commit).This looks like any other git arcane incantation. If this is a common pattern and jj aims to make things easier, should probably be part of the core commands, no?
Thanks, I replaced my Frankenstein's monster of a parsing pipeline with this, very useful!
There's an experimental-advance-branches feature which helps with that!
That particular case can be solved much easier by rebasing outer-most branch with `--update-refs` flag.
I came into the comments specifically to ask if this flag existed. I feel bad that the author developed this whole flow just because they didn't know about this, but that's pretty common with git.
I'm pretty sure the author was Claude, so don't feel too bad for it.
Exactly, I was reading the blog and wondering the whole time how it's better than --update-refs, which I have been using a lot recently.
Thanks. This is going to be so useful, but it pains me to know I could have been using —update-refs for the last three years.
I used to dutifully read release notes for every git release, but stopped at some point. Apparently that point was more than three years ago.
discoverability is a big problem, especially for CLI tools which can't afford to show small hints or "what's new" popups. I myself learned it from someone else, not docs.
I plan to pay it forward today with a post on my work slack. I just need to try it a time or two myself first.
Yep. I set this in .gitconfig
I'm guilty lol. I wrote a helper to do rebase chains like this
update-refs works only in a narrow case when every branch starts form the tip of a previous. Your helper might still be useful if it properly "replants" whole tree keeping its structure.
I've settled on a workflow that reverses the situation. I simply commit all my work to the main branch and cherry pick commits into temporary feature branches only when submitting PRs.
This way I only need to worry about maintaining a single consistent lineage of commits. I've been using this workflow for about a year now and find it to be much easier than juggling and rebasing feature branches.
In case anyone's interested, I made a tool that automates this workflow. The worfklow and tool are described here: https://github.com/bjvanderweij/dflock/
You might like Jujutsu – you commit without being on any branch and then later you can decide where and how you put things onto branches.
Interesting, I'll be sure to check it out. It sounds pretty similar to the tool I built which lets you edit a "plan" in a text editor to assign commits to feature branches - the plan is saved so it can be amended continuously.
How many people on the team?
I work in a small team, about five people.
What I would really like is a Git equivalent to Mercurial's "fold" operation. I usually make a bunch of commits as I work, just as checkpoints, which I then want to turn into a single final commit when it's done, which could be quite some time later, e.g. "started on thing", "broke it", "broke it more", "Add thing to improve foo of bar".
Mercurial's "histedit" command offers two operations to combine the commits: "fold" and "roll", where "fold" brings the earlier commit into the later commit and "roll" does the reverse. The "fold" operation does exactly what I want in this case: It brings all the changes into the final commit from when I actually finished it, using the commit date of that commit.
Git's "rebase -i" command offers "squash" and "fixup" and "fixup -c" and "fixup -C", but they all do the same thing as "roll", i.e. they keep the earliest commit's date, the date when I started working on the thing and not the date when I finished working on it. (So I then cut and paste the correct date and update the commit afterwards. This may not be the best way to do it.)
I think ‘git rebase —-update-refs’ is the better way to go for this scenario
Sweet, looks like this is pretty new (2022).
Running a git command on one branch and multiple branches being affected is really unusual for me! This really does look like it is designed for just this problem, though. Simple overview: https://blog.hot-coffee.dev/en/blog/git_update_refs/
It breaks if you amend the top commit instead of adding a new one.
Is there any good guide on how to solve the issue which OP solves?
I was reading this the other day when I came across this feature because I’m stacking PRs recently which I don’t usually do
https://andrewlock.net/working-with-stacked-branches-in-git-...
Another commenter posted this link which was a bit more succinct
https://blog.hot-coffee.dev/en/blog/git_update_refs/
There isn’t much to it though, you just go to the branch and run git rebase with the update refs flag.
You don’t really need docs as --update-refs does what the OP does automatically instead of manually like the OP does.
How? I tried recreating the scenario from the article (the section "First rebase –onto") and ran the first rebase with "--update-refs":
But all it did was update feature-2-base. It still left feature-2 pointing to the old commits. So I guess it automates "git branch -f feature-2-base feature-1" (step 3), but it doesn't seem to automate "git rebase --onto feature-1 feature-2-base feature-2" (step 2).Presumably I'm doing something wrong?
Yeah, you need to rebase the tip of the feature branch stack. git will then update all the refs that point to ancestor commits that are moved. So in this case
Thanks! Yup, that does the trick.
First, you don't need the extra "marker" commit. This flag obviates the entire workflow.
Second, you run it on the outermost branch: feature 2. It updates all refs in the chain.
GitButler handles all of this pretty automatically, if you don't want to deal with the Git gymnastics needed here.
https://blog.gitbutler.com/stacked-branches-with-gitbutler
Stacked commits is awesome, but it sucks that with git you need all these workarounds, and that the servers (GitHub etc) are not designed with that workflow in mind.
I left Google a few months back to work on another project. I missed the internal version control so much that I dropped that other project and am now focused http://twigg.vc It's very similar to what's used at Meta and Google. Atomic commits, linear history, auto rebase, code review compatible with stacked commits.
I usually just `git rebase origin/main -i` after the base branch has been merged there, and this means I need to explicitly drop the merged commits, but I can inspect what's happening.
Yeah, I do this too: The `--onto` solution feels a bit too magical at times and an interactive rebase is pretty clear about what's happening.
This made good sense, though by the time I got to bits saying "that last step is critical. Without it, your next sync will break" and "Don't forget!" I was laughing out loud. I love git :-)
I’ve been meaning to write this article for a long time @flexdinesh . Thanks for taking the time to share this technique for managing stacked diffs using vanilla git rebase!
Even if `--update-refs` didn't exist, my experience is that git can identify duplicate commits produced by rebase, and knows to skip them when rebasing the same commits to the same place again. Am I imagining that?
That works until you had to fix conflicts during the rebase and the commits are no longer identical.
It definitely fast-forwards unchanged commits.
I'm a heavy user of git-spice: https://abhinav.github.io/git-spice (created by a former coworker) and can't really go back to a time without it. While still not nearly as good as Facebook's Phabricator, it's probably the best workflow for small, focused stacked PRs you can achieve in a Github / Gitlab based repository.
For the example given, would merging branch 2 into branch 1 then branch 1 into main achieve the same effect?
Perhaps not an option if you need to release the work in branch 1 before the work in branch 2 is ready/reviewed/etc.
The point of this technique is to keep them separate. See the other comments about `--update-refs`.
I believe the author would love stg: https://stacked-git.github.io/guides/tutorial/#patches
Fun stuff, but I'll stick to trunk based dev, small PRs... thanks!
Stacking commits lets you do that without having to wait for each change to be reviewed/merged to the main branch before you iterate on top of those changes.
True. I find I rarely need it: standard rebase or merge do the trick. If they don't the review cycle or PR size may be too high. Super rare I need onto. So rare I look up how to do it when I do.
It's such a complicated way to work though, you start another set of changes then you go back addressing comments then you go back updating the stacked branch and you might need to do that few times... Teams should focus on getting stuff merged in and not create massive PRs that live forever, life becomes so much easier.
This marker branch step feels like a workaround to a missing capability. It's something I can easily see one forgetting especially if they haven't been doing stacked diff workflows regularly.
The capability is there.
Just use git rebase --update-refs ...
Wow you aren't wrong, the first blog post on Google talking about this is exactly what this complicated method does just built-in.
I agree it seems error prone. I'm not sure if I'm misunderstanding something, but I use `git cherry-pick` when I know I need to move commits around that might have conflicts. The problem with rebase can be that the user doesn't fully understand all the options being applied and end up with a "bad" merge.
I don't usually want to rewrite history. I just want the target branch with all my commits on top (I usually squash the feature branch into one commit anyway). I have yet to run into a situation where this isn't good enough.
If the branch diverges so much and has so many commits that this simpler approach doesn't work, that might not be a git problem, but a project management one. It's still always nice to know git has tools to get me out of a jam.
I consider myself a shmedium experienced dev who likes to learn their tools and read source.
This seems like a house of cards whose juice isn’t worth the squeeze. But I would love to split up my PRs into smaller pieces.
I just would hate to waste time with an incomprehensibly goofed git history because I forgot a command.
Ah, I've been doing this for ages but apparently this practice has a name
Is there any reason besides merge commits ending up in history to not do this with merges instead? ie merge main into feature-1, then feature-1 into feature-2.
Sounds like using --update-refs would let you do all that in a single operation, but you still need to force-push and don't maintain an explicit merge/conflict resolution history, both of which could be considered sub-optimal for collaborative scenarios.
The use case is that they are not ready to merge yet.
Instead of "stacked diffs", isn't the more "continuous integration" solution to split a big feature into small chunks that actually get merged?
Having to rebase again and again is a symptom that a dev branch is living for too long.
This problem isn't specific to cases where you are rebasing "again and again". Just needing to do a single rebase (e.g. prior to opening a PR) for a stacked feature is enough to need a solution.
And, as others have pointed out, the modern solution is `--update-refs`, so there's no need for complicated workflows any more anyway.
If you mean rebasing as each PR in the stack is merged, most Git platforms have the ability to do that automatically.
No, I mean small chunks that are actually merged instead of having a stack of them floating around.
A rebase before a merge can always happen, of course. But there are not stacked commits then it is just a standard rebase of your small chunk and that's it. And this will also have a shorter life than a stack so rebases will be rarer and simpler.
Oh, yes - but this is more about your situation than your style. Sometimes a feature is large and varied enough to beg multiple PRs, yet singular enough that you are not developing them serially (i.e. as you work on later parts, you are changing earlier parts). Most of the time this isn't the case.
I’m amazed that this comment is so low down
Stacked diffs seems like a solution to managing high WIP - but the best solution to high WIP is always to lower WIP
Absolutely everything gets easier when you lower your work in progress.
This seems idealistic. It's very normal to be working on a feature that depends on a not-yet-merged feature.
I invite you to look into feature flagging.
It is entirely viable to never have more than 1 or 2 open pull requests on any particular code repository, and to use continuous delivery practices to keep deploying small changes to production 1 at a time.
That's exactly how I've worked for the past decade or so.
Eh I just use `git rebase -i` and delete the commits I don't want. Much easier to think about.
But the real problem with this workflow is that neither Github nor Gitlab support it at all. Not even Forgejo does. Which blows my mind because is such an obvious way to work.
As far as I know only Tangled actually supports it, but unfortunately Tangled is tangled up in its own weird federation ideas. There's no way to privately host it at all.
We use graphite at work to automate this workflow. The whole point is avoid this toil.