diff --git a/README.rst b/README.rst index aa6b660..50c3df9 100644 --- a/README.rst +++ b/README.rst @@ -31,8 +31,20 @@ Changelog - Make pygit work in a frozen environment `#453 `_ +- New ``pygit2.DiffDelta`` and ``pygit2.DiffFile`` + - Rename ``pygit2.Hunk`` to ``pygit2.DiffHunk`` +API changes:: + + Patch.old_file_path => Patch.delta.old_file.path + Patch.new_file_path => Patch.delta.new_file.path + Patch.old_id => Patch.delta.old_file.id + Patch.new_id => Patch.delta.new_file.id + Patch.status => Patch.delta.status + Patch.similarity => Patch.delta.similarity + Patch.is_binary => Patch.delta.is_binary + 0.22.0 (2015-01-16) ------------------- diff --git a/src/diff.c b/src/diff.c index 6ce2074..79b0a77 100644 --- a/src/diff.c +++ b/src/diff.c @@ -40,6 +40,8 @@ extern PyObject *GitError; extern PyTypeObject TreeType; extern PyTypeObject IndexType; extern PyTypeObject DiffType; +extern PyTypeObject DiffDeltaType; +extern PyTypeObject DiffFileType; extern PyTypeObject DiffHunkType; extern PyTypeObject RepositoryType; @@ -58,6 +60,180 @@ wrap_diff(git_diff *diff, Repository *repo) return (PyObject*) py_diff; } +PyObject * +wrap_diff_file(const git_diff_file *file) +{ + DiffFile *py_file; + + if (!file) + Py_RETURN_NONE; + + py_file = PyObject_New(DiffFile, &DiffFileType); + if (py_file) { + py_file->id = git_oid_to_python(&file->id); + py_file->path = file->path != NULL ? strdup(file->path) : NULL; + } + + return (PyObject *) py_file; +} + +PyObject * +wrap_diff_delta(const git_diff_delta *delta) +{ + DiffDelta *py_delta; + + if (!delta) + Py_RETURN_NONE; + + py_delta = PyObject_New(DiffDelta, &DiffDeltaType); + if (py_delta) { + py_delta->status = git_diff_status_char(delta->status); + py_delta->flags = delta->flags; + py_delta->similarity = delta->similarity; + py_delta->nfiles = delta->nfiles; + py_delta->old_file = wrap_diff_file(&delta->old_file); + py_delta->new_file = wrap_diff_file(&delta->new_file); + } + + return (PyObject *) py_delta; +} + +static void +DiffFile_dealloc(DiffFile *self) +{ + Py_CLEAR(self->id); + if (self->path) + free(self->path); + PyObject_Del(self); +} + +PyMemberDef DiffFile_members[] = { + MEMBER(DiffFile, id, T_OBJECT, "Oid of the item."), + MEMBER(DiffFile, path, T_STRING, "Path to the entry."), + {NULL} +}; + + +PyDoc_STRVAR(DiffFile__doc__, "DiffFile object."); + +PyTypeObject DiffFileType = { + PyVarObject_HEAD_INIT(NULL, 0) + "_pygit2.DiffFile", /* tp_name */ + sizeof(DiffFile), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)DiffFile_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + DiffFile__doc__, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + DiffFile_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ +}; + +PyDoc_STRVAR(DiffDelta_is_binary__doc__, "True if binary data, False if not."); + +PyObject * +DiffDelta_is_binary__get__(DiffDelta *self) +{ + if (!(self->flags & GIT_DIFF_FLAG_NOT_BINARY) && + (self->flags & GIT_DIFF_FLAG_BINARY)) + Py_RETURN_TRUE; + Py_RETURN_FALSE; +} + +static void +DiffDelta_dealloc(DiffDelta *self) +{ + Py_CLEAR(self->old_file); + Py_CLEAR(self->new_file); + PyObject_Del(self); +} + +PyMemberDef DiffDelta_members[] = { + MEMBER(DiffDelta, status, T_CHAR, "A GIT_DELTA_* constant."), + MEMBER(DiffDelta, flags, T_UINT, "Combination of GIT_DIFF_FLAG_* flags."), + MEMBER(DiffDelta, similarity, T_USHORT, "For renamed and copied."), + MEMBER(DiffDelta, nfiles, T_USHORT, "Number of files in the delta."), + MEMBER(DiffDelta, old_file, T_OBJECT, "\"from\" side of the diff."), + MEMBER(DiffDelta, new_file, T_OBJECT, "\"to\" side of the diff."), + {NULL} +}; + +PyGetSetDef DiffDelta_getseters[] = { + GETTER(DiffDelta, is_binary), + {NULL} +}; + +PyDoc_STRVAR(DiffDelta__doc__, "DiffDelta object."); + +PyTypeObject DiffDeltaType = { + PyVarObject_HEAD_INIT(NULL, 0) + "_pygit2.DiffDelta", /* tp_name */ + sizeof(DiffDelta), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)DiffDelta_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + DiffDelta__doc__, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + DiffDelta_members, /* tp_members */ + DiffDelta_getseters, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ +}; + PyObject * diff_get_patch_byindex(git_diff *diff, size_t idx) { diff --git a/src/diff.h b/src/diff.h index 9b45ec3..20740e0 100644 --- a/src/diff.h +++ b/src/diff.h @@ -37,5 +37,7 @@ PyObject* Diff_changes(Diff *self); PyObject* Diff_patch(Diff *self); PyObject* wrap_diff(git_diff *diff, Repository *repo); +PyObject* wrap_diff_file(const git_diff_file *file); +PyObject* wrap_diff_delta(const git_diff_delta *delta); #endif diff --git a/src/patch.c b/src/patch.c index 934f0dc..013227b 100644 --- a/src/patch.c +++ b/src/patch.c @@ -28,6 +28,7 @@ #define PY_SSIZE_T_CLEAN #include #include +#include "diff.h" #include "error.h" #include "oid.h" #include "types.h" @@ -53,15 +54,9 @@ wrap_patch(git_patch *patch) const git_diff_line *line; int err; - delta = git_patch_get_delta(patch); + py_patch->patch = patch; - py_patch->old_file_path = strdup(delta->old_file.path); - py_patch->new_file_path = strdup(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_id = git_oid_to_python(&delta->old_file.id); - py_patch->new_id = git_oid_to_python(&delta->new_file.id); + delta = git_patch_get_delta(patch); git_patch_line_stats(NULL, &additions, &deletions, patch); py_patch->additions = additions; @@ -107,7 +102,6 @@ wrap_patch(git_patch *patch) } } } - git_patch_free(patch); return (PyObject*) py_patch; } @@ -116,39 +110,30 @@ static void Patch_dealloc(Patch *self) { Py_CLEAR(self->hunks); - Py_CLEAR(self->old_id); - Py_CLEAR(self->new_id); - free(self->old_file_path); - free(self->new_file_path); + git_patch_free(self->patch); PyObject_Del(self); } +PyDoc_STRVAR(Patch_delta__doc__, "Get the delta associated with a patch."); + +PyObject * +Patch_delta__get__(Patch *self) +{ + if (!self->patch) + Py_RETURN_NONE; + + return wrap_diff_delta(git_patch_get_delta(self->patch)); +} + PyMemberDef Patch_members[] = { - MEMBER(Patch, old_file_path, T_STRING, "old file path"), - MEMBER(Patch, new_file_path, T_STRING, "new file path"), - MEMBER(Patch, old_id, T_OBJECT, "old oid"), - MEMBER(Patch, new_id, T_OBJECT, "new oid"), - 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_is_binary__doc__, "True if binary data, False if not."); - -PyObject * -Patch_is_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, is_binary), + GETTER(Patch, delta), {NULL} }; diff --git a/src/pygit2.c b/src/pygit2.c index 42aaaa6..ff424f9 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -44,8 +44,10 @@ extern PyTypeObject ObjectType; extern PyTypeObject CommitType; extern PyTypeObject DiffType; extern PyTypeObject DiffIterType; -extern PyTypeObject PatchType; +extern PyTypeObject DiffDeltaType; +extern PyTypeObject DiffFileType; extern PyTypeObject DiffHunkType; +extern PyTypeObject PatchType; extern PyTypeObject TreeType; extern PyTypeObject TreeBuilderType; extern PyTypeObject TreeEntryType; @@ -287,11 +289,15 @@ moduleinit(PyObject* m) */ INIT_TYPE(DiffType, NULL, NULL) INIT_TYPE(DiffIterType, NULL, NULL) - INIT_TYPE(PatchType, NULL, NULL) + INIT_TYPE(DiffDeltaType, NULL, NULL) + INIT_TYPE(DiffFileType, NULL, NULL) INIT_TYPE(DiffHunkType, NULL, NULL) + INIT_TYPE(PatchType, NULL, NULL) ADD_TYPE(m, Diff) - ADD_TYPE(m, Patch) + ADD_TYPE(m, DiffDelta) + ADD_TYPE(m, DiffFile) ADD_TYPE(m, DiffHunk) + ADD_TYPE(m, Patch) ADD_CONSTANT_INT(m, GIT_DIFF_NORMAL) ADD_CONSTANT_INT(m, GIT_DIFF_REVERSE) ADD_CONSTANT_INT(m, GIT_DIFF_FORCE_TEXT) diff --git a/src/types.h b/src/types.h index d7e93e6..ea78b90 100644 --- a/src/types.h +++ b/src/types.h @@ -90,6 +90,14 @@ typedef struct { char* ref; } NoteIter; +/* git_patch */ +typedef struct { + PyObject_HEAD + git_patch *patch; + PyObject* hunks; + unsigned additions; + unsigned deletions; +} Patch; /* git_diff */ SIMPLE_TYPE(Diff, git_diff, diff) @@ -103,17 +111,19 @@ typedef struct { typedef struct { PyObject_HEAD - PyObject* hunks; - char * old_file_path; - char * new_file_path; - PyObject* old_id; - PyObject* new_id; - char status; - unsigned similarity; - unsigned additions; - unsigned deletions; - unsigned flags; -} Patch; + PyObject *id; + char *path; +} DiffFile; + +typedef struct { + PyObject_HEAD + git_delta_t status; + uint32_t flags; + uint16_t similarity; + uint16_t nfiles; + PyObject *old_file; + PyObject *new_file; +} DiffDelta; typedef struct { PyObject_HEAD diff --git a/test/test_diff.py b/test/test_diff.py index 6fd4fbb..3db5f48 100644 --- a/test/test_diff.py +++ b/test/test_diff.py @@ -107,11 +107,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] + files = [patch.delta.new_file.path for patch in diff] self.assertEqual(DIFF_HEAD_TO_INDEX_EXPECTED, files) diff = repo.diff('HEAD', cached=True) - files = [patch.new_file_path for patch in diff] + files = [patch.delta.new_file.path for patch in diff] self.assertEqual(DIFF_HEAD_TO_INDEX_EXPECTED, files) def test_workdir_to_tree(self): @@ -119,16 +119,16 @@ class DiffDirtyTest(utils.DirtyRepoTestCase): head = repo[repo.lookup_reference('HEAD').resolve().target] diff = head.tree.diff_to_workdir() - files = [patch.new_file_path for patch in diff] + files = [patch.delta.new_file.path for patch in diff] self.assertEqual(DIFF_HEAD_TO_WORKDIR_EXPECTED, files) diff = repo.diff('HEAD') - files = [patch.new_file_path for patch in diff] + files = [patch.delta.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] + files = [patch.delta.new_file.path for patch in diff] self.assertEqual(DIFF_INDEX_TO_WORK_EXPECTED, files) @@ -145,15 +145,15 @@ class DiffTest(utils.BareRepoTestCase): 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] + files = [patch.delta.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] + files = [patch.delta.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] + files = [patch.delta.new_file.path.split('/')[0] for patch in diff] self.assertEqual([x.name for x in head.tree], files) def test_diff_tree(self): @@ -173,9 +173,9 @@ class DiffTest(utils.BareRepoTestCase): 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') - self.assertEqual(patch.is_binary, False) + self.assertEqual(patch.delta.old_file.path, 'a') + self.assertEqual(patch.delta.new_file.path, 'a') + self.assertEqual(patch.delta.is_binary, False) _test(commit_a.tree.diff_to_tree(commit_b.tree)) _test(self.repo.diff(COMMIT_SHA1_1, COMMIT_SHA1_2)) @@ -190,12 +190,12 @@ class DiffTest(utils.BareRepoTestCase): lines = chain(*map(lambda x: x.lines, hunks)) return map(lambda x: x[0], lines) - entries = [p.new_file_path for p in diff] + entries = [p.delta.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] + entries = [p.delta.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)) @@ -231,13 +231,13 @@ class DiffTest(utils.BareRepoTestCase): self.assertTrue(diff_c is not None) # assertIn / assertNotIn are 2.7 only - self.assertFalse('b' in [patch.new_file_path for patch in diff_b]) - self.assertTrue('b' in [patch.new_file_path for patch in diff_c]) + self.assertFalse('b' in [patch.delta.new_file.path for patch in diff_b]) + self.assertTrue('b' in [patch.delta.new_file.path for patch in diff_c]) diff_b.merge(diff_c) # assertIn is 2.7 only - self.assertTrue('b' in [patch.new_file_path for patch in diff_b]) + self.assertTrue('b' in [patch.delta.new_file.path for patch in diff_b]) patch = diff_b[0] hunk = patch.hunks[0] @@ -246,8 +246,8 @@ class DiffTest(utils.BareRepoTestCase): 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') + self.assertEqual(patch.delta.old_file.path, 'a') + self.assertEqual(patch.delta.new_file.path, 'a') def test_diff_patch(self): commit_a = self.repo[COMMIT_SHA1_1] @@ -261,9 +261,9 @@ class DiffTest(utils.BareRepoTestCase): commit_a = self.repo[COMMIT_SHA1_1] commit_b = self.repo[COMMIT_SHA1_2] patch = commit_a.tree.diff_to_tree(commit_b.tree)[0] - self.assertEqual(patch.old_id.hex, + self.assertEqual(patch.delta.old_file.id.hex, '7f129fd57e31e935c6d60a0c794efe4e6927664b') - self.assertEqual(patch.new_id.hex, + self.assertEqual(patch.delta.new_file.id.hex, 'af431f20fc541ed6d5afede3e2dc7160f6f01f16') def test_hunk_content(self): @@ -282,9 +282,9 @@ class DiffTest(utils.BareRepoTestCase): #~ --find-copies-harder during rename transformion... diff = commit_a.tree.diff_to_tree(commit_b.tree, GIT_DIFF_INCLUDE_UNMODIFIED) - self.assertAll(lambda x: x.status != 'R', diff) + self.assertAll(lambda x: x.delta.status != 'R', diff) diff.find_similar() - self.assertAny(lambda x: x.status == 'R', diff) + self.assertAny(lambda x: x.delta.status == 'R', diff) if __name__ == '__main__': unittest.main()