Skip to content

Commit 6b7b1ab

Browse files
kostandinangclaude
andcommitted
FE-883 (1d): retire the dead promoteBrownfieldRun
Slice 1 (1b) replaced the brownfield promotion path with harvestCookRun's merge-tree fold, leaving promoteBrownfieldRun (file-copy commit-tree of a composed tree) used only by its own tests. Delete it + BrownfieldPromoteOptions + its test block, and clean the now-unused imports (mkdtempSync/tmpdir/rmSync/join). The landCookBranch test built its promoted-branch fixture via promoteBrownfieldRun; rebuild it with a plain commit on brunch/run/r1 through a throwaway worktree — the same branch shape landCookBranch merges. mergeSlicesIntoEpicSandbox and promoteGreenfieldRun stay (the greenfield path). 🍳 Built with brunch Co-Authored-By: Opus 4.8 <noreply@anthropic.com>
1 parent a051f23 commit 6b7b1ab

2 files changed

Lines changed: 14 additions & 234 deletions

File tree

src/orchestrator/src/promote-run.test.ts

Lines changed: 12 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { join } from 'node:path';
55

66
import { 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

1010
const dirs: string[] = [];
1111
const 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-
/brunch\/run\/missing/,
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-
362209
describe('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

src/orchestrator/src/promote-run.ts

Lines changed: 2 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { execFileSync } from 'node:child_process';
2-
import { cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, realpathSync, rmSync } from 'node:fs';
3-
import { tmpdir } from 'node:os';
4-
import { basename, isAbsolute, join, relative, resolve } from 'node:path';
2+
import { cpSync, existsSync, mkdirSync, readdirSync, realpathSync } from 'node:fs';
3+
import { basename, isAbsolute, relative, resolve } from 'node:path';
54

65
import { brunchRef } from './run-refs.js';
76

@@ -25,14 +24,6 @@ export type PromoteOptions = {
2524
force: boolean;
2625
};
2726

28-
export type BrownfieldPromoteOptions = {
29-
/** The user's repo root the brownfield cook ran against (a worktree of it). */
30-
sourceDir: string;
31-
/** The composed final tree to land (from `promotionSourceDir`). */
32-
sourceTreeDir: string;
33-
runId: string;
34-
};
35-
3627
function git(args: string[], cwd: string, env?: NodeJS.ProcessEnv): string {
3728
return execFileSync('git', args, { cwd, env, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
3829
}
@@ -142,68 +133,6 @@ export function promoteGreenfieldRun(opts: PromoteOptions): PromoteResult {
142133
return { target, branch, commit };
143134
}
144135

145-
/**
146-
* Land a completed *brownfield* run's composed tree onto the `brunch/run/<runId>`
147-
* branch of the user's repo as one reviewable commit — the brownfield analogue
148-
* of `promoteGreenfieldRun`. The brownfield sandbox was created with
149-
* `git worktree add -b brunch/run/<runId> … HEAD`, so the branch already exists at the
150-
* base the run started from; this commits the result on top of it via plumbing
151-
* (`commit-tree` + compare-and-swap `update-ref`) using a throwaway index and an
152-
* external work-tree, so the user's real working tree, index, and active branch
153-
* are never touched. Merging `brunch/run/<runId>` into the working branch stays the
154-
* user's call — promotion never freelances into it.
155-
*/
156-
export function promoteBrownfieldRun(opts: BrownfieldPromoteOptions): PromoteResult {
157-
const sourceDir = resolve(opts.sourceDir);
158-
const sourceTreeDir = resolve(opts.sourceTreeDir);
159-
const branch = brunchRef.run(opts.runId);
160-
const ref = `refs/heads/${branch}`;
161-
162-
// The branch must already exist (the sandbox branched it from HEAD); its tip is
163-
// the parent we commit on top of and the CAS expected-value for update-ref.
164-
let parent: string;
165-
try {
166-
parent = git(['rev-parse', '--verify', ref], sourceDir);
167-
} catch {
168-
throw new Error(
169-
`Brownfield promotion expects an existing ${branch} branch in ${sourceDir} (created by the cook worktree).`,
170-
);
171-
}
172-
173-
// Absolute git dir so a throwaway index + external work-tree can target the
174-
// user's object store without depending on cwd.
175-
const gitDir = resolve(sourceDir, git(['rev-parse', '--git-dir'], sourceDir));
176-
const tmp = mkdtempSync(join(tmpdir(), 'brunch-promote-'));
177-
const env: NodeJS.ProcessEnv = { ...process.env, GIT_INDEX_FILE: join(tmp, 'index') };
178-
const plumb = ['--git-dir', gitDir, '--work-tree', sourceTreeDir];
179-
try {
180-
// Seed the index from the base, then stage the composed tree as the delta —
181-
// adds, modifications, and deletions, all relative to the base commit.
182-
git([...plumb, 'read-tree', parent], sourceDir, env);
183-
git([...plumb, 'add', '-A'], sourceDir, env);
184-
const tree = git(['--git-dir', gitDir, 'write-tree'], sourceDir, env);
185-
const commit = git(
186-
[
187-
...COMMIT_IDENTITY,
188-
'--git-dir',
189-
gitDir,
190-
'commit-tree',
191-
tree,
192-
'-p',
193-
parent,
194-
'-m',
195-
`cook: ${opts.runId}`,
196-
],
197-
sourceDir,
198-
env,
199-
);
200-
git(['--git-dir', gitDir, 'update-ref', ref, commit, parent], sourceDir, env);
201-
return { target: sourceDir, branch, commit };
202-
} finally {
203-
rmSync(tmp, { recursive: true, force: true });
204-
}
205-
}
206-
207136
/**
208137
* Merge a promoted `brunch/run/<runId>` branch into the repo's checked-out branch — the
209138
* opt-in counterpart to brownfield promotion's hands-off default. Promotion

0 commit comments

Comments
 (0)