git-separate-tangled-changes-into-prs

Par divinevideo · divine-mobile

Séparez un répertoire de travail désordonné contenant plusieurs fonctionnalités enchevêtrées en PRs propres et ciblées. À utiliser quand : (1) le répertoire de travail contient des modifications non commitées provenant de plusieurs fonctionnalités mélangées, (2) vous devez créer une PR pour une seule fonctionnalité depuis un répertoire de travail mixte, (3) `git stash pop` échoue avec « already exists, no checkout » pour des fichiers non suivis, (4) des pre-commit hooks bloquent les commits à cause de fichiers non suivis sans rapport qui génèrent des erreurs, (5) des changements d'interface dans une fonctionnalité cassent les fichiers consommateurs d'une autre fonctionnalité dans le même répertoire de travail. Couvre les workflows `git stash`, la séparation de branches, la résolution de conflits et les contournements de pre-commit hooks. Ne jamais empiler des PRs — toujours cibler main.

npx skills add https://github.com/divinevideo/divine-mobile --skill git-separate-tangled-changes-into-prs

Séparer les changements enchevêtrés en PRs propres

Problème

L'arborescence de travail accumule des changements non commitées provenant de plusieurs fonctionnalités au fil du temps. Quand vous devez créer des PRs ciblées, les changements sont enchevêtrés ensemble — certains fichiers ont des changements de plusieurs fonctionnalités, les fichiers générés (comme .mocks.dart) reflètent toutes les fonctionnalités combinées, et les hooks de pre-commit échouent sur du code WIP non lié.

Contexte / Conditions déclenchantes

  • L'arborescence de travail a 50+ fichiers modifiés/non suivis couvrant plusieurs fonctionnalités
  • git diff --name-only affiche des fichiers provenant clairement de différentes fonctionnalités
  • Vous devez créer une PR pour la Fonctionnalité A mais les fichiers de la Fonctionnalité B causent des erreurs d'analyse
  • Les hooks de pre-commit exécutent dart analyze / eslint / etc. sur TOUS les fichiers (pas seulement ceux en staging), donc les fichiers WIP non suivis bloquent les commits
  • Certains fichiers ont des changements de DEUX fonctionnalités (fichiers à changements mixtes)

Solution

Étape 1 : Stasher tout en sécurité

git stash -u -m "descriptive-name-for-safety"

Le flag -u inclut les fichiers non suivis. C'est votre filet de sécurité.

Étape 2 : Créer une branche de fonctionnalité propre

git checkout main  # ou la base appropriée
git pull
git checkout -b feature/clean-branch

Étape 3 : Récupérer le stash et gérer les conflits

git stash pop

Problème courant : « already exists, no checkout » — se produit quand les fichiers non suivis du stash existent déjà dans l'arborescence de travail. Les changements des fichiers suivis s'appliquent toujours, mais le stash est préservé (non supprimé). C'est sûr ; les fichiers dont vous avez besoin sont déjà là.

Conflits de fusion : Résolvez chacun. Pour les fichiers appartenant à D'AUTRES fonctionnalités, prenez la version de base :

git checkout --theirs path/to/other-feature-file.dart

Pour les fichiers appartenant à CETTE fonctionnalité, prenez la version du stash ou résolvez manuellement.

Étape 4 : Gérer les fichiers à changements mixtes

