mirror of
https://github.com/garrytan/gstack.git
synced 2026-06-20 00:30:10 +02:00
fix(gbrain-sync): add .gbrain-source to consumer repo .gitignore (#1384)
The v1.29.0.0 changelog promised .gbrain-source would be added to the consuming repo's .gitignore so the per-worktree pin stays local, but the change actually only added it to gstack's own .gitignore. Without the consumer-side entry, the pin gets committed and Conductor sibling worktrees of the same repo + branch step on each other's pin every time anyone commits. Add ensureGbrainSourceGitignored after a successful gbrain sources attach in runCodeImport. Idempotent on repeat runs (line-trim match), creates .gitignore if missing, logs a warning and continues on permission errors so a read-only checkout doesn't fail the sync. Gate the top-level main() call behind import.meta.main so tests can import the helper without triggering a full sync run on module load. Tests in test/gbrain-source-gitignore.test.ts cover: create-when-missing, append-without-trailing-newline, append-with-trailing-newline, idempotent on repeat, recognize whitespace-surrounded entry, no-throw on read-only file. 6 pass.
This commit is contained in:
@@ -666,6 +666,13 @@ async function runCodeImport(args: CliArgs): Promise<StageResult> {
|
||||
};
|
||||
}
|
||||
|
||||
// v1.29.0.0 changelog promised the per-worktree pin would be ignored in the
|
||||
// consuming repo, but the change actually only added .gbrain-source to
|
||||
// gstack's own .gitignore. Without the consumer-side entry, the pin gets
|
||||
// committed and breaks the per-worktree promise: Conductor sibling worktrees
|
||||
// step on each other's pin every time anyone commits (#1384).
|
||||
ensureGbrainSourceGitignored(root);
|
||||
|
||||
return {
|
||||
name: "code",
|
||||
ran: true,
|
||||
@@ -682,6 +689,39 @@ async function runCodeImport(args: CliArgs): Promise<StageResult> {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure `.gbrain-source` is listed in the consumer repo's `.gitignore`.
|
||||
*
|
||||
* Idempotent: only appends when the entry is not already present (matched on
|
||||
* trimmed lines so a leading/trailing whitespace difference doesn't add a
|
||||
* second copy). Wraps writes in try/catch so a read-only checkout or weird
|
||||
* perms logs a warning and lets the rest of the sync continue.
|
||||
*/
|
||||
export function ensureGbrainSourceGitignored(root: string): void {
|
||||
const gitignorePath = join(root, ".gitignore");
|
||||
try {
|
||||
let existing = "";
|
||||
try {
|
||||
existing = readFileSync(gitignorePath, "utf-8");
|
||||
} catch {
|
||||
// No .gitignore yet — we'll create it.
|
||||
}
|
||||
const alreadyIgnored = existing
|
||||
.split("\n")
|
||||
.some((line) => line.trim() === ".gbrain-source");
|
||||
if (alreadyIgnored) {
|
||||
return;
|
||||
}
|
||||
const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
||||
writeFileSync(gitignorePath, existing + sep + ".gbrain-source\n");
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
console.warn(
|
||||
`[sync:code] could not add .gbrain-source to ${gitignorePath}: ${msg}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function runMemoryIngest(args: CliArgs): StageResult {
|
||||
const t0 = Date.now();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user