Skip to content

Commit 5214a0f

Browse files
Fix dev deployments
Name of deployment is now <branch name>@<target branch number>, to know precisely what the transient build is, but also where it would land. Issue: ZENKO-5132
1 parent 2d4415a commit 5214a0f

3 files changed

Lines changed: 154 additions & 7 deletions

File tree

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/usr/bin/env bash
2+
# Print the closest origin/development/* branch that HEAD descends from via
3+
# --first-parent (e.g. "development/2.14"), or nothing if none is found.
4+
set -eu
5+
6+
dev_refs=$(git for-each-ref --format='%(refname:short)' 'refs/remotes/origin/development/*')
7+
[[ -z "${dev_refs}" ]] && exit 0
8+
9+
# Walk HEAD's first-parent backward, excluding anything reachable from a dev
10+
# branch. The --boundary commit (prefixed '-') is where HEAD first meets a dev
11+
# branch's reachable set — the fork point.
12+
fork=$(git rev-list --first-parent --boundary HEAD --not ${dev_refs} \
13+
| sed -n 's/^-//p;/^-/q' | head -1)
14+
15+
# Empty output means HEAD is itself reachable from a dev branch (no feature
16+
# commits ahead), so HEAD itself is the fork point.
17+
fork=${fork:-HEAD}
18+
19+
# name-rev's BFS penalises second-parent hops, so for waterfalled commits the
20+
# native origin dev (reached via first-parent) beats downstream ones (reached
21+
# via the merge's second parent).
22+
name=$(git name-rev --refs='refs/remotes/origin/development/*' --name-only "${fork}")
23+
[[ "${name}" == "undefined" ]] && exit 0
24+
25+
name=${name%%[~^]*}
26+
echo "${name##*origin/}"

