Merge remote-tracking branch 'origin/next'
This commit is contained in:
commit
f037074198
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "vendor/libgit2"]
|
||||||
|
path = vendor/libgit2
|
||||||
|
url = https://github.com/libgit2/libgit2
|
|
@ -5,10 +5,6 @@ sudo: required
|
||||||
install: ./script/install-libgit2.sh
|
install: ./script/install-libgit2.sh
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.1
|
|
||||||
- 1.2
|
|
||||||
- 1.3
|
|
||||||
- 1.4
|
|
||||||
- 1.5
|
- 1.5
|
||||||
- 1.6
|
- 1.6
|
||||||
- 1.7
|
- 1.7
|
||||||
|
|
7
Makefile
7
Makefile
|
@ -1,8 +1,11 @@
|
||||||
default: test
|
default: test
|
||||||
|
|
||||||
test:
|
build-libgit2:
|
||||||
|
./script/build-libgit2-static.sh
|
||||||
|
|
||||||
|
test: build-libgit2
|
||||||
go run script/check-MakeGitError-thread-lock.go
|
go run script/check-MakeGitError-thread-lock.go
|
||||||
go test ./...
|
go test ./...
|
||||||
|
|
||||||
install:
|
install: build-libgit2
|
||||||
go install ./...
|
go install ./...
|
||||||
|
|
11
README.md
11
README.md
|
@ -22,7 +22,7 @@ to use a version of git2go which will work against libgit2 v0.22 and dynamically
|
||||||
|
|
||||||
import "github.com/libgit2/git2go"
|
import "github.com/libgit2/git2go"
|
||||||
|
|
||||||
to use the version which works against the latest release.
|
to use the 'master' branch, which works against the latest release of libgit2, whichever that one is at the time.
|
||||||
|
|
||||||
### From `next`
|
### From `next`
|
||||||
|
|
||||||
|
@ -44,15 +44,14 @@ libgit2 uses OpenSSL and LibSSH2 for performing encrypted network connections. F
|
||||||
Running the tests
|
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
|
make test
|
||||||
|
|
||||||
Alternatively, if you want to pass arguments to `go test`, you can use the script that sets it all up
|
Alternatively, you can build the library manually first and then run the tests
|
||||||
|
|
||||||
./script/with-static.sh go test -v
|
./script/build-libgit2-static.sh
|
||||||
|
go test -v
|
||||||
which will run the specified arguments with the correct environment variables.
|
|
||||||
|
|
||||||
License
|
License
|
||||||
-------
|
-------
|
||||||
|
|
76
blob.go
76
blob.go
|
@ -4,15 +4,13 @@ package git
|
||||||
#include <git2.h>
|
#include <git2.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
extern int _go_git_blob_create_fromchunks(git_oid *id,
|
int _go_git_writestream_write(git_writestream *stream, const char *buffer, size_t len);
|
||||||
git_repository *repo,
|
void _go_git_writestream_free(git_writestream *stream);
|
||||||
const char *hintpath,
|
|
||||||
void *payload);
|
|
||||||
|
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
@ -87,27 +85,71 @@ func blobChunkCb(buffer *C.char, maxLen C.size_t, handle unsafe.Pointer) int {
|
||||||
return len(goBuf)
|
return len(goBuf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *Repository) CreateBlobFromChunks(hintPath string, callback BlobChunkCallback) (*Oid, error) {
|
func (repo *Repository) CreateFromStream(hintPath string) (*BlobWriteStream, error) {
|
||||||
runtime.LockOSThread()
|
|
||||||
defer runtime.UnlockOSThread()
|
|
||||||
|
|
||||||
var chintPath *C.char = nil
|
var chintPath *C.char = nil
|
||||||
|
var stream *C.git_writestream
|
||||||
|
|
||||||
if len(hintPath) > 0 {
|
if len(hintPath) > 0 {
|
||||||
chintPath = C.CString(hintPath)
|
chintPath = C.CString(hintPath)
|
||||||
defer C.free(unsafe.Pointer(chintPath))
|
defer C.free(unsafe.Pointer(chintPath))
|
||||||
}
|
}
|
||||||
oid := C.git_oid{}
|
|
||||||
|
|
||||||
payload := &BlobCallbackData{Callback: callback}
|
runtime.LockOSThread()
|
||||||
handle := pointerHandles.Track(payload)
|
defer runtime.UnlockOSThread()
|
||||||
defer pointerHandles.Untrack(handle)
|
|
||||||
|
|
||||||
ecode := C._go_git_blob_create_fromchunks(&oid, repo.ptr, chintPath, handle)
|
ecode := C.git_blob_create_fromstream(&stream, repo.ptr, chintPath)
|
||||||
if payload.Error != nil {
|
|
||||||
return nil, payload.Error
|
|
||||||
}
|
|
||||||
if ecode < 0 {
|
if ecode < 0 {
|
||||||
return nil, MakeGitError(ecode)
|
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)
|
||||||
|
|
||||||
|
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
|
return newOidFromC(&oid), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,10 @@ func (c Commit) Message() string {
|
||||||
return C.GoString(C.git_commit_message(c.cast_ptr))
|
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 {
|
func (c Commit) Summary() string {
|
||||||
return C.GoString(C.git_commit_summary(c.cast_ptr))
|
return C.GoString(C.git_commit_summary(c.cast_ptr))
|
||||||
}
|
}
|
||||||
|
|
4
diff.go
4
diff.go
|
@ -20,6 +20,7 @@ const (
|
||||||
DiffFlagBinary DiffFlag = C.GIT_DIFF_FLAG_BINARY
|
DiffFlagBinary DiffFlag = C.GIT_DIFF_FLAG_BINARY
|
||||||
DiffFlagNotBinary DiffFlag = C.GIT_DIFF_FLAG_NOT_BINARY
|
DiffFlagNotBinary DiffFlag = C.GIT_DIFF_FLAG_NOT_BINARY
|
||||||
DiffFlagValidOid DiffFlag = C.GIT_DIFF_FLAG_VALID_ID
|
DiffFlagValidOid DiffFlag = C.GIT_DIFF_FLAG_VALID_ID
|
||||||
|
DiffFlagExists DiffFlag = C.GIT_DIFF_FLAG_EXISTS
|
||||||
)
|
)
|
||||||
|
|
||||||
type Delta int
|
type Delta int
|
||||||
|
@ -34,6 +35,8 @@ const (
|
||||||
DeltaIgnored Delta = C.GIT_DELTA_IGNORED
|
DeltaIgnored Delta = C.GIT_DELTA_IGNORED
|
||||||
DeltaUntracked Delta = C.GIT_DELTA_UNTRACKED
|
DeltaUntracked Delta = C.GIT_DELTA_UNTRACKED
|
||||||
DeltaTypeChange Delta = C.GIT_DELTA_TYPECHANGE
|
DeltaTypeChange Delta = C.GIT_DELTA_TYPECHANGE
|
||||||
|
DeltaUnreadable Delta = C.GIT_DELTA_UNREADABLE
|
||||||
|
DeltaConflicted Delta = C.GIT_DELTA_CONFLICTED
|
||||||
)
|
)
|
||||||
|
|
||||||
type DiffLineType int
|
type DiffLineType int
|
||||||
|
@ -399,6 +402,7 @@ const (
|
||||||
DiffIgnoreFilemode DiffOptionsFlag = C.GIT_DIFF_IGNORE_FILEMODE
|
DiffIgnoreFilemode DiffOptionsFlag = C.GIT_DIFF_IGNORE_FILEMODE
|
||||||
DiffIgnoreSubmodules DiffOptionsFlag = C.GIT_DIFF_IGNORE_SUBMODULES
|
DiffIgnoreSubmodules DiffOptionsFlag = C.GIT_DIFF_IGNORE_SUBMODULES
|
||||||
DiffIgnoreCase DiffOptionsFlag = C.GIT_DIFF_IGNORE_CASE
|
DiffIgnoreCase DiffOptionsFlag = C.GIT_DIFF_IGNORE_CASE
|
||||||
|
DiffIncludeCaseChange DiffOptionsFlag = C.GIT_DIFF_INCLUDE_CASECHANGE
|
||||||
|
|
||||||
DiffDisablePathspecMatch DiffOptionsFlag = C.GIT_DIFF_DISABLE_PATHSPEC_MATCH
|
DiffDisablePathspecMatch DiffOptionsFlag = C.GIT_DIFF_DISABLE_PATHSPEC_MATCH
|
||||||
DiffSkipBinaryCheck DiffOptionsFlag = C.GIT_DIFF_SKIP_BINARY_CHECK
|
DiffSkipBinaryCheck DiffOptionsFlag = C.GIT_DIFF_SKIP_BINARY_CHECK
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
18
git.go
18
git.go
|
@ -1,12 +1,15 @@
|
||||||
package git
|
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.h>
|
||||||
#include <git2/sys/openssl.h>
|
#include <git2/sys/openssl.h>
|
||||||
#cgo pkg-config: libgit2
|
|
||||||
|
|
||||||
#if LIBGIT2_VER_MAJOR != 0 || LIBGIT2_VER_MINOR != 24
|
#if LIBGIT2_VER_MAJOR != 0 || LIBGIT2_VER_MINOR != 25
|
||||||
# error "Invalid libgit2 version; this git2go supports libgit2 v0.24"
|
# error "Invalid libgit2 version; this git2go supports libgit2 v0.25"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
@ -125,6 +128,15 @@ func init() {
|
||||||
|
|
||||||
C.git_libgit2_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
|
// This is not something we should be doing, as we may be
|
||||||
// stomping all over someone else's setup. The user should do
|
// stomping all over someone else's setup. The user should do
|
||||||
// this themselves or use some binding/wrapper which does it
|
// this themselves or use some binding/wrapper which does it
|
||||||
|
|
|
@ -93,6 +93,8 @@ func updateReadme(t *testing.T, repo *Repository, content string) (*Oid, *Oid) {
|
||||||
checkFatal(t, err)
|
checkFatal(t, err)
|
||||||
err = idx.AddByPath("README")
|
err = idx.AddByPath("README")
|
||||||
checkFatal(t, err)
|
checkFatal(t, err)
|
||||||
|
err = idx.Write()
|
||||||
|
checkFatal(t, err)
|
||||||
treeId, err := idx.WriteTree()
|
treeId, err := idx.WriteTree()
|
||||||
checkFatal(t, err)
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
|
4
odb.go
4
odb.go
|
@ -226,6 +226,10 @@ func (object *OdbObject) Len() (len uint64) {
|
||||||
return uint64(C.git_odb_object_size(object.ptr))
|
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) {
|
func (object *OdbObject) Data() (data []byte) {
|
||||||
var c_blob unsafe.Pointer = C.git_odb_object_data(object.ptr)
|
var c_blob unsafe.Pointer = C.git_odb_object_data(object.ptr)
|
||||||
var blob []byte
|
var blob []byte
|
||||||
|
|
55
remote.go
55
remote.go
|
@ -117,6 +117,30 @@ type FetchOptions struct {
|
||||||
Headers []string
|
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 {
|
type Remote struct {
|
||||||
ptr *C.git_remote
|
ptr *C.git_remote
|
||||||
callbacks RemoteCallbacks
|
callbacks RemoteCallbacks
|
||||||
|
@ -320,6 +344,20 @@ func pushUpdateReferenceCallback(refname, status *C.char, data unsafe.Pointer) i
|
||||||
return int(callbacks.PushUpdateReferenceCallback(C.GoString(refname), C.GoString(status)))
|
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 {
|
func RemoteIsValidName(name string) bool {
|
||||||
cname := C.CString(name)
|
cname := C.CString(name)
|
||||||
defer C.free(unsafe.Pointer(cname))
|
defer C.free(unsafe.Pointer(cname))
|
||||||
|
@ -674,12 +712,12 @@ func (o *Remote) Fetch(refspecs []string, opts *FetchOptions, msg string) error
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Remote) ConnectFetch(callbacks *RemoteCallbacks, headers []string) error {
|
func (o *Remote) ConnectFetch(callbacks *RemoteCallbacks, proxyOpts *ProxyOptions, headers []string) error {
|
||||||
return o.Connect(ConnectDirectionFetch, callbacks, headers)
|
return o.Connect(ConnectDirectionFetch, callbacks, proxyOpts, headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Remote) ConnectPush(callbacks *RemoteCallbacks, headers []string) error {
|
func (o *Remote) ConnectPush(callbacks *RemoteCallbacks, proxyOpts *ProxyOptions, headers []string) error {
|
||||||
return o.Connect(ConnectDirectionPush, callbacks, headers)
|
return o.Connect(ConnectDirectionPush, callbacks, proxyOpts, headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect opens a connection to a remote.
|
// Connect opens a connection to a remote.
|
||||||
|
@ -689,19 +727,24 @@ func (o *Remote) ConnectPush(callbacks *RemoteCallbacks, headers []string) error
|
||||||
// starts up a specific binary which can only do the one or the other.
|
// starts up a specific binary which can only do the one or the other.
|
||||||
//
|
//
|
||||||
// 'headers' are extra HTTP headers to use in this connection.
|
// '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
|
var ccallbacks C.git_remote_callbacks
|
||||||
populateRemoteCallbacks(&ccallbacks, callbacks)
|
populateRemoteCallbacks(&ccallbacks, callbacks)
|
||||||
|
|
||||||
|
var cproxy C.git_proxy_options
|
||||||
|
populateProxyOptions(&cproxy, proxyOpts)
|
||||||
|
defer freeProxyOptions(&cproxy)
|
||||||
|
|
||||||
cheaders := C.git_strarray{}
|
cheaders := C.git_strarray{}
|
||||||
cheaders.count = C.size_t(len(headers))
|
cheaders.count = C.size_t(len(headers))
|
||||||
cheaders.strings = makeCStringsFromStrings(headers)
|
cheaders.strings = makeCStringsFromStrings(headers)
|
||||||
defer freeStrarray(&cheaders)
|
defer freeStrarray(&cheaders)
|
||||||
|
|
||||||
|
|
||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
defer runtime.UnlockOSThread()
|
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 MakeGitError(ret)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -61,7 +61,7 @@ func TestRemoteConnect(t *testing.T) {
|
||||||
remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
|
remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
|
||||||
checkFatal(t, err)
|
checkFatal(t, err)
|
||||||
|
|
||||||
err = remote.ConnectFetch(nil, nil)
|
err = remote.ConnectFetch(nil, nil, nil)
|
||||||
checkFatal(t, err)
|
checkFatal(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ func TestRemoteLs(t *testing.T) {
|
||||||
remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
|
remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
|
||||||
checkFatal(t, err)
|
checkFatal(t, err)
|
||||||
|
|
||||||
err = remote.ConnectFetch(nil, nil)
|
err = remote.ConnectFetch(nil, nil, nil)
|
||||||
checkFatal(t, err)
|
checkFatal(t, err)
|
||||||
|
|
||||||
heads, err := remote.Ls()
|
heads, err := remote.Ls()
|
||||||
|
@ -92,7 +92,7 @@ func TestRemoteLsFiltering(t *testing.T) {
|
||||||
remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
|
remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
|
||||||
checkFatal(t, err)
|
checkFatal(t, err)
|
||||||
|
|
||||||
err = remote.ConnectFetch(nil, nil)
|
err = remote.ConnectFetch(nil, nil, nil)
|
||||||
checkFatal(t, err)
|
checkFatal(t, err)
|
||||||
|
|
||||||
heads, err := remote.Ls("master")
|
heads, err := remote.Ls("master")
|
||||||
|
@ -173,7 +173,7 @@ func TestRemotePrune(t *testing.T) {
|
||||||
rr, err := repo.Remotes.Lookup("origin")
|
rr, err := repo.Remotes.Lookup("origin")
|
||||||
checkFatal(t, err)
|
checkFatal(t, err)
|
||||||
|
|
||||||
err = rr.ConnectFetch(nil, nil)
|
err = rr.ConnectFetch(nil, nil, nil)
|
||||||
checkFatal(t, err)
|
checkFatal(t, err)
|
||||||
|
|
||||||
err = rr.Prune(nil)
|
err = rr.Prune(nil)
|
||||||
|
|
|
@ -30,6 +30,9 @@ type Repository struct {
|
||||||
// Tags represents the collection of tags and can be used to create,
|
// Tags represents the collection of tags and can be used to create,
|
||||||
// list, iterate and remove tags in this repository.
|
// list, iterate and remove tags in this repository.
|
||||||
Tags TagsCollection
|
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 {
|
func newRepositoryFromC(ptr *C.git_repository) *Repository {
|
||||||
|
@ -40,6 +43,7 @@ func newRepositoryFromC(ptr *C.git_repository) *Repository {
|
||||||
repo.References.repo = repo
|
repo.References.repo = repo
|
||||||
repo.Notes.repo = repo
|
repo.Notes.repo = repo
|
||||||
repo.Tags.repo = repo
|
repo.Tags.repo = repo
|
||||||
|
repo.Stashes.repo = repo
|
||||||
|
|
||||||
runtime.SetFinalizer(repo, (*Repository).Free)
|
runtime.SetFinalizer(repo, (*Repository).Free)
|
||||||
|
|
||||||
|
@ -264,6 +268,40 @@ func (v *Repository) IsHeadDetached() (bool, error) {
|
||||||
return ret != 0, nil
|
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) {
|
func (v *Repository) Walk() (*RevWalk, error) {
|
||||||
|
|
||||||
var walkPtr *C.git_revwalk
|
var walkPtr *C.git_revwalk
|
||||||
|
|
|
@ -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"
|
|
||||||
|
|
||||||
$@
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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: "random@hacker.com",
|
||||||
|
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: "random@hacker.com",
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit ee89941fa2b861332defb5e3a8ce12c23c496ed7
|
36
wrapper.c
36
wrapper.c
|
@ -114,19 +114,6 @@ void _go_git_setup_callbacks(git_remote_callbacks *callbacks) {
|
||||||
callbacks->push_update_reference = (push_update_reference_cb) pushUpdateReferenceCallback;
|
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) {
|
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;
|
git_index_matched_path_cb cb = callback ? (git_index_matched_path_cb) &indexMatchedPathCallback : NULL;
|
||||||
return git_index_add_all(index, pathspec, flags, cb, callback);
|
return git_index_add_all(index, pathspec, flags, cb, callback);
|
||||||
|
@ -170,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);
|
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 */
|
/* EOF */
|
||||||
|
|
Loading…
Reference in New Issue