restack
restack copied to clipboard
Atomic updates
Currently, if a rebase fails after a few branches have been updated, you're left in an inconsistent state where half the branches have been updated and the other ones are still pointing to the old tree.
For example, in,
pick A
pick B
exec git branch -f feature1
pick C
pick D
exec git branch -f feature2
If pick C fails and you git rebase --abort, feature1 has still been
moved to a new position.
An alternative method is to use update-ref to place markers for where
branches will go if everything succeeds and commit these changes only if
everything was successful.
pick A
pick B
exec git update-ref refs/restack/feature1 HEAD
pick C
pick D
exec git update-ref refs/restack/feature2 HEAD
Then, if the rebase succeeds, we'll want to run branch -f with these refs.
Something equivalent to the following should suffice,
git for-each-ref --shell \
--format='git branch -f %(refname:lstrip=3) %(objectname) && \
git update-ref -d %(refname)' refs/restack/ | sh
This would be pretty unreadable to users but I think we can provide a nicer UI on top.
Instead of adding exec git branch -f or exec git update-ref instructions,
the git-rebase-todo that the user sees will contain a custom mark
instruction. The main instruction set will be followed by a lone restack
command which indicates the point at which the branches will be updated. The
opt-in push will follow the restack.
pick A
pick B
mark feature1
pick C
pick D
mark feature2
restack
# Uncomment this section to push the changes.
# exec git push -f origin feature1
# exec git push -f origin feature2
Caveat: This introduces custom language to git-rebase-todo. We can
technically avoid this by calling back into restack from the instruction
list without too much loss in readability. I'm undecided on this.
exec restack mark feature1
exec restack commit-marks
CC @kriskowal
It's probably a lot easier to implement this now that git has included a new hook https://git-scm.com/docs/githooks#_reference_transaction that enable refs to be updated within a transation.
You can check the documentation of git-update-ref to see how to start/commit/abort a ref update transaction https://www.git-scm.com/docs/git-update-ref
Another option is to have restack capture all the branch names a hash has gone by using a refs/notes/restack git note and liberally collecting note garbage between uses.
@sluongng Thanks for the tip! I looked around. It looks like that'll require a process that runs as long as the entire rebase operation, posting updates to the open transaction in the background, and then eventually committing. That sounds more complex than recording state on-disk, and then committing that state at the end.
@kriskowal thanks for that suggestion as well. Recording state in a note is a less noisy version of leaving placeholder refs inside refs/restack.
I'll try to play with both options a little more later if I get a chance.