Git surgery #1
Tags: git, gitcommands, gitflow
Published: 2019-04-28 17:39:36

---
title: Git surgery #1
published: true
description: Two features are merged, but only the second one must go to production? Don't panic!
tags: git, gitcommands, gitflow
series: Git surgery
---

## The context

At work we use Vincent Driessen's [Git Flow](https://nvie.com/posts/a-successful-git-branching-model/) 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):

![](https://thepracticaldev.s3.amazonaws.com/i/26jgkkdkvn5fiht9iofg.jpg)

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*)
1. On branch `develop`, [we reverted the commit that merged `feature/A` into `develop`](https://git-scm.com/book/en/v2/Git-Tools-Advanced-Merging#_reverse_commit) (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`
    ![](https://thepracticaldev.s3.amazonaws.com/i/9uuealxqegrlh9cecdrb.jpg)

1. 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`

1. Still on `release/1.2.3`, we made a commit to bump the version number (a change [required by Git Flow](https://nvie.com/posts/a-successful-git-branching-model/#creating-a-release-branch))
    - `<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`
    ![](https://thepracticaldev.s3.amazonaws.com/i/31ggvxrstvsugoond64c.jpg)
    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.

1. 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`
    ![](https://thepracticaldev.s3.amazonaws.com/i/latzqfbtzmzo8v6i0r3b.jpg)
    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.

1. 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`
    ![](https://thepracticaldev.s3.amazonaws.com/i/1joeg4k6mnmi6q0hm7gy.jpg)
    In practice, that brought the code of feature **A** (inserted in commits *B*, *C*, *D* and *E*) back to develop.

1. 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`
    ![](https://thepracticaldev.s3.amazonaws.com/i/olg2dr23ost655l0rlze.jpg)

## 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!](https://media.giphy.com/media/a0h7sAqON67nO/giphy.gif)