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_s(ctx, str) \
78 parse_advance_expected(ctx, str, sizeof(str) - 1)
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 %d", 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 %d",
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_s(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_s(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 %d",
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 %d",
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 { "--- ", parse_header_git_oldpath
},
383 { "+++ ", parse_header_git_newpath
},
384 { "index ", parse_header_git_index
},
385 { "old mode ", parse_header_git_oldmode
},
386 { "new mode ", parse_header_git_newmode
},
387 { "deleted file mode ", parse_header_git_deletedfilemode
},
388 { "new file mode ", parse_header_git_newfilemode
},
389 { "rename from ", parse_header_renamefrom
},
390 { "rename to ", parse_header_renameto
},
391 { "rename old ", parse_header_renamefrom
},
392 { "rename new ", parse_header_renameto
},
393 { "copy from ", parse_header_copyfrom
},
394 { "copy to ", parse_header_copyto
},
395 { "similarity index ", parse_header_similarity
},
396 { "dissimilarity index ", parse_header_dissimilarity
},
399 static int parse_header_git(
400 git_patch_parsed
*patch
,
401 git_patch_parse_ctx
*ctx
)
406 /* Parse the diff --git line */
407 if (parse_advance_expected_s(ctx
, "diff --git ") < 0)
408 return parse_err("corrupt git diff header at line %d", ctx
->line_num
);
410 if (parse_header_path(&patch
->header_old_path
, ctx
) < 0)
411 return parse_err("corrupt old path in git diff header at line %d",
414 if (parse_advance_ws(ctx
) < 0 ||
415 parse_header_path(&patch
->header_new_path
, ctx
) < 0)
416 return parse_err("corrupt new path in git diff header at line %d",
419 /* Parse remaining header lines */
420 for (parse_advance_line(ctx
);
422 parse_advance_line(ctx
)) {
426 if (ctx
->line_len
== 0 || ctx
->line
[ctx
->line_len
- 1] != '\n')
429 for (i
= 0; i
< ARRAY_SIZE(header_git_ops
); i
++) {
430 const header_git_op
*op
= &header_git_ops
[i
];
431 size_t op_len
= strlen(op
->str
);
433 if (memcmp(ctx
->line
, op
->str
, min(op_len
, ctx
->line_len
)) != 0)
436 /* Do not advance if this is the patch separator */
440 parse_advance_chars(ctx
, op_len
);
442 if ((error
= op
->fn(patch
, ctx
)) < 0)
445 parse_advance_ws(ctx
);
446 parse_advance_expected_s(ctx
, "\n");
448 if (ctx
->line_len
> 0) {
449 error
= parse_err("trailing data at line %d", ctx
->line_num
);
458 error
= parse_err("invalid patch header at line %d",
468 static int parse_number(git_off_t
*out
, git_patch_parse_ctx
*ctx
)
473 if (!git__isdigit(ctx
->line
[0]))
476 if (git__strntol64(&num
, ctx
->line
, ctx
->line_len
, &end
, 10) < 0)
483 parse_advance_chars(ctx
, (end
- ctx
->line
));
488 static int parse_int(int *out
, git_patch_parse_ctx
*ctx
)
492 if (parse_number(&num
, ctx
) < 0 || !git__is_int(num
))
499 static int parse_hunk_header(
500 git_patch_hunk
*hunk
,
501 git_patch_parse_ctx
*ctx
)
503 const char *header_start
= ctx
->line
;
505 hunk
->hunk
.old_lines
= 1;
506 hunk
->hunk
.new_lines
= 1;
508 if (parse_advance_expected_s(ctx
, "@@ -") < 0 ||
509 parse_int(&hunk
->hunk
.old_start
, ctx
) < 0)
512 if (ctx
->line_len
> 0 && ctx
->line
[0] == ',') {
513 if (parse_advance_expected_s(ctx
, ",") < 0 ||
514 parse_int(&hunk
->hunk
.old_lines
, ctx
) < 0)
518 if (parse_advance_expected_s(ctx
, " +") < 0 ||
519 parse_int(&hunk
->hunk
.new_start
, ctx
) < 0)
522 if (ctx
->line_len
> 0 && ctx
->line
[0] == ',') {
523 if (parse_advance_expected_s(ctx
, ",") < 0 ||
524 parse_int(&hunk
->hunk
.new_lines
, ctx
) < 0)
528 if (parse_advance_expected_s(ctx
, " @@") < 0)
531 parse_advance_line(ctx
);
533 if (!hunk
->hunk
.old_lines
&& !hunk
->hunk
.new_lines
)
536 hunk
->hunk
.header_len
= ctx
->line
- header_start
;
537 if (hunk
->hunk
.header_len
> (GIT_DIFF_HUNK_HEADER_SIZE
- 1))
538 return parse_err("oversized patch hunk header at line %d",
541 memcpy(hunk
->hunk
.header
, header_start
, hunk
->hunk
.header_len
);
542 hunk
->hunk
.header
[hunk
->hunk
.header_len
] = '\0';
547 giterr_set(GITERR_PATCH
, "invalid patch hunk header at line %d",
552 static int parse_hunk_body(
553 git_patch_parsed
*patch
,
554 git_patch_hunk
*hunk
,
555 git_patch_parse_ctx
*ctx
)
560 int oldlines
= hunk
->hunk
.old_lines
;
561 int newlines
= hunk
->hunk
.new_lines
;
564 ctx
->remain_len
> 4 && (oldlines
|| newlines
) &&
565 memcmp(ctx
->line
, "@@ -", 4) != 0;
566 parse_advance_line(ctx
)) {
571 if (ctx
->line_len
== 0 || ctx
->line
[ctx
->line_len
- 1] != '\n') {
572 error
= parse_err("invalid patch instruction at line %d",
577 switch (ctx
->line
[0]) {
582 origin
= GIT_DIFF_LINE_CONTEXT
;
588 origin
= GIT_DIFF_LINE_DELETION
;
593 origin
= GIT_DIFF_LINE_ADDITION
;
598 error
= parse_err("invalid patch hunk at line %d", ctx
->line_num
);
602 line
= git_array_alloc(patch
->base
.lines
);
603 GITERR_CHECK_ALLOC(line
);
605 memset(line
, 0x0, sizeof(git_diff_line
));
607 line
->content
= ctx
->line
+ prefix
;
608 line
->content_len
= ctx
->line_len
- prefix
;
609 line
->content_offset
= ctx
->content_len
- ctx
->remain_len
;
610 line
->origin
= origin
;
615 if (oldlines
|| newlines
) {
617 "invalid patch hunk, expected %d old lines and %d new lines",
618 hunk
->hunk
.old_lines
, hunk
->hunk
.new_lines
);
622 /* Handle "\ No newline at end of file". Only expect the leading
623 * backslash, though, because the rest of the string could be
624 * localized. Because `diff` optimizes for the case where you
625 * want to apply the patch by hand.
627 if (parse_ctx_contains_s(ctx
, "\\ ") &&
628 git_array_size(patch
->base
.lines
) > 0) {
630 line
= git_array_get(patch
->base
.lines
, git_array_size(patch
->base
.lines
) - 1);
632 if (line
->content_len
< 1) {
633 error
= parse_err("cannot trim trailing newline of empty line");
639 parse_advance_line(ctx
);
646 static int parse_patch_header(
647 git_patch_parsed
*patch
,
648 git_patch_parse_ctx
*ctx
)
652 for (ctx
->line
= ctx
->remain
;
654 parse_advance_line(ctx
)) {
656 /* This line is too short to be a patch header. */
657 if (ctx
->line_len
< 6)
660 /* This might be a hunk header without a patch header, provide a
661 * sensible error message. */
662 if (parse_ctx_contains_s(ctx
, "@@ -")) {
663 size_t line_num
= ctx
->line_num
;
666 /* If this cannot be parsed as a hunk header, it's just leading
669 if (parse_hunk_header(&hunk
, ctx
) < 0) {
674 error
= parse_err("invalid hunk header outside patch at line %d",
679 /* This buffer is too short to contain a patch. */
680 if (ctx
->remain_len
< ctx
->line_len
+ 6)
683 /* A proper git patch */
684 if (parse_ctx_contains_s(ctx
, "diff --git ")) {
685 error
= parse_header_git(patch
, ctx
);
693 giterr_set(GITERR_PATCH
, "no patch found");
694 error
= GIT_ENOTFOUND
;
700 static int parse_patch_binary_side(
701 git_diff_binary_file
*binary
,
702 git_patch_parse_ctx
*ctx
)
704 git_diff_binary_t type
= GIT_DIFF_BINARY_NONE
;
705 git_buf base85
= GIT_BUF_INIT
, decoded
= GIT_BUF_INIT
;
709 if (parse_ctx_contains_s(ctx
, "literal ")) {
710 type
= GIT_DIFF_BINARY_LITERAL
;
711 parse_advance_chars(ctx
, 8);
712 } else if (parse_ctx_contains_s(ctx
, "delta ")) {
713 type
= GIT_DIFF_BINARY_DELTA
;
714 parse_advance_chars(ctx
, 6);
717 "unknown binary delta type at line %d", ctx
->line_num
);
721 if (parse_number(&len
, ctx
) < 0 || parse_advance_nl(ctx
) < 0 || len
< 0) {
722 error
= parse_err("invalid binary size at line %d", ctx
->line_num
);
726 while (ctx
->line_len
) {
727 char c
= ctx
->line
[0];
728 size_t encoded_len
, decoded_len
= 0, decoded_orig
= decoded
.size
;
732 else if (c
>= 'A' && c
<= 'Z')
733 decoded_len
= c
- 'A' + 1;
734 else if (c
>= 'a' && c
<= 'z')
735 decoded_len
= c
- 'a' + (('z' - 'a') + 1) + 1;
738 error
= parse_err("invalid binary length at line %d", ctx
->line_num
);
742 parse_advance_chars(ctx
, 1);
744 encoded_len
= ((decoded_len
/ 4) + !!(decoded_len
% 4)) * 5;
746 if (encoded_len
> ctx
->line_len
- 1) {
747 error
= parse_err("truncated binary data at line %d", ctx
->line_num
);
751 if ((error
= git_buf_decode_base85(
752 &decoded
, ctx
->line
, encoded_len
, decoded_len
)) < 0)
755 if (decoded
.size
- decoded_orig
!= decoded_len
) {
756 error
= parse_err("truncated binary data at line %d", ctx
->line_num
);
760 parse_advance_chars(ctx
, encoded_len
);
762 if (parse_advance_nl(ctx
) < 0) {
763 error
= parse_err("trailing data at line %d", ctx
->line_num
);
769 binary
->inflatedlen
= (size_t)len
;
770 binary
->datalen
= decoded
.size
;
771 binary
->data
= git_buf_detach(&decoded
);
774 git_buf_free(&base85
);
775 git_buf_free(&decoded
);
779 static int parse_patch_binary(
780 git_patch_parsed
*patch
,
781 git_patch_parse_ctx
*ctx
)
785 if (parse_advance_expected_s(ctx
, "GIT binary patch") < 0 ||
786 parse_advance_nl(ctx
) < 0)
787 return parse_err("corrupt git binary header at line %d", ctx
->line_num
);
789 /* parse old->new binary diff */
790 if ((error
= parse_patch_binary_side(
791 &patch
->base
.binary
.new_file
, ctx
)) < 0)
794 if (parse_advance_nl(ctx
) < 0)
795 return parse_err("corrupt git binary separator at line %d",
798 /* parse new->old binary diff */
799 if ((error
= parse_patch_binary_side(
800 &patch
->base
.binary
.old_file
, ctx
)) < 0)
803 if (parse_advance_nl(ctx
) < 0)
804 return parse_err("corrupt git binary patch separator at line %d",
807 patch
->base
.delta
->flags
|= GIT_DIFF_FLAG_BINARY
;
811 static int parse_patch_hunks(
812 git_patch_parsed
*patch
,
813 git_patch_parse_ctx
*ctx
)
815 git_patch_hunk
*hunk
;
818 while (parse_ctx_contains_s(ctx
, "@@ -")) {
819 hunk
= git_array_alloc(patch
->base
.hunks
);
820 GITERR_CHECK_ALLOC(hunk
);
822 memset(hunk
, 0, sizeof(git_patch_hunk
));
824 hunk
->line_start
= git_array_size(patch
->base
.lines
);
825 hunk
->line_count
= 0;
827 if ((error
= parse_hunk_header(hunk
, ctx
)) < 0 ||
828 (error
= parse_hunk_body(patch
, hunk
, ctx
)) < 0)
832 patch
->base
.delta
->flags
|= GIT_DIFF_FLAG_NOT_BINARY
;
838 static int parse_patch_body(
839 git_patch_parsed
*patch
, git_patch_parse_ctx
*ctx
)
841 if (parse_ctx_contains_s(ctx
, "GIT binary patch"))
842 return parse_patch_binary(patch
, ctx
);
844 return parse_patch_hunks(patch
, ctx
);
847 int check_header_names(
850 const char *old_or_new
,
856 if (two_null
&& strcmp(two
, "/dev/null") != 0)
857 return parse_err("expected %s path of '/dev/null'", old_or_new
);
859 else if (!two_null
&& strcmp(one
, two
) != 0)
860 return parse_err("mismatched %s path names", old_or_new
);
865 static int check_prefix(
868 git_patch_parsed
*patch
,
869 const char *path_start
)
871 const char *path
= path_start
;
872 size_t prefix_len
= patch
->ctx
->opts
.prefix_len
;
873 size_t remain_len
= prefix_len
;
881 /* leading slashes do not count as part of the prefix in git apply */
885 while (*path
&& remain_len
) {
892 if (remain_len
|| !*path
)
894 "header filename does not contain %d path components",
898 *out_len
= (path
- path_start
);
899 *out
= git__strndup(path_start
, *out_len
);
901 return (*out
== NULL
) ? -1 : 0;
904 static int check_filenames(git_patch_parsed
*patch
)
906 const char *prefixed_new
, *prefixed_old
;
907 size_t old_prefixlen
= 0, new_prefixlen
= 0;
908 bool added
= (patch
->base
.delta
->status
== GIT_DELTA_ADDED
);
909 bool deleted
= (patch
->base
.delta
->status
== GIT_DELTA_DELETED
);
911 if (patch
->old_path
&& !patch
->new_path
)
912 return parse_err("missing new path");
914 if (!patch
->old_path
&& patch
->new_path
)
915 return parse_err("missing old path");
917 /* Ensure (non-renamed) paths match */
918 if (check_header_names(
919 patch
->header_old_path
, patch
->old_path
, "old", added
) < 0 ||
921 patch
->header_new_path
, patch
->new_path
, "new", deleted
) < 0)
924 prefixed_old
= (!added
&& patch
->old_path
) ? patch
->old_path
:
925 patch
->header_old_path
;
926 prefixed_new
= (!deleted
&& patch
->new_path
) ? patch
->new_path
:
927 patch
->header_new_path
;
930 &patch
->old_prefix
, &old_prefixlen
, patch
, prefixed_old
) < 0 ||
932 &patch
->new_prefix
, &new_prefixlen
, patch
, prefixed_new
) < 0)
935 /* Prefer the rename filenames as they are unambiguous and unprefixed */
936 if (patch
->rename_old_path
)
937 patch
->base
.delta
->old_file
.path
= patch
->rename_old_path
;
939 patch
->base
.delta
->old_file
.path
= prefixed_old
+ old_prefixlen
;
941 if (patch
->rename_new_path
)
942 patch
->base
.delta
->new_file
.path
= patch
->rename_new_path
;
944 patch
->base
.delta
->new_file
.path
= prefixed_new
+ new_prefixlen
;
946 if (!patch
->base
.delta
->old_file
.path
&&
947 !patch
->base
.delta
->new_file
.path
)
948 return parse_err("git diff header lacks old / new paths");
953 static int check_patch(git_patch_parsed
*patch
)
955 git_diff_delta
*delta
= patch
->base
.delta
;
957 if (check_filenames(patch
) < 0)
960 if (delta
->old_file
.path
&&
961 delta
->status
!= GIT_DELTA_DELETED
&&
962 !delta
->new_file
.mode
)
963 delta
->new_file
.mode
= delta
->old_file
.mode
;
965 if (delta
->status
== GIT_DELTA_MODIFIED
&&
966 !(delta
->flags
& GIT_DIFF_FLAG_BINARY
) &&
967 delta
->new_file
.mode
== delta
->old_file
.mode
&&
968 git_array_size(patch
->base
.hunks
) == 0)
969 return parse_err("patch with no hunks");
971 if (delta
->status
== GIT_DELTA_ADDED
) {
972 memset(&delta
->old_file
.id
, 0x0, sizeof(git_oid
));
973 delta
->old_file
.id_abbrev
= 0;
976 if (delta
->status
== GIT_DELTA_DELETED
) {
977 memset(&delta
->new_file
.id
, 0x0, sizeof(git_oid
));
978 delta
->new_file
.id_abbrev
= 0;
984 git_patch_parse_ctx
*git_patch_parse_ctx_init(
987 const git_patch_options
*opts
)
989 git_patch_parse_ctx
*ctx
;
990 git_patch_options default_opts
= GIT_PATCH_OPTIONS_INIT
;
992 if ((ctx
= git__calloc(1, sizeof(git_patch_parse_ctx
))) == NULL
)
996 if ((ctx
->content
= git__malloc(content_len
)) == NULL
)
999 memcpy((char *)ctx
->content
, content
, content_len
);
1002 ctx
->content_len
= content_len
;
1003 ctx
->remain
= ctx
->content
;
1004 ctx
->remain_len
= ctx
->content_len
;
1007 memcpy(&ctx
->opts
, opts
, sizeof(git_patch_options
));
1009 memcpy(&ctx
->opts
, &default_opts
, sizeof(git_patch_options
));
1011 GIT_REFCOUNT_INC(ctx
);
1015 static void patch_parse_ctx_free(git_patch_parse_ctx
*ctx
)
1020 git__free((char *)ctx
->content
);
1024 void git_patch_parse_ctx_free(git_patch_parse_ctx
*ctx
)
1026 GIT_REFCOUNT_DEC(ctx
, patch_parse_ctx_free
);
1029 int git_patch_parsed_from_diff(git_patch
**out
, git_diff
*d
, size_t idx
)
1031 git_diff_parsed
*diff
= (git_diff_parsed
*)d
;
1034 if ((p
= git_vector_get(&diff
->patches
, idx
)) == NULL
)
1037 GIT_REFCOUNT_INC(p
);
1043 static void patch_parsed__free(git_patch
*p
)
1045 git_patch_parsed
*patch
= (git_patch_parsed
*)p
;
1050 git_patch_parse_ctx_free(patch
->ctx
);
1052 git__free((char *)patch
->base
.binary
.old_file
.data
);
1053 git__free((char *)patch
->base
.binary
.new_file
.data
);
1054 git_array_clear(patch
->base
.hunks
);
1055 git_array_clear(patch
->base
.lines
);
1056 git__free(patch
->base
.delta
);
1058 git__free(patch
->old_prefix
);
1059 git__free(patch
->new_prefix
);
1060 git__free(patch
->header_old_path
);
1061 git__free(patch
->header_new_path
);
1062 git__free(patch
->rename_old_path
);
1063 git__free(patch
->rename_new_path
);
1064 git__free(patch
->old_path
);
1065 git__free(patch
->new_path
);
1069 int git_patch_parse(
1071 git_patch_parse_ctx
*ctx
)
1073 git_patch_parsed
*patch
;
1081 patch
= git__calloc(1, sizeof(git_patch_parsed
));
1082 GITERR_CHECK_ALLOC(patch
);
1085 GIT_REFCOUNT_INC(patch
->ctx
);
1087 patch
->base
.free_fn
= patch_parsed__free
;
1089 patch
->base
.delta
= git__calloc(1, sizeof(git_diff_delta
));
1090 GITERR_CHECK_ALLOC(patch
->base
.delta
);
1092 patch
->base
.delta
->status
= GIT_DELTA_MODIFIED
;
1093 patch
->base
.delta
->nfiles
= 2;
1095 start
= ctx
->remain_len
;
1097 if ((error
= parse_patch_header(patch
, ctx
)) < 0 ||
1098 (error
= parse_patch_body(patch
, ctx
)) < 0 ||
1099 (error
= check_patch(patch
)) < 0)
1102 used
= start
- ctx
->remain_len
;
1103 ctx
->remain
+= used
;
1105 patch
->base
.diff_opts
.old_prefix
= patch
->old_prefix
;
1106 patch
->base
.diff_opts
.new_prefix
= patch
->new_prefix
;
1107 patch
->base
.diff_opts
.flags
|= GIT_DIFF_SHOW_BINARY
;
1109 GIT_REFCOUNT_INC(patch
);
1110 *out
= &patch
->base
;
1114 patch_parsed__free(&patch
->base
);
1119 int git_patch_from_buffer(
1121 const char *content
,
1123 const git_patch_options
*opts
)
1125 git_patch_parse_ctx
*ctx
;
1128 ctx
= git_patch_parse_ctx_init(content
, content_len
, opts
);
1129 GITERR_CHECK_ALLOC(ctx
);
1131 error
= git_patch_parse(out
, ctx
);
1133 git_patch_parse_ctx_free(ctx
);