Tuesday, March 24, 2015

Rollback a specific commit and replace with the correct one - in git

For a specific task I should delete some files, move some others and update some more.
I committed something that was not completely correct (commit A). I needed to delete that commit and create a new one, to replace the old commit.
My options were:

1. to amend the commit (commit A) by making some corrections
(commit Y) <=================================== master
   |
   \
    -(commit X)-(commit A) <================ my-branch-name

2. to create a reverse commit and create a new correct commit (results in multiple commits)
(commit Y) <=================================== master
   |
   \
    -(commit X)-(commit A)-(reverse commit A)-(commit B) <== my-branch-name

3. to reset to the commit X, create a correct commit (commit B) on it, and then reset the HEAD of my-branch-name to that commit.
(commit Y) <=================================== master
   |
   |                     (commit A)
   \
    -(commit X)-(commit B) <================ my-branch-name

Option 1: amend previous commit

Option 1 would be as follows:
On top of the erroneous "commit A" I should provide all the corrective actions, stage them, and then amend the previous commit:
    git commit --amend

Option 2: create reverse commit

Option 2 has the below defects:
        * There were multiple deleted and moved files. It wouldn't be optimal to create a reverse commit and reapply my changes, since the commits should be squashed later.
        * The final changes should be included in a single commit, not multiple.

Option 3: reset to a specific commit

Option 3 is the most elegant and appropriate solution, especially when the changes are created by a script.
E.g. on the first run, the input of the script caused some changes and the commit A has been created. After that, new entries for the input of the script are coming. Then instead of finding which changes are not yet committed with commit A, it's better to reset to the previous commit, and execute again the script with the full new input. This will create a new complete set of changes that can be included in a single commit.
Execution of the script created changes A, B, D, F and G. All these changes are included in a new commit: "commit A". "Commit A" is the HEAD of my-branch-name.
New extra input to the script creates changes A, B, C, D, E, F and G. Imagine that this list could include thousands of entries. It's not easy to find the differences between the lists of changes and amend the "commit A".

To remove from the history my last erroneous commit (commit A), I need to reset the current branch to one commit backwards:
        git reset --hard HEAD~1

Perform the correct set of changes, and commit (commit B)
        git commit -m "comment for commit B"

To reset the current branch HEAD to the correct "commit B", that is not shown in git log, I can find the sha1 id of that particular commit executing:
        git reflog
       
And to reset the HEAD of my-branch-name to that commit I execute:
        git reset --hard <commit-B-sha1>