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:
+13
-2
@@ -285,6 +285,9 @@ func runCommandDepth(line string, depth int) bool {
|
||||
case "pushremote": // mirror the repo to a public git server via its API
|
||||
handlePushRemote(strings.Join(fields[1:], " "))
|
||||
|
||||
case "release": // tag a commit and publish it as a release on the mirrors
|
||||
handleRelease(strings.Join(fields[1:], " "))
|
||||
|
||||
case "edit": // interactively edit the last N commits
|
||||
if !requireRepo() {
|
||||
break
|
||||
@@ -516,7 +519,14 @@ func runCommandDepth(line string, depth int) bool {
|
||||
sub := word(words, 1)
|
||||
switch {
|
||||
case sub == "add" && word(words, 2) != "":
|
||||
if gitOK(DIR, "tag", "-a", words[2], "-m", words[2]) {
|
||||
// `tag add v1.0 why this exists` annotates with that text; without
|
||||
// one the tag name is the message, as before. The annotation is
|
||||
// what `release` falls back to for its notes.
|
||||
msg := words[2]
|
||||
if len(fields) > 3 {
|
||||
msg = strings.Join(fields[3:], " ")
|
||||
}
|
||||
if gitOK(DIR, "tag", "-a", words[2], "-m", msg) {
|
||||
gitOK(DIR, "push", "origin", words[2])
|
||||
}
|
||||
case sub == "checkout" && word(words, 2) != "":
|
||||
@@ -697,6 +707,7 @@ var helpItems = []struct{ cmd, desc string }{
|
||||
{"init", "make new repository from current directory"},
|
||||
{"push [comment]", "push changes to git server"},
|
||||
{"pushremote [@name] [desc]", "mirror repo to the public server(s) (gitea/github/gitlab)"},
|
||||
{"release <tag> [notes]", "tag and publish a release on the public server(s)"},
|
||||
{"pull", "pull changes from git server"},
|
||||
{"fetch", "fetch changes from git server"},
|
||||
{"status [-a]", "short git status (-a: overview of all projects)"},
|
||||
@@ -714,7 +725,7 @@ var helpItems = []struct{ cmd, desc string }{
|
||||
{"login", "connect to git server"},
|
||||
{"count", "count lines in project"},
|
||||
{"tag", "show tags"},
|
||||
{"tag add <tag>", "add tag"},
|
||||
{"tag add <tag> [msg]", "add tag (msg becomes the annotation)"},
|
||||
{"tag checkout <tag>", "checkout tag"},
|
||||
{"tag delete <tag>", "delete tag"},
|
||||
{"alias [name [cmd]]", "list, show or define an alias ($1..$N, $* args)"},
|
||||
|
||||
Reference in New Issue
Block a user