This is an automated email from the ASF dual-hosted git repository.
kezhenxu94 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-eyes.git
The following commit(s) were added to refs/heads/main by this push:
new 049742d Fix null pointer panic in listFiles when repository has no
commits or invalid HEAD (#202)
049742d is described below
commit 049742de2276515409e3109ca2a91934053e080d
Author: Hoshea <[email protected]>
AuthorDate: Mon Jul 7 11:50:03 2025 +0800
Fix null pointer panic in listFiles when repository has no commits or
invalid HEAD (#202)
Fixes a panic that occurs when calling repo.Head().Hash() in empty git
repositories,
git worktrees, or repositories with invalid HEAD references. The issue
happened in
pkg/header/check.go where head could be nil, causing a null pointer
dereference
when calling head.Hash().
This commonly occurs in the following scenarios:
- Empty git repositories with no initial commits
- Git worktrees with detached HEAD or invalid branch references
- Corrupted repositories with invalid HEAD pointers
- New worktrees created from non-existent commits or branches
Changes:
- Add proper null check for head reference before calling head.Hash()
- Add error handling for repo.Head() to gracefully handle edge cases
- Skip git-based file discovery when repository has no commits or invalid
HEAD
- Add test cases for empty repositories and worktree scenarios in
check_test.go
- Ensure graceful fallback to glob-based file discovery in problematic git
states
Resolves panic at pkg/header/check.go:99 when working with empty git
repositories
or git worktree environments with invalid HEAD references.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-authored-by: Claude <[email protected]>
---
pkg/header/check.go | 35 ++++++----
pkg/header/check_test.go | 173 +++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 195 insertions(+), 13 deletions(-)
diff --git a/pkg/header/check.go b/pkg/header/check.go
index 11f2954..9c0318b 100644
--- a/pkg/header/check.go
+++ b/pkg/header/check.go
@@ -92,20 +92,29 @@ func listFiles(config *ConfigHeader) ([]string, error) {
candidates = append(candidates, file)
}
- head, _ := repo.Head()
- commit, _ := repo.CommitObject(head.Hash())
- tree, err := commit.Tree()
- if err != nil {
- return nil, err
- }
- if err := tree.Files().ForEach(func(file *object.File) error {
- if file == nil {
- return errors.New("file pointer is nil")
+ head, err := repo.Head()
+ if err != nil || head == nil {
+ // Repository has no commits or invalid HEAD, skip
git-based file discovery
+ logger.Log.Debugf("Repository has no commits or invalid
HEAD (head: %v), skipping git-based file discovery. Error: %v", head, err)
+ } else {
+ commit, err := repo.CommitObject(head.Hash())
+ if err != nil {
+ logger.Log.Debugln("Failed to get commit
object:", err)
+ } else {
+ tree, err := commit.Tree()
+ if err != nil {
+ return nil, err
+ }
+ if err := tree.Files().ForEach(func(file
*object.File) error {
+ if file == nil {
+ return errors.New("file pointer
is nil")
+ }
+ candidates = append(candidates,
file.Name)
+ return nil
+ }); err != nil {
+ return nil, err
+ }
}
- candidates = append(candidates, file.Name)
- return nil
- }); err != nil {
- return nil, err
}
seen := make(map[string]bool)
diff --git a/pkg/header/check_test.go b/pkg/header/check_test.go
index 6f1074d..4e33686 100644
--- a/pkg/header/check_test.go
+++ b/pkg/header/check_test.go
@@ -23,6 +23,9 @@ import (
"strings"
"testing"
+ "github.com/go-git/go-git/v5"
+ "github.com/go-git/go-git/v5/plumbing"
+ "github.com/go-git/go-git/v5/plumbing/object"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)
@@ -96,3 +99,173 @@ func TestCheckFile(t *testing.T) {
}
})
}
+
+func TestListFilesWithEmptyRepo(t *testing.T) {
+ // Create a temporary directory for testing
+ tempDir, err := os.MkdirTemp("", "skywalking-eyes-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tempDir)
+
+ // Change to temp directory
+ originalDir, err := os.Getwd()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Chdir(originalDir)
+
+ if err := os.Chdir(tempDir); err != nil {
+ t.Fatal(err)
+ }
+
+ // Initialize an empty git repository
+ _, err = git.PlainInit(".", false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Create a test file
+ testFile := filepath.Join(tempDir, "test.go")
+ err = os.WriteFile(testFile, []byte("package main"), 0644)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Create a basic config
+ config := &ConfigHeader{
+ Paths: []string{"**/*.go"},
+ }
+
+ // This should not panic even with empty repository
+ fileList, err := listFiles(config)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Should still find files using glob fallback
+ if len(fileList) == 0 {
+ t.Error("Expected to find at least one file")
+ }
+}
+
+func TestListFilesWithWorktreeDetachedHEAD(t *testing.T) {
+ // Create a temporary directory for testing
+ tempDir, err := os.MkdirTemp("", "skywalking-eyes-worktree-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tempDir)
+
+ // Change to temp directory
+ originalDir, err := os.Getwd()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Chdir(originalDir)
+
+ if err := os.Chdir(tempDir); err != nil {
+ t.Fatal(err)
+ }
+
+ // Initialize a git repository with a commit
+ repo, err := git.PlainInit(".", false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Create and commit a file
+ testFile := "test.go"
+ err = os.WriteFile(testFile, []byte("package main"), 0644)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ worktree, err := repo.Worktree()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = worktree.Add(testFile)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ commit, err := worktree.Commit("Initial commit", &git.CommitOptions{
+ Author: &object.Signature{
+ Name: "Test User",
+ Email: "[email protected]",
+ },
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // First, verify normal case works
+ config := &ConfigHeader{
+ Paths: []string{"**/*.go"},
+ }
+
+ fileList, err := listFiles(config)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(fileList) == 0 {
+ t.Error("Expected to find files with valid commit")
+ }
+
+ // Now simulate detached HEAD by checking out to a non-existent commit
hash
+ // This will create an invalid HEAD state that our fix should handle
+ invalidHash :=
plumbing.NewHash("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")
+ err = worktree.Checkout(&git.CheckoutOptions{
+ Hash: invalidHash,
+ })
+ // We expect this to fail, creating an invalid state
+ if err == nil {
+ t.Fatal("Expected checkout to invalid hash to fail")
+ }
+
+ // This should not panic even with problematic git state
+ fileList2, err := listFiles(config)
+ if err != nil {
+ // It's okay if there's an error, we just don't want a panic
+ t.Logf("Got expected error: %v", err)
+ }
+
+ // Should still find files using glob fallback
+ if len(fileList2) == 0 {
+ t.Error("Expected to find at least one file via fallback")
+ }
+
+ t.Logf("Found %d files: %v", len(fileList2), fileList2)
+
+ // Verify we can find our test file
+ found := false
+ for _, file := range fileList2 {
+ if filepath.Base(file) == testFile {
+ found = true
+ break
+ }
+ }
+ if !found {
+ t.Error("Expected to find test.go in file list")
+ }
+
+ // Test with valid commit to ensure normal case still works
+ err = worktree.Checkout(&git.CheckoutOptions{
+ Hash: commit,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ fileList3, err := listFiles(config)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(fileList3) == 0 {
+ t.Error("Expected to find files with valid commit")
+ }
+}