5a54097c4b
When an attribute does not start with '!', '-', or contain an '=', we had previously treated it as malformed. Instead, assume that it is a 'set' attribute, which we interpret to mean as being set implicitly to "true" (and therefore the dual of the '-' prefix). From the relevant documentation [1]: Set The path has the attribute with special value "true"; this is specified by listing only the name of the attribute in the attribute list. [1]: https://git-scm.com/docs/gitattributes
129 lines
3.2 KiB
Go
129 lines
3.2 KiB
Go
package gitattr
|
|
|
|
import (
|
|
"bufio"
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/git-lfs/git-lfs/errors"
|
|
"github.com/git-lfs/wildmatch"
|
|
)
|
|
|
|
// Line carries a single line from a repository's .gitattributes file, affecting
|
|
// a single pattern and applying zero or more attributes.
|
|
type Line struct {
|
|
// Pattern is a wildmatch pattern that, when matched, indicates that all
|
|
// of the below attributes (Attrs) should be applied to that tree entry.
|
|
//
|
|
// Pattern is relative to the tree in which the .gitattributes was read
|
|
// from. For example, /.gitattributes affects all blobs in the
|
|
// repository, while /path/to/.gitattributes affects all blobs that are
|
|
// direct or indirect children of /path/to.
|
|
Pattern *wildmatch.Wildmatch
|
|
// Attrs is the list of attributes to be applied when the above pattern
|
|
// matches a given filename.
|
|
//
|
|
// It is populated in-order as it was written in the .gitattributes file
|
|
// being read, from left to right.
|
|
Attrs []*Attr
|
|
}
|
|
|
|
// Attr is a single attribute that may be applied to a file.
|
|
type Attr struct {
|
|
// K is the name of the attribute. It is commonly, "filter", "diff",
|
|
// "merge", or "text".
|
|
//
|
|
// It will never contain the special "false" shorthand ("-"), or the
|
|
// unspecify declarative ("!").
|
|
K string
|
|
// V is the value held by that attribute. It is commonly "lfs", or
|
|
// "false", indicating the special value given by a "-"-prefixed name.
|
|
V string
|
|
// Unspecified indicates whether or not this attribute was explicitly
|
|
// unset by prefixing the keyname with "!".
|
|
Unspecified bool
|
|
}
|
|
|
|
// ParseLines parses the given io.Reader "r" line-wise as if it were the
|
|
// contents of a .gitattributes file.
|
|
//
|
|
// If an error was encountered, it will be returned and the []*Line should be
|
|
// considered unusable.
|
|
func ParseLines(r io.Reader) ([]*Line, error) {
|
|
var lines []*Line
|
|
|
|
scanner := bufio.NewScanner(r)
|
|
for scanner.Scan() {
|
|
|
|
text := strings.TrimSpace(scanner.Text())
|
|
if len(text) == 0 {
|
|
continue
|
|
}
|
|
|
|
var pattern string
|
|
var applied string
|
|
|
|
switch text[0] {
|
|
case '#':
|
|
continue
|
|
case '"':
|
|
var err error
|
|
last := strings.LastIndex(text, "\"")
|
|
if last == 0 {
|
|
return nil, errors.Errorf("git/gitattr: unbalanced quote: %s", text)
|
|
}
|
|
pattern, err = strconv.Unquote(text[:last+1])
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "git/gitattr")
|
|
}
|
|
applied = strings.TrimSpace(text[last+1:])
|
|
default:
|
|
splits := strings.SplitN(text, " ", 2)
|
|
|
|
pattern = splits[0]
|
|
if len(splits) == 2 {
|
|
applied = splits[1]
|
|
}
|
|
}
|
|
|
|
var attrs []*Attr
|
|
|
|
for _, s := range strings.Split(applied, " ") {
|
|
if s == "" {
|
|
continue
|
|
}
|
|
|
|
var attr Attr
|
|
|
|
if strings.HasPrefix(s, "-") {
|
|
attr.K = strings.TrimPrefix(s, "-")
|
|
attr.V = "false"
|
|
} else if strings.HasPrefix(s, "!") {
|
|
attr.K = strings.TrimPrefix(s, "!")
|
|
attr.Unspecified = true
|
|
} else if eq := strings.Index(s, "="); eq > -1 {
|
|
attr.K = s[:eq]
|
|
attr.V = s[eq+1:]
|
|
} else {
|
|
attr.K = s
|
|
attr.V = "true"
|
|
}
|
|
|
|
attrs = append(attrs, &attr)
|
|
}
|
|
|
|
lines = append(lines, &Line{
|
|
Pattern: wildmatch.NewWildmatch(pattern,
|
|
wildmatch.Basename, wildmatch.SystemCase,
|
|
),
|
|
Attrs: attrs,
|
|
})
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return lines, nil
|
|
}
|