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
;
46 if (!pi
->oid_strlen
) {
48 pi
->oid_strlen
= GIT_ABBREV_DEFAULT
;
49 else if (git_repository__cvar(&pi
->oid_strlen
, repo
, GIT_CVAR_ABBREV
) < 0)
53 pi
->oid_strlen
+= 1; /* for NUL byte */
55 if (pi
->oid_strlen
> GIT_OID_HEXSZ
+ 1)
56 pi
->oid_strlen
= GIT_OID_HEXSZ
+ 1;
58 memset(&pi
->line
, 0, sizeof(pi
->line
));
59 pi
->line
.old_lineno
= -1;
60 pi
->line
.new_lineno
= -1;
61 pi
->line
.num_lines
= 1;
66 static int diff_print_info_init_fromdiff(
70 git_diff_format_t format
,
74 git_repository
*repo
= diff
? diff
->repo
: NULL
;
76 memset(pi
, 0, sizeof(diff_print_info
));
79 pi
->flags
= diff
->opts
.flags
;
80 pi
->oid_strlen
= diff
->opts
.id_abbrev
;
81 pi
->old_prefix
= diff
->opts
.old_prefix
;
82 pi
->new_prefix
= diff
->opts
.new_prefix
;
84 pi
->strcomp
= diff
->strcomp
;
87 return diff_print_info_init__common(pi
, out
, repo
, format
, cb
, payload
);
90 static int diff_print_info_init_frompatch(
94 git_diff_format_t format
,
100 memset(pi
, 0, sizeof(diff_print_info
));
102 pi
->flags
= patch
->diff_opts
.flags
;
103 pi
->oid_strlen
= patch
->diff_opts
.id_abbrev
;
104 pi
->old_prefix
= patch
->diff_opts
.old_prefix
;
105 pi
->new_prefix
= patch
->diff_opts
.new_prefix
;
107 return diff_print_info_init__common(pi
, out
, patch
->repo
, format
, cb
, payload
);
110 static char diff_pick_suffix(int mode
)
114 else if (GIT_PERMS_IS_EXEC(mode
)) /* -V536 */
115 /* in git, modes are very regular, so we must have 0100755 mode */
121 char git_diff_status_char(git_delta_t status
)
126 case GIT_DELTA_ADDED
: code
= 'A'; break;
127 case GIT_DELTA_DELETED
: code
= 'D'; break;
128 case GIT_DELTA_MODIFIED
: code
= 'M'; break;
129 case GIT_DELTA_RENAMED
: code
= 'R'; break;
130 case GIT_DELTA_COPIED
: code
= 'C'; break;
131 case GIT_DELTA_IGNORED
: code
= 'I'; break;
132 case GIT_DELTA_UNTRACKED
: code
= '?'; break;
133 case GIT_DELTA_UNREADABLE
: code
= 'X'; break;
134 default: code
= ' '; break;
140 static int diff_print_one_name_only(
141 const git_diff_delta
*delta
, float progress
, void *data
)
143 diff_print_info
*pi
= data
;
144 git_buf
*out
= pi
->buf
;
146 GIT_UNUSED(progress
);
148 if ((pi
->flags
& GIT_DIFF_SHOW_UNMODIFIED
) == 0 &&
149 delta
->status
== GIT_DELTA_UNMODIFIED
)
153 git_buf_puts(out
, delta
->new_file
.path
);
154 git_buf_putc(out
, '\n');
155 if (git_buf_oom(out
))
158 pi
->line
.origin
= GIT_DIFF_LINE_FILE_HDR
;
159 pi
->line
.content
= git_buf_cstr(out
);
160 pi
->line
.content_len
= git_buf_len(out
);
162 return pi
->print_cb(delta
, NULL
, &pi
->line
, pi
->payload
);
165 static int diff_print_one_name_status(
166 const git_diff_delta
*delta
, float progress
, void *data
)
168 diff_print_info
*pi
= data
;
169 git_buf
*out
= pi
->buf
;
170 char old_suffix
, new_suffix
, code
= git_diff_status_char(delta
->status
);
171 int(*strcomp
)(const char *, const char *) = pi
->strcomp
?
172 pi
->strcomp
: git__strcmp
;
174 GIT_UNUSED(progress
);
176 if ((pi
->flags
& GIT_DIFF_SHOW_UNMODIFIED
) == 0 && code
== ' ')
179 old_suffix
= diff_pick_suffix(delta
->old_file
.mode
);
180 new_suffix
= diff_pick_suffix(delta
->new_file
.mode
);
184 if (delta
->old_file
.path
!= delta
->new_file
.path
&&
185 strcomp(delta
->old_file
.path
,delta
->new_file
.path
) != 0)
186 git_buf_printf(out
, "%c\t%s%c %s%c\n", code
,
187 delta
->old_file
.path
, old_suffix
, delta
->new_file
.path
, new_suffix
);
188 else if (delta
->old_file
.mode
!= delta
->new_file
.mode
&&
189 delta
->old_file
.mode
!= 0 && delta
->new_file
.mode
!= 0)
190 git_buf_printf(out
, "%c\t%s%c %s%c\n", code
,
191 delta
->old_file
.path
, old_suffix
, delta
->new_file
.path
, new_suffix
);
192 else if (old_suffix
!= ' ')
193 git_buf_printf(out
, "%c\t%s%c\n", code
, delta
->old_file
.path
, old_suffix
);
195 git_buf_printf(out
, "%c\t%s\n", code
, delta
->old_file
.path
);
196 if (git_buf_oom(out
))
199 pi
->line
.origin
= GIT_DIFF_LINE_FILE_HDR
;
200 pi
->line
.content
= git_buf_cstr(out
);
201 pi
->line
.content_len
= git_buf_len(out
);
203 return pi
->print_cb(delta
, NULL
, &pi
->line
, pi
->payload
);
206 static int diff_print_one_raw(
207 const git_diff_delta
*delta
, float progress
, void *data
)
209 diff_print_info
*pi
= data
;
210 git_buf
*out
= pi
->buf
;
212 char code
= git_diff_status_char(delta
->status
);
213 char start_oid
[GIT_OID_HEXSZ
+1], end_oid
[GIT_OID_HEXSZ
+1];
215 GIT_UNUSED(progress
);
217 if ((pi
->flags
& GIT_DIFF_SHOW_UNMODIFIED
) == 0 && code
== ' ')
222 id_abbrev
= delta
->old_file
.mode
? delta
->old_file
.id_abbrev
:
223 delta
->new_file
.id_abbrev
;
225 if (pi
->oid_strlen
- 1 > id_abbrev
) {
226 giterr_set(GITERR_PATCH
,
227 "The patch input contains %d id characters (cannot print %d)",
228 id_abbrev
, pi
->oid_strlen
);
232 git_oid_tostr(start_oid
, pi
->oid_strlen
, &delta
->old_file
.id
);
233 git_oid_tostr(end_oid
, pi
->oid_strlen
, &delta
->new_file
.id
);
236 out
, (pi
->oid_strlen
<= GIT_OID_HEXSZ
) ?
237 ":%06o %06o %s... %s... %c" : ":%06o %06o %s %s %c",
238 delta
->old_file
.mode
, delta
->new_file
.mode
, start_oid
, end_oid
, code
);
240 if (delta
->similarity
> 0)
241 git_buf_printf(out
, "%03u", delta
->similarity
);
243 if (delta
->old_file
.path
!= delta
->new_file
.path
)
245 out
, "\t%s %s\n", delta
->old_file
.path
, delta
->new_file
.path
);
248 out
, "\t%s\n", delta
->old_file
.path
?
249 delta
->old_file
.path
: delta
->new_file
.path
);
251 if (git_buf_oom(out
))
254 pi
->line
.origin
= GIT_DIFF_LINE_FILE_HDR
;
255 pi
->line
.content
= git_buf_cstr(out
);
256 pi
->line
.content_len
= git_buf_len(out
);
258 return pi
->print_cb(delta
, NULL
, &pi
->line
, pi
->payload
);
261 static int diff_print_oid_range(
262 git_buf
*out
, const git_diff_delta
*delta
, int oid_strlen
)
264 char start_oid
[GIT_OID_HEXSZ
+1], end_oid
[GIT_OID_HEXSZ
+1];
266 if (delta
->old_file
.mode
&&
267 oid_strlen
- 1 > delta
->old_file
.id_abbrev
) {
268 giterr_set(GITERR_PATCH
,
269 "The patch input contains %d id characters (cannot print %d)",
270 delta
->old_file
.id_abbrev
, oid_strlen
);
274 if ((delta
->new_file
.mode
&&
275 oid_strlen
- 1 > delta
->new_file
.id_abbrev
)) {
276 giterr_set(GITERR_PATCH
,
277 "The patch input contains %d id characters (cannot print %d)",
278 delta
->new_file
.id_abbrev
, oid_strlen
);
282 git_oid_tostr(start_oid
, oid_strlen
, &delta
->old_file
.id
);
283 git_oid_tostr(end_oid
, oid_strlen
, &delta
->new_file
.id
);
285 if (delta
->old_file
.mode
== delta
->new_file
.mode
) {
286 git_buf_printf(out
, "index %s..%s %o\n",
287 start_oid
, end_oid
, delta
->old_file
.mode
);
289 if (delta
->old_file
.mode
== 0) {
290 git_buf_printf(out
, "new file mode %o\n", delta
->new_file
.mode
);
291 } else if (delta
->new_file
.mode
== 0) {
292 git_buf_printf(out
, "deleted file mode %o\n", delta
->old_file
.mode
);
294 git_buf_printf(out
, "old mode %o\n", delta
->old_file
.mode
);
295 git_buf_printf(out
, "new mode %o\n", delta
->new_file
.mode
);
297 git_buf_printf(out
, "index %s..%s\n", start_oid
, end_oid
);
300 return git_buf_oom(out
) ? -1 : 0;
303 static int diff_delta_format_with_paths(
305 const git_diff_delta
*delta
,
308 const char *template)
310 const char *oldpath
= delta
->old_file
.path
;
311 const char *newpath
= delta
->new_file
.path
;
313 if (git_oid_iszero(&delta
->old_file
.id
)) {
315 oldpath
= "/dev/null";
317 if (git_oid_iszero(&delta
->new_file
.id
)) {
319 newpath
= "/dev/null";
322 return git_buf_printf(out
, template, oldpfx
, oldpath
, newpfx
, newpath
);
325 int diff_delta_format_rename_header(
327 const git_diff_delta
*delta
)
329 if (delta
->similarity
> 100) {
330 giterr_set(GITERR_PATCH
, "invalid similarity %d", delta
->similarity
);
335 "similarity index %d%%\n"
339 delta
->old_file
.path
,
340 delta
->new_file
.path
);
342 return git_buf_oom(out
) ? -1 : 0;
345 int git_diff_delta__format_file_header(
347 const git_diff_delta
*delta
,
353 oldpfx
= DIFF_OLD_PREFIX_DEFAULT
;
355 newpfx
= DIFF_NEW_PREFIX_DEFAULT
;
357 oid_strlen
= GIT_ABBREV_DEFAULT
+ 1;
361 git_buf_printf(out
, "diff --git %s%s %s%s\n",
362 oldpfx
, delta
->old_file
.path
, newpfx
, delta
->new_file
.path
);
364 if (delta
->status
== GIT_DELTA_RENAMED
)
365 GITERR_CHECK_ERROR(diff_delta_format_rename_header(out
, delta
));
367 GITERR_CHECK_ERROR(diff_print_oid_range(out
, delta
, oid_strlen
));
369 if ((delta
->flags
& GIT_DIFF_FLAG_BINARY
) == 0)
370 diff_delta_format_with_paths(
371 out
, delta
, oldpfx
, newpfx
, "--- %s%s\n+++ %s%s\n");
373 return git_buf_oom(out
) ? -1 : 0;
376 static int format_binary(
378 git_diff_binary_t type
,
383 const char *typename
= type
== GIT_DIFF_BINARY_DELTA
?
385 const char *scan
, *end
;
387 git_buf_printf(pi
->buf
, "%s %" PRIuZ
"\n", typename
, inflatedlen
);
388 pi
->line
.num_lines
++;
390 for (scan
= data
, end
= data
+ datalen
; scan
< end
; ) {
391 size_t chunk_len
= end
- scan
;
396 git_buf_putc(pi
->buf
, (char)chunk_len
+ 'A' - 1);
398 git_buf_putc(pi
->buf
, (char)chunk_len
- 26 + 'a' - 1);
400 git_buf_encode_base85(pi
->buf
, scan
, chunk_len
);
401 git_buf_putc(pi
->buf
, '\n');
403 if (git_buf_oom(pi
->buf
))
407 pi
->line
.num_lines
++;
409 git_buf_putc(pi
->buf
, '\n');
414 static int diff_print_patch_file_binary(
415 diff_print_info
*pi
, git_diff_delta
*delta
,
416 const char *old_pfx
, const char *new_pfx
,
417 const git_diff_binary
*binary
)
419 size_t pre_binary_size
;
422 if ((pi
->flags
& GIT_DIFF_SHOW_BINARY
) == 0)
425 if (binary
->new_file
.datalen
== 0 && binary
->old_file
.datalen
== 0)
428 pre_binary_size
= pi
->buf
->size
;
429 git_buf_printf(pi
->buf
, "GIT binary patch\n");
430 pi
->line
.num_lines
++;
432 if ((error
= format_binary(pi
, binary
->new_file
.type
, binary
->new_file
.data
,
433 binary
->new_file
.datalen
, binary
->new_file
.inflatedlen
)) < 0 ||
434 (error
= format_binary(pi
, binary
->old_file
.type
, binary
->old_file
.data
,
435 binary
->old_file
.datalen
, binary
->old_file
.inflatedlen
)) < 0) {
437 if (error
== GIT_EBUFS
) {
439 git_buf_truncate(pi
->buf
, pre_binary_size
);
444 pi
->line
.num_lines
++;
448 pi
->line
.num_lines
= 1;
449 return diff_delta_format_with_paths(
450 pi
->buf
, delta
, old_pfx
, new_pfx
,
451 "Binary files %s%s and %s%s differ\n");
454 static int diff_print_patch_file(
455 const git_diff_delta
*delta
, float progress
, void *data
)
458 diff_print_info
*pi
= data
;
460 pi
->old_prefix
? pi
->old_prefix
: DIFF_OLD_PREFIX_DEFAULT
;
462 pi
->new_prefix
? pi
->new_prefix
: DIFF_NEW_PREFIX_DEFAULT
;
464 bool binary
= (delta
->flags
& GIT_DIFF_FLAG_BINARY
) ||
465 (pi
->flags
& GIT_DIFF_FORCE_BINARY
);
466 bool show_binary
= !!(pi
->flags
& GIT_DIFF_SHOW_BINARY
);
467 int oid_strlen
= binary
&& show_binary
?
468 GIT_OID_HEXSZ
+ 1 : pi
->oid_strlen
;
470 GIT_UNUSED(progress
);
472 if (S_ISDIR(delta
->new_file
.mode
) ||
473 delta
->status
== GIT_DELTA_UNMODIFIED
||
474 delta
->status
== GIT_DELTA_IGNORED
||
475 delta
->status
== GIT_DELTA_UNREADABLE
||
476 (delta
->status
== GIT_DELTA_UNTRACKED
&&
477 (pi
->flags
& GIT_DIFF_SHOW_UNTRACKED_CONTENT
) == 0))
480 if ((error
= git_diff_delta__format_file_header(
481 pi
->buf
, delta
, oldpfx
, newpfx
, oid_strlen
)) < 0)
484 pi
->line
.origin
= GIT_DIFF_LINE_FILE_HDR
;
485 pi
->line
.content
= git_buf_cstr(pi
->buf
);
486 pi
->line
.content_len
= git_buf_len(pi
->buf
);
488 return pi
->print_cb(delta
, NULL
, &pi
->line
, pi
->payload
);
491 static int diff_print_patch_binary(
492 const git_diff_delta
*delta
,
493 const git_diff_binary
*binary
,
496 diff_print_info
*pi
= data
;
497 const char *old_pfx
=
498 pi
->old_prefix
? pi
->old_prefix
: DIFF_OLD_PREFIX_DEFAULT
;
499 const char *new_pfx
=
500 pi
->new_prefix
? pi
->new_prefix
: DIFF_NEW_PREFIX_DEFAULT
;
503 git_buf_clear(pi
->buf
);
505 if ((error
= diff_print_patch_file_binary(
506 pi
, (git_diff_delta
*)delta
, old_pfx
, new_pfx
, binary
)) < 0)
509 pi
->line
.origin
= GIT_DIFF_LINE_BINARY
;
510 pi
->line
.content
= git_buf_cstr(pi
->buf
);
511 pi
->line
.content_len
= git_buf_len(pi
->buf
);
513 return pi
->print_cb(delta
, NULL
, &pi
->line
, pi
->payload
);
516 static int diff_print_patch_hunk(
517 const git_diff_delta
*d
,
518 const git_diff_hunk
*h
,
521 diff_print_info
*pi
= data
;
523 if (S_ISDIR(d
->new_file
.mode
))
526 pi
->line
.origin
= GIT_DIFF_LINE_HUNK_HDR
;
527 pi
->line
.content
= h
->header
;
528 pi
->line
.content_len
= h
->header_len
;
530 return pi
->print_cb(d
, h
, &pi
->line
, pi
->payload
);
533 static int diff_print_patch_line(
534 const git_diff_delta
*delta
,
535 const git_diff_hunk
*hunk
,
536 const git_diff_line
*line
,
539 diff_print_info
*pi
= data
;
541 if (S_ISDIR(delta
->new_file
.mode
))
544 return pi
->print_cb(delta
, hunk
, line
, pi
->payload
);
547 /* print a git_diff to an output callback */
550 git_diff_format_t format
,
551 git_diff_line_cb print_cb
,
555 git_buf buf
= GIT_BUF_INIT
;
557 git_diff_file_cb print_file
= NULL
;
558 git_diff_binary_cb print_binary
= NULL
;
559 git_diff_hunk_cb print_hunk
= NULL
;
560 git_diff_line_cb print_line
= NULL
;
563 case GIT_DIFF_FORMAT_PATCH
:
564 print_file
= diff_print_patch_file
;
565 print_binary
= diff_print_patch_binary
;
566 print_hunk
= diff_print_patch_hunk
;
567 print_line
= diff_print_patch_line
;
569 case GIT_DIFF_FORMAT_PATCH_HEADER
:
570 print_file
= diff_print_patch_file
;
572 case GIT_DIFF_FORMAT_RAW
:
573 print_file
= diff_print_one_raw
;
575 case GIT_DIFF_FORMAT_NAME_ONLY
:
576 print_file
= diff_print_one_name_only
;
578 case GIT_DIFF_FORMAT_NAME_STATUS
:
579 print_file
= diff_print_one_name_status
;
582 giterr_set(GITERR_INVALID
, "Unknown diff output format (%d)", format
);
586 if (!(error
= diff_print_info_init_fromdiff(
587 &pi
, &buf
, diff
, format
, print_cb
, payload
))) {
588 error
= git_diff_foreach(
589 diff
, print_file
, print_binary
, print_hunk
, print_line
, &pi
);
591 if (error
) /* make sure error message is set */
592 giterr_set_after_callback_function(error
, "git_diff_print");
600 int git_diff_print_callback__to_buf(
601 const git_diff_delta
*delta
,
602 const git_diff_hunk
*hunk
,
603 const git_diff_line
*line
,
606 git_buf
*output
= payload
;
607 GIT_UNUSED(delta
); GIT_UNUSED(hunk
);
610 giterr_set(GITERR_INVALID
, "Buffer pointer must be provided");
614 if (line
->origin
== GIT_DIFF_LINE_ADDITION
||
615 line
->origin
== GIT_DIFF_LINE_DELETION
||
616 line
->origin
== GIT_DIFF_LINE_CONTEXT
)
617 git_buf_putc(output
, line
->origin
);
619 return git_buf_put(output
, line
->content
, line
->content_len
);
622 int git_diff_print_callback__to_file_handle(
623 const git_diff_delta
*delta
,
624 const git_diff_hunk
*hunk
,
625 const git_diff_line
*line
,
628 FILE *fp
= payload
? payload
: stdout
;
630 GIT_UNUSED(delta
); GIT_UNUSED(hunk
);
632 if (line
->origin
== GIT_DIFF_LINE_CONTEXT
||
633 line
->origin
== GIT_DIFF_LINE_ADDITION
||
634 line
->origin
== GIT_DIFF_LINE_DELETION
)
635 fputc(line
->origin
, fp
);
636 fwrite(line
->content
, 1, line
->content_len
, fp
);
640 /* print a git_patch to an output callback */
643 git_diff_line_cb print_cb
,
647 git_buf temp
= GIT_BUF_INIT
;
650 assert(patch
&& print_cb
);
652 if (!(error
= diff_print_info_init_frompatch(
654 GIT_DIFF_FORMAT_PATCH
, print_cb
, payload
)))
656 error
= git_patch__invoke_callbacks(
658 diff_print_patch_file
, diff_print_patch_binary
,
659 diff_print_patch_hunk
, diff_print_patch_line
,
662 if (error
) /* make sure error message is set */
663 giterr_set_after_callback_function(error
, "git_patch_print");
671 /* print a git_patch to a git_buf */
672 int git_patch_to_buf(git_buf
*out
, git_patch
*patch
)
674 assert(out
&& patch
);
675 git_buf_sanitize(out
);
676 return git_patch_print(patch
, git_diff_print_callback__to_buf
, out
);