diff --git a/include/pygit2/diff.h b/include/pygit2/diff.h index 3060234..1f1b793 100644 --- a/include/pygit2/diff.h +++ b/include/pygit2/diff.h @@ -6,6 +6,11 @@ #include #include +#define DIFF_CHECK_TYPES(_x, _y, _type_x, _type_y) \ + PyObject_TypeCheck(_x, _type_x) && \ + PyObject_TypeCheck(_y, _type_y) + + PyObject* Diff_changes(Diff *self); PyObject* Diff_patch(Diff *self); diff --git a/include/pygit2/types.h b/include/pygit2/types.h index 81f302f..37edf89 100644 --- a/include/pygit2/types.h +++ b/include/pygit2/types.h @@ -38,8 +38,8 @@ typedef struct { typedef struct { PyObject_HEAD - Tree *t0; - Tree *t1; + PyObject *a; + PyObject *b; } Diff; typedef struct { diff --git a/src/pygit2/diff.c b/src/pygit2/diff.c index c957b9c..81be398 100644 --- a/src/pygit2/diff.c +++ b/src/pygit2/diff.c @@ -6,9 +6,60 @@ #include #include +extern PyObject *GitError; + +extern PyTypeObject TreeType; +extern PyTypeObject IndexType; extern PyTypeObject DiffType; extern PyTypeObject HunkType; +int diff_get_list(Diff *diff, git_diff_options* opts, git_diff_list** list) +{ + int err = -1; + + if(diff->b == NULL) { + if(PyObject_TypeCheck(diff->a, &TreeType)) { + err = git_diff_workdir_to_tree( + ((Tree*) diff->a)->repo->repo, + opts, + ((Tree*) diff->a)->tree, + list + ); + + } else { + err = git_diff_workdir_to_index( + ((Index*) diff->a)->repo->repo, + opts, + list + ); + } + } else if(DIFF_CHECK_TYPES(diff->a, diff->b, &TreeType, &TreeType)) { + err = git_diff_tree_to_tree( + ((Tree*) diff->a)->repo->repo, + opts, + ((Tree*) diff->a)->tree, + ((Tree*) diff->b)->tree, + list + ); + } else if(DIFF_CHECK_TYPES(diff->a, diff->b, &IndexType, &TreeType)) { + err = git_diff_index_to_tree( + ((Tree*) diff->b)->repo->repo, + opts, + ((Tree*) diff->b)->tree, + list + ); + } else if(DIFF_CHECK_TYPES(diff->a, diff->b, &TreeType, &IndexType)) { + err = git_diff_index_to_tree( + ((Tree*) diff->a)->repo->repo, + opts, + ((Tree*) diff->a)->tree, + list + ); + } + + return err; +} + static int diff_data_cb( void *cb_data, git_diff_delta *delta, @@ -23,12 +74,12 @@ static int diff_data_cb( hunks = PyDict_GetItemString(cb_data, "hunks"); if(hunks == NULL) - return -1; + return -1; size = PyList_Size(hunks); hunk = (Hunk*) PyList_GetItem(hunks, size-1); if(hunk == NULL) - return -1; + return -1; tmp = PyBytes_FromStringAndSize(line, line_len); @@ -38,7 +89,7 @@ static int diff_data_cb( if(usage != GIT_DIFF_LINE_ADDITION) PyBytes_Concat(&hunk->old_data, tmp); - return 0; + return 0; } static int diff_hunk_cb( @@ -48,66 +99,77 @@ static int diff_hunk_cb( const char *header, size_t header_len) { - PyObject *hunks; - Hunk *hunk; + PyObject *hunks; + Hunk *hunk; - hunks = PyDict_GetItemString(cb_data, "hunks"); - if(hunks == NULL) { - hunks = PyList_New(0); - PyDict_SetItemString(cb_data, "hunks", hunks); - } + hunks = PyDict_GetItemString(cb_data, "hunks"); + if(hunks == NULL) { + hunks = PyList_New(0); + PyDict_SetItemString(cb_data, "hunks", hunks); + } - hunk = (Hunk*) PyType_GenericNew(&HunkType, NULL, NULL); + hunk = (Hunk*) PyType_GenericNew(&HunkType, NULL, NULL); - hunk->old_start = range->old_start; - hunk->old_lines = range->old_lines; - hunk->new_start = range->new_start; - hunk->new_lines = range->new_lines; + hunk->old_start = range->old_start; + hunk->old_lines = range->old_lines; + hunk->new_start = range->new_start; + hunk->new_lines = range->new_lines; - int len; - char* old_path, *new_path; + int len; + char* old_path, *new_path; - len = strlen(delta->old_file.path) + 1; - old_path = malloc(sizeof(char) * len); - memcpy(old_path, delta->old_file.path, len); - hunk->old_file = old_path; + if(delta->old_file.path != NULL) { + len = strlen(delta->old_file.path) + 1; + old_path = malloc(sizeof(char) * len); + memcpy(old_path, delta->old_file.path, len); + hunk->old_file = old_path; + } else { + hunk->old_file = ""; + } - len = strlen(delta->new_file.path) + 1; - new_path = malloc(sizeof(char) * len); - memcpy(new_path, delta->new_file.path, len); - hunk->new_file = new_path; + if(delta->new_file.path != NULL) { + len = strlen(delta->new_file.path) + 1; + new_path = malloc(sizeof(char) * len); + memcpy(new_path, delta->new_file.path, len); + hunk->new_file = new_path; + } else { + hunk->new_file = ""; + } #if PY_MAJOR_VERSION >= 3 - hunk->old_data = Py_BuildValue("y", ""); - hunk->new_data = Py_BuildValue("y", ""); + hunk->old_data = Py_BuildValue("y", ""); + hunk->new_data = Py_BuildValue("y", ""); #else - hunk->old_data = Py_BuildValue("s", ""); - hunk->new_data = Py_BuildValue("s", ""); + hunk->old_data = Py_BuildValue("s", ""); + hunk->new_data = Py_BuildValue("s", ""); #endif - PyList_Append(hunks, (PyObject*) hunk); + PyList_Append(hunks, (PyObject*) hunk); - return 0; + return 0; }; static int diff_file_cb(void *cb_data, git_diff_delta *delta, float progress) { PyObject *files, *file; - files = PyDict_GetItemString(cb_data, "files"); - if(files == NULL) { - files = PyList_New(0); - PyDict_SetItemString(cb_data, "files", files); + if(delta->old_file.path != NULL && delta->new_file.path != NULL) { + files = PyDict_GetItemString(cb_data, "files"); + + if(files == NULL) { + files = PyList_New(0); + PyDict_SetItemString(cb_data, "files", files); + } + + file = Py_BuildValue("(s,s,i)", + delta->old_file.path, + delta->new_file.path, + delta->status + ); + + PyList_Append(files, file); } - file = Py_BuildValue("(s,s,i)", - delta->old_file.path, - delta->new_file.path, - delta->status - ); - - PyList_Append(files, file); - return 0; } @@ -116,23 +178,18 @@ Diff_changes(Diff *self) { git_diff_options opts = {0}; git_diff_list *changes; + PyObject *payload; int err; - err = git_diff_tree_to_tree( - self->t0->repo->repo, - &opts, - self->t0->tree, - self->t1->tree, - &changes); + err = diff_get_list(self, &opts, &changes); if(err < 0) { Error_set(err); return NULL; } - - PyObject *payload; - payload = PyDict_New(); + payload = PyDict_New(); + git_diff_foreach( changes, payload, @@ -140,6 +197,7 @@ Diff_changes(Diff *self) &diff_hunk_cb, &diff_data_cb ); + git_diff_list_free(changes); return payload; @@ -153,10 +211,10 @@ static int diff_print_cb( const char *line, size_t line_len) { - PyObject *data = PyBytes_FromStringAndSize(line, line_len); - PyBytes_ConcatAndDel((PyObject**) cb_data, data); + PyObject *data = PyBytes_FromStringAndSize(line, line_len); + PyBytes_ConcatAndDel((PyObject**) cb_data, data); - return 0; + return 0; } @@ -167,13 +225,7 @@ Diff_patch(Diff *self) git_diff_list *changes; int err; - err = git_diff_tree_to_tree( - self->t0->repo->repo, - &opts, - self->t0->tree, - self->t1->tree, - &changes); - + err = diff_get_list(self, &opts, &changes); if(err < 0) { Error_set(err); return NULL; @@ -197,14 +249,14 @@ Hunk_init(Hunk *self, PyObject *args, PyObject *kwds) self->old_data = PyString_FromString(""); if (self->old_data == NULL) { - Py_DECREF(self); - return -1; + Py_XDECREF(self); + return -1; } self->new_data = PyString_FromString(""); if (self->new_data == NULL) { - Py_DECREF(self); - return -1; + Py_XDECREF(self); + return -1; } return 0; @@ -275,8 +327,8 @@ PyTypeObject HunkType = { static void Diff_dealloc(Diff *self) { - Py_XDECREF(self->t0); - Py_XDECREF(self->t1); + Py_XDECREF(self->a); + Py_XDECREF(self->b); PyObject_Del(self); } diff --git a/src/pygit2/tree.c b/src/pygit2/tree.c index 77c77d1..3bb671b 100644 --- a/src/pygit2/tree.c +++ b/src/pygit2/tree.c @@ -9,6 +9,7 @@ extern PyTypeObject TreeType; extern PyTypeObject DiffType; extern PyTypeObject TreeIterType; +extern PyTypeObject IndexType; void TreeEntry_dealloc(TreeEntry *self) @@ -232,20 +233,26 @@ PyObject * Tree_diff_tree(Tree *self, PyObject *args) { Diff *py_diff; - Tree *py_tree; + PyObject* py_obj = NULL; - if (!PyArg_ParseTuple(args, "O!", &TreeType, &py_tree)) { + if (!PyArg_ParseTuple(args, "|O", &py_obj)) return NULL; + + if (py_obj != NULL && !PyObject_TypeCheck(py_obj, &TreeType) && + !PyObject_TypeCheck(py_obj, &IndexType)) { + + PyErr_SetObject(PyExc_TypeError, py_obj); + return NULL; } py_diff = PyObject_New(Diff, &DiffType); if (py_diff) { Py_INCREF(py_diff); - Py_INCREF(py_tree); Py_INCREF(self); + Py_XINCREF(py_obj); - py_diff->t0 = self; - py_diff->t1 = py_tree; + py_diff->a = (PyObject*) self; + py_diff->b = (PyObject*) py_obj; } return (PyObject*) py_diff; @@ -269,8 +276,14 @@ PyMappingMethods Tree_as_mapping = { }; PyMethodDef Tree_methods[] = { - {"diff", (PyCFunction)Tree_diff_tree, METH_VARARGS, - "Diff two trees."}, + { + "diff", (PyCFunction)Tree_diff_tree, METH_VARARGS, + "Get changes between current tree instance with another tree, an " + "index or the working dir.\n\n" + "@param obj : if not given compare diff against working dir. " + "Possible valid arguments are instances of Tree or Index.\n" + "@returns Diff instance" + }, {NULL} }; diff --git a/test/test_diff.py b/test/test_diff.py index fadaff2..bc88af2 100644 --- a/test/test_diff.py +++ b/test/test_diff.py @@ -39,6 +39,7 @@ __author__ = 'Nico.Geyso@FU-Berlin.de (Nico von Geyso)' COMMIT_SHA1_1 = '5fe808e8953c12735680c257f56600cb0de44b10' COMMIT_SHA1_2 = 'c2792cfa289ae6321ecf2cd5806c2194b0fd070c' + PATCH = b"""diff --git a/a b/a index 7f129fd..af431f2 100644 --- a/a @@ -55,6 +56,45 @@ index 297efb8..0000000 -c/d contents """ +DIFF_INDEX_EXPECTED = [ + "staged_changes", + "staged_changes_file_deleted", + "staged_changes_file_modified", + "staged_delete", + "staged_delete_file_modified", + "staged_new", + "staged_new_file_deleted", + "staged_new_file_modified" +] + +DIFF_WORKDIR_EXPECTED = [ + 'file_deleted', + 'modified_file', + 'staged_changes', + 'staged_changes_file_deleted', + 'staged_changes_file_modified', + 'staged_delete', + 'staged_delete_file_modified', + 'subdir/deleted_file', + 'subdir/modified_file' +] + +class DiffDirtyTest(utils.DirtyRepoTestCase): + def test_diff_empty_index(self): + repo = self.repo + head = repo[repo.lookup_reference('HEAD').resolve().oid] + diff = head.tree.diff(repo.index) + + files = [x[1] for x in diff.changes['files']] + self.assertEqual(DIFF_INDEX_EXPECTED, files) + + def test_workdir_to_tree(self): + repo = self.repo + head = repo[repo.lookup_reference('HEAD').resolve().oid] + diff = head.tree.diff() + + files = [x[1] for x in diff.changes['files']] + self.assertEqual(DIFF_WORKDIR_EXPECTED, files) class DiffTest(utils.BareRepoTestCase): @@ -63,6 +103,14 @@ class DiffTest(utils.BareRepoTestCase): commit_b = self.repo[COMMIT_SHA1_2] self.assertRaises(TypeError, commit_a.tree.diff, commit_b) + def test_diff_empty_index(self): + repo = self.repo + head = repo[repo.lookup_reference('HEAD').resolve().oid] + diff = head.tree.diff(repo.index) + + files = [x[0].split('/')[0] for x in diff.changes['files']] + self.assertEqual([x.name for x in head.tree], files) + def test_diff_tree(self): commit_a = self.repo[COMMIT_SHA1_1] commit_b = self.repo[COMMIT_SHA1_2]