mcaplan
mcaplan

Reputation: 77

GIT Branch / Merge Strategy (No Junk In The Trunk)

I'm currently investigating how to move a subversion repo to git (bitbucket). The biggest blocker for me is understanding how to translate our current SVN based branches / merge strategy to git.

We practice something similar to "no junk in the trunk." Specifically, we have two branches (one for active development, one for staging / QA), and trunk (production):

dev ______________
         \  \ \  (selective merges)
staging ___________
             \  \  (selective merges)
trunk ________________________________

Practically speaking:

  1. each developer works on a new development feature, committing their changes to the development branch
  2. When the feature is ready for testing, the development revisions related to the feature get merged to staging
  3. Once the feature is ready for rollout, the related staging revisions get merged to trunk

From a subversion vantage, it kinda looks like this:

  1. Work in development
    • svn checkout .../branches/dev (ie: working in dev branch)
    • svn commit -m "re ticket #1234" (several commits could take place, and in the example they are revisions 100, 102, and 105)
  2. Merge feature to staging
    • svn checkout .../branches/staging (ie: working in staging branch)
    • svn merge .../branches/dev -c 100,102,105
    • svn commit -m "re #1234 revs 100,102,105" (this could then create revision 110)
  3. Merge feature to production
    • svn checkout .../trunk (ie: working in trunk)
    • svn merge .../branches/staging -c 110 (assuming that there is only one revision for this feature in staging... there could be more)
    • svn commit -m "re #1234 revs 110"

This structure allows us to hit the following goals:

For all the googling around on git branch / merge strategies, the best solution I have seen involves topic / feature branches that then get merged into a main code line. This seems to involve a merge all or nothing process.

For the most part, I don't care if the process is functionally the same in git, but the features of the strategy are comparable.

Any pointers for the git newbie?

Thanks,

Mike

PS - After more digging, I came across the skullcandy workflow outlined here seems to be the closest to what I want to do. My best guess is that it would be done via git (with lots of bitbucket pull requests sprinkled in) like this (assuming two existing branches - master and staging):

This seems like a good process. Best practice? I don't know.

After the process is completed, one "side effect" I don't understand is that bitbucket reports the staging branch is being behing and ahead of master?

Upvotes: 0

Views: 1254

Answers (1)

Schwern
Schwern

Reputation: 164769

The workflow you outline at the end of your post is the basic Github workflow, but it has a flaw. Your feature branches are off of master but they are merged into staging and master. This is the result after the first double merge.

          - - - - - 5 [staging]
         /         /
        | Z - Y - X
        |/         \
1 - 2 - 3 - - - - - 4 [master]

Note that while staging and master have the same content, they have different commit ids and histories. This will make things complicated. Not only will you have to resolve any merge conflicts twice (and possibly differently) but it kills a major feature of Git: the same commits have the same IDs.

Wait, it gets worse. Because you merge to staging immediately, then QA, then merge to master after QA, it's possible for features to be merged to master and staging in different orders. Not only does this produce a gross history, it can produce different merge conflicts with different resolutions. Now you really don't know if master and staging are the same. You can check with git diff master staging but it'll be hard to track down where the differences leaked in and why.

            - - - - - 5 - - - - 6 - 7 [staging]
           /         /         /  /
          /         / E - F - G -+-
         /         / /          /  \
        | Z - Y - X /  J - K - L    \
        |/         \|/          \    \
1 - 2 - 3 - - - - - 4 - - - - - - 8 - 9 [master]

In this example, EFG and JKL both start from the same commit on master. EFG is merged into staging before JKL, but JKL completes QA first and is merged back into master before EFG. master and staging are now possibly different. Also your history graph is gross and hard to understand.

I guess you're using this Skullcandy workflow? I've never seen a workflow like that in practice where you habitually merge to two branches. It seems overly complicated. You're new to Git, keep it simple. Just branch from master and merge back into master. This produces a graph with the desirable "feature bubbles".

          Z - Y - X   J - K - L
         /         \ /         \
1 - 2 - 3 - - - - - 4 - - - - - 8 - 9 [master]
                     \             /
                      E - F - G - -

Now there is only one order in which features are merged.

Staging is not necessary in this workflow. Individual changes can be QA'd directly from the feature branch before merging back into master.

It would look like this...

  1. git fetch origin; git checkout -b feature/1234 origin/master
  2. Work on the feature.
    1. Make changes.
    2. Run dev tests, fix until they pass.
    3. Commit.
    4. Repeat.
  3. Update the feature branch as necessary.
    1. git fetch origin
    2. If not shared, git rebase origin/master.
    3. If shared, git merge origin/master.
  4. git push -u origin feature/1234.
    1. CI server runs automated tests against feature/1234.
    2. If they fail, back to step 3.
  5. Create pull request for feature/1234 to master.
  6. QA tests feature/1234.
    1. Update the feature branch if necessary (see step 3).
    2. If it fails, back to step 2.
  7. After feature passes QA, then it is merged into master.
    1. Merge to master. git merge --no-ff master (or use Bitbucket's built in merging)
    2. Delete the remote branch. git push origin :feature/1234.
    3. Delete the local branch. git branch -d feature/1234.

The key difference is that the change is QA'd directly from the feature branch before merging. There's no need to maintain a separate staging branch and duplicate merging. No untested branches get merged into anything.

It's important to note that updating the feature branch by merging (or rebasing) master into it has the same content as if you merged the feature branch into master. This is why you can QA directly from the feature branch.

A staging branch should instead be used to track what you're testing for the next release candidate. It's used as a stable spot for QA to shield the release candidate from changes to master. It is nothing more than a spot on the normal history. In our example above, staging might point to commit 2.

Also note that rather than doing one big git commit -a when you're done with your feature, you can do small commits as you go. Fix a typo? Test & commit. Add a new method? Test & commit. Perform a refactoring? Test & commit. This breaks up your work into easy to understand chunks, it's much easier to map each line with an explanation of why it's there. When combined with testing, it makes debugging much easier. It was passing at the last commit, so it must be something changed since then. If your git diff is small it will be easier to spot the change which caused the bug. If you decide a change was a bad idea, it's easier to throw just that change out.


My central philosophy is that under normal operation feature work should be isolated, merging should be simple and the dev branch should be ready for QA at all times. Developers shouldn't have to merge into multiple branches (like in Skullcandy). Cherry picking changes between branches should be an exceptional operation. The dev branch should be CI tested. Feature branches should be complete before merging to development. Staging and production should be simply older commits on the development branch.

Git is far more flexible than SVN, exceptional cases can be handled as they happen. If hot fixes and cherry picking are necessary, they should be rolled back into development as quickly as possible. You can even use rebase to rewrite history and smooth everything out to make the normal flow work better. I know this idea of rewriting history probably freaks you out right now, you'll get used to it.

This makes development in branches faster and simpler and it increases the number of people who can review and merge changes.

Upvotes: 2

Related Questions