.github/workflows/end2end.yaml

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -691,20 +691,23 @@ jobs:
691691
steps:
692692
- name: Checkout
693693
uses: actions/checkout@v6
694+
with:
695+
fetch-depth: 0
696+
filter: blob:none
694697
- name: Determine target environment
695698
id: env
696699
env:
697-
GH_TOKEN: ${{ github.token }}
698700
REF_NAME: ${{ github.ref_name }}
699701
run: |
700-
base_ref=$(gh pr list --head "$REF_NAME" --state open \
701-
--json baseRefName --jq '.[0].baseRefName // empty')
702-
if [[ -n "$base_ref" && "$base_ref" == development/* ]]; then
703-
echo "environment=zenko/$base_ref" >> "$GITHUB_OUTPUT"
702+
base_ref=$(./.github/scripts/resolve-base-branch.sh)
703+
704+
if [[ -n "$base_ref" ]]; then
705+
environment="zenko/$REF_NAME@${base_ref#development/}"
706+
echo "environment=$environment" >> "$GITHUB_OUTPUT"
704707
echo "target_branch=$base_ref" >> "$GITHUB_OUTPUT"
705-
echo "Target environment: zenko/$base_ref"
708+
echo "Target environment: $environment"
706709
else
707-
echo "No open PR targeting a development branch found, skipping deployments"
710+
echo "No development branch ancestor found, skipping deployments"
708711
fi
709712
- name: Create transient deployments
710713
if: steps.env.outputs.environment != ''
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { MockGithub } from "@kie/mock-github";
2+
import path from "path";
3+
import { exec as execCb } from "node:child_process";
4+
import { promisify } from "node:util";
5+
6+
const exec = promisify(execCb);
7+
8+
const SCRIPT = path.resolve(__dirname, "../..", ".github/scripts/resolve-base-branch.sh");
9+
10+
let github: MockGithub;
11+
let repoPath: string;
12+
const devChain: string[] = [];
13+
14+
async function git(args: string): Promise<string> {
15+
const { stdout } = await exec(`git -C "${repoPath}" ${args}`);
16+
return stdout.trim();
17+
}
18+
19+
async function createDev(name: string) {
20+
const base = devChain[devChain.length - 1];
21+
await git(base ? `checkout -b ${name} ${base}` : `checkout -b ${name}`);
22+
devChain.push(name);
23+
}
24+
25+
async function commitOn(branch: string, message: string) {
26+
await git(`checkout ${branch}`);
27+
await git(`commit --allow-empty -m "${message}"`);
28+
29+
// Waterfall to every newer dev branch in the chain
30+
const idx = devChain.indexOf(branch);
31+
if (idx < 0) {
32+
return;
33+
}
34+
35+
let prev = branch;
36+
for (let i = idx + 1; i < devChain.length; i++) {
37+
const next = devChain[i];
38+
await git(`checkout ${next}`);
39+
await git(`merge --no-ff ${prev}`);
40+
prev = next;
41+
}
42+
}
43+
44+
async function branchFrom(name: string, base: string, message: string) {
45+
await git(`checkout -b ${name} ${base}`);
46+
await git(`commit --allow-empty -m "${message}"`);
47+
}
48+
49+
async function runScript(): Promise<string> {
50+
const { stdout } = await exec(SCRIPT, { cwd: repoPath });
51+
return stdout.trim();
52+
}
53+
54+
beforeAll(async () => {
55+
github = new MockGithub({
56+
repo: { zenko: {} },
57+
});
58+
59+
await github.setup();
60+
repoPath = github.repo.getPath("zenko") as string;
61+
62+
await createDev("development/2.11");
63+
await commitOn("development/2.11", "2.11: A1");
64+
await createDev("development/2.12");
65+
await createDev("development/2.13");
66+
67+
await commitOn("development/2.11", "2.11: A2");
68+
await commitOn("development/2.12", "2.12: B1");
69+
await branchFrom("feat-on-outdated-2.12", "development/2.12", "feat-outdated: K1");
70+
await commitOn("development/2.13", "2.13: C1");
71+
72+
await commitOn("development/2.11", "2.11: A3");
73+
await commitOn("development/2.12", "2.12: B2");
74+
await commitOn("development/2.13", "2.13: C2");
75+
76+
await git("push origin development/2.11 development/2.12 development/2.13");
77+
78+
await branchFrom("feat-on-2.13", "development/2.13", "feat-on-2.13: F1");
79+
await branchFrom("stacked-on-2.13", "feat-on-2.13", "stacked: H1");
80+
await branchFrom("feat-on-2.12", "development/2.12", "feat-on-2.12: G1");
81+
await branchFrom("feat-on-2.11", "development/2.11", "feat-on-2.11: I1");
82+
});
83+
84+
afterAll(async () => {
85+
await github.teardown();
86+
});
87+
88+
describe("resolve-base-branch.sh", () => {
89+
it("resolves feature branched off latest dev", async () => {
90+
await git("checkout feat-on-2.13");
91+
expect(await runScript()).toBe("development/2.13");
92+
});
93+
94+
it("resolves feature branched off middle dev", async () => {
95+
await git("checkout feat-on-2.12");
96+
expect(await runScript()).toBe("development/2.12");
97+
});
98+
99+
it("resolves feature branched off oldest dev", async () => {
100+
await git("checkout feat-on-2.11");
101+
expect(await runScript()).toBe("development/2.11");
102+
});
103+
104+
it("resolves stacked branch (feature off feature off dev)", async () => {
105+
await git("checkout stacked-on-2.13");
106+
expect(await runScript()).toBe("development/2.13");
107+
});
108+
109+
it("resolves a dev branch itself to itself", async () => {
110+
await git("checkout development/2.12");
111+
expect(await runScript()).toBe("development/2.12");
112+
});
113+
114+
it("resolves feature branched off an outdated dev commit", async () => {
115+
await git("checkout feat-on-outdated-2.12");
116+
expect(await runScript()).toBe("development/2.12");
117+
});
118+
});

0 commit comments

Comments
 (0)