Fast implementation of Git in pure Go
1package furgit
2
3import (
4 "bytes"
5 "errors"
6 "fmt"
7)
8
9// Tag represents a Git annotated tag object.
10type Tag struct {
11 // Target represents the hash of the object being tagged.
12 Target Hash
13 // TargetType represents the type of the object being tagged.
14 TargetType ObjectType
15 // Name represents the name of the tag.
16 Name []byte
17 // Tagger represents the identity of the tagger.
18 Tagger *Ident
19 // Message represents the tag message.
20 Message []byte
21}
22
23// TODO: ExtraHeaders and signatures
24
25// StoredTag represents a tag stored in the object database.
26type StoredTag struct {
27 Tag
28 hash Hash
29}
30
31// Hash returns the hash of the stored tag.
32func (sTag *StoredTag) Hash() Hash {
33 return sTag.hash
34}
35
36// ObjectType returns the object type of the tag.
37//
38// It always returns ObjectTypeTag.
39func (tag *Tag) ObjectType() ObjectType {
40 _ = tag
41 return ObjectTypeTag
42}
43
44// parseTag parses a tag object body.
45func parseTag(id Hash, body []byte, repo *Repository) (*StoredTag, error) {
46 t := new(StoredTag)
47 t.hash = id
48 i := 0
49 var haveTarget, haveType bool
50
51 for i < len(body) {
52 rel := bytes.IndexByte(body[i:], '\n')
53 if rel < 0 {
54 return nil, errors.New("furgit: tag: missing newline")
55 }
56 line := body[i : i+rel]
57 i += rel + 1
58 if len(line) == 0 {
59 break
60 }
61
62 switch {
63 case bytes.HasPrefix(line, []byte("object ")):
64 hash, err := repo.ParseHash(string(line[7:]))
65 if err != nil {
66 return nil, fmt.Errorf("furgit: tag: object: %w", err)
67 }
68 t.Target = hash
69 haveTarget = true
70 case bytes.HasPrefix(line, []byte("type ")):
71 switch string(line[5:]) {
72 case "commit":
73 t.TargetType = ObjectTypeCommit
74 case "tree":
75 t.TargetType = ObjectTypeTree
76 case "blob":
77 t.TargetType = ObjectTypeBlob
78 case "tag":
79 t.TargetType = ObjectTypeTag
80 default:
81 t.TargetType = ObjectTypeInvalid
82 return nil, errors.New("furgit: tag: unknown target type")
83 }
84 haveType = true
85 case bytes.HasPrefix(line, []byte("tag ")):
86 t.Name = append([]byte(nil), line[4:]...)
87 case bytes.HasPrefix(line, []byte("tagger ")):
88 idt, err := parseIdent(line[7:])
89 if err != nil {
90 return nil, fmt.Errorf("furgit: tag: tagger: %w", err)
91 }
92 t.Tagger = idt
93 case bytes.HasPrefix(line, []byte("gpgsig ")), bytes.HasPrefix(line, []byte("gpgsig-sha256 ")):
94 for i < len(body) {
95 nextRel := bytes.IndexByte(body[i:], '\n')
96 if nextRel < 0 {
97 return nil, errors.New("furgit: tag: unterminated gpgsig")
98 }
99 if body[i] != ' ' {
100 break
101 }
102 i += nextRel + 1
103 }
104 default:
105 // ignore unknown headers
106 }
107 }
108
109 if !haveTarget || !haveType {
110 return nil, errors.New("furgit: tag: missing required headers")
111 }
112
113 t.Message = append([]byte(nil), body[i:]...)
114 return t, nil
115}
116
117func (tag *Tag) serialize() ([]byte, error) {
118 var buf bytes.Buffer
119 fmt.Fprintf(&buf, "object %s\n", tag.Target.String())
120 buf.WriteString("type ")
121 switch tag.TargetType {
122 case ObjectTypeCommit:
123 buf.WriteString("commit")
124 case ObjectTypeTree:
125 buf.WriteString("tree")
126 case ObjectTypeBlob:
127 buf.WriteString("blob")
128 case ObjectTypeTag:
129 buf.WriteString("tag")
130 case ObjectTypeInvalid, ObjectTypeFuture, ObjectTypeOfsDelta, ObjectTypeRefDelta:
131 return nil, fmt.Errorf("furgit: tag: invalid target type %d", tag.TargetType)
132 default:
133 return nil, fmt.Errorf("furgit: tag: invalid target type %d", tag.TargetType)
134 }
135 buf.WriteByte('\n')
136 buf.WriteString("tag ")
137 buf.Write(tag.Name)
138 buf.WriteByte('\n')
139 if tag.Tagger != nil {
140 buf.WriteString("tagger ")
141 tb, err := tag.Tagger.Serialize()
142 if err != nil {
143 return nil, err
144 }
145 buf.Write(tb)
146 buf.WriteByte('\n')
147 }
148 buf.WriteByte('\n')
149 buf.Write(tag.Message)
150
151 return buf.Bytes(), nil
152}
153
154// Serialize renders the tag into its raw byte representation,
155// including the header (i.e., "type size\0").
156func (tag *Tag) Serialize() ([]byte, error) {
157 body, err := tag.serialize()
158 if err != nil {
159 return nil, err
160 }
161 header, err := headerForType(ObjectTypeTag, body)
162 if err != nil {
163 return nil, err
164 }
165 raw := make([]byte, len(header)+len(body))
166 copy(raw, header)
167 copy(raw[len(header):], body)
168 return raw, nil
169}