`release` now uploads every file in the project's ./bin and ./assets when
those directories exist. Nothing to configure, and nothing happens for a
project that has neither.
This is the part deliberately left out when `release` was written,
because it is where the three providers stop resembling each other:
Gitea multipart POST to .../releases/<id>/assets?name=<name>
GitHub raw POST to the separate upload host the release object names
in upload_url, whose RFC 6570 template suffix has to go first
GitLab a release stores links, not files: the file goes into the
project's generic package registry and the release gets a
package link pointing at it
So findRelease and createRelease now return a releaseRef carrying the id
and, for GitHub, that upload host -- the id alone cannot address an
upload. Uploads stream from disk rather than buffering: these are whole
binaries, and the Gitea multipart body is assembled through a pipe.
Only regular files directly in those directories are taken. Symlinks are
skipped, which matters here: build.sh leaves bin/mgsh pointing at one of
its siblings, and uploading the same 9M twice under two names helps
nobody. A name present in both directories is used from bin and reported
for assets. Re-releasing a tag replaces same-named assets rather than
failing on them, since rebuilding and publishing again is the normal
reason to do it, and a file that fails does not stop the rest.
Each provider's request shape is pinned down against the recording
stand-in, and the whole chain was run once end to end -- real repository,
real binaries, a fake Gitea that also serves git-http-backend so the tag
push is real too.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`release [@name ...] <tag> [notes]` does the whole chain in one step —
create the annotated tag, push it to the internal server, then push it to
each selected mirror and turn it into a release object there. Target
selection reuses pushremote's @name mechanism, so the two behave alike.
Notes are generated when none are given: the tag's own annotation when it
carries more than the default, otherwise the commit subjects since the
previous tag, capped at 50 lines. `tag add v1.0 "why this exists"` now
takes a message, which is what that fallback reads; before, the
annotation was always just the tag name.
Tags ending in -rc/-alpha/-beta/-pre are marked as pre-releases on Gitea
and GitHub. Releasing the same tag twice updates the existing release;
a tag that already points at a different commit stops the command, since
moving a published tag makes one version mean different things per
server. A repository that is not on the mirror yet is reported instead of
being created as a side effect.
Binary assets are deliberately out of scope: Gitea attaches them to the
release, GitHub uses a separate upload host, and GitLab does not host
them at all but wants a link into its package registry.
The providers differ in path shape and field names -- GitLab addresses
projects by URL-encoded path, calls the notes "description" and has no
pre-release flag -- so this comes with a recording httptest stand-in that
asserts the exact requests for all three. That harness also covers
authUser, repoExists and the auth header forms, which had no test at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>