restack icon indicating copy to clipboard operation
restack copied to clipboard

Atomic updates

Open abhinav opened this issue 8 years ago • 3 comments

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

abhinav avatar Nov 02 '17 03:11 abhinav

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

sluongng avatar Aug 14 '22 10:08 sluongng

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.

kriskowal avatar Sep 01 '22 01:09 kriskowal

@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.

abhinav avatar Sep 01 '22 03:09 abhinav