From 19be4b6aa4a8e0b06f9f7bbe0cfe071ee40e35f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 16 May 2014 03:18:14 +0200 Subject: [PATCH 1/6] clone: wrap clone_into() This allows the user to prepare the repository and remote with whichever custom settings they want before performing the "clone" proper. --- pygit2/__init__.py | 23 +++++++++++++++++++++++ pygit2/decl.h | 1 + test/test_repository.py | 9 ++++++++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/pygit2/__init__.py b/pygit2/__init__.py index 62954f6..9aa0c42 100644 --- a/pygit2/__init__.py +++ b/pygit2/__init__.py @@ -129,4 +129,27 @@ def clone_repository( return Repository(path) +def clone_into(repo, remote, branch=None): + """Clone into an empty repository from the specified remote + + :param Repository repo: The empty repository into which to clone + + :param Remote remote: The remote from which to clone + + :param str branch: Branch to checkout after the clone. Pass None + to use the remotes's default branch. + + This allows you specify arbitrary repository and remote configurations + before performing the clone step itself. E.g. you can replicate git-clone's + '--mirror' option by setting a refspec of '+refs/*:refs/*', 'core.mirror' to true + and calling this function. + """ + + err = C.git_clone_into(repo._repo, remote._remote, ffi.NULL, to_str(branch)) + + if remote._stored_exception: + raise remote._stored_exception + + check_error(err) + settings = Settings() diff --git a/pygit2/decl.h b/pygit2/decl.h index 29d4e1c..9f82b1c 100644 --- a/pygit2/decl.h +++ b/pygit2/decl.h @@ -216,6 +216,7 @@ int git_clone(git_repository **out, const char *local_path, const git_clone_options *options); +int git_clone_into(git_repository *repo, git_remote *remote, const git_checkout_opts *co_opts, const char *branch); typedef enum { GIT_CONFIG_LEVEL_SYSTEM = 1, diff --git a/test/test_repository.py b/test/test_repository.py index 71214cc..e70fe97 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -40,7 +40,7 @@ from os.path import join, realpath # Import from pygit2 from pygit2 import GIT_OBJ_ANY, GIT_OBJ_BLOB, GIT_OBJ_COMMIT -from pygit2 import init_repository, clone_repository, discover_repository +from pygit2 import init_repository, clone_repository, clone_into, discover_repository from pygit2 import Oid, Reference, hashfile import pygit2 from . import utils @@ -461,6 +461,13 @@ class CloneRepositoryTest(utils.NoRepoTestCase): self.assertFalse(repo.is_empty) self.assertEqual(repo.remotes[0].name, "custom_remote") + def test_clone_into(self): + repo_path = "./test/data/testrepo.git/" + repo = init_repository(os.path.join(self._temp_dir, "clone-into")) + remote = repo.create_remote("origin", 'file://' + os.path.realpath(repo_path)) + clone_into(repo, remote) + self.assertTrue('refs/remotes/origin/master' in repo.listall_references()) + def test_clone_with_credentials(self): credentials = pygit2.UserPass("libgit2", "libgit2") repo = clone_repository( From 97c0e476a31ccc7fc0d9a8c238892f99f1299582 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 27 May 2014 18:06:28 +0200 Subject: [PATCH 2/6] Index: add failing tests for a standalone Index The index can exist purely as a data structure. Let's test that so we make sure we support that. --- test/test_index.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/test/test_index.py b/test/test_index.py index 7bed86b..eb7fa05 100644 --- a/test/test_index.py +++ b/test/test_index.py @@ -34,7 +34,7 @@ import unittest import tempfile import pygit2 -from pygit2 import Repository +from pygit2 import Repository, Index from . import utils @@ -206,5 +206,19 @@ class IndexEntryTest(utils.RepoTestCase): tree_id = index.write_tree() self.assertEqual('60e769e57ae1d6a2ab75d8d253139e6260e1f912', str(tree_id)) +class StandaloneIndexTest(utils.RepoTestCase): + + def test_create_empty(self): + index = Index() + + def test_create_empty_read_tree_as_string(self): + index = Index() + # no repo associated, so we don't know where to read from + self.assertRaises(TypeError, index, 'read_tree', 'fd937514cb799514d4b81bb24c5fcfeb6472b245') + + def test_create_empty_read_tree(self): + index = Index() + index.read_tree(self.repo['fd937514cb799514d4b81bb24c5fcfeb6472b245']) + if __name__ == '__main__': unittest.main() From f69a57a82a05c161511cb4f651aa231dd6e05ad7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 27 May 2014 18:10:06 +0200 Subject: [PATCH 3/6] Index: make the file optional There is no obligation for an index to exist as a file at all. --- src/index.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/index.c b/src/index.c index 7269749..0cb8207 100644 --- a/src/index.c +++ b/src/index.c @@ -45,7 +45,7 @@ extern PyTypeObject RepositoryType; int Index_init(Index *self, PyObject *args, PyObject *kwds) { - char *path; + char *path = NULL; int err; if (kwds && PyDict_Size(kwds) > 0) { @@ -53,9 +53,10 @@ Index_init(Index *self, PyObject *args, PyObject *kwds) return -1; } - if (!PyArg_ParseTuple(args, "s", &path)) + if (!PyArg_ParseTuple(args, "|s", &path)) return -1; + self->repo = NULL; err = git_index_open(&self->index, path); if (err < 0) { Error_set_str(err, path); From 9e91a390cc39625ebc72d609e1be6d04c71fab25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 27 May 2014 18:24:53 +0200 Subject: [PATCH 4/6] Index: accept a tree for read_tree() An index may not have an associated repository, so giving it an id in that case is useless. Raise an error in that case and accept a Tree object to make the function useful then. --- docs/working-copy.rst | 8 ++++++++ src/index.c | 38 +++++++++++++++++++++++++++++--------- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/docs/working-copy.rst b/docs/working-copy.rst index 76bcada..88762b7 100644 --- a/docs/working-copy.rst +++ b/docs/working-copy.rst @@ -26,6 +26,14 @@ Custom entries:: >>> entry = pygit2.IndexEntry('README.md', blob_id, blob_filemode) >>> repo.index.add(entry) +The index fulfills a dual role as the in-memory representation of the +index file and data structure which represents a flat list of a +tree. You can use it independently of the index file, e.g. + + >>> index = pygit2.Index() + >>> entry = pygit2.IndexEntry('README.md', blob_id, blob_filemode) + >>> index.add(entry) + The Index type ==================== diff --git a/src/index.c b/src/index.c index 0cb8207..6a855f4 100644 --- a/src/index.c +++ b/src/index.c @@ -426,26 +426,46 @@ Index_remove(Index *self, PyObject *args) PyDoc_STRVAR(Index_read_tree__doc__, "read_tree(tree)\n" "\n" - "Update the index file from the tree identified by the given oid."); + "Update the index file from the specified tree. The tree can be a Tree object or an Oid.\n" + "Using an Oid is only possible if this index is associated with a repository"); PyObject * Index_read_tree(Index *self, PyObject *value) { git_oid oid; - git_tree *tree; - int err; + git_tree *tree = NULL; + int err, need_free = 0; size_t len; len = py_oid_to_git_oid(value, &oid); - if (len == 0) - return NULL; + if (len == 0) { + Tree *py_tree; + if (!PyObject_TypeCheck(value, &TreeType)) { + return NULL; + } - err = git_tree_lookup_prefix(&tree, self->repo->repo, &oid, len); - if (err < 0) - return Error_set(err); + PyErr_Clear(); + py_tree = (Tree *) value; + tree = py_tree->tree; + } + + /* + * if the user passed in an id but we're not associated with a + * repo, we can't do anything + */ + if (tree == NULL && self->repo == NULL) { + PyErr_SetString(PyExc_TypeError, "id given but no associated repository"); + return NULL; + } else if (tree == NULL) { + need_free = 1; + err = git_tree_lookup_prefix(&tree, self->repo->repo, &oid, len); + if (err < 0) + return Error_set(err); + } err = git_index_read_tree(self->index, tree); - git_tree_free(tree); + if (need_free) + git_tree_free(tree); if (err < 0) return Error_set(err); From 6cf06ba9fe89e0673c0d8c96d1fc8cbae738e852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Mon, 2 Jun 2014 18:50:55 +0200 Subject: [PATCH 5/6] Rewrite init_repository using cffi --- pygit2/__init__.py | 6 ++++-- pygit2/decl.h | 9 ++++++++- src/pygit2.c | 32 -------------------------------- 3 files changed, 12 insertions(+), 35 deletions(-) diff --git a/pygit2/__init__.py b/pygit2/__init__.py index 9aa0c42..a067a3b 100644 --- a/pygit2/__init__.py +++ b/pygit2/__init__.py @@ -29,7 +29,6 @@ from __future__ import absolute_import # Low level API -import _pygit2 from _pygit2 import * # High level API @@ -49,7 +48,10 @@ def init_repository(path, bare=False): If *bare* is True the repository will be bare, i.e. it will not have a working copy. """ - _pygit2.init_repository(path, bare) + crepository = ffi.new('git_repository **') + err = C.git_repository_init(crepository, to_str(path), bare) + check_error(err) + return Repository(path) diff --git a/pygit2/decl.h b/pygit2/decl.h index 9f82b1c..1fb28e7 100644 --- a/pygit2/decl.h +++ b/pygit2/decl.h @@ -254,7 +254,10 @@ int git_config_iterator_new(git_config_iterator **out, const git_config *cfg); int git_config_next(git_config_entry **entry, git_config_iterator *iter); void git_config_iterator_free(git_config_iterator *iter); -int git_config_multivar_iterator_new(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp); +int git_config_multivar_iterator_new(git_config_iterator **out, + const git_config *cfg, const char *name, + const char *regexp); + int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value); int git_config_new(git_config **out); @@ -262,3 +265,7 @@ int git_config_open_ondisk(git_config **out, const char *path); int git_config_find_system(char *out, size_t length); int git_config_find_global(char *out, size_t length); int git_config_find_xdg(char *out, size_t length); + + +int git_repository_init(git_repository **out, const char *path, + unsigned is_bare); diff --git a/src/pygit2.c b/src/pygit2.c index 2ed0808..4808f74 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -82,37 +82,6 @@ extern PyTypeObject MergeResultType; -PyDoc_STRVAR(init_repository__doc__, - "init_repository(path, bare)\n" - "\n" - "Creates a new Git repository in the given path.\n" - "\n" - "Arguments:\n" - "\n" - "path\n" - " Path where to create the repository.\n" - "\n" - "bare\n" - " Whether the repository will be bare or not.\n"); - -PyObject * -init_repository(PyObject *self, PyObject *args) { - git_repository *repo; - const char *path; - unsigned int bare; - int err; - - if (!PyArg_ParseTuple(args, "sI", &path, &bare)) - return NULL; - - err = git_repository_init(&repo, path, bare); - if (err < 0) - return Error_set_str(err, path); - - git_repository_free(repo); - Py_RETURN_NONE; -}; - PyDoc_STRVAR(discover_repository__doc__, "discover_repository(path[, across_fs[, ceiling_dirs]]) -> str\n" "\n" @@ -186,7 +155,6 @@ hash(PyObject *self, PyObject *args) PyMethodDef module_methods[] = { - {"init_repository", init_repository, METH_VARARGS, init_repository__doc__}, {"discover_repository", discover_repository, METH_VARARGS, discover_repository__doc__}, {"hashfile", hashfile, METH_VARARGS, hashfile__doc__}, From 95e6593625f2a870c8729bc65537b19f0b5df8aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Tue, 3 Jun 2014 12:52:46 +0200 Subject: [PATCH 6/6] init_repository now wraps git_repository_init_ext Fixes #347 --- pygit2/__init__.py | 46 +++++++++++++++++- pygit2/decl.h | 113 ++++++++++++++++++++++++++++++++++++--------- pygit2/ffi.py | 9 ++-- 3 files changed, 139 insertions(+), 29 deletions(-) diff --git a/pygit2/__init__.py b/pygit2/__init__.py index a067a3b..eed50c8 100644 --- a/pygit2/__init__.py +++ b/pygit2/__init__.py @@ -41,17 +41,59 @@ from .config import Config from .errors import check_error from .ffi import ffi, C, to_str -def init_repository(path, bare=False): +def init_repository(path, bare=False, + flags=C.GIT_REPOSITORY_INIT_MKPATH, + mode=0, + workdir_path=None, + description=None, + template_path=None, + initial_head=None, + origin_url=None): """ Creates a new Git repository in the given *path*. If *bare* is True the repository will be bare, i.e. it will not have a working copy. + + The *flags* may be a combination of: + + - GIT_REPOSITORY_INIT_BARE (overriden by the *bare* parameter) + - GIT_REPOSITORY_INIT_NO_REINIT + - GIT_REPOSITORY_INIT_NO_DOTGIT_DIR + - GIT_REPOSITORY_INIT_MKDIR + - GIT_REPOSITORY_INIT_MKPATH (set by default) + - GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE + + The *mode* parameter may be any of GIT_REPOSITORY_SHARED_UMASK (default), + GIT_REPOSITORY_SHARED_GROUP or GIT_REPOSITORY_INIT_SHARED_ALL, or a custom + value. + + The *workdir_path*, *description*, *template_path*, *initial_head* and + *origin_url* are all strings. + + See libgit2's documentation on git_repository_init_ext for further details. """ + # Pre-process input parameters + if bare: + flags |= C.GIT_REPOSITORY_INIT_BARE + + # Options + options = ffi.new('git_repository_init_options *') + options.version = 1 + options.flags = flags + options.mode = mode + options.workdir_path = to_str(workdir_path) + options.description = to_str(description) + options.template_path = to_str(template_path) + options.initial_head = to_str(initial_head) + options.origin_url = to_str(origin_url) + + # Call crepository = ffi.new('git_repository **') - err = C.git_repository_init(crepository, to_str(path), bare) + err = C.git_repository_init_ext(crepository, to_str(path), options) check_error(err) + # Ok return Repository(path) diff --git a/pygit2/decl.h b/pygit2/decl.h index 1fb28e7..fd1c22e 100644 --- a/pygit2/decl.h +++ b/pygit2/decl.h @@ -5,8 +5,6 @@ typedef ... git_push; typedef ... git_cred; typedef ... git_diff_file; typedef ... git_tree; -typedef ... git_config; -typedef ... git_config_iterator; #define GIT_OID_RAWSZ ... #define GIT_PATH_MAX ... @@ -20,6 +18,7 @@ typedef struct git_strarray { size_t count; } git_strarray; + typedef enum { GIT_OK = 0, GIT_ERROR = -1, @@ -71,11 +70,13 @@ typedef enum { GIT_DIRECTION_PUSH = 1 } git_direction; + typedef enum { - GIT_CREDTYPE_USERPASS_PLAINTEXT = ..., - GIT_CREDTYPE_SSH_KEY = ..., - GIT_CREDTYPE_SSH_CUSTOM = ..., - GIT_CREDTYPE_DEFAULT = ..., + GIT_CREDTYPE_USERPASS_PLAINTEXT, + GIT_CREDTYPE_SSH_KEY, + GIT_CREDTYPE_SSH_CUSTOM, + GIT_CREDTYPE_DEFAULT, + ... } git_credtype_t; typedef struct git_remote_callbacks { @@ -90,10 +91,12 @@ typedef struct git_remote_callbacks { int git_remote_list(git_strarray *out, git_repository *repo); int git_remote_load(git_remote **out, git_repository *repo, const char *name); -int git_remote_create(git_remote **out, - git_repository *repo, - const char *name, - const char *url); +int git_remote_create( + git_remote **out, + git_repository *repo, + const char *name, + const char *url); + const char * git_remote_name(const git_remote *remote); typedef int (*git_remote_rename_problem_cb)(const char *problematic_refspec, void *payload); int git_remote_rename(git_remote *remote, @@ -125,9 +128,10 @@ int git_push_add_refspec(git_push *push, const char *refspec); int git_push_finish(git_push *push); int git_push_unpack_ok(git_push *push); -int git_push_status_foreach(git_push *push, - int (*cb)(const char *ref, const char *msg, void *data), - void *data); +int git_push_status_foreach( + git_push *push, + int (*cb)(const char *ref, const char *msg, void *data), + void *data); int git_push_update_tips(git_push *push); void git_push_free(git_push *push); @@ -155,6 +159,10 @@ int git_cred_ssh_key_new( const char *privatekey, const char *passphrase); +/* + * git_checkout + */ + typedef enum { ... } git_checkout_notify_t; typedef int (*git_checkout_notify_cb)( @@ -199,6 +207,10 @@ typedef struct git_checkout_opts { } git_checkout_opts; +/* + * git_clone + */ + typedef struct git_clone_options { unsigned int version; @@ -212,11 +224,22 @@ typedef struct git_clone_options { } git_clone_options; int git_clone(git_repository **out, - const char *url, - const char *local_path, - const git_clone_options *options); + const char *url, + const char *local_path, + const git_clone_options *options); -int git_clone_into(git_repository *repo, git_remote *remote, const git_checkout_opts *co_opts, const char *branch); +int git_clone_into( + git_repository *repo, + git_remote *remote, + const git_checkout_opts *co_opts, + const char *branch); + +/* + * git_config + */ + +typedef ... git_config; +typedef ... git_config_iterator; typedef enum { GIT_CONFIG_LEVEL_SYSTEM = 1, @@ -254,11 +277,17 @@ int git_config_iterator_new(git_config_iterator **out, const git_config *cfg); int git_config_next(git_config_entry **entry, git_config_iterator *iter); void git_config_iterator_free(git_config_iterator *iter); -int git_config_multivar_iterator_new(git_config_iterator **out, - const git_config *cfg, const char *name, - const char *regexp); +int git_config_multivar_iterator_new( + git_config_iterator **out, + const git_config *cfg, + const char *name, + const char *regexp); -int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value); +int git_config_set_multivar( + git_config *cfg, + const char *name, + const char *regexp, + const char *value); int git_config_new(git_config **out); int git_config_open_ondisk(git_config **out, const char *path); @@ -267,5 +296,43 @@ int git_config_find_global(char *out, size_t length); int git_config_find_xdg(char *out, size_t length); -int git_repository_init(git_repository **out, const char *path, - unsigned is_bare); +/* + * git_repository_init + */ +typedef enum { + GIT_REPOSITORY_INIT_BARE, + GIT_REPOSITORY_INIT_NO_REINIT, + GIT_REPOSITORY_INIT_NO_DOTGIT_DIR, + GIT_REPOSITORY_INIT_MKDIR, + GIT_REPOSITORY_INIT_MKPATH, + GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE, + ... +} git_repository_init_flag_t; + +typedef enum { + GIT_REPOSITORY_INIT_SHARED_UMASK, + GIT_REPOSITORY_INIT_SHARED_GROUP, + GIT_REPOSITORY_INIT_SHARED_ALL, + ... +} git_repository_init_mode_t; + +typedef struct { + unsigned int version; + uint32_t flags; + uint32_t mode; + const char *workdir_path; + const char *description; + const char *template_path; + const char *initial_head; + const char *origin_url; +} git_repository_init_options; + +int git_repository_init( + git_repository **out, + const char *path, + unsigned is_bare); + +int git_repository_init_ext( + git_repository **out, + const char *repo_path, + git_repository_init_options *opts); diff --git a/pygit2/ffi.py b/pygit2/ffi.py index 468b9bc..0dba2f3 100644 --- a/pygit2/ffi.py +++ b/pygit2/ffi.py @@ -38,7 +38,7 @@ import sys if major_version < 3: def to_str(s, encoding='utf-8', errors='strict'): - if s == ffi.NULL or s == None: + if s == ffi.NULL or s is None: return ffi.NULL if isinstance(s, unicode): @@ -48,7 +48,7 @@ if major_version < 3: return s else: def to_str(s, encoding='utf-8', errors='strict'): - if s == ffi.NULL or s == None: + if s == ffi.NULL or s is None: return ffi.NULL if isinstance(s, bytes): @@ -108,7 +108,7 @@ dir_path = path.dirname(path.abspath(inspect.getfile(inspect.currentframe()))) decl_path = path.join(dir_path, 'decl.h') with codecs.open(decl_path, 'r', 'utf-8') as header: - ffi.cdef(header.read()) + ffi.cdef(header.read()) # if LIBGIT2 exists, set build and link against that version libgit2_path = getenv('LIBGIT2') @@ -118,4 +118,5 @@ if libgit2_path: include_dirs = [path.join(libgit2_path, 'include')] library_dirs = [path.join(libgit2_path, 'lib')] -C = ffi.verify("#include ", libraries=["git2"], include_dirs=include_dirs, library_dirs=library_dirs) +C = ffi.verify("#include ", libraries=["git2"], + include_dirs=include_dirs, library_dirs=library_dirs)