fix(git-hooks): skip sequencer pre-commit formatting (#95842)

* fix(git-hooks): skip sequencer pre-commit formatting

* chore: rerun CI

* fix(git-hooks): skip revert sequencer formatting

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
David 2026-06-25 16:40:12 +08:00 committed by GitHub
parent 08b7fddf80
commit af906225fa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 144 additions and 3 deletions

View file

@ -16,6 +16,18 @@ if [[ ! -f "$FILTER_FILES" ]]; then
exit 1
fi
GIT_DIR="$(git rev-parse --git-dir 2>/dev/null || true)"
if [[ -n "$GIT_DIR" ]] && \
{ [[ -f "$GIT_DIR/MERGE_HEAD" ]] || \
[[ -f "$GIT_DIR/CHERRY_PICK_HEAD" ]] || \
[[ -f "$GIT_DIR/REVERT_HEAD" ]] || \
[[ -f "$GIT_DIR/REBASE_HEAD" ]] || \
[[ -d "$GIT_DIR/rebase-merge" ]] || \
[[ -d "$GIT_DIR/rebase-apply" ]]; }; then
# Sequencer commits stage the operation result, not just the user's local edits.
exit 0
fi
# Security: avoid option-injection from malicious file names (e.g. "--all", "--force").
# Robustness: NUL-delimited file list handles spaces/newlines safely.
# Compatibility: use read loops instead of `mapfile` so this runs on macOS Bash 3.x.

View file

@ -1,6 +1,6 @@
// Git hook tests validate pre-commit hook behavior and scripts.
import { execFileSync } from "node:child_process";
import { existsSync, mkdirSync, symlinkSync, writeFileSync } from "node:fs";
import { existsSync, mkdirSync, readFileSync, symlinkSync, writeFileSync } from "node:fs";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { cleanupTempDirs, makeTempRepoRoot } from "./helpers/temp-repo.js";
@ -39,8 +39,8 @@ const runFailure = (
const failure = error as Error & { status?: number; stderr?: string; stdout?: string };
return {
status: failure.status ?? 1,
stderr: String(failure.stderr ?? ""),
stdout: String(failure.stdout ?? ""),
stderr: failure.stderr ?? "",
stdout: failure.stdout ?? "",
};
}
throw error;
@ -83,6 +83,34 @@ function installPreCommitFixture(dir: string): string {
return fakeBinDir;
}
function installFormattingRecorder(dir: string): string {
const logPath = path.join(dir, "hook-tool.log");
writeFileSync(
path.join(dir, "scripts", "pre-commit", "filter-staged-files.mjs"),
`const files = process.argv.slice(3).filter((arg) => arg !== "--");
for (const file of files) {
if (file.endsWith(".ts")) {
process.stdout.write(file);
process.stdout.write("\0");
}
}
`,
"utf8",
);
writeFileSync(
path.join(dir, "scripts", "pre-commit", "run-node-tool.sh"),
`#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' "$*" >> ${JSON.stringify(logPath)}
`,
{
encoding: "utf8",
mode: 0o755,
},
);
return logPath;
}
function installRunNodeToolFixture(dir: string): void {
mkdirSync(path.join(dir, "scripts", "pre-commit"), { recursive: true });
symlinkSync(
@ -101,6 +129,13 @@ function splitNonEmptyLines(output: string): string[] {
return lines;
}
function readFormatterLog(logPath: string): string[] {
if (!existsSync(logPath)) {
return [];
}
return splitNonEmptyLines(readFileSync(logPath, "utf8"));
}
afterEach(() => {
cleanupTempDirs(tempDirs);
});
@ -128,6 +163,100 @@ describe("git-hooks/pre-commit (integration)", () => {
expect(staged).toEqual(["--all"]);
});
it("skips formatting staged files while a merge commit is in progress", () => {
const dir = makeTempRepoRoot(tempDirs, "openclaw-pre-commit-merge-");
run(dir, "git", ["init", "-q", "--initial-branch=main"]);
installPreCommitFixture(dir);
const logPath = installFormattingRecorder(dir);
writeFileSync(path.join(dir, "changed.ts"), "export const value = 1;\n", "utf8");
run(dir, "git", ["add", "--", "changed.ts"]);
run(dir, "git", [
"-c",
"user.name=Test User",
"-c",
"user.email=test@example.invalid",
"commit",
"-q",
"-m",
"initial",
]);
run(dir, "git", ["checkout", "-q", "-b", "side"]);
writeFileSync(path.join(dir, "changed.ts"), "export const value = 2;\n", "utf8");
run(dir, "git", ["add", "--", "changed.ts"]);
run(dir, "git", [
"-c",
"user.name=Test User",
"-c",
"user.email=test@example.invalid",
"commit",
"-q",
"-m",
"side change",
]);
run(dir, "git", ["checkout", "-q", "main"]);
run(dir, "git", [
"-c",
"user.name=Test User",
"-c",
"user.email=test@example.invalid",
"merge",
"--no-commit",
"--no-ff",
"side",
]);
expect(existsSync(path.join(dir, ".git", "MERGE_HEAD"))).toBe(true);
expect(run(dir, "git", ["diff", "--cached", "--name-only"])).toBe("changed.ts");
run(dir, "bash", ["git-hooks/pre-commit"]);
expect(readFormatterLog(logPath)).toEqual([]);
});
it.each([
["cherry-pick", "CHERRY_PICK_HEAD", "file"],
["revert", "REVERT_HEAD", "file"],
["rebase head", "REBASE_HEAD", "file"],
["merge rebase state", "rebase-merge", "dir"],
["apply rebase state", "rebase-apply", "dir"],
])("skips formatting staged files while %s metadata is present", (_label, gitPath, kind) => {
const dir = makeTempRepoRoot(tempDirs, "openclaw-pre-commit-sequencer-");
run(dir, "git", ["init", "-q", "--initial-branch=main"]);
installPreCommitFixture(dir);
const logPath = installFormattingRecorder(dir);
writeFileSync(path.join(dir, "changed.ts"), "export const value = 1;\n", "utf8");
run(dir, "git", ["add", "--", "changed.ts"]);
const metadataPath = path.join(dir, ".git", gitPath);
if (kind === "dir") {
mkdirSync(metadataPath, { recursive: true });
} else {
writeFileSync(metadataPath, "sequencer state\n", "utf8");
}
run(dir, "bash", ["git-hooks/pre-commit"]);
expect(readFormatterLog(logPath)).toEqual([]);
});
it("still formats staged files during a normal commit", () => {
const dir = makeTempRepoRoot(tempDirs, "openclaw-pre-commit-normal-");
run(dir, "git", ["init", "-q", "--initial-branch=main"]);
installPreCommitFixture(dir);
const logPath = installFormattingRecorder(dir);
writeFileSync(path.join(dir, "changed.ts"), "export const value = 1;\n", "utf8");
run(dir, "git", ["add", "--", "changed.ts"]);
run(dir, "bash", ["git-hooks/pre-commit"]);
expect(readFormatterLog(logPath)).toEqual([
"oxfmt --write --no-error-on-unmatched-pattern changed.ts",
]);
});
it("does not run the changed-scope check for non-doc staged changes", () => {
const dir = makeTempRepoRoot(tempDirs, "openclaw-pre-commit-no-check-changed-");
run(dir, "git", ["init", "-q", "--initial-branch=main"]);