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_dispose(&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_dispose(&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)
285 *out
= (uint16_t)val
;
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_ctx_contains(&ctx
->parse_ctx
, "\r\n", 2)) {
333 git_parse_advance_chars(&ctx
->parse_ctx
, ctx
->parse_ctx
.line_len
- 1);
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
;
358 } parse_header_state
;
362 parse_header_state expected_state
;
363 parse_header_state next_state
;
364 int(*fn
)(git_patch_parsed
*, git_patch_parse_ctx
*);
365 } parse_header_transition
;
367 static const parse_header_transition transitions
[] = {
369 { "diff --git " , STATE_START
, STATE_DIFF
, parse_header_start
},
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
},
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
},
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
},
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
},
395 { "diff --git " , STATE_END
, 0, NULL
},
396 { "@@ -" , STATE_END
, 0, NULL
},
397 { "-- " , STATE_END
, 0, NULL
},
400 static int parse_header_git(
401 git_patch_parsed
*patch
,
402 git_patch_parse_ctx
*ctx
)
406 parse_header_state state
= STATE_START
;
408 /* Parse remaining header lines */
409 for (; ctx
->parse_ctx
.remain_len
> 0; git_parse_advance_line(&ctx
->parse_ctx
)) {
412 if (ctx
->parse_ctx
.line_len
== 0 || ctx
->parse_ctx
.line
[ctx
->parse_ctx
.line_len
- 1] != '\n')
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
);
419 if (transition
->expected_state
!= state
||
420 git__prefixcmp(ctx
->parse_ctx
.line
, transition
->str
) != 0)
423 state
= transition
->next_state
;
425 /* Do not advance if this is the patch separator */
426 if (transition
->fn
== NULL
)
429 git_parse_advance_chars(&ctx
->parse_ctx
, op_len
);
431 if ((error
= transition
->fn(patch
, ctx
)) < 0)
434 git_parse_advance_ws(&ctx
->parse_ctx
);
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
);
447 error
= git_parse_err("invalid patch header at line %"PRIuZ
,
448 ctx
->parse_ctx
.line_num
);
453 if (state
!= STATE_END
) {
454 error
= git_parse_err("unexpected header line %"PRIuZ
, ctx
->parse_ctx
.line_num
);
462 static int parse_int(int *out
, git_patch_parse_ctx
*ctx
)
466 if (git_parse_advance_digit(&num
, &ctx
->parse_ctx
, 10) < 0 || !git__is_int(num
))
473 static int parse_hunk_header(
474 git_patch_hunk
*hunk
,
475 git_patch_parse_ctx
*ctx
)
477 const char *header_start
= ctx
->parse_ctx
.line
;
480 hunk
->hunk
.old_lines
= 1;
481 hunk
->hunk
.new_lines
= 1;
483 if (git_parse_advance_expected_str(&ctx
->parse_ctx
, "@@ -") < 0 ||
484 parse_int(&hunk
->hunk
.old_start
, ctx
) < 0)
487 if (git_parse_peek(&c
, &ctx
->parse_ctx
, 0) == 0 && c
== ',') {
488 if (git_parse_advance_expected_str(&ctx
->parse_ctx
, ",") < 0 ||
489 parse_int(&hunk
->hunk
.old_lines
, ctx
) < 0)
493 if (git_parse_advance_expected_str(&ctx
->parse_ctx
, " +") < 0 ||
494 parse_int(&hunk
->hunk
.new_start
, ctx
) < 0)
497 if (git_parse_peek(&c
, &ctx
->parse_ctx
, 0) == 0 && c
== ',') {
498 if (git_parse_advance_expected_str(&ctx
->parse_ctx
, ",") < 0 ||
499 parse_int(&hunk
->hunk
.new_lines
, ctx
) < 0)
503 if (git_parse_advance_expected_str(&ctx
->parse_ctx
, " @@") < 0)
506 git_parse_advance_line(&ctx
->parse_ctx
);
508 if (!hunk
->hunk
.old_lines
&& !hunk
->hunk
.new_lines
)
511 hunk
->hunk
.header_len
= ctx
->parse_ctx
.line
- header_start
;
512 if (hunk
->hunk
.header_len
> (GIT_DIFF_HUNK_HEADER_SIZE
- 1))
513 return git_parse_err("oversized patch hunk header at line %"PRIuZ
,
514 ctx
->parse_ctx
.line_num
);
516 memcpy(hunk
->hunk
.header
, header_start
, hunk
->hunk
.header_len
);
517 hunk
->hunk
.header
[hunk
->hunk
.header_len
] = '\0';
522 git_error_set(GIT_ERROR_PATCH
, "invalid patch hunk header at line %"PRIuZ
,
523 ctx
->parse_ctx
.line_num
);
527 static int parse_hunk_body(
528 git_patch_parsed
*patch
,
529 git_patch_hunk
*hunk
,
530 git_patch_parse_ctx
*ctx
)
535 int oldlines
= hunk
->hunk
.old_lines
;
536 int newlines
= hunk
->hunk
.new_lines
;
539 ctx
->parse_ctx
.remain_len
> 1 &&
540 (oldlines
|| newlines
) &&
541 !git_parse_ctx_contains_s(&ctx
->parse_ctx
, "@@ -");
542 git_parse_advance_line(&ctx
->parse_ctx
)) {
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
);
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
);
556 git_parse_peek(&c
, &ctx
->parse_ctx
, 0);
564 origin
= GIT_DIFF_LINE_CONTEXT
;
570 origin
= GIT_DIFF_LINE_DELETION
;
576 origin
= GIT_DIFF_LINE_ADDITION
;
582 error
= git_parse_err("invalid patch hunk at line %"PRIuZ
, ctx
->parse_ctx
.line_num
);
586 line
= git_array_alloc(patch
->base
.lines
);
587 GIT_ERROR_CHECK_ALLOC(line
);
589 memset(line
, 0x0, sizeof(git_diff_line
));
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
;
594 line
->origin
= origin
;
596 line
->old_lineno
= old_lineno
;
597 line
->new_lineno
= new_lineno
;
602 if (oldlines
|| newlines
) {
603 error
= git_parse_err(
604 "invalid patch hunk, expected %d old lines and %d new lines",
605 hunk
->hunk
.old_lines
, hunk
->hunk
.new_lines
);
609 /* Handle "\ No newline at end of file". Only expect the leading
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.
614 if (git_parse_ctx_contains_s(&ctx
->parse_ctx
, "\\ ") &&
615 git_array_size(patch
->base
.lines
) > 0) {
617 line
= git_array_get(patch
->base
.lines
, git_array_size(patch
->base
.lines
) - 1);
619 if (line
->content_len
< 1) {
620 error
= git_parse_err("cannot trim trailing newline of empty line");
626 git_parse_advance_line(&ctx
->parse_ctx
);
633 static int parse_patch_header(
634 git_patch_parsed
*patch
,
635 git_patch_parse_ctx
*ctx
)
639 for (; ctx
->parse_ctx
.remain_len
> 0; git_parse_advance_line(&ctx
->parse_ctx
)) {
640 /* This line is too short to be a patch header. */
641 if (ctx
->parse_ctx
.line_len
< 6)
644 /* This might be a hunk header without a patch header, provide a
645 * sensible error message. */
646 if (git_parse_ctx_contains_s(&ctx
->parse_ctx
, "@@ -")) {
647 size_t line_num
= ctx
->parse_ctx
.line_num
;
650 /* If this cannot be parsed as a hunk header, it's just leading
653 if (parse_hunk_header(&hunk
, ctx
) < 0) {
658 error
= git_parse_err("invalid hunk header outside patch at line %"PRIuZ
,
663 /* This buffer is too short to contain a patch. */
664 if (ctx
->parse_ctx
.remain_len
< ctx
->parse_ctx
.line_len
+ 6)
667 /* A proper git patch */
668 if (git_parse_ctx_contains_s(&ctx
->parse_ctx
, "diff --git ")) {
669 error
= parse_header_git(patch
, ctx
);
677 git_error_set(GIT_ERROR_PATCH
, "no patch found");
678 error
= GIT_ENOTFOUND
;
684 static int parse_patch_binary_side(
685 git_diff_binary_file
*binary
,
686 git_patch_parse_ctx
*ctx
)
688 git_diff_binary_t type
= GIT_DIFF_BINARY_NONE
;
689 git_buf base85
= GIT_BUF_INIT
, decoded
= GIT_BUF_INIT
;
693 if (git_parse_ctx_contains_s(&ctx
->parse_ctx
, "literal ")) {
694 type
= GIT_DIFF_BINARY_LITERAL
;
695 git_parse_advance_chars(&ctx
->parse_ctx
, 8);
696 } else if (git_parse_ctx_contains_s(&ctx
->parse_ctx
, "delta ")) {
697 type
= GIT_DIFF_BINARY_DELTA
;
698 git_parse_advance_chars(&ctx
->parse_ctx
, 6);
700 error
= git_parse_err(
701 "unknown binary delta type at line %"PRIuZ
, ctx
->parse_ctx
.line_num
);
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
);
711 while (ctx
->parse_ctx
.line_len
) {
713 size_t encoded_len
, decoded_len
= 0, decoded_orig
= decoded
.size
;
715 git_parse_peek(&c
, &ctx
->parse_ctx
, 0);
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;
725 error
= git_parse_err("invalid binary length at line %"PRIuZ
, ctx
->parse_ctx
.line_num
);
729 git_parse_advance_chars(&ctx
->parse_ctx
, 1);
731 encoded_len
= ((decoded_len
/ 4) + !!(decoded_len
% 4)) * 5;
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
);
738 if ((error
= git_buf_decode_base85(
739 &decoded
, ctx
->parse_ctx
.line
, encoded_len
, decoded_len
)) < 0)
742 if (decoded
.size
- decoded_orig
!= decoded_len
) {
743 error
= git_parse_err("truncated binary data at line %"PRIuZ
, ctx
->parse_ctx
.line_num
);
747 git_parse_advance_chars(&ctx
->parse_ctx
, encoded_len
);
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
);
756 binary
->inflatedlen
= (size_t)len
;
757 binary
->datalen
= decoded
.size
;
758 binary
->data
= git_buf_detach(&decoded
);
761 git_buf_dispose(&base85
);
762 git_buf_dispose(&decoded
);
766 static int parse_patch_binary(
767 git_patch_parsed
*patch
,
768 git_patch_parse_ctx
*ctx
)
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
);
776 /* parse old->new binary diff */
777 if ((error
= parse_patch_binary_side(
778 &patch
->base
.binary
.new_file
, ctx
)) < 0)
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
);
785 /* parse new->old binary diff */
786 if ((error
= parse_patch_binary_side(
787 &patch
->base
.binary
.old_file
, ctx
)) < 0)
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
);
794 patch
->base
.binary
.contains_data
= 1;
795 patch
->base
.delta
->flags
|= GIT_DIFF_FLAG_BINARY
;
799 static int parse_patch_binary_nodata(
800 git_patch_parsed
*patch
,
801 git_patch_parse_ctx
*ctx
)
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
);
811 patch
->base
.binary
.contains_data
= 0;
812 patch
->base
.delta
->flags
|= GIT_DIFF_FLAG_BINARY
;
816 static int parse_patch_hunks(
817 git_patch_parsed
*patch
,
818 git_patch_parse_ctx
*ctx
)
820 git_patch_hunk
*hunk
;
823 while (git_parse_ctx_contains_s(&ctx
->parse_ctx
, "@@ -")) {
824 hunk
= git_array_alloc(patch
->base
.hunks
);
825 GIT_ERROR_CHECK_ALLOC(hunk
);
827 memset(hunk
, 0, sizeof(git_patch_hunk
));
829 hunk
->line_start
= git_array_size(patch
->base
.lines
);
830 hunk
->line_count
= 0;
832 if ((error
= parse_hunk_header(hunk
, ctx
)) < 0 ||
833 (error
= parse_hunk_body(patch
, hunk
, ctx
)) < 0)
837 patch
->base
.delta
->flags
|= GIT_DIFF_FLAG_NOT_BINARY
;
843 static int parse_patch_body(
844 git_patch_parsed
*patch
, git_patch_parse_ctx
*ctx
)
846 if (git_parse_ctx_contains_s(&ctx
->parse_ctx
, "GIT binary patch"))
847 return parse_patch_binary(patch
, ctx
);
848 else if (git_parse_ctx_contains_s(&ctx
->parse_ctx
, "Binary files "))
849 return parse_patch_binary_nodata(patch
, ctx
);
851 return parse_patch_hunks(patch
, ctx
);
854 int check_header_names(
857 const char *old_or_new
,
863 if (two_null
&& strcmp(two
, "/dev/null") != 0)
864 return git_parse_err("expected %s path of '/dev/null'", old_or_new
);
866 else if (!two_null
&& strcmp(one
, two
) != 0)
867 return git_parse_err("mismatched %s path names", old_or_new
);
872 static int check_prefix(
875 git_patch_parsed
*patch
,
876 const char *path_start
)
878 const char *path
= path_start
;
879 size_t prefix_len
= patch
->ctx
->opts
.prefix_len
;
880 size_t remain_len
= prefix_len
;
888 /* leading slashes do not count as part of the prefix in git apply */
892 while (*path
&& remain_len
) {
899 if (remain_len
|| !*path
)
900 return git_parse_err(
901 "header filename does not contain %"PRIuZ
" path components",
905 *out_len
= (path
- path_start
);
906 *out
= git__strndup(path_start
, *out_len
);
908 return (*out
== NULL
) ? -1 : 0;
911 static int check_filenames(git_patch_parsed
*patch
)
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
);
918 if (patch
->old_path
&& !patch
->new_path
)
919 return git_parse_err("missing new path");
921 if (!patch
->old_path
&& patch
->new_path
)
922 return git_parse_err("missing old path");
924 /* Ensure (non-renamed) paths match */
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)
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
;
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))
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
;
940 patch
->base
.delta
->old_file
.path
= prefixed_old
+ old_prefixlen
;
942 if (patch
->rename_new_path
)
943 patch
->base
.delta
->new_file
.path
= patch
->rename_new_path
;
945 patch
->base
.delta
->new_file
.path
= prefixed_new
+ new_prefixlen
;
947 if (!patch
->base
.delta
->old_file
.path
&&
948 !patch
->base
.delta
->new_file
.path
)
949 return git_parse_err("git diff header lacks old / new paths");
954 static int check_patch(git_patch_parsed
*patch
)
956 git_diff_delta
*delta
= patch
->base
.delta
;
958 if (check_filenames(patch
) < 0)
961 if (delta
->old_file
.path
&&
962 delta
->status
!= GIT_DELTA_DELETED
&&
963 !delta
->new_file
.mode
)
964 delta
->new_file
.mode
= delta
->old_file
.mode
;
966 if (delta
->status
== GIT_DELTA_MODIFIED
&&
967 !(delta
->flags
& GIT_DIFF_FLAG_BINARY
) &&
968 delta
->new_file
.mode
== delta
->old_file
.mode
&&
969 git_array_size(patch
->base
.hunks
) == 0)
970 return git_parse_err("patch with no hunks");
972 if (delta
->status
== GIT_DELTA_ADDED
) {
973 memset(&delta
->old_file
.id
, 0x0, sizeof(git_oid
));
974 delta
->old_file
.id_abbrev
= 0;
977 if (delta
->status
== GIT_DELTA_DELETED
) {
978 memset(&delta
->new_file
.id
, 0x0, sizeof(git_oid
));
979 delta
->new_file
.id_abbrev
= 0;
985 git_patch_parse_ctx
*git_patch_parse_ctx_init(
988 const git_patch_options
*opts
)
990 git_patch_parse_ctx
*ctx
;
991 git_patch_options default_opts
= GIT_PATCH_OPTIONS_INIT
;
993 if ((ctx
= git__calloc(1, sizeof(git_patch_parse_ctx
))) == NULL
)
996 if ((git_parse_ctx_init(&ctx
->parse_ctx
, content
, content_len
)) < 0) {
1002 memcpy(&ctx
->opts
, opts
, sizeof(git_patch_options
));
1004 memcpy(&ctx
->opts
, &default_opts
, sizeof(git_patch_options
));
1006 GIT_REFCOUNT_INC(ctx
);
1010 static void patch_parse_ctx_free(git_patch_parse_ctx
*ctx
)
1015 git_parse_ctx_clear(&ctx
->parse_ctx
);
1019 void git_patch_parse_ctx_free(git_patch_parse_ctx
*ctx
)
1021 GIT_REFCOUNT_DEC(ctx
, patch_parse_ctx_free
);
1024 int git_patch_parsed_from_diff(git_patch
**out
, git_diff
*d
, size_t idx
)
1026 git_diff_parsed
*diff
= (git_diff_parsed
*)d
;
1029 if ((p
= git_vector_get(&diff
->patches
, idx
)) == NULL
)
1032 GIT_REFCOUNT_INC(p
);
1038 static void patch_parsed__free(git_patch
*p
)
1040 git_patch_parsed
*patch
= (git_patch_parsed
*)p
;
1045 git_patch_parse_ctx_free(patch
->ctx
);
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
);
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
);
1064 int git_patch_parse(
1066 git_patch_parse_ctx
*ctx
)
1068 git_patch_parsed
*patch
;
1076 patch
= git__calloc(1, sizeof(git_patch_parsed
));
1077 GIT_ERROR_CHECK_ALLOC(patch
);
1080 GIT_REFCOUNT_INC(patch
->ctx
);
1082 patch
->base
.free_fn
= patch_parsed__free
;
1084 patch
->base
.delta
= git__calloc(1, sizeof(git_diff_delta
));
1085 GIT_ERROR_CHECK_ALLOC(patch
->base
.delta
);
1087 patch
->base
.delta
->status
= GIT_DELTA_MODIFIED
;
1088 patch
->base
.delta
->nfiles
= 2;
1090 start
= ctx
->parse_ctx
.remain_len
;
1092 if ((error
= parse_patch_header(patch
, ctx
)) < 0 ||
1093 (error
= parse_patch_body(patch
, ctx
)) < 0 ||
1094 (error
= check_patch(patch
)) < 0)
1097 used
= start
- ctx
->parse_ctx
.remain_len
;
1098 ctx
->parse_ctx
.remain
+= used
;
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
;
1104 GIT_REFCOUNT_INC(&patch
->base
);
1105 *out
= &patch
->base
;
1109 patch_parsed__free(&patch
->base
);
1114 int git_patch_from_buffer(
1116 const char *content
,
1118 const git_patch_options
*opts
)
1120 git_patch_parse_ctx
*ctx
;
1123 ctx
= git_patch_parse_ctx_init(content
, content_len
, opts
);
1124 GIT_ERROR_CHECK_ALLOC(ctx
);
1126 error
= git_patch_parse(out
, ctx
);
1128 git_patch_parse_ctx_free(ctx
);