DEV Community

khalil la
khalil la

Posted on • Edited on

Streamlined Git Flow: Managing Projects with Master, Feature, and Delivery Branches

A Practical Approach to Git Branching that Maintains Code Integrity While Preserving Release History

1. Introduction

Version control is the backbone of modern software development, and having a clear, consistent Git workflow is essential for team productivity and code quality. This article presents a streamlined Git flow that balances simplicity with robustness, using three primary branch types: master, feature, and delivery.

2. The Branch Structure

Our Git flow revolves around four key branch types, each with a specific purpose:

  • Master Branch: The main branch where active development occurs, serving as the integration point and the source of truth for your codebase.

  • Feature Branches: Short-lived branches for developing new functionality.

  • Delivery Branches: Long-lived branches that capture the state of each release.

  • Hotfix Branches: Branches created to quickly address and fix critical issues in a released version, ensuring immediate resolution without disrupting ongoing development.

3. The Workflow in Detail

3.1. Starting a New Feature

Every new development task begins by creating a feature branch from the latest master:

git checkout master
git pull
git checkout -b feature/my-new-feature
Enter fullscreen mode Exit fullscreen mode

This ensures your feature is based on the most current codebase.

3.2. Feature Development

As you develop, commit your changes regularly to your feature branch:

git add .
git commit -m "Implement specific functionality"
Enter fullscreen mode Exit fullscreen mode

To avoid painful rebase conflicts later, regularly incorporate changes from master by rebasing:

git checkout feature/my-new-feature
git fetch origin
git rebase origin/master
Enter fullscreen mode Exit fullscreen mode

Rebasing instead of merging keeps the history linear and avoids unnecessary merge commits, which can clutter the history.

3.3. Completing Your Feature

When your feature is ready for review, push it to the remote repository:

git push origin feature/my-new-feature
Enter fullscreen mode Exit fullscreen mode

Then create a pull request to merge your feature into master. This step often involves:

  • Code review by team members
  • Running automated tests
  • Making requested changes

Note: Before submitting the pull request, it's good practice to rebase the feature branch onto the latest master to ensure your changes are based on the most up-to-date version of the code:

git checkout feature/my-new-feature
git fetch origin
git rebase origin/master
git push --force-with-lease
Enter fullscreen mode Exit fullscreen mode

This ensures that the pull request will apply cleanly and reduce potential merge conflicts.

3.4. Managing Releases

When preparing for a release, it's important to handle versioning carefully, ensuring each phase—next version, release candidate, and final version—has its own distinct marker.

3.4.1. Next Version Tagging

The next version refers to the ongoing work for a new release and can be tracked using the -next suffix. This version helps developers track continuous progress during the development cycle, signifying that the code is still evolving but intended for a future release.

3.4.1.1. Version Tagging Format:
  • Example: v1.0.0-next-0, v1.0.0-next-1, v1.0.0-next-2, etc.

The -next suffix is followed by a sequential number that increments with each iteration or set of changes in the code. This makes it easy to distinguish between different stages of the development process.

3.4.1.2. Creating and Pushing a Next Version Tag:
3.4.1.2.1. Update Master Branch:

Make sure your local master branch is up-to-date:

git checkout master
git pull origin master
Enter fullscreen mode Exit fullscreen mode
3.4.1.2.2. Create the Next Version Tag:

Tag the master branch with the next version:

git tag v1.0.0-next-0
git push origin v1.0.0-next-0
Enter fullscreen mode Exit fullscreen mode
3.4.1.2.3. Incrementing the next Version:

Each time a new iteration is made, update the -next suffix to reflect the next step in development, e.g., v1.0.0-next-1.

3.4.2. Release Candidate (RC) Versioning

The release candidate (RC) is a stable, pre-release version that has passed all functional testing but may still need some validation. This version indicates that the code is ready for production but may require final tweaks.

3.4.2.1. Version Tagging Format:
  • Example: v1.0.0-rc-0, v1.0.0-rc-1, v1.0.0-rc-2, etc.

Similar to the next version, RC versions increment sequentially as new release candidates are tested and prepared for final release.

3.4.2.2. Creating and Pushing an RC Tag:
3.4.2.2.1. Create a Delivery Branch from Master:

First, ensure that your delivery branch is ready by creating it from master:

git checkout master
git pull origin master
git checkout -b delivery/v1.0.0
Enter fullscreen mode Exit fullscreen mode
3.4.2.2.2. Final Testing:

Perform final tests, fix bugs, and prepare documentation or version updates as necessary.

3.4.2.2.3. Tag the RC Version:

Once the release candidate is stable, create an RC tag:

git tag v1.0.0-rc-0
git push origin v1.0.0-rc-0
Enter fullscreen mode Exit fullscreen mode
3.4.2.2.4. Iterate Through RC Versions:

If there are issues in the RC version, increment the rc number with each new fix, e.g., v1.0.0-rc-1, v1.0.0-rc-2, etc.

3.4.3. Final Release Version

The final release version is the last step in the release cycle. It represents the version that is stable and ready for deployment to production.

