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