diff --git a/docs/diff.rst b/docs/diff.rst index 4d50ed8..d81fa9c 100644 --- a/docs/diff.rst +++ b/docs/diff.rst @@ -2,27 +2,34 @@ Diff ********************************************************************** -A diff shows the changes between trees, an index or the working dir:: +.. contents:: - >>> head = repo.revparse_single('HEAD') +A diff shows the changes between trees, an index or the working dir. - # Diff two trees - >>> t0 = head.tree - >>> t1 = head.parents[0].tree - >>> diff = t1.diff(t0) +.. automethod:: pygit2.Repository.diff - # Diff a tree with the index - >>> diff = head.tree.diff(repo.index) +Examples - # Diff a tree with the current working dir - >>> diff = head.tree.diff() +.. code-block:: python + + # Changes between commits + >>> t0 = revparse_single('HEAD') + >>> t1 = revparse_single('HEAD^') + >>> repo.diff(t0, t1) + >>> t0.diff(t1) # equivalent + >>> repo.diff('HEAD', 'HEAD^') # equivalent # Get all patches for a diff - >>> t0 = repo.revparse_single('HEAD^').tree - >>> t1 = repo.revparse_single('HEAD~3').tree - >>> diff = t0.diff(t1) + >>> diff = repo.diff('HEAD^', 'HEAD~3') >>> patches = [p for p in diff] + # Diffing the empty tree + >>> tree = revparse_single('HEAD').tree + >>> tree.diff_to_tree() + + # Diff empty tree to a tree + >>> tree = revparse_single('HEAD').tree + >>> tree.diff_to_tree(swap=True) The Diff type ==================== diff --git a/docs/objects.rst b/docs/objects.rst index 04f466e..970a0c3 100644 --- a/docs/objects.rst +++ b/docs/objects.rst @@ -159,7 +159,9 @@ interfaces. Return an iterator over the entries of the tree. -.. automethod:: pygit2.Tree.diff +.. automethod:: pygit2.Tree.diff_to_tree +.. automethod:: pygit2.Tree.diff_to_workdir +.. automethod:: pygit2.Tree.diff_to_index Tree entries ------------ diff --git a/docs/working-copy.rst b/docs/working-copy.rst index 3491552..bb10f71 100644 --- a/docs/working-copy.rst +++ b/docs/working-copy.rst @@ -33,7 +33,8 @@ The Index type .. automethod:: pygit2.Index.write .. automethod:: pygit2.Index.read_tree .. automethod:: pygit2.Index.write_tree -.. automethod:: pygit2.Index.diff +.. automethod:: pygit2.Index.diff_to_tree +.. automethod:: pygit2.Index.diff_to_workdir The IndexEntry type diff --git a/pygit2/repository.py b/pygit2/repository.py index 93f8f54..3d8521b 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -31,8 +31,8 @@ from string import hexdigits # Import from pygit2 from _pygit2 import Repository as _Repository from _pygit2 import Oid, GIT_OID_HEXSZ, GIT_OID_MINPREFIXLEN -from _pygit2 import GIT_CHECKOUT_SAFE_CREATE -from _pygit2 import Reference +from _pygit2 import GIT_CHECKOUT_SAFE_CREATE, GIT_DIFF_NORMAL +from _pygit2 import Reference, Tree, Commit, Blob class Repository(_Repository): @@ -128,3 +128,90 @@ class Repository(_Repository): treeish = self[oid] self.checkout_tree(treeish, strategy) self.head = refname + + + # + # Diff + # + def diff(self, a=None, b=None, cached=False, flags=GIT_DIFF_NORMAL, + context_lines=3, interhunk_lines=0): + """ + Show changes between the working tree and the index or a tree, + changes between the index and a tree, changes between two trees, or + changes between two blobs. + + Keyword arguments: + + cached + use staged changes instead of workdir + + flag + a GIT_DIFF_* constant + + context_lines + the number of unchanged lines that define the boundary + of a hunk (and to display before and after)\n" + + interhunk_lines + the maximum number of unchanged lines between hunk + boundaries before the hunks will be merged into a one + + Examples:: + + # Changes in the working tree not yet staged for the next commit + >>> diff() + + # Changes between the index and your last commit + >>> diff(cached=True) + + # Changes in the working tree since your last commit + >>> diff('HEAD') + + # Changes between commits + >>> t0 = revparse_single('HEAD') + >>> t1 = revparse_single('HEAD^') + >>> diff(t0, t1) + >>> diff('HEAD', 'HEAD^') # equivalent + + If you want to diff a tree against an empty tree, use the low level + API (Tree.diff_to_tree()) directly. + """ + + def treeish_to_tree(obj): + try: + obj = self.revparse_single(obj) + except: + pass + + if isinstance(obj, Commit): + return obj.tree + elif isinstance(obj, Reference): + oid = obj.resolve().target + return self[oid] + + 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] + + # Case 1: Diff tree to tree + if isinstance(a, Tree) and isinstance(b, Tree): + return a.diff_to_tree(b, **dict(zip(opt_keys, opt_values))) + + # Case 2: Index to workdir + elif a is None and b is None: + return self.index.diff_to_workdir(*opt_values) + + # Case 3: Diff tree to index or workdir + elif isinstance(a, Tree) and b is None: + if cached: + return a.diff_to_index(self.index, *opt_values) + else: + return a.diff_to_workdir(*opt_values) + + # Case 4: Diff blob to blob + if isinstance(a, Blob) and isinstance(b, Blob): + raise NotImplementedError('git_diff_blob_to_blob()') + + raise ValueError("Only blobs and treeish can be diffed") diff --git a/src/diff.c b/src/diff.c index 8e87c70..f46d4bd 100644 --- a/src/diff.c +++ b/src/diff.c @@ -42,6 +42,21 @@ extern PyTypeObject HunkType; PyTypeObject PatchType; +PyObject* +wrap_diff(git_diff_list *diff, Repository *repo) +{ + Diff *py_diff; + + py_diff = PyObject_New(Diff, &DiffType); + if (py_diff) { + Py_INCREF(repo); + py_diff->repo = repo; + py_diff->list = diff; + } + + return (PyObject*) py_diff; +} + PyObject* diff_get_patch_byindex(git_diff_list* list, size_t idx) { @@ -50,9 +65,11 @@ diff_get_patch_byindex(git_diff_list* list, size_t idx) git_diff_patch* patch = NULL; size_t i, j, hunk_amounts, lines_in_hunk, line_len, header_len; const char* line, *header; + char line_origin; int err; Hunk *py_hunk = NULL; Patch *py_patch = NULL; + PyObject *py_line_origin=NULL, *py_line=NULL; err = git_diff_get_patch(&patch, &delta, list, idx); if (err < 0) @@ -84,18 +101,24 @@ diff_get_patch_byindex(git_diff_list* list, size_t idx) py_hunk->new_start = range->new_start; py_hunk->new_lines = range->new_lines; - py_hunk->lines = PyList_New(lines_in_hunk + 1); - PyList_SetItem(py_hunk->lines, 0, - to_unicode_n(header, header_len, NULL, NULL)); - for (j=1; j < lines_in_hunk + 1; ++j) { - err = git_diff_patch_get_line_in_hunk(&py_hunk->origin, - &line, &line_len, NULL, NULL, patch, i, j - 1); + py_hunk->lines = PyList_New(lines_in_hunk); + for (j=0; j < lines_in_hunk; ++j) { + err = git_diff_patch_get_line_in_hunk(&line_origin, + &line, &line_len, NULL, NULL, patch, i, j); if (err < 0) goto cleanup; + py_line_origin = to_unicode_n(&line_origin, 1, NULL, NULL); + py_line = to_unicode_n(line, line_len, NULL, NULL); PyList_SetItem(py_hunk->lines, j, - to_unicode_n(line, line_len, NULL, NULL)); + Py_BuildValue("OO", + py_line_origin, + py_line + ) + ); + Py_DECREF(py_line_origin); + Py_DECREF(py_line); } PyList_SetItem((PyObject*) py_patch->hunks, i, @@ -279,7 +302,6 @@ Hunk_dealloc(Hunk *self) } PyMemberDef Hunk_members[] = { - MEMBER(Hunk, origin, T_CHAR, "origin."), MEMBER(Hunk, old_start, T_INT, "Old start."), MEMBER(Hunk, old_lines, T_INT, "Old lines."), MEMBER(Hunk, new_start, T_INT, "New start."), diff --git a/src/diff.h b/src/diff.h index b045593..75e6333 100644 --- a/src/diff.h +++ b/src/diff.h @@ -41,4 +41,6 @@ PyObject* Diff_changes(Diff *self); PyObject* Diff_patch(Diff *self); +PyObject* wrap_diff(git_diff_list *diff, Repository *repo); + #endif diff --git a/src/index.c b/src/index.c index c62e5f2..3b637a8 100644 --- a/src/index.c +++ b/src/index.c @@ -31,6 +31,7 @@ #include "types.h" #include "utils.h" #include "oid.h" +#include "diff.h" #include "index.h" extern PyTypeObject IndexType; @@ -114,54 +115,83 @@ Index_clear(Index *self) } -PyDoc_STRVAR(Index_diff__doc__, - "diff([tree]) -> Diff\n" +PyDoc_STRVAR(Index_diff_to_workdir__doc__, + "diff_to_workdir([flag, context_lines, interhunk_lines]) -> Diff\n" "\n" "Return a :py:class:`~pygit2.Diff` object with the differences between the\n" - "index and the working copy. If a :py:class:`~pygit2.Tree` object is\n" - "passed, return the diferences between the index and the given tree."); + "index and the working copy.\n" + "\n" + "Arguments:\n" + "\n" + "flag: 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" + "\n" + "interhunk_lines: the maximum number of unchanged lines between hunk\n" + " boundaries before the hunks will be merged into a one.\n"); PyObject * -Index_diff(Index *self, PyObject *args) +Index_diff_to_workdir(Index *self, PyObject *args) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; git_diff_list *diff; int err; - Diff *py_diff; - PyObject *py_obj = NULL; - - if (!PyArg_ParseTuple(args, "|O", &py_obj)) + if (!PyArg_ParseTuple(args, "|IHH", &opts.flags, &opts.context_lines, + &opts.interhunk_lines)) return NULL; - if (py_obj == NULL) { - err = git_diff_index_to_workdir( - &diff, - self->repo->repo, - self->index, - &opts); - } else if (PyObject_TypeCheck(py_obj, &TreeType)) { - err = git_diff_tree_to_index( - &diff, - self->repo->repo, - ((Tree *)py_obj)->tree, - self->index, - &opts); - } else { - PyErr_SetObject(PyExc_TypeError, py_obj); - return NULL; - } + err = git_diff_index_to_workdir( + &diff, + self->repo->repo, + self->index, + &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 wrap_diff(diff, self->repo); +} - return (PyObject*)py_diff; +PyDoc_STRVAR(Index_diff_to_tree__doc__, + "diff_to_tree(tree [, flag, context_lines, interhunk_lines]) -> Diff\n" + "\n" + "Return a :py:class:`~pygit2.Diff` object with the differences between the\n" + "index and the given tree.\n" + "\n" + "Arguments:\n" + "\n" + "tree: the tree to diff.\n" + "\n" + "flag: 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" + "\n" + "interhunk_lines: the maximum number of unchanged lines between hunk\n" + " boundaries before the hunks will be merged into a one.\n"); + +PyObject * +Index_diff_to_tree(Index *self, PyObject *args) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff_list *diff; + git_repository* repo; + int err; + + Tree *py_tree = NULL; + + if (!PyArg_ParseTuple(args, "O!|IHH", &TreeType, &py_tree, &opts.flags, + &opts.context_lines, &opts.interhunk_lines)) + return NULL; + + repo = git_tree_owner(py_tree->tree); + err = git_diff_tree_to_index(&diff, repo, py_tree->tree, self->index, &opts); + if (err < 0) + return Error_set(err); + + return wrap_diff(diff, self->repo); } @@ -393,7 +423,8 @@ PyMethodDef Index_methods[] = { METHOD(Index, add, METH_VARARGS), METHOD(Index, remove, METH_VARARGS), METHOD(Index, clear, METH_NOARGS), - METHOD(Index, diff, METH_VARARGS), + METHOD(Index, diff_to_workdir, METH_VARARGS), + METHOD(Index, diff_to_tree, METH_VARARGS), METHOD(Index, _find, METH_O), METHOD(Index, read, METH_NOARGS), METHOD(Index, write, METH_NOARGS), diff --git a/src/pygit2.c b/src/pygit2.c index 8362891..94bb06d 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -383,6 +383,14 @@ moduleinit(PyObject* m) ADD_CONSTANT_INT(m, GIT_DIFF_INCLUDE_UNTRACKED) ADD_CONSTANT_INT(m, GIT_DIFF_INCLUDE_UNMODIFIED) ADD_CONSTANT_INT(m, GIT_DIFF_RECURSE_UNTRACKED_DIRS) + ADD_CONSTANT_INT(m, GIT_DIFF_RECURSE_UNTRACKED_DIRS) + ADD_CONSTANT_INT(m, GIT_DIFF_DISABLE_PATHSPEC_MATCH) + ADD_CONSTANT_INT(m, GIT_DIFF_DELTAS_ARE_ICASE) + ADD_CONSTANT_INT(m, GIT_DIFF_INCLUDE_UNTRACKED_CONTENT) + ADD_CONSTANT_INT(m, GIT_DIFF_SKIP_BINARY_CHECK) + ADD_CONSTANT_INT(m, GIT_DIFF_INCLUDE_TYPECHANGE) + ADD_CONSTANT_INT(m, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) + ADD_CONSTANT_INT(m, GIT_DIFF_RECURSE_IGNORED_DIRS) /* Flags for diff find similar */ /* --find-renames */ ADD_CONSTANT_INT(m, GIT_DIFF_FIND_RENAMES) diff --git a/src/repository.c b/src/repository.c index 9f9893a..c2c8b94 100644 --- a/src/repository.c +++ b/src/repository.c @@ -497,6 +497,9 @@ Repository_config__get__(Repository *self) py_config->config = config; self->config = (PyObject*)py_config; + // We need 2 refs here. + // One is returned, one is keep internally. + Py_INCREF(self->config); } else { Py_INCREF(self->config); } diff --git a/src/tree.c b/src/tree.c index a3ac026..378792d 100644 --- a/src/tree.c +++ b/src/tree.c @@ -33,6 +33,7 @@ #include "repository.h" #include "oid.h" #include "tree.h" +#include "diff.h" extern PyTypeObject TreeType; extern PyTypeObject DiffType; @@ -270,70 +271,138 @@ Tree_getitem(Tree *self, PyObject *value) } -PyDoc_STRVAR(Tree_diff__doc__, - "diff([obj, flags]) -> Diff\n" +PyDoc_STRVAR(Tree_diff_to_workdir__doc__, + "diff_to_workdir([flags, context_lines, interhunk_lines]) -> Diff\n" "\n" - "Get changes between current tree instance with another tree, an index or\n" - "the working dir.\n" + "Show the changes between the :py:class:`~pygit2.Tree` and the workdir.\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" + "flag: a GIT_DIFF_* constant.\n" "\n" - "flags\n" - " TODO"); + "context_lines: the number of unchanged lines that define the boundary\n" + " of a hunk (and to display before and after)\n" + "\n" + "interhunk_lines: the maximum number of unchanged lines between hunk\n" + " boundaries before the hunks will be merged into a one.\n"); PyObject * -Tree_diff(Tree *self, PyObject *args, PyObject *kwds) +Tree_diff_to_workdir(Tree *self, PyObject *args) { 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", NULL}; + git_repository* repo; + int err; - Diff *py_diff; - PyObject *py_obj = NULL; - - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Oii", keywords, - &py_obj, &opts.flags, &empty_tree)) + if (!PyArg_ParseTuple(args, "|IHH", &opts.flags, &opts.context_lines, + &opts.interhunk_lines)) 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 { - PyErr_SetObject(PyExc_TypeError, py_obj); - return NULL; - } + err = git_diff_tree_to_workdir(&diff, repo, self->tree, &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 wrap_diff(diff, self->repo); +} + + +PyDoc_STRVAR(Tree_diff_to_index__doc__, + "diff_to_index(index, [flags, context_lines, interhunk_lines]) -> Diff\n" + "\n" + "Show the changes between the index and a given :py:class:`~pygit2.Tree`.\n" + "\n" + "Arguments:\n" + "\n" + "tree: the :py:class:`~pygit2.Tree` to diff.\n" + "\n" + "flag: 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" + "\n" + "interhunk_lines: the maximum number of unchanged lines between hunk\n" + " boundaries before the hunks will be merged into a one.\n"); + +PyObject * +Tree_diff_to_index(Tree *self, PyObject *args, PyObject *kwds) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff_list *diff; + git_repository* repo; + int err; + + Index *py_idx = NULL; + + if (!PyArg_ParseTuple(args, "O!|IHH", &IndexType, &py_idx, &opts.flags, + &opts.context_lines, + &opts.interhunk_lines)) + return NULL; + + repo = git_tree_owner(self->tree); + err = git_diff_tree_to_index(&diff, repo, self->tree, py_idx->index, &opts); + if (err < 0) + return Error_set(err); + + return wrap_diff(diff, self->repo); +} + + +PyDoc_STRVAR(Tree_diff_to_tree__doc__, + "diff_to_tree([tree, flags, context_lines, interhunk_lines, swap]) -> Diff\n" + "\n" + "Show the changes between two trees\n" + "\n" + "Arguments:\n" + "\n" + "tree: the :py:class:`~pygit2.Tree` to diff. If no tree is given the empty\n" + " tree will be used instead.\n" + "\n" + "flag: 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" + "\n" + "interhunk_lines: the maximum number of unchanged lines between hunk\n" + " boundaries before the hunks will be merged into a one.\n" + "\n" + "swap: instead of diffing a to b. Diff b to a.\n"); + +PyObject * +Tree_diff_to_tree(Tree *self, PyObject *args, PyObject *kwds) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff_list *diff; + git_tree *from, *to, *tmp; + git_repository* repo; + int err, swap = 0; + char *keywords[] = {"obj", "flags", "context_lines", "interhunk_lines", + "swap", NULL}; + + Tree *py_tree = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O!IHHi", keywords, + &TreeType, &py_tree, &opts.flags, + &opts.context_lines, + &opts.interhunk_lines, &swap)) + return NULL; + + repo = git_tree_owner(self->tree); + to = (py_tree == NULL) ? NULL : py_tree->tree; + from = self->tree; + if (swap > 0) { + tmp = from; + from = to; + to = tmp; } - return (PyObject*)py_diff; + err = git_diff_tree_to_tree(&diff, repo, from, to, &opts); + + if (err < 0) + return Error_set(err); + + return wrap_diff(diff, self->repo); } @@ -355,7 +424,9 @@ PyMappingMethods Tree_as_mapping = { }; PyMethodDef Tree_methods[] = { - METHOD(Tree, diff, METH_VARARGS | METH_KEYWORDS), + 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), {NULL} }; diff --git a/src/types.h b/src/types.h index a4ac5ec..ce27142 100644 --- a/src/types.h +++ b/src/types.h @@ -118,7 +118,6 @@ typedef struct { typedef struct { PyObject_HEAD PyObject* lines; - char origin; int old_start; int old_lines; int new_start; diff --git a/test/test_diff.py b/test/test_diff.py index 3246b51..7ff9946 100644 --- a/test/test_diff.py +++ b/test/test_diff.py @@ -33,6 +33,7 @@ import unittest import pygit2 from pygit2 import GIT_DIFF_INCLUDE_UNMODIFIED from . import utils +from itertools import chain COMMIT_SHA1_1 = '5fe808e8953c12735680c257f56600cb0de44b10' @@ -60,7 +61,7 @@ index 297efb8..0000000 -c/d contents """ -DIFF_INDEX_EXPECTED = [ +DIFF_HEAD_TO_INDEX_EXPECTED = [ 'staged_changes', 'staged_changes_file_deleted', 'staged_changes_file_modified', @@ -71,7 +72,7 @@ DIFF_INDEX_EXPECTED = [ 'staged_new_file_modified' ] -DIFF_WORKDIR_EXPECTED = [ +DIFF_HEAD_TO_WORKDIR_EXPECTED = [ 'file_deleted', 'modified_file', 'staged_changes', @@ -83,27 +84,50 @@ DIFF_WORKDIR_EXPECTED = [ 'subdir/modified_file' ] -HUNK_EXPECTED = """@@ -1 +1 @@ -a contents 2 -a contents +DIFF_INDEX_TO_WORK_EXPECTED = [ + 'file_deleted', + 'modified_file', + 'staged_changes_file_deleted', + 'staged_changes_file_modified', + 'staged_new_file_deleted', + 'staged_new_file_modified', + 'subdir/deleted_file', + 'subdir/modified_file' +] + +HUNK_EXPECTED = """- a contents 2 ++ a contents """ class DiffDirtyTest(utils.DirtyRepoTestCase): def test_diff_empty_index(self): repo = self.repo - head = repo[repo.lookup_reference('HEAD').resolve().target] - diff = head.tree.diff(repo.index) + head = repo[repo.lookup_reference('HEAD').resolve().target] + diff = head.tree.diff_to_index(repo.index) files = [patch.new_file_path for patch in diff] - self.assertEqual(DIFF_INDEX_EXPECTED, files) + self.assertEqual(DIFF_HEAD_TO_INDEX_EXPECTED, files) + + diff = repo.diff('HEAD', cached=True) + files = [patch.new_file_path for patch in diff] + self.assertEqual(DIFF_HEAD_TO_INDEX_EXPECTED, files) def test_workdir_to_tree(self): repo = self.repo head = repo[repo.lookup_reference('HEAD').resolve().target] - diff = head.tree.diff() + diff = head.tree.diff_to_workdir() files = [patch.new_file_path for patch in diff] - self.assertEqual(DIFF_WORKDIR_EXPECTED, files) + self.assertEqual(DIFF_HEAD_TO_WORKDIR_EXPECTED, files) + + diff = repo.diff('HEAD') + files = [patch.new_file_path for patch in diff] + self.assertEqual(DIFF_HEAD_TO_WORKDIR_EXPECTED, files) + + def test_index_to_workdir(self): + diff = self.repo.diff() + files = [patch.new_file_path for patch in diff] + self.assertEqual(DIFF_INDEX_TO_WORK_EXPECTED, files) class DiffTest(utils.BareRepoTestCase): @@ -111,13 +135,22 @@ class DiffTest(utils.BareRepoTestCase): def test_diff_invalid(self): commit_a = self.repo[COMMIT_SHA1_1] commit_b = self.repo[COMMIT_SHA1_2] - self.assertRaises(TypeError, commit_a.tree.diff, commit_b) + self.assertRaises(TypeError, commit_a.tree.diff_to_tree, commit_b) + self.assertRaises(TypeError, commit_a.tree.diff_to_index, commit_b) def test_diff_empty_index(self): repo = self.repo head = repo[repo.lookup_reference('HEAD').resolve().target] - diff = head.tree.diff(repo.index) + diff = self.repo.index.diff_to_tree(head.tree) + files = [patch.new_file_path.split('/')[0] for patch in diff] + self.assertEqual([x.name for x in head.tree], files) + + diff = head.tree.diff_to_index(repo.index) + files = [patch.new_file_path.split('/')[0] for patch in diff] + self.assertEqual([x.name for x in head.tree], files) + + diff = repo.diff('HEAD', cached=True) files = [patch.new_file_path.split('/')[0] for patch in diff] self.assertEqual([x.name for x in head.tree], files) @@ -125,41 +158,59 @@ class DiffTest(utils.BareRepoTestCase): commit_a = self.repo[COMMIT_SHA1_1] commit_b = self.repo[COMMIT_SHA1_2] - diff = commit_a.tree.diff(commit_b.tree) + def _test(diff): + # self.assertIsNotNone is 2.7 only + self.assertTrue(diff is not None) + # self.assertIn is 2.7 only + self.assertEqual(2, sum(map(lambda x: len(x.hunks), diff))) - # self.assertIsNotNone is 2.7 only - self.assertTrue(diff is not None) - # self.assertIn is 2.7 only - self.assertEqual(2, sum(map(lambda x: len(x.hunks), diff))) + patch = diff[0] + hunk = patch.hunks[0] + self.assertEqual(hunk.old_start, 1) + self.assertEqual(hunk.old_lines, 1) + self.assertEqual(hunk.new_start, 1) + self.assertEqual(hunk.new_lines, 1) - patch = diff[0] - hunk = patch.hunks[0] - self.assertEqual(hunk.origin, '+') - self.assertEqual(hunk.old_start, 1) - self.assertEqual(hunk.old_lines, 1) - self.assertEqual(hunk.new_start, 1) - self.assertEqual(hunk.new_lines, 1) + self.assertEqual(patch.old_file_path, 'a') + self.assertEqual(patch.new_file_path, 'a') + + _test(commit_a.tree.diff_to_tree(commit_b.tree)) + _test(self.repo.diff(COMMIT_SHA1_1, COMMIT_SHA1_2)) - self.assertEqual(patch.old_file_path, 'a') - self.assertEqual(patch.new_file_path, 'a') def test_diff_empty_tree(self): commit_a = self.repo[COMMIT_SHA1_1] - diff = commit_a.tree.diff(empty_tree=True) + diff = commit_a.tree.diff_to_tree() + + def get_context_for_lines(diff): + hunks = chain(*map(lambda x: x.hunks, [p for p in diff])) + lines = chain(*map(lambda x: x.lines, hunks)) + return map(lambda x: x[0], lines) + entries = [p.new_file_path for p in diff] self.assertAll(lambda x: commit_a.tree[x], entries) + self.assertAll(lambda x: '-' == x, get_context_for_lines(diff)) + + diff_swaped = commit_a.tree.diff_to_tree(swap=True) + entries = [p.new_file_path for p in diff_swaped] + self.assertAll(lambda x: commit_a.tree[x], entries) + self.assertAll(lambda x: '+' == x, get_context_for_lines(diff_swaped)) + + def test_diff_revparse(self): + diff = self.repo.diff('HEAD','HEAD~6') + self.assertEqual(type(diff), pygit2.Diff) def test_diff_tree_opts(self): commit_c = self.repo[COMMIT_SHA1_3] commit_d = self.repo[COMMIT_SHA1_4] - for opt in [pygit2.GIT_DIFF_IGNORE_WHITESPACE, + for flag in [pygit2.GIT_DIFF_IGNORE_WHITESPACE, pygit2.GIT_DIFF_IGNORE_WHITESPACE_EOL]: - diff = commit_c.tree.diff(commit_d.tree, opt) + diff = commit_c.tree.diff_to_tree(commit_d.tree, flag) self.assertTrue(diff is not None) self.assertEqual(0, len(diff[0].hunks)) - diff = commit_c.tree.diff(commit_d.tree) + diff = commit_c.tree.diff_to_tree(commit_d.tree) self.assertTrue(diff is not None) self.assertEqual(1, len(diff[0].hunks)) @@ -168,11 +219,11 @@ class DiffTest(utils.BareRepoTestCase): commit_b = self.repo[COMMIT_SHA1_2] commit_c = self.repo[COMMIT_SHA1_3] - diff_b = commit_a.tree.diff(commit_b.tree) + diff_b = commit_a.tree.diff_to_tree(commit_b.tree) # self.assertIsNotNone is 2.7 only self.assertTrue(diff_b is not None) - diff_c = commit_b.tree.diff(commit_c.tree) + diff_c = commit_b.tree.diff_to_tree(commit_c.tree) # self.assertIsNotNone is 2.7 only self.assertTrue(diff_c is not None) @@ -199,13 +250,13 @@ class DiffTest(utils.BareRepoTestCase): commit_a = self.repo[COMMIT_SHA1_1] commit_b = self.repo[COMMIT_SHA1_2] - diff = commit_a.tree.diff(commit_b.tree) + diff = commit_a.tree.diff_to_tree(commit_b.tree) self.assertEqual(diff.patch, PATCH) def test_diff_oids(self): commit_a = self.repo[COMMIT_SHA1_1] commit_b = self.repo[COMMIT_SHA1_2] - patch = commit_a.tree.diff(commit_b.tree)[0] + patch = commit_a.tree.diff_to_tree(commit_b.tree)[0] self.assertEqual(patch.old_oid, '7f129fd57e31e935c6d60a0c794efe4e6927664b') self.assertEqual(patch.new_oid, @@ -214,9 +265,10 @@ class DiffTest(utils.BareRepoTestCase): def test_hunk_content(self): commit_a = self.repo[COMMIT_SHA1_1] commit_b = self.repo[COMMIT_SHA1_2] - patch = commit_a.tree.diff(commit_b.tree)[0] + patch = commit_a.tree.diff_to_tree(commit_b.tree)[0] hunk = patch.hunks[0] - self.assertEqual(HUNK_EXPECTED, ''.join(hunk.lines)) + lines = ('{0} {1}'.format(*x) for x in hunk.lines) + self.assertEqual(HUNK_EXPECTED, ''.join(lines)) def test_find_similar(self): commit_a = self.repo[COMMIT_SHA1_6] @@ -224,7 +276,8 @@ class DiffTest(utils.BareRepoTestCase): #~ Must pass GIT_DIFF_INCLUDE_UNMODIFIED if you expect to emulate #~ --find-copies-harder during rename transformion... - diff = commit_a.tree.diff(commit_b.tree, GIT_DIFF_INCLUDE_UNMODIFIED) + diff = commit_a.tree.diff_to_tree(commit_b.tree, + GIT_DIFF_INCLUDE_UNMODIFIED) self.assertAll(lambda x: x.status != 'R', diff) diff.find_similar() self.assertAny(lambda x: x.status == 'R', diff)