diff options
| -rw-r--r-- | commit.go | 17 | ||||
| -rw-r--r-- | git.go | 77 | ||||
| -rw-r--r-- | odb.go | 29 | ||||
| -rw-r--r-- | packbuilder.go | 130 | ||||
| -rw-r--r-- | reference.go | 73 | ||||
| -rw-r--r-- | reference_test.go | 92 | ||||
| -rw-r--r-- | repository.go | 4 | ||||
| -rw-r--r-- | wrapper.c | 10 |
8 files changed, 431 insertions, 1 deletions
@@ -47,6 +47,23 @@ func (c Commit) Committer() *Signature { return newSignatureFromC(ptr) } +func (c *Commit) Parent(n uint) *Commit { + par := &Commit{} + ret := C.git_commit_parent(&par.ptr, c.ptr, C.uint(n)) + if ret != 0 { + return nil + } + return par +} + +func (c *Commit) ParentId(n uint) *Oid { + return newOidFromC(C.git_commit_parent_id(c.ptr, C.uint(n))) +} + +func (c *Commit) ParentCount() uint { + return uint(C.git_commit_parentcount(c.ptr)) +} + // Signature type Signature struct { @@ -7,7 +7,11 @@ package git */ import "C" import ( + "bytes" + "errors" "unsafe" + "strings" + "fmt" ) const ( @@ -16,6 +20,10 @@ const ( ENOTFOUND = C.GIT_ENOTFOUND ) +var ( + ErrIterOver = errors.New("Iteration is over") +) + func init() { C.git_threads_init() } @@ -67,6 +75,53 @@ func (oid *Oid) Bytes() []byte { return oid.bytes[0:] } +func (oid *Oid) Cmp(oid2 *Oid) int { + return bytes.Compare(oid.bytes[:], oid2.bytes[:]) +} + +func (oid *Oid) Copy() *Oid { + ret := new(Oid) + copy(ret.bytes[:], oid.bytes[:]) + return ret +} + +func (oid *Oid) Equal(oid2 *Oid) bool { + return bytes.Equal(oid.bytes[:], oid2.bytes[:]) +} + +func (oid *Oid) IsZero() bool { + for _, a := range(oid.bytes) { + if a != '0' { + return false + } + } + return true +} + +func (oid *Oid) NCmp(oid2 *Oid, n uint) int { + return bytes.Compare(oid.bytes[:n], oid2.bytes[:n]) +} + +func ShortenOids(ids []*Oid, minlen int) (int, error) { + shorten := C.git_oid_shorten_new(C.size_t(minlen)) + if shorten == nil { + panic("Out of memory") + } + defer C.git_oid_shorten_free(shorten) + + var ret C.int + for _, id := range ids { + buf := make([]byte, 41) + C.git_oid_fmt((*C.char)(unsafe.Pointer(&buf[0])), id.toC()) + buf[40] = 0 + ret = C.git_oid_shorten_add(shorten, (*C.char)(unsafe.Pointer(&buf[0]))) + if ret < 0 { + return int(ret), LastError() + } + } + return int(ret), nil +} + type GitError struct { Message string Code int @@ -78,6 +133,9 @@ func (e GitError) Error() string{ func LastError() error { err := C.giterr_last() + if err == nil { + return &GitError{"No message", 0} + } return &GitError{C.GoString(err.message), int(err.klass)} } @@ -94,3 +152,22 @@ func ucbool(b bool) C.uint { } return C.uint(0) } + +func Discover(start string, across_fs bool, ceiling_dirs []string) (string, error) { + ceildirs := C.CString(strings.Join(ceiling_dirs, string(C.GIT_PATH_LIST_SEPARATOR))) + defer C.free(unsafe.Pointer(ceildirs)) + + cstart := C.CString(start) + defer C.free(unsafe.Pointer(cstart)) + + retpath := (*C.char)(C.malloc(C.GIT_PATH_MAX)) + defer C.free(unsafe.Pointer(retpath)) + + r := C.git_repository_discover(retpath, C.GIT_PATH_MAX, cstart, cbool(across_fs), ceildirs) + + if r == 0 { + return C.GoString(retpath), nil + } + + return "", LastError() +} @@ -4,6 +4,8 @@ package git #cgo pkg-config: libgit2 #include <git2.h> #include <git2/errors.h> + +extern int _go_git_odb_foreach(git_odb *db, void *payload); */ import "C" import ( @@ -44,6 +46,32 @@ func (v *Odb) Read(oid *Oid) (obj *OdbObject, err error) { return } +//export odbForEachCb +func odbForEachCb(id *C.git_oid, payload unsafe.Pointer) int { + ch := *(*chan *Oid)(payload) + oid := newOidFromC(id) + // Because the channel is unbuffered, we never read our own data. If ch is + // readable, the user has sent something on it, which means we should + // abort. + select { + case ch <- oid: + case <-ch: + return -1 + } + return 0; +} + +func (v *Odb) forEachWrap(ch chan *Oid) { + C._go_git_odb_foreach(v.ptr, unsafe.Pointer(&ch)) + close(ch) +} + +func (v *Odb) ForEach() chan *Oid { + ch := make(chan *Oid, 0) + go v.forEachWrap(ch) + return ch +} + type OdbObject struct { ptr *C.git_odb_object } @@ -74,4 +102,3 @@ func (object *OdbObject) Data() (data []byte) { return blob } - diff --git a/packbuilder.go b/packbuilder.go new file mode 100644 index 0000000..292604d --- /dev/null +++ b/packbuilder.go @@ -0,0 +1,130 @@ +package git + +/* +#cgo pkg-config: libgit2 +#include <git2.h> +#include <git2/errors.h> +#include <git2/pack.h> +#include <stdlib.h> + +extern int _go_git_packbuilder_foreach(git_packbuilder *pb, void *payload); +*/ +import "C" +import ( + "io" + "runtime" + "unsafe" +) + +type Packbuilder struct { + ptr *C.git_packbuilder +} + +func (repo *Repository) NewPackbuilder() (*Packbuilder, error) { + builder := &Packbuilder{} + ret := C.git_packbuilder_new(&builder.ptr, repo.ptr) + if ret != 0 { + return nil, LastError() + } + runtime.SetFinalizer(builder, (*Packbuilder).Free) + return builder, nil +} + +func (pb *Packbuilder) Free() { + runtime.SetFinalizer(pb, nil) + C.git_packbuilder_free(pb.ptr) +} + +func (pb *Packbuilder) Insert(id *Oid, name string) error { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + ret := C.git_packbuilder_insert(pb.ptr, id.toC(), cname) + if ret != 0 { + return LastError() + } + return nil +} + +func (pb *Packbuilder) InsertCommit(id *Oid) error { + ret := C.git_packbuilder_insert_commit(pb.ptr, id.toC()) + if ret != 0 { + return LastError() + } + return nil +} + +func (pb *Packbuilder) InsertTree(id *Oid) error { + ret := C.git_packbuilder_insert_tree(pb.ptr, id.toC()) + if ret != 0 { + return LastError() + } + return nil +} + +func (pb *Packbuilder) ObjectCount() uint32 { + return uint32(C.git_packbuilder_object_count(pb.ptr)) +} + +func (pb *Packbuilder) WriteToFile(name string) error { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + ret := C.git_packbuilder_write(pb.ptr, cname, nil, nil) + if ret != 0 { + return LastError() + } + return nil +} + +func (pb *Packbuilder) Write(w io.Writer) error { + ch, stop := pb.ForEach() + for slice := range ch { + _, err := w.Write(slice) + if err != nil { + close(stop) + return err + } + } + return nil +} + +func (pb *Packbuilder) Written() uint32 { + return uint32(C.git_packbuilder_written(pb.ptr)) +} + +type packbuilderCbData struct { + ch chan<- []byte + stop <-chan bool +} + +//export packbuilderForEachCb +func packbuilderForEachCb(buf unsafe.Pointer, size C.size_t, payload unsafe.Pointer) int { + data := (*packbuilderCbData)(payload) + ch := data.ch + stop := data.stop + + slice := C.GoBytes(buf, C.int(size)) + select { + case <- stop: + return -1 + case ch <- slice: + } + + return 0 +} + +func (pb *Packbuilder) forEachWrap(data *packbuilderCbData) { + C._go_git_packbuilder_foreach(pb.ptr, unsafe.Pointer(data)) + close(data.ch) +} + +// Foreach sends the packfile as slices through the "data" channel. If +// you want to stop the pack-building process (e.g. there's an error +// writing to the output), close or write a value into the "stop" +// channel. +func (pb *Packbuilder) ForEach() (data <-chan []byte, stop chan<- bool) { + ch := make(chan []byte) + stop := make(chan bool) + data := packbuilderCbData{ch, stop} + go pb.forEachWrap(&data) + return ch, stop +} diff --git a/reference.go b/reference.go index 820d166..93ab7de 100644 --- a/reference.go +++ b/reference.go @@ -111,3 +111,76 @@ func (v *Reference) Free() { runtime.SetFinalizer(v, nil) C.git_reference_free(v.ptr) } + +type ReferenceIterator struct { + ptr *C.git_reference_iterator + repo *Repository +} + +// NewReferenceIterator creates a new iterator over reference names +func (repo *Repository) NewReferenceIterator() (*ReferenceIterator, error) { + var ptr *C.git_reference_iterator + ret := C.git_reference_iterator_new(&ptr, repo.ptr) + if ret < 0 { + return nil, LastError() + } + + iter := &ReferenceIterator{repo: repo, ptr: ptr} + runtime.SetFinalizer(iter, (*ReferenceIterator).Free) + return iter, nil +} + +// NewReferenceIteratorGlob creates an iterator over reference names +// that match the speicified glob. The glob is of the usual fnmatch +// type. +func (repo *Repository) NewReferenceIteratorGlob(glob string) (*ReferenceIterator, error) { + cstr := C.CString(glob) + defer C.free(unsafe.Pointer(cstr)) + var ptr *C.git_reference_iterator + ret := C.git_reference_iterator_glob_new(&ptr, repo.ptr, cstr) + if ret < 0 { + return nil, LastError() + } + + iter := &ReferenceIterator{repo: repo, ptr: ptr} + runtime.SetFinalizer(iter, (*ReferenceIterator).Free) + return iter, nil +} + +// Next retrieves the next reference name. If the iteration is over, +// the returned error is git.ErrIterOver +func (v *ReferenceIterator) Next() (string, error) { + var ptr *C.char + ret := C.git_reference_next(&ptr, v.ptr) + if ret == ITEROVER { + return "", ErrIterOver + } + if ret < 0 { + return "", LastError() + } + + return C.GoString(ptr), nil +} + +// Create a channel from the iterator. You can use range on the +// returned channel to iterate over all the references. The channel +// will be closed in case any error is found. +func (v *ReferenceIterator) Iter() <-chan string { + ch := make(chan string) + go func() { + defer close(ch) + name, err := v.Next() + for err == nil { + ch <- name + name, err = v.Next() + } + }() + + return ch +} + +// Free the reference iterator +func (v *ReferenceIterator) Free() { + runtime.SetFinalizer(v, nil) + C.git_reference_iterator_free(v.ptr) +} diff --git a/reference_test.go b/reference_test.go index 3ea421d..6ac4ece 100644 --- a/reference_test.go +++ b/reference_test.go @@ -3,6 +3,7 @@ package git import ( "os" "runtime" + "sort" "testing" ) @@ -51,6 +52,97 @@ func TestRefModification(t *testing.T) { } +func TestIterator(t *testing.T) { + repo := createTestRepo(t) + defer os.RemoveAll(repo.Workdir()) + + loc, err := time.LoadLocation("Europe/Berlin") + checkFatal(t, err) + sig := &Signature{ + Name: "Rand Om Hacker", + Email: "[email protected]", + When: time.Date(2013, 03, 06, 14, 30, 0, 0, loc), + } + + idx, err := repo.Index() + checkFatal(t, err) + err = idx.AddByPath("README") + checkFatal(t, err) + treeId, err := idx.WriteTree() + checkFatal(t, err) + + message := "This is a commit\n" + tree, err := repo.LookupTree(treeId) + checkFatal(t, err) + commitId, err := repo.CreateCommit("HEAD", sig, sig, message, tree) + checkFatal(t, err) + + _, err = repo.CreateReference("refs/heads/one", commitId, true) + checkFatal(t, err) + + _, err = repo.CreateReference("refs/heads/two", commitId, true) + checkFatal(t, err) + + _, err = repo.CreateReference("refs/heads/three", commitId, true) + checkFatal(t, err) + + iter, err := repo.NewReferenceIterator() + checkFatal(t, err) + + var list []string + expected := []string{ + "refs/heads/master", + "refs/heads/one", + "refs/heads/three", + "refs/heads/two", + } + + // test some manual iteration + name, err := iter.Next() + for err == nil { + list = append(list, name) + name, err = iter.Next() + } + if err != ErrIterOver { + t.Fatal("Iteration not over") + } + + + sort.Strings(list) + compareStringList(t, expected, list) + + // test the channel iteration + list = []string{} + iter, err = repo.NewReferenceIterator() + for name := range iter.Iter() { + list = append(list, name) + } + + sort.Strings(list) + compareStringList(t, expected, list) + + iter, err = repo.NewReferenceIteratorGlob("refs/heads/t*") + expected = []string{ + "refs/heads/three", + "refs/heads/two", + } + + list = []string{} + for name := range iter.Iter() { + list = append(list, name) + } + + compareStringList(t, expected, list) +} + +func compareStringList(t *testing.T, expected, actual []string) { + for i, v := range expected { + if actual[i] != v { + t.Fatalf("Bad list") + } + } +} + func checkRefType(t *testing.T, ref *Reference, kind int) { if ref.Type() == kind { return diff --git a/repository.go b/repository.go index 0a07dc3..3a44000 100644 --- a/repository.go +++ b/repository.go @@ -227,6 +227,10 @@ func (repo *Repository) Path() string { return C.GoString(C.git_repository_path(repo.ptr)) } +func (repo *Repository) IsBare() (bool) { + return C.git_repository_is_bare(repo.ptr) != 0 +} + func (repo *Repository) Workdir() string { return C.GoString(C.git_repository_workdir(repo.ptr)) } @@ -1,6 +1,7 @@ #include "_cgo_export.h" #include "git2.h" #include "git2/submodule.h" +#include "git2/pack.h" typedef int (*gogit_submodule_cbk)(git_submodule *sm, const char *name, void *payload); @@ -14,4 +15,13 @@ int _go_git_treewalk(git_tree *tree, git_treewalk_mode mode, void *ptr) return git_tree_walk(tree, mode, (git_treewalk_cb)&CallbackGitTreeWalk, ptr); } +int _go_git_packbuilder_foreach(git_packbuilder *pb, void *payload) +{ + return git_packbuilder_foreach(pb, (git_packbuilder_foreach_cb)&packbuilderForEachCb, payload); +} + +int _go_git_odb_foreach(git_odb *db, void *payload) +{ + return git_odb_foreach(db, (git_odb_foreach_cb)&odbForEachCb, payload); +} /* EOF */ |
