1package gitattributes
2
3import (
4 "os"
5 "path/filepath"
6 "strings"
7
8 "github.com/go-git/go-billy/v5"
9
10 "github.com/go-git/go-git/v5/plumbing/format/config"
11 gioutil "github.com/go-git/go-git/v5/utils/ioutil"
12)
13
14const (
15 coreSection = "core"
16 attributesfile = "attributesfile"
17 gitDir = ".git"
18 gitattributesFile = ".gitattributes"
19 gitconfigFile = ".gitconfig"
20 systemFile = "/etc/gitconfig"
21)
22
23func ReadAttributesFile(fs billy.Filesystem, path []string, attributesFile string, allowMacro bool) ([]MatchAttribute, error) {
24 f, err := fs.Open(fs.Join(append(path, attributesFile)...))
25 if os.IsNotExist(err) {
26 return nil, nil
27 }
28 if err != nil {
29 return nil, err
30 }
31
32 defer gioutil.CheckClose(f, &err)
33
34 return ReadAttributes(f, path, allowMacro)
35}
36
37// ReadPatterns reads gitattributes patterns recursively through the directory
38// structure. The result is in ascending order of priority (last higher).
39//
40// The .gitattribute file in the root directory will allow custom macro
41// definitions. Custom macro definitions in other directories .gitattributes
42// will return an error.
43func ReadPatterns(fs billy.Filesystem, path []string) (attributes []MatchAttribute, err error) {
44 attributes, err = ReadAttributesFile(fs, path, gitattributesFile, true)
45 if err != nil {
46 return
47 }
48
49 attrs, err := walkDirectory(fs, path)
50 return append(attributes, attrs...), err
51}
52
53func walkDirectory(fs billy.Filesystem, root []string) (attributes []MatchAttribute, err error) {
54 fis, err := fs.ReadDir(fs.Join(root...))
55 if err != nil {
56 return attributes, err
57 }
58
59 for _, fi := range fis {
60 if !fi.IsDir() || fi.Name() == ".git" {
61 continue
62 }
63
64 p := fi.Name()
65
66 // Handles the case whereby just the volume name ("C:") is appended,
67 // to root. Change it to "C:\", which is better handled by fs.Join().
68 if filepath.VolumeName(p) != "" && !strings.HasSuffix(p, string(filepath.Separator)) {
69 p = p + string(filepath.Separator)
70 }
71 path := append(root, p)
72
73 dirAttributes, err := ReadAttributesFile(fs, path, gitattributesFile, false)
74 if err != nil {
75 return attributes, err
76 }
77
78 subAttributes, err := walkDirectory(fs, path)
79 if err != nil {
80 return attributes, err
81 }
82
83 attributes = append(attributes, append(dirAttributes, subAttributes...)...)
84 }
85
86 return
87}
88
89func loadPatterns(fs billy.Filesystem, path string) ([]MatchAttribute, error) {
90 f, err := fs.Open(path)
91 if os.IsNotExist(err) {
92 return nil, nil
93 }
94 if err != nil {
95 return nil, err
96 }
97 defer gioutil.CheckClose(f, &err)
98
99 raw := config.New()
100 if err = config.NewDecoder(f).Decode(raw); err != nil {
101 return nil, nil
102 }
103
104 path = raw.Section(coreSection).Options.Get(attributesfile)
105 if path == "" {
106 return nil, nil
107 }
108
109 return ReadAttributesFile(fs, nil, path, true)
110}
111
112// LoadGlobalPatterns loads gitattributes patterns and attributes from the
113// gitattributes file declared in a user's ~/.gitconfig file. If the
114// ~/.gitconfig file does not exist the function will return nil. If the
115// core.attributesFile property is not declared, the function will return nil.
116// If the file pointed to by the core.attributesfile property does not exist,
117// the function will return nil. The function assumes fs is rooted at the root
118// filesystem.
119func LoadGlobalPatterns(fs billy.Filesystem) (attributes []MatchAttribute, err error) {
120 home, err := os.UserHomeDir()
121 if err != nil {
122 return
123 }
124
125 return loadPatterns(fs, fs.Join(home, gitconfigFile))
126}
127
128// LoadSystemPatterns loads gitattributes patterns and attributes from the
129// gitattributes file declared in a system's /etc/gitconfig file. If the
130// /etc/gitconfig file does not exist the function will return nil. If the
131// core.attributesfile property is not declared, the function will return nil.
132// If the file pointed to by the core.attributesfile property does not exist,
133// the function will return nil. The function assumes fs is rooted at the root
134// filesystem.
135func LoadSystemPatterns(fs billy.Filesystem) (attributes []MatchAttribute, err error) {
136 return loadPatterns(fs, systemFile)
137}