| @@ -1,8 +1,6 @@ |
| 1 | 1 | package main |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | | "text/tabwriter" |
| 5 | | |
| 6 | 4 | "encoding/json" |
| 7 | 5 | "fmt" |
| 8 | 6 | "golang.org/x/term" |
| @@ -11,6 +9,7 @@ import ( |
| 11 | 9 | "os/exec" |
| 12 | 10 | "path/filepath" |
| 13 | 11 | "regexp" |
| 12 | "slices" |
| 14 | 13 | "strconv" |
| 15 | 14 | "strings" |
| 16 | 15 | |
| @@ -109,10 +108,52 @@ func sshArgs(inst cliconfig.Instance) []string { |
| 109 | 108 | return append(args, inst.SSHOptions...) |
| 110 | 109 | } |
| 111 | 110 | |
| 111 | // noColor is --no-color, stripped from argv in main. |
| 112 | var noColor bool |
| 113 | |
| 114 | // termValue is GITBAY_TERM for this invocation: the terminal's width, |
| 115 | // and whether colour is wanted. Empty when stdout is not a terminal, |
| 116 | // so piped output stays the rows stock ssh prints. |
| 117 | func termValue(isTerminal bool, cols int, env func(string) string) string { |
| 118 | if !isTerminal || cols < 40 { |
| 119 | return "" |
| 120 | } |
| 121 | v := strconv.Itoa(cols) |
| 122 | if !noColor && env("NO_COLOR") == "" && env("TERM") != "dumb" { |
| 123 | v += ",color" |
| 124 | } |
| 125 | return v |
| 126 | } |
| 127 | |
| 128 | // stripNoColor removes --no-color wherever it appears. |
| 129 | func stripNoColor(args []string) ([]string, bool) { |
| 130 | out := args[:0:0] |
| 131 | found := false |
| 132 | for _, a := range args { |
| 133 | if a == "--no-color" { |
| 134 | found = true |
| 135 | continue |
| 136 | } |
| 137 | out = append(out, a) |
| 138 | } |
| 139 | return out, found |
| 140 | } |
| 141 | |
| 112 | 142 | // runSSH executes the server command over the system ssh binary, wiring |
| 113 | 143 | // stdio through. It returns the remote exit code. |
| 114 | 144 | func runSSH(t target, serverArgv []string, stdin io.Reader) int { |
| 115 | 145 | args := sshArgs(t.inst) |
| 146 | |
| 147 | fd := int(os.Stdout.Fd()) |
| 148 | cols := 0 |
| 149 | isTTY := term.IsTerminal(fd) |
| 150 | if isTTY { |
| 151 | cols, _, _ = term.GetSize(fd) |
| 152 | } |
| 153 | if v := termValue(isTTY, cols, os.Getenv); v != "" && !slices.Contains(serverArgv, "--json") { |
| 154 | serverArgv = append([]string{"--term=" + v}, serverArgv...) |
| 155 | } |
| 156 | |
| 116 | 157 | quoted := make([]string, len(serverArgv)) |
| 117 | 158 | for i, a := range serverArgv { |
| 118 | 159 | quoted[i] = shellQuote(a) |
| @@ -123,15 +164,7 @@ func runSSH(t target, serverArgv []string, stdin io.Reader) int { |
| 123 | 164 | cmd.Stdin = stdin |
| 124 | 165 | cmd.Stdout = os.Stdout |
| 125 | 166 | cmd.Stderr = os.Stderr |
| 126 | | var tw *tabwriter.Writer |
| 127 | | if alignColumns(serverArgv) { |
| 128 | | tw = tabwriter.NewWriter(os.Stdout, 0, 8, 2, ' ', 0) |
| 129 | | cmd.Stdout = tw |
| 130 | | } |
| 131 | 167 | err := cmd.Run() |
| 132 | | if tw != nil { |
| 133 | | tw.Flush() |
| 134 | | } |
| 135 | 168 | if err == nil { |
| 136 | 169 | return 0 |
| 137 | 170 | } |
| @@ -204,34 +237,3 @@ func withRepo(t target, args []string) ([]string, error) { |
| 204 | 237 | } |
| 205 | 238 | return append([]string{t.repo}, args...), nil |
| 206 | 239 | } |
| 207 | | |
| 208 | | // listVerbs are the server commands whose plain output is one row per |
| 209 | | // item with tab-separated columns. |
| 210 | | var listVerbs = map[string]bool{ |
| 211 | | "list": true, "runners": true, "deliveries": true, "refs": true, |
| 212 | | "revisions": true, "threads": true, "bookmarks": true, "jobs": true, |
| 213 | | "prune": true, |
| 214 | | } |
| 215 | | |
| 216 | | // alignColumns reports whether a command's rows should be padded into |
| 217 | | // columns: a list command, printed for a person at a terminal. Piped |
| 218 | | // output keeps the server's tabs so cut and awk see the same bytes stock |
| 219 | | // ssh prints, and --json is never touched. |
| 220 | | func alignColumns(serverArgv []string) bool { |
| 221 | | verb := "" |
| 222 | | for _, a := range serverArgv { |
| 223 | | if strings.HasPrefix(a, "-") { |
| 224 | | break |
| 225 | | } |
| 226 | | if a == "--json" { |
| 227 | | return false |
| 228 | | } |
| 229 | | verb = a |
| 230 | | } |
| 231 | | for _, a := range serverArgv { |
| 232 | | if a == "--json" { |
| 233 | | return false |
| 234 | | } |
| 235 | | } |
| 236 | | return listVerbs[verb] && term.IsTerminal(int(os.Stdout.Fd())) |
| 237 | | } |