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
);
447 parse_advance_expected_str(ctx
, "\n");
449 if (ctx
->line_len
> 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
> 4 && (oldlines
|| newlines
) &&
566 memcmp(ctx
->line
, "@@ -", 4) != 0;
567 parse_advance_line(ctx
)) {
572 if (ctx
->line_len
== 0 || ctx
->line
[ctx
->line_len
- 1] != '\n') {
573 error
= parse_err("invalid patch instruction at line %"PRIuZ
,
578 switch (ctx
->line
[0]) {
583 origin
= GIT_DIFF_LINE_CONTEXT
;
589 origin
= GIT_DIFF_LINE_DELETION
;
594 origin
= GIT_DIFF_LINE_ADDITION
;
599 error
= parse_err("invalid patch hunk at line %"PRIuZ
, ctx
->line_num
);
603 line
= git_array_alloc(patch
->base
.lines
);
604 GITERR_CHECK_ALLOC(line
);
606 memset(line
, 0x0, sizeof(git_diff_line
));
608 line
->content
= ctx
->line
+ prefix
;
609 line
->content_len
= ctx
->line_len
- prefix
;
610 line
->content_offset
= ctx
->content_len
- ctx
->remain_len
;
611 line
->origin
= origin
;
616 if (oldlines
|| newlines
) {
618 "invalid patch hunk, expected %d old lines and %d new lines",
619 hunk
->hunk
.old_lines
, hunk
->hunk
.new_lines
);
623 /* Handle "\ No newline at end of file". Only expect the leading
624 * backslash, though, because the rest of the string could be
625 * localized. Because `diff` optimizes for the case where you
626 * want to apply the patch by hand.
628 if (parse_ctx_contains_s(ctx
, "\\ ") &&
629 git_array_size(patch
->base
.lines
) > 0) {
631 line
= git_array_get(patch
->base
.lines
, git_array_size(patch
->base
.lines
) - 1);
633 if (line
->content_len
< 1) {
634 error
= parse_err("cannot trim trailing newline of empty line");
640 parse_advance_line(ctx
);
647 static int parse_patch_header(
648 git_patch_parsed
*patch
,
649 git_patch_parse_ctx
*ctx
)
653 for (ctx
->line
= ctx
->remain
;
655 parse_advance_line(ctx
)) {
657 /* This line is too short to be a patch header. */
658 if (ctx
->line_len
< 6)
661 /* This might be a hunk header without a patch header, provide a
662 * sensible error message. */
663 if (parse_ctx_contains_s(ctx
, "@@ -")) {
664 size_t line_num
= ctx
->line_num
;
667 /* If this cannot be parsed as a hunk header, it's just leading
670 if (parse_hunk_header(&hunk
, ctx
) < 0) {
675 error
= parse_err("invalid hunk header outside patch at line %"PRIuZ
,
680 /* This buffer is too short to contain a patch. */
681 if (ctx
->remain_len
< ctx
->line_len
+ 6)
684 /* A proper git patch */
685 if (parse_ctx_contains_s(ctx
, "diff --git ")) {
686 error
= parse_header_git(patch
, ctx
);
694 giterr_set(GITERR_PATCH
, "no patch found");
695 error
= GIT_ENOTFOUND
;
701 static int parse_patch_binary_side(
702 git_diff_binary_file
*binary
,
703 git_patch_parse_ctx
*ctx
)
705 git_diff_binary_t type
= GIT_DIFF_BINARY_NONE
;
706 git_buf base85
= GIT_BUF_INIT
, decoded
= GIT_BUF_INIT
;
710 if (parse_ctx_contains_s(ctx
, "literal ")) {
711 type
= GIT_DIFF_BINARY_LITERAL
;
712 parse_advance_chars(ctx
, 8);
713 } else if (parse_ctx_contains_s(ctx
, "delta ")) {
714 type
= GIT_DIFF_BINARY_DELTA
;
715 parse_advance_chars(ctx
, 6);
718 "unknown binary delta type at line %"PRIuZ
, ctx
->line_num
);
722 if (parse_number(&len
, ctx
) < 0 || parse_advance_nl(ctx
) < 0 || len
< 0) {
723 error
= parse_err("invalid binary size at line %"PRIuZ
, ctx
->line_num
);
727 while (ctx
->line_len
) {
728 char c
= ctx
->line
[0];
729 size_t encoded_len
, decoded_len
= 0, decoded_orig
= decoded
.size
;
733 else if (c
>= 'A' && c
<= 'Z')
734 decoded_len
= c
- 'A' + 1;
735 else if (c
>= 'a' && c
<= 'z')
736 decoded_len
= c
- 'a' + (('z' - 'a') + 1) + 1;
739 error
= parse_err("invalid binary length at line %"PRIuZ
, ctx
->line_num
);
743 parse_advance_chars(ctx
, 1);
745 encoded_len
= ((decoded_len
/ 4) + !!(decoded_len
% 4)) * 5;
747 if (encoded_len
> ctx
->line_len
- 1) {
748 error
= parse_err("truncated binary data at line %"PRIuZ
, ctx
->line_num
);
752 if ((error
= git_buf_decode_base85(
753 &decoded
, ctx
->line
, encoded_len
, decoded_len
)) < 0)
756 if (decoded
.size
- decoded_orig
!= decoded_len
) {
757 error
= parse_err("truncated binary data at line %"PRIuZ
, ctx
->line_num
);
761 parse_advance_chars(ctx
, encoded_len
);
763 if (parse_advance_nl(ctx
) < 0) {
764 error
= parse_err("trailing data at line %"PRIuZ
, ctx
->line_num
);
770 binary
->inflatedlen
= (size_t)len
;
771 binary
->datalen
= decoded
.size
;
772 binary
->data
= git_buf_detach(&decoded
);
775 git_buf_free(&base85
);
776 git_buf_free(&decoded
);
780 static int parse_patch_binary(
781 git_patch_parsed
*patch
,
782 git_patch_parse_ctx
*ctx
)
786 if (parse_advance_expected_str(ctx
, "GIT binary patch") < 0 ||
787 parse_advance_nl(ctx
) < 0)
788 return parse_err("corrupt git binary header at line %"PRIuZ
, ctx
->line_num
);
790 /* parse old->new binary diff */
791 if ((error
= parse_patch_binary_side(
792 &patch
->base
.binary
.new_file
, ctx
)) < 0)
795 if (parse_advance_nl(ctx
) < 0)
796 return parse_err("corrupt git binary separator at line %"PRIuZ
,
799 /* parse new->old binary diff */
800 if ((error
= parse_patch_binary_side(
801 &patch
->base
.binary
.old_file
, ctx
)) < 0)
804 if (parse_advance_nl(ctx
) < 0)
805 return parse_err("corrupt git binary patch separator at line %"PRIuZ
,
808 patch
->base
.binary
.contains_data
= 1;
809 patch
->base
.delta
->flags
|= GIT_DIFF_FLAG_BINARY
;
813 static int parse_patch_binary_nodata(
814 git_patch_parsed
*patch
,
815 git_patch_parse_ctx
*ctx
)
817 if (parse_advance_expected_str(ctx
, "Binary files ") < 0 ||
818 parse_advance_expected_str(ctx
, patch
->header_old_path
) < 0 ||
819 parse_advance_expected_str(ctx
, " and ") < 0 ||
820 parse_advance_expected_str(ctx
, patch
->header_new_path
) < 0 ||
821 parse_advance_expected_str(ctx
, " differ") < 0 ||
822 parse_advance_nl(ctx
) < 0)
823 return parse_err("corrupt git binary header at line %"PRIuZ
, ctx
->line_num
);
825 patch
->base
.binary
.contains_data
= 0;
826 patch
->base
.delta
->flags
|= GIT_DIFF_FLAG_BINARY
;
830 static int parse_patch_hunks(
831 git_patch_parsed
*patch
,
832 git_patch_parse_ctx
*ctx
)
834 git_patch_hunk
*hunk
;
837 while (parse_ctx_contains_s(ctx
, "@@ -")) {
838 hunk
= git_array_alloc(patch
->base
.hunks
);
839 GITERR_CHECK_ALLOC(hunk
);
841 memset(hunk
, 0, sizeof(git_patch_hunk
));
843 hunk
->line_start
= git_array_size(patch
->base
.lines
);
844 hunk
->line_count
= 0;
846 if ((error
= parse_hunk_header(hunk
, ctx
)) < 0 ||
847 (error
= parse_hunk_body(patch
, hunk
, ctx
)) < 0)
851 patch
->base
.delta
->flags
|= GIT_DIFF_FLAG_NOT_BINARY
;
857 static int parse_patch_body(
858 git_patch_parsed
*patch
, git_patch_parse_ctx
*ctx
)
860 if (parse_ctx_contains_s(ctx
, "GIT binary patch"))
861 return parse_patch_binary(patch
, ctx
);
862 else if (parse_ctx_contains_s(ctx
, "Binary files "))
863 return parse_patch_binary_nodata(patch
, ctx
);
865 return parse_patch_hunks(patch
, ctx
);
868 int check_header_names(
871 const char *old_or_new
,
877 if (two_null
&& strcmp(two
, "/dev/null") != 0)
878 return parse_err("expected %s path of '/dev/null'", old_or_new
);
880 else if (!two_null
&& strcmp(one
, two
) != 0)
881 return parse_err("mismatched %s path names", old_or_new
);
886 static int check_prefix(
889 git_patch_parsed
*patch
,
890 const char *path_start
)
892 const char *path
= path_start
;
893 size_t prefix_len
= patch
->ctx
->opts
.prefix_len
;
894 size_t remain_len
= prefix_len
;
902 /* leading slashes do not count as part of the prefix in git apply */
906 while (*path
&& remain_len
) {
913 if (remain_len
|| !*path
)
915 "header filename does not contain %"PRIuZ
" path components",
919 *out_len
= (path
- path_start
);
920 *out
= git__strndup(path_start
, *out_len
);
922 return (*out
== NULL
) ? -1 : 0;
925 static int check_filenames(git_patch_parsed
*patch
)
927 const char *prefixed_new
, *prefixed_old
;
928 size_t old_prefixlen
= 0, new_prefixlen
= 0;
929 bool added
= (patch
->base
.delta
->status
== GIT_DELTA_ADDED
);
930 bool deleted
= (patch
->base
.delta
->status
== GIT_DELTA_DELETED
);
932 if (patch
->old_path
&& !patch
->new_path
)
933 return parse_err("missing new path");
935 if (!patch
->old_path
&& patch
->new_path
)
936 return parse_err("missing old path");
938 /* Ensure (non-renamed) paths match */
939 if (check_header_names(
940 patch
->header_old_path
, patch
->old_path
, "old", added
) < 0 ||
942 patch
->header_new_path
, patch
->new_path
, "new", deleted
) < 0)
945 prefixed_old
= (!added
&& patch
->old_path
) ? patch
->old_path
:
946 patch
->header_old_path
;
947 prefixed_new
= (!deleted
&& patch
->new_path
) ? patch
->new_path
:
948 patch
->header_new_path
;
951 &patch
->old_prefix
, &old_prefixlen
, patch
, prefixed_old
) < 0 ||
953 &patch
->new_prefix
, &new_prefixlen
, patch
, prefixed_new
) < 0)
956 /* Prefer the rename filenames as they are unambiguous and unprefixed */
957 if (patch
->rename_old_path
)
958 patch
->base
.delta
->old_file
.path
= patch
->rename_old_path
;
960 patch
->base
.delta
->old_file
.path
= prefixed_old
+ old_prefixlen
;
962 if (patch
->rename_new_path
)
963 patch
->base
.delta
->new_file
.path
= patch
->rename_new_path
;
965 patch
->base
.delta
->new_file
.path
= prefixed_new
+ new_prefixlen
;
967 if (!patch
->base
.delta
->old_file
.path
&&
968 !patch
->base
.delta
->new_file
.path
)
969 return parse_err("git diff header lacks old / new paths");
974 static int check_patch(git_patch_parsed
*patch
)
976 git_diff_delta
*delta
= patch
->base
.delta
;
978 if (check_filenames(patch
) < 0)
981 if (delta
->old_file
.path
&&
982 delta
->status
!= GIT_DELTA_DELETED
&&
983 !delta
->new_file
.mode
)
984 delta
->new_file
.mode
= delta
->old_file
.mode
;
986 if (delta
->status
== GIT_DELTA_MODIFIED
&&
987 !(delta
->flags
& GIT_DIFF_FLAG_BINARY
) &&
988 delta
->new_file
.mode
== delta
->old_file
.mode
&&
989 git_array_size(patch
->base
.hunks
) == 0)
990 return parse_err("patch with no hunks");
992 if (delta
->status
== GIT_DELTA_ADDED
) {
993 memset(&delta
->old_file
.id
, 0x0, sizeof(git_oid
));
994 delta
->old_file
.id_abbrev
= 0;
997 if (delta
->status
== GIT_DELTA_DELETED
) {
998 memset(&delta
->new_file
.id
, 0x0, sizeof(git_oid
));
999 delta
->new_file
.id_abbrev
= 0;
1005 git_patch_parse_ctx
*git_patch_parse_ctx_init(
1006 const char *content
,
1008 const git_patch_options
*opts
)
1010 git_patch_parse_ctx
*ctx
;
1011 git_patch_options default_opts
= GIT_PATCH_OPTIONS_INIT
;
1013 if ((ctx
= git__calloc(1, sizeof(git_patch_parse_ctx
))) == NULL
)
1017 if ((ctx
->content
= git__malloc(content_len
)) == NULL
)
1020 memcpy((char *)ctx
->content
, content
, content_len
);
1023 ctx
->content_len
= content_len
;
1024 ctx
->remain
= ctx
->content
;
1025 ctx
->remain_len
= ctx
->content_len
;
1028 memcpy(&ctx
->opts
, opts
, sizeof(git_patch_options
));
1030 memcpy(&ctx
->opts
, &default_opts
, sizeof(git_patch_options
));
1032 GIT_REFCOUNT_INC(ctx
);
1036 static void patch_parse_ctx_free(git_patch_parse_ctx
*ctx
)
1041 git__free((char *)ctx
->content
);
1045 void git_patch_parse_ctx_free(git_patch_parse_ctx
*ctx
)
1047 GIT_REFCOUNT_DEC(ctx
, patch_parse_ctx_free
);
1050 int git_patch_parsed_from_diff(git_patch
**out
, git_diff
*d
, size_t idx
)
1052 git_diff_parsed
*diff
= (git_diff_parsed
*)d
;
1055 if ((p
= git_vector_get(&diff
->patches
, idx
)) == NULL
)
1058 GIT_REFCOUNT_INC(p
);
1064 static void patch_parsed__free(git_patch
*p
)
1066 git_patch_parsed
*patch
= (git_patch_parsed
*)p
;
1071 git_patch_parse_ctx_free(patch
->ctx
);
1073 git__free((char *)patch
->base
.binary
.old_file
.data
);
1074 git__free((char *)patch
->base
.binary
.new_file
.data
);
1075 git_array_clear(patch
->base
.hunks
);
1076 git_array_clear(patch
->base
.lines
);
1077 git__free(patch
->base
.delta
);
1079 git__free(patch
->old_prefix
);
1080 git__free(patch
->new_prefix
);
1081 git__free(patch
->header_old_path
);
1082 git__free(patch
->header_new_path
);
1083 git__free(patch
->rename_old_path
);
1084 git__free(patch
->rename_new_path
);
1085 git__free(patch
->old_path
);
1086 git__free(patch
->new_path
);
1090 int git_patch_parse(
1092 git_patch_parse_ctx
*ctx
)
1094 git_patch_parsed
*patch
;
1102 patch
= git__calloc(1, sizeof(git_patch_parsed
));
1103 GITERR_CHECK_ALLOC(patch
);
1106 GIT_REFCOUNT_INC(patch
->ctx
);
1108 patch
->base
.free_fn
= patch_parsed__free
;
1110 patch
->base
.delta
= git__calloc(1, sizeof(git_diff_delta
));
1111 GITERR_CHECK_ALLOC(patch
->base
.delta
);
1113 patch
->base
.delta
->status
= GIT_DELTA_MODIFIED
;
1114 patch
->base
.delta
->nfiles
= 2;
1116 start
= ctx
->remain_len
;
1118 if ((error
= parse_patch_header(patch
, ctx
)) < 0 ||
1119 (error
= parse_patch_body(patch
, ctx
)) < 0 ||
1120 (error
= check_patch(patch
)) < 0)
1123 used
= start
- ctx
->remain_len
;
1124 ctx
->remain
+= used
;
1126 patch
->base
.diff_opts
.old_prefix
= patch
->old_prefix
;
1127 patch
->base
.diff_opts
.new_prefix
= patch
->new_prefix
;
1128 patch
->base
.diff_opts
.flags
|= GIT_DIFF_SHOW_BINARY
;
1130 GIT_REFCOUNT_INC(patch
);
1131 *out
= &patch
->base
;
1135 patch_parsed__free(&patch
->base
);
1140 int git_patch_from_buffer(
1142 const char *content
,
1144 const git_patch_options
*opts
)
1146 git_patch_parse_ctx
*ctx
;
1149 ctx
= git_patch_parse_ctx_init(content
, content_len
, opts
);
1150 GITERR_CHECK_ALLOC(ctx
);
1152 error
= git_patch_parse(out
, ctx
);
1154 git_patch_parse_ctx_free(ctx
);