Certains fichiers ont des changements de DEUX fonctionnalités. Pour ceux-ci :

  1. Restaurez à la version de base : git checkout main -- path/to/file.dart
  2. Réappliquez UNIQUEMENT les changements de votre fonctionnalité manuellement (généralement juste des changements d'import ou quelques lignes)

Étape 5 : Gérer les hooks de pre-commit qui vérifient tous les fichiers

Si votre hook de pre-commit analyse TOUS les fichiers (pas seulement ceux en staging), les fichiers WIP non suivis d'autres fonctionnalités bloqueront votre commit.

Contournement : Déplacez temporairement les fichiers non suivis problématiques :

mkdir -p /tmp/other-features-backup
mv lib/unrelated_feature/ /tmp/other-features-backup/
mv test/unrelated_feature/ /tmp/other-features-backup/

Committez, puis restaurez :

cp -r /tmp/other-features-backup/* .

Étape 6 : Régénérer les fichiers générés

Si le projet utilise la génération de code (Mockito, Riverpod, Freezed, etc.), restaurez les fichiers générés à la base et régénérez :

git checkout main -- **/*.mocks.dart **/*.g.dart
dart run build_runner build --delete-conflicting-outputs

Cela garantit que les fichiers générés ne reflètent que les changements de VOTRE fonctionnalité.

Étape 7 : Corriger les cascades de rupture d'interface

Quand la Fonctionnalité A change une interface (renomme une méthode, change les types de paramètres), les fichiers consommateurs qui n'ont pas été modifiés par la Fonctionnalité A seront cassés. L'analyseur détecte cela. Corrigez-le avant de committer.

Motif courant :

  • L'état du Provider a changé CompleteParameters? en Map<String, dynamic>
  • Le widget Canvas appelle toujours .colorFilters sur ce qui est maintenant une Map
  • Correction : Mettez à jour le consommateur pour utiliser la nouvelle interface

Étape 8 : NE PAS empiler les PRs — Combiner les fonctionnalités dépendantes en une PR

Ne créez jamais une PR qui cible la branche d'une autre PR au lieu de main. Les PRs empilées causent de vrais problèmes dans ce projet : les rebases se propagent, le contexte du relecteur se fragmente, l'ordre de fusion devient critique, CI s'exécute contre la mauvaise base, et « fix on parent » force une re-review de l'enfant.

Si la Fonctionnalité B dépend de la Fonctionnalité A, livrez-les comme UNE SEULE PR combinée.

# Les deux fonctionnalités vivent sur une seule branche, ciblant main
git checkout main && git pull
git checkout -b feature-a-and-b
# ... committez les changements de la Fonctionnalité A ...
# ... committez les changements de la Fonctionnalité B (des commits séparés sont OK) ...
gh pr create --base main  # une PR, deux fonctionnalités

Écrivez la description de PR de façon que les deux fonctionnalités soient clairement délimitées — sections séparées, plans de test séparés. Les relecteurs peuvent la lire comme une unité unique, et l'histoire de fusion est un clic. C'est la valeur par défaut quand les changements sont interdépendants.

La seule fois où scinder est quand les fonctionnalités sont vraiment indépendantes — auquel cas elles obtiennent chacune leur propre PR ciblant main, dans n'importe quel ordre. Si vous vous trouvez à vouloir « B basé sur A », c'est le signal de les combiner, pas de les empiler.

Vérification

  1. git diff main...HEAD --stat affiche uniquement les fichiers de VOTRE fonctionnalité
  2. dart analyze (ou équivalent) passe
  3. Les tests passent
  4. Le stash original existe toujours comme filet de sécurité (si pop a échoué partiellement) : git stash list

Exemple

Session où les changements de model-dedup et les changements de fonctionnalité collaborateur/watermark étaient enchevêtrés dans 140+ fichiers modifiés. Le travail model-dedup était indépendant de la fonctionnalité collab/watermark, donc ils se sont séparés proprement :

  1. Ont stashé tout de fix/profile-copy-shares-url
  2. Créé chore/model-deduplication depuis main, récupéré le stash
  3. Séparé les fichiers model-dedup, déplacé 16 fichiers de fonctionnalité non suivis vers /tmp/
  4. Committé, poussé, créé la PR #1411 (cible : main)
  5. Créé feat/collabs-watermark-inspired-by depuis main, récupéré le stash
  6. Corrigé 3 ruptures d'interface dans video_editor_canvas.dart et video_metadata_preview_thumbnail.dart
  7. Committé, poussé, créé la PR #1412 (cible : main — indépendante de #1411)

Si les deux fonctionnalités avaient été interdépendantes, elles auraient été livrées comme une seule PR combinée — jamais empilées.

Alternative : Cherry-pick de commits + stash appliqué sélectif

Quand les changements sont répartis entre commits ET stash (pas seulement le stash), utilisez plutôt cette approche :

# 1. Créer les branches depuis origin/main
git branch feature-a origin/main
git branch feature-b origin/main

# 2. Cherry-pick les commits vers les branches appropriées
git checkout feature-a
git cherry-pick <commit-hash-for-feature-a>

# 3. Appliquer les fichiers stashés sélectif (voir les pièges ci-dessous)

PIÈGE : Fichiers supprimés dans le stash

git checkout stash@{0} -- <path> ÉCHOUE pour les fichiers qui ont été SUPPRIMÉS dans le stash — ils n'existent pas dans l'arborescence du stash. Gérez les suppressions séparément :

# Pour les fichiers MODIFIÉS — checkout depuis le stash fonctionne
git checkout stash@{0} -- path/to/modified_file.dart

# Pour les fichiers SUPPRIMÉS — doit utiliser git rm à la place
git rm path/to/deleted_file.dart

# Vérifiez d'abord lesquels sont des suppressions vs modifications :
git stash show stash@{0} --name-status
# D = deleted (utiliser git rm), M = modified (utiliser checkout)

PIÈGE : Préfixes de chemin du stash dans un mono-repo

Quand la racine git est un répertoire parent mais que vous travaillez dans un sous-répertoire, les chemins du stash incluent le préfixe du sous-répertoire :

# Travail dans : /project/mobile/
# Racine git :   /project/
# Le stash affiche : mobile/lib/file.dart (PAS lib/file.dart)

# Doit exécuter checkout depuis la racine du repo :
cd $(git rev-parse --show-toplevel)
git checkout stash@{0} -- mobile/lib/file.dart

PIÈGE : Cherry-pick produit un commit vide

Si un commit a déjà été fusionné à main via une autre PR (hash différent), cherry-pick produira un commit vide :

# "The previous cherry-pick is now empty"
git cherry-pick --skip  # L'ignorer, il est déjà sur main

PIÈGE : Hooks de pre-commit sur les branches cherry-pickées

Quand vous cherry-pickez sur des branches depuis origin/main, l'analyseur peut trouver des erreurs préexistantes sur main non liées à vos changements. Utilisez --no-verify seulement quand vous avez confirmé que les erreurs ne sont pas les vôtres :

git commit --no-verify -m "feat: your changes"

Vérifier l'exhaustivité

Comparez le contenu du stash contre tous les diffs de branche :

git stash show stash@{0} --name-only | sort > /tmp/stash_files.txt
git diff --name-only origin/main..feature-a
git diff --name-only origin/main..feature-b
# Chaque fichier du stash doit apparaître dans exactement une branche

Notes

  • Toujours stasher avec -u pour capturer les fichiers non suivis
  • Ne jamais force-drop un stash tant que vous n'avez pas vérifié que tout est sûr
  • Les fichiers à changements mixtes sont la partie la plus difficile — identifiez-les tôt en vérifiant quels fichiers ont des changements de plusieurs fonctionnalités
  • Les hooks de pre-commit qui vérifient tous les fichiers (pas seulement ceux en staging) sont le plus gros blocage. Connaissez le comportement des hooks de votre projet avant de commencer.
  • Ne jamais empiler les PRs. Toujours cibler main. Si les fonctionnalités dépendent les unes des autres, combinez-les en une PR plus grande — voir l'Étape 8.
  • Le stash est préservé quand pop échoue sur des fichiers non suivis — ne paniquez pas, vos données sont sûres

Skills similaires