mirror of
https://github.com/go-gitea/gitea
synced 2024-09-19 18:26:04 +00:00
154 lines
3.0 KiB
Go
154 lines
3.0 KiB
Go
|
package gitignore
|
||
|
|
||
|
import (
|
||
|
"path/filepath"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
// MatchResult defines outcomes of a match, no match, exclusion or inclusion.
|
||
|
type MatchResult int
|
||
|
|
||
|
const (
|
||
|
// NoMatch defines the no match outcome of a match check
|
||
|
NoMatch MatchResult = iota
|
||
|
// Exclude defines an exclusion of a file as a result of a match check
|
||
|
Exclude
|
||
|
// Include defines an explicit inclusion of a file as a result of a match check
|
||
|
Include
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
inclusionPrefix = "!"
|
||
|
zeroToManyDirs = "**"
|
||
|
patternDirSep = "/"
|
||
|
)
|
||
|
|
||
|
// Pattern defines a single gitignore pattern.
|
||
|
type Pattern interface {
|
||
|
// Match matches the given path to the pattern.
|
||
|
Match(path []string, isDir bool) MatchResult
|
||
|
}
|
||
|
|
||
|
type pattern struct {
|
||
|
domain []string
|
||
|
pattern []string
|
||
|
inclusion bool
|
||
|
dirOnly bool
|
||
|
isGlob bool
|
||
|
}
|
||
|
|
||
|
// ParsePattern parses a gitignore pattern string into the Pattern structure.
|
||
|
func ParsePattern(p string, domain []string) Pattern {
|
||
|
res := pattern{domain: domain}
|
||
|
|
||
|
if strings.HasPrefix(p, inclusionPrefix) {
|
||
|
res.inclusion = true
|
||
|
p = p[1:]
|
||
|
}
|
||
|
|
||
|
if !strings.HasSuffix(p, "\\ ") {
|
||
|
p = strings.TrimRight(p, " ")
|
||
|
}
|
||
|
|
||
|
if strings.HasSuffix(p, patternDirSep) {
|
||
|
res.dirOnly = true
|
||
|
p = p[:len(p)-1]
|
||
|
}
|
||
|
|
||
|
if strings.Contains(p, patternDirSep) {
|
||
|
res.isGlob = true
|
||
|
}
|
||
|
|
||
|
res.pattern = strings.Split(p, patternDirSep)
|
||
|
return &res
|
||
|
}
|
||
|
|
||
|
func (p *pattern) Match(path []string, isDir bool) MatchResult {
|
||
|
if len(path) <= len(p.domain) {
|
||
|
return NoMatch
|
||
|
}
|
||
|
for i, e := range p.domain {
|
||
|
if path[i] != e {
|
||
|
return NoMatch
|
||
|
}
|
||
|
}
|
||
|
|
||
|
path = path[len(p.domain):]
|
||
|
if p.isGlob && !p.globMatch(path, isDir) {
|
||
|
return NoMatch
|
||
|
} else if !p.isGlob && !p.simpleNameMatch(path, isDir) {
|
||
|
return NoMatch
|
||
|
}
|
||
|
|
||
|
if p.inclusion {
|
||
|
return Include
|
||
|
} else {
|
||
|
return Exclude
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (p *pattern) simpleNameMatch(path []string, isDir bool) bool {
|
||
|
for i, name := range path {
|
||
|
if match, err := filepath.Match(p.pattern[0], name); err != nil {
|
||
|
return false
|
||
|
} else if !match {
|
||
|
continue
|
||
|
}
|
||
|
if p.dirOnly && !isDir && i == len(path)-1 {
|
||
|
return false
|
||
|
}
|
||
|
return true
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func (p *pattern) globMatch(path []string, isDir bool) bool {
|
||
|
matched := false
|
||
|
canTraverse := false
|
||
|
for i, pattern := range p.pattern {
|
||
|
if pattern == "" {
|
||
|
canTraverse = false
|
||
|
continue
|
||
|
}
|
||
|
if pattern == zeroToManyDirs {
|
||
|
if i == len(p.pattern)-1 {
|
||
|
break
|
||
|
}
|
||
|
canTraverse = true
|
||
|
continue
|
||
|
}
|
||
|
if strings.Contains(pattern, zeroToManyDirs) {
|
||
|
return false
|
||
|
}
|
||
|
if len(path) == 0 {
|
||
|
return false
|
||
|
}
|
||
|
if canTraverse {
|
||
|
canTraverse = false
|
||
|
for len(path) > 0 {
|
||
|
e := path[0]
|
||
|
path = path[1:]
|
||
|
if match, err := filepath.Match(pattern, e); err != nil {
|
||
|
return false
|
||
|
} else if match {
|
||
|
matched = true
|
||
|
break
|
||
|
} else if len(path) == 0 {
|
||
|
// if nothing left then fail
|
||
|
matched = false
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
if match, err := filepath.Match(pattern, path[0]); err != nil || !match {
|
||
|
return false
|
||
|
}
|
||
|
matched = true
|
||
|
path = path[1:]
|
||
|
}
|
||
|
}
|
||
|
if matched && p.dirOnly && !isDir && len(path) == 0 {
|
||
|
matched = false
|
||
|
}
|
||
|
return matched
|
||
|
}
|