Merge remote-tracking branch 'upstream/master' into next
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/blob.go b/blob.go
index 8b3e94f..73a4a19 100644
--- a/blob.go
+++ b/blob.go
@@ -4,15 +4,13 @@
#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"
)
@@ -87,27 +85,71 @@
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._go_git_blob_create_fromchunks(&oid, repo.ptr, chintPath, handle)
- if payload.Error != nil {
- return nil, payload.Error
- }
+ 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)
+
+ 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/diff.go b/diff.go
index 8ae0512..e8d5007 100644
--- a/diff.go
+++ b/diff.go
@@ -20,6 +20,7 @@
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 @@
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
@@ -399,6 +402,7 @@
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/git_test.go b/git_test.go
index bdb6837..807dcc2 100644
--- a/git_test.go
+++ b/git_test.go
@@ -58,6 +58,8 @@
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 @@
checkFatal(t, err)
err = idx.AddByPath("README")
checkFatal(t, err)
+ err = idx.Write()
+ checkFatal(t, err)
treeId, err := idx.WriteTree()
checkFatal(t, err)
diff --git a/odb.go b/odb.go
index 9c6baa3..28bfad6 100644
--- a/odb.go
+++ b/odb.go
@@ -226,6 +226,10 @@
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/remote.go b/remote.go
index 8a57280..bacdfa3 100644
--- a/remote.go
+++ b/remote.go
@@ -117,6 +117,30 @@
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
@@ -320,6 +344,20 @@
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))
@@ -654,12 +692,12 @@
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,19 +707,24 @@
// 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
diff --git a/remote_test.go b/remote_test.go
index 3d00640..8b20fd2 100644
--- a/remote_test.go
+++ b/remote_test.go
@@ -61,7 +61,7 @@
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)
}
@@ -73,7 +73,7 @@
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()
@@ -92,7 +92,7 @@
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")
@@ -173,7 +173,7 @@
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 @@
// 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 @@
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 @@
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/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: "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
+}
diff --git a/vendor/libgit2 b/vendor/libgit2
new file mode 160000
index 0000000..73dab76
--- /dev/null
+++ b/vendor/libgit2
@@ -0,0 +1 @@
+Subproject commit 73dab7692e780e1df96093a54854795428eb66b4
diff --git a/wrapper.c b/wrapper.c
index a0688c0..7407611 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -108,19 +108,6 @@
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 +151,27 @@
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 */