diff --git a/server/create-content.mjs b/server/create-content.mjs index 586f664..dbf6a71 100644 --- a/server/create-content.mjs +++ b/server/create-content.mjs @@ -15,6 +15,7 @@ import { mkdirSync, writeFileSync, existsSync } from "node:fs"; import { join, dirname } from "node:path"; import { execFileSync } from "node:child_process"; import { descriptorById } from "./content-types.mjs"; +import { ANGLESITE_COMMIT_IDENTITY } from "./git-identity.mjs"; /** * Scaffold a new Astro page under `src/pages/`. @@ -215,14 +216,24 @@ function renderEntry(descriptor, title, now = new Date()) { * Stage and commit exactly `relPath` on the current branch. Returns the new HEAD SHA, or null * on any failure (not a git repo, no prior commit, a pre-commit hook rejecting, git missing). * Commits a single pathspec so unrelated staged/working changes are left untouched. + * + * Passes `ANGLESITE_COMMIT_IDENTITY` on the commit itself: guest environments (e.g. the + * Anglesite-app container) have no `user.name`/`user.email` git config, and `git commit`'s + * own auto-detect fallback ("Please tell me who you are") fails there too — same root cause + * as #428, see `git-identity.mjs`. */ function commitFile(projectRoot, relPath, message) { - const run = (args) => - execFileSync("git", args, { cwd: projectRoot, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).trim(); + const run = (args, env = {}) => + execFileSync("git", args, { + cwd: projectRoot, + encoding: "utf-8", + stdio: ["ignore", "pipe", "pipe"], + env: { ...process.env, ...env }, + }).trim(); try { run(["rev-parse", "--git-dir"]); run(["add", "--", relPath]); - run(["commit", "-m", message, "--", relPath]); + run(["commit", "-m", message, "--", relPath], ANGLESITE_COMMIT_IDENTITY); return run(["rev-parse", "HEAD"]); } catch { return null; diff --git a/test/create-content.test.js b/test/create-content.test.js index 64be772..b79bf53 100644 --- a/test/create-content.test.js +++ b/test/create-content.test.js @@ -212,3 +212,50 @@ describe("createTyped", () => { } }); }); + +describe("commitFile — no ambient git identity (#428)", () => { + // Reproduces the container guest environment from #428: no ~/.gitconfig, no + // GIT_AUTHOR/COMMITTER_NAME/EMAIL in the ambient environment. `git commit`'s own + // auto-detect fallback fails there too ("Please tell me who you are"), same as + // commit-tree — createPage/createPost/createTyped must supply their own identity + // rather than relying on it (see git-identity.mjs). + const identityKeys = [ + "GIT_AUTHOR_NAME", "GIT_AUTHOR_EMAIL", "GIT_COMMITTER_NAME", "GIT_COMMITTER_EMAIL", + "GIT_CONFIG_GLOBAL", "GIT_CONFIG_SYSTEM", "GIT_CONFIG_NOSYSTEM", + ]; + let saved; + + beforeEach(() => { + git(["config", "--unset", "user.email"]); + git(["config", "--unset", "user.name"]); + // Also isolate from this sandbox's own ~/.gitconfig and any system config, so the + // ambient environment matches the container guest in #428: no identity available + // from config OR env, anywhere. + saved = Object.fromEntries(identityKeys.map((k) => [k, process.env[k]])); + identityKeys.forEach((k) => delete process.env[k]); + process.env.GIT_CONFIG_GLOBAL = "/dev/null"; + process.env.GIT_CONFIG_NOSYSTEM = "1"; + }); + + afterEach(() => { + identityKeys.forEach((k) => { + if (saved[k] === undefined) delete process.env[k]; + else process.env[k] = saved[k]; + }); + }); + + it("createPage still commits", () => { + const result = createPage(repo, { name: "About Us" }); + expect(result.commit).toMatch(/^[0-9a-f]{40}$/); + }); + + it("createPost still commits", () => { + const result = createPost(repo, { title: "Hello, World!" }); + expect(result.commit).toMatch(/^[0-9a-f]{40}$/); + }); + + it("createTyped still commits", () => { + const result = createTyped(repo, { type: "note", title: "Quick thought" }); + expect(result.commit).toMatch(/^[0-9a-f]{40}$/); + }); +});