github.com/justinjmoses/evergreen@v0.0.0-20170530173719-1d50e381ff0d/thirdparty/git.go (about)

     1  package thirdparty
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"os/exec"
     9  	"path/filepath"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"github.com/evergreen-ci/evergreen/model/patch"
    14  	"github.com/evergreen-ci/evergreen/util"
    15  	"github.com/mongodb/grip"
    16  	"github.com/pkg/errors"
    17  )
    18  
    19  // GitApplyNumstat attempts to apply a given patch; it returns the patch's bytes
    20  // if it is successful
    21  func GitApplyNumstat(patch string) (*bytes.Buffer, error) {
    22  	handle, err := ioutil.TempFile("", util.RandomString())
    23  	if err != nil {
    24  		return nil, errors.New("Unable to create local patch file")
    25  	}
    26  	// convert the patch to bytes
    27  	buf := []byte(patch)
    28  	buffer := bytes.NewBuffer(buf)
    29  	for {
    30  		// read a chunk
    31  		n, err := buffer.Read(buf)
    32  		if err != nil && err != io.EOF {
    33  			return nil, errors.New("Unable to read supplied patch file")
    34  		}
    35  		if n == 0 {
    36  			break
    37  		}
    38  		// write a chunk
    39  		if _, err := handle.Write(buf[:n]); err != nil {
    40  			return nil, errors.New("Unable to read supplied patch file")
    41  		}
    42  	}
    43  
    44  	// pseudo-validate the patch set by attempting to get a summary
    45  	var summaryBuffer bytes.Buffer
    46  	cmd := exec.Command("git", "apply", "--numstat", handle.Name())
    47  	cmd.Stdout = &summaryBuffer
    48  	cmd.Stderr = &summaryBuffer
    49  	cmd.Dir = filepath.Dir(handle.Name())
    50  
    51  	// this should never happen if patch is initially validated
    52  	if err := cmd.Start(); err != nil {
    53  		return nil, errors.Wrapf(err, "Error validating patch: 424 - %v",
    54  			summaryBuffer.String())
    55  	}
    56  
    57  	// this should never happen if patch is initially validated
    58  	if err := cmd.Wait(); err != nil {
    59  		return nil, errors.Wrapf(err, "Error waiting on patch: 562 - %v",
    60  			summaryBuffer.String())
    61  	}
    62  	return &summaryBuffer, nil
    63  }
    64  
    65  // ParseGitSummary takes in a buffer of data and parses it into a slice of
    66  // git summaries. It returns an error if it is unable to parse the data
    67  func ParseGitSummary(gitOutput fmt.Stringer) (summaries []patch.Summary, err error) {
    68  	// separate stats per file
    69  	fileStats := strings.Split(gitOutput.String(), "\n")
    70  
    71  	var additions, deletions int
    72  
    73  	for _, fileDetails := range fileStats {
    74  		details := strings.SplitN(fileDetails, "\t", 3)
    75  		// we expect to get the number of additions,
    76  		// the number of deletions, and the filename
    77  		if len(details) != 3 {
    78  			grip.Errorf("File stat details for '%v' has length '%v'",
    79  				details, len(details))
    80  			continue
    81  		}
    82  
    83  		additions, err = strconv.Atoi(details[0])
    84  		if err != nil {
    85  			if details[0] == "-" {
    86  				grip.Warningf("Line addition count for %v is '%v' assuming "+
    87  					"binary data diff, using 0", details[2], details[0])
    88  				additions = 0
    89  			} else {
    90  				return nil, errors.Wrap(err, "Error getting patch additions summary")
    91  			}
    92  		}
    93  
    94  		deletions, err = strconv.Atoi(details[1])
    95  		if err != nil {
    96  			if details[1] == "-" {
    97  				grip.Warningf("Line deletion count for %v is '%v' assuming "+
    98  					"binary data diff, using 0", details[2], details[1])
    99  				deletions = 0
   100  			} else {
   101  				return nil, errors.Wrap(err, "Error getting patch deletions summary")
   102  			}
   103  		}
   104  
   105  		summary := patch.Summary{
   106  			Name:      details[2],
   107  			Additions: additions,
   108  			Deletions: deletions,
   109  		}
   110  		summaries = append(summaries, summary)
   111  	}
   112  	return summaries, nil
   113  }