diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..44fa6fdf6 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "vendor/libgit2"] + path = vendor/libgit2 + url = https://github.com/libgit2/libgit2.git diff --git a/docs/objects.rst b/docs/objects.rst index 36edf958f..73db010a9 100644 --- a/docs/objects.rst +++ b/docs/objects.rst @@ -216,6 +216,7 @@ committer and others. .. autoattribute:: pygit2.Commit.committer .. autoattribute:: pygit2.Commit.message .. autoattribute:: pygit2.Commit.message_encoding +.. autoattribute:: pygit2.Commit.raw_message .. autoattribute:: pygit2.Commit.tree .. autoattribute:: pygit2.Commit.parents .. autoattribute:: pygit2.Commit.commit_time @@ -232,7 +233,9 @@ objects:: .. autoattribute:: pygit2.Signature.name +.. autoattribute:: pygit2.Signature.raw_name .. autoattribute:: pygit2.Signature.email +.. autoattribute:: pygit2.Signature.raw_email .. autoattribute:: pygit2.Signature.time .. autoattribute:: pygit2.Signature.offset @@ -267,6 +270,8 @@ A tag is a static label for a commit. See references for more information. .. autoattribute:: pygit2.Tag.tagger .. autoattribute:: pygit2.Tag.message +.. automethod:: pygit2.Tag.get_object + Creating tags -------------------- diff --git a/docs/recipes/git-log.rst b/docs/recipes/git-log.rst index e9c10b736..b234e0f61 100644 --- a/docs/recipes/git-log.rst +++ b/docs/recipes/git-log.rst @@ -16,7 +16,7 @@ Show HEAD commit .. code-block:: python - >>> commit = repo[repo.head.oid] + >>> commit = repo[repo.head.target] >>> commit.message 'commit message' @@ -30,7 +30,7 @@ Traverse commit history .. code-block:: python - >>> last = repo[repo.head.oid] + >>> last = repo[repo.head.target] >>> for commit in repo.walk(last.oid, pygit2.GIT_SORT_TIME): >>> print(commit.message) # or some other operation diff --git a/docs/references.rst b/docs/references.rst index 4db5eae7a..c3e4c3dd5 100644 --- a/docs/references.rst +++ b/docs/references.rst @@ -38,7 +38,7 @@ Example. These two lines are equivalent:: .. autoattribute:: pygit2.Repository.head .. autoattribute:: pygit2.Repository.head_is_detached -.. autoattribute:: pygit2.Repository.head_is_orphaned +.. autoattribute:: pygit2.Repository.head_is_unborn Branches ==================== diff --git a/docs/remotes.rst b/docs/remotes.rst index 91a69d055..4b9692fcd 100644 --- a/docs/remotes.rst +++ b/docs/remotes.rst @@ -15,4 +15,5 @@ The Remote type .. autoattribute:: pygit2.Remote.refspec_count .. automethod:: pygit2.Remote.get_refspec .. automethod:: pygit2.Remote.fetch +.. automethod:: pygit2.Remote.push .. automethod:: pygit2.Remote.save diff --git a/pygit2/repository.py b/pygit2/repository.py index fbb77ced3..aee797331 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -134,7 +134,7 @@ def checkout(self, refname=None, strategy=GIT_CHECKOUT_SAFE_CREATE): # Diff # def diff(self, a=None, b=None, cached=False, flags=GIT_DIFF_NORMAL, - context_lines=3, interhunk_lines=0): + context_lines=3, interhunk_lines=0, paths=None): """ Show changes between the working tree and the index or a tree, changes between the index and a tree, changes between two trees, or @@ -156,6 +156,9 @@ def diff(self, a=None, b=None, cached=False, flags=GIT_DIFF_NORMAL, the maximum number of unchanged lines between hunk boundaries before the hunks will be merged into a one + paths + paths to diff + Examples:: # Changes in the working tree not yet staged for the next commit @@ -192,8 +195,8 @@ def treeish_to_tree(obj): a = treeish_to_tree(a) or a b = treeish_to_tree(b) or b - opt_keys = ['flags', 'context_lines', 'interhunk_lines'] - opt_values = [flags, context_lines, interhunk_lines] + opt_keys = ['flags', 'context_lines', 'interhunk_lines', 'paths'] + opt_values = [flags, context_lines, interhunk_lines, paths] # Case 1: Diff tree to tree if isinstance(a, Tree) and isinstance(b, Tree): diff --git a/setup.py b/setup.py index 0717839a8..f246f7cd0 100644 --- a/setup.py +++ b/setup.py @@ -40,6 +40,7 @@ from subprocess import Popen, PIPE import sys import unittest +import shutil # Read version from local pygit2/version.py without pulling in # pygit2/__init__.py @@ -53,19 +54,46 @@ else: u = str +print('checkout libgit2 source') +popen = Popen(['git', 'submodule', 'update', '--init'], stdout=PIPE, stderr=PIPE) +stdoutdata, stderrdata = popen.communicate() +if popen.returncode != 0: + print(stderrdata) + sys.exit() +pygit2_libs = ['git2'] # Use environment variable LIBGIT2 to set your own libgit2 configuration. libgit2_path = os.getenv("LIBGIT2") -if libgit2_path is None: +if libgit2_path: + libgit2_bin = os.path.join(libgit2_path, 'bin') + libgit2_include = os.path.join(libgit2_path, 'include') + libgit2_lib = os.getenv('LIBGIT2_LIB', os.path.join(libgit2_path, 'lib')) +else: if os.name == 'nt': program_files = os.getenv("ProgramFiles") libgit2_path = '%s\libgit2' % program_files else: - libgit2_path = '/usr/local' + # use libgit2 embed + cwd = os.path.dirname(os.path.realpath(__file__)) + libgit2_dir = os.path.join(cwd, 'vendor', 'libgit2') + libgit2_lib_path = cwd + "/libgit2_embed.a" + if os.path.isfile(libgit2_lib_path): + os.remove(libgit2_lib_path) + if not os.path.isfile(libgit2_lib_path): + os.chdir(libgit2_dir) + print('build libgit2 embed') + popen = Popen(['make', '-f', 'Makefile.embed'], stdout=PIPE, stderr=PIPE) + stdoutdata, stderrdata = popen.communicate() + if popen.returncode != 0: + print(stderrdata) + sys.exit() + shutil.copy('libgit2.a', libgit2_lib_path) + os.chdir(cwd) + libgit2_bin = '' + libgit2_include = os.path.join(libgit2_dir, 'include') + libgit2_lib = cwd + pygit2_libs = ['git2_embed'] -libgit2_bin = os.path.join(libgit2_path, 'bin') -libgit2_include = os.path.join(libgit2_path, 'include') -libgit2_lib = os.getenv('LIBGIT2_LIB', os.path.join(libgit2_path, 'lib')) pygit2_exts = [os.path.join('src', name) for name in os.listdir('src') if name.endswith('.c')] @@ -188,6 +216,6 @@ def get_file_list(self): Extension('_pygit2', pygit2_exts, include_dirs=[libgit2_include, 'include'], library_dirs=[libgit2_lib], - libraries=['git2']), + libraries=pygit2_libs), ], cmdclass=cmdclass) diff --git a/src/blob.c b/src/blob.c index 465d8d651..1d41a2283 100644 --- a/src/blob.c +++ b/src/blob.c @@ -41,12 +41,24 @@ Blob_size__get__(Blob *self) } +PyDoc_STRVAR(Blob_binary__doc__, "Binary."); + +PyObject * +Blob_binary__get__(Blob *self) +{ + if (git_blob_is_binary(self->blob)) + Py_RETURN_TRUE; + Py_RETURN_FALSE; +} + + PyDoc_STRVAR(Blob_data__doc__, "The contents of the blob, a bytes string. This is the same as\n" "Blob.read_raw()"); PyGetSetDef Blob_getseters[] = { GETTER(Blob, size), + GETTER(Blob, binary), {"data", (getter)Object_read_raw, NULL, Blob_data__doc__, NULL}, {NULL} }; diff --git a/src/commit.c b/src/commit.c index 74b1ecc16..861c7380f 100644 --- a/src/commit.c +++ b/src/commit.c @@ -35,6 +35,380 @@ extern PyTypeObject TreeType; +int +compare_delta_path(const git_diff_delta *delta, git_diff_options *opts) +{ + unsigned int i; + int res = -1; + int cmp; + int length = opts->pathspec.count; + char **paths = opts->pathspec.strings; + for (i = 0; i < length; i++) { + cmp = strncmp(delta->old_file.path, paths[i], strlen(paths[i])); + if (cmp != 0) + continue; + res = i; + break; + } + return res; +} + +int +diff_path_bytree(git_diff_list *diff, git_diff_options *opts, int *indexs) +{ + const git_diff_delta *delta; + unsigned int i; + int ndeltas; + int err; + int index; + int count = 0; + ndeltas = (int)git_diff_num_deltas(diff); + for (i = 0; i < ndeltas; i++) { + err = git_diff_get_patch(NULL, &delta, diff, i); + if (err < 0) + goto cleanup; + index = compare_delta_path(delta, opts); + if (index < 0) + continue; + indexs[index] ++; + count ++; + } + return count; +cleanup: + return err; +} + +int +diff_tree_byparent(git_repository *repo, git_commit *commit, unsigned int index, + git_diff_options *opts, git_diff_list **diff) +{ + const git_oid *parent_oid; + git_commit *parent; + git_tree* parent_tree = NULL; + git_tree* tree = NULL; + int err; + parent_oid = git_commit_parent_id(commit, index); + if (parent_oid == NULL) { + err = GIT_ENOTFOUND; + goto cleanup; + } + + err = git_commit_lookup(&parent, repo, parent_oid); + if (err < 0) + goto cleanup; + + err = git_commit_tree(&parent_tree, parent); + if (err < 0) + goto cleanup_ptree; + + err = git_commit_tree(&tree, commit); + if (err < 0) + goto cleanup_tree; + + err = git_diff_tree_to_tree(diff, repo, parent_tree, tree, opts); + if (err < 0) + goto cleanup_diff; + +cleanup_diff: + git_tree_free(tree); +cleanup_tree: + git_tree_free(parent_tree); +cleanup_ptree: + git_commit_free(parent); +cleanup: + return err; +} + +int +diff_tree(git_repository *repo, git_commit *commit, + git_diff_options *opts, git_diff_list **diff) +{ + git_tree* tree = NULL; + int err; + + err = git_commit_tree(&tree, commit); + if (err < 0) + goto cleanup_tree; + + err = git_diff_tree_to_tree(diff, repo, NULL, tree, opts); + if (err < 0) + goto cleanup_diff; + +cleanup_diff: + git_tree_free(tree); +cleanup_tree: + return err; +} + +struct diff_thread_t +{ + int id; + pthread_t thread; + git_tree *tree; + git_tree *parent_tree; + char *path; + int *indexs; +}; + +typedef struct diff_thread_t diff_thread_t; + +void +diff_path_byentry_func(void *arg) +{ + git_tree *tree; + git_tree *parent_tree; + char *path; + const git_tree_entry *entry; + const git_tree_entry *parent_entry; + diff_thread_t *thread = (diff_thread_t *)arg; + tree = thread->tree; + parent_tree = thread->parent_tree; + path = thread->path; + int err; + int re; + + err = git_tree_entry_bypath((git_tree_entry **)&entry, tree, path); + if (err < 0) { + if (err != GIT_ENOTFOUND) + goto cleanup; + entry = NULL; + } + if (parent_tree == NULL) { + if (entry == NULL) + goto cleanup; + git_tree_entry_free((git_tree_entry *)entry); + } else { + err = git_tree_entry_bypath((git_tree_entry **)&parent_entry, parent_tree, path); + if (err < 0) { + if (err != GIT_ENOTFOUND) + goto cleanup_error; + parent_entry = NULL; + } + if (entry == NULL && parent_entry == NULL) + goto cleanup; + if (entry != NULL && parent_entry != NULL) { + re = memcmp(git_tree_entry_id(entry)->id, git_tree_entry_id(parent_entry)->id, + GIT_OID_RAWSZ); + if (re == 0) { + git_tree_entry_free((git_tree_entry *)parent_entry); + git_tree_entry_free((git_tree_entry *)entry); + goto cleanup; + } + } + if (entry != NULL) + git_tree_entry_free((git_tree_entry *)entry); + if (parent_entry != NULL) + git_tree_entry_free((git_tree_entry *)parent_entry); + } + thread->indexs[thread->id] ++; + pthread_exit(0); +cleanup_error: + git_tree_entry_free((git_tree_entry *)entry); +cleanup: + pthread_exit(0); +} + +int +diff_path_byentry_threaded(git_tree *tree, git_tree *parent_tree, + git_diff_options *opts, int *indexs) +{ + int length = opts->pathspec.count; + char **paths = opts->pathspec.strings; + int i; + int err; + diff_thread_t *threads; + diff_thread_t *thread; + threads = (diff_thread_t *)calloc(length, sizeof (diff_thread_t)); + for (i = 0; i < length; i++) { + thread = &threads[i]; + thread->id = i; + thread->tree = tree; + thread->parent_tree = parent_tree; + thread->path = paths[i]; + thread->indexs = indexs; + err = pthread_create(&thread->thread, NULL, (void *) &diff_path_byentry_func, (void *) thread); + } + for (i = 0; i < length; i++) { + thread = &threads[i]; + err = pthread_join(thread->thread, NULL); + } + free(threads); + return 0; +} + +int +diff_path_byentry(git_tree *tree, git_tree *parent_tree, + git_diff_options *opts, int *indexs) +{ + const git_tree_entry *entry; + const git_tree_entry *parent_entry; + int length = opts->pathspec.count; + char **paths = opts->pathspec.strings; + int i; + int err; + int re; + for (i = 0; i < length; i++) { + if (indexs[i] > 0) + continue; + err = git_tree_entry_bypath((git_tree_entry **)&entry, tree, paths[i]); + if (err < 0) { + if (err != GIT_ENOTFOUND) + goto cleanup; + entry = NULL; + } + if (parent_tree == NULL) { + if (entry == NULL) + continue; + git_tree_entry_free((git_tree_entry *)entry); + } else { + err = git_tree_entry_bypath((git_tree_entry **)&parent_entry, parent_tree, paths[i]); + if (err < 0) { + if (err != GIT_ENOTFOUND) + goto cleanup_error; + parent_entry = NULL; + } + if (entry == NULL && parent_entry == NULL) + continue; + if (entry != NULL && parent_entry != NULL) { + re = memcmp(git_tree_entry_id(entry)->id, git_tree_entry_id(parent_entry)->id, + GIT_OID_RAWSZ); + if (re == 0) { + git_tree_entry_free((git_tree_entry *)parent_entry); + git_tree_entry_free((git_tree_entry *)entry); + continue; + } + } + if (entry != NULL) + git_tree_entry_free((git_tree_entry *)entry); + if (parent_entry != NULL) + git_tree_entry_free((git_tree_entry *)parent_entry); + } + indexs[i] ++; + } + return 0; + +cleanup_error: + git_tree_entry_free((git_tree_entry *)entry); +cleanup: + return err; +} + +int +diff_entry(git_repository *repo, git_commit *commit, + git_diff_options *opts, int *indexs) +{ + git_tree* tree = NULL; + int err; + err = git_commit_tree(&tree, commit); + if (err < 0) + goto cleanup; + + err = diff_path_byentry(tree, NULL, opts, indexs); + +cleanup_entry: + git_tree_free(tree); +cleanup: + return err; +} + +int +diff_entry_byparent(git_repository *repo, git_commit *commit, unsigned int index, + git_diff_options *opts, int *indexs) +{ + const git_oid *parent_oid; + git_commit *parent; + git_tree* parent_tree = NULL; + git_tree* tree = NULL; + int err; + int count; + parent_oid = git_commit_parent_id(commit, index); + if (parent_oid == NULL) { + err = GIT_ENOTFOUND; + goto cleanup; + } + + err = git_commit_lookup(&parent, repo, parent_oid); + if (err < 0) + goto cleanup; + + err = git_commit_tree(&parent_tree, parent); + if (err < 0) + goto cleanup_ptree; + + err = git_commit_tree(&tree, commit); + if (err < 0) + goto cleanup_tree; + + err = diff_path_byentry(tree, parent_tree, opts, indexs); + +cleanup_entry: + git_tree_free(tree); +cleanup_tree: + git_tree_free(parent_tree); +cleanup_ptree: + git_commit_free(parent); +cleanup: + return err; +} + +int +diff_entry_threaded(git_repository *repo, git_commit *commit, + git_diff_options *opts, int *indexs) +{ + git_tree* tree = NULL; + int err; + err = git_commit_tree(&tree, commit); + if (err < 0) + goto cleanup; + + err = diff_path_byentry_threaded(tree, NULL, opts, indexs); + +cleanup_entry: + git_tree_free(tree); +cleanup: + return err; +} + +int +diff_entry_byparent_threaded(git_repository *repo, git_commit *commit, unsigned int index, + git_diff_options *opts, int *indexs) +{ + const git_oid *parent_oid; + git_commit *parent; + git_tree* parent_tree = NULL; + git_tree* tree = NULL; + int err; + int count; + parent_oid = git_commit_parent_id(commit, index); + if (parent_oid == NULL) { + err = GIT_ENOTFOUND; + goto cleanup; + } + + err = git_commit_lookup(&parent, repo, parent_oid); + if (err < 0) + goto cleanup; + + err = git_commit_tree(&parent_tree, parent); + if (err < 0) + goto cleanup_ptree; + + err = git_commit_tree(&tree, commit); + if (err < 0) + goto cleanup_tree; + + err = diff_path_byentry_threaded(tree, parent_tree, opts, indexs); + +cleanup_entry: + git_tree_free(tree); +cleanup_tree: + git_tree_free(parent_tree); +cleanup_ptree: + git_commit_free(parent); +cleanup: + return err; +} + PyDoc_STRVAR(Commit_message_encoding__doc__, "Message encoding."); @@ -64,10 +438,10 @@ Commit_message__get__(Commit *commit) } -PyDoc_STRVAR(Commit__message__doc__, "Message (bytes)."); +PyDoc_STRVAR(Commit_raw_message__doc__, "Message (bytes)."); PyObject * -Commit__message__get__(Commit *commit) +Commit_raw_message__get__(Commit *commit) { return PyBytes_FromString(git_commit_message(commit->commit)); } @@ -195,7 +569,7 @@ Commit_parents__get__(Commit *self) PyGetSetDef Commit_getseters[] = { GETTER(Commit, message_encoding), GETTER(Commit, message), - GETTER(Commit, _message), + GETTER(Commit, raw_message), GETTER(Commit, commit_time), GETTER(Commit, commit_time_offset), GETTER(Commit, committer), @@ -206,6 +580,176 @@ PyGetSetDef Commit_getseters[] = { }; +PyDoc_STRVAR(Commit_is_changed__doc__, + "is_changed(paths, [flags, no_merges]) -> Diff\n" + "\n" + "check the paths are changed in current commit\n" + "\n" + "Arguments:\n" + "\n" + "paths: file path list.\n" + "\n" + "no_merges: boolean, escape merge commit or not.\n" + "\n" + "flags: a GIT_DIFF_* constant.\n" + "\n"); + +PyObject * +Commit_is_changed(Commit *self, PyObject *args, PyObject *kwds) +{ + const git_oid *parent_oid; + git_commit *parent; + git_tree* tree = NULL; + git_tree* parent_tree = NULL; + git_diff_list *diff; + git_repository *repo; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + unsigned int i; + unsigned int parent_count; + int err; + int ndeltas; + char *keywords[] = {"paths", "flags", "no_merges", "no_diff", "thread", NULL}; + Repository *py_repo; + PyObject *py_paths = NULL; + PyObject *py_diff_paths = NULL; + PyObject *py_no_merges = NULL; + PyObject *py_no_diff = NULL; + PyObject *py_thread = NULL; + int *path_indexs; + + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|iOOO", keywords, + &py_paths, &opts.flags, &py_no_merges, + &py_no_diff, &py_thread)) + return NULL; + + if (!PyObject_TypeCheck(py_paths, &PyList_Type)) { + PyErr_SetObject(PyExc_TypeError, py_paths); + return NULL; + } + + if (py_no_merges != NULL && + (py_no_merges != Py_None && + !PyObject_TypeCheck(py_no_merges, &PyBool_Type)) + ) { + PyErr_SetObject(PyExc_TypeError, py_no_merges); + return NULL; + } + + if (py_no_diff != NULL && + (py_no_diff != Py_None && + !PyObject_TypeCheck(py_no_diff, &PyBool_Type)) + ) { + PyErr_SetObject(PyExc_TypeError, py_no_diff); + return NULL; + } + + int paths_length = 0; + PyObject *py_path = NULL; + paths_length = PyList_Size(py_paths); + if (paths_length <= 0) { + PyErr_SetObject(PyExc_ValueError, py_paths); + return NULL; + } + for (i = 0; i < paths_length; i++) { + py_path = PyList_GetItem(py_paths, i); + if (!PyObject_TypeCheck(py_path, &PyString_Type)) { + PyErr_SetObject(PyExc_TypeError, py_path); + return NULL; + } + } + opts.pathspec.count = paths_length; + opts.pathspec.strings = (char **) PyMem_Malloc(paths_length * sizeof (char *)); + path_indexs = (int *)PyMem_Malloc(paths_length * sizeof (int)); + for (i = 0; i < paths_length; i++) { + py_path = PyList_GetItem(py_paths, i); + opts.pathspec.strings[i] = PyString_AsString(py_path); + path_indexs[i] = 0; + } + + py_repo = self->repo; + repo = py_repo->repo; + parent_count = git_commit_parentcount(self->commit); + if (py_no_merges != NULL && + py_no_merges != Py_None && PyObject_IsTrue(py_no_merges)) { + if (parent_count > 1) + goto cleanup_empty; + } + + if (py_thread != NULL && + py_thread != Py_None && PyObject_IsTrue(py_thread)) { + if (parent_count > 0) { + for (i = 0; i < parent_count; i++) { + err = diff_entry_byparent_threaded(repo, self->commit, i, &opts, path_indexs); + if (err < 0) + goto cleanup_error; + } + } else { + err = diff_entry_threaded(repo, self->commit, &opts, path_indexs); + if (err < 0) + goto cleanup_error; + } + goto cleanup_empty; + } + + if (py_no_diff != NULL && + py_no_diff != Py_None && PyObject_IsTrue(py_no_diff)) { + if (parent_count > 0) { + for (i = 0; i < parent_count; i++) { + err = diff_entry_byparent(repo, self->commit, i, &opts, path_indexs); + if (err < 0) + goto cleanup_error; + } + } else { + err = diff_entry(repo, self->commit, &opts, path_indexs); + if (err < 0) + goto cleanup_error; + } + goto cleanup_empty; + } + + if (parent_count > 0) { + for (i = 0; i < parent_count; i++) { + err = diff_tree_byparent(repo, self->commit, i, &opts, &diff); + if (err < 0) + goto cleanup_error; + err = diff_path_bytree(diff, &opts, path_indexs); + git_diff_list_free(diff); + if (err < 0) + goto cleanup_error; + } + } else { + err = diff_tree(repo, self->commit, &opts, &diff); + if (err < 0) + goto cleanup_error; + err = diff_path_bytree(diff, &opts, path_indexs); + git_diff_list_free(diff); + if (err < 0) + goto cleanup_error; + } + +cleanup_empty: + py_diff_paths = PyList_New(paths_length); + for (i = 0; i < paths_length; i++) { + PyList_SetItem(py_diff_paths, i, Py_BuildValue("i", path_indexs[i])); + } + +cleanup: + PyMem_Free(opts.pathspec.strings); + PyMem_Free(path_indexs); + return py_diff_paths; + +cleanup_error: + PyMem_Free(opts.pathspec.strings); + PyMem_Free(path_indexs); + return NULL; +} + +PyMethodDef Commit_methods[] = { + METHOD(Commit, is_changed, METH_VARARGS|METH_KEYWORDS), + {NULL} +}; + PyDoc_STRVAR(Commit__doc__, "Commit objects."); PyTypeObject CommitType = { @@ -236,7 +780,7 @@ PyTypeObject CommitType = { 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ - 0, /* tp_methods */ + Commit_methods, /* tp_methods */ 0, /* tp_members */ Commit_getseters, /* tp_getset */ 0, /* tp_base */ diff --git a/src/config.c b/src/config.c index dc9b44f53..e7e0a864c 100644 --- a/src/config.c +++ b/src/config.c @@ -314,10 +314,10 @@ Config_add_file(Config *self, PyObject *args, PyObject *kwds) char *keywords[] = {"path", "level", "force", NULL}; int err; char *path; - unsigned int level = 0; + int level = -1; int force = 0; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|Ii", keywords, + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|ii", keywords, &path, &level, &force)) return NULL; @@ -370,7 +370,7 @@ Config_get_multivar(Config *self, PyObject *args) return NULL; list = PyList_New(0); - err = git_config_get_multivar(self->config, name, regex, + err = git_config_get_multivar_foreach(self->config, name, regex, Config_get_multivar_fn_wrapper, (void *)list); diff --git a/src/diff.c b/src/diff.c index f46d4bdb7..bdc9db402 100644 --- a/src/diff.c +++ b/src/diff.c @@ -63,7 +63,7 @@ diff_get_patch_byindex(git_diff_list* list, size_t idx) const git_diff_delta* delta; const git_diff_range* range; git_diff_patch* patch = NULL; - size_t i, j, hunk_amounts, lines_in_hunk, line_len, header_len; + size_t i, j, hunk_amounts, lines_in_hunk, line_len, header_len, additions, deletions; const char* line, *header; char line_origin; int err; @@ -81,9 +81,13 @@ diff_get_patch_byindex(git_diff_list* list, size_t idx) py_patch->new_file_path = delta->new_file.path; py_patch->status = git_diff_status_char(delta->status); py_patch->similarity = delta->similarity; + py_patch->flags = delta->flags; py_patch->old_oid = git_oid_allocfmt(&delta->old_file.oid); py_patch->new_oid = git_oid_allocfmt(&delta->new_file.oid); + git_diff_patch_line_stats(NULL, &additions, &deletions, patch); + py_patch->additions = additions; + py_patch->deletions = deletions; hunk_amounts = git_diff_patch_num_hunks(patch); py_patch->hunks = PyList_New(hunk_amounts); @@ -152,6 +156,24 @@ PyMemberDef Patch_members[] = { MEMBER(Patch, status, T_CHAR, "status"), MEMBER(Patch, similarity, T_INT, "similarity"), MEMBER(Patch, hunks, T_OBJECT, "hunks"), + MEMBER(Patch, additions, T_INT, "additions"), + MEMBER(Patch, deletions, T_INT, "deletions"), + {NULL} +}; + +PyDoc_STRVAR(Patch_binary__doc__, "Binary."); + +PyObject * +Patch_binary__get__(Patch *self) +{ + if (!(self->flags & GIT_DIFF_FLAG_NOT_BINARY) && + (self->flags & GIT_DIFF_FLAG_BINARY)) + Py_RETURN_TRUE; + Py_RETURN_FALSE; +} + +PyGetSetDef Patch_getseters[] = { + GETTER(Patch, binary), {NULL} }; @@ -187,7 +209,7 @@ PyTypeObject PatchType = { 0, /* tp_iternext */ 0, /* tp_methods */ Patch_members, /* tp_members */ - 0, /* tp_getset */ + Patch_getseters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ @@ -250,6 +272,14 @@ PyTypeObject DiffIterType = { }; +PyDoc_STRVAR(Diff_size__doc__, "Returns the number of deltas/patches in this diff."); + +PyObject * +Diff_size__get__(Diff *self) +{ + return PyLong_FromSize_t(git_diff_num_deltas(self->list)); +} + PyDoc_STRVAR(Diff_patch__doc__, "Patch diff string."); PyObject * @@ -440,6 +470,7 @@ Diff_dealloc(Diff *self) PyGetSetDef Diff_getseters[] = { GETTER(Diff, patch), + GETTER(Diff, size), {NULL} }; diff --git a/src/index.c b/src/index.c index 3c73f2178..9751b1549 100644 --- a/src/index.c +++ b/src/index.c @@ -136,10 +136,15 @@ Index_diff_to_workdir(Index *self, PyObject *args) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; git_diff_list *diff; + PyObject *py_paths = NULL; int err; - if (!PyArg_ParseTuple(args, "|IHH", &opts.flags, &opts.context_lines, - &opts.interhunk_lines)) + if (!PyArg_ParseTuple(args, "|IHHO", &opts.flags, &opts.context_lines, + &opts.interhunk_lines, &py_paths)) + return NULL; + + err = py_list_to_opts(py_paths, &opts); + if (err < 0) return NULL; err = git_diff_index_to_workdir( @@ -148,6 +153,8 @@ Index_diff_to_workdir(Index *self, PyObject *args) self->index, &opts); + free_opts_pathspec(py_paths, &opts); + if (err < 0) return Error_set(err); diff --git a/src/pygit2.c b/src/pygit2.c index a4729df63..18f1fa93d 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -65,7 +65,30 @@ extern PyTypeObject RemoteType; extern PyTypeObject NoteType; extern PyTypeObject NoteIterType; +PyDoc_STRVAR(is_repository__doc__, + "is_repository(path) -> Boolean\n" + "\n" + "Test if the path is a git repository."); + +PyObject * +is_repository(PyObject *self, PyObject *args) { + char* path = NULL; + int err; + + git_repository *repo; + + if (!PyArg_ParseTuple(args, "s", &path)) { + return NULL; + } + err = git_repository_open(&repo, path); + + if (err < 0) + Py_RETURN_FALSE; + + git_repository_free(repo); + Py_RETURN_TRUE; +} PyDoc_STRVAR(init_repository__doc__, "init_repository(path, bare)\n" @@ -136,6 +159,7 @@ clone_repository(PyObject *self, PyObject *args) { unsigned int bare; const char *remote_name, *push_url, *fetch_spec; const char *push_spec, *checkout_branch; + const git_error *git_err; int err; git_clone_options opts = GIT_CLONE_OPTIONS_INIT; @@ -240,6 +264,7 @@ PyMethodDef module_methods[] = { discover_repository__doc__}, {"hashfile", hashfile, METH_VARARGS, hashfile__doc__}, {"hash", hash, METH_VARARGS, hash__doc__}, + {"is_repository", is_repository, METH_VARARGS, is_repository__doc__}, {NULL} }; @@ -281,7 +306,7 @@ moduleinit(PyObject* m) INIT_TYPE(TreeType, &ObjectType, NULL) INIT_TYPE(TreeEntryType, NULL, NULL) INIT_TYPE(TreeIterType, NULL, NULL) - INIT_TYPE(TreeBuilderType, NULL, PyType_GenericNew) + INIT_TYPE(TreeBuilderType, NULL, NULL) INIT_TYPE(BlobType, &ObjectType, NULL) INIT_TYPE(TagType, &ObjectType, NULL) ADD_TYPE(m, Object) diff --git a/src/reference.c b/src/reference.c index 4d11cb5c5..78291ea09 100644 --- a/src/reference.c +++ b/src/reference.c @@ -40,6 +40,7 @@ extern PyObject *GitError; extern PyTypeObject RefLogEntryType; +extern PyTypeObject SignatureType; void RefLogIter_dealloc(RefLogIter *self) @@ -313,6 +314,57 @@ Reference_log(Reference *self) } +PyDoc_STRVAR(Reference_append_log__doc__, + "append_log(committer, message) -> Boolean\n" + "\n" + "Append reflog to the current reference."); + +PyObject * +Reference_append_log(Reference *self, PyObject *args, PyObject *kwds) +{ + git_signature *committer; + const char *message = NULL; + git_reflog *reflog; + int err; + Signature *py_committer; + PyObject *py_message = NULL; + char *keywords[] = {"committer", "message", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!O", keywords, + &SignatureType, &py_committer, + &py_message)) + return NULL; + + // FIXME: encoding + message = py_str_to_c_str(py_message, NULL); + if (message == NULL) + return NULL; + + CHECK_REFERENCE(self); + + err = git_reflog_read(&reflog, self->reference); + if (err < 0) { + free((void *)message); + return NULL; + } + + committer = (git_signature *)py_committer->signature; + if (!(err = git_reflog_append(reflog, + git_reference_target(self->reference), + committer, + message))) + err = git_reflog_write(reflog); + + git_reflog_free(reflog); + free((void *)message); + + if (err < 0) + return NULL; + + Py_RETURN_TRUE; +} + + PyDoc_STRVAR(Reference_get_object__doc__, "get_object() -> object\n" "\n" @@ -426,6 +478,7 @@ PyMethodDef Reference_methods[] = { METHOD(Reference, rename, METH_O), METHOD(Reference, resolve, METH_NOARGS), METHOD(Reference, log, METH_NOARGS), + METHOD(Reference, append_log, METH_VARARGS|METH_KEYWORDS), METHOD(Reference, get_object, METH_NOARGS), {NULL} }; diff --git a/src/remote.c b/src/remote.c index 9faf11435..4aa63c5b8 100644 --- a/src/remote.c +++ b/src/remote.c @@ -125,6 +125,69 @@ Remote_url__set__(Remote *self, PyObject* py_url) } +PyDoc_STRVAR(Remote_fetchspec__doc__, + "= (source:str, destination:str)\n" + "\n" + "Name of the remote source and destination fetch refspecs\n"); + + +PyObject * +Remote_fetchspec__get__(Remote *self) +{ + PyObject *py_tuple = NULL; + PyObject *py_string = NULL; + git_strarray refspecs; + int err; + int index; + + err = git_remote_get_fetch_refspecs(&refspecs, self->remote); + if (err < 0) + return Error_set(err); + + py_tuple = PyTuple_New(refspecs.count); + for (index = 0; index < refspecs.count; index++) { + py_string = to_path(refspecs.strings[index]); + if (py_string == NULL) { + Py_CLEAR(py_tuple); + goto out; + } + PyTuple_SET_ITEM(py_tuple, index, py_string); + } + +out: + git_strarray_free(&refspecs); + return py_tuple; +} + + +int +Remote_fetchspec__set__(Remote *self, PyObject* py_tuple) +{ + int err; + size_t length = 0; + char* src = NULL, *dst = NULL, *buf = NULL; + + if (!PyArg_ParseTuple(py_tuple, "ss", &src, &dst)) + return -1; + + /* length is strlen('+' + src + ':' + dst) and Null-Byte */ + length = strlen(src) + strlen(dst) + 3; + buf = (char*) calloc(length, sizeof(char)); + if (buf != NULL) { + sprintf(buf, "+%s:%s", src, dst); + err = git_remote_add_fetch(self->remote, buf); + free(buf); + + if (err == GIT_OK) + return 0; + + Error_set_exc(PyExc_ValueError); + } + + return -1; +} + + PyDoc_STRVAR(Remote_refspec_count__doc__, "Number of refspecs."); PyObject * @@ -218,10 +281,76 @@ Remote_save(Remote *self, PyObject *args) } +int +push_status_foreach_callback(const char *ref, const char *msg, void *data) +{ + const char **msg_dst = (const char **)data; + if (msg != NULL && *msg_dst == NULL) + *msg_dst = msg; + return 0; +} + +PyDoc_STRVAR(Remote_push__doc__, + "push(refspec)\n" + "\n" + "Push the given refspec to the remote. Raises ``GitError`` on error."); + +PyObject * +Remote_push(Remote *self, PyObject *args) +{ + git_push *push = NULL; + const char *refspec = NULL; + const char *msg = NULL; + int err; + + if (!PyArg_ParseTuple(args, "s", &refspec)) + return NULL; + + err = git_push_new(&push, self->remote); + if (err < 0) + return Error_set(err); + + err = git_push_add_refspec(push, refspec); + if (err < 0) + goto error; + + err = git_push_finish(push); + if (err < 0) + goto error; + + if (!git_push_unpack_ok(push)) { + git_push_free(push); + PyErr_SetString(GitError, "Remote failed to unpack objects"); + return NULL; + } + + err = git_push_status_foreach(push, push_status_foreach_callback, &msg); + if (err < 0) + goto error; + if (msg != NULL) { + git_push_free(push); + PyErr_SetString(GitError, msg); + return NULL; + } + + err = git_push_update_tips(push); + if (err < 0) + goto error; + + git_push_free(push); + Py_RETURN_NONE; + +error: + git_push_free(push); + return Error_set(err); +} + + PyMethodDef Remote_methods[] = { METHOD(Remote, fetch, METH_NOARGS), METHOD(Remote, save, METH_NOARGS), METHOD(Remote, get_refspec, METH_O), + METHOD(Remote, push, METH_VARARGS), {NULL} }; @@ -229,6 +358,7 @@ PyGetSetDef Remote_getseters[] = { GETSET(Remote, name), GETSET(Remote, url), GETTER(Remote, refspec_count), + GETSET(Remote, fetchspec), /* FIXME: compatibility */ {NULL} }; diff --git a/src/repository.c b/src/repository.c index 935c1665f..28a242347 100644 --- a/src/repository.c +++ b/src/repository.c @@ -208,14 +208,14 @@ Repository_head_is_detached__get__(Repository *self) } -PyDoc_STRVAR(Repository_head_is_orphaned__doc__, - "An orphan branch is one named from HEAD but which doesn't exist in the\n" +PyDoc_STRVAR(Repository_head_is_unborn__doc__, + "An unborn branch is one named from HEAD but which doesn't exist in the\n" "refs namespace, because it doesn't have any commit to point to."); PyObject * -Repository_head_is_orphaned__get__(Repository *self) +Repository_head_is_unborn__get__(Repository *self) { - if (git_repository_head_orphan(self->repo) > 0) + if (git_repository_head_unborn(self->repo) > 0) Py_RETURN_TRUE; Py_RETURN_FALSE; @@ -430,9 +430,10 @@ Repository_write(Repository *self, PyObject *args) if (err < 0) return Error_set(err); - stream->write(stream, buffer, buflen); - err = stream->finalize_write(&oid, stream); - stream->free(stream); + err = git_odb_stream_write(stream, buffer, buflen); + if (!err) + err = git_odb_stream_finalize_write(&oid, stream); + git_odb_stream_free(stream); return git_oid_to_python(&oid); } @@ -591,9 +592,9 @@ PyDoc_STRVAR(Repository_walk__doc__, " >>> from pygit2 import Repository\n" " >>> from pygit2 import GIT_SORT_TOPOLOGICAL, GIT_SORT_REVERSE\n" " >>> repo = Repository('.git')\n" - " >>> for commit in repo.walk(repo.head.oid, GIT_SORT_TOPOLOGICAL):\n" + " >>> for commit in repo.walk(repo.head.target, GIT_SORT_TOPOLOGICAL):\n" " ... print commit.message\n" - " >>> for commit in repo.walk(repo.head.oid, GIT_SORT_TOPOLOGICAL | GIT_SORT_REVERSE):\n" + " >>> for commit in repo.walk(repo.head.target, GIT_SORT_TOPOLOGICAL | GIT_SORT_REVERSE):\n" " ... print commit.message\n" " >>>\n"); @@ -1460,7 +1461,7 @@ PyGetSetDef Repository_getseters[] = { GETTER(Repository, path), GETSET(Repository, head), GETTER(Repository, head_is_detached), - GETTER(Repository, head_is_orphaned), + GETTER(Repository, head_is_unborn), GETTER(Repository, is_empty), GETTER(Repository, is_bare), GETTER(Repository, config), diff --git a/src/signature.c b/src/signature.c index 24e02a83e..61fd0746c 100644 --- a/src/signature.c +++ b/src/signature.c @@ -107,19 +107,19 @@ Signature__encoding__get__(Signature *self) } -PyDoc_STRVAR(Signature__name__doc__, "Name (bytes)."); +PyDoc_STRVAR(Signature_raw_name__doc__, "Name (bytes)."); PyObject * -Signature__name__get__(Signature *self) +Signature_raw_name__get__(Signature *self) { return to_bytes(self->signature->name); } -PyDoc_STRVAR(Signature__email__doc__, "Email (bytes)."); +PyDoc_STRVAR(Signature_raw_email__doc__, "Email (bytes)."); PyObject * -Signature__email__get__(Signature *self) +Signature_raw_email__get__(Signature *self) { return to_bytes(self->signature->email); } @@ -162,8 +162,8 @@ Signature_offset__get__(Signature *self) PyGetSetDef Signature_getseters[] = { GETTER(Signature, _encoding), - GETTER(Signature, _name), - GETTER(Signature, _email), + GETTER(Signature, raw_name), + GETTER(Signature, raw_email), GETTER(Signature, name), GETTER(Signature, email), GETTER(Signature, time), diff --git a/src/tag.c b/src/tag.c index bc4eaada7..c5fdd0511 100644 --- a/src/tag.c +++ b/src/tag.c @@ -27,6 +27,7 @@ #define PY_SSIZE_T_CLEAN #include +#include "object.h" #include "error.h" #include "types.h" #include "utils.h" @@ -47,6 +48,25 @@ Tag_target__get__(Tag *self) } +PyDoc_STRVAR(Tag_get_object__doc__, + "get_object() -> object\n" + "\n" + "Retrieves the object the current tag is pointing to."); + +PyObject * +Tag_get_object(Tag *self) +{ + int err; + git_object* obj; + + err = git_tag_peel(&obj, self->tag); + if (err < 0) + return Error_set(err); + + return wrap_object(obj, self->repo); +} + + PyDoc_STRVAR(Tag_name__doc__, "Tag name."); PyObject * @@ -94,6 +114,11 @@ Tag__message__get__(Tag *self) return PyBytes_FromString(git_tag_message(self->tag)); } +PyMethodDef Tag_methods[] = { + METHOD(Tag, get_object, METH_NOARGS), + {NULL} +}; + PyGetSetDef Tag_getseters[] = { GETTER(Tag, target), GETTER(Tag, name), @@ -134,7 +159,7 @@ PyTypeObject TagType = { 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ - 0, /* tp_methods */ + Tag_methods, /* tp_methods */ 0, /* tp_members */ Tag_getseters, /* tp_getset */ 0, /* tp_base */ diff --git a/src/tree.c b/src/tree.c index e0abbbd57..e56b4ffd4 100644 --- a/src/tree.c +++ b/src/tree.c @@ -271,14 +271,92 @@ Tree_getitem(Tree *self, PyObject *value) } +PyDoc_STRVAR(Tree_diff__doc__, + "diff([obj, flags, empty_tree, context_lines, paths]) -> Diff\n" + "\n" + "Get changes between current tree instance with another tree, an index or\n" + "the working dir.\n" + "\n" + "Arguments:\n" + "\n" + "obj\n" + " If not given compare diff against working dir. Possible valid\n" + " arguments are instances of Tree or Index.\n" + "\n" + "flags: a GIT_DIFF_* constant.\n" + "\n" + "context_lines: the number of unchanged lines that define the boundary\n" + " of a hunk (and to display before and after)\n"); + +PyObject * +Tree_diff(Tree *self, PyObject *args, PyObject *kwds) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff_list *diff; + git_tree* tree = NULL; + git_index* index; + git_repository *repo; + int err, empty_tree = 0; + char *keywords[] = {"obj", "flags", "empty_tree", "context_lines", "paths", NULL}; + + Diff *py_diff; + PyObject *py_obj = NULL; + PyObject *py_paths = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OiiiO", keywords, + &py_obj, &opts.flags, &empty_tree, + &opts.context_lines, &py_paths)) + return NULL; + + err = py_list_to_opts(py_paths, &opts); + if (err < 0) + return NULL; + + repo = git_tree_owner(self->tree); + if (py_obj == NULL) { + if (empty_tree > 0) + err = git_diff_tree_to_tree(&diff, repo, self->tree, NULL, &opts); + else + err = git_diff_tree_to_workdir(&diff, repo, self->tree, &opts); + + } else if (PyObject_TypeCheck(py_obj, &TreeType)) { + tree = ((Tree *)py_obj)->tree; + err = git_diff_tree_to_tree(&diff, repo, self->tree, tree, &opts); + + } else if (PyObject_TypeCheck(py_obj, &IndexType)) { + index = ((Index *)py_obj)->index; + err = git_diff_tree_to_index(&diff, repo, self->tree, index, &opts); + + } else { + free_opts_pathspec(py_paths, &opts); + PyErr_SetObject(PyExc_TypeError, py_obj); + return NULL; + } + + free_opts_pathspec(py_paths, &opts); + + if (err < 0) + return Error_set(err); + + py_diff = PyObject_New(Diff, &DiffType); + if (py_diff) { + Py_INCREF(self->repo); + py_diff->repo = self->repo; + py_diff->list = diff; + } + + return (PyObject*)py_diff; +} + + PyDoc_STRVAR(Tree_diff_to_workdir__doc__, - "diff_to_workdir([flags, context_lines, interhunk_lines]) -> Diff\n" + "diff_to_workdir([flags, context_lines, interhunk_lines, paths]) -> Diff\n" "\n" "Show the changes between the :py:class:`~pygit2.Tree` and the workdir.\n" "\n" "Arguments:\n" "\n" - "flag: a GIT_DIFF_* constant.\n" + "flags: a GIT_DIFF_* constant.\n" "\n" "context_lines: the number of unchanged lines that define the boundary\n" " of a hunk (and to display before and after)\n" @@ -292,14 +370,22 @@ Tree_diff_to_workdir(Tree *self, PyObject *args) git_diff_options opts = GIT_DIFF_OPTIONS_INIT; git_diff_list *diff; Repository *py_repo; + PyObject *py_paths = NULL; int err; - if (!PyArg_ParseTuple(args, "|IHH", &opts.flags, &opts.context_lines, - &opts.interhunk_lines)) + if (!PyArg_ParseTuple(args, "|IHHO", &opts.flags, &opts.context_lines, + &opts.interhunk_lines, &py_paths)) + return NULL; + + err = py_list_to_opts(py_paths, &opts); + if (err < 0) return NULL; py_repo = self->repo; err = git_diff_tree_to_workdir(&diff, py_repo->repo, self->tree, &opts); + + free_opts_pathspec(py_paths, &opts); + if (err < 0) return Error_set(err); @@ -308,7 +394,7 @@ Tree_diff_to_workdir(Tree *self, PyObject *args) PyDoc_STRVAR(Tree_diff_to_index__doc__, - "diff_to_index(index, [flags, context_lines, interhunk_lines]) -> Diff\n" + "diff_to_index(index, [flags, context_lines, interhunk_lines, paths]) -> Diff\n" "\n" "Show the changes between the index and a given :py:class:`~pygit2.Tree`.\n" "\n" @@ -316,7 +402,7 @@ PyDoc_STRVAR(Tree_diff_to_index__doc__, "\n" "tree: the :py:class:`~pygit2.Tree` to diff.\n" "\n" - "flag: a GIT_DIFF_* constant.\n" + "flags: a GIT_DIFF_* constant.\n" "\n" "context_lines: the number of unchanged lines that define the boundary\n" " of a hunk (and to display before and after)\n" @@ -330,18 +416,27 @@ Tree_diff_to_index(Tree *self, PyObject *args, PyObject *kwds) git_diff_options opts = GIT_DIFF_OPTIONS_INIT; git_diff_list *diff; Repository *py_repo; + PyObject *py_paths = NULL; int err; Index *py_idx = NULL; - if (!PyArg_ParseTuple(args, "O!|IHH", &IndexType, &py_idx, &opts.flags, + if (!PyArg_ParseTuple(args, "O!|IHHO", &IndexType, &py_idx, &opts.flags, &opts.context_lines, - &opts.interhunk_lines)) + &opts.interhunk_lines, + &py_paths)) + return NULL; + + err = py_list_to_opts(py_paths, &opts); + if (err < 0) return NULL; py_repo = self->repo; err = git_diff_tree_to_index(&diff, py_repo->repo, self->tree, py_idx->index, &opts); + + free_opts_pathspec(py_paths, &opts); + if (err < 0) return Error_set(err); @@ -350,7 +445,7 @@ Tree_diff_to_index(Tree *self, PyObject *args, PyObject *kwds) PyDoc_STRVAR(Tree_diff_to_tree__doc__, - "diff_to_tree([tree, flags, context_lines, interhunk_lines, swap]) -> Diff\n" + "diff_to_tree([tree, flags, context_lines, interhunk_lines, swap, paths]) -> Diff\n" "\n" "Show the changes between two trees\n" "\n" @@ -378,14 +473,20 @@ Tree_diff_to_tree(Tree *self, PyObject *args, PyObject *kwds) Repository *py_repo; int err, swap = 0; char *keywords[] = {"obj", "flags", "context_lines", "interhunk_lines", - "swap", NULL}; + "swap", "paths", NULL}; + PyObject *py_paths = NULL; Tree *py_tree = NULL; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O!IHHi", keywords, + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O!IHHiO", keywords, &TreeType, &py_tree, &opts.flags, &opts.context_lines, - &opts.interhunk_lines, &swap)) + &opts.interhunk_lines, &swap, + &py_paths)) + return NULL; + + err = py_list_to_opts(py_paths, &opts); + if (err < 0) return NULL; py_repo = self->repo; @@ -398,6 +499,9 @@ Tree_diff_to_tree(Tree *self, PyObject *args, PyObject *kwds) } err = git_diff_tree_to_tree(&diff, py_repo->repo, from, to, &opts); + + free_opts_pathspec(py_paths, &opts); + if (err < 0) return Error_set(err); @@ -423,6 +527,7 @@ PyMappingMethods Tree_as_mapping = { }; PyMethodDef Tree_methods[] = { + METHOD(Tree, diff, METH_VARARGS | METH_KEYWORDS), /* compatibility */ METHOD(Tree, diff_to_tree, METH_VARARGS | METH_KEYWORDS), METHOD(Tree, diff_to_workdir, METH_VARARGS), METHOD(Tree, diff_to_index, METH_VARARGS | METH_KEYWORDS), diff --git a/src/types.h b/src/types.h index 926d74ad2..683a05bd2 100644 --- a/src/types.h +++ b/src/types.h @@ -113,6 +113,9 @@ typedef struct { char* new_oid; char status; unsigned similarity; + unsigned additions; + unsigned deletions; + unsigned flags; } Patch; typedef struct { @@ -124,7 +127,6 @@ typedef struct { int new_lines; } Hunk; - /* git_tree_walk , git_treebuilder*/ SIMPLE_TYPE(TreeBuilder, git_treebuilder, bld) diff --git a/src/utils.c b/src/utils.c index 31f0472aa..8b9a79d1b 100644 --- a/src/utils.c +++ b/src/utils.c @@ -60,3 +60,53 @@ py_str_to_c_str(PyObject *value, const char *encoding) Py_TYPE(value)->tp_name); return NULL; } + +int +py_list_to_opts(PyObject *py_paths, git_diff_options *opts) +{ + int i; + int paths_length = 0; + PyObject *py_path = NULL; + + if (py_paths == NULL) + return 1; + + if (py_paths == Py_None) + return 1; + + if (!PyObject_TypeCheck(py_paths, &PyList_Type)) { + PyErr_SetObject(PyExc_TypeError, py_paths); + return -1; + } + + paths_length = PyList_Size(py_paths); + for (i = 0; i < paths_length; i++) { + py_path = PyList_GetItem(py_paths, i); + if (!PyObject_TypeCheck(py_path, &PyString_Type)) { + PyErr_SetObject(PyExc_TypeError, py_path); + return -1; + } + } + + opts->pathspec.count = paths_length; + opts->pathspec.strings = (char **) PyMem_Malloc(paths_length * sizeof (char *)); + for (i = 0; i < paths_length; i++) { + py_path = PyList_GetItem(py_paths, i); + opts->pathspec.strings[i] = PyString_AsString(py_path); + } + + return 0; +} + +int +free_opts_pathspec(PyObject *py_paths, git_diff_options *opts) +{ + if (py_paths == NULL) + return 1; + + if (py_paths == Py_None) + return 1; + + PyMem_Free(opts->pathspec.strings); + return 0; +} diff --git a/test/data/testrepo.git/refs/heads/test b/test/data/testrepo.git/refs/heads/test new file mode 100644 index 000000000..436950fb2 --- /dev/null +++ b/test/data/testrepo.git/refs/heads/test @@ -0,0 +1 @@ +784855caf26449a1914d2cf62d12b9374d76ae78 diff --git a/test/test_blob.py b/test/test_blob.py index 8105455f7..d1a6897e9 100644 --- a/test/test_blob.py +++ b/test/test_blob.py @@ -53,6 +53,7 @@ def test_read_blob(self): sha = blob.oid.hex self.assertEqual(sha, BLOB_SHA) self.assertTrue(isinstance(blob, pygit2.Blob)) + self.assertFalse(blob.binary) self.assertEqual(pygit2.GIT_OBJ_BLOB, blob.type) self.assertEqual(BLOB_CONTENT, blob.data) self.assertEqual(len(BLOB_CONTENT), blob.size) @@ -93,13 +94,12 @@ def test_create_blob_fromworkdir(self): def test_create_blob_outside_workdir(self): - path = join(dirname(__file__), 'data', self.repo_dir + '.tar') + path = __file__ self.assertRaises(KeyError, self.repo.create_blob_fromworkdir, path) def test_create_blob_fromdisk(self): - path = join(dirname(__file__), 'data', self.repo_dir + '.tar') - blob_oid = self.repo.create_blob_fromdisk(path) + blob_oid = self.repo.create_blob_fromdisk(__file__) blob = self.repo[blob_oid] self.assertTrue(isinstance(blob, pygit2.Blob)) diff --git a/test/test_branch.py b/test/test_branch.py index 8dc62cb1a..a9717b27c 100644 --- a/test/test_branch.py +++ b/test/test_branch.py @@ -138,7 +138,7 @@ def test_lookup_branch_remote(self): def test_listall_branches(self): branches = sorted(self.repo.listall_branches(pygit2.GIT_BRANCH_REMOTE)) - self.assertEqual(branches, ['origin/master']) + self.assertEqual(branches, ['origin/master', 'origin/test']) def test_branch_remote_name(self): self.repo.remotes[0].fetch() diff --git a/test/test_commit.py b/test/test_commit.py index 13acf982e..55a690677 100644 --- a/test/test_commit.py +++ b/test/test_commit.py @@ -113,7 +113,7 @@ def test_new_commit_encoding(self): self.assertEqual(GIT_OBJ_COMMIT, commit.type) self.assertEqual('iso-8859-1', commit.message_encoding) - self.assertEqual(message, commit.message) + self.assertEqual(message.encode(encoding), commit.raw_message) self.assertEqual(12346, commit.commit_time) self.assertEqualSignature(committer, commit.committer) self.assertEqualSignature(author, commit.author) @@ -134,6 +134,39 @@ def test_modify_commit(self): self.assertRaises(AttributeError, setattr, commit, 'tree', None) self.assertRaises(AttributeError, setattr, commit, 'parents', None) + def test_is_changed(self): + commit = self.repo['c2792cfa289ae6321ecf2cd5806c2194b0fd070c'] + self.assertRaises(TypeError, commit.is_changed, b'a') + self.assertEqual(commit.is_changed([b'a']), [1]) + self.assertEqual(commit.is_changed([b'c']), [0]) + self.assertEqual(commit.is_changed([b'a'], no_merges=True), [1]) + self.assertEqual(commit.is_changed([b'c'], no_merges=True), [0]) + self.assertEqual(commit.is_changed([b'a', b'b']), [1, 1]) + self.assertEqual(commit.is_changed([b'a', b'c']), [1, 0]) + self.assertEqual(commit.is_changed([b'a'], no_diff=True), [1]) + self.assertEqual(commit.is_changed([b'c'], no_diff=True), [0]) + self.assertEqual(commit.is_changed([b'a'], no_merges=True, no_diff=True), [1]) + self.assertEqual(commit.is_changed([b'c'], no_merges=True, no_diff=True), [0]) + self.assertEqual(commit.is_changed([b'a', b'b'], no_diff=True), [1, 1]) + self.assertEqual(commit.is_changed([b'a', b'c'], no_diff=True), [1, 0]) + + self.assertEqual(commit.is_changed([b'a', b'b'], thread=True), [1, 1]) + self.assertEqual(commit.is_changed([b'a', b'c'], thread=True), [1, 0]) + + commit = self.repo['f5e5aa4e36ab0fe62ee1ccc6eb8f79b866863b87'] + self.assertEqual(commit.is_changed([b'lorem']), [1]) + self.assertEqual(commit.is_changed([b'a']), [0]) + self.assertEqual(commit.is_changed([b'lorem'], no_merges=True), [1]) + self.assertEqual(commit.is_changed([b'a'], no_merges=True), [0]) + self.assertEqual(commit.is_changed([b'lorem', b'a']), [1, 0]) + self.assertEqual(commit.is_changed([b'lorem'], no_diff=True), [1]) + self.assertEqual(commit.is_changed([b'a'], no_diff=True), [0]) + self.assertEqual(commit.is_changed([b'lorem'], no_merges=True, no_diff=True), [1]) + self.assertEqual(commit.is_changed([b'a'], no_merges=True, no_diff=True), [0]) + self.assertEqual(commit.is_changed([b'lorem', b'a'], no_diff=True), [1, 0]) + + self.assertEqual(commit.is_changed([b'lorem', b'a'], thread=True), [1, 0]) + if __name__ == '__main__': unittest.main() diff --git a/test/test_config.py b/test/test_config.py index a8afe86cb..cedb22282 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -46,6 +46,7 @@ def foreach_test_wrapper(key, name, lst): class ConfigTest(utils.RepoTestCase): def tearDown(self): + super(ConfigTest, self).tearDown() try: os.remove(CONFIG_FILENAME) except OSError: @@ -117,6 +118,7 @@ def test_read(self): new_file = open(CONFIG_FILENAME, "w") new_file.write("[this]\n\tthat = foobar\n\tthat = foobeer\n") + new_file.write("[something \"other\"]\n\there = false") new_file.close() config.add_file(CONFIG_FILENAME, 0) diff --git a/test/test_diff.py b/test/test_diff.py index 697b8be19..52ca9e8ad 100644 --- a/test/test_diff.py +++ b/test/test_diff.py @@ -176,6 +176,8 @@ def _test(diff): self.assertEqual(patch.old_file_path, 'a') self.assertEqual(patch.new_file_path, 'a') + self.assertEqual(patch.binary, False) + _test(commit_a.tree.diff_to_tree(commit_b.tree)) _test(self.repo.diff(COMMIT_SHA1_1, COMMIT_SHA1_2)) @@ -253,6 +255,7 @@ def test_diff_patch(self): commit_b = self.repo[COMMIT_SHA1_2] diff = commit_a.tree.diff_to_tree(commit_b.tree) + self.assertEqual(diff.size, len([patch for patch in diff])) self.assertEqual(diff.patch, PATCH) def test_diff_oids(self): diff --git a/test/test_remote.py b/test/test_remote.py index a4a125966..f5d07a14e 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -89,6 +89,23 @@ def test_refspec(self): # self.assertEqual(new_fetchspec[0], refspec[0]) # self.assertEqual(new_fetchspec[1], refspec[1]) + # FIXME: compatibility + # FIXME: create spec class + fetchspec = remote.fetchspec[0] + spec = '+%s:%s' % (REMOTE_FETCHSPEC_SRC, REMOTE_FETCHSPEC_DST) + self.assertEqual(spec, fetchspec) + #self.assertEqual(REMOTE_FETCHSPEC_SRC, remote.fetchspec[0]) + #self.assertEqual(REMOTE_FETCHSPEC_DST, remote.fetchspec[1]) + + # FIXME: compatibility + new_fetchspec = ('refs/foo/*', 'refs/remotes/foo/*') + remote.fetchspec = new_fetchspec + spec = '+%s:%s' % new_fetchspec + fetchspec = remote.fetchspec[1] + self.assertEqual(spec, fetchspec) + #self.assertEqual(new_fetchspec[0], remote.fetchspec[0]) + #self.assertEqual(new_fetchspec[1], remote.fetchspec[1]) + def test_remote_list(self): self.assertEqual(1, len(self.repo.remotes)) @@ -124,5 +141,45 @@ def test_fetch(self): self.assertEqual(stats['received_objects'], REMOTE_REPO_OBJECTS) +class PushTestCase(unittest.TestCase): + def setUp(self): + self.origin_ctxtmgr = utils.TemporaryRepository(('git', 'testrepo.git')) + self.clone_ctxtmgr = utils.TemporaryRepository(('git', 'testrepo.git')) + self.origin = pygit2.Repository(self.origin_ctxtmgr.__enter__()) + self.clone = pygit2.Repository(self.clone_ctxtmgr.__enter__()) + self.remote = self.clone.create_remote('origin', self.origin.path) + + def tearDown(self): + self.origin_ctxtmgr.__exit__(None, None, None) + self.clone_ctxtmgr.__exit__(None, None, None) + + def test_push_fast_forward_commits_to_remote_succeeds(self): + tip = self.clone[self.clone.head.target] + oid = self.clone.create_commit( + 'refs/heads/master', tip.author, tip.author, 'empty commit', + tip.tree.oid, [tip.oid] + ) + self.remote.push('refs/heads/master') + self.assertEqual(self.origin[self.origin.head.target].oid, oid) + + def test_push_when_up_to_date_succeeds(self): + self.remote.push('refs/heads/master') + origin_tip = self.origin[self.origin.head.target].oid + clone_tip = self.clone[self.clone.head.target].oid + self.assertEqual(origin_tip, clone_tip) + + def test_push_non_fast_forward_commits_to_remote_fails(self): + tip = self.origin[self.origin.head.target] + oid = self.origin.create_commit( + 'refs/heads/master', tip.author, tip.author, 'some commit', + tip.tree.oid, [tip.oid] + ) + tip = self.clone[self.clone.head.target] + oid = self.clone.create_commit( + 'refs/heads/master', tip.author, tip.author, 'other commit', + tip.tree.oid, [tip.oid] + ) + self.assertRaises(pygit2.GitError, self.remote.push, 'refs/heads/master') + if __name__ == '__main__': unittest.main() diff --git a/test/test_repository.py b/test/test_repository.py index 87c9fc704..272ef388d 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -40,8 +40,11 @@ # 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 Oid, Reference, hashfile +from pygit2 import ( + init_repository, clone_repository, discover_repository, + Reference, hashfile, is_repository +) +from pygit2 import Oid import pygit2 from . import utils @@ -65,7 +68,7 @@ def test_head(self): head = self.repo.head self.assertEqual(HEAD_SHA, head.target.hex) self.assertEqual(type(head), Reference) - self.assertFalse(self.repo.head_is_orphaned) + self.assertFalse(self.repo.head_is_unborn) self.assertFalse(self.repo.head_is_detached) def test_read(self): @@ -143,7 +146,7 @@ def test_lookup_commit_prefix(self): def test_get_path(self): directory = realpath(self.repo.path) - expected = realpath(join(self._temp_dir, 'testrepo.git')) + expected = realpath(self.repo_path) self.assertEqual(directory, expected) def test_get_workdir(self): @@ -168,7 +171,6 @@ def test_hashfile(self): written_sha1 = self.repo.create_blob(data) self.assertEqual(hashed_sha1, written_sha1) - class RepositoryTest_II(utils.RepoTestCase): def test_is_empty(self): @@ -179,12 +181,12 @@ def test_is_bare(self): def test_get_path(self): directory = realpath(self.repo.path) - expected = realpath(join(self._temp_dir, 'testrepo', '.git')) + expected = realpath(join(self.repo_path, '.git')) self.assertEqual(directory, expected) def test_get_workdir(self): directory = realpath(self.repo.workdir) - expected = realpath(join(self._temp_dir, 'testrepo')) + expected = realpath(self.repo_path) self.assertEqual(directory, expected) def test_checkout_ref(self): @@ -238,6 +240,15 @@ def test_merge_base(self): self.assertEqual(commit.hex, 'acecd5ea2924a4b900e7e149496e1f4b57976e51') +class IsRepositoryTest(utils.RepoTestCase): + + def test_is_repository(self): + directory = realpath(self.repo.workdir) + + self.assertRaises(TypeError, is_repository) + self.assertRaises(TypeError, is_repository, '', '') + self.assertFalse(is_repository("sadgasdfasdfasfdasfasdfsafasdfasdf")) + self.assertTrue(is_repository(directory)) class NewRepositoryTest(utils.NoRepoTestCase): @@ -294,7 +305,7 @@ def test_is_base(self): self.assertFalse(self.repo.is_bare) def test_head(self): - self.assertTrue(self.repo.head_is_orphaned) + self.assertTrue(self.repo.head_is_unborn) self.assertFalse(self.repo.head_is_detached) @@ -322,56 +333,37 @@ def test_clone_remote_name(self): self.assertEqual(repo.remotes[0].name, "custom_remote") - # FIXME The tests below are commented because they are broken: - # - # - test_clone_push_url: Passes, but does nothing useful. - # - # - test_clone_fetch_spec: Segfaults because of a bug in libgit2 0.19, - # this has been fixed already, so wait for 0.20 - # - # - test_clone_push_spec: Passes, but does nothing useful. - # - # - test_clone_checkout_branch: Fails, because the test fixture does not - # have any branch named "test" - -# def test_clone_push_url(self): -# repo_path = "./test/data/testrepo.git/" -# repo = clone_repository( -# repo_path, self._temp_dir, push_url="custom_push_url" -# ) -# self.assertFalse(repo.is_empty) -# # FIXME: When pygit2 supports retrieving the pushurl parameter, -# # enable this test -# # self.assertEqual(repo.remotes[0].pushurl, "custom_push_url") - -# def test_clone_fetch_spec(self): -# repo_path = "./test/data/testrepo.git/" -# repo = clone_repository(repo_path, self._temp_dir, -# fetch_spec="refs/heads/test") -# self.assertFalse(repo.is_empty) -# # FIXME: When pygit2 retrieve the fetchspec we passed to git clone. -# # fetchspec seems to be going through, but the Repository class is -# # not getting it. -# # self.assertEqual(repo.remotes[0].fetchspec, "refs/heads/test") - -# def test_clone_push_spec(self): -# repo_path = "./test/data/testrepo.git/" -# repo = clone_repository(repo_path, self._temp_dir, -# push_spec="refs/heads/test") -# self.assertFalse(repo.is_empty) -# # FIXME: When pygit2 supports retrieving the pushspec parameter, -# # enable this test -# # not sure how to test this either... couldn't find pushspec -# # self.assertEqual(repo.remotes[0].fetchspec, "refs/heads/test") - -# def test_clone_checkout_branch(self): -# repo_path = "./test/data/testrepo.git/" -# repo = clone_repository(repo_path, self._temp_dir, -# checkout_branch="test") -# self.assertFalse(repo.is_empty) -# # FIXME: When pygit2 supports retrieving the current branch, -# # enable this test -# # self.assertEqual(repo.remotes[0].current_branch, "test") + def test_clone_fetch_spec(self): + repo_path = "./test/data/testrepo.git/" + repo = clone_repository( + repo_path, self._temp_dir, fetch_spec="+refs/heads/master:refs/heads/test" + ) + self.assertFalse(repo.is_empty) + # FIXME: When pygit2 retrieve the fetchspec we passed to git clone. + # fetchspec seems to be going through, but the Repository class is + # not getting it. + # self.assertEqual(repo.remotes[0].fetchspec, "refs/heads/test") + + def test_clone_push_spec(self): + repo_path = "./test/data/testrepo.git/" + repo = clone_repository( + repo_path, self._temp_dir, push_spec="+refs/heads/master:refs/heads/test" + ) + self.assertFalse(repo.is_empty) + # FIXME: When pygit2 supports retrieving the pushspec parameter, + # enable this test + # not sure how to test this either... couldn't find pushspec + # self.assertEqual(repo.remotes[0].fetchspec, "refs/heads/test") + + def test_clone_checkout_branch(self): + repo_path = "./test/data/testrepo.git/" + repo = clone_repository( + repo_path, self._temp_dir, checkout_branch="test" + ) + self.assertFalse(repo.is_empty) + # FIXME: When pygit2 supports retrieving the current branch, + # enable this test + # self.assertEqual(repo.remotes[0].current_branch, "test") if __name__ == '__main__': diff --git a/test/test_signature.py b/test/test_signature.py index cf21b33f1..4131c6c83 100644 --- a/test/test_signature.py +++ b/test/test_signature.py @@ -41,8 +41,12 @@ def test_default(self): 'Foo', 'foo@example.com', 1322174594, 60) encoding = signature._encoding self.assertEqual(encoding, 'ascii') - self.assertEqual(signature.name, signature._name.decode(encoding)) - self.assertEqual(signature.name.encode(encoding), signature._name) + self.assertEqual(signature.name, signature.raw_name.decode(encoding)) + self.assertEqual(signature.name.encode(encoding), signature.raw_name) + self.assertEqual(signature.email, + signature.raw_email.decode(encoding)) + self.assertEqual(signature.email.encode(encoding), + signature.raw_email) def test_ascii(self): self.assertRaises(UnicodeEncodeError, @@ -53,16 +57,24 @@ def test_latin1(self): signature = Signature( 'Foo Ibáñez', 'foo@example.com', encoding=encoding) self.assertEqual(encoding, signature._encoding) - self.assertEqual(signature.name, signature._name.decode(encoding)) - self.assertEqual(signature.name.encode(encoding), signature._name) + self.assertEqual(signature.name, signature.raw_name.decode(encoding)) + self.assertEqual(signature.name.encode(encoding), signature.raw_name) + self.assertEqual(signature.email, + signature.raw_email.decode(encoding)) + self.assertEqual(signature.email.encode(encoding), + signature.raw_email) def test_now(self): encoding = 'utf-8' signature = Signature( 'Foo Ibáñez', 'foo@example.com', encoding=encoding) self.assertEqual(encoding, signature._encoding) - self.assertEqual(signature.name, signature._name.decode(encoding)) - self.assertEqual(signature.name.encode(encoding), signature._name) + self.assertEqual(signature.name, signature.raw_name.decode(encoding)) + self.assertEqual(signature.name.encode(encoding), signature.raw_name) + self.assertEqual(signature.email, + signature.raw_email.decode(encoding)) + self.assertEqual(signature.email.encode(encoding), + signature.raw_email) self.assertTrue(abs(signature.time - time.time()) < 5) diff --git a/test/test_tag.py b/test/test_tag.py index 12d89f055..9530eb5cc 100644 --- a/test/test_tag.py +++ b/test/test_tag.py @@ -89,6 +89,11 @@ def test_modify_tag(self): self.assertRaises(AttributeError, setattr, tag, 'tagger', tagger) self.assertRaises(AttributeError, setattr, tag, 'message', message) + def test_get_object(self): + repo = self.repo + tag = repo[TAG_SHA] + self.assertEqual(repo[tag.target].oid, tag.get_object().oid) + if __name__ == '__main__': unittest.main() diff --git a/test/utils.py b/test/utils.py index 10a4e1719..fb7ca58f9 100644 --- a/test/utils.py +++ b/test/utils.py @@ -65,6 +65,27 @@ def rmtree(path): shutil.rmtree(path, onerror=onerror) +class TemporaryRepository(object): + def __init__(self, repo_spec): + self.repo_spec = repo_spec + + def __enter__(self): + container, name = self.repo_spec + repo_path = os.path.join(os.path.dirname(__file__), 'data', name) + self.temp_dir = tempfile.mkdtemp() + temp_repo_path = os.path.join(self.temp_dir, name) + if container == 'tar': + tar = tarfile.open('.'.join((repo_path, 'tar'))) + tar.extractall(self.temp_dir) + tar.close() + else: + shutil.copytree(repo_path, temp_repo_path) + return temp_repo_path + + def __exit__(self, exc_type, exc_value, traceback): + rmtree(self.temp_dir) + + class NoRepoTestCase(unittest.TestCase): def setUp(self): @@ -103,45 +124,33 @@ def assertEqualSignature(self, a, b): self.assertEqual(a.offset, b.offset) -class BareRepoTestCase(NoRepoTestCase): - - repo_dir = 'testrepo.git' - +class AutoRepoTestCase(NoRepoTestCase): def setUp(self): - super(BareRepoTestCase, self).setUp() - - repo_dir = self.repo_dir - repo_path = os.path.join(os.path.dirname(__file__), 'data', repo_dir) - temp_repo_path = os.path.join(self._temp_dir, repo_dir) - - shutil.copytree(repo_path, temp_repo_path) - - self.repo = pygit2.Repository(temp_repo_path) + super(AutoRepoTestCase, self).setUp() + self.repo_ctxtmgr = TemporaryRepository(self.repo_spec) + self.repo_path = self.repo_ctxtmgr.__enter__() + self.repo = pygit2.Repository(self.repo_path) + def tearDown(self): + self.repo_ctxtmgr.__exit__(None, None, None) + super(AutoRepoTestCase, self).tearDown() -class RepoTestCase(NoRepoTestCase): - repo_dir = 'testrepo' +class BareRepoTestCase(AutoRepoTestCase): - def setUp(self): - super(RepoTestCase, self).setUp() + repo_spec = 'git', 'testrepo.git' - repo_dir = self.repo_dir - repo_path = os.path.join(os.path.dirname(__file__), 'data', repo_dir) - temp_repo_path = os.path.join(self._temp_dir, repo_dir, '.git') - tar = tarfile.open(repo_path + '.tar') - tar.extractall(self._temp_dir) - tar.close() +class RepoTestCase(AutoRepoTestCase): - self.repo = pygit2.Repository(temp_repo_path) + repo_spec = 'tar', 'testrepo' -class DirtyRepoTestCase(RepoTestCase): +class DirtyRepoTestCase(AutoRepoTestCase): - repo_dir = 'dirtyrepo' + repo_spec = 'tar', 'dirtyrepo' -class EmptyRepoTestCase(RepoTestCase): +class EmptyRepoTestCase(AutoRepoTestCase): - repo_dir = 'emptyrepo' + repo_spec = 'tar', 'emptyrepo' diff --git a/vendor/libgit2 b/vendor/libgit2 new file mode 160000 index 000000000..5a284edca --- /dev/null +++ b/vendor/libgit2 @@ -0,0 +1 @@ +Subproject commit 5a284edca42dd75cf62b3fe75020819fcfcea9f3