Skip to content

Latest commit

 

History

History
487 lines (357 loc) · 16.7 KB

File metadata and controls

487 lines (357 loc) · 16.7 KB

Preventing Develop Branch Drift

Core Principle

Three simple rules prevent both drift and contamination:

  1. Merging FROM develop is NEVER allowed - Develop is dirty, so block it as a merge source
  2. Merging TO develop is ALLOWED EXCEPT FOR UNFINSHED features and epics - This makes merging to develop easier since no intermediate branch is needed for this task
  3. Auto-merge main → develop BEFORE Git Stream merges anything TO develop - Keeps develop current

This approach:

  • Prevents drift: Develop always has latest production baseline before receiving new code
  • Prevents contamination: Develop's unreleased code never leaks to other branches
  • Simplifies workflow: No intermediate branches needed, direct merges to develop work safely

Problem Statement

The Drift Problem

In Git Stream's workflow model, develop serves as a "dirty integration branch" where features and epics are merged for testing before being selectively included in releases. This creates a fundamental problem: develop accumulates unreleased code while main continues to evolve through releases and hotfixes.

Timeline:
T1: Feature A merges to develop
T2: Feature B merges to develop
T3: Release 1.0 (Feature A only) → main
T4: Feature C merges to develop
T5: Hotfix 1.0.1 → main
T6: Feature D merges to develop

At T6, develop contains:

  • ✅ Feature A (released in 1.0)
  • ⚠️ Feature B (unreleased - only in develop)
  • ❌ Missing hotfix 1.0.1 changes
  • ⚠️ Feature C (unreleased - only in develop)
  • ⚠️ Feature D (unreleased - only in develop)

Meanwhile, main (production) contains:

  • ✅ Release 1.0 (Feature A)
  • ✅ Hotfix 1.0.1

Result: develop has diverged significantly from the production baseline. When Feature B is eventually selected for a release, it may conflict with hotfix changes that were never merged back to develop.

The Cascade Effect

This drift compounds over time:

  1. Merge Conflicts: Features tested on an outdated develop may conflict with current main
  2. Integration Issues: Code tested against stale dependencies fails in production
  3. Duplicate Work: Fixes applied to main (via hotfix) must be manually reapplied to features in develop
  4. Release Delays: Unexpected conflicts during release creation block deployments
  5. Lost Context: Weeks-old drift makes conflict resolution harder as team members forget implementation details

Why Manual Syncing Fails

Current practice relies on developers remembering to sync:

  • Inconsistent: Developers forget during feature work
  • Too Late: Conflicts discovered during release creation
  • Incomplete: Only syncs when problems are already visible
  • Reactive: Fixes symptoms rather than prevents drift

Solution Overview

Simplified Synchronization Strategy

The solution is elegantly simple: allow merging TO develop from anywhere, but NEVER merge FROM develop.

Since develop is dirty (contains unreleased code), we prevent it from polluting other branches by:

  1. Allowing all branches to merge TO develop: main, releases, hotfixes, finished features/epics
  2. Blocking all merges FROM develop: Nothing should ever branch from or merge from develop

This approach:

  • Keeps develop current: Any branch can sync changes into develop
  • Prevents contamination: Develop's unreleased code never leaks out
  • Simplifies implementation: No complex sync timing or conflict resolution needed
  • Maintains safety: Develop remains isolated as a testing ground

Key Principles

  • One-Way Flow: All branches → develop (never develop → anywhere)
  • Develop is Dirty: Accept that develop contains unreleased code
  • Isolation: Keep develop's unreleased code contained
  • Simple Rules: Easy to understand and enforce
  • Production-First: main is source of truth, develop tracks it

Implementation Strategy

Core Rule Enforcement

The implementation is straightforward: allow merges TO develop, block merges FROM develop.

Merge Direction Rules

Source Branch Target Branch Allowed? Rationale
main develop ✅ YES Keep develop current with production
release/* develop ✅ YES Sync released code back to develop
hotfix/* develop ✅ YES Propagate fixes to develop
feature/* develop ✅ YES Merge finished features for testing
epic/* develop ✅ YES Merge finished epics for testing
epic-feature/* develop ❌ NO Must merge to parent epic first
develop ANY NO Develop is dirty, never merge FROM it

Synchronization Points

Git Stream automatically syncs main → develop BEFORE merging anything TO develop:

Workflow Stage Command Sync Action Purpose
Feature Finish feature finish 1. Merge main → develop
2. Merge feature → develop
Ensure develop is current before adding feature
Epic Finish epic finish 1. Merge main → develop
2. Merge epic → develop
Ensure develop is current before adding epic
Release Finish release finish 1. Merge to main
2. Merge main → develop
Propagate released code back to develop
Hotfix Finish hotfix finish 1. Merge to main
2. Merge main → develop
Propagate fixes immediately to develop

Key Insight: Auto-syncing main → develop BEFORE each merge ensures:

  1. Develop always has the latest production baseline
  2. No drift accumulates between operations
  3. Conflicts are detected early with full context
  4. Direct merges to develop are safe (no intermediate branches needed)

Simplified Flow

graph LR
    main[main<br/>production] -->|merge| develop[develop<br/>dirty testing]
    release[release/*] -->|merge| develop
    hotfix[hotfix/*] -->|merge| develop
    feature[feature/*] -->|merge| develop
    epic[epic/*] -->|merge| develop
    
    develop -.->|BLOCKED| main
    develop -.->|BLOCKED| release
    develop -.->|BLOCKED| feature
    develop -.->|BLOCKED| epic
    
    style develop fill:#f9f,stroke:#333,stroke-width:2px
    style main fill:#9f9,stroke:#333,stroke-width:2px
Loading

Conflict Handling

Conflicts only occur when merging TO develop:

  • Source: The branch being merged (feature, epic, main, etc.)
  • Target: develop (dirty, but that's okay)
  • Resolution: Standard git conflict resolution
  • Impact: Limited to develop only (doesn't affect production)

Code Implementation

1. Validation: Block Merges FROM Develop

Prevent any merges FROM develop:

// In SafetyValidator or Repository class

/**
 * Validate that develop is never used as a merge source
 *
 * @throws ValidationException if attempting to merge from develop
 */
