Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 33 additions & 4 deletions cmd/post_checkout.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/spf13/cobra"
Expand All @@ -20,7 +21,7 @@ func postCheckoutCmd() *cobra.Command {
return nil
}

branch, err := resolveBranch()
branch, err := resolveBranch(".")
if err != nil {
return err
}
Expand All @@ -38,10 +39,14 @@ func postCheckoutCmd() *cobra.Command {
}
}

func resolveBranch() (string, error) {
data, err := os.ReadFile(".git/HEAD")
func resolveBranch(dir string) (string, error) {
gitDir, err := resolveGitDir(dir)
if err != nil {
return "", fmt.Errorf("reading .git/HEAD: %w", err)
return "", err
}
data, err := os.ReadFile(filepath.Join(gitDir, "HEAD"))
if err != nil {
return "", fmt.Errorf("reading HEAD: %w", err)
}
head := strings.TrimSpace(string(data))
const prefix = "ref: refs/heads/"
Expand All @@ -51,3 +56,27 @@ func resolveBranch() (string, error) {
// detached HEAD — return commit hash as-is
return head, nil
}

// resolveGitDir returns the path to the actual git directory.
// In a worktree, .git is a file containing "gitdir: <path>" rather than a directory.
func resolveGitDir(dir string) (string, error) {
dotGit := filepath.Join(dir, ".git")
info, err := os.Stat(dotGit)
if err != nil {
return "", fmt.Errorf("stat .git: %w", err)
}
if info.IsDir() {
return dotGit, nil
}
// worktree case: .git is a file pointing to the real git dir
data, err := os.ReadFile(dotGit)
if err != nil {
return "", fmt.Errorf("reading .git: %w", err)
}
line := strings.TrimSpace(string(data))
const prefix = "gitdir: "
if !strings.HasPrefix(line, prefix) {
return "", fmt.Errorf("unexpected .git file content: %q", line)
}
return line[len(prefix):], nil
}
124 changes: 124 additions & 0 deletions cmd/post_checkout_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package cmd

import (
"os"
"path/filepath"
"testing"
)

func TestResolveGitDir_Directory(t *testing.T) {
dir := t.TempDir()
if err := os.Mkdir(filepath.Join(dir, ".git"), 0755); err != nil {
t.Fatal(err)
}

got, err := resolveGitDir(dir)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != filepath.Join(dir, ".git") {
t.Errorf("got %q, want %q", got, filepath.Join(dir, ".git"))
}
}

func TestResolveGitDir_Worktree(t *testing.T) {
dir := t.TempDir()
realGitDir := "/some/repo/.git/worktrees/my-worktree"
content := "gitdir: " + realGitDir + "\n"
if err := os.WriteFile(filepath.Join(dir, ".git"), []byte(content), 0644); err != nil {
t.Fatal(err)
}

got, err := resolveGitDir(dir)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != realGitDir {
t.Errorf("got %q, want %q", got, realGitDir)
}
}

func TestResolveGitDir_UnexpectedFileContent(t *testing.T) {
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, ".git"), []byte("not a gitdir pointer\n"), 0644); err != nil {
t.Fatal(err)
}

_, err := resolveGitDir(dir)
if err == nil {
t.Fatal("expected error for unexpected .git file content, got nil")
}
}

func TestResolveBranch_Normal(t *testing.T) {
dir := t.TempDir()
if err := os.Mkdir(filepath.Join(dir, ".git"), 0755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, ".git", "HEAD"), []byte("ref: refs/heads/my-feature\n"), 0644); err != nil {
t.Fatal(err)
}

got, err := resolveBranch(dir)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != "my-feature" {
t.Errorf("got %q, want %q", got, "my-feature")
}
}

func TestResolveBranch_DetachedHead(t *testing.T) {
dir := t.TempDir()
hash := "abc1234def5678900000000000000000000000000"
if err := os.Mkdir(filepath.Join(dir, ".git"), 0755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, ".git", "HEAD"), []byte(hash+"\n"), 0644); err != nil {
t.Fatal(err)
}

got, err := resolveBranch(dir)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != hash {
t.Errorf("got %q, want %q", got, hash)
}
}

func TestResolveBranch_Worktree(t *testing.T) {
// simulate the worktree structure:
// <worktree>/.git (file) → <realGitDir>/HEAD
realGitDir := t.TempDir()
if err := os.WriteFile(filepath.Join(realGitDir, "HEAD"), []byte("ref: refs/heads/worktree-branch\n"), 0644); err != nil {
t.Fatal(err)
}

worktreeDir := t.TempDir()
gitFile := "gitdir: " + realGitDir + "\n"
if err := os.WriteFile(filepath.Join(worktreeDir, ".git"), []byte(gitFile), 0644); err != nil {
t.Fatal(err)
}

got, err := resolveBranch(worktreeDir)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != "worktree-branch" {
t.Errorf("got %q, want %q", got, "worktree-branch")
}
}

func TestResolveBranch_MissingHead(t *testing.T) {
dir := t.TempDir()
if err := os.Mkdir(filepath.Join(dir, ".git"), 0755); err != nil {
t.Fatal(err)
}
// no HEAD file written

_, err := resolveBranch(dir)
if err == nil {
t.Fatal("expected error for missing HEAD, got nil")
}
}
2 changes: 1 addition & 1 deletion cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func runCmd() *cobra.Command {
Short: "Manually apply env patching for the current branch",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
branch, err := resolveBranch()
branch, err := resolveBranch(".")
if err != nil {
return err
}
Expand Down