|
6 | 6 | package git
|
7 | 7 |
|
8 | 8 | import (
|
| 9 | + "bytes" |
9 | 10 | "container/list"
|
10 | 11 | "fmt"
|
11 | 12 | "io"
|
| 13 | + "regexp" |
12 | 14 | "strconv"
|
13 | 15 | "strings"
|
14 | 16 | "time"
|
@@ -84,14 +86,97 @@ func (repo *Repository) GetCompareInfo(basePath, baseBranch, headBranch string)
|
84 | 86 | }
|
85 | 87 |
|
86 | 88 | // Count number of changed files.
|
87 |
| - stdout, err := NewCommand("diff", "--name-only", remoteBranch+"..."+headBranch).RunInDir(repo.Path) |
| 89 | + // This probably should be removed as we need to use shortstat elsewhere |
| 90 | + // Now there is git diff --shortstat but this appears to be slower than simply iterating with --nameonly |
| 91 | + compareInfo.NumFiles, err = repo.GetDiffNumChangedFiles(remoteBranch, headBranch) |
88 | 92 | if err != nil {
|
89 | 93 | return nil, err
|
90 | 94 | }
|
91 |
| - compareInfo.NumFiles = len(strings.Split(stdout, "\n")) - 1 |
92 | 95 | return compareInfo, nil
|
93 | 96 | }
|
94 | 97 |
|
| 98 | +type lineCountWriter struct { |
| 99 | + numLines int |
| 100 | +} |
| 101 | + |
| 102 | +// Write counts the number of newlines in the provided bytestream |
| 103 | +func (l *lineCountWriter) Write(p []byte) (n int, err error) { |
| 104 | + n = len(p) |
| 105 | + l.numLines += bytes.Count(p, []byte{'\000'}) |
| 106 | + return |
| 107 | +} |
| 108 | + |
| 109 | +// GetDiffNumChangedFiles counts the number of changed files |
| 110 | +// This is substantially quicker than shortstat but... |
| 111 | +func (repo *Repository) GetDiffNumChangedFiles(base, head string) (int, error) { |
| 112 | + // Now there is git diff --shortstat but this appears to be slower than simply iterating with --nameonly |
| 113 | + w := &lineCountWriter{} |
| 114 | + stderr := new(bytes.Buffer) |
| 115 | + |
| 116 | + if err := NewCommand("diff", "-z", "--name-only", base+"..."+head). |
| 117 | + RunInDirPipeline(repo.Path, w, stderr); err != nil { |
| 118 | + return 0, fmt.Errorf("%v: Stderr: %s", err, stderr) |
| 119 | + } |
| 120 | + return w.numLines, nil |
| 121 | +} |
| 122 | + |
| 123 | +// GetDiffShortStat counts number of changed files, number of additions and deletions |
| 124 | +func (repo *Repository) GetDiffShortStat(base, head string) (numFiles, totalAdditions, totalDeletions int, err error) { |
| 125 | + return GetDiffShortStat(repo.Path, base+"..."+head) |
| 126 | +} |
| 127 | + |
| 128 | +// GetDiffShortStat counts number of changed files, number of additions and deletions |
| 129 | +func GetDiffShortStat(repoPath string, args ...string) (numFiles, totalAdditions, totalDeletions int, err error) { |
| 130 | + // Now if we call: |
| 131 | + // $ git diff --shortstat 1ebb35b98889ff77299f24d82da426b434b0cca0...788b8b1440462d477f45b0088875 |
| 132 | + // we get: |
| 133 | + // " 9902 files changed, 2034198 insertions(+), 298800 deletions(-)\n" |
| 134 | + args = append([]string{ |
| 135 | + "diff", |
| 136 | + "--shortstat", |
| 137 | + }, args...) |
| 138 | + |
| 139 | + stdout, err := NewCommand(args...).RunInDir(repoPath) |
| 140 | + if err != nil { |
| 141 | + return 0, 0, 0, err |
| 142 | + } |
| 143 | + |
| 144 | + return parseDiffStat(stdout) |
| 145 | +} |
| 146 | + |
| 147 | +var shortStatFormat = regexp.MustCompile( |
| 148 | + `\s*(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?`) |
| 149 | + |
| 150 | +func parseDiffStat(stdout string) (numFiles, totalAdditions, totalDeletions int, err error) { |
| 151 | + if len(stdout) == 0 || stdout == "\n" { |
| 152 | + return 0, 0, 0, nil |
| 153 | + } |
| 154 | + groups := shortStatFormat.FindStringSubmatch(stdout) |
| 155 | + if len(groups) != 4 { |
| 156 | + return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s groups: %s", stdout, groups) |
| 157 | + } |
| 158 | + |
| 159 | + numFiles, err = strconv.Atoi(groups[1]) |
| 160 | + if err != nil { |
| 161 | + return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s. Error parsing NumFiles %v", stdout, err) |
| 162 | + } |
| 163 | + |
| 164 | + if len(groups[2]) != 0 { |
| 165 | + totalAdditions, err = strconv.Atoi(groups[2]) |
| 166 | + if err != nil { |
| 167 | + return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s. Error parsing NumAdditions %v", stdout, err) |
| 168 | + } |
| 169 | + } |
| 170 | + |
| 171 | + if len(groups[3]) != 0 { |
| 172 | + totalDeletions, err = strconv.Atoi(groups[3]) |
| 173 | + if err != nil { |
| 174 | + return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s. Error parsing NumDeletions %v", stdout, err) |
| 175 | + } |
| 176 | + } |
| 177 | + return |
| 178 | +} |
| 179 | + |
95 | 180 | // GetDiffOrPatch generates either diff or formatted patch data between given revisions
|
96 | 181 | func (repo *Repository) GetDiffOrPatch(base, head string, w io.Writer, formatted bool) error {
|
97 | 182 | if formatted {
|
|
0 commit comments