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)
|
||||
|
||||
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
|
||||
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) {
|
||||
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")
|
||||
}
|
||||
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) {
|
||||
repository := newTreeRepository(t)
|
||||
writeTreeFile(t, repository, "go.mod", []byte("module example.com/validation-tree\n\ngo 1.25.0\n"))
|
||||
|
||||
Reference in New Issue
Block a user