blob: 8743da89e9d69d2deb4189aed008c2e30983e8a0 [file] [log] [blame]
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))
runtime.KeepAlive(c)
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)
runtime.KeepAlive(c)
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)
runtime.KeepAlive(c)
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))
runtime.KeepAlive(c)
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)
runtime.KeepAlive(c)
if ret == C.GIT_EUSER {
return progressData.Error
}
if ret < 0 {
return MakeGitError(ret)
}
return nil
}