238 lines
6.0 KiB
Go
238 lines
6.0 KiB
Go
|
// Package object contains implementations of all Git objects and utility
|
||
|
// functions to work with them.
|
||
|
package object
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"strconv"
|
||
|
"time"
|
||
|
|
||
|
"gopkg.in/src-d/go-git.v4/plumbing"
|
||
|
"gopkg.in/src-d/go-git.v4/plumbing/storer"
|
||
|
)
|
||
|
|
||
|
// ErrUnsupportedObject trigger when a non-supported object is being decoded.
|
||
|
var ErrUnsupportedObject = errors.New("unsupported object type")
|
||
|
|
||
|
// Object is a generic representation of any git object. It is implemented by
|
||
|
// Commit, Tree, Blob, and Tag, and includes the functions that are common to
|
||
|
// them.
|
||
|
//
|
||
|
// Object is returned when an object can be of any type. It is frequently used
|
||
|
// with a type cast to acquire the specific type of object:
|
||
|
//
|
||
|
// func process(obj Object) {
|
||
|
// switch o := obj.(type) {
|
||
|
// case *Commit:
|
||
|
// // o is a Commit
|
||
|
// case *Tree:
|
||
|
// // o is a Tree
|
||
|
// case *Blob:
|
||
|
// // o is a Blob
|
||
|
// case *Tag:
|
||
|
// // o is a Tag
|
||
|
// }
|
||
|
// }
|
||
|
//
|
||
|
// This interface is intentionally different from plumbing.EncodedObject, which
|
||
|
// is a lower level interface used by storage implementations to read and write
|
||
|
// objects in its encoded form.
|
||
|
type Object interface {
|
||
|
ID() plumbing.Hash
|
||
|
Type() plumbing.ObjectType
|
||
|
Decode(plumbing.EncodedObject) error
|
||
|
Encode(plumbing.EncodedObject) error
|
||
|
}
|
||
|
|
||
|
// GetObject gets an object from an object storer and decodes it.
|
||
|
func GetObject(s storer.EncodedObjectStorer, h plumbing.Hash) (Object, error) {
|
||
|
o, err := s.EncodedObject(plumbing.AnyObject, h)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return DecodeObject(s, o)
|
||
|
}
|
||
|
|
||
|
// DecodeObject decodes an encoded object into an Object and associates it to
|
||
|
// the given object storer.
|
||
|
func DecodeObject(s storer.EncodedObjectStorer, o plumbing.EncodedObject) (Object, error) {
|
||
|
switch o.Type() {
|
||
|
case plumbing.CommitObject:
|
||
|
return DecodeCommit(s, o)
|
||
|
case plumbing.TreeObject:
|
||
|
return DecodeTree(s, o)
|
||
|
case plumbing.BlobObject:
|
||
|
return DecodeBlob(o)
|
||
|
case plumbing.TagObject:
|
||
|
return DecodeTag(s, o)
|
||
|
default:
|
||
|
return nil, plumbing.ErrInvalidType
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// DateFormat is the format being used in the original git implementation
|
||
|
const DateFormat = "Mon Jan 02 15:04:05 2006 -0700"
|
||
|
|
||
|
// Signature is used to identify who and when created a commit or tag.
|
||
|
type Signature struct {
|
||
|
// Name represents a person name. It is an arbitrary string.
|
||
|
Name string
|
||
|
// Email is an email, but it cannot be assumed to be well-formed.
|
||
|
Email string
|
||
|
// When is the timestamp of the signature.
|
||
|
When time.Time
|
||
|
}
|
||
|
|
||
|
// Decode decodes a byte slice into a signature
|
||
|
func (s *Signature) Decode(b []byte) {
|
||
|
open := bytes.LastIndexByte(b, '<')
|
||
|
close := bytes.LastIndexByte(b, '>')
|
||
|
if open == -1 || close == -1 {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if close < open {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
s.Name = string(bytes.Trim(b[:open], " "))
|
||
|
s.Email = string(b[open+1 : close])
|
||
|
|
||
|
hasTime := close+2 < len(b)
|
||
|
if hasTime {
|
||
|
s.decodeTimeAndTimeZone(b[close+2:])
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Encode encodes a Signature into a writer.
|
||
|
func (s *Signature) Encode(w io.Writer) error {
|
||
|
if _, err := fmt.Fprintf(w, "%s <%s> ", s.Name, s.Email); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if err := s.encodeTimeAndTimeZone(w); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
var timeZoneLength = 5
|
||
|
|
||
|
func (s *Signature) decodeTimeAndTimeZone(b []byte) {
|
||
|
space := bytes.IndexByte(b, ' ')
|
||
|
if space == -1 {
|
||
|
space = len(b)
|
||
|
}
|
||
|
|
||
|
ts, err := strconv.ParseInt(string(b[:space]), 10, 64)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
s.When = time.Unix(ts, 0).In(time.UTC)
|
||
|
var tzStart = space + 1
|
||
|
if tzStart >= len(b) || tzStart+timeZoneLength > len(b) {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Include a dummy year in this time.Parse() call to avoid a bug in Go:
|
||
|
// https://github.com/golang/go/issues/19750
|
||
|
//
|
||
|
// Parsing the timezone with no other details causes the tl.Location() call
|
||
|
// below to return time.Local instead of the parsed zone in some cases
|
||
|
tl, err := time.Parse("2006 -0700", "1970 "+string(b[tzStart:tzStart+timeZoneLength]))
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
s.When = s.When.In(tl.Location())
|
||
|
}
|
||
|
|
||
|
func (s *Signature) encodeTimeAndTimeZone(w io.Writer) error {
|
||
|
u := s.When.Unix()
|
||
|
if u < 0 {
|
||
|
u = 0
|
||
|
}
|
||
|
_, err := fmt.Fprintf(w, "%d %s", u, s.When.Format("-0700"))
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
func (s *Signature) String() string {
|
||
|
return fmt.Sprintf("%s <%s>", s.Name, s.Email)
|
||
|
}
|
||
|
|
||
|
// ObjectIter provides an iterator for a set of objects.
|
||
|
type ObjectIter struct {
|
||
|
storer.EncodedObjectIter
|
||
|
s storer.EncodedObjectStorer
|
||
|
}
|
||
|
|
||
|
// NewObjectIter takes a storer.EncodedObjectStorer and a
|
||
|
// storer.EncodedObjectIter and returns an *ObjectIter that iterates over all
|
||
|
// objects contained in the storer.EncodedObjectIter.
|
||
|
func NewObjectIter(s storer.EncodedObjectStorer, iter storer.EncodedObjectIter) *ObjectIter {
|
||
|
return &ObjectIter{iter, s}
|
||
|
}
|
||
|
|
||
|
// Next moves the iterator to the next object and returns a pointer to it. If
|
||
|
// there are no more objects, it returns io.EOF.
|
||
|
func (iter *ObjectIter) Next() (Object, error) {
|
||
|
for {
|
||
|
obj, err := iter.EncodedObjectIter.Next()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
o, err := iter.toObject(obj)
|
||
|
if err == plumbing.ErrInvalidType {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return o, nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ForEach call the cb function for each object contained on this iter until
|
||
|
// an error happens or the end of the iter is reached. If ErrStop is sent
|
||
|
// the iteration is stop but no error is returned. The iterator is closed.
|
||
|
func (iter *ObjectIter) ForEach(cb func(Object) error) error {
|
||
|
return iter.EncodedObjectIter.ForEach(func(obj plumbing.EncodedObject) error {
|
||
|
o, err := iter.toObject(obj)
|
||
|
if err == plumbing.ErrInvalidType {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return cb(o)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func (iter *ObjectIter) toObject(obj plumbing.EncodedObject) (Object, error) {
|
||
|
switch obj.Type() {
|
||
|
case plumbing.BlobObject:
|
||
|
blob := &Blob{}
|
||
|
return blob, blob.Decode(obj)
|
||
|
case plumbing.TreeObject:
|
||
|
tree := &Tree{s: iter.s}
|
||
|
return tree, tree.Decode(obj)
|
||
|
case plumbing.CommitObject:
|
||
|
commit := &Commit{}
|
||
|
return commit, commit.Decode(obj)
|
||
|
case plumbing.TagObject:
|
||
|
tag := &Tag{}
|
||
|
return tag, tag.Decode(obj)
|
||
|
default:
|
||
|
return nil, plumbing.ErrInvalidType
|
||
|
}
|
||
|
}
|