perf(ci): reuse tree-equivalent merge candidates
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user