github.com/psexton/git-lfs@v2.1.1-0.20170517224304-289a18b2bc53+incompatible/git/attribs.go (about) 1 package git 2 3 import ( 4 "bufio" 5 "bytes" 6 "os" 7 "path/filepath" 8 "strings" 9 10 "github.com/git-lfs/git-lfs/tools" 11 "github.com/rubyist/tracerx" 12 ) 13 14 const ( 15 LockableAttrib = "lockable" 16 ) 17 18 // AttributePath is a path entry in a gitattributes file which has the LFS filter 19 type AttributePath struct { 20 // Path entry in the attribute file 21 Path string 22 // The attribute file which was the source of this entry 23 Source *AttributeSource 24 // Path also has the 'lockable' attribute 25 Lockable bool 26 } 27 28 type AttributeSource struct { 29 Path string 30 LineEnding string 31 } 32 33 func (s *AttributeSource) String() string { 34 return s.Path 35 } 36 37 // GetAttributePaths returns a list of entries in .gitattributes which are 38 // configured with the filter=lfs attribute 39 // workingDIr is the root of the working copy 40 // gitDir is the root of the git repo 41 func GetAttributePaths(workingDir, gitDir string) []AttributePath { 42 paths := make([]AttributePath, 0) 43 44 for _, path := range findAttributeFiles(workingDir, gitDir) { 45 attributes, err := os.Open(path) 46 if err != nil { 47 continue 48 } 49 50 relfile, _ := filepath.Rel(workingDir, path) 51 reldir := filepath.Dir(relfile) 52 source := &AttributeSource{Path: relfile} 53 54 le := &lineEndingSplitter{} 55 scanner := bufio.NewScanner(attributes) 56 scanner.Split(le.ScanLines) 57 58 for scanner.Scan() { 59 line := scanner.Text() 60 if strings.Contains(line, "filter=lfs") { 61 fields := strings.Fields(line) 62 pattern := fields[0] 63 if len(reldir) > 0 { 64 pattern = filepath.Join(reldir, pattern) 65 } 66 // Find lockable flag in any position after pattern to avoid 67 // edge case of matching "lockable" to a file pattern 68 lockable := false 69 for _, f := range fields[1:] { 70 if f == LockableAttrib { 71 lockable = true 72 break 73 } 74 } 75 paths = append(paths, AttributePath{ 76 Path: pattern, 77 Source: source, 78 Lockable: lockable, 79 }) 80 } 81 } 82 83 source.LineEnding = le.LineEnding() 84 } 85 86 return paths 87 } 88 89 // copies bufio.ScanLines(), counting LF vs CRLF in a file 90 type lineEndingSplitter struct { 91 LFCount int 92 CRLFCount int 93 } 94 95 func (s *lineEndingSplitter) LineEnding() string { 96 if s.CRLFCount > s.LFCount { 97 return "\r\n" 98 } else if s.LFCount == 0 { 99 return "" 100 } 101 return "\n" 102 } 103 104 func (s *lineEndingSplitter) ScanLines(data []byte, atEOF bool) (advance int, token []byte, err error) { 105 if atEOF && len(data) == 0 { 106 return 0, nil, nil 107 } 108 if i := bytes.IndexByte(data, '\n'); i >= 0 { 109 // We have a full newline-terminated line. 110 return i + 1, s.dropCR(data[0:i]), nil 111 } 112 // If we're at EOF, we have a final, non-terminated line. Return it. 113 if atEOF { 114 return len(data), data, nil 115 } 116 // Request more data. 117 return 0, nil, nil 118 } 119 120 // dropCR drops a terminal \r from the data. 121 func (s *lineEndingSplitter) dropCR(data []byte) []byte { 122 if len(data) > 0 && data[len(data)-1] == '\r' { 123 s.CRLFCount++ 124 return data[0 : len(data)-1] 125 } 126 s.LFCount++ 127 return data 128 } 129 130 func findAttributeFiles(workingDir, gitDir string) []string { 131 var paths []string 132 133 repoAttributes := filepath.Join(gitDir, "info", "attributes") 134 if info, err := os.Stat(repoAttributes); err == nil && !info.IsDir() { 135 paths = append(paths, repoAttributes) 136 } 137 138 tools.FastWalkGitRepo(workingDir, func(parentDir string, info os.FileInfo, err error) { 139 if err != nil { 140 tracerx.Printf("Error finding .gitattributes: %v", err) 141 return 142 } 143 144 if info.IsDir() || info.Name() != ".gitattributes" { 145 return 146 } 147 paths = append(paths, filepath.Join(parentDir, info.Name())) 148 }) 149 150 // reverse the order of the files so more specific entries are found first 151 // when iterating from the front (respects precedence) 152 for i, j := 0, len(paths)-1; i < j; i, j = i+1, j-1 { 153 paths[i], paths[j] = paths[j], paths[i] 154 } 155 156 return paths 157 }