Search code examples
git-revert

git revert : Unable to undo an individual commit even in a simple case


In order to try and understand git revert, I made a series of 4 simple commits -- A, B, C, D -- to a text file, foo.txt, with the intention of undoing only commit B later and leaving commits A, C, D intact.

So, in each commit, I added a new line each to the file, emulating either a feature added or a bug introduced.

After commit A, contents of foo.txt:

Feature A

After commit B, contents of foo.txt: (Here, I introduce a bug that I'll later try to undo/revert.)

Feature A
Bug

After commit C, contents of foo.txt:

Feature A
Bug
Feature C

After commit D, contents of foo.txt:

Feature A
Bug
Feature C
Feature D

Now, to undo the effects of Commit B (which introduced the bug), I did:

git revert master^^

What I expected to happen was, a new Commit E that removed the Bug line from the file, leaving the file contents as:

Feature A
Feature C
Feature D

However, I got the error:

error: could not revert bb58ed3... Bug introduced
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'

with the contents of the file following the unsuccessful git revert being:

Feature A
<<<<<<< HEAD
Bug
Feature C
Feature D
=======
>>>>>>> parent of bb58ed3... Bug introduced

( bb58ed3 is the hash of Commit B, and 'Bug introduced' this commit's comment.)

Question:

  1. What is going on here?

  2. If even such a simple, one-line commit cannot be reverted/undone automatically, and must require manual resolution from me, then how could I revert a much more complex commit whose original developer may even be someone else!

  3. Is there a special set of cases where git revert would be better applicable?


Solution

  • git sees each commit as a changelist (I simplify things here) and tries to "unapply" that when you call git revert. Each changelist also includes some context to ensure that the change makes sense. For example, if the change we want to make is "add return after line 10", it's more likely to break things than "add return after line 10, if lines 7-9 contain X, Y, and Z". So, we can describe your second commit as (again, simplifying this a little here):

    1. Assuming that the first line of the file is Feature A.
    2. Assuming that there is no second line.
    3. Make the second line contain Bug.

    After you've added few more lines, context of the Bug changed significantly, so git revert is not sure whether it can simply remove the line. Maybe the newly added lines actually fixed the bug. So it asks you to explicitly resolve the conflict of contexts.

    As for your questions 2-3: yes, git revert is usable in cases when you're reverting a piece of file which was not changed since then. For example, the bug was introduced in a foo function, but only bar function (which is located ten lines below) was modified since then. In that case, git revert is likely to automatically revert the change, because it sees that the context is unchanged.

    UPD: here is an example of why context matters even if you're trying to revert your own code:

    Commit A (mind the mistype):

    int some_vlue = 0;
    read_int_into(some_vlue);
    some_vlue = some_vlue++;
    

    Commit B (bug introduced):

    int some_vlue = 0;
    some_vlue = 123;
    some_vlue = some_vlue++;
    

    Commit C (name fixed):

    int some_value = 0;
    some_value = 123;
    some_value = some_value++;
    

    Now, in order to revert commit B, one have to have some context, as we cannot simply replace some_value = 123 with older line read_int_into(some_vlue) - it would be compilation error.