From 0dd7564ab044944d0357511c184aab152285ee3e Mon Sep 17 00:00:00 2001 From: boban Date: Mon, 22 Jun 2026 20:53:44 +0200 Subject: [PATCH] =?UTF-8?q?feat(release):=20promote.sh=20=E2=80=94=20clean?= =?UTF-8?q?=20per-release=20tree=20copy=20to=20the=20public=20repo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- scripts/promote.sh | 26 ++++++++++++++++++++++++++ scripts/promote.test.sh | 28 ++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100755 scripts/promote.sh create mode 100644 scripts/promote.test.sh diff --git a/scripts/promote.sh b/scripts/promote.sh new file mode 100755 index 0000000..233943c --- /dev/null +++ b/scripts/promote.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Promote a tag's tree from the source repo into the PUBLIC repo working dir as one clean release +# commit + tag. Excludes .git (via git archive) and .github (private CI stays private). The public +# repo's own .git is preserved. The PUSH is done by the calling workflow (it holds the credentials). +# usage: promote.sh +set -euo pipefail + +src="${1:?src repo required}" +tag="${2:?tag required}" +pub="${3:?public repo dir required}" +msg="${4:?commit message required}" + +git -C "$src" rev-parse "$tag" >/dev/null 2>&1 || { echo "unknown tag: $tag" >&2; exit 1; } + +# Clear the public working tree but KEEP its .git. +find "$pub" -mindepth 1 -maxdepth 1 ! -name '.git' -exec rm -rf {} + + +# Extract exactly the tag's tree (git archive omits .git automatically). +git -C "$src" archive --format=tar "$tag" | tar -x -C "$pub" + +# Private CI never goes public. +rm -rf "${pub:?}/.github" + +git -C "$pub" add -A +git -C "$pub" commit -q -m "$msg" +git -C "$pub" tag "$tag" diff --git a/scripts/promote.test.sh b/scripts/promote.test.sh new file mode 100644 index 0000000..ab54c5a --- /dev/null +++ b/scripts/promote.test.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Test: promote.sh extracts a tag's tree into the public repo working dir (excluding .git + .github), +# commits "Release " and creates the tag. The public repo's own .git is preserved. +set -euo pipefail +here="$(cd "$(dirname "$0")" && pwd)" +work="$(mktemp -d)"; trap 'rm -rf "$work"' EXIT +export GIT_AUTHOR_NAME=t GIT_AUTHOR_EMAIL=t@t GIT_COMMITTER_NAME=t GIT_COMMITTER_EMAIL=t@t + +# source repo with a tag, a .github dir and an .env.example +git init -q "$work/src" +mkdir -p "$work/src/.github/workflows" +printf 'app\n' > "$work/src/app.txt" +printf 'CLUSEV_REPOSITORY=\n' > "$work/src/.env.example" +printf 'name: ci\n' > "$work/src/.github/workflows/ci.yml" +git -C "$work/src" add -A && git -C "$work/src" commit -qm init && git -C "$work/src" tag v1.0.0 + +# empty public repo (its own history) +git init -q "$work/pub" + +bash "$here/promote.sh" "$work/src" v1.0.0 "$work/pub" "Release v1.0.0" + +test -f "$work/pub/app.txt" || { echo "FAIL: tree not copied"; exit 1; } +test -f "$work/pub/.env.example" || { echo "FAIL: .env.example missing"; exit 1; } +test ! -d "$work/pub/.github" || { echo "FAIL: .github leaked to public"; exit 1; } +test -d "$work/pub/.git" || { echo "FAIL: public .git clobbered"; exit 1; } +git -C "$work/pub" rev-parse v1.0.0 >/dev/null 2>&1 || { echo "FAIL: tag missing"; exit 1; } +git -C "$work/pub" log -1 --pretty=%s | grep -qx 'Release v1.0.0' || { echo "FAIL: commit message"; exit 1; } +echo "PASS"