Skip to content

Commit 3070a22

Browse files
committed
Merge branch 'master' into patch_tests
2 parents d463fd7 + 91dfaf2 commit 3070a22

File tree

6 files changed

+166
-32
lines changed

6 files changed

+166
-32
lines changed

src/diff.c

+81-1
Original file line numberDiff line numberDiff line change
@@ -475,13 +475,92 @@ PyTypeObject DiffIterType = {
475475
(iternextfunc) DiffIter_iternext, /* tp_iternext */
476476
};
477477

478+
PyObject *
479+
diff_get_delta_byindex(git_diff *diff, size_t idx)
480+
{
481+
const git_diff_delta *delta = git_diff_get_delta(diff, idx);
482+
if (delta == NULL) {
483+
PyErr_SetObject(PyExc_IndexError, PyInt_FromSize_t(idx));
484+
return NULL;
485+
}
486+
487+
return (PyObject*) wrap_diff_delta(delta);
488+
}
489+
490+
PyObject *
491+
DeltasIter_iternext(DeltasIter *self)
492+
{
493+
if (self->i < self->n)
494+
return diff_get_delta_byindex(self->diff->diff, self->i++);
495+
496+
PyErr_SetNone(PyExc_StopIteration);
497+
return NULL;
498+
}
499+
500+
void
501+
DeltasIter_dealloc(DeltasIter *self)
502+
{
503+
Py_CLEAR(self->diff);
504+
PyObject_Del(self);
505+
}
506+
507+
PyDoc_STRVAR(DeltasIter__doc__, "Deltas iterator object.");
508+
509+
PyTypeObject DeltasIterType = {
510+
PyVarObject_HEAD_INIT(NULL, 0)
511+
"_pygit2.DeltasIter", /* tp_name */
512+
sizeof(DeltasIter), /* tp_basicsize */
513+
0, /* tp_itemsize */
514+
(destructor)DeltasIter_dealloc, /* tp_dealloc */
515+
0, /* tp_print */
516+
0, /* tp_getattr */
517+
0, /* tp_setattr */
518+
0, /* tp_compare */
519+
0, /* tp_repr */
520+
0, /* tp_as_number */
521+
0, /* tp_as_sequence */
522+
0, /* tp_as_mapping */
523+
0, /* tp_hash */
524+
0, /* tp_call */
525+
0, /* tp_str */
526+
0, /* tp_getattro */
527+
0, /* tp_setattro */
528+
0, /* tp_as_buffer */
529+
Py_TPFLAGS_DEFAULT, /* tp_flags */
530+
DeltasIter__doc__, /* tp_doc */
531+
0, /* tp_traverse */
532+
0, /* tp_clear */
533+
0, /* tp_richcompare */
534+
0, /* tp_weaklistoffset */
535+
PyObject_SelfIter, /* tp_iter */
536+
(iternextfunc) DeltasIter_iternext, /* tp_iternext */
537+
};
538+
539+
478540
Py_ssize_t
479541
Diff_len(Diff *self)
480542
{
481543
assert(self->diff);
482544
return (Py_ssize_t)git_diff_num_deltas(self->diff);
483545
}
484546

547+
PyDoc_STRVAR(Diff_deltas__doc__, "Iterate over the diff deltas.");
548+
549+
PyObject *
550+
Diff_deltas__get__(Diff *self)
551+
{
552+
DeltasIter *iter;
553+
554+
iter = PyObject_New(DeltasIter, &DeltasIterType);
555+
if (iter != NULL) {
556+
Py_INCREF(self);
557+
iter->diff = self;
558+
iter->i = 0;
559+
iter->n = git_diff_num_deltas(self->diff);
560+
}
561+
return (PyObject*)iter;
562+
}
563+
485564
PyDoc_STRVAR(Diff_patch__doc__,
486565
"Patch diff string. Can be None in some cases, such as empty commits.");
487566

@@ -814,7 +893,7 @@ Diff_getitem(Diff *self, PyObject *value)
814893
return diff_get_patch_byindex(self->diff, i);
815894
}
816895

817-
PyDoc_STRVAR(Diff_stats__doc__, "Accumulate diff statistics for all patches");
896+
PyDoc_STRVAR(Diff_stats__doc__, "Accumulate diff statistics for all patches.");
818897

