Add the git primitives a release pipeline checks against - #39
Merged
Conversation
This was referenced Sep 11, 2026
Closed
Adopt releasetools/cli helpers, which fixes two bugs in the copied scripts
MihaiBojin/jazzy-fish#114
Closed
Closed
MihaiBojin
added this pull request to stack #41
September 11, 2026 00:48
git::remote resolves the remote from checkout.defaultRemote, the current branch's remote, or a sole remote, and refuses when several exist and nothing says which. git::latest_version now asks it instead of reading remote.origin.url, so a fork stops resolving releases against the fork. git::tags_at_head lists the release tags on HEAD, highest first. git::assert_tag_version refuses unless one of them names the version passed in. The version is an argument rather than something read from a manifest, so the library needs no TOML, JSON or YAML reader for callers who never release a Python, Node or Helm project. git::assert_tag_free refuses unless the remote definitely lacks the tag. It is an assertion rather than a predicate because '|| release' fires on every non-zero status, which turns a network failure into "the tag is free". git::release reuses a tag that already points at HEAD instead of failing. A run whose push failed used to leave a tag that only --force could clear, and that same flag is handed to the push, so the retry either aborted or moved a published tag. A tag pointing at another commit is still refused. install.sh links rt alongside releasetools, which until now existed only for Homebrew users. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lp5si1kwiFK1w52a6usA33
release.yaml and test-release.yaml trigger on 'v0.*' tags, so a pull request reached the merge button with no check having read a line of bash. tests.yaml runs 'make all' and 'make test' on every pull request and every push to main: shellcheck over src/, scripts/ and the built dist/, then sourcing the distributable and running base::check_deps. Building the dist also exercises generate-dist.sh and generate-install.sh, which nothing outside a release run otherwise does. shellcheck 0.9.0 ships with the ubuntu-latest runner image, so there is nothing to install and no third-party action in the path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lp5si1kwiFK1w52a6usA33
MihaiBojin
force-pushed
the
git-primitives
branch
from
September 11, 2026 00:53
af92e0c to
5d0c114
Compare
git::release pushes to the remote git::remote resolves. It still said 'origin' in both pushes, so on a checkout whose remote is named anything else it created the tag locally and then died, leaving exactly the half-done state the reuse path exists to clean up after. Reusing an existing tag refuses when --sign was requested and that tag carries no signature. Reuse kept whatever the first attempt created, so a retry with --sign pushed an unsigned tag and said only that it was reusing one. install.sh leaves a binary name it did not create. 'ln -sf' is unconditional and RELEASETOOLS_BINARY_DIR defaults to ~/.local/bin, which every installer writes into; 'rt' in particular is already shipped by RBTools and Request Tracker. A link this project made points at releasetools.bash, so an upgrade still replaces its own. git::remote's comment and the README claimed a fork stops resolving releases against itself. It does not: a fork's origin and its tracking branch both name the fork, and both of the first two steps answer with it. checkout.defaultRemote is the one that can be told otherwise, and the docs now say so. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lp5si1kwiFK1w52a6usA33
The branches filter matches a pull request's base, so one stacked on another skipped the workflow entirely: #42 opened against gh-release-helpers with no check reported at all, while #39 and #40 ran. A stack is the normal shape for anything larger than one change here, so the filter costs more than it saves. Pushes are still filtered to main, where the filter does what it says. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lp5si1kwiFK1w52a6usA33
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Four assertions and two accessors that a release pipeline runs before it publishes
anything, so the checks live here once rather than as a copy per repository. Three
byte-identical copies of
check-tag-version.bashexist acrosstemplate-python-package,jazzy-fishandMihaiBojin/worktrees#2today, and the helper under them has alreadydrifted: jazzy-fish's
get_tags_at_headlost the'v*'filter, so a chart tag on the samecommit outranks the release tag.
The remote is resolved, not assumed
git::remoteanswers fromcheckout.defaultRemote, then the current branch's remote, thenthe sole remote, and refuses when several exist and nothing says which, naming the fix:
originis a convention rather than an answer. On a fork it names the fork, whose tags arenot the project's releases.
remote.pushDefaultstays unread because it says where commitsgo rather than where they come from, and those differ in exactly that case.
git::latest_versionasksgit::remoteinstead of readingremote.origin.url. CIcheckouts have one remote named
origin, so the answer there is unchanged.The version is passed in
Reading
pyproject.tomlhere would mean a TOML parser, andpython::project_namewasdeleted in #35 for exactly that cost: 9 recursive Homebrew formulae and 126MB for one
function nobody called. Every ecosystem already has a one-liner, so the caller runs its own
and this stays dependency-free.
Accepts
1.2.3andv1.2.3. A commit can carry several tags, so one match is enough.Uncertainty is a refusal
An assertion rather than a predicate, because
git::tag_exists X || releasefires on everynon-zero status, so no network, no permission and no remote all read as "the tag is free".
Only git's exit 2, its "no such ref", means carry on. That is the bug jazzy-fish's
tag-release.bashcarries today, where2>/dev/nullhides the difference.The retry that used to need --force
git::releasereuses a tag that already points at HEAD:git tag -afails on an existing tag even when it names the same commit, and the only flagthat cleared it was
--force, which is also handed to the push. So the retry was eitherblocked or it moved a published tag.
--forcekeeps meaning the remote.rt on PATH
install.shlinksrtalongsidereleasetools. Until nowrtcame only from theHomebrew formula, so
uses: releasetools/cli@v0followed byrt ...failed while the samecommand worked on a brew machine.
Not here
The
github::,changelog::,net::andrelease::commands are stacked on top of thisbranch, along with removing
github::get_versionand declaringghingithub::_internal_check_deps. Nothing in this branch callsgh.#38 brings
git::version_tagandgit::latest_versioninto line with the prefix rule thenew commands follow. Doing it here would mix a breaking rename into a branch that only
adds.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Lp5si1kwiFK1w52a6usA33