Skip to content

Commit 37eb6a9

Browse files
mjudeikisclaude
andauthored
Build provider images & charts at their own version, not the hub version (#319)
* Build provider images & charts at their own version, not the hub version Provider images and charts were published stamped with the hub version (e.g. v0.0.83) rather than each provider's own release version. Every provider chart leaves image.tag empty (-> .Chart.AppVersion), and both images.yaml and helm-images.yaml stamped provider artifacts with the hub tag, so the per-provider release tags (providers/<name>/vX.Y.Z) never affected the deployed artifact. For quickstart/kuery/app-studio the mirror repos also rebuilt the same image/chart; code/infrastructure had no provider-versioned artifact at all. Move provider image+chart publishing into the monorepo, keyed off the per-provider tag and stamped with the provider's own version: - Add provider-release.yaml: sole publisher of provider images+charts. Triggers on providers/<name>/v*, parses name+version from the tag, and builds the image (vX.Y.Z / X.Y.Z) + chart (version X.Y.Z, appVersion vX.Y.Z) so the chart resolves to the matching provider-versioned image. - images.yaml: provider matrix job is now PR build-only validation; it no longer publishes hub-versioned provider images. - helm-images.yaml: provider charts are only linted/packaged on PRs; the hub release publishes platform charts (kedge-hub, kedge-agent) only. - split-*.yaml: made source-only (dropped the per-provider tag trigger and tag-push). This prevents the mirror repos from rebuilding and colliding on the same image/chart tag the monorepo now produces. - cmd/kedge-release: update plan descriptions + doc comment to match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * provider-release: stamp chart version/appVersion via helm flags, not sed Address review: rewriting Chart.yaml in place with sed -i is brittle (silently no-ops on reformatting) and mutates the checked-out tree. helm package --version/--app-version stamps both at package time without touching source. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 941b856 commit 37eb6a9

9 files changed

Lines changed: 202 additions & 146 deletions

File tree

.github/workflows/helm-images.yaml

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,18 @@ jobs:
7575
APP_VERSION="${CHART_VERSION}"
7676
fi
7777
78-
# Package (and on non-PR events push) each platform chart in
79-
# deploy/charts/ plus every per-provider chart in
80-
# providers/*/deploy/chart/. The provider chart dirs are all named
81-
# "chart", so the chart name is read from the Chart.yaml name: field
82-
# rather than the directory basename. Provider charts are built here
83-
# in the monorepo (not their synced-out mirrors) so a broken chart is
84-
# caught in the PR that changes it.
85-
for chart_dir in deploy/charts/*/ providers/*/deploy/chart/; do
78+
# Platform charts (deploy/charts/*) are versioned + published here with
79+
# the hub version. Provider charts (providers/*/deploy/chart/) are only
80+
# LINTED/PACKAGED here on PRs for validation — they are published, with
81+
# each provider's OWN version, by provider-release.yaml on a
82+
# `providers/<name>/v*` tag. Publishing them here would stamp them with
83+
# the hub version. The provider chart dirs are all named "chart", so the
84+
# chart name is read from the Chart.yaml name: field.
85+
chart_dirs=(deploy/charts/*/)
86+
if [ "${PUSH}" != "true" ]; then
87+
chart_dirs+=(providers/*/deploy/chart/)
88+
fi
89+
for chart_dir in "${chart_dirs[@]}"; do
8690
if [ -f "${chart_dir}Chart.yaml" ]; then
8791
chart_name=$(grep -E '^name:' "${chart_dir}Chart.yaml" | head -1 | awk '{print $2}')
8892
echo "Processing chart: $chart_name ($chart_dir)"

.github/workflows/images.yaml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,14 @@ jobs:
6363
GIT_COMMIT=${{ github.sha }}
6464
BUILD_DATE=${{ github.event.release.created_at }}
6565
66-
# Per-provider images are built here in the monorepo (not in the synced-out
67-
# mirror repos) so a broken provider Dockerfile is caught in the PR that
68-
# changes it — before the split sync ever reaches a mirror. Each provider is
69-
# a self-contained module/build context (providers/<name>/), so the build is
70-
# identical to what the mirror would run. The image name matches the
71-
# provider chart's values.yaml image.repository so deployments resolve it.
66+
# PR-only validation that every provider Dockerfile + build context still
67+
# builds, before the split sync ever reaches a mirror. Each provider is a
68+
# self-contained build context (providers/<name>/). These builds are NEVER
69+
# pushed here — provider images are published per-provider, stamped with the
70+
# provider's own version, by provider-release.yaml on a `providers/<name>/v*`
71+
# tag. (Publishing here would tag provider images with the hub version.)
7272
build-and-push-provider-images:
73+
if: github.event_name == 'pull_request'
7374
runs-on: ubuntu-latest
7475
permissions:
7576
contents: read
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
name: Provider release (image + chart)
2+
3+
# Builds and publishes a single provider's container image and Helm chart from
4+
# the monorepo, stamped with that provider's OWN version — not the hub version.
5+
#
6+
# Triggered by a per-provider release tag `providers/<name>/v<X.Y.Z>` (cut by
7+
# `cmd/kedge-release`). The provider name and version are parsed from the tag;
8+
# the image is tagged `vX.Y.Z` and `X.Y.Z`, and the chart's version/appVersion
9+
# are set to that version. Because every provider chart leaves `image.tag` empty
10+
# (→ defaults to `.Chart.AppVersion`), the deployed chart resolves to the
11+
# matching provider-versioned image built here.
12+
#
13+
# This is the sole publisher of provider images/charts. The hub `v*` release
14+
# (images.yaml / helm-images.yaml) builds providers in build-only PR validation
15+
# mode and no longer publishes them, and the split-*.yaml mirrors publish source
16+
# only (no version tag → no mirror image build), so there is no double-build.
17+
18+
on:
19+
push:
20+
tags:
21+
- 'providers/*/v*'
22+
23+
permissions:
24+
contents: read
25+
packages: write
26+
id-token: write
27+
28+
env:
29+
REGISTRY: ghcr.io
30+
31+
jobs:
32+
release:
33+
runs-on: ubuntu-latest
34+
steps:
35+
- name: Checkout repository
36+
uses: actions/checkout@v4
37+
38+
- name: Parse provider + version from tag
39+
id: tag
40+
run: |
41+
set -euo pipefail
42+
# refs/tags/providers/<name>/vX.Y.Z
43+
FULL="${GITHUB_REF#refs/tags/}" # providers/code/v0.0.9
44+
REST="${FULL#providers/}" # code/v0.0.9
45+
PROVIDER="${REST%%/*}" # code
46+
VERSION="${FULL##*/}" # v0.0.9
47+
[ -d "providers/${PROVIDER}" ] || { echo "unknown provider ${PROVIDER}"; exit 1; }
48+
[ -f "providers/${PROVIDER}/Dockerfile" ] || { echo "no Dockerfile for ${PROVIDER}"; exit 1; }
49+
# Image repo matches the provider chart's values.yaml image.repository.
50+
# app-studio is published under a nested path; the rest are flat.
51+
case "${PROVIDER}" in
52+
app-studio) IMAGE="${{ github.repository_owner }}/kedge/app-studio-provider" ;;
53+
*) IMAGE="${{ github.repository_owner }}/kedge-${PROVIDER}-provider" ;;
54+
esac
55+
echo "provider=${PROVIDER}" >> "$GITHUB_OUTPUT"
56+
echo "version=${VERSION}" >> "$GITHUB_OUTPUT" # v0.0.9 (chart appVersion + image tag)
57+
echo "semver=${VERSION#v}" >> "$GITHUB_OUTPUT" # 0.0.9 (chart version, OCI convention)
58+
echo "image=${IMAGE}" >> "$GITHUB_OUTPUT"
59+
60+
- name: Log in to the Container registry
61+
uses: docker/login-action@v3
62+
with:
63+
registry: ${{ env.REGISTRY }}
64+
username: ${{ github.actor }}
65+
password: ${{ secrets.GITHUB_TOKEN }}
66+
67+
- name: Set up Docker Buildx
68+
uses: docker/setup-buildx-action@v3
69+
70+
- name: Build and push image
71+
uses: docker/build-push-action@v6
72+
with:
73+
context: ./providers/${{ steps.tag.outputs.provider }}
74+
file: ./providers/${{ steps.tag.outputs.provider }}/Dockerfile
75+
platforms: linux/amd64,linux/arm64
76+
push: true
77+
tags: |
78+
${{ env.REGISTRY }}/${{ steps.tag.outputs.image }}:${{ steps.tag.outputs.version }}
79+
${{ env.REGISTRY }}/${{ steps.tag.outputs.image }}:${{ steps.tag.outputs.semver }}
80+
81+
- name: Install Helm
82+
uses: azure/setup-helm@v3
83+
with:
84+
version: 'v3.12.0'
85+
86+
- name: Package and push chart
87+
env:
88+
HELM_EXPERIMENTAL_OCI: 1
89+
run: |
90+
set -euo pipefail
91+
echo "${{ github.token }}" | helm registry login "${REGISTRY}" --username ${{ github.actor }} --password-stdin
92+
93+
chart_dir="providers/${{ steps.tag.outputs.provider }}/deploy/chart/"
94+
chart_name=$(grep -E '^name:' "${chart_dir}Chart.yaml" | head -1 | awk '{print $2}')
95+
96+
# Chart version follows OCI/semver convention (no 'v'); appVersion keeps
97+
# the 'v' so the chart's image.tag default (.Chart.AppVersion) matches
98+
# the 'vX.Y.Z' image tag pushed above.
99+
CHART_VERSION="${{ steps.tag.outputs.semver }}"
100+
APP_VERSION="${{ steps.tag.outputs.version }}"
101+
102+
# lint renders templates against default values (package only tars), so
103+
# a broken template fails here rather than shipping.
104+
helm lint "$chart_dir"
105+
# Stamp version + appVersion at package time via flags rather than
106+
# rewriting Chart.yaml, so the checked-out source is never mutated.
107+
helm package "$chart_dir" --version "${CHART_VERSION}" --app-version "${APP_VERSION}"
108+
helm push "${chart_name}-${CHART_VERSION}.tgz" "oci://${REGISTRY}/${{ github.repository_owner }}/charts"
109+
echo "Pushed oci://${REGISTRY}/${{ github.repository_owner }}/charts/${chart_name}:${CHART_VERSION} (appVersion ${APP_VERSION})"