819898
PyObject *
820899
Diff_stats__get__(Diff *self)
@@ -831,6 +910,7 @@ Diff_dealloc(Diff *self)
831910
}
832911

833912
PyGetSetDef Diff_getseters[] = {
913+
GETTER(Diff, deltas),
834914
GETTER(Diff, patch),
835915
GETTER(Diff, stats),
836916
{NULL}

src/patch.c

+21-31
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,7 @@ wrap_patch(git_patch *patch)
4646
PyObject *py_hunk;
4747
size_t i, hunk_amounts;
4848

49-
if (!patch)
50-
Py_RETURN_NONE;
49+
assert(patch);
5150

5251
py_patch = PyObject_New(Patch, &PatchType);
5352
if (py_patch) {
@@ -78,9 +77,7 @@ PyDoc_STRVAR(Patch_delta__doc__, "Get the delta associated with a patch.");
7877
PyObject *
7978
Patch_delta__get__(Patch *self)
8079
{
81-
if (!self->patch)
82-
Py_RETURN_NONE;
83-
80+
assert(self->patch);
8481
return wrap_diff_delta(git_patch_get_delta(self->patch));
8582
}
8683

@@ -93,11 +90,8 @@ Patch_line_stats__get__(Patch *self)
9390
size_t context, additions, deletions;
9491
int err;
9592

96-
if (!self->patch)
97-
Py_RETURN_NONE;
98-
99-
err = git_patch_line_stats(&context, &additions, &deletions,
100-
self->patch);
93+
assert(self->patch);
94+
err = git_patch_line_stats(&context, &additions, &deletions, self->patch);
10195
if (err < 0)
10296
return Error_set(err);
10397

@@ -124,11 +118,14 @@ Patch_create_from(PyObject *self, PyObject *args, PyObject *kwds)
124118
Py_ssize_t oldbuflen, newbuflen;
125119
int err;
126120

127-
char *keywords[] = {"old", "new", "flag", "old_as_path", "new_as_path", NULL};
121+
char *keywords[] = {"old", "new", "old_as_path", "new_as_path",
122+
"flag", "context_lines", "interhunk_lines",
123+
NULL};
128124

129-
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|Izz", keywords,
130-
&oldobj, &newobj, &opts.flags,
131-
&old_as_path, &new_as_path))
125+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|zzIHH", keywords,
126+
&oldobj, &newobj, &old_as_path, &new_as_path,
127+
&opts.flags, &opts.context_lines,
128+
&opts.interhunk_lines))
132129
return NULL;
133130

134131
if (oldobj != Py_None && PyObject_TypeCheck(oldobj, &BlobType))
@@ -168,12 +165,11 @@ Patch_create_from(PyObject *self, PyObject *args, PyObject *kwds)
168165
err = git_patch_from_buffers(&patch, oldbuf, oldbuflen, old_as_path,
169166
newbuf, newbuflen, new_as_path, &opts);
170167
}
171-
168+
172169
if (err < 0)
173170
return Error_set(err);
174171

175172
return wrap_patch(patch);
176-
177173
}
178174

179175

