github.com/symfony-cli/symfony-cli@v0.0.0-20240514161054-ece2df437dfa/git/exec.go (about) 1 /* 2 * Copyright (c) 2021-present Fabien Potencier <fabien@symfony.com> 3 * 4 * This file is part of Symfony CLI project 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU Affero General Public License as 8 * published by the Free Software Foundation, either version 3 of the 9 * License, or (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU Affero General Public License for more details. 15 * 16 * You should have received a copy of the GNU Affero General Public License 17 * along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package git 21 22 import ( 23 "bytes" 24 "io" 25 "os" 26 "os/exec" 27 "syscall" 28 29 "github.com/pkg/errors" 30 "github.com/symfony-cli/terminal" 31 ) 32 33 func execGitQuiet(cwd string, args ...string) (*bytes.Buffer, error) { 34 return doExecGit(cwd, args, true) 35 } 36 37 func execGit(cwd string, args ...string) error { 38 _, err := doExecGit(cwd, args, false) 39 return err 40 } 41 42 func doExecGit(cwd string, args []string, quiet bool) (*bytes.Buffer, error) { 43 var out bytes.Buffer 44 cmd := exec.Command("git", args...) 45 if quiet { 46 cmd.Stdout = &out 47 cmd.Stderr = &out 48 } else { 49 cmd.Stdin = os.Stdin 50 cmd.Stdout = &gitOutputWriter{output: terminal.Stdout} 51 cmd.Stderr = os.Stderr 52 } 53 54 if cwd != "" { 55 cmd.Dir = cwd 56 } 57 58 err := cmd.Run() 59 if exitError, ok := err.(*exec.ExitError); ok { 60 if waitStatus, ok := exitError.Sys().(syscall.WaitStatus); ok { 61 if waitStatus.ExitStatus() == 1 { 62 return &out, errors.Errorf("Command failed") 63 } 64 } 65 return &out, errors.WithStack(err) 66 } 67 68 return &out, nil 69 } 70 71 type gitOutputWriter struct { 72 output io.Writer 73 74 // Internal state 75 buf bytes.Buffer 76 } 77 78 func (w *gitOutputWriter) Write(p []byte) (int, error) { 79 n, err := w.buf.Write(p) 80 if err != nil { 81 return n, errors.WithStack(err) 82 } 83 84 return n, w.scan() 85 } 86 87 func (w *gitOutputWriter) scan() error { 88 for { 89 b := w.buf.Bytes() 90 // no new line, let's reset the buffer to save some memory 91 if len(b) == 0 { 92 w.buf.Reset() 93 return nil 94 } 95 96 pos := bytes.IndexAny(b, "\r\n") 97 // incomplete line, let's discard everything we already read to save 98 // some memory 99 if pos == -1 { 100 w.buf.Truncate(len(b)) 101 return nil 102 } 103 104 b = w.buf.Next(pos + 1) 105 if len(b) > 1 { 106 if _, err := w.output.Write([]byte(" ")); err != nil { 107 return errors.WithStack(err) 108 } 109 } 110 111 if _, err := w.output.Write(b); err != nil { 112 return errors.WithStack(err) 113 } 114 } 115 }