Skip to content

Commit d174bb9

Browse files
committed
refactor(kdev): Support several record extractors
1 parent 48aba2f commit d174bb9

File tree

4 files changed

+238
-38
lines changed

4 files changed

+238
-38
lines changed

cmd/kdev/main.go

+53-36
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@ import (
55
"fmt"
66
"io/fs"
77
"os"
8-
"os/exec"
8+
"path"
99
"path/filepath"
10+
"runtime/pprof"
1011
"slices"
11-
"strconv"
1212
"strings"
1313
"time"
1414

15+
"github.com/go-git/go-git/v5"
16+
"github.com/go-git/go-git/v5/plumbing/object"
1517
"github.com/martinlehoux/kagamigo/kcore"
1618
"github.com/samber/lo"
19+
"github.com/schollz/progressbar/v3"
20+
"golang.org/x/exp/slog"
1721
)
1822

1923
type Record struct {
@@ -23,25 +27,37 @@ type Record struct {
2327
date time.Time
2428
}
2529

30+
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
31+
var sortBy = flag.String("sort", "date", "Sort by date or random")
32+
var maxRecords = flag.Int("max", 25, "Maximum number of records to display")
33+
var repoPath = flag.String("repo", ".", "Path to the repository")
34+
var afterS = flag.String("after", "2022-09-01", "Only show records after this date")
35+
2636
func main() {
2737
var err error
28-
sortBy := flag.String("sort", "date", "Sort by date or random")
29-
maxRecords := flag.Int("max", 25, "Maximum number of records to display")
30-
repoPath := flag.String("repo", ".", "Path to the repository")
31-
afterS := flag.String("after", "2022-09-01", "Only show records after this date")
3238
flag.Parse()
3339
after, err := time.Parse(time.DateOnly, *afterS)
3440
kcore.Expect(err, "Error parsing date")
35-
excludes := []string{".venv", ".git", ".ruff_cache", "db_dumps", ".mypy_cache"}
41+
excludes := []string{".venv", ".git", ".ruff_cache", "db_dumps", ".mypy_cache", "uploads", "__pycache__", ".coverage"}
3642
keywords := []string{"# TODO"}
3743
records := []Record{}
38-
// repo, err := git.PlainOpen(root)
44+
if *cpuprofile != "" {
45+
f, err := os.Create(*cpuprofile)
46+
kcore.Expect(err, "Error creating CPU profile")
47+
kcore.Expect(pprof.StartCPUProfile(f), "Error starting CPU profile")
48+
defer pprof.StopCPUProfile()
49+
}
50+
slog.Info("Scanning repository", "repo", *repoPath, "sort", *sortBy, "max", *maxRecords)
51+
repo, err := git.PlainOpen(path.Join(*repoPath, ".git"))
52+
// repo, err := git.Clone(memory.NewStorage(), nil, &git.CloneOptions{URL: path.Join(*repoPath, ".git")})
3953
kcore.Expect(err, "Error opening repository")
40-
// ref, err := repo.Head()
54+
ref, err := repo.Head()
4155
kcore.Expect(err, "Error getting HEAD")
42-
// commit, err := repo.CommitObject(ref.Hash())
56+
head, err := repo.CommitObject(ref.Hash())
4357
kcore.Expect(err, "Error getting commit object")
58+
progress := progressbar.Default(-1, "Scanning")
4459
err = filepath.WalkDir(*repoPath, func(path string, d fs.DirEntry, err error) error {
60+
relativePath := strings.TrimPrefix(path, *repoPath+"/")
4561
if err != nil {
4662
return err
4763
}
@@ -52,7 +68,8 @@ func main() {
5268
return nil
5369
}
5470
if !d.IsDir() {
55-
return processFile(path, keywords, &records, *repoPath)
71+
kcore.Expect(progress.Add(1), "Error incrementing progress")
72+
return processFile(*repoPath, relativePath, keywords, &records, head)
5673
}
5774
return nil
5875
})
@@ -63,46 +80,46 @@ func main() {
6380
} else {
6481
slices.SortFunc(records, func(a, b Record) int { return -int(a.date.Sub(b.date).Nanoseconds()) })
6582
}
83+
fmt.Println("")
6684
for _, record := range records[:*maxRecords] {
6785
fmt.Printf("%s\t %s:%d\n", record.date.Format("2006-01-02"), record.path, record.line)
6886
}
6987
}
7088

71-
func processFile(path string, keywords []string, records *[]Record, repoPath string) error {
72-
content, err := os.ReadFile(path) // #nosec G304
89+
type MatchingLine struct {
90+
line int
91+
keyword string
92+
}
93+
94+
func processFile(repoPath string, relativePath string, keywords []string, records *[]Record, head *object.Commit) error {
95+
absolutePath := path.Join(repoPath, relativePath)
96+
content, err := os.ReadFile(absolutePath) // #nosec G304
7397
if err != nil {
7498
return err
7599
}
100+
76101
lines := strings.Split(string(content), "\n")
77-
for i, line := range lines {
102+
matchingLines := lo.FilterMap(lines, func(line string, i int) (MatchingLine, bool) {
78103
for _, keyword := range keywords {
79104
if strings.Contains(line, keyword) {
80-
*records = append(*records, extractRecord(keyword, path, i+1, repoPath))
105+
return MatchingLine{i + 1, keyword}, true
81106
}
82107
}
108+
return MatchingLine{}, false
109+
})
110+
if len(matchingLines) == 0 {
111+
return nil
83112
}
84-
return nil
85-
}
86113

87-
func extractRecord(keyword string, path string, line int, repoPath string) Record {
88-
record := Record{
89-
keyword: keyword,
90-
path: path,
91-
line: line,
92-
}
93-
cmd := exec.Command("git", "blame", "-L", fmt.Sprintf("%d,%d", record.line, record.line), "--porcelain", path) // #nosec G204
94-
cmd.Dir = repoPath
95-
output, err := cmd.Output()
96-
if err != nil {
97-
panic(kcore.Wrap(err, "Error running git blame"))
114+
recordExtractor := NewStatRecordExtractor(absolutePath)
115+
// recordExtractor = NewGoGitRecordExtractor(head, relativePath)
116+
// recordExtractor := &GitRecordExtractor{repoPath: repoPath}
117+
if recordExtractor == nil {
118+
return nil
98119
}
99-
for _, blameLine := range strings.Split(string(output), "\n") {
100-
if strings.HasPrefix(blameLine, "author-time ") {
101-
timestamp, err := strconv.ParseInt(strings.TrimPrefix(blameLine, "author-time "), 10, 64)
102-
kcore.Expect(err, "Error parsing timestamp")
103-
record.date = time.Unix(timestamp, 0)
104-
break
105-
}
120+
for _, line := range matchingLines {
121+
*records = append(*records, recordExtractor.Extract(line.keyword, relativePath, line.line))
106122
}
107-
return record
123+
124+
return nil
108125
}

cmd/kdev/record_extractor.go

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"io/fs"
6+
"os"
7+
"os/exec"
8+
"strconv"
9+
"strings"
10+
"time"
11+
12+
"github.com/go-git/go-git/v5"
13+
"github.com/go-git/go-git/v5/plumbing/object"
14+
"github.com/martinlehoux/kagamigo/kcore"
15+
)
16+
17+
type RecordExtractor interface {
18+
Extract(keyword string, path string, line int) Record
19+
}
20+
21+
type StatRecordExtractor struct {
22+
fileInfo fs.FileInfo
23+
}
24+
25+
func NewStatRecordExtractor(absolutePath string) *StatRecordExtractor {
26+
fileInfo, err := os.Stat(absolutePath)
27+
kcore.Expect(err, "Error getting file info")
28+
29+
return &StatRecordExtractor{
30+
fileInfo: fileInfo,
31+
}
32+
}
33+
34+
func (re *StatRecordExtractor) Extract(keyword string, path string, line int) Record {
35+
return Record{
36+
keyword: keyword,
37+
path: path,
38+
line: line,
39+
date: re.fileInfo.ModTime(),
40+
}
41+
}
42+
43+
type GoGitRecordExtractor struct {
44+
blame *git.BlameResult
45+
}
46+
47+
func NewGoGitRecordExtractor(head *object.Commit, path string) *GoGitRecordExtractor {
48+
blame, err := git.Blame(head, path)
49+
if err == object.ErrFileNotFound {
50+
return nil
51+
}
52+
return &GoGitRecordExtractor{
53+
blame: blame,
54+
}
55+
}
56+
57+
func (re *GoGitRecordExtractor) Extract(keyword string, path string, line int) Record {
58+
return Record{
59+
keyword: keyword,
60+
path: path,
61+
line: line,
62+
date: re.blame.Lines[line-1].Date,
63+
}
64+
}
65+
66+
type GitRecordExtractor struct {
67+
repoPath string
68+
}
69+
70+
func (re *GitRecordExtractor) Extract(keyword string, path string, line int) Record {
71+
record := Record{
72+
keyword: keyword,
73+
path: path,
74+
line: line,
75+
}
76+
gitArgs := []string{"blame", "-L", fmt.Sprintf("%d,%d", line, line), "--porcelain", path}
77+
cmd := exec.Command("git", gitArgs...) // #nosec G204
78+
cmd.Dir = re.repoPath
79+
output, err := cmd.Output()
80+
81+
if err != nil {
82+
panic(kcore.Wrap(err, fmt.Sprintf("Error running: git %s", strings.Join(gitArgs, " "))))
83+
}
84+
85+
for _, blameLine := range strings.Split(string(output), "\n") {
86+
if strings.HasPrefix(blameLine, "author-time ") {
87+
timestamp, err := strconv.ParseInt(strings.TrimPrefix(blameLine, "author-time "), 10, 64)
88+
kcore.Expect(err, "Error parsing timestamp")
89+
record.date = time.Unix(timestamp, 0)
90+
return record
91+
}
92+
}
93+
kcore.Assert(false, "No date found in git blame output")
94+
return Record{}
95+
}

go.mod

+21-1
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ toolchain go1.22.6
77
require (
88
github.com/a-h/templ v0.2.778
99
github.com/fzipp/gocyclo v0.6.0
10+
github.com/go-git/go-git/v5 v5.12.0
1011
github.com/gofrs/uuid v4.4.0+incompatible
1112
github.com/golangci/golangci-lint v1.61.0
1213
github.com/kataras/i18n v0.0.8
1314
github.com/samber/lo v1.47.0
15+
github.com/schollz/progressbar/v3 v3.16.0
1416
github.com/securego/gosec/v2 v2.21.4
1517
github.com/stretchr/testify v1.9.0
1618
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0
@@ -28,6 +30,7 @@ require (
2830
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
2931
cloud.google.com/go/compute/metadata v0.5.1 // indirect
3032
cloud.google.com/go/longrunning v0.5.7 // indirect
33+
dario.cat/mergo v1.0.0 // indirect
3134
github.com/4meepo/tagalign v1.3.4 // indirect
3235
github.com/Abirdcfly/dupword v0.1.1 // indirect
3336
github.com/Antonboom/errname v0.1.13 // indirect
@@ -38,7 +41,9 @@ require (
3841
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect
3942
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect
4043
github.com/Masterminds/semver/v3 v3.3.0 // indirect
44+
github.com/Microsoft/go-winio v0.6.1 // indirect
4145
github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect
46+
github.com/ProtonMail/go-crypto v1.0.0 // indirect
4247
github.com/alecthomas/go-check-sumtype v0.1.4 // indirect
4348
github.com/alexkohler/nakedret/v2 v2.0.4 // indirect
4449
github.com/alexkohler/prealloc v1.0.0 // indirect
@@ -59,10 +64,13 @@ require (
5964
github.com/charithe/durationcheck v0.0.10 // indirect
6065
github.com/chavacava/garif v0.1.0 // indirect
6166
github.com/ckaznocha/intrange v0.2.0 // indirect
67+
github.com/cloudflare/circl v1.3.7 // indirect
6268
github.com/curioswitch/go-reassign v0.2.0 // indirect
69+
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
6370
github.com/daixiang0/gci v0.13.5 // indirect
6471
github.com/davecgh/go-spew v1.1.1 // indirect
6572
github.com/denis-tingaikin/go-header v0.5.0 // indirect
73+
github.com/emirpasic/gods v1.18.1 // indirect
6674
github.com/ettle/strcase v0.2.0 // indirect
6775
github.com/fatih/color v1.17.0 // indirect
6876
github.com/fatih/structtag v1.2.0 // indirect
@@ -71,6 +79,8 @@ require (
7179
github.com/fsnotify/fsnotify v1.7.0 // indirect
7280
github.com/ghostiam/protogetter v0.3.6 // indirect
7381
github.com/go-critic/go-critic v0.11.4 // indirect
82+
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
83+
github.com/go-git/go-billy/v5 v5.5.0 // indirect
7484
github.com/go-logr/logr v1.4.2 // indirect
7585
github.com/go-logr/stdr v1.2.2 // indirect
7686
github.com/go-toolsmith/astcast v1.1.0 // indirect
@@ -109,12 +119,14 @@ require (
109119
github.com/hashicorp/hcl v1.0.0 // indirect
110120
github.com/hexops/gotextdiff v1.0.3 // indirect
111121
github.com/inconshreveable/mousetrap v1.1.0 // indirect
122+
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
112123
github.com/jgautheron/goconst v1.7.1 // indirect
113124
github.com/jingyugao/rowserrcheck v1.1.1 // indirect
114125
github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect
115126
github.com/jjti/go-spancheck v0.6.2 // indirect
116127
github.com/julz/importas v0.1.0 // indirect
117128
github.com/karamaru-alpha/copyloopvar v1.1.0 // indirect
129+
github.com/kevinburke/ssh_config v1.2.0 // indirect
118130
github.com/kisielk/errcheck v1.7.0 // indirect
119131
github.com/kkHAIKE/contextcheck v1.1.5 // indirect
120132
github.com/kulti/thelper v0.6.3 // indirect
@@ -132,9 +144,10 @@ require (
132144
github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect
133145
github.com/mattn/go-colorable v0.1.13 // indirect
134146
github.com/mattn/go-isatty v0.0.20 // indirect
135-
github.com/mattn/go-runewidth v0.0.9 // indirect
147+
github.com/mattn/go-runewidth v0.0.16 // indirect
136148
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
137149
github.com/mgechev/revive v1.3.9 // indirect
150+
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
138151
github.com/mitchellh/go-homedir v1.1.0 // indirect
139152
github.com/mitchellh/mapstructure v1.5.0 // indirect
140153
github.com/moricho/tparallel v0.3.2 // indirect
@@ -145,6 +158,7 @@ require (
145158
github.com/olekukonko/tablewriter v0.0.5 // indirect
146159
github.com/pelletier/go-toml v1.9.5 // indirect
147160
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
161+
github.com/pjbgf/sha1cd v0.3.0 // indirect
148162
github.com/pmezard/go-difflib v1.0.0 // indirect
149163
github.com/polyfloyd/go-errorlint v1.6.0 // indirect
150164
github.com/prometheus/client_golang v1.12.1 // indirect
@@ -156,16 +170,19 @@ require (
156170
github.com/quasilyte/gogrep v0.5.0 // indirect
157171
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
158172
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect
173+
github.com/rivo/uniseg v0.4.7 // indirect
159174
github.com/ryancurrah/gomodguard v1.3.5 // indirect
160175
github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect
161176
github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect
162177
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect
163178
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
164179
github.com/sashamelentyev/usestdlibvars v1.27.0 // indirect
180+
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
165181
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect
166182
github.com/sirupsen/logrus v1.9.3 // indirect
167183
github.com/sivchari/containedctx v1.0.3 // indirect
168184
github.com/sivchari/tenv v1.10.0 // indirect
185+
github.com/skeema/knownhosts v1.2.2 // indirect
169186
github.com/sonatard/noctx v0.0.2 // indirect
170187
github.com/sourcegraph/go-diff v0.7.0 // indirect
171188
github.com/spf13/afero v1.11.0 // indirect
@@ -187,6 +204,7 @@ require (
187204
github.com/ultraware/funlen v0.1.0 // indirect
188205
github.com/ultraware/whitespace v0.1.1 // indirect
189206
github.com/uudashr/gocognit v1.1.3 // indirect
207+
github.com/xanzy/ssh-agent v0.3.3 // indirect
190208
github.com/xen0n/gosmopolitan v1.2.2 // indirect
191209
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
192210
github.com/yagipy/maintidx v1.0.0 // indirect
@@ -211,6 +229,7 @@ require (
211229
golang.org/x/oauth2 v0.23.0 // indirect
212230
golang.org/x/sync v0.8.0 // indirect
213231
golang.org/x/sys v0.25.0 // indirect
232+
golang.org/x/term v0.24.0 // indirect
214233
golang.org/x/text v0.18.0 // indirect
215234
golang.org/x/time v0.6.0 // indirect
216235
google.golang.org/api v0.198.0 // indirect
@@ -219,6 +238,7 @@ require (
219238
google.golang.org/grpc v1.66.2 // indirect
220239
google.golang.org/protobuf v1.34.2 // indirect
221240
gopkg.in/ini.v1 v1.67.0 // indirect
241+
gopkg.in/warnings.v0 v0.1.2 // indirect
222242
gopkg.in/yaml.v2 v2.4.0 // indirect
223243
mvdan.cc/gofumpt v0.7.0 // indirect
224244
mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect

0 commit comments

Comments
 (0)