perf(ci): reuse tree-equivalent merge candidates
Nuvisphere/Platform-CI: Immutable QuickStack OCI deployment / Build once and deploy exact digest (push) Successful in 23s
Platform CI tests / QuickStack deploy action tests (push) Successful in 31s

This commit is contained in:
2026-08-26 15:16:12 +02:00
parent 0cc4f072e7
commit f520255d61
3 changed files with 100 additions and 10 deletions
+55 -9
View File
@@ -369,6 +369,16 @@ function run(command, args, options = {}) {
return `${result.stdout ?? ""}${result.stderr ?? ""}`;
}
function tryGit(workspace, args) {
const result = spawnSync("git", args, {
cwd: workspace,
encoding: "utf8",
stdio: ["pipe", "pipe", "pipe"],
});
if (result.error || result.status !== 0) return null;
return String(result.stdout ?? "").trim();
}
function safeRelative(value, name) {
const normalized = requiredString(value, name).replace(/^\.\//, "");
if (path.isAbsolute(normalized) || normalized.split(/[\\/]/).includes("..")) {
@@ -660,6 +670,28 @@ export function validatePromotionPullRequestSource(pipeline, headBranch) {
return sourceBranch;
}
export function resolveTreeEquivalentCandidateSha(workspace, sha, source = {}) {
if (source.requireTreeMatch === false) return sha;
const mergeParent = Number(source.mergeParent ?? 2);
if (!Number.isInteger(mergeParent) || mergeParent < 1) {
throw new Error("Candidate mergeParent must be a positive integer.");
}
let currentSha = sha;
const visited = new Set();
while (!visited.has(currentSha)) {
visited.add(currentSha);
const parentSha = tryGit(workspace, ["rev-parse", `${currentSha}^${mergeParent}`]);
if (!parentSha) return currentSha;
const commitTree = run("git", ["rev-parse", `${currentSha}^{tree}`], { cwd: workspace, capture: true }).trim();
const parentTree = run("git", ["rev-parse", `${parentSha}^{tree}`], { cwd: workspace, capture: true }).trim();
if (commitTree !== parentTree) return currentSha;
currentSha = parentSha;
}
return currentSha;
}
function appendSummary(text) {
const summary = process.env.GITHUB_STEP_SUMMARY ?? process.env.GITEA_STEP_SUMMARY;
if (summary) fs.appendFileSync(summary, `${text}\n`);
@@ -912,6 +944,12 @@ async function executeVersion2({
const useRegistryCache =
execution !== "build-validation" ||
isSameRepositoryPullRequest({ eventName, environment: process.env });
const candidateSha = execution === "build-deploy"
? resolveTreeEquivalentCandidateSha(workspace, sha, pipeline.source)
: sha;
if (candidateSha !== sha) {
console.log(`Branch commit ${sha} has the same tree as tested candidate parent ${candidateSha}; reusing candidate artifacts.`);
}
try {
const needsRegistryLogin =
!(actionMode === "coordinate" && execution === "build-validation") &&
@@ -931,7 +969,7 @@ async function executeVersion2({
const built = buildArtifact({
artifact,
registry,
sha,
sha: candidateSha,
workspace,
dockerEnv,
validationOnly,
@@ -959,7 +997,7 @@ async function executeVersion2({
artifacts.set(artifact.name, pullCandidateArtifact({
artifact,
registry,
sourceSha: sha,
sourceSha: candidateSha,
workspace,
dockerEnv,
}));
@@ -968,7 +1006,7 @@ async function executeVersion2({
baseUrl: process.env.QUICKSTACK_BASE_URL,
token: process.env.QUICKSTACK_API_TOKEN,
});
await deployApplications({ applications: pipeline.applications, artifacts, sha, client });
await deployApplications({ applications: pipeline.applications, artifacts, sha: candidateSha, client });
return;
}
@@ -978,7 +1016,7 @@ async function executeVersion2({
artifacts.set(artifact.name, buildArtifact({
artifact,
registry,
sha,
sha: candidateSha,
workspace,
dockerEnv,
validationOnly,
@@ -998,29 +1036,37 @@ async function executeVersion2({
baseUrl: process.env.QUICKSTACK_BASE_URL,
token: process.env.QUICKSTACK_API_TOKEN,
});
await deployApplications({ applications: pipeline.applications, artifacts, sha, client });
await deployApplications({ applications: pipeline.applications, artifacts, sha: candidateSha, client });
return;
}
if (execution === "validate-candidate") {
const sourceBranch = validatePromotionPullRequestSource(pipeline, resolvePullRequestHeadBranch());
const release = loadRelease(workspace, pipeline.release);
const sourceSha = resolveTreeEquivalentCandidateSha(workspace, sha, pipeline.source);
if (sourceSha !== sha) {
console.log(`Pull request head ${sha} has the same tree as tested candidate parent ${sourceSha}; validating parent candidate artifacts.`);
}
const artifacts = new Map();
for (const artifact of pipeline.artifacts) {
artifacts.set(artifact.name, pullCandidateArtifact({
artifact,
registry,
sourceSha: sha,
sourceSha,
workspace,
dockerEnv,
}));
}
console.log(`Validated ${release.tag} against ${artifacts.size} tested candidate artifact(s) from ${sourceBranch} at ${sha}; no rebuild or deployment performed.`);
appendSummary(`Validated release ${release.tag} against tested candidate \`${sha}\` from \`${sourceBranch}\` without rebuilding.`);
console.log(`Validated ${release.tag} against ${artifacts.size} tested candidate artifact(s) from ${sourceBranch} at ${sourceSha}; no rebuild or deployment performed.`);
appendSummary(`Validated release ${release.tag} against tested candidate \`${sourceSha}\` from \`${sourceBranch}\` without rebuilding.`);
return;
}
const sourceSha = resolvePromotionSource(workspace, pipeline.source);
const mergeSourceSha = resolvePromotionSource(workspace, pipeline.source);
const sourceSha = resolveTreeEquivalentCandidateSha(workspace, mergeSourceSha, pipeline.source);
if (sourceSha !== mergeSourceSha) {
console.log(`Promotion source ${mergeSourceSha} has the same tree as tested candidate parent ${sourceSha}; promoting parent candidate artifacts.`);
}
const release = loadRelease(workspace, pipeline.release);
const artifacts = new Map();
for (const artifact of pipeline.artifacts) {
@@ -1,4 +1,8 @@
import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import {
@@ -17,6 +21,7 @@ import {
orderApplications,
resolveDeploymentBranch,
resolvePullRequestHeadBranch,
resolveTreeEquivalentCandidateSha,
selectDeployment,
selectPipeline,
toSavePayload,
@@ -130,6 +135,45 @@ test("same-repository pull requests publish reusable candidates while forks stay
);
});
test("tree-equivalent merge commits resolve to their tested candidate parent", () => {
const workspace = fs.mkdtempSync(path.join(os.tmpdir(), "quickstack-merge-candidate-"));
const git = (args) => execFileSync("git", args, {
cwd: workspace,
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
}).trim();
try {
git(["init"]);
git(["checkout", "-b", "main"]);
git(["config", "user.email", "ci@example.test"]);
git(["config", "user.name", "CI"]);
fs.writeFileSync(path.join(workspace, "app.txt"), "base\n");
git(["add", "app.txt"]);
git(["commit", "-m", "base"]);
const baseSha = git(["rev-parse", "HEAD"]);
git(["checkout", "-b", "feature"]);
fs.writeFileSync(path.join(workspace, "app.txt"), "feature\n");
git(["commit", "-am", "feature"]);
const featureSha = git(["rev-parse", "HEAD"]);
git(["checkout", "main"]);
git(["merge", "--no-ff", "feature", "-m", "merge feature"]);
const mergeSha = git(["rev-parse", "HEAD"]);
git(["checkout", "-b", "prod", baseSha]);
git(["merge", "--no-ff", "main", "-m", "merge staging"]);
const prodMergeSha = git(["rev-parse", "HEAD"]);
assert.equal(resolveTreeEquivalentCandidateSha(workspace, mergeSha), featureSha);
assert.equal(resolveTreeEquivalentCandidateSha(workspace, prodMergeSha), featureSha);
assert.equal(resolveTreeEquivalentCandidateSha(workspace, featureSha), featureSha);
assert.equal(
resolveTreeEquivalentCandidateSha(workspace, mergeSha, { requireTreeMatch: false }),
mergeSha,
);
} finally {
fs.rmSync(workspace, { recursive: true, force: true });
}
});
test("validates split action modes and artifact build slots", () => {
assert.equal(resolveActionMode(undefined), "all");
assert.equal(resolveActionMode("BUILD"), "build");