mirror of
https://github.com/numtide/treefmt.git
synced 2024-09-11 08:55:33 +03:00
Merge pull request #348 from numtide/fix/relative-path-resolution
fix: relative path resolution in filesystem walker
This commit is contained in:
commit
5afe6441e6
5
cache/cache.go
vendored
5
cache/cache.go
vendored
@ -218,11 +218,6 @@ func ChangeSet(ctx context.Context, walker walk.Walker, filesCh chan<- *walk.Fil
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore symlinks
|
|
||||||
if file.Info.Mode()&os.ModeSymlink == os.ModeSymlink {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// open a new read tx if there isn't one in progress
|
// open a new read tx if there isn't one in progress
|
||||||
// we have to periodically open a new read tx to prevent writes from being blocked
|
// we have to periodically open a new read tx to prevent writes from being blocked
|
||||||
if tx == nil {
|
if tx == nil {
|
||||||
|
@ -237,12 +237,9 @@ func (f *Format) walkFilesystem(ctx context.Context) func() error {
|
|||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
default:
|
default:
|
||||||
// ignore symlinks and directories
|
stats.Add(stats.Traversed, 1)
|
||||||
if !(file.Info.IsDir() || file.Info.Mode()&os.ModeSymlink == os.ModeSymlink) {
|
stats.Add(stats.Emitted, 1)
|
||||||
stats.Add(stats.Traversed, 1)
|
f.filesCh <- file
|
||||||
stats.Add(stats.Emitted, 1)
|
|
||||||
f.filesCh <- file
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -4,39 +4,45 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
type filesystemWalker struct {
|
type filesystemWalker struct {
|
||||||
root string
|
root string
|
||||||
pathsCh chan string
|
pathsCh chan string
|
||||||
|
relPathOffset int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f filesystemWalker) Root() string {
|
func (f filesystemWalker) Root() string {
|
||||||
return f.root
|
return f.root
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f filesystemWalker) Walk(_ context.Context, fn WalkFunc) error {
|
func (f filesystemWalker) relPath(path string) (string, error) {
|
||||||
relPathOffset := len(f.root) + 1
|
// quick optimization for the majority of use cases
|
||||||
|
if len(path) >= f.relPathOffset && path[:len(f.root)] == f.root {
|
||||||
relPathFn := func(path string) (string, error) {
|
return path[f.relPathOffset:], nil
|
||||||
// quick optimisation for the majority of use cases
|
|
||||||
// todo check that root is a prefix in path?
|
|
||||||
if len(path) >= relPathOffset {
|
|
||||||
return path[relPathOffset:], nil
|
|
||||||
}
|
|
||||||
return filepath.Rel(f.root, path)
|
|
||||||
}
|
}
|
||||||
|
// fallback to proper relative path resolution
|
||||||
|
return filepath.Rel(f.root, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f filesystemWalker) Walk(_ context.Context, fn WalkFunc) error {
|
||||||
walkFn := func(path string, info fs.FileInfo, _ error) error {
|
walkFn := func(path string, info fs.FileInfo, _ error) error {
|
||||||
if info == nil {
|
if info == nil {
|
||||||
return fmt.Errorf("no such file or directory '%s'", path)
|
return fmt.Errorf("no such file or directory '%s'", path)
|
||||||
}
|
}
|
||||||
|
|
||||||
relPath, err := relPathFn(path)
|
// ignore directories and symlinks
|
||||||
|
if info.IsDir() || info.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
relPath, err := f.relPath(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to determine a relative path for %s: %w", path, err)
|
return fmt.Errorf("failed to determine a relative path for %s: %w", path, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
file := File{
|
file := File{
|
||||||
Path: path,
|
Path: path,
|
||||||
RelPath: relPath,
|
RelPath: relPath,
|
||||||
@ -55,5 +61,9 @@ func (f filesystemWalker) Walk(_ context.Context, fn WalkFunc) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewFilesystem(root string, paths chan string) (Walker, error) {
|
func NewFilesystem(root string, paths chan string) (Walker, error) {
|
||||||
return filesystemWalker{root, paths}, nil
|
return filesystemWalker{
|
||||||
|
root: root,
|
||||||
|
pathsCh: paths,
|
||||||
|
relPathOffset: len(root) + 1,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
76
walk/filesystem_test.go
Normal file
76
walk/filesystem_test.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package walk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.numtide.com/numtide/treefmt/test"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
var examplesPaths = []string{
|
||||||
|
"elm/elm.json",
|
||||||
|
"elm/src/Main.elm",
|
||||||
|
"go/go.mod",
|
||||||
|
"go/main.go",
|
||||||
|
"haskell/CHANGELOG.md",
|
||||||
|
"haskell/Foo.hs",
|
||||||
|
"haskell/Main.hs",
|
||||||
|
"haskell/Nested/Foo.hs",
|
||||||
|
"haskell/Setup.hs",
|
||||||
|
"haskell/haskell.cabal",
|
||||||
|
"haskell/treefmt.toml",
|
||||||
|
"haskell-frontend/CHANGELOG.md",
|
||||||
|
"haskell-frontend/Main.hs",
|
||||||
|
"haskell-frontend/Setup.hs",
|
||||||
|
"haskell-frontend/haskell-frontend.cabal",
|
||||||
|
"html/index.html",
|
||||||
|
"html/scripts/.gitkeep",
|
||||||
|
"javascript/source/hello.js",
|
||||||
|
"nix/sources.nix",
|
||||||
|
"nixpkgs.toml",
|
||||||
|
"python/main.py",
|
||||||
|
"python/requirements.txt",
|
||||||
|
"python/virtualenv_proxy.py",
|
||||||
|
"ruby/bundler.rb",
|
||||||
|
"rust/Cargo.toml",
|
||||||
|
"rust/src/main.rs",
|
||||||
|
"shell/foo.sh",
|
||||||
|
"terraform/main.tf",
|
||||||
|
"terraform/two.tf",
|
||||||
|
"touch.toml",
|
||||||
|
"treefmt.toml",
|
||||||
|
"yaml/test.yaml",
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilesystemWalker_Walk(t *testing.T) {
|
||||||
|
tempDir := test.TempExamples(t)
|
||||||
|
|
||||||
|
paths := make(chan string, 1)
|
||||||
|
go func() {
|
||||||
|
paths <- tempDir
|
||||||
|
close(paths)
|
||||||
|
}()
|
||||||
|
|
||||||
|
as := require.New(t)
|
||||||
|
|
||||||
|
walker, err := NewFilesystem(tempDir, paths)
|
||||||
|
as.NoError(err)
|
||||||
|
|
||||||
|
idx := 0
|
||||||
|
err = walker.Walk(context.Background(), func(file *File, err error) error {
|
||||||
|
as.Equal(examplesPaths[idx], file.RelPath)
|
||||||
|
idx += 1
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
as.NoError(err)
|
||||||
|
|
||||||
|
// capture current cwd, so we can replace it after the test is finished
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
as.NoError(err)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
// return to the previous working directory
|
||||||
|
as.NoError(os.Chdir(cwd))
|
||||||
|
})
|
||||||
|
}
|
57
walk/git.go
57
walk/git.go
@ -13,26 +13,26 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type gitWalker struct {
|
type gitWalker struct {
|
||||||
root string
|
root string
|
||||||
paths chan string
|
paths chan string
|
||||||
repo *git.Repository
|
repo *git.Repository
|
||||||
|
relPathOffset int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *gitWalker) Root() string {
|
func (g gitWalker) Root() string {
|
||||||
return g.root
|
return g.root
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *gitWalker) Walk(ctx context.Context, fn WalkFunc) error {
|
func (g gitWalker) relPath(path string) (string, error) {
|
||||||
// for quick relative paths
|
// quick optimization for the majority of use cases
|
||||||
relPathOffset := len(g.root) + 1
|
if len(path) >= g.relPathOffset && path[:len(g.root)] == g.root {
|
||||||
|
return path[g.relPathOffset:], nil
|
||||||
relPathFn := func(path string) (relPath string) {
|
|
||||||
if len(path) >= relPathOffset {
|
|
||||||
relPath = path[relPathOffset:]
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
// fallback to proper relative path resolution
|
||||||
|
return filepath.Rel(g.root, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gitWalker) Walk(ctx context.Context, fn WalkFunc) error {
|
||||||
idx, err := g.repo.Storer.Index()
|
idx, err := g.repo.Storer.Index()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to open git index: %w", err)
|
return fmt.Errorf("failed to open git index: %w", err)
|
||||||
@ -50,14 +50,28 @@ func (g *gitWalker) Walk(ctx context.Context, fn WalkFunc) error {
|
|||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
default:
|
default:
|
||||||
path := filepath.Join(g.root, entry.Name)
|
// we only want regular files, not directories or symlinks
|
||||||
|
if !entry.Mode.IsRegular() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// stat the file
|
// stat the file
|
||||||
|
path := filepath.Join(g.root, entry.Name)
|
||||||
|
|
||||||
info, err := os.Lstat(path)
|
info, err := os.Lstat(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to stat %s: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine a relative path
|
||||||
|
relPath, err := g.relPath(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to determine a relative path for %s: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
file := File{
|
file := File{
|
||||||
Path: path,
|
Path: path,
|
||||||
RelPath: relPathFn(path),
|
RelPath: relPath,
|
||||||
Info: info,
|
Info: info,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,9 +107,9 @@ func (g *gitWalker) Walk(ctx context.Context, fn WalkFunc) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
relPath, err := filepath.Rel(g.root, path)
|
relPath, err := g.relPath(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("failed to determine a relative path for %s: %w", path, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := cache[relPath]; !ok {
|
if _, ok := cache[relPath]; !ok {
|
||||||
@ -105,7 +119,7 @@ func (g *gitWalker) Walk(ctx context.Context, fn WalkFunc) error {
|
|||||||
|
|
||||||
file := File{
|
file := File{
|
||||||
Path: path,
|
Path: path,
|
||||||
RelPath: relPathFn(path),
|
RelPath: relPath,
|
||||||
Info: info,
|
Info: info,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,5 +135,10 @@ func NewGit(root string, paths chan string) (Walker, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to open git repo: %w", err)
|
return nil, fmt.Errorf("failed to open git repo: %w", err)
|
||||||
}
|
}
|
||||||
return &gitWalker{root, paths, repo}, nil
|
return &gitWalker{
|
||||||
|
root: root,
|
||||||
|
paths: paths,
|
||||||
|
repo: repo,
|
||||||
|
relPathOffset: len(root) + 1,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user