Search code examples
gitmergecommitrebaseconflict

Remove git merge commit without conflict


Below is my git commit history:

HEAD
... +500 commits
A   4140759b
A   c5cf69a2
A   3e5282cb  ⎯┐   << Merge branch B
|   94936548    B
|   02a550ce    B   
A   5d09daa9  ⎯┤   << Pull request
|   afbe397b    B   << Merge branch A into B
|   74fe4f3c    B
|   86cdc993    B
|   223e1740    B
|   a7ea596a    B
|   0d4c36cf    B
A   2740d295    |
A   24437962    |
A   73725f04    |
A   90be4300    |
A   6b1720d6  ⎯┘
A   d65bf4bd
...

I want to remove B branch history and merge commit, and leave A branch history only.

But, they are very old commits, with hundreds of commits between the latest commits,
so git rebase -i command makes a lot of conflicts that I couldn't solve.

It doesn't matter if a few commits, between d65bf4bd and c5cf69a2, dropped.
However, recent commits, after c5cf69a2, should not be affected.

How can I completely remove B branch history, from history?

My expected result looks like this:

HEAD
... +500 commits
pick    A   4140759b
pick    A   c5cf69a2
[drop]  A   3e5282cb  ⎯┐   << Merge branch B
[drop]  |   94936548    B
[drop]  |   02a550ce    B   
[drop]  A   5d09daa9  ⎯┤   << Pull request
[drop]  |   afbe397b    B   << Merge branch A into B
[drop]  |   74fe4f3c    B
[drop]  |   86cdc993    B
[drop]  |   223e1740    B
[drop]  |   a7ea596a    B
[drop]  |   0d4c36cf    B
pick    A   2740d295    |
pick    A   24437962    |
pick    A   73725f04    |
pick    A   90be4300    |
pick    A   6b1720d6  ⎯┘
pick    A   d65bf4bd
...

Solution

  • Here is one way to squash together the history of branch B:

    1. use git replace to create a commit, with the same end content but only one parent, and a replacement rule
    git replace --graft [target commit] [the parent you want]
    # with the hashes you provided:
    git replace --graft 3e5282cb 2740d295
    

    on your machine, git log --oneline --graph should already display a simplified history

    1. to make this replacement rule permanent and actually rewrite the history, use git filter-repo or git filter-branch
    git filter-repo --replace-refs delete-no-add
    
    # or
    git filter-branch --env-filter true
    

    a note about git filter-branch: making the replacement permanent is actually a side effect of git filter-branch [any command], you can use any subcommand (--index-filter, --env-filter, --tree-filter ...) and any scripted action as long as it makes git filter-branch go through. --env-filter true happens to be convenient, because git will not read or write any file on disk, and true has no side effects.