.github/workflows/split-app-studio.yaml

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@ name: Split app-studio provider
55
#
66
# Uses splitsh-lite (https://github.com/splitsh/lite) which is deterministic:
77
# the same source always splits to the same commit sha1s, so pushes normally
8-
# fast-forward. Runs on every push to main (mirrors the branch) and on
9-
# per-provider release tags (see below). workflow_dispatch is provided for the
10-
# initial seed / manual re-sync. It also runs on PRs that touch the split
8+
# fast-forward. Runs on every push to main (mirrors the branch); workflow_dispatch
9+
# is provided for the initial seed / manual re-sync. It also runs on PRs that touch the split
1110
# surface, but those only validate (install + split) — the deploy-key and push
1211
# steps are gated to non-PR events, so a PR never writes to the mirror.
1312
#
14-
# Releases are tagged per-provider in the monorepo as
15-
# `providers/app-studio/vX.Y.Z`; the prefix is stripped on the way out, so the
16-
# mirror receives a plain `vX.Y.Z` tag (which then triggers its own image/chart
17-
# release workflows). A repo-wide `vX.Y.Z` tag does NOT release the mirror.
13+
# This mirror is SOURCE ONLY: it tracks `main`, and does NOT receive release
14+
# tags. Provider images and charts are built+published from the monorepo by
15+
# provider-release.yaml (on a `providers/app-studio/vX.Y.Z` tag), stamped with
16+
# the provider's own version. Pushing the tag here too would make the mirror
17+
# rebuild the same image/chart and collide on the published tag.
1818
#
1919
# Required secret: APP_STUDIO_DEPLOY_KEY
2020
# An SSH private key whose public half is added as a *write* deploy key on
@@ -24,7 +24,6 @@ name: Split app-studio provider
2424
on:
2525
push:
2626
branches: [main]
27-
tags: ['providers/app-studio/v*']
2827
pull_request:
2928
branches: [main]
3029
paths:
@@ -114,19 +113,8 @@ jobs:
114113
115114
git remote add mirror "${TARGET_REPO}"
116115
117-
if [ "${GITHUB_REF}" != "${GITHUB_REF#refs/tags/}" ]; then
118-
# Per-provider tag: refs/tags/providers/app-studio/vX.Y.Z. Strip the
119-
# path prefix (everything up to the last '/') so the mirror gets a
120-
# plain vX.Y.Z tag.
121-
FULL_TAG="${GITHUB_REF#refs/tags/}"
122-
TAG="${FULL_TAG##*/}"
123-
echo "Publishing tag ${FULL_TAG} -> ${TARGET_REPO} as ${TAG}"
124-
# Tags are immutable; push without force so an accidental re-tag fails loudly.
125-
git push mirror "${SHA}:refs/tags/${TAG}"
126-
else
127-
echo "Publishing branch ${TARGET_BRANCH} -> ${TARGET_REPO}"
128-
# --force keeps the mirror a faithful reflection even if monorepo
129-
# history is ever rewritten. The mirror is read-only, so there are
130-
# no downstream commits to clobber.
131-
git push --force mirror "${SHA}:refs/heads/${TARGET_BRANCH}"
132-
fi
116+
echo "Publishing branch ${TARGET_BRANCH} -> ${TARGET_REPO}"
117+
# --force keeps the mirror a faithful reflection even if monorepo
118+
# history is ever rewritten. The mirror is read-only, so there are
119+
# no downstream commits to clobber.
120+
git push --force mirror "${SHA}:refs/heads/${TARGET_BRANCH}"

