From 11162e11c830fec51517d86835a884a5547a4f16 Mon Sep 17 00:00:00 2001 From: Sunny Date: Thu, 12 Oct 2017 00:31:16 +0530 Subject: Add Stats() to Patch and fix diffstat output --- plumbing/object/commit.go | 64 +++++------------------ plumbing/object/commit_test.go | 4 +- plumbing/object/patch.go | 115 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 130 insertions(+), 53 deletions(-) (limited to 'plumbing') diff --git a/plumbing/object/commit.go b/plumbing/object/commit.go index e5faa38..dfbb0d5 100644 --- a/plumbing/object/commit.go +++ b/plumbing/object/commit.go @@ -265,32 +265,22 @@ func (b *Commit) Encode(o plumbing.EncodedObject) error { return err } -// 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 { - totalChanges := fs.Addition + fs.Deletion - adds := strings.Repeat("+", fs.Addition) - dels := strings.Repeat("-", fs.Deletion) - return fmt.Sprintf(" %s | %d %s%s", fs.Name, totalChanges, adds, dels) -} - -// FileStats is a collection of FileStat. -type FileStats []FileStat - -// Stats shows the status +// Stats shows the status of commit. func (c *Commit) Stats() (FileStats, error) { - var fileStats FileStats - // Get the previous commit. ci := c.Parents() parentCommit, err := ci.Next() if err != nil { - return nil, err + 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) @@ -298,37 +288,7 @@ func (c *Commit) Stats() (FileStats, error) { return nil, err } - filePatches := patch.FilePatches() - 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. - cs.Name = fmt.Sprintf("%s => %s", from.Path(), to.Path()) - } else { - // File is modified. - cs.Name = from.Path() - } - - for _, chunk := range fp.Chunks() { - switch chunk.Type() { - case 1: - cs.Addition += strings.Count(chunk.Content(), "\n") - case 2: - cs.Deletion += strings.Count(chunk.Content(), "\n") - } - } - - fileStats = append(fileStats, cs) - } - - return fileStats, nil + return getFileStatsFromFilePatches(patch.FilePatches()), nil } func (c *Commit) String() string { diff --git a/plumbing/object/commit_test.go b/plumbing/object/commit_test.go index f17f1c6..e84160b 100644 --- a/plumbing/object/commit_test.go +++ b/plumbing/object/commit_test.go @@ -267,7 +267,7 @@ func (s *SuiteCommit) TestStat(c *C) { 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 +++++++") + c.Assert(fileStats[0].String(), Equals, " vendor/foo.go | 7 +++++++\n") // Stats for another commit. aCommit = s.commit(c, plumbing.NewHash("918c48b83bd081e863dbe1b80f8998f058cd8294")) @@ -277,8 +277,10 @@ func (s *SuiteCommit) TestStat(c *C) { 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: + // |<+++/---> + // example: " main.go | 10 +++++++--- " + + // + leftTextLength := padLength + longestLength + padLength + + // <+++++/-----> + // 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 +} -- cgit