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:
What is going on here?
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!
Is there a special set of cases where git revert
would be better applicable?
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):
Feature A
.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.