tree: add a managed version of the tree

It offers the most of the same interface as the libgit2 tree, but the parsed
memory has gone straight into managed, avoiding the expensive calls into C for
accessors.
This commit is contained in:
Carlos Martín Nieto 2017-10-19 21:22:35 +02:00
parent f439cc93e9
commit e40cf421a0
2 changed files with 139 additions and 0 deletions

119
managed_tree.go Normal file
View File

@ -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
}
}

View File

@ -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) { func TestTreeBuilderInsert(t *testing.T) {
t.Parallel() t.Parallel()
repo := createTestRepo(t) repo := createTestRepo(t)