Fix review findings, add per-project config and multi-target pushremote
Correctness and security fixes found by a review of the initial commit: - init decided whether a server repository existed from the *local* remote.origin.url, so an unlinked project directory skipped the confirmation and rm -rf'd the remote history. It now asks the server, and aborts when the server cannot be reached. - Project names and paths were interpolated unquoted into the remote shell command strings: a space split one path into two arguments and a backtick executed on the git server. Everything now goes through shq(), and chained remote commands use && so a failed cd cannot let the next command run in the login directory. - Bare `cd` panicked with an index-out-of-range and took down the shell; it now deselects the project. - Command-line mode set PRJ to the whole path below BASE, so `mgsh push` from a subdirectory staged only that subtree and addressed a bogus server path. It now truncates at the first path element. - `list` hardcoded owner and group "git git" in its regex and silently printed nothing on any server where the repositories are owned by someone else. - The config parser kept inline "#" comments in values although the README and the example file document them, so `mirror = true # ...` silently disabled mirroring. - ~/.mgshrc holds an API token but was created world-readable. - The mirror token was passed on git's command line, visible in the process table; it now goes through GIT_CONFIG_*. - tag, count and dist ran without a repository and operated on BASE. - checkout dropped its git options, because the dispatcher strips -x flags from the word list. - REPO was read with a plain `git config`, inheriting a foreign origin from an enclosing repository; it is now local-only and, being dead state otherwise, no longer recomputed on every prompt. - getkey consumed a single byte, leaving the rest of a typed answer in the tty queue where readline ran it as a command. - The REPL spun on any readline error that was neither EOF nor interrupt. - Tab completion cached an empty repository list after one failed ssh. - Startup did a blocking DNS lookup and three `git config --global` writes on every invocation. New: - A project may carry its own .mgshrc, overriding the global settings while it is active. Resolution order is ~/.mgshrc -> <project>/.mgshrc -> MGSH_*; base and the git identity keys stay global. It is read when the project changes, and `rescan` reloads it. - pushremote mirrors to any number of servers, configured as remote.<name>.url/key/type/visibility blocks. `pushremote` pushes to all of them, `pushremote @name ...` to a selection, and `remotes = ...` restricts and orders the set. Each target owns a git remote of the same name; a failing target no longer stops the others. - `config` shows the resolved configuration, its sources and the mirror targets with masked tokens; `config -k` lists the setting names. - gitkey was parsed and documented but never used. It is now the ssh identity for the git server, for mgsh's own ssh calls and, via GIT_SSH_COMMAND, for the git commands mgsh runs. - config, count, login and cloneall work from the command line too. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -47,7 +47,7 @@ var builtinCmds = map[string]bool{
|
||||
"pushremote": true, "overview": true, "archive": true, "init": true,
|
||||
"login": true, "cd": true, "checkout": true, "clone": true, "cloneall": true,
|
||||
"open": true, "view": true, "count": true, "tag": true, "alias": true,
|
||||
"unalias": true,
|
||||
"unalias": true, "config": true,
|
||||
}
|
||||
|
||||
func isBuiltin(name string) bool { return builtinCmds[name] }
|
||||
@@ -169,7 +169,9 @@ func saveAliases() {
|
||||
if blk := aliasBlock(aliases); blk != "" {
|
||||
b.WriteString("\n" + blk)
|
||||
}
|
||||
if err := os.WriteFile(path, []byte(b.String()), 0644); err != nil {
|
||||
// os.WriteFile only applies the mode when creating the file, so an existing
|
||||
// ~/.mgshrc keeps whatever the user set; a fresh one is created private.
|
||||
if err := os.WriteFile(path, []byte(b.String()), configMode); err != nil {
|
||||
errorln("could not save aliases: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user