2017-05-15 21:59:55 +00:00
|
|
|
package odb
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"syscall"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Tree encapsulates a Git tree object.
|
|
|
|
type Tree struct {
|
|
|
|
// Entries is the list of entries held by this tree.
|
|
|
|
Entries []*TreeEntry
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ Object = (*Tree)(nil)
|
|
|
|
|
|
|
|
// Type implements Object.ObjectType by returning the correct object type for
|
|
|
|
// Trees, TreeObjectType.
|
|
|
|
func (t *Tree) Type() ObjectType { return TreeObjectType }
|
|
|
|
|
|
|
|
// Decode implements Object.Decode and decodes the uncompressed tree being
|
|
|
|
// read. It returns the number of uncompressed bytes being consumed off of the
|
|
|
|
// stream, which should be strictly equal to the size given.
|
|
|
|
//
|
|
|
|
// If any error was encountered along the way, that will be returned, along with
|
|
|
|
// the number of bytes read up to that point.
|
|
|
|
func (t *Tree) Decode(from io.Reader, size int64) (n int, err error) {
|
|
|
|
buf := bufio.NewReader(from)
|
|
|
|
|
|
|
|
var entries []*TreeEntry
|
|
|
|
for {
|
|
|
|
modes, err := buf.ReadString(' ')
|
|
|
|
if err != nil {
|
|
|
|
if err == io.EOF {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
return n, err
|
|
|
|
}
|
|
|
|
n += len(modes)
|
|
|
|
modes = strings.TrimSuffix(modes, " ")
|
|
|
|
|
|
|
|
mode, _ := strconv.ParseInt(modes, 8, 32)
|
|
|
|
|
|
|
|
fname, err := buf.ReadString('\x00')
|
|
|
|
if err != nil {
|
|
|
|
return n, err
|
|
|
|
}
|
|
|
|
n += len(fname)
|
|
|
|
fname = strings.TrimSuffix(fname, "\x00")
|
|
|
|
|
|
|
|
var sha [20]byte
|
|
|
|
if _, err = buf.Read(sha[:]); err != nil {
|
|
|
|
return n, err
|
|
|
|
}
|
|
|
|
n += 20
|
|
|
|
|
|
|
|
var typ ObjectType
|
|
|
|
switch mode & syscall.S_IFMT {
|
|
|
|
case syscall.S_IFREG:
|
|
|
|
typ = BlobObjectType
|
|
|
|
case syscall.S_IFDIR:
|
|
|
|
typ = TreeObjectType
|
|
|
|
case syscall.S_IFLNK:
|
|
|
|
typ = BlobObjectType
|
|
|
|
default:
|
2017-05-16 18:10:58 +00:00
|
|
|
if mode == 0xe000 {
|
|
|
|
// Mode 0xe000, or a gitlink, has no formal
|
|
|
|
// filesystem (`syscall.S_IF<t>`) equivalent.
|
|
|
|
//
|
|
|
|
// Safeguard that catch here, or otherwise
|
|
|
|
// panic.
|
|
|
|
typ = CommitObjectType
|
|
|
|
} else {
|
|
|
|
panic(fmt.Sprintf("git/odb: unknown object type: %q %q", modes, fname))
|
|
|
|
}
|
2017-05-15 21:59:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
entries = append(entries, &TreeEntry{
|
|
|
|
Name: fname,
|
|
|
|
Type: typ,
|
|
|
|
Oid: sha[:],
|
|
|
|
Filemode: int32(mode),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
t.Entries = entries
|
|
|
|
|
|
|
|
return n, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Encode encodes the tree's contents to the given io.Writer, "w". If there was
|
|
|
|
// any error copying the tree's contents, that error will be returned.
|
|
|
|
//
|
|
|
|
// Otherwise, the number of bytes written will be returned.
|
|
|
|
func (t *Tree) Encode(to io.Writer) (n int, err error) {
|
|
|
|
const entryTmpl = "%s %s\x00%s"
|
|
|
|
|
|
|
|
for _, entry := range t.Entries {
|
|
|
|
fmode := strconv.FormatInt(int64(entry.Filemode), 8)
|
|
|
|
|
|
|
|
ne, err := fmt.Fprintf(to, entryTmpl,
|
|
|
|
fmode,
|
|
|
|
entry.Name,
|
|
|
|
entry.Oid)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return n, err
|
|
|
|
}
|
|
|
|
|
|
|
|
n = n + ne
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// TreeEntry encapsulates information about a single tree entry in a tree
|
|
|
|
// listing.
|
|
|
|
type TreeEntry struct {
|
|
|
|
// Name is the entry name relative to the tree in which this entry is
|
|
|
|
// contained.
|
|
|
|
Name string
|
|
|
|
// Type is the type of entry (either blob: BlobObjectType, or a
|
|
|
|
// sub-tree: TreeObjectType).
|
|
|
|
Type ObjectType
|
|
|
|
// Oid is the object ID for this tree entry.
|
|
|
|
Oid []byte
|
|
|
|
// Filemode is the filemode of this tree entry on disk.
|
|
|
|
Filemode int32
|
|
|
|
}
|