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
17 changes: 14 additions & 3 deletions server/create-content.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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/`.
Expand Down Expand Up @@ -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;
Expand Down
47 changes: 47 additions & 0 deletions test/create-content.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}$/);
});
});
Loading