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  }