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",
|
"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",
|
"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",
|
"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": "oxfmt",
|
||||||
"format:check": "pnpm run version:ensure && oxfmt --check",
|
"format:check": "pnpm run version:ensure && oxfmt --check",
|
||||||
"version:sync": "node scripts/sync-package-versions.mjs",
|
"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