diff --git a/internal/strdist/strdist.go b/internal/strdist/strdist.go index f205bfcf..d5b640ef 100644 --- a/internal/strdist/strdist.go +++ b/internal/strdist/strdist.go @@ -105,6 +105,15 @@ func Distance(a, b string, f CostFunc, cut int64) int64 { // * - Any zero or more characters, except for / // ** - Any zero or more characters, including / func GlobPath(a, b string) bool { + if !wildcardPrefixMatch(a, b) { + // Fast path. + return false + } + if !wildcardSuffixMatch(a, b) { + // Fast path. + return false + } + a = strings.ReplaceAll(a, "**", "⁑") b = strings.ReplaceAll(b, "**", "⁑") return Distance(a, b, globCost, 1) == 0 @@ -125,3 +134,37 @@ func globCost(ar, br rune) Cost { } return Cost{SwapAB: 1, DeleteA: 1, InsertB: 1} } + +// wildcardPrefixMatch compares whether the prefixes of a and b are equal up +// to the shortest one. The prefix is defined as the longest substring that +// starts at index 0 and does not contain a wildcard. +func wildcardPrefixMatch(a, b string) bool { + ai := strings.IndexAny(a, "*?") + bi := strings.IndexAny(b, "*?") + if ai == -1 { + ai = len(a) + } + if bi == -1 { + bi = len(b) + } + mini := min(ai, bi) + return a[:mini] == b[:mini] +} + +// wildcardSuffixMatch compares whether the suffixes of a and b are equal up +// to the shortest one. The suffix is defined as the longest substring that ends +// at the string length and does not contain a wildcard. +func wildcardSuffixMatch(a, b string) bool { + ai := strings.LastIndexAny(a, "*?") + la := 0 + if ai != -1 { + la = len(a) - ai - 1 + } + lb := 0 + bi := strings.LastIndexAny(b, "*?") + if bi != -1 { + lb = len(b) - bi - 1 + } + minl := min(la, lb) + return a[len(a)-minl:] == b[len(b)-minl:] +}