Skip to content

Commit

Permalink
refactor(kdev): Support several record extractors
Browse files Browse the repository at this point in the history
  • Loading branch information
martinlehoux committed Oct 4, 2024
1 parent 48aba2f commit d174bb9
Show file tree
Hide file tree
Showing 4 changed files with 238 additions and 38 deletions.
89 changes: 53 additions & 36 deletions cmd/kdev/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,19 @@ import (
"fmt"
"io/fs"
"os"
"os/exec"
"path"
"path/filepath"
"runtime/pprof"
"slices"
"strconv"
"strings"
"time"

"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/martinlehoux/kagamigo/kcore"
"github.com/samber/lo"
"github.com/schollz/progressbar/v3"
"golang.org/x/exp/slog"
)

type Record struct {
Expand All @@ -23,25 +27,37 @@ type Record struct {
date time.Time
}

var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
var sortBy = flag.String("sort", "date", "Sort by date or random")
var maxRecords = flag.Int("max", 25, "Maximum number of records to display")
var repoPath = flag.String("repo", ".", "Path to the repository")
var afterS = flag.String("after", "2022-09-01", "Only show records after this date")

func main() {
var err error
sortBy := flag.String("sort", "date", "Sort by date or random")
maxRecords := flag.Int("max", 25, "Maximum number of records to display")
repoPath := flag.String("repo", ".", "Path to the repository")
afterS := flag.String("after", "2022-09-01", "Only show records after this date")
flag.Parse()
after, err := time.Parse(time.DateOnly, *afterS)
kcore.Expect(err, "Error parsing date")
excludes := []string{".venv", ".git", ".ruff_cache", "db_dumps", ".mypy_cache"}
excludes := []string{".venv", ".git", ".ruff_cache", "db_dumps", ".mypy_cache", "uploads", "__pycache__", ".coverage"}
keywords := []string{"# TODO"}
records := []Record{}
// repo, err := git.PlainOpen(root)
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
kcore.Expect(err, "Error creating CPU profile")
kcore.Expect(pprof.StartCPUProfile(f), "Error starting CPU profile")
defer pprof.StopCPUProfile()
}
slog.Info("Scanning repository", "repo", *repoPath, "sort", *sortBy, "max", *maxRecords)
repo, err := git.PlainOpen(path.Join(*repoPath, ".git"))
// repo, err := git.Clone(memory.NewStorage(), nil, &git.CloneOptions{URL: path.Join(*repoPath, ".git")})
kcore.Expect(err, "Error opening repository")
// ref, err := repo.Head()
ref, err := repo.Head()
kcore.Expect(err, "Error getting HEAD")
// commit, err := repo.CommitObject(ref.Hash())
head, err := repo.CommitObject(ref.Hash())
kcore.Expect(err, "Error getting commit object")
progress := progressbar.Default(-1, "Scanning")
err = filepath.WalkDir(*repoPath, func(path string, d fs.DirEntry, err error) error {
relativePath := strings.TrimPrefix(path, *repoPath+"/")
if err != nil {
return err
}
Expand All @@ -52,7 +68,8 @@ func main() {
return nil
}
if !d.IsDir() {
return processFile(path, keywords, &records, *repoPath)
kcore.Expect(progress.Add(1), "Error incrementing progress")
return processFile(*repoPath, relativePath, keywords, &records, head)
}
return nil
})
Expand All @@ -63,46 +80,46 @@ func main() {
} else {
slices.SortFunc(records, func(a, b Record) int { return -int(a.date.Sub(b.date).Nanoseconds()) })
}
fmt.Println("")
for _, record := range records[:*maxRecords] {
fmt.Printf("%s\t %s:%d\n", record.date.Format("2006-01-02"), record.path, record.line)
}
}

