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