prow/gitattributes/pattern.go
func parsePattern(p string) (Pattern, error) {
	res := pattern{}

	// negative patterns are forbidden
	if strings.HasPrefix(p, negativePrefix) {
		return nil, fmt.Errorf("negative patterns are forbidden: <%s>", p)
	}

	// trailing spaces are ignored unless they are quoted with backslash
	if !strings.HasSuffix(p, `\ `) {
		p = strings.TrimRight(p, " ")
	}

	// patterns that match a directory do not recursively match paths inside that directory
	if strings.HasSuffix(p, patternDirSep) {
		return nil, fmt.Errorf("directory patterns are not matched recursively, use path/** instead: <%s>", p)
	}

	if strings.Contains(p, patternDirSep) {
		res.isPath = true
	}

	res.pattern = strings.Split(p, patternDirSep)
	return &res, nil
}
func parsePattern(p string) (Pattern, error) {
	res := pattern{}

	// negative patterns are forbidden
	if strings.HasPrefix(p, negativePrefix) {
		return nil, fmt.Errorf("negative patterns are forbidden: <%s>", p)
	}

	// trailing spaces are ignored unless they are quoted with backslash
	if !strings.HasSuffix(p, `\ `) {
		p = strings.TrimRight(p, " ")
	}

	// patterns that match a directory do not recursively match paths inside that directory
	if strings.HasSuffix(p, patternDirSep) {
		return nil, fmt.Errorf("directory patterns are not matched recursively, use path/** instead: <%s>", p)
	}

	if strings.Contains(p, patternDirSep) {
		res.isPath = true
	}

	res.pattern = strings.Split(p, patternDirSep)
	return &res, nil
}
// parsePattern parses a gitattributes pattern string into the Pattern structure.
// The rules by which the pattern matches paths are the same as in .gitignore files (see https://git-scm.com/docs/gitignore), with a few exceptions:
//   - negative patterns are forbidden
//   - patterns that match a directory do not recursively match paths inside that directory
// https://git-scm.com/docs/gitattributes