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.
7 #include "git2/patch.h"
9 #include "patch_parse.h"
10 #include "diff_parse.h"
13 #define parse_err(...) \
14 ( giterr_set(GITERR_PATCH, __VA_ARGS__), -1 )
19 git_patch_parse_ctx
*ctx
;
21 /* the paths from the `diff --git` header, these will be used if this is not
22 * a rename (and rename paths are specified) or if no `+++`/`---` line specify
25 char *header_old_path
, *header_new_path
;
27 /* renamed paths are precise and are not prefixed */
28 char *rename_old_path
, *rename_new_path
;
30 /* the paths given in `---` and `+++` lines */
31 char *old_path
, *new_path
;
33 /* the prefixes from the old/new paths */
34 char *old_prefix
, *new_prefix
;
38 GIT_INLINE(bool) parse_ctx_contains(
39 git_patch_parse_ctx
*ctx
, const char *str
, size_t len
)
41 return (ctx
->line_len
>= len
&& memcmp(ctx
->line
, str
, len
) == 0);
44 #define parse_ctx_contains_s(ctx, str) \
45 parse_ctx_contains(ctx, str, sizeof(str) - 1)
47 static void parse_advance_line(git_patch_parse_ctx
*ctx
)
49 ctx
->line
+= ctx
->line_len
;
50 ctx
->remain_len
-= ctx
->line_len
;
51 ctx
->line_len
= git__linenlen(ctx
->line
, ctx
->remain_len
);
55 static void parse_advance_chars(git_patch_parse_ctx
*ctx
, size_t char_cnt
)
57 ctx
->line
+= char_cnt
;
58 ctx
->remain_len
-= char_cnt
;
59 ctx
->line_len
-= char_cnt
;
62 static int parse_advance_expected(
63 git_patch_parse_ctx
*ctx
,
67 if (ctx
->line_len
< expected_len
)
70 if (memcmp(ctx
->line
, expected
, expected_len
) != 0)
73 parse_advance_chars(ctx
, expected_len
);
77 #define parse_advance_expected_str(ctx, str) \
78 parse_advance_expected(ctx, str, strlen(str))
80 static int parse_advance_ws(git_patch_parse_ctx
*ctx
)
84 while (ctx
->line_len
> 0 &&
85 ctx
->line
[0] != '\n' &&
86 git__isspace(ctx
->line
[0])) {
96 static int parse_advance_nl(git_patch_parse_ctx
*ctx
)
98 if (ctx
->line_len
!= 1 || ctx
->line
[0] != '\n')
101 parse_advance_line(ctx
);
105 static int header_path_len(git_patch_parse_ctx
*ctx
)
108 bool quoted
= (ctx
->line_len
> 0 && ctx
->line
[0] == '"');
111 for (len
= quoted
; len
< ctx
->line_len
; len
++) {
112 if (!quoted
&& git__isspace(ctx
->line
[len
]))
114 else if (quoted
&& !inquote
&& ctx
->line
[len
] == '"') {
119 inquote
= (!inquote
&& ctx
->line
[len
] == '\\');
125 static int parse_header_path_buf(git_buf
*path
, git_patch_parse_ctx
*ctx
)
127 int path_len
, error
= 0;
129 path_len
= header_path_len(ctx
);
131 if ((error
= git_buf_put(path
, ctx
->line
, path_len
)) < 0)
134 parse_advance_chars(ctx
, path_len
);
138 if (path
->size
> 0 && path
->ptr
[0] == '"')
139 error
= git_buf_unquote(path
);
144 git_path_squash_slashes(path
);
150 static int parse_header_path(char **out
, git_patch_parse_ctx
*ctx
)
152 git_buf path
= GIT_BUF_INIT
;
153 int error
= parse_header_path_buf(&path
, ctx
);
155 *out
= git_buf_detach(&path
);
160 static int parse_header_git_oldpath(
161 git_patch_parsed
*patch
, git_patch_parse_ctx
*ctx
)
163 return parse_header_path(&patch
->old_path
, ctx
);
166 static int parse_header_git_newpath(
167 git_patch_parsed
*patch
, git_patch_parse_ctx
*ctx
)
169 return parse_header_path(&patch
->new_path
, ctx
);
172 static int parse_header_mode(uint16_t *mode
, git_patch_parse_ctx
*ctx
)
178 if (ctx
->line_len
< 1 || !git__isdigit(ctx
->line
[0]))
179 return parse_err("invalid file mode at line %"PRIuZ
, ctx
->line_num
);
181 if ((ret
= git__strntol32(&m
, ctx
->line
, ctx
->line_len
, &end
, 8)) < 0)
189 parse_advance_chars(ctx
, (end
- ctx
->line
));
194 static int parse_header_oid(
197 git_patch_parse_ctx
*ctx
)
201 for (len
= 0; len
< ctx
->line_len
&& len
< GIT_OID_HEXSZ
; len
++) {
202 if (!git__isxdigit(ctx
->line
[len
]))
206 if (len
< GIT_OID_MINPREFIXLEN
|| len
> GIT_OID_HEXSZ
||
207 git_oid_fromstrn(oid
, ctx
->line
, len
) < 0)
208 return parse_err("invalid hex formatted object id at line %"PRIuZ
,
211 parse_advance_chars(ctx
, len
);
213 *oid_len
= (uint16_t)len
;
218 static int parse_header_git_index(
219 git_patch_parsed
*patch
, git_patch_parse_ctx
*ctx
)
221 if (parse_header_oid(&patch
->base
.delta
->old_file
.id
,
222 &patch
->base
.delta
->old_file
.id_abbrev
, ctx
) < 0 ||
223 parse_advance_expected_str(ctx
, "..") < 0 ||
224 parse_header_oid(&patch
->base
.delta
->new_file
.id
,
225 &patch
->base
.delta
->new_file
.id_abbrev
, ctx
) < 0)
228 if (ctx
->line_len
> 0 && ctx
->line
[0] == ' ') {
231 parse_advance_chars(ctx
, 1);
233 if (parse_header_mode(&mode
, ctx
) < 0)
236 if (!patch
->base
.delta
->new_file
.mode
)
237 patch
->base
.delta
->new_file
.mode
= mode
;
239 if (!patch
->base
.delta
->old_file
.mode
)
240 patch
->base
.delta
->old_file
.mode
= mode
;
246 static int parse_header_git_oldmode(
247 git_patch_parsed
*patch
, git_patch_parse_ctx
*ctx
)
249 return parse_header_mode(&patch
->base
.delta
->old_file
.mode
, ctx
);
252 static int parse_header_git_newmode(
253 git_patch_parsed
*patch
, git_patch_parse_ctx
*ctx
)
255 return parse_header_mode(&patch
->base
.delta
->new_file
.mode
, ctx
);
258 static int parse_header_git_deletedfilemode(
259 git_patch_parsed
*patch
,
260 git_patch_parse_ctx
*ctx
)
262 git__free((char *)patch
->base
.delta
->old_file
.path
);
264 patch
->base
.delta
->old_file
.path
= NULL
;
265 patch
->base
.delta
->status
= GIT_DELTA_DELETED
;
266 patch
->base
.delta
->nfiles
= 1;
268 return parse_header_mode(&patch
->base
.delta
->old_file
.mode
, ctx
);
271 static int parse_header_git_newfilemode(
272 git_patch_parsed
*patch
,
273 git_patch_parse_ctx
*ctx
)
275 git__free((char *)patch
->base
.delta
->new_file
.path
);
277 patch
->base
.delta
->new_file
.path
= NULL
;
278 patch
->base
.delta
->status
= GIT_DELTA_ADDED
;
279 patch
->base
.delta
->nfiles
= 1;
281 return parse_header_mode(&patch
->base
.delta
->new_file
.mode
, ctx
);
284 static int parse_header_rename(
286 git_patch_parse_ctx
*ctx
)
288 git_buf path
= GIT_BUF_INIT
;
290 if (parse_header_path_buf(&path
, ctx
) < 0)
293 /* Note: the `rename from` and `rename to` lines include the literal
294 * filename. They do *not* include the prefix. (Who needs consistency?)
296 *out
= git_buf_detach(&path
);
300 static int parse_header_renamefrom(
301 git_patch_parsed
*patch
, git_patch_parse_ctx
*ctx
)
303 patch
->base
.delta
->status
= GIT_DELTA_RENAMED
;
304 return parse_header_rename(&patch
->rename_old_path
, ctx
);
307 static int parse_header_renameto(
308 git_patch_parsed
*patch
, git_patch_parse_ctx
*ctx
)
310 patch
->base
.delta
->status
= GIT_DELTA_RENAMED
;
311 return parse_header_rename(&patch
->rename_new_path
, ctx
);
314 static int parse_header_copyfrom(
315 git_patch_parsed
*patch
, git_patch_parse_ctx
*ctx
)
317 patch
->base
.delta
->status
= GIT_DELTA_COPIED
;
318 return parse_header_rename(&patch
->rename_old_path
, ctx
);
321 static int parse_header_copyto(
322 git_patch_parsed
*patch
, git_patch_parse_ctx
*ctx
)
324 patch
->base
.delta
->status
= GIT_DELTA_COPIED
;
325 return parse_header_rename(&patch
->rename_new_path
, ctx
);
328 static int parse_header_percent(uint16_t *out
, git_patch_parse_ctx
*ctx
)
333 if (ctx
->line_len
< 1 || !git__isdigit(ctx
->line
[0]) ||
334 git__strntol32(&val
, ctx
->line
, ctx
->line_len
, &end
, 10) < 0)
337 parse_advance_chars(ctx
, (end
- ctx
->line
));
339 if (parse_advance_expected_str(ctx
, "%") < 0)
349 static int parse_header_similarity(
350 git_patch_parsed
*patch
, git_patch_parse_ctx
*ctx
)
352 if (parse_header_percent(&patch
->base
.delta
->similarity
, ctx
) < 0)
353 return parse_err("invalid similarity percentage at line %"PRIuZ
,
359 static int parse_header_dissimilarity(
360 git_patch_parsed
*patch
, git_patch_parse_ctx
*ctx
)
362 uint16_t dissimilarity
;
364 if (parse_header_percent(&dissimilarity
, ctx
) < 0)
365 return parse_err("invalid similarity percentage at line %"PRIuZ
,
368 patch
->base
.delta
->similarity
= 100 - dissimilarity
;
375 int(*fn
)(git_patch_parsed
*, git_patch_parse_ctx
*);
378 static const header_git_op header_git_ops
[] = {
379 { "diff --git ", NULL
},
381 { "GIT binary patch", NULL
},
382 { "Binary files ", NULL
},
383 { "--- ", parse_header_git_oldpath
},
384 { "+++ ", parse_header_git_newpath
},
385 { "index ", parse_header_git_index
},
386 { "old mode ", parse_header_git_oldmode
},
387 { "new mode ", parse_header_git_newmode
},
388 { "deleted file mode ", parse_header_git_deletedfilemode
},
389 { "new file mode ", parse_header_git_newfilemode
},
390 { "rename from ", parse_header_renamefrom
},
391 { "rename to ", parse_header_renameto
},
392 { "rename old ", parse_header_renamefrom
},
393 { "rename new ", parse_header_renameto
},
394 { "copy from ", parse_header_copyfrom
},
395 { "copy to ", parse_header_copyto
},
396 { "similarity index ", parse_header_similarity
},
397 { "dissimilarity index ", parse_header_dissimilarity
},
400 static int parse_header_git(
401 git_patch_parsed
*patch
,
402 git_patch_parse_ctx
*ctx
)
407 /* Parse the diff --git line */
408 if (parse_advance_expected_str(ctx
, "diff --git ") < 0)
409 return parse_err("corrupt git diff header at line %"PRIuZ
, ctx
->line_num
);
411 if (parse_header_path(&patch
->header_old_path
, ctx
) < 0)
412 return parse_err("corrupt old path in git diff header at line %"PRIuZ
,
415 if (parse_advance_ws(ctx
) < 0 ||
416 parse_header_path(&patch
->header_new_path
, ctx
) < 0)
417 return parse_err("corrupt new path in git diff header at line %"PRIuZ
,
420 /* Parse remaining header lines */
421 for (parse_advance_line(ctx
);
423 parse_advance_line(ctx
)) {
427 if (ctx
->line_len
== 0 || ctx
->line
[ctx
->line_len
- 1] != '\n')
430 for (i
= 0; i
< ARRAY_SIZE(header_git_ops
); i
++) {
431 const header_git_op
*op
= &header_git_ops
[i
];
432 size_t op_len
= strlen(op
->str
);
434 if (memcmp(ctx
->line
, op
->str
, min(op_len
, ctx
->line_len
)) != 0)
437 /* Do not advance if this is the patch separator */
441 parse_advance_chars(ctx
, op_len
);
443 if ((error
= op
->fn(patch
, ctx
)) < 0)
446 parse_advance_ws(ctx
);
448 if (parse_advance_expected_str(ctx
, "\n") < 0 ||
450 error
= parse_err("trailing data at line %"PRIuZ
, ctx
->line_num
);
459 error
= parse_err("invalid patch header at line %"PRIuZ
,
469 static int parse_number(git_off_t
*out
, git_patch_parse_ctx
*ctx
)
474 if (!git__isdigit(ctx
->line
[0]))
477 if (git__strntol64(&num
, ctx
->line
, ctx
->line_len
, &end
, 10) < 0)
484 parse_advance_chars(ctx
, (end
- ctx
->line
));
489 static int parse_int(int *out
, git_patch_parse_ctx
*ctx
)
493 if (parse_number(&num
, ctx
) < 0 || !git__is_int(num
))
500 static int parse_hunk_header(
501 git_patch_hunk
*hunk
,
502 git_patch_parse_ctx
*ctx
)
504 const char *header_start
= ctx
->line
;
506 hunk
->hunk
.old_lines
= 1;
507 hunk
->hunk
.new_lines
= 1;
509 if (parse_advance_expected_str(ctx
, "@@ -") < 0 ||
510 parse_int(&hunk
->hunk
.old_start
, ctx
) < 0)
513 if (ctx
->line_len
> 0 && ctx
->line
[0] == ',') {
514 if (parse_advance_expected_str(ctx
, ",") < 0 ||
515 parse_int(&hunk
->hunk
.old_lines
, ctx
) < 0)
519 if (parse_advance_expected_str(ctx
, " +") < 0 ||
520 parse_int(&hunk
->hunk
.new_start
, ctx
) < 0)
523 if (ctx
->line_len
> 0 && ctx
->line
[0] == ',') {
524 if (parse_advance_expected_str(ctx
, ",") < 0 ||
525 parse_int(&hunk
->hunk
.new_lines
, ctx
) < 0)
529 if (parse_advance_expected_str(ctx
, " @@") < 0)
532 parse_advance_line(ctx
);
534 if (!hunk
->hunk
.old_lines
&& !hunk
->hunk
.new_lines
)
537 hunk
->hunk
.header_len
= ctx
->line
- header_start
;
538 if (hunk
->hunk
.header_len
> (GIT_DIFF_HUNK_HEADER_SIZE
- 1))
539 return parse_err("oversized patch hunk header at line %"PRIuZ
,
542 memcpy(hunk
->hunk
.header
, header_start
, hunk
->hunk
.header_len
);
543 hunk
->hunk
.header
[hunk
->hunk
.header_len
] = '\0';
548 giterr_set(GITERR_PATCH
, "invalid patch hunk header at line %"PRIuZ
,
553 static int parse_hunk_body(
554 git_patch_parsed
*patch
,
555 git_patch_hunk
*hunk
,
556 git_patch_parse_ctx
*ctx
)
561 int oldlines
= hunk
->hunk
.old_lines
;
562 int newlines
= hunk
->hunk
.new_lines
;
565 ctx
->remain_len
> 1 &&
566 (oldlines
|| newlines
) &&
567 (ctx
->remain_len
<= 4 || memcmp(ctx
->line
, "@@ -", 4) != 0);
568 parse_advance_line(ctx
)) {
573 if (ctx
->line_len
== 0 || ctx
->line
[ctx
->line_len
- 1] != '\n') {
574 error
= parse_err("invalid patch instruction at line %"PRIuZ
,
579 switch (ctx
->line
[0]) {
584 origin
= GIT_DIFF_LINE_CONTEXT
;
590 origin
= GIT_DIFF_LINE_DELETION
;
595 origin
= GIT_DIFF_LINE_ADDITION
;
600 error
= parse_err("invalid patch hunk at line %"PRIuZ
, ctx
->line_num
);
604 line
= git_array_alloc(patch
->base
.lines
);
605 GITERR_CHECK_ALLOC(line
);
607 memset(line
, 0x0, sizeof(git_diff_line
));
609 line
->content
= ctx
->line
+ prefix
;
610 line
->content_len
= ctx
->line_len
- prefix
;
611 line
->content_offset
= ctx
->content_len
- ctx
->remain_len
;
612 line
->origin
= origin
;
617 if (oldlines
|| newlines
) {
619 "invalid patch hunk, expected %d old lines and %d new lines",
620 hunk
->hunk
.old_lines
, hunk
->hunk
.new_lines
);
624 /* Handle "\ No newline at end of file". Only expect the leading
625 * backslash, though, because the rest of the string could be
626 * localized. Because `diff` optimizes for the case where you
627 * want to apply the patch by hand.
629 if (parse_ctx_contains_s(ctx
, "\\ ") &&
630 git_array_size(patch
->base
.lines
) > 0) {
632 line
= git_array_get(patch
->base
.lines
, git_array_size(patch
->base
.lines
) - 1);
634 if (line
->content_len
< 1) {
635 error
= parse_err("cannot trim trailing newline of empty line");
641 parse_advance_line(ctx
);
648 static int parse_patch_header(
649 git_patch_parsed
*patch
,
650 git_patch_parse_ctx
*ctx
)
654 for (ctx
->line
= ctx
->remain
;
656 parse_advance_line(ctx
)) {
658 /* This line is too short to be a patch header. */
659 if (ctx
->line_len
< 6)
662 /* This might be a hunk header without a patch header, provide a
663 * sensible error message. */
664 if (parse_ctx_contains_s(ctx
, "@@ -")) {
665 size_t line_num
= ctx
->line_num
;
668 /* If this cannot be parsed as a hunk header, it's just leading
671 if (parse_hunk_header(&hunk
, ctx
) < 0) {
676 error
= parse_err("invalid hunk header outside patch at line %"PRIuZ
,
681 /* This buffer is too short to contain a patch. */
682 if (ctx
->remain_len
< ctx
->line_len
+ 6)
685 /* A proper git patch */
686 if (parse_ctx_contains_s(ctx
, "diff --git ")) {
687 error
= parse_header_git(patch
, ctx
);
695 giterr_set(GITERR_PATCH
, "no patch found");
696 error
= GIT_ENOTFOUND
;
702 static int parse_patch_binary_side(
703 git_diff_binary_file
*binary
,
704 git_patch_parse_ctx
*ctx
)
706 git_diff_binary_t type
= GIT_DIFF_BINARY_NONE
;
707 git_buf base85
= GIT_BUF_INIT
, decoded
= GIT_BUF_INIT
;
711 if (parse_ctx_contains_s(ctx
, "literal ")) {
712 type
= GIT_DIFF_BINARY_LITERAL
;
713 parse_advance_chars(ctx
, 8);
714 } else if (parse_ctx_contains_s(ctx
, "delta ")) {
715 type
= GIT_DIFF_BINARY_DELTA
;
716 parse_advance_chars(ctx
, 6);
719 "unknown binary delta type at line %"PRIuZ
, ctx
->line_num
);
723 if (parse_number(&len
, ctx
) < 0 || parse_advance_nl(ctx
) < 0 || len
< 0) {
724 error
= parse_err("invalid binary size at line %"PRIuZ
, ctx
->line_num
);
728 while (ctx
->line_len
) {
729 char c
= ctx
->line
[0];
730 size_t encoded_len
, decoded_len
= 0, decoded_orig
= decoded
.size
;
734 else if (c
>= 'A' && c
<= 'Z')
735 decoded_len
= c
- 'A' + 1;
736 else if (c
>= 'a' && c
<= 'z')
737 decoded_len
= c
- 'a' + (('z' - 'a') + 1) + 1;
740 error
= parse_err("invalid binary length at line %"PRIuZ
, ctx
->line_num
);
744 parse_advance_chars(ctx
, 1);
746 encoded_len
= ((decoded_len
/ 4) + !!(decoded_len
% 4)) * 5;
748 if (encoded_len
> ctx
->line_len
- 1) {
749 error
= parse_err("truncated binary data at line %"PRIuZ
, ctx
->line_num
);
753 if ((error
= git_buf_decode_base85(
754 &decoded
, ctx
->line
, encoded_len
, decoded_len
)) < 0)
757 if (decoded
.size
- decoded_orig
!= decoded_len
) {
758 error
= parse_err("truncated binary data at line %"PRIuZ
, ctx
->line_num
);
762 parse_advance_chars(ctx
, encoded_len
);
764 if (parse_advance_nl(ctx
) < 0) {
765 error
= parse_err("trailing data at line %"PRIuZ
, ctx
->line_num
);
771 binary
->inflatedlen
= (size_t)len
;
772 binary
->datalen
= decoded
.size
;
773 binary
->data
= git_buf_detach(&decoded
);
776 git_buf_free(&base85
);
777 git_buf_free(&decoded
);
781 static int parse_patch_binary(
782 git_patch_parsed
*patch
,
783 git_patch_parse_ctx
*ctx
)
787 if (parse_advance_expected_str(ctx
, "GIT binary patch") < 0 ||
788 parse_advance_nl(ctx
) < 0)
789 return parse_err("corrupt git binary header at line %"PRIuZ
, ctx
->line_num
);
791 /* parse old->new binary diff */
792 if ((error
= parse_patch_binary_side(
793 &patch
->base
.binary
.new_file
, ctx
)) < 0)
796 if (parse_advance_nl(ctx
) < 0)
797 return parse_err("corrupt git binary separator at line %"PRIuZ
,
800 /* parse new->old binary diff */
801 if ((error
= parse_patch_binary_side(
802 &patch
->base
.binary
.old_file
, ctx
)) < 0)
805 if (parse_advance_nl(ctx
) < 0)
806 return parse_err("corrupt git binary patch separator at line %"PRIuZ
,
809 patch
->base
.binary
.contains_data
= 1;
810 patch
->base
.delta
->flags
|= GIT_DIFF_FLAG_BINARY
;
814 static int parse_patch_binary_nodata(
815 git_patch_parsed
*patch
,
816 git_patch_parse_ctx
*ctx
)
818 if (parse_advance_expected_str(ctx
, "Binary files ") < 0 ||
819 parse_advance_expected_str(ctx
, patch
->header_old_path
) < 0 ||
820 parse_advance_expected_str(ctx
, " and ") < 0 ||
821 parse_advance_expected_str(ctx
, patch
->header_new_path
) < 0 ||
822 parse_advance_expected_str(ctx
, " differ") < 0 ||
823 parse_advance_nl(ctx
) < 0)
824 return parse_err("corrupt git binary header at line %"PRIuZ
, ctx
->line_num
);
826 patch
->base
.binary
.contains_data
= 0;
827 patch
->base
.delta
->flags
|= GIT_DIFF_FLAG_BINARY
;
831 static int parse_patch_hunks(
832 git_patch_parsed
*patch
,
833 git_patch_parse_ctx
*ctx
)
835 git_patch_hunk
*hunk
;
838 while (parse_ctx_contains_s(ctx
, "@@ -")) {
839 hunk
= git_array_alloc(patch
->base
.hunks
);
840 GITERR_CHECK_ALLOC(hunk
);
842 memset(hunk
, 0, sizeof(git_patch_hunk
));
844 hunk
->line_start
= git_array_size(patch
->base
.lines
);
845 hunk
->line_count
= 0;
847 if ((error
= parse_hunk_header(hunk
, ctx
)) < 0 ||
848 (error
= parse_hunk_body(patch
, hunk
, ctx
)) < 0)
852 patch
->base
.delta
->flags
|= GIT_DIFF_FLAG_NOT_BINARY
;
858 static int parse_patch_body(
859 git_patch_parsed
*patch
, git_patch_parse_ctx
*ctx
)
861 if (parse_ctx_contains_s(ctx
, "GIT binary patch"))
862 return parse_patch_binary(patch
, ctx
);
863 else if (parse_ctx_contains_s(ctx
, "Binary files "))
864 return parse_patch_binary_nodata(patch
, ctx
);
866 return parse_patch_hunks(patch
, ctx
);
869 int check_header_names(
872 const char *old_or_new
,
878 if (two_null
&& strcmp(two
, "/dev/null") != 0)
879 return parse_err("expected %s path of '/dev/null'", old_or_new
);
881 else if (!two_null
&& strcmp(one
, two
) != 0)
882 return parse_err("mismatched %s path names", old_or_new
);
887 static int check_prefix(
890 git_patch_parsed
*patch
,
891 const char *path_start
)
893 const char *path
= path_start
;
894 size_t prefix_len
= patch
->ctx
->opts
.prefix_len
;
895 size_t remain_len
= prefix_len
;
903 /* leading slashes do not count as part of the prefix in git apply */
907 while (*path
&& remain_len
) {
914 if (remain_len
|| !*path
)
916 "header filename does not contain %"PRIuZ
" path components",
920 *out_len
= (path
- path_start
);
921 *out
= git__strndup(path_start
, *out_len
);
923 return (*out
== NULL
) ? -1 : 0;
926 static int check_filenames(git_patch_parsed
*patch
)
928 const char *prefixed_new
, *prefixed_old
;
929 size_t old_prefixlen
= 0, new_prefixlen
= 0;
930 bool added
= (patch
->base
.delta
->status
== GIT_DELTA_ADDED
);
931 bool deleted
= (patch
->base
.delta
->status
== GIT_DELTA_DELETED
);
933 if (patch
->old_path
&& !patch
->new_path
)
934 return parse_err("missing new path");
936 if (!patch
->old_path
&& patch
->new_path
)
937 return parse_err("missing old path");
939 /* Ensure (non-renamed) paths match */
940 if (check_header_names(
941 patch
->header_old_path
, patch
->old_path
, "old", added
) < 0 ||
943 patch
->header_new_path
, patch
->new_path
, "new", deleted
) < 0)
946 prefixed_old
= (!added
&& patch
->old_path
) ? patch
->old_path
:
947 patch
->header_old_path
;
948 prefixed_new
= (!deleted
&& patch
->new_path
) ? patch
->new_path
:
949 patch
->header_new_path
;
952 &patch
->old_prefix
, &old_prefixlen
, patch
, prefixed_old
) < 0 ||
954 &patch
->new_prefix
, &new_prefixlen
, patch
, prefixed_new
) < 0)
957 /* Prefer the rename filenames as they are unambiguous and unprefixed */
958 if (patch
->rename_old_path
)
959 patch
->base
.delta
->old_file
.path
= patch
->rename_old_path
;
961 patch
->base
.delta
->old_file
.path
= prefixed_old
+ old_prefixlen
;
963 if (patch
->rename_new_path
)
964 patch
->base
.delta
->new_file
.path
= patch
->rename_new_path
;
966 patch
->base
.delta
->new_file
.path
= prefixed_new
+ new_prefixlen
;
968 if (!patch
->base
.delta
->old_file
.path
&&
969 !patch
->base
.delta
->new_file
.path
)
970 return parse_err("git diff header lacks old / new paths");
975 static int check_patch(git_patch_parsed
*patch
)
977 git_diff_delta
*delta
= patch
->base
.delta
;
979 if (check_filenames(patch
) < 0)
982 if (delta
->old_file
.path
&&
983 delta
->status
!= GIT_DELTA_DELETED
&&
984 !delta
->new_file
.mode
)
985 delta
->new_file
.mode
= delta
->old_file
.mode
;
987 if (delta
->status
== GIT_DELTA_MODIFIED
&&
988 !(delta
->flags
& GIT_DIFF_FLAG_BINARY
) &&
989 delta
->new_file
.mode
== delta
->old_file
.mode
&&
990 git_array_size(patch
->base
.hunks
) == 0)
991 return parse_err("patch with no hunks");
993 if (delta
->status
== GIT_DELTA_ADDED
) {
994 memset(&delta
->old_file
.id
, 0x0, sizeof(git_oid
));
995 delta
->old_file
.id_abbrev
= 0;
998 if (delta
->status
== GIT_DELTA_DELETED
) {
999 memset(&delta
->new_file
.id
, 0x0, sizeof(git_oid
));
1000 delta
->new_file
.id_abbrev
= 0;
1006 git_patch_parse_ctx
*git_patch_parse_ctx_init(
1007 const char *content
,
1009 const git_patch_options
*opts
)
1011 git_patch_parse_ctx
*ctx
;
1012 git_patch_options default_opts
= GIT_PATCH_OPTIONS_INIT
;
1014 if ((ctx
= git__calloc(1, sizeof(git_patch_parse_ctx
))) == NULL
)
1018 if ((ctx
->content
= git__malloc(content_len
)) == NULL
) {
1023 memcpy((char *)ctx
->content
, content
, content_len
);
1026 ctx
->content_len
= content_len
;
1027 ctx
->remain
= ctx
->content
;
1028 ctx
->remain_len
= ctx
->content_len
;
1031 memcpy(&ctx
->opts
, opts
, sizeof(git_patch_options
));
1033 memcpy(&ctx
->opts
, &default_opts
, sizeof(git_patch_options
));
1035 GIT_REFCOUNT_INC(ctx
);
1039 static void patch_parse_ctx_free(git_patch_parse_ctx
*ctx
)
1044 git__free((char *)ctx
->content
);
1048 void git_patch_parse_ctx_free(git_patch_parse_ctx
*ctx
)
1050 GIT_REFCOUNT_DEC(ctx
, patch_parse_ctx_free
);
1053 int git_patch_parsed_from_diff(git_patch
**out
, git_diff
*d
, size_t idx
)
1055 git_diff_parsed
*diff
= (git_diff_parsed
*)d
;
1058 if ((p
= git_vector_get(&diff
->patches
, idx
)) == NULL
)
1061 GIT_REFCOUNT_INC(p
);
1067 static void patch_parsed__free(git_patch
*p
)
1069 git_patch_parsed
*patch
= (git_patch_parsed
*)p
;
1074 git_patch_parse_ctx_free(patch
->ctx
);
1076 git__free((char *)patch
->base
.binary
.old_file
.data
);
1077 git__free((char *)patch
->base
.binary
.new_file
.data
);
1078 git_array_clear(patch
->base
.hunks
);
1079 git_array_clear(patch
->base
.lines
);
1080 git__free(patch
->base
.delta
);
1082 git__free(patch
->old_prefix
);
1083 git__free(patch
->new_prefix
);
1084 git__free(patch
->header_old_path
);
1085 git__free(patch
->header_new_path
);
1086 git__free(patch
->rename_old_path
);
1087 git__free(patch
->rename_new_path
);
1088 git__free(patch
->old_path
);
1089 git__free(patch
->new_path
);
1093 int git_patch_parse(
1095 git_patch_parse_ctx
*ctx
)
1097 git_patch_parsed
*patch
;
1105 patch
= git__calloc(1, sizeof(git_patch_parsed
));
1106 GITERR_CHECK_ALLOC(patch
);
1109 GIT_REFCOUNT_INC(patch
->ctx
);
1111 patch
->base
.free_fn
= patch_parsed__free
;
1113 patch
->base
.delta
= git__calloc(1, sizeof(git_diff_delta
));
1114 GITERR_CHECK_ALLOC(patch
->base
.delta
);
1116 patch
->base
.delta
->status
= GIT_DELTA_MODIFIED
;
1117 patch
->base
.delta
->nfiles
= 2;
1119 start
= ctx
->remain_len
;
1121 if ((error
= parse_patch_header(patch
, ctx
)) < 0 ||
1122 (error
= parse_patch_body(patch
, ctx
)) < 0 ||
1123 (error
= check_patch(patch
)) < 0)
1126 used
= start
- ctx
->remain_len
;
1127 ctx
->remain
+= used
;
1129 patch
->base
.diff_opts
.old_prefix
= patch
->old_prefix
;
1130 patch
->base
.diff_opts
.new_prefix
= patch
->new_prefix
;
1131 patch
->base
.diff_opts
.flags
|= GIT_DIFF_SHOW_BINARY
;
1133 GIT_REFCOUNT_INC(patch
);
1134 *out
= &patch
->base
;
1138 patch_parsed__free(&patch
->base
);
1143 int git_patch_from_buffer(
1145 const char *content
,
1147 const git_patch_options
*opts
)
1149 git_patch_parse_ctx
*ctx
;
1152 ctx
= git_patch_parse_ctx_init(content
, content_len
, opts
);
1153 GITERR_CHECK_ALLOC(ctx
);
1155 error
= git_patch_parse(out
, ctx
);
1157 git_patch_parse_ctx_free(ctx
);