diff options
author | Máximo Cuadros <mcuadros@gmail.com> | 2017-11-08 12:43:36 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-11-08 12:43:36 +0100 |
commit | e2791ac8605432dbdc5085fc165ace2c9e487f23 (patch) | |
tree | 70d50e19ed4652265175a09d0a5a0f12d2195cfe /plumbing/object | |
parent | 8ab19f6f035b0f85cd70fa6e8ca9e93d02656cf7 (diff) | |
parent | 11162e11c830fec51517d86835a884a5547a4f16 (diff) | |
download | go-git-e2791ac8605432dbdc5085fc165ace2c9e487f23.tar.gz |
Merge pull request #613 from darkowlzz/482-commit-stats
Add Stats() to Commit
Diffstat (limited to 'plumbing/object')
-rw-r--r-- | plumbing/object/commit.go | 26 | ||||
-rw-r--r-- | plumbing/object/commit_test.go | 26 | ||||
-rw-r--r-- | plumbing/object/patch.go | 115 |
3 files changed, 167 insertions, 0 deletions
diff --git a/plumbing/object/commit.go b/plumbing/object/commit.go index b70128c..fad3dac 100644 --- a/plumbing/object/commit.go +++ b/plumbing/object/commit.go @@ -265,6 +265,32 @@ func (b *Commit) Encode(o plumbing.EncodedObject) error { return err } +// Stats shows the status of commit. +func (c *Commit) Stats() (FileStats, error) { + // Get the previous commit. + ci := c.Parents() + parentCommit, err := ci.Next() + if err != nil { + if err == io.EOF { + emptyNoder := treeNoder{} + parentCommit = &Commit{ + Hash: emptyNoder.hash, + // TreeHash: emptyNoder.parent.Hash, + s: c.s, + } + } else { + return nil, err + } + } + + patch, err := parentCommit.Patch(c) + if err != nil { + return nil, err + } + + return getFileStatsFromFilePatches(patch.FilePatches()), nil +} + func (c *Commit) String() string { return fmt.Sprintf( "%s %s\nAuthor: %s\nDate: %s\n\n%s\n", diff --git a/plumbing/object/commit_test.go b/plumbing/object/commit_test.go index 2d04e77..e84160b 100644 --- a/plumbing/object/commit_test.go +++ b/plumbing/object/commit_test.go @@ -258,3 +258,29 @@ RUysgqjcpT8+iQM1PblGfHR4XAhuOqN5Fx06PSaFZhqvWFezJ28/CLyX5q+oIVk= c.Assert(err, IsNil) c.Assert(decoded.PGPSignature, Equals, pgpsignature) } + +func (s *SuiteCommit) TestStat(c *C) { + aCommit := s.commit(c, plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")) + fileStats, err := aCommit.Stats() + c.Assert(err, IsNil) + + c.Assert(fileStats[0].Name, Equals, "vendor/foo.go") + c.Assert(fileStats[0].Addition, Equals, 7) + c.Assert(fileStats[0].Deletion, Equals, 0) + c.Assert(fileStats[0].String(), Equals, " vendor/foo.go | 7 +++++++\n") + + // Stats for another commit. + aCommit = s.commit(c, plumbing.NewHash("918c48b83bd081e863dbe1b80f8998f058cd8294")) + fileStats, err = aCommit.Stats() + c.Assert(err, IsNil) + + c.Assert(fileStats[0].Name, Equals, "go/example.go") + c.Assert(fileStats[0].Addition, Equals, 142) + c.Assert(fileStats[0].Deletion, Equals, 0) + c.Assert(fileStats[0].String(), Equals, " go/example.go | 142 +++++++++++++++++++++++++++++++++++++++++++++++++++++\n") + + c.Assert(fileStats[1].Name, Equals, "php/crappy.php") + c.Assert(fileStats[1].Addition, Equals, 259) + c.Assert(fileStats[1].Deletion, Equals, 0) + c.Assert(fileStats[1].String(), Equals, " php/crappy.php | 259 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n") +} diff --git a/plumbing/object/patch.go b/plumbing/object/patch.go index d413114..a920631 100644 --- a/plumbing/object/patch.go +++ b/plumbing/object/patch.go @@ -4,6 +4,8 @@ import ( "bytes" "fmt" "io" + "math" + "strings" "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing/filemode" @@ -105,6 +107,10 @@ func (p *Patch) Encode(w io.Writer) error { return ue.Encode(p) } +func (p *Patch) Stats() FileStats { + return getFileStatsFromFilePatches(p.FilePatches()) +} + func (p *Patch) String() string { buf := bytes.NewBuffer(nil) err := p.Encode(buf) @@ -185,3 +191,112 @@ func (t *textChunk) Content() string { func (t *textChunk) Type() fdiff.Operation { return t.op } + +// FileStat stores the status of changes in content of a file. +type FileStat struct { + Name string + Addition int + Deletion int +} + +func (fs FileStat) String() string { + return printStat([]FileStat{fs}) +} + +// FileStats is a collection of FileStat. +type FileStats []FileStat + +func (fileStats FileStats) String() string { + return printStat(fileStats) +} + +func printStat(fileStats []FileStat) string { + padLength := float64(len(" ")) + newlineLength := float64(len("\n")) + separatorLength := float64(len("|")) + // Soft line length limit. The text length calculation below excludes + // length of the change number. Adding that would take it closer to 80, + // but probably not more than 80, until it's a huge number. + lineLength := 72.0 + + // Get the longest filename and longest total change. + var longestLength float64 + var longestTotalChange float64 + for _, fs := range fileStats { + if int(longestLength) < len(fs.Name) { + longestLength = float64(len(fs.Name)) + } + totalChange := fs.Addition + fs.Deletion + if int(longestTotalChange) < totalChange { + longestTotalChange = float64(totalChange) + } + } + + // Parts of the output: + // <pad><filename><pad>|<pad><changeNumber><pad><+++/---><newline> + // example: " main.go | 10 +++++++--- " + + // <pad><filename><pad> + leftTextLength := padLength + longestLength + padLength + + // <pad><number><pad><+++++/-----><newline> + // Excluding number length here. + rightTextLength := padLength + padLength + newlineLength + + totalTextArea := leftTextLength + separatorLength + rightTextLength + heightOfHistogram := lineLength - totalTextArea + + // Scale the histogram. + var scaleFactor float64 + if longestTotalChange > heightOfHistogram { + // Scale down to heightOfHistogram. + scaleFactor = float64(longestTotalChange / heightOfHistogram) + } else { + scaleFactor = 1.0 + } + + finalOutput := "" + for _, fs := range fileStats { + addn := float64(fs.Addition) + deln := float64(fs.Deletion) + adds := strings.Repeat("+", int(math.Floor(addn/scaleFactor))) + dels := strings.Repeat("-", int(math.Floor(deln/scaleFactor))) + finalOutput += fmt.Sprintf(" %s | %d %s%s\n", fs.Name, (fs.Addition + fs.Deletion), adds, dels) + } + + return finalOutput +} + +func getFileStatsFromFilePatches(filePatches []fdiff.FilePatch) FileStats { + var fileStats FileStats + + for _, fp := range filePatches { + cs := FileStat{} + from, to := fp.Files() + if from == nil { + // New File is created. + cs.Name = to.Path() + } else if to == nil { + // File is deleted. + cs.Name = from.Path() + } else if from.Path() != to.Path() { + // File is renamed. Not supported. + // cs.Name = fmt.Sprintf("%s => %s", from.Path(), to.Path()) + } else { + cs.Name = from.Path() + } + + for _, chunk := range fp.Chunks() { + switch chunk.Type() { + case fdiff.Add: + cs.Addition += strings.Count(chunk.Content(), "\n") + case fdiff.Delete: + cs.Deletion += strings.Count(chunk.Content(), "\n") + } + } + + fileStats = append(fileStats, cs) + } + + return fileStats +} |