protected function validateDevelopNotSource(string $sourceBranch): void
{
    $developBranch = $this->configManager->get('branches.develop', 'develop');
    
    if ($sourceBranch === $developBranch) {
        throw new ValidationException(
            "Cannot merge FROM {$developBranch}. " .
            "Develop is a dirty testing branch and should never be used as a merge source. " .
            "All branches should branch from 'main' instead."
        );
    }
}

2. Helper Method: Auto-Sync Main to Develop

Add reusable sync method in PromptsBaseCommand:

/**
 * Sync develop with main before merging to develop
 * Prevents drift by ensuring develop has latest production baseline
 *
 * @return bool Success status
 */
protected function syncDevelopBeforeMerge(): bool
{
    $developBranch = $this->configManager->get('branches.develop', 'develop');
    $mainBranch = $this->configManager->get('branches.main', 'main');
    
    $this->step("Syncing {$developBranch} with {$mainBranch}");
    
    try {
        $currentBranch = $this->repository->getCurrentBranch();
        $this->repository->checkout($developBranch);
        $this->repository->merge($mainBranch, "Auto-sync: Update develop with main before merge");
        
        if ($currentBranch !== $developBranch) {
            $this->repository->checkout($currentBranch);
        }
        
        $this->stepSuccess("Develop is now current with production");
        return true;
        
    } catch (RepositoryException $e) {
        $this->stepError("Sync failed: " . $e->getMessage());
        return false;
    }
}

3. Integration Points

FeatureCommand - Sync Then Merge

// In finishFeature() method

// 1. Sync develop with main first
if (!$this->syncDevelopBeforeMerge()) {
    throw new RepositoryException("Cannot merge feature: develop sync failed");
}

// 2. Merge feature to develop
$this->step("Merging feature to {$developBranch}");
$this->repository->checkout($developBranch);
$this->repository->merge($featureBranch, "Merge feature/{$featureName}");

EpicCommand - Sync Then Merge

// In finishEpic() method

// 1. Sync develop with main first
if (!$this->syncDevelopBeforeMerge()) {
    throw new RepositoryException("Cannot merge epic: develop sync failed");
}

// 2. Merge epic to develop
$this->step("Merging epic to {$developBranch}");
$this->repository->checkout($developBranch);
$this->repository->merge($epicBranch, "Merge epic/{$epicKey}");

ReleaseCommand - Merge to Main Then Sync Develop

// In finishRelease() method

// 1. Merge release to main
$this->step("Merging release to {$mainBranch}");
// ... existing merge logic

// 2. Sync develop with updated main
$this->step("Syncing {$developBranch} with {$mainBranch}");
$this->repository->checkout($developBranch);
$this->repository->merge($mainBranch, "Sync develop with released code");

// 3. Update deployment tags
// ... existing tag logic

HotfixCommand - Merge to Main Then Sync Develop

// In finishHotfix() method

// 1. Merge hotfix to main
$this->step("Merging hotfix to {$mainBranch}");
// ... existing merge logic

// 2. Sync develop with main (includes hotfix)
$this->step("Syncing {$developBranch} with {$mainBranch}");
$this->repository->checkout($developBranch);
$this->repository->merge($mainBranch, "Sync develop with hotfix");

// 3. Update deployment tags
// ... existing tag logic

Git Hooks Integration

Add git hook to prevent accidental merges FROM develop:

#!/bin/bash
# .git/hooks/pre-merge-commit

# Get the branch being merged FROM
MERGE_HEAD=$(git rev-parse MERGE_HEAD 2>/dev/null)
if [ -n "$MERGE_HEAD" ]; then
    MERGE_BRANCH=$(git name-rev --name-only $MERGE_HEAD)
    
    # Check if merging from develop
    if [[ "$MERGE_BRANCH" == *"develop"* ]]; then
        echo "ERROR: Cannot merge FROM develop branch"
        echo "Develop is a dirty testing branch and should never be used as a merge source"
        echo "All branches should branch from 'main' instead"
        exit 1
    fi
fi

This hook prevents developers from accidentally using git merge develop on any branch.

