diff --git a/note.go b/note.go new file mode 100644 index 0000000..c8d9248 --- /dev/null +++ b/note.go @@ -0,0 +1,99 @@ +package git + +/* +#include +*/ +import "C" + +import ( + "runtime" + "unsafe" +) + +// Note +type Note struct { + ptr *C.git_note +} + +// Free frees a git_note object +func (n *Note) Free() error { + if n.ptr == nil { + return ErrInvalid + } + runtime.SetFinalizer(n, nil) + C.git_note_free(n.ptr) + n.ptr = nil + return nil +} + +// Author returns the signature of the note author +func (n Note) Author() *Signature { + ptr := C.git_note_author(n.ptr) + return newSignatureFromC(ptr) +} + +// Id returns the note object's id +func (n Note) Id() *Oid { + ptr := C.git_note_id(n.ptr) + return newOidFromC(ptr) +} + +// Committer returns the signature of the note committer +func (n Note) Committer() *Signature { + ptr := C.git_note_committer(n.ptr) + return newSignatureFromC(ptr) +} + +// Message returns the note message +func (n Note) Message() string { + return C.GoString(C.git_note_message(n.ptr)) +} + +// NoteIterator +type NoteIterator struct { + ptr *C.git_note_iterator +} + +// NewNoteIterator creates a new iterator for notes +func (repo *Repository) NewNoteIterator(ref string) (*NoteIterator, error) { + var cref *C.char + if ref == "" { + cref = nil + } else { + cref = C.CString(ref) + defer C.free(unsafe.Pointer(cref)) + } + + var ptr *C.git_note_iterator + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ret := C.git_note_iterator_new(&ptr, repo.ptr, cref); ret < 0 { + return nil, MakeGitError(ret) + } + + iter := &NoteIterator{ptr: ptr} + runtime.SetFinalizer(iter, (*NoteIterator).Free) + return iter, nil +} + +// Free frees the note interator +func (v *NoteIterator) Free() { + runtime.SetFinalizer(v, nil) + C.git_note_iterator_free(v.ptr) +} + +// Next returns the current item (note id & annotated id) and advances the +// iterator internally to the next item +func (it *NoteIterator) Next() (noteId, annotatedId *Oid, err error) { + noteId, annotatedId = new(Oid), new(Oid) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ret := C.git_note_next(noteId.toC(), annotatedId.toC(), it.ptr); ret < 0 { + err = MakeGitError(ret) + } + return +} diff --git a/note_test.go b/note_test.go new file mode 100644 index 0000000..f5e9c01 --- /dev/null +++ b/note_test.go @@ -0,0 +1,113 @@ +package git + +import ( + "fmt" + "os" + "reflect" + "testing" + "time" +) + +func TestCreateNote(t *testing.T) { + repo := createTestRepo(t) + defer os.RemoveAll(repo.Workdir()) + + commitId, _ := seedTestRepo(t, repo) + + commit, err := repo.LookupCommit(commitId) + checkFatal(t, err) + + note, noteId := createTestNote(t, repo, commit) + + compareStrings(t, "I am a note\n", note.Message()) + compareStrings(t, noteId.String(), note.Id().String()) + compareStrings(t, "alice", note.Author().Name) + compareStrings(t, "alice@example.com", note.Author().Email) + compareStrings(t, "alice", note.Committer().Name) + compareStrings(t, "alice@example.com", note.Committer().Email) +} + +func TestNoteIterator(t *testing.T) { + repo := createTestRepo(t) + defer os.RemoveAll(repo.Workdir()) + seedTestRepo(t, repo) + + notes := make([]*Note, 5) + for i := range notes { + commitId, _ := updateReadme(t, repo, fmt.Sprintf("README v%d\n", i+1)) + commit, err := repo.LookupCommit(commitId) + checkFatal(t, err) + + note, _ := createTestNote(t, repo, commit) + notes[i] = note + } + + iter, err := repo.NewNoteIterator("") + checkFatal(t, err) + for { + noteId, commitId, err := iter.Next() + if err != nil { + if !IsErrorCode(err, ErrIterOver) { + checkFatal(t, err) + } + break + } + + note, err := repo.ReadNote("", commitId) + checkFatal(t, err) + + if !reflect.DeepEqual(note.Id(), noteId) { + t.Errorf("expected note oid '%v', actual '%v'", note.Id(), noteId) + } + } +} + +func TestRemoveNote(t *testing.T) { + repo := createTestRepo(t) + defer os.RemoveAll(repo.Workdir()) + + commitId, _ := seedTestRepo(t, repo) + + commit, err := repo.LookupCommit(commitId) + checkFatal(t, err) + + note, _ := createTestNote(t, repo, commit) + + _, err = repo.ReadNote("", commit.Id()) + checkFatal(t, err) + + err = repo.RemoveNote("", note.Author(), note.Committer(), commitId) + checkFatal(t, err) + + _, err = repo.ReadNote("", commit.Id()) + if err == nil { + t.Fatal("note remove failed") + } +} + +func TestDefaultNoteRef(t *testing.T) { + repo := createTestRepo(t) + defer os.RemoveAll(repo.Workdir()) + + ref, err := repo.DefaultNoteRef() + checkFatal(t, err) + + compareStrings(t, "refs/notes/commits", ref) +} + +func createTestNote(t *testing.T, repo *Repository, commit *Commit) (*Note, *Oid) { + loc, err := time.LoadLocation("Europe/Berlin") + sig := &Signature{ + Name: "alice", + Email: "alice@example.com", + When: time.Date(2015, 01, 05, 13, 0, 0, 0, loc), + } + + noteId, err := repo.CreateNote("", sig, sig, commit.Id(), "I am a note\n", false) + checkFatal(t, err) + + note, err := repo.ReadNote("", commit.Id()) + checkFatal(t, err) + + return note, noteId +} diff --git a/repository.go b/repository.go index 222ede3..7760c3a 100644 --- a/repository.go +++ b/repository.go @@ -530,3 +530,103 @@ func (v *Repository) DwimReference(name string) (*Reference, error) { return newReferenceFromC(ptr, v), nil } + +// CreateNote adds a note for an object +func (v *Repository) CreateNote( + ref string, author, committer *Signature, id *Oid, + note string, force bool) (*Oid, error) { + + oid := new(Oid) + + var cref *C.char + if ref == "" { + cref = nil + } else { + cref = C.CString(ref) + defer C.free(unsafe.Pointer(cref)) + } + + authorSig := author.toC() + defer C.git_signature_free(authorSig) + + committerSig := committer.toC() + defer C.git_signature_free(committerSig) + + cnote := C.CString(note) + defer C.free(unsafe.Pointer(cnote)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_note_create( + oid.toC(), v.ptr, cref, authorSig, + committerSig, id.toC(), cnote, cbool(force)) + + if ret < 0 { + return nil, MakeGitError(ret) + } + return oid, nil +} + +// ReadNote reads the note for an object +func (v *Repository) ReadNote(ref string, id *Oid) (*Note, error) { + var cref *C.char + if ref == "" { + cref = nil + } else { + cref = C.CString(ref) + defer C.free(unsafe.Pointer(cref)) + } + + note := new(Note) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ret := C.git_note_read(¬e.ptr, v.ptr, cref, id.toC()); ret < 0 { + return nil, MakeGitError(ret) + } + + runtime.SetFinalizer(note, (*Note).Free) + return note, nil +} + +// RemoveNote removes the note for an object +func (v *Repository) RemoveNote(ref string, author, committer *Signature, id *Oid) error { + var cref *C.char + if ref == "" { + cref = nil + } else { + cref = C.CString(ref) + defer C.free(unsafe.Pointer(cref)) + } + + authorSig := author.toC() + defer C.git_signature_free(authorSig) + + committerSig := committer.toC() + defer C.git_signature_free(committerSig) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_note_remove(v.ptr, cref, authorSig, committerSig, id.toC()) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +// DefaultNoteRef returns the default notes reference for a repository +func (v *Repository) DefaultNoteRef() (string, error) { + var ptr *C.char + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ret := C.git_note_default_ref(&ptr, v.ptr); ret < 0 { + return "", MakeGitError(ret) + } + + return C.GoString(ptr), nil +}