Merge pull request #188 from gsd-build/worktree-agent-a3ed52d1
fix: untrack runtime files already in git index to prevent merge conflicts
This commit is contained in:
commit
83af235d86
4 changed files with 84 additions and 3 deletions
|
|
@ -48,7 +48,7 @@ import {
|
|||
validateCompleteBoundary,
|
||||
formatValidationIssues,
|
||||
} from "./observability-validator.js";
|
||||
import { ensureGitignore } from "./gitignore.js";
|
||||
import { ensureGitignore, untrackRuntimeFiles } from "./gitignore.js";
|
||||
import { runGSDDoctor, rebuildState } from "./doctor.js";
|
||||
import { snapshotSkills, clearSkillSnapshot } from "./skill-discovery.js";
|
||||
import {
|
||||
|
|
@ -381,6 +381,7 @@ export async function startAuto(
|
|||
|
||||
// Ensure .gitignore has baseline patterns
|
||||
ensureGitignore(base);
|
||||
untrackRuntimeFiles(base);
|
||||
|
||||
// Bootstrap .gsd/ if it doesn't exist
|
||||
const gsdDir = join(base, ".gsd");
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import { join } from "node:path";
|
||||
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { execSync } from "node:child_process";
|
||||
|
||||
/**
|
||||
* Patterns that are always correct regardless of project type.
|
||||
|
|
@ -106,6 +107,32 @@ export function ensureGitignore(basePath: string): boolean {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove BASELINE_PATTERNS runtime paths from the git index if they are
|
||||
* currently tracked. This fixes repos that started tracking these files
|
||||
* before the .gitignore rule was added — git continues tracking files
|
||||
* already in the index even after .gitignore is updated.
|
||||
*
|
||||
* Only removes from the index (`--cached`), never from disk. Idempotent.
|
||||
*/
|
||||
export function untrackRuntimeFiles(basePath: string): void {
|
||||
// The GSD runtime paths are the first 7 entries in BASELINE_PATTERNS
|
||||
const runtimePaths = BASELINE_PATTERNS.slice(0, 7);
|
||||
|
||||
for (const pattern of runtimePaths) {
|
||||
// Use -r for directory patterns (trailing slash), strip the slash for the command
|
||||
const target = pattern.endsWith("/") ? pattern.slice(0, -1) : pattern;
|
||||
try {
|
||||
execSync(`git rm -r --cached ${target}`, {
|
||||
cwd: basePath,
|
||||
stdio: ["ignore", "ignore", "ignore"],
|
||||
});
|
||||
} catch {
|
||||
// File not tracked or doesn't exist — expected, ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure basePath/.gsd/PREFERENCES.md exists as an empty template.
|
||||
* Creates the file with frontmatter only if it doesn't exist.
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import {
|
|||
import { join } from "node:path";
|
||||
import { readFileSync, existsSync, mkdirSync, readdirSync, rmSync } from "node:fs";
|
||||
import { execSync, execFileSync } from "node:child_process";
|
||||
import { ensureGitignore, ensurePreferences } from "./gitignore.js";
|
||||
import { ensureGitignore, ensurePreferences, untrackRuntimeFiles } from "./gitignore.js";
|
||||
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
||||
import { showConfirm } from "../shared/confirm-ui.js";
|
||||
|
||||
|
|
@ -457,6 +457,7 @@ export async function showSmartEntry(
|
|||
|
||||
// ── Ensure .gitignore has baseline patterns ──────────────────────────
|
||||
ensureGitignore(basePath);
|
||||
untrackRuntimeFiles(basePath);
|
||||
|
||||
// ── No GSD project OR no milestone → Create first/next milestone ────
|
||||
if (!existsSync(join(basePath, ".gsd"))) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
||||
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync } from "node:fs";
|
||||
import { join, dirname } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import { execSync } from "node:child_process";
|
||||
|
|
@ -1370,6 +1370,58 @@ async function main(): Promise<void> {
|
|||
assert(true, "PreMergeCheckResult type exported and usable");
|
||||
}
|
||||
|
||||
// ─── untrackRuntimeFiles: removes tracked runtime files from index ───
|
||||
|
||||
console.log("\n=== untrackRuntimeFiles ===");
|
||||
|
||||
{
|
||||
const { untrackRuntimeFiles } = await import("../gitignore.ts");
|
||||
const repo = mkdtempSync(join(tmpdir(), "gsd-untrack-"));
|
||||
run("git init -b main", repo);
|
||||
run("git config user.email test@test.com", repo);
|
||||
run("git config user.name Test", repo);
|
||||
|
||||
// Create and track runtime files (simulates pre-.gitignore state)
|
||||
mkdirSync(join(repo, ".gsd", "activity"), { recursive: true });
|
||||
mkdirSync(join(repo, ".gsd", "runtime"), { recursive: true });
|
||||
writeFileSync(join(repo, ".gsd", "completed-units.json"), '["u1"]');
|
||||
writeFileSync(join(repo, ".gsd", "metrics.json"), '{}');
|
||||
writeFileSync(join(repo, ".gsd", "STATE.md"), "# State");
|
||||
writeFileSync(join(repo, ".gsd", "activity", "log.jsonl"), "{}");
|
||||
writeFileSync(join(repo, ".gsd", "runtime", "data.json"), "{}");
|
||||
writeFileSync(join(repo, "src.ts"), "code");
|
||||
run("git add -A", repo);
|
||||
run("git commit -m init", repo);
|
||||
|
||||
// Precondition: runtime files are tracked
|
||||
const trackedBefore = run("git ls-files .gsd/", repo);
|
||||
assert(trackedBefore.includes("completed-units.json"), "untrack: precondition — completed-units tracked");
|
||||
assert(trackedBefore.includes("metrics.json"), "untrack: precondition — metrics tracked");
|
||||
|
||||
// Run untrackRuntimeFiles
|
||||
untrackRuntimeFiles(repo);
|
||||
|
||||
// Runtime files should be removed from the index
|
||||
const trackedAfter = run("git ls-files .gsd/", repo);
|
||||
assertEq(trackedAfter, "", "untrack: all runtime files removed from index");
|
||||
|
||||
// Non-runtime files remain tracked
|
||||
const srcTracked = run("git ls-files src.ts", repo);
|
||||
assert(srcTracked.includes("src.ts"), "untrack: non-runtime files remain tracked");
|
||||
|
||||
// Files still exist on disk
|
||||
assert(existsSync(join(repo, ".gsd", "completed-units.json")),
|
||||
"untrack: completed-units.json still on disk");
|
||||
assert(existsSync(join(repo, ".gsd", "metrics.json")),
|
||||
"untrack: metrics.json still on disk");
|
||||
|
||||
// Idempotent — running again doesn't error
|
||||
untrackRuntimeFiles(repo);
|
||||
assert(true, "untrack: second call is idempotent (no error)");
|
||||
|
||||
rmSync(repo, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
console.log(`\nResults: ${passed} passed, ${failed} failed`);
|
||||
if (failed > 0) process.exit(1);
|
||||
console.log("All tests passed ✓");
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue