|
1 | 1 | package discovery |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "os" |
| 5 | + "os/exec" |
| 6 | + "path/filepath" |
4 | 7 | "testing" |
5 | 8 | ) |
6 | 9 |
|
@@ -100,3 +103,144 @@ func TestParseWorktreeList_BranchStripping(t *testing.T) { |
100 | 103 | t.Errorf("wt branch = %q, want %q", got[1].Branch, "feature/nested") |
101 | 104 | } |
102 | 105 | } |
| 106 | + |
| 107 | +// initGitRepo creates a git repo in dir with an initial commit. |
| 108 | +func initGitRepo(t *testing.T, dir string) { |
| 109 | + t.Helper() |
| 110 | + for _, args := range [][]string{ |
| 111 | + {"init"}, |
| 112 | + {"config", "user.email", "test@test.com"}, |
| 113 | + {"config", "user.name", "Test"}, |
| 114 | + {"commit", "--allow-empty", "-m", "init"}, |
| 115 | + } { |
| 116 | + cmd := exec.Command("git", append([]string{"-C", dir}, args...)...) |
| 117 | + if out, err := cmd.CombinedOutput(); err != nil { |
| 118 | + t.Fatalf("git %v: %v\n%s", args, err, out) |
| 119 | + } |
| 120 | + } |
| 121 | +} |
| 122 | + |
| 123 | +// resolveSymlinks resolves symlinks in a path for reliable comparison on macOS |
| 124 | +// where /var → /private/var. |
| 125 | +func resolveSymlinks(t *testing.T, path string) string { |
| 126 | + t.Helper() |
| 127 | + resolved, err := filepath.EvalSymlinks(path) |
| 128 | + if err != nil { |
| 129 | + t.Fatalf("EvalSymlinks(%q): %v", path, err) |
| 130 | + } |
| 131 | + return resolved |
| 132 | +} |
| 133 | + |
| 134 | +// E-PENPAL-WORKTREE-WATCH: verifies gitWorktreesDir returns the .git/worktrees/ dir for a repo with worktrees. |
| 135 | +func TestWorktreesDir_MainWorktree(t *testing.T) { |
| 136 | + mainDir := resolveSymlinks(t, t.TempDir()) |
| 137 | + initGitRepo(t, mainDir) |
| 138 | + |
| 139 | + // Before adding a worktree, the dir doesn't exist |
| 140 | + if got := gitWorktreesDir(mainDir); got != "" { |
| 141 | + t.Fatalf("expected empty before worktree add, got %q", got) |
| 142 | + } |
| 143 | + |
| 144 | + // Add a worktree |
| 145 | + wtDir := filepath.Join(resolveSymlinks(t, t.TempDir()), "my-worktree") |
| 146 | + cmd := exec.Command("git", "-C", mainDir, "worktree", "add", "-b", "test-branch", wtDir) |
| 147 | + if out, err := cmd.CombinedOutput(); err != nil { |
| 148 | + t.Fatalf("git worktree add: %v\n%s", err, out) |
| 149 | + } |
| 150 | + |
| 151 | + // Now gitWorktreesDir should return the .git/worktrees/ path |
| 152 | + got := gitWorktreesDir(mainDir) |
| 153 | + want := filepath.Join(mainDir, ".git", "worktrees") |
| 154 | + if got != want { |
| 155 | + t.Errorf("gitWorktreesDir(main) = %q, want %q", got, want) |
| 156 | + } |
| 157 | + |
| 158 | + // It should also work when called from the linked worktree |
| 159 | + got2 := gitWorktreesDir(wtDir) |
| 160 | + if got2 != want { |
| 161 | + t.Errorf("gitWorktreesDir(linked) = %q, want %q", got2, want) |
| 162 | + } |
| 163 | +} |
| 164 | + |
| 165 | +// E-PENPAL-WORKTREE-WATCH: verifies gitWorktreesDir returns "" for a non-git directory. |
| 166 | +func TestWorktreesDir_NotGitRepo(t *testing.T) { |
| 167 | + dir := t.TempDir() |
| 168 | + if got := gitWorktreesDir(dir); got != "" { |
| 169 | + t.Errorf("gitWorktreesDir(non-git) = %q, want empty", got) |
| 170 | + } |
| 171 | +} |
| 172 | + |
| 173 | +// E-PENPAL-WORKTREE-WATCH: verifies gitWorktreesDir returns "" for a repo with no worktrees. |
| 174 | +func TestWorktreesDir_NoWorktrees(t *testing.T) { |
| 175 | + dir := t.TempDir() |
| 176 | + initGitRepo(t, dir) |
| 177 | + if got := gitWorktreesDir(dir); got != "" { |
| 178 | + t.Errorf("gitWorktreesDir(no worktrees) = %q, want empty", got) |
| 179 | + } |
| 180 | +} |
| 181 | + |
| 182 | +// E-PENPAL-WORKTREE-WATCH: verifies worktree directory appears after git worktree add |
| 183 | +// and disappears after git worktree remove. |
| 184 | +func TestWorktreesDir_AddRemoveCycle(t *testing.T) { |
| 185 | + mainDir := resolveSymlinks(t, t.TempDir()) |
| 186 | + initGitRepo(t, mainDir) |
| 187 | + |
| 188 | + wtPath := filepath.Join(resolveSymlinks(t, t.TempDir()), "wt") |
| 189 | + cmd := exec.Command("git", "-C", mainDir, "worktree", "add", "-b", "wt-branch", wtPath) |
| 190 | + if out, err := cmd.CombinedOutput(); err != nil { |
| 191 | + t.Fatalf("git worktree add: %v\n%s", err, out) |
| 192 | + } |
| 193 | + |
| 194 | + wtDir := gitWorktreesDir(mainDir) |
| 195 | + if wtDir == "" { |
| 196 | + t.Fatal("expected non-empty after add") |
| 197 | + } |
| 198 | + |
| 199 | + // Verify the specific worktree entry exists |
| 200 | + entries, err := os.ReadDir(wtDir) |
| 201 | + if err != nil { |
| 202 | + t.Fatal(err) |
| 203 | + } |
| 204 | + found := false |
| 205 | + for _, e := range entries { |
| 206 | + if e.Name() == filepath.Base(wtPath) { |
| 207 | + found = true |
| 208 | + } |
| 209 | + } |
| 210 | + if !found { |
| 211 | + t.Errorf("expected entry %q in %s", filepath.Base(wtPath), wtDir) |
| 212 | + } |
| 213 | + |
| 214 | + // Remove the worktree |
| 215 | + cmd = exec.Command("git", "-C", mainDir, "worktree", "remove", wtPath) |
| 216 | + if out, err := cmd.CombinedOutput(); err != nil { |
| 217 | + t.Fatalf("git worktree remove: %v\n%s", err, out) |
| 218 | + } |
| 219 | + |
| 220 | + // After removing the last worktree, the worktrees/ dir should be gone |
| 221 | + if got := gitWorktreesDir(mainDir); got != "" { |
| 222 | + t.Errorf("expected empty after removing last worktree, got %q", got) |
| 223 | + } |
| 224 | +} |
| 225 | + |
| 226 | +// E-PENPAL-WORKTREE-WATCH: verifies gitCommonDirFS returns "" for malformed .git file. |
| 227 | +func TestGitCommonDirFS_MalformedGitFile(t *testing.T) { |
| 228 | + dir := t.TempDir() |
| 229 | + // .git file with no "gitdir:" prefix |
| 230 | + os.WriteFile(filepath.Join(dir, ".git"), []byte("not a gitdir line\n"), 0o644) |
| 231 | + if got := gitCommonDirFS(dir); got != "" { |
| 232 | + t.Errorf("expected empty for malformed .git file, got %q", got) |
| 233 | + } |
| 234 | +} |
| 235 | + |
| 236 | +// E-PENPAL-WORKTREE-WATCH: verifies gitCommonDirFS returns "" when commondir file is missing. |
| 237 | +func TestGitCommonDirFS_MissingCommondir(t *testing.T) { |
| 238 | + dir := t.TempDir() |
| 239 | + gitDir := filepath.Join(dir, "fake-gitdir") |
| 240 | + os.MkdirAll(gitDir, 0o755) |
| 241 | + // .git file points to a valid directory but commondir file doesn't exist |
| 242 | + os.WriteFile(filepath.Join(dir, ".git"), []byte("gitdir: "+gitDir+"\n"), 0o644) |
| 243 | + if got := gitCommonDirFS(dir); got != "" { |
| 244 | + t.Errorf("expected empty for missing commondir, got %q", got) |
| 245 | + } |
| 246 | +} |
0 commit comments