loading...

Git surgery #1

victorosilva profile image Victor Silva ・4 min read

The context

At work we use Vincent Driessen's Git Flow branching model. Recently we found ourselves in the following situation:

  • feature A had been finished and merged into develop
  • release 1.2.3, containing feature A, had been started
  • feature B had been started after the start of the release 1.2.3
  • there were some adjustment commits for feature A in the release 1.2.3 branch
  • feature B had been finished and merged into develop
  • feature B didn't depend on any code added by feature A.

Visually, that's what we had (each column represents a branch; and each point represents a commit, with a single letter instead of its hash):

Now we just had to test and finish the release 1.2.3 (merge it into master, create a tag 1.2.3 at the merge, and then merge the tag into develop in order to integrate commits F, G and J) and deploy the newly created tag into production. Then we would create another release for feature B (probably 1.2.4), test, finish and deploy it.

The problem

For reasons that are beside the point of this post, it was decided that feature B should make it into production before feature A. In order to not mess up our version numbers:

  • release 1.2.3 now should contain feature B, but
    • without the code of feature A commits (B, C, D and E)
    • without the code of the adjustment commits F, G and J
  • the "new" release 1.2.3 should be tested, finished and deployed
  • feature A and its adjustment commits should be in a new release (1.2.4)
  • commit history of develop should not be rewritten, for it's a shared branch and other features may have been started based on it.

The solution

Note: for simplicity, in the Git commands below I'll use the letter that was used to represent each commit in the images instead of the commits' hashes.

  1. We took note of the hashes of the feature A adjustment commits (F, G and J)
  2. On branch develop, we reverted the commit that merged feature/A into develop (the merge commit E), so that code related to feature A was "removed" from develop:

    • <master> $ git checkout develop
    • <develop> $ git revert -m 1 E # creates commit M
  3. We used reset --hard to reset the branch release/1.2.3 to develop, so that release/1.2.3 pointed to commit M

    • <develop> $ git checkout release/1.2.3
    • <release/1.2.3> $ git reset --hard develop
  4. Still on release/1.2.3, we made a commit to bump the version number (a change required by Git Flow)

    • <release/1.2.3> $ echo '1.2.3' > version.txt
    • <release/1.2.3> $ git add version.txt && git commit -m "Bump version number" # creates commit N Note that, since the reset --hard, the commits F, G and J have been "lost". That's why we took note of their hashes in the beggining - we'll bring them back later.
  5. After testing release 1.2.3, which now only contained code related to feature B, we finished (creating its tag and merging the tag into develop) and deployed it

    • <release/1.2.3> $ git checkout master
    • <master> $ git merge --no-ff release/1.2.3 # creates commit O
    • <master> $ git tag -a 1.2.3
    • ./fictional-deploy.sh
    • <master> $ git checkout develop
    • <develop> $ git merge --no-ff 1.2.3 # creates commit P At this point, we have released only feature B, but now we must bring back the code of feature A, create a new release with it, and also bring back its adjustments commits.
  6. On develop, we reverted the commit M, which was a revert commit itself. Yes, we "reverted a revert".

    • <develop> $ git revert M # creates commit Q In practice, that brought the code of feature A (inserted in commits B, C, D and E) back to develop.
  7. Finally, we created the new release (1.2.4) and brought the adjustments previously made to feature A into it

    • <develop> $ git checkout -b release/1.2.4
    • <release/1.2.4> $ git cherry-pick F # creates commit R
    • <release/1.2.4> $ git cherry-pick G # creates commit S
    • <release/1.2.4> $ git cherry-pick J # creates commit T

Conclusion

With a few basic Git commands (reset, revert, cherry-pick, etc.) we were able to solve our problem:

  • only code related to feature B has been deployed
  • feature A and its adjustment commits have not been lost
  • we haven't messed up our versioning numbers
  • the history of the branch develop has not been rewritten.

Success!

Discussion

pic
Editor guide