func processFile(path string, keywords []string, records *[]Record, repoPath string) error {
content, err := os.ReadFile(path) // #nosec G304
type MatchingLine struct {
line int
keyword string
}

func processFile(repoPath string, relativePath string, keywords []string, records *[]Record, head *object.Commit) error {
absolutePath := path.Join(repoPath, relativePath)
content, err := os.ReadFile(absolutePath) // #nosec G304
if err != nil {
return err
}

lines := strings.Split(string(content), "\n")
for i, line := range lines {
matchingLines := lo.FilterMap(lines, func(line string, i int) (MatchingLine, bool) {
for _, keyword := range keywords {
if strings.Contains(line, keyword) {
*records = append(*records, extractRecord(keyword, path, i+1, repoPath))
return MatchingLine{i + 1, keyword}, true
}
}
return MatchingLine{}, false
})
if len(matchingLines) == 0 {
return nil
}
return nil
}

func extractRecord(keyword string, path string, line int, repoPath string) Record {
record := Record{
keyword: keyword,
path: path,
line: line,
}
cmd := exec.Command("git", "blame", "-L", fmt.Sprintf("%d,%d", record.line, record.line), "--porcelain", path) // #nosec G204
cmd.Dir = repoPath
output, err := cmd.Output()
if err != nil {
panic(kcore.Wrap(err, "Error running git blame"))
recordExtractor := NewStatRecordExtractor(absolutePath)
// recordExtractor = NewGoGitRecordExtractor(head, relativePath)
// recordExtractor := &GitRecordExtractor{repoPath: repoPath}
if recordExtractor == nil {
return nil
}
for _, blameLine := range strings.Split(string(output), "\n") {
if strings.HasPrefix(blameLine, "author-time ") {
timestamp, err := strconv.ParseInt(strings.TrimPrefix(blameLine, "author-time "), 10, 64)
kcore.Expect(err, "Error parsing timestamp")
record.date = time.Unix(timestamp, 0)
break
}
for _, line := range matchingLines {
*records = append(*records, recordExtractor.Extract(line.keyword, relativePath, line.line))
}
return record

return nil
}
95 changes: 95 additions & 0 deletions cmd/kdev/record_extractor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package main

import (
"fmt"
"io/fs"
"os"
"os/exec"
"strconv"
"strings"
"time"

"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/martinlehoux/kagamigo/kcore"
)

type RecordExtractor interface {
Extract(keyword string, path string, line int) Record
}

type StatRecordExtractor struct {
fileInfo fs.FileInfo
}

func NewStatRecordExtractor(absolutePath string) *StatRecordExtractor {
fileInfo, err := os.Stat(absolutePath)
kcore.Expect(err, "Error getting file info")

return &StatRecordExtractor{
fileInfo: fileInfo,
}
}

func (re *StatRecordExtractor) Extract(keyword string, path string, line int) Record {
return Record{
keyword: keyword,
path: path,
line: line,
date: re.fileInfo.ModTime(),
}
}

type GoGitRecordExtractor struct {
blame *git.BlameResult
}

func NewGoGitRecordExtractor(head *object.Commit, path string) *GoGitRecordExtractor {
blame, err := git.Blame(head, path)
if err == object.ErrFileNotFound {
return nil
}
return &GoGitRecordExtractor{
blame: blame,
}
}

func (re *GoGitRecordExtractor) Extract(keyword string, path string, line int) Record {
return Record{
keyword: keyword,
path: path,
line: line,
date: re.blame.Lines[line-1].Date,
}
}

type GitRecordExtractor struct {
repoPath string
}

func (re *GitRecordExtractor) Extract(keyword string, path string, line int) Record {
record := Record{
keyword: keyword,
path: path,
line: line,
}
gitArgs := []string{"blame", "-L", fmt.Sprintf("%d,%d", line, line), "--porcelain", path}
cmd := exec.Command("git", gitArgs...) // #nosec G204
cmd.Dir = re.repoPath
output, err := cmd.Output()

if err != nil {
panic(kcore.Wrap(err, fmt.Sprintf("Error running: git %s", strings.Join(gitArgs, " "))))
}

for _, blameLine := range strings.Split(string(output), "\n") {
if strings.HasPrefix(blameLine, "author-time ") {
timestamp, err := strconv.ParseInt(strings.TrimPrefix(blameLine, "author-time "), 10, 64)
kcore.Expect(err, "Error parsing timestamp")
record.date = time.Unix(timestamp, 0)
return record
}
}
kcore.Assert(false, "No date found in git blame output")
return Record{}
}
22 changes: 21 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ toolchain go1.22.6
require (
github.com/a-h/templ v0.2.778
github.com/fzipp/gocyclo v0.6.0
github.com/go-git/go-git/v5 v5.12.0
github.com/gofrs/uuid v4.4.0+incompatible
github.com/golangci/golangci-lint v1.61.0
github.com/kataras/i18n v0.0.8
github.com/samber/lo v1.47.0
github.com/schollz/progressbar/v3 v3.16.0
github.com/securego/gosec/v2 v2.21.4
github.com/stretchr/testify v1.9.0
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0
Expand All @@ -28,6 +30,7 @@ require (
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
cloud.google.com/go/compute/metadata v0.5.1 // indirect
cloud.google.com/go/longrunning v0.5.7 // indirect
dario.cat/mergo v1.0.0 // indirect
github.com/4meepo/tagalign v1.3.4 // indirect
github.com/Abirdcfly/dupword v0.1.1 // indirect
github.com/Antonboom/errname v0.1.13 // indirect
Expand All @@ -38,7 +41,9 @@ require (
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect
github.com/Masterminds/semver/v3 v3.3.0 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect
github.com/ProtonMail/go-crypto v1.0.0 // indirect
github.com/alecthomas/go-check-sumtype v0.1.4 // indirect
github.com/alexkohler/nakedret/v2 v2.0.4 // indirect
github.com/alexkohler/prealloc v1.0.0 // indirect
Expand All @@ -59,10 +64,13 @@ require (
github.com/charithe/durationcheck v0.0.10 // indirect
github.com/chavacava/garif v0.1.0 // indirect
github.com/ckaznocha/intrange v0.2.0 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/curioswitch/go-reassign v0.2.0 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/daixiang0/gci v0.13.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/denis-tingaikin/go-header v0.5.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/ettle/strcase v0.2.0 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/fatih/structtag v1.2.0 // indirect
Expand All @@ -71,6 +79,8 @@ require (
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/ghostiam/protogetter v0.3.6 // indirect
github.com/go-critic/go-critic v0.11.4 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.5.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-toolsmith/astcast v1.1.0 // indirect
Expand Down Expand Up @@ -109,12 +119,14 @@ require (
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jgautheron/goconst v1.7.1 // indirect
github.com/jingyugao/rowserrcheck v1.1.1 // indirect
github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect
github.com/jjti/go-spancheck v0.6.2 // indirect
github.com/julz/importas v0.1.0 // indirect
github.com/karamaru-alpha/copyloopvar v1.1.0 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/kisielk/errcheck v1.7.0 // indirect
github.com/kkHAIKE/contextcheck v1.1.5 // indirect
github.com/kulti/thelper v0.6.3 // indirect
Expand All @@ -132,9 +144,10 @@ require (
github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mgechev/revive v1.3.9 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moricho/tparallel v0.3.2 // indirect
Expand All @@ -145,6 +158,7 @@ require (
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/polyfloyd/go-errorlint v1.6.0 // indirect
github.com/prometheus/client_golang v1.12.1 // indirect
Expand All @@ -156,16 +170,19 @@ require (
github.com/quasilyte/gogrep v0.5.0 // indirect
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/ryancurrah/gomodguard v1.3.5 // indirect
github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect
github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
github.com/sashamelentyev/usestdlibvars v1.27.0 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sivchari/containedctx v1.0.3 // indirect
github.com/sivchari/tenv v1.10.0 // indirect
github.com/skeema/knownhosts v1.2.2 // indirect
github.com/sonatard/noctx v0.0.2 // indirect
github.com/sourcegraph/go-diff v0.7.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
Expand All @@ -187,6 +204,7 @@ require (
github.com/ultraware/funlen v0.1.0 // indirect
github.com/ultraware/whitespace v0.1.1 // indirect
github.com/uudashr/gocognit v1.1.3 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xen0n/gosmopolitan v1.2.2 // indirect
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
github.com/yagipy/maintidx v1.0.0 // indirect
Expand All @@ -211,6 +229,7 @@ require (
golang.org/x/oauth2 v0.23.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/term v0.24.0 // indirect
golang.org/x/text v0.18.0 // indirect
golang.org/x/time v0.6.0 // indirect
google.golang.org/api v0.198.0 // indirect
Expand All @@ -219,6 +238,7 @@ require (
google.golang.org/grpc v1.66.2 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
mvdan.cc/gofumpt v0.7.0 // indirect
mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect
Expand Down
Loading

0 comments on commit d174bb9

Please sign in to comment.