feat: support .gitignore files #19
11
internal/cache/cache.go
vendored
11
internal/cache/cache.go
vendored
|
@ -9,6 +9,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.numtide.com/numtide/treefmt/internal/walk"
|
||||||
|
|
||||||
"git.numtide.com/numtide/treefmt/internal/format"
|
"git.numtide.com/numtide/treefmt/internal/format"
|
||||||
"github.com/charmbracelet/log"
|
"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.
|
// 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.
|
// 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 tx *bolt.Tx
|
||||||
var bucket *bolt.Bucket
|
var bucket *bolt.Bucket
|
||||||
var processed int
|
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 {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return ctx.Err()
|
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
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"git.numtide.com/numtide/treefmt/internal/walk"
|
||||||
"github.com/alecthomas/kong"
|
"github.com/alecthomas/kong"
|
||||||
"github.com/charmbracelet/log"
|
"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."`
|
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."`
|
Formatters []string `help:"Specify formatters to apply. Defaults to all formatters."`
|
||||||
brianmcgee marked this conversation as resolved
Outdated
|
|||||||
TreeRoot string `type:"existingdir" default:"."`
|
TreeRoot string `type:"existingdir" default:"."`
|
||||||
brianmcgee marked this conversation as resolved
Outdated
zimbatm
commented
The default could be added to the config file, or detected. The default could be added to the config file, or detected.
brianmcgee
commented
Made some relevant changes in Made some relevant changes in https://git.numtide.com/numtide/treefmt/commit/bcc4902ed849d0742e9be3f49c2ae327a709b375
|
|||||||
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."`
|
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:"."`
|
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
zimbatm
commented
You didn't want to use the go-git package for this? You didn't want to use the go-git package for this?
brianmcgee
commented
I tried with 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.
|
|||||||
|
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
The phrasing is a bit ambiguous. Maybe "Don't respect .gitignore files"?
That's a fair point.