mirror of
https://github.com/numtide/treefmt.git
synced 2024-08-17 04:40:27 +03:00
126 lines
2.4 KiB
Go
126 lines
2.4 KiB
Go
package walk
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/charmbracelet/log"
|
|
|
|
"github.com/go-git/go-git/v5"
|
|
)
|
|
|
|
type gitWalker struct {
|
|
root string
|
|
paths chan string
|
|
repo *git.Repository
|
|
}
|
|
|
|
func (g *gitWalker) Root() string {
|
|
return g.root
|
|
}
|
|
|
|
func (g *gitWalker) Walk(ctx context.Context, fn WalkFunc) error {
|
|
// for quick relative paths
|
|
relPathOffset := len(g.root) + 1
|
|
|
|
relPathFn := func(path string) (relPath string) {
|
|
if len(path) >= relPathOffset {
|
|
relPath = path[relPathOffset:]
|
|
}
|
|
return
|
|
}
|
|
|
|
idx, err := g.repo.Storer.Index()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open git index: %w", err)
|
|
}
|
|
|
|
// cache in-memory whether a path is present in the git index
|
|
var cache map[string]bool
|
|
|
|
for path := range g.paths {
|
|
|
|
if path == g.root {
|
|
// we can just iterate the index entries
|
|
for _, entry := range idx.Entries {
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
default:
|
|
path := filepath.Join(g.root, entry.Name)
|
|
|
|
// stat the file
|
|
info, err := os.Lstat(path)
|
|
|
|
file := File{
|
|
Path: path,
|
|
RelPath: relPathFn(path),
|
|
Info: info,
|
|
}
|
|
|
|
if err = fn(&file, err); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
|
|
// otherwise we ensure the git index entries are cached and then check if they are in the git index
|
|
if cache == nil {
|
|
cache = make(map[string]bool)
|
|
for _, entry := range idx.Entries {
|
|
cache[entry.Name] = true
|
|
}
|
|
}
|
|
|
|
relPath, err := filepath.Rel(g.root, path)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to find relative path for %v: %w", path, err)
|
|
}
|
|
|
|
_, ok := cache[relPath]
|
|
if !(path == g.root || ok) {
|
|
log.Debugf("path %v not found in git index, skipping", path)
|
|
continue
|
|
}
|
|
|
|
return filepath.Walk(path, func(path string, info fs.FileInfo, err error) error {
|
|
if info.IsDir() {
|
|
return nil
|
|
}
|
|
|
|
relPath, err := filepath.Rel(g.root, path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, ok := cache[relPath]; !ok {
|
|
log.Debugf("path %v not found in git index, skipping", path)
|
|
return nil
|
|
}
|
|
|
|
file := File{
|
|
Path: path,
|
|
RelPath: relPathFn(path),
|
|
Info: info,
|
|
}
|
|
|
|
return fn(&file, err)
|
|
})
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func NewGit(root string, paths chan string) (Walker, error) {
|
|
repo, err := git.PlainOpen(root)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open git repo: %w", err)
|
|
}
|
|
return &gitWalker{root, paths, repo}, nil
|
|
}
|