diff --git a/CLAUDE.md b/CLAUDE.md index b846c389..0eecccb4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -60,3 +60,4 @@ git diff → diff.ParseUnifiedDiff() → []DiffLine - Highlighted lines are pre-computed once per file load, stored parallel to `diffLines` - `DiffLine.Content` has no `+`/`-` prefix - prefix is re-added at render time - Tab replacement happens at render time in `renderDiffLine`, not in diff parsing +- `run()` resolves git repo root via `git rev-parse --show-toplevel` so revdiff works from any subdirectory diff --git a/cmd/revdiff/main.go b/cmd/revdiff/main.go index e0ba0457..11cec213 100644 --- a/cmd/revdiff/main.go +++ b/cmd/revdiff/main.go @@ -1,9 +1,11 @@ package main import ( + "context" "errors" "fmt" "os" + "os/exec" "path/filepath" "runtime" "strings" @@ -161,7 +163,11 @@ func defaultConfigPath() string { } func run(opts options) error { - renderer := diff.NewGit(".") + repoRoot, err := gitTopLevel() + if err != nil { + return fmt.Errorf("find git root: %w", err) + } + renderer := diff.NewGit(repoRoot) store := annotation.NewStore() hl := highlight.New(opts.ChromaStyle, !opts.NoColors) model := ui.NewModel(renderer, store, hl, ui.ModelConfig{ @@ -216,3 +222,17 @@ func run(opts options) error { fmt.Print(output) return nil } + +// gitTopLevel returns the root directory of the current git repository. +func gitTopLevel() (string, error) { + cmd := exec.CommandContext(context.Background(), "git", "rev-parse", "--show-toplevel") + out, err := cmd.Output() + if err != nil { + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { + return "", fmt.Errorf("git rev-parse --show-toplevel: %s", strings.TrimSpace(string(exitErr.Stderr))) + } + return "", fmt.Errorf("git rev-parse --show-toplevel: %w", err) + } + return strings.TrimSpace(string(out)), nil +} diff --git a/cmd/revdiff/main_test.go b/cmd/revdiff/main_test.go index c7414d83..eef51d24 100644 --- a/cmd/revdiff/main_test.go +++ b/cmd/revdiff/main_test.go @@ -232,3 +232,19 @@ func TestDefaultConfigPath(t *testing.T) { assert.Contains(t, path, "revdiff") assert.Contains(t, path, "config") } + +func TestGitTopLevel(t *testing.T) { + t.Run("inside repo", func(t *testing.T) { + root, err := gitTopLevel() + require.NoError(t, err) + assert.DirExists(t, root) + assert.NotEmpty(t, root) + }) + + t.Run("outside repo", func(t *testing.T) { + t.Chdir(t.TempDir()) + _, err := gitTopLevel() + require.Error(t, err) + assert.Contains(t, err.Error(), "git rev-parse --show-toplevel") + }) +}