SWA Client-Specific Branch Sync Strategy
Problem Statement
Our application serves multiple clients with different configurations and customizations:
- Master branch: Latest version of the code, published to production for primary client
- Client-specific branches:
dnv-prod: DNV client production branchiitd-prod: IITD client production branch- Additional clients may be added in the future
Current Issues
- Manual Sync Process: Client branches must be manually kept up-to-date with master
- Drift Risk: Client branches can become significantly out-of-sync with master
- Inconsistent Deployments: Different clients may have different versions of the application
- Preview Environment Cleanup: Azure Static Web Apps preview environments are not automatically cleaned up when PRs are resolved before deployment completes
Recommended Solution: Automated Sync with Conflict Detection
Overview
Implement an automated GitHub Actions workflow that:
- Automatically syncs master to client branches
- Detects and handles merge conflicts
- Creates PRs when conflicts require manual resolution
- Maintains audit trail of sync operations
Branch Relationships
Development Flow
Implementation Details
1. Auto-Sync Workflow
name: Auto-Sync master to Client Branches
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
on:
push:
branches: [master]
workflow_dispatch:
inputs:
target_branch:
description: 'Target client branch to sync'
required: true
default: 'dnv-prod'
type: choice
options:
- dnv-prod
- iitd-prod
force_sync:
description: 'Force sync even if no changes detected'
required: false
default: false
type: boolean
jobs:
sync-branches:
runs-on: ubuntu-latest
strategy:
matrix:
client_branch: [dnv-prod, iitd-prod]
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
- name: Setup Git configuration
run: |
git config user.name "GitHub Actions Bot"
git config user.email "actions@github.com"
- name: Determine target branch
id: target-branch
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "target_branch=${{ github.event.inputs.target_branch }}" >> $GITHUB_OUTPUT
else
echo "target_branch=${{ matrix.client_branch }}" >> $GITHUB_OUTPUT
fi
- name: Check if target branch exists
id: check-branch
run: |
TARGET_BRANCH="${{ steps.target-branch.outputs.target_branch }}"
if ! git ls-remote --heads origin $TARGET_BRANCH | grep -q $TARGET_BRANCH; then
echo "branch_exists=false" >> $GITHUB_OUTPUT
echo "Target branch $TARGET_BRANCH does not exist. Will create it from master."
else
echo "branch_exists=true" >> $GITHUB_OUTPUT
echo "Target branch $TARGET_BRANCH exists."
fi
- name: Create target branch if it doesn't exist
if: steps.check-branch.outputs.branch_exists == 'false'
run: |
TARGET_BRANCH="${{ steps.target-branch.outputs.target_branch }}"
git checkout -b $TARGET_BRANCH
git push origin $TARGET_BRANCH
echo "✅ Created and pushed new branch: $TARGET_BRANCH"
- name: Checkout target branch
if: steps.check-branch.outputs.branch_exists == 'true'
run: |
TARGET_BRANCH="${{ steps.target-branch.outputs.target_branch }}"
git checkout $TARGET_BRANCH
git pull origin $TARGET_BRANCH
- name: Check for conflicts
id: check-conflicts
run: |
TARGET_BRANCH="${{ steps.target-branch.outputs.target_branch }}"
# Get the current commit on target branch
TARGET_COMMIT=$(git rev-parse HEAD)
# Get the latest commit from master
git fetch origin master
MASTER_COMMIT=$(git rev-parse origin/master)
# Check if target branch is already up to date
if [ "$TARGET_COMMIT" = "$MASTER_COMMIT" ]; then
echo "up_to_date=true" >> $GITHUB_OUTPUT
echo "conflicts=false" >> $GITHUB_OUTPUT
echo "✅ $TARGET_BRANCH is already up to date with master"
exit 0
fi
echo "up_to_date=false" >> $GITHUB_OUTPUT
# Check for conflicts using merge-tree
if git merge-tree $(git merge-base origin/master $TARGET_BRANCH) origin/master $TARGET_BRANCH | grep -q "<<<<<<<"; then
echo "conflicts=true" >> $GITHUB_OUTPUT
echo "⚠️ Merge conflicts detected in $TARGET_BRANCH"
else
echo "conflicts=false" >> $GITHUB_OUTPUT
echo "✅ No conflicts detected in $TARGET_BRANCH"
fi
- name: Auto-merge if no conflicts
if: steps.check-conflicts.outputs.conflicts == 'false' && steps.check-conflicts.outputs.up_to_date == 'false'
run: |
TARGET_BRANCH="${{ steps.target-branch.outputs.target_branch }}"
git merge origin/master --no-edit
git push origin $TARGET_BRANCH
echo "✅ Successfully synced master to $TARGET_BRANCH"
- name: Create PR for conflict resolution
if: steps.check-conflicts.outputs.conflicts == 'true'
run: |
TARGET_BRANCH="${{ steps.target-branch.outputs.target_branch }}"
# Create PR using GitHub CLI
gh pr create \
--base "$TARGET_BRANCH" \
--head "master" \
--title "⚠️ Sync: Conflicts detected - manual merge required" \
--body "## Automated Sync from master Branch
This PR was created automatically by the sync workflow because merge conflicts were detected in \`$TARGET_BRANCH\`.
### Manual Intervention Required
**Please resolve the conflicts manually before merging this PR.**
### What to do:
1. Review the conflicts in the files below
2. Resolve conflicts by choosing the appropriate changes
3. Test the application to ensure it works correctly
4. Merge this PR once conflicts are resolved
### Changes from master:
- [Commits will be listed automatically]
**Note:** This PR was created automatically by the sync workflow on $(date)."
echo "✅ Created PR from master to $TARGET_BRANCH for conflict resolution"
- name: Skip sync if already up to date
if: steps.check-conflicts.outputs.up_to_date == 'true'
run: |
echo "ℹ️ ${{ steps.target-branch.outputs.target_branch }} is already up to date with master. No sync needed."
2. Scheduled Sync Workflow
name: Weekly Sync Status Check
on:
schedule:
- cron: '0 9 * * 1' # Every Monday at 9 AM UTC
workflow_dispatch:
inputs:
check_all_branches:
description: 'Check all client branches for sync status'
required: false
default: true
type: boolean
jobs:
check-sync-status:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
- name: Setup Git configuration
run: |
git config user.name "GitHub Actions Bot"
git config user.email "actions@github.com"
- name: Check branch sync status
id: sync-status
run: |
CLIENT_BRANCHES=("dnv-prod" "iitd-prod")
SYNC_NEEDED=false
BRANCHES_TO_SYNC=""
echo "## Sync Status Report" >> $GITHUB_STEP_SUMMARY
echo "Generated on: $(date)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Get the latest commit from main
git fetch origin main
MASTER_COMMIT=$(git rev-parse origin/main)
MASTER_COMMIT_SHORT=$(git rev-parse --short origin/main)
for branch in "${CLIENT_BRANCHES[@]}"; do
echo "Checking sync status for $branch..."
# Check if branch exists
if ! git ls-remote --heads origin $branch | grep -q $branch; then
echo "❌ **$branch**: Branch does not exist" >> $GITHUB_STEP_SUMMARY
SYNC_NEEDED=true
BRANCHES_TO_SYNC="$BRANCHES_TO_SYNC $branch"
continue
fi
# Get the latest commit from client branch
git checkout $branch
git pull origin $branch
CLIENT_COMMIT=$(git rev-parse HEAD)
CLIENT_COMMIT_SHORT=$(git rev-parse --short HEAD)
# Check if client branch is behind main
if git merge-base --is-ancestor $CLIENT_COMMIT $MASTER_COMMIT; then
if [ "$CLIENT_COMMIT" = "$MASTER_COMMIT" ]; then
echo "✅ **$branch**: Up to date with main ($CLIENT_COMMIT_SHORT)" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ **$branch**: Behind main (client: $CLIENT_COMMIT_SHORT, main: $MASTER_COMMIT_SHORT)" >> $GITHUB_STEP_SUMMARY
SYNC_NEEDED=true
BRANCHES_TO_SYNC="$BRANCHES_TO_SYNC $branch"
fi
else
echo "❓ **$branch**: Diverged from main (client: $CLIENT_COMMIT_SHORT, main: $MASTER_COMMIT_SHORT)" >> $GITHUB_STEP_SUMMARY
SYNC_NEEDED=true
BRANCHES_TO_SYNC="$BRANCHES_TO_SYNC $branch"
fi
done
echo "" >> $GITHUB_STEP_SUMMARY
if [ "$SYNC_NEEDED" = true ]; then
echo "🔄 **Action Required**: Sync needed for branches: $BRANCHES_TO_SYNC" >> $GITHUB_STEP_SUMMARY
echo "sync_needed=true" >> $GITHUB_OUTPUT
echo "branches_to_sync=$BRANCHES_TO_SYNC" >> $GITHUB_OUTPUT
else
echo "✅ **All branches are up to date**" >> $GITHUB_STEP_SUMMARY
echo "sync_needed=false" >> $GITHUB_OUTPUT
fi
- name: Trigger sync workflow if needed
if: steps.sync-status.outputs.sync_needed == 'true'
run: |
BRANCHES="${{ steps.sync-status.outputs.branches_to_sync }}"
for branch in $BRANCHES; do
echo "Triggering sync for branch: $branch"
gh workflow run "Auto-Sync Main to Client Branches" \
--field target_branch="$branch" \
--field force_sync="false"
done
echo "✅ Triggered sync workflows for branches: $BRANCHES"
- name: Create summary comment
if: github.event_name == 'workflow_dispatch'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const summary = fs.readFileSync(process.env.GITHUB_STEP_SUMMARY, 'utf8');
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: summary
});
Preview Environment Cleanup Issue
Problem
Azure Static Web Apps has a known issue where preview environments are not automatically cleaned up when:
- PRs are resolved before the deployment finishes
- PRs are closed/merged quickly
- Multiple PRs are created in rapid succession
This leads to:
- Accumulation of unused preview environments
- Hitting the 10-preview environment limit
- New PRs failing to get preview environments
Solution: Automated Cleanup Workflow
name: Cleanup Stale Preview Environments
on:
schedule:
- cron: '0 2 * * *' # Daily at 2 AM
workflow_dispatch:
jobs:
cleanup-environments:
runs-on: ubuntu-latest
steps:
- name: Cleanup stale preview environments
uses: Azure/static-web-apps-deploy@v1
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_AMBITIOUS_FOREST_04ACFAB0F }}
action: "close"
skip_app_build: true
skip_api_build: true
Enhanced PR Cleanup Workflow
name: PR Environment Cleanup
on:
pull_request:
types: [closed]
jobs:
cleanup-preview:
if: github.event.pull_request.merged == true || github.event.pull_request.merged == false
runs-on: ubuntu-latest
steps:
- name: Wait for deployment to complete
run: |
# Wait up to 10 minutes for deployment to complete
for i in {1..60}; do
sleep 10
# Check if deployment is still in progress
# This is a simplified check - you might need to call Azure API
done
- name: Cleanup preview environment
uses: Azure/static-web-apps-deploy@v1
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_AMBITIOUS_FOREST_04ACFAB0F }}
action: "close"
skip_app_build: true
skip_api_build: true
Manual Cleanup Script
For immediate cleanup of all environments:
#!/bin/bash
# cleanup-all-environments.sh
# Get list of all preview environments
ENVIRONMENTS=$(az staticwebapp environment list \
--name your-static-web-app-name \
--resource-group your-resource-group \
--query "[?environmentType=='Preview'].name" \
--output tsv)
# Delete each environment
for env in $ENVIRONMENTS; do
echo "Deleting environment: $env"
az staticwebapp environment delete \
--name your-static-web-app-name \
--resource-group your-resource-group \
--environment-name $env \
--yes
done
Best Practices
1. Conflict Resolution Strategy
- Client-specific changes: Keep in client branches
- Core functionality: Merge from master
- Configuration: Use environment variables
- Documentation: Maintain conflict resolution guides
2. Testing Strategy
- Automated tests: Run on all branches
- Manual testing: Required for client-specific features
- Staging environments: Use for pre-production testing
3. Monitoring and Alerts
- Sync failures: Alert on workflow failures
- Environment limits: Monitor preview environment count
- Deployment status: Track deployment success rates
4. Rollback Strategy
- Quick rollback: Ability to revert to previous commit
- Database migrations: Backward-compatible changes
- Feature flags: Gradual rollout capabilities
Implementation Timeline
- Week 1: Implement auto-sync workflow
- Week 2: Add conflict detection and PR creation
- Week 3: Implement preview environment cleanup
- Week 4: Testing and monitoring setup
- Week 5: Documentation and team training
Success Metrics
- Sync frequency: Weekly automated syncs
- Conflict rate: < 10% of syncs require manual resolution
- Environment cleanup: < 5 stale preview environments
- Deployment success: > 95% successful deployments
- Time to resolution: < 2 hours for conflict resolution
Conclusion
This automated sync strategy provides:
- Consistency: All clients stay up-to-date with master
- Reliability: Automated conflict detection and resolution
- Efficiency: Reduced manual sync overhead
- Scalability: Easy addition of new client branches
- Cleanliness: Proper preview environment management
The combination of automated sync and environment cleanup ensures a robust, maintainable deployment pipeline for multi-client applications.