feat: support .gitignore files #19
11
internal/cache/cache.go
vendored
11
internal/cache/cache.go
vendored
|
@ -9,6 +9,8 @@ import (
|
|||
"os"
|
||||
"time"
|
||||
|
||||
"git.numtide.com/numtide/treefmt/internal/walk"
|
||||
|
||||
"git.numtide.com/numtide/treefmt/internal/format"
|
||||
"github.com/charmbracelet/log"
|
||||
|
||||
|
@ -171,7 +173,7 @@ func putEntry(bucket *bolt.Bucket, path string, entry *Entry) error {
|
|||
|
||||
// ChangeSet is used to walk a filesystem, starting at root, and outputting any new or changed paths using pathsCh.
|
||||
// It determines if a path is new or has changed by comparing against cache entries.
|
||||
func ChangeSet(ctx context.Context, root string, walker string, pathsCh chan<- string) error {
|
||||
func ChangeSet(ctx context.Context, root string, walkerType walk.Type, pathsCh chan<- string) error {
|
||||
var tx *bolt.Tx
|
||||
var bucket *bolt.Bucket
|
||||
var processed int
|
||||
|
@ -183,7 +185,12 @@ func ChangeSet(ctx context.Context, root string, walker string, pathsCh chan<- s
|
|||
}
|
||||
}()
|
||||
|
||||
return walk(ctx, root, walker, func(path string, info fs.FileInfo, err error) error {
|
||||
w, err := walk.New(walkerType, root)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: failed to create walker", err)
|
||||
}
|
||||
|
||||
return w.Walk(ctx, func(path string, info fs.FileInfo, err error) error {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
|
|
83
internal/cache/walk.go
vendored
83
internal/cache/walk.go
vendored
|
@ -1,83 +0,0 @@
|
|||
package cache
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/charmbracelet/log"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
const (
|
||||
GitWalker = "git"
|
||||
FilesystemWalker = "filesystem"
|
||||
)
|
||||
|
||||
func walk(ctx context.Context, root string, walker string, fn filepath.WalkFunc) error {
|
||||
l := log.WithPrefix("walk")
|
||||
|
||||
if walker == GitWalker {
|
||||
// check if we're dealing with a git repository
|
||||
cmd := exec.Command("git", "-C", root, "rev-parse", "--is-inside-work-tree")
|
||||
_, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
l.Info("git repo check failed, falling back to filesystem", "err", err)
|
||||
walker = "filesystem"
|
||||
}
|
||||
}
|
||||
|
||||
l.Infof("walking %s with %s", root, walker)
|
||||
|
||||
switch walker {
|
||||
case GitWalker:
|
||||
return walkGit(ctx, root, fn)
|
||||
case FilesystemWalker:
|
||||
return filepath.Walk(root, fn)
|
||||
default:
|
||||
return fmt.Errorf("unknown walker: %s", walker)
|
||||
}
|
||||
}
|
||||
|
||||
func walkGit(ctx context.Context, root string, fn filepath.WalkFunc) error {
|
||||
r, w := io.Pipe()
|
||||
|
||||
cmd := exec.Command("git", "-C", root, "ls-files")
|
||||
cmd.Stdout = w
|
||||
cmd.Stderr = w
|
||||
|
||||
eg := errgroup.Group{}
|
||||
|
||||
eg.Go(func() error {
|
||||
scanner := bufio.NewScanner(r)
|
||||
|
||||
for scanner.Scan() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
line := scanner.Text()
|
||||
path := filepath.Join(root, line)
|
||||
|
||||
// stat the file
|
||||
info, err := os.Lstat(path)
|
||||
if err = fn(path, info, err); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err := w.CloseWithError(cmd.Run()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return eg.Wait()
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"git.numtide.com/numtide/treefmt/internal/walk"
|
||||
"github.com/alecthomas/kong"
|
||||
"github.com/charmbracelet/log"
|
||||
)
|
||||
|
@ -15,7 +16,7 @@ type Options struct {
|
|||
FailOnChange bool `help:"Exit with error if any changes were made. Useful for CI."`
|
||||
Formatters []string `help:"Specify formatters to apply. Defaults to all formatters."`
|
||||
TreeRoot string `type:"existingdir" default:"."`
|
||||
Walk string `enum:"filesystem,git" default:"git" help:"The method used to traverse the files within --tree-root. Currently supports 'git' or 'filesystem'."`
|
||||
Walk walk.Type `enum:"auto,git,filesystem" default:"auto" help:"The method used to traverse the files within --tree-root. Currently supports 'auto', 'git' or 'filesystem'."`
|
||||
Verbosity int `name:"verbose" short:"v" type:"counter" default:"0" env:"LOG_LEVEL" help:"Set the verbosity of logs e.g. -vv."`
|
||||
|
||||
Format Format `cmd:"" default:"."`
|
||||
|
|
22
internal/walk/filesystem.go
Normal file
22
internal/walk/filesystem.go
Normal file
|
@ -0,0 +1,22 @@
|
|||
package walk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type filesystem struct {
|
||||
root string
|
||||
}
|
||||
|
||||
func (f filesystem) Root() string {
|
||||
return f.root
|
||||
}
|
||||
|
||||
func (f filesystem) Walk(_ context.Context, fn filepath.WalkFunc) error {
|
||||
return filepath.Walk(f.root, fn)
|
||||
}
|
||||
|
||||
func NewFilesystem(root string) (Walker, error) {
|
||||
return filesystem{root}, nil
|
||||
}
|
69
internal/walk/git.go
Normal file
69
internal/walk/git.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
package walk
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type git struct {
|
||||
root string
|
||||
}
|
||||
|
||||
func (g *git) Root() string {
|
||||
return g.root
|
||||
}
|
||||
|
||||
func (g *git) Walk(ctx context.Context, fn filepath.WalkFunc) error {
|
||||
r, w := io.Pipe()
|
||||
|
||||
cmd := exec.Command("git", "-C", g.root, "ls-files")
|
||||
brianmcgee marked this conversation as resolved
|
||||
cmd.Stdout = w
|
||||
cmd.Stderr = w
|
||||
|
||||
eg := errgroup.Group{}
|
||||
|
||||
eg.Go(func() error {
|
||||
scanner := bufio.NewScanner(r)
|
||||
|
||||
for scanner.Scan() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
line := scanner.Text()
|
||||
path := filepath.Join(g.root, line)
|
||||
|
||||
// stat the file
|
||||
info, err := os.Lstat(path)
|
||||
if err = fn(path, info, err); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err := w.CloseWithError(cmd.Run()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
func NewGit(root string) (Walker, error) {
|
||||
// check if we're dealing with a git repository
|
||||
cmd := exec.Command("git", "-C", root, "rev-parse", "--is-inside-work-tree")
|
||||
_, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: git repo check failed", err)
|
||||
}
|
||||
return &git{root}, nil
|
||||
}
|
42
internal/walk/walker.go
Normal file
42
internal/walk/walker.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
package walk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type Type string
|
||||
|
||||
const (
|
||||
Git Type = "git"
|
||||
Auto Type = "auto"
|
||||
Filesystem Type = "filesystem"
|
||||
)
|
||||
|
||||
type Walker interface {
|
||||
Root() string
|
||||
Walk(ctx context.Context, fn filepath.WalkFunc) error
|
||||
}
|
||||
|
||||
func New(walkerType Type, root string) (Walker, error) {
|
||||
switch walkerType {
|
||||
case Git:
|
||||
return NewGit(root)
|
||||
case Auto:
|
||||
return Detect(root)
|
||||
case Filesystem:
|
||||
return NewFilesystem(root)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown walker type: %v", walkerType)
|
||||
}
|
||||
}
|
||||
|
||||
func Detect(root string) (Walker, error) {
|
||||
// for now, we keep it simple and try git first, filesystem second
|
||||
w, err := NewGit(root)
|
||||
if err == nil {
|
||||
return w, err
|
||||
}
|
||||
return NewFilesystem(root)
|
||||
}
|
Reference in New Issue
Block a user
You didn't want to use the go-git package for this?
I tried with
go-git
but couldn't find an efficient way or traversing the worktree. It worked fine on a small repo, but on nixpkgs it broke down badly. Will revisit this again in the future.