I'm in the middle of a large "rebase in progress" with numerous conflicts.
I would like to set this progress aside and attempt to resolve this issue using another approach.
Is there a way I can save an in-progress rebase such that I can finish it later?
If you're sitting at a conflicted merge as part of a rebase, you are kind of stuck. Here is why, how, and what you can do.
Fundamentally, a rebase operation in Git is just a series of cherry-pick operations. We start with something like this:
...--A1--A2--A3--A4--A5 <-- branchA
\
B1--B2--B3 <-- branchB
and we want to end up with:
...--A1--A2--A3--A4--A5 <-- branchA
\ \
\ B1'-B2'-B3' <-- branchB
\
B1--B2--B3 [abandoned]
The way we (or Git) achieve this is by using git cherry-pick
, or something equivalent, to copy existing commit B1
(turning it into a patch and applying it) to come just after A5
, and copy B2
to come after B1'
, and so on.
Interactive rebase literally runs git cherry-pick
for each "pick" operation you leave in the instructions. Non-interactive rebase has several options, including running git cherry-pick
.
When cherry-picking a commit, if there are conflicts while applying it, Git can use a three-way merge. This can still fail with a conflict. This stops the rebase. Or, when using interactive rebase, you can choose to "edit" a commit, in which case Git cherry-picks that commit and then stops the rebase. In either case, Git leaves behind enough information for you to have Git resume the rebase later.
As a quick reminder, let's note that Git's index is where you build the next commit. Normally there is one entry for each file to be committed, so that if your next commit will consist of just three files named README
, file
, and otherfile
, there will be three index entries.
Note that the index is separate from the work tree, which contains files in normal, non-Gitty format. You can edit these files, compile them, use them to serve web pages, and so on, unlike the internal Git format for index and repository files. (The work-tree can also hold untracked files, not that this matters during rebase.)
During a conflicted merge, each index entry exposes its individual slots. There are up to four slots per entry, and they are numbered. Slot zero holds the normal, unconflicted file (if that exists), otherwise it's empty. Slots 1-3, if in use, hold the three conflicting parts that must be resolved.1 These are the base version (from the merge base), the "local" or --ours
version, and the other or --theirs
or sometimes "remote" version respectively. Your job is to edit the work-tree version of the file, resolve the conflicts, and then git add
the result. This copies the adjusted work-tree version into slot zero in the index, wiping out the slot 1-3 entries. Now the file is resolved and ready to commit.
1Hence, either slot 0 is occupied and 1-3 are empty, or else slot 0 is empty and slots 1-3 are occupied. There are some oddball cases where slot 1, 2, and/or 3 can also be empty, e.g., if you get a modify/delete conflict or an add/add conflict, but usually it's "0 empty means 1-3 are full" and vice versa.
The very phrase the index implies that there is only one. This is mostly true.
Because the unmerged state is in this ("the") index, and there is only one index, anything else that needs to use the index cannot proceed until you finish resolving the conflicts (and then make a commit).
You can, if you like, simply git add
the un-fixed/un-resolved items and git commit
the result, just to get the conflicts out of the way. The drawback here is that Git won't retain which files were conflicted: you will wipe out the slot 1-3 entries and Git will think you are all done.
You could save the index—it's an ordinary file; you can copy it out of .git/index
somewhere else. But because it's a binary file with various kinds of special internal use—the index is also called the "cache" and it caches internal file system data for speed—this is not really very safe. (It would be nice if Git had a way to "export" the index state, and then "import" it again later, so that you really could save and restore merge conflict states. But Git doesn't.)
So, for safety if nothing else, it's advisable to finish resolving this conflicted merge state. Or, if you have not started resolving, just don't even start: then there's no work to save.
Let's say you started that "branch B" rebase I drew above, and are currently stuck in the middle of copying commit B2
, with some conflicts unresolved. Here's what you actually have right now:
...--A1--A2--A3--A4--A5 <-- branchA
\ \
\ B1' <-- HEAD
\
B1--B2--B3 <-- branchB
with the index in conflicted state. You also have a "detached HEAD": Git is building the new chain of commits this way. The name HEAD
points to all completed commits.
If you have done some resolving work, you should finish it up (since it's too hard to save unresolved state) or at least make note of what's unresolved (since you can add files to your next commit) and then run git commit
to create commit B2'
:
...--A1--A2--A3--A4--A5 <-- branchA
\ \
\ B1'-B2' <-- HEAD
\
B1--B2--B3 <-- branchB
If you have not done any resolving work, there's no actual work to save, so don't run git commit
. But either way, now it's time to create a branch or tag name, pointing to the same commit that HEAD
now points to:
$ git branch saveme # or git tag saveme
Now you have this:
...--A1--A2--A3--A4--A5 <-- branchA
\ \
\ B1'-B2' <-- HEAD, saveme
\
B1--B2--B3 <-- branchB
Now you can just:
$ git rebase --abort
which makes Git stop the rebase attempt and move back to branchB
:
...--A1--A2--A3--A4--A5 <-- branchA
\ \
\ B1'-B2' <-- saveme
\
B1--B2--B3 <-- HEAD->branchB
Now you have saved all the work you have done so far, and can go back and retry the rebase later. You have the resolution you (or Git) made for B1'
, and if you made commit B2'
, you have the resolution you made for that as well. These are commits saveme~1
and saveme
, respectively; or just commit saveme
, if there is only the one commit.
I came up with an approach that works, but a bit of a hack:
Re-clone the repo into a different directory, leaving the rebase-in-progress as-is.
As torek mentioned, rebase is just a series of cherry-picks. If you are in a middle of a conflicting rebase (means some cherry-picks already happened) and you have to abort it, execute git rebase --abort
.
Later you can hit git reflog
which is kind of a history of the past git events. This will show you a list where you will see the abort event and all the previous cherry-pick
events one-by-one.
Every event belongs a hash. You can now jump to that last step before the abort by git reset --hard <hash-from-reflog-list>
.
You wont'be in rebasing mode of course, but you can now continue the rebasing with normal cherry-picks.
Be aware! This will roll back all your other git changes as well what you have done after the abortion.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With