Repository navigation
fix(ci,#20007): les jonctions NTFS ne sont plus invisibles a clean_tolerated_artifacts - #20010
Conversation
…lerated_artifacts `Path.is_symlink()` ne reconnait que les liens de type *name surrogate* : une jonction NTFS (reparse point IO_REPARSE_TAG_MOUNT_POINT, le geste courant pour partager un node_modules ou un cache entre worktrees) rend False sur les trois voies -- is_symlink, os.path.islink, S_ISLNK(st_mode) -- tout en portant FILE_ATTRIBUTE_REPARSE_POINT (mesure sur une jonction mklink /J : 0x410). Un artefact tolere qui est une jonction tombait donc dans la branche is_dir(), ou shutil.rmtree leve "Cannot call rmtree on a symbolic link" -- un OSError avale par ignore_errors=True. L'artefact n'etait ni retire ni liste, et le `git worktree remove` qui suit (sans --force) echouait sur l'untracked restant : le rapport annoncait REMOVE pour un --apply incapable d'aboutir -- exactement le "removable n'est pas une prevision de applied" que le docstring de clean_tolerated_artifacts (#14619) dit corriger, et la classe de l'incident jonctions po-2023 (c.525). Correctif : is_link_like(p) = is_symlink() OU FILE_ATTRIBUTE_REPARSE_POINT, avec repli False sur OSError/AttributeError (plateformes sans st_file_attributes) -- on retombe alors sur le comportement d'avant, jamais sur une suppression. os.unlink retire une jonction sans suivre sa cible (mesure : la cible et son contenu survivent), donc le geste de la branche lien convient aux deux formes : seul le predicat changeait. Preuves : - controle negatif : la logique d'origine rejouee telle quelle sur une jonction reelle rend removed == [], jonction survivante, cible intacte -- le defaut est reproduit avant le fix, pas suppose - pytest scripts/tests/test_prune_merged_worktrees.py : 139 passed, 2 skipped, 0 failed ; le test de retrait verifie les deux moities du contrat (la jonction est retiree ET listee, et la cible survit) ; premiere couverture de clean_tolerated_artifacts, qu'aucun test n'exercait - ruff check sur les 2 fichiers : 49 erreurs = baseline de main a l'identique (mesuree par git stash + re-run) -- zero finding net introduit ; le depot ne gate pas ruff, la comparaison est donc explicite Frequence, honnetement : zero observe SUR CE SIEGE (scan de 29 351 repertoires, 0 reparse point). La ligne du dashboard global portant "Recette : delier jonctions node_modules AVANT git worktree remove" est une compensation manuelle derivee ailleurs -- preuve que la classe est rencontree en pratique, mais aucune mesure ne relie ce defaut a un incident. Closes #20007 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
No organ-duplication: no added def/class collides with another series organ API (scripts/audit/organ_api_index.yaml). Detector: |
Path-collision (organ #13359/#13615)Cette PR #20010 (
Le verdict terminal (#15578) signale qu'un cote de la paire est deja sur |
|
[ADJOINT PREFLIGHT] |
Grain: MED/tooling — lane myia-po-2027:CoursIA — prev: MED/notebook-dotnet #19823
fix(ci,#20007): les jonctions NTFS ne sont plus invisibles a clean_tolerated_artifacts
Path.is_symlink()ne reconnait que les liens de type name surrogate. Une jonction NTFS — reparse pointIO_REPARSE_TAG_MOUNT_POINT, le geste courant pour partager unnode_modulesou un cache entre worktrees — rendFalsesur les trois voies, tout en portant bel et bienFILE_ATTRIBUTE_REPARSE_POINT.Le defaut
Un artefact tolere qui est une jonction tombait donc dans la branche
is_dir(), ou :shutil.rmtreedetecte pourtant la jonction et leve — maisignore_errors=Trueavale l'exception. La jonction survivait,removedla taisait, et legit worktree removequi suit (sans--force) echouait sur l'untracked restant : le rapport annoncaitREMOVEpour un--applyincapable d'aboutir. C'est exactement le « removable n'est pas une prevision de applied » que le docstring declean_tolerated_artifacts(#14619) dit corriger, et la classe de l'incident jonctions po-2023 (c.525).Mesures
Predicats sur une jonction
mklink /Jreelle (Windows 11, Python 3.13) :Path.is_symlink()os.path.islink()stat.S_ISLNK(os.lstat(p).st_mode)os.lstat(p).st_file_attributes & FILE_ATTRIBUTE_REPARSE_POINT0x410= reparse point + directory)Controle negatif — la logique d'origine rejouee telle quelle sur une jonction reelle :
removed == [], jonction survivante, cible intacte. Sans ce controle, le test ajoute ne prouverait rien.Geste de suppression :
os.unlink()retire une jonction sans suivre sa cible (mesure : la cible et son contenu survivent), exactement comme un lien symbolique. Seul le predicat devait changer, pas le geste — la brancheunlinkexistait deja et etait correcte.Frequence : zero observe. Le scan prealable de 29 351 repertoires (po-2027, 08/10) a trouve 0 reparse point. Le defaut est donc latent — il est signale pour la classe qu'il laisse ouverte, pas pour un incident.
Le correctif
Un helper
is_link_like(p):is_symlink()ouFILE_ATTRIBUTE_REPARSE_POINT, avec repliFalsesurOSError/AttributeError(plateformes sansst_file_attributes) — on retombe alors sur le comportement d'avant, jamais sur une suppression. Le predicat est utilise dansclean_tolerated_artifacts, et la liste des garde-fous du docstring le nomme.Validation
python -m pytest scripts/tests/test_prune_merged_worktrees.py: 139 passed, 2 skipped, 0 failed (805,9 s — les tests d'intégration lancent de vraisgit/gh)ruff checksur les 2 fichiers : 49 erreurs — la baseline demaina l'identique (mesuree pargit stash+ re-run ; le depot ne gate pas ruff) : zero finding net introduitPerimetre
2 fichiers :
scripts/ci/prune_merged_worktrees.py— helper, predicat, docstring ;scripts/tests/test_prune_merged_worktrees.py— 4 tests, premiere couverture declean_tolerated_artifacts(aucun test ne l'exercait avant,grepsurdef test_+clean_tolerated: 0 hit).Aucun elargissement du predicat : un repertoire ordinaire, un fichier ordinaire et un chemin absent ne sont pas des liens (
TestIsLinkLike), donc la brancheunlinkne s'ouvre pas plus large qu'avant.Closes #20007
🤖 Generated with Claude Code