---
description: Merge multiple migrations into one (for releases)
agent: efcore-squash
model: sonnet
---

# EF Core Squash

> **Ref:** [_shared.md](_shared.md) - `determine_base_branch()`, `detect_efcore_project()`
> **Usage:** Consolider migrations avant merge ou release
> **WARNING:** DESTRUCTIF - historique migrations perdu

---

## Conformité

| INTERDIT | OBLIGATOIRE |
|----------|-------------|
| Scripts .sql dans repo | `dotnet ef migrations add` |
| `migrationBuilder.Sql()` brut | `dotnet ef migrations remove` |
| Squash sans récupérer snapshot parent | Backup dans `.claude/` (gitignored) |

---

## STEP 0: Déterminer Branche Parente (CRITIQUE)

```bash
determine_base_branch  # Voir _shared.md

# Résultat:
# - BASE_BRANCH = branche parente (develop, main, etc.)
# - BRANCH_TYPE = type de branche courante
# - Exit si main/master (BLOQUÉ)
```

| Branche courante | BASE_BRANCH | Action |
|------------------|-------------|--------|
| `feature/*` | `develop` | Récupère snapshot develop |
| `develop` | `main` | Récupère snapshot main |
| `release/*` | `main` | Récupère snapshot main |
| `hotfix/*` | `main` | Récupère snapshot main |
| `main/master` | **BLOQUÉ** | Exit code 1 |

---

## STEP 1: Vérifier Prérequis

```bash
CURRENT_BRANCH=$(git branch --show-current)
[ -n "$(git status --porcelain)" ] && { echo "Working directory non propre"; exit 1; }
detect_efcore_project

# Compter migrations LOCALES uniquement (pas celles héritées)
git fetch origin "$BASE_BRANCH" --quiet
BASE_MIGRATIONS=$(git ls-tree -r --name-only "origin/$BASE_BRANCH" -- "$MIGRATIONS_DIR" | grep "\.cs$" | grep -v "Designer\|Snapshot")
LOCAL_MIGRATIONS=$(find "$MIGRATIONS_DIR" -name "*.cs" | grep -v "Designer\|Snapshot" | xargs -I{} basename {})

# Migrations propres à cette branche
BRANCH_ONLY_MIGRATIONS=()
for mig in $LOCAL_MIGRATIONS; do
  echo "$BASE_MIGRATIONS" | grep -q "$mig" || BRANCH_ONLY_MIGRATIONS+=("$mig")
done

MIGRATION_COUNT=${#BRANCH_ONLY_MIGRATIONS[@]}
echo "Migrations à consolider: $MIGRATION_COUNT (propres à $CURRENT_BRANCH)"
echo "Branche parente: $BASE_BRANCH"
```

---

## STEP 2: Confirmation Utilisateur

```javascript
AskUserQuestion({
  questions: [{
    question: `Squash ${MIGRATION_COUNT} migrations de ${CURRENT_BRANCH}?

Le ModelSnapshot de ${BASE_BRANCH} sera récupéré comme base.
Vos migrations seront consolidées en UNE seule.

Cette opération est DESTRUCTIVE et IRRÉVERSIBLE (sauf backup).`,
    header: "Squash",
    options: [
      { label: "Oui, squash", description: `Consolider sur base de ${BASE_BRANCH}` },
      { label: "Voir détails", description: "Afficher migrations concernées" },
      { label: "Annuler", description: "Ne rien faire" }
    ],
    multiSelect: false
  }]
})
```

---

## STEP 3: Backup Local

```bash
BACKUP_DIR=".claude/gitflow/backup/migrations/squash_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$BACKUP_DIR"
cp "$MIGRATIONS_DIR"/*.cs "$BACKUP_DIR/"
echo "Backup: $BACKUP_DIR"
```

---

## STEP 4: Récupérer ModelSnapshot de la Branche Parente (CRUCIAL)

```bash
# Sauvegarder snapshot actuel
cp "$MIGRATIONS_DIR"/*ModelSnapshot.cs "$BACKUP_DIR/"

# Récupérer le snapshot de la branche parente
git fetch origin "$BASE_BRANCH" --quiet
git checkout "origin/$BASE_BRANCH" -- "$MIGRATIONS_DIR"/*ModelSnapshot.cs

echo "ModelSnapshot récupéré depuis: origin/$BASE_BRANCH"
```

**Pourquoi c'est crucial:**
- Le snapshot de `$BASE_BRANCH` contient l'état VALIDÉ et COMPLET
- Les migrations parallèles sur d'autres branches sont incluses
- Évite la perte d'information de schéma (cf. [Bokio best practices](https://www.bokio.se/engineering-blog/how-to-squash-ef-core-migrations/))