Benefits

1. Prevents Drift

Auto-syncing main → develop before each merge ensures develop always has the latest production baseline.

2. Prevents Contamination

Blocking merges FROM develop ensures unreleased code never leaks to production branches.

3. Simpler Mental Model

Two clear rules: "Auto-sync main → develop before merging" and "Never merge FROM develop."

4. No Intermediate Branches Needed

Direct merges to develop work safely because drift is prevented automatically.

5. Maintains Code Quality

Hotfixes and improvements in main automatically flow to develop before new features are added.

6. Early Conflict Detection

Conflicts are discovered when merging to develop (with full context), not weeks later during release.

7. Reduces Cognitive Load

Developers don't need to remember to sync—Git Stream handles it automatically.

Workflow Examples

Example 1: Feature Finish with Auto-Sync

# Developer works on feature-login
git checkout feature/login
# ... make changes ...
gstr commit "Implement login form"

# Finish feature
gstr feature finish login

# Git Stream automatically:
# 1. Syncs develop with main (ensures current baseline)
# 2. Merges feature to develop
# 3. Handles any conflicts (standard git resolution)

# Result: feature in develop, which is current with production

Key Point: Auto-sync ensures develop has latest production baseline before receiving the feature. No drift accumulates.

Example 2: Hotfix Integration

# Emergency production fix
gstr hotfix start security-patch
# ... fix vulnerability ...
gstr hotfix finish security-patch

# Git Stream automatically:
# 1. Merges hotfix to main
# 2. Merges main to develop (propagates fix)
# 3. Updates deploy/production tag

# Result: fix in production AND in develop for future features

Benefit: Security fix immediately available in develop for all unreleased features.

Example 3: Release Finish Propagation

# Release finishes
gstr release finish 2.0

# Git Stream automatically:
# 1. Merges release to main
# 2. Merges main to develop (propagates released code)
# 3. Updates deploy/production tag

# Result: develop immediately reflects production state

Key Point: Released code flows back to develop automatically.

Example 4: Blocked Merge FROM Develop

# Developer accidentally tries to merge from develop
git checkout feature/new-feature
git merge develop

# Git Stream hook blocks this:
# ERROR: Cannot merge FROM develop branch
# Develop is a dirty testing branch and should never be used as a merge source
# All branches should branch from 'main' instead

Protection: Prevents contamination of clean branches with unreleased code.

Implementation Checklist

Core Changes Required

  1. Add validation in SafetyValidator

    • Block any merge operation where source branch is develop
    • Clear error message explaining the rule
  2. Add helper method in PromptsBaseCommand

    • syncDevelopBeforeMerge() - auto-sync main → develop
    • Handle conflicts with standard resolution
  3. Update FeatureCommand.finishFeature()

    • Call syncDevelopBeforeMerge() before merging feature
    • Then merge feature → develop
  4. Update EpicCommand.finishEpic()

    • Call syncDevelopBeforeMerge() before merging epic
    • Then merge epic → develop
  5. Update ReleaseCommand.finishRelease()

    • After merging to main, merge main → develop
    • Handle conflicts with standard resolution
  6. Update HotfixCommand.finishHotfix()

    • After merging to main, merge main → develop
    • Handle conflicts with standard resolution
  7. Install git hook

    • Add pre-merge-commit hook to block git merge develop
    • Provide clear error message
  8. Update documentation

    • Explain both rules (auto-sync + block FROM develop)
    • Update workflow diagrams
    • Add examples of correct usage

Testing Strategy

  1. Test blocked merges FROM develop

    • Attempt git merge develop from feature branch → should fail
    • Attempt release from develop → should fail
  2. Test allowed merges TO develop

    • Merge feature → develop → should succeed
    • Merge main → develop → should succeed
    • Merge hotfix → develop → should succeed
  3. Test conflict handling

    • Create conflicting changes in main and feature
    • Merge feature → develop → should prompt for resolution
    • Verify develop contains resolved changes

Migration Notes

  • Existing repositories: Install new git hook during next gstr init or gstr sync
  • Documentation: Update all examples to reflect new approach
  • User communication: Explain simplified model in release notes

Summary

Preventing develop branch drift requires two complementary rules:

✅ Auto-sync main → develop BEFORE Git Stream merges anything TO develop ❌ Never merge FROM develop (it's dirty)

This dual approach:

  • Prevents drift: Develop always has latest production baseline before receiving new code
  • Prevents contamination: Develop's unreleased code never leaks to production branches
  • Enables direct merges: No intermediate branches needed—develop stays current automatically
  • Reduces cognitive load: Git Stream handles synchronization automatically

Implementation Summary

  1. Validation: Block any merge operation where source is develop
  2. Auto-sync helper: syncDevelopBeforeMerge() merges main → develop
  3. Feature/Epic finish: Auto-sync, then merge to develop
  4. Release/Hotfix finish: Merge to main, then sync develop
  5. Git hooks: Prevent accidental git merge develop commands

Remember: develop is dirty by design. It's a testing ground for unreleased features. The key is:

  1. Keep it current (auto-sync main → develop before each merge)
  2. Keep it isolated (never merge FROM it)