Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
git/odb: add implementation of annotated Tag object
- Loading branch information
Showing
2 changed files
with
181 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} |