[HN Gopher] Stacked Diffs with git rebase --onto
___________________________________________________________________
Stacked Diffs with git rebase --onto
Author : flexdinesh
Score : 117 points
Date : 2025-12-01 04:47 UTC (4 days ago)
(HTM) web link (dineshpandiyan.com)
(TXT) w3m dump (dineshpandiyan.com)
| hahahacorn wrote:
| 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.
| the_gipsy wrote:
| 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.
| Xophmeister wrote:
| 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.
| WorldMaker wrote:
| Add `--update-refs` to your interactive rebase and it will
| give you an easy line to know how many commits to drop
| because it will add an `update-ref` line for the old branch.
| You can just easily delete everything up to and including
| that `update-ref` line and don't have to manually pull up a
| git log of the other branch to remember which commits already
| merged.
|
| (Plus, of course, if you have multiple branches stacked,
| `--update-refs` makes it easier to update all of them if you
| start from the outermost branch.)
| perspectivezoom wrote:
| 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.
| jbjbjbjb wrote:
| I think 'git rebase ---update-refs' is the better way to go for
| this scenario
| enbugger wrote:
| Is there any good guide on how to solve the issue which OP
| solves?
| sirsuki wrote:
| You don't really need docs as --update-refs does what the OP
| does automatically instead of manually like the OP does.
| ptx wrote:
| 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).
|
| Presumably I'm doing something wrong?
| happytoexplain wrote:
| 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.
| mhw wrote:
| 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
| $ git rebase --update-refs main feature-2
| ptx wrote:
| Thanks! Yup, that does the trick.
| jbjbjbjb wrote:
| 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.
| lelandfe wrote:
| 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/
| YmiYugy wrote:
| It breaks if you amend the top commit instead of adding a new
| one.
| dimitrieh wrote:
| 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
| ndr wrote:
| 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.
| baq wrote:
| 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.
| arccy wrote:
| 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.
| lima wrote:
| There's an experimental-advance-branches feature which
| helps with that!
| pimeys wrote:
| There's a very common alias `jj tug` for this case:
| tug = ["bookmark", "move", "--from", "heads(::@- &
| bookmarks())", "--to", "@-"]
|
| It moves the nearest bookmark to the commit before the
| current one (which should be your working commit).
| stavros wrote:
| Thanks, I replaced my Frankenstein's monster of a parsing
| pipeline with this, very useful!
| mhitza wrote:
| 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?
| steveklabnik wrote:
| It's something that makes a specific workflow easier, a
| lot of folks that use jj don't necessarily use that
| workflow.
|
| That doesn't mean it couldn't be a core command someday,
| but given that the alias works well for people, there's
| not a ton of reason to make a whole new command. You
| configure the alias and you're off to the races.
| gcr wrote:
| To expand: In jj, bookmarks point to "changes," not
| commits. Rebases, history manipulations, etc. preserve
| change ID, so this "just works."
| WorldMaker wrote:
| `--update-refs` flag helps a lot in vanilla git. That and
| `--autosquash` should probably be default flags to `git
| rebase`. I also don't entirely trust rebase without `-i`
| (`--interactive`), personally. I hear there is talk about
| shaking up the out-of-the-box default flags in git 3, and I
| think rebase should especially get new defaults.
| 1718627440 wrote:
| I also use these flag when I have the need to, but I very
| much don't want them to become the default.
| crabmusket wrote:
| 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...
| politelemon wrote:
| 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.
| sublinear wrote:
| 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.
| imron wrote:
| The capability is there.
|
| Just use git rebase --update-refs ...
| url00 wrote:
| Wow you aren't wrong, the first blog post on Google talking
| about this is exactly what this complicated method does just
| built-in.
| nopurpose wrote:
| That particular case can be solved much easier by rebasing outer-
| most branch with `--update-refs` flag.
| imron wrote:
| Yep. I set this in .gitconfig
| happytoexplain wrote:
| 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.
| fwip wrote:
| I'm pretty sure the author was Claude, so don't feel too bad
| for it.
| duskdozer wrote:
| I'm guilty lol. I wrote a helper to do rebase chains like this
| nopurpose wrote:
| 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.
| WorldMaker wrote:
| Though at that point it may be easier to rewrite your
| helper to manage rebase's interactive scripts.
| onionisafruit wrote:
| 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.
| nopurpose wrote:
| 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.
| onionisafruit wrote:
| 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.
| 1718627440 wrote:
| Except they do. You can type <tab>, search the man page or
| read the release notes. They just don't force the user to.
| ananthakumaran wrote:
| 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.
| schacon wrote:
| 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
| motoboi wrote:
| I believe the author would love stg: https://stacked-
| git.github.io/guides/tutorial/#patches
| nrhrjrjrjtntbt wrote:
| Fun stuff, but I'll stick to trunk based dev, small PRs...
| thanks!
| taejavu wrote:
| 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.
| nrhrjrjrjtntbt wrote:
| 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.
| flr03 wrote:
| 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.
| sockbot wrote:
| We use graphite at work to automate this workflow. The whole
| point is avoid this toil.
| ragebol wrote:
| Ah, I've been doing this for ages but apparently this practice
| has a name
| dspillett wrote:
| 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.
| happytoexplain wrote:
| The point of this technique is to keep them separate. See the
| other comments about `--update-refs`.
| happytoexplain wrote:
| 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?
| 1718627440 wrote:
| It definitely fast-forwards unchanged commits.
| adrianN wrote:
| That works until you had to fix conflicts during the rebase and
| the commits are no longer identical.
| swaits wrote:
| 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???
| YmiYugy wrote:
| 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.
| gcr wrote:
| 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
| baq wrote:
| can confirm jjui is super nice, except I've never been a jj
| cli power user ;)
| smcameron wrote:
| For me, the answer is stgit. https://stacked-git.github.io/
| max_k wrote:
| 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.
| michaelbuckbee wrote:
| That's really neat.
| Hendrikto wrote:
| 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.
| quietbritishjim wrote:
| > 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.
| Hendrikto wrote:
| > 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.
| hansvm wrote:
| > 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.
| tcoff91 wrote:
| But with jj there are better workflows that aren't really
| doable with git.
| jhhh wrote:
| Last time I saw this claimed (maybe from steve's tutorial?)
| it was just autosquash. Do you have another example?
| tcoff91 wrote:
| https://ofcr.se/jujutsu-merge-workflow
|
| With `jjui` this strategy takes only a few keystrokes to
| do operations like adding/removing parents from merge
| commits.
|
| It's so nice to have like 4 parallel PRs in flight and
| then rebase all of them and all the other experimental
| branches you have on top onto main in 1 command.
|
| Also, I cannot even stress to you how much first-class-
| conflicts is a game changer. Like seriously you do NOT
| understand how much better it is to not have to resolve
| conflicts immediately when rebasing and being able to
| come back and resolve them whenever you want. It cannot
| be overstated how much better this is than git.
|
| Also, anonymous branches are SOOOO much better than git
| stashes.
| 1718627440 wrote:
| > Also, anonymous branches are SOOOO much better than git
| stashes.
|
| You can do anonymous branches in Git as well. I use both
| for different use cases.
| tcoff91 wrote:
| The UX around anonymous branches in git is not nearly as
| good as jj though.
|
| Also git has no equivalent to the operation log. `jj
| undo` and `jj op restore` are so sweet.
| steveklabnik wrote:
| It really just depends. I was very comfortable with the git
| cli. It didn't take long to learn jj's, and I'm faster with
| it now than I ever was with git, simply because a lot of
| things are easier to do and take less commands.
| stouset wrote:
| If jj took many weeks of relearning, I might be right there
| with you. But the overwhelming majority of people I've
| personally seen who try the switch convert within a day, are
| barely slowed down by day two, and are effectively fluent
| within three days to a week at most.
| unshavedyak wrote:
| 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.
| oscillonoscope wrote:
| 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 @`
| steveklabnik wrote:
| "creating a commit would abandon the branch" is certainly
| something lost in translation. There are other reasons you
| may have not liked the UX, largely that if you create
| branches and then add a bunch of commits after it, the branch
| head doesn't automatically move by default. There is a config
| setting you can change if you prefer that, or the `jj tug`
| alias some people set up.
|
| Auto-commit is still a thing, but you can regain the stuff
| you like with a workflow change, this is called the "squash
| workflow" and is very popular:
| https://steveklabnik.github.io/jujutsu-tutorial/real-
| world-w...
| dlisboa wrote:
| 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.
| stouset wrote:
| This really isn't true. All of my git knowledge--except for
| the CLI flags--is directly useful for jj.
|
| The jj CLI is very easy to grok, even for a seasoned git
| user. Maybe even especially so for a seasoned git user.
| IshKebab wrote:
| > 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".
| tcoff91 wrote:
| There is a vscode jj gui extension
| steveklabnik wrote:
| Colocating is the default now, and you should be able to use
| both at the same time, though running git commands that
| mutate can confuse jj. Ideally you only use read-only git
| commands.
|
| Both submodules and LFS are things that jj wants to address,
| but they take time.
| bilalq wrote:
| 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.
| yegle wrote:
| jj does not support git submodules, this precludes even a
| casual use on my own personal repo.
| steveklabnik wrote:
| It just means that you use git commands to update your
| submodules, jj still works for the rest of the repo just
| fine.
| jhhh wrote:
| 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.
| mwcz wrote:
| I'm one of the git users you describe who are resistant to jj.
| jj sounds great, Steve Klabnik's endorsement is very
| convincing, and I would probably love it, but here's the issue:
| I've used git for 17 years and have internalized its
| idiosyncracies sufficiently to practically never run into
| problems, and help anyone on my team who does.
|
| jj is harder to adopt for people with a thorough mental model
| of git, because it's harder to accept jj commands at face
| value. I know it's modifying my git tree, so I feel compelled
| to grok exactly what it's doing, but that's distracting and
| time consuming.
|
| People like me should probably trial jj exclusively for two
| weeks, to shed that antijjotic resistance and form a more
| clear-headed opinion.
| Valodim wrote:
| I was in the same position. Then I tried jj. I knew within
| the day that I wouldn't switch back.
| steveklabnik wrote:
| Glad you like that I like it!
|
| What I will say is this: there is certainly an adjustment
| period, and I also totally hear you about how learning
| internals can be time consuming.
|
| I think you can get a lot of the way there with at least the
| core concept with something like this, if you'll indulge me:
|
| With git, you build up stuff on your filesystem. You then
| select which bits go into a diff in your index, and then when
| you're done, you stamp out a commit.
|
| With jj, you instead _start_ by creating a commit. In this
| case, it 's empty. Then, every time you run a jj command, it
| does a snapshot, and this produces a new commit. However,
| because we want to have a stable identifier for a commit, we
| introduce a new one: the change id. This is basically an
| alias for the latest commit snapshot that was made. Your git
| history is just made up of each of these latest commits for
| each change.
|
| ... does that make any sense? Obviously there's more to all
| of the features than this, but that's sort of the core way
| that the histories map to each other.
| stouset wrote:
| > jj is harder to adopt for people with a thorough mental
| model of git
|
| No, it really isn't. I have used git since shortly after it
| was first released and I've written a git implementation.
|
| I switched to jj in one day. And the amount of git arcana I
| have to keep in working memory is now basically nil. My VCS
| now works in almost a 1:1 mapping with how my brain wants to
| interact with my repo rather than having to go through a
| translation layer.
|
| If you understand what git commands are doing, what jj does
| is essentially trivial to add to your mental model.
|
| I also get the benefit of being able to use workflows that I
| always want to use in git but which are an enormous pain in
| practice. _And_ I get access to wildly powerful new workflows
| I didn't even consider because they would be outlandish in
| git.
| Fang_ wrote:
| 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.
| happytoexplain wrote:
| The use case is that they are not ready to merge yet.
| bvdw wrote:
| 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/
| motoboi wrote:
| How many people on the team?
| bvdw wrote:
| I work in a small team, about five people.
| JimDabell wrote:
| 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.
| bvdw wrote:
| 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.
| mytailorisrich wrote:
| 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.
| glenjamin wrote:
| 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.
| happytoexplain wrote:
| This seems idealistic. It's very normal to be working on a
| feature that depends on a not-yet-merged feature.
| glenjamin wrote:
| 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.
| SideburnsOfDoom wrote:
| > It's very normal to be working on a feature that depends
| on a not-yet-merged feature.
|
| Oh sure, many bad ideas and poor practises such as that one
| are quite "normal". It's not a recommendation.
| happytoexplain wrote:
| 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.
| mytailorisrich wrote:
| 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.
| happytoexplain wrote:
| 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.
| mytailorisrich wrote:
| My experience is that you should always aim for frequent
| small commits. This is what continuous integration and
| trunk-based development advocate and it saves a lot of
| headaches and the need to come up with "exotic" solutions
| to problems created by big, long-lived dev branches.
|
| This is quite orthogonal to developing parts serially or
| not, and it is perfectly fine to change or refactor
| ealier parts when you work on later parts. Development is
| an iterative process.
|
| It seems to me that "stacked diffs" are an example of
| looking for a technical solution to process issue.
| andrebianchessi wrote:
| 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.
| davidfstr wrote:
| 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!
| IshKebab wrote:
| 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.
| taejavu wrote:
| I'm not sure what you mean, I stack PRs all the time with
| GitHub - select the earlier branch as the merge target instead
| of the main branch.
| ptx wrote:
| 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.)
| WorldMaker wrote:
| That is an interesting desire to use a later commit date rather
| than an earlier one. So many prefer that commit date of when an
| effort started.
|
| One way to accomplish that is to reorder the commmits in
| `rebase -i`: "pick" the last commit first and squash/fixup the
| rest after. That can produce some very weird merge conflicts,
| but it works well more often than you might think, too.
|
| At the very least, you can do your hand editing of the commits
| during the rebase instead of after by switching the earliest
| commit from "pick" to "edit" to have the full power to amend
| the commit before it moves on (with `git rebase --continue`
| when you are satisfied). (Versus "reword" if you just want to
| change the commit message only.)
|
| Also, instead of naming commits things like "broke it" and
| "broke it more" an option is to use `git commit
| --fixup={previous commit hash}`. That auto-generates a "fixup!"
| name based on the previous commit and auto-marks it as a fixup
| if you use the `--autosquash` flag to rebase.
| ptx wrote:
| I do use fixup commits as well, for fixing small issues
| discovered after I make the proper commit, and in that case
| it makes sense to use the date and message of the earlier
| commit.
|
| But in the case I'm describing, the earlier commits are
| essentially just temporary snapshots on the way to making a
| proper commit. Just a more explicit undo history, basically.
| I usually don't even bother naming them anything as
| meaningful as "broke it", actually. Maybe most people
| wouldn't even bother making these commits - or maybe they
| would if Git supported "fold".
| WorldMaker wrote:
| My pattern in a case like that is often to start a commit
| named "WIP thing I'm doing" and `commit --amend` each
| snapshot, updating the commit message as it starts to come
| together. `commit --amend` is nicer/gentler than `rebase`,
| most of the time.
|
| Though there are also times I don't mind working in a very
| dirty worktree and `git add -p` (interactive add that also
| makes it easy to stage only parts of files) pieces only
| once they feel complete and start to tell a story. (And in
| those cases I may use a lot of `git stash -u` and `git
| stash pop` snapshots, too, especially if I need to switch
| branches in that worktree.)
| ptx wrote:
| Hmm, I may have actually solved my problem. I think what
| I mostly want to do is this, if I'm working on the
| "feature-1" branch based on "main": $ git
| checkout feature-1 $ git reset main $ git
| commit -a -C feature-1@{1}
|
| This squashes all the changes and keeps the date and and
| message of the final commit on the branch.
|
| Weirdly, although the "-c" and "-C" flags for the fixup
| operations sound like they should correspond to the same
| flags for "git commit", which grabs the date along with
| the message from the specified commit, the fixup flags
| only affect the message and not the date.
|
| It would be nice if it worked the same for rebase as for
| commit. Then "fixup -C" would essentially correspond to
| Mercurial's "fold".
| WorldMaker wrote:
| Ah, yeah, that use of `git reset --soft` is how many
| places do squash merging, so that makes sense. You may
| want to make sure to include the `--soft` flag explicitly
| just as a precaution.
|
| Also yeah, I would expect the fixup -c and -C flags to be
| more aligned with commit in a rebase.
| 1718627440 wrote:
| git fixup doesn't take the old commit message either, so
| I would be not so surprised that it doesn't take the
| commit message. It is really for preparing to do rebase
| fixup to the older commit.
| 1718627440 wrote:
| You could use "git reset --soft oldest-commit" and then "git
| commit -C newest-commit".
| steveklabnik wrote:
| If you ever try out jj, both fold and roll exist as `jj
| squash`. You'd choose as the destination the change of the time
| you'd want to keep.
| foobarian wrote:
| 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
| :-)
___________________________________________________________________
(page generated 2025-12-05 23:01 UTC)