| @@ -12,6 +12,7 @@ import ( |
| 12 | 12 | "slices" |
| 13 | 13 | "strconv" |
| 14 | 14 | "strings" |
| 15 | "syscall" |
| 15 | 16 | |
| 16 | 17 | "gitbay.org/gitbay/internal/cliconfig" |
| 17 | 18 | "gitbay.org/gitbay/internal/protocol" |
| @@ -139,9 +140,40 @@ func stripNoColor(args []string) ([]string, bool) { |
| 139 | 140 | return out, found |
| 140 | 141 | } |
| 141 | 142 | |
| 143 | // pagerArgv is the pager to run long output through: GITBAY_PAGER, then |
| 144 | // PAGER, then less. An empty GITBAY_PAGER turns paging off. |
| 145 | func pagerArgv(env func(string) (string, bool)) []string { |
| 146 | if v, ok := env("GITBAY_PAGER"); ok { |
| 147 | return strings.Fields(v) |
| 148 | } |
| 149 | if v, ok := env("PAGER"); ok && v != "" { |
| 150 | return strings.Fields(v) |
| 151 | } |
| 152 | return []string{"less"} |
| 153 | } |
| 154 | |
| 155 | // pages reports whether a command's output goes through the pager at a |
| 156 | // terminal: views, diffs and logs, never a follow or JSON. |
| 157 | func pages(server, args []string) bool { |
| 158 | if len(server) == 0 || slices.Contains(args, "--json") || slices.Contains(args, "--follow") { |
| 159 | return false |
| 160 | } |
| 161 | switch server[len(server)-1] { |
| 162 | case "show", "diff", "log": |
| 163 | return true |
| 164 | } |
| 165 | return false |
| 166 | } |
| 167 | |
| 142 | 168 | // runSSH executes the server command over the system ssh binary, wiring |
| 143 | | // stdio through. It returns the remote exit code. |
| 169 | // stdio through, with no pager. It returns the remote exit code. |
| 144 | 170 | func runSSH(t target, serverArgv []string, stdin io.Reader) int { |
| 171 | return runSSHPaged(t, serverArgv, stdin, false) |
| 172 | } |
| 173 | |
| 174 | // runSSHPaged is runSSH with output optionally run through the pager when |
| 175 | // stdout is a terminal and page is true. It returns the remote exit code. |
| 176 | func runSSHPaged(t target, serverArgv []string, stdin io.Reader, page bool) int { |
| 145 | 177 | args := sshArgs(t.inst) |
| 146 | 178 | |
| 147 | 179 | fd := int(os.Stdout.Fd()) |
| @@ -164,11 +196,41 @@ func runSSH(t target, serverArgv []string, stdin io.Reader) int { |
| 164 | 196 | cmd.Stdin = stdin |
| 165 | 197 | cmd.Stdout = os.Stdout |
| 166 | 198 | cmd.Stderr = os.Stderr |
| 199 | |
| 200 | var pager *exec.Cmd |
| 201 | var pw io.WriteCloser |
| 202 | if page && isTTY { |
| 203 | if argv := pagerArgv(os.LookupEnv); len(argv) > 0 { |
| 204 | pager = exec.Command(toolpath.Look(argv[0]), argv[1:]...) |
| 205 | pager.Stdout, pager.Stderr = os.Stdout, os.Stderr |
| 206 | if _, ok := os.LookupEnv("LESS"); !ok { |
| 207 | pager.Env = append(os.Environ(), "LESS=FRX") |
| 208 | } |
| 209 | if w, err := pager.StdinPipe(); err == nil && pager.Start() == nil { |
| 210 | pw = w |
| 211 | cmd.Stdout = pw |
| 212 | } else { |
| 213 | pager = nil |
| 214 | } |
| 215 | } |
| 216 | } |
| 217 | |
| 167 | 218 | err := cmd.Run() |
| 219 | if pager != nil { |
| 220 | pw.Close() |
| 221 | pager.Wait() |
| 222 | } |
| 168 | 223 | if err == nil { |
| 169 | 224 | return 0 |
| 170 | 225 | } |
| 171 | 226 | if ee, ok := err.(*exec.ExitError); ok { |
| 227 | if pager != nil { |
| 228 | if ws, ok := ee.Sys().(syscall.WaitStatus); ok && ws.Signaled() && ws.Signal() == syscall.SIGPIPE { |
| 229 | // The user quit the pager before ssh finished writing; |
| 230 | // that is not a failure of the command itself. |
| 231 | return 0 |
| 232 | } |
| 233 | } |
| 172 | 234 | code := ee.ExitCode() |
| 173 | 235 | if code == 255 { // ssh-level failure (connection, auth, host key) |
| 174 | 236 | fmt.Fprintln(os.Stderr, "gitbay: ssh could not connect or authenticate; if this worked a moment ago,"+ |