Skip to content

Commit dcc9051

Browse files
committed
Support diff for blobs
1 parent cde2456 commit dcc9051

File tree

4 files changed

+149
-34
lines changed

4 files changed

+149
-34
lines changed

src/blob.c

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,105 @@
2727

2828
#define PY_SSIZE_T_CLEAN
2929
#include <Python.h>
30+
#include "diff.h"
31+
#include "error.h"
3032
#include "utils.h"
3133
#include "object.h"
3234
#include "blob.h"
3335

36+
extern PyObject *GitError;
37+
38+
extern PyTypeObject BlobType;
39+
40+
PyDoc_STRVAR(Blob_diff__doc__,
41+
"diff([blob, flag, old_as_path, new_as_path] -> Patch\n"
42+
"\n"
43+
"Directly generate a :py:class:`pygit2.Patch` from the difference\n"
44+
" between two blobs.\n"
45+
"\n"
46+
"Arguments:\n"
47+
"\n"
48+
"blob: the :py:class:`~pygit2.Blob` to diff.\n"
49+
"\n"
50+
"flag: a GIT_DIFF_* constant.\n"
51+
"\n"
52+
"old_as_path: treat old blob as if it had this filename.\n"
53+
"\n"
54+
"new_as_path: treat new blob as if it had this filename.\n");
55+
56+
PyObject *
57+
Blob_diff(Blob *self, PyObject *args, PyObject *kwds)
58+
{
59+
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
60+
git_patch *patch;
61+
char *old_as_path = NULL, *new_as_path = NULL;
62+
Blob *py_blob = NULL;
63+
int err;
64+
char *keywords[] = {"blob", "flag", "old_as_path", "new_as_path", NULL};
65+
66+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O!ssI", keywords,
67+
&BlobType, &py_blob, &opts.flags,
68+
&old_as_path, &new_as_path))
69+
return NULL;
70+
71+
err = git_patch_from_blobs(&patch, self->blob, old_as_path,
72+
py_blob ? py_blob->blob : NULL, new_as_path,
73+
&opts);
74+
if (err < 0)
75+
return Error_set(err);
76+
77+
return wrap_patch(patch);
78+
}
79+
80+
81+
PyDoc_STRVAR(Blob_diff_to_buffer__doc__,
82+
"diff_to_buffer([buffer, flag, old_as_path, buffer_as_path] -> Patch\n"
83+
"\n"
84+
"Directly generate a :py:class:`~pygit2.Patch` from the difference\n"
85+
" between a blob and a buffer.\n"
86+
"\n"
87+
"Arguments:\n"
88+
"\n"
89+
"buffer: Raw data for new side of diff.\n"
90+
"\n"
91+
"flag: a GIT_DIFF_* constant.\n"
92+
"\n"
93+
"old_as_path: treat old blob as if it had this filename.\n"
94+
"\n"
95+
"buffer_as_path: treat buffer as if it had this filename.\n");
96+
97+
PyObject *
98+
Blob_diff_to_buffer(Blob *self, PyObject *args, PyObject *kwds)
99+
{
100+
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
101+
git_patch *patch;
102+
char *old_as_path = NULL, *buffer_as_path = NULL;
103+
const char *buffer = NULL;
104+
Py_ssize_t buffer_len;
105+
int err;
106+
char *keywords[] = {"buffer", "flag", "old_as_path", "buffer_as_path",
107+
NULL};
108+
109+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|s#ssI", keywords,
110+
&buffer, &buffer_len, &opts.flags,
111+
&old_as_path, &buffer_as_path))
112+
return NULL;
113+
114+
err = git_patch_from_blob_and_buffer(&patch, self->blob, old_as_path,
115+
buffer, buffer_len, buffer_as_path,
116+
&opts);
117+
if (err < 0)
118+
return Error_set(err);
119+
120+
return wrap_patch(patch);
121+
}
122+
123+
static PyMethodDef Blob_methods[] = {
124+
METHOD(Blob, diff, METH_VARARGS | METH_KEYWORDS),
125+
METHOD(Blob, diff_to_buffer, METH_VARARGS | METH_KEYWORDS),
126+
{NULL}
127+
};
128+
34129

35130
PyDoc_STRVAR(Blob_size__doc__, "Size.");
36131

@@ -94,7 +189,7 @@ PyTypeObject BlobType = {
94189
0, /* tp_weaklistoffset */
95190
0, /* tp_iter */
96191
0, /* tp_iternext */
97-
0, /* tp_methods */
192+
Blob_methods, /* tp_methods */
98193
0, /* tp_members */
99194
Blob_getseters, /* tp_getset */
100195
0, /* tp_base */

src/diff.c

Lines changed: 41 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -57,27 +57,24 @@ wrap_diff(git_diff *diff, Repository *repo)
5757
return (PyObject*) py_diff;
5858
}
5959

