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:
parent
f439cc93e9
commit
e40cf421a0
|
@ -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
|
||||
}
|
||||
}
|
20
tree_test.go
20
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)
|
||||
|
|
Loading…
Reference in New Issue