fork of go-git with some jj specific features
1// Package object contains implementations of all Git objects and utility
2// functions to work with them.
3package object
4
5import (
6 "bytes"
7 "errors"
8 "fmt"
9 "io"
10 "strconv"
11 "time"
12
13 "github.com/go-git/go-git/v5/plumbing"
14 "github.com/go-git/go-git/v5/plumbing/storer"
15)
16
17// ErrUnsupportedObject trigger when a non-supported object is being decoded.
18var ErrUnsupportedObject = errors.New("unsupported object type")
19
20// Object is a generic representation of any git object. It is implemented by
21// Commit, Tree, Blob, and Tag, and includes the functions that are common to
22// them.
23//
24// Object is returned when an object can be of any type. It is frequently used
25// with a type cast to acquire the specific type of object:
26//
27// func process(obj Object) {
28// switch o := obj.(type) {
29// case *Commit:
30// // o is a Commit
31// case *Tree:
32// // o is a Tree
33// case *Blob:
34// // o is a Blob
35// case *Tag:
36// // o is a Tag
37// }
38// }
39//
40// This interface is intentionally different from plumbing.EncodedObject, which
41// is a lower level interface used by storage implementations to read and write
42// objects in its encoded form.
43type Object interface {
44 ID() plumbing.Hash
45 Type() plumbing.ObjectType
46 Decode(plumbing.EncodedObject) error
47 Encode(plumbing.EncodedObject) error
48}
49
50// GetObject gets an object from an object storer and decodes it.
51func GetObject(s storer.EncodedObjectStorer, h plumbing.Hash) (Object, error) {
52 o, err := s.EncodedObject(plumbing.AnyObject, h)
53 if err != nil {
54 return nil, err
55 }
56
57 return DecodeObject(s, o)
58}
59
60// DecodeObject decodes an encoded object into an Object and associates it to
61// the given object storer.
62func DecodeObject(s storer.EncodedObjectStorer, o plumbing.EncodedObject) (Object, error) {
63 switch o.Type() {
64 case plumbing.CommitObject:
65 return DecodeCommit(s, o)
66 case plumbing.TreeObject:
67 return DecodeTree(s, o)
68 case plumbing.BlobObject:
69 return DecodeBlob(o)
70 case plumbing.TagObject:
71 return DecodeTag(s, o)
72 default:
73 return nil, plumbing.ErrInvalidType
74 }
75}
76
77// DateFormat is the format being used in the original git implementation
78const DateFormat = "Mon Jan 02 15:04:05 2006 -0700"
79
80// Signature is used to identify who and when created a commit or tag.
81type Signature struct {
82 // Name represents a person name. It is an arbitrary string.
83 Name string
84 // Email is an email, but it cannot be assumed to be well-formed.
85 Email string
86 // When is the timestamp of the signature.
87 When time.Time
88}
89
90// Decode decodes a byte slice into a signature
91func (s *Signature) Decode(b []byte) {
92 open := bytes.LastIndexByte(b, '<')
93 close := bytes.LastIndexByte(b, '>')
94 if open == -1 || close == -1 {
95 return
96 }
97
98 if close < open {
99 return
100 }
101
102 s.Name = string(bytes.Trim(b[:open], " "))
103 s.Email = string(b[open+1 : close])
104
105 hasTime := close+2 < len(b)
106 if hasTime {
107 s.decodeTimeAndTimeZone(b[close+2:])
108 }
109}
110
111// Encode encodes a Signature into a writer.
112func (s *Signature) Encode(w io.Writer) error {
113 if _, err := fmt.Fprintf(w, "%s <%s> ", s.Name, s.Email); err != nil {
114 return err
115 }
116 if err := s.encodeTimeAndTimeZone(w); err != nil {
117 return err
118 }
119 return nil
120}
121
122var timeZoneLength = 5
123
124func (s *Signature) decodeTimeAndTimeZone(b []byte) {
125 space := bytes.IndexByte(b, ' ')
126 if space == -1 {
127 space = len(b)
128 }
129
130 ts, err := strconv.ParseInt(string(b[:space]), 10, 64)
131 if err != nil {
132 return
133 }
134
135 s.When = time.Unix(ts, 0).In(time.UTC)
136 var tzStart = space + 1
137 if tzStart >= len(b) || tzStart+timeZoneLength > len(b) {
138 return
139 }
140
141 timezone := string(b[tzStart : tzStart+timeZoneLength])
142 tzhours, err1 := strconv.ParseInt(timezone[0:3], 10, 64)
143 tzmins, err2 := strconv.ParseInt(timezone[3:], 10, 64)
144 if err1 != nil || err2 != nil {
145 return
146 }
147 if tzhours < 0 {
148 tzmins *= -1
149 }
150
151 tz := time.FixedZone("", int(tzhours*60*60+tzmins*60))
152
153 s.When = s.When.In(tz)
154}
155
156func (s *Signature) encodeTimeAndTimeZone(w io.Writer) error {
157 u := s.When.Unix()
158 if u < 0 {
159 u = 0
160 }
161 _, err := fmt.Fprintf(w, "%d %s", u, s.When.Format("-0700"))
162 return err
163}
164
165func (s *Signature) String() string {
166 return fmt.Sprintf("%s <%s>", s.Name, s.Email)
167}
168
169// ObjectIter provides an iterator for a set of objects.
170type ObjectIter struct {
171 storer.EncodedObjectIter
172 s storer.EncodedObjectStorer
173}
174
175// NewObjectIter takes a storer.EncodedObjectStorer and a
176// storer.EncodedObjectIter and returns an *ObjectIter that iterates over all
177// objects contained in the storer.EncodedObjectIter.
178func NewObjectIter(s storer.EncodedObjectStorer, iter storer.EncodedObjectIter) *ObjectIter {
179 return &ObjectIter{iter, s}
180}
181
182// Next moves the iterator to the next object and returns a pointer to it. If
183// there are no more objects, it returns io.EOF.
184func (iter *ObjectIter) Next() (Object, error) {
185 for {
186 obj, err := iter.EncodedObjectIter.Next()
187 if err != nil {
188 return nil, err
189 }
190
191 o, err := iter.toObject(obj)
192 if err == plumbing.ErrInvalidType {
193 continue
194 }
195
196 if err != nil {
197 return nil, err
198 }
199
200 return o, nil
201 }
202}
203
204// ForEach call the cb function for each object contained on this iter until
205// an error happens or the end of the iter is reached. If ErrStop is sent
206// the iteration is stop but no error is returned. The iterator is closed.
207func (iter *ObjectIter) ForEach(cb func(Object) error) error {
208 return iter.EncodedObjectIter.ForEach(func(obj plumbing.EncodedObject) error {
209 o, err := iter.toObject(obj)
210 if err == plumbing.ErrInvalidType {
211 return nil
212 }
213
214 if err != nil {
215 return err
216 }
217
218 return cb(o)
219 })
220}
221
222func (iter *ObjectIter) toObject(obj plumbing.EncodedObject) (Object, error) {
223 switch obj.Type() {
224 case plumbing.BlobObject:
225 blob := &Blob{}
226 return blob, blob.Decode(obj)
227 case plumbing.TreeObject:
228 tree := &Tree{s: iter.s}
229 return tree, tree.Decode(obj)
230 case plumbing.CommitObject:
231 commit := &Commit{}
232 return commit, commit.Decode(obj)
233 case plumbing.TagObject:
234 tag := &Tag{}
235 return tag, tag.Decode(obj)
236 default:
237 return nil, plumbing.ErrInvalidType
238 }
239}