diff --git a/signature.go b/signature.go index dfd9718..ad74e1d 100644 --- a/signature.go +++ b/signature.go @@ -5,6 +5,8 @@ package git */ import "C" import ( + "fmt" + "math" "runtime" "time" "unsafe" @@ -31,11 +33,29 @@ func newSignatureFromC(sig *C.git_signature) *Signature { } // Offset returns the time zone offset of v.When in minutes, which is what git wants. -func (v *Signature) Offset() int { - _, offset := v.When.Zone() +func (sig *Signature) Offset() int { + _, offset := sig.When.Zone() return offset / 60 } +// ToString creates a string representation of signature, optionally with time. +func (sig *Signature) ToString(withTime bool) string { + str := sig.Name + " <" + sig.Email + ">" + + if !withTime { + return str + } + + offsetModulus := int(math.Abs(float64(sig.Offset()))) + + sign := "+" + if sig.Offset() < 0 { + sign = "-" + } + + return str + fmt.Sprintf(" %d %s%02d%02d", sig.When.Unix(), sign, offsetModulus/60, offsetModulus%60) +} + func (sig *Signature) toC() (*C.git_signature, error) { if sig == nil { return nil, nil diff --git a/signature_test.go b/signature_test.go new file mode 100644 index 0000000..cbe3057 --- /dev/null +++ b/signature_test.go @@ -0,0 +1,25 @@ +package git + +import ( + "fmt" + "testing" + "time" +) + +func TestSignatureToString(t *testing.T) { + loc, err := time.LoadLocation("Europe/Moscow") + checkFatal(t, err) + + sig := &Signature{ + Name: "Alice", + Email: "alice@example.com", + When: time.Date(2022, 8, 14, 11, 22, 33, 0, loc), + } + + fmt.Println(sig.When.Unix()) + + actual := sig.ToString(true) + expected := "Alice 1660465353 +0300" + + compareStrings(t, actual, expected) +} diff --git a/tag.go b/tag.go index 9ced680..42e58dd 100644 --- a/tag.go +++ b/tag.go @@ -7,7 +7,9 @@ extern int _go_git_tag_foreach(git_repository *repo, void *payload); */ import "C" import ( + "bytes" "runtime" + "strings" "unsafe" ) @@ -99,6 +101,53 @@ func (c *TagsCollection) Create(name string, obj Objecter, tagger *Signature, me return oid, nil } +// CreateTagBuffer creates a tag and write it into a Golang-buffer. +// libgit2 does not contain git_tag_create_buffer function. +func (c *TagsCollection) CreateTagBuffer(name string, obj Objecter, tagger *Signature, message string) []byte { + buf := bytes.NewBuffer(nil) + buf.WriteString("object " + obj.AsObject().Id().String() + "\n") + buf.WriteString("type " + strings.ToLower(obj.AsObject().Type().String()) + "\n") + buf.WriteString("tag " + name + "\n") + buf.WriteString("tagger " + tagger.ToString(true) + "\n\n") + if !strings.HasSuffix(message, "\n") { + buf.WriteString(message + "\n") + } else { + buf.WriteString(message) + } + + return buf.Bytes() +} + +// CreateTagWithSignature creates a tag object from the given contents and +// signature. +func (c *TagsCollection) CreateTagWithSignature( + tagContent, signature string, force bool, +) (*Oid, error) { + if !strings.HasSuffix(signature, "\n") { + signature += "\n" + } + + tagContent += signature + + cTagContent := C.CString(tagContent) + defer C.free(unsafe.Pointer(cTagContent)) + cForce := cbool(force) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + oid := new(Oid) + ret := C.git_tag_create_from_buffer(oid.toC(), c.repo.ptr, cTagContent, cForce) + + runtime.KeepAlive(c) + runtime.KeepAlive(oid) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return oid, nil +} + func (c *TagsCollection) Remove(name string) error { runtime.LockOSThread() defer runtime.UnlockOSThread() diff --git a/tag_test.go b/tag_test.go index 3404923..7ed778e 100644 --- a/tag_test.go +++ b/tag_test.go @@ -1,9 +1,14 @@ package git import ( + "bytes" "errors" + "strings" "testing" "time" + + "golang.org/x/crypto/openpgp" + "golang.org/x/crypto/openpgp/packet" ) func TestCreateTag(t *testing.T) { @@ -26,6 +31,39 @@ func TestCreateTag(t *testing.T) { compareStrings(t, commitId.String(), tag.TargetId().String()) } +func TestCreateTagWithSignature(t *testing.T) { + t.Parallel() + repo := createTestRepo(t) + defer cleanupTestRepo(t, repo) + + commitID, _ := seedTestRepo(t, repo) + + commit, err := repo.LookupCommit(commitID) + checkFatal(t, err) + + tagContent := repo.Tags.CreateTagBuffer("v0.1.0", commit, &Signature{ + Name: "Alice", + Email: "alice@example.com", + When: time.Now(), + }, "This is a tag") + + entity, err := openpgp.NewEntity("Alice", "test comment", "alice@example.com", nil) + checkFatal(t, err) + + pgpSignatureBuf := new(bytes.Buffer) + err = openpgp.ArmoredDetachSignText(pgpSignatureBuf, entity, strings.NewReader(string(tagContent)), &packet.Config{}) + checkFatal(t, err) + + tagID, err := repo.Tags.CreateTagWithSignature(string(tagContent), pgpSignatureBuf.String(), false) + checkFatal(t, err) + + tag, err := repo.LookupTag(tagID) + checkFatal(t, err) + + compareStrings(t, "v0.1.0", tag.Name()) + compareStrings(t, commitID.String(), tag.TargetId().String()) +} + func TestCreateTagLightweight(t *testing.T) { t.Parallel() repo := createTestRepo(t)