aboutsummaryrefslogtreecommitdiff
path: root/obj_tag.go
diff options
context:
space:
mode:
authorGravatar Runxi Yu2025-11-11 00:00:00 +0000
committerGravatar Runxi Yu2025-11-13 00:00:00 +0000
commit15855e3249754ab7dc07183c9383f8a8e8c26af2 (patch)
tree83b32bdd63f7e672152f07d89268e9b268d1f3f5 /obj_tag.go
signature
Initial commit
Diffstat (limited to 'obj_tag.go')
-rw-r--r--obj_tag.go145
1 files changed, 145 insertions, 0 deletions
diff --git a/obj_tag.go b/obj_tag.go
new file mode 100644
index 00000000..348afd48
--- /dev/null
+++ b/obj_tag.go
@@ -0,0 +1,145 @@
+package furgit
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+)
+
+// Tag models an annotated Git tag object.
+type Tag struct {
+ objectBase
+
+ Target Hash
+ TargetType ObjType
+ Name []byte
+ Tagger *Ident
+ Message []byte
+}
+
+// ObjType allows Tag to satisfy the Object interface.
+func (*Tag) ObjType() ObjType {
+ return ObjTag
+}
+
+// parseTag parses a tag object body.
+func parseTag(id Hash, body []byte) (*Tag, error) {
+ t := new(Tag)
+ t.objectBase = objectBase{Hash: id}
+ i := 0
+ var haveTarget, haveType bool
+
+ for i < len(body) {
+ rel := bytes.IndexByte(body[i:], '\n')
+ if rel < 0 {
+ return nil, errors.New("furgit: tag: missing newline")
+ }
+ line := body[i : i+rel]
+ i += rel + 1
+ if len(line) == 0 {
+ break
+ }
+
+ switch {
+ case bytes.HasPrefix(line, []byte("object ")):
+ hash, err := ParseHash(string(line[7:]))
+ if err != nil {
+ return nil, fmt.Errorf("furgit: tag: object: %w", err)
+ }
+ t.Target = hash
+ haveTarget = true
+ case bytes.HasPrefix(line, []byte("type ")):
+ switch string(line[5:]) {
+ case "commit":
+ t.TargetType = ObjCommit
+ case "tree":
+ t.TargetType = ObjTree
+ case "blob":
+ t.TargetType = ObjBlob
+ case "tag":
+ t.TargetType = ObjTag
+ default:
+ t.TargetType = ObjInvalid
+ return nil, errors.New("furgit: tag: unknown target type")
+ }
+ haveType = true
+ case bytes.HasPrefix(line, []byte("tag ")):
+ t.Name = append([]byte(nil), line[4:]...)
+ case bytes.HasPrefix(line, []byte("tagger ")):
+ idt, err := parseIdent(line[7:])
+ if err != nil {
+ return nil, fmt.Errorf("furgit: tag: tagger: %w", err)
+ }
+ t.Tagger = idt
+ case bytes.HasPrefix(line, []byte("gpgsig ")), bytes.HasPrefix(line, []byte("gpgsig-sha256 ")):
+ for i < len(body) {
+ nextRel := bytes.IndexByte(body[i:], '\n')
+ if nextRel < 0 {
+ return nil, errors.New("furgit: tag: unterminated gpgsig")
+ }
+ if body[i] != ' ' {
+ break
+ }
+ i += nextRel + 1
+ }
+ default:
+ // ignore unknown headers
+ }
+ }
+
+ if !haveTarget || !haveType {
+ return nil, errors.New("furgit: tag: missing required headers")
+ }
+
+ t.Message = append([]byte(nil), body[i:]...)
+ return t, nil
+}
+
+func tagBody(t *Tag) ([]byte, error) {
+ var buf bytes.Buffer
+ fmt.Fprintf(&buf, "object %s\n", t.Target.String())
+ buf.WriteString("type ")
+ switch t.TargetType {
+ case ObjCommit:
+ buf.WriteString("commit")
+ case ObjTree:
+ buf.WriteString("tree")
+ case ObjBlob:
+ buf.WriteString("blob")
+ case ObjTag:
+ buf.WriteString("tag")
+ case ObjInvalid, ObjFuture, ObjOfsDelta, ObjRefDelta:
+ return nil, fmt.Errorf("furgit: tag: invalid target type %d", t.TargetType)
+ default:
+ return nil, fmt.Errorf("furgit: tag: invalid target type %d", t.TargetType)
+ }
+ buf.WriteByte('\n')
+ buf.WriteString("tag ")
+ buf.Write(t.Name)
+ buf.WriteByte('\n')
+ if t.Tagger != nil {
+ buf.WriteString("tagger ")
+ buf.Write(t.Tagger.Serialize())
+ buf.WriteByte('\n')
+ }
+ buf.WriteByte('\n')
+ buf.Write(t.Message)
+
+ return buf.Bytes(), nil
+}
+
+// Serialize renders a Tag into canonical Git format.
+func (t *Tag) Serialize() ([]byte, error) {
+ body, err := tagBody(t)
+ if err != nil {
+ return nil, err
+ }
+ header, err := headerForType(ObjTag, body)
+ if err != nil {
+ return nil, err
+ }
+ raw := make([]byte, len(header)+len(body))
+ copy(raw, header)
+ copy(raw[len(header):], body)
+ return raw, nil
+}