1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
|
package gitattr
import (
"strings"
"github.com/git-lfs/git-lfs/v3/errors"
"github.com/git-lfs/git-lfs/v3/tr"
"github.com/git-lfs/gitobj/v2"
)
// Tree represents the .gitattributes file at one layer of the tree in a Git
// repository.
type Tree struct {
// Lines are the lines of the .gitattributes at this level of the tree.
Lines []Line
// Children are the named child directories in the repository.
Children map[string]*Tree
}
// New constructs a *Tree starting at the given tree "t" and reading objects
// from the given ObjectDatabase. If a tree was not able to be read, an error
// will be propagated up accordingly.
func New(db *gitobj.ObjectDatabase, t *gitobj.Tree) (*Tree, error) {
children := make(map[string]*Tree)
lines, _, err := linesInTree(db, t)
if err != nil {
return nil, err
}
for _, entry := range t.Entries {
if entry.Type() != gitobj.TreeObjectType {
continue
}
// For every entry in the current tree, parse its sub-trees to
// see if they might contain a .gitattributes.
t, err := db.Tree(entry.Oid)
if err != nil {
return nil, err
}
at, err := New(db, t)
if err != nil {
return nil, err
}
if len(at.Children) > 0 || len(at.Lines) > 0 {
// Only include entries that have either (1) a
// .gitattributes in their tree, or (2) a .gitattributes
// in a sub-tree.
children[entry.Name] = at
}
}
return &Tree{
Lines: lines,
Children: children,
}, nil
}
// linesInTree parses a given tree's .gitattributes and returns a slice of lines
// in that .gitattributes, or an error. If no .gitattributes blob was found,
// return nil.
func linesInTree(db *gitobj.ObjectDatabase, t *gitobj.Tree) ([]Line, string, error) {
var at int = -1
for i, e := range t.Entries {
if e.Name == ".gitattributes" {
if e.IsLink() {
return nil, "", errors.Errorf("migrate: %s", tr.Tr.Get("expected '.gitattributes' to be a file, got a symbolic link"))
}
at = i
break
}
}
if at < 0 {
return nil, "", nil
}
blob, err := db.Blob(t.Entries[at].Oid)
if err != nil {
return nil, "", err
}
defer blob.Close()
return ParseLines(blob.Contents)
}
// Applied returns a slice of attributes applied to the given path, relative to
// the receiving tree. It traverse through sub-trees in a topological ordering,
// if there are relevant .gitattributes matching that path.
func (t *Tree) Applied(to string) []*Attr {
var attrs []*Attr
for _, line := range t.Lines {
if l, ok := line.(PatternLine); ok {
if l.Pattern().Match(to) {
attrs = append(attrs, line.Attrs()...)
}
}
}
splits := strings.SplitN(to, "/", 2)
if len(splits) == 2 {
car, cdr := splits[0], splits[1]
if child, ok := t.Children[car]; ok {
attrs = append(attrs, child.Applied(cdr)...)
}
}
return attrs
}
|