Git Flow Branching Strategy

Git Flow

Git Flow uses multiple long-lived branches, including main, develop, release, and hotfix, in addition to short-lived feature branches. This strategy provides a structured process for managing different types of changes, making it ideal for large teams and complex projects.

Advantages:

  • Organized and structured process

  • Clear separation between production and development code

  • Scales well for large teams and complex projects

Disadvantages:

  • Can be overly complex for smaller teams or projects

  • Frequent merging and branch management can be time-consuming

Explain the Git Flow Branching Strategy

  • "In Git Flow, we manage our development using several long-lived and short-lived branches:

    • The main branch (or master) holds production-ready code and is always stable.

    • The develop branch is used for ongoing development and serves as the integration branch for features before they go live.

    • Feature branches are created for new features or tasks and merged into develop once they are complete and tested.

    • For releases, we use release branches to prepare the code for production.

    • In case of urgent fixes in production, we create hotfix branches off the main branch to resolve issues quickly."

(Note: Please *ignore the typo 'develop').

💻 Key Topics Covered:

  • How Git Flow branches work (main, develop, feature, release, hotfix)

  • Automating deployment using CI/CD Pipelines

  • Merging best practices

  • Use cases and benefits for large teams

Summary of Steps in Git Flow Process:

  1. Create the develop branch.

  2. Create a feature branch from develop for each new feature.

  3. Develop and commit the feature in the feature branch.

  4. Open a pull request and trigger the CI pipeline for testing and review.

  5. Merge the feature branch back into develop.

  6. Create a release branch for finalizing the release.

  7. Merge the release branch into main for production deployment.

  8. Tag the release for versioning.

  9. Merge the release branch back into develop to sync changes.

  10. Handle hotfixes by creating a hotfix branch from main and merging it back into both main and develop.

How Git Flow branches work (main, develop, feature, release, hotfix):

Step 1: Create and Switch to the develop Branch

Since develop acts as the main integration branch for ongoing development, the first step is to create it if it doesn’t already exist:

# Create and switch to the develop branch
git checkout -b develop

Step 2: Create a Feature Branch from develop

Each new feature or task should be developed in its own branch, created from the develop branch. This isolates new development until it’s ready to be merged back into develop.

# Create a feature branch from develop
git checkout -b feature-branch develop

Step 3: Develop the Feature

Now, make changes to your code in the feature branch and commit them. This may include writing code, adding tests, and making necessary changes.

# Make changes to the feature and commit them
git add .
git commit -m "Implement new feature"

Step 4: Open a Pull Request

Push your feature branch to the remote repository and open a pull request (PR) to merge it back into develop.

# Push the feature branch to the remote repository
git push origin feature-branch

After pushing, open a pull request on GitHub (or your Git platform) to merge the feature branch into develop. This triggers a Jenkins pipeline that will:

CI/CD Pipeline for Feature Branches

  • Use Case: Automatically run tests on feature branches before merging into develop.

    Automation Steps:

    • Set up a pipeline triggered by pull requests (PRs) to ensure that the code passes all tests before it is merged into develop.

    • This pipeline can:

      • Build the application.

      • Run unit and integration tests.

      • Perform code quality checks using tools like SonarQube.

      • Conduct security scans using tools like Trivy or Aqua Security.

Benefits:

  • Ensures that only clean, tested code is merged into develop.

Feature Branche Pipeline Example:

pipeline {
    agent any
    stages {
        stage('Checkout') {
            steps {
                git branch: 'feature/*', url: 'https://github.com/your-repo-url.git'
            }
        }
        stage('Build') {
            steps {
                sh 'mvn clean package'  // Example build command for Java app
            }
        }
        stage('Unit Tests') {
            steps {
                sh 'mvn test'  // Run unit tests
            }
        }
        stage('Code Quality Check') {
            steps {
                sh 'mvn sonar:sonar'  // Code quality analysis using SonarQube
            }
        }
        stage('Security Scan') {
            steps {
                sh 'trivy image your-app-image:tag'  // Security scan for Docker image
            }
        }
    }
    post {
        success {
            echo 'Feature branch build and tests passed!'
        }
        failure {
            echo 'Build or tests failed, please review.'
        }
    }
}

Step 5: Merge Feature Branch Back into develop

Once the Jenkins pipeline passes, and the pull request has been reviewed and approved, the feature branch can be merged back into develop.

# Switch to the develop branch
git checkout develop

# Merge the feature branch into develop
git merge feature-branch

