ref: 71dd999fd8c13a6e4e707e525e0f5097d48fc48e
dir: /obj_tag.go/
package furgit
import (
"bytes"
"errors"
"fmt"
)
// Tag represents a Git annotated tag object.
type Tag struct {
// Target represents the hash of the object being tagged.
Target Hash
// TargetType represents the type of the object being tagged.
TargetType ObjectType
// Name represents the name of the tag.
Name []byte
// Tagger represents the identity of the tagger.
Tagger *Ident
// Message represents the tag message.
Message []byte
}
// TODO: ExtraHeaders and signatures
// StoredTag represents a tag stored in the object database.
type StoredTag struct {
Tag
hash Hash
}
// Hash returns the hash of the stored tag.
func (sTag *StoredTag) Hash() Hash {
return sTag.hash
}
// ObjectType returns the object type of the tag.
//
// It always returns ObjectTypeTag.
func (tag *Tag) ObjectType() ObjectType {
_ = tag
return ObjectTypeTag
}
// parseTag parses a tag object body.
func parseTag(id Hash, body []byte, repo *Repository) (*StoredTag, error) {
t := new(StoredTag)
t.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 := repo.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 = ObjectTypeCommit
case "tree":
t.TargetType = ObjectTypeTree
case "blob":
t.TargetType = ObjectTypeBlob
case "tag":
t.TargetType = ObjectTypeTag
default:
t.TargetType = ObjectTypeInvalid
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 (tag *Tag) serialize() ([]byte, error) {
var buf bytes.Buffer
fmt.Fprintf(&buf, "object %s\n", tag.Target.String())
buf.WriteString("type ")
switch tag.TargetType {
case ObjectTypeCommit:
buf.WriteString("commit")
case ObjectTypeTree:
buf.WriteString("tree")
case ObjectTypeBlob:
buf.WriteString("blob")
case ObjectTypeTag:
buf.WriteString("tag")
case ObjectTypeInvalid, ObjectTypeFuture, ObjectTypeOfsDelta, ObjectTypeRefDelta:
return nil, fmt.Errorf("furgit: tag: invalid target type %d", tag.TargetType)
default:
return nil, fmt.Errorf("furgit: tag: invalid target type %d", tag.TargetType)
}
buf.WriteByte('\n')
buf.WriteString("tag ")
buf.Write(tag.Name)
buf.WriteByte('\n')
if tag.Tagger != nil {
buf.WriteString("tagger ")
tb, err := tag.Tagger.Serialize()
if err != nil {
return nil, err
}
buf.Write(tb)
buf.WriteByte('\n')
}
buf.WriteByte('\n')
buf.Write(tag.Message)
return buf.Bytes(), nil
}
// Serialize renders the tag into its raw byte representation,
// including the header (i.e., "type size\0").
func (tag *Tag) Serialize() ([]byte, error) {
body, err := tag.serialize()
if err != nil {
return nil, err
}
header, err := headerForType(ObjectTypeTag, 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
}