.github/workflows/split-code.yaml

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@ name: Split code provider
55
#
66
# Uses splitsh-lite (https://github.com/splitsh/lite) which is deterministic:
77
# the same source always splits to the same commit sha1s, so pushes normally
8-
# fast-forward. Runs on every push to main (mirrors the branch) and on
9-
# per-provider release tags (see below). workflow_dispatch is provided for the
10-
# initial seed / manual re-sync. It also runs on PRs that touch the split
8+
# fast-forward. Runs on every push to main (mirrors the branch); workflow_dispatch
9+
# is provided for the initial seed / manual re-sync. It also runs on PRs that touch the split
1110
# surface, but those only validate (install + split) — the deploy-key and push
1211
# steps are gated to non-PR events, so a PR never writes to the mirror.
1312
#
14-
# Releases are tagged per-provider in the monorepo as
15-
# `providers/code/vX.Y.Z`; the prefix is stripped on the way out, so the
16-
# mirror receives a plain `vX.Y.Z` tag (which then triggers its own image/chart
17-
# release workflows). A repo-wide `vX.Y.Z` tag does NOT release the mirror.
13+
# This mirror is SOURCE ONLY: it tracks `main`, and does NOT receive release
14+
# tags. Provider images and charts are built+published from the monorepo by
15+
# provider-release.yaml (on a `providers/code/vX.Y.Z` tag), stamped with the
16+
# provider's own version. Pushing the tag here too would make the mirror
17+
# rebuild the same image/chart and collide on the published tag.
1818
#
1919
# Required secret: CODE_DEPLOY_KEY
2020
# An SSH private key whose public half is added as a *write* deploy key on
@@ -24,7 +24,6 @@ name: Split code provider
2424
on:
2525
push:
2626
branches: [main]
27-
tags: ['providers/code/v*']
2827
pull_request:
2928
branches: [main]
3029
paths:
@@ -114,19 +113,8 @@ jobs:
114113
115114
git remote add mirror "${TARGET_REPO}"
116115
117-
if [ "${GITHUB_REF}" != "${GITHUB_REF#refs/tags/}" ]; then
118-
# Per-provider tag: refs/tags/providers/code/vX.Y.Z. Strip the
119-
# path prefix (everything up to the last '/') so the mirror gets a
120-
# plain vX.Y.Z tag.
121-
FULL_TAG="${GITHUB_REF#refs/tags/}"
122-
TAG="${FULL_TAG##*/}"
123-
echo "Publishing tag ${FULL_TAG} -> ${TARGET_REPO} as ${TAG}"
124-
# Tags are immutable; push without force so an accidental re-tag fails loudly.
125-
git push mirror "${SHA}:refs/tags/${TAG}"
126-
else
127-
echo "Publishing branch ${TARGET_BRANCH} -> ${TARGET_REPO}"
128-
# --force keeps the mirror a faithful reflection even if monorepo
129-
# history is ever rewritten. The mirror is read-only, so there are
130-
# no downstream commits to clobber.
131-
git push --force mirror "${SHA}:refs/heads/${TARGET_BRANCH}"
132-
fi
116+
echo "Publishing branch ${TARGET_BRANCH} -> ${TARGET_REPO}"
117+
# --force keeps the mirror a faithful reflection even if monorepo
118+
# history is ever rewritten. The mirror is read-only, so there are
119+
# no downstream commits to clobber.
120+
git push --force mirror "${SHA}:refs/heads/${TARGET_BRANCH}"

