Add release: publish tagged releases on the mirror servers

`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>
This commit is contained in:
2026-07-26 15:47:05 +02:00
co-authored by Claude Opus 5
parent fa44a4056a
commit 915ef1783a
9 changed files with 716 additions and 25 deletions
+47 -5
View File
@@ -7,7 +7,8 @@ directory. Go port of the original Perl `mgsh` (`mgsh.perl`).
## Contents
- [Build](#build) · [Usage](#usage) · [Commands](#commands) · [Aliases](#aliases)
- [Public mirror (`pushremote`)](#public-mirror-pushremote)
- [Public mirror (`pushremote`)](#public-mirror-pushremote) ·
[Releases](#releases)
- [Configuration](#configuration) · [Settings reference](#settings-reference) ·
[Per-project configuration](#per-project-configuration)
- [Git server layout](#git-server-layout)
@@ -38,9 +39,9 @@ stand in. Outside `base` no project is selected. `mgsh <project>` starts the
interactive shell with that project preselected.
The commands available directly from the shell are `clone`, `init`, `log`,
`push`, `pushremote`, `list`, `tag`, `archive`, `show`, `open`, `pull`, `fetch`,
`status`, `diff`, `overview`, `config`, `count`, `login` and `cloneall`; every
other command is interactive-only.
`push`, `pushremote`, `release`, `list`, `tag`, `archive`, `show`, `open`,
`pull`, `fetch`, `status`, `diff`, `overview`, `config`, `count`, `login` and
`cloneall`; every other command is interactive-only.
The interactive prompt is colored (Catppuccin Mocha) and shows the active
project, its git branch and a `*` dirty marker:
@@ -87,7 +88,8 @@ Run `help` for the full list. Highlights:
| `show <repo>` | show a repository log directly on the server |
| `archive [comment]` | snapshot the server-side repo into `./archive` |
| `init` | make a new repository from the current directory |
| `tag [add/checkout/delete]` | manage tags |
| `tag [add/checkout/delete]` | manage tags (`tag add v1.0 "why"` annotates) |
| `release [@name] <tag> [notes]` | tag and publish a release on the mirrors |
| `alias [name [cmd]]` | list, show or define a command alias |
| `unalias <name>` | remove a command alias |
| `config [-k]` | show the effective configuration and its sources |
@@ -200,6 +202,46 @@ existing file is readable by others. The provider is auto-detected from `remoteu
GitHub, `gitlab*` → GitLab, otherwise Gitea) and can be forced with
`remotetype`. Set `mirror = true` to have every `push` mirror automatically.
### Releases
`release` turns a commit into a published release on the mirror servers, in one
step — creating the tag, getting it onto the internal server, and then onto each
mirror as a release object:
```
release [@name ...] <tag> [notes]
```
```
< src/mgsh > release v4.1.0 first public build
remote hub released https://github.com/mike/mgsh.git
```
Without `@name` it releases to every configured mirror target, exactly like
`pushremote`. Everything after the tag becomes the release notes *and* the tag's
annotation.
**Notes are generated when you do not write any**: the tag's own annotation if
it has a real one, otherwise the commit subjects since the previous tag
(`- <subject>` per line, at most 50). So a plain `release v4.1.0` already
produces a usable changelog.
A tag ending in `-rc`, `-alpha`, `-beta` or `-pre` (optionally with digits) is
marked as a **pre-release** on Gitea and GitHub; GitLab has no such flag.
Releasing the same tag twice updates the existing release rather than failing.
But a tag that already exists **on a different commit** stops the command —
moving a published tag is how one version quietly comes to mean different things
on different servers. `release` also refuses when the repository is not on the
mirror yet and tells you to run `pushremote` first, rather than creating it as a
side effect.
**No binary assets.** The three providers handle uploads in three incompatible
ways — Gitea attaches them to the release, GitHub uses a separate upload host,
and GitLab does not host them at all but expects a link into its package
registry. mgsh publishes source releases with notes; if you need binaries,
upload them with the provider's own tooling.
## Configuration
mgsh has **no built-in defaults**. Settings are resolved in three steps, each