Once merged, Jenkins triggers the pipeline for the develop branch:

CI/CD Pipeline for develop Branch

Use Case: Deploy code from the develop branch to a staging environment for integration testing.

Automation Steps:

  • Once a feature branch is merged into develop, trigger an automated pipeline to:

    • Build and test the code.

    • Deploy the application to a staging or QA environment for further testing.

    • Run end-to-end tests to ensure the full functionality works as expected.

Benefits:

  • Ensures that the develop branch remains stable and ready for integration.

Develop Branch Pipeline Example:

pipeline {
    agent any
    stages {
        stage('Checkout') {
            steps {
                git branch: 'develop', url: 'https://github.com/your-repo-url.git'
            }
        }
        stage('Build') {
            steps {
                sh 'mvn clean package'
            }
        }
        stage('Integration Tests') {
            steps {
                sh 'mvn verify'  // Run integration tests to ensure compatibility
            }
        }
        stage('Deploy to Staging') {
            steps {
                sh './deploy-to-staging.sh'  // Custom script to deploy to staging environment
            }
        }
        stage('End-to-End Tests') {
            steps {
                sh './run-e2e-tests.sh'  // End-to-end tests in staging
            }
        }
    }
    post {
        success {
            echo 'Code successfully tested in staging environment.'
        }
        failure {
            echo 'Issues found in integration or e2e tests.'
        }
    }
}

Step 6: Create a Release Branch

When the develop branch is stable and ready for release, create a release branch. This branch is used to prepare the code for production, fix any final bugs, and ensure documentation is updated.

# Create a release branch from develop
git checkout -b release/v1.0 develop

On creating the release branch, Jenkins will trigger another pipeline to perform:

  • CI/CD Pipeline for Release Branches

    Use Case: Prepare the code for production by running final tests and deployment scripts.

    Automation Steps:

    • When a release branch is created, trigger the following in your CI/CD pipeline:

      • Run all final tests.

      • Build the application with production-ready configurations.

      • Optionally, update documentation and run security audits.

      • Deploy the code to a staging environment for final validation before it’s merged into main.

Benefits:

  • Ensures that the release branch is fully ready for production with minimal risk.

Release Branch Pipeline Example:

pipeline {
    agent any

    // Trigger the pipeline when working with release branches
    triggers {
        pollSCM('* * * * *') // Adjust polling as per need (e.g., using Git hooks instead)
    }

    environment {
        SONARQUBE_URL = 'http://sonarqube-url'
        TRIVY_IMAGE = 'your-app-image:latest'
        DOCS_GENERATOR_SCRIPT = './generate-docs.sh' // Adjust the script path as needed
    }

    stages {
        stage('Checkout Code') {
            steps {
                // Only run for release branches
                script {
                    if (env.BRANCH_NAME.startsWith("release/")) {
                        echo "Processing release branch: ${env.BRANCH_NAME}"
                    } else {
                        error("This pipeline should only run on release branches.")
                    }
                }
                git branch: env.BRANCH_NAME, url: 'https://github.com/your-repo-url.git'
            }
        }

        stage('Build') {
            steps {
                echo 'Building the release...'
                sh 'mvn clean package' // Example for Java app
            }
        }

        stage('Final Tests') {
            steps {
                echo 'Running final tests...'
                sh 'mvn test'
                sh 'mvn verify' // Integration and other tests
            }
        }

        stage('Code Quality Check') {
            steps {
                echo 'Running code quality check...'
                withSonarQubeEnv('SonarQube') {  // Use your SonarQube setup
                    sh 'mvn sonar:sonar'
                }
            }
        }

        stage('Security Scan') {
            steps {
                echo 'Running security scan using Trivy...'
                sh "trivy image ${TRIVY_IMAGE}"
            }
        }

        stage('Documentation Preparation') {
            steps {
                echo 'Generating documentation...'
                sh "${DOCS_GENERATOR_SCRIPT}" // Custom script to generate documentation
            }
        }

        stage('Archive Artifacts') {
            steps {
                echo 'Archiving build artifacts...'
                archiveArtifacts artifacts: '**/target/*.jar', allowEmptyArchive: true // For Java apps
            }
        }
    }

    post {
        always {
            echo 'Cleaning up workspace...'
            cleanWs()
        }

        success {
            echo 'Release branch successfully processed!'
            // Additional steps like sending notifications can be added here
        }

        failure {
            echo 'Error occurred while processing the release branch.'
            // You can add failure notifications or reports here
        }
    }
}

