Reputation: 3154
I have 2 branches: master and feature.
• master
• feature
I make two commits on feature branch
• master
• C1 • C2 • feature
Now, I want to merge feature
in master. I use git merge --squash feature
to merge.
• C3 • master // C3 is the squashed commit
• C1 • C2 • feature
At this moment, is there a way to revert C1
from master?
One option is to revert C1
on feature, and squash merge feature again in master.
Upvotes: 3
Views: 540
Reputation: 78813
Yes, you can revert some commit even though it was included in the aggregate, using merge --squash
. git-revert
works by identifying the changes that were introduced in the commit that you identified, and then creating a new change that undoes ("reverts") those changes.
It uses the three-way merge algorithm to do this - using the commit to revert as the base, and then comparing against that commit's ancestor and the current commit (HEAD
). This will isolate the changes that were introduced only in that commit.
To look at a very contrived example, imagine that you had some file that had three changes (C0
, C1
and C2
) and these are the contents of that file at each version:
| C0 | C1 | C2 |
|-------|-------|-------|
| one | one | one |
| two | 2 | 2 |
| three | three | three |
| four | four | FOUR |
Now, if you want to revert C1
, we set up a three-way merge with it as the base, and C0
and C2
as each side to take changes from.
In a three-way merge algorithm, you look at each side, compared to the base. If all three lines are equal, you take that line into the result unmodified. If one side has made a change, you take the changed line into the result. (If both sides have made a change on the same line, you mark that line as a conflict.)
Setting up a revert gives you:
base sides result
---- ----- ------
one
/ two \
/ three \
one / four \ one
2 two
three three
four \ one / FOUR
\ 2 /
\ three /
FOUR
You can see that the result (on the right) that has undone the changes that were introduced in C1
, because one of the sides (C0
, in this case) was unique, so its changes were kept in the result. This has the logical effect of undoing ("reverting") the changes introduced therein.
This would be true even if you had done a squash merge - in this case, it's looking at the repository's contents. It doesn't matter that
CM
doesn't actually haveC1
as a common ancestor.
You can prove this to yourself in a simple repository with these contents. Even after a squash merge:
commit 9e7497c7ae34aa35cdb7d7b965a00d56bf0b9dfa
Author: Edward Thomson <[email protected]>
Date: Thu Nov 9 10:20:31 2017 +0000
Squashed commit of the following:
commit 8a8a9e73e62e21683e15269d89e1fbfbbf35cfa1
Author: Edward Thomson <[email protected]>
Date: Thu Nov 9 10:20:18 2017 +0000
C2
commit d984b27140e48c5faa8968364c415d29dcd7034c
Author: Edward Thomson <[email protected]>
Date: Thu Nov 9 10:20:08 2017 +0000
C1
You can still revert one of the components correctly. In this case, I'll revert C1
:
> git revert d984b27
[master 405f108] Revert "C1"
1 file changed, 1 insertion(+), 1 deletion(-)
> git show HEAD
commit 405f1080e24504fa418d423a0755a2123b85ecd8 (HEAD -> master)
Author: Edward Thomson <[email protected]>
Date: Thu Nov 9 10:20:42 2017 +0000
Revert "C1"
This reverts commit d984b27140e48c5faa8968364c415d29dcd7034c.
diff --git a/hello.txt b/hello.txt
index 9d980ae..14cf0bc 100644
--- a/hello.txt
+++ b/hello.txt
@@ -1,5 +1,5 @@
Hello, world!
one
-2
+two
three
four
Upvotes: 3