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 }