Make the ssh layer injectable and cover the destructive server commands

The remote commands are the only code in mgsh that can destroy data, and
they were the least verifiable: each one is a single string handed to a
login shell, so a missing quote silently changes which paths it touches.
Every one of them now goes through the sshExec variable, and yesno is a
variable too, so a test can record what would have been sent and answer
the confirmations without a terminal.

The tests pin down what the previous commit fixed by reasoning alone:
that a declined or unreachable `init` sends no rm -rf, that a project
named "my 'weird' project" reaches the server fully quoted, and that
archive's cp/tar/rm sequence is named, ordered and quoted correctly.
push is driven end to end against a real local bare repository.

archive also gained the server-side existence check that init, clone and
show already had, so a project that was never pushed reports that instead
of failing inside cp -r.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-26 11:38:22 +02:00
co-authored by Claude Opus 5
parent ae8c7a3ec0
commit 689a3fe594
4 changed files with 369 additions and 6 deletions
+11
View File
@@ -304,6 +304,17 @@ func runCommandDepth(line string, depth int) bool {
if !requireProject() {
break
}
// like `init`/`clone`/`show`, ask the server before acting on it —
// otherwise a missing repository surfaces as a raw `cp -r` failure
exists, err := serverEntryExists(".", PRJ+".git")
if err != nil {
errorln("could not reach the git server: " + err.Error())
break
}
if !exists {
errorln("no repository " + PRJ + " on the git server — run 'init' first")
break
}
comment := sanitizeComment(strings.Join(fields[1:], " "))
z := archiveStamp()
name := PRJ + "_" + z