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
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:
- Announce migration date (1 week notice)
- Merge all pending PRs
- Create
developbranch frommain - Set up branch protection
- Update CI/CD configuration
- Conduct team training
- 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
developbranch appears - Set
developas 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:
-
Introduction (10 min)
- Why GitFlow?
- Benefits for our team
- Overview of branch types
-
GitFlow Principles (20 min)
- Permanent vs temporary branches
- Feature development workflow
- Release process
- Hotfix procedure
-
Hands-On Practice (30 min)
- Create feature branch
- Make commits
- Create merge request
- Resolve merge conflicts
- Review code
-
CI/CD Integration (15 min)
- Pipeline stages by branch type
- Quality gates
- Deployment automation
-
Common Scenarios (10 min)
- Keeping feature branch updated
- Addressing review feedback
- Emergency hotfixes
-
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.nameanduser.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
- GitFlow Branching Strategy - Comprehensive GitFlow guide
- Git Daily Workflow - Day-to-day Git commands
- Merge Request Guidelines - Creating effective MRs
- Code Review Process - Reviewing code effectively
External Resources
- Original GitFlow Article - Vincent Driessen's original GitFlow proposal
- GitLab Flow - GitLab's workflow documentation
- Atlassian GitFlow Tutorial - Comprehensive GitFlow guide
Summary
Key Takeaways:
- Assess before migrating: Understand current state and readiness
- Choose migration strategy: Clean cut vs gradual based on team size
- Set up infrastructure: Branch protection, code owners, CI/CD
- Train the team: Hands-on exercises and clear documentation
- Migrate existing work carefully: Handle in-flight features appropriately
- Enforce with tooling: Git hooks, branch protection, CI/CD gates
- Monitor success: Track metrics and adjust based on feedback
- Have rollback plan: Be prepared to revert if necessary
- Iterate and improve: Continuous optimization based on team needs
- Celebrate wins: Recognize successful adoption and improvements