---

## STEP 5: Supprimer Migrations de la Branche (pas celles héritées)

```bash
# Supprimer UNIQUEMENT les migrations propres à cette branche
for mig in "${BRANCH_ONLY_MIGRATIONS[@]}"; do
  rm -f "$MIGRATIONS_DIR"/${mig%.cs}.cs
  rm -f "$MIGRATIONS_DIR"/${mig%.cs}.Designer.cs
done

echo "Supprimé: ${#BRANCH_ONLY_MIGRATIONS[@]} migrations"
```

---

## STEP 6: Créer Migration Consolidée

```bash
# Nommage selon contexte
case "$BRANCH_TYPE" in
  feature)
    BRANCH_NAME=$(echo "$CURRENT_BRANCH" | sed 's|feature/||')
    MIGRATION_NAME="Feature_${BRANCH_NAME}_Consolidated"
    ;;
  release)
    VERSION=$(echo "$CURRENT_BRANCH" | sed 's|release/||' | tr '.' '_')
    MIGRATION_NAME="Release_${VERSION}_Initial"
    ;;
  hotfix)
    VERSION=$(echo "$CURRENT_BRANCH" | sed 's|hotfix/||' | tr '.' '_')
    MIGRATION_NAME="Hotfix_${VERSION}_Fix"
    ;;
  develop)
    MIGRATION_NAME="Develop_Consolidated_$(date +%Y%m%d)"
    ;;
  *)
    MIGRATION_NAME="Consolidated_$(date +%Y%m%d_%H%M%S)"
    ;;
esac

dotnet ef migrations add "$MIGRATION_NAME" \
  --project "$INFRA_PROJECT" \
  --startup-project "$STARTUP_PROJECT" \
  --verbose
```

---

## STEP 7: Validation

```bash
# Build
dotnet build --no-restore
[ $? -ne 0 ] && { echo "Build FAILED - restauration backup"; cp "$BACKUP_DIR"/*.cs "$MIGRATIONS_DIR/"; exit 1; }

# Liste migrations
dotnet ef migrations list --no-build

# Générer script (validation)
dotnet ef migrations script --no-build --idempotent > /dev/null
[ $? -ne 0 ] && { echo "Script generation FAILED"; exit 1; }
```

---

## Résumé

```
SQUASH - {CURRENT_BRANCH}
  Base:       {BASE_BRANCH} (snapshot récupéré)
  Backup:     {BACKUP_DIR}
  Avant:      {MIGRATION_COUNT} migrations
  Apres:      1 migration ({MIGRATION_NAME})
  Conformite: EF Core CLI, Pas de .sql, Snapshot parent

Prochains: /efcore:db-reset, /efcore:db-deploy, /gitflow:3-commit
```

**Si échec:** Restaurer avec `cp $BACKUP_DIR/*.cs $MIGRATIONS_DIR/`

---

## Options

| Option | Description |
|--------|-------------|
| `--no-backup` | Sans backup (dangereux) |
| `--name <name>` | Forcer nom migration |
| `--dry-run` | Afficher sans exécuter |
| `--include-inherited` | Inclure migrations héritées (rare) |

---

## Matrice d'Utilisation

| Situation | Utiliser squash? | Alternative |
|-----------|------------------|-------------|
| Feature avec plusieurs migrations | OUI | `/efcore:rebase-snapshot` |
| Avant merge feature vers develop | OUI (recommandé) | - |
| Sur develop avant release | Créer release d'abord | `/gitflow:10-start release` |
| Sur release avant tag | OUI (idéal) | - |
| Hotfix urgent | OUI | - |
| Sur main/master | **JAMAIS** | BLOQUÉ automatiquement |

---

## Workflow Complet Recommandé

```
1. feature/* : développement avec N migrations

2. Avant merge sur develop:
   /efcore:squash
   → Récupère snapshot develop
   → Consolide en 1 migration

3. Merge sur develop:
   /gitflow:7-pull-request
   /gitflow:9-merge

4. Avant release:
   /gitflow:10-start release vX.Y.Z
   /efcore:squash (sur release/*)
   → Récupère snapshot main
   → Consolide TOUT en Release_X_Y_Z_Initial

5. Finaliser:
   /gitflow:11-finish
```

---

## Sources Best Practices

- [Microsoft - Managing Migrations](https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/managing)
- [Bokio - How to squash EF Core migrations](https://www.bokio.se/engineering-blog/how-to-squash-ef-core-migrations/)
- [Mitchel Sellers - Squashing EF Core Migrations Safely](https://www.mitchelsellers.com/blog/article/squashing-ef-core-migrations-safely)
