github.com/x-oss-byte/git-lfs@v2.5.2+incompatible/git/gitattr/attr.go (about) 1 package gitattr 2 3 import ( 4 "bufio" 5 "io" 6 "strconv" 7 "strings" 8 9 "github.com/git-lfs/git-lfs/errors" 10 "github.com/git-lfs/wildmatch" 11 ) 12 13 // Line carries a single line from a repository's .gitattributes file, affecting 14 // a single pattern and applying zero or more attributes. 15 type Line struct { 16 // Pattern is a wildmatch pattern that, when matched, indicates that all 17 // of the below attributes (Attrs) should be applied to that tree entry. 18 // 19 // Pattern is relative to the tree in which the .gitattributes was read 20 // from. For example, /.gitattributes affects all blobs in the 21 // repository, while /path/to/.gitattributes affects all blobs that are 22 // direct or indirect children of /path/to. 23 Pattern *wildmatch.Wildmatch 24 // Attrs is the list of attributes to be applied when the above pattern 25 // matches a given filename. 26 // 27 // It is populated in-order as it was written in the .gitattributes file 28 // being read, from left to right. 29 Attrs []*Attr 30 } 31 32 // Attr is a single attribute that may be applied to a file. 33 type Attr struct { 34 // K is the name of the attribute. It is commonly, "filter", "diff", 35 // "merge", or "text". 36 // 37 // It will never contain the special "false" shorthand ("-"), or the 38 // unspecify declarative ("!"). 39 K string 40 // V is the value held by that attribute. It is commonly "lfs", or 41 // "false", indicating the special value given by a "-"-prefixed name. 42 V string 43 // Unspecified indicates whether or not this attribute was explicitly 44 // unset by prefixing the keyname with "!". 45 Unspecified bool 46 } 47 48 // ParseLines parses the given io.Reader "r" line-wise as if it were the 49 // contents of a .gitattributes file. 50 // 51 // If an error was encountered, it will be returned and the []*Line should be 52 // considered unusable. 53 func ParseLines(r io.Reader) ([]*Line, error) { 54 var lines []*Line 55 56 scanner := bufio.NewScanner(r) 57 for scanner.Scan() { 58 59 text := strings.TrimSpace(scanner.Text()) 60 if len(text) == 0 { 61 continue 62 } 63 64 var pattern string 65 var applied string 66 67 switch text[0] { 68 case '#': 69 continue 70 case '"': 71 var err error 72 last := strings.LastIndex(text, "\"") 73 if last == 0 { 74 return nil, errors.Errorf("git/gitattr: unbalanced quote: %s", text) 75 } 76 pattern, err = strconv.Unquote(text[:last+1]) 77 if err != nil { 78 return nil, errors.Wrapf(err, "git/gitattr") 79 } 80 applied = strings.TrimSpace(text[last+1:]) 81 default: 82 splits := strings.SplitN(text, " ", 2) 83 84 pattern = splits[0] 85 if len(splits) == 2 { 86 applied = splits[1] 87 } 88 } 89 90 var attrs []*Attr 91 92 for _, s := range strings.Split(applied, " ") { 93 if s == "" { 94 continue 95 } 96 97 var attr Attr 98 99 if strings.HasPrefix(s, "-") { 100 attr.K = strings.TrimPrefix(s, "-") 101 attr.V = "false" 102 } else if strings.HasPrefix(s, "!") { 103 attr.K = strings.TrimPrefix(s, "!") 104 attr.Unspecified = true 105 } else { 106 splits := strings.SplitN(s, "=", 2) 107 if len(splits) != 2 { 108 return nil, errors.Errorf("git/gitattr: malformed attribute: %s", s) 109 } 110 attr.K = splits[0] 111 attr.V = splits[1] 112 } 113 114 attrs = append(attrs, &attr) 115 } 116 117 lines = append(lines, &Line{ 118 Pattern: wildmatch.NewWildmatch(pattern, 119 wildmatch.Basename, wildmatch.SystemCase, 120 ), 121 Attrs: attrs, 122 }) 123 } 124 125 if err := scanner.Err(); err != nil { 126 return nil, err 127 } 128 return lines, nil 129 }