mirror of
https://github.com/siyuan-note/siyuan.git
synced 2026-02-19 13:38:06 +01:00
205 lines
4.9 KiB
Go
205 lines
4.9 KiB
Go
|
|
// SiYuan - Build Your Eternal Digital Garden
|
|||
|
|
// Copyright (c) 2020-present, b3log.org
|
|||
|
|
//
|
|||
|
|
// This program is free software: you can redistribute it and/or modify
|
|||
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|||
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|||
|
|
// (at your option) any later version.
|
|||
|
|
//
|
|||
|
|
// This program is distributed in the hope that it will be useful,
|
|||
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|||
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|||
|
|
// GNU Affero General Public License for more details.
|
|||
|
|
//
|
|||
|
|
// You should have received a copy of the GNU Affero General Public License
|
|||
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
|
|
|
|||
|
|
package util
|
|||
|
|
|
|||
|
|
// 该文件代码来自 https://github.com/go-git/go-git 项目,Apache-2.0 license
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"path/filepath"
|
|||
|
|
"strings"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// https://github.com/go-git/go-git/blob/master/plumbing/format/gitignore/matcher.go
|
|||
|
|
|
|||
|
|
// Matcher defines a global multi-pattern matcher for gitignore patterns
|
|||
|
|
type Matcher interface {
|
|||
|
|
// Match matches patterns in the order of priorities. As soon as an inclusion or
|
|||
|
|
// exclusion is found, not further matching is performed.
|
|||
|
|
Match(path []string, isDir bool) bool
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// NewMatcher constructs a new global matcher. Patterns must be given in the order of
|
|||
|
|
// increasing priority. That is most generic settings files first, then the content of
|
|||
|
|
// the repo .gitignore, then content of .gitignore down the path or the repo and then
|
|||
|
|
// the content command line arguments.
|
|||
|
|
func NewMatcher(ps []Pattern) Matcher {
|
|||
|
|
return &matcher{ps}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type matcher struct {
|
|||
|
|
patterns []Pattern
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (m *matcher) Match(path []string, isDir bool) bool {
|
|||
|
|
n := len(m.patterns)
|
|||
|
|
for i := n - 1; i >= 0; i-- {
|
|||
|
|
if match := m.patterns[i].Match(path, isDir); match > NoMatch {
|
|||
|
|
return match == Exclude
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// https://github.com/go-git/go-git/blob/master/plumbing/format/gitignore/pattern.go
|
|||
|
|
|
|||
|
|
// 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
|
|||
|
|
}
|