'What's different between `--abort` and `--quit` as sequencer subcommands for `cherry-pick`?

As per the doc, among the three sequencer subcommands for cherry-pick, we have these two which are, to me, oddly similar :

--quit

Forget about the current operation in progress. Can be used to clear the sequencer state after a failed cherry-pick or revert.

--abort

Cancel the operation and return to the pre-sequence state.


So far I've always used --abort, and it works great. What would be a use-case where --quit is different/preferable?



Solution 1:[1]

If you remember that git rebase is a sequence of git cherry-pick operations,1 plus a couple of useful gimmicks at the start and end, it makes more sense.

Imagine you have the following series of commits:

...--o--*--...--o   <-- mainline
         \
          A--B--C   <-- feature

You want to bring feature up to date with mainline so you:

git checkout feature
git rebase mainline

Git starts out by enumerating the commits reachable from feature (C, B, A, *, ...) and those reachable from mainline (unnamed, ..., *, ...). It subtracts the mainline set from the feature set2 and uses the reverse topological order so that it now has hash IDs A, B, and C listed in the thing that Git calls the sequencer. (The sequencer also records the operation, in this case rebase / cherry-pick. The sequencer is also used for multiple reverts. The sequencer can stop partway through, then be resumed with --continue. This is why it needs to know the operation: are we continuing a cherry-pick, a revert, or a rebase?)

Git then detaches HEAD at the commit at the tip of mainline and runs the op, whatever it is, via the sequencer. Since the op is "rebase", each step of the sequencer is a simple one-commit cherry-pick:

(with)

...--o--*--...--o   <-- mainline, HEAD
         \
          A--B--C   <-- feature

(execute git cherry-pick A to produce)

                  A'   <-- HEAD
                 /
...--o--*--...--o   <-- mainline
         \
          A--B--C   <-- feature

This can fail with a merge conflict. If so, the sequencer halts, leaving you a mess of merge conflicts in your index and work-tree. You can fix them and resume the sequencer (which will commit for you to make A' if needed, if you did not make A' yourself), or choose one of the two kinds of stop, "abort" or "quit". If you resume—or if things went well—we go on to (attempt to) cherry-pick B:

                  A'-B'   <-- HEAD
                 /
...--o--*--...--o   <-- mainline
         \
          A--B--C   <-- feature

Let's say that also succeeds and we go on to attempt to cherry-pick C but this attempt fails. The sequencer stops, leaving you a mess in your index and work-tree, with the same graph we see above: A' and B' exist but C' does not.

Let's say we decide to stop: that completing the cherry-pick of C is too hard for the moment and we need to back off and do something else for a bit. You now have the two options, abort or quit.

If you choose --abort, Git re-attaches your HEAD to feature, giving:

...--o--*--...--o   <-- mainline
         \
          A--B--C   <-- feature (HEAD)

Where are A' and B'? Well, if you know what you're doing, you can fish them out of the reflogs, or you already cleverly attached a branch or tag name to B' before choosing --abort. But if you choose --quit, Git terminates the rebase without moving HEAD, so that you wind up with:

                  A'-B'   <-- HEAD
                 /
...--o--*--...--o   <-- mainline
         \
          A--B--C   <-- feature

but can get yourself a clean index and work-tree with git reset --hard now. (Or, you can leave the mess in place.) So you don't have to be clever enough to attach a branch name before the --quit.

That's basically all there is to it. :-) But after a long and frustrating rebase with a lot of conflicts, where you want to save what you've achieved so far and go back to non-rebase work before coming back at the rebase again later, the "quit" variant feels more satisfying somehow.

(I think what's really missing here is the option to save the remainder of the sequencer state, and restore it later. However, the sequencer state is per-worktree, so if you have an ongoing rebase task and have to interrupt it with a higher priority task, you can just add a work-tree for the higher priority task. The various bugs in added work-trees through Git 2.15 is not all that confidence-inspiring, but they do seem to behave well now. Added work-trees also paper over the other, bigger missing piece, which is the ability to save an in-progress conflicted merge and restore it later.)


1Note, however, that the old-style non-interactive git-rebase--am still uses git format-patch and git am. This process does not work as well in some cases with renamed files and cannot copy a "makes no changes" commit, but does run faster. In most cases, both this and the cherry-pick style should give the same results, despite the change in underlying mechanism, especially since the cherry-pick variant defaults to not copying a "makes no changes" commit.

2The rebase also subtracts away any commits that exist in the mainline set and have the same git patch-id as any commits in the feature set, and of course, by default, it subtracts away all merges.

Solution 2:[2]

--abort will take you back to where you where before you started the cherry-pick operation whereas --quit will get out of the operation and keep you at the revision you are currently on. I can't think of a use case other than you wanting to go on a different direction after you hit a revision that could not be automagically cherry-picked, which is why cherry-pick stopped in the first place, right?

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 eftshift0