60-
PyObject*
61-
diff_get_patch_byindex(git_diff* diff, size_t idx)
60+
PyObject *
61+
wrap_patch(git_patch *patch)
6262
{
63-
const git_diff_delta* delta;
64-
const git_diff_hunk *hunk;
65-
const git_diff_line *line;
66-
git_patch* patch = NULL;
67-
size_t i, j, hunk_amounts, lines_in_hunk, additions, deletions;
68-
int err;
69-
Hunk *py_hunk = NULL;
70-
Patch *py_patch = NULL;
71-
PyObject *py_line_origin=NULL, *py_line=NULL;
72-
73-
err = git_patch_from_diff(&patch, diff, idx);
74-
if (err < 0)
75-
return Error_set(err);
63+
Patch *py_patch;
7664

77-
delta = git_patch_get_delta(patch);
65+
if (!patch)
66+
Py_RETURN_NONE;
7867

7968
py_patch = PyObject_New(Patch, &PatchType);
80-
if (py_patch != NULL) {
69+
if (py_patch) {
70+
size_t i, j, hunk_amounts, lines_in_hunk, additions, deletions;
71+
const git_diff_delta *delta;
72+
const git_diff_hunk *hunk;
73+
const git_diff_line *line;
74+
int err;
75+
76+
delta = git_patch_get_delta(patch);
77+
8178
py_patch->old_file_path = delta->old_file.path;
8279
py_patch->new_file_path = delta->new_file.path;
8380
py_patch->status = git_diff_status_char(delta->status);
@@ -92,11 +89,12 @@ diff_get_patch_byindex(git_diff* diff, size_t idx)
9289

9390
hunk_amounts = git_patch_num_hunks(patch);
9491
py_patch->hunks = PyList_New(hunk_amounts);
95-
for (i=0; i < hunk_amounts; ++i) {
96-
err = git_patch_get_hunk(&hunk, &lines_in_hunk, patch, i);
92+
for (i = 0; i < hunk_amounts; ++i) {
93+
Hunk *py_hunk = NULL;
9794

95+
err = git_patch_get_hunk(&hunk, &lines_in_hunk, patch, i);
9896
if (err < 0)
99-
goto cleanup;
97+
return Error_set(err);
10098

10199
py_hunk = PyObject_New(Hunk, &HunkType);
102100
if (py_hunk != NULL) {
@@ -106,20 +104,20 @@ diff_get_patch_byindex(git_diff* diff, size_t idx)
106104
py_hunk->new_lines = hunk->new_lines;
107105

108106
py_hunk->lines = PyList_New(lines_in_hunk);
109-
for (j=0; j < lines_in_hunk; ++j) {
110-
err = git_patch_get_line_in_hunk(&line, patch, i, j);
107+
for (j = 0; j < lines_in_hunk; ++j) {
108+
PyObject *py_line_origin = NULL, *py_line = NULL;
111109

110+
err = git_patch_get_line_in_hunk(&line, patch, i, j);
112111
if (err < 0)
113-
goto cleanup;
112+
return Error_set(err);
114113

115-
py_line_origin = to_unicode_n(&line->origin, 1, NULL, NULL);
116-
py_line = to_unicode_n(line->content, line->content_len, NULL, NULL);
114+
py_line_origin = to_unicode_n(&line->origin, 1,
115+
NULL, NULL);
116+
py_line = to_unicode_n(line->content, line->content_len,
117+
NULL, NULL);
117118
PyList_SetItem(py_hunk->lines, j,
118-
Py_BuildValue("OO",
119-
py_line_origin,
120-
py_line
121-
)
122-
);
119+
Py_BuildValue("OO", py_line_origin, py_line));
120+
123121
Py_DECREF(py_line_origin);
124122
Py_DECREF(py_line);
125123
}
@@ -130,10 +128,20 @@ diff_get_patch_byindex(git_diff* diff, size_t idx)
130128
}
131129
}
132130

133-
cleanup:
134-
git_patch_free(patch);
131+
return (PyObject*) py_patch;
132+
}
133+
134+
PyObject*
135+
diff_get_patch_byindex(git_diff *diff, size_t idx)
136+
{
137+
git_patch *patch = NULL;
138+
int err;
139+
140+
err = git_patch_from_diff(&patch, diff, idx);
141+
if (err < 0)
142+
return Error_set(err);
135143

136-
return (err < 0) ? Error_set(err) : (PyObject*) py_patch;
144+
return (PyObject*) wrap_patch(patch);
137145
}
138146

139147
static void

src/diff.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,6 @@ PyObject* Diff_changes(Diff *self);
4242
PyObject* Diff_patch(Diff *self);
4343

4444
PyObject* wrap_diff(git_diff *diff, Repository *repo);
45+
PyObject* wrap_patch(git_patch *patch);
4546

4647
#endif

test/test_blob.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,5 +105,16 @@ def test_create_blob_fromdisk(self):
105105
self.assertTrue(isinstance(blob, pygit2.Blob))
106106
self.assertEqual(pygit2.GIT_OBJ_BLOB, blob.type)
107107

108+
def test_diff_blob(self):
109+
blob = self.repo[BLOB_SHA]
110+
old_blob = self.repo['3b18e512dba79e4c8300dd08aeb37f8e728b8dad']
111+
patch = blob.diff(old_blob, old_as_path="hello.txt")
112+
self.assertEqual(len(patch.hunks), 1)
113+
114+
def test_diff_blob_to_buffer(self):
115+
blob = self.repo[BLOB_SHA]
116+
patch = blob.diff_to_buffer("hello world")
117+
self.assertEqual(len(patch.hunks), 1)
118+
108119
if __name__ == '__main__':
109120
unittest.main()

0 commit comments

Comments
 (0)