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.
10 #include "patch_diff.h"
15 #include "git2/sys/diff.h"
18 git_diff_format_t format
;
19 git_diff_line_cb print_cb
;
25 const char *old_prefix
;
26 const char *new_prefix
;
30 int (*strcomp
)(const char *, const char *);
33 static int diff_print_info_init__common(
37 git_diff_format_t format
,
43 pi
->payload
= payload
;
48 pi
->id_strlen
= GIT_ABBREV_DEFAULT
;
49 else if (git_repository__cvar(&pi
->id_strlen
, repo
, GIT_CVAR_ABBREV
) < 0)
53 if (pi
->id_strlen
> GIT_OID_HEXSZ
)
54 pi
->id_strlen
= GIT_OID_HEXSZ
;
56 memset(&pi
->line
, 0, sizeof(pi
->line
));
57 pi
->line
.old_lineno
= -1;
58 pi
->line
.new_lineno
= -1;
59 pi
->line
.num_lines
= 1;
64 static int diff_print_info_init_fromdiff(
68 git_diff_format_t format
,
72 git_repository
*repo
= diff
? diff
->repo
: NULL
;
74 memset(pi
, 0, sizeof(diff_print_info
));
77 pi
->flags
= diff
->opts
.flags
;
78 pi
->id_strlen
= diff
->opts
.id_abbrev
;
79 pi
->old_prefix
= diff
->opts
.old_prefix
;
80 pi
->new_prefix
= diff
->opts
.new_prefix
;
82 pi
->strcomp
= diff
->strcomp
;
85 return diff_print_info_init__common(pi
, out
, repo
, format
, cb
, payload
);
88 static int diff_print_info_init_frompatch(
92 git_diff_format_t format
,
98 memset(pi
, 0, sizeof(diff_print_info
));
100 pi
->flags
= patch
->diff_opts
.flags
;
101 pi
->id_strlen
= patch
->diff_opts
.id_abbrev
;
102 pi
->old_prefix
= patch
->diff_opts
.old_prefix
;
103 pi
->new_prefix
= patch
->diff_opts
.new_prefix
;
105 return diff_print_info_init__common(pi
, out
, patch
->repo
, format
, cb
, payload
);
108 static char diff_pick_suffix(int mode
)
112 else if (GIT_PERMS_IS_EXEC(mode
)) /* -V536 */
113 /* in git, modes are very regular, so we must have 0100755 mode */
119 char git_diff_status_char(git_delta_t status
)
124 case GIT_DELTA_ADDED
: code
= 'A'; break;
125 case GIT_DELTA_DELETED
: code
= 'D'; break;
126 case GIT_DELTA_MODIFIED
: code
= 'M'; break;
127 case GIT_DELTA_RENAMED
: code
= 'R'; break;
128 case GIT_DELTA_COPIED
: code
= 'C'; break;
129 case GIT_DELTA_IGNORED
: code
= 'I'; break;
130 case GIT_DELTA_UNTRACKED
: code
= '?'; break;
131 case GIT_DELTA_UNREADABLE
: code
= 'X'; break;
132 default: code
= ' '; break;
138 static int diff_print_one_name_only(
139 const git_diff_delta
*delta
, float progress
, void *data
)
141 diff_print_info
*pi
= data
;
142 git_buf
*out
= pi
->buf
;
144 GIT_UNUSED(progress
);
146 if ((pi
->flags
& GIT_DIFF_SHOW_UNMODIFIED
) == 0 &&
147 delta
->status
== GIT_DELTA_UNMODIFIED
)
151 git_buf_puts(out
, delta
->new_file
.path
);
152 git_buf_putc(out
, '\n');
153 if (git_buf_oom(out
))
156 pi
->line
.origin
= GIT_DIFF_LINE_FILE_HDR
;
157 pi
->line
.content
= git_buf_cstr(out
);
158 pi
->line
.content_len
= git_buf_len(out
);
160 return pi
->print_cb(delta
, NULL
, &pi
->line
, pi
->payload
);
163 static int diff_print_one_name_status(
164 const git_diff_delta
*delta
, float progress
, void *data
)
166 diff_print_info
*pi
= data
;
167 git_buf
*out
= pi
->buf
;
168 char old_suffix
, new_suffix
, code
= git_diff_status_char(delta
->status
);
169 int(*strcomp
)(const char *, const char *) = pi
->strcomp
?
170 pi
->strcomp
: git__strcmp
;
172 GIT_UNUSED(progress
);
174 if ((pi
->flags
& GIT_DIFF_SHOW_UNMODIFIED
) == 0 && code
== ' ')
177 old_suffix
= diff_pick_suffix(delta
->old_file
.mode
);
178 new_suffix
= diff_pick_suffix(delta
->new_file
.mode
);
182 if (delta
->old_file
.path
!= delta
->new_file
.path
&&
183 strcomp(delta
->old_file
.path
,delta
->new_file
.path
) != 0)
184 git_buf_printf(out
, "%c\t%s%c %s%c\n", code
,
185 delta
->old_file
.path
, old_suffix
, delta
->new_file
.path
, new_suffix
);
186 else if (delta
->old_file
.mode
!= delta
->new_file
.mode
&&
187 delta
->old_file
.mode
!= 0 && delta
->new_file
.mode
!= 0)
188 git_buf_printf(out
, "%c\t%s%c %s%c\n", code
,
189 delta
->old_file
.path
, old_suffix
, delta
->new_file
.path
, new_suffix
);
190 else if (old_suffix
!= ' ')
191 git_buf_printf(out
, "%c\t%s%c\n", code
, delta
->old_file
.path
, old_suffix
);
193 git_buf_printf(out
, "%c\t%s\n", code
, delta
->old_file
.path
);
194 if (git_buf_oom(out
))
197 pi
->line
.origin
= GIT_DIFF_LINE_FILE_HDR
;
198 pi
->line
.content
= git_buf_cstr(out
);
199 pi
->line
.content_len
= git_buf_len(out
);
201 return pi
->print_cb(delta
, NULL
, &pi
->line
, pi
->payload
);
204 static int diff_print_one_raw(
205 const git_diff_delta
*delta
, float progress
, void *data
)
207 diff_print_info
*pi
= data
;
208 git_buf
*out
= pi
->buf
;
210 char code
= git_diff_status_char(delta
->status
);
211 char start_oid
[GIT_OID_HEXSZ
+1], end_oid
[GIT_OID_HEXSZ
+1];
213 GIT_UNUSED(progress
);
215 if ((pi
->flags
& GIT_DIFF_SHOW_UNMODIFIED
) == 0 && code
== ' ')
220 id_abbrev
= delta
->old_file
.mode
? delta
->old_file
.id_abbrev
:
221 delta
->new_file
.id_abbrev
;
223 if (pi
->id_strlen
> id_abbrev
) {
224 giterr_set(GITERR_PATCH
,
225 "The patch input contains %d id characters (cannot print %d)",
226 id_abbrev
, pi
->id_strlen
);
230 git_oid_tostr(start_oid
, pi
->id_strlen
+ 1, &delta
->old_file
.id
);
231 git_oid_tostr(end_oid
, pi
->id_strlen
+ 1, &delta
->new_file
.id
);
234 out
, (pi
->id_strlen
<= GIT_OID_HEXSZ
) ?
235 ":%06o %06o %s... %s... %c" : ":%06o %06o %s %s %c",
236 delta
->old_file
.mode
, delta
->new_file
.mode
, start_oid
, end_oid
, code
);
238 if (delta
->similarity
> 0)
239 git_buf_printf(out
, "%03u", delta
->similarity
);
241 if (delta
->old_file
.path
!= delta
->new_file
.path
)
243 out
, "\t%s %s\n", delta
->old_file
.path
, delta
->new_file
.path
);
246 out
, "\t%s\n", delta
->old_file
.path
?
247 delta
->old_file
.path
: delta
->new_file
.path
);
249 if (git_buf_oom(out
))
252 pi
->line
.origin
= GIT_DIFF_LINE_FILE_HDR
;
253 pi
->line
.content
= git_buf_cstr(out
);
254 pi
->line
.content_len
= git_buf_len(out
);
256 return pi
->print_cb(delta
, NULL
, &pi
->line
, pi
->payload
);
259 static int diff_print_modes(
260 git_buf
*out
, const git_diff_delta
*delta
)
262 git_buf_printf(out
, "old mode %o\n", delta
->old_file
.mode
);
263 git_buf_printf(out
, "new mode %o\n", delta
->new_file
.mode
);
265 return git_buf_oom(out
) ? -1 : 0;
268 static int diff_print_oid_range(
269 git_buf
*out
, const git_diff_delta
*delta
, int id_strlen
)
271 char start_oid
[GIT_OID_HEXSZ
+1], end_oid
[GIT_OID_HEXSZ
+1];
273 if (delta
->old_file
.mode
&&
274 id_strlen
> delta
->old_file
.id_abbrev
) {
275 giterr_set(GITERR_PATCH
,
276 "The patch input contains %d id characters (cannot print %d)",
277 delta
->old_file
.id_abbrev
, id_strlen
);
281 if ((delta
->new_file
.mode
&&
282 id_strlen
> delta
->new_file
.id_abbrev
)) {
283 giterr_set(GITERR_PATCH
,
284 "The patch input contains %d id characters (cannot print %d)",
285 delta
->new_file
.id_abbrev
, id_strlen
);
289 git_oid_tostr(start_oid
, id_strlen
+ 1, &delta
->old_file
.id
);
290 git_oid_tostr(end_oid
, id_strlen
+ 1, &delta
->new_file
.id
);
292 if (delta
->old_file
.mode
== delta
->new_file
.mode
) {
293 git_buf_printf(out
, "index %s..%s %o\n",
294 start_oid
, end_oid
, delta
->old_file
.mode
);
296 if (delta
->old_file
.mode
== 0)
297 git_buf_printf(out
, "new file mode %o\n", delta
->new_file
.mode
);
298 else if (delta
->new_file
.mode
== 0)
299 git_buf_printf(out
, "deleted file mode %o\n", delta
->old_file
.mode
);
301 diff_print_modes(out
, delta
);
303 git_buf_printf(out
, "index %s..%s\n", start_oid
, end_oid
);
306 return git_buf_oom(out
) ? -1 : 0;
309 static int diff_delta_format_path(
310 git_buf
*out
, const char *prefix
, const char *filename
)
312 if (git_buf_joinpath(out
, prefix
, filename
) < 0)
315 return git_buf_quote(out
);
318 static int diff_delta_format_with_paths(
320 const git_diff_delta
*delta
,
321 const char *template,
325 if (git_oid_iszero(&delta
->old_file
.id
))
326 oldpath
= "/dev/null";
328 if (git_oid_iszero(&delta
->new_file
.id
))
329 newpath
= "/dev/null";
331 return git_buf_printf(out
, template, oldpath
, newpath
);
334 int diff_delta_format_rename_header(
336 const git_diff_delta
*delta
)
338 git_buf old_path
= GIT_BUF_INIT
, new_path
= GIT_BUF_INIT
;
341 if (delta
->similarity
> 100) {
342 giterr_set(GITERR_PATCH
, "invalid similarity %d", delta
->similarity
);
347 if ((error
= git_buf_puts(&old_path
, delta
->old_file
.path
)) < 0 ||
348 (error
= git_buf_puts(&new_path
, delta
->new_file
.path
)) < 0 ||
349 (error
= git_buf_quote(&old_path
)) < 0 ||
350 (error
= git_buf_quote(&new_path
)) < 0)
354 "similarity index %d%%\n"
361 if (git_buf_oom(out
))
365 git_buf_free(&old_path
);
366 git_buf_free(&new_path
);
371 int git_diff_delta__format_file_header(
373 const git_diff_delta
*delta
,
378 git_buf old_path
= GIT_BUF_INIT
, new_path
= GIT_BUF_INIT
;
383 oldpfx
= DIFF_OLD_PREFIX_DEFAULT
;
385 newpfx
= DIFF_NEW_PREFIX_DEFAULT
;
387 id_strlen
= GIT_ABBREV_DEFAULT
;
389 if ((error
= diff_delta_format_path(
390 &old_path
, oldpfx
, delta
->old_file
.path
)) < 0 ||
391 (error
= diff_delta_format_path(
392 &new_path
, newpfx
, delta
->new_file
.path
)) < 0)
397 git_buf_printf(out
, "diff --git %s %s\n",
398 old_path
.ptr
, new_path
.ptr
);
400 if (delta
->status
== GIT_DELTA_RENAMED
) {
401 if ((error
= diff_delta_format_rename_header(out
, delta
)) < 0)
405 unchanged
= (git_oid_iszero(&delta
->old_file
.id
) &&
406 git_oid_iszero(&delta
->new_file
.id
));
409 if ((error
= diff_print_oid_range(out
, delta
, id_strlen
)) < 0)
412 if ((delta
->flags
& GIT_DIFF_FLAG_BINARY
) == 0)
413 diff_delta_format_with_paths(out
, delta
,
414 "--- %s\n+++ %s\n", old_path
.ptr
, new_path
.ptr
);
417 if (unchanged
&& delta
->old_file
.mode
!= delta
->new_file
.mode
)
418 diff_print_modes(out
, delta
);
420 if (git_buf_oom(out
))
424 git_buf_free(&old_path
);
425 git_buf_free(&new_path
);
430 static int format_binary(
432 git_diff_binary_t type
,
437 const char *typename
= type
== GIT_DIFF_BINARY_DELTA
?
439 const char *scan
, *end
;
441 git_buf_printf(pi
->buf
, "%s %" PRIuZ
"\n", typename
, inflatedlen
);
442 pi
->line
.num_lines
++;
444 for (scan
= data
, end
= data
+ datalen
; scan
< end
; ) {
445 size_t chunk_len
= end
- scan
;
450 git_buf_putc(pi
->buf
, (char)chunk_len
+ 'A' - 1);
452 git_buf_putc(pi
->buf
, (char)chunk_len
- 26 + 'a' - 1);
454 git_buf_encode_base85(pi
->buf
, scan
, chunk_len
);
455 git_buf_putc(pi
->buf
, '\n');
457 if (git_buf_oom(pi
->buf
))
461 pi
->line
.num_lines
++;
463 git_buf_putc(pi
->buf
, '\n');
468 static int diff_print_patch_file_binary_noshow(
469 diff_print_info
*pi
, git_diff_delta
*delta
,
470 const char *old_pfx
, const char *new_pfx
,
471 const git_diff_binary
*binary
)
473 git_buf old_path
= GIT_BUF_INIT
, new_path
= GIT_BUF_INIT
;
476 if ((error
= diff_delta_format_path(
477 &old_path
, old_pfx
, delta
->old_file
.path
)) < 0 ||
478 (error
= diff_delta_format_path(
479 &new_path
, new_pfx
, delta
->new_file
.path
)) < 0)
483 pi
->line
.num_lines
= 1;
484 error
= diff_delta_format_with_paths(
485 pi
->buf
, delta
, "Binary files %s and %s differ\n",
486 old_path
.ptr
, new_path
.ptr
);
489 git_buf_free(&old_path
);
490 git_buf_free(&new_path
);
495 static int diff_print_patch_file_binary(
496 diff_print_info
*pi
, git_diff_delta
*delta
,
497 const char *old_pfx
, const char *new_pfx
,
498 const git_diff_binary
*binary
)
500 size_t pre_binary_size
;
503 if ((pi
->flags
& GIT_DIFF_SHOW_BINARY
) == 0)
504 return diff_print_patch_file_binary_noshow(
505 pi
, delta
, old_pfx
, new_pfx
, binary
);
507 if (binary
->new_file
.datalen
== 0 && binary
->old_file
.datalen
== 0)
510 pre_binary_size
= pi
->buf
->size
;
511 git_buf_printf(pi
->buf
, "GIT binary patch\n");
512 pi
->line
.num_lines
++;
514 if ((error
= format_binary(pi
, binary
->new_file
.type
, binary
->new_file
.data
,
515 binary
->new_file
.datalen
, binary
->new_file
.inflatedlen
)) < 0 ||
516 (error
= format_binary(pi
, binary
->old_file
.type
, binary
->old_file
.data
,
517 binary
->old_file
.datalen
, binary
->old_file
.inflatedlen
)) < 0) {
519 if (error
== GIT_EBUFS
) {
521 git_buf_truncate(pi
->buf
, pre_binary_size
);
523 return diff_print_patch_file_binary_noshow(
524 pi
, delta
, old_pfx
, new_pfx
, binary
);
528 pi
->line
.num_lines
++;
532 static int diff_print_patch_file(
533 const git_diff_delta
*delta
, float progress
, void *data
)
536 diff_print_info
*pi
= data
;
538 pi
->old_prefix
? pi
->old_prefix
: DIFF_OLD_PREFIX_DEFAULT
;
540 pi
->new_prefix
? pi
->new_prefix
: DIFF_NEW_PREFIX_DEFAULT
;
542 bool binary
= (delta
->flags
& GIT_DIFF_FLAG_BINARY
) ||
543 (pi
->flags
& GIT_DIFF_FORCE_BINARY
);
544 bool show_binary
= !!(pi
->flags
& GIT_DIFF_SHOW_BINARY
);
545 int id_strlen
= binary
&& show_binary
?
546 GIT_OID_HEXSZ
: pi
->id_strlen
;
548 GIT_UNUSED(progress
);
550 if (S_ISDIR(delta
->new_file
.mode
) ||
551 delta
->status
== GIT_DELTA_UNMODIFIED
||
552 delta
->status
== GIT_DELTA_IGNORED
||
553 delta
->status
== GIT_DELTA_UNREADABLE
||
554 (delta
->status
== GIT_DELTA_UNTRACKED
&&
555 (pi
->flags
& GIT_DIFF_SHOW_UNTRACKED_CONTENT
) == 0))
558 if ((error
= git_diff_delta__format_file_header(
559 pi
->buf
, delta
, oldpfx
, newpfx
, id_strlen
)) < 0)
562 pi
->line
.origin
= GIT_DIFF_LINE_FILE_HDR
;
563 pi
->line
.content
= git_buf_cstr(pi
->buf
);
564 pi
->line
.content_len
= git_buf_len(pi
->buf
);
566 return pi
->print_cb(delta
, NULL
, &pi
->line
, pi
->payload
);
569 static int diff_print_patch_binary(
570 const git_diff_delta
*delta
,
571 const git_diff_binary
*binary
,
574 diff_print_info
*pi
= data
;
575 const char *old_pfx
=
576 pi
->old_prefix
? pi
->old_prefix
: DIFF_OLD_PREFIX_DEFAULT
;
577 const char *new_pfx
=
578 pi
->new_prefix
? pi
->new_prefix
: DIFF_NEW_PREFIX_DEFAULT
;
581 git_buf_clear(pi
->buf
);
583 if ((error
= diff_print_patch_file_binary(
584 pi
, (git_diff_delta
*)delta
, old_pfx
, new_pfx
, binary
)) < 0)
587 pi
->line
.origin
= GIT_DIFF_LINE_BINARY
;
588 pi
->line
.content
= git_buf_cstr(pi
->buf
);
589 pi
->line
.content_len
= git_buf_len(pi
->buf
);
591 return pi
->print_cb(delta
, NULL
, &pi
->line
, pi
->payload
);
594 static int diff_print_patch_hunk(
595 const git_diff_delta
*d
,
596 const git_diff_hunk
*h
,
599 diff_print_info
*pi
= data
;
601 if (S_ISDIR(d
->new_file
.mode
))
604 pi
->line
.origin
= GIT_DIFF_LINE_HUNK_HDR
;
605 pi
->line
.content
= h
->header
;
606 pi
->line
.content_len
= h
->header_len
;
608 return pi
->print_cb(d
, h
, &pi
->line
, pi
->payload
);
611 static int diff_print_patch_line(
612 const git_diff_delta
*delta
,
613 const git_diff_hunk
*hunk
,
614 const git_diff_line
*line
,
617 diff_print_info
*pi
= data
;
619 if (S_ISDIR(delta
->new_file
.mode
))
622 return pi
->print_cb(delta
, hunk
, line
, pi
->payload
);
625 /* print a git_diff to an output callback */
628 git_diff_format_t format
,
629 git_diff_line_cb print_cb
,
633 git_buf buf
= GIT_BUF_INIT
;
635 git_diff_file_cb print_file
= NULL
;
636 git_diff_binary_cb print_binary
= NULL
;
637 git_diff_hunk_cb print_hunk
= NULL
;
638 git_diff_line_cb print_line
= NULL
;
641 case GIT_DIFF_FORMAT_PATCH
:
642 print_file
= diff_print_patch_file
;
643 print_binary
= diff_print_patch_binary
;
644 print_hunk
= diff_print_patch_hunk
;
645 print_line
= diff_print_patch_line
;
647 case GIT_DIFF_FORMAT_PATCH_HEADER
:
648 print_file
= diff_print_patch_file
;
650 case GIT_DIFF_FORMAT_RAW
:
651 print_file
= diff_print_one_raw
;
653 case GIT_DIFF_FORMAT_NAME_ONLY
:
654 print_file
= diff_print_one_name_only
;
656 case GIT_DIFF_FORMAT_NAME_STATUS
:
657 print_file
= diff_print_one_name_status
;
660 giterr_set(GITERR_INVALID
, "Unknown diff output format (%d)", format
);
664 if (!(error
= diff_print_info_init_fromdiff(
665 &pi
, &buf
, diff
, format
, print_cb
, payload
))) {
666 error
= git_diff_foreach(
667 diff
, print_file
, print_binary
, print_hunk
, print_line
, &pi
);
669 if (error
) /* make sure error message is set */
670 giterr_set_after_callback_function(error
, "git_diff_print");
678 int git_diff_print_callback__to_buf(
679 const git_diff_delta
*delta
,
680 const git_diff_hunk
*hunk
,
681 const git_diff_line
*line
,
684 git_buf
*output
= payload
;
685 GIT_UNUSED(delta
); GIT_UNUSED(hunk
);
688 giterr_set(GITERR_INVALID
, "Buffer pointer must be provided");
692 if (line
->origin
== GIT_DIFF_LINE_ADDITION
||
693 line
->origin
== GIT_DIFF_LINE_DELETION
||
694 line
->origin
== GIT_DIFF_LINE_CONTEXT
)
695 git_buf_putc(output
, line
->origin
);
697 return git_buf_put(output
, line
->content
, line
->content_len
);
700 int git_diff_print_callback__to_file_handle(
701 const git_diff_delta
*delta
,
702 const git_diff_hunk
*hunk
,
703 const git_diff_line
*line
,
706 FILE *fp
= payload
? payload
: stdout
;
708 GIT_UNUSED(delta
); GIT_UNUSED(hunk
);
710 if (line
->origin
== GIT_DIFF_LINE_CONTEXT
||
711 line
->origin
== GIT_DIFF_LINE_ADDITION
||
712 line
->origin
== GIT_DIFF_LINE_DELETION
)
713 fputc(line
->origin
, fp
);
714 fwrite(line
->content
, 1, line
->content_len
, fp
);
718 /* print a git_patch to an output callback */
721 git_diff_line_cb print_cb
,
725 git_buf temp
= GIT_BUF_INIT
;
728 assert(patch
&& print_cb
);
730 if (!(error
= diff_print_info_init_frompatch(
732 GIT_DIFF_FORMAT_PATCH
, print_cb
, payload
)))
734 error
= git_patch__invoke_callbacks(
736 diff_print_patch_file
, diff_print_patch_binary
,
737 diff_print_patch_hunk
, diff_print_patch_line
,
740 if (error
) /* make sure error message is set */
741 giterr_set_after_callback_function(error
, "git_patch_print");
749 /* print a git_patch to a git_buf */
750 int git_patch_to_buf(git_buf
*out
, git_patch
*patch
)
752 assert(out
&& patch
);
753 git_buf_sanitize(out
);
754 return git_patch_print(patch
, git_diff_print_callback__to_buf
, out
);