Repository: numtide/treefmt (go)

Issue:
Git walker ignores any files which don't have the mode 100644
**Describe the bug**

This causes executable files (like shellscripts) to not get formatted.

**To Reproduce**

Steps to reproduce the behavior:

1. Create a shell script which is formatted according to shfmt, make it executable
2. Add config for shfmt in treefmt.toml
3. Run treefmt

**Expected behavior**

The shellscript is formatted according to shfmt

**System information**

treefmt v2.0.4
OS: Linux

**Additional context**

These lines seem to be ignoring too much:  
https://github.com/numtide/treefmt/blob/1427cc2d98d5270f5720e5edac1f34e10c602876/walk/git.go#L54-L57

Perhaps a check explicitly for directories and symlinks would be good.


Pull request:
fix: Do not exclude executables in git walker
Fixes #388 

Patch:
diff --git a/walk/git.go b/walk/git.go
index f0c5b52..08465a8 100644
--- a/walk/git.go
+++ b/walk/git.go
@@ -10,6 +10,7 @@ import (
 	"github.com/charmbracelet/log"
 
 	"github.com/go-git/go-git/v5"
+	"github.com/go-git/go-git/v5/plumbing/filemode"
 )
 
 type gitWalker struct {
@@ -52,7 +53,7 @@ func (g gitWalker) Walk(ctx context.Context, fn WalkFunc) error {
 					return ctx.Err()
 				default:
 					// we only want regular files, not directories or symlinks
-					if !entry.Mode.IsRegular() {
+					if entry.Mode == filemode.Dir || entry.Mode == filemode.Symlink {
 						continue
 					}
 


Test patch:
diff --git a/cli/format_test.go b/cli/format_test.go
index b8532c3..86be79b 100644
--- a/cli/format_test.go
+++ b/cli/format_test.go
@@ -274,37 +274,34 @@ func TestCache(t *testing.T) {
 		},
 	}
 
-	var (
-		out []byte
-		err error
-	)
+	var err error
 
 	test.WriteConfig(t, configPath, cfg)
 	_, err = cmd(t, "--config-file", configPath, "--tree-root", tempDir)
 	as.NoError(err)
 	assertStats(t, as, 32, 32, 32, 0)
 
-	out, err = cmd(t, "--config-file", configPath, "--tree-root", tempDir)
+	_, err = cmd(t, "--config-file", configPath, "--tree-root", tempDir)
 	as.NoError(err)
-	assertFormatted(t, as, out, 0)
+	assertStats(t, as, 32, 0, 0, 0)
 
 	// clear cache
 	_, err = cmd(t, "--config-file", configPath, "--tree-root", tempDir, "-c")
 	as.NoError(err)
 	assertStats(t, as, 32, 32, 32, 0)
 
-	out, err = cmd(t, "--config-file", configPath, "--tree-root", tempDir)
+	_, err = cmd(t, "--config-file", configPath, "--tree-root", tempDir)
 	as.NoError(err)
-	assertFormatted(t, as, out, 0)
+	assertStats(t, as, 32, 0, 0, 0)
 
 	// clear cache
 	_, err = cmd(t, "--config-file", configPath, "--tree-root", tempDir, "-c")
 	as.NoError(err)
 	assertStats(t, as, 32, 32, 32, 0)
 
-	out, err = cmd(t, "--config-file", configPath, "--tree-root", tempDir)
+	_, err = cmd(t, "--config-file", configPath, "--tree-root", tempDir)
 	as.NoError(err)
-	assertFormatted(t, as, out, 0)
+	assertStats(t, as, 32, 0, 0, 0)
 
 	// no cache
 	_, err = cmd(t, "--config-file", configPath, "--tree-root", tempDir, "--no-cache")
@@ -516,10 +513,10 @@ func TestGitWorktree(t *testing.T) {
 	wt, err := repo.Worktree()
 	as.NoError(err, "failed to get git worktree")
 
-	run := func(traversed int, emitted int, matched int, formatted int) {
-		out, err := cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
+	run := func(traversed int32, emitted int32, matched int32, formatted int32) {
+		_, err := cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
 		as.NoError(err)
-		assertFormatted(t, as, out, formatted)
+		assertStats(t, as, traversed, emitted, matched, formatted)
 	}
 
 	// run before adding anything to the worktree
@@ -532,11 +529,11 @@ func TestGitWorktree(t *testing.T) {
 
 	// remove python directory from the worktree
 	as.NoError(wt.RemoveGlob("python/*"))
-	run(28, 28, 28, 0)
+	run(29, 29, 29, 0)
 
 	// remove nixpkgs.toml from the filesystem but leave it in the index
 	as.NoError(os.Remove(filepath.Join(tempDir, "nixpkgs.toml")))
-	run(27, 27, 27, 0)
+	run(28, 28, 28, 0)
 
 	// walk with filesystem instead of git
 	_, err = cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir, "--walk", "filesystem")
diff --git a/cli/helpers_test.go b/cli/helpers_test.go
index 4779cea..7c4fe02 100644
--- a/cli/helpers_test.go
+++ b/cli/helpers_test.go
@@ -83,8 +83,3 @@ func assertStats(t *testing.T, as *require.Assertions, traversed int32, emitted
 	as.Equal(matched, stats.Value(stats.Matched), "stats.matched")
 	as.Equal(formatted, stats.Value(stats.Formatted), "stats.formatted")
 }
-
-func assertFormatted(t *testing.T, as *require.Assertions, output []byte, count int) {
-	t.Helper()
-	as.Contains(string(output), fmt.Sprintf("(%d changed)", count))
-}
diff --git a/config/config_test.go b/config/config_test.go
index abb9344..69cc4df 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -107,22 +107,23 @@ func TestReadConfigFile(t *testing.T) {
 	as.Equal([]string{"*.rs"}, rust.Includes)
 	as.Nil(rust.Excludes)
 
-	// shell
-	shell, ok := cfg.Formatters["shell"]
-	as.True(ok, "shell formatter not found")
-	as.Equal("/bin/sh", shell.Command)
-	as.Equal([]string{
-		"-euc",
-		`# First lint all the scripts
-shellcheck "$@"
-
-# Then format them
-shfmt -i 2 -s -w "$@"
-    `,
-		"--",
-	}, shell.Options)
-	as.Equal([]string{"*.sh"}, shell.Includes)
-	as.Nil(shell.Excludes)
+	// shellcheck
+	shellcheck, ok := cfg.Formatters["shellcheck"]
+	as.True(ok, "shellcheck formatter not found")
+	as.Equal("shellcheck", shellcheck.Command)
+	as.Equal(1, shellcheck.Priority)
+	as.Nil(shellcheck.Options)
+	as.Equal([]string{"*.sh"}, shellcheck.Includes)
+	as.Nil(shellcheck.Excludes)
+
+	//shfmt
+	shfmt, ok := cfg.Formatters["shfmt"]
+	as.True(ok, "shfmt formatter not found")
+	as.Equal("shfmt", shfmt.Command)
+	as.Equal(2, shfmt.Priority)
+	as.Equal(shfmt.Options, []string{"-i", "2", "-s", "-w"})
+	as.Equal([]string{"*.sh"}, shfmt.Includes)
+	as.Nil(shfmt.Excludes)
 
 	// terraform
 	terraform, ok := cfg.Formatters["terraform"]
diff --git a/test/examples/treefmt.toml b/test/examples/treefmt.toml
index 56cffbd..687dd6d 100644
--- a/test/examples/treefmt.toml
+++ b/test/examples/treefmt.toml
@@ -69,20 +69,16 @@ command = "rustfmt"
 options = ["--edition", "2018"]
 includes = ["*.rs"]
 
-[formatter.shell]
-command = "/bin/sh"
-options = [
-    "-euc",
-    """
-# First lint all the scripts
-shellcheck "$@"
-
-# Then format them
-shfmt -i 2 -s -w "$@"
-    """,
-    "--", # bash swallows the second argument when using -c
-]
+[formatter.shellcheck]
+command = "shellcheck"
+includes = ["*.sh"]
+priority = 1
+
+[formatter.shfmt]
+command = "shfmt"
+options = ["-i", "2", "-s", "-w"]
 includes = ["*.sh"]
+priority = 2
 
 [formatter.terraform]
 # Careful, only terraform 1.3.0 or later accept a list of files.