Step 7: Merge the Release Branch to main

Once the release branch has passed all testing and is ready for production, merge the release branch into main (or master).

# Switch to the master (main) branch
git checkout master

# Merge the release branch into master
git merge release/v1.0

At this point, Jenkins will trigger the production deployment pipeline, deploying the code to the live environment.

CI/CD Pipeline for main Branch with Manual Approval

Use Case: Automatically deploy code from the main branch to the production environment after a successful merge from the release branch, but with a manual approval step.

Automation Steps:

  1. After merging the release branch into main, trigger the pipeline to:

    • Build the final production artifacts.

    • Wait for manual approval to proceed with deployment.

    • Once approved, deploy the application to the production environment.

    • Run post-deployment health checks to ensure the application is running correctly.

Benefits:

  • Provides an additional layer of verification through manual approval before production deployment.

  • Reduces the risk of unintended changes being deployed to production by requiring human intervention.

Production Deployment Pipeline Example:

pipeline {
    agent any

    environment {
        MAIN_BRANCH = 'master'  // Use 'main' if that's your default branch
        DEPLOY_SCRIPT = './deploy-to-prod.sh'  // Script for production deployment
    }

    stages {
        stage('Build Production Artifacts') {
            when {
                branch "${MAIN_BRANCH}"  // Only run when on main branch
            }
            steps {
                echo 'Building final production artifacts...'
                sh 'mvn clean package'
            }
        }

        stage('Manual Approval for Production Deployment') {
            input {
                message "Approve deployment to production?"  // Prompt for manual approval
                ok "Deploy to Production"  // Button text for approval
            }
            steps {
                echo 'Manual approval received. Proceeding to deploy to production...'
            }
        }

        stage('Deploy to Production') {
            steps {
                echo 'Deploying application to production...'
                sh "${DEPLOY_SCRIPT}"  // Custom script for production deployment
            }
        }

        stage('Post-Deployment Health Checks') {
            steps {
                echo 'Running post-deployment health checks...'
                sh './check-app-health.sh'  // Custom script for verifying app health
            }
        }
    }

    post {
        success {
            echo 'Production deployment completed successfully!'
            // Optionally, send notifications about successful deployment
        }

        failure {
            echo 'Production deployment failed.'
            // Optionally, send failure notifications
        }

        always {
            cleanWs()  // Clean workspace after the job completes
        }
    }
}

Step 8: Tag the Release

After deploying the release to production, create a tag to mark the specific version of the release.

# Tag the release in the master branch
git tag -a v1.0 -m "Release version 1.0"

# Push the tag to the remote repository
git push origin v1.0

Step 9: Merge the Release Branch Back into develop

To ensure that any final changes or fixes in the release branch are not lost, merge the release branch back into develop.

# Switch to the develop branch
git checkout develop

# Merge the release branch into develop
git merge release/v1.0

Step 10: Handle Hotfixes

If a critical bug is found in production, create a hotfix branch from main, apply the fix, and merge it back into both main and develop.

# Create a hotfix branch from master
git checkout -b hotfix/critical-issue master

# Make the fix and commit
git add .
git commit -m "Fix critical production issue"

# Merge the hotfix back into master
git checkout master
git merge hotfix/critical-issue

# Merge the hotfix back into develop
git checkout develop
git merge hotfix/critical-issue

Once the hotfix is tested, Jenkins will deploy it to production and ensure both the main and develop branches are up to date.

CI/CD Pipeline for Hotfix Branches:

Use Case: Deploy urgent hotfixes to production directly from the hotfix branch.

Automation Steps:

  • When a hotfix branch is created, automate the process to:

    • Build the code and run any critical tests.

    • Deploy the hotfix to production immediately.

    • Merge the hotfix back into both develop and main to ensure consistency.

Benefits:

  • Ensures that production issues are resolved quickly without waiting for the next release cycle.

Hotfix to Production Deployment Pipeline Example:

