@@ -5,7 +5,7 @@ import { join } from 'node:path';
55
66import { afterEach , describe , expect , it } from 'vitest' ;
77
8- import { landCookBranch , promoteBrownfieldRun , promoteGreenfieldRun } from './promote-run.js' ;
8+ import { landCookBranch , promoteGreenfieldRun } from './promote-run.js' ;
99
1010const dirs : string [ ] = [ ] ;
1111const GIT_TEST_TIMEOUT_MS = 20_000 ;
@@ -206,164 +206,12 @@ describe('promoteGreenfieldRun', () => {
206206 ) ;
207207} ) ;
208208
209- describe ( 'promoteBrownfieldRun' , ( ) => {
210- const id = [ '-c' , 'user.name=t' , '-c' , 'user.email=t@e' ] ;
211-
212- // A user repo on `main` with a base commit, plus a brunch/run/<runId> branch at the
213- // same base (as `git worktree add -b brunch/run/<runId> … HEAD` would create).
214- function userRepo ( ) : { dir : string ; baseHead : string } {
215- const dir = mkdtempSync ( join ( tmpdir ( ) , 'cook-userrepo-' ) ) ;
216- dirs . push ( dir ) ;
217- execFileSync ( 'git' , [ 'init' , '-q' , '-b' , 'main' ] , { cwd : dir } ) ;
218- writeFileSync ( join ( dir , 'app.ts' ) , 'export const v = 1;\n' ) ;
219- writeFileSync ( join ( dir , '.gitignore' ) , 'node_modules/\n' ) ;
220- execFileSync ( 'git' , [ 'add' , '.' ] , { cwd : dir } ) ;
221- execFileSync ( 'git' , [ ...id , 'commit' , '-q' , '-m' , 'base' ] , { cwd : dir } ) ;
222- execFileSync ( 'git' , [ 'branch' , 'brunch/run/r1' ] , { cwd : dir } ) ;
223- const baseHead = execFileSync ( 'git' , [ 'rev-parse' , 'HEAD' ] , { cwd : dir , encoding : 'utf8' } ) . trim ( ) ;
224- return { dir, baseHead } ;
225- }
226-
227- // The composed cook result: a full tree (base + the cook delta).
228- function composedTree ( ) : string {
229- const d = mkdtempSync ( join ( tmpdir ( ) , 'cook-composed-' ) ) ;
230- dirs . push ( d ) ;
231- writeFileSync ( join ( d , 'app.ts' ) , 'export const v = 2;\n' ) ; // modified
232- writeFileSync ( join ( d , 'feature.ts' ) , 'export const f = true;\n' ) ; // added
233- writeFileSync ( join ( d , '.gitignore' ) , 'node_modules/\n' ) ;
234- mkdirSync ( join ( d , 'node_modules' ) ) ;
235- writeFileSync ( join ( d , 'node_modules' , 'dep.js' ) , 'junk\n' ) ; // gitignored — must not land
236- return d ;
237- }
238-
239- it (
240- 'commits the composed tree onto brunch/run/<runId>, leaving the active branch and working tree untouched' ,
241- ( ) => {
242- const { dir, baseHead } = userRepo ( ) ;
243- const tree = composedTree ( ) ;
244- const branchesBefore = execFileSync ( 'git' , [ 'branch' , '--list' ] , { cwd : dir , encoding : 'utf8' } ) ;
245-
246- const result = promoteBrownfieldRun ( { sourceDir : dir , sourceTreeDir : tree , runId : 'r1' } ) ;
247-
248- // brunch/run/r1 advanced by one commit on top of the base.
249- expect ( result . branch ) . toBe ( 'brunch/run/r1' ) ;
250- expect ( result . commit ) . not . toBe ( baseHead ) ;
251- const parent = execFileSync ( 'git' , [ 'rev-parse' , 'brunch/run/r1^' ] , {
252- cwd : dir ,
253- encoding : 'utf8' ,
254- } ) . trim ( ) ;
255- expect ( parent ) . toBe ( baseHead ) ;
256-
257- // The commit's tree carries the delta — and not the gitignored deps.
258- const files = execFileSync ( 'git' , [ 'ls-tree' , '-r' , '--name-only' , 'brunch/run/r1' ] , {
259- cwd : dir ,
260- encoding : 'utf8' ,
261- } ) ;
262- expect ( files ) . toContain ( 'feature.ts' ) ;
263- expect ( files ) . toContain ( 'app.ts' ) ;
264- expect ( files ) . not . toContain ( 'node_modules' ) ;
265- const appAtCook = execFileSync ( 'git' , [ 'show' , 'brunch/run/r1:app.ts' ] , { cwd : dir , encoding : 'utf8' } ) ;
266- expect ( appAtCook ) . toContain ( 'v = 2' ) ;
267-
268- // The user's active branch (main), HEAD, working tree, and index are untouched.
269- expect ( execFileSync ( 'git' , [ 'rev-parse' , 'HEAD' ] , { cwd : dir , encoding : 'utf8' } ) . trim ( ) ) . toBe (
270- baseHead ,
271- ) ;
272- expect (
273- execFileSync ( 'git' , [ 'symbolic-ref' , '--short' , 'HEAD' ] , { cwd : dir , encoding : 'utf8' } ) . trim ( ) ,
274- ) . toBe ( 'main' ) ;
275- expect ( readFileSync ( join ( dir , 'app.ts' ) , 'utf8' ) ) . toContain ( 'v = 1' ) ;
276- expect ( existsSync ( join ( dir , 'feature.ts' ) ) ) . toBe ( false ) ;
277- expect ( execFileSync ( 'git' , [ 'status' , '--porcelain' ] , { cwd : dir , encoding : 'utf8' } ) ) . toBe ( '' ) ;
278- // Only brunch/run/r1 moved — no stray branches.
279- expect ( execFileSync ( 'git' , [ 'branch' , '--list' ] , { cwd : dir , encoding : 'utf8' } ) ) . toBe ( branchesBefore ) ;
280- } ,
281- GIT_TEST_TIMEOUT_MS ,
282- ) ;
283-
284- it ( 'throws when the brunch/run/<runId> branch is absent (must be created by the worktree)' , ( ) => {
285- const { dir } = userRepo ( ) ;
286- const tree = composedTree ( ) ;
287- expect ( ( ) => promoteBrownfieldRun ( { sourceDir : dir , sourceTreeDir : tree , runId : 'missing' } ) ) . toThrow (
288- / b r u n c h \/ r u n \/ m i s s i n g / ,
289- ) ;
290- } ) ;
291-
292- it (
293- 'works in the real linked-worktree topology — the live sandbox worktree is left to be discarded, the main checkout untouched' ,
294- ( ) => {
295- // Mirror production: brunch/run/r1 exists *because* a linked worktree checked it out.
296- const dir = mkdtempSync ( join ( tmpdir ( ) , 'cook-userrepo-' ) ) ;
297- dirs . push ( dir ) ;
298- execFileSync ( 'git' , [ 'init' , '-q' , '-b' , 'main' ] , { cwd : dir } ) ;
299- writeFileSync ( join ( dir , 'app.ts' ) , 'export const v = 1;\n' ) ;
300- writeFileSync ( join ( dir , '.gitignore' ) , 'node_modules/\n' ) ;
301- execFileSync ( 'git' , [ 'add' , '.' ] , { cwd : dir } ) ;
302- execFileSync ( 'git' , [ ...id , 'commit' , '-q' , '-m' , 'base' ] , { cwd : dir } ) ;
303- const wt = join ( dir , 'wt' ) ;
304- execFileSync ( 'git' , [ 'worktree' , 'add' , '-q' , '-b' , 'brunch/run/r1' , wt , 'HEAD' ] , { cwd : dir } ) ;
305- const baseHead = execFileSync ( 'git' , [ 'rev-parse' , 'HEAD' ] , { cwd : dir , encoding : 'utf8' } ) . trim ( ) ;
306-
307- const result = promoteBrownfieldRun ( { sourceDir : dir , sourceTreeDir : composedTree ( ) , runId : 'r1' } ) ;
308-
309- // Only brunch/run/r1 moved (one commit on the base).
310- expect (
311- execFileSync ( 'git' , [ 'rev-parse' , 'brunch/run/r1^' ] , { cwd : dir , encoding : 'utf8' } ) . trim ( ) ,
312- ) . toBe ( baseHead ) ;
313- expect ( execFileSync ( 'git' , [ 'show' , 'brunch/run/r1:app.ts' ] , { cwd : dir , encoding : 'utf8' } ) ) . toContain (
314- 'v = 2' ,
315- ) ;
316- // The main checkout is wholly untouched.
317- expect ( execFileSync ( 'git' , [ 'rev-parse' , 'HEAD' ] , { cwd : dir , encoding : 'utf8' } ) . trim ( ) ) . toBe (
318- baseHead ,
319- ) ;
320- expect (
321- execFileSync ( 'git' , [ 'symbolic-ref' , '--short' , 'HEAD' ] , { cwd : dir , encoding : 'utf8' } ) . trim ( ) ,
322- ) . toBe ( 'main' ) ;
323- expect ( readFileSync ( join ( dir , 'app.ts' ) , 'utf8' ) ) . toContain ( 'v = 1' ) ;
324- // tracked files untouched (the linked `wt/` dir is an expected untracked entry).
325- expect (
326- execFileSync ( 'git' , [ 'status' , '--porcelain' , '--untracked-files=no' ] , {
327- cwd : dir ,
328- encoding : 'utf8' ,
329- } ) ,
330- ) . toBe ( '' ) ;
331- expect ( result . commit ) . not . toBe ( baseHead ) ;
332- } ,
333- GIT_TEST_TIMEOUT_MS ,
334- ) ;
335-
336- it ( 'stages tracked deletions — a file removed in the composed tree is removed in the cook commit' , ( ) => {
337- const dir = mkdtempSync ( join ( tmpdir ( ) , 'cook-userrepo-' ) ) ;
338- dirs . push ( dir ) ;
339- execFileSync ( 'git' , [ 'init' , '-q' , '-b' , 'main' ] , { cwd : dir } ) ;
340- writeFileSync ( join ( dir , 'keep.ts' ) , 'keep\n' ) ;
341- writeFileSync ( join ( dir , 'old.ts' ) , 'remove me\n' ) ;
342- execFileSync ( 'git' , [ 'add' , '.' ] , { cwd : dir } ) ;
343- execFileSync ( 'git' , [ ...id , 'commit' , '-q' , '-m' , 'base' ] , { cwd : dir } ) ;
344- execFileSync ( 'git' , [ 'branch' , 'brunch/run/r1' ] , { cwd : dir } ) ;
345-
346- // Composed tree drops old.ts.
347- const tree = mkdtempSync ( join ( tmpdir ( ) , 'cook-composed-' ) ) ;
348- dirs . push ( tree ) ;
349- writeFileSync ( join ( tree , 'keep.ts' ) , 'keep\n' ) ;
350-
351- promoteBrownfieldRun ( { sourceDir : dir , sourceTreeDir : tree , runId : 'r1' } ) ;
352-
353- const files = execFileSync ( 'git' , [ 'ls-tree' , '-r' , '--name-only' , 'brunch/run/r1' ] , {
354- cwd : dir ,
355- encoding : 'utf8' ,
356- } ) ;
357- expect ( files ) . toContain ( 'keep.ts' ) ;
358- expect ( files ) . not . toContain ( 'old.ts' ) ;
359- } ) ;
360- } ) ;
361-
362209describe ( 'landCookBranch' , ( ) => {
363210 const id = [ '-c' , 'user.name=t' , '-c' , 'user.email=t@e' ] ;
364211
365212 // A user repo on `main` with one base commit and a promoted brunch/run/r1 branch
366- // (the composed result already committed on top of base via promoteBrownfieldRun).
213+ // carrying the composed result one commit ahead of base (what a cook run leaves —
214+ // built here via a throwaway worktree on the run branch, the shape landCookBranch merges).
367215 function repoWithPromotedCook ( ) : { dir : string ; baseHead : string ; cookCommit : string } {
368216 const dir = mkdtempSync ( join ( tmpdir ( ) , 'cook-land-' ) ) ;
369217 dirs . push ( dir ) ;
@@ -375,12 +223,15 @@ describe('landCookBranch', () => {
375223 execFileSync ( 'git' , [ 'branch' , 'brunch/run/r1' ] , { cwd : dir } ) ;
376224 const baseHead = execFileSync ( 'git' , [ 'rev-parse' , 'HEAD' ] , { cwd : dir , encoding : 'utf8' } ) . trim ( ) ;
377225
378- const tree = mkdtempSync ( join ( tmpdir ( ) , 'cook-land-tree-' ) ) ;
379- dirs . push ( tree ) ;
380- writeFileSync ( join ( tree , 'app.ts' ) , 'export const v = 2;\n' ) ;
381- writeFileSync ( join ( tree , 'feature.ts' ) , 'export const f = true;\n' ) ;
382- writeFileSync ( join ( tree , '.gitignore' ) , 'node_modules/\n' ) ;
383- const { commit } = promoteBrownfieldRun ( { sourceDir : dir , sourceTreeDir : tree , runId : 'r1' } ) ;
226+ const wt = mkdtempSync ( join ( tmpdir ( ) , 'cook-land-wt-' ) ) ;
227+ dirs . push ( wt ) ;
228+ execFileSync ( 'git' , [ 'worktree' , 'add' , '-q' , wt , 'brunch/run/r1' ] , { cwd : dir } ) ;
229+ writeFileSync ( join ( wt , 'app.ts' ) , 'export const v = 2;\n' ) ;
230+ writeFileSync ( join ( wt , 'feature.ts' ) , 'export const f = true;\n' ) ;
231+ execFileSync ( 'git' , [ 'add' , '-A' ] , { cwd : wt } ) ;
232+ execFileSync ( 'git' , [ ...id , 'commit' , '-q' , '-m' , 'cook: r1' ] , { cwd : wt } ) ;
233+ const commit = execFileSync ( 'git' , [ 'rev-parse' , 'HEAD' ] , { cwd : wt , encoding : 'utf8' } ) . trim ( ) ;
234+ execFileSync ( 'git' , [ 'worktree' , 'remove' , '--force' , wt ] , { cwd : dir } ) ;
384235 return { dir, baseHead, cookCommit : commit } ;
385236 }
386237
0 commit comments