mirror of
https://github.com/sinanyuntu/trade-message-center.git
synced 2026-09-17 13:22:11 +08:00
chore: protect main from force push
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
set -e
|
||||
|
||||
zero_object_id=0000000000000000000000000000000000000000
|
||||
|
||||
while IFS=' ' read -r local_ref local_object_id remote_ref remote_object_id
|
||||
do
|
||||
if [ "$remote_ref" != "refs/heads/main" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ "$local_object_id" = "$zero_object_id" ]; then
|
||||
echo "拒绝推送:禁止删除远端 main。" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$remote_object_id" = "$zero_object_id" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! git merge-base --is-ancestor "$remote_object_id" "$local_object_id"; then
|
||||
echo "拒绝推送:远端 main 只允许快进更新,禁止强制推送。" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
"dev:harness": "pnpm --filter @trade-message-center/mind-test-harness run dev",
|
||||
"build": "pnpm run clean:dist && pnpm run version:ensure && node scripts/with-build-hash.mjs --filter @trade-message-center/onetalk-contract --filter @trade-message-center/server --filter @trade-message-center/chrome-extension --if-present run build",
|
||||
"typecheck": "pnpm run clean:dist && pnpm run version:ensure && pnpm --filter @trade-message-center/onetalk-contract --filter @trade-message-center/server --filter @trade-message-center/chrome-extension --if-present run typecheck",
|
||||
"test": "pnpm run clean:dist && pnpm run version:ensure && node --test scripts/package-version.test.mjs scripts/release.test.mjs scripts/dev-startup.test.mjs && pnpm --filter @trade-message-center/onetalk-contract --filter @trade-message-center/server --filter @trade-message-center/chrome-extension --if-present run test",
|
||||
"test": "pnpm run clean:dist && pnpm run version:ensure && node --test scripts/package-version.test.mjs scripts/release.test.mjs scripts/dev-startup.test.mjs scripts/pre-push-main-protection.test.mjs && pnpm --filter @trade-message-center/onetalk-contract --filter @trade-message-center/server --filter @trade-message-center/chrome-extension --if-present run test",
|
||||
"format": "oxfmt",
|
||||
"format:check": "pnpm run version:ensure && oxfmt --check",
|
||||
"version:sync": "node scripts/sync-package-versions.mjs",
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
// 验证 pre-push 对远端 main 的非快进更新进行保护。
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import test from "node:test";
|
||||
|
||||
const repositoryRoot = fileURLToPath(new URL("../", import.meta.url));
|
||||
const hookPath = join(repositoryRoot, ".husky", "pre-push");
|
||||
const zeroObjectId = "0".repeat(40);
|
||||
|
||||
const runGit = (cwd, arguments_) => {
|
||||
const result = spawnSync("git", arguments_, {
|
||||
cwd,
|
||||
encoding: "utf8",
|
||||
});
|
||||
assert.equal(result.status, 0, result.stderr);
|
||||
return result.stdout.trim();
|
||||
};
|
||||
|
||||
const createRepository = () => {
|
||||
const repository = mkdtempSync(join(tmpdir(), "tmc-pre-push-"));
|
||||
runGit(repository, ["init", "--quiet"]);
|
||||
runGit(repository, ["config", "user.email", "test@example.com"]);
|
||||
runGit(repository, ["config", "user.name", "Pre-push test"]);
|
||||
|
||||
const commit = (fileName, content, message) => {
|
||||
writeFileSync(join(repository, fileName), content);
|
||||
runGit(repository, ["add", fileName]);
|
||||
runGit(repository, ["commit", "--quiet", "-m", message]);
|
||||
return runGit(repository, ["rev-parse", "HEAD"]);
|
||||
};
|
||||
|
||||
const base = commit("base.txt", "base\n", "base");
|
||||
const main = commit("main.txt", "main\n", "main");
|
||||
runGit(repository, ["switch", "--detach", base]);
|
||||
const divergent = commit("divergent.txt", "divergent\n", "divergent");
|
||||
|
||||
return { base, divergent, main, repository };
|
||||
};
|
||||
|
||||
const runHook = (repository, localObjectId, remoteRef, remoteObjectId) =>
|
||||
spawnSync("sh", [hookPath], {
|
||||
cwd: repository,
|
||||
encoding: "utf8",
|
||||
input: `refs/heads/local ${localObjectId} ${remoteRef} ${remoteObjectId}\n`,
|
||||
});
|
||||
|
||||
test("allows a fast-forward update to remote main", () => {
|
||||
const { base, main, repository } = createRepository();
|
||||
try {
|
||||
const result = runHook(repository, main, "refs/heads/main", base);
|
||||
|
||||
assert.equal(result.status, 0, result.stderr);
|
||||
} finally {
|
||||
rmSync(repository, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("rejects a non-fast-forward update to remote main", () => {
|
||||
const { divergent, main, repository } = createRepository();
|
||||
try {
|
||||
const result = runHook(repository, divergent, "refs/heads/main", main);
|
||||
|
||||
assert.notEqual(result.status, 0);
|
||||
assert.match(result.stderr, /只允许快进更新/u);
|
||||
} finally {
|
||||
rmSync(repository, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("does not restrict non-fast-forward updates to other remote branches", () => {
|
||||
const { divergent, main, repository } = createRepository();
|
||||
try {
|
||||
const result = runHook(repository, divergent, "refs/heads/release", main);
|
||||
|
||||
assert.equal(result.status, 0, result.stderr);
|
||||
} finally {
|
||||
rmSync(repository, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("rejects deleting remote main", () => {
|
||||
const { main, repository } = createRepository();
|
||||
try {
|
||||
const result = runHook(repository, zeroObjectId, "refs/heads/main", main);
|
||||
|
||||
assert.notEqual(result.status, 0);
|
||||
assert.match(result.stderr, /禁止删除远端 main/u);
|
||||
} finally {
|
||||
rmSync(repository, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user