9580353dca
We currently have a function to shell quote a list of strings, but in some cases we may want to just quote a single string. To avoid the complexity of having to create a bunch of temporaries and then index out the result, create a function that shell quotes just a single string.
238 lines
6.6 KiB
Go
238 lines
6.6 KiB
Go
// Package subprocess provides helper functions for forking new processes
|
|
// NOTE: Subject to change, do not rely on this package from outside git-lfs source
|
|
package subprocess
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/rubyist/tracerx"
|
|
)
|
|
|
|
// BufferedExec starts up a command and creates a stdin pipe and a buffered
|
|
// stdout & stderr pipes, wrapped in a BufferedCmd. The stdout buffer will be
|
|
// of stdoutBufSize bytes.
|
|
func BufferedExec(name string, args ...string) (*BufferedCmd, error) {
|
|
cmd := ExecCommand(name, args...)
|
|
stdout, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stderr, err := cmd.StderrPipe()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
stdin, err := cmd.StdinPipe()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &BufferedCmd{
|
|
cmd,
|
|
stdin,
|
|
bufio.NewReaderSize(stdout, stdoutBufSize),
|
|
bufio.NewReaderSize(stderr, stdoutBufSize),
|
|
}, nil
|
|
}
|
|
|
|
// SimpleExec is a small wrapper around os/exec.Command.
|
|
func SimpleExec(name string, args ...string) (string, error) {
|
|
Trace(name, args...)
|
|
return Output(ExecCommand(name, args...))
|
|
}
|
|
|
|
func Output(cmd *Cmd) (string, error) {
|
|
//start copied from Go 1.6 exec.go
|
|
captureErr := cmd.Stderr == nil
|
|
if captureErr {
|
|
cmd.Stderr = &prefixSuffixSaver{N: 32 << 10}
|
|
}
|
|
//end copied from Go 1.6 exec.go
|
|
|
|
out, err := cmd.Output()
|
|
|
|
if exitError, ok := err.(*exec.ExitError); ok {
|
|
// TODO for min Go 1.6+, replace with ExitError.Stderr
|
|
errorOutput := strings.TrimSpace(string(cmd.Stderr.(*prefixSuffixSaver).Bytes()))
|
|
if errorOutput == "" {
|
|
// some commands might write nothing to stderr but something to stdout in error-conditions, in which case, we'll use that
|
|
// in the error string
|
|
errorOutput = strings.TrimSpace(string(out))
|
|
}
|
|
|
|
ran := cmd.Path
|
|
if len(cmd.Args) > 1 {
|
|
ran = fmt.Sprintf("%s %s", cmd.Path, quotedArgs(cmd.Args[1:]))
|
|
}
|
|
formattedErr := fmt.Errorf("Error running %s: '%s' '%s'", ran, errorOutput, strings.TrimSpace(exitError.Error()))
|
|
|
|
// return "" as output in error case, for callers that don't care about errors but rely on "" returned, in-case stdout != ""
|
|
return "", formattedErr
|
|
}
|
|
|
|
return strings.Trim(string(out), " \n"), err
|
|
}
|
|
|
|
var shellWordRe = regexp.MustCompile(`\A[A-Za-z0-9_@/.-]+\z`)
|
|
|
|
// ShellQuoteSingle returns a string which is quoted suitably for sh.
|
|
func ShellQuoteSingle(str string) string {
|
|
// Quote anything that looks slightly complicated.
|
|
if shellWordRe.FindStringIndex(str) == nil {
|
|
return "'" + strings.Replace(str, "'", "'\\''", -1) + "'"
|
|
}
|
|
return str
|
|
}
|
|
|
|
// ShellQuote returns a copied string slice where each element is quoted
|
|
// suitably for sh.
|
|
func ShellQuote(strs []string) []string {
|
|
dup := make([]string, 0, len(strs))
|
|
|
|
for _, str := range strs {
|
|
dup = append(dup, ShellQuoteSingle(str))
|
|
}
|
|
return dup
|
|
}
|
|
|
|
// FormatForShell takes a command name and an argument string and returns a
|
|
// command and arguments that pass this command to the shell. Note that neither
|
|
// the command nor the arguments are quoted. Consider FormatForShellQuoted
|
|
// instead.
|
|
func FormatForShell(name string, args string) (string, []string) {
|
|
return "sh", []string{"-c", name + " " + args}
|
|
}
|
|
|
|
// FormatForShellQuotedArgs takes a command name and an argument string and
|
|
// returns a command and arguments that pass this command to the shell. The
|
|
// arguments are escaped, but the name of the command is not.
|
|
func FormatForShellQuotedArgs(name string, args []string) (string, []string) {
|
|
return FormatForShell(name, strings.Join(ShellQuote(args), " "))
|
|
}
|
|
|
|
func Trace(name string, args ...string) {
|
|
tracerx.Printf("exec: %s %s", name, quotedArgs(args))
|
|
}
|
|
|
|
func quotedArgs(args []string) string {
|
|
if len(args) == 0 {
|
|
return ""
|
|
}
|
|
|
|
quoted := make([]string, len(args))
|
|
for i, arg := range args {
|
|
quoted[i] = fmt.Sprintf("'%s'", arg)
|
|
}
|
|
return strings.Join(quoted, " ")
|
|
}
|
|
|
|
// An env for an exec.Command without GIT_TRACE
|
|
var env []string
|
|
var traceEnv = "GIT_TRACE="
|
|
|
|
func init() {
|
|
realEnv := os.Environ()
|
|
env = make([]string, 0, len(realEnv))
|
|
|
|
for _, kv := range realEnv {
|
|
if strings.HasPrefix(kv, traceEnv) {
|
|
continue
|
|
}
|
|
env = append(env, kv)
|
|
}
|
|
}
|
|
|
|
// remaining code in file copied from Go 1.6 (c4fa25f4fc8f4419d0b0707bcdae9199a745face) exec.go and can be removed if moving to Go 1.6 minimum.
|
|
// go 1.6 adds ExitError.Stderr with nice prefix/suffix trimming, which could replace cmd.Stderr above
|
|
|
|
//start copied from Go 1.6 exec.go
|
|
// prefixSuffixSaver is an io.Writer which retains the first N bytes
|
|
// and the last N bytes written to it. The Bytes() methods reconstructs
|
|
// it with a pretty error message.
|
|
type prefixSuffixSaver struct {
|
|
N int // max size of prefix or suffix
|
|
prefix []byte
|
|
suffix []byte // ring buffer once len(suffix) == N
|
|
suffixOff int // offset to write into suffix
|
|
skipped int64
|
|
|
|
// TODO(bradfitz): we could keep one large []byte and use part of it for
|
|
// the prefix, reserve space for the '... Omitting N bytes ...' message,
|
|
// then the ring buffer suffix, and just rearrange the ring buffer
|
|
// suffix when Bytes() is called, but it doesn't seem worth it for
|
|
// now just for error messages. It's only ~64KB anyway.
|
|
}
|
|
|
|
func (w *prefixSuffixSaver) Write(p []byte) (n int, err error) {
|
|
lenp := len(p)
|
|
p = w.fill(&w.prefix, p)
|
|
|
|
// Only keep the last w.N bytes of suffix data.
|
|
if overage := len(p) - w.N; overage > 0 {
|
|
p = p[overage:]
|
|
w.skipped += int64(overage)
|
|
}
|
|
p = w.fill(&w.suffix, p)
|
|
|
|
// w.suffix is full now if p is non-empty. Overwrite it in a circle.
|
|
for len(p) > 0 { // 0, 1, or 2 iterations.
|
|
n := copy(w.suffix[w.suffixOff:], p)
|
|
p = p[n:]
|
|
w.skipped += int64(n)
|
|
w.suffixOff += n
|
|
if w.suffixOff == w.N {
|
|
w.suffixOff = 0
|
|
}
|
|
}
|
|
return lenp, nil
|
|
}
|
|
|
|
// fill appends up to len(p) bytes of p to *dst, such that *dst does not
|
|
// grow larger than w.N. It returns the un-appended suffix of p.
|
|
func (w *prefixSuffixSaver) fill(dst *[]byte, p []byte) (pRemain []byte) {
|
|
if remain := w.N - len(*dst); remain > 0 {
|
|
add := minInt(len(p), remain)
|
|
*dst = append(*dst, p[:add]...)
|
|
p = p[add:]
|
|
}
|
|
return p
|
|
}
|
|
|
|
func (w *prefixSuffixSaver) Bytes() []byte {
|
|
if w.suffix == nil {
|
|
return w.prefix
|
|
}
|
|
if w.skipped == 0 {
|
|
return append(w.prefix, w.suffix...)
|
|
}
|
|
var buf bytes.Buffer
|
|
buf.Grow(len(w.prefix) + len(w.suffix) + 50)
|
|
buf.Write(w.prefix)
|
|
buf.WriteString("\n... omitting ")
|
|
buf.WriteString(strconv.FormatInt(w.skipped, 10))
|
|
buf.WriteString(" bytes ...\n")
|
|
buf.Write(w.suffix[w.suffixOff:])
|
|
buf.Write(w.suffix[:w.suffixOff])
|
|
return buf.Bytes()
|
|
}
|
|
|
|
func minInt(a, b int) int {
|
|
if a < b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
//end copied from Go 1.6 exec.go
|