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.
9 #include "diff_patch.h"
14 #include "git2/sys/diff.h"
18 git_diff_format_t format
;
19 git_diff_line_cb print_cb
;
27 content_allocated
: 1;
28 git_diff_file_content
*ofile
;
29 git_diff_file_content
*nfile
;
32 static int diff_print_info_init__common(
36 git_diff_format_t format
,
42 pi
->payload
= payload
;
45 if (!pi
->oid_strlen
) {
47 pi
->oid_strlen
= GIT_ABBREV_DEFAULT
;
48 else if (git_repository__cvar(&pi
->oid_strlen
, repo
, GIT_CVAR_ABBREV
) < 0)
52 pi
->oid_strlen
+= 1; /* for NUL byte */
54 if (pi
->oid_strlen
> GIT_OID_HEXSZ
+ 1)
55 pi
->oid_strlen
= GIT_OID_HEXSZ
+ 1;
57 memset(&pi
->line
, 0, sizeof(pi
->line
));
58 pi
->line
.old_lineno
= -1;
59 pi
->line
.new_lineno
= -1;
60 pi
->line
.num_lines
= 1;
65 static int diff_print_info_init_fromdiff(
69 git_diff_format_t format
,
73 git_repository
*repo
= diff
? diff
->repo
: NULL
;
75 memset(pi
, 0, sizeof(diff_print_info
));
80 pi
->flags
= diff
->opts
.flags
;
81 pi
->oid_strlen
= diff
->opts
.id_abbrev
;
84 return diff_print_info_init__common(pi
, out
, repo
, format
, cb
, payload
);
87 static int diff_print_info_init_frompatch(
91 git_diff_format_t format
,
95 git_repository
*repo
= patch
&& patch
->diff
? patch
->diff
->repo
: NULL
;
97 memset(pi
, 0, sizeof(diff_print_info
));
99 pi
->diff
= patch
->diff
;
101 pi
->flags
= patch
->diff_opts
.flags
;
102 pi
->oid_strlen
= patch
->diff_opts
.id_abbrev
;
104 pi
->content_loaded
= 1;
105 pi
->ofile
= &patch
->ofile
;
106 pi
->nfile
= &patch
->nfile
;
108 return diff_print_info_init__common(pi
, out
, repo
, format
, cb
, payload
);
111 static char diff_pick_suffix(int mode
)
115 else if (GIT_PERMS_IS_EXEC(mode
)) /* -V536 */
116 /* in git, modes are very regular, so we must have 0100755 mode */
122 char git_diff_status_char(git_delta_t status
)
127 case GIT_DELTA_ADDED
: code
= 'A'; break;
128 case GIT_DELTA_DELETED
: code
= 'D'; break;
129 case GIT_DELTA_MODIFIED
: code
= 'M'; break;
130 case GIT_DELTA_RENAMED
: code
= 'R'; break;
131 case GIT_DELTA_COPIED
: code
= 'C'; break;
132 case GIT_DELTA_IGNORED
: code
= 'I'; break;
133 case GIT_DELTA_UNTRACKED
: code
= '?'; break;
134 case GIT_DELTA_UNREADABLE
: code
= 'X'; break;
135 default: code
= ' '; break;
141 static int diff_print_one_name_only(
142 const git_diff_delta
*delta
, float progress
, void *data
)
144 diff_print_info
*pi
= data
;
145 git_buf
*out
= pi
->buf
;
147 GIT_UNUSED(progress
);
149 if ((pi
->flags
& GIT_DIFF_SHOW_UNMODIFIED
) == 0 &&
150 delta
->status
== GIT_DELTA_UNMODIFIED
)
154 git_buf_puts(out
, delta
->new_file
.path
);
155 git_buf_putc(out
, '\n');
156 if (git_buf_oom(out
))
159 pi
->line
.origin
= GIT_DIFF_LINE_FILE_HDR
;
160 pi
->line
.content
= git_buf_cstr(out
);
161 pi
->line
.content_len
= git_buf_len(out
);
163 return pi
->print_cb(delta
, NULL
, &pi
->line
, pi
->payload
);
166 static int diff_print_one_name_status(
167 const git_diff_delta
*delta
, float progress
, void *data
)
169 diff_print_info
*pi
= data
;
170 git_buf
*out
= pi
->buf
;
171 char old_suffix
, new_suffix
, code
= git_diff_status_char(delta
->status
);
172 int (*strcomp
)(const char *, const char *) =
173 pi
->diff
? pi
->diff
->strcomp
: git__strcmp
;
175 GIT_UNUSED(progress
);
177 if ((pi
->flags
& GIT_DIFF_SHOW_UNMODIFIED
) == 0 && code
== ' ')
180 old_suffix
= diff_pick_suffix(delta
->old_file
.mode
);
181 new_suffix
= diff_pick_suffix(delta
->new_file
.mode
);
185 if (delta
->old_file
.path
!= delta
->new_file
.path
&&
186 strcomp(delta
->old_file
.path
,delta
->new_file
.path
) != 0)
187 git_buf_printf(out
, "%c\t%s%c %s%c\n", code
,
188 delta
->old_file
.path
, old_suffix
, delta
->new_file
.path
, new_suffix
);
189 else if (delta
->old_file
.mode
!= delta
->new_file
.mode
&&
190 delta
->old_file
.mode
!= 0 && delta
->new_file
.mode
!= 0)
191 git_buf_printf(out
, "%c\t%s%c %s%c\n", code
,
192 delta
->old_file
.path
, old_suffix
, delta
->new_file
.path
, new_suffix
);
193 else if (old_suffix
!= ' ')
194 git_buf_printf(out
, "%c\t%s%c\n", code
, delta
->old_file
.path
, old_suffix
);
196 git_buf_printf(out
, "%c\t%s\n", code
, delta
->old_file
.path
);
197 if (git_buf_oom(out
))
200 pi
->line
.origin
= GIT_DIFF_LINE_FILE_HDR
;
201 pi
->line
.content
= git_buf_cstr(out
);
202 pi
->line
.content_len
= git_buf_len(out
);
204 return pi
->print_cb(delta
, NULL
, &pi
->line
, pi
->payload
);
207 static int diff_print_one_raw(
208 const git_diff_delta
*delta
, float progress
, void *data
)
210 diff_print_info
*pi
= data
;
211 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 git_oid_tostr(start_oid
, pi
->oid_strlen
, &delta
->old_file
.id
);
223 git_oid_tostr(end_oid
, pi
->oid_strlen
, &delta
->new_file
.id
);
226 out
, (pi
->oid_strlen
<= GIT_OID_HEXSZ
) ?
227 ":%06o %06o %s... %s... %c" : ":%06o %06o %s %s %c",
228 delta
->old_file
.mode
, delta
->new_file
.mode
, start_oid
, end_oid
, code
);
230 if (delta
->similarity
> 0)
231 git_buf_printf(out
, "%03u", delta
->similarity
);
233 if (delta
->old_file
.path
!= delta
->new_file
.path
)
235 out
, "\t%s %s\n", delta
->old_file
.path
, delta
->new_file
.path
);
238 out
, "\t%s\n", delta
->old_file
.path
?
239 delta
->old_file
.path
: delta
->new_file
.path
);
241 if (git_buf_oom(out
))
244 pi
->line
.origin
= GIT_DIFF_LINE_FILE_HDR
;
245 pi
->line
.content
= git_buf_cstr(out
);
246 pi
->line
.content_len
= git_buf_len(out
);
248 return pi
->print_cb(delta
, NULL
, &pi
->line
, pi
->payload
);
251 static int diff_print_oid_range(
252 git_buf
*out
, const git_diff_delta
*delta
, int oid_strlen
)
254 char start_oid
[GIT_OID_HEXSZ
+1], end_oid
[GIT_OID_HEXSZ
+1];
256 git_oid_tostr(start_oid
, oid_strlen
, &delta
->old_file
.id
);
257 git_oid_tostr(end_oid
, oid_strlen
, &delta
->new_file
.id
);
259 /* TODO: Match git diff more closely */
260 if (delta
->old_file
.mode
== delta
->new_file
.mode
) {
261 git_buf_printf(out
, "index %s..%s %o\n",
262 start_oid
, end_oid
, delta
->old_file
.mode
);
264 if (delta
->old_file
.mode
== 0) {
265 git_buf_printf(out
, "new file mode %o\n", delta
->new_file
.mode
);
266 } else if (delta
->new_file
.mode
== 0) {
267 git_buf_printf(out
, "deleted file mode %o\n", delta
->old_file
.mode
);
269 git_buf_printf(out
, "old mode %o\n", delta
->old_file
.mode
);
270 git_buf_printf(out
, "new mode %o\n", delta
->new_file
.mode
);
272 git_buf_printf(out
, "index %s..%s\n", start_oid
, end_oid
);
275 return git_buf_oom(out
) ? -1 : 0;
278 static int diff_delta_format_with_paths(
280 const git_diff_delta
*delta
,
283 const char *template)
285 const char *oldpath
= delta
->old_file
.path
;
286 const char *newpath
= delta
->new_file
.path
;
288 if (git_oid_iszero(&delta
->old_file
.id
)) {
290 oldpath
= "/dev/null";
292 if (git_oid_iszero(&delta
->new_file
.id
)) {
294 newpath
= "/dev/null";
297 return git_buf_printf(out
, template, oldpfx
, oldpath
, newpfx
, newpath
);
300 int git_diff_delta__format_file_header(
302 const git_diff_delta
*delta
,
308 oldpfx
= DIFF_OLD_PREFIX_DEFAULT
;
310 newpfx
= DIFF_NEW_PREFIX_DEFAULT
;
312 oid_strlen
= GIT_ABBREV_DEFAULT
+ 1;
316 git_buf_printf(out
, "diff --git %s%s %s%s\n",
317 oldpfx
, delta
->old_file
.path
, newpfx
, delta
->new_file
.path
);
319 GITERR_CHECK_ERROR(diff_print_oid_range(out
, delta
, oid_strlen
));
321 if ((delta
->flags
& GIT_DIFF_FLAG_BINARY
) == 0)
322 diff_delta_format_with_paths(
323 out
, delta
, oldpfx
, newpfx
, "--- %s%s\n+++ %s%s\n");
325 return git_buf_oom(out
) ? -1 : 0;
328 static int format_binary(
330 git_diff_binary_t type
,
335 const char *typename
= type
== GIT_DIFF_BINARY_DELTA
?
337 const char *scan
, *end
;
339 git_buf_printf(pi
->buf
, "%s %lu\n", typename
, inflatedlen
);
340 pi
->line
.num_lines
++;
342 for (scan
= data
, end
= data
+ datalen
; scan
< end
; ) {
343 size_t chunk_len
= end
- scan
;
348 git_buf_putc(pi
->buf
, (char)chunk_len
+ 'A' - 1);
350 git_buf_putc(pi
->buf
, (char)chunk_len
- 26 + 'a' - 1);
352 git_buf_encode_base85(pi
->buf
, scan
, chunk_len
);
353 git_buf_putc(pi
->buf
, '\n');
355 if (git_buf_oom(pi
->buf
))
359 pi
->line
.num_lines
++;
365 static int diff_print_load_content(
367 git_diff_delta
*delta
)
369 git_diff_file_content
*ofile
, *nfile
;
374 ofile
= git__calloc(1, sizeof(git_diff_file_content
));
375 nfile
= git__calloc(1, sizeof(git_diff_file_content
));
377 GITERR_CHECK_ALLOC(ofile
);
378 GITERR_CHECK_ALLOC(nfile
);
380 if ((error
= git_diff_file_content__init_from_diff(
381 ofile
, pi
->diff
, delta
, true)) < 0 ||
382 (error
= git_diff_file_content__init_from_diff(
383 nfile
, pi
->diff
, delta
, true)) < 0) {
390 pi
->content_loaded
= 1;
391 pi
->content_allocated
= 1;
398 static int diff_print_patch_file_binary(
399 diff_print_info
*pi
, git_diff_delta
*delta
,
400 const char *old_pfx
, const char *new_pfx
,
401 const git_diff_binary
*binary
)
403 size_t pre_binary_size
;
406 if ((pi
->flags
& GIT_DIFF_SHOW_BINARY
) == 0)
409 if (!pi
->content_loaded
&&
410 (error
= diff_print_load_content(pi
, delta
)) < 0)
413 pre_binary_size
= pi
->buf
->size
;
414 git_buf_printf(pi
->buf
, "GIT binary patch\n");
415 pi
->line
.num_lines
++;
417 if ((error
= format_binary(pi
, binary
->new_file
.type
, binary
->new_file
.data
,
418 binary
->new_file
.datalen
, binary
->new_file
.inflatedlen
)) < 0 ||
419 (error
= git_buf_putc(pi
->buf
, '\n')) < 0 ||
420 (error
= format_binary(pi
, binary
->old_file
.type
, binary
->old_file
.data
,
421 binary
->old_file
.datalen
, binary
->old_file
.inflatedlen
)) < 0) {
423 if (error
== GIT_EBUFS
) {
425 git_buf_truncate(pi
->buf
, pre_binary_size
);
430 pi
->line
.num_lines
++;
434 pi
->line
.num_lines
= 1;
435 return diff_delta_format_with_paths(
436 pi
->buf
, delta
, old_pfx
, new_pfx
,
437 "Binary files %s%s and %s%s differ\n");
440 static int diff_print_patch_file(
441 const git_diff_delta
*delta
, float progress
, void *data
)
444 diff_print_info
*pi
= data
;
446 pi
->diff
? pi
->diff
->opts
.old_prefix
: DIFF_OLD_PREFIX_DEFAULT
;
448 pi
->diff
? pi
->diff
->opts
.new_prefix
: DIFF_NEW_PREFIX_DEFAULT
;
450 bool binary
= (delta
->flags
& GIT_DIFF_FLAG_BINARY
) ||
451 (pi
->flags
& GIT_DIFF_FORCE_BINARY
);
452 bool show_binary
= !!(pi
->flags
& GIT_DIFF_SHOW_BINARY
);
453 int oid_strlen
= binary
&& show_binary
?
454 GIT_OID_HEXSZ
+ 1 : pi
->oid_strlen
;
456 GIT_UNUSED(progress
);
458 if (S_ISDIR(delta
->new_file
.mode
) ||
459 delta
->status
== GIT_DELTA_UNMODIFIED
||
460 delta
->status
== GIT_DELTA_IGNORED
||
461 delta
->status
== GIT_DELTA_UNREADABLE
||
462 (delta
->status
== GIT_DELTA_UNTRACKED
&&
463 (pi
->flags
& GIT_DIFF_SHOW_UNTRACKED_CONTENT
) == 0))
466 if ((error
= git_diff_delta__format_file_header(
467 pi
->buf
, delta
, oldpfx
, newpfx
, oid_strlen
)) < 0)
470 pi
->line
.origin
= GIT_DIFF_LINE_FILE_HDR
;
471 pi
->line
.content
= git_buf_cstr(pi
->buf
);
472 pi
->line
.content_len
= git_buf_len(pi
->buf
);
474 return pi
->print_cb(delta
, NULL
, &pi
->line
, pi
->payload
);
477 static int diff_print_patch_binary(
478 const git_diff_delta
*delta
,
479 const git_diff_binary
*binary
,
482 diff_print_info
*pi
= data
;
483 const char *old_pfx
=
484 pi
->diff
? pi
->diff
->opts
.old_prefix
: DIFF_OLD_PREFIX_DEFAULT
;
485 const char *new_pfx
=
486 pi
->diff
? pi
->diff
->opts
.new_prefix
: DIFF_NEW_PREFIX_DEFAULT
;
489 git_buf_clear(pi
->buf
);
491 if ((error
= diff_print_patch_file_binary(
492 pi
, (git_diff_delta
*)delta
, old_pfx
, new_pfx
, binary
)) < 0)
495 pi
->line
.origin
= GIT_DIFF_LINE_BINARY
;
496 pi
->line
.content
= git_buf_cstr(pi
->buf
);
497 pi
->line
.content_len
= git_buf_len(pi
->buf
);
499 return pi
->print_cb(delta
, NULL
, &pi
->line
, pi
->payload
);
502 static int diff_print_patch_hunk(
503 const git_diff_delta
*d
,
504 const git_diff_hunk
*h
,
507 diff_print_info
*pi
= data
;
509 if (S_ISDIR(d
->new_file
.mode
))
512 pi
->line
.origin
= GIT_DIFF_LINE_HUNK_HDR
;
513 pi
->line
.content
= h
->header
;
514 pi
->line
.content_len
= h
->header_len
;
516 return pi
->print_cb(d
, h
, &pi
->line
, pi
->payload
);
519 static int diff_print_patch_line(
520 const git_diff_delta
*delta
,
521 const git_diff_hunk
*hunk
,
522 const git_diff_line
*line
,
525 diff_print_info
*pi
= data
;
527 if (S_ISDIR(delta
->new_file
.mode
))
530 return pi
->print_cb(delta
, hunk
, line
, pi
->payload
);
533 /* print a git_diff to an output callback */
536 git_diff_format_t format
,
537 git_diff_line_cb print_cb
,
541 git_buf buf
= GIT_BUF_INIT
;
543 git_diff_file_cb print_file
= NULL
;
544 git_diff_binary_cb print_binary
= NULL
;
545 git_diff_hunk_cb print_hunk
= NULL
;
546 git_diff_line_cb print_line
= NULL
;
549 case GIT_DIFF_FORMAT_PATCH
:
550 print_file
= diff_print_patch_file
;
551 print_binary
= diff_print_patch_binary
;
552 print_hunk
= diff_print_patch_hunk
;
553 print_line
= diff_print_patch_line
;
555 case GIT_DIFF_FORMAT_PATCH_HEADER
:
556 print_file
= diff_print_patch_file
;
558 case GIT_DIFF_FORMAT_RAW
:
559 print_file
= diff_print_one_raw
;
561 case GIT_DIFF_FORMAT_NAME_ONLY
:
562 print_file
= diff_print_one_name_only
;
564 case GIT_DIFF_FORMAT_NAME_STATUS
:
565 print_file
= diff_print_one_name_status
;
568 giterr_set(GITERR_INVALID
, "Unknown diff output format (%d)", format
);
572 if (!(error
= diff_print_info_init_fromdiff(
573 &pi
, &buf
, diff
, format
, print_cb
, payload
))) {
574 error
= git_diff_foreach(
575 diff
, print_file
, print_binary
, print_hunk
, print_line
, &pi
);
577 if (error
) /* make sure error message is set */
578 giterr_set_after_callback_function(error
, "git_diff_print");
586 /* print a git_patch to an output callback */
589 git_diff_line_cb print_cb
,
593 git_buf temp
= GIT_BUF_INIT
;
596 assert(patch
&& print_cb
);
598 if (!(error
= diff_print_info_init_frompatch(
600 GIT_DIFF_FORMAT_PATCH
, print_cb
, payload
)))
602 error
= git_patch__invoke_callbacks(
603 patch
, diff_print_patch_file
, diff_print_patch_binary
,
604 diff_print_patch_hunk
, diff_print_patch_line
, &pi
);
606 if (error
) /* make sure error message is set */
607 giterr_set_after_callback_function(error
, "git_patch_print");
615 int git_diff_print_callback__to_buf(
616 const git_diff_delta
*delta
,
617 const git_diff_hunk
*hunk
,
618 const git_diff_line
*line
,
621 git_buf
*output
= payload
;
622 GIT_UNUSED(delta
); GIT_UNUSED(hunk
);
625 giterr_set(GITERR_INVALID
, "Buffer pointer must be provided");
629 if (line
->origin
== GIT_DIFF_LINE_ADDITION
||
630 line
->origin
== GIT_DIFF_LINE_DELETION
||
631 line
->origin
== GIT_DIFF_LINE_CONTEXT
)
632 git_buf_putc(output
, line
->origin
);
634 return git_buf_put(output
, line
->content
, line
->content_len
);
637 int git_diff_print_callback__to_file_handle(
638 const git_diff_delta
*delta
,
639 const git_diff_hunk
*hunk
,
640 const git_diff_line
*line
,
643 FILE *fp
= payload
? payload
: stdout
;
645 GIT_UNUSED(delta
); GIT_UNUSED(hunk
);
647 if (line
->origin
== GIT_DIFF_LINE_CONTEXT
||
648 line
->origin
== GIT_DIFF_LINE_ADDITION
||
649 line
->origin
== GIT_DIFF_LINE_DELETION
)
650 fputc(line
->origin
, fp
);
651 fwrite(line
->content
, 1, line
->content_len
, fp
);
655 /* print a git_patch to a git_buf */
656 int git_patch_to_buf(git_buf
*out
, git_patch
*patch
)
658 assert(out
&& patch
);
659 git_buf_sanitize(out
);
660 return git_patch_print(patch
, git_diff_print_callback__to_buf
, out
);