@@ -31,13 +31,54 @@ permissions:
3131
3232concurrency :
3333 group : dockerhub-${{ inputs.overwrite_existing_release && inputs.release_tag || github.ref_name }}
34- cancel-in-progress : true
34+ cancel-in-progress : false
3535
3636jobs :
37+ source-head :
38+ name : " 🧭 Verify Current Source Head"
39+ runs-on : ubuntu-latest
40+ outputs :
41+ current : ${{ steps.verify.outputs.current }}
42+ steps :
43+ - name : Skip stale branch events
44+ id : verify
45+ uses : actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
46+ with :
47+ script : |
48+ const isBranch = context.ref.startsWith('refs/heads/');
49+ const checksBranchHead =
50+ isBranch &&
51+ (context.eventName === 'push' || context.eventName === 'workflow_dispatch');
52+
53+ if (!checksBranchHead) {
54+ core.setOutput('current', 'true');
55+ return;
56+ }
57+
58+ const branch = context.ref.slice('refs/heads/'.length);
59+ const { data } = await github.rest.repos.getBranch({
60+ owner: context.repo.owner,
61+ repo: context.repo.repo,
62+ branch,
63+ });
64+ const currentSha = data.commit.sha;
65+ const isCurrent = currentSha === context.sha;
66+
67+ core.setOutput('current', String(isCurrent));
68+ if (isCurrent) {
69+ core.info(`${context.sha} is still the current head of ${branch}.`);
70+ } else {
71+ core.notice(
72+ `Skipping stale ${context.eventName} event for ${context.sha}; ` +
73+ `${branch} now points to ${currentSha}.`
74+ );
75+ }
76+
3777 prepare :
3878 name : " 🔧 Prepare Metadata"
3979 runs-on : ubuntu-latest
40- if : github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[ci skip]')
80+ needs : source-head
81+ if : needs.source-head.outputs.current == 'true' && github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[ci skip]')
4182 outputs :
4283 sha_short : ${{ steps.vars.outputs.sha_short }}
4384 version : ${{ steps.mode.outputs.version }}
@@ -578,6 +619,53 @@ jobs:
578619 needs : [prepare, build-linux]
579620 if : github.event_name != 'pull_request' && needs.prepare.outputs.mode != 'master'
580621 steps :
622+ - name : Prevent stale dev image promotion
623+ if : needs.prepare.outputs.mode == 'dev'
624+ uses : actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
625+ env :
626+ SOURCE_SHA : ${{ github.sha }}
627+ with :
628+ script : |
629+ const branch = context.ref.replace('refs/heads/', '');
630+ const sourceSha = process.env.SOURCE_SHA;
631+ const { data: branchData } = await github.rest.repos.getBranch({
632+ owner: context.repo.owner,
633+ repo: context.repo.repo,
634+ branch,
635+ });
636+ const currentSha = branchData.commit.sha;
637+
638+ if (currentSha === sourceSha) {
639+ core.info(`${sourceSha} is still the current head of ${branch}.`);
640+ return;
641+ }
642+
643+ const { data: currentCommit } = await github.rest.repos.getCommit({
644+ owner: context.repo.owner,
645+ repo: context.repo.repo,
646+ ref: currentSha,
647+ });
648+ const generatedCommitTitle =
649+ 'chore: update generated development inputs after build [skip ci]';
650+ const isExpectedGeneratedChild =
651+ currentCommit.parents.length === 1 &&
652+ currentCommit.parents[0].sha === sourceSha &&
653+ currentCommit.commit.message.split('\n', 1)[0] === generatedCommitTitle &&
654+ currentCommit.committer?.login === 'github-actions[bot]';
655+
656+ if (isExpectedGeneratedChild) {
657+ core.info(
658+ `${currentSha} is the expected generated child of ${sourceSha}; ` +
659+ 'promotion remains valid.'
660+ );
661+ return;
662+ }
663+
664+ core.setFailed(
665+ `Refusing to promote stale image for ${sourceSha}; ` +
666+ `${branch} now points to ${currentSha}.`
667+ );
668+
581669 - name : Set up Docker Buildx
582670 uses : docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
583671
0 commit comments