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:
@@ -108,9 +108,29 @@ func shq(s string) string {
|
||||
return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'"
|
||||
}
|
||||
|
||||
// sshExec runs one command on the git server. Every remote command mgsh issues
|
||||
// — including the `rm -rf` in `init` — funnels through here, which is why it is
|
||||
// a variable: tests replace it with a recorder and assert on exactly what would
|
||||
// reach the server. With capture set the output is returned, otherwise the
|
||||
// command inherits mgsh's stdio.
|
||||
var sshExec = func(remote string, capture bool) ([]byte, error) {
|
||||
args := sshArgs(cfg.GitUser+"@"+cfg.GitHost, remote)
|
||||
c := exec.Command("ssh", args...)
|
||||
if !capture {
|
||||
c.Stdin = os.Stdin
|
||||
c.Stdout = os.Stdout
|
||||
c.Stderr = os.Stderr
|
||||
return nil, c.Run()
|
||||
}
|
||||
c.Stderr = os.Stderr
|
||||
out, err := c.Output()
|
||||
return out, err
|
||||
}
|
||||
|
||||
// ssh runs a single remote command over ssh with inherited stdio.
|
||||
func ssh(remote string) error {
|
||||
return runInDir("", "ssh", sshArgs(cfg.GitUser+"@"+cfg.GitHost, remote)...)
|
||||
_, err := sshExec(remote, false)
|
||||
return err
|
||||
}
|
||||
|
||||
// sshOK runs a remote command and, on failure, prints a red summary.
|
||||
@@ -124,9 +144,7 @@ func sshOK(remote string) bool {
|
||||
|
||||
// sshOut runs a remote command and returns its stdout split into lines.
|
||||
func sshOut(remote string) ([]string, error) {
|
||||
c := exec.Command("ssh", sshArgs(cfg.GitUser+"@"+cfg.GitHost, remote)...)
|
||||
c.Stderr = os.Stderr
|
||||
out, err := c.Output()
|
||||
out, err := sshExec(remote, true)
|
||||
lines := strings.Split(string(out), "\n")
|
||||
for len(lines) > 0 && lines[len(lines)-1] == "" {
|
||||
lines = lines[:len(lines)-1]
|
||||
|
||||
Reference in New Issue
Block a user