From 2d812b671a9fb5f6a96842b1395ee26452b7636f Mon Sep 17 00:00:00 2001 From: Nico von Geyso Date: Tue, 7 May 2013 13:23:17 +0200 Subject: [PATCH 01/17] fixed line_origin in Hunks every line in a hunk has a origin like '+','-' or ' '. --- src/diff.c | 17 +++++++++-------- src/types.h | 1 - test/test_diff.py | 9 ++++----- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/diff.c b/src/diff.c index 8e87c70..0994209 100644 --- a/src/diff.c +++ b/src/diff.c @@ -50,6 +50,7 @@ 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; @@ -84,18 +85,19 @@ 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; PyList_SetItem(py_hunk->lines, j, - to_unicode_n(line, line_len, NULL, NULL)); + Py_BuildValue("cO", line_origin, + to_unicode_n(line, line_len, NULL, NULL) + ) + ); } PyList_SetItem((PyObject*) py_patch->hunks, i, @@ -279,7 +281,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/types.h b/src/types.h index a1a0105..3c559ea 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..7dd5a01 100644 --- a/test/test_diff.py +++ b/test/test_diff.py @@ -83,9 +83,8 @@ DIFF_WORKDIR_EXPECTED = [ 'subdir/modified_file' ] -HUNK_EXPECTED = """@@ -1 +1 @@ -a contents 2 -a contents +HUNK_EXPECTED = """- a contents 2 ++ a contents """ class DiffDirtyTest(utils.DirtyRepoTestCase): @@ -134,7 +133,6 @@ class DiffTest(utils.BareRepoTestCase): 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) @@ -216,7 +214,8 @@ class DiffTest(utils.BareRepoTestCase): commit_b = self.repo[COMMIT_SHA1_2] patch = commit_a.tree.diff(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] From ae6cd8a0ca378c3911dc86b949d90402bfdbe1be Mon Sep 17 00:00:00 2001 From: Nico von Geyso Date: Tue, 7 May 2013 14:06:20 +0200 Subject: [PATCH 02/17] refactoring Tree.diff() into seperate methods * Tree.diff_to_tree() * Tree.diff_to_workdir() * Tree.diff-to_index() --- src/diff.c | 15 ++++++ src/diff.h | 2 + src/tree.c | 114 +++++++++++++++++++++++++++------------------- test/test_diff.py | 30 ++++++------ 4 files changed, 100 insertions(+), 61 deletions(-) diff --git a/src/diff.c b/src/diff.c index 0994209..fd78f49 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) { 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/tree.c b/src/tree.c index f6a01cf..13309be 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,87 @@ Tree_getitem(Tree *self, PyObject *value) } -PyDoc_STRVAR(Tree_diff__doc__, - "diff([obj, flags]) -> 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\n" - " TODO"); +PyDoc_STRVAR(Tree_diff_to_workdir__doc__, "\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}; + 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, "|i", &opts.flags)) return NULL; repo = self->repo->repo; - 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); +} - return (PyObject*)py_diff; + +PyDoc_STRVAR(Tree_diff_to_index__doc__, "\n"); + +PyObject * +Tree_diff_to_index(Tree *self, PyObject *args) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff_list *diff; + git_index* index; + git_repository* repo; + int err; + char *keywords[] = {"obj", "flags", NULL}; + + Diff *py_diff; + Index *py_idx = NULL; + + if (!PyArg_ParseTuple(args, "O!|i", &IndexType, &py_idx, &opts.flags)) + return NULL; + + repo = self->repo->repo; + 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__, "\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* tree; + git_repository* repo; + int err; + char *keywords[] = {"obj", "flags", NULL}; + + Diff *py_diff; + Tree *py_tree = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O!i", keywords, + &TreeType, &py_tree, &opts.flags)) + return NULL; + + repo = self->repo->repo; + tree = (py_tree == NULL) ? NULL : py_tree->tree; + err = git_diff_tree_to_tree(&diff, repo, self->tree, tree, &opts); + + if (err < 0) + return Error_set(err); + + return wrap_diff(diff, self->repo); } @@ -355,7 +373,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), {NULL} }; diff --git a/test/test_diff.py b/test/test_diff.py index 7dd5a01..d68d7ed 100644 --- a/test/test_diff.py +++ b/test/test_diff.py @@ -91,7 +91,7 @@ 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) + diff = head.tree.diff_to_index(repo.index) files = [patch.new_file_path for patch in diff] self.assertEqual(DIFF_INDEX_EXPECTED, files) @@ -99,7 +99,7 @@ class DiffDirtyTest(utils.DirtyRepoTestCase): 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) @@ -110,12 +110,13 @@ 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 = 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) @@ -124,7 +125,7 @@ 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.assertIsNotNone is 2.7 only self.assertTrue(diff is not None) @@ -143,7 +144,7 @@ class DiffTest(utils.BareRepoTestCase): 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() entries = [p.new_file_path for p in diff] self.assertAll(lambda x: commit_a.tree[x], entries) @@ -153,11 +154,11 @@ class DiffTest(utils.BareRepoTestCase): for opt 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, opt) 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)) @@ -166,11 +167,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) @@ -197,13 +198,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, @@ -212,7 +213,7 @@ 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] lines = ('{0} {1}'.format(*x) for x in hunk.lines) self.assertEqual(HUNK_EXPECTED, ''.join(lines)) @@ -223,7 +224,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) From 8c76a14a99a2e840427e2199c42f9f6545e8b01b Mon Sep 17 00:00:00 2001 From: Nico von Geyso Date: Thu, 9 May 2013 14:40:45 +0200 Subject: [PATCH 03/17] added reorder trees possibility for diff_to_tree() --- src/tree.c | 22 +++++++++++++++------- test/test_diff.py | 13 +++++++++++++ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/tree.c b/src/tree.c index 13309be..4ffa5c9 100644 --- a/src/tree.c +++ b/src/tree.c @@ -332,21 +332,29 @@ Tree_diff_to_tree(Tree *self, PyObject *args, PyObject *kwds) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; git_diff_list *diff; - git_tree* tree; + git_tree *from, *to, *tmp; git_repository* repo; - int err; - char *keywords[] = {"obj", "flags", NULL}; + int err, swap = 0; + char *keywords[] = {"obj", "flags", "swap", NULL}; Diff *py_diff; Tree *py_tree = NULL; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O!i", keywords, - &TreeType, &py_tree, &opts.flags)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O!ii", keywords, + &TreeType, &py_tree, &opts.flags, + &swap)) return NULL; repo = self->repo->repo; - tree = (py_tree == NULL) ? NULL : py_tree->tree; - err = git_diff_tree_to_tree(&diff, repo, self->tree, tree, &opts); + to = (py_tree == NULL) ? NULL : py_tree->tree; + from = self->tree; + if (swap > 0) { + tmp = from; + from = to; + to = tmp; + } + + err = git_diff_tree_to_tree(&diff, repo, from, to, &opts); if (err < 0) return Error_set(err); diff --git a/test/test_diff.py b/test/test_diff.py index d68d7ed..b336624 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' @@ -145,8 +146,20 @@ class DiffTest(utils.BareRepoTestCase): def test_diff_empty_tree(self): commit_a = self.repo[COMMIT_SHA1_1] 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_tree_opts(self): commit_c = self.repo[COMMIT_SHA1_3] From 196d0595b0ce42fe17c0feffbd7c1989c281d4c4 Mon Sep 17 00:00:00 2001 From: Nico von Geyso Date: Sat, 18 May 2013 14:42:31 +0200 Subject: [PATCH 04/17] Added: Repository.diff() - porcelain method similiar to --- pygit2/repository.py | 68 +++++++++++++++++++++++++++++++++++++++++++- pygit2/utils.py | 10 +++++++ src/tree.c | 4 +-- test/test_diff.py | 45 +++++++++++++++++++---------- 4 files changed, 109 insertions(+), 18 deletions(-) diff --git a/pygit2/repository.py b/pygit2/repository.py index 93f8f54..c233de3 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -29,10 +29,11 @@ from string import hexdigits # Import from pygit2 +from utils import text_type 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 Reference, Tree, Commit, Blob class Repository(_Repository): @@ -128,3 +129,68 @@ 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=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. + + 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): + if isinstance(obj, text_type): + obj = self.revparse_single(obj) + + 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 + + # Case 1: Diff tree to tree + if isinstance(a, Tree) and isinstance(b, Tree): + return a.diff_to_tree(b, flags=flags) + + # Case 2: Index to workdir + elif a is None and b is None: + return self.index.diff() + + # 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, flags=flags) + else: + return a.diff_to_workdir(flags) + + # 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/pygit2/utils.py b/pygit2/utils.py index 79e6040..2f6645a 100644 --- a/pygit2/utils.py +++ b/pygit2/utils.py @@ -27,3 +27,13 @@ # feel free to add utils functions here + +import sys + +# python2/3 compability types for isinstance() +if sys.version < '3': + text_type = unicode + binary_type = str +else: + text_type = str + binary_type = bytes diff --git a/src/tree.c b/src/tree.c index 4ffa5c9..d311b88 100644 --- a/src/tree.c +++ b/src/tree.c @@ -300,7 +300,7 @@ Tree_diff_to_workdir(Tree *self, PyObject *args) PyDoc_STRVAR(Tree_diff_to_index__doc__, "\n"); PyObject * -Tree_diff_to_index(Tree *self, PyObject *args) +Tree_diff_to_index(Tree *self, PyObject *args, PyObject *kwds) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; git_diff_list *diff; @@ -383,7 +383,7 @@ PyMappingMethods Tree_as_mapping = { PyMethodDef Tree_methods[] = { METHOD(Tree, diff_to_tree, METH_VARARGS | METH_KEYWORDS), METHOD(Tree, diff_to_workdir, METH_VARARGS), - METHOD(Tree, diff_to_index, METH_VARARGS), + METHOD(Tree, diff_to_index, METH_VARARGS | METH_KEYWORDS), {NULL} }; diff --git a/test/test_diff.py b/test/test_diff.py index b336624..6de34af 100644 --- a/test/test_diff.py +++ b/test/test_diff.py @@ -91,17 +91,25 @@ HUNK_EXPECTED = """- a contents 2 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_to_index(repo.index) + files = [patch.new_file_path for patch in diff] + self.assertEqual(DIFF_INDEX_EXPECTED, files) + diff = repo.diff('HEAD', cached=True) files = [patch.new_file_path for patch in diff] self.assertEqual(DIFF_INDEX_EXPECTED, files) def test_workdir_to_tree(self): repo = self.repo head = repo[repo.lookup_reference('HEAD').resolve().target] - diff = head.tree.diff_to_workdir() + diff = head.tree.diff_to_workdir() + files = [patch.new_file_path for patch in diff] + self.assertEqual(DIFF_WORKDIR_EXPECTED, files) + + diff = repo.diff('HEAD') files = [patch.new_file_path for patch in diff] self.assertEqual(DIFF_WORKDIR_EXPECTED, files) @@ -117,8 +125,12 @@ class DiffTest(utils.BareRepoTestCase): def test_diff_empty_index(self): repo = self.repo head = repo[repo.lookup_reference('HEAD').resolve().target] - diff = head.tree.diff_to_index(repo.index) + 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) @@ -126,22 +138,25 @@ class DiffTest(utils.BareRepoTestCase): commit_a = self.repo[COMMIT_SHA1_1] commit_b = self.repo[COMMIT_SHA1_2] - diff = commit_a.tree.diff_to_tree(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.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] From 0fc7a4fbad68a2bd4a70c4ec6cc7c7811136abea Mon Sep 17 00:00:00 2001 From: Nico von Geyso Date: Sat, 18 May 2013 15:29:28 +0200 Subject: [PATCH 05/17] Refactored Index.diff() into Index.diff_to_tree()/diff_to_workdir() --- pygit2/repository.py | 2 +- src/diff.c | 3 +- src/index.c | 75 ++++++++++++++++++++++++-------------------- src/tree.c | 1 - test/test_diff.py | 32 +++++++++++++++---- 5 files changed, 70 insertions(+), 43 deletions(-) diff --git a/pygit2/repository.py b/pygit2/repository.py index c233de3..5427462 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -180,7 +180,7 @@ class Repository(_Repository): # Case 2: Index to workdir elif a is None and b is None: - return self.index.diff() + return self.index.diff_to_workdir() # Case 3: Diff tree to index or workdir elif isinstance(a, Tree) and b is None: diff --git a/src/diff.c b/src/diff.c index fd78f49..876cddb 100644 --- a/src/diff.c +++ b/src/diff.c @@ -109,7 +109,8 @@ diff_get_patch_byindex(git_diff_list* list, size_t idx) goto cleanup; PyList_SetItem(py_hunk->lines, j, - Py_BuildValue("cO", line_origin, + Py_BuildValue("OO", + to_unicode_n(&line_origin, 1, NULL, NULL), to_unicode_n(line, line_len, NULL, NULL) ) ); diff --git a/src/index.c b/src/index.c index 55fe01a..9bf1fe1 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,59 @@ Index_clear(Index *self) } -PyDoc_STRVAR(Index_diff__doc__, - "diff([tree]) -> Diff\n" +PyDoc_STRVAR(Index_diff_to_workdir__doc__, + "diff_to_workdir() -> 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"); 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, "|i", &opts.flags)) 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) -> Diff\n" + "\n" + "Return a :py:class:`~pygit2.Diff` object with the differences between the\n" + "index and the given tree.\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!|i", &TreeType, &py_tree, &opts.flags)) + return NULL; + + repo = self->repo->repo; + 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 +399,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/tree.c b/src/tree.c index d311b88..ede8d71 100644 --- a/src/tree.c +++ b/src/tree.c @@ -317,7 +317,6 @@ Tree_diff_to_index(Tree *self, PyObject *args, PyObject *kwds) repo = self->repo->repo; err = git_diff_tree_to_index(&diff, repo, self->tree, py_idx->index, &opts); - if (err < 0) return Error_set(err); diff --git a/test/test_diff.py b/test/test_diff.py index 6de34af..d690265 100644 --- a/test/test_diff.py +++ b/test/test_diff.py @@ -61,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', @@ -72,7 +72,7 @@ DIFF_INDEX_EXPECTED = [ 'staged_new_file_modified' ] -DIFF_WORKDIR_EXPECTED = [ +DIFF_HEAD_TO_WORKDIR_EXPECTED = [ 'file_deleted', 'modified_file', 'staged_changes', @@ -84,6 +84,17 @@ DIFF_WORKDIR_EXPECTED = [ 'subdir/modified_file' ] +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 """ @@ -95,11 +106,11 @@ class DiffDirtyTest(utils.DirtyRepoTestCase): 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_INDEX_EXPECTED, files) + self.assertEqual(DIFF_HEAD_TO_INDEX_EXPECTED, files) def test_workdir_to_tree(self): repo = self.repo @@ -107,11 +118,16 @@ class DiffDirtyTest(utils.DirtyRepoTestCase): 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_WORKDIR_EXPECTED, files) + 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): @@ -126,6 +142,10 @@ class DiffTest(utils.BareRepoTestCase): repo = self.repo head = repo[repo.lookup_reference('HEAD').resolve().target] + 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) From bc4c32b65cdaf57a95b220c787d460d926021472 Mon Sep 17 00:00:00 2001 From: Nico von Geyso Date: Sat, 18 May 2013 15:37:04 +0200 Subject: [PATCH 06/17] Added: documentation for Tree.diff_*-Methods --- docs/diff.rst | 33 +++++++++++++++++++++------------ src/tree.c | 16 +++++++++++++--- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/docs/diff.rst b/docs/diff.rst index 4d50ed8..f604696 100644 --- a/docs/diff.rst +++ b/docs/diff.rst @@ -4,25 +4,34 @@ Diff A diff shows the changes between trees, an index or the working dir:: - >>> head = repo.revparse_single('HEAD') + # Changes in the working tree not yet staged for the next commit + >>> repo.diff() - # Diff two trees - >>> t0 = head.tree - >>> t1 = head.parents[0].tree - >>> diff = t1.diff(t0) + # Changes between the index and your last commit + >>> self.diff(cached=True) - # Diff a tree with the index - >>> diff = head.tree.diff(repo.index) + # Changes in the working tree since your last commit + >>> self.diff('HEAD') - # Diff a tree with the current working dir - >>> diff = head.tree.diff() + # 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/src/tree.c b/src/tree.c index ede8d71..c6edaab 100644 --- a/src/tree.c +++ b/src/tree.c @@ -271,7 +271,10 @@ Tree_getitem(Tree *self, PyObject *value) } -PyDoc_STRVAR(Tree_diff_to_workdir__doc__, "\n"); +PyDoc_STRVAR(Tree_diff_to_workdir__doc__, + "diff_to_workdir([flags]) -> Diff\n" + "\n" + "Show the changes between the tree and the workdir.\n"); PyObject * Tree_diff_to_workdir(Tree *self, PyObject *args) @@ -297,7 +300,10 @@ Tree_diff_to_workdir(Tree *self, PyObject *args) } -PyDoc_STRVAR(Tree_diff_to_index__doc__, "\n"); +PyDoc_STRVAR(Tree_diff_to_index__doc__, + "diff_to_index(index, [flags]) -> Diff\n" + "\n" + "Show the changes between the index and a given tree.\n"); PyObject * Tree_diff_to_index(Tree *self, PyObject *args, PyObject *kwds) @@ -324,7 +330,11 @@ Tree_diff_to_index(Tree *self, PyObject *args, PyObject *kwds) } -PyDoc_STRVAR(Tree_diff_to_tree__doc__, "\n"); +PyDoc_STRVAR(Tree_diff_to_tree__doc__, + "diff_to_tree([tree, flags, swap]) -> Diff\n" + "\n" + "Show the changes between two trees. If no tree is given the empty tree will" + "be used instead.\n"); PyObject * Tree_diff_to_tree(Tree *self, PyObject *args, PyObject *kwds) From 99263af164ce6cb1b4a34c87de98e5b6eb1637d3 Mon Sep 17 00:00:00 2001 From: Nico von Geyso Date: Sat, 18 May 2013 15:49:30 +0200 Subject: [PATCH 07/17] use git_tree_owner in Index_diff_to_tree() --- src/index.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.c b/src/index.c index dccd2a1..b756a09 100644 --- a/src/index.c +++ b/src/index.c @@ -162,7 +162,7 @@ Index_diff_to_tree(Index *self, PyObject *args) if (!PyArg_ParseTuple(args, "O!|i", &TreeType, &py_tree, &opts.flags)) return NULL; - repo = self->repo->repo; + 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); From 60c37bd7bf4a0b6254b6bdc1a7a7cbd33f8efbbe Mon Sep 17 00:00:00 2001 From: Nico von Geyso Date: Sat, 18 May 2013 16:19:44 +0200 Subject: [PATCH 08/17] Fixed: documentation for diff_*()-Methods --- docs/diff.rst | 14 ++++++-------- docs/objects.rst | 4 +++- docs/working-copy.rst | 3 ++- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/diff.rst b/docs/diff.rst index f604696..d81fa9c 100644 --- a/docs/diff.rst +++ b/docs/diff.rst @@ -2,16 +2,15 @@ Diff ********************************************************************** -A diff shows the changes between trees, an index or the working dir:: +.. contents:: - # Changes in the working tree not yet staged for the next commit - >>> repo.diff() +A diff shows the changes between trees, an index or the working dir. - # Changes between the index and your last commit - >>> self.diff(cached=True) +.. automethod:: pygit2.Repository.diff - # Changes in the working tree since your last commit - >>> self.diff('HEAD') +Examples + +.. code-block:: python # Changes between commits >>> t0 = revparse_single('HEAD') @@ -32,7 +31,6 @@ A diff shows the changes between trees, an index or the working dir:: >>> 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 From 35d8bc4fc4f136d627e640c067d4d64dc23a52b2 Mon Sep 17 00:00:00 2001 From: Nico von Geyso Date: Mon, 20 May 2013 12:55:28 +0200 Subject: [PATCH 09/17] Added: Missing CONSTANTS for GIT_DIFF_* --- src/pygit2.c | 8 ++++++++ 1 file changed, 8 insertions(+) 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) From 6ce71a279961fa17316bf581c1507306689be6bb Mon Sep 17 00:00:00 2001 From: Nico von Geyso Date: Mon, 20 May 2013 13:57:40 +0200 Subject: [PATCH 10/17] Added: support for context_lines and interhunk_lines for diffs --- pygit2/repository.py | 34 +++++++++++++++++++---- src/index.c | 36 ++++++++++++++++++++---- src/tree.c | 65 ++++++++++++++++++++++++++++++++++++-------- 3 files changed, 112 insertions(+), 23 deletions(-) diff --git a/pygit2/repository.py b/pygit2/repository.py index 5427462..d62d08a 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -27,6 +27,7 @@ # Import from the Standard Library from string import hexdigits +from collections import OrderedDict # Import from pygit2 from utils import text_type @@ -134,12 +135,29 @@ class Repository(_Repository): # # Diff # - def diff(self, a=None, b=None, cached=False, flags=0): + def diff(self, a=None, b=None, cached=False, flags=0, 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 @@ -174,20 +192,26 @@ class Repository(_Repository): a = treeish_to_tree(a) or a b = treeish_to_tree(b) or b + opts = OrderedDict([ + ('flags', flags), + ('context_lines', context_lines), + ('interhunk_lines', interhunk_lines) + ]) + # Case 1: Diff tree to tree if isinstance(a, Tree) and isinstance(b, Tree): - return a.diff_to_tree(b, flags=flags) + return a.diff_to_tree(b, **opts) # Case 2: Index to workdir elif a is None and b is None: - return self.index.diff_to_workdir() + return self.index.diff_to_workdir(*opts.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, flags=flags) + return a.diff_to_index(self.index, **opts) else: - return a.diff_to_workdir(flags) + return a.diff_to_workdir(*opts.values()) # Case 4: Diff blob to blob if isinstance(a, Blob) and isinstance(b, Blob): diff --git a/src/index.c b/src/index.c index b756a09..3b637a8 100644 --- a/src/index.c +++ b/src/index.c @@ -116,10 +116,20 @@ Index_clear(Index *self) PyDoc_STRVAR(Index_diff_to_workdir__doc__, - "diff_to_workdir() -> Diff\n" + "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.\n"); + "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_to_workdir(Index *self, PyObject *args) @@ -128,7 +138,8 @@ Index_diff_to_workdir(Index *self, PyObject *args) git_diff_list *diff; int err; - if (!PyArg_ParseTuple(args, "|i", &opts.flags)) + if (!PyArg_ParseTuple(args, "|IHH", &opts.flags, &opts.context_lines, + &opts.interhunk_lines)) return NULL; err = git_diff_index_to_workdir( @@ -144,10 +155,22 @@ Index_diff_to_workdir(Index *self, PyObject *args) } PyDoc_STRVAR(Index_diff_to_tree__doc__, - "diff_to_tree(tree) -> Diff\n" + "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"); + "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) @@ -159,7 +182,8 @@ Index_diff_to_tree(Index *self, PyObject *args) Tree *py_tree = NULL; - if (!PyArg_ParseTuple(args, "O!|i", &TreeType, &py_tree, &opts.flags)) + 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); diff --git a/src/tree.c b/src/tree.c index b8dee9b..58e27a1 100644 --- a/src/tree.c +++ b/src/tree.c @@ -272,9 +272,19 @@ Tree_getitem(Tree *self, PyObject *value) PyDoc_STRVAR(Tree_diff_to_workdir__doc__, - "diff_to_workdir([flags]) -> Diff\n" + "diff_to_workdir([flags, context_lines, interhunk_lines]) -> Diff\n" "\n" - "Show the changes between the tree and the workdir.\n"); + "Show the changes between the :py:class:`~pygit2.Tree` and the workdir.\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 * Tree_diff_to_workdir(Tree *self, PyObject *args) @@ -287,7 +297,8 @@ Tree_diff_to_workdir(Tree *self, PyObject *args) Diff *py_diff; PyObject *py_obj = NULL; - if (!PyArg_ParseTuple(args, "|i", &opts.flags)) + if (!PyArg_ParseTuple(args, "|IHH", &opts.flags, &opts.context_lines, + &opts.interhunk_lines)) return NULL; repo = git_tree_owner(self->tree); @@ -301,9 +312,21 @@ Tree_diff_to_workdir(Tree *self, PyObject *args) PyDoc_STRVAR(Tree_diff_to_index__doc__, - "diff_to_index(index, [flags]) -> Diff\n" + "diff_to_index(index, [flags, context_lines, interhunk_lines]) -> Diff\n" "\n" - "Show the changes between the index and a given tree.\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) @@ -318,7 +341,9 @@ Tree_diff_to_index(Tree *self, PyObject *args, PyObject *kwds) Diff *py_diff; Index *py_idx = NULL; - if (!PyArg_ParseTuple(args, "O!|i", &IndexType, &py_idx, &opts.flags)) + 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); @@ -331,10 +356,24 @@ Tree_diff_to_index(Tree *self, PyObject *args, PyObject *kwds) PyDoc_STRVAR(Tree_diff_to_tree__doc__, - "diff_to_tree([tree, flags, swap]) -> Diff\n" + "diff_to_tree([tree, flags, context_lines, interhunk_lines, swap]) -> Diff\n" "\n" - "Show the changes between two trees. If no tree is given the empty tree will" - "be used instead.\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) @@ -344,14 +383,16 @@ Tree_diff_to_tree(Tree *self, PyObject *args, PyObject *kwds) git_tree *from, *to, *tmp; git_repository* repo; int err, swap = 0; - char *keywords[] = {"obj", "flags", "swap", NULL}; + char *keywords[] = {"obj", "flags", "context_lines", "interhunk_lines", + "swap", NULL}; Diff *py_diff; Tree *py_tree = NULL; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O!ii", keywords, + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O!IHHi", keywords, &TreeType, &py_tree, &opts.flags, - &swap)) + &opts.context_lines, + &opts.interhunk_lines, &swap)) return NULL; repo = git_tree_owner(self->tree); From 77b5cdce5b50b9edcde3a1aed4d7117e6e47bc24 Mon Sep 17 00:00:00 2001 From: Nico von Geyso Date: Mon, 20 May 2013 14:58:54 +0200 Subject: [PATCH 11/17] Refactored: get rid of type check for treeish_to_tree in Diff --- pygit2/repository.py | 5 +++-- pygit2/utils.py | 10 ---------- test/test_diff.py | 8 ++++++-- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/pygit2/repository.py b/pygit2/repository.py index d62d08a..6a5ec38 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -30,7 +30,6 @@ from string import hexdigits from collections import OrderedDict # Import from pygit2 -from utils import text_type from _pygit2 import Repository as _Repository from _pygit2 import Oid, GIT_OID_HEXSZ, GIT_OID_MINPREFIXLEN from _pygit2 import GIT_CHECKOUT_SAFE_CREATE @@ -180,8 +179,10 @@ class Repository(_Repository): """ def treeish_to_tree(obj): - if isinstance(obj, text_type): + try: obj = self.revparse_single(obj) + except: + pass if isinstance(obj, Commit): return obj.tree diff --git a/pygit2/utils.py b/pygit2/utils.py index 2f6645a..79e6040 100644 --- a/pygit2/utils.py +++ b/pygit2/utils.py @@ -27,13 +27,3 @@ # feel free to add utils functions here - -import sys - -# python2/3 compability types for isinstance() -if sys.version < '3': - text_type = unicode - binary_type = str -else: - text_type = str - binary_type = bytes diff --git a/test/test_diff.py b/test/test_diff.py index d690265..7ff9946 100644 --- a/test/test_diff.py +++ b/test/test_diff.py @@ -196,13 +196,17 @@ class DiffTest(utils.BareRepoTestCase): 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_to_tree(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)) From 86aafebb6deabbf5183906f4c0c79dade410ed95 Mon Sep 17 00:00:00 2001 From: Nico von Geyso Date: Mon, 20 May 2013 15:13:04 +0200 Subject: [PATCH 12/17] Fixed: remove OrderedDict for compability reasons (python2.6) --- pygit2/repository.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/pygit2/repository.py b/pygit2/repository.py index 6a5ec38..bf0e835 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -27,7 +27,6 @@ # Import from the Standard Library from string import hexdigits -from collections import OrderedDict # Import from pygit2 from _pygit2 import Repository as _Repository @@ -193,26 +192,23 @@ class Repository(_Repository): a = treeish_to_tree(a) or a b = treeish_to_tree(b) or b - opts = OrderedDict([ - ('flags', flags), - ('context_lines', context_lines), - ('interhunk_lines', interhunk_lines) - ]) + 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, **opts) + 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(*opts.values()) + 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, **opts) + return a.diff_to_index(self.index, *opt_values) else: - return a.diff_to_workdir(*opts.values()) + return a.diff_to_workdir(*opt_values) # Case 4: Diff blob to blob if isinstance(a, Blob) and isinstance(b, Blob): From 6dd14d799e4ecfe542f3b41a3e905e33429efd24 Mon Sep 17 00:00:00 2001 From: Nico von Geyso Date: Mon, 20 May 2013 15:38:49 +0200 Subject: [PATCH 13/17] Fixed: Use GIT_DIFF_NORMAL as default flag for Diff --- pygit2/repository.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pygit2/repository.py b/pygit2/repository.py index bf0e835..3d8521b 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -31,7 +31,7 @@ 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 GIT_CHECKOUT_SAFE_CREATE, GIT_DIFF_NORMAL from _pygit2 import Reference, Tree, Commit, Blob @@ -133,8 +133,8 @@ class Repository(_Repository): # # Diff # - def diff(self, a=None, b=None, cached=False, flags=0, context_lines=3, - interhunk_lines=0): + 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 From bdff1c3145992364c2088c86415c7281616dc7f9 Mon Sep 17 00:00:00 2001 From: Nico von Geyso Date: Mon, 20 May 2013 15:39:17 +0200 Subject: [PATCH 14/17] Removed: unnecessary PyObject in tree.c --- src/tree.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tree.c b/src/tree.c index 58e27a1..b7177f3 100644 --- a/src/tree.c +++ b/src/tree.c @@ -295,7 +295,6 @@ Tree_diff_to_workdir(Tree *self, PyObject *args) int err; Diff *py_diff; - PyObject *py_obj = NULL; if (!PyArg_ParseTuple(args, "|IHH", &opts.flags, &opts.context_lines, &opts.interhunk_lines)) From 7bccfc8d26bd20e48e88a9c3e1222e8493e96870 Mon Sep 17 00:00:00 2001 From: Nico von Geyso Date: Tue, 21 May 2013 13:46:58 +0200 Subject: [PATCH 15/17] Fixed: Memleak in diff_get_patch_byindex() There was a memleak because of a missing Py_DECREF. Thanks to @delanne --- src/diff.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/diff.c b/src/diff.c index 876cddb..f46d4bd 100644 --- a/src/diff.c +++ b/src/diff.c @@ -69,6 +69,7 @@ diff_get_patch_byindex(git_diff_list* list, size_t idx) 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) @@ -108,12 +109,16 @@ diff_get_patch_byindex(git_diff_list* list, size_t idx) 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, Py_BuildValue("OO", - to_unicode_n(&line_origin, 1, NULL, NULL), - to_unicode_n(line, line_len, NULL, NULL) + py_line_origin, + py_line ) ); + Py_DECREF(py_line_origin); + Py_DECREF(py_line); } PyList_SetItem((PyObject*) py_patch->hunks, i, From b50a8d05fe8784fc21db8f839a2c79b21a7bfcb6 Mon Sep 17 00:00:00 2001 From: Nico von Geyso Date: Tue, 21 May 2013 14:06:44 +0200 Subject: [PATCH 16/17] Fixed: Memleak in Repository_config__get__() There was a memleak because of a missing Py_INCREF() for self->config. We need 2 Py_INCREF() here because one is returned and one is kept internally. Thanks @delanne for reporting and providing the patch. --- src/repository.c | 3 +++ 1 file changed, 3 insertions(+) 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); } From d59343730665624451c47451b0d89cdd6f4a39fc Mon Sep 17 00:00:00 2001 From: Nico von Geyso Date: Tue, 21 May 2013 14:11:37 +0200 Subject: [PATCH 17/17] Removed: unused variables in tree.c (thanks @delanne) --- src/tree.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/tree.c b/src/tree.c index b7177f3..378792d 100644 --- a/src/tree.c +++ b/src/tree.c @@ -294,8 +294,6 @@ Tree_diff_to_workdir(Tree *self, PyObject *args) git_repository* repo; int err; - Diff *py_diff; - if (!PyArg_ParseTuple(args, "|IHH", &opts.flags, &opts.context_lines, &opts.interhunk_lines)) return NULL; @@ -332,12 +330,9 @@ Tree_diff_to_index(Tree *self, PyObject *args, PyObject *kwds) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; git_diff_list *diff; - git_index* index; git_repository* repo; int err; - char *keywords[] = {"obj", "flags", NULL}; - Diff *py_diff; Index *py_idx = NULL; if (!PyArg_ParseTuple(args, "O!|IHH", &IndexType, &py_idx, &opts.flags, @@ -385,7 +380,6 @@ Tree_diff_to_tree(Tree *self, PyObject *args, PyObject *kwds) char *keywords[] = {"obj", "flags", "context_lines", "interhunk_lines", "swap", NULL}; - Diff *py_diff; Tree *py_tree = NULL; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O!IHHi", keywords,