Fast implementation of Git in pure Go
at legacy 169 lines 4.1 kB view raw
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}