Skip to content

Commit 7cb904b

Browse files
Edward ThomsonEdward Thomson
authored andcommitted
Introduce git_apply_patch
The beginnings of patch application from an existing (diff-created) git_patch object: applies the hunks of a git_patch to a buffer.
1 parent 784bb30 commit 7cb904b

File tree

8 files changed

+811
-2
lines changed

8 files changed

+811
-2
lines changed

include/git2/errors.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ typedef enum {
9898
GITERR_CHERRYPICK,
9999
GITERR_DESCRIBE,
100100
GITERR_REBASE,
101-
GITERR_FILESYSTEM
101+
GITERR_FILESYSTEM,
102+
GITERR_PATCH,
102103
} git_error_t;
103104

104105
/**

src/apply.c

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
/*
2+
* Copyright (C) the libgit2 contributors. All rights reserved.
3+
*
4+
* This file is part of libgit2, distributed under the GNU GPL v2 with
5+
* a Linking Exception. For full terms see the included COPYING file.
6+
*/
7+
8+
#include <assert.h>
9+
10+
#include "git2/patch.h"
11+
#include "git2/filter.h"
12+
#include "array.h"
13+
#include "diff_patch.h"
14+
#include "fileops.h"
15+
#include "apply.h"
16+
17+
#define apply_err(...) \
18+
( giterr_set(GITERR_PATCH, __VA_ARGS__), -1 )
19+
20+
typedef struct {
21+
/* The lines that we allocate ourself are allocated out of the pool.
22+
* (Lines may have been allocated out of the diff.)
23+
*/
24+
git_pool pool;
25+
git_vector lines;
26+
} patch_image;
27+
28+
static void patch_line_init(
29+
git_diff_line *out,
30+
const char *in,
31+
size_t in_len,
32+
size_t in_offset)
33+
{
34+
out->content = in;
35+
out->content_len = in_len;
36+
out->content_offset = in_offset;
37+
}
38+
39+
static unsigned int patch_image_init(patch_image *out)
40+
{
41+
memset(out, 0x0, sizeof(patch_image));
42+
return 0;
43+
}
44+
45+
static int patch_image_init_fromstr(
46+
patch_image *out, const char *in, size_t in_len)
47+
{
48+
git_diff_line *line;
49+
const char *start, *end;
50+
51+
memset(out, 0x0, sizeof(patch_image));
52+
53+
git_pool_init(&out->pool, sizeof(git_diff_line));
54+
55+
for (start = in; start < in + in_len; start = end) {
56+
end = memchr(start, '\n', in_len);
57+
58+
if (end < in + in_len)
59+
end++;
60+
61+
line = git_pool_mallocz(&out->pool, 1);
62+
GITERR_CHECK_ALLOC(line);
63+
64+
if (git_vector_insert(&out->lines, line) < 0)
65+
return -1;
66+
67+
patch_line_init(line, start, (end - start), (start - in));
68+
}
69+
70+
return 0;
71+
}
72+
73+
static void patch_image_free(patch_image *image)
74+
{
75+
if (image == NULL)
76+
return;
77+
78+
git_pool_clear(&image->pool);
79+
git_vector_free(&image->lines);
80+
}
81+
82+
static bool match_hunk(
83+
patch_image *image,
84+
patch_image *preimage,
85+
size_t linenum)
86+
{
87+
bool match = 0;
88+
size_t i;
89+
90+
/* Ensure this hunk is within the image boundaries. */
91+
if (git_vector_length(&preimage->lines) + linenum >
92+
git_vector_length(&image->lines))
93+
return 0;
94+
95+
match = 1;
96+
97+
/* Check exact match. */
98+
for (i = 0; i < git_vector_length(&preimage->lines); i++) {
99+
git_diff_line *preimage_line = git_vector_get(&preimage->lines, i);
100+
git_diff_line *image_line = git_vector_get(&image->lines, linenum + i);
101+
102+
if (preimage_line->content_len != preimage_line->content_len ||
103+
memcmp(preimage_line->content, image_line->content, image_line->content_len) != 0) {
104+
match = 0;
105+
break;
106+
}
107+
}
108+
109+
return match;
110+
}
111+
112+
static bool find_hunk_linenum(
113+
size_t *out,
114+
patch_image *image,
115+
patch_image *preimage,
116+
size_t linenum)
117+
{
118+
size_t max = git_vector_length(&image->lines);
119+
bool match;
120+
121+
if (linenum > max)
122+
linenum = max;
123+
124+
match = match_hunk(image, preimage, linenum);
125+
126+
*out = linenum;
127+
return match;
128+
}
129+
130+
static int update_hunk(
131+
patch_image *image,
132+
unsigned int linenum,
133+
patch_image *preimage,
134+
patch_image *postimage)
135+
{
136+
size_t postlen = git_vector_length(&postimage->lines);
137+
size_t prelen = git_vector_length(&preimage->lines);
138+
size_t i;
139+
int error = 0;
140+
141+
if (postlen > prelen)
142+
error = git_vector_grow_at(
143+
&image->lines, linenum, (postlen - prelen));
144+
else if (prelen > postlen)
145+
error = git_vector_shrink_at(
146+
&image->lines, linenum, (prelen - postlen));
147+
148+
if (error) {
149+
giterr_set_oom();
150+
return -1;
151+
}
152+
153+
for (i = 0; i < git_vector_length(&postimage->lines); i++) {
154+
image->lines.contents[linenum + i] =
155+
git_vector_get(&postimage->lines, i);
156+
}
157+
158+
return 0;
159+
}
160+
161+
static int apply_hunk(
162+
patch_image *image,
163+
git_patch *patch,
164+
diff_patch_hunk *hunk)
165+
{
166+
patch_image preimage, postimage;
167+
size_t line_num, i;
168+
int error = 0;
169+
170+
if ((error = patch_image_init(&preimage)) < 0 ||
171+
(error = patch_image_init(&postimage)) < 0)
172+
goto done;
173+
174+
for (i = 0; i < hunk->line_count; i++) {
175+
size_t linenum = hunk->line_start + i;
176+
git_diff_line *line = git_array_get(patch->lines, linenum);
177+
178+
if (!line) {
179+
error = apply_err("Preimage does not contain line %d", linenum);
180+
goto done;
181+
}
182+
183+
if (line->origin == GIT_DIFF_LINE_CONTEXT ||
184+
line->origin == GIT_DIFF_LINE_DELETION) {
185+
if ((error = git_vector_insert(&preimage.lines, line)) < 0)
186+
goto done;
187+
}
188+
189+
if (line->origin == GIT_DIFF_LINE_CONTEXT ||
190+
line->origin == GIT_DIFF_LINE_ADDITION) {
191+
if ((error = git_vector_insert(&postimage.lines, line)) < 0)
192+
goto done;
193+
}
194+
}
195+
196+
line_num = hunk->hunk.new_start ? hunk->hunk.new_start - 1 : 0;
197+
198+
if (!find_hunk_linenum(&line_num, image, &preimage, line_num)) {
199+
error = apply_err("Hunk at line %d did not apply",
200+
hunk->hunk.new_start);
201+
goto done;
202+
}
203+
204+
error = update_hunk(image, line_num, &preimage, &postimage);
205+
206+
done:
207+
patch_image_free(&preimage);
208+
patch_image_free(&postimage);
209+
210+
return error;
211+
}
212+
213+
static int apply_hunks(
214+
git_buf *out,
215+
const char *source,
216+
size_t source_len,
217+
git_patch *patch)
218+
{
219+
diff_patch_hunk *hunk;
220+
git_diff_line *line;
221+
patch_image image;
222+
size_t i;
223+
int error = 0;
224+
225+
if ((error = patch_image_init_fromstr(&image, source, source_len)) < 0)
226+
goto done;
227+
228+
git_array_foreach(patch->hunks, i, hunk) {
229+
if ((error = apply_hunk(&image, patch, hunk)) < 0)
230+
goto done;
231+
}
232+
233+
git_vector_foreach(&image.lines, i, line)
234+
git_buf_put(out, line->content, line->content_len);
235+
236+
done:
237+
patch_image_free(&image);
238+
239+
return error;
240+
}
241+
242+
int git_apply__patch(
243+
git_buf *contents_out,
244+
char **filename_out,
245+
unsigned int *mode_out,
246+
const char *source,
247+
size_t source_len,
248+
git_patch *patch)
249+
{
250+
char *filename = NULL;
251+
unsigned int mode = 0;
252+
int error = 0;
253+
254+
assert(contents_out && filename_out && mode_out && (source || !source_len) && patch);
255+
256+
*filename_out = NULL;
257+
*mode_out = 0;
258+
259+
if (patch->delta->status != GIT_DELTA_DELETED) {
260+
filename = git__strdup(patch->nfile.file->path);
261+
mode = patch->nfile.file->mode ?
262+
patch->nfile.file->mode : GIT_FILEMODE_BLOB;
263+
}
264+
265+
if ((error = apply_hunks(contents_out, source, source_len, patch)) < 0)
266+
goto done;
267+
268+
if (patch->delta->status == GIT_DELTA_DELETED &&
269+
git_buf_len(contents_out) > 0) {
270+
error = apply_err("removal patch leaves file contents");
271+
goto done;
272+
}
273+
274+
*filename_out = filename;
275+
*mode_out = mode;
276+
277+
done:
278+
if (error < 0)
279+
git__free(filename);
280+
281+
return error;
282+
}

