]>
Commit | Line | Data |
---|---|---|
17572f67 ET |
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 | */ | |
eae0bfdc PP |
7 | |
8 | #include "patch_parse.h" | |
9 | ||
804d5fe9 ET |
10 | #include "git2/patch.h" |
11 | #include "patch.h" | |
b859faa6 | 12 | #include "diff_parse.h" |
804d5fe9 ET |
13 | #include "path.h" |
14 | ||
17572f67 ET |
15 | typedef struct { |
16 | git_patch base; | |
17 | ||
18 | git_patch_parse_ctx *ctx; | |
4117a235 | 19 | |
82175084 ET |
20 | /* the paths from the `diff --git` header, these will be used if this is not |
21 | * a rename (and rename paths are specified) or if no `+++`/`---` line specify | |
22 | * the paths. | |
23 | */ | |
24 | char *header_old_path, *header_new_path; | |
25 | ||
26 | /* renamed paths are precise and are not prefixed */ | |
27 | char *rename_old_path, *rename_new_path; | |
28 | ||
29 | /* the paths given in `---` and `+++` lines */ | |
30 | char *old_path, *new_path; | |
31 | ||
32 | /* the prefixes from the old/new paths */ | |
33 | char *old_prefix, *new_prefix; | |
804d5fe9 ET |
34 | } git_patch_parsed; |
35 | ||
17572f67 | 36 | static int header_path_len(git_patch_parse_ctx *ctx) |
804d5fe9 ET |
37 | { |
38 | bool inquote = 0; | |
eae0bfdc | 39 | bool quoted = git_parse_ctx_contains_s(&ctx->parse_ctx, "\""); |
804d5fe9 ET |
40 | size_t len; |
41 | ||
eae0bfdc PP |
42 | for (len = quoted; len < ctx->parse_ctx.line_len; len++) { |
43 | if (!quoted && git__isspace(ctx->parse_ctx.line[len])) | |
804d5fe9 | 44 | break; |
eae0bfdc | 45 | else if (quoted && !inquote && ctx->parse_ctx.line[len] == '"') { |
804d5fe9 ET |
46 | len++; |
47 | break; | |
48 | } | |
49 | ||
eae0bfdc | 50 | inquote = (!inquote && ctx->parse_ctx.line[len] == '\\'); |
804d5fe9 ET |
51 | } |
52 | ||
53 | return len; | |
54 | } | |
55 | ||
eae0bfdc | 56 | static int parse_header_path_buf(git_buf *path, git_patch_parse_ctx *ctx, size_t path_len) |
804d5fe9 | 57 | { |
eae0bfdc | 58 | int error; |
804d5fe9 | 59 | |
eae0bfdc | 60 | if ((error = git_buf_put(path, ctx->parse_ctx.line, path_len)) < 0) |
804d5fe9 ET |
61 | goto done; |
62 | ||
eae0bfdc | 63 | git_parse_advance_chars(&ctx->parse_ctx, path_len); |
804d5fe9 ET |
64 | |
65 | git_buf_rtrim(path); | |
66 | ||
67 | if (path->size > 0 && path->ptr[0] == '"') | |
68 | error = git_buf_unquote(path); | |
69 | ||
70 | if (error < 0) | |
71 | goto done; | |
72 | ||
73 | git_path_squash_slashes(path); | |
74 | ||
75 | done: | |
76 | return error; | |
77 | } | |
78 | ||
17572f67 | 79 | static int parse_header_path(char **out, git_patch_parse_ctx *ctx) |
804d5fe9 ET |
80 | { |
81 | git_buf path = GIT_BUF_INIT; | |
eae0bfdc | 82 | int error = parse_header_path_buf(&path, ctx, header_path_len(ctx)); |
804d5fe9 ET |
83 | |
84 | *out = git_buf_detach(&path); | |
85 | ||
86 | return error; | |
87 | } | |
88 | ||
89 | static int parse_header_git_oldpath( | |
17572f67 | 90 | git_patch_parsed *patch, git_patch_parse_ctx *ctx) |
804d5fe9 | 91 | { |
eae0bfdc PP |
92 | git_buf old_path = GIT_BUF_INIT; |
93 | int error; | |
94 | ||
95 | if ((error = parse_header_path_buf(&old_path, ctx, ctx->parse_ctx.line_len - 1)) < 0) | |
96 | goto out; | |
97 | ||
98 | patch->old_path = git_buf_detach(&old_path); | |
99 | ||
100 | out: | |
ac3d33df | 101 | git_buf_dispose(&old_path); |
eae0bfdc | 102 | return error; |
804d5fe9 ET |
103 | } |
104 | ||
105 | static int parse_header_git_newpath( | |
17572f67 | 106 | git_patch_parsed *patch, git_patch_parse_ctx *ctx) |
804d5fe9 | 107 | { |
eae0bfdc PP |
108 | git_buf new_path = GIT_BUF_INIT; |
109 | int error; | |
110 | ||
111 | if ((error = parse_header_path_buf(&new_path, ctx, ctx->parse_ctx.line_len - 1)) < 0) | |
112 | goto out; | |
113 | ||
114 | patch->new_path = git_buf_detach(&new_path); | |
115 | ||
116 | out: | |
ac3d33df | 117 | git_buf_dispose(&new_path); |
eae0bfdc | 118 | return error; |
804d5fe9 ET |
119 | } |
120 | ||
17572f67 | 121 | static int parse_header_mode(uint16_t *mode, git_patch_parse_ctx *ctx) |
804d5fe9 | 122 | { |
eae0bfdc | 123 | int64_t m; |
804d5fe9 | 124 | |
eae0bfdc PP |
125 | if ((git_parse_advance_digit(&m, &ctx->parse_ctx, 8)) < 0) |
126 | return git_parse_err("invalid file mode at line %"PRIuZ, ctx->parse_ctx.line_num); | |
804d5fe9 ET |
127 | |
128 | if (m > UINT16_MAX) | |
129 | return -1; | |
130 | ||
131 | *mode = (uint16_t)m; | |
132 | ||
eae0bfdc | 133 | return 0; |
804d5fe9 ET |
134 | } |
135 | ||
136 | static int parse_header_oid( | |
137 | git_oid *oid, | |
002c8e29 | 138 | uint16_t *oid_len, |
17572f67 | 139 | git_patch_parse_ctx *ctx) |
804d5fe9 ET |
140 | { |
141 | size_t len; | |
142 | ||
eae0bfdc PP |
143 | for (len = 0; len < ctx->parse_ctx.line_len && len < GIT_OID_HEXSZ; len++) { |
144 | if (!git__isxdigit(ctx->parse_ctx.line[len])) | |
804d5fe9 ET |
145 | break; |
146 | } | |
147 | ||
002c8e29 | 148 | if (len < GIT_OID_MINPREFIXLEN || len > GIT_OID_HEXSZ || |
eae0bfdc PP |
149 | git_oid_fromstrn(oid, ctx->parse_ctx.line, len) < 0) |
150 | return git_parse_err("invalid hex formatted object id at line %"PRIuZ, | |
151 | ctx->parse_ctx.line_num); | |
804d5fe9 | 152 | |
eae0bfdc | 153 | git_parse_advance_chars(&ctx->parse_ctx, len); |
804d5fe9 | 154 | |
002c8e29 | 155 | *oid_len = (uint16_t)len; |
804d5fe9 ET |
156 | |
157 | return 0; | |
158 | } | |
159 | ||
160 | static int parse_header_git_index( | |
17572f67 | 161 | git_patch_parsed *patch, git_patch_parse_ctx *ctx) |
804d5fe9 | 162 | { |
eae0bfdc PP |
163 | char c; |
164 | ||
d68cb736 ET |
165 | if (parse_header_oid(&patch->base.delta->old_file.id, |
166 | &patch->base.delta->old_file.id_abbrev, ctx) < 0 || | |
eae0bfdc | 167 | git_parse_advance_expected_str(&ctx->parse_ctx, "..") < 0 || |
d68cb736 ET |
168 | parse_header_oid(&patch->base.delta->new_file.id, |
169 | &patch->base.delta->new_file.id_abbrev, ctx) < 0) | |
804d5fe9 ET |
170 | return -1; |
171 | ||
eae0bfdc | 172 | if (git_parse_peek(&c, &ctx->parse_ctx, 0) == 0 && c == ' ') { |
804d5fe9 ET |
173 | uint16_t mode; |
174 | ||
eae0bfdc | 175 | git_parse_advance_chars(&ctx->parse_ctx, 1); |
804d5fe9 ET |
176 | |
177 | if (parse_header_mode(&mode, ctx) < 0) | |
178 | return -1; | |
179 | ||
180 | if (!patch->base.delta->new_file.mode) | |
181 | patch->base.delta->new_file.mode = mode; | |
182 | ||
183 | if (!patch->base.delta->old_file.mode) | |
184 | patch->base.delta->old_file.mode = mode; | |
185 | } | |
186 | ||
187 | return 0; | |
188 | } | |
189 | ||
190 | static int parse_header_git_oldmode( | |
17572f67 | 191 | git_patch_parsed *patch, git_patch_parse_ctx *ctx) |
804d5fe9 | 192 | { |
b85bd8ce | 193 | return parse_header_mode(&patch->base.delta->old_file.mode, ctx); |
804d5fe9 ET |
194 | } |
195 | ||
196 | static int parse_header_git_newmode( | |
17572f67 | 197 | git_patch_parsed *patch, git_patch_parse_ctx *ctx) |
804d5fe9 | 198 | { |
b85bd8ce | 199 | return parse_header_mode(&patch->base.delta->new_file.mode, ctx); |
804d5fe9 ET |
200 | } |
201 | ||
202 | static int parse_header_git_deletedfilemode( | |
203 | git_patch_parsed *patch, | |
17572f67 | 204 | git_patch_parse_ctx *ctx) |
804d5fe9 | 205 | { |
b85bd8ce | 206 | git__free((char *)patch->base.delta->old_file.path); |
804d5fe9 | 207 | |
b85bd8ce | 208 | patch->base.delta->old_file.path = NULL; |
804d5fe9 | 209 | patch->base.delta->status = GIT_DELTA_DELETED; |
bc6a31c9 | 210 | patch->base.delta->nfiles = 1; |
804d5fe9 | 211 | |
b85bd8ce | 212 | return parse_header_mode(&patch->base.delta->old_file.mode, ctx); |
804d5fe9 ET |
213 | } |
214 | ||
215 | static int parse_header_git_newfilemode( | |
216 | git_patch_parsed *patch, | |
17572f67 | 217 | git_patch_parse_ctx *ctx) |
804d5fe9 | 218 | { |
b85bd8ce | 219 | git__free((char *)patch->base.delta->new_file.path); |
804d5fe9 | 220 | |
b85bd8ce | 221 | patch->base.delta->new_file.path = NULL; |
804d5fe9 | 222 | patch->base.delta->status = GIT_DELTA_ADDED; |
bc6a31c9 | 223 | patch->base.delta->nfiles = 1; |
804d5fe9 | 224 | |
b85bd8ce | 225 | return parse_header_mode(&patch->base.delta->new_file.mode, ctx); |
804d5fe9 ET |
226 | } |
227 | ||
228 | static int parse_header_rename( | |
229 | char **out, | |
17572f67 | 230 | git_patch_parse_ctx *ctx) |
804d5fe9 ET |
231 | { |
232 | git_buf path = GIT_BUF_INIT; | |
804d5fe9 | 233 | |
eae0bfdc | 234 | if (parse_header_path_buf(&path, ctx, header_path_len(ctx)) < 0) |
804d5fe9 ET |
235 | return -1; |
236 | ||
82175084 ET |
237 | /* Note: the `rename from` and `rename to` lines include the literal |
238 | * filename. They do *not* include the prefix. (Who needs consistency?) | |
239 | */ | |
240 | *out = git_buf_detach(&path); | |
804d5fe9 ET |
241 | return 0; |
242 | } | |
243 | ||
244 | static int parse_header_renamefrom( | |
17572f67 | 245 | git_patch_parsed *patch, git_patch_parse_ctx *ctx) |
804d5fe9 | 246 | { |
19e46645 | 247 | patch->base.delta->status = GIT_DELTA_RENAMED; |
82175084 | 248 | return parse_header_rename(&patch->rename_old_path, ctx); |
804d5fe9 ET |
249 | } |
250 | ||
251 | static int parse_header_renameto( | |
17572f67 | 252 | git_patch_parsed *patch, git_patch_parse_ctx *ctx) |
804d5fe9 | 253 | { |
19e46645 | 254 | patch->base.delta->status = GIT_DELTA_RENAMED; |
82175084 | 255 | return parse_header_rename(&patch->rename_new_path, ctx); |
804d5fe9 ET |
256 | } |
257 | ||
1a79cd95 ET |
258 | static int parse_header_copyfrom( |
259 | git_patch_parsed *patch, git_patch_parse_ctx *ctx) | |
260 | { | |
261 | patch->base.delta->status = GIT_DELTA_COPIED; | |
262 | return parse_header_rename(&patch->rename_old_path, ctx); | |
263 | } | |
264 | ||
265 | static int parse_header_copyto( | |
266 | git_patch_parsed *patch, git_patch_parse_ctx *ctx) | |
267 | { | |
268 | patch->base.delta->status = GIT_DELTA_COPIED; | |
269 | return parse_header_rename(&patch->rename_new_path, ctx); | |
270 | } | |
271 | ||
17572f67 | 272 | static int parse_header_percent(uint16_t *out, git_patch_parse_ctx *ctx) |
804d5fe9 | 273 | { |
eae0bfdc | 274 | int64_t val; |
804d5fe9 | 275 | |
eae0bfdc | 276 | if (git_parse_advance_digit(&val, &ctx->parse_ctx, 10) < 0) |
804d5fe9 ET |
277 | return -1; |
278 | ||
eae0bfdc | 279 | if (git_parse_advance_expected_str(&ctx->parse_ctx, "%") < 0) |
804d5fe9 ET |
280 | return -1; |
281 | ||
eae0bfdc | 282 | if (val < 0 || val > 100) |
804d5fe9 ET |
283 | return -1; |
284 | ||
ac3d33df | 285 | *out = (uint16_t)val; |
804d5fe9 ET |
286 | return 0; |
287 | } | |
288 | ||
289 | static int parse_header_similarity( | |
17572f67 | 290 | git_patch_parsed *patch, git_patch_parse_ctx *ctx) |
804d5fe9 ET |
291 | { |
292 | if (parse_header_percent(&patch->base.delta->similarity, ctx) < 0) | |
eae0bfdc PP |
293 | return git_parse_err("invalid similarity percentage at line %"PRIuZ, |
294 | ctx->parse_ctx.line_num); | |
804d5fe9 ET |
295 | |
296 | return 0; | |
297 | } | |
298 | ||
299 | static int parse_header_dissimilarity( | |
17572f67 | 300 | git_patch_parsed *patch, git_patch_parse_ctx *ctx) |
804d5fe9 ET |
301 | { |
302 | uint16_t dissimilarity; | |
303 | ||
304 | if (parse_header_percent(&dissimilarity, ctx) < 0) | |
eae0bfdc PP |
305 | return git_parse_err("invalid similarity percentage at line %"PRIuZ, |
306 | ctx->parse_ctx.line_num); | |
804d5fe9 ET |
307 | |
308 | patch->base.delta->similarity = 100 - dissimilarity; | |
309 | ||
310 | return 0; | |
311 | } | |
312 | ||
eae0bfdc PP |
313 | static int parse_header_start(git_patch_parsed *patch, git_patch_parse_ctx *ctx) |
314 | { | |
315 | if (parse_header_path(&patch->header_old_path, ctx) < 0) | |
316 | return git_parse_err("corrupt old path in git diff header at line %"PRIuZ, | |
317 | ctx->parse_ctx.line_num); | |
318 | ||
319 | if (git_parse_advance_ws(&ctx->parse_ctx) < 0 || | |
320 | parse_header_path(&patch->header_new_path, ctx) < 0) | |
321 | return git_parse_err("corrupt new path in git diff header at line %"PRIuZ, | |
322 | ctx->parse_ctx.line_num); | |
323 | ||
324 | /* | |
325 | * We cannot expect to be able to always parse paths correctly at this | |
326 | * point. Due to the possibility of unquoted names, whitespaces in | |
327 | * filenames and custom prefixes we have to allow that, though, and just | |
328 | * proceeed here. We then hope for the "---" and "+++" lines to fix that | |
329 | * for us. | |
330 | */ | |
6147f643 PP |
331 | if (!git_parse_ctx_contains(&ctx->parse_ctx, "\n", 1) && |
332 | !git_parse_ctx_contains(&ctx->parse_ctx, "\r\n", 2)) { | |
eae0bfdc PP |
333 | git_parse_advance_chars(&ctx->parse_ctx, ctx->parse_ctx.line_len - 1); |
334 | ||
335 | git__free(patch->header_old_path); | |
336 | patch->header_old_path = NULL; | |
337 | git__free(patch->header_new_path); | |
338 | patch->header_new_path = NULL; | |
339 | } | |
340 | ||
341 | return 0; | |
342 | } | |
343 | ||
344 | typedef enum { | |
345 | STATE_START, | |
346 | ||
347 | STATE_DIFF, | |
348 | STATE_FILEMODE, | |
349 | STATE_MODE, | |
350 | STATE_INDEX, | |
351 | STATE_PATH, | |
352 | ||
353 | STATE_SIMILARITY, | |
354 | STATE_RENAME, | |
355 | STATE_COPY, | |
356 | ||
357 | STATE_END, | |
358 | } parse_header_state; | |
359 | ||
804d5fe9 ET |
360 | typedef struct { |
361 | const char *str; | |
eae0bfdc PP |
362 | parse_header_state expected_state; |
363 | parse_header_state next_state; | |
17572f67 | 364 | int(*fn)(git_patch_parsed *, git_patch_parse_ctx *); |
eae0bfdc PP |
365 | } parse_header_transition; |
366 | ||
367 | static const parse_header_transition transitions[] = { | |
368 | /* Start */ | |
369 | { "diff --git " , STATE_START, STATE_DIFF, parse_header_start }, | |
370 | ||
371 | { "deleted file mode " , STATE_DIFF, STATE_FILEMODE, parse_header_git_deletedfilemode }, | |
372 | { "new file mode " , STATE_DIFF, STATE_FILEMODE, parse_header_git_newfilemode }, | |
373 | { "old mode " , STATE_DIFF, STATE_MODE, parse_header_git_oldmode }, | |
374 | { "new mode " , STATE_MODE, STATE_END, parse_header_git_newmode }, | |
375 | ||
376 | { "index " , STATE_FILEMODE, STATE_INDEX, parse_header_git_index }, | |
377 | { "index " , STATE_DIFF, STATE_INDEX, parse_header_git_index }, | |
378 | { "index " , STATE_END, STATE_INDEX, parse_header_git_index }, | |
379 | ||
380 | { "--- " , STATE_INDEX, STATE_PATH, parse_header_git_oldpath }, | |
381 | { "+++ " , STATE_PATH, STATE_END, parse_header_git_newpath }, | |
382 | { "GIT binary patch" , STATE_INDEX, STATE_END, NULL }, | |
383 | { "Binary files " , STATE_INDEX, STATE_END, NULL }, | |
384 | ||
385 | { "similarity index " , STATE_DIFF, STATE_SIMILARITY, parse_header_similarity }, | |
386 | { "dissimilarity index ", STATE_DIFF, STATE_SIMILARITY, parse_header_dissimilarity }, | |
387 | { "rename from " , STATE_SIMILARITY, STATE_RENAME, parse_header_renamefrom }, | |
388 | { "rename old " , STATE_SIMILARITY, STATE_RENAME, parse_header_renamefrom }, | |
389 | { "copy from " , STATE_SIMILARITY, STATE_COPY, parse_header_copyfrom }, | |
390 | { "rename to " , STATE_RENAME, STATE_END, parse_header_renameto }, | |
391 | { "rename new " , STATE_RENAME, STATE_END, parse_header_renameto }, | |
392 | { "copy to " , STATE_COPY, STATE_END, parse_header_copyto }, | |
393 | ||
394 | /* Next patch */ | |
395 | { "diff --git " , STATE_END, 0, NULL }, | |
396 | { "@@ -" , STATE_END, 0, NULL }, | |
397 | { "-- " , STATE_END, 0, NULL }, | |
804d5fe9 ET |
398 | }; |
399 | ||
400 | static int parse_header_git( | |
401 | git_patch_parsed *patch, | |
17572f67 | 402 | git_patch_parse_ctx *ctx) |
804d5fe9 ET |
403 | { |
404 | size_t i; | |
405 | int error = 0; | |
eae0bfdc | 406 | parse_header_state state = STATE_START; |
804d5fe9 ET |
407 | |
408 | /* Parse remaining header lines */ | |
eae0bfdc | 409 | for (; ctx->parse_ctx.remain_len > 0; git_parse_advance_line(&ctx->parse_ctx)) { |
7166bb16 ET |
410 | bool found = false; |
411 | ||
eae0bfdc | 412 | if (ctx->parse_ctx.line_len == 0 || ctx->parse_ctx.line[ctx->parse_ctx.line_len - 1] != '\n') |
804d5fe9 ET |
413 | break; |
414 | ||
eae0bfdc PP |
415 | for (i = 0; i < ARRAY_SIZE(transitions); i++) { |
416 | const parse_header_transition *transition = &transitions[i]; | |
417 | size_t op_len = strlen(transition->str); | |
804d5fe9 | 418 | |
eae0bfdc PP |
419 | if (transition->expected_state != state || |
420 | git__prefixcmp(ctx->parse_ctx.line, transition->str) != 0) | |
804d5fe9 ET |
421 | continue; |
422 | ||
eae0bfdc PP |
423 | state = transition->next_state; |
424 | ||
804d5fe9 | 425 | /* Do not advance if this is the patch separator */ |
eae0bfdc | 426 | if (transition->fn == NULL) |
804d5fe9 ET |
427 | goto done; |
428 | ||
eae0bfdc | 429 | git_parse_advance_chars(&ctx->parse_ctx, op_len); |
804d5fe9 | 430 | |
eae0bfdc | 431 | if ((error = transition->fn(patch, ctx)) < 0) |
804d5fe9 ET |
432 | goto done; |
433 | ||
eae0bfdc | 434 | git_parse_advance_ws(&ctx->parse_ctx); |
804d5fe9 | 435 | |
eae0bfdc PP |
436 | if (git_parse_advance_expected_str(&ctx->parse_ctx, "\n") < 0 || |
437 | ctx->parse_ctx.line_len > 0) { | |
438 | error = git_parse_err("trailing data at line %"PRIuZ, ctx->parse_ctx.line_num); | |
804d5fe9 ET |
439 | goto done; |
440 | } | |
441 | ||
7166bb16 | 442 | found = true; |
804d5fe9 ET |
443 | break; |
444 | } | |
eae0bfdc | 445 | |
7166bb16 | 446 | if (!found) { |
eae0bfdc PP |
447 | error = git_parse_err("invalid patch header at line %"PRIuZ, |
448 | ctx->parse_ctx.line_num); | |
7166bb16 ET |
449 | goto done; |
450 | } | |
804d5fe9 ET |
451 | } |
452 | ||
eae0bfdc PP |
453 | if (state != STATE_END) { |
454 | error = git_parse_err("unexpected header line %"PRIuZ, ctx->parse_ctx.line_num); | |
455 | goto done; | |
456 | } | |
457 | ||
804d5fe9 ET |
458 | done: |
459 | return error; | |
460 | } | |
461 | ||
17572f67 | 462 | static int parse_int(int *out, git_patch_parse_ctx *ctx) |
804d5fe9 ET |
463 | { |
464 | git_off_t num; | |
465 | ||
eae0bfdc | 466 | if (git_parse_advance_digit(&num, &ctx->parse_ctx, 10) < 0 || !git__is_int(num)) |
804d5fe9 ET |
467 | return -1; |
468 | ||
469 | *out = (int)num; | |
470 | return 0; | |
471 | } | |
472 | ||
473 | static int parse_hunk_header( | |
474 | git_patch_hunk *hunk, | |
17572f67 | 475 | git_patch_parse_ctx *ctx) |
804d5fe9 | 476 | { |
eae0bfdc PP |
477 | const char *header_start = ctx->parse_ctx.line; |
478 | char c; | |
804d5fe9 ET |
479 | |
480 | hunk->hunk.old_lines = 1; | |
481 | hunk->hunk.new_lines = 1; | |
482 | ||
eae0bfdc | 483 | if (git_parse_advance_expected_str(&ctx->parse_ctx, "@@ -") < 0 || |
804d5fe9 ET |
484 | parse_int(&hunk->hunk.old_start, ctx) < 0) |
485 | goto fail; | |
486 | ||
eae0bfdc PP |
487 | if (git_parse_peek(&c, &ctx->parse_ctx, 0) == 0 && c == ',') { |
488 | if (git_parse_advance_expected_str(&ctx->parse_ctx, ",") < 0 || | |
804d5fe9 ET |
489 | parse_int(&hunk->hunk.old_lines, ctx) < 0) |
490 | goto fail; | |
491 | } | |
492 | ||
eae0bfdc | 493 | if (git_parse_advance_expected_str(&ctx->parse_ctx, " +") < 0 || |
804d5fe9 ET |
494 | parse_int(&hunk->hunk.new_start, ctx) < 0) |
495 | goto fail; | |
496 | ||
eae0bfdc PP |
497 | if (git_parse_peek(&c, &ctx->parse_ctx, 0) == 0 && c == ',') { |
498 | if (git_parse_advance_expected_str(&ctx->parse_ctx, ",") < 0 || | |
804d5fe9 ET |
499 | parse_int(&hunk->hunk.new_lines, ctx) < 0) |
500 | goto fail; | |
501 | } | |
502 | ||
eae0bfdc | 503 | if (git_parse_advance_expected_str(&ctx->parse_ctx, " @@") < 0) |
804d5fe9 ET |
504 | goto fail; |
505 | ||
eae0bfdc | 506 | git_parse_advance_line(&ctx->parse_ctx); |
804d5fe9 ET |
507 | |
508 | if (!hunk->hunk.old_lines && !hunk->hunk.new_lines) | |
509 | goto fail; | |
510 | ||
eae0bfdc | 511 | hunk->hunk.header_len = ctx->parse_ctx.line - header_start; |
804d5fe9 | 512 | if (hunk->hunk.header_len > (GIT_DIFF_HUNK_HEADER_SIZE - 1)) |
eae0bfdc PP |
513 | return git_parse_err("oversized patch hunk header at line %"PRIuZ, |
514 | ctx->parse_ctx.line_num); | |
804d5fe9 ET |
515 | |
516 | memcpy(hunk->hunk.header, header_start, hunk->hunk.header_len); | |
517 | hunk->hunk.header[hunk->hunk.header_len] = '\0'; | |
518 | ||
519 | return 0; | |
520 | ||
521 | fail: | |
ac3d33df | 522 | git_error_set(GIT_ERROR_PATCH, "invalid patch hunk header at line %"PRIuZ, |
eae0bfdc | 523 | ctx->parse_ctx.line_num); |
804d5fe9 ET |
524 | return -1; |
525 | } | |
526 | ||
527 | static int parse_hunk_body( | |
528 | git_patch_parsed *patch, | |
529 | git_patch_hunk *hunk, | |
17572f67 | 530 | git_patch_parse_ctx *ctx) |
804d5fe9 ET |
531 | { |
532 | git_diff_line *line; | |
533 | int error = 0; | |
534 | ||
535 | int oldlines = hunk->hunk.old_lines; | |
536 | int newlines = hunk->hunk.new_lines; | |
537 | ||
538 | for (; | |
eae0bfdc | 539 | ctx->parse_ctx.remain_len > 1 && |
ad5a909c | 540 | (oldlines || newlines) && |
eae0bfdc PP |
541 | !git_parse_ctx_contains_s(&ctx->parse_ctx, "@@ -"); |
542 | git_parse_advance_line(&ctx->parse_ctx)) { | |
804d5fe9 | 543 | |
eae0bfdc | 544 | char c; |
804d5fe9 ET |
545 | int origin; |
546 | int prefix = 1; | |
6c7cee42 RD |
547 | int old_lineno = hunk->hunk.old_start + (hunk->hunk.old_lines - oldlines); |
548 | int new_lineno = hunk->hunk.new_start + (hunk->hunk.new_lines - newlines); | |
804d5fe9 | 549 | |
eae0bfdc PP |
550 | if (ctx->parse_ctx.line_len == 0 || ctx->parse_ctx.line[ctx->parse_ctx.line_len - 1] != '\n') { |
551 | error = git_parse_err("invalid patch instruction at line %"PRIuZ, | |
552 | ctx->parse_ctx.line_num); | |
804d5fe9 ET |
553 | goto done; |
554 | } | |
555 | ||
eae0bfdc PP |
556 | git_parse_peek(&c, &ctx->parse_ctx, 0); |
557 | ||
558 | switch (c) { | |
804d5fe9 ET |
559 | case '\n': |
560 | prefix = 0; | |
eae0bfdc | 561 | /* fall through */ |
804d5fe9 ET |
562 | |
563 | case ' ': | |
564 | origin = GIT_DIFF_LINE_CONTEXT; | |
565 | oldlines--; | |
566 | newlines--; | |
567 | break; | |
568 | ||
569 | case '-': | |
570 | origin = GIT_DIFF_LINE_DELETION; | |
571 | oldlines--; | |
6c7cee42 | 572 | new_lineno = -1; |
804d5fe9 ET |
573 | break; |
574 | ||
575 | case '+': | |
576 | origin = GIT_DIFF_LINE_ADDITION; | |
577 | newlines--; | |
6c7cee42 | 578 | old_lineno = -1; |
804d5fe9 ET |
579 | break; |
580 | ||
581 | default: | |
eae0bfdc | 582 | error = git_parse_err("invalid patch hunk at line %"PRIuZ, ctx->parse_ctx.line_num); |
804d5fe9 ET |
583 | goto done; |
584 | } | |
585 | ||
586 | line = git_array_alloc(patch->base.lines); | |
ac3d33df | 587 | GIT_ERROR_CHECK_ALLOC(line); |
804d5fe9 ET |
588 | |
589 | memset(line, 0x0, sizeof(git_diff_line)); | |
590 | ||
eae0bfdc PP |
591 | line->content = ctx->parse_ctx.line + prefix; |
592 | line->content_len = ctx->parse_ctx.line_len - prefix; | |
593 | line->content_offset = ctx->parse_ctx.content_len - ctx->parse_ctx.remain_len; | |
804d5fe9 | 594 | line->origin = origin; |
6c7cee42 RD |
595 | line->num_lines = 1; |
596 | line->old_lineno = old_lineno; | |
597 | line->new_lineno = new_lineno; | |
804d5fe9 ET |
598 | |
599 | hunk->line_count++; | |
600 | } | |
601 | ||
602 | if (oldlines || newlines) { | |
eae0bfdc | 603 | error = git_parse_err( |
804d5fe9 ET |
604 | "invalid patch hunk, expected %d old lines and %d new lines", |
605 | hunk->hunk.old_lines, hunk->hunk.new_lines); | |
606 | goto done; | |
607 | } | |
608 | ||
609 | /* Handle "\ No newline at end of file". Only expect the leading | |
aa4bfb32 ET |
610 | * backslash, though, because the rest of the string could be |
611 | * localized. Because `diff` optimizes for the case where you | |
612 | * want to apply the patch by hand. | |
613 | */ | |
eae0bfdc | 614 | if (git_parse_ctx_contains_s(&ctx->parse_ctx, "\\ ") && |
804d5fe9 ET |
615 | git_array_size(patch->base.lines) > 0) { |
616 | ||
617 | line = git_array_get(patch->base.lines, git_array_size(patch->base.lines) - 1); | |
618 | ||
619 | if (line->content_len < 1) { | |
eae0bfdc | 620 | error = git_parse_err("cannot trim trailing newline of empty line"); |
804d5fe9 ET |
621 | goto done; |
622 | } | |
623 | ||
624 | line->content_len--; | |
625 | ||
eae0bfdc | 626 | git_parse_advance_line(&ctx->parse_ctx); |
804d5fe9 ET |
627 | } |
628 | ||
629 | done: | |
630 | return error; | |
631 | } | |
632 | ||
17572f67 | 633 | static int parse_patch_header( |
804d5fe9 | 634 | git_patch_parsed *patch, |
17572f67 | 635 | git_patch_parse_ctx *ctx) |
804d5fe9 ET |
636 | { |
637 | int error = 0; | |
638 | ||
eae0bfdc | 639 | for (; ctx->parse_ctx.remain_len > 0; git_parse_advance_line(&ctx->parse_ctx)) { |
804d5fe9 | 640 | /* This line is too short to be a patch header. */ |
eae0bfdc | 641 | if (ctx->parse_ctx.line_len < 6) |
804d5fe9 ET |
642 | continue; |
643 | ||
644 | /* This might be a hunk header without a patch header, provide a | |
aa4bfb32 | 645 | * sensible error message. */ |
eae0bfdc PP |
646 | if (git_parse_ctx_contains_s(&ctx->parse_ctx, "@@ -")) { |
647 | size_t line_num = ctx->parse_ctx.line_num; | |
804d5fe9 ET |
648 | git_patch_hunk hunk; |
649 | ||
650 | /* If this cannot be parsed as a hunk header, it's just leading | |
651 | * noise, continue. | |
652 | */ | |
653 | if (parse_hunk_header(&hunk, ctx) < 0) { | |
ac3d33df | 654 | git_error_clear(); |
804d5fe9 ET |
655 | continue; |
656 | } | |
657 | ||
eae0bfdc | 658 | error = git_parse_err("invalid hunk header outside patch at line %"PRIuZ, |
804d5fe9 ET |
659 | line_num); |
660 | goto done; | |
661 | } | |
662 | ||
663 | /* This buffer is too short to contain a patch. */ | |
eae0bfdc | 664 | if (ctx->parse_ctx.remain_len < ctx->parse_ctx.line_len + 6) |
804d5fe9 ET |
665 | break; |
666 | ||
667 | /* A proper git patch */ | |
eae0bfdc | 668 | if (git_parse_ctx_contains_s(&ctx->parse_ctx, "diff --git ")) { |
82175084 | 669 | error = parse_header_git(patch, ctx); |
804d5fe9 ET |
670 | goto done; |
671 | } | |
672 | ||
673 | error = 0; | |
674 | continue; | |
675 | } | |
676 | ||
ac3d33df | 677 | git_error_set(GIT_ERROR_PATCH, "no patch found"); |
94e488a0 | 678 | error = GIT_ENOTFOUND; |
804d5fe9 ET |
679 | |
680 | done: | |
681 | return error; | |
682 | } | |
683 | ||
17572f67 | 684 | static int parse_patch_binary_side( |
804d5fe9 | 685 | git_diff_binary_file *binary, |
17572f67 | 686 | git_patch_parse_ctx *ctx) |
804d5fe9 ET |
687 | { |
688 | git_diff_binary_t type = GIT_DIFF_BINARY_NONE; | |
689 | git_buf base85 = GIT_BUF_INIT, decoded = GIT_BUF_INIT; | |
690 | git_off_t len; | |
691 | int error = 0; | |
692 | ||
eae0bfdc | 693 | if (git_parse_ctx_contains_s(&ctx->parse_ctx, "literal ")) { |
804d5fe9 | 694 | type = GIT_DIFF_BINARY_LITERAL; |
eae0bfdc PP |
695 | git_parse_advance_chars(&ctx->parse_ctx, 8); |
696 | } else if (git_parse_ctx_contains_s(&ctx->parse_ctx, "delta ")) { | |
804d5fe9 | 697 | type = GIT_DIFF_BINARY_DELTA; |
eae0bfdc | 698 | git_parse_advance_chars(&ctx->parse_ctx, 6); |
aa4bfb32 | 699 | } else { |
eae0bfdc PP |
700 | error = git_parse_err( |
701 | "unknown binary delta type at line %"PRIuZ, ctx->parse_ctx.line_num); | |
804d5fe9 ET |
702 | goto done; |
703 | } | |
704 | ||
eae0bfdc PP |
705 | if (git_parse_advance_digit(&len, &ctx->parse_ctx, 10) < 0 || |
706 | git_parse_advance_nl(&ctx->parse_ctx) < 0 || len < 0) { | |
707 | error = git_parse_err("invalid binary size at line %"PRIuZ, ctx->parse_ctx.line_num); | |
804d5fe9 ET |
708 | goto done; |
709 | } | |
710 | ||
eae0bfdc PP |
711 | while (ctx->parse_ctx.line_len) { |
712 | char c; | |
804d5fe9 ET |
713 | size_t encoded_len, decoded_len = 0, decoded_orig = decoded.size; |
714 | ||
eae0bfdc PP |
715 | git_parse_peek(&c, &ctx->parse_ctx, 0); |
716 | ||
804d5fe9 ET |
717 | if (c == '\n') |
718 | break; | |
719 | else if (c >= 'A' && c <= 'Z') | |
720 | decoded_len = c - 'A' + 1; | |
721 | else if (c >= 'a' && c <= 'z') | |
722 | decoded_len = c - 'a' + (('z' - 'a') + 1) + 1; | |
723 | ||
724 | if (!decoded_len) { | |
eae0bfdc | 725 | error = git_parse_err("invalid binary length at line %"PRIuZ, ctx->parse_ctx.line_num); |
804d5fe9 ET |
726 | goto done; |
727 | } | |
728 | ||
eae0bfdc | 729 | git_parse_advance_chars(&ctx->parse_ctx, 1); |
804d5fe9 ET |
730 | |
731 | encoded_len = ((decoded_len / 4) + !!(decoded_len % 4)) * 5; | |
732 | ||
eae0bfdc PP |
733 | if (encoded_len > ctx->parse_ctx.line_len - 1) { |
734 | error = git_parse_err("truncated binary data at line %"PRIuZ, ctx->parse_ctx.line_num); | |
804d5fe9 ET |
735 | goto done; |
736 | } | |
737 | ||
738 | if ((error = git_buf_decode_base85( | |
eae0bfdc | 739 | &decoded, ctx->parse_ctx.line, encoded_len, decoded_len)) < 0) |
804d5fe9 ET |
740 | goto done; |
741 | ||
742 | if (decoded.size - decoded_orig != decoded_len) { | |
eae0bfdc | 743 | error = git_parse_err("truncated binary data at line %"PRIuZ, ctx->parse_ctx.line_num); |
804d5fe9 ET |
744 | goto done; |
745 | } | |
746 | ||
eae0bfdc | 747 | git_parse_advance_chars(&ctx->parse_ctx, encoded_len); |
804d5fe9 | 748 | |
eae0bfdc PP |
749 | if (git_parse_advance_nl(&ctx->parse_ctx) < 0) { |
750 | error = git_parse_err("trailing data at line %"PRIuZ, ctx->parse_ctx.line_num); | |
804d5fe9 ET |
751 | goto done; |
752 | } | |
753 | } | |
754 | ||
755 | binary->type = type; | |
756 | binary->inflatedlen = (size_t)len; | |
757 | binary->datalen = decoded.size; | |
758 | binary->data = git_buf_detach(&decoded); | |
759 | ||
760 | done: | |
ac3d33df JK |
761 | git_buf_dispose(&base85); |
762 | git_buf_dispose(&decoded); | |
804d5fe9 ET |
763 | return error; |
764 | } | |
765 | ||
17572f67 | 766 | static int parse_patch_binary( |
804d5fe9 | 767 | git_patch_parsed *patch, |
17572f67 | 768 | git_patch_parse_ctx *ctx) |
804d5fe9 ET |
769 | { |
770 | int error; | |
771 | ||
eae0bfdc PP |
772 | if (git_parse_advance_expected_str(&ctx->parse_ctx, "GIT binary patch") < 0 || |
773 | git_parse_advance_nl(&ctx->parse_ctx) < 0) | |
774 | return git_parse_err("corrupt git binary header at line %"PRIuZ, ctx->parse_ctx.line_num); | |
804d5fe9 ET |
775 | |
776 | /* parse old->new binary diff */ | |
17572f67 | 777 | if ((error = parse_patch_binary_side( |
804d5fe9 ET |
778 | &patch->base.binary.new_file, ctx)) < 0) |
779 | return error; | |
780 | ||
eae0bfdc PP |
781 | if (git_parse_advance_nl(&ctx->parse_ctx) < 0) |
782 | return git_parse_err("corrupt git binary separator at line %"PRIuZ, | |
783 | ctx->parse_ctx.line_num); | |
804d5fe9 ET |
784 | |
785 | /* parse new->old binary diff */ | |
17572f67 | 786 | if ((error = parse_patch_binary_side( |
804d5fe9 ET |
787 | &patch->base.binary.old_file, ctx)) < 0) |
788 | return error; | |
789 | ||
eae0bfdc PP |
790 | if (git_parse_advance_nl(&ctx->parse_ctx) < 0) |
791 | return git_parse_err("corrupt git binary patch separator at line %"PRIuZ, | |
792 | ctx->parse_ctx.line_num); | |
7166bb16 | 793 | |
adedac5a ET |
794 | patch->base.binary.contains_data = 1; |
795 | patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY; | |
796 | return 0; | |
797 | } | |
798 | ||
799 | static int parse_patch_binary_nodata( | |
800 | git_patch_parsed *patch, | |
801 | git_patch_parse_ctx *ctx) | |
802 | { | |
eae0bfdc PP |
803 | if (git_parse_advance_expected_str(&ctx->parse_ctx, "Binary files ") < 0 || |
804 | git_parse_advance_expected_str(&ctx->parse_ctx, patch->header_old_path) < 0 || | |
805 | git_parse_advance_expected_str(&ctx->parse_ctx, " and ") < 0 || | |
806 | git_parse_advance_expected_str(&ctx->parse_ctx, patch->header_new_path) < 0 || | |
807 | git_parse_advance_expected_str(&ctx->parse_ctx, " differ") < 0 || | |
808 | git_parse_advance_nl(&ctx->parse_ctx) < 0) | |
809 | return git_parse_err("corrupt git binary header at line %"PRIuZ, ctx->parse_ctx.line_num); | |
adedac5a ET |
810 | |
811 | patch->base.binary.contains_data = 0; | |
804d5fe9 ET |
812 | patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY; |
813 | return 0; | |
814 | } | |
815 | ||
17572f67 | 816 | static int parse_patch_hunks( |
804d5fe9 | 817 | git_patch_parsed *patch, |
17572f67 | 818 | git_patch_parse_ctx *ctx) |
804d5fe9 ET |
819 | { |
820 | git_patch_hunk *hunk; | |
821 | int error = 0; | |
822 | ||
eae0bfdc | 823 | while (git_parse_ctx_contains_s(&ctx->parse_ctx, "@@ -")) { |
804d5fe9 | 824 | hunk = git_array_alloc(patch->base.hunks); |
ac3d33df | 825 | GIT_ERROR_CHECK_ALLOC(hunk); |
804d5fe9 ET |
826 | |
827 | memset(hunk, 0, sizeof(git_patch_hunk)); | |
828 | ||
829 | hunk->line_start = git_array_size(patch->base.lines); | |
830 | hunk->line_count = 0; | |
831 | ||
832 | if ((error = parse_hunk_header(hunk, ctx)) < 0 || | |
833 | (error = parse_hunk_body(patch, hunk, ctx)) < 0) | |
834 | goto done; | |
835 | } | |
836 | ||
33ae8762 ET |
837 | patch->base.delta->flags |= GIT_DIFF_FLAG_NOT_BINARY; |
838 | ||
804d5fe9 ET |
839 | done: |
840 | return error; | |
841 | } | |
842 | ||
17572f67 ET |
843 | static int parse_patch_body( |
844 | git_patch_parsed *patch, git_patch_parse_ctx *ctx) | |
804d5fe9 | 845 | { |
eae0bfdc | 846 | if (git_parse_ctx_contains_s(&ctx->parse_ctx, "GIT binary patch")) |
17572f67 | 847 | return parse_patch_binary(patch, ctx); |
eae0bfdc | 848 | else if (git_parse_ctx_contains_s(&ctx->parse_ctx, "Binary files ")) |
adedac5a | 849 | return parse_patch_binary_nodata(patch, ctx); |
33ae8762 | 850 | else |
17572f67 | 851 | return parse_patch_hunks(patch, ctx); |
804d5fe9 ET |
852 | } |
853 | ||
82175084 ET |
854 | int check_header_names( |
855 | const char *one, | |
856 | const char *two, | |
857 | const char *old_or_new, | |
858 | bool two_null) | |
859 | { | |
860 | if (!one || !two) | |
861 | return 0; | |
862 | ||
863 | if (two_null && strcmp(two, "/dev/null") != 0) | |
eae0bfdc | 864 | return git_parse_err("expected %s path of '/dev/null'", old_or_new); |
82175084 ET |
865 | |
866 | else if (!two_null && strcmp(one, two) != 0) | |
eae0bfdc | 867 | return git_parse_err("mismatched %s path names", old_or_new); |
82175084 ET |
868 | |
869 | return 0; | |
870 | } | |
871 | ||
872 | static int check_prefix( | |
873 | char **out, | |
874 | size_t *out_len, | |
875 | git_patch_parsed *patch, | |
876 | const char *path_start) | |
877 | { | |
878 | const char *path = path_start; | |
17572f67 | 879 | size_t prefix_len = patch->ctx->opts.prefix_len; |
7166bb16 | 880 | size_t remain_len = prefix_len; |
82175084 ET |
881 | |
882 | *out = NULL; | |
883 | *out_len = 0; | |
884 | ||
17572f67 | 885 | if (prefix_len == 0) |
82175084 ET |
886 | goto done; |
887 | ||
888 | /* leading slashes do not count as part of the prefix in git apply */ | |
889 | while (*path == '/') | |
890 | path++; | |
891 | ||
7166bb16 | 892 | while (*path && remain_len) { |
82175084 | 893 | if (*path == '/') |
7166bb16 | 894 | remain_len--; |
82175084 ET |
895 | |
896 | path++; | |
897 | } | |
898 | ||
7166bb16 | 899 | if (remain_len || !*path) |
eae0bfdc | 900 | return git_parse_err( |
c77a55a9 | 901 | "header filename does not contain %"PRIuZ" path components", |
17572f67 | 902 | prefix_len); |
82175084 ET |
903 | |
904 | done: | |
905 | *out_len = (path - path_start); | |
906 | *out = git__strndup(path_start, *out_len); | |
907 | ||
c065f6a1 | 908 | return (*out == NULL) ? -1 : 0; |
82175084 ET |
909 | } |
910 | ||
911 | static int check_filenames(git_patch_parsed *patch) | |
804d5fe9 | 912 | { |
82175084 ET |
913 | const char *prefixed_new, *prefixed_old; |
914 | size_t old_prefixlen = 0, new_prefixlen = 0; | |
915 | bool added = (patch->base.delta->status == GIT_DELTA_ADDED); | |
916 | bool deleted = (patch->base.delta->status == GIT_DELTA_DELETED); | |
917 | ||
918 | if (patch->old_path && !patch->new_path) | |
eae0bfdc | 919 | return git_parse_err("missing new path"); |
82175084 ET |
920 | |
921 | if (!patch->old_path && patch->new_path) | |
eae0bfdc | 922 | return git_parse_err("missing old path"); |
82175084 ET |
923 | |
924 | /* Ensure (non-renamed) paths match */ | |
6147f643 PP |
925 | if (check_header_names(patch->header_old_path, patch->old_path, "old", added) < 0 || |
926 | check_header_names(patch->header_new_path, patch->new_path, "new", deleted) < 0) | |
82175084 ET |
927 | return -1; |
928 | ||
6147f643 PP |
929 | prefixed_old = (!added && patch->old_path) ? patch->old_path : patch->header_old_path; |
930 | prefixed_new = (!deleted && patch->new_path) ? patch->new_path : patch->header_new_path; | |
82175084 | 931 | |
6147f643 PP |
932 | if ((prefixed_old && check_prefix(&patch->old_prefix, &old_prefixlen, patch, prefixed_old) < 0) || |
933 | (prefixed_new && check_prefix(&patch->new_prefix, &new_prefixlen, patch, prefixed_new) < 0)) | |
82175084 ET |
934 | return -1; |
935 | ||
936 | /* Prefer the rename filenames as they are unambiguous and unprefixed */ | |
937 | if (patch->rename_old_path) | |
938 | patch->base.delta->old_file.path = patch->rename_old_path; | |
939 | else | |
940 | patch->base.delta->old_file.path = prefixed_old + old_prefixlen; | |
941 | ||
942 | if (patch->rename_new_path) | |
943 | patch->base.delta->new_file.path = patch->rename_new_path; | |
944 | else | |
945 | patch->base.delta->new_file.path = prefixed_new + new_prefixlen; | |
946 | ||
b85bd8ce | 947 | if (!patch->base.delta->old_file.path && |
6147f643 | 948 | !patch->base.delta->new_file.path) |
eae0bfdc | 949 | return git_parse_err("git diff header lacks old / new paths"); |
804d5fe9 | 950 | |
82175084 ET |
951 | return 0; |
952 | } | |
953 | ||
954 | static int check_patch(git_patch_parsed *patch) | |
955 | { | |
853e585f ET |
956 | git_diff_delta *delta = patch->base.delta; |
957 | ||
82175084 ET |
958 | if (check_filenames(patch) < 0) |
959 | return -1; | |
804d5fe9 | 960 | |
853e585f | 961 | if (delta->old_file.path && |
6147f643 PP |
962 | delta->status != GIT_DELTA_DELETED && |
963 | !delta->new_file.mode) | |
853e585f | 964 | delta->new_file.mode = delta->old_file.mode; |
804d5fe9 | 965 | |
853e585f | 966 | if (delta->status == GIT_DELTA_MODIFIED && |
6147f643 PP |
967 | !(delta->flags & GIT_DIFF_FLAG_BINARY) && |
968 | delta->new_file.mode == delta->old_file.mode && | |
969 | git_array_size(patch->base.hunks) == 0) | |
eae0bfdc | 970 | return git_parse_err("patch with no hunks"); |
804d5fe9 | 971 | |
853e585f ET |
972 | if (delta->status == GIT_DELTA_ADDED) { |
973 | memset(&delta->old_file.id, 0x0, sizeof(git_oid)); | |
974 | delta->old_file.id_abbrev = 0; | |
975 | } | |
976 | ||
977 | if (delta->status == GIT_DELTA_DELETED) { | |
978 | memset(&delta->new_file.id, 0x0, sizeof(git_oid)); | |
979 | delta->new_file.id_abbrev = 0; | |
980 | } | |
981 | ||
804d5fe9 ET |
982 | return 0; |
983 | } | |
984 | ||
7166bb16 | 985 | git_patch_parse_ctx *git_patch_parse_ctx_init( |
17572f67 ET |
986 | const char *content, |
987 | size_t content_len, | |
988 | const git_patch_options *opts) | |
989 | { | |
990 | git_patch_parse_ctx *ctx; | |
991 | git_patch_options default_opts = GIT_PATCH_OPTIONS_INIT; | |
992 | ||
993 | if ((ctx = git__calloc(1, sizeof(git_patch_parse_ctx))) == NULL) | |
994 | return NULL; | |
995 | ||
eae0bfdc PP |
996 | if ((git_parse_ctx_init(&ctx->parse_ctx, content, content_len)) < 0) { |
997 | git__free(ctx); | |
998 | return NULL; | |
17572f67 ET |
999 | } |
1000 | ||
17572f67 ET |
1001 | if (opts) |
1002 | memcpy(&ctx->opts, opts, sizeof(git_patch_options)); | |
1003 | else | |
1004 | memcpy(&ctx->opts, &default_opts, sizeof(git_patch_options)); | |
1005 | ||
1006 | GIT_REFCOUNT_INC(ctx); | |
1007 | return ctx; | |
1008 | } | |
1009 | ||
1010 | static void patch_parse_ctx_free(git_patch_parse_ctx *ctx) | |
1011 | { | |
1012 | if (!ctx) | |
1013 | return; | |
1014 | ||
eae0bfdc | 1015 | git_parse_ctx_clear(&ctx->parse_ctx); |
17572f67 ET |
1016 | git__free(ctx); |
1017 | } | |
1018 | ||
7166bb16 | 1019 | void git_patch_parse_ctx_free(git_patch_parse_ctx *ctx) |
17572f67 ET |
1020 | { |
1021 | GIT_REFCOUNT_DEC(ctx, patch_parse_ctx_free); | |
1022 | } | |
1023 | ||
b859faa6 ET |
1024 | int git_patch_parsed_from_diff(git_patch **out, git_diff *d, size_t idx) |
1025 | { | |
1026 | git_diff_parsed *diff = (git_diff_parsed *)d; | |
1027 | git_patch *p; | |
1028 | ||
1029 | if ((p = git_vector_get(&diff->patches, idx)) == NULL) | |
1030 | return -1; | |
1031 | ||
1032 | GIT_REFCOUNT_INC(p); | |
1033 | *out = p; | |
1034 | ||
1035 | return 0; | |
1036 | } | |
1037 | ||
82175084 ET |
1038 | static void patch_parsed__free(git_patch *p) |
1039 | { | |
1040 | git_patch_parsed *patch = (git_patch_parsed *)p; | |
1041 | ||
1042 | if (!patch) | |
1043 | return; | |
1044 | ||
17572f67 ET |
1045 | git_patch_parse_ctx_free(patch->ctx); |
1046 | ||
4117a235 ET |
1047 | git__free((char *)patch->base.binary.old_file.data); |
1048 | git__free((char *)patch->base.binary.new_file.data); | |
1049 | git_array_clear(patch->base.hunks); | |
1050 | git_array_clear(patch->base.lines); | |
1051 | git__free(patch->base.delta); | |
1052 | ||
82175084 ET |
1053 | git__free(patch->old_prefix); |
1054 | git__free(patch->new_prefix); | |
1055 | git__free(patch->header_old_path); | |
1056 | git__free(patch->header_new_path); | |
1057 | git__free(patch->rename_old_path); | |
1058 | git__free(patch->rename_new_path); | |
1059 | git__free(patch->old_path); | |
1060 | git__free(patch->new_path); | |
6278fbc5 | 1061 | git__free(patch); |
82175084 ET |
1062 | } |
1063 | ||
7166bb16 | 1064 | int git_patch_parse( |
804d5fe9 | 1065 | git_patch **out, |
17572f67 | 1066 | git_patch_parse_ctx *ctx) |
804d5fe9 | 1067 | { |
804d5fe9 | 1068 | git_patch_parsed *patch; |
7166bb16 | 1069 | size_t start, used; |
804d5fe9 ET |
1070 | int error = 0; |
1071 | ||
17572f67 ET |
1072 | assert(out && ctx); |
1073 | ||
804d5fe9 ET |
1074 | *out = NULL; |
1075 | ||
1076 | patch = git__calloc(1, sizeof(git_patch_parsed)); | |
ac3d33df | 1077 | GIT_ERROR_CHECK_ALLOC(patch); |
804d5fe9 | 1078 | |
17572f67 ET |
1079 | patch->ctx = ctx; |
1080 | GIT_REFCOUNT_INC(patch->ctx); | |
82175084 ET |
1081 | |
1082 | patch->base.free_fn = patch_parsed__free; | |
e7ec327d | 1083 | |
804d5fe9 | 1084 | patch->base.delta = git__calloc(1, sizeof(git_diff_delta)); |
ac3d33df | 1085 | GIT_ERROR_CHECK_ALLOC(patch->base.delta); |
4117a235 | 1086 | |
804d5fe9 | 1087 | patch->base.delta->status = GIT_DELTA_MODIFIED; |
bc6a31c9 | 1088 | patch->base.delta->nfiles = 2; |
804d5fe9 | 1089 | |
eae0bfdc | 1090 | start = ctx->parse_ctx.remain_len; |
7166bb16 | 1091 | |
17572f67 ET |
1092 | if ((error = parse_patch_header(patch, ctx)) < 0 || |
1093 | (error = parse_patch_body(patch, ctx)) < 0 || | |
804d5fe9 ET |
1094 | (error = check_patch(patch)) < 0) |
1095 | goto done; | |
1096 | ||
eae0bfdc PP |
1097 | used = start - ctx->parse_ctx.remain_len; |
1098 | ctx->parse_ctx.remain += used; | |
7166bb16 | 1099 | |
82175084 ET |
1100 | patch->base.diff_opts.old_prefix = patch->old_prefix; |
1101 | patch->base.diff_opts.new_prefix = patch->new_prefix; | |
1102 | patch->base.diff_opts.flags |= GIT_DIFF_SHOW_BINARY; | |
1103 | ||
eae0bfdc | 1104 | GIT_REFCOUNT_INC(&patch->base); |
804d5fe9 ET |
1105 | *out = &patch->base; |
1106 | ||
1107 | done: | |
6278fbc5 ET |
1108 | if (error < 0) |
1109 | patch_parsed__free(&patch->base); | |
1110 | ||
804d5fe9 ET |
1111 | return error; |
1112 | } | |
17572f67 ET |
1113 | |
1114 | int git_patch_from_buffer( | |
1115 | git_patch **out, | |
1116 | const char *content, | |
1117 | size_t content_len, | |
1118 | const git_patch_options *opts) | |
1119 | { | |
1120 | git_patch_parse_ctx *ctx; | |
1121 | int error; | |
1122 | ||
1123 | ctx = git_patch_parse_ctx_init(content, content_len, opts); | |
ac3d33df | 1124 | GIT_ERROR_CHECK_ALLOC(ctx); |
17572f67 ET |
1125 | |
1126 | error = git_patch_parse(out, ctx); | |
1127 | ||
1128 | git_patch_parse_ctx_free(ctx); | |
1129 | return error; | |
1130 | } | |
1131 |