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

Add Stats() to Commit #613

Merged
merged 2 commits into from
Nov 8, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions plumbing/object/commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mcuadros I think I'm not using it the way you meant.
This is for the first commit scenario, no parent commit.
Without assigning a TreeHash I get panic: object not found.
And if I uncomment the above line, it panics with invalid memory address error.

Missing something really simple? 😅

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",
Expand Down
26 changes: 26 additions & 0 deletions plumbing/object/commit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
115 changes: 115 additions & 0 deletions plumbing/object/patch.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}