Skip to content

Commit f24fc53

Browse files
authored
Merge pull request #588 from numtide/fix/default-tree-root-cmd
Fix defaulting of `tree-root-cmd`.
2 parents 60f791c + 02113ae commit f24fc53

File tree

6 files changed

+118
-35
lines changed

6 files changed

+118
-35
lines changed

cmd/root_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,10 @@ func TestConfigFile(t *testing.T) {
502502
t.Run(name, func(t *testing.T) {
503503
tempDir := test.TempExamples(t)
504504

505+
// change to a temp directory to avoid interference with config file and auto walk detection from
506+
// the treefmt repository
507+
test.ChangeWorkDir(t, t.TempDir())
508+
505509
// use a config file in a different temp directory
506510
configPath := filepath.Join(t.TempDir(), name)
507511

config/config.go

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616

1717
"github.com/charmbracelet/log"
1818
"github.com/google/shlex"
19+
"github.com/numtide/treefmt/v2/git"
1920
"github.com/numtide/treefmt/v2/walk"
2021
"github.com/spf13/pflag"
2122
"github.com/spf13/viper"
@@ -290,58 +291,68 @@ func determineTreeRoot(v *viper.Viper, cfg *Config, logger *log.Logger) error {
290291
return errors.New("at most one of tree-root, tree-root-cmd or tree-root-file can be specified")
291292
}
292293

293-
// set git-based tree root command if the walker is git and no tree root has been specified
294-
if cfg.Walk == walk.Git.String() && count == 0 {
295-
cfg.TreeRootCmd = "git rev-parse --show-toplevel"
296-
297-
logger.Infof(
298-
"git walker enabled and tree root has not been specified: defaulting tree-root-cmd to '%s'",
299-
cfg.TreeRootCmd,
300-
)
301-
}
302-
303294
switch {
304295
case cfg.TreeRoot != "":
305-
logger.Debugf("tree root specified explicitly: %s", cfg.TreeRoot)
296+
logger.Infof("tree root specified explicitly: %s", cfg.TreeRoot)
306297

307298
case cfg.TreeRootFile != "":
308-
logger.Debugf("searching for tree root using tree-root-file: %s", cfg.TreeRootFile)
299+
logger.Infof("searching for tree root using tree-root-file: %s", cfg.TreeRootFile)
309300

310301
_, cfg.TreeRoot, err = FindUp(cfg.WorkingDirectory, cfg.TreeRootFile)
311302
if err != nil {
312303
return fmt.Errorf("failed to find tree-root based on tree-root-file: %w", err)
313304
}
314305

315306
case cfg.TreeRootCmd != "":
316-
logger.Debugf("searching for tree root using tree-root-cmd: %s", cfg.TreeRootCmd)
307+
logger.Infof("searching for tree root using tree-root-cmd: %s", cfg.TreeRootCmd)
317308

318-
if cfg.TreeRoot, err = execTreeRootCmd(cfg); err != nil {
309+
if cfg.TreeRoot, err = execTreeRootCmd(cfg.TreeRootCmd, cfg.WorkingDirectory); err != nil {
319310
return err
320311
}
321312

322313
default:
323314
// no tree root was specified
324-
logger.Debugf(
325-
"no tree root specified, defaulting to the directory containing the config file: %s",
326-
v.ConfigFileUsed(),
327-
)
315+
logger.Infof("no tree root specified")
316+
317+
// attempt to resolve with git
318+
if cfg.Walk == walk.Auto.String() || cfg.Walk == walk.Git.String() {
319+
logger.Infof("attempting to resolve tree root using git: %s", git.TreeRootCmd)
320+
321+
// attempt to resolve the tree root with git
322+
cfg.TreeRoot, err = execTreeRootCmd(git.TreeRootCmd, cfg.WorkingDirectory)
323+
if err != nil && cfg.Walk == walk.Git.String() {
324+
return fmt.Errorf("failed to resolve tree root with git: %w", err)
325+
}
328326

329-
cfg.TreeRoot = filepath.Dir(v.ConfigFileUsed())
327+
if err != nil {
328+
logger.Infof("failed to resolve tree root with git: %v", err)
329+
}
330+
}
331+
332+
if cfg.TreeRoot == "" {
333+
// fallback to the directory containing the config file
334+
logger.Infof(
335+
"setting tree root to the directory containing the config file: %s",
336+
v.ConfigFileUsed(),
337+
)
338+
339+
cfg.TreeRoot = filepath.Dir(v.ConfigFileUsed())
340+
}
330341
}
331342

332343
// resolve tree root to an absolute path
333344
if cfg.TreeRoot, err = filepath.Abs(cfg.TreeRoot); err != nil {
334345
return fmt.Errorf("failed to get absolute path for tree root: %w", err)
335346
}
336347

337-
logger.Debugf("tree root: %s", cfg.TreeRoot)
348+
logger.Infof("tree root: %s", cfg.TreeRoot)
338349

339350
return nil
340351
}
341352

342-
func execTreeRootCmd(cfg *Config) (string, error) {
353+
func execTreeRootCmd(treeRootCmd string, workingDir string) (string, error) {
343354
// split the command first, resolving any '' and "" entries
344-
parts, splitErr := shlex.Split(cfg.TreeRootCmd)
355+
parts, splitErr := shlex.Split(treeRootCmd)
345356
if splitErr != nil {
346357
return "", fmt.Errorf("failed to parse tree-root-cmd: %w", splitErr)
347358
}
@@ -356,7 +367,7 @@ func execTreeRootCmd(cfg *Config) (string, error) {
356367
// construct the command, setting the correct working directory
357368
//nolint:gosec
358369
cmd := exec.CommandContext(ctx, parts[0], parts[1:]...)
359-
cmd.Dir = cfg.WorkingDirectory
370+
cmd.Dir = workingDir
360371

361372
// setup some pipes to capture stdout and stderr
362373
stdout, err := cmd.StdoutPipe()
@@ -428,13 +439,13 @@ func execTreeRootCmd(cfg *Config) (string, error) {
428439

429440
case 0:
430441
// no output was received on stdout
431-
return "", fmt.Errorf("empty output received after executing tree-root-cmd: %s", cfg.TreeRootCmd)
442+
return "", fmt.Errorf("empty output received after executing tree-root-cmd: %s", treeRootCmd)
432443

433444
default:
434445
// multiple lines received on stdout, dump the output to make it clear what happened and throw an error
435446
log.WithPrefix("tree-root-cmd | stdout").Errorf("\n%s", outputStr)
436447

437-
return "", fmt.Errorf("tree-root-cmd cannot output multiple lines: %s", cfg.TreeRootCmd)
448+
return "", fmt.Errorf("tree-root-cmd cannot output multiple lines: %s", treeRootCmd)
438449
}
439450
}
440451

config/config_test.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -617,12 +617,14 @@ func TestWorkingDirectory(t *testing.T) {
617617
checkValue(cwd)
618618

619619
// env override
620-
t.Setenv("TREEFMT_WORKING_DIR", "/fizz/buzz/..")
621-
checkValue("/fizz")
620+
cwd = t.TempDir()
621+
t.Setenv("TREEFMT_WORKING_DIR", cwd+"/buzz/..")
622+
checkValue(cwd)
622623

623624
// flag override
624-
as.NoError(flags.Set("working-dir", "/flip/flop"))
625-
checkValue("/flip/flop")
625+
cwd = t.TempDir()
626+
as.NoError(flags.Set("working-dir", cwd))
627+
checkValue(cwd)
626628
}
627629

628630
func TestStdin(t *testing.T) {

docs/site/getting-started/configure.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,38 @@ Defaults to the directory containing the config file.
306306
tree-root = "/tmp/foo"
307307
```
308308

309+
### `tree-root-cmd`
310+
311+
Command to run to find the tree root.
312+
It is parsed using [shlex](https://github.com/google/shlex/tree/master), to allow quoting arguments that contain whitespace.
313+
If you wish to pass arguments containing quotes, you should use nested quotes e.g. `"'"` or `'"'`.
314+
315+
!!!note
316+
317+
If [walk](#walk) is set to `git` and no tree root option has been defined, `tree-root-cmd` will be defaulted to
318+
`git rev-parse --show-toplevel`.
319+
320+
if [walk](#walk) is set to `auto` (the default), `treefmt` will check if the [working directory](#working-dir) is
321+
inside a git worktree. If it is, `tree-root-cmd` will be defaulted as described above for `git`.
322+
323+
=== "Flag"
324+
325+
```console
326+
treefmt --tree-root-cmd "git rev-parse --show-toplevel"
327+
```
328+
329+
=== "Env"
330+
331+
```console
332+
TREEFMT_TREE_ROOT_CMD="git rev-parse --show-toplevel" treefmt
333+
```
334+
335+
=== "Config"
336+
337+
```toml
338+
tree-root-cmd = "git rev-parse --show-toplevel"
339+
```
340+
309341
### `tree-root-file`
310342

311343
File to search for to find the tree root (if `tree-root` is not set)

git/git.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package git
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"os/exec"
7+
"strings"
8+
)
9+
10+
const TreeRootCmd = "git rev-parse --show-toplevel"
11+
12+
func IsInsideWorktree(path string) (bool, error) {
13+
// check if the root is a git repository
14+
cmd := exec.Command("git", "rev-parse", "--is-inside-work-tree")
15+
cmd.Dir = path
16+
17+
out, err := cmd.Output()
18+
if err != nil {
19+
var exitErr *exec.ExitError
20+
if errors.As(err, &exitErr) && strings.Contains(string(exitErr.Stderr), "not a git repository") {
21+
return false, nil
22+
}
23+
24+
return false, fmt.Errorf("failed to check if %s is a git repository: %w", path, err)
25+
}
26+
27+
if strings.Trim(string(out), "\n") != "true" {
28+
// not a git repo
29+
return false, nil
30+
}
31+
32+
// is a git repo
33+
return true, nil
34+
}

walk/git.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import (
99
"os/exec"
1010
"path/filepath"
1111
"strconv"
12-
"strings"
1312

1413
"github.com/charmbracelet/log"
14+
"github.com/numtide/treefmt/v2/git"
1515
"github.com/numtide/treefmt/v2/stats"
1616
"golang.org/x/sync/errgroup"
1717
)
@@ -143,12 +143,12 @@ func NewGitReader(
143143
statz *stats.Stats,
144144
) (*GitReader, error) {
145145
// check if the root is a git repository
146-
cmd := exec.Command("git", "rev-parse", "--is-inside-work-tree")
147-
cmd.Dir = root
148-
149-
if out, err := cmd.Output(); err != nil {
146+
isGit, err := git.IsInsideWorktree(root)
147+
if err != nil {
150148
return nil, fmt.Errorf("failed to check if %s is a git repository: %w", root, err)
151-
} else if strings.Trim(string(out), "\n") != "true" {
149+
}
150+
151+
if !isGit {
152152
return nil, fmt.Errorf("%s is not a git repository", root)
153153
}
154154

0 commit comments

Comments
 (0)