.github/workflows/split-infrastructure.yaml

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,16 @@ name: Split infrastructure provider
55
#
66
# Uses splitsh-lite (https://github.com/splitsh/lite) which is deterministic:
77
# the same source always splits to the same commit sha1s, so pushes normally
8-
# fast-forward. Runs on every push to main (mirrors the branch) and on
9-
# per-provider release tags (see below). workflow_dispatch is provided for the
10-
# initial seed / manual re-sync. It also runs on PRs that touch the split
8+
# fast-forward. Runs on every push to main (mirrors the branch); workflow_dispatch
9+
# is provided for the initial seed / manual re-sync. It also runs on PRs that touch the split
1110
# surface, but those only validate (install + split) — the deploy-key and push
1211
# steps are gated to non-PR events, so a PR never writes to the mirror.
1312
#
14-
# Releases are tagged per-provider in the monorepo as
15-
# `providers/infrastructure/vX.Y.Z`; the prefix is stripped on the way out, so
16-
# the mirror receives a plain `vX.Y.Z` tag (which then triggers its own
17-
# image/chart release workflows). A repo-wide `vX.Y.Z` tag does NOT release the
18-
# mirror.
13+
# This mirror is SOURCE ONLY: it tracks `main`, and does NOT receive release
14+
# tags. Provider images and charts are built+published from the monorepo by
15+
# provider-release.yaml (on a `providers/infrastructure/vX.Y.Z` tag), stamped
16+
# with the provider's own version. Pushing the tag here too would make the
17+
# mirror rebuild the same image/chart and collide on the published tag.
1918
#
2019
# Required secret: INFRA_DEPLOY_KEY
2120
# An SSH private key whose public half is added as a *write* deploy key on
@@ -25,7 +24,6 @@ name: Split infrastructure provider
2524
on:
2625
push:
2726
branches: [main]
28-
tags: ['providers/infrastructure/v*']
2927
pull_request:
3028
branches: [main]
3129
paths:
@@ -115,19 +113,8 @@ jobs:
115113
116114
git remote add mirror "${TARGET_REPO}"
117115
118-
if [ "${GITHUB_REF}" != "${GITHUB_REF#refs/tags/}" ]; then
119-
# Per-provider tag: refs/tags/providers/infrastructure/vX.Y.Z. Strip
120-
# the path prefix (everything up to the last '/') so the mirror gets
121-
# a plain vX.Y.Z tag.
122-
FULL_TAG="${GITHUB_REF#refs/tags/}"
123-
TAG="${FULL_TAG##*/}"
124-
echo "Publishing tag ${FULL_TAG} -> ${TARGET_REPO} as ${TAG}"
125-
# Tags are immutable; push without force so an accidental re-tag fails loudly.
126-
git push mirror "${SHA}:refs/tags/${TAG}"
127-
else
128-
echo "Publishing branch ${TARGET_BRANCH} -> ${TARGET_REPO}"
129-
# --force keeps the mirror a faithful reflection even if monorepo
130-
# history is ever rewritten. The mirror is read-only, so there are
131-
# no downstream commits to clobber.
132-
git push --force mirror "${SHA}:refs/heads/${TARGET_BRANCH}"
133-
fi
116+
echo "Publishing branch ${TARGET_BRANCH} -> ${TARGET_REPO}"
117+
# --force keeps the mirror a faithful reflection even if monorepo
118+
# history is ever rewritten. The mirror is read-only, so there are
119+
# no downstream commits to clobber.
120+
git push --force mirror "${SHA}:refs/heads/${TARGET_BRANCH}"

