1 /*
2  * Copyright 2010-2021 The pygit2 contributors
3  *
4  * This file is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License, version 2,
6  * as published by the Free Software Foundation.
7  *
8  * In addition to the permissions in the GNU General Public License,
9  * the authors give you unlimited permission to link the compiled
10  * version of this file into combinations with other programs,
11  * and to distribute those combinations without any restriction
12  * coming from the use of this file.  (The General Public License
13  * restrictions do apply in other respects; for example, they cover
14  * modification of the file, and distribution when not linked into
15  * a combined executable.)
16  *
17  * This file is distributed in the hope that it will be useful, but
18  * WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; see the file COPYING.  If not, write to
24  * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
25  * Boston, MA 02110-1301, USA.
26  */
27 
28 #define PY_SSIZE_T_CLEAN
29 #include <Python.h>
30 #include "blob.h"
31 #include "diff.h"
32 #include "error.h"
33 #include "object.h"
34 #include "patch.h"
35 #include "utils.h"
36 
37 extern PyObject *GitError;
38 
39 extern PyTypeObject BlobType;
40 
41 PyDoc_STRVAR(Blob_diff__doc__,
42   "diff([blob, flag, old_as_path, new_as_path])\n"
43   "\n"
44   "Directly generate a :py:class:`pygit2.Patch` from the difference\n"
45   "between two blobs.\n"
46   "\n"
47   "Returns: Patch.\n"
48   "\n"
49   "Parameters:\n"
50   "\n"
51   "blob : Blob\n"
52   "    The :py:class:`~pygit2.Blob` to diff.\n"
53   "\n"
54   "flag\n"
55   "    A GIT_DIFF_* constant.\n"
56   "\n"
57   "old_as_path : str\n"
58   "    Treat old blob as if it had this filename.\n"
59   "\n"
60   "new_as_path : str\n"
61   "    Treat new blob as if it had this filename.\n");
62 
63 PyObject *
Blob_diff(Blob * self,PyObject * args,PyObject * kwds)64 Blob_diff(Blob *self, PyObject *args, PyObject *kwds)
65 {
66     git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
67     git_patch *patch;
68     char *old_as_path = NULL, *new_as_path = NULL;
69     Blob *other = NULL;
70     int err;
71     char *keywords[] = {"blob", "flag", "old_as_path", "new_as_path", NULL};
72 
73     if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O!Iss", keywords,
74                                      &BlobType, &other, &opts.flags,
75                                      &old_as_path, &new_as_path))
76         return NULL;
77 
78     if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load
79     if (other && Object__load((Object*)other) == NULL) { return NULL; } // Lazy load
80 
81     err = git_patch_from_blobs(&patch, self->blob, old_as_path,
82                                other ? other->blob : NULL, new_as_path,
83                                &opts);
84     if (err < 0)
85         return Error_set(err);
86 
87     return wrap_patch(patch, self, other);
88 }
89 
90 
91 PyDoc_STRVAR(Blob_diff_to_buffer__doc__,
92   "diff_to_buffer([buffer, flag, old_as_path, buffer_as_path])\n"
93   "\n"
94   "Directly generate a :py:class:`~pygit2.Patch` from the difference\n"
95   "between a blob and a buffer.\n"
96   "\n"
97   "Returns: Patch.\n"
98   "\n"
99   "Parameters:\n"
100   "\n"
101   "buffer : Blob\n"
102   "    Raw data for new side of diff.\n"
103   "\n"
104   "flag\n"
105   "    A GIT_DIFF_* constant.\n"
106   "\n"
107   "old_as_path : str\n"
108   "    Treat old blob as if it had this filename.\n"
109   "\n"
110   "buffer_as_path : str\n"
111   "    Treat buffer as if it had this filename.\n");
112 
113 PyObject *
Blob_diff_to_buffer(Blob * self,PyObject * args,PyObject * kwds)114 Blob_diff_to_buffer(Blob *self, PyObject *args, PyObject *kwds)
115 {
116     git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
117     git_patch *patch;
118     char *old_as_path = NULL, *buffer_as_path = NULL;
119     const char *buffer = NULL;
120     Py_ssize_t buffer_len;
121     int err;
122     char *keywords[] = {"buffer", "flag", "old_as_path", "buffer_as_path",
123                         NULL};
124 
125     if (!PyArg_ParseTupleAndKeywords(args, kwds, "|z#Iss", keywords,
126                                      &buffer, &buffer_len, &opts.flags,
127                                      &old_as_path, &buffer_as_path))
128         return NULL;
129 
130     if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load
131 
132     err = git_patch_from_blob_and_buffer(&patch, self->blob, old_as_path,
133                                          buffer, buffer_len, buffer_as_path,
134                                          &opts);
135     if (err < 0)
136         return Error_set(err);
137 
138     return wrap_patch(patch, self, NULL);
139 }
140 
141 static PyMethodDef Blob_methods[] = {
142     METHOD(Blob, diff, METH_VARARGS | METH_KEYWORDS),
143     METHOD(Blob, diff_to_buffer, METH_VARARGS | METH_KEYWORDS),
144     {NULL}
145 };
146 
147 
148 PyDoc_STRVAR(Blob_size__doc__,
149     "Size in bytes.\n"
150     "\n"
151     "Example:\n"
152     "\n"
153     "    >>> print(blob.size)\n"
154     "    130\n");
155 
156 PyObject *
Blob_size__get__(Blob * self)157 Blob_size__get__(Blob *self)
158 {
159     if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load
160     return PyLong_FromLongLong(git_blob_rawsize(self->blob));
161 }
162 
163 
164 PyDoc_STRVAR(Blob_is_binary__doc__, "True if binary data, False if not.");
165 
166 PyObject *
Blob_is_binary__get__(Blob * self)167 Blob_is_binary__get__(Blob *self)
168 {
169     if (Object__load((Object*)self) == NULL) { return NULL; } // Lazy load
170 
171     if (git_blob_is_binary(self->blob))
172         Py_RETURN_TRUE;
173     Py_RETURN_FALSE;
174 }
175 
176 
177 PyDoc_STRVAR(Blob_data__doc__,
178     "The contents of the blob, a byte string. This is the same as\n"
179     "Blob.read_raw().\n"
180     "\n"
181     "Example, print the contents of the ``.gitignore`` file:\n"
182     "\n"
183     "    >>> blob = repo['d8022420bf6db02e906175f64f66676df539f2fd']\n"
184     "    >>> print(blob.data)\n"
185     "    MANIFEST\n"
186     "    build\n"
187     "    dist\n");
188 
189 PyGetSetDef Blob_getseters[] = {
190     GETTER(Blob, size),
191     GETTER(Blob, is_binary),
192     {"data", (getter)Object_read_raw, NULL, Blob_data__doc__, NULL},
193     {NULL}
194 };
195 
196 static int
Blob_getbuffer(Blob * self,Py_buffer * view,int flags)197 Blob_getbuffer(Blob *self, Py_buffer *view, int flags)
198 {
199     if (Object__load((Object*)self) == NULL) { return -1; } // Lazy load
200     return PyBuffer_FillInfo(view, (PyObject *) self,
201                              (void *) git_blob_rawcontent(self->blob),
202                              git_blob_rawsize(self->blob), 1, flags);
203 }
204 
205 static PyBufferProcs Blob_as_buffer = {
206     (getbufferproc)Blob_getbuffer,
207 };
208 
209 PyDoc_STRVAR(Blob__doc__, "Blob object.\n"
210   "\n"
211   "Blobs implement the buffer interface, which means you can get access\n"
212   "to its data via `memoryview(blob)` without the need to create a copy."
213 );
214 
215 PyTypeObject BlobType = {
216     PyVarObject_HEAD_INIT(NULL, 0)
217     "_pygit2.Blob",                            /* tp_name           */
218     sizeof(Blob),                              /* tp_basicsize      */
219     0,                                         /* tp_itemsize       */
220     0,                                         /* tp_dealloc        */
221     0,                                         /* tp_print          */
222     0,                                         /* tp_getattr        */
223     0,                                         /* tp_setattr        */
224     0,                                         /* tp_compare        */
225     (reprfunc)Object_repr,                     /* tp_repr           */
226     0,                                         /* tp_as_number      */
227     0,                                         /* tp_as_sequence    */
228     0,                                         /* tp_as_mapping     */
229     0,                                         /* tp_hash           */
230     0,                                         /* tp_call           */
231     0,                                         /* tp_str            */
232     0,                                         /* tp_getattro       */
233     0,                                         /* tp_setattro       */
234     &Blob_as_buffer,                           /* tp_as_buffer      */
235     Py_TPFLAGS_DEFAULT,                        /* tp_flags          */
236     Blob__doc__,                               /* tp_doc            */
237     0,                                         /* tp_traverse       */
238     0,                                         /* tp_clear          */
239     0,                                         /* tp_richcompare    */
240     0,                                         /* tp_weaklistoffset */
241     0,                                         /* tp_iter           */
242     0,                                         /* tp_iternext       */
243     Blob_methods,                              /* tp_methods        */
244     0,                                         /* tp_members        */
245     Blob_getseters,                            /* tp_getset         */
246     0,                                         /* tp_base           */
247     0,                                         /* tp_dict           */
248     0,                                         /* tp_descr_get      */
249     0,                                         /* tp_descr_set      */
250     0,                                         /* tp_dictoffset     */
251     0,                                         /* tp_init           */
252     0,                                         /* tp_alloc          */
253     0,                                         /* tp_new            */
254 };
255