3

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.

Ayush Goel
  • 3,134
  • 27
  • 36
  • How are master and feature related? It only makes sense to revert a commit on the same branch the commit was made (meaning that you can trace back from the point where you want to apply the revert back to the original commit). – Lasse V. Karlsen Nov 09 '17 at 08:34
  • 1
    Can you find the difference between C1 and its parent, and apply that as a patch on master? Like in https://stackoverflow.com/questions/16675766/get-the-difference-between-two-branches-in-git – Martin Cook Nov 09 '17 at 08:35
  • If you can't revert C1 on top of master, how about reverting it on top of your feature branch, then cherry-picking/rebasing that commit on top of master? And if that is possible I would simply revert C1 on top of C1, and rebase (move) it onto master. – Lasse V. Karlsen Nov 09 '17 at 08:37
  • @LasseVågsætherKarlsen master is the build branch, in which all the feature branches are to be merged. I am preferring to squash merge so that in case I can revert a feature easily if required – Ayush Goel Nov 09 '17 at 08:49
  • Also, rebasing wouldn't help because of the same reason, easiness in reverting a whole feature. – Ayush Goel Nov 09 '17 at 08:55
  • have you pushed your master to origin? one simple straight forward approach is use `git reset --hard HEAD~1` and remove your branch commit from master head. – danglingpointer Nov 09 '17 at 09:02
  • Yes I would have mostly pushed to origin. – Ayush Goel Nov 09 '17 at 09:18

1 Answers1

3

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 have C1 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 <ethomson@edwardthomson.com>
Date:   Thu Nov 9 10:20:31 2017 +0000

    Squashed commit of the following:

    commit 8a8a9e73e62e21683e15269d89e1fbfbbf35cfa1
    Author: Edward Thomson <ethomson@edwardthomson.com>
    Date:   Thu Nov 9 10:20:18 2017 +0000

        C2

    commit d984b27140e48c5faa8968364c415d29dcd7034c
    Author: Edward Thomson <ethomson@edwardthomson.com>
    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 <ethomson@edwardthomson.com>
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
Edward Thomson
  • 74,857
  • 14
  • 158
  • 187