Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

Commit e2791ac

Browse files
authored
Merge pull request #613 from darkowlzz/482-commit-stats
Add Stats() to Commit
2 parents 8ab19f6 + 11162e1 commit e2791ac

File tree

3 files changed

+167
-0
lines changed

3 files changed

+167
-0
lines changed

plumbing/object/commit.go

+26
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,32 @@ func (b *Commit) Encode(o plumbing.EncodedObject) error {
265265
return err
266266
}
267267

268+
// Stats shows the status of commit.
269+
func (c *Commit) Stats() (FileStats, error) {
270+
// Get the previous commit.
271+
ci := c.Parents()
272+
parentCommit, err := ci.Next()
273+
if err != nil {
274+
if err == io.EOF {
275+
emptyNoder := treeNoder{}
276+
parentCommit = &Commit{
277+
Hash: emptyNoder.hash,
278+
// TreeHash: emptyNoder.parent.Hash,
279+
s: c.s,
280+
}
281+
} else {
282+
return nil, err
283+
}
284+
}
285+
286+
patch, err := parentCommit.Patch(c)
287+
if err != nil {
288+
return nil, err
289+
}
290+
291+
return getFileStatsFromFilePatches(patch.FilePatches()), nil
292+
}
293+
268294
func (c *Commit) String() string {
269295
return fmt.Sprintf(
270296
"%s %s\nAuthor: %s\nDate: %s\n\n%s\n",

plumbing/object/commit_test.go

+26
Original file line numberDiff line numberDiff line change
@@ -258,3 +258,29 @@ RUysgqjcpT8+iQM1PblGfHR4XAhuOqN5Fx06PSaFZhqvWFezJ28/CLyX5q+oIVk=
258258
c.Assert(err, IsNil)
259259
c.Assert(decoded.PGPSignature, Equals, pgpsignature)
260260
}
261+
262+
func (s *SuiteCommit) TestStat(c *C) {
263+
aCommit := s.commit(c, plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5"))
264+
fileStats, err := aCommit.Stats()
265+
c.Assert(err, IsNil)
266+
267+
c.Assert(fileStats[0].Name, Equals, "vendor/foo.go")
268+
c.Assert(fileStats[0].Addition, Equals, 7)
269+
c.Assert(fileStats[0].Deletion, Equals, 0)
270+
c.Assert(fileStats[0].String(), Equals, " vendor/foo.go | 7 +++++++\n")
271+
272+
// Stats for another commit.
273+
aCommit = s.commit(c, plumbing.NewHash("918c48b83bd081e863dbe1b80f8998f058cd8294"))
274+
fileStats, err = aCommit.Stats()
275+
c.Assert(err, IsNil)
276+
277+
c.Assert(fileStats[0].Name, Equals, "go/example.go")
278+
c.Assert(fileStats[0].Addition, Equals, 142)
279+
c.Assert(fileStats[0].Deletion, Equals, 0)
280+
c.Assert(fileStats[0].String(), Equals, " go/example.go | 142 +++++++++++++++++++++++++++++++++++++++++++++++++++++\n")
281+
282+
c.Assert(fileStats[1].Name, Equals, "php/crappy.php")
283+
c.Assert(fileStats[1].Addition, Equals, 259)
284+
c.Assert(fileStats[1].Deletion, Equals, 0)
285+
c.Assert(fileStats[1].String(), Equals, " php/crappy.php | 259 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n")
286+
}

plumbing/object/patch.go

+115
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"bytes"
55
"fmt"
66
"io"
7+
"math"
8+
"strings"
79

810
"gopkg.in/src-d/go-git.v4/plumbing"
911
"gopkg.in/src-d/go-git.v4/plumbing/filemode"
@@ -105,6 +107,10 @@ func (p *Patch) Encode(w io.Writer) error {
105107
return ue.Encode(p)
106108
}
107109

110+
func (p *Patch) Stats() FileStats {
111+
return getFileStatsFromFilePatches(p.FilePatches())
112+
}
113+
108114
func (p *Patch) String() string {
109115
buf := bytes.NewBuffer(nil)
110116
err := p.Encode(buf)
@@ -185,3 +191,112 @@ func (t *textChunk) Content() string {
185191
func (t *textChunk) Type() fdiff.Operation {
186192
return t.op
187193
}
194+
195+
// FileStat stores the status of changes in content of a file.
196+
type FileStat struct {
197+
Name string
198+
Addition int
199+
Deletion int
200+
}
201+
202+
func (fs FileStat) String() string {
203+
return printStat([]FileStat{fs})
204+
}
205+
206+
// FileStats is a collection of FileStat.
207+
type FileStats []FileStat
208+
209+
func (fileStats FileStats) String() string {
210+
return printStat(fileStats)
211+
}
212+
213+
func printStat(fileStats []FileStat) string {
214+
padLength := float64(len(" "))
215+
newlineLength := float64(len("\n"))
216+
separatorLength := float64(len("|"))
217+
// Soft line length limit. The text length calculation below excludes
218+
// length of the change number. Adding that would take it closer to 80,
219+
// but probably not more than 80, until it's a huge number.
220+
lineLength := 72.0
221+
222+
// Get the longest filename and longest total change.
223+
var longestLength float64
224+
var longestTotalChange float64
225+
for _, fs := range fileStats {
226+
if int(longestLength) < len(fs.Name) {
227+
longestLength = float64(len(fs.Name))
228+
}
229+
totalChange := fs.Addition + fs.Deletion
230+
if int(longestTotalChange) < totalChange {
231+
longestTotalChange = float64(totalChange)
232+
}
233+
}
234+
235+
// Parts of the output:
236+
// <pad><filename><pad>|<pad><changeNumber><pad><+++/---><newline>
237+
// example: " main.go | 10 +++++++--- "
238+
239+
// <pad><filename><pad>
240+
leftTextLength := padLength + longestLength + padLength
241+
242+
// <pad><number><pad><+++++/-----><newline>
243+
// Excluding number length here.
244+
rightTextLength := padLength + padLength + newlineLength
245+
246+
totalTextArea := leftTextLength + separatorLength + rightTextLength
247+
heightOfHistogram := lineLength - totalTextArea
248+
249+
// Scale the histogram.
250+
var scaleFactor float64
251+
if longestTotalChange > heightOfHistogram {
252+
// Scale down to heightOfHistogram.
253+
scaleFactor = float64(longestTotalChange / heightOfHistogram)
254+
} else {
255+
scaleFactor = 1.0
256+
}
257+
258+
finalOutput := ""
259+
for _, fs := range fileStats {
260+
addn := float64(fs.Addition)
261+
deln := float64(fs.Deletion)
262+
adds := strings.Repeat("+", int(math.Floor(addn/scaleFactor)))
263+
dels := strings.Repeat("-", int(math.Floor(deln/scaleFactor)))
264+
finalOutput += fmt.Sprintf(" %s | %d %s%s\n", fs.Name, (fs.Addition + fs.Deletion), adds, dels)
265+
}
266+
267+
return finalOutput
268+
}
269+
270+
func getFileStatsFromFilePatches(filePatches []fdiff.FilePatch) FileStats {
271+
var fileStats FileStats
272+
273+
for _, fp := range filePatches {
274+
cs := FileStat{}
275+
from, to := fp.Files()
276+
if from == nil {
277+
// New File is created.
278+
cs.Name = to.Path()
279+
} else if to == nil {
280+
// File is deleted.
281+
cs.Name = from.Path()
282+
} else if from.Path() != to.Path() {
283+
// File is renamed. Not supported.
284+
// cs.Name = fmt.Sprintf("%s => %s", from.Path(), to.Path())
285+
} else {
286+
cs.Name = from.Path()
287+
}
288+
289+
for _, chunk := range fp.Chunks() {
290+
switch chunk.Type() {
291+
case fdiff.Add:
292+
cs.Addition += strings.Count(chunk.Content(), "\n")
293+
case fdiff.Delete:
294+
cs.Deletion += strings.Count(chunk.Content(), "\n")
295+
}
296+
}
297+
298+
fileStats = append(fileStats, cs)
299+
}
300+
301+
return fileStats
302+
}

0 commit comments

Comments
 (0)