summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--commit.go17
-rw-r--r--git.go77
-rw-r--r--odb.go29
-rw-r--r--packbuilder.go130
-rw-r--r--reference.go73
-rw-r--r--reference_test.go92
-rw-r--r--repository.go4
-rw-r--r--wrapper.c10
8 files changed, 431 insertions, 1 deletions
diff --git a/commit.go b/commit.go
index 9730a70..cacaa33 100644
--- a/commit.go
+++ b/commit.go
@@ -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 {
diff --git a/git.go b/git.go
index 68712d4..d8cacc4 100644
--- a/git.go
+++ b/git.go
@@ -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()
+}
diff --git a/odb.go b/odb.go
index 37d9fcd..9d7d02e 100644
--- a/odb.go
+++ b/odb.go
@@ -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))
}
diff --git a/wrapper.c b/wrapper.c
index 13478cb..2af3974 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -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 */