Initial import from garrytan/gstack@026751e (main snapshot via local relay)
Some checks failed
Workflow Lint / actionlint (push) Has been cancelled
Build CI Image / build (push) Has been cancelled
Skill Docs Freshness / check-freshness (push) Has been cancelled
Periodic Evals / build-image (push) Has been cancelled
Periodic Evals / evals (map[file:test/codex-e2e.test.ts name:e2e-codex]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/gemini-e2e.test.ts name:e2e-gemini]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-design.test.ts name:e2e-design]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-plan.test.ts name:e2e-plan]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-qa-bugs.test.ts name:e2e-qa-bugs]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-qa-workflow.test.ts name:e2e-qa-workflow]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-review.test.ts name:e2e-review]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-workflow.test.ts name:e2e-workflow]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-routing-e2e.test.ts name:e2e-routing]) (push) Has been cancelled
Some checks failed
Workflow Lint / actionlint (push) Has been cancelled
Build CI Image / build (push) Has been cancelled
Skill Docs Freshness / check-freshness (push) Has been cancelled
Periodic Evals / build-image (push) Has been cancelled
Periodic Evals / evals (map[file:test/codex-e2e.test.ts name:e2e-codex]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/gemini-e2e.test.ts name:e2e-gemini]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-design.test.ts name:e2e-design]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-plan.test.ts name:e2e-plan]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-qa-bugs.test.ts name:e2e-qa-bugs]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-qa-workflow.test.ts name:e2e-qa-workflow]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-review.test.ts name:e2e-review]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-workflow.test.ts name:e2e-workflow]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-routing-e2e.test.ts name:e2e-routing]) (push) Has been cancelled
Source: https://github.com/garrytan/gstack/commit/026751e
This commit is contained in:
96
test/gbrain-source-gitignore.test.ts
Normal file
96
test/gbrain-source-gitignore.test.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Unit tests for the `.gbrain-source` gitignore append done by
|
||||
* `runCodeImport` after a successful `gbrain sources attach`.
|
||||
*
|
||||
* Covers #1384: 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, Conductor sibling worktrees commit the pin and clobber each other.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from "bun:test";
|
||||
import { mkdtempSync, writeFileSync, readFileSync, existsSync, rmSync, chmodSync, statSync } from "fs";
|
||||
import { tmpdir } from "os";
|
||||
import { join } from "path";
|
||||
|
||||
import { ensureGbrainSourceGitignored } from "../bin/gstack-gbrain-sync";
|
||||
|
||||
describe("ensureGbrainSourceGitignored", () => {
|
||||
let root: string;
|
||||
|
||||
beforeEach(() => {
|
||||
root = mkdtempSync(join(tmpdir(), "gstack-gbrain-gitignore-"));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("creates .gitignore with the pin entry when none exists", () => {
|
||||
const gitignorePath = join(root, ".gitignore");
|
||||
expect(existsSync(gitignorePath)).toBe(false);
|
||||
|
||||
ensureGbrainSourceGitignored(root);
|
||||
|
||||
expect(existsSync(gitignorePath)).toBe(true);
|
||||
expect(readFileSync(gitignorePath, "utf-8")).toBe(".gbrain-source\n");
|
||||
});
|
||||
|
||||
it("appends the pin entry to an existing .gitignore without trailing newline", () => {
|
||||
const gitignorePath = join(root, ".gitignore");
|
||||
writeFileSync(gitignorePath, "node_modules\n.env");
|
||||
|
||||
ensureGbrainSourceGitignored(root);
|
||||
|
||||
expect(readFileSync(gitignorePath, "utf-8")).toBe(
|
||||
"node_modules\n.env\n.gbrain-source\n",
|
||||
);
|
||||
});
|
||||
|
||||
it("appends the pin entry to an existing .gitignore with trailing newline", () => {
|
||||
const gitignorePath = join(root, ".gitignore");
|
||||
writeFileSync(gitignorePath, "node_modules\n.env\n");
|
||||
|
||||
ensureGbrainSourceGitignored(root);
|
||||
|
||||
expect(readFileSync(gitignorePath, "utf-8")).toBe(
|
||||
"node_modules\n.env\n.gbrain-source\n",
|
||||
);
|
||||
});
|
||||
|
||||
it("is idempotent: does not duplicate the pin entry on a second call", () => {
|
||||
const gitignorePath = join(root, ".gitignore");
|
||||
writeFileSync(gitignorePath, "node_modules\n.gbrain-source\n.env\n");
|
||||
|
||||
ensureGbrainSourceGitignored(root);
|
||||
ensureGbrainSourceGitignored(root);
|
||||
|
||||
const lines = readFileSync(gitignorePath, "utf-8").split("\n");
|
||||
const hits = lines.filter((line) => line.trim() === ".gbrain-source");
|
||||
expect(hits.length).toBe(1);
|
||||
});
|
||||
|
||||
it("recognizes the entry even when it has surrounding whitespace", () => {
|
||||
const gitignorePath = join(root, ".gitignore");
|
||||
writeFileSync(gitignorePath, "node_modules\n .gbrain-source \n");
|
||||
|
||||
ensureGbrainSourceGitignored(root);
|
||||
|
||||
const lines = readFileSync(gitignorePath, "utf-8").split("\n");
|
||||
const hits = lines.filter((line) => line.trim() === ".gbrain-source");
|
||||
expect(hits.length).toBe(1);
|
||||
});
|
||||
|
||||
it("does not throw when the .gitignore is read-only", () => {
|
||||
const gitignorePath = join(root, ".gitignore");
|
||||
writeFileSync(gitignorePath, "node_modules\n");
|
||||
const originalMode = statSync(gitignorePath).mode;
|
||||
chmodSync(gitignorePath, 0o444);
|
||||
try {
|
||||
// Must not throw — sync stage continues on write failure.
|
||||
expect(() => ensureGbrainSourceGitignored(root)).not.toThrow();
|
||||
} finally {
|
||||
chmodSync(gitignorePath, originalMode);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user