[mike@mwxm4]

This commit is contained in:
2026-08-11 11:40:32 +02:00
parent 113527f220
commit 39f5b48d94
14 changed files with 719 additions and 39 deletions
+76 -7
View File
@@ -9,6 +9,7 @@ directory. Go port of the original Perl `mgsh` (`mgsh.perl`).
- [Build](#build) · [Usage](#usage) · [Commands](#commands) · [Aliases](#aliases)
- [Overview](#overview) · [Credential check](#credential-check)
- [Public mirror (`pushremote`)](#public-mirror-pushremote) ·
[Deleting a mirror (`deleteremote`)](#deleting-a-mirror-deleteremote) ·
[Releases](#releases)
- [Configuration](#configuration) · [Settings reference](#settings-reference) ·
[Per-project configuration](#per-project-configuration)
@@ -62,9 +63,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`, `release`, `list`, `tag`, `archive`, `show`,
`pull`, `fetch`, `status`, `diff`, `overview`, `config`, `count`, `login` and
`cloneall`; every other command is interactive-only.
`push`, `pushremote`, `deleteremote`, `release`, `list`, `tag`, `archive`,
`show`, `pull`, `fetch`, `status`, `diff`, `overview`, `config`, `count`,
`login` and `cloneall`; every other command is interactive-only.
The interactive prompt is colored (Catppuccin-flavored) and shows the active
project, its git branch and a `*` dirty marker:
@@ -130,6 +131,7 @@ Run `help` for the full list. Highlights:
| `cd [project]` | change project (no argument: back to the base) |
| `push [comment]` | commit everything and push to the server |
| `pushremote [desc]` | mirror the repo to a public server (gitea/github/gitlab) |
| `deleteremote <@name\|host>` | delete the repo on that public server (asks first) |
| `pull` / `fetch` | pull / fetch from the server |
| `status [-a]` / `diff` | short git status (`-a`: overview of all projects) |
| `overview` | inventory of all projects, local and on the server |
@@ -321,6 +323,7 @@ remote.gitlab.url = https://gitlab.example.com
remote.gitlab.key = <personal-access-token>
remote.gitlab.type = gitlab
remote.gitlab.visibility = public
remote.gitlab.active = false # only used when named (default true)
# remotes = gitea, gitlab # optional: restrict and order the set
# mirror = true # `push` also mirrors via pushremote
@@ -333,10 +336,29 @@ name those versions used.
| command | pushes to |
|------------------------|-----------------------------------------------|
| `pushremote` | every configured target, in order |
| `pushremote @gitea` | only `gitea` |
| `pushremote` | every **active** target, in order |
| `pushremote @gitea` | only `gitea`, active or not |
| `pushremote @gitea @gitlab` | those two |
| `pushremote a fix` | every target, description "a fix" |
| `pushremote a fix` | every active target, description "a fix" |
#### Active and inactive targets
`remote.<name>.active = false` takes a target out of the default set: it is
skipped by a bare `pushremote`, by `release` without a `@name`, and by the
automatic mirror of `mirror = true`. Naming it uses it exactly as before —
`pushremote @gitea`, `release @gitea v1.2`, or by host, `pushremote @git.example.com`.
That is the difference to the alternatives: removing the block loses the token,
and leaving the target in `remotes = …` keeps it on the automatic path. An
inactive target stays configured, keeps its credentials, is listed by `config`
(a shade dimmer, marked `inactive`) and completes with Tab — it just no longer
goes along by default. Useful for a server that is behind a VPN, rate-limited,
or only occasionally interesting.
Unset means active, so a configuration written before this flag existed keeps
mirroring where it always did. When *every* configured target is inactive, a
bare `pushremote` says so and names one instead of claiming nothing is
configured.
Each target owns a git remote of the same name in the repository, so
`git push gitlab` keeps working outside mgsh. A
@@ -358,6 +380,52 @@ existing file is readable by others. The provider is auto-detected from the url
with `remote.<name>.type`. Set `mirror = true` to have every `push` mirror
automatically.
### Deleting a mirror (`deleteremote`)
`deleteremote` is the counterpart: it deletes the active project's repository on
a public server, through the same API and with the same token.
```
deleteremote <@name|host> [...]
```
```
< src/myproject > deleteremote git.example.com
remote gitea https://git.example.com (as mike)
this deletes https://git.example.com/mike/myproject.git with its issues,
releases and history, and cannot be undone
delete mike/myproject on gitea? y/N ? y
deleted mike/myproject on gitea
removed git remote gitea
```
A target can be named either way: `@gitea` (its configured name) or
`git.example.com` (the host of its url) select the same server, and the `@` is
optional here. Both spellings work for `pushremote` and `release` too.
`active = false` changes nothing for this command — it always needs the server
named anyway.
The guard rails, because nothing on the other end can be undone from here:
- **the server must be named.** Unlike `pushremote`, a bare `deleteremote` does
*not* mean "every configured target" — it lists them and stops.
- the repository is looked up first, so a typo is reported as "nothing to
delete" instead of becoming a request nobody meant to send.
- each deletion is confirmed on its own, naming owner/repo and server, and the
default answer is **no**.
- the **local clone is never touched**. Only the git remote of that name is
dropped, and only while it still points at the repository that was deleted.
Only the mirror is affected — the repository on the internal ssh git server, and
everything `list`/`archive` deal with, stays where it is.
Deletion needs more from the token than pushing does: the `delete_repo` scope on
GitHub, `write:repository` on Gitea, and the `api` scope plus the Owner role on
GitLab. When it is missing the server answers 403, and mgsh names the scope
rather than passing the bare status on. GitLab may only *schedule* the deletion
(HTTP 202): depending on the plan the project stays visible until its retention
period is over.
### Releases
`release` turns a commit into a published release on the mirror servers, in one
@@ -373,7 +441,7 @@ release [@name ...] <tag> [notes]
remote hub released https://github.com/mike/mgsh.git
```
Without `@name` it releases to every configured mirror target, exactly like
Without `@name` it releases to every active mirror target, exactly like
`pushremote`. Everything after the tag becomes the release notes *and* the tag's
annotation.
@@ -491,6 +559,7 @@ setting.
| `remote.<name>.key` | project | API token for that target |
| `remote.<name>.type` | project | `gitea`\|`github`\|`gitlab`; auto-detected from the url when unset |
| `remote.<name>.visibility` | project | `private` (default) or `public` for repositories `pushremote` creates |
| `remote.<name>.active` | project | `false` keeps the target out of the default set; it is then only used when named (`pushremote @<name>`). Default `true` |
| `remotes` | project | comma- or space-separated list restricting and ordering the mirror targets |
| `mirror` | project | truthy (`1`/`true`/`yes`/`on`) → every `push` also mirrors |
| `secretscan` | project | `off` disables the credential check `push` runs before committing (on by default; only an explicit `off` disables it) |