Git
CapyCode initie à Git avec un vrai dépôt : isomorphic-git (Git en JavaScript) travaille sur /workspace/.git, dans le même système de fichiers LightningFS que les fichiers de l'élève. Pour les débutants, Git est proposé sous la forme d'une extension intégrée (src/services/extensions/, src/stores/extensions.ts), désactivée par défaut pour garder une interface épurée au premier abord.
Une fois activée depuis le panneau Extensions (Ctrl+Maj+X), on s'en sert depuis l'interface (panneau Git, graphe, comparaisons) ou depuis le terminal (git …). Les deux passent par le même service, si bien que l'interface suit toujours, quelle que soit la façon dont on a agi. Si la page hôte (Capytale) désactive Git (gitEnabled: false), l'extension est masquée et verrouillée.
Le service : src/services/git/
GitService est composé de trois parties :
| Classe | Rôle |
|---|---|
GitRepository (repository.ts) | Lecture : branches, tags, statut, historique, branche courante |
GitInspection (inspection.ts) | Contenus d'un fichier (HEAD, index, copie de travail), diffs, statistiques des commits ; reçoit le GitRepository à lire |
GitService (index.ts) | Opérations qui modifient le dépôt et leurs notifications ; possède un repo et une inspection, et expose aussi leurs lectures |
Le singleton gitService suffit donc aux appelants. La séparation rend visible ce qui modifie le dépôt (et doit notifier) de ce qui le lit ; la composition permet de remplacer ou de tester une partie seule. Les autres lecteurs du dépôt, comme GitGraphService (src/services/gitGraph.ts), reçoivent un GitService et lisent par son repo : aucun n'accède au système de fichiers de son côté.
Les opérations et leurs notifications
Toute opération passe par mutate(change, task, worktree) :
changevautindex(indexer, désindexer, abandonner une modification) ourefs(commit, branches, tags, checkout, fusion) ;refsl'emporte quand les deux se mêlent ;- les opérations imbriquées (
stageAll→add) ne notifient qu'une fois, à la fin de la plus extérieure ; worktreesignale qu'isomorphic-git a écrit des fichiers de travail (checkout, fusion) sans passer par le VFS : le service appelle alorsvfs.notifyChange()pour que l'arborescence et les onglets suivent.
gitService.batch(task) regroupe plusieurs opérations (le rejeu d'un historique partagé) en une seule notification refs.
Opérations disponibles : init, add, stageAll, resetIndex, discardFile, commit, createBranch, renameBranch, deleteBranch, checkout, checkoutBranch, merge, abortMerge, createTag, deleteTag ; et en lecture, getMergeState.
Ce qu'il faut savoir d'isomorphic-git
- Binaire. Les objets sont compressés : tout ce qui est sous
.gitse lit et s'écrit enUint8Array(règle absolue du projet). - Chemins relatifs. isomorphic-git attend des chemins relatifs à
/workspace:getRelativeWorkspacePathouworkspace.relative. - Fusion.
git.mergemet à jour les références et l'index mais pas les fichiers de travail : le service fait un checkout forcé de la branche après une fusion réussie. Pour une fusion en conflit, voir plus bas. - Statut. Dans
statusMatrix,stage === 3veut dire « indexé, puis modifié de nouveau », pas « conflit » : l'étatconflictd'un fichier vient de la fusion en cours (GitService.status). - Statut et dates. Le statut compare taille et date de modification : un fichier réécrit avec la même taille dans la même milliseconde peut paraître inchangé (piège rencontré dans les tests).
- Identité. Les commits sont signés par
gitService.author(workspace.setAuthor), une identité par défaut tant que Capytale ne fournit pas celle de l'élève.
La fusion en conflit
Une fusion en conflit ne s'annule pas : comme avec Git, elle reste en cours jusqu'à ce que l'élève la termine ou l'abandonne.
- Avant.
mergerefuse de partir d'une copie de travail modifiée (les fichiers non suivis sont permis) : isomorphic-git ne saurait pas toujours la restaurer à l'abandon. Pendant une fusion, une nouvelle fusion et un changement de branche (checkoutBranch) sont refusés. - Le conflit.
git.mergeest appelé avecabortOnConflict: false: les fichiers en conflit reçoivent les marqueurs<<<<<<< main/=======/>>>>>>> feature. isomorphic-git n'indexe que les conflits ; le service indexe lui-même les fichiers fusionnés sans conflit, comme Git.mergerenvoie alors{ conflicts }au lieu de lever une erreur. - L'état de fusion est gardé comme dans Git, dans
.git/MERGE_HEAD(le commit fusionné) et.git/MERGE_MSG(le message du futur commit, suivi de# Conflicts:et des fichiers) : il survit au rechargement et à l'export ZIP, et le vrai Git le reconnaît (mergeState.ts).getMergeState()le lit, avec les fichiers non résolus : ceux qui gardent des marqueurs (src/utils/conflicts.ts).status()les marqueconflict. - Terminer. Pendant une fusion,
commitcrée le commit de fusion (parents : HEAD et le commit fusionné), puis efface l'état. Il lèveMergeConflictErrortant qu'un fichier garde des marqueurs, ou qu'un fichier résolu n'est pas indexé (isomorphic-git n'expose pas les conflits de l'index : il refuse le commit,UnmergedPathsError). - Abandonner.
abortMergeappellegit.abortMerge, retire les fichiers que la fusion avait ajoutés (isomorphic-git les laisse), puis efface l'état.
Le store git
src/stores/git.ts tient ce que l'interface affiche : branche courante, branches, tags, statut des fichiers (avec des compteurs et un index par chemin, consulté par chaque nœud de l'explorateur), historique, message de commit en cours.
Il s'abonne au VFS et au service Git, et choisit la réaction la moins coûteuse :
| Notification | Réaction |
|---|---|
fichier de travail écrit, créé, supprimé ; index | statut seul |
quelque chose sous .git, reset, refs | tout : branches, tags, statut, historique |
Le store expose aussi mergeState, la fusion en cours, relu avec le statut : une résolution enregistrée dans un fichier suffit à la faire passer pour résolue. finishMerge indexe les fichiers qui étaient en conflit (les autres le sont déjà) et crée le commit de fusion, avec le message saisi ou celui de la fusion ; abortMerge l'abandonne.
Les rafraîchissements concurrents sont regroupés (coalesce). Quand la page hôte désactive Git, le store se comporte comme sans dépôt et ne lit plus rien (voir Intégration dans une page hôte).
L'interface
GitPanel(sidebar/) et ses morceaux danssidebar/git/: fichiers indexés et modifiés (GitFileItem), message et bouton de commit, menus de branche et de fusion (GitBranchMenu,GitMergeMenu, surMenuButton), historique (GitHistorySection,GitHistoryCommit, infobulleGitCommitTooltip).GitMergeBanner(sidebar/git/) : pendant une fusion en conflit, en tête du panneau, les fichiers à résoudre (un clic les ouvre), une explication, et les boutons « Terminer la fusion » (désactivé tant qu'un fichier garde des marqueurs) et « Abandonner la fusion ». Les fichiers en conflit portent la lettreCdans le panneau, l'explorateur et les onglets ; un clic sur l'un d'eux dans le panneau ouvre le fichier plutôt que sa comparaison. Pendant la fusion, le bouton Valider fait le commit de fusion même sans changement indexé.useGitActions(composable) : les actions qui demandent une confirmation ou un message (fusionner, terminer ou abandonner une fusion, créer une branche, abandonner des modifications…), communes au panneau, au graphe et aux comparaisons. Terminer et abandonner sont aussi des commandes de la palette (git.finishMerge,git.abortMerge).- Blocs de conflit dans l'éditeur : surlignés, avec des boutons pour garder une version, l'autre ou les deux, et les commandes équivalentes au clavier (voir Éditeur).
- Graphe (
GitGraphVieweteditor/git-graph/) : un onglet spécial (git://graph).GitGraphServicecalcule les nœuds, leurs voies et leurs couleurs, et les tracés SVG des liens ;GitGraphTableest une liste navigable au clavier (motiflistbox),GitCommitDetailsles fichiers d'un commit. - Comparaisons (
DiffEditor,@codemirror/merge) : ongletsdiff://staged:…,diff://unstaged:…oudiff://commit:…(modulespecialTabsdu storeeditor), rafraîchis à chaque notification du service Git. - Statuts :
src/services/gitStatusMeta.tsdonne pour chaque statut sa lettre (commegit status --short), sa couleur, son nom et une explication pour débutants, traduits.
Dans le terminal
src/services/terminal/git/ implémente git et ses sous-commandes, rangées par famille : init, status, add, restore, reset, commit (changes.ts), switch, checkout, branch, merge (branches.ts), log, diff, show (history.ts), tag (tags.ts). Chaque sous-commande est un GitSubcommand (exécution, aide, complétion), et passe par ctx.ws.git. Voir Terminal.
Une fusion en conflit s'y vit comme avec Git : git merge annonce chaque CONFLICT (content), git status montre les chemins non fusionnés (UU en version courte), git add indexe un fichier même s'il garde des marqueurs mais prévient, git commit (sans -m, avec le message de la fusion) ou git merge --continue termine, git merge --abort abandonne.
Ajouter une fonction Git
- L'opération dans
GitService(parmutate), la lecture dansGitRepositoryouGitInspection. - L'état à afficher dans le store
git, s'il ne suit pas déjà les notifications. - L'action d'interface dans
useGitActionssi elle demande confirmation ; la commande dansgitCommands(src/commands/catalog.ts) ; la sous-commande du terminal danssrc/services/terminal/git/. - Vérifier qu'elle disparaît quand Git est désactivé par la page hôte (règle d'
AGENTS.md). - Tests dans
tests/git/.