summaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
authorMartin Kagamino Lehoux <martin@lehoux.net>2025-07-20 14:16:34 +0200
committerMartin Kagamino Lehoux <martin@lehoux.net>2025-07-20 14:16:34 +0200
commit6a7215688babd968edc05d0ddb2caaefe92a5ca3 (patch)
treecb6b4a756331d11dbab7bf6af932deeaebd11856 /cmd
parent972dd7453f8429d291bfb4dc7cba58a8900cd7cb (diff)
refactor(kdev): Extract algo & interfaces
Diffstat (limited to 'cmd')
-rw-r--r--cmd/kdev/main.go51
-rw-r--r--cmd/kdev/record_extractor.go92
2 files changed, 97 insertions, 46 deletions
diff --git a/cmd/kdev/main.go b/cmd/kdev/main.go
index e72b37c..8163041 100644
--- a/cmd/kdev/main.go
+++ b/cmd/kdev/main.go
@@ -13,8 +13,6 @@ import (
"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/samber/lo/mutable"
@@ -25,10 +23,10 @@ import (
)
type Record struct {
- keyword string
- path string
- line int
- date time.Time
+ keyword string
+ relativePath string
+ line int
+ date time.Time
}
var (
@@ -108,13 +106,10 @@ func main() {
}
slog.Info("Scanning repository", "repo", repoPath, "sort", sortBy, "max", maxRecords, "after", after.Format(time.DateOnly), "before", before.Format(time.DateOnly), "algo", algo)
- repo, err := git.PlainOpen(path.Join(repoPath, ".git"))
- kcore.Expect(err, "Error opening repository")
- ref, err := repo.Head()
- kcore.Expect(err, "Error getting HEAD")
- head, err := repo.CommitObject(ref.Hash())
- kcore.Expect(err, "Error getting commit object")
- kcore.Expect(walkRepo(keywords, head, &records), "Error walking directory")
+ factory, err := CreateRecordExtractorFactory(algo, repoPath)
+ kcore.Expect(err, "Error creating record extractor factory")
+
+ kcore.Expect(walkRepo(keywords, factory, &records), "Error walking directory")
if !after.IsZero() {
records = lo.Filter(records, func(record Record, index int) bool { return record.date.After(after) })
@@ -129,7 +124,7 @@ func main() {
}
fmt.Println("")
for _, record := range records[:min(maxRecords, len(records))] {
- fmt.Printf("%s\t %s:%d\n", record.date.Format("2006-01-02"), record.path, record.line)
+ fmt.Printf("%s\t %s:%d\n", record.date.Format(time.DateOnly), record.relativePath, record.line)
}
}
@@ -161,7 +156,7 @@ func isBinaryFile(absolutePath, relativePath string) bool {
return false
}
-func walkRepo(keywords []string, head *object.Commit, records *[]Record) error {
+func walkRepo(keywords []string, factory RecordExtractorFactory, records *[]Record) error {
progress := progressbar.Default(-1, "Scanning")
return filepath.WalkDir(repoPath, func(path string, d fs.DirEntry, err error) error {
relativePath := strings.TrimPrefix(path, repoPath)
@@ -186,7 +181,7 @@ func walkRepo(keywords []string, head *object.Commit, records *[]Record) error {
return nil
}
kcore.Expect(progress.Add(1), "Error incrementing progress")
- return processFile(repoPath, relativePath, keywords, records, head)
+ return processFile(repoPath, relativePath, keywords, records, factory)
})
}
@@ -195,7 +190,7 @@ type MatchingLine struct {
keyword string
}
-func processFile(repoPath string, relativePath string, keywords []string, records *[]Record, head *object.Commit) error {
+func processFile(repoPath string, relativePath string, keywords []string, records *[]Record, factory RecordExtractorFactory) error {
absolutePath := path.Join(repoPath, relativePath)
content, err := os.ReadFile(absolutePath) // #nosec G304
if err != nil {
@@ -215,22 +210,22 @@ func processFile(repoPath string, relativePath string, keywords []string, record
return nil
}
- // TODO: Extract outer
- var recordExtractor RecordExtractor
- switch algo {
- case "git":
- recordExtractor = &GitRecordExtractor{repoPath: repoPath}
- case "go-git":
- recordExtractor = NewGoGitRecordExtractor(head, relativePath)
- case "stat":
- recordExtractor = NewStatRecordExtractor(absolutePath)
+ recordExtractor, err := factory.CreateExtractor(relativePath)
+ switch err {
+ case ErrNotTracked:
+ return nil
+ case nil:
+ break
+ default:
+ return kcore.Wrap(err, "Error creating record extractor")
}
- kcore.Assert(recordExtractor != nil, "wrong algo value (git, go-git, stat)")
+
for _, line := range matchingLines {
- record, err := recordExtractor.Extract(line.keyword, relativePath, line.line)
+ record, err := recordExtractor.Extract(line.keyword, line.line)
if err != nil {
slog.Warn("Error extracting record", "err", err)
} else {
+ record.relativePath = relativePath
*records = append(*records, record)
}
}
diff --git a/cmd/kdev/record_extractor.go b/cmd/kdev/record_extractor.go
index a44d6c6..05a5e5c 100644
--- a/cmd/kdev/record_extractor.go
+++ b/cmd/kdev/record_extractor.go
@@ -5,6 +5,7 @@ import (
"io/fs"
"os"
"os/exec"
+ "path"
"strconv"
"strings"
"time"
@@ -14,27 +15,84 @@ import (
"github.com/martinlehoux/kagamigo/kcore"
)
+type RecordExtractorFactory interface {
+ CreateExtractor(relativePath string) (RecordExtractor, error)
+}
+
type RecordExtractor interface {
- Extract(keyword string, path string, line int) (Record, error)
+ Extract(keyword string, line int) (Record, error)
}
-type StatRecordExtractor struct {
- fileInfo fs.FileInfo
+var ErrNotTracked = fmt.Errorf("file is not tracked by git")
+
+func CreateRecordExtractorFactory(algo, repoPath string) (RecordExtractorFactory, error) {
+ switch algo {
+ case "git":
+ return &GitRecordExtractorFactory{repoPath: repoPath}, nil
+ case "go-git":
+ return NewGoGitRecordExtractorFactory(repoPath)
+ case "stat":
+ return &StatRecordExtractorFactory{repoPath: repoPath}, nil
+ default:
+ return nil, fmt.Errorf("unsupported algorithm: %s", algo)
+ }
+}
+
+type GitRecordExtractorFactory struct {
+ repoPath string
+}
+
+func (f *GitRecordExtractorFactory) CreateExtractor(relativePath string) (RecordExtractor, error) {
+ cmd := exec.Command("git", "ls-files", "--error-unmatch", relativePath)
+ cmd.Dir = f.repoPath
+ if err := cmd.Run(); err != nil {
+ return nil, ErrNotTracked
+ }
+ return &GitRecordExtractor{repoPath: f.repoPath, relativePath: relativePath}, nil
+}
+
+type GoGitRecordExtractorFactory struct {
+ head *object.Commit
+}
+
+func NewGoGitRecordExtractorFactory(repoPath string) (*GoGitRecordExtractorFactory, error) {
+ repo, err := git.PlainOpen(path.Join(repoPath, ".git"))
+ kcore.Expect(err, "Error opening repository")
+ ref, err := repo.Head()
+ kcore.Expect(err, "Error getting HEAD")
+ head, err := repo.CommitObject(ref.Hash())
+ kcore.Expect(err, "Error getting commit object")
+ return &GoGitRecordExtractorFactory{head: head}, nil
}
-func NewStatRecordExtractor(absolutePath string) *StatRecordExtractor {
- fileInfo, err := os.Stat(absolutePath)
+func (f *GoGitRecordExtractorFactory) CreateExtractor(relativePath string) (RecordExtractor, error) {
+ blame, err := git.Blame(f.head, relativePath)
+ if err == object.ErrFileNotFound {
+ return nil, err
+ }
+ return &GoGitRecordExtractor{blame: blame}, nil
+}
+
+type StatRecordExtractorFactory struct {
+ repoPath string
+}
+
+func (f *StatRecordExtractorFactory) CreateExtractor(relativePath string) (RecordExtractor, error) {
+ fileInfo, err := os.Stat(path.Join(f.repoPath, relativePath))
kcore.Expect(err, "Error getting file info")
return &StatRecordExtractor{
fileInfo: fileInfo,
- }
+ }, nil
}
-func (re *StatRecordExtractor) Extract(keyword string, path string, line int) (Record, error) {
+type StatRecordExtractor struct {
+ fileInfo fs.FileInfo
+}
+
+func (re *StatRecordExtractor) Extract(keyword string, line int) (Record, error) {
return Record{
keyword: keyword,
- path: path,
line: line,
date: re.fileInfo.ModTime(),
}, nil
@@ -44,8 +102,8 @@ type GoGitRecordExtractor struct {
blame *git.BlameResult
}
-func NewGoGitRecordExtractor(head *object.Commit, path string) *GoGitRecordExtractor {
- blame, err := git.Blame(head, path)
+func NewGoGitRecordExtractor(head *object.Commit, relativePath string) *GoGitRecordExtractor {
+ blame, err := git.Blame(head, relativePath)
if err == object.ErrFileNotFound {
return nil
}
@@ -54,32 +112,30 @@ func NewGoGitRecordExtractor(head *object.Commit, path string) *GoGitRecordExtra
}
}
-func (re *GoGitRecordExtractor) Extract(keyword string, path string, line int) (Record, error) {
+func (re *GoGitRecordExtractor) Extract(keyword string, line int) (Record, error) {
record := Record{
keyword: keyword,
- path: path,
line: line,
+ date: re.blame.Lines[line-1].Date,
}
- record.date = re.blame.Lines[line-1].Date
return record, nil
}
type GitRecordExtractor struct {
- repoPath string
+ repoPath string
+ relativePath string
}
-func (re *GitRecordExtractor) Extract(keyword string, path string, line int) (Record, error) {
+func (re *GitRecordExtractor) Extract(keyword string, line int) (Record, error) {
record := Record{
keyword: keyword,
- path: path,
line: line,
}
- gitArgs := []string{"blame", "-L", fmt.Sprintf("%d,%d", line, line), "--porcelain", path}
+ gitArgs := []string{"blame", "-L", fmt.Sprintf("%d,%d", line, line), "--porcelain", re.relativePath}
cmd := exec.Command("git", gitArgs...) // #nosec G204
cmd.Dir = re.repoPath
output, err := cmd.Output()
-
if err != nil {
return record, kcore.Wrap(err, fmt.Sprintf("Error running: git %s", strings.Join(gitArgs, " ")))
}