Andrew Willems
Andrew Willems

Reputation: 12458

Reversing an unintended git rebase change

I have performed a git command on my repository that wasn't appropriate and I'm trying to figure out how to reverse the effect of my actions.

My "pre-error" state (i.e. the state to which I am trying to revert) looked as shown here when logged with git log --oneline --graph --decorate --all (with my own extra comments added after-the-fact for clarity):

* 9aaaaaa (HEAD -> master) commit message 9
* 8aaaaaa commit message 8                  <=== note that this...
| * 7aaaaaa (branchB) commit message 7
|/  
| * 6aaaaaa (branchA) commit message 6
| * 5aaaaaa commit message 5
| * 4aaaaaa commit message 4, i.e. branchA~2
| * 3aaaaaa commit message 3
|/  
* 2aaaaaa commit message 2                  <=== ...descends from this
* 1aaaaaa commit message 1

I wanted to reword a commit in the branchA branch. So I entered the following command: git rebase -i branchA~2 intending to use the reword command and finishing. Once in the rebase window I decided against making any changes and quit. However, I suspect that I did not cancel the operation and quit as I should have, but rather saved the file and quit. The problem, I believe, is that to attempt what I was doing I should have checked myself into branchA first and then performed the git rebase -i branchA~2. However, instead, I inappropriately started from the master branch. Thus, I think I somehow told git that I now wanted my master branch to be connected in a new way to commit branchA~2, i.e. commit 4aaaaaa, which I do NOT want.

So, after the save-and-quit, my commit tree now looks like the following:

* 9bbbbbb (HEAD -> master) commmit message 9
* 8bbbbbb commit message 8                    <=== now, instead, this...
| * 7aaaaaa (branchB) commit message 7
| | * 6aaaaaa (branchA) commit message 6
| | * 5aaaaaa commit message 5
| |/  
|/|   
* | 4aaaaaa commit message 4, i.e. branchA~2  <=== ...descends from this...
* | 3aaaaaa commit message 3
|/  
* 2aaaaaa commit message 2
* 1aaaaaa commit message 1

Note that, as shown in the comments above, in the original state, commit 8aaaaaa directly descends from commit 2aaaaaa, but after my rebase command, the corresponding commit 8bbbbbb now descends from commit 4aaaaaa, i.e. from commit branchA~2.

So, how to I get back to my original state?

I haven't tried anything, because I'm very nervous about screwing things up further. Here are some possibilities:

Any suggestions?

Upvotes: 3

Views: 115

Answers (2)

zoul
zoul

Reputation: 104145

  1. Copy the whole repo so that you don’t have to be afraid to make further changes.
  2. Run git reflog. You should see the previous tips of your branches: 9aaaaaa, 7aaaaaa, 6aaaaaa.
  3. Reset your branches to these SHA1s: git checkout master && git reset --hard 9aaaaaa, etc.
  4. Did that work?
    • Yes: Great, let’s party.
    • No: Sorry about that, recover from the copy and wait for a better answer.

The important point here is that whenever you rewrite history, the old commits are not changed in place or deleted. Before rewrite:

...--A--B <-- branch

After rewrite:

...--A--B
  \
   \--A'--B' <-- branch

As you can see, nothing points to B, so the whole branch is “lost”, but it’s not deleted (unless you run git gc or something like that) and all you have to do to recover it is to flip the branch pointer back to B.

Upvotes: 4

Chris
Chris

Reputation: 8656

I'm not sure I entirely undertand what's happened from the graph, but my usual "panic button" in case of a borked rebase or change, is to inspect the reflog using git reflog.

From the documentation:

Reference logs, or "reflogs", record when the tips of branches and other references were updated in the local repository. Reflogs are useful in various Git commands, to specify the old value of a reference.

Essentially, you should be able to see where the tip of your current branch was before your rebase attempt. You can then use git reset <commit hash> to reset your branch to the state it was in, essentially ignoring your changes. It's often more straightforward than attempting to undo whatever your just did (and dealing with potential implications for people who have copies of the branch).

The reflog output may look something like (generated from a local repo):

75d2e74 HEAD@{0}: rebase -i (finish): returning to refs/heads/master
75d2e74 HEAD@{1}: rebase -i (reword): I was trying to do something with a change here. 
3b7e664 HEAD@{2}: cherry-pick: fast-forward
16180cd HEAD@{3}: <some other git operation>

You can see the prior operation to the rebase attempt was a cherry-pick, for you it will likely be something different. Inspect it with git show and see if it's an appropriate commit to use as your reset point.

Even if your reset goes wrong (e.g. you reset too far back), you can again inspect the reflog and have another go.

Upvotes: 2

Related Questions