PLB
PLB

Reputation: 197

Delete a feature merged and unmerged this feature in all following commits

I'm new with git, can't find an answer on internet with my terms for this question, but it probably has been asked.

So let's say my defaut branch is develop. I made a branch from develop called feature/test, do some commits on this branch, and then merge it to develop.

 ---1---2---3---4---M---5---6---7--- (develop)
             \     /                
              A---B    (feature/new)

After the merge M, I continue my project on develop, with several commits.

But now I want to unmerge the feature branch from all the commits following the branch, like if the branch develop has never merge feature/new.

So that's what I like to have :

 ---1---2---3---4---5'---6'---7'--- (develop)
             \                     
              A---B    (feature/new)

As you can see, all the commits following the M are modified to undo the modifications brought by the deleted merge.

Is it possible to do so and which command should I use ?

Thanks

Upvotes: 1

Views: 99

Answers (1)

Mark Adelsberger
Mark Adelsberger

Reputation: 45649

There are a number of ways to do it.

To get the history you're describing in your picture requires that you rewrite history. You should be aware that if this repo is shared with others, and if the history you want to rewrite has been shared (pushed to the remote), then doing it correctly takes some coordination - because rewriting shared history leads to error conditions which, if resolved incorrectly, will cause your work to be undone.

That said, to avoid rewriting history you would use git revert (or equivalent). Reverting a merge also has costs, and these costs apply whether or not you have shared history with others.

So in general it's best if you can avoid merging branches that you might have to "unmerge" later. But not being able to change the past, you'll have to make the best of the options you have.

To decide if you should rewrite history, I'd refer to the git rebase docs (https://git-scm.com/docs/git-rebase) in the section "Recovering From Upstream Rebase". You might also look at the git revert docs regarding merges (https://git-scm.com/docs/git-revert). Then depending on what you decide you can do one of the following:

Rewriting History

So: The easiest way to do what you've asked is

git rebase --onto develop~4 develop~3 develop

This takes the commits you want to keep, calculates patches for them, and applies those patches to a new parent; some details:

The expressions develop~4 and develop~3 are specific to your example.

develop~4 refers to the commit you find by following "first parent" pointers four times, starting from the commit pointed to by develop. This leads to 4. By giving this as the --onto argument, you tell rebase that the patches it calculates should be applied starting here.

Similarly develop~3 leads to M, so this is the "upstream". That means neither M nor any commit reachable from M (by parent pointers) will be used to create the patches. (If you didn't use --onto the upsteram would also play a role in where the patches are applied; but here we did use --onto so that doesn't matter.)

When the patches are applied, there could be conflicts. That's because something in 5, 6, or 7 might edit a line that had also been edited by M (or a line "close enough" to an edit by M, that it causes git to be unsure if there's a problem). Any reliable method of doing what you're asking would create the same conflicts, so you just have to figure out what the change "should have" looked like, if the merge had never happened.

If develop exists in a remote, and if M has already been pushed to that remote, then by default git will resist pushing the result of this change (since it removes M from the branch history). You can override this with the --force-with-lease option to the push command. (Some people say to use the -f option, but while this is easier to type, it is also less safe. -f won't check whether new commits you don't know about had been pushed to develop; --force-with-lease will.)

Without Rewriting History

If you can't (or don't want to) coordinate a history rewrite, then you have to accept that any history you've pushed is immutable. In that case you can revert the merge.

git revert -m 1 develop~3

The -m option tells git which of the merge's parents youre "reverting back to"; so by saying -m 1, you effectively undo the changes from the branch.

Just like the history-rewrite approach, this could conflict and require manual conflict resolution. The end reult would be

1 -- 2 -- 3 -- 4 -- M -- 5 -- 6 -- 7 -- !AB <--(develop)
           \       /
            A --- B <--(feature/new)

where !AB reverses the changes from A and B (which had been merged in at M).

Because A and B are still in the history of develop, if you later want to re-merge them it's not straightforward. You would either have to revert !AB, or use rebase -f to rewrite the history of feature/new before mreging.

Upvotes: 3

Related Questions