src/apply.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright (C) the libgit2 contributors. All rights reserved.
3+
*
4+
* This file is part of libgit2, distributed under the GNU GPL v2 with
5+
* a Linking Exception. For full terms see the included COPYING file.
6+
*/
7+
#ifndef INCLUDE_apply_h__
8+
#define INCLUDE_apply_h__
9+
10+
#include "git2/patch.h"
11+
#include "buffer.h"
12+
13+
extern int git_apply__patch(
14+
git_buf *out,
15+
char **filename,
16+
unsigned int *mode,
17+
const char *source,
18+
size_t source_len,
19+
git_patch *patch);
20+
21+
#endif

src/array.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ GIT_INLINE(void *) git_array_grow(void *_a, size_t item_size)
8585
#define git_array_foreach(a, i, element) \
8686
for ((i) = 0; (i) < (a).size && ((element) = &(a).ptr[(i)]); (i)++)
8787

88-
8988
GIT_INLINE(int) git_array__search(
9089
size_t *out,
9190
void *array_ptr,

src/vector.c

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,47 @@ int git_vector_resize_to(git_vector *v, size_t new_length)
330330
return 0;
331331
}
332332

333+
int git_vector_grow_at(git_vector *v, size_t idx, size_t grow_len)
334+
{
335+
size_t new_length = v->length + grow_len;
336+
size_t new_idx = idx + grow_len;
337+
338+
assert(grow_len > 0);
339+
assert (idx <= v->length);
340+
341+
if (new_length < v->length ||
342+
(new_length > v->_alloc_size && resize_vector(v, new_length) < 0))
343+
return -1;
344+
345+
memmove(&v->contents[new_idx], &v->contents[idx],
346+
sizeof(void *) * (v->length - idx));
347+
memset(&v->contents[idx], 0, sizeof(void *) * grow_len);
348+
349+
v->length = new_length;
350+
return 0;
351+
}
352+
353+
int git_vector_shrink_at(git_vector *v, size_t idx, size_t shrink_len)
354+
{
355+
size_t new_length = v->length - shrink_len;
356+
size_t end_idx = idx + shrink_len;
357+
358+
assert(shrink_len > 0 && shrink_len <= v->length);
359+
assert(idx <= v->length);
360+
361+
if (new_length > v->length)
362+
return -1;
363+
364+
if (idx > v->length)
365+
memmove(&v->contents[idx], &v->contents[end_idx],
366+
sizeof(void *) * (v->length - idx));
367+
368+
memset(&v->contents[new_length], 0, sizeof(void *) * shrink_len);
369+
370+
v->length = new_length;
371+
return 0;
372+
}
373+
333374
int git_vector_set(void **old, git_vector *v, size_t position, void *value)
334375
{
335376
if (position + 1 > v->length) {

src/vector.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ void git_vector_remove_matching(
9393
void *payload);
9494

9595
int git_vector_resize_to(git_vector *v, size_t new_length);
96+
int git_vector_grow_at(git_vector *v, size_t idx, size_t grow_len);
97+
int git_vector_shrink_at(git_vector *v, size_t idx, size_t shrink_len);
98+
9699
int git_vector_set(void **old, git_vector *v, size_t position, void *value);
97100

98101
/** Check if vector is sorted */

0 commit comments

Comments
 (0)