Document the settings, the server layout and the new commands
The README had no complete list of settings, so gitkey being inert and `remotes` being undocumented were invisible. It now carries a reference table of every setting with its meaning and whether a project .mgshrc may override it, kept honest by a test that checks each listed key really has an MGSH_* override. It also documents what mgsh expects of the git server, which was assumed but never written down: the login directory of gituser *is* gitpath — every remote command runs there without a cd — and archive/ has to exist before `archive` and `clone -a` can work. Plus the per-project config, the multi-target pushremote forms, `config`, and how the project is derived from the working directory in command-line mode. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,8 +2,15 @@
|
||||
|
||||
A small interactive shell / command-line wrapper around a self-hosted bare git
|
||||
server reachable over ssh. It manages a flat set of projects living under a base
|
||||
directory (default `$HOME/src`, or `/db/src` on Linux). Go port of the original
|
||||
Perl `mgsh` (`mgsh.perl`).
|
||||
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)
|
||||
- [Configuration](#configuration) · [Settings reference](#settings-reference) ·
|
||||
[Per-project configuration](#per-project-configuration)
|
||||
- [Git server layout](#git-server-layout)
|
||||
|
||||
## Build
|
||||
|
||||
@@ -24,6 +31,17 @@ Run the tests with `go test ./...`.
|
||||
Launch `mgsh` for the interactive shell, or run a single command directly from a
|
||||
project directory, e.g. `mgsh push "message"`, `mgsh status`, `mgsh log`.
|
||||
|
||||
In command-line mode the project is taken from the working directory: anywhere
|
||||
below `<base>/myproject` — however deep — the active project is `myproject`, and
|
||||
the command applies to the whole project, not the subdirectory you happen to
|
||||
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.
|
||||
|
||||
The interactive prompt is colored (Catppuccin Mocha) and shows the active
|
||||
project, its git branch and a `*` dirty marker:
|
||||
|
||||
@@ -33,8 +51,11 @@ project, its git branch and a `*` dirty marker:
|
||||
|
||||
Features: command history (`~/.mgsh_history`), Tab completion (commands, local
|
||||
projects for `cd`/`open`, server repos for `clone`/`show`, branches/tags for
|
||||
`checkout`/`tag`, filesystem paths for `dist`), and colored `list`/`log`/error
|
||||
output.
|
||||
`checkout`/`tag`, mirror targets for `pushremote`, filesystem paths for `dist`),
|
||||
and colored `list`/`log`/error output.
|
||||
|
||||
The server repository list is fetched once per session on the first Tab that
|
||||
needs it; `rescan` refreshes it (and reloads the configuration).
|
||||
|
||||
Exit with `quit`, `exit`, `Ctrl-D`, or `Ctrl-C` on an empty line.
|
||||
|
||||
@@ -53,7 +74,7 @@ Run `help` for the full list. Highlights:
|
||||
|
||||
| command | description |
|
||||
|---------------------------|------------------------------------------------|
|
||||
| `cd [project]` | change project |
|
||||
| `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) |
|
||||
| `pull` / `fetch` | pull / fetch from the server |
|
||||
@@ -69,9 +90,39 @@ Run `help` for the full list. Highlights:
|
||||
| `tag [add/checkout/delete]` | manage tags |
|
||||
| `alias [name [cmd]]` | list, show or define a command alias |
|
||||
| `unalias <name>` | remove a command alias |
|
||||
| `rescan` | refresh the cached server repository list |
|
||||
| `config [-k]` | show the effective configuration and its sources |
|
||||
| `rescan` | reload the config, refresh the cached repo list |
|
||||
| `!<command>` | run `<command>` in the shell |
|
||||
|
||||
Commands that touch a repository (`push`, `pull`, `log`, `diff`, `tag`, `dist`,
|
||||
`checkout`, `edit`, …) require an active project with a `.git`; they refuse to
|
||||
run rather than silently operating on the base directory. `checkout` and `diff`
|
||||
forward their arguments to git unchanged, so `checkout -b topic` works.
|
||||
|
||||
`config` is the quickest way to see what mgsh actually resolved — which server,
|
||||
which mirror targets, and which of them came from the project's own `.mgshrc`:
|
||||
|
||||
```
|
||||
< src/myproject > config
|
||||
global /Users/me/.mgshrc
|
||||
project /Users/me/src/myproject/.mgshrc
|
||||
|
||||
base /Users/me/src
|
||||
githost git.example.com
|
||||
gitport 22
|
||||
gituser git
|
||||
gitpath /home/git
|
||||
editor code (.mgshrc)
|
||||
remotes hub (.mgshrc)
|
||||
clone url ssh://git@git.example.com:22/home/git
|
||||
|
||||
pushremote targets (in push order):
|
||||
@hub https://github.com github (detected), public, key gh***************xk
|
||||
```
|
||||
|
||||
Tokens are masked, so the output is safe to paste into a bug report.
|
||||
`config -k` prints just the setting names, one per line.
|
||||
|
||||
### Aliases
|
||||
|
||||
`alias <name> '<command>'` defines a reusable shortcut, persisted to
|
||||
@@ -99,8 +150,8 @@ alias ec '!echo $1' # ec hello -> echo hello (shell)
|
||||
### Public mirror (`pushremote`)
|
||||
|
||||
Besides the internal ssh git server, `pushremote` mirrors the active project to
|
||||
a public hosting server (Gitea, GitHub or GitLab) over its REST API. It reads
|
||||
two settings from `~/.mgshrc`:
|
||||
one or more public hosting servers (Gitea, GitHub or GitLab) over their REST
|
||||
API. A single server is configured flat:
|
||||
|
||||
```ini
|
||||
remoteurl = https://git.example.com # base URL of the server
|
||||
@@ -110,26 +161,64 @@ remotekey = <personal-access-token> # API token
|
||||
# mirror = true # `push` also mirrors via pushremote
|
||||
```
|
||||
|
||||
Several servers get one `remote.<name>.*` block each:
|
||||
|
||||
```ini
|
||||
remote.gitea.url = https://git.example.com
|
||||
remote.gitea.key = <personal-access-token>
|
||||
|
||||
remote.hub.url = https://github.com
|
||||
remote.hub.key = <personal-access-token>
|
||||
remote.hub.visibility = public
|
||||
|
||||
# remotes = gitea, hub # optional: restrict and order the set
|
||||
```
|
||||
|
||||
| command | pushes to |
|
||||
|------------------------|-----------------------------------------------|
|
||||
| `pushremote` | every configured target, in order |
|
||||
| `pushremote @hub` | only `hub` |
|
||||
| `pushremote @hub @gitea` | those two |
|
||||
| `pushremote a fix` | every target, description "a fix" |
|
||||
|
||||
Each target owns a git remote of the same name in the repository (the flat form
|
||||
uses `public`, as before), so `git push hub` keeps working outside mgsh. A
|
||||
target that fails does not stop the others; with more than one target
|
||||
`pushremote` prints an `n/m remotes updated` summary. `remotes = …` restricts
|
||||
and orders the set, which is mostly useful in a project `.mgshrc` — see below.
|
||||
|
||||
`pushremote` authenticates with the token, creates the repository (named after
|
||||
the current project) if it does not exist yet, adds a credential-free remote
|
||||
named `public`, and pushes all branches and tags. New repositories are
|
||||
**private** unless `remotevisibility = public`; any words after the command
|
||||
(`pushremote <description>`) are set as the repository description on creation.
|
||||
The token is sent as a one-shot HTTP auth header, never written into the repo's
|
||||
git config. The provider is auto-detected from `remoteurl` (`github.com` →
|
||||
the current project) if it does not exist yet, adds the credential-free remote,
|
||||
and pushes all branches and tags. New repositories are **private** unless
|
||||
`visibility = public`; any words after the command (and after any `@name`) are
|
||||
set as the repository description on creation.
|
||||
The token is sent as a one-shot HTTP auth header: it is never written into the
|
||||
repo's git config, and it reaches git through the environment rather than the
|
||||
command line, so it does not show up in the process table. Because `~/.mgshrc`
|
||||
then holds a credential, mgsh creates it mode `600` and warns at startup if an
|
||||
existing file is readable by others. The provider is auto-detected from `remoteurl` (`github.com` →
|
||||
GitHub, `gitlab*` → GitLab, otherwise Gitea) and can be forced with
|
||||
`remotetype`. Set `mirror = true` to have every `push` mirror automatically.
|
||||
|
||||
## Configuration
|
||||
|
||||
mgsh has **no built-in defaults**. Configuration comes entirely from `~/.mgshrc`
|
||||
(overlaid with `MGSH_*` environment variables). On first run mgsh writes a blank,
|
||||
annotated `~/.mgshrc` template (migrating any aliases from a pre-4.x
|
||||
`~/.mgsh_aliases`) and then exits with an error until the required settings —
|
||||
`base`, `githost`, `gitport`, `gituser`, `gitpath` — are filled in.
|
||||
mgsh has **no built-in defaults**. Settings are resolved in three steps, each
|
||||
overriding the one before:
|
||||
|
||||
It uses simple `key = value` (or `key: value`) lines (`#` comments allowed);
|
||||
alias definitions live in the same file:
|
||||
```
|
||||
~/.mgshrc -> <project>/.mgshrc -> MGSH_* environment variables
|
||||
```
|
||||
|
||||
On first run mgsh writes a blank, annotated `~/.mgshrc` template (migrating any
|
||||
aliases from a pre-4.x `~/.mgsh_aliases`) and then exits with an error until the
|
||||
required settings — `base`, `githost`, `gitport`, `gituser`, `gitpath` — are
|
||||
filled in. The global file must be complete on its own: mgsh has to work outside
|
||||
any project, so a project file can only refine it, never complete it.
|
||||
|
||||
It uses simple `key = value` (or `key: value`) lines; a `#` preceded by
|
||||
whitespace starts a comment, so a value may still contain a literal `#` (quote
|
||||
the value to keep a trailing comment-like part). Alias definitions live in the
|
||||
same file:
|
||||
|
||||
```ini
|
||||
# --- required ---
|
||||
@@ -148,9 +237,97 @@ editor = code # fallback opener for `open`
|
||||
alias co 'checkout $1'
|
||||
```
|
||||
|
||||
Environment overrides: `MGSH_BASE`, `MGSH_GITHOST`, `MGSH_GITPORT`,
|
||||
`MGSH_GITUSER`, `MGSH_GITPATH`, `MGSH_GITKEY`, `MGSH_GITNAME`, `MGSH_GITEMAIL`,
|
||||
`MGSH_PUSHDEFAULT`, `MGSH_EDITOR`, `MGSH_REMOTEURL`, `MGSH_REMOTEKEY`,
|
||||
`MGSH_REMOTETYPE`, `MGSH_REMOTEVISIBILITY`, `MGSH_MIRROR`.
|
||||
`config` shows the resolved result at any time.
|
||||
|
||||
### Settings reference
|
||||
|
||||
Every setting can also be given as an environment variable named `MGSH_<KEY>`
|
||||
(e.g. `MGSH_GITHOST`), which wins over both files. "Scope" says whether a
|
||||
project `.mgshrc` may override the setting.
|
||||
|
||||
| setting | scope | meaning |
|
||||
|---|---|---|
|
||||
| `base` | global | directory holding the projects — **required** |
|
||||
| `githost` | project | git server host — **required** |
|
||||
| `gitport` | project | ssh port — **required** |
|
||||
| `gituser` | project | ssh user on the git server — **required** |
|
||||
| `gitpath` | project | path of the bare repositories on the server — **required** |
|
||||
| `gitkey` | project | ssh identity for the git server: a bare name is looked up in `~/.ssh`, a path is used as given. Applies to mgsh's own ssh calls and to git's ssh transport. Unset = normal ssh/`~/.ssh/config` behaviour |
|
||||
| `gitname` | global | `user.name` written to the **global** git config at startup |
|
||||
| `gitemail` | global | `user.email` written to the global git config |
|
||||
| `pushdefault` | global | `push.default` written to the global git config |
|
||||
| `editor` | project | opener used by `open`/`view` when the project has no Xcode workspace (default `coda`) |
|
||||
| `remoteurl` | project | base URL of a single mirror server (target name `public`) |
|
||||
| `remotekey` | project | API token for `remoteurl` |
|
||||
| `remotetype` | project | `gitea`\|`github`\|`gitlab`; auto-detected from the URL when unset |
|
||||
| `remotevisibility` | project | `private` (default) or `public` for repositories created by `pushremote` |
|
||||
| `remote.<name>.url` | project | base URL of the named mirror target |
|
||||
| `remote.<name>.key` | project | API token for that target |
|
||||
| `remote.<name>.type` | project | provider override for that target |
|
||||
| `remote.<name>.visibility` | project | visibility for that target |
|
||||
| `remotes` | project | comma- or space-separated list restricting and ordering the mirror targets |
|
||||
| `mirror` | project | truthy (`1`/`true`/`yes`/`on`) → every `push` also mirrors |
|
||||
|
||||
The three settings written to the global git config are applied at startup, and
|
||||
only when they actually differ, so a plain `mgsh status` does not rewrite
|
||||
`~/.gitconfig`.
|
||||
|
||||
### Per-project configuration
|
||||
|
||||
A project may carry its own `.mgshrc`, which overrides the global settings while
|
||||
that project is active — a project on a different git server, with a different
|
||||
editor, or mirrored to a different place:
|
||||
|
||||
```ini
|
||||
# ~/src/myproject/.mgshrc
|
||||
githost = other.example.com
|
||||
remotes = hub # mirror this project to github only
|
||||
mirror = true
|
||||
```
|
||||
|
||||
It is read when you `cd` into the project and applies to every command that
|
||||
follows. `base`, `gitname`, `gitemail` and `pushdefault` stay global — `base`
|
||||
defines where projects live, and the identity keys are written to your *global*
|
||||
git config, which should not change as you move between projects. mgsh says so
|
||||
when a project file sets one of them. Aliases also remain global; `alias` always
|
||||
writes to `~/.mgshrc`.
|
||||
|
||||
`rescan` re-reads both files, so an edit takes effect without restarting. A
|
||||
reload that would leave the global config incomplete is refused, and a changed
|
||||
`base` needs a restart — mgsh says so rather than half-applying it.
|
||||
|
||||
A project `.mgshrc` is a normal file in the working tree, and `push` commits
|
||||
everything: **keep API tokens out of it** unless it is git-ignored. mgsh warns
|
||||
when a project config holds a token that git is not ignoring — including before
|
||||
`init`, since that first commit would publish it too.
|
||||
|
||||
See `mgshrc.example` for an annotated template.
|
||||
|
||||
## Git server layout
|
||||
|
||||
mgsh expects nothing more than a user with ssh access whose **login directory is
|
||||
`gitpath`** — every remote command runs there without a `cd`. Bare repositories
|
||||
live directly in it, snapshots in an `archive/` subdirectory:
|
||||
|
||||
```
|
||||
/home/git/ <- gitpath, and the login directory of gituser
|
||||
├── myproject.git <- created by `init`, cloned by `clone`
|
||||
├── notes.git
|
||||
└── archive/ <- created by hand; needed by `archive` and `clone -a`
|
||||
└── myproject_170126.1432_before_rewrite.git.tar.gz
|
||||
```
|
||||
|
||||
Setting this up once:
|
||||
|
||||
```sh
|
||||
ssh git@git.example.com 'mkdir -p /home/git/archive'
|
||||
```
|
||||
|
||||
Ownership does not matter — `list` reads whatever `ls -ltr` reports, whichever
|
||||
user or group owns the repositories. Everything mgsh runs there is plain
|
||||
`ls`/`mkdir`/`cp`/`tar`/`rm` plus `git --bare init`, with all names shell-quoted,
|
||||
so a restricted shell that allows those commands is enough.
|
||||
|
||||
`archive` copies the *server-side* repository (not the working tree) to
|
||||
`archive/<project>_DDMMYY.HHMM[_comment].git.tar.gz`; `clone -a <name>` unpacks
|
||||
one and clones it back. `list -a` lists them.
|
||||
|
||||
+31
-5
@@ -2,9 +2,11 @@
|
||||
# in the required settings (mgsh refuses to start until they are set). This file
|
||||
# is just an annotated reference.
|
||||
#
|
||||
# Simple "key = value" (or "key: value") lines; '#' starts a comment. ~/.mgshrc
|
||||
# is the sole source of configuration (there are no built-in defaults); MGSH_*
|
||||
# environment variables override individual settings.
|
||||
# Simple "key = value" (or "key: value") lines; a '#' after whitespace starts a
|
||||
# comment. There are no built-in defaults. Settings are resolved in this order,
|
||||
# each step overriding the one before:
|
||||
#
|
||||
# ~/.mgshrc -> <project>/.mgshrc -> MGSH_* environment variables
|
||||
|
||||
# --- required ---
|
||||
base = /Users/me/src
|
||||
@@ -20,13 +22,37 @@ gitpath = /home/git
|
||||
# pushdefault = matching
|
||||
# editor = code
|
||||
|
||||
# --- pushremote: mirror to a public server (gitea/github/gitlab) via its API ---
|
||||
# --- pushremote: mirror to public servers (gitea/github/gitlab) via their API ---
|
||||
# A single server, the flat form (this target is named "public"):
|
||||
# remoteurl = https://git.example.com
|
||||
# remotekey = <personal-access-token>
|
||||
# remotetype = gitea # optional; auto-detected from remoteurl
|
||||
# remotetype = gitea # optional; auto-detected from remoteurl
|
||||
# remotevisibility = private # visibility of created repos (default private)
|
||||
#
|
||||
# Or any number of named servers. `pushremote` pushes to all of them in the
|
||||
# order given, `pushremote @hub` to a single one. Each target gets a git remote
|
||||
# of the same name in the repository.
|
||||
# remote.gitea.url = https://git.example.com
|
||||
# remote.gitea.key = <personal-access-token>
|
||||
# remote.hub.url = https://github.com
|
||||
# remote.hub.key = <personal-access-token>
|
||||
# remote.hub.type = github # optional; auto-detected from the url
|
||||
# remote.hub.visibility = public # default private
|
||||
# remotes = gitea, hub # optional: restrict and order the set
|
||||
#
|
||||
# mirror = true # `push` also mirrors via pushremote
|
||||
|
||||
# --- per-project overrides ---
|
||||
# A <project>/.mgshrc overrides all of the above for that project only, except
|
||||
# base, gitname, gitemail and pushdefault, which stay global. Typical use:
|
||||
#
|
||||
# # ~/src/myproject/.mgshrc
|
||||
# remotes = hub # this project is mirrored to github only
|
||||
# mirror = true
|
||||
#
|
||||
# Keep tokens out of a project .mgshrc unless it is git-ignored — mgsh warns
|
||||
# when it is not.
|
||||
|
||||
# Aliases live in the same file (managed by the `alias` command). The expansion
|
||||
# is a mgsh command line; $1..$N and $*/$@ expand arguments, and a shell command
|
||||
# needs a leading '!'.
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
4.0.11
|
||||
4.0.23
|
||||
|
||||
Reference in New Issue
Block a user