diff --git a/managed_tree.go b/managed_tree.go new file mode 100644 index 0000000..cc509a7 --- /dev/null +++ b/managed_tree.go @@ -0,0 +1,119 @@ +package git + +import ( + "bytes" + "errors" + "runtime" + "strconv" +) + +// An equivalent of the Tree struct except this one is parsed in Go. There is no +// corresponding libgit2 object so you cannot do everything that you can with a +// Tree. +type ManagedTree struct { + m map[string]*TreeEntry + l []*TreeEntry +} + +func (t *ManagedTree) EntryById(id *Oid) *TreeEntry { + for _, entry := range t.l { + if entry.Id.Equal(id) { + return entry + } + } + + return nil +} + +func (t *ManagedTree) EntryByName(filename string) *TreeEntry { + return t.m[filename] +} + +func (t *ManagedTree) EntryByIndex(index uint64) *TreeEntry { + return t.l[index] +} + +func (t *ManagedTree) EntryCount() uint64 { + return uint64(len(t.l)) +} + +// NewManagedTree retrieves the tree with the given id and parses it. +func NewManagedTree(r *Repository, id *Oid) (*ManagedTree, error) { + odb, err := r.Odb() + if err != nil { + return nil, err + } + + obj, err := odb.Read(id) + if err != nil { + return nil, err + } + + if obj.Type() != ObjectTree { + return nil, errors.New("object is not a tree") + } + + data := obj.Data() + l := make([]*TreeEntry, 0, 8) + m := make(map[string]*TreeEntry) + + var done bool + for !done { + spAt := bytes.IndexByte(data, ' ') + if spAt < 0 { + return nil, errors.New("failed to find SP after mode") + } + mode, err := strconv.ParseInt(string(data[:spAt]), 8, 32) + if err != nil { + return nil, err + } + + data = data[spAt+1:] + nulAt := bytes.IndexByte(data, 0) + if nulAt < 0 { + return nil, errors.New("failed to find NUL after filename") + } + + var name bytes.Buffer + name.Grow(nulAt) + name.Write(data[:nulAt]) + + data = data[nulAt+1:] + oid := NewOidFromBytes(data) + if len(data) > 20 { + data = data[20:] + } else { + done = true + } + + entry := &TreeEntry{ + Name: name.String(), + Id: oid, + Type: typeFromMode(mode), + Filemode: Filemode(mode), + } + + l = append(l, entry) + m[entry.Name] = entry + } + + // This avoids the runtime from garbage-collecting 'obj' and freeing the + // memory we're borrowing from libgit2. + runtime.KeepAlive(obj) + + return &ManagedTree{ + l: l, + m: m, + }, nil +} + +func typeFromMode(mode int64) ObjectType { + switch Filemode(mode) { + case FilemodeTree: + return ObjectTree + case FilemodeCommit: + return ObjectCommit + default: + return ObjectBlob + } +} diff --git a/tree_test.go b/tree_test.go index f5b6822..108819d 100644 --- a/tree_test.go +++ b/tree_test.go @@ -22,6 +22,26 @@ func TestTreeEntryById(t *testing.T) { } } +func TestManagedTreeEntryById(t *testing.T) { + t.Parallel() + repo := createTestRepo(t) + defer cleanupTestRepo(t, repo) + + _, treeID := seedTestRepo(t, repo) + + tree, err := NewManagedTree(repo, treeID) + checkFatal(t, err) + + id, err := NewOid("257cc5642cb1a054f08cc83f2d943e56fd3ebe99") + checkFatal(t, err) + + entry := tree.EntryById(id) + + if entry == nil { + t.Fatalf("entry id %v was not found", id) + } +} + func TestTreeBuilderInsert(t *testing.T) { t.Parallel() repo := createTestRepo(t)