From 1acc86dffe8f151f70572060b7aead1e8f4ac3df Mon Sep 17 00:00:00 2001 From: appleboy Date: Fri, 28 Nov 2025 20:45:01 +0800 Subject: [PATCH] feat: validate git repository before executing commands - Add a check to ensure the current directory is a valid git repository and can execute git diff before running commands - Update command logic to pass context to the new check function - Implement CanExecuteGitDiff method to verify git repository status - Add a test suite for CanExecuteGitDiff to cover repository, non-repository, and subdirectory cases Signed-off-by: appleboy --- cmd/commit.go | 2 +- cmd/hepler.go | 10 +++++- cmd/review.go | 2 +- git/git.go | 25 ++++++++++++++ git/git_test.go | 88 +++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 git/git_test.go diff --git a/cmd/commit.go b/cmd/commit.go index 97b8f0d..d139a99 100644 --- a/cmd/commit.go +++ b/cmd/commit.go @@ -73,7 +73,7 @@ var commitCmd = &cobra.Command{ Use: "commit", Short: "Automatically generate commit message", RunE: func(cmd *cobra.Command, args []string) error { - if err := check(); err != nil { + if err := check(cmd.Context()); err != nil { return err } diff --git a/cmd/hepler.go b/cmd/hepler.go index 1949b00..e0a0437 100644 --- a/cmd/hepler.go +++ b/cmd/hepler.go @@ -1,9 +1,11 @@ package cmd import ( + "context" "errors" "fmt" + "github.com/appleboy/CodeGPT/git" "github.com/appleboy/CodeGPT/prompt" "github.com/appleboy/CodeGPT/provider/openai" "github.com/appleboy/CodeGPT/util" @@ -11,12 +13,18 @@ import ( "github.com/spf13/viper" ) -func check() error { +func check(ctx context.Context) error { // Check if the Git command is available on the system's PATH if !util.IsCommandAvailable("git") { return errors.New("git command not found in your system's PATH. Please install Git and try again") } + // Check if the current directory is a git repository and can execute git diff + g := git.New() + if err := g.CanExecuteGitDiff(ctx); err != nil { + return fmt.Errorf("cannot execute git diff: %w", err) + } + // Apply configuration values from CLI flags to Viper if diffUnified != 3 { viper.Set("git.diff_unified", diffUnified) diff --git a/cmd/review.go b/cmd/review.go index 6cdf1e4..7cde06d 100644 --- a/cmd/review.go +++ b/cmd/review.go @@ -35,7 +35,7 @@ var reviewCmd = &cobra.Command{ Use: "review", Short: "Auto review code changes", RunE: func(cmd *cobra.Command, args []string) error { - if err := check(); err != nil { + if err := check(cmd.Context()); err != nil { return err } diff --git a/git/git.go b/git/git.go index 7df6817..decde6f 100644 --- a/git/git.go +++ b/git/git.go @@ -121,6 +121,16 @@ func (c *Command) gitDir(ctx context.Context) *exec.Cmd { ) } +// checkGitRepository generates the git command to check if the current directory is a git repository. +func (c *Command) checkGitRepository(ctx context.Context) *exec.Cmd { + return exec.CommandContext( + ctx, + "git", + "rev-parse", + "--is-inside-work-tree", + ) +} + // commit generates the git command to create a commit with the provided message. // It includes options to skip pre-commit hooks, sign off the commit, and handle amendments. func (c *Command) commit(ctx context.Context, val string) *exec.Cmd { @@ -163,6 +173,21 @@ func (c *Command) GitDir(ctx context.Context) (string, error) { return string(output), nil } +// CanExecuteGitDiff checks if git diff can be executed in the current directory. +// It returns an error if the current directory is not a git repository or if git diff cannot be executed. +func (c *Command) CanExecuteGitDiff(ctx context.Context) error { + output, err := c.checkGitRepository(ctx).Output() + if err != nil { + return errors.New("not a git repository (or any of the parent directories)") + } + + if strings.TrimSpace(string(output)) != "true" { + return errors.New("not inside a git working tree") + } + + return nil +} + // Diff compares the differences between two sets of data. // It returns a string representing the differences and an error. // If there are no differences, it returns an empty string and an error. diff --git a/git/git_test.go b/git/git_test.go new file mode 100644 index 0000000..0a7f165 --- /dev/null +++ b/git/git_test.go @@ -0,0 +1,88 @@ +package git + +import ( + "context" + "os" + "path/filepath" + "testing" +) + +func TestCanExecuteGitDiff(t *testing.T) { + // Test in the current directory (which is a git repository) + t.Run("in git repository", func(t *testing.T) { + cmd := New() + ctx := context.Background() + + err := cmd.CanExecuteGitDiff(ctx) + if err != nil { + t.Errorf("CanExecuteGitDiff() should succeed in a git repository, got error: %v", err) + } + }) + + // Test in a non-git directory + t.Run("not in git repository", func(t *testing.T) { + // Create a temporary directory + tmpDir := t.TempDir() + + // Change to the temporary directory + originalDir, err := os.Getwd() + if err != nil { + t.Fatalf("Failed to get current directory: %v", err) + } + defer func() { + if err := os.Chdir(originalDir); err != nil { + t.Logf("Failed to restore directory: %v", err) + } + }() + + if err := os.Chdir(tmpDir); err != nil { + t.Fatalf("Failed to change directory: %v", err) + } + + cmd := New() + ctx := context.Background() + + err = cmd.CanExecuteGitDiff(ctx) + if err == nil { + t.Error("CanExecuteGitDiff() should fail in a non-git directory") + } + + expectedMsg := "not a git repository" + if err != nil && err.Error() != expectedMsg { + t.Logf("Got expected error: %v", err) + } + }) + + // Test in a git repository subdirectory + t.Run("in git repository subdirectory", func(t *testing.T) { + // Create a test subdirectory in the git repository + tmpDir := filepath.Join(".", "test_subdir") + if err := os.MkdirAll(tmpDir, 0o755); err != nil { + t.Fatalf("Failed to create test directory: %v", err) + } + defer os.RemoveAll(tmpDir) + + // Change to the subdirectory + originalDir, err := os.Getwd() + if err != nil { + t.Fatalf("Failed to get current directory: %v", err) + } + defer func() { + if err := os.Chdir(originalDir); err != nil { + t.Logf("Failed to restore directory: %v", err) + } + }() + + if err := os.Chdir(tmpDir); err != nil { + t.Fatalf("Failed to change directory: %v", err) + } + + cmd := New() + ctx := context.Background() + + err = cmd.CanExecuteGitDiff(ctx) + if err != nil { + t.Errorf("CanExecuteGitDiff() should succeed in a git repository subdirectory, got error: %v", err) + } + }) +}