package gitattributes import ( "os" "os/user" "github.com/go-git/go-billy/v5" "github.com/go-git/go-git/v5/plumbing/format/config" gioutil "github.com/go-git/go-git/v5/utils/ioutil" ) const ( coreSection = "core" attributesfile = "attributesfile" gitDir = ".git" gitattributesFile = ".gitattributes" gitconfigFile = ".gitconfig" systemFile = "/etc/gitconfig" ) func ReadAttributesFile(fs billy.Filesystem, path []string, attributesFile string, allowMacro bool) ([]MatchAttribute, error) { f, err := fs.Open(fs.Join(append(path, attributesFile)...)) if os.IsNotExist(err) { return nil, nil } if err != nil { return nil, err } return ReadAttributes(f, path, allowMacro) } // ReadPatterns reads gitattributes patterns recursively through the directory // structure. The result is in ascending order of priority (last higher). // // The .gitattribute file in the root directory will allow custom macro // definitions. Custom macro definitions in other directories .gitattributes // will return an error. func ReadPatterns(fs billy.Filesystem, path []string) (attributes []MatchAttribute, err error) { attributes, err = ReadAttributesFile(fs, path, gitattributesFile, true) if err != nil { return } attrs, err := walkDirectory(fs, path) return append(attributes, attrs...), err } func walkDirectory(fs billy.Filesystem, root []string) (attributes []MatchAttribute, err error) { fis, err := fs.ReadDir(fs.Join(root...)) if err != nil { return attributes, err } for _, fi := range fis { if !fi.IsDir() || fi.Name() == ".git" { continue } path := append(root, fi.Name()) dirAttributes, err := ReadAttributesFile(fs, path, gitattributesFile, false) if err != nil { return attributes, err } subAttributes, err := walkDirectory(fs, path) if err != nil { return attributes, err } attributes = append(attributes, append(dirAttributes, subAttributes...)...) } return } func loadPatterns(fs billy.Filesystem, path string) ([]MatchAttribute, error) { f, err := fs.Open(path) if os.IsNotExist(err) { return nil, nil } if err != nil { return nil, err } defer gioutil.CheckClose(f, &err) raw := config.New() if err = config.NewDecoder(f).Decode(raw); err != nil { return nil, nil } path = raw.Section(coreSection).Options.Get(attributesfile) if path == "" { return nil, nil } return ReadAttributesFile(fs, nil, path, true) } // LoadGlobalPatterns loads gitattributes patterns and attributes from the // gitattributes file declared in a user's ~/.gitconfig file. If the // ~/.gitconfig file does not exist the function will return nil. If the // core.attributesFile property is not declared, the function will return nil. // If the file pointed to by the core.attributesfile property does not exist, // the function will return nil. The function assumes fs is rooted at the root // filesystem. func LoadGlobalPatterns(fs billy.Filesystem) (attributes []MatchAttribute, err error) { usr, err := user.Current() if err != nil { return } return loadPatterns(fs, fs.Join(usr.HomeDir, gitconfigFile)) } // LoadSystemPatterns loads gitattributes patterns and attributes from the // gitattributes file declared in a system's /etc/gitconfig file. If the // /etc/gitconfig file does not exist the function will return nil. If the // core.attributesfile property is not declared, the function will return nil. // If the file pointed to by the core.attributesfile property does not exist, // the function will return nil. The function assumes fs is rooted at the root // filesystem. func LoadSystemPatterns(fs billy.Filesystem) (attributes []MatchAttribute, err error) { return loadPatterns(fs, systemFile) }