Skip to content

Commit 0cadba8

Browse files
Merge pull request #22 from augmentable-dev/todo-history
add ability to parse when a TODO was added, include in report
2 parents dd55f41 + f9ff939 commit 0cadba8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+4843
-10
lines changed

cmd/commands/todos.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
package commands
22

33
import (
4+
"context"
5+
"fmt"
46
"os"
57
"path/filepath"
8+
"sort"
9+
"time"
610

711
"github.com/augmentable-dev/tickgit/pkg/comments"
812
"github.com/augmentable-dev/tickgit/pkg/todos"
13+
"github.com/briandowns/spinner"
914
"github.com/spf13/cobra"
1015
"gopkg.in/src-d/go-git.v4"
16+
"gopkg.in/src-d/go-git.v4/plumbing/object"
1117
)
1218

1319
func init() {
@@ -20,6 +26,11 @@ var todosCmd = &cobra.Command{
2026
Long: `Scans a given git repository looking for any code comments with TODOs. Displays a report of all the TODO items found.`,
2127
Args: cobra.MaximumNArgs(1),
2228
Run: func(cmd *cobra.Command, args []string) {
29+
s := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
30+
s.Suffix = " finding TODOs"
31+
s.Writer = os.Stderr
32+
s.Start()
33+
2334
cwd, err := os.Getwd()
2435
handleError(err)
2536

@@ -47,6 +58,21 @@ var todosCmd = &cobra.Command{
4758
// handleError(err)
4859

4960
t := todos.NewToDos(comments)
61+
62+
ctx := context.Background()
63+
// timeout after 30 seconds
64+
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
65+
defer cancel()
66+
_, err = t.FindBlame(ctx, r, commit, func(commit *object.Commit, remaining int) {
67+
total := len(t)
68+
s.Suffix = fmt.Sprintf(" (%d/%d) %s: %s", total-remaining, total, commit.Hash, commit.Author.When)
69+
})
70+
71+
sort.Sort(&t)
72+
73+
handleError(err)
74+
75+
s.Stop()
5076
todos.WriteTodos(t, os.Stdout)
5177
},
5278
}

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ go 1.13
55
require (
66
github.com/agext/levenshtein v1.2.2 // indirect
77
github.com/augmentable-dev/lege v0.0.0-20191028004410-79cb985065a1
8+
github.com/briandowns/spinner v1.7.0
9+
github.com/dustin/go-humanize v1.0.0
810
github.com/google/go-cmp v0.3.1 // indirect
911
github.com/hashicorp/hcl/v2 v2.0.0
1012
github.com/mitchellh/go-wordwrap v1.0.0 // indirect

go.sum

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
1515
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
1616
github.com/augmentable-dev/lege v0.0.0-20191028004410-79cb985065a1 h1:NBe2//2MA/Z7X4wuKnHSIN+xI/oBTLYMJVc8VCwXK4o=
1717
github.com/augmentable-dev/lege v0.0.0-20191028004410-79cb985065a1/go.mod h1:DtuvAW6+SE9e44O6eLaMJp8PFiadmk6NfXslCKYCiB0=
18+
github.com/briandowns/spinner v1.7.0 h1:aan1hBBOoscry2TXAkgtxkJiq7Se0+9pt+TUWaPrB4g=
19+
github.com/briandowns/spinner v1.7.0/go.mod h1://Zf9tMcxfRUA36V23M6YGEAv+kECGfvpnLTnb8n4XQ=
1820
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
1921
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
2022
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
@@ -23,8 +25,12 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do
2325
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2426
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2527
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
28+
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
29+
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
2630
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
2731
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
32+
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
33+
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
2834
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
2935
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
3036
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
@@ -60,6 +66,10 @@ github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3v
6066
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
6167
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
6268
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
69+
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
70+
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
71+
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
72+
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
6373
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
6474
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
6575
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
@@ -133,6 +143,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
133143
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
134144
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
135145
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
146+
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
136147
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
137148
golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
138149
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e h1:D5TXcfTk7xF7hvieo4QErS3qqCB4teTffacDWr7CI+0=

pkg/comments/comments.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ var LanguageParseOptions map[Language]*lege.ParseOptions = map[Language]*lege.Pa
6161
}
6262

6363
// Comments is a list of comments
64-
type Comments []Comment
64+
type Comments []*Comment
6565

6666
// Comment represents a comment in a source code file
6767
type Comment struct {
@@ -96,7 +96,7 @@ func SearchFile(filePath string, reader io.ReadCloser) (Comments, error) {
9696
comments := make(Comments, 0)
9797
for _, c := range collections {
9898
comment := Comment{*c, filePath}
99-
comments = append(comments, comment)
99+
comments = append(comments, &comment)
100100
}
101101

102102
return comments, nil

pkg/todos/report.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@ import (
99
const DefaultTemplate = `
1010
{{- range $index, $todo := . }}
1111
{{ print "\u001b[33m" }}TODO{{ print "\u001b[0m" }}: {{ .String }}
12-
=> {{ with .Comment }}{{ .FilePath }}:{{ .StartLocation.Line }}:{{ .StartLocation.Pos }}{{ end }}
12+
=> {{ .Comment.FilePath }}:{{ .Comment.StartLocation.Line }}:{{ .Comment.StartLocation.Pos }}
13+
{{- if .Commit }}
14+
=> added {{ .TimeAgo }} by {{ .Commit.Author }} in {{ .Commit.Hash }}
15+
{{- end }}
1316
{{ else }}
1417
no todos 🎉
1518
{{- end }}
16-
{{ .Count }} TODOs Found 📝
19+
{{ len . }} TODOs Found 📝
1720
`
1821

1922
// WriteTodos renders a report of todos

pkg/todos/todos.go

Lines changed: 123 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,34 @@
11
package todos
22

33
import (
4+
"context"
45
"regexp"
56
"strings"
7+
"sync"
68

79
"github.com/augmentable-dev/tickgit/pkg/comments"
10+
"github.com/dustin/go-humanize"
11+
"gopkg.in/src-d/go-git.v4"
12+
"gopkg.in/src-d/go-git.v4/plumbing/object"
13+
"gopkg.in/src-d/go-git.v4/plumbing/storer"
814
)
915

1016
// ToDo represents a ToDo item
1117
type ToDo struct {
1218
comments.Comment
1319
String string
20+
Commit *object.Commit
1421
}
1522

1623
// ToDos represents a list of ToDo items
17-
type ToDos []ToDo
24+
type ToDos []*ToDo
1825

19-
// Count returns the number of todos
20-
func (t ToDos) Count() int {
21-
return len(t)
26+
// TimeAgo returns a human readable string indicating the time since the todo was added
27+
func (t *ToDo) TimeAgo() string {
28+
if t.Commit == nil {
29+
return "<unknown>"
30+
}
31+
return humanize.Time(t.Commit.Author.When)
2232
}
2333

2434
// NewToDo produces a pointer to a ToDo from a comment
@@ -39,10 +49,117 @@ func NewToDo(comment comments.Comment) *ToDo {
3949
func NewToDos(comments comments.Comments) ToDos {
4050
todos := make(ToDos, 0)
4151
for _, comment := range comments {
42-
todo := NewToDo(comment)
52+
todo := NewToDo(*comment)
4353
if todo != nil {
44-
todos = append(todos, *todo)
54+
todos = append(todos, todo)
4555
}
4656
}
4757
return todos
4858
}
59+
60+
// Len returns the number of todos
61+
func (t *ToDos) Len() int {
62+
return len(*t)
63+
}
64+
65+
// Less compares two todos by their creation time
66+
func (t *ToDos) Less(i, j int) bool {
67+
first := (*t)[i]
68+
second := (*t)[j]
69+
if first.Commit == nil || second.Commit == nil {
70+
return false
71+
}
72+
return first.Commit.Author.When.Before(second.Commit.Author.When)
73+
}
74+
75+
// Swap swaps two todoss
76+
func (t *ToDos) Swap(i, j int) {
77+
temp := (*t)[i]
78+
(*t)[i] = (*t)[j]
79+
(*t)[j] = temp
80+
}
81+
82+
// CountWithCommits returns the number of todos with an associated commit (in which that todo was added)
83+
func (t *ToDos) CountWithCommits() (count int) {
84+
for _, todo := range *t {
85+
if todo.Commit != nil {
86+
count++
87+
}
88+
}
89+
return count
90+
}
91+
92+
func (t *ToDo) existsInCommit(commit *object.Commit) (bool, error) {
93+
f, err := commit.File(t.FilePath)
94+
if err != nil {
95+
if err == object.ErrFileNotFound {
96+
return false, nil
97+
}
98+
return false, err
99+
}
100+
c, err := f.Contents()
101+
if err != nil {
102+
return false, err
103+
}
104+
contains := strings.Contains(c, t.Comment.String())
105+
return contains, nil
106+
}
107+
108+
// FindBlame sets the blame information on each todo in a set of todos
109+
func (t *ToDos) FindBlame(ctx context.Context, repo *git.Repository, from *object.Commit, cb func(*object.Commit, int)) error {
110+
commitIter, err := repo.Log(&git.LogOptions{
111+
From: from.Hash,
112+
})
113+
if err != nil {
114+
return err
115+
}
116+
defer commitIter.Close()
117+
118+
remainingTodos := *t
119+
prevCommit := from
120+
err = commitIter.ForEach(func(commit *object.Commit) error {
121+
if len(remainingTodos) == 0 {
122+
return storer.ErrStop
123+
}
124+
if commit.NumParents() > 1 {
125+
return nil
126+
}
127+
select {
128+
case <-ctx.Done():
129+
return nil
130+
default:
131+
newRemainingTodos := make(ToDos, 0)
132+
errs := make(chan error)
133+
var wg sync.WaitGroup
134+
var mux sync.Mutex
135+
for _, todo := range remainingTodos {
136+
wg.Add(1)
137+
go func(todo *ToDo, commit *object.Commit, errs chan error) {
138+
defer wg.Done()
139+
mux.Lock()
140+
exists, err := todo.existsInCommit(commit)
141+
if err != nil {
142+
errs <- err
143+
}
144+
mux.Unlock()
145+
if !exists { // if the todo doesn't exist in this commit, it was added in the previous commit (previous wrt the iterator, more recent in time)
146+
todo.Commit = prevCommit
147+
} else { // if the todo does exist in this commit, add it to the new list of remaining todos
148+
newRemainingTodos = append(newRemainingTodos, todo)
149+
}
150+
}(todo, commit, errs)
151+
}
152+
wg.Wait()
153+
if cb != nil {
154+
cb(commit, len(newRemainingTodos))
155+
}
156+
prevCommit = commit
157+
remainingTodos = newRemainingTodos
158+
return nil
159+
}
160+
})
161+
if err != nil {
162+
return err
163+
}
164+
return nil
165+
}

vendor/github.com/briandowns/spinner/.gitignore

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/briandowns/spinner/.travis.yml

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)