@@ -183,24 +179,18 @@ PyDoc_STRVAR(Patch_patch__doc__,
183179
PyObject *
184180
Patch_patch__get__(Patch *self)
185181
{
186-
git_buf buf = {NULL};
187-
int err = GIT_ERROR;
188-
PyObject *py_patch = NULL;
189-
190-
err = git_patch_to_buf(&buf, self->patch);
191-
192-
if (!self->patch)
193-
Py_RETURN_NONE;
194-
195-
if (err < 0)
196-
goto cleanup;
182+
git_buf buf = {NULL};
183+
int err;
184+
PyObject *py_patch;
197185

198-
py_patch = to_unicode(buf.ptr, NULL, NULL);
199-
git_buf_free(&buf);
186+
assert(self->patch);
187+
err = git_patch_to_buf(&buf, self->patch);
188+
if (err < 0)
189+
return Error_set(err);
200190

201-
cleanup:
191+
py_patch = to_unicode(buf.ptr, NULL, NULL);
202192
git_buf_free(&buf);
203-
return (err < 0) ? Error_set(err) : py_patch;
193+
return py_patch;
204194
}
205195

206196
PyMethodDef Patch_methods[] = {

src/pygit2.c

+2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ extern PyTypeObject OidType;
4343
extern PyTypeObject ObjectType;
4444
extern PyTypeObject CommitType;
4545
extern PyTypeObject DiffType;
46+
extern PyTypeObject DeltasIterType;
4647
extern PyTypeObject DiffIterType;
4748
extern PyTypeObject DiffDeltaType;
4849
extern PyTypeObject DiffFileType;
@@ -335,6 +336,7 @@ moduleinit(PyObject* m)
335336
* Diff
336337
*/
337338
INIT_TYPE(DiffType, NULL, NULL)
339+
INIT_TYPE(DeltasIterType, NULL, NULL)
338340
INIT_TYPE(DiffIterType, NULL, NULL)
339341
INIT_TYPE(DiffDeltaType, NULL, NULL)
340342
INIT_TYPE(DiffFileType, NULL, NULL)

src/types.h

+7
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,13 @@ typedef struct {
100100
/* git_diff */
101101
SIMPLE_TYPE(Diff, git_diff, diff)
102102

103+
typedef struct {
104+
PyObject_HEAD
105+
Diff *diff;
106+
size_t i;
107+
size_t n;
108+
} DeltasIter;
109+
103110
typedef struct {
104111
PyObject_HEAD
105112
Diff *diff;

test/test_diff.py

+18
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,24 @@ def test_diff_stats(self):
325325
width=80)
326326
self.assertEqual(STATS_EXPECTED, formatted)
327327

328+
def test_deltas(self):
329+
commit_a = self.repo[COMMIT_SHA1_1]
330+
commit_b = self.repo[COMMIT_SHA1_2]
331+
diff = commit_a.tree.diff_to_tree(commit_b.tree)
332+
deltas = list(diff.deltas)
333+
patches = list(diff)
334+
self.assertEqual(len(deltas), len(patches))
335+
for i, delta in enumerate(deltas):
336+
patch_delta = patches[i].delta
337+
self.assertEqual(delta.status, patch_delta.status)
338+
self.assertEqual(delta.similarity, patch_delta.similarity)
339+
self.assertEqual(delta.nfiles, patch_delta.nfiles)
340+
self.assertEqual(delta.old_file.id, patch_delta.old_file.id)
341+
self.assertEqual(delta.new_file.id, patch_delta.new_file.id)
342+
343+
# As explained in the libgit2 documentation, flags are not set
344+
#self.assertEqual(delta.flags, patch_delta.flags)
345+
328346

329347
class BinaryDiffTest(utils.BinaryFileRepoTestCase):
330348
def test_binary_diff(self):

test/test_patch.py

+37
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,46 @@ def test_patch_create_from_bad_new_type_arg(self):
156156
self.repo,
157157
)
158158

159+
def test_context_lines(self):
160+
old_blob = self.repo[BLOB_OLD_SHA]
161+
new_blob = self.repo[BLOB_NEW_SHA]
162+
163+
patch = pygit2.Patch.create_from(
164+
old_blob,
165+
new_blob,
166+
old_as_path=BLOB_OLD_PATH,
167+
new_as_path=BLOB_NEW_PATH,
168+
)
169+
170+
context_count = (
171+
len([line for line in patch.patch.splitlines() if line.startswith(" ")])
172+
)
173+
174+
self.assertNotEqual(context_count, 0)
175+
176+
def test_no_context_lines(self):
177+
old_blob = self.repo[BLOB_OLD_SHA]
178+
new_blob = self.repo[BLOB_NEW_SHA]
179+
180+
patch = pygit2.Patch.create_from(
181+
old_blob,
182+
new_blob,
183+
old_as_path=BLOB_OLD_PATH,
184+
new_as_path=BLOB_NEW_PATH,
185+
context_lines=0,
186+
)
187+
188+
context_count = (
189+
len([line for line in patch.patch.splitlines() if line.startswith(" ")])
190+
)
191+
192+
self.assertEqual(context_count, 0)
193+
194+
159195
def test_patch_create_blob_blobs(self):
160196
old_blob = self.repo[self.repo.create_blob(BLOB_OLD_CONTENT)]
161197
new_blob = self.repo[self.repo.create_blob(BLOB_NEW_CONTENT)]
198+
162199
patch = pygit2.Patch.create_from(
163200
old_blob,
164201
new_blob,

0 commit comments

Comments
 (0)