channels: the dev channel publishes on dev-rolling, so its tag stops shadowing the branch
CI & Build / Build now, or wait for Android? (push) Successful in 3s
Android / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 6s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / Python tests (push) Successful in 10s
CI & Build / integration (push) Successful in 44s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Tauri desktop (Linux) (push) Failing after 3m20s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m20s
Desktop (Tauri) / Update manifest (push) Skipped
Android / Kotlin + Rust (APK) (push) Successful in 9m13s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
Android / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 6s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / Python tests (push) Successful in 10s
CI & Build / integration (push) Successful in 44s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Tauri desktop (Linux) (push) Failing after 3m20s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m20s
Desktop (Tauri) / Update manifest (push) Skipped
Android / Kotlin + Rust (APK) (push) Successful in 9m13s
The rolling dev release lived on a tag named `dev`, beside the branch named `dev`. Once a clone had fetched tags, `git push origin dev` failed with "src refspec dev matches more than one" (Scribe #2184, note #3042), and every session had to know to spell out refs/heads/dev. The channel is still `dev` everywhere a person sees it: the app's setting, `install.sh --channel dev`, the stored pref. Only the release tag moves, to `dev-rolling`, matching roundtable-android. `stable` has no branch to collide with and keeps its name. - packaging/channel-tag.sh is the one channel -> tag mapping CI reads: the publish steps in android.yml and desktop.yml, the manifest job, fetch-clients.sh and guard-forward.sh. guard-forward exits 2 on an unmapped channel instead of fetching an empty URL and passing. - update.rs and install.sh carry their own copy because neither can run it; update.rs gains a test that no channel feed is named like a branch. - tests/test_channel_tag.py runs the script: no tag is a branch name, dev is exactly dev-rolling, an unknown channel fails with no output. - publish-release.sh titles the release "ThoughtSync dev (rolling)", so the tag name does not leak into what people read. TEMPORARY bridge: desktop apps installed before this have .../download/dev/latest.json compiled in. The dev manifest job sets BRIDGE_TAG=dev, and write-manifest.sh writes the same latest.json to the old `dev` release. Its URLs name dev-rolling assets, so those apps update once into a build that reads the new tag. The bridge, and the old release and tag, are removed once installed apps have crossed over. Until then the push still needs the explicit refspec, as ci-requirements.md now says. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DwoKYuw3qJmUUYsJeNherB
This commit is contained in:
@@ -256,9 +256,12 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
case "$GITHUB_REF_NAME" in
|
||||
main) RELEASE_TAG=stable; RELEASE_PRERELEASE=false ;;
|
||||
*) RELEASE_TAG=dev; RELEASE_PRERELEASE=true ;;
|
||||
main) channel=stable; RELEASE_PRERELEASE=false ;;
|
||||
*) channel=dev; RELEASE_PRERELEASE=true ;;
|
||||
esac
|
||||
# The channel's release TAG, not its name: `dev` publishes on `dev-rolling`
|
||||
# (packaging/channel-tag.sh). A tag named `dev` shadowed the branch.
|
||||
RELEASE_TAG="$(sh packaging/channel-tag.sh "$channel")"
|
||||
export RELEASE_TAG RELEASE_PRERELEASE
|
||||
echo "Publishing the APK to the $RELEASE_TAG channel."
|
||||
bash desktop/packaging/publish-release.sh
|
||||
|
||||
@@ -278,9 +278,12 @@ jobs:
|
||||
# `prerelease` is true for dev so it does not read as a supported build,
|
||||
# and false for stable, which is the real thing.
|
||||
case "$GITHUB_REF_NAME" in
|
||||
main) RELEASE_TAG=stable; RELEASE_PRERELEASE=false ;;
|
||||
*) RELEASE_TAG=dev; RELEASE_PRERELEASE=true ;;
|
||||
main) channel=stable; RELEASE_PRERELEASE=false ;;
|
||||
*) channel=dev; RELEASE_PRERELEASE=true ;;
|
||||
esac
|
||||
# The channel's release TAG, not its name: `dev` publishes on `dev-rolling`
|
||||
# (packaging/channel-tag.sh). A tag named `dev` shadowed the branch.
|
||||
RELEASE_TAG="$(sh packaging/channel-tag.sh "$channel")"
|
||||
export RELEASE_TAG RELEASE_PRERELEASE
|
||||
echo "Publishing to the $RELEASE_TAG channel."
|
||||
bash desktop/packaging/publish-release.sh
|
||||
@@ -412,9 +415,12 @@ jobs:
|
||||
# `prerelease` is true for dev so it does not read as a supported build,
|
||||
# and false for stable, which is the real thing.
|
||||
case "$GITHUB_REF_NAME" in
|
||||
main) RELEASE_TAG=stable; RELEASE_PRERELEASE=false ;;
|
||||
*) RELEASE_TAG=dev; RELEASE_PRERELEASE=true ;;
|
||||
main) channel=stable; RELEASE_PRERELEASE=false ;;
|
||||
*) channel=dev; RELEASE_PRERELEASE=true ;;
|
||||
esac
|
||||
# The channel's release TAG, not its name: `dev` publishes on `dev-rolling`
|
||||
# (packaging/channel-tag.sh). A tag named `dev` shadowed the branch.
|
||||
RELEASE_TAG="$(sh packaging/channel-tag.sh "$channel")"
|
||||
export RELEASE_TAG RELEASE_PRERELEASE
|
||||
echo "Publishing to the $RELEASE_TAG channel."
|
||||
bash desktop/packaging/publish-release.sh
|
||||
@@ -473,11 +479,21 @@ jobs:
|
||||
# No tag arm any more. A `v*` tag does not reach this workflow at all — it
|
||||
# triggers release.yml, which writes a changelog and builds nothing.
|
||||
case "${GITHUB_REF_NAME}" in
|
||||
main) export RELEASE_TAG=stable
|
||||
main) RELEASE_TAG="$(sh packaging/channel-tag.sh stable)"
|
||||
export RELEASE_NOTES="Stable build from ${GITHUB_SHA}" ;;
|
||||
*) export RELEASE_TAG=dev
|
||||
*) RELEASE_TAG="$(sh packaging/channel-tag.sh dev)"
|
||||
# TEMPORARY one-shot bridge (Scribe #2184). Desktop apps installed
|
||||
# before the rename have .../download/dev/latest.json compiled in,
|
||||
# so the manifest is also written to the old `dev` release; its
|
||||
# URLs name dev-rolling assets, so those apps update once into a
|
||||
# build that reads the new tag. Delete this line together with the
|
||||
# old `dev` release and tag.
|
||||
export BRIDGE_TAG=dev
|
||||
export RELEASE_NOTES="Development build from ${GITHUB_SHA}" ;;
|
||||
esac
|
||||
# Assigned bare above so a failing channel-tag.sh fails this step;
|
||||
# `export X="$(...)"` would swallow its exit status.
|
||||
export RELEASE_TAG
|
||||
export PRUNE_OLD_ASSETS=true
|
||||
APP_VERSION="$version" DISPLAY_VERSION="$display" \
|
||||
bash desktop/packaging/write-manifest.sh
|
||||
|
||||
+16
-9
@@ -409,18 +409,25 @@ the lockfile format and the picked versions identical to what CI would have
|
||||
chosen. Commit the result in the same change as the `Cargo.toml` edit — a
|
||||
manifest change pushed without it fails the gate.
|
||||
|
||||
## Pushing: `dev` is both a branch and a tag
|
||||
## Channel releases: `dev-rolling` and `stable`
|
||||
|
||||
`git push origin dev` fails in this repo:
|
||||
The two update channels are releases on fixed tags, because Fabled-Git has no
|
||||
`/releases/latest/download/<asset>` route and the updater needs a permanent URL.
|
||||
The **channel** is still called `dev` everywhere a person sees it; its **release
|
||||
tag** is `dev-rolling`. `packaging/channel-tag.sh` is the one mapping CI reads.
|
||||
`desktop/src-tauri/src/update.rs` and `desktop/packaging/install.sh` carry their
|
||||
own copy because neither can run it — change all three together.
|
||||
|
||||
```
|
||||
error: src refspec dev matches more than one
|
||||
```
|
||||
The tag used to be `dev`, which shadowed the branch of the same name: once a clone
|
||||
had fetched it, `git push origin dev` failed with
|
||||
`error: src refspec dev matches more than one` (Scribe #2184). Never name a channel
|
||||
tag after a branch; `tests/test_channel_tag.py` and the `update.rs` tests fail if
|
||||
one is.
|
||||
|
||||
The rolling update channel is a release on a **fixed tag named `dev`** (the tag
|
||||
never moves — Fabled-Git has no `/releases/latest/download/<asset>` route, so the
|
||||
updater needs a permanent URL). Once that tag is fetched locally, the short name
|
||||
`dev` resolves to both `refs/heads/dev` and `refs/tags/dev`. Fully qualify it:
|
||||
**Transitional, from 2026-09-10:** the old `dev` release still exists so desktop
|
||||
apps installed from it can update across — the manifest job writes `latest.json`
|
||||
to it too (`BRIDGE_TAG=dev` in `desktop.yml`). Until that release and its tag are
|
||||
deleted, fully qualify pushes:
|
||||
|
||||
```
|
||||
git push origin refs/heads/dev:refs/heads/dev
|
||||
|
||||
@@ -93,7 +93,16 @@ say "Finding the latest ThoughtSync build on the $channel channel…"
|
||||
# are pruned to the current build, so the tag alone names the newest build on that
|
||||
# channel — which is exactly what an installer wants and what the in-app updater
|
||||
# already reads.
|
||||
json="$(curl -fsSL "$API/releases/tags/$channel" 2>/dev/null)" ||
|
||||
# The channel's RELEASE TAG, which is not always its name: `dev` publishes on the
|
||||
# tag `dev-rolling`, because a tag called `dev` shadowed the branch of the same name
|
||||
# and broke `git push origin dev` (Scribe #2184). Same mapping as
|
||||
# packaging/channel-tag.sh — inlined because this script is fetched alone through a
|
||||
# pipe and has nothing to source.
|
||||
case "$channel" in
|
||||
dev) tag=dev-rolling ;;
|
||||
*) tag="$channel" ;;
|
||||
esac
|
||||
json="$(curl -fsSL "$API/releases/tags/$tag" 2>/dev/null)" ||
|
||||
die "the $channel channel has nothing published yet."
|
||||
|
||||
# Pull asset URLs straight out of the release JSON (no jq). Anchored on the closing
|
||||
|
||||
@@ -32,8 +32,9 @@ set -euo pipefail
|
||||
: "${GITHUB_REF_NAME:?GITHUB_REF_NAME is required (the tag, e.g. v0.1.0)}"
|
||||
|
||||
# The release to publish to. Defaults to the pushed tag (the versioned, stable
|
||||
# case). M10.9 also calls this with RELEASE_TAG=dev to maintain the rolling
|
||||
# development channel — a release whose tag never moves, because Forgejo has no
|
||||
# case). The channel publish steps call this with the channel's fixed tag —
|
||||
# `stable`, or `dev-rolling` for the dev channel (packaging/channel-tag.sh) — to
|
||||
# maintain a rolling release whose tag never moves, because Forgejo has no
|
||||
# `/releases/latest/download/<asset>` route for an updater to point at.
|
||||
RELEASE_TAG="${RELEASE_TAG:-$GITHUB_REF_NAME}"
|
||||
RELEASE_PRERELEASE="${RELEASE_PRERELEASE:-false}"
|
||||
@@ -123,7 +124,11 @@ first_id() { grep -oE '"id"[[:space:]]*:[[:space:]]*[0-9]+' | head -1 | grep -oE
|
||||
# every green push to dev, `stable` on every merge to main. Each says so, because a
|
||||
# release that prunes its own assets behaves differently from a versioned one and a
|
||||
# reader deserves to know which they are looking at.
|
||||
if [ "$TAG" = "dev" ]; then
|
||||
# Keyed on the TAG, which for the dev channel is `dev-rolling`, not `dev` — a tag
|
||||
# named `dev` shadowed the branch (Scribe #2184). CHANNEL_LABEL is the name a person
|
||||
# reads in the release title, so the rename does not leak into it.
|
||||
if [ "$TAG" = "dev-rolling" ]; then
|
||||
CHANNEL_LABEL='dev (rolling)'
|
||||
INSTALL_TAIL='sh -s -- --channel dev'
|
||||
# Backticks BARE, not `\``. The heredoc below is unquoted, so there the backslash
|
||||
# is the shell's — it suppresses command substitution and never reaches the JSON.
|
||||
@@ -131,18 +136,20 @@ if [ "$TAG" = "dev" ]; then
|
||||
# body as `\``, which is not a legal JSON escape: Forgejo answers 422.
|
||||
CHANNEL_NOTE='\n\nThis is the rolling **dev** channel: republished on every green push to `dev`, and pruned to the current build.'
|
||||
elif [ "$TAG" = "stable" ]; then
|
||||
CHANNEL_LABEL='stable'
|
||||
# install.sh defaults to stable, so no flag.
|
||||
INSTALL_TAIL='sh'
|
||||
CHANNEL_NOTE='\n\nThis is the rolling **stable** channel: republished on every merge to `main`, and pruned to the current build. No tag is required for a build to arrive here.'
|
||||
else
|
||||
CHANNEL_LABEL="$TAG"
|
||||
INSTALL_TAIL='sh'
|
||||
CHANNEL_NOTE=''
|
||||
fi
|
||||
|
||||
echo "==> Creating release for $TAG"
|
||||
BODY=$(cat <<JSON
|
||||
{"tag_name":"$TAG","name":"ThoughtSync $TAG","draft":false,"prerelease":$RELEASE_PRERELEASE,
|
||||
"body":"ThoughtSync $TAG.\n\n**Desktop**\n\n- **Debian / Ubuntu** — native \`.deb\`\n- **Arch / CachyOS** — native \`.pkg.tar.*\`\n- **everything else** — \`.AppImage\` (de-bundled graphics: renders on any GPU/Wayland setup)\n\nInstall / update — picks the right one for your system:\n\`\`\`\ncurl -fsSL $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/raw/branch/dev/desktop/packaging/install.sh | $INSTALL_TAIL\n\`\`\`\n\n**Android** — \`thoughtsync.apk\`. Copy it and \`thoughtsync-android.json\` into your server's \`/var/thoughtsync/client/\` and the server will offer it to your devices; see docs/android-distribution.md.$CHANNEL_NOTE"}
|
||||
{"tag_name":"$TAG","name":"ThoughtSync $CHANNEL_LABEL","draft":false,"prerelease":$RELEASE_PRERELEASE,
|
||||
"body":"ThoughtSync $CHANNEL_LABEL.\n\n**Desktop**\n\n- **Debian / Ubuntu** — native \`.deb\`\n- **Arch / CachyOS** — native \`.pkg.tar.*\`\n- **everything else** — \`.AppImage\` (de-bundled graphics: renders on any GPU/Wayland setup)\n\nInstall / update — picks the right one for your system:\n\`\`\`\ncurl -fsSL $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/raw/branch/dev/desktop/packaging/install.sh | $INSTALL_TAIL\n\`\`\`\n\n**Android** — \`thoughtsync.apk\`. Copy it and \`thoughtsync-android.json\` into your server's \`/var/thoughtsync/client/\` and the server will offer it to your devices; see docs/android-distribution.md.$CHANNEL_NOTE"}
|
||||
JSON
|
||||
)
|
||||
|
||||
@@ -158,7 +165,7 @@ JSON
|
||||
# whether it has a JSON encoder; this script cannot assume python3 is on PATH in
|
||||
# every image that sources it.
|
||||
if [ -n "${RELEASE_BODY_JSON:-}" ]; then
|
||||
BODY="{\"tag_name\":\"$TAG\",\"name\":\"ThoughtSync $TAG\",\"draft\":false,\"prerelease\":$RELEASE_PRERELEASE,\"body\":\"$RELEASE_BODY_JSON\"}"
|
||||
BODY="{\"tag_name\":\"$TAG\",\"name\":\"ThoughtSync $CHANNEL_LABEL\",\"draft\":false,\"prerelease\":$RELEASE_PRERELEASE,\"body\":\"$RELEASE_BODY_JSON\"}"
|
||||
fi
|
||||
# 409 = a release for this tag already exists (re-run) — fall through to lookup.
|
||||
release="$(ALLOW_CODES=409 api POST "$API/releases" -H "Content-Type: application/json" -d "$BODY")"
|
||||
|
||||
@@ -161,6 +161,41 @@ printf '{\n "version_name": "%s",\n "version_code": "%s"\n}\n' \
|
||||
"$DISPLAY_VERSION" "$APP_VERSION" > "$work/thoughtsync-desktop.json"
|
||||
replace_asset "$work/thoughtsync-desktop.json" "thoughtsync-desktop.json"
|
||||
|
||||
# --- TEMPORARY: the one-shot bridge off a retired channel tag ------------------
|
||||
#
|
||||
# The dev channel's tag was renamed `dev` -> `dev-rolling`, because a tag named `dev`
|
||||
# shadowed the branch and broke `git push origin dev` (Scribe #2184). Desktop apps
|
||||
# installed before the rename have `.../download/dev/latest.json` compiled in, so
|
||||
# without this they would never be offered another build.
|
||||
#
|
||||
# BRIDGE_TAG names the old release; the SAME manifest is written there too. Its URLs
|
||||
# are absolute and name `dev-rolling` assets, so an old app updates once into a build
|
||||
# that reads the new tag, and never reads this one again. Only latest.json goes
|
||||
# across: the sidecar is read by CI and the image build, which already follow the
|
||||
# new tag.
|
||||
#
|
||||
# A missing bridge release is logged and skipped, not fatal — by then it has been
|
||||
# deleted on purpose. A failed WRITE to one that exists fails the job: a bridge that
|
||||
# silently stops is an installed base stranded without a word. Remove this block,
|
||||
# the BRIDGE_TAG line in desktop.yml, and the old release and tag together.
|
||||
if [ -n "${BRIDGE_TAG:-}" ]; then
|
||||
bridge_release="$(curl -sS "${AUTH[@]}" "$API/releases/tags/$BRIDGE_TAG" || true)"
|
||||
bridge_id="$(printf '%s' "$bridge_release" | grep -oE '"id"[[:space:]]*:[[:space:]]*[0-9]+' | head -1 | grep -oE '[0-9]+' || true)"
|
||||
if [ -z "$bridge_id" ]; then
|
||||
echo "==> No release tagged $BRIDGE_TAG — nothing to bridge (retired?)."
|
||||
else
|
||||
# A subshell, so replace_asset's globals point at the bridge release for this
|
||||
# one call only and the prune below still works on $RELEASE_TAG.
|
||||
(
|
||||
release_id="$bridge_id"
|
||||
RELEASE_TAG="$BRIDGE_TAG"
|
||||
assets="$(curl -sS "${AUTH[@]}" "$API/releases/$release_id/assets")"
|
||||
replace_asset "$work/latest.json" "latest.json"
|
||||
)
|
||||
echo "==> Bridged: $BRIDGE_TAG/latest.json now advertises the same build."
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "==> Done. $RELEASE_TAG now advertises $DISPLAY_VERSION ($APP_VERSION) for ${#entries[@]} platform(s)."
|
||||
|
||||
# --- prune superseded builds from a rolling channel ---------------------------
|
||||
|
||||
@@ -84,8 +84,21 @@ impl Channel {
|
||||
}
|
||||
}
|
||||
|
||||
/// The release tag holding this channel's builds — not the channel's name.
|
||||
///
|
||||
/// `dev` used to be both, and a tag named `dev` shadowed the branch of the same
|
||||
/// name, so `git push origin dev` failed as ambiguous (Scribe #2184). The channel
|
||||
/// is still `dev` everywhere a person sees it and in the stored pref; only the tag
|
||||
/// moved. Mirrors `packaging/channel-tag.sh`, which is what CI publishes with.
|
||||
fn release_tag(self) -> &'static str {
|
||||
match self {
|
||||
Channel::Stable => "stable",
|
||||
Channel::Dev => "dev-rolling",
|
||||
}
|
||||
}
|
||||
|
||||
fn feed_url(self) -> String {
|
||||
format!("{FEED_BASE}/{}/latest.json", self.as_str())
|
||||
format!("{FEED_BASE}/{}/latest.json", self.release_tag())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,10 +428,23 @@ mod tests {
|
||||
assert_eq!(effective(&db), Channel::Dev);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_channel_feed_is_named_like_a_branch() {
|
||||
// A release tag spelled like a branch makes `git push origin <branch>`
|
||||
// ambiguous once the tag is fetched (Scribe #2184). `dev` and `main` are the
|
||||
// branches this repo pushes.
|
||||
for channel in [Channel::Stable, Channel::Dev] {
|
||||
assert!(
|
||||
!["dev", "main"].contains(&channel.release_tag()),
|
||||
"{channel:?} publishes on a tag named like a branch"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_channel_has_its_own_fixed_feed() {
|
||||
assert!(Channel::Stable.feed_url().ends_with("/stable/latest.json"));
|
||||
assert!(Channel::Dev.feed_url().ends_with("/dev/latest.json"));
|
||||
assert!(Channel::Dev.feed_url().ends_with("/dev-rolling/latest.json"));
|
||||
assert_ne!(Channel::Stable.feed_url(), Channel::Dev.feed_url());
|
||||
}
|
||||
|
||||
|
||||
@@ -55,8 +55,8 @@ real values are already known.
|
||||
so a build dropped there survives container recreation — and survives an image
|
||||
upgrade, which is the point of an override.
|
||||
|
||||
Both files are published to the rolling `dev` release on every green Android
|
||||
build:
|
||||
Both files are published to the rolling dev-channel release (tag `dev-rolling`)
|
||||
on every green Android build:
|
||||
|
||||
```sh
|
||||
REPO=https://git.fabledsword.com/bvandeusen/thoughtsync
|
||||
@@ -64,7 +64,7 @@ TOKEN=... # a Fabled-Git token with read access; the instance is private
|
||||
|
||||
for f in thoughtsync.apk thoughtsync-android.json; do
|
||||
curl -fsSL -H "Authorization: token $TOKEN" \
|
||||
-o "/tmp/$f" "$REPO/releases/download/dev/$f"
|
||||
-o "/tmp/$f" "$REPO/releases/download/dev-rolling/$f"
|
||||
done
|
||||
|
||||
# Copy the sidecar LAST. A sidecar that does not match the APK beside it is not a
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env sh
|
||||
#
|
||||
# The release tag that holds a channel's builds.
|
||||
#
|
||||
# channel-tag.sh <dev|stable>
|
||||
#
|
||||
# A channel and its release tag are two different names, and for `dev` they differ.
|
||||
# The channel is what a person picks and sees (`install.sh --channel dev`, the app's
|
||||
# settings); the tag is where CI publishes and where every reader fetches from.
|
||||
#
|
||||
# `dev` used to be both. A tag named `dev` shadowed the branch named `dev`, so once
|
||||
# a clone had fetched tags, `git push origin dev` failed with "src refspec dev
|
||||
# matches more than one" (Scribe #2184). The tag is `dev-rolling` now, matching
|
||||
# roundtable-android. Never spell a channel tag like a branch — tests/test_channel_tag.py
|
||||
# fails if one is.
|
||||
#
|
||||
# The ONE mapping CI reads. Two copies exist because their readers cannot run this:
|
||||
# `desktop/src-tauri/src/update.rs` (compiled into the app) and
|
||||
# `desktop/packaging/install.sh` (fetched alone through a pipe). Change all three.
|
||||
set -eu
|
||||
|
||||
case "${1:-}" in
|
||||
dev) echo dev-rolling ;;
|
||||
stable) echo stable ;;
|
||||
*)
|
||||
echo "channel-tag.sh: unknown channel '${1:-}' (expected dev or stable)" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
@@ -38,7 +38,10 @@ esac
|
||||
|
||||
SERVER="${GITHUB_SERVER_URL:-https://git.fabledsword.com}"
|
||||
REPO="${GITHUB_REPOSITORY:-bvandeusen/thoughtsync}"
|
||||
BASE="$SERVER/$REPO/releases/download/$channel"
|
||||
# The channel's release tag, not its name — `dev` lives on `dev-rolling`
|
||||
# (packaging/channel-tag.sh, Scribe #2184).
|
||||
tag="$(sh "$(dirname "$0")/channel-tag.sh" "$channel")"
|
||||
BASE="$SERVER/$REPO/releases/download/$tag"
|
||||
|
||||
mkdir -p "$dest"
|
||||
|
||||
|
||||
@@ -96,16 +96,21 @@ fetch() {
|
||||
# merge to `main` (run 4857): exit 1, no output, 0.16 seconds, on the one channel that
|
||||
# had no APK published yet. Its three neighbours hid it until then.
|
||||
published_for() {
|
||||
# The channel's release TAG, not its name — `dev` lives on `dev-rolling`
|
||||
# (packaging/channel-tag.sh, Scribe #2184). `|| exit 2` rather than letting an
|
||||
# empty tag through: a lookup against `.../download//latest.json` would find
|
||||
# nothing and read as "nothing published", which this guard PASSES.
|
||||
_tag="$(sh "$ROOT/packaging/channel-tag.sh" "$2")" || exit 2
|
||||
case "$1" in
|
||||
desktop)
|
||||
# What the UPDATER reads. The manifest is the thing that decides whether a
|
||||
# client is offered a build, so it is the authority on what is published.
|
||||
{ fetch "$SERVER/$REPO/releases/download/$2/latest.json" \
|
||||
{ fetch "$SERVER/$REPO/releases/download/$_tag/latest.json" \
|
||||
| grep -oE '"version"[[:space:]]*:[[:space:]]*"[^"]+"' | head -1 \
|
||||
| sed -E 's/.*"([^"]+)"$/\1/'; } || true
|
||||
;;
|
||||
android)
|
||||
{ fetch "$SERVER/$REPO/releases/download/$2/thoughtsync-android.json" \
|
||||
{ fetch "$SERVER/$REPO/releases/download/$_tag/thoughtsync-android.json" \
|
||||
| grep -oE '"version_code"[[:space:]]*:[[:space:]]*[0-9]+' | head -1 \
|
||||
| grep -oE '[0-9]+$'; } || true
|
||||
;;
|
||||
@@ -117,10 +122,11 @@ published_for() {
|
||||
# compares identity — for Android those are different fields, and conflating them
|
||||
# would make every build look like a change (the code is build-time; it always moves).
|
||||
published_name() {
|
||||
_tag="$(sh "$ROOT/packaging/channel-tag.sh" "$2")" || exit 2
|
||||
case "$1" in
|
||||
desktop) published_for desktop "$2" ;;
|
||||
android)
|
||||
{ fetch "$SERVER/$REPO/releases/download/$2/thoughtsync-android.json" \
|
||||
{ fetch "$SERVER/$REPO/releases/download/$_tag/thoughtsync-android.json" \
|
||||
| grep -oE '"version_name"[[:space:]]*:[[:space:]]*"[^"]+"' | head -1 \
|
||||
| sed -E 's/.*"([^"]+)"$/\1/'; } || true
|
||||
;;
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
"""The channel -> release tag mapping — `packaging/channel-tag.sh`.
|
||||
|
||||
A channel's release tag must never be spelled like a branch. The dev channel's tag
|
||||
was `dev`, which shadowed the `dev` branch: once a clone had fetched tags,
|
||||
`git push origin dev` failed as an ambiguous refspec (Scribe #2184).
|
||||
|
||||
These run the script itself rather than reading it, so a mapping that is correct
|
||||
on the page but broken in execution still fails here.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
SCRIPT = Path(__file__).resolve().parent.parent / "packaging" / "channel-tag.sh"
|
||||
|
||||
# The branches this repo pushes. A tag with either name makes the push ambiguous.
|
||||
BRANCHES = {"dev", "main"}
|
||||
|
||||
|
||||
def tag(channel: str) -> subprocess.CompletedProcess[str]:
|
||||
return subprocess.run(
|
||||
["sh", str(SCRIPT), channel],
|
||||
capture_output=True, text=True,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("channel", ["dev", "stable"])
|
||||
def test_no_channel_tag_is_spelled_like_a_branch(channel: str) -> None:
|
||||
result = tag(channel)
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert result.stdout.strip() not in BRANCHES
|
||||
|
||||
|
||||
def test_the_dev_channel_publishes_on_dev_rolling() -> None:
|
||||
# Pinned exactly, not just "not a branch": update.rs and install.sh carry
|
||||
# their own copy of this value, and a silent rename here would strand them.
|
||||
assert tag("dev").stdout.strip() == "dev-rolling"
|
||||
|
||||
|
||||
def test_stable_keeps_its_tag() -> None:
|
||||
assert tag("stable").stdout.strip() == "stable"
|
||||
|
||||
|
||||
def test_an_unknown_channel_fails_instead_of_naming_a_tag() -> None:
|
||||
# Every caller captures the output as a URL segment. An empty string on
|
||||
# success would build `.../download//latest.json` and read as "nothing
|
||||
# published" — so an unknown channel must exit non-zero and print nothing.
|
||||
result = tag("nightly")
|
||||
assert result.returncode != 0
|
||||
assert result.stdout == ""
|
||||
Reference in New Issue
Block a user