Skip to main content

Migrating to GitFlow

Step-by-step guide for migrating existing projects to GitFlow and onboarding teams to the workflow.

Overview

This guide covers:

  • Migrating existing repositories to GitFlow
  • Setting up GitLab protection rules
  • Team onboarding and training
  • Tooling configuration
  • Common migration challenges
When to Migrate

Best times to migrate to GitFlow:

  • [GOOD] Between major releases: After v2.0.0 ships and before v2.1.0 development begins, there's minimal in-flight work to migrate. The team has mental space to learn new processes without delivery pressure.
  • [GOOD] During sprint planning: The sprint boundary provides a natural reset point. Features committed in the old workflow can complete, while new sprint stories start with GitFlow. This creates a clean transition without abandoning work.
  • [GOOD] When team size grows (>3 developers): Small teams can coordinate informally, but larger teams need structured workflows. Beyond 3 developers, merge conflicts and coordination overhead increase nonlinearly without branch conventions.
  • [GOOD] When release process becomes chaotic: Symptoms include hotfixes breaking new features, uncertainty about what's in production, or inability to deploy older versions. These indicate trunk-based development has exceeded its complexity threshold.

Avoid migrating:

  • [BAD] During active release preparation: Release stabilization requires focus. Learning a new workflow while managing release candidates divides attention and increases risk of mistakes.
  • [BAD] In middle of major feature development: Half-completed features create migration complexity. Either finish in the old workflow or pause work, migrate, then resume in the new workflow.
  • [BAD] During incident response: Production issues demand rapid fixes without process friction. Introduce GitFlow during stable periods when teams can invest time in learning.

Pre-Migration Assessment

Current State Analysis

Evaluate existing workflow:

# Analyze current branching patterns
git branch -r | head -20

# Check commit frequency
git log --since="1 month ago" --oneline | wc -l

# Identify active developers
git shortlog -sn --since="1 month ago"

# Review merge patterns
git log --merges --oneline --since="1 month ago" | head -20

Questions to answer:

  • How many active branches exist?
  • How many developers commit regularly?
  • What is the current release frequency?
  • Are there existing branch naming conventions?
  • Is there a code review process?
  • Are automated CI/CD pipelines in place?

Migration Readiness Checklist

  • Team buy-in: All developers agree to new workflow
  • Training scheduled: Time allocated for learning GitFlow
  • CI/CD ready: Pipelines can handle multiple branches
  • Protected branches supported: GitLab has branch protection
  • Code review process: MR approval workflow exists
  • Documentation updated: README reflects new workflow
  • Migration window identified: Low-risk time for transition
  • Rollback plan: Can revert if issues occur

Migration Strategy

Option 1: Clean Cut Migration

Best for: Small teams, projects with few active branches

Timeline: 1-2 days

Steps:

  1. Announce migration date (1 week notice)
  2. Merge all pending PRs
  3. Create develop branch from main
  4. Set up branch protection
  5. Update CI/CD configuration
  6. Conduct team training
  7. Start new features in feature branches

Option 2: Gradual Migration

Best for: Large teams, active projects with many in-flight features

Timeline: 4 weeks

Advantages:

  • Less disruptive to ongoing work: Features started under the old workflow can complete without process changes. Developers maintain productivity rather than stopping work to learn new conventions. Only new features adopt GitFlow, reducing cognitive load.
  • Team learns incrementally: Week 1 focuses on creating feature branches (simple). Week 2 adds code review requirements (moderate). Week 3 enforces protection (complex). This staged learning prevents overwhelming developers with all concepts simultaneously.
  • Can revert easily if issues arise: If Week 2 reveals the team isn't ready (e.g., too many conflicts, confusion about workflow), you can pause enforcement while providing additional training. Complete rollback is simple - just remove branch protection. Clean-cut migration requires full rollback if any part fails.

Step-by-Step Migration

Step 1: Create Develop Branch

# 1. Clone repository (if not already)
git clone [email protected]:company/payment-service.git
cd payment-service

# 2. Ensure main is up-to-date
git checkout main
git pull origin main