pipeline {
    agent any

    environment {
        HOTFIX_BRANCH = env.BRANCH_NAME // Assuming the pipeline is triggered by the hotfix branch
        MAIN_BRANCH = 'master'           // Name of your main branch (can be 'master' or 'main')
        DEVELOP_BRANCH = 'develop'       // Name of your develop branch
        TRIVY_IMAGE = 'your-app-image:hotfix'  // Docker image for hotfix
    }

    stages {
        stage('Validate Hotfix Branch') {
            steps {
                script {
                    if (!env.BRANCH_NAME.startsWith('hotfix/')) {
                        error('This pipeline should only run on hotfix branches.')
                    } else {
                        echo "Processing hotfix branch: ${env.BRANCH_NAME}"
                    }
                }
            }
        }

        stage('Checkout Hotfix Branch') {
            steps {
                echo 'Checking out the hotfix branch...'
                git branch: "${HOTFIX_BRANCH}", url: 'https://github.com/your-repo-url.git'
            }
        }

        stage('Build Hotfix') {
            steps {
                echo 'Building the hotfix...'
                sh 'mvn clean package' // Example build command
            }
        }

        stage('Run Hotfix Tests') {
            steps {
                echo 'Running tests on the hotfix...'
                sh 'mvn test'
            }
        }

        stage('Security Scan') {
            steps {
                echo 'Running security scan on the hotfix...'
                sh "trivy image ${TRIVY_IMAGE}"
            }
        }

        stage('Deploy Hotfix to Production') {
            steps {
                echo 'Deploying the hotfix to production...'
                sh './deploy-to-prod.sh' // Custom script to deploy hotfix to production
            }
        }

        stage('Merge Hotfix into Master') {
            steps {
                echo 'Merging hotfix into master...'
                script {
                    // Merge hotfix branch into master branch
                    sh '''
                    git checkout ${MAIN_BRANCH}
                    git pull origin ${MAIN_BRANCH}
                    git merge ${HOTFIX_BRANCH}
                    git push origin ${MAIN_BRANCH}
                    '''
                }
            }
        }

        stage('Merge Hotfix into Develop') {
            steps {
                echo 'Merging hotfix into develop...'
                script {
                    // Merge hotfix branch into develop branch
                    sh '''
                    git checkout ${DEVELOP_BRANCH}
                    git pull origin ${DEVELOP_BRANCH}
                    git merge ${HOTFIX_BRANCH}
                    git push origin ${DEVELOP_BRANCH}
                    '''
                }
            }
        }
    }

    post {
        success {
            echo 'Hotfix successfully applied, merged into main and develop!'
            // Optional: send notification to the team
        }

        failure {
            echo 'Hotfix process failed. Please investigate.'
            // Optional: send failure notification
        }

        always {
            cleanWs() // Clean up workspace after the job
        }
    }
}

Merging Best Practices in Git Flow:

  1. Frequent Merges into develop:
    Merge feature branches into develop often to avoid complex conflicts and keep the codebase up to date.

  2. Use Pull Requests:
    Always merge via pull requests to encourage peer reviews, ensuring code quality and adherence to standards.

  3. Keep Feature Branches Small:
    Keep feature branches short-lived and focused on specific tasks to reduce complexity and simplify merges.

  4. Test Before Merging:
    Run automated tests as part of your CI pipeline before merging into develop or main to prevent introducing bugs.

  5. Rebase Regularly:
    Rebase feature branches onto develop regularly instead of merging to keep the commit history clean.

  6. Squash Commits:
    Squash commits when merging to create a clean, meaningful commit history.

  7. Merge Back into develop and main:
    After a release or hotfix is merged into main, merge it back into develop to keep branches in sync.

  8. Resolve Conflicts Early:
    Address merge conflicts immediately to avoid escalating complexity and potential issues.

Use Cases of Git Flow for Large Teams:

  1. Complex Projects with Multiple Features:
    Git Flow allows developers to work on separate feature branches, ensuring isolated development without affecting the main codebase.

  2. Organized Release Management:
    Release branches help teams finalize code for production while allowing ongoing development on other features.

  3. Quick Fixes for Production Issues:
    Hotfix branches enable rapid fixes for critical production bugs without waiting for the next release.

  4. CI/CD Integration:
    Git Flow integrates well with CI/CD pipelines, allowing large teams to automate testing, building, and deployment for smoother workflows.

In conclusion, Git Flow provides a structured approach to managing code for large teams and complex projects by using a mix of long-lived and short-lived branches. It ensures clear separation between production and development environments, streamlining workflows and collaboration. While its complexity may be unnecessary for smaller projects, Git Flow enhances stability and quality in software releases.

"Tell me and I forget, teach me and I may remember, involve me and I learn." – Benjamin Franklin

Thank you, Happy Learning!

0
Subscribe to my newsletter

Read articles from Subbu Tech Tutorials directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Subbu Tech Tutorials
Subbu Tech Tutorials