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")
+       }
+}

Reply via email to