Resolve absolute destinations inside the candidate checkout

This commit is contained in:
2026-10-07 20:29:16 +00:00
parent 9d2ca66a79
commit 1dfa774164
4 changed files with 117 additions and 9 deletions

View File

@@ -230,3 +230,67 @@ func TestResolveDestinationPreservesParentTraversalOrder(t *testing.T) {
t.Fatalf("neighbor changed resolution: %#v -> %#v, %v", before, after, err)
}
}
func TestResolveDestinationMapsAbsolutePathsIntoCandidate(t *testing.T) {
repository := newTreeRepository(t)
for _, name := range []string{"README.md", "docs/file.md", "nested/file.md", "nested/inner/item.txt"} {
writeTreeFile(t, repository, name, []byte("captured\n"))
}
for name, target := range map[string]string{
"absolute-file": filepath.ToSlash(filepath.Join(repository, "README.md")),
"absolute-directory": filepath.ToSlash(filepath.Join(repository, "nested", "inner")),
"absolute-outside": filepath.ToSlash(repository + "-neighbor/README.md"),
"docs/link": "../nested/inner",
"absolute-parent": filepath.ToSlash(repository) + "/docs/link/../file.md",
} {
if err := os.Symlink(target, filepath.Join(repository, filepath.FromSlash(name))); err != nil {
t.Fatal(err)
}
}
gitTree(t, repository, "add", "--all")
gitTree(t, repository, "commit", "-m", "capture absolute links")
tree, err := ReadCandidateTree(t.Context(), repository, strings.TrimSpace(gitTree(t, repository, "rev-parse", "HEAD")))
if err != nil {
t.Fatal(err)
}
if err := os.Remove(filepath.Join(repository, "README.md")); err != nil {
t.Fatal(err)
}
writeTreeFile(t, repository, "live-only.md", []byte("not committed"))
neighbor := repository + "-neighbor"
if err := os.MkdirAll(neighbor, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(neighbor, "README.md"), []byte("outside"), 0o644); err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
if err := os.RemoveAll(neighbor); err != nil {
t.Error(err)
}
})
root := filepath.ToSlash(repository)
for _, test := range []struct {
destination string
kind DestinationKind
resolved string
}{
{root + "/README.md", DestinationEntry, "README.md"},
{root, DestinationDirectory, "."},
{root + "/nested/inner", DestinationDirectory, "nested/inner"},
{"absolute-file", DestinationEntry, "README.md"},
{"absolute-directory/item.txt", DestinationEntry, "nested/inner/item.txt"},
{root + "/docs/link/../file.md", DestinationEntry, "nested/file.md"},
{"absolute-parent", DestinationEntry, "nested/file.md"},
{root + "/live-only.md", DestinationMissing, "live-only.md"},
{root + "-neighbor/README.md", DestinationOutsideCheckout, root + "-neighbor/README.md"},
{"absolute-outside", DestinationOutsideCheckout, root + "-neighbor/README.md"},
} {
t.Run(test.destination, func(t *testing.T) {
got, err := tree.ResolveDestination(t.Context(), "README.md", test.destination)
if err != nil || got.Kind != test.kind || got.Path != test.resolved {
t.Fatalf("resolution = %#v, %v; want %s %q", got, err, test.kind, test.resolved)
}
})
}
}