Skip to content

Commit

Permalink
git/odb: add implementation of annotated Tag object
Browse files Browse the repository at this point in the history
  • Loading branch information
ttaylorr committed Jul 5, 2018
1 parent 5f35d33 commit cb1adc6
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 0 deletions.
116 changes: 116 additions & 0 deletions tag.go
@@ -0,0 +1,116 @@
package gitobj

import (
"bufio"
"bytes"
"encoding/hex"
"fmt"
"io"
"strings"
)

type Tag struct {
Object []byte
ObjectType ObjectType
Name string
Tagger string

Message string
}

// Decode implements Object.Decode and decodes the uncompressed tag being
// read. It returns the number of uncompressed bytes being consumed off of the
// stream, which should be strictly equal to the size given.
//
// If any error was encountered along the way it will be returned, and the
// receiving *Tag is considered invalid.
func (t *Tag) Decode(r io.Reader, size int64) (int, error) {
scanner := bufio.NewScanner(io.LimitReader(r, size))

var (
finishedHeaders bool
message []string
)

for scanner.Scan() {
if finishedHeaders {
message = append(message, scanner.Text())
} else {
if len(scanner.Bytes()) == 0 {
finishedHeaders = true
continue
}

parts := strings.SplitN(scanner.Text(), " ", 2)
if len(parts) < 2 {
return 0, fmt.Errorf("git/odb: invalid tag header: %s", scanner.Text())
}

switch parts[0] {
case "object":
sha, err := hex.DecodeString(parts[1])
if err != nil {
return 0, fmt.Errorf("git/odb: unable to decode SHA-1: %s", err)
}

t.Object = sha
case "type":
t.ObjectType = ObjectTypeFromString(parts[1])
case "tag":
t.Name = parts[1]
case "tagger":
t.Tagger = parts[1]
default:
return 0, fmt.Errorf("git/odb: unknown tag header: %s", parts[0])
}
}
}

if err := scanner.Err(); err != nil {
return 0, err
}

t.Message = strings.Join(message, "\n")

return int(size), nil
}

// Encode encodes the Tag's contents to the given io.Writer, "w". If there was
// any error copying the Tag's contents, that error will be returned.
//
// Otherwise, the number of bytes written will be returned.
func (t *Tag) Encode(w io.Writer) (int, error) {
headers := []string{
fmt.Sprintf("object %s", hex.EncodeToString(t.Object)),
fmt.Sprintf("type %s", t.ObjectType),
fmt.Sprintf("tag %s", t.Name),
fmt.Sprintf("tagger %s", t.Tagger),
}

return fmt.Fprintf(w, "%s\n\n%s\n", strings.Join(headers, "\n"), t.Message)
}

// Equal returns whether the receiving and given Tags are equal, or in other
// words, whether they are represented by the same SHA-1 when saved to the
// object database.
func (t *Tag) Equal(other *Tag) bool {
if (t == nil) != (other == nil) {
return false
}

if t != nil {
return bytes.Equal(t.Object, other.Object) &&
t.ObjectType == other.ObjectType &&
t.Name == other.Name &&
t.Tagger == other.Tagger &&
t.Message == other.Message
}

return true
}

// Type implements Object.ObjectType by returning the correct object type for
// Tags, TagObjectType.
func (t *Tag) Type() ObjectType {
return TagObjectType
}
65 changes: 65 additions & 0 deletions tag_test.go
@@ -0,0 +1,65 @@
package gitobj

import (
"bytes"
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

func TestTagTypeReturnsCorrectObjectType(t *testing.T) {
assert.Equal(t, TagObjectType, new(Tag).Type())
}

func TestTagEncode(t *testing.T) {
tag := &Tag{
Object: []byte("aaaaaaaaaaaaaaaaaaaa"),
ObjectType: CommitObjectType,
Name: "v2.4.0",
Tagger: "A U Thor <author@example.com>",

Message: "The quick brown fox jumps over the lazy dog.",
}

buf := new(bytes.Buffer)

n, err := tag.Encode(buf)

assert.Nil(t, err)
assert.EqualValues(t, buf.Len(), n)

assertLine(t, buf, "object 6161616161616161616161616161616161616161")
assertLine(t, buf, "type commit")
assertLine(t, buf, "tag v2.4.0")
assertLine(t, buf, "tagger A U Thor <author@example.com>")
assertLine(t, buf, "")
assertLine(t, buf, "The quick brown fox jumps over the lazy dog.")

assert.Equal(t, 0, buf.Len())
}

func TestTagDecode(t *testing.T) {
from := new(bytes.Buffer)

fmt.Fprintf(from, "object 6161616161616161616161616161616161616161\n")
fmt.Fprintf(from, "type commit\n")
fmt.Fprintf(from, "tag v2.4.0\n")
fmt.Fprintf(from, "tagger A U Thor <author@example.com>\n")
fmt.Fprintf(from, "\n")
fmt.Fprintf(from, "The quick brown fox jumps over the lazy dog.\n")

flen := from.Len()

tag := new(Tag)
n, err := tag.Decode(from, int64(flen))

assert.Nil(t, err)
assert.Equal(t, n, flen)

assert.Equal(t, []byte("aaaaaaaaaaaaaaaaaaaa"), tag.Object)
assert.Equal(t, CommitObjectType, tag.ObjectType)
assert.Equal(t, "v2.4.0", tag.Name)
assert.Equal(t, "A U Thor <author@example.com>", tag.Tagger)
assert.Equal(t, "The quick brown fox jumps over the lazy dog.", tag.Message)
}

0 comments on commit cb1adc6

Please sign in to comment.