feat(worktrees): création intelligente (auto) + commit/push/promotion + grille agrandissable
- addWorktree résout la branche par dépôt (branchExists) : checkout si présente, suivi de origin/<b> si seulement remote, sinon création (-b) depuis la branche par défaut. Corrige le 'fatal: invalid reference' sur les groupes hétérogènes ; newBranch -> mode (déprécié, mappé en route).
- Nouvelles opérations git : commit (add -A), push (upstream auto), promotion « en principal » (la branche du worktree devient le checkout principal, sans merge ni conflit, ancienne branche conservée). Routes /worktrees/{commit,push,promote} auditées + GET /repos/:id/branches.
- Web : actions Commit/Push/Promouvoir sur WorktreeCard, sélecteur de branche de base (datalist) dans RepoSection et GroupSessionModal, agrandissement d'un terminal au choix dans TerminalGrid.
- Court-circuite la session de groupe quand aucun worktree n'a pu être créé (fini le masquage par « no resolvable worktree »).
- Tests : git.ts (branchExists/listBranches/commitAll/auto), worktree-manager (commit/promote/branches) ; acceptance P5 étend le scénario worktree de groupe sur branche neuve + commit + promotion.
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
// group_removed sur le topic 'groups', ajout/retrait de repo, et purge CASCADE de la membership
|
||||
// quand le repo est supprimé (PRAGMA foreign_keys = ON).
|
||||
import { spawn, execFileSync } from 'node:child_process';
|
||||
import { mkdtempSync, rmSync } from 'node:fs';
|
||||
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
@@ -142,6 +142,34 @@ try {
|
||||
const pushedSession = await c.waitMsg((m) => m.type === 'session_update' && m.session?.id === gsession.id);
|
||||
check('broadcast WS session_update (session de groupe)', !!pushedSession);
|
||||
|
||||
// ---- Worktree de groupe sur une NOUVELLE branche (scénario corrigé : mode auto, plus de -b raté) ----
|
||||
const wtA = await j(`/api/v1/repos/${repoSummary.id}/worktrees`, 'POST', cookie, { branch: 'feature/cross', mode: 'auto', startSession: null });
|
||||
const wtABody = await wtA.json();
|
||||
check('POST /repos/:id/worktrees (branche neuve) → 201 + action=created', wtA.status === 201 && wtABody.action === 'created');
|
||||
const wtB = await j(`/api/v1/repos/${repo2Summary.id}/worktrees`, 'POST', cookie, { branch: 'feature/cross', mode: 'auto', startSession: null });
|
||||
check('POST /repos (2e repo) worktree branche neuve → 201', wtB.status === 201);
|
||||
|
||||
// GET /branches : la nouvelle branche apparaît (alimente le sélecteur de base côté UI).
|
||||
const branchesA = await (await j(`/api/v1/repos/${repoSummary.id}/branches`, 'GET', cookie)).json();
|
||||
check('GET /repos/:id/branches : feature/cross présente', Array.isArray(branchesA.local) && branchesA.local.includes('feature/cross'));
|
||||
|
||||
// session de groupe SUR cette branche → résout les worktrees créés (le cas qui échouait avant).
|
||||
const gsBranch = await j(`/api/v1/groups/${group.id}/session`, 'POST', cookie, { command: 'bash', branch: 'feature/cross' });
|
||||
const gsBranchBody = await gsBranch.json();
|
||||
check('POST /groups/:id/session (branche) → 201 + 2 dirs', gsBranch.status === 201 && gsBranchBody.dirs?.length === 2);
|
||||
await j(`/api/v1/sessions/${gsBranchBody.session.id}`, 'DELETE', cookie);
|
||||
|
||||
// commit : fichier neuf dans le worktree de repo1 puis commit via l'endpoint.
|
||||
writeFileSync(join(wtABody.worktree.path, 'cross.txt'), 'wip\n');
|
||||
const commitRes = await j(`/api/v1/repos/${repoSummary.id}/worktrees/commit`, 'POST', cookie, { path: wtABody.worktree.path, message: 'wip cross' });
|
||||
check('POST /worktrees/commit → 200 + dirty=0', commitRes.status === 200 && (await commitRes.json()).worktree?.git?.dirtyCount === 0);
|
||||
|
||||
// promotion « en principal » : feature/cross devient le checkout principal de repo1, worktree supprimé.
|
||||
const promRes = await j(`/api/v1/repos/${repoSummary.id}/worktrees/promote`, 'POST', cookie, { path: wtABody.worktree.path });
|
||||
check('POST /worktrees/promote → 200', promRes.status === 200);
|
||||
const wtsAfter = await (await j(`/api/v1/repos/${repoSummary.id}/worktrees`, 'GET', cookie)).json();
|
||||
check('promotion : checkout principal désormais sur feature/cross', wtsAfter.worktrees?.find((w) => w.isMain)?.branch === 'feature/cross');
|
||||
|
||||
// groupe sans worktree résolu (branche inexistante) → 400.
|
||||
const gsBad = await j(`/api/v1/groups/${group.id}/session`, 'POST', cookie, { command: 'bash', branch: 'no/such/branch' });
|
||||
check('POST /groups/:id/session branche absente → 400', gsBad.status === 400);
|
||||
|
||||
Reference in New Issue
Block a user