3.4.3.1. Version Tagging Format:
  • Example: v1.0.0

Once the release candidate has passed all tests and final adjustments have been made, the version is tagged as the final release version.

3.4.3.2. Steps for Creating the Final Release Tag:
3.4.3.2.1. Finalize Delivery Branch:

Once the RC is ready, finalize the delivery branch by ensuring it's up-to-date and stable:

git checkout delivery/v1.0.0
git pull origin delivery/v1.0.0
Enter fullscreen mode Exit fullscreen mode
3.4.3.2.2. Create the Final Release Tag:

Tag the final version with the v1.0.0 label:

git tag v1.0.0
git push origin v1.0.0
Enter fullscreen mode Exit fullscreen mode
3.4.3.2.3. Do not merge the Delivery branch into Master:

After creating the final release tag on your delivery branch, it's important to avoid merging the delivery branch back into master. There are several critical reasons for this approach:

  1. Maintaining Release Integrity: Delivery branches should remain as historical snapshots of what was released, unaltered by future development.

  2. Preventing Feature Contamination: Master typically evolves with new features and changes during the release stabilization period. These features weren't intended for the current release and shouldn't be mixed with released code.

  3. Avoiding Unexpected Side Effects: Merging delivery branches back to master can introduce unexpected behavior, especially if master has significantly diverged since the delivery branch was created.

Instead, when issues need to be fixed in a released version:

# Use the hotfix workflow described in Section 6
git checkout delivery/v1.0.0
git checkout -b hotfix/critical-issue-fix

# After implementing and testing the fix
# Apply to both the delivery branch and master separately
Enter fullscreen mode Exit fullscreen mode

This approach ensures clean separation between versions while still maintaining the ability to apply critical fixes where needed. It preserves the purpose of your branches: master for active development and delivery branches as immutable records of releases.

3.5. Housekeeping

After a successful release, clean up your completed feature branches:

git branch -d feature/my-new-feature
Enter fullscreen mode Exit fullscreen mode

Importantly, keep your delivery branches as a permanent record of each release.

3.6. Handling Hotfixes

When a critical issue is discovered in a released version, start the fix directly from the relevant delivery branch. This ensures the fix is based on the exact code that was deployed:

git checkout delivery/v1.0.0
git pull
git checkout -b hotfix/critical-issue-fix
Enter fullscreen mode Exit fullscreen mode

After implementing and testing the fix, follow these steps:

3.6.1. Update the Delivery Branch with the Hotfix

Create a pull request to merge the hotfix into the delivery branch:

# After committing your changes to the hotfix branch
git push origin hotfix/critical-issue-fix
Enter fullscreen mode Exit fullscreen mode

Once your team has reviewed and approved the hotfix, merge it into the delivery branch using your Git hosting platform (GitHub, GitLab, etc.).

3.6.2. Tag the Updated Delivery Branch with a Patch Version

After merging the hotfix, create a new patch version tag on the delivery branch:

git checkout delivery/v1.0.0
git pull origin delivery/v1.0.0
git tag v1.0.1
git push origin v1.0.1
Enter fullscreen mode Exit fullscreen mode

This creates a new release version that includes your hotfix.

3.6.3. Apply the Hotfix to Master

To ensure future releases also include this critical fix, create a separate pull request to apply the same fix to master:

# Create a new branch from master for the same fix
git checkout master
git pull origin master
git checkout -b hotfix/critical-issue-fix-master

# Apply the same changes as in the original hotfix
# This may require cherry-picking or manually implementing the same changes
Enter fullscreen mode Exit fullscreen mode

Push this branch and create a pull request to master, ensuring the commit message references the original hotfix and issue number for traceability.

3.6.4. Clean Up Branches

After both pull requests have been merged, clean up the temporary branches:

git branch -d hotfix/critical-issue-fix
git branch -d hotfix/critical-issue-fix-master
Enter fullscreen mode Exit fullscreen mode

This approach ensures your hotfix is applied both to the released version (via the delivery branch) and to ongoing development (via master), while keeping these processes separate to maintain the integrity of each branch. It also preserves the delivery branch as an accurate historical record of what was released.

4. Benefits of This Approach

4.1. Simplicity + Structure: Using just three core branch types (with hotfixes as needed) keeps cognitive load low.

4.2. Release Preservation: Delivery branches act as immutable records of what was released—this is excellent for traceability and auditing.

4.3. Rebase-Driven Clean History: Encouraging rebasing over merging avoids tangled commit graphs and unnecessary noise in the log.

4.4. Isolated Hotfix Workflow: Handling hotfixes off the delivery branch directly is an elegant solution that minimizes risk to in-progress work.

5. Conclusion

This streamlined Git flow provides a balanced approach that works well for many development teams. By incorporating git rebase into your workflow, you ensure a cleaner, more linear commit history, which is easier to maintain and navigate. It also reduces merge commits, which can sometimes clutter the project's history. By following these straightforward patterns, your team can focus less on Git complexities and more on delivering great software.


Have you implemented a similar Git flow in your team? What challenges or improvements have you discovered? Share your experiences in the comments below.

Top comments (0)