ipld-eth-server/vendor/github.com/ipfs/go-ipld-git/git.go
2019-12-02 13:24:51 -06:00

479 lines
8.6 KiB
Go

package ipldgit
import (
"bufio"
"bytes"
"compress/zlib"
"encoding/hex"
"errors"
"fmt"
"io"
"io/ioutil"
"strconv"
"strings"
blocks "github.com/ipfs/go-block-format"
cid "github.com/ipfs/go-cid"
node "github.com/ipfs/go-ipld-format"
mh "github.com/multiformats/go-multihash"
)
func DecodeBlock(block blocks.Block) (node.Node, error) {
prefix := block.Cid().Prefix()
if prefix.Codec != cid.GitRaw || prefix.MhType != mh.SHA1 || prefix.MhLength != mh.DefaultLengths[mh.SHA1] {
return nil, errors.New("invalid CID prefix")
}
return ParseObjectFromBuffer(block.RawData())
}
var _ node.DecodeBlockFunc = DecodeBlock
func ParseObjectFromBuffer(b []byte) (node.Node, error) {
return ParseObject(bytes.NewReader(b))
}
func ParseCompressedObject(r io.Reader) (node.Node, error) {
rc, err := zlib.NewReader(r)
if err != nil {
return nil, err
}
defer rc.Close()
return ParseObject(rc)
}
func ParseObject(r io.Reader) (node.Node, error) {
rd := bufio.NewReader(r)
typ, err := rd.ReadString(' ')
if err != nil {
return nil, err
}
typ = typ[:len(typ)-1]
switch typ {
case "tree":
return ReadTree(rd)
case "commit":
return ReadCommit(rd)
case "blob":
return ReadBlob(rd)
case "tag":
return ReadTag(rd)
default:
return nil, fmt.Errorf("unrecognized object type: %s", typ)
}
}
func ReadBlob(rd *bufio.Reader) (*Blob, error) {
size, err := rd.ReadString(0)
if err != nil {
return nil, err
}
sizen, err := strconv.Atoi(size[:len(size)-1])
if err != nil {
return nil, err
}
buf := new(bytes.Buffer)
fmt.Fprintf(buf, "blob %d\x00", sizen)
n, err := io.Copy(buf, rd)
if err != nil {
return nil, err
}
if n != int64(sizen) {
return nil, fmt.Errorf("blob size was not accurate")
}
out := &Blob{}
out.rawData = buf.Bytes()
out.cid = hashObject(out.RawData())
return out, nil
}
func ReadCommit(rd *bufio.Reader) (*Commit, error) {
size, err := rd.ReadString(0)
if err != nil {
return nil, err
}
out := &Commit{
DataSize: size[:len(size)-1],
}
for {
line, _, err := rd.ReadLine()
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
err = parseCommitLine(out, line, rd)
if err != nil {
return nil, err
}
}
out.cid = hashObject(out.RawData())
return out, nil
}
func parseCommitLine(out *Commit, line []byte, rd *bufio.Reader) error {
switch {
case bytes.HasPrefix(line, []byte("tree ")):
sha, err := hex.DecodeString(string(line[5:]))
if err != nil {
return err
}
out.GitTree = shaToCid(sha)
case bytes.HasPrefix(line, []byte("parent ")):
psha, err := hex.DecodeString(string(line[7:]))
if err != nil {
return err
}
out.Parents = append(out.Parents, shaToCid(psha))
case bytes.HasPrefix(line, []byte("author ")):
a, err := parsePersonInfo(line)
if err != nil {
return err
}
out.Author = a
case bytes.HasPrefix(line, []byte("committer ")):
c, err := parsePersonInfo(line)
if err != nil {
return err
}
out.Committer = c
case bytes.HasPrefix(line, []byte("encoding ")):
out.Encoding = string(line[9:])
case bytes.HasPrefix(line, []byte("mergetag object ")):
sha, err := hex.DecodeString(string(line)[16:])
if err != nil {
return err
}
mt, rest, err := ReadMergeTag(sha, rd)
if err != nil {
return err
}
out.MergeTag = append(out.MergeTag, mt)
if rest != nil {
err = parseCommitLine(out, rest, rd)
if err != nil {
return err
}
}
case bytes.HasPrefix(line, []byte("gpgsig ")):
sig, err := ReadGpgSig(rd)
if err != nil {
return err
}
out.Sig = sig
case len(line) == 0:
rest, err := ioutil.ReadAll(rd)
if err != nil {
return err
}
out.Message = string(rest)
default:
out.Other = append(out.Other, string(line))
}
return nil
}
func ReadTag(rd *bufio.Reader) (*Tag, error) {
size, err := rd.ReadString(0)
if err != nil {
return nil, err
}
out := &Tag{
dataSize: size[:len(size)-1],
}
for {
line, _, err := rd.ReadLine()
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
switch {
case bytes.HasPrefix(line, []byte("object ")):
sha, err := hex.DecodeString(string(line[7:]))
if err != nil {
return nil, err
}
out.Object = shaToCid(sha)
case bytes.HasPrefix(line, []byte("tag ")):
out.Tag = string(line[4:])
case bytes.HasPrefix(line, []byte("tagger ")):
c, err := parsePersonInfo(line)
if err != nil {
return nil, err
}
out.Tagger = c
case bytes.HasPrefix(line, []byte("type ")):
out.Type = string(line[5:])
case len(line) == 0:
rest, err := ioutil.ReadAll(rd)
if err != nil {
return nil, err
}
out.Message = string(rest)
default:
fmt.Println("unhandled line: ", string(line))
}
}
out.cid = hashObject(out.RawData())
return out, nil
}
func hashObject(data []byte) cid.Cid {
c, err := cid.Prefix{
MhType: mh.SHA1,
MhLength: -1,
Codec: cid.GitRaw,
Version: 1,
}.Sum(data)
if err != nil {
panic(err)
}
return c
}
func ReadMergeTag(hash []byte, rd *bufio.Reader) (*MergeTag, []byte, error) {
out := new(MergeTag)
out.Object = shaToCid(hash)
for {
line, _, err := rd.ReadLine()
if err != nil {
if err == io.EOF {
break
}
return nil, nil, err
}
switch {
case bytes.HasPrefix(line, []byte(" type ")):
out.Type = string(line[6:])
case bytes.HasPrefix(line, []byte(" tag ")):
out.Tag = string(line[5:])
case bytes.HasPrefix(line, []byte(" tagger ")):
tagger, err := parsePersonInfo(line[1:])
if err != nil {
return nil, nil, err
}
out.Tagger = tagger
case string(line) == " ":
for {
line, _, err := rd.ReadLine()
if err != nil {
return nil, nil, err
}
if !bytes.HasPrefix(line, []byte(" ")) {
return out, line, nil
}
out.Text += string(line) + "\n"
}
}
}
return out, nil, nil
}
func ReadGpgSig(rd *bufio.Reader) (*GpgSig, error) {
line, _, err := rd.ReadLine()
if err != nil {
return nil, err
}
out := new(GpgSig)
if string(line) != " " {
if strings.HasPrefix(string(line), " Version: ") || strings.HasPrefix(string(line), " Comment: ") {
out.Text += string(line) + "\n"
} else {
return nil, fmt.Errorf("expected first line of sig to be a single space or version")
}
} else {
out.Text += " \n"
}
for {
line, _, err := rd.ReadLine()
if err != nil {
return nil, err
}
if bytes.Equal(line, []byte(" -----END PGP SIGNATURE-----")) {
break
}
out.Text += string(line) + "\n"
}
return out, nil
}
func parsePersonInfo(line []byte) (*PersonInfo, error) {
parts := bytes.Split(line, []byte{' '})
if len(parts) < 3 {
fmt.Println(string(line))
return nil, fmt.Errorf("incorrectly formatted person info line")
}
//TODO: just use regex?
//skip prefix
at := 1
var pi PersonInfo
var name string
for {
if at == len(parts) {
return nil, fmt.Errorf("invalid personInfo: %s\n", line)
}
part := parts[at]
if len(part) != 0 {
if part[0] == '<' {
break
}
name += string(part) + " "
} else if len(name) > 0 {
name += " "
}
at++
}
if len(name) != 0 {
pi.Name = name[:len(name)-1]
}
var email string
for {
if at == len(parts) {
return nil, fmt.Errorf("invalid personInfo: %s\n", line)
}
part := parts[at]
if part[0] == '<' {
part = part[1:]
}
at++
if part[len(part)-1] == '>' {
email += string(part[:len(part)-1])
break
}
email += string(part) + " "
}
pi.Email = email
if at == len(parts) {
return &pi, nil
}
pi.Date = string(parts[at])
at++
if at == len(parts) {
return &pi, nil
}
pi.Timezone = string(parts[at])
return &pi, nil
}
func ReadTree(rd *bufio.Reader) (*Tree, error) {
lstr, err := rd.ReadString(0)
if err != nil {
return nil, err
}
lstr = lstr[:len(lstr)-1]
n, err := strconv.Atoi(lstr)
if err != nil {
return nil, err
}
t := &Tree{
entries: make(map[string]*TreeEntry),
size: n,
}
var order []string
for {
e, err := ReadEntry(rd)
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
order = append(order, e.name)
t.entries[e.name] = e
}
t.order = order
t.cid = hashObject(t.RawData())
return t, nil
}
func cidToSha(c cid.Cid) []byte {
h := c.Hash()
return h[len(h)-20:]
}
func shaToCid(sha []byte) cid.Cid {
h, _ := mh.Encode(sha, mh.SHA1)
return cid.NewCidV1(cid.GitRaw, h)
}
func ReadEntry(r *bufio.Reader) (*TreeEntry, error) {
data, err := r.ReadString(' ')
if err != nil {
return nil, err
}
data = data[:len(data)-1]
name, err := r.ReadString(0)
if err != nil {
return nil, err
}
name = name[:len(name)-1]
sha := make([]byte, 20)
_, err = io.ReadFull(r, sha)
if err != nil {
return nil, err
}
return &TreeEntry{
name: name,
Mode: data,
Hash: shaToCid(sha),
}, nil
}