From b78fa93988bff45515c235ae7cd3a1ed8e5c9818 Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Mon, 11 Dec 2017 15:21:13 -0800 Subject: [PATCH] git/odb: add implementation of annotated Tag object --- git/odb/tag.go | 118 ++++++++++++++++++++++++++++++++++++++++++++ git/odb/tag_test.go | 65 ++++++++++++++++++++++++ 2 files changed, 183 insertions(+) create mode 100644 git/odb/tag.go create mode 100644 git/odb/tag_test.go diff --git a/git/odb/tag.go b/git/odb/tag.go new file mode 100644 index 00000000..9ef7c565 --- /dev/null +++ b/git/odb/tag.go @@ -0,0 +1,118 @@ +package odb + +import ( + "bufio" + "bytes" + "encoding/hex" + "fmt" + "io" + "strings" + + "github.com/git-lfs/git-lfs/errors" +) + +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, errors.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, errors.Wrap(err, "git/odb: unable to decode SHA-1") + } + + 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, errors.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 +} diff --git a/git/odb/tag_test.go b/git/odb/tag_test.go new file mode 100644 index 00000000..786a04bf --- /dev/null +++ b/git/odb/tag_test.go @@ -0,0 +1,65 @@ +package odb + +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 ", + + 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 ") + 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 \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 ", tag.Tagger) + assert.Equal(t, "The quick brown fox jumps over the lazy dog.", tag.Message) +}