Artifact 2376228a894c865466682d33ec4a7045d74f046d11c4643529ad1b96d7947578:
- File
cli/pathmatch.go
— part of check-in
[430dd5ec70]
at
2026-10-20 01:49:55
on branch fix/cli-path-matching
— fix(cli): match PATH as fossil does; run pragmas in a fixed order
log --path, cat --at and diff drop a leading ./, a trailing / and doubled or /./ separators, and compare names with the repository's case-sensitive setting, fossil's platform default when unset. Not found stays exit 2, where fossil exits 1; the docs now say so. SettingBoolean moves to internal/repo, shared by sync and checkout.
Opening a repository runs every pragma in a fixed order, not only busy_timeout first.
Tracked in #451 (user: [email protected], size: 2607) [annotate] [blame] [check-ins using] [more...]
package cli import ( "path" libfossil "github.com/danmestas/go-libfossil" ) // repoPaths matches a PATH argument against a repository's file names as // fossil does. fossil normalizes the argument (file_tree_name: no leading // "./", trailing or doubled "/", "/./" or "/x/../") and compares it with // strcmp when the case-sensitive setting is on, and with fossil_stricmp, // which folds ASCII letters only, when it is off. The setting is the // repository's, else fossil's platform default: off on macOS and Windows. type repoPaths struct { foldCase bool } func newRepoPaths(r *libfossil.Repo) repoPaths { return repoPaths{foldCase: !r.Inner().FilenamesCaseSensitive()} } // clean is name as fossil's file_tree_name leaves it for a path relative to // the repository root. An empty name stays empty. func (p repoPaths) clean(name string) string { if name == "" { return "" } return path.Clean(name) } // equal reports whether the file names a and b are the same file. func (p repoPaths) equal(a, b string) bool { if !p.foldCase { return a == b } return equalASCIIFold(a, b) } // within reports whether file is the file named name, or a file under the // directory named name, as fossil's file_dir_match does. The name "." is // every file. func (p repoPaths) within(file, name string) bool { if name == "." { return true } if p.equal(file, name) { return true } return len(file) > len(name) && file[len(name)] == '/' && p.equal(file[:len(name)], name) } // equalASCIIFold is fossil_stricmp(a, b) == 0: a and b are equal once ASCII // upper case letters are folded to lower case. Other bytes compare exactly, // where strings.EqualFold would also fold Unicode. func equalASCIIFold(a, b string) bool { if len(a) != len(b) { return false } for i := 0; i < len(a); i++ { if lowerASCII(a[i]) != lowerASCII(b[i]) { return false } } return true } func lowerASCII(c byte) byte { if 'A' <= c && c <= 'Z' { return c + 'a' - 'A' } return c } // fileIn is the name by which version rid holds the file name asks for. With // case folding that is the first file, in manifest order, that equals name, // as fossil's manifest_file_find returns it, and "" when there is none; // without it, name itself, cleaned, whether or not the version holds it. func (p repoPaths) fileIn(r *libfossil.Repo, rid int64, name string) (string, error) { name = p.clean(name) if !p.foldCase { return name, nil } files, err := r.ListFiles(rid) if err != nil { return "", err } for _, f := range files { if p.equal(f.Name, name) { return f.Name, nil } } return "", nil }