Skip to content

Wrap git_stash_* #695

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 30, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/working-copy.rst
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,11 @@ Lower level API:
.. automethod:: pygit2.Repository.checkout_head
.. automethod:: pygit2.Repository.checkout_tree
.. automethod:: pygit2.Repository.checkout_index

Stash
====================

.. automethod:: pygit2.Repository.stash
.. automethod:: pygit2.Repository.stash_apply
.. automethod:: pygit2.Repository.stash_drop
.. automethod:: pygit2.Repository.stash_pop
50 changes: 50 additions & 0 deletions pygit2/decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,56 @@ int git_merge_trees(git_index **out, git_repository *repo, const git_tree *ances
int git_merge_file_from_index(git_merge_file_result *out, git_repository *repo, const git_index_entry *ancestor, const git_index_entry *ours, const git_index_entry *theirs, const git_merge_file_options *opts);
void git_merge_file_result_free(git_merge_file_result *result);

/*
* git_stash
*/

typedef int (*git_stash_cb)(
size_t index, const char* message, const git_oid *stash_id, void *payload);

typedef enum {
GIT_STASH_APPLY_PROGRESS_NONE = 0,
GIT_STASH_APPLY_PROGRESS_LOADING_STASH = 1,
GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX = 2,
GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED = 3,
GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED = 4,
GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED = 5,
GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED = 6,
GIT_STASH_APPLY_PROGRESS_DONE = 7,
} git_stash_apply_progress_t;

typedef int (*git_stash_apply_progress_cb)(
git_stash_apply_progress_t progress, void *payload);

typedef enum {
GIT_STASH_DEFAULT = 0,
GIT_STASH_KEEP_INDEX = 1,
GIT_STASH_INCLUDE_UNTRACKED = 2,
GIT_STASH_INCLUDE_IGNORED = 4,
} git_stash_flags;

typedef enum {
GIT_STASH_APPLY_DEFAULT = 0,
GIT_STASH_APPLY_REINSTATE_INDEX = 1,
} git_stash_apply_flags;

typedef struct git_stash_apply_options {
unsigned int version;
git_stash_apply_flags flags;
git_checkout_options checkout_options;
git_stash_apply_progress_cb progress_cb;
void *progress_payload;
} git_stash_apply_options;

#define GIT_STASH_APPLY_OPTIONS_VERSION ...

int git_stash_save(git_oid *out, git_repository *repo, const git_signature *stasher, const char *message, uint32_t flags);
int git_stash_apply_init_options(git_stash_apply_options *opts, unsigned int version);
int git_stash_apply(git_repository *repo, size_t index, const git_stash_apply_options *options);
int git_stash_foreach(git_repository *repo, git_stash_cb callback, void *payload);
int git_stash_drop(git_repository *repo, size_t index);
int git_stash_pop(git_repository *repo, size_t index, const git_stash_apply_options *options);

/*
* Describe
*/
Expand Down
92 changes: 91 additions & 1 deletion pygit2/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
from _pygit2 import Oid, GIT_OID_HEXSZ, GIT_OID_MINPREFIXLEN
from _pygit2 import GIT_CHECKOUT_SAFE, GIT_CHECKOUT_RECREATE_MISSING, GIT_DIFF_NORMAL
from _pygit2 import GIT_FILEMODE_LINK
from _pygit2 import Reference, Tree, Commit, Blob
from _pygit2 import Reference, Tree, Commit, Blob, Signature

from .config import Config
from .errors import check_error
Expand Down Expand Up @@ -735,6 +735,96 @@ def describe(self, committish=None, max_candidates_tags=None,
C.git_buf_free(buf)
finally:
C.git_describe_result_free(result[0])
#
# Stash
#
def stash(self, stasher, message=None, keep_index=False,
include_untracked=False, include_ignored=False):
"""Save changes to the working directory to the stash.

:param Signature stasher: The identity of the person doing the stashing.
:param str message: An optional description of stashed state.
:param bool keep_index: Leave changes already added to the index
in the working directory.
:param bool include_untracked: Also stash untracked files.
:param bool include_ignored: Also stash ignored files.

:returns: The Oid of the stash merge commit.
:rtype: Oid

Example::

>>> repo = pygit2.Repsitory('.')
>>> repo.stash(repo.default_signature(), 'WIP: stashing')
"""

if message is not None:
stash_msg = ffi.new('char[]', to_bytes(message)) if message else ffi.NULL
else:
stash_msg = ffi.NULL

flags = 0
flags |= keep_index * C.GIT_STASH_KEEP_INDEX
flags |= include_untracked * C.GIT_STASH_INCLUDE_UNTRACKED
flags |= include_ignored * C.GIT_STASH_INCLUDE_IGNORED

stasher_cptr = ffi.new('git_signature **')
ffi.buffer(stasher_cptr)[:] = stasher._pointer[:]

coid = ffi.new('git_oid *')
err = C.git_stash_save(coid, self._repo, stasher_cptr[0], stash_msg, flags)
check_error(err)

return Oid(raw=bytes(ffi.buffer(coid)[:]))

@staticmethod
def _stash_args_to_options(reinstate_index=False, **kwargs):
stash_opts = ffi.new('git_stash_apply_options *')
check_error(C.git_stash_apply_init_options(stash_opts, 1))

flags = reinstate_index * C.GIT_STASH_APPLY_REINSTATE_INDEX
stash_opts.flags = flags

copts, refs = Repository._checkout_args_to_options(**kwargs)
stash_opts.checkout_options = copts[0]

return stash_opts

def stash_apply(self, index=0, **kwargs):
"""Apply a stashed state in the stash list to the working directory.

:param int index: The position within the stash list of the stash to apply.
0 is the most recent stash.
:param bool reinstate_index: Try to reinstate stashed changes to the index.

The checkout options may be customized using the same arguments taken by
Repository.checkout().

Example::

>>> repo = pygit2.Repsitory('.')
>>> repo.stash(repo.default_signature(), 'WIP: stashing')
>>> repo.stash_apply(strategy=GIT_CHECKOUT_ALLOW_CONFLICTS)
"""
stash_opts = Repository._stash_args_to_options(**kwargs)
check_error(C.git_stash_apply(self._repo, index, stash_opts))

def stash_drop(self, index=0):
"""Remove a stashed state from the stash list.

:param int index: The position within the stash list of the stash to remove.
0 is the most recent stash.
"""
check_error(C.git_stash_drop(self._repo, index))


def stash_pop(self, index=0, **kwargs):
"""Apply a stashed state and remove it from the stash list.

For arguments, see Repository.stash_apply().
"""
stash_opts = Repository._stash_args_to_options(**kwargs)
check_error(C.git_stash_pop(self._repo, index, stash_opts))

#
# Utility for writing a tree into an archive
Expand Down
8 changes: 8 additions & 0 deletions src/pygit2.c
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,14 @@ moduleinit(PyObject* m)
ADD_CONSTANT_INT(m, GIT_DESCRIBE_TAGS);
ADD_CONSTANT_INT(m, GIT_DESCRIBE_ALL);

/* Stash */
ADD_CONSTANT_INT(m, GIT_STASH_DEFAULT);
ADD_CONSTANT_INT(m, GIT_STASH_KEEP_INDEX);
ADD_CONSTANT_INT(m, GIT_STASH_INCLUDE_UNTRACKED);
ADD_CONSTANT_INT(m, GIT_STASH_INCLUDE_IGNORED);
ADD_CONSTANT_INT(m, GIT_STASH_APPLY_DEFAULT);
ADD_CONSTANT_INT(m, GIT_STASH_APPLY_REINSTATE_INDEX);

/* Global initialization of libgit2 */
git_libgit2_init();

Expand Down
4 changes: 2 additions & 2 deletions src/signature.c
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,10 @@ Signature_dealloc(Signature *self)

PyDoc_STRVAR(Signature__pointer__doc__, "Get the signature's pointer. For internal use only.");
PyObject *
Signature__pointer__get__(Repository *self)
Signature__pointer__get__(Signature *self)
{
/* Bytes means a raw buffer */
return PyBytes_FromStringAndSize((char *) &self->repo, sizeof(git_repository *));
return PyBytes_FromStringAndSize((char *) &self->signature, sizeof(git_signature *));
}

PyDoc_STRVAR(Signature__encoding__doc__, "Encoding.");
Expand Down
13 changes: 13 additions & 0 deletions test/test_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,19 @@ def test_reset_mixed(self):
self.assertTrue("hola mundo\n" in diff.patch)
self.assertTrue("bonjour le monde\n" in diff.patch)

def test_stash(self):
# some changes to working dir
with open(os.path.join(self.repo.workdir, 'hello.txt'), 'w') as f:
f.write('new content')

sig = pygit2.Signature('Stasher', '[email protected]')
self.repo.stash(sig, include_untracked=True)
self.assertFalse('hello.txt' in self.repo.status())
self.repo.stash_apply()
self.assertTrue('hello.txt' in self.repo.status())
self.repo.stash_drop()
self.assertRaises(KeyError, self.repo.stash_pop)

class RepositorySignatureTest(utils.RepoTestCase):

def test_default_signature(self):
Expand Down