Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/__tests__/dry-run-load-path.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -527,4 +527,32 @@ describe('--dry-run on a fresh self-mode clone (#866)', () => {
// A dry run may parse the remote, but nothing else may reach a provider.
expect(providerCalls).toEqual([]);
});

// `pull` also refreshes the team repo, and in self mode that refresh self-heals
// an older `.teamai/.gitignore` — a TRACKED file in the member's own checkout.
// The fixture above has no `.teamai/.gitignore` at all, and the migration
// returns early when the file is missing, so this was the one path in the
// refresh the assertion above could not reach (#866 review).
it('pull --dry-run does not rewrite an existing .teamai/.gitignore (#866)', async () => {
const project = process.cwd();
const gitignore = path.join(project, '.teamai', '.gitignore');
// A pre-beta.5 shape: a bare `env` line, the one the self-heal drops so team
// env vars can reach main. Present and committed, as it would be for a
// teammate who cloned before the migration landed.
const preBeta5 = 'token\nenv\nenv.local\n';
fs.writeFileSync(gitignore, preBeta5);
const git = (...args: string[]) =>
execFileSync('git', ['-c', 'user.name=t', '-c', 'user.email=t@t', ...args], { cwd: project, stdio: 'ignore' });
git('add', '-A');
git('commit', '-q', '-m', 'pre-beta.5 gitignore');

const before = snapshotTree(root);
const error = await pull({ dryRun: true }).then(() => null, (e: unknown) => e);

expect(error).toBeNull();
expect(fs.readFileSync(gitignore, 'utf-8')).toBe(preBeta5);
const after = snapshotTree(root);
for (const key of Object.keys(before)) expect(after[key]).toBe(before[key]);
expect(providerCalls).toEqual([]);
});
});
20 changes: 15 additions & 5 deletions src/pull.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,14 @@ const FILE_NOT_FOUND_ERROR_CODE = 'ENOENT';
* `submodulesChanged` marks a run whose submodule update succeeded but moved the
* tree on disk: the caller must then NOT take that same fast path *this* run,
* because the parent rev alone cannot see the change (issue #525).
*
* `options.dryRun` marks a preview. The refresh still reads the tree, but the
* self-mode `.gitignore` self-heal is skipped: it rewrites a tracked file in the
* member's checkout, and a preview writes nothing (#866).
*/
async function refreshTeamRepo(
localConfig: LocalConfig,
options: { dryRun?: boolean } = {},
): Promise<{ label: string; version: string | null; submodulesFailed: boolean; submodulesChanged: boolean }> {
if (localConfig.repo.kind === 'http') {
const { resolveApiKey } = await import('./api-key.js');
Expand All @@ -108,10 +113,15 @@ async function refreshTeamRepo(
// Self-heal an older .teamai/.gitignore that still ignores `env` (pre-beta.5),
// which would keep team env vars off main. Best-effort; prompts the user to
// commit the change.
try {
const { migrateSelfModeGitignore } = await import('./init.js');
await migrateSelfModeGitignore(localConfig);
} catch { /* best-effort */ }
//
// It rewrites a TRACKED file, so a preview must not run it. The migration is
// idempotent, so the next real pull performs it (#866).
if (!options.dryRun) {
try {
const { migrateSelfModeGitignore } = await import('./init.js');
await migrateSelfModeGitignore(localConfig);
} catch { /* best-effort */ }
}

let version: string | null = null;
try {
Expand Down Expand Up @@ -913,7 +923,7 @@ async function pullForScope(
// unchanged-rev fast path for THIS run — the parent rev cannot see it (#525).
let submodulesChanged = false;
try {
const refresh = await refreshTeamRepo(localConfig);
const refresh = await refreshTeamRepo(localConfig, options);
currentRev = refresh.version;
submodulesFailed = refresh.submodulesFailed;
submodulesChanged = refresh.submodulesChanged;
Expand Down
Loading