# 3. Create develop branch from main
git checkout -b develop
git push -u origin develop

# 4. Verify both branches exist
git branch -r
# Output:
# origin/develop
# origin/main

Verify in GitLab UI:

  • Navigate to Repository > Branches
  • Confirm develop branch appears
  • Set develop as default branch (Settings > Repository > Default branch)

Step 2: Configure Branch Protection

Branch protection rules in GitLab enforce workflow policies by adding server-side checks before allowing pushes or merges. These rules are stored in GitLab's database and evaluated on every push attempt and merge request. When configured, GitLab rejects API calls (including git push) that violate protection rules, returning a 403 Forbidden error. Protection levels are hierarchical: "No one" (0), "Developers" (30), "Maintainers" (40), and "Admins" (60).

Protect main Branch

Navigate to Settings > Repository > Protected branches

Branch: main
Allowed to merge:
- Maintainers
Allowed to push:
- No one
Allowed to force push:
- No (disabled)
Code owner approval required: Yes

Via GitLab API:

curl --request POST --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"https://gitlab.com/api/v4/projects/$PROJECT_ID/protected_branches?name=main&push_access_level=0&merge_access_level=40&code_owner_approval_required=true"

Protect develop Branch

Branch: develop
Allowed to merge:
- Developers + Maintainers
Allowed to push:
- No one
Allowed to force push:
- No (disabled)
Code owner approval required: No (unless specified)

Create Protected Branch Pattern for release/*

Branch pattern: release/*
Allowed to merge:
- Maintainers
Allowed to push:
- Maintainers
Allowed to force push:
- No (disabled)

Step 3: Configure Merge Request Approval Rules

Navigate to: Settings > Merge requests > Merge request approvals

Approvals required for main:
- Number of approvals: 2
- Eligible approvers: Maintainers, Tech Leads
- Prevent approval by author: Yes
- Prevent editing approval rules: Yes
- Remove all approvals on new push: Yes

Approvals required for develop:
- Number of approvals: 1
- Eligible approvers: All developers
- Prevent approval by author: Yes
- Remove all approvals on new push: Yes

Step 4: Set Up Code Owners

Create .gitlab/CODEOWNERS file:

# Default owners for everything
* @platform-team

# Payment processing requires payment team approval
/src/services/payment/ @payment-lead @payment-senior-dev
/src/models/payment/ @payment-lead

# Security-critical files
/src/security/ @security-team
/src/auth/ @security-team

# Infrastructure and CI/CD
/.gitlab-ci.yml @devops-team
/infrastructure/ @devops-team

# Database migrations always need DBA approval
**/db/migration/ @dba-team @backend-lead

Commit and push:

mkdir -p .gitlab
cat > .gitlab/CODEOWNERS << 'EOF'
# Code owners configuration
* @platform-team
EOF

git add .gitlab/CODEOWNERS
git commit -m "chore: add code owners configuration"
git push origin develop

Step 5: Update CI/CD Pipeline Configuration

Modify .gitlab-ci.yml to handle multiple branches:

# .gitlab-ci.yml

workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH

stages:
- lint
- test
- build
- security
- deploy

# Define variables for branch-specific deployments
variables:
DEPLOY_ENV: ""

