Search code examples
gitmergeversion-controlgit-mergegit-revert

GIT Workflow: Merge most of a feature branch into master, omitting one specific commit?


The context:

Two separate features were committed onto the same branch. The oldest commit represents one cohesive feature ("Feature 1"). All the subsequent commits represent a second, separate cohesive feature ("Feature 2").

In retrospect, these should have been separate feature branches, but they were all committed to the same branch.

The problem:

Now: The predictable problem has occurred. The dev is asked to merge Feature 2 into Master, but not Feature 1.

What they did:

#0 they branch off MASTER, intending to use the branch for feature 2 - calling their new branch "Feature_2".

#1 they forget what branch they're on (feature 2), and make/test/commit/push a load of file changes for "Feature 1" in a single commit.

#2, 3, 4 they then go on to make all the necessary changes for Feature 2, committing and pushing, to the same branch, as they go.

Now - someone asks them to commit feature 2 (commits #2, #3, #4) without feature 1 (commit #1).

#4 they created a new branch and called it, say, "BEFORE_REVERTING_#1"

#5 They Git Revert commit#1 (Via sourcetree's 'reverse changes...' option.)

#6 they merge commit#5 into master.

Final state:

Graph of final GIT history

So now they've succeeded in merging #2,3,4 into master, without the code from #1.

Questions:

  • Now or later, how do we pull Feature 1 / Commit #1 into Master? (Such that both Features 1 and 2 are in Master, and the history is somewhat decipherable, if not entirely streamlined)

    • Am I right in saying we can't do it with just merging, because BEFORE_REVERTING_#1 is just a pointer to the same node that was later reverted, and will be fast forwarded, and need to use some other .git feature?

    • If you happne to know SourceTree UI - Is there an easy way to do this using the SourceTree?

  • What should the dev have done, instead of the approach in step 4/5, assuming #1-3 had already happened?

Thank you for any help or clarification you can provide!


Solution

  • Now or later, how do we pull Feature 1 / Commit #1 into Master?

    Cherry pick it. Essentially this copies the commit.

    git checkout master
    git cherry-pick <commit id of feature 1>
    

    What should the dev have done, instead of the approach in step 4/5, assuming #1-3 had already happened?

    Reverting the commit was fine. But they also could have split the feature branch into two branches.

    You had this.

    A - B [master]
         \
          1A - 2A - 2B [feature]
    

    You want this.

          2A - 2B [feature]
         /
    A - B [master]
         \
          1A [feature1]
    

    First, stick a new branch on 1A.

    git branch feature1 1A
    
    A - B [master]
         \
          1A [feature1]
            \
             2A - 2B [feature]
    

    Then rebase 2A and 2B on top of master.

    git rebase --onto master 1A 2B
    
          2A1 - 2B1 [feature]
         /
    A - B [master]
         \
          1A [feature1]
    

    And you're done.


    For a more complex untangling, use an interactive rebase to selectively remove commits. For example, if you had...

    A - B [master]
         \
          1A - 2A - 1B - 2B [feature]
    

    And you wanted...

          2A - 2B [feature]
         /
    A - B [master]
         \
          1A - 1B [feature1]
    

    Make a new branch at feature.

    git checkout feature
    git branch feature1
    
    A - B [master]
         \
          1A - 2A - 1B - 2B [feature]
                            [feature1]
    

    Then do an interactive rebase on each of them. Remove the offending commits. From feature remove 1A and 1B. From feature1 remove 2A and 2B.

    git checkout feature
    git rebase -i master
    # delete 1A and 1B
    
          2A - 2B [feature]
         /
    A - B [master]
         \
          1A - 2A - 1B - 2B [feature1]
    
    git checkout feature1
    git rebase -i master
    # delete 2A and 2B
    
          2A - 2B [feature]
         /
    A - B [master]
         \
          1A - 1B [feature1]