.github/workflows/split-kuery.yaml

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@ name: Split kuery provider
55
#
66
# Uses splitsh-lite (https://github.com/splitsh/lite) which is deterministic:
77
# the same source always splits to the same commit sha1s, so pushes normally
8-
# fast-forward. Runs on every push to main (mirrors the branch) and on
9-
# per-provider release tags (see below). workflow_dispatch is provided for the
10-
# initial seed / manual re-sync. It also runs on PRs that touch the split
8+
# fast-forward. Runs on every push to main (mirrors the branch); workflow_dispatch
9+
# is provided for the initial seed / manual re-sync. It also runs on PRs that touch the split
1110
# surface, but those only validate (install + split) — the deploy-key and push
1211
# steps are gated to non-PR events, so a PR never writes to the mirror.
1312
#
14-
# Releases are tagged per-provider in the monorepo as
15-
# `providers/kuery/vX.Y.Z`; the prefix is stripped on the way out, so the
16-
# mirror receives a plain `vX.Y.Z` tag (which then triggers its own image/chart
17-
# release workflows). A repo-wide `vX.Y.Z` tag does NOT release the mirror.
13+
# This mirror is SOURCE ONLY: it tracks `main`, and does NOT receive release
14+
# tags. Provider images and charts are built+published from the monorepo by
15+
# provider-release.yaml (on a `providers/kuery/vX.Y.Z` tag), stamped with the
16+
# provider's own version. Pushing the tag here too would make the mirror
17+
# rebuild the same image/chart and collide on the published tag.
1818
#
1919
# Required secret: KUERY_DEPLOY_KEY
2020
# An SSH private key whose public half is added as a *write* deploy key on
@@ -24,7 +24,6 @@ name: Split kuery provider
2424
on:
2525
push:
2626
branches: [main]
27-
tags: ['providers/kuery/v*']
2827
pull_request:
2928
branches: [main]
3029
paths:
@@ -114,19 +113,8 @@ jobs:
114113
115114
git remote add mirror "${TARGET_REPO}"
116115
117-
if [ "${GITHUB_REF}" != "${GITHUB_REF#refs/tags/}" ]; then
118-
# Per-provider tag: refs/tags/providers/kuery/vX.Y.Z. Strip the
119-
# path prefix (everything up to the last '/') so the mirror gets a
120-
# plain vX.Y.Z tag.
121-
FULL_TAG="${GITHUB_REF#refs/tags/}"
122-
TAG="${FULL_TAG##*/}"
123-
echo "Publishing tag ${FULL_TAG} -> ${TARGET_REPO} as ${TAG}"
124-
# Tags are immutable; push without force so an accidental re-tag fails loudly.
125-
git push mirror "${SHA}:refs/tags/${TAG}"
126-
else
127-
echo "Publishing branch ${TARGET_BRANCH} -> ${TARGET_REPO}"
128-
# --force keeps the mirror a faithful reflection even if monorepo
129-
# history is ever rewritten. The mirror is read-only, so there are
130-
# no downstream commits to clobber.
131-
git push --force mirror "${SHA}:refs/heads/${TARGET_BRANCH}"
132-
fi
116+
echo "Publishing branch ${TARGET_BRANCH} -> ${TARGET_REPO}"
117+
# --force keeps the mirror a faithful reflection even if monorepo
118+
# history is ever rewritten. The mirror is read-only, so there are
119+
# no downstream commits to clobber.
120+
git push --force mirror "${SHA}:refs/heads/${TARGET_BRANCH}"

0 commit comments

Comments
 (0)