diff options
| author | 2025-11-11 00:00:00 +0000 | |
|---|---|---|
| committer | 2025-11-13 00:00:00 +0000 | |
| commit | 15855e3249754ab7dc07183c9383f8a8e8c26af2 (patch) | |
| tree | 83b32bdd63f7e672152f07d89268e9b268d1f3f5 /obj_tag.go | |
| signature | ||
Initial commit
Diffstat (limited to 'obj_tag.go')
| -rw-r--r-- | obj_tag.go | 145 |
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 +} |
