summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitmodules3
-rw-r--r--.travis.yml12
-rw-r--r--Makefile7
-rw-r--r--README.md44
-rw-r--r--blame_test.go1
-rw-r--r--blob.go91
-rw-r--r--blob_test.go25
-rw-r--r--branch.go5
-rw-r--r--branch_test.go2
-rw-r--r--checkout.go86
-rw-r--r--cherrypick_test.go1
-rw-r--r--clone.go1
-rw-r--r--clone_test.go2
-rw-r--r--commit.go4
-rw-r--r--config_test.go1
-rw-r--r--credentials.go24
-rw-r--r--describe_test.go7
-rw-r--r--diff.go31
-rw-r--r--diff_test.go4
-rw-r--r--features.go30
-rw-r--r--git.go18
-rw-r--r--git_test.go6
-rw-r--r--index.go16
-rw-r--r--index_test.go47
-rw-r--r--merge_test.go5
-rw-r--r--note_test.go4
-rw-r--r--object_test.go3
-rw-r--r--odb.go8
-rw-r--r--odb_test.go5
-rw-r--r--patch_test.go1
-rw-r--r--push_test.go1
-rw-r--r--refdb.go5
-rw-r--r--reference_test.go6
-rw-r--r--remote.go84
-rw-r--r--remote_test.go15
-rw-r--r--repository.go38
-rw-r--r--reset_test.go1
-rw-r--r--revparse_test.go3
-rwxr-xr-xscript/with-static.sh12
-rw-r--r--stash.go338
-rw-r--r--stash_test.go198
-rw-r--r--status.go1
-rw-r--r--status_test.go2
-rw-r--r--submodule_test.go1
-rw-r--r--tag_test.go5
-rw-r--r--tree_test.go2
m---------vendor/libgit20
-rw-r--r--walk.go4
-rw-r--r--wrapper.c42
49 files changed, 1142 insertions, 110 deletions
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..8eb5872
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "vendor/libgit2"]
+ path = vendor/libgit2
+ url = https://github.com/libgit2/libgit2
diff --git a/.travis.yml b/.travis.yml
index f796389..d9130bc 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,13 +5,17 @@ sudo: required
install: ./script/install-libgit2.sh
go:
- - 1.1
- - 1.2
- - 1.3
- - 1.4
- 1.5
+ - 1.6
+ - 1.7
- tip
matrix:
allow_failures:
- go: tip
+
+branches:
+ only:
+ - master
+ - /v\d+/
+ - next
diff --git a/Makefile b/Makefile
index 9c42283..39fc558 100644
--- a/Makefile
+++ b/Makefile
@@ -1,8 +1,11 @@
default: test
-test:
+build-libgit2:
+ ./script/build-libgit2-static.sh
+
+test: build-libgit2
go run script/check-MakeGitError-thread-lock.go
go test ./...
-install:
+install: build-libgit2
go install ./...
diff --git a/README.md b/README.md
index 315032f..4ecfa34 100644
--- a/README.md
+++ b/README.md
@@ -2,57 +2,59 @@ git2go
======
[![GoDoc](https://godoc.org/github.com/libgit2/git2go?status.svg)](http://godoc.org/github.com/libgit2/git2go) [![Build Status](https://travis-ci.org/libgit2/git2go.svg?branch=master)](https://travis-ci.org/libgit2/git2go)
+Go bindings for [libgit2](http://libgit2.github.com/).
-Go bindings for [libgit2](http://libgit2.github.com/). The `master` branch follows the latest libgit2 release. The versioned branches indicate which libgit2 version they work against.
+### Which branch to use
-Installing
-----------
+The numbered branches work against the version of libgit2 as specified by their number. You can import them in your project via gopkg.in, e.g. if you have libgit2 v0.25 installed you'd import with
-This project wraps the functionality provided by libgit2. If you're using a stable version, install it to your system via your system's package manager and then install git2go as usual.
+ import "gopkg.in/libgit2/git2go.v25"
-Otherwise (`next` which tracks an unstable version), we need to build libgit2 as well. In order to build it, you need `cmake`, `pkg-config` and a C compiler. You will also need the development packages for OpenSSL and LibSSH2 installed if you want libgit2 to support HTTPS and SSH respectively.
+which will ensure there are no sudden changes to the API.
-### Stable version
+The `master` branch follows the tip of libgit2 itself (with some lag) and as such has no guarantees on its own API nor does it have expectations the stability of libgit2's. Thus this only supports statically linking against libgit2.
-git2go has `master` which tracks the latest release of libgit2, and versioned branches which indicate which version of libgit2 they work against. Install the development package on your system via your favourite package manager or from source and you can use a service like gopkg.in to use the appropriate version. For the libgit2 v0.22 case, you can use
+Installing
+----------
- import "gopkg.in/libgit2/git2go.v22"
+This project wraps the functionality provided by libgit2. It thus needs it in order to perform the work.
-to use a version of git2go which will work against libgit2 v0.22 and dynamically link to the library. You can use
+This project wraps the functionality provided by libgit2. If you're using a versioned branch, install it to your system via your system's package manager and then install git2go.
- import "github.com/libgit2/git2go"
-to use the version which works against the latest release.
+### Versioned branch, dynamic linking
-### From `next`
+When linking dynamically against a released version of libgit2, install it via your system's package manager. CGo will take care of finding its pkg-config file and set up the linking. Import via gopkg.in, e.g. to work against libgit2 v0.25
-The `next` branch follows libgit2's master branch, which means there is no stable API or ABI to link against. git2go can statically link against a vendored version of libgit2.
+ import "gopkg.in/libgit2/git2go.v25"
+
+### Master branch, or static linking
+
+If using `master` or building a branch statically, we need to build libgit2 first. In order to build it, you need `cmake`, `pkg-config` and a C compiler. You will also need the development packages for OpenSSL (outside of Windows or macOS) and LibSSH2 installed if you want libgit2 to support HTTPS and SSH respectively. Note that even if libgit2 is included in the resulting binary, its dependencies will not be.
Run `go get -d github.com/libgit2/git2go` to download the code and go to your `$GOPATH/src/github.com/libgit2/git2go` directory. From there, we need to build the C code and put it into the resulting go binary.
- git checkout next
git submodule update --init # get libgit2
make install
-will compile libgit2. Run `go install` so that it's statically linked to the git2go package.
+will compile libgit2, link it into git2go and install it.
Parallelism and network operations
----------------------------------
-libgit2 uses OpenSSL and LibSSH2 for performing encrypted network connections. For now, git2go asks libgit2 to set locking for OpenSSL. This makes HTTPS connections thread-safe, but it is fragile and will likely stop doing it soon. This may also make SSH connections thread-safe if your copy of libssh2 is linked against OpenSSL. Check libgit2's `THREADSAFE.md` for more information.
+libgit2 may use OpenSSL and LibSSH2 for performing encrypted network connections. For now, git2go asks libgit2 to set locking for OpenSSL. This makes HTTPS connections thread-safe, but it is fragile and will likely stop doing it soon. This may also make SSH connections thread-safe if your copy of libssh2 is linked against OpenSSL. Check libgit2's `THREADSAFE.md` for more information.
Running the tests
-----------------
-For the stable version, `go test` will work as usual. For the `next` branch, similarly to installing, running the tests requires linking against the local libgit2 library, so the Makefile provides a wrapper
+For the stable version, `go test` will work as usual. For the `next` branch, similarly to installing, running the tests requires building a local libgit2 library, so the Makefile provides a wrapper that makes sure it's built
make test
-Alternatively, if you want to pass arguments to `go test`, you can use the script that sets it all up
-
- ./script/with-static.sh go test -v
+Alternatively, you can build the library manually first and then run the tests
-which will run the specified arguments with the correct environment variables.
+ ./script/build-libgit2-static.sh
+ go test -v
License
-------
diff --git a/blame_test.go b/blame_test.go
index a2a4d38..ec96af7 100644
--- a/blame_test.go
+++ b/blame_test.go
@@ -6,6 +6,7 @@ import (
)
func TestBlame(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
diff --git a/blob.go b/blob.go
index 1a86e60..73a4a19 100644
--- a/blob.go
+++ b/blob.go
@@ -4,15 +4,13 @@ package git
#include <git2.h>
#include <string.h>
-extern int _go_git_blob_create_fromchunks(git_oid *id,
- git_repository *repo,
- const char *hintpath,
- void *payload);
-
+int _go_git_writestream_write(git_writestream *stream, const char *buffer, size_t len);
+void _go_git_writestream_free(git_writestream *stream);
*/
import "C"
import (
"io"
+ "reflect"
"runtime"
"unsafe"
)
@@ -37,15 +35,24 @@ func (repo *Repository) CreateBlobFromBuffer(data []byte) (*Oid, error) {
defer runtime.UnlockOSThread()
var id C.git_oid
- var ptr unsafe.Pointer
+ var size C.size_t
+ // Go 1.6 added some increased checking of passing pointer to
+ // C, but its check depends on its expectations of waht we
+ // pass to the C function, so unless we take the address of
+ // its contents at the call site itself, it can fail when
+ // 'data' is a slice of a slice.
+ //
+ // When we're given an empty slice, create a dummy one where 0
+ // isn't out of bounds.
if len(data) > 0 {
- ptr = unsafe.Pointer(&data[0])
+ size = C.size_t(len(data))
} else {
- ptr = unsafe.Pointer(nil)
+ data = []byte{0}
+ size = C.size_t(0)
}
- ecode := C.git_blob_create_frombuffer(&id, repo.ptr, ptr, C.size_t(len(data)))
+ ecode := C.git_blob_create_frombuffer(&id, repo.ptr, unsafe.Pointer(&data[0]), size)
if ecode < 0 {
return nil, MakeGitError(ecode)
}
@@ -78,27 +85,71 @@ func blobChunkCb(buffer *C.char, maxLen C.size_t, handle unsafe.Pointer) int {
return len(goBuf)
}
-func (repo *Repository) CreateBlobFromChunks(hintPath string, callback BlobChunkCallback) (*Oid, error) {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
-
+func (repo *Repository) CreateFromStream(hintPath string) (*BlobWriteStream, error) {
var chintPath *C.char = nil
+ var stream *C.git_writestream
+
if len(hintPath) > 0 {
chintPath = C.CString(hintPath)
defer C.free(unsafe.Pointer(chintPath))
}
- oid := C.git_oid{}
- payload := &BlobCallbackData{Callback: callback}
- handle := pointerHandles.Track(payload)
- defer pointerHandles.Untrack(handle)
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ ecode := C.git_blob_create_fromstream(&stream, repo.ptr, chintPath)
+ if ecode < 0 {
+ return nil, MakeGitError(ecode)
+ }
+
+ return newBlobWriteStreamFromC(stream), nil
+}
+
+type BlobWriteStream struct {
+ ptr *C.git_writestream
+}
+
+func newBlobWriteStreamFromC(ptr *C.git_writestream) *BlobWriteStream {
+ stream := &BlobWriteStream{
+ ptr: ptr,
+ }
+
+ runtime.SetFinalizer(stream, (*BlobWriteStream).Free)
+ return stream
+}
+
+// Implement io.Writer
+func (stream *BlobWriteStream) Write(p []byte) (int, error) {
+ header := (*reflect.SliceHeader)(unsafe.Pointer(&p))
+ ptr := (*C.char)(unsafe.Pointer(header.Data))
+ size := C.size_t(header.Len)
- ecode := C._go_git_blob_create_fromchunks(&oid, repo.ptr, chintPath, handle)
- if payload.Error != nil {
- return nil, payload.Error
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ ecode := C._go_git_writestream_write(stream.ptr, ptr, size)
+ if ecode < 0 {
+ return 0, MakeGitError(ecode)
}
+
+ return len(p), nil
+}
+
+func (stream *BlobWriteStream) Free() {
+ runtime.SetFinalizer(stream, nil)
+ C._go_git_writestream_free(stream.ptr)
+}
+
+func (stream *BlobWriteStream) Commit() (*Oid, error) {
+ oid := C.git_oid{}
+
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ ecode := C.git_blob_create_fromstream_commit(&oid, stream.ptr)
if ecode < 0 {
return nil, MakeGitError(ecode)
}
+
return newOidFromC(&oid), nil
}
diff --git a/blob_test.go b/blob_test.go
index 2b5ec4f..815ab3d 100644
--- a/blob_test.go
+++ b/blob_test.go
@@ -1,10 +1,23 @@
package git
import (
+ "bytes"
"testing"
)
+type bufWrapper struct {
+ buf [64]byte
+ pointer []byte
+}
+
+func doublePointerBytes() []byte {
+ o := &bufWrapper{}
+ o.pointer = o.buf[0:10]
+ return o.pointer[0:1]
+}
+
func TestCreateBlobFromBuffer(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -14,4 +27,16 @@ func TestCreateBlobFromBuffer(t *testing.T) {
if id.String() != "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391" {
t.Fatal("Empty buffer did not deliver empty blob id")
}
+
+ for _, data := range []([]byte){[]byte("hello there"), doublePointerBytes()} {
+ id, err = repo.CreateBlobFromBuffer(data)
+ checkFatal(t, err)
+
+ blob, err := repo.LookupBlob(id)
+ checkFatal(t, err)
+ if !bytes.Equal(blob.Contents(), data) {
+ t.Fatal("Loaded bytes don't match original bytes:",
+ blob.Contents(), "!=", data)
+ }
+ }
}
diff --git a/branch.go b/branch.go
index df72dba..d381c23 100644
--- a/branch.go
+++ b/branch.go
@@ -13,6 +13,7 @@ import (
type BranchType uint
const (
+ BranchAll BranchType = C.GIT_BRANCH_ALL
BranchLocal BranchType = C.GIT_BRANCH_LOCAL
BranchRemote BranchType = C.GIT_BRANCH_REMOTE
)
@@ -72,6 +73,10 @@ func (i *BranchIterator) ForEach(f BranchIteratorFunc) error {
}
}
+ if err != nil && IsErrorCode(err, ErrIterOver) {
+ return nil
+ }
+
return err
}
diff --git a/branch_test.go b/branch_test.go
index a0834a8..01a2e28 100644
--- a/branch_test.go
+++ b/branch_test.go
@@ -5,6 +5,7 @@ import (
)
func TestBranchIterator(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -27,6 +28,7 @@ func TestBranchIterator(t *testing.T) {
}
func TestBranchIteratorEach(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
diff --git a/checkout.go b/checkout.go
index a2e312b..f5822c9 100644
--- a/checkout.go
+++ b/checkout.go
@@ -2,6 +2,8 @@ package git
/*
#include <git2.h>
+
+extern void _go_git_populate_checkout_cb(git_checkout_options *opts);
*/
import "C"
import (
@@ -10,9 +12,18 @@ import (
"unsafe"
)
+type CheckoutNotifyType uint
type CheckoutStrategy uint
const (
+ CheckoutNotifyNone CheckoutNotifyType = C.GIT_CHECKOUT_NOTIFY_NONE
+ CheckoutNotifyConflict CheckoutNotifyType = C.GIT_CHECKOUT_NOTIFY_CONFLICT
+ CheckoutNotifyDirty CheckoutNotifyType = C.GIT_CHECKOUT_NOTIFY_DIRTY
+ CheckoutNotifyUpdated CheckoutNotifyType = C.GIT_CHECKOUT_NOTIFY_UPDATED
+ CheckoutNotifyUntracked CheckoutNotifyType = C.GIT_CHECKOUT_NOTIFY_UNTRACKED
+ CheckoutNotifyIgnored CheckoutNotifyType = C.GIT_CHECKOUT_NOTIFY_IGNORED
+ CheckoutNotifyAll CheckoutNotifyType = C.GIT_CHECKOUT_NOTIFY_ALL
+
CheckoutNone CheckoutStrategy = C.GIT_CHECKOUT_NONE // Dry run, no actual updates
CheckoutSafe CheckoutStrategy = C.GIT_CHECKOUT_SAFE // Allow safe updates that cannot overwrite uncommitted data
CheckoutForce CheckoutStrategy = C.GIT_CHECKOUT_FORCE // Allow all updates to force working directory to look like index
@@ -37,15 +48,21 @@ const (
CheckoutUpdateSubmodulesIfChanged CheckoutStrategy = C.GIT_CHECKOUT_UPDATE_SUBMODULES_IF_CHANGED // Recursively checkout submodules if HEAD moved in super repo (NOT IMPLEMENTED)
)
+type CheckoutNotifyCallback func(why CheckoutNotifyType, path string, baseline, target, workdir DiffFile) ErrorCode
+type CheckoutProgressCallback func(path string, completed, total uint) ErrorCode
+
type CheckoutOpts struct {
- Strategy CheckoutStrategy // Default will be a dry run
- DisableFilters bool // Don't apply filters like CRLF conversion
- DirMode os.FileMode // Default is 0755
- FileMode os.FileMode // Default is 0644 or 0755 as dictated by blob
- FileOpenFlags int // Default is O_CREAT | O_TRUNC | O_WRONLY
- TargetDirectory string // Alternative checkout path to workdir
- Paths []string
- Baseline *Tree
+ Strategy CheckoutStrategy // Default will be a dry run
+ DisableFilters bool // Don't apply filters like CRLF conversion
+ DirMode os.FileMode // Default is 0755
+ FileMode os.FileMode // Default is 0644 or 0755 as dictated by blob
+ FileOpenFlags int // Default is O_CREAT | O_TRUNC | O_WRONLY
+ NotifyFlags CheckoutNotifyType // Default will be none
+ NotifyCallback CheckoutNotifyCallback
+ ProgressCallback CheckoutProgressCallback
+ TargetDirectory string // Alternative checkout path to workdir
+ Paths []string
+ Baseline *Tree
}
func checkoutOptionsFromC(c *C.git_checkout_options) CheckoutOpts {
@@ -55,6 +72,13 @@ func checkoutOptionsFromC(c *C.git_checkout_options) CheckoutOpts {
opts.DirMode = os.FileMode(c.dir_mode)
opts.FileMode = os.FileMode(c.file_mode)
opts.FileOpenFlags = int(c.file_open_flags)
+ opts.NotifyFlags = CheckoutNotifyType(c.notify_flags)
+ if c.notify_payload != nil {
+ opts.NotifyCallback = pointerHandles.Get(c.notify_payload).(*CheckoutOpts).NotifyCallback
+ }
+ if c.progress_payload != nil {
+ opts.ProgressCallback = pointerHandles.Get(c.progress_payload).(*CheckoutOpts).ProgressCallback
+ }
if c.target_directory != nil {
opts.TargetDirectory = C.GoString(c.target_directory)
}
@@ -70,6 +94,38 @@ func (opts *CheckoutOpts) toC() *C.git_checkout_options {
return &c
}
+//export checkoutNotifyCallback
+func checkoutNotifyCallback(why C.git_checkout_notify_t, cpath *C.char, cbaseline, ctarget, cworkdir, data unsafe.Pointer) int {
+ if data == nil {
+ return 0
+ }
+ path := C.GoString(cpath)
+ var baseline, target, workdir DiffFile
+ if cbaseline != nil {
+ baseline = diffFileFromC((*C.git_diff_file)(cbaseline))
+ }
+ if ctarget != nil {
+ target = diffFileFromC((*C.git_diff_file)(ctarget))
+ }
+ if cworkdir != nil {
+ workdir = diffFileFromC((*C.git_diff_file)(cworkdir))
+ }
+ opts := pointerHandles.Get(data).(*CheckoutOpts)
+ if opts.NotifyCallback == nil {
+ return 0
+ }
+ return int(opts.NotifyCallback(CheckoutNotifyType(why), path, baseline, target, workdir))
+}
+
+//export checkoutProgressCallback
+func checkoutProgressCallback(path *C.char, completed_steps, total_steps C.size_t, data unsafe.Pointer) int {
+ opts := pointerHandles.Get(data).(*CheckoutOpts)
+ if opts.ProgressCallback == nil {
+ return 0
+ }
+ return int(opts.ProgressCallback(C.GoString(path), uint(completed_steps), uint(total_steps)))
+}
+
// Convert the CheckoutOpts struct to the corresponding
// C-struct. Returns a pointer to ptr, or nil if opts is nil, in order
// to help with what to pass.
@@ -83,6 +139,17 @@ func populateCheckoutOpts(ptr *C.git_checkout_options, opts *CheckoutOpts) *C.gi
ptr.disable_filters = cbool(opts.DisableFilters)
ptr.dir_mode = C.uint(opts.DirMode.Perm())
ptr.file_mode = C.uint(opts.FileMode.Perm())
+ ptr.notify_flags = C.uint(opts.NotifyFlags)
+ if opts.NotifyCallback != nil || opts.ProgressCallback != nil {
+ C._go_git_populate_checkout_cb(ptr)
+ }
+ payload := pointerHandles.Track(opts)
+ if opts.NotifyCallback != nil {
+ ptr.notify_payload = payload
+ }
+ if opts.ProgressCallback != nil {
+ ptr.progress_payload = payload
+ }
if opts.TargetDirectory != "" {
ptr.target_directory = C.CString(opts.TargetDirectory)
}
@@ -106,6 +173,9 @@ func freeCheckoutOpts(ptr *C.git_checkout_options) {
if ptr.paths.count > 0 {
freeStrarray(&ptr.paths)
}
+ if ptr.notify_payload != nil {
+ pointerHandles.Untrack(ptr.notify_payload)
+ }
}
// Updates files in the index and the working tree to match the content of
diff --git a/cherrypick_test.go b/cherrypick_test.go
index a3246bd..bfa5ca8 100644
--- a/cherrypick_test.go
+++ b/cherrypick_test.go
@@ -33,6 +33,7 @@ func readReadme(t *testing.T, repo *Repository) string {
}
func TestCherrypick(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
diff --git a/clone.go b/clone.go
index e80d14d..1ff5124 100644
--- a/clone.go
+++ b/clone.go
@@ -41,7 +41,6 @@ func Clone(url string, path string, options *CloneOptions) (*Repository, error)
var ptr *C.git_repository
ret := C.git_clone(&ptr, curl, cpath, copts)
- freeCheckoutOpts(&copts.checkout_opts)
if ret < 0 {
return nil, MakeGitError(ret)
diff --git a/clone_test.go b/clone_test.go
index a6bbf94..24c3a09 100644
--- a/clone_test.go
+++ b/clone_test.go
@@ -10,6 +10,7 @@ const (
)
func TestClone(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -35,6 +36,7 @@ func TestClone(t *testing.T) {
}
func TestCloneWithCallback(t *testing.T) {
+ t.Parallel()
testPayload := 0
repo := createTestRepo(t)
diff --git a/commit.go b/commit.go
index 6830da3..07b7c37 100644
--- a/commit.go
+++ b/commit.go
@@ -22,6 +22,10 @@ func (c Commit) Message() string {
return C.GoString(C.git_commit_message(c.cast_ptr))
}
+func (c Commit) RawMessage() string {
+ return C.GoString(C.git_commit_message_raw(c.cast_ptr))
+}
+
func (c Commit) Summary() string {
return C.GoString(C.git_commit_summary(c.cast_ptr))
}
diff --git a/config_test.go b/config_test.go
index fea8d8a..f31e73e 100644
--- a/config_test.go
+++ b/config_test.go
@@ -88,6 +88,7 @@ var tests = []TestRunner{
}
func TestConfigLookups(t *testing.T) {
+ t.Parallel()
var (
err error
c *Config
diff --git a/credentials.go b/credentials.go
index bb0ec41..4e42b6e 100644
--- a/credentials.go
+++ b/credentials.go
@@ -44,13 +44,15 @@ func NewCredUserpassPlaintext(username string, password string) (int, Cred) {
return int(ret), cred
}
-func NewCredSshKey(username string, publickey string, privatekey string, passphrase string) (int, Cred) {
+// NewCredSshKey creates new ssh credentials reading the public and private keys
+// from the file system.
+func NewCredSshKey(username string, publicKeyPath string, privateKeyPath string, passphrase string) (int, Cred) {
cred := Cred{}
cusername := C.CString(username)
defer C.free(unsafe.Pointer(cusername))
- cpublickey := C.CString(publickey)
+ cpublickey := C.CString(publicKeyPath)
defer C.free(unsafe.Pointer(cpublickey))
- cprivatekey := C.CString(privatekey)
+ cprivatekey := C.CString(privateKeyPath)
defer C.free(unsafe.Pointer(cprivatekey))
cpassphrase := C.CString(passphrase)
defer C.free(unsafe.Pointer(cpassphrase))
@@ -58,6 +60,22 @@ func NewCredSshKey(username string, publickey string, privatekey string, passphr
return int(ret), cred
}
+// NewCredSshKeyFromMemory creates new ssh credentials using the publicKey and privateKey
+// arguments as the values for the public and private keys.
+func NewCredSshKeyFromMemory(username string, publicKey string, privateKey string, passphrase string) (int, Cred) {
+ cred := Cred{}
+ cusername := C.CString(username)
+ defer C.free(unsafe.Pointer(cusername))
+ cpublickey := C.CString(publicKey)
+ defer C.free(unsafe.Pointer(cpublickey))
+ cprivatekey := C.CString(privateKey)
+ defer C.free(unsafe.Pointer(cprivatekey))
+ cpassphrase := C.CString(passphrase)
+ defer C.free(unsafe.Pointer(cpassphrase))
+ ret := C.git_cred_ssh_key_memory_new(&cred.ptr, cusername, cpublickey, cprivatekey, cpassphrase)
+ return int(ret), cred
+}
+
func NewCredSshKeyFromAgent(username string) (int, Cred) {
cred := Cred{}
cusername := C.CString(username)
diff --git a/describe_test.go b/describe_test.go
index 25af107..3181f23 100644
--- a/describe_test.go
+++ b/describe_test.go
@@ -8,6 +8,7 @@ import (
)
func TestDescribeCommit(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -92,14 +93,16 @@ func TestDescribeCommit(t *testing.T) {
func checkDescribeNoRefsFound(t *testing.T, err error) {
// The failure happens at wherever we were called, not here
_, file, line, ok := runtime.Caller(1)
+ expectedString := "no reference found, cannot describe anything"
if !ok {
t.Fatalf("Unable to get caller")
}
- if err == nil || !strings.Contains(err.Error(), "No reference found, cannot describe anything") {
+ if err == nil || !strings.Contains(err.Error(), expectedString) {
t.Fatalf(
- "%s:%v: was expecting error 'No reference found, cannot describe anything', got %v",
+ "%s:%v: was expecting error %v, got %v",
path.Base(file),
line,
+ expectedString,
err,
)
}
diff --git a/diff.go b/diff.go
index 9a7ac78..e8d5007 100644
--- a/diff.go
+++ b/diff.go
@@ -20,6 +20,7 @@ const (
DiffFlagBinary DiffFlag = C.GIT_DIFF_FLAG_BINARY
DiffFlagNotBinary DiffFlag = C.GIT_DIFF_FLAG_NOT_BINARY
DiffFlagValidOid DiffFlag = C.GIT_DIFF_FLAG_VALID_ID
+ DiffFlagExists DiffFlag = C.GIT_DIFF_FLAG_EXISTS
)
type Delta int
@@ -34,6 +35,8 @@ const (
DeltaIgnored Delta = C.GIT_DELTA_IGNORED
DeltaUntracked Delta = C.GIT_DELTA_UNTRACKED
DeltaTypeChange Delta = C.GIT_DELTA_TYPECHANGE
+ DeltaUnreadable Delta = C.GIT_DELTA_UNREADABLE
+ DeltaConflicted Delta = C.GIT_DELTA_CONFLICTED
)
type DiffLineType int
@@ -217,6 +220,33 @@ func (stats *DiffStats) FilesChanged() int {
return int(C.git_diff_stats_files_changed(stats.ptr))
}
+type DiffStatsFormat int
+
+const (
+ DiffStatsNone DiffStatsFormat = C.GIT_DIFF_STATS_NONE
+ DiffStatsFull DiffStatsFormat = C.GIT_DIFF_STATS_FULL
+ DiffStatsShort DiffStatsFormat = C.GIT_DIFF_STATS_SHORT
+ DiffStatsNumber DiffStatsFormat = C.GIT_DIFF_STATS_NUMBER
+ DiffStatsIncludeSummary DiffStatsFormat = C.GIT_DIFF_STATS_INCLUDE_SUMMARY
+)
+
+func (stats *DiffStats) String(format DiffStatsFormat,
+ width uint) (string, error) {
+ buf := C.git_buf{}
+ defer C.git_buf_free(&buf)
+
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ ret := C.git_diff_stats_to_buf(&buf,
+ stats.ptr, C.git_diff_stats_format_t(format), C.size_t(width))
+ if ret < 0 {
+ return "", MakeGitError(ret)
+ }
+
+ return C.GoString(buf.ptr), nil
+}
+
func (diff *Diff) Stats() (*DiffStats, error) {
stats := new(DiffStats)
@@ -372,6 +402,7 @@ const (
DiffIgnoreFilemode DiffOptionsFlag = C.GIT_DIFF_IGNORE_FILEMODE
DiffIgnoreSubmodules DiffOptionsFlag = C.GIT_DIFF_IGNORE_SUBMODULES
DiffIgnoreCase DiffOptionsFlag = C.GIT_DIFF_IGNORE_CASE
+ DiffIncludeCaseChange DiffOptionsFlag = C.GIT_DIFF_INCLUDE_CASECHANGE
DiffDisablePathspecMatch DiffOptionsFlag = C.GIT_DIFF_DISABLE_PATHSPEC_MATCH
DiffSkipBinaryCheck DiffOptionsFlag = C.GIT_DIFF_SKIP_BINARY_CHECK
diff --git a/diff_test.go b/diff_test.go
index 850ed8e..6fbad51 100644
--- a/diff_test.go
+++ b/diff_test.go
@@ -7,6 +7,7 @@ import (
)
func TestFindSimilar(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -61,7 +62,7 @@ func TestFindSimilar(t *testing.T) {
}
func TestDiffTreeToTree(t *testing.T) {
-
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -189,6 +190,7 @@ func createTestTrees(t *testing.T, repo *Repository) (originalTree *Tree, newTre
}
func TestDiffBlobs(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
diff --git a/features.go b/features.go
new file mode 100644
index 0000000..f6474a0
--- /dev/null
+++ b/features.go
@@ -0,0 +1,30 @@
+package git
+
+/*
+#include <git2.h>
+*/
+import "C"
+
+type Feature int
+
+const (
+ // libgit2 was built with threading support
+ FeatureThreads Feature = C.GIT_FEATURE_THREADS
+
+ // libgit2 was built with HTTPS support built-in
+ FeatureHttps Feature = C.GIT_FEATURE_HTTPS
+
+ // libgit2 was build with SSH support built-in
+ FeatureSsh Feature = C.GIT_FEATURE_SSH
+
+ // libgit2 was built with nanosecond support for files
+ FeatureNSec Feature = C.GIT_FEATURE_NSEC
+)
+
+// Features returns a bit-flag of Feature values indicating which features the
+// loaded libgit2 library has.
+func Features() Feature {
+ features := C.git_libgit2_features()
+
+ return Feature(features)
+}
diff --git a/git.go b/git.go
index e272389..c032b0a 100644
--- a/git.go
+++ b/git.go
@@ -1,12 +1,15 @@
package git
/*
+#cgo CFLAGS: -I${SRCDIR}/vendor/libgit2/include
+#cgo LDFLAGS: -L${SRCDIR}/vendor/libgit2/build/ -lgit2
+#cgo windows LDFLAGS: -lwinhttp
+#cgo !windows pkg-config: --static ${SRCDIR}/vendor/libgit2/build/libgit2.pc
#include <git2.h>
#include <git2/sys/openssl.h>
-#cgo pkg-config: libgit2
-#if LIBGIT2_VER_MAJOR != 0 || LIBGIT2_VER_MINOR != 24
-# error "Invalid libgit2 version; this git2go supports libgit2 v0.24"
+#if LIBGIT2_VER_MAJOR != 0 || LIBGIT2_VER_MINOR != 25
+# error "Invalid libgit2 version; this git2go supports libgit2 v0.25"
#endif
*/
@@ -126,6 +129,15 @@ func init() {
C.git_libgit2_init()
+ // Due to the multithreaded nature of Go and its interaction with
+ // calling C functions, we cannot work with a library that was not built
+ // with multi-threading support. The most likely outcome is a segfault
+ // or panic at an incomprehensible time, so let's make it easy by
+ // panicking right here.
+ if Features()&FeatureThreads == 0 {
+ panic("libgit2 was not built with threading support")
+ }
+
// This is not something we should be doing, as we may be
// stomping all over someone else's setup. The user should do
// this themselves or use some binding/wrapper which does it
diff --git a/git_test.go b/git_test.go
index 58caf71..807dcc2 100644
--- a/git_test.go
+++ b/git_test.go
@@ -58,6 +58,8 @@ func seedTestRepo(t *testing.T, repo *Repository) (*Oid, *Oid) {
checkFatal(t, err)
err = idx.AddByPath("README")
checkFatal(t, err)
+ err = idx.Write()
+ checkFatal(t, err)
treeId, err := idx.WriteTree()
checkFatal(t, err)
@@ -91,6 +93,8 @@ func updateReadme(t *testing.T, repo *Repository, content string) (*Oid, *Oid) {
checkFatal(t, err)
err = idx.AddByPath("README")
checkFatal(t, err)
+ err = idx.Write()
+ checkFatal(t, err)
treeId, err := idx.WriteTree()
checkFatal(t, err)
@@ -109,6 +113,7 @@ func updateReadme(t *testing.T, repo *Repository, content string) (*Oid, *Oid) {
}
func TestOidZero(t *testing.T) {
+ t.Parallel()
var zeroId Oid
if !zeroId.IsZero() {
@@ -117,6 +122,7 @@ func TestOidZero(t *testing.T) {
}
func TestEmptyOid(t *testing.T) {
+ t.Parallel()
_, err := NewOid("")
if err == nil || !IsErrorCode(err, ErrGeneric) {
t.Fatal("Should have returned invalid error")
diff --git a/index.go b/index.go
index ae94864..2afdcdf 100644
--- a/index.go
+++ b/index.go
@@ -278,6 +278,22 @@ func (v *Index) RemoveByPath(path string) error {
return nil
}
+// RemoveDirectory removes all entries from the index under a given directory.
+func (v *Index) RemoveDirectory(dir string, stage int) error {
+ cstr := C.CString(dir)
+ defer C.free(unsafe.Pointer(cstr))
+
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ ret := C.git_index_remove_directory(v.ptr, cstr, C.int(stage))
+ if ret < 0 {
+ return MakeGitError(ret)
+ }
+
+ return nil
+}
+
func (v *Index) WriteTreeTo(repo *Repository) (*Oid, error) {
oid := new(Oid)
diff --git a/index_test.go b/index_test.go
index 5f6b375..f47dace 100644
--- a/index_test.go
+++ b/index_test.go
@@ -8,6 +8,7 @@ import (
)
func TestCreateRepoAndStage(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -24,6 +25,7 @@ func TestCreateRepoAndStage(t *testing.T) {
}
func TestIndexReadTree(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -53,6 +55,7 @@ func TestIndexReadTree(t *testing.T) {
}
func TestIndexWriteTreeTo(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -72,6 +75,7 @@ func TestIndexWriteTreeTo(t *testing.T) {
}
func TestIndexAddAndWriteTreeTo(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -105,7 +109,48 @@ func TestIndexAddAndWriteTreeTo(t *testing.T) {
}
}
+func TestIndexRemoveDirectory(t *testing.T) {
+ repo := createTestRepo(t)
+ defer cleanupTestRepo(t, repo)
+
+ odb, err := repo.Odb()
+ checkFatal(t, err)
+
+ blobID, err := odb.Write([]byte("fou\n"), ObjectBlob)
+ checkFatal(t, err)
+
+ idx, err := NewIndex()
+ checkFatal(t, err)
+
+ entryCount := idx.EntryCount()
+ if entryCount != 0 {
+ t.Fatal("Index should count 0 entry")
+ }
+
+ entry := IndexEntry{
+ Path: "path/to/LISEZ_MOI",
+ Id: blobID,
+ Mode: FilemodeBlob,
+ }
+
+ err = idx.Add(&entry)
+ checkFatal(t, err)
+
+ entryCount = idx.EntryCount()
+ if entryCount != 1 {
+ t.Fatal("Index should count 1 entry")
+ }
+
+ err = idx.RemoveDirectory("path", 0)
+
+ entryCount = idx.EntryCount()
+ if entryCount != 0 {
+ t.Fatal("Index should count 0 entry")
+ }
+}
+
func TestIndexAddAllNoCallback(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -127,6 +172,7 @@ func TestIndexAddAllNoCallback(t *testing.T) {
}
func TestIndexAddAllCallback(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -155,6 +201,7 @@ func TestIndexAddAllCallback(t *testing.T) {
}
func TestIndexOpen(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
diff --git a/merge_test.go b/merge_test.go
index 8059727..f2c84bc 100644
--- a/merge_test.go
+++ b/merge_test.go
@@ -6,6 +6,7 @@ import (
)
func TestMergeWithSelf(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -24,6 +25,7 @@ func TestMergeWithSelf(t *testing.T) {
}
func TestMergeAnalysisWithSelf(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -46,6 +48,7 @@ func TestMergeAnalysisWithSelf(t *testing.T) {
}
func TestMergeSameFile(t *testing.T) {
+ t.Parallel()
file := MergeFileInput{
Path: "test",
Mode: 33188,
@@ -68,6 +71,7 @@ func TestMergeSameFile(t *testing.T) {
}
func TestMergeTreesWithoutAncestor(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -125,6 +129,7 @@ func appendCommit(t *testing.T, repo *Repository) (*Oid, *Oid) {
}
func TestMergeBase(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
diff --git a/note_test.go b/note_test.go
index 27e04be..9f64eb8 100644
--- a/note_test.go
+++ b/note_test.go
@@ -8,6 +8,7 @@ import (
)
func TestCreateNote(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -27,6 +28,7 @@ func TestCreateNote(t *testing.T) {
}
func TestNoteIterator(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -63,6 +65,7 @@ func TestNoteIterator(t *testing.T) {
}
func TestRemoveNote(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -86,6 +89,7 @@ func TestRemoveNote(t *testing.T) {
}
func TestDefaultNoteRef(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
diff --git a/object_test.go b/object_test.go
index 2ae2a6a..cb262de 100644
--- a/object_test.go
+++ b/object_test.go
@@ -5,6 +5,7 @@ import (
)
func TestObjectPoymorphism(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -88,6 +89,7 @@ func checkOwner(t *testing.T, repo *Repository, obj Object) {
}
func TestObjectOwner(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -104,6 +106,7 @@ func TestObjectOwner(t *testing.T) {
}
func TestObjectPeel(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
diff --git a/odb.go b/odb.go
index 9c6baa3..a728cd3 100644
--- a/odb.go
+++ b/odb.go
@@ -36,8 +36,8 @@ func NewOdb() (odb *Odb, err error) {
return odb, nil
}
-func NewOdbBackendFromC(ptr *C.git_odb_backend) (backend *OdbBackend) {
- backend = &OdbBackend{ptr}
+func NewOdbBackendFromC(ptr unsafe.Pointer) (backend *OdbBackend) {
+ backend = &OdbBackend{(*C.git_odb_backend)(ptr)}
return backend
}
@@ -226,6 +226,10 @@ func (object *OdbObject) Len() (len uint64) {
return uint64(C.git_odb_object_size(object.ptr))
}
+func (object *OdbObject) Type() ObjectType {
+ return ObjectType(C.git_odb_object_type(object.ptr))
+}
+
func (object *OdbObject) Data() (data []byte) {
var c_blob unsafe.Pointer = C.git_odb_object_data(object.ptr)
var blob []byte
diff --git a/odb_test.go b/odb_test.go
index dfd2ad0..3d22fc9 100644
--- a/odb_test.go
+++ b/odb_test.go
@@ -7,6 +7,7 @@ import (
)
func TestOdbReadHeader(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -35,6 +36,7 @@ func TestOdbReadHeader(t *testing.T) {
}
func TestOdbStream(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -64,7 +66,7 @@ func TestOdbStream(t *testing.T) {
}
func TestOdbHash(t *testing.T) {
-
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -92,6 +94,7 @@ Initial commit.`
}
func TestOdbForeach(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
diff --git a/patch_test.go b/patch_test.go
index 2d52fb4..291c705 100644
--- a/patch_test.go
+++ b/patch_test.go
@@ -6,6 +6,7 @@ import (
)
func TestPatch(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
diff --git a/push_test.go b/push_test.go
index 8f6e806..f372882 100644
--- a/push_test.go
+++ b/push_test.go
@@ -5,6 +5,7 @@ import (
)
func TestRemotePush(t *testing.T) {
+ t.Parallel()
repo := createBareTestRepo(t)
defer cleanupTestRepo(t, repo)
diff --git a/refdb.go b/refdb.go
index 0d1e241..4e3ebf9 100644
--- a/refdb.go
+++ b/refdb.go
@@ -9,6 +9,7 @@ extern void _go_git_refdb_backend_free(git_refdb_backend *backend);
import "C"
import (
"runtime"
+ "unsafe"
)
type Refdb struct {
@@ -34,8 +35,8 @@ func (v *Repository) NewRefdb() (refdb *Refdb, err error) {
return refdb, nil
}
-func NewRefdbBackendFromC(ptr *C.git_refdb_backend) (backend *RefdbBackend) {
- backend = &RefdbBackend{ptr}
+func NewRefdbBackendFromC(ptr unsafe.Pointer) (backend *RefdbBackend) {
+ backend = &RefdbBackend{(*C.git_refdb_backend)(ptr)}
return backend
}
diff --git a/reference_test.go b/reference_test.go
index 761daf8..b6721e1 100644
--- a/reference_test.go
+++ b/reference_test.go
@@ -8,6 +8,7 @@ import (
)
func TestRefModification(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -53,6 +54,7 @@ func TestRefModification(t *testing.T) {
}
func TestReferenceIterator(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -131,6 +133,7 @@ func TestReferenceIterator(t *testing.T) {
}
func TestReferenceOwner(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -150,6 +153,7 @@ func TestReferenceOwner(t *testing.T) {
}
func TestUtil(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -177,6 +181,7 @@ func TestUtil(t *testing.T) {
}
func TestIsNote(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -210,6 +215,7 @@ func TestIsNote(t *testing.T) {
}
func TestReferenceIsValidName(t *testing.T) {
+ t.Parallel()
if !ReferenceIsValidName("HEAD") {
t.Errorf("HEAD should be a valid reference name")
}
diff --git a/remote.go b/remote.go
index 8a57280..d3f437d 100644
--- a/remote.go
+++ b/remote.go
@@ -117,6 +117,30 @@ type FetchOptions struct {
Headers []string
}
+type ProxyType uint
+
+const (
+ // Do not attempt to connect through a proxy
+ //
+ // If built against lbicurl, it itself may attempt to connect
+ // to a proxy if the environment variables specify it.
+ ProxyTypeNone ProxyType = C.GIT_PROXY_NONE
+
+ // Try to auto-detect the proxy from the git configuration.
+ ProxyTypeAuto ProxyType = C.GIT_PROXY_AUTO
+
+ // Connect via the URL given in the options
+ ProxyTypeSpecified ProxyType = C.GIT_PROXY_SPECIFIED
+)
+
+type ProxyOptions struct {
+ // The type of proxy to use (or none)
+ Type ProxyType
+
+ // The proxy's URL
+ Url string
+}
+
type Remote struct {
ptr *C.git_remote
callbacks RemoteCallbacks
@@ -215,7 +239,7 @@ func completionCallback(completion_type C.git_remote_completion_type, data unsaf
func credentialsCallback(_cred **C.git_cred, _url *C.char, _username_from_url *C.char, allowed_types uint, data unsafe.Pointer) int {
callbacks, _ := pointerHandles.Get(data).(*RemoteCallbacks)
if callbacks.CredentialsCallback == nil {
- return 0
+ return C.GIT_PASSTHROUGH
}
url := C.GoString(_url)
username_from_url := C.GoString(_username_from_url)
@@ -320,6 +344,20 @@ func pushUpdateReferenceCallback(refname, status *C.char, data unsafe.Pointer) i
return int(callbacks.PushUpdateReferenceCallback(C.GoString(refname), C.GoString(status)))
}
+func populateProxyOptions(ptr *C.git_proxy_options, opts *ProxyOptions) {
+ C.git_proxy_init_options(ptr, C.GIT_PROXY_OPTIONS_VERSION)
+ if opts == nil {
+ return
+ }
+
+ ptr._type = C.git_proxy_t(opts.Type)
+ ptr.url = C.CString(opts.Url)
+}
+
+func freeProxyOptions(ptr *C.git_proxy_options) {
+ C.free(unsafe.Pointer(ptr.url))
+}
+
func RemoteIsValidName(name string) bool {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
@@ -454,6 +492,26 @@ func (o *Remote) PushUrl() string {
return C.GoString(C.git_remote_pushurl(o.ptr))
}
+func (c *RemoteCollection) Rename(remote, newname string) ([]string, error) {
+ cproblems := C.git_strarray{}
+ defer freeStrarray(&cproblems)
+ cnewname := C.CString(newname)
+ defer C.free(unsafe.Pointer(cnewname))
+ cremote := C.CString(remote)
+ defer C.free(unsafe.Pointer(cremote))
+
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ ret := C.git_remote_rename(&cproblems, c.repo.ptr, cremote, cnewname)
+ if ret < 0 {
+ return []string{}, MakeGitError(ret)
+ }
+
+ problems := makeStringsFromCStrings(cproblems.strings, int(cproblems.count))
+ return problems, nil
+}
+
func (c *RemoteCollection) SetUrl(remote, url string) error {
curl := C.CString(url)
defer C.free(unsafe.Pointer(curl))
@@ -654,12 +712,12 @@ func (o *Remote) Fetch(refspecs []string, opts *FetchOptions, msg string) error
return nil
}
-func (o *Remote) ConnectFetch(callbacks *RemoteCallbacks, headers []string) error {
- return o.Connect(ConnectDirectionFetch, callbacks, headers)
+func (o *Remote) ConnectFetch(callbacks *RemoteCallbacks, proxyOpts *ProxyOptions, headers []string) error {
+ return o.Connect(ConnectDirectionFetch, callbacks, proxyOpts, headers)
}
-func (o *Remote) ConnectPush(callbacks *RemoteCallbacks, headers []string) error {
- return o.Connect(ConnectDirectionPush, callbacks, headers)
+func (o *Remote) ConnectPush(callbacks *RemoteCallbacks, proxyOpts *ProxyOptions, headers []string) error {
+ return o.Connect(ConnectDirectionPush, callbacks, proxyOpts, headers)
}
// Connect opens a connection to a remote.
@@ -669,24 +727,36 @@ func (o *Remote) ConnectPush(callbacks *RemoteCallbacks, headers []string) error
// starts up a specific binary which can only do the one or the other.
//
// 'headers' are extra HTTP headers to use in this connection.
-func (o *Remote) Connect(direction ConnectDirection, callbacks *RemoteCallbacks, headers []string) error {
+func (o *Remote) Connect(direction ConnectDirection, callbacks *RemoteCallbacks, proxyOpts *ProxyOptions, headers []string) error {
var ccallbacks C.git_remote_callbacks
populateRemoteCallbacks(&ccallbacks, callbacks)
+ var cproxy C.git_proxy_options
+ populateProxyOptions(&cproxy, proxyOpts)
+ defer freeProxyOptions(&cproxy)
+
cheaders := C.git_strarray{}
cheaders.count = C.size_t(len(headers))
cheaders.strings = makeCStringsFromStrings(headers)
defer freeStrarray(&cheaders)
+
runtime.LockOSThread()
defer runtime.UnlockOSThread()
- if ret := C.git_remote_connect(o.ptr, C.git_direction(direction), &ccallbacks, &cheaders); ret != 0 {
+ if ret := C.git_remote_connect(o.ptr, C.git_direction(direction), &ccallbacks, &cproxy, &cheaders); ret != 0 {
return MakeGitError(ret)
}
return nil
}
+func (o *Remote) Disconnect() {
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ C.git_remote_disconnect(o.ptr)
+}
+
func (o *Remote) Ls(filterRefs ...string) ([]RemoteHead, error) {
var refs **C.git_remote_head
diff --git a/remote_test.go b/remote_test.go
index 978b803..8b20fd2 100644
--- a/remote_test.go
+++ b/remote_test.go
@@ -6,6 +6,7 @@ import (
)
func TestListRemotes(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -33,6 +34,7 @@ func assertHostname(cert *Certificate, valid bool, hostname string, t *testing.T
}
func TestCertificateCheck(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -52,24 +54,26 @@ func TestCertificateCheck(t *testing.T) {
}
func TestRemoteConnect(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
checkFatal(t, err)
- err = remote.ConnectFetch(nil, nil)
+ err = remote.ConnectFetch(nil, nil, nil)
checkFatal(t, err)
}
func TestRemoteLs(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
checkFatal(t, err)
- err = remote.ConnectFetch(nil, nil)
+ err = remote.ConnectFetch(nil, nil, nil)
checkFatal(t, err)
heads, err := remote.Ls()
@@ -81,13 +85,14 @@ func TestRemoteLs(t *testing.T) {
}
func TestRemoteLsFiltering(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
checkFatal(t, err)
- err = remote.ConnectFetch(nil, nil)
+ err = remote.ConnectFetch(nil, nil, nil)
checkFatal(t, err)
heads, err := remote.Ls("master")
@@ -107,6 +112,7 @@ func TestRemoteLsFiltering(t *testing.T) {
}
func TestRemotePruneRefs(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -129,6 +135,7 @@ func TestRemotePruneRefs(t *testing.T) {
}
func TestRemotePrune(t *testing.T) {
+ t.Parallel()
remoteRepo := createTestRepo(t)
defer cleanupTestRepo(t, remoteRepo)
@@ -166,7 +173,7 @@ func TestRemotePrune(t *testing.T) {
rr, err := repo.Remotes.Lookup("origin")
checkFatal(t, err)
- err = rr.ConnectFetch(nil, nil)
+ err = rr.ConnectFetch(nil, nil, nil)
checkFatal(t, err)
err = rr.Prune(nil)
diff --git a/repository.go b/repository.go
index efc506e..0612b5f 100644
--- a/repository.go
+++ b/repository.go
@@ -30,6 +30,9 @@ type Repository struct {
// Tags represents the collection of tags and can be used to create,
// list, iterate and remove tags in this repository.
Tags TagsCollection
+ // Stashes represents the collection of stashes and can be used to
+ // save, apply and iterate over stash states in this repository.
+ Stashes StashCollection
}
func newRepositoryFromC(ptr *C.git_repository) *Repository {
@@ -40,6 +43,7 @@ func newRepositoryFromC(ptr *C.git_repository) *Repository {
repo.References.repo = repo
repo.Notes.repo = repo
repo.Tags.repo = repo
+ repo.Stashes.repo = repo
runtime.SetFinalizer(repo, (*Repository).Free)
@@ -264,6 +268,40 @@ func (v *Repository) IsHeadDetached() (bool, error) {
return ret != 0, nil
}
+func (v *Repository) IsHeadUnborn() (bool, error) {
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ ret := C.git_repository_head_unborn(v.ptr)
+ if ret < 0 {
+ return false, MakeGitError(ret)
+ }
+ return ret != 0, nil
+}
+
+func (v *Repository) IsEmpty() (bool, error) {
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ ret := C.git_repository_is_empty(v.ptr)
+ if ret < 0 {
+ return false, MakeGitError(ret)
+ }
+
+ return ret != 0, nil
+}
+
+func (v *Repository) IsShallow() (bool, error) {
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ ret := C.git_repository_is_shallow(v.ptr)
+ if ret < 0 {
+ return false, MakeGitError(ret)
+ }
+ return ret != 0, nil
+}
+
func (v *Repository) Walk() (*RevWalk, error) {
var walkPtr *C.git_revwalk
diff --git a/reset_test.go b/reset_test.go
index ec578bd..45777e4 100644
--- a/reset_test.go
+++ b/reset_test.go
@@ -6,6 +6,7 @@ import (
)
func TestResetToCommit(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
seedTestRepo(t, repo)
// create commit to reset to
diff --git a/revparse_test.go b/revparse_test.go
index 75e9ffd..2835434 100644
--- a/revparse_test.go
+++ b/revparse_test.go
@@ -5,6 +5,7 @@ import (
)
func TestRevparse(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -17,6 +18,7 @@ func TestRevparse(t *testing.T) {
}
func TestRevparseSingle(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -29,6 +31,7 @@ func TestRevparseSingle(t *testing.T) {
}
func TestRevparseExt(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
diff --git a/script/with-static.sh b/script/with-static.sh
deleted file mode 100755
index 3f60e31..0000000
--- a/script/with-static.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/sh
-
-set -ex
-
-export BUILD="$PWD/vendor/libgit2/build"
-export PCFILE="$BUILD/libgit2.pc"
-
-FLAGS=$(pkg-config --static --libs $PCFILE) || exit 1
-export CGO_LDFLAGS="$BUILD/libgit2.a -L$BUILD ${FLAGS}"
-export CGO_CFLAGS="-I$PWD/vendor/libgit2/include"
-
-$@
diff --git a/stash.go b/stash.go
new file mode 100644
index 0000000..809732e
--- /dev/null
+++ b/stash.go
@@ -0,0 +1,338 @@
+package git
+
+/*
+#include <git2.h>
+
+extern void _go_git_setup_stash_apply_progress_callbacks(git_stash_apply_options *opts);
+extern int _go_git_stash_foreach(git_repository *repo, void *payload);
+*/
+import "C"
+import (
+ "runtime"
+ "unsafe"
+)
+
+// StashFlag are flags that affect the stash save operation.
+type StashFlag int
+
+const (
+ // StashDefault represents no option, default.
+ StashDefault StashFlag = C.GIT_STASH_DEFAULT
+
+ // StashKeepIndex leaves all changes already added to the
+ // index intact in the working directory.
+ StashKeepIndex StashFlag = C.GIT_STASH_KEEP_INDEX
+
+ // StashIncludeUntracked means all untracked files are also
+ // stashed and then cleaned up from the working directory.
+ StashIncludeUntracked StashFlag = C.GIT_STASH_INCLUDE_UNTRACKED
+
+ // StashIncludeIgnored means all ignored files are also
+ // stashed and then cleaned up from the working directory.
+ StashIncludeIgnored StashFlag = C.GIT_STASH_INCLUDE_IGNORED
+)
+
+// StashCollection represents the possible operations that can be
+// performed on the collection of stashes for a repository.
+type StashCollection struct {
+ repo *Repository
+}
+
+// Save saves the local modifications to a new stash.
+//
+// Stasher is the identity of the person performing the stashing.
+// Message is the optional description along with the stashed state.
+// Flags control the stashing process and are given as bitwise OR.
+func (c *StashCollection) Save(
+ stasher *Signature, message string, flags StashFlag) (*Oid, error) {
+
+ oid := new(Oid)
+
+ stasherC, err := stasher.toC()
+ if err != nil {
+ return nil, err
+ }
+ defer C.git_signature_free(stasherC)
+
+ messageC := C.CString(message)
+ defer C.free(unsafe.Pointer(messageC))
+
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ ret := C.git_stash_save(
+ oid.toC(), c.repo.ptr,
+ stasherC, messageC, C.uint32_t(flags))
+
+ if ret < 0 {
+ return nil, MakeGitError(ret)
+ }
+ return oid, nil
+}
+
+// StashApplyFlag are flags that affect the stash apply operation.
+type StashApplyFlag int
+
+const (
+ // StashApplyDefault is the default.
+ StashApplyDefault StashApplyFlag = C.GIT_STASH_APPLY_DEFAULT
+
+ // StashApplyReinstateIndex will try to reinstate not only the
+ // working tree's changes, but also the index's changes.
+ StashApplyReinstateIndex StashApplyFlag = C.GIT_STASH_APPLY_REINSTATE_INDEX
+)
+
+// StashApplyProgress are flags describing the progress of the apply operation.
+type StashApplyProgress int
+
+const (
+ // StashApplyProgressNone means loading the stashed data from the object store.
+ StashApplyProgressNone StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_NONE
+
+ // StashApplyProgressLoadingStash means the stored index is being analyzed.
+ StashApplyProgressLoadingStash StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_LOADING_STASH
+
+ // StashApplyProgressAnalyzeIndex means the stored index is being analyzed.
+ StashApplyProgressAnalyzeIndex StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX
+
+ // StashApplyProgressAnalyzeModified means the modified files are being analyzed.
+ StashApplyProgressAnalyzeModified StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED
+
+ // StashApplyProgressAnalyzeUntracked means the untracked and ignored files are being analyzed.
+ StashApplyProgressAnalyzeUntracked StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED
+
+ // StashApplyProgressCheckoutUntracked means the untracked files are being written to disk.
+ StashApplyProgressCheckoutUntracked StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED
+
+ // StashApplyProgressCheckoutModified means the modified files are being written to disk.
+ StashApplyProgressCheckoutModified StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED
+
+ // StashApplyProgressDone means the stash was applied successfully.
+ StashApplyProgressDone StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_DONE
+)
+
+// StashApplyProgressCallback is the apply operation notification callback.
+type StashApplyProgressCallback func(progress StashApplyProgress) error
+
+type stashApplyProgressData struct {
+ Callback StashApplyProgressCallback
+ Error error
+}
+
+//export stashApplyProgressCb
+func stashApplyProgressCb(progress C.git_stash_apply_progress_t, handle unsafe.Pointer) int {
+ payload := pointerHandles.Get(handle)
+ data, ok := payload.(*stashApplyProgressData)
+ if !ok {
+ panic("could not retrieve data for handle")
+ }
+
+ if data != nil {
+ err := data.Callback(StashApplyProgress(progress))
+ if err != nil {
+ data.Error = err
+ return C.GIT_EUSER
+ }
+ }
+ return 0
+}
+
+// StashApplyOptions represents options to control the apply operation.
+type StashApplyOptions struct {
+ Flags StashApplyFlag
+ CheckoutOptions CheckoutOpts // options to use when writing files to the working directory
+ ProgressCallback StashApplyProgressCallback // optional callback to notify the consumer of application progress
+}
+
+// DefaultStashApplyOptions initializes the structure with default values.
+func DefaultStashApplyOptions() (StashApplyOptions, error) {
+ optsC := C.git_stash_apply_options{}
+
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ ecode := C.git_stash_apply_init_options(&optsC, C.GIT_STASH_APPLY_OPTIONS_VERSION)
+ if ecode < 0 {
+ return StashApplyOptions{}, MakeGitError(ecode)
+ }
+ return StashApplyOptions{
+ Flags: StashApplyFlag(optsC.flags),
+ CheckoutOptions: checkoutOptionsFromC(&optsC.checkout_options),
+ }, nil
+}
+
+func (opts *StashApplyOptions) toC() (
+ optsC *C.git_stash_apply_options, progressData *stashApplyProgressData) {
+
+ if opts != nil {
+ progressData = &stashApplyProgressData{
+ Callback: opts.ProgressCallback,
+ }
+
+ optsC = &C.git_stash_apply_options{
+ version: C.GIT_STASH_APPLY_OPTIONS_VERSION,
+ flags: C.git_stash_apply_flags(opts.Flags),
+ }
+ populateCheckoutOpts(&optsC.checkout_options, &opts.CheckoutOptions)
+ if opts.ProgressCallback != nil {
+ C._go_git_setup_stash_apply_progress_callbacks(optsC)
+ optsC.progress_payload = pointerHandles.Track(progressData)
+ }
+ }
+ return
+}
+
+// should be called after every call to toC() as deferred.
+func untrackStashApplyOptionsCallback(optsC *C.git_stash_apply_options) {
+ if optsC != nil && optsC.progress_payload != nil {
+ pointerHandles.Untrack(optsC.progress_payload)
+ }
+}
+
+func freeStashApplyOptions(optsC *C.git_stash_apply_options) {
+ if optsC != nil {
+ freeCheckoutOpts(&optsC.checkout_options)
+ }
+}
+
+// Apply applies a single stashed state from the stash list.
+//
+// If local changes in the working directory conflict with changes in the
+// stash then ErrConflict will be returned. In this case, the index
+// will always remain unmodified and all files in the working directory will
+// remain unmodified. However, if you are restoring untracked files or
+// ignored files and there is a conflict when applying the modified files,
+// then those files will remain in the working directory.
+//
+// If passing the StashApplyReinstateIndex flag and there would be conflicts
+// when reinstating the index, the function will return ErrConflict
+// and both the working directory and index will be left unmodified.
+//
+// Note that a minimum checkout strategy of 'CheckoutSafe' is implied.
+//
+// 'index' is the position within the stash list. 0 points to the most
+// recent stashed state.
+//
+// Returns error code ErrNotFound if there's no stashed state for the given
+// index, error code ErrConflict if local changes in the working directory
+// conflict with changes in the stash, the user returned error from the
+// StashApplyProgressCallback, if any, or other error code.
+//
+// Error codes can be interogated with IsErrorCode(err, ErrNotFound).
+func (c *StashCollection) Apply(index int, opts StashApplyOptions) error {
+ optsC, progressData := opts.toC()
+ defer untrackStashApplyOptionsCallback(optsC)
+ defer freeStashApplyOptions(optsC)
+
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ ret := C.git_stash_apply(c.repo.ptr, C.size_t(index), optsC)
+ if ret == C.GIT_EUSER {
+ return progressData.Error
+ }
+ if ret < 0 {
+ return MakeGitError(ret)
+ }
+ return nil
+}
+
+// StashCallback is called per entry when interating over all
+// the stashed states.
+//
+// 'index' is the position of the current stash in the stash list,
+// 'message' is the message used when creating the stash and 'id'
+// is the commit id of the stash.
+type StashCallback func(index int, message string, id *Oid) error
+
+type stashCallbackData struct {
+ Callback StashCallback
+ Error error
+}
+
+//export stashForeachCb
+func stashForeachCb(index C.size_t, message *C.char, id *C.git_oid, handle unsafe.Pointer) int {
+ payload := pointerHandles.Get(handle)
+ data, ok := payload.(*stashCallbackData)
+ if !ok {
+ panic("could not retrieve data for handle")
+ }
+
+ err := data.Callback(int(index), C.GoString(message), newOidFromC(id))
+ if err != nil {
+ data.Error = err
+ return C.GIT_EUSER
+ }
+ return 0
+}
+
+// Foreach loops over all the stashed states and calls the callback
+// for each one.
+//
+// If callback returns an error, this will stop looping.
+func (c *StashCollection) Foreach(callback StashCallback) error {
+ data := stashCallbackData{
+ Callback: callback,
+ }
+
+ handle := pointerHandles.Track(&data)
+ defer pointerHandles.Untrack(handle)
+
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ ret := C._go_git_stash_foreach(c.repo.ptr, handle)
+ if ret == C.GIT_EUSER {
+ return data.Error
+ }
+ if ret < 0 {
+ return MakeGitError(ret)
+ }
+ return nil
+}
+
+// Drop removes a single stashed state from the stash list.
+//
+// 'index' is the position within the stash list. 0 points
+// to the most recent stashed state.
+//
+// Returns error code ErrNotFound if there's no stashed
+// state for the given index.
+func (c *StashCollection) Drop(index int) error {
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ ret := C.git_stash_drop(c.repo.ptr, C.size_t(index))
+ if ret < 0 {
+ return MakeGitError(ret)
+ }
+ return nil
+}
+
+// Pop applies a single stashed state from the stash list
+// and removes it from the list if successful.
+//
+// 'index' is the position within the stash list. 0 points
+// to the most recent stashed state.
+//
+// 'opts' controls how stashes are applied.
+//
+// Returns error code ErrNotFound if there's no stashed
+// state for the given index.
+func (c *StashCollection) Pop(index int, opts StashApplyOptions) error {
+ optsC, progressData := opts.toC()
+ defer untrackStashApplyOptionsCallback(optsC)
+ defer freeStashApplyOptions(optsC)
+
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ ret := C.git_stash_pop(c.repo.ptr, C.size_t(index), optsC)
+ if ret == C.GIT_EUSER {
+ return progressData.Error
+ }
+ if ret < 0 {
+ return MakeGitError(ret)
+ }
+ return nil
+}
diff --git a/stash_test.go b/stash_test.go
new file mode 100644
index 0000000..180a16b
--- /dev/null
+++ b/stash_test.go
@@ -0,0 +1,198 @@
+package git
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path"
+ "reflect"
+ "runtime"
+ "testing"
+ "time"
+)
+
+func TestStash(t *testing.T) {
+ repo := createTestRepo(t)
+ defer cleanupTestRepo(t, repo)
+
+ prepareStashRepo(t, repo)
+
+ sig := &Signature{
+ Name: "Rand Om Hacker",
+ Email: "[email protected]",
+ When: time.Now(),
+ }
+
+ stash1, err := repo.Stashes.Save(sig, "First stash", StashDefault)
+ checkFatal(t, err)
+
+ _, err = repo.LookupCommit(stash1)
+ checkFatal(t, err)
+
+ b, err := ioutil.ReadFile(pathInRepo(repo, "README"))
+ checkFatal(t, err)
+ if string(b) == "Update README goes to stash\n" {
+ t.Errorf("README still contains the uncommitted changes")
+ }
+
+ if !fileExistsInRepo(repo, "untracked.txt") {
+ t.Errorf("untracked.txt doesn't exist in the repo; should be untracked")
+ }
+
+ // Apply: default
+
+ opts, err := DefaultStashApplyOptions()
+ checkFatal(t, err)
+
+ err = repo.Stashes.Apply(0, opts)
+ checkFatal(t, err)
+
+ b, err = ioutil.ReadFile(pathInRepo(repo, "README"))
+ checkFatal(t, err)
+ if string(b) != "Update README goes to stash\n" {
+ t.Errorf("README changes aren't here")
+ }
+
+ // Apply: no stash for the given index
+
+ err = repo.Stashes.Apply(1, opts)
+ if !IsErrorCode(err, ErrNotFound) {
+ t.Errorf("expecting GIT_ENOTFOUND error code %d, got %v", ErrNotFound, err)
+ }
+
+ // Apply: callback stopped
+
+ opts.ProgressCallback = func(progress StashApplyProgress) error {
+ if progress == StashApplyProgressCheckoutModified {
+ return fmt.Errorf("Stop")
+ }
+ return nil
+ }
+
+ err = repo.Stashes.Apply(0, opts)
+ if err.Error() != "Stop" {
+ t.Errorf("expecting error 'Stop', got %v", err)
+ }
+
+ // Create second stash with ignored files
+
+ os.MkdirAll(pathInRepo(repo, "tmp"), os.ModeDir|os.ModePerm)
+ err = ioutil.WriteFile(pathInRepo(repo, "tmp/ignored.txt"), []byte("Ignore me\n"), 0644)
+ checkFatal(t, err)
+
+ stash2, err := repo.Stashes.Save(sig, "Second stash", StashIncludeIgnored)
+ checkFatal(t, err)
+
+ if fileExistsInRepo(repo, "tmp/ignored.txt") {
+ t.Errorf("tmp/ignored.txt should not exist anymore in the work dir")
+ }
+
+ // Stash foreach
+
+ expected := []stash{
+ {0, "On master: Second stash", stash2.String()},
+ {1, "On master: First stash", stash1.String()},
+ }
+ checkStashes(t, repo, expected)
+
+ // Stash pop
+
+ opts, _ = DefaultStashApplyOptions()
+ err = repo.Stashes.Pop(1, opts)
+ checkFatal(t, err)
+
+ b, err = ioutil.ReadFile(pathInRepo(repo, "README"))
+ checkFatal(t, err)
+ if string(b) != "Update README goes to stash\n" {
+ t.Errorf("README changes aren't here")
+ }
+
+ expected = []stash{
+ {0, "On master: Second stash", stash2.String()},
+ }
+ checkStashes(t, repo, expected)
+
+ // Stash drop
+
+ err = repo.Stashes.Drop(0)
+ checkFatal(t, err)
+
+ expected = []stash{}
+ checkStashes(t, repo, expected)
+}
+
+type stash struct {
+ index int
+ msg string
+ id string
+}
+
+func checkStashes(t *testing.T, repo *Repository, expected []stash) {
+ var actual []stash
+
+ repo.Stashes.Foreach(func(index int, msg string, id *Oid) error {
+ stash := stash{index, msg, id.String()}
+ if len(expected) > len(actual) {
+ if s := expected[len(actual)]; s.id == "" {
+ stash.id = "" // don't check id
+ }
+ }
+ actual = append(actual, stash)
+ return nil
+ })
+
+ if len(expected) > 0 && !reflect.DeepEqual(expected, actual) {
+ // The failure happens at wherever we were called, not here
+ _, file, line, ok := runtime.Caller(1)
+ if !ok {
+ t.Fatalf("Unable to get caller")
+ }
+ t.Errorf("%v:%v: expecting %#v\ngot %#v", path.Base(file), line, expected, actual)
+ }
+}
+
+func prepareStashRepo(t *testing.T, repo *Repository) {
+ seedTestRepo(t, repo)
+
+ err := ioutil.WriteFile(pathInRepo(repo, ".gitignore"), []byte("tmp\n"), 0644)
+ checkFatal(t, err)
+
+ sig := &Signature{
+ Name: "Rand Om Hacker",
+ Email: "[email protected]",
+ When: time.Now(),
+ }
+
+ idx, err := repo.Index()
+ checkFatal(t, err)
+ err = idx.AddByPath(".gitignore")
+ checkFatal(t, err)
+ treeID, err := idx.WriteTree()
+ checkFatal(t, err)
+ err = idx.Write()
+ checkFatal(t, err)
+
+ currentBranch, err := repo.Head()
+ checkFatal(t, err)
+ currentTip, err := repo.LookupCommit(currentBranch.Target())
+ checkFatal(t, err)
+
+ message := "Add .gitignore\n"
+ tree, err := repo.LookupTree(treeID)
+ checkFatal(t, err)
+ _, err = repo.CreateCommit("HEAD", sig, sig, message, tree, currentTip)
+ checkFatal(t, err)
+
+ err = ioutil.WriteFile(pathInRepo(repo, "README"), []byte("Update README goes to stash\n"), 0644)
+ checkFatal(t, err)
+
+ err = ioutil.WriteFile(pathInRepo(repo, "untracked.txt"), []byte("Hello, World\n"), 0644)
+ checkFatal(t, err)
+}
+
+func fileExistsInRepo(repo *Repository, name string) bool {
+ if _, err := os.Stat(pathInRepo(repo, name)); err != nil {
+ return false
+ }
+ return true
+}
diff --git a/status.go b/status.go
index 068a474..e68e6e9 100644
--- a/status.go
+++ b/status.go
@@ -25,6 +25,7 @@ const (
StatusWtTypeChange Status = C.GIT_STATUS_WT_TYPECHANGE
StatusWtRenamed Status = C.GIT_STATUS_WT_RENAMED
StatusIgnored Status = C.GIT_STATUS_IGNORED
+ StatusConflicted Status = C.GIT_STATUS_CONFLICTED
)
type StatusEntry struct {
diff --git a/status_test.go b/status_test.go
index 5b97b00..17ed94f 100644
--- a/status_test.go
+++ b/status_test.go
@@ -7,6 +7,7 @@ import (
)
func TestStatusFile(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -27,6 +28,7 @@ func TestStatusFile(t *testing.T) {
}
func TestStatusList(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
diff --git a/submodule_test.go b/submodule_test.go
index 43c890a..fa2e98c 100644
--- a/submodule_test.go
+++ b/submodule_test.go
@@ -5,6 +5,7 @@ import (
)
func TestSubmoduleForeach(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
diff --git a/tag_test.go b/tag_test.go
index 2fdfe00..3404923 100644
--- a/tag_test.go
+++ b/tag_test.go
@@ -7,6 +7,7 @@ import (
)
func TestCreateTag(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -26,6 +27,7 @@ func TestCreateTag(t *testing.T) {
}
func TestCreateTagLightweight(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -50,6 +52,7 @@ func TestCreateTagLightweight(t *testing.T) {
}
func TestListTags(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -79,6 +82,7 @@ func TestListTags(t *testing.T) {
}
func TestListTagsWithMatch(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -116,6 +120,7 @@ func TestListTagsWithMatch(t *testing.T) {
}
func TestTagForeach(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
diff --git a/tree_test.go b/tree_test.go
index fae395a..f5b6822 100644
--- a/tree_test.go
+++ b/tree_test.go
@@ -3,6 +3,7 @@ package git
import "testing"
func TestTreeEntryById(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
@@ -22,6 +23,7 @@ func TestTreeEntryById(t *testing.T) {
}
func TestTreeBuilderInsert(t *testing.T) {
+ t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
diff --git a/vendor/libgit2 b/vendor/libgit2
new file mode 160000
+Subproject df4dfaadcf709646ebab2e57e3589952cf1ac80
diff --git a/walk.go b/walk.go
index 60e618d..ab1de61 100644
--- a/walk.go
+++ b/walk.go
@@ -173,10 +173,6 @@ func (v *RevWalk) Iterate(fun RevWalkIterator) (err error) {
return nil
}
if err != nil {
- if err.(GitError).Code == ErrIterOver {
- err = nil
- }
-
return err
}
diff --git a/wrapper.c b/wrapper.c
index a0688c0..11c2f32 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -10,6 +10,12 @@ void _go_git_populate_remote_cb(git_clone_options *opts)
opts->remote_cb = (git_remote_create_cb)remoteCreateCallback;
}
+void _go_git_populate_checkout_cb(git_checkout_options *opts)
+{
+ opts->notify_cb = (git_checkout_notify_cb)checkoutNotifyCallback;
+ opts->progress_cb = (git_checkout_progress_cb)checkoutProgressCallback;
+}
+
int _go_git_visit_submodule(git_repository *repo, void *fct)
{
return git_submodule_foreach(repo, (gogit_submodule_cbk)&SubmoduleVisitor, fct);
@@ -108,19 +114,6 @@ void _go_git_setup_callbacks(git_remote_callbacks *callbacks) {
callbacks->push_update_reference = (push_update_reference_cb) pushUpdateReferenceCallback;
}
-int _go_blob_chunk_cb(char *buffer, size_t maxLen, void *payload)
-{
- return blobChunkCb(buffer, maxLen, payload);
-}
-
-int _go_git_blob_create_fromchunks(git_oid *id,
- git_repository *repo,
- const char *hintpath,
- void *payload)
-{
- return git_blob_create_fromchunks(id, repo, hintpath, _go_blob_chunk_cb, payload);
-}
-
int _go_git_index_add_all(git_index *index, const git_strarray *pathspec, unsigned int flags, void *callback) {
git_index_matched_path_cb cb = callback ? (git_index_matched_path_cb) &indexMatchedPathCallback : NULL;
return git_index_add_all(index, pathspec, flags, cb, callback);
@@ -164,4 +157,27 @@ int _go_git_merge_file(git_merge_file_result* out, char* ancestorContents, size_
return git_merge_file(out, &ancestor, &ours, &theirs, copts);
}
+void _go_git_setup_stash_apply_progress_callbacks(git_stash_apply_options *opts) {
+ opts->progress_cb = (git_stash_apply_progress_cb)stashApplyProgressCb;
+}
+
+int _go_git_stash_foreach(git_repository *repo, void *payload) {
+ return git_stash_foreach(repo, (git_stash_cb)&stashForeachCb, payload);
+}
+
+int _go_git_writestream_write(git_writestream *stream, const char *buffer, size_t len)
+{
+ return stream->write(stream, buffer, len);
+}
+
+int _go_git_writestream_close(git_writestream *stream)
+{
+ return stream->close(stream);
+}
+
+void _go_git_writestream_free(git_writestream *stream)
+{
+ stream->free(stream);
+}
+
/* EOF */