# Determine deployment environment based on branch
.set_deploy_env: &set_deploy_env
before_script:
- |
if [ "$CI_COMMIT_BRANCH" == "main" ]; then
export DEPLOY_ENV="production"
elif [ "$CI_COMMIT_BRANCH" == "develop" ]; then
export DEPLOY_ENV="development"
elif [[ "$CI_COMMIT_BRANCH" == release/* ]]; then
export DEPLOY_ENV="staging"
fi

# Lint job (all branches)
lint:
stage: lint
script:
- echo "Running linters"
- npm run lint
- npm run format:check
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH =~ /^(feature|bugfix|hotfix)\//
- if: $CI_COMMIT_BRANCH =~ /^(main|develop|release\/)/

# Test job (all branches)
test:
stage: test
script:
- npm run test:unit
- npm run test:integration
coverage: '/Statements\s+:\s+(\d+.\d+)%/'
artifacts:
reports:
junit: junit.xml
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH

# Build job (all branches)
build:
stage: build
script:
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 week
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH

# Security scanning (all branches)
security-scan:
stage: security
script:
- npm audit --audit-level=high
- npm run test:security
allow_failure: false
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH

# Deploy to development (develop branch only)
deploy-dev:
stage: deploy
<<: *set_deploy_env
script:
- echo "Deploying to development environment"
- ./scripts/deploy.sh development
environment:
name: development
url: https://dev.payments.company.com
rules:
- if: $CI_COMMIT_BRANCH == "develop"

# Deploy to staging (release branches only)
deploy-staging:
stage: deploy
<<: *set_deploy_env
script:
- echo "Deploying to staging environment"
- ./scripts/deploy.sh staging
environment:
name: staging
url: https://staging.payments.company.com
rules:
- if: $CI_COMMIT_BRANCH =~ /^release\//

# Deploy to production (main branch only, manual trigger)
deploy-production:
stage: deploy
<<: *set_deploy_env
script:
- echo "Deploying to production environment"
- ./scripts/deploy.sh production
environment:
name: production
url: https://payments.company.com
when: manual
only:
- main

Commit pipeline changes:

git add .gitlab-ci.yml
git commit -m "ci: update pipeline for GitFlow branching strategy"
git push origin develop

Step 6: Update Documentation

Update README.md with new workflow:

# Payment Service

## Development Workflow

We use GitFlow branching strategy. See [Branching Strategy](branching-strategy.md) for details.

### Quick Start

```bash
# 1. Clone repository
git clone [email protected]:company/payment-service.git
cd payment-service

# 2. Create feature branch from develop
git checkout develop
git pull origin develop
git checkout -b feature/JIRA-1234-add-retry-logic

# 3. Make changes and commit
git add .
git commit -m "feat(payments): add retry logic"

# 4. Push and create MR
git push -u origin feature/JIRA-1234-add-retry-logic
# Create MR in GitLab UI targeting 'develop' branch
```

### Branch Structure

- `main` - Production-ready code
- `develop` - Integration branch for features
- `feature/*` - New features (branch from develop)
- `bugfix/*` - Bug fixes (branch from develop)
- `release/*` - Release preparation (branch from develop)
- `hotfix/*` - Emergency production fixes (branch from main)

### Code Review

All changes require:
- [GOOD] 1 approval for develop
- [GOOD] 2 approvals for main
- [GOOD] All CI checks passing
- [GOOD] All discussions resolved

Commit documentation:

git add README.md
git commit -m "docs: update README with GitFlow workflow"
git push origin develop

Team Onboarding

Training Session Agenda

Duration: 90 minutes

Agenda:

  1. Introduction (10 min)

    • Why GitFlow?
    • Benefits for our team
    • Overview of branch types
  2. GitFlow Principles (20 min)

    • Permanent vs temporary branches
    • Feature development workflow
    • Release process
    • Hotfix procedure
  3. Hands-On Practice (30 min)

    • Create feature branch
    • Make commits
    • Create merge request
    • Resolve merge conflicts
    • Review code
  4. CI/CD Integration (15 min)

    • Pipeline stages by branch type
    • Quality gates
    • Deployment automation
  5. Common Scenarios (10 min)

    • Keeping feature branch updated
    • Addressing review feedback
    • Emergency hotfixes
  6. Q&A (5 min)

Hands-On Exercise

Exercise: Implement a simple feature using GitFlow

# 1. Create feature branch
git checkout develop
git pull origin develop
git checkout -b feature/TRAINING-001-add-logging

# 2. Make a change
echo "console.log('Payment processed');" >> src/services/PaymentService.js

# 3. Commit
git add src/services/PaymentService.js
git commit -m "feat(payments): add logging for payment processing"

# 4. Push and create MR
git push -u origin feature/TRAINING-001-add-logging

# 5. Create MR in GitLab (instructor demonstrates)

# 6. Address review feedback (simulate)
echo "console.log('Payment processed:', paymentId);" > src/services/PaymentService.js
git add src/services/PaymentService.js
git commit -m "fix(payments): include payment ID in log"
git push origin feature/TRAINING-001-add-logging

# 7. Merge after approval (instructor demonstrates)

# 8. Clean up
git checkout develop
git pull origin develop
git branch -d feature/TRAINING-001-add-logging

Onboarding Checklist for New Developers

  • Read GitFlow Branching Strategy
  • Read Git Daily Workflow
  • Configure Git identity: git config user.name and user.email
  • Set up SSH key for GitLab
  • Clone repository and verify access
  • Complete hands-on training exercise
  • Create first feature branch
  • Submit first merge request
  • Perform first code review
  • Ask questions in team channel

Quick Reference Card

Provide developers with a laminated reference card or wiki page:

╔═══════════════════════════════════════════════════════════╗
║ GitFlow Quick Reference ║
╠═══════════════════════════════════════════════════════════╣
║ START NEW FEATURE ║
║ git checkout develop && git pull ║
║ git checkout -b feature/JIRA-XXX-description ║
║ ║
║ COMMIT CHANGES ║
║ git add . ║
║ git commit -m "feat(scope): description" ║
║ git push origin feature/JIRA-XXX-description ║
║ ║
║ UPDATE FEATURE BRANCH ║
║ git checkout feature/JIRA-XXX-description ║
║ git rebase develop ║
║ git push --force-with-lease origin feature/... ║
║ ║
║ CREATE MERGE REQUEST ║
║ 1. Push branch to GitLab ║
║ 2. Click "Create merge request" in GitLab ║
║ 3. Target: develop ║
║ 4. Fill in template ║
║ 5. Assign reviewers ║
║ ║
║ AFTER MERGE ║
║ git checkout develop && git pull ║
║ git branch -d feature/JIRA-XXX-description ║
╚═══════════════════════════════════════════════════════════╝

Migration of Existing Work

Handling In-Flight Features

Option 1: Complete before migration

  • Best for: Features close to completion (within 1-2 days)
  • Merge all pending PRs to main before creating develop

Option 2: Migrate to feature branches

# For each in-flight feature currently on main

# 1. Identify commit range for feature
git log --oneline --since="1 week ago"

# 2. Create feature branch from develop
git checkout develop
git checkout -b feature/JIRA-1234-in-flight-work

# 3. Cherry-pick commits
git cherry-pick <commit-hash-1>
git cherry-pick <commit-hash-2>
# ... repeat for all feature commits

# 4. Push feature branch
git push -u origin feature/JIRA-1234-in-flight-work

# 5. Create MR targeting develop

Option 3: Revert from main, reapply to feature branch

# 1. Revert incomplete feature from main
git checkout main
git revert <commit-hash> --no-commit
git commit -m "revert: temporary removal for GitFlow migration"
git push origin main

# 2. Create feature branch from develop
git checkout develop
git checkout -b feature/JIRA-1234-restored-feature

# 3. Revert the revert (restore original changes)
git revert <revert-commit-hash>
git push -u origin feature/JIRA-1234-restored-feature

Migrating Tags and Releases

# Tags remain on commits regardless of branch reorganization
# Verify all tags are present
git fetch --tags
git tag -l

# If needed, retag release commits
git tag -a v1.4.0 <commit-hash> -m "Release 1.4.0"
git push origin v1.4.0

Handling Long-Running Branches

Problem: Custom branches like demo, staging-hotfix

Solution:

# Option 1: Merge to develop or main, then delete
git checkout develop
git merge demo-branch
git push origin develop
git push origin --delete demo-branch

# Option 2: Keep as feature branch with new naming
git branch -m demo-branch feature/DEMO-maintain-demo-environment
git push origin feature/DEMO-maintain-demo-environment
git push origin --delete demo-branch

# Option 3: Tag and delete if historical reference needed
git tag -a archive/demo-2024-01 demo-branch -m "Archived demo branch"
git push origin archive/demo-2024-01
git push origin --delete demo-branch

Tooling Setup

Git Hooks for Enforcement

Git hooks are scripts that run automatically at specific points in the Git workflow (pre-commit, pre-push, post-merge, etc.). They're stored in .git/hooks/ by default, but Git 2.9+ supports core.hooksPath to reference a shared directory tracked in version control. Hooks execute in the repository context with access to Git commands and environment variables. A non-zero exit code from a hook script aborts the Git operation, providing client-side enforcement of policies before code reaches the server.

Create shared Git hooks repository:

.git-hooks/pre-commit:

#!/bin/bash
# Prevent direct commits to main/develop

BRANCH=$(git rev-parse --abbrev-ref HEAD)

if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "develop" ]; then
BAD: echo " ERROR: Direct commits to '$BRANCH' are not allowed!"
echo "Please create a feature branch:"
echo " git checkout -b feature/JIRA-XXX-description"
exit 1
fi

# Check commit message format
COMMIT_MSG_FILE=$1
COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")

if ! echo "$COMMIT_MSG" | grep -qE '^(feat|fix|docs|style|refactor|perf|test|chore|ci|revert)(\(.+\))?: .{10,}'; then
BAD: echo " ERROR: Commit message doesn't follow Conventional Commits format"
echo ""
echo "Format: <type>(<scope>): <subject>"
echo ""
echo "Example: feat(payments): add retry logic"
echo ""
echo "Types: feat, fix, docs, style, refactor, perf, test, chore, ci, revert"
exit 1
fi

GOOD: echo " Pre-commit checks passed"

Install hooks for team:

# In repository root
mkdir -p .git-hooks

# Copy hooks
cp scripts/git-hooks/* .git-hooks/

# Configure Git to use hooks directory
git config core.hooksPath .git-hooks

# Make hooks executable
chmod +x .git-hooks/*

# Commit hooks to repository
git add .git-hooks/
git commit -m "chore: add Git hooks for GitFlow enforcement"
git push origin develop

Team installation:

# Each developer runs after cloning:
git config core.hooksPath .git-hooks

GitLab CLI Setup

# Install glab (GitLab CLI)
# macOS
brew install glab

# Linux
curl -s https://gitlab.com/gitlab-org/cli/-/releases/latest/downloads/glab_Linux_x86_64.tar.gz | tar xz -C /usr/local/bin

# Windows (using Chocolatey)
choco install glab

# Authenticate
glab auth login

# Configure default project
glab config set --host gitlab.com

Useful glab commands:

# Create MR from command line
glab mr create --base develop --title "[JIRA-1234] feat: Add payment retry"

# List open MRs
glab mr list

# View MR details
glab mr view 123

# Check out MR branch for review
glab mr checkout 123

# Approve MR
glab mr approve 123

# Merge MR
glab mr merge 123

IDE Integration

VS Code GitLens Extension:

// .vscode/settings.json
{
"gitlens.gitCommands.closeOnFocusOut": false,
"gitlens.gitCommands.skipConfirmations": [
"fetch:command",
"switch:command"
],
"gitlens.showWelcomeOnInstall": false,
"gitlens.currentLine.enabled": true,
"gitlens.hovers.currentLine.over": "line",
"gitlens.defaultDateFormat": "YYYY-MM-DD HH:mm",
"gitlens.blame.format": "${author}, ${date}"
}

IntelliJ IDEA / WebStorm:

  • Enable Git integration: Preferences > Version Control > Git
  • Configure commit template: Preferences > Version Control > Commit
  • Set up code review: Preferences > Version Control > GitLab

Monitoring Migration Success

Metrics to Track

Week 1-2 (Learning phase):

  • Number of feature branches created
  • Number of direct commits to main/develop (should be 0)
  • Number of MRs created
  • Average MR review time

Week 3-4 (Adoption phase):

  • MR approval rate
  • CI/CD pipeline success rate
  • Number of merge conflicts
  • Developer feedback/questions

Week 5+ (Steady state):

  • Release frequency
  • Hotfix frequency
  • Average feature branch lifetime
  • Code review quality metrics

Success Criteria

Migration is successful when:

  • [GOOD] Zero direct commits to main/develop for 2 consecutive weeks
  • [GOOD] All team members creating feature branches correctly
  • [GOOD] 95%+ MRs following conventional commit format
  • [GOOD] CI/CD pipelines running smoothly on all branch types
  • [GOOD] Code review process integrated into workflow
  • [GOOD] No major incidents related to branching/merging

Rollback Plan

If migration causes significant issues:

Emergency Rollback

# 1. Announce rollback decision
# Communicate in team channels

# 2. Remove branch protection temporarily
# Via GitLab UI: Settings > Repository > Protected branches
# Unprotect main and develop

# 3. Merge all in-flight features to main
git checkout main
git merge feature/JIRA-1234 --no-ff
git merge feature/JIRA-5678 --no-ff
git push origin main

# 4. Delete develop branch (optional)
git push origin --delete develop

# 5. Resume original workflow
# Direct commits to main allowed again

# 6. Schedule post-mortem
# Understand why migration failed
# Plan better for retry

Partial Rollback

Keep GitFlow but relax some rules:

  • Allow direct commits to develop (temporarily)
  • Reduce MR approval requirements
  • Simplify CI/CD pipeline
  • Provide more training and support

Common Migration Challenges

Challenge 1: Resistance to Change

Symptoms:

  • Developers bypassing workflow
  • Complaints about "too much process"
  • Continued direct commits to main

Solutions:

  • Emphasize benefits (cleaner history, better reviews, safer releases)
  • Provide hands-on training and pair programming
  • Automate enforcement (Git hooks, branch protection)
  • Celebrate early wins (successful features, smooth releases)
  • Address pain points (slow CI, unclear documentation)

Challenge 2: CI/CD Pipeline Complexity

Symptoms:

  • Pipelines failing on feature branches
  • Deployment confusion
  • Long pipeline execution times

Solutions:

  • Start simple, add complexity gradually
  • Clear branch-to-environment mapping
  • Optimize pipeline performance (caching, parallelization)
  • Provide pipeline status visibility (Slack notifications)

Challenge 3: Merge Conflict Hell

Symptoms:

  • Frequent merge conflicts
  • Feature branches living too long
  • Developers afraid to update branches

Solutions:

  • Enforce short-lived feature branches (<5 days)
  • Encourage frequent rebasing on develop
  • Pair programming for complex conflicts
  • Provide merge conflict training
  • Consider smaller, more focused features

Challenge 4: Inconsistent Adoption

Symptoms:

  • Some developers follow GitFlow, others don't
  • Mixed commit message styles
  • Unclear when to use which branch type

Solutions:

  • Technical enforcement (Git hooks, branch protection)
  • Regular retrospectives to address concerns
  • Designate GitFlow champions to help others
  • Update documentation based on feedback
  • Recognize and reward good practices

Post-Migration Optimization

After 1 Month

Review and adjust:

  • Are branch protection rules too strict/loose?
  • Is CI/CD pipeline configuration optimal?
  • Are there unnecessary bottlenecks?
  • Do developers understand the workflow?
  • Are commit messages improving?

Continuous Improvement

  • Quarterly review of GitFlow effectiveness
  • Update documentation based on team feedback
  • Automate repetitive tasks
  • Streamline MR approval process
  • Optimize CI/CD for speed and reliability

Further Reading

External Resources


Summary

Key Takeaways:

  1. Assess before migrating: Understand current state and readiness
  2. Choose migration strategy: Clean cut vs gradual based on team size
  3. Set up infrastructure: Branch protection, code owners, CI/CD
  4. Train the team: Hands-on exercises and clear documentation
  5. Migrate existing work carefully: Handle in-flight features appropriately
  6. Enforce with tooling: Git hooks, branch protection, CI/CD gates
  7. Monitor success: Track metrics and adjust based on feedback
  8. Have rollback plan: Be prepared to revert if necessary
  9. Iterate and improve: Continuous optimization based on team needs
  10. Celebrate wins: Recognize successful adoption and improvements