Preserve literal characters in shared file paths
This commit is contained in:
@@ -267,7 +267,13 @@ reference-cycle, outside-checkout, unavailable-content, and source-tag guards.
|
|||||||
|
|
||||||
## Issue 8: Preserve literal shared filenames (P2)
|
## Issue 8: Preserve literal shared filenames (P2)
|
||||||
|
|
||||||
Status: open. Category: unrequested path prohibition.
|
Status: complete. Category: unrequested path prohibition.
|
||||||
|
|
||||||
|
**Completion:** Shared selection preserves literal backslashes in committed Git
|
||||||
|
paths. A Unix regression selects and reads such a file once despite duplicates;
|
||||||
|
traversal, absolute paths, and directories remain invalid selections. Existing
|
||||||
|
literal-metacharacter, missing/default/empty-list, scope, and lazy-input tests
|
||||||
|
pass offline.
|
||||||
|
|
||||||
**Evidence:** [`normalizeSharedFile`](../../internal/validation/tree.go) rejects
|
**Evidence:** [`normalizeSharedFile`](../../internal/validation/tree.go) rejects
|
||||||
every backslash. On Linux, a committed file named `docs/notice\copy.txt` is a
|
every backslash. On Linux, a committed file named `docs/notice\copy.txt` is a
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ func insideVendor(moduleRelative string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func normalizeSharedFile(name string) (string, error) {
|
func normalizeSharedFile(name string) (string, error) {
|
||||||
if name == "" || path.IsAbs(name) || strings.Contains(name, "\\") {
|
if name == "" || path.IsAbs(name) {
|
||||||
return "", fmt.Errorf("shared file path must be a repository-relative file path")
|
return "", fmt.Errorf("shared file path must be a repository-relative file path")
|
||||||
}
|
}
|
||||||
clean := path.Clean(name)
|
clean := path.Clean(name)
|
||||||
|
|||||||
@@ -231,6 +231,36 @@ func TestReadCandidateTreeDefaultsAreOptionalAndExplicitMissingFilesFail(t *test
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSharedFileSelectionPreservesLiteralBackslash(t *testing.T) {
|
||||||
|
if filepath.Separator == '\\' {
|
||||||
|
t.Skip("backslash is a filesystem separator on this platform")
|
||||||
|
}
|
||||||
|
repository := newTreeRepository(t)
|
||||||
|
writeTreeFile(t, repository, "module/go.mod", []byte("module example.com/module\n"))
|
||||||
|
const name = `docs/notice\copy.txt`
|
||||||
|
contents := []byte("literal filename\n")
|
||||||
|
writeTreeFile(t, repository, name, contents)
|
||||||
|
commit := commitCandidateTree(t, repository)
|
||||||
|
moduleDir := filepath.Join(repository, "module")
|
||||||
|
tree, err := ReadCandidateTreeForScope(t.Context(), repository, commit, TreeScope{ModuleDir: moduleDir, SharedFiles: []string{name, name}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
cleanupCandidateTree(t, tree)
|
||||||
|
if got := inputPaths(tree); strings.Join(got, "\n") != name+"\nmodule/go.mod" {
|
||||||
|
t.Fatalf("literal selection = %q", got)
|
||||||
|
}
|
||||||
|
read, err := tree.ReadFile(t.Context(), name)
|
||||||
|
if err != nil || string(read) != string(contents) {
|
||||||
|
t.Fatalf("literal file content = %q, error = %v", read, err)
|
||||||
|
}
|
||||||
|
for _, invalid := range []string{"../outside", "/absolute", "docs"} {
|
||||||
|
if _, err := ReadCandidateTreeForScope(t.Context(), repository, commit, TreeScope{ModuleDir: moduleDir, SharedFiles: []string{invalid}}); err == nil {
|
||||||
|
t.Fatalf("shared selection accepted %q", invalid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRunBuildsInputsOnlyForChecksOrExplicitSharedFiles(t *testing.T) {
|
func TestRunBuildsInputsOnlyForChecksOrExplicitSharedFiles(t *testing.T) {
|
||||||
repository := newTreeRepository(t)
|
repository := newTreeRepository(t)
|
||||||
writeTreeFile(t, repository, "go.mod", []byte("module example.com/validation-tree\n\ngo 1.25.0\n"))
|
writeTreeFile(t, repository, "go.mod", []byte("module example.com/validation-tree\n\ngo 1.25.0\n"))
|
||||||
|
|||||||
Reference in New Issue
Block a user