2 * Copyright (C) the libgit2 contributors. All rights reserved.
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.
8 #include "patch_parse.h"
10 #include "git2/patch.h"
12 #include "diff_parse.h"
18 git_patch_parse_ctx
*ctx
;
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
24 char *header_old_path
, *header_new_path
;
26 /* renamed paths are precise and are not prefixed */
27 char *rename_old_path
, *rename_new_path
;
29 /* the paths given in `---` and `+++` lines */
30 char *old_path
, *new_path
;
32 /* the prefixes from the old/new paths */
33 char *old_prefix
, *new_prefix
;
36 static int header_path_len(git_patch_parse_ctx
*ctx
)
39 bool quoted
= git_parse_ctx_contains_s(&ctx
->parse_ctx
, "\"");
42 for (len
= quoted
; len
< ctx
->parse_ctx
.line_len
; len
++) {
43 if (!quoted
&& git__isspace(ctx
->parse_ctx
.line
[len
]))
45 else if (quoted
&& !inquote
&& ctx
->parse_ctx
.line
[len
] == '"') {
50 inquote
= (!inquote
&& ctx
->parse_ctx
.line
[len
] == '\\');
56 static int parse_header_path_buf(git_buf
*path
, git_patch_parse_ctx
*ctx
, size_t path_len
)
60 if ((error
= git_buf_put(path
, ctx
->parse_ctx
.line
, path_len
)) < 0)
63 git_parse_advance_chars(&ctx
->parse_ctx
, path_len
);
67 if (path
->size
> 0 && path
->ptr
[0] == '"')
68 error
= git_buf_unquote(path
);
73 git_path_squash_slashes(path
);
79 static int parse_header_path(char **out
, git_patch_parse_ctx
*ctx
)
81 git_buf path
= GIT_BUF_INIT
;
82 int error
= parse_header_path_buf(&path
, ctx
, header_path_len(ctx
));
84 *out
= git_buf_detach(&path
);
89 static int parse_header_git_oldpath(
90 git_patch_parsed
*patch
, git_patch_parse_ctx
*ctx
)
92 git_buf old_path
= GIT_BUF_INIT
;
95 if ((error
= parse_header_path_buf(&old_path
, ctx
, ctx
->parse_ctx
.line_len
- 1)) < 0)
98 patch
->old_path
= git_buf_detach(&old_path
);
101 git_buf_free(&old_path
);
105 static int parse_header_git_newpath(
106 git_patch_parsed
*patch
, git_patch_parse_ctx
*ctx
)
108 git_buf new_path
= GIT_BUF_INIT
;
111 if ((error
= parse_header_path_buf(&new_path
, ctx
, ctx
->parse_ctx
.line_len
- 1)) < 0)
114 patch
->new_path
= git_buf_detach(&new_path
);
117 git_buf_free(&new_path
);
121 static int parse_header_mode(uint16_t *mode
, git_patch_parse_ctx
*ctx
)
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
);
136 static int parse_header_oid(
139 git_patch_parse_ctx
*ctx
)
143 for (len
= 0; len
< ctx
->parse_ctx
.line_len
&& len
< GIT_OID_HEXSZ
; len
++) {
144 if (!git__isxdigit(ctx
->parse_ctx
.line
[len
]))
148 if (len
< GIT_OID_MINPREFIXLEN
|| len
> GIT_OID_HEXSZ
||
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
);
153 git_parse_advance_chars(&ctx
->parse_ctx
, len
);
155 *oid_len
= (uint16_t)len
;
160 static int parse_header_git_index(
161 git_patch_parsed
*patch
, git_patch_parse_ctx
*ctx
)
165 if (parse_header_oid(&patch
->base
.delta
->old_file
.id
,
166 &patch
->base
.delta
->old_file
.id_abbrev
, ctx
) < 0 ||
167 git_parse_advance_expected_str(&ctx
->parse_ctx
, "..") < 0 ||
168 parse_header_oid(&patch
->base
.delta
->new_file
.id
,
169 &patch
->base
.delta
->new_file
.id_abbrev
, ctx
) < 0)
172 if (git_parse_peek(&c
, &ctx
->parse_ctx
, 0) == 0 && c
== ' ') {
175 git_parse_advance_chars(&ctx
->parse_ctx
, 1);
177 if (parse_header_mode(&mode
, ctx
) < 0)
180 if (!patch
->base
.delta
->new_file
.mode
)
181 patch
->base
.delta
->new_file
.mode
= mode
;
183 if (!patch
->base
.delta
->old_file
.mode
)
184 patch
->base
.delta
->old_file
.mode
= mode
;
190 static int parse_header_git_oldmode(
191 git_patch_parsed
*patch
, git_patch_parse_ctx
*ctx
)
193 return parse_header_mode(&patch
->base
.delta
->old_file
.mode
, ctx
);
196 static int parse_header_git_newmode(
197 git_patch_parsed
*patch
, git_patch_parse_ctx
*ctx
)
199 return parse_header_mode(&patch
->base
.delta
->new_file
.mode
, ctx
);
202 static int parse_header_git_deletedfilemode(
203 git_patch_parsed
*patch
,
204 git_patch_parse_ctx
*ctx
)
206 git__free((char *)patch
->base
.delta
->old_file
.path
);
208 patch
->base
.delta
->old_file
.path
= NULL
;
209 patch
->base
.delta
->status
= GIT_DELTA_DELETED
;
210 patch
->base
.delta
->nfiles
= 1;
212 return parse_header_mode(&patch
->base
.delta
->old_file
.mode
, ctx
);
215 static int parse_header_git_newfilemode(
216 git_patch_parsed
*patch
,
217 git_patch_parse_ctx
*ctx
)
219 git__free((char *)patch
->base
.delta
->new_file
.path
);
221 patch
->base
.delta
->new_file
.path
= NULL
;
222 patch
->base
.delta
->status
= GIT_DELTA_ADDED
;
223 patch
->base
.delta
->nfiles
= 1;
225 return parse_header_mode(&patch
->base
.delta
->new_file
.mode
, ctx
);
228 static int parse_header_rename(
230 git_patch_parse_ctx
*ctx
)
232 git_buf path
= GIT_BUF_INIT
;
234 if (parse_header_path_buf(&path
, ctx
, header_path_len(ctx
)) < 0)
237 /* Note: the `rename from` and `rename to` lines include the literal
238 * filename. They do *not* include the prefix. (Who needs consistency?)
240 *out
= git_buf_detach(&path
);
244 static int parse_header_renamefrom(
245 git_patch_parsed
*patch
, git_patch_parse_ctx
*ctx
)
247 patch
->base
.delta
->status
= GIT_DELTA_RENAMED
;
248 return parse_header_rename(&patch
->rename_old_path
, ctx
);
251 static int parse_header_renameto(
252 git_patch_parsed
*patch
, git_patch_parse_ctx
*ctx
)
254 patch
->base
.delta
->status
= GIT_DELTA_RENAMED
;
255 return parse_header_rename(&patch
->rename_new_path
, ctx
);
258 static int parse_header_copyfrom(
259 git_patch_parsed
*patch
, git_patch_parse_ctx
*ctx
)
261 patch
->base
.delta
->status
= GIT_DELTA_COPIED
;
262 return parse_header_rename(&patch
->rename_old_path
, ctx
);
265 static int parse_header_copyto(
266 git_patch_parsed
*patch
, git_patch_parse_ctx
*ctx
)
268 patch
->base
.delta
->status
= GIT_DELTA_COPIED
;
269 return parse_header_rename(&patch
->rename_new_path
, ctx
);
272 static int parse_header_percent(uint16_t *out
, git_patch_parse_ctx
*ctx
)
276 if (git_parse_advance_digit(&val
, &ctx
->parse_ctx
, 10) < 0)
279 if (git_parse_advance_expected_str(&ctx
->parse_ctx
, "%") < 0)
282 if (val
< 0 || val
> 100)
289 static int parse_header_similarity(
290 git_patch_parsed
*patch
, git_patch_parse_ctx
*ctx
)
292 if (parse_header_percent(&patch
->base
.delta
->similarity
, ctx
) < 0)
293 return git_parse_err("invalid similarity percentage at line %"PRIuZ
,
294 ctx
->parse_ctx
.line_num
);
299 static int parse_header_dissimilarity(
300 git_patch_parsed
*patch
, git_patch_parse_ctx
*ctx
)
302 uint16_t dissimilarity
;
304 if (parse_header_percent(&dissimilarity
, ctx
) < 0)
305 return git_parse_err("invalid similarity percentage at line %"PRIuZ
,
306 ctx
->parse_ctx
.line_num
);
308 patch
->base
.delta
->similarity
= 100 - dissimilarity
;
313 static int parse_header_start(git_patch_parsed
*patch
, git_patch_parse_ctx
*ctx
)
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
);
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
);
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
331 if (!git_parse_ctx_contains(&ctx
->parse_ctx
, "\n", 1)) {
332 git_parse_advance_chars(&ctx
->parse_ctx
, ctx
->parse_ctx
.line_len
- 1);
334 git__free(patch
->header_old_path
);
335 patch
->header_old_path
= NULL
;
336 git__free(patch
->header_new_path
);
337 patch
->header_new_path
= NULL
;
357 } parse_header_state
;
361 parse_header_state expected_state
;
362 parse_header_state next_state
;
363 int(*fn
)(git_patch_parsed
*, git_patch_parse_ctx
*);
364 } parse_header_transition
;
366 static const parse_header_transition transitions
[] = {
368 { "diff --git " , STATE_START
, STATE_DIFF
, parse_header_start
},
370 { "deleted file mode " , STATE_DIFF
, STATE_FILEMODE
, parse_header_git_deletedfilemode
},
371 { "new file mode " , STATE_DIFF
, STATE_FILEMODE
, parse_header_git_newfilemode
},
372 { "old mode " , STATE_DIFF
, STATE_MODE
, parse_header_git_oldmode
},
373 { "new mode " , STATE_MODE
, STATE_END
, parse_header_git_newmode
},
375 { "index " , STATE_FILEMODE
, STATE_INDEX
, parse_header_git_index
},
376 { "index " , STATE_DIFF
, STATE_INDEX
, parse_header_git_index
},
377 { "index " , STATE_END
, STATE_INDEX
, parse_header_git_index
},
379 { "--- " , STATE_INDEX
, STATE_PATH
, parse_header_git_oldpath
},
380 { "+++ " , STATE_PATH
, STATE_END
, parse_header_git_newpath
},
381 { "GIT binary patch" , STATE_INDEX
, STATE_END
, NULL
},
382 { "Binary files " , STATE_INDEX
, STATE_END
, NULL
},
384 { "similarity index " , STATE_DIFF
, STATE_SIMILARITY
, parse_header_similarity
},
385 { "dissimilarity index ", STATE_DIFF
, STATE_SIMILARITY
, parse_header_dissimilarity
},
386 { "rename from " , STATE_SIMILARITY
, STATE_RENAME
, parse_header_renamefrom
},
387 { "rename old " , STATE_SIMILARITY
, STATE_RENAME
, parse_header_renamefrom
},
388 { "copy from " , STATE_SIMILARITY
, STATE_COPY
, parse_header_copyfrom
},
389 { "rename to " , STATE_RENAME
, STATE_END
, parse_header_renameto
},
390 { "rename new " , STATE_RENAME
, STATE_END
, parse_header_renameto
},
391 { "copy to " , STATE_COPY
, STATE_END
, parse_header_copyto
},
394 { "diff --git " , STATE_END
, 0, NULL
},
395 { "@@ -" , STATE_END
, 0, NULL
},
396 { "-- " , STATE_END
, 0, NULL
},
399 static int parse_header_git(
400 git_patch_parsed
*patch
,
401 git_patch_parse_ctx
*ctx
)
405 parse_header_state state
= STATE_START
;
407 /* Parse remaining header lines */
408 for (; ctx
->parse_ctx
.remain_len
> 0; git_parse_advance_line(&ctx
->parse_ctx
)) {
411 if (ctx
->parse_ctx
.line_len
== 0 || ctx
->parse_ctx
.line
[ctx
->parse_ctx
.line_len
- 1] != '\n')
414 for (i
= 0; i
< ARRAY_SIZE(transitions
); i
++) {
415 const parse_header_transition
*transition
= &transitions
[i
];
416 size_t op_len
= strlen(transition
->str
);
418 if (transition
->expected_state
!= state
||
419 git__prefixcmp(ctx
->parse_ctx
.line
, transition
->str
) != 0)
422 state
= transition
->next_state
;
424 /* Do not advance if this is the patch separator */
425 if (transition
->fn
== NULL
)
428 git_parse_advance_chars(&ctx
->parse_ctx
, op_len
);
430 if ((error
= transition
->fn(patch
, ctx
)) < 0)
433 git_parse_advance_ws(&ctx
->parse_ctx
);
435 if (git_parse_advance_expected_str(&ctx
->parse_ctx
, "\n") < 0 ||
436 ctx
->parse_ctx
.line_len
> 0) {
437 error
= git_parse_err("trailing data at line %"PRIuZ
, ctx
->parse_ctx
.line_num
);
446 error
= git_parse_err("invalid patch header at line %"PRIuZ
,
447 ctx
->parse_ctx
.line_num
);
452 if (state
!= STATE_END
) {
453 error
= git_parse_err("unexpected header line %"PRIuZ
, ctx
->parse_ctx
.line_num
);
461 static int parse_number(git_off_t
*out
, git_patch_parse_ctx
*ctx
)
466 if (!git__isdigit(ctx
->parse_ctx
.line
[0]))
469 if (git__strntol64(&num
, ctx
->parse_ctx
.line
, ctx
->parse_ctx
.line_len
, &end
, 10) < 0)
476 git_parse_advance_chars(&ctx
->parse_ctx
, (end
- ctx
->parse_ctx
.line
));
481 static int parse_int(int *out
, git_patch_parse_ctx
*ctx
)
485 if (git_parse_advance_digit(&num
, &ctx
->parse_ctx
, 10) < 0 || !git__is_int(num
))
492 static int parse_hunk_header(
493 git_patch_hunk
*hunk
,
494 git_patch_parse_ctx
*ctx
)
496 const char *header_start
= ctx
->parse_ctx
.line
;
499 hunk
->hunk
.old_lines
= 1;
500 hunk
->hunk
.new_lines
= 1;
502 if (git_parse_advance_expected_str(&ctx
->parse_ctx
, "@@ -") < 0 ||
503 parse_int(&hunk
->hunk
.old_start
, ctx
) < 0)
506 if (git_parse_peek(&c
, &ctx
->parse_ctx
, 0) == 0 && c
== ',') {
507 if (git_parse_advance_expected_str(&ctx
->parse_ctx
, ",") < 0 ||
508 parse_int(&hunk
->hunk
.old_lines
, ctx
) < 0)
512 if (git_parse_advance_expected_str(&ctx
->parse_ctx
, " +") < 0 ||
513 parse_int(&hunk
->hunk
.new_start
, ctx
) < 0)
516 if (git_parse_peek(&c
, &ctx
->parse_ctx
, 0) == 0 && c
== ',') {
517 if (git_parse_advance_expected_str(&ctx
->parse_ctx
, ",") < 0 ||
518 parse_int(&hunk
->hunk
.new_lines
, ctx
) < 0)
522 if (git_parse_advance_expected_str(&ctx
->parse_ctx
, " @@") < 0)
525 git_parse_advance_line(&ctx
->parse_ctx
);
527 if (!hunk
->hunk
.old_lines
&& !hunk
->hunk
.new_lines
)
530 hunk
->hunk
.header_len
= ctx
->parse_ctx
.line
- header_start
;
531 if (hunk
->hunk
.header_len
> (GIT_DIFF_HUNK_HEADER_SIZE
- 1))
532 return git_parse_err("oversized patch hunk header at line %"PRIuZ
,
533 ctx
->parse_ctx
.line_num
);
535 memcpy(hunk
->hunk
.header
, header_start
, hunk
->hunk
.header_len
);
536 hunk
->hunk
.header
[hunk
->hunk
.header_len
] = '\0';
541 giterr_set(GITERR_PATCH
, "invalid patch hunk header at line %"PRIuZ
,
542 ctx
->parse_ctx
.line_num
);
546 static int parse_hunk_body(
547 git_patch_parsed
*patch
,
548 git_patch_hunk
*hunk
,
549 git_patch_parse_ctx
*ctx
)
554 int oldlines
= hunk
->hunk
.old_lines
;
555 int newlines
= hunk
->hunk
.new_lines
;
558 ctx
->parse_ctx
.remain_len
> 1 &&
559 (oldlines
|| newlines
) &&
560 !git_parse_ctx_contains_s(&ctx
->parse_ctx
, "@@ -");
561 git_parse_advance_line(&ctx
->parse_ctx
)) {
566 int old_lineno
= hunk
->hunk
.old_start
+ (hunk
->hunk
.old_lines
- oldlines
);
567 int new_lineno
= hunk
->hunk
.new_start
+ (hunk
->hunk
.new_lines
- newlines
);
569 if (ctx
->parse_ctx
.line_len
== 0 || ctx
->parse_ctx
.line
[ctx
->parse_ctx
.line_len
- 1] != '\n') {
570 error
= git_parse_err("invalid patch instruction at line %"PRIuZ
,
571 ctx
->parse_ctx
.line_num
);
575 git_parse_peek(&c
, &ctx
->parse_ctx
, 0);
583 origin
= GIT_DIFF_LINE_CONTEXT
;
589 origin
= GIT_DIFF_LINE_DELETION
;
595 origin
= GIT_DIFF_LINE_ADDITION
;
601 error
= git_parse_err("invalid patch hunk at line %"PRIuZ
, ctx
->parse_ctx
.line_num
);
605 line
= git_array_alloc(patch
->base
.lines
);
606 GITERR_CHECK_ALLOC(line
);
608 memset(line
, 0x0, sizeof(git_diff_line
));
610 line
->content
= ctx
->parse_ctx
.line
+ prefix
;
611 line
->content_len
= ctx
->parse_ctx
.line_len
- prefix
;
612 line
->content_offset
= ctx
->parse_ctx
.content_len
- ctx
->parse_ctx
.remain_len
;
613 line
->origin
= origin
;
615 line
->old_lineno
= old_lineno
;
616 line
->new_lineno
= new_lineno
;
621 if (oldlines
|| newlines
) {
622 error
= git_parse_err(
623 "invalid patch hunk, expected %d old lines and %d new lines",
624 hunk
->hunk
.old_lines
, hunk
->hunk
.new_lines
);
628 /* Handle "\ No newline at end of file". Only expect the leading
629 * backslash, though, because the rest of the string could be
630 * localized. Because `diff` optimizes for the case where you
631 * want to apply the patch by hand.
633 if (git_parse_ctx_contains_s(&ctx
->parse_ctx
, "\\ ") &&
634 git_array_size(patch
->base
.lines
) > 0) {
636 line
= git_array_get(patch
->base
.lines
, git_array_size(patch
->base
.lines
) - 1);
638 if (line
->content_len
< 1) {
639 error
= git_parse_err("cannot trim trailing newline of empty line");
645 git_parse_advance_line(&ctx
->parse_ctx
);
652 static int parse_patch_header(
653 git_patch_parsed
*patch
,
654 git_patch_parse_ctx
*ctx
)
658 for (; ctx
->parse_ctx
.remain_len
> 0; git_parse_advance_line(&ctx
->parse_ctx
)) {
659 /* This line is too short to be a patch header. */
660 if (ctx
->parse_ctx
.line_len
< 6)
663 /* This might be a hunk header without a patch header, provide a
664 * sensible error message. */
665 if (git_parse_ctx_contains_s(&ctx
->parse_ctx
, "@@ -")) {
666 size_t line_num
= ctx
->parse_ctx
.line_num
;
669 /* If this cannot be parsed as a hunk header, it's just leading
672 if (parse_hunk_header(&hunk
, ctx
) < 0) {
677 error
= git_parse_err("invalid hunk header outside patch at line %"PRIuZ
,
682 /* This buffer is too short to contain a patch. */
683 if (ctx
->parse_ctx
.remain_len
< ctx
->parse_ctx
.line_len
+ 6)
686 /* A proper git patch */
687 if (git_parse_ctx_contains_s(&ctx
->parse_ctx
, "diff --git ")) {
688 error
= parse_header_git(patch
, ctx
);
696 giterr_set(GITERR_PATCH
, "no patch found");
697 error
= GIT_ENOTFOUND
;
703 static int parse_patch_binary_side(
704 git_diff_binary_file
*binary
,
705 git_patch_parse_ctx
*ctx
)
707 git_diff_binary_t type
= GIT_DIFF_BINARY_NONE
;
708 git_buf base85
= GIT_BUF_INIT
, decoded
= GIT_BUF_INIT
;
712 if (git_parse_ctx_contains_s(&ctx
->parse_ctx
, "literal ")) {
713 type
= GIT_DIFF_BINARY_LITERAL
;
714 git_parse_advance_chars(&ctx
->parse_ctx
, 8);
715 } else if (git_parse_ctx_contains_s(&ctx
->parse_ctx
, "delta ")) {
716 type
= GIT_DIFF_BINARY_DELTA
;
717 git_parse_advance_chars(&ctx
->parse_ctx
, 6);
719 error
= git_parse_err(
720 "unknown binary delta type at line %"PRIuZ
, ctx
->parse_ctx
.line_num
);
724 if (git_parse_advance_digit(&len
, &ctx
->parse_ctx
, 10) < 0 ||
725 git_parse_advance_nl(&ctx
->parse_ctx
) < 0 || len
< 0) {
726 error
= git_parse_err("invalid binary size at line %"PRIuZ
, ctx
->parse_ctx
.line_num
);
730 while (ctx
->parse_ctx
.line_len
) {
732 size_t encoded_len
, decoded_len
= 0, decoded_orig
= decoded
.size
;
734 git_parse_peek(&c
, &ctx
->parse_ctx
, 0);
738 else if (c
>= 'A' && c
<= 'Z')
739 decoded_len
= c
- 'A' + 1;
740 else if (c
>= 'a' && c
<= 'z')
741 decoded_len
= c
- 'a' + (('z' - 'a') + 1) + 1;
744 error
= git_parse_err("invalid binary length at line %"PRIuZ
, ctx
->parse_ctx
.line_num
);
748 git_parse_advance_chars(&ctx
->parse_ctx
, 1);
750 encoded_len
= ((decoded_len
/ 4) + !!(decoded_len
% 4)) * 5;
752 if (encoded_len
> ctx
->parse_ctx
.line_len
- 1) {
753 error
= git_parse_err("truncated binary data at line %"PRIuZ
, ctx
->parse_ctx
.line_num
);
757 if ((error
= git_buf_decode_base85(
758 &decoded
, ctx
->parse_ctx
.line
, encoded_len
, decoded_len
)) < 0)
761 if (decoded
.size
- decoded_orig
!= decoded_len
) {
762 error
= git_parse_err("truncated binary data at line %"PRIuZ
, ctx
->parse_ctx
.line_num
);
766 git_parse_advance_chars(&ctx
->parse_ctx
, encoded_len
);
768 if (git_parse_advance_nl(&ctx
->parse_ctx
) < 0) {
769 error
= git_parse_err("trailing data at line %"PRIuZ
, ctx
->parse_ctx
.line_num
);
775 binary
->inflatedlen
= (size_t)len
;
776 binary
->datalen
= decoded
.size
;
777 binary
->data
= git_buf_detach(&decoded
);
780 git_buf_free(&base85
);
781 git_buf_free(&decoded
);
785 static int parse_patch_binary(
786 git_patch_parsed
*patch
,
787 git_patch_parse_ctx
*ctx
)
791 if (git_parse_advance_expected_str(&ctx
->parse_ctx
, "GIT binary patch") < 0 ||
792 git_parse_advance_nl(&ctx
->parse_ctx
) < 0)
793 return git_parse_err("corrupt git binary header at line %"PRIuZ
, ctx
->parse_ctx
.line_num
);
795 /* parse old->new binary diff */
796 if ((error
= parse_patch_binary_side(
797 &patch
->base
.binary
.new_file
, ctx
)) < 0)
800 if (git_parse_advance_nl(&ctx
->parse_ctx
) < 0)
801 return git_parse_err("corrupt git binary separator at line %"PRIuZ
,
802 ctx
->parse_ctx
.line_num
);
804 /* parse new->old binary diff */
805 if ((error
= parse_patch_binary_side(
806 &patch
->base
.binary
.old_file
, ctx
)) < 0)
809 if (git_parse_advance_nl(&ctx
->parse_ctx
) < 0)
810 return git_parse_err("corrupt git binary patch separator at line %"PRIuZ
,
811 ctx
->parse_ctx
.line_num
);
813 patch
->base
.binary
.contains_data
= 1;
814 patch
->base
.delta
->flags
|= GIT_DIFF_FLAG_BINARY
;
818 static int parse_patch_binary_nodata(
819 git_patch_parsed
*patch
,
820 git_patch_parse_ctx
*ctx
)
822 if (git_parse_advance_expected_str(&ctx
->parse_ctx
, "Binary files ") < 0 ||
823 git_parse_advance_expected_str(&ctx
->parse_ctx
, patch
->header_old_path
) < 0 ||
824 git_parse_advance_expected_str(&ctx
->parse_ctx
, " and ") < 0 ||
825 git_parse_advance_expected_str(&ctx
->parse_ctx
, patch
->header_new_path
) < 0 ||
826 git_parse_advance_expected_str(&ctx
->parse_ctx
, " differ") < 0 ||
827 git_parse_advance_nl(&ctx
->parse_ctx
) < 0)
828 return git_parse_err("corrupt git binary header at line %"PRIuZ
, ctx
->parse_ctx
.line_num
);
830 patch
->base
.binary
.contains_data
= 0;
831 patch
->base
.delta
->flags
|= GIT_DIFF_FLAG_BINARY
;
835 static int parse_patch_hunks(
836 git_patch_parsed
*patch
,
837 git_patch_parse_ctx
*ctx
)
839 git_patch_hunk
*hunk
;
842 while (git_parse_ctx_contains_s(&ctx
->parse_ctx
, "@@ -")) {
843 hunk
= git_array_alloc(patch
->base
.hunks
);
844 GITERR_CHECK_ALLOC(hunk
);
846 memset(hunk
, 0, sizeof(git_patch_hunk
));
848 hunk
->line_start
= git_array_size(patch
->base
.lines
);
849 hunk
->line_count
= 0;
851 if ((error
= parse_hunk_header(hunk
, ctx
)) < 0 ||
852 (error
= parse_hunk_body(patch
, hunk
, ctx
)) < 0)
856 patch
->base
.delta
->flags
|= GIT_DIFF_FLAG_NOT_BINARY
;
862 static int parse_patch_body(
863 git_patch_parsed
*patch
, git_patch_parse_ctx
*ctx
)
865 if (git_parse_ctx_contains_s(&ctx
->parse_ctx
, "GIT binary patch"))
866 return parse_patch_binary(patch
, ctx
);
867 else if (git_parse_ctx_contains_s(&ctx
->parse_ctx
, "Binary files "))
868 return parse_patch_binary_nodata(patch
, ctx
);
870 return parse_patch_hunks(patch
, ctx
);
873 int check_header_names(
876 const char *old_or_new
,
882 if (two_null
&& strcmp(two
, "/dev/null") != 0)
883 return git_parse_err("expected %s path of '/dev/null'", old_or_new
);
885 else if (!two_null
&& strcmp(one
, two
) != 0)
886 return git_parse_err("mismatched %s path names", old_or_new
);
891 static int check_prefix(
894 git_patch_parsed
*patch
,
895 const char *path_start
)
897 const char *path
= path_start
;
898 size_t prefix_len
= patch
->ctx
->opts
.prefix_len
;
899 size_t remain_len
= prefix_len
;
907 /* leading slashes do not count as part of the prefix in git apply */
911 while (*path
&& remain_len
) {
918 if (remain_len
|| !*path
)
919 return git_parse_err(
920 "header filename does not contain %"PRIuZ
" path components",
924 *out_len
= (path
- path_start
);
925 *out
= git__strndup(path_start
, *out_len
);
927 return (*out
== NULL
) ? -1 : 0;
930 static int check_filenames(git_patch_parsed
*patch
)
932 const char *prefixed_new
, *prefixed_old
;
933 size_t old_prefixlen
= 0, new_prefixlen
= 0;
934 bool added
= (patch
->base
.delta
->status
== GIT_DELTA_ADDED
);
935 bool deleted
= (patch
->base
.delta
->status
== GIT_DELTA_DELETED
);
937 if (patch
->old_path
&& !patch
->new_path
)
938 return git_parse_err("missing new path");
940 if (!patch
->old_path
&& patch
->new_path
)
941 return git_parse_err("missing old path");
943 /* Ensure (non-renamed) paths match */
944 if (check_header_names(
945 patch
->header_old_path
, patch
->old_path
, "old", added
) < 0 ||
947 patch
->header_new_path
, patch
->new_path
, "new", deleted
) < 0)
950 prefixed_old
= (!added
&& patch
->old_path
) ? patch
->old_path
:
951 patch
->header_old_path
;
952 prefixed_new
= (!deleted
&& patch
->new_path
) ? patch
->new_path
:
953 patch
->header_new_path
;
956 &patch
->old_prefix
, &old_prefixlen
, patch
, prefixed_old
) < 0 ||
958 &patch
->new_prefix
, &new_prefixlen
, patch
, prefixed_new
) < 0)
961 /* Prefer the rename filenames as they are unambiguous and unprefixed */
962 if (patch
->rename_old_path
)
963 patch
->base
.delta
->old_file
.path
= patch
->rename_old_path
;
965 patch
->base
.delta
->old_file
.path
= prefixed_old
+ old_prefixlen
;
967 if (patch
->rename_new_path
)
968 patch
->base
.delta
->new_file
.path
= patch
->rename_new_path
;
970 patch
->base
.delta
->new_file
.path
= prefixed_new
+ new_prefixlen
;
972 if (!patch
->base
.delta
->old_file
.path
&&
973 !patch
->base
.delta
->new_file
.path
)
974 return git_parse_err("git diff header lacks old / new paths");
979 static int check_patch(git_patch_parsed
*patch
)
981 git_diff_delta
*delta
= patch
->base
.delta
;
983 if (check_filenames(patch
) < 0)
986 if (delta
->old_file
.path
&&
987 delta
->status
!= GIT_DELTA_DELETED
&&
988 !delta
->new_file
.mode
)
989 delta
->new_file
.mode
= delta
->old_file
.mode
;
991 if (delta
->status
== GIT_DELTA_MODIFIED
&&
992 !(delta
->flags
& GIT_DIFF_FLAG_BINARY
) &&
993 delta
->new_file
.mode
== delta
->old_file
.mode
&&
994 git_array_size(patch
->base
.hunks
) == 0)
995 return git_parse_err("patch with no hunks");
997 if (delta
->status
== GIT_DELTA_ADDED
) {
998 memset(&delta
->old_file
.id
, 0x0, sizeof(git_oid
));
999 delta
->old_file
.id_abbrev
= 0;
1002 if (delta
->status
== GIT_DELTA_DELETED
) {
1003 memset(&delta
->new_file
.id
, 0x0, sizeof(git_oid
));
1004 delta
->new_file
.id_abbrev
= 0;
1010 git_patch_parse_ctx
*git_patch_parse_ctx_init(
1011 const char *content
,
1013 const git_patch_options
*opts
)
1015 git_patch_parse_ctx
*ctx
;
1016 git_patch_options default_opts
= GIT_PATCH_OPTIONS_INIT
;
1018 if ((ctx
= git__calloc(1, sizeof(git_patch_parse_ctx
))) == NULL
)
1021 if ((git_parse_ctx_init(&ctx
->parse_ctx
, content
, content_len
)) < 0) {
1027 memcpy(&ctx
->opts
, opts
, sizeof(git_patch_options
));
1029 memcpy(&ctx
->opts
, &default_opts
, sizeof(git_patch_options
));
1031 GIT_REFCOUNT_INC(ctx
);
1035 static void patch_parse_ctx_free(git_patch_parse_ctx
*ctx
)
1040 git_parse_ctx_clear(&ctx
->parse_ctx
);
1044 void git_patch_parse_ctx_free(git_patch_parse_ctx
*ctx
)
1046 GIT_REFCOUNT_DEC(ctx
, patch_parse_ctx_free
);
1049 int git_patch_parsed_from_diff(git_patch
**out
, git_diff
*d
, size_t idx
)
1051 git_diff_parsed
*diff
= (git_diff_parsed
*)d
;
1054 if ((p
= git_vector_get(&diff
->patches
, idx
)) == NULL
)
1057 GIT_REFCOUNT_INC(p
);
1063 static void patch_parsed__free(git_patch
*p
)
1065 git_patch_parsed
*patch
= (git_patch_parsed
*)p
;
1070 git_patch_parse_ctx_free(patch
->ctx
);
1072 git__free((char *)patch
->base
.binary
.old_file
.data
);
1073 git__free((char *)patch
->base
.binary
.new_file
.data
);
1074 git_array_clear(patch
->base
.hunks
);
1075 git_array_clear(patch
->base
.lines
);
1076 git__free(patch
->base
.delta
);
1078 git__free(patch
->old_prefix
);
1079 git__free(patch
->new_prefix
);
1080 git__free(patch
->header_old_path
);
1081 git__free(patch
->header_new_path
);
1082 git__free(patch
->rename_old_path
);
1083 git__free(patch
->rename_new_path
);
1084 git__free(patch
->old_path
);
1085 git__free(patch
->new_path
);
1089 int git_patch_parse(
1091 git_patch_parse_ctx
*ctx
)
1093 git_patch_parsed
*patch
;
1101 patch
= git__calloc(1, sizeof(git_patch_parsed
));
1102 GITERR_CHECK_ALLOC(patch
);
1105 GIT_REFCOUNT_INC(patch
->ctx
);
1107 patch
->base
.free_fn
= patch_parsed__free
;
1109 patch
->base
.delta
= git__calloc(1, sizeof(git_diff_delta
));
1110 GITERR_CHECK_ALLOC(patch
->base
.delta
);
1112 patch
->base
.delta
->status
= GIT_DELTA_MODIFIED
;
1113 patch
->base
.delta
->nfiles
= 2;
1115 start
= ctx
->parse_ctx
.remain_len
;
1117 if ((error
= parse_patch_header(patch
, ctx
)) < 0 ||
1118 (error
= parse_patch_body(patch
, ctx
)) < 0 ||
1119 (error
= check_patch(patch
)) < 0)
1122 used
= start
- ctx
->parse_ctx
.remain_len
;
1123 ctx
->parse_ctx
.remain
+= used
;
1125 patch
->base
.diff_opts
.old_prefix
= patch
->old_prefix
;
1126 patch
->base
.diff_opts
.new_prefix
= patch
->new_prefix
;
1127 patch
->base
.diff_opts
.flags
|= GIT_DIFF_SHOW_BINARY
;
1129 GIT_REFCOUNT_INC(&patch
->base
);
1130 *out
= &patch
->base
;
1134 patch_parsed__free(&patch
->base
);
1139 int git_patch_from_buffer(
1141 const char *content
,
1143 const git_patch_options
*opts
)
1145 git_patch_parse_ctx
*ctx
;
1148 ctx
= git_patch_parse_ctx_init(content
, content_len
, opts
);
1149 GITERR_CHECK_ALLOC(ctx
);
1151 error
= git_patch_parse(out
, ctx
);
1153 git_patch_parse_ctx_free(ctx
);