]> git.proxmox.com Git - libgit2.git/blob - src/diff_print.c
patch printing: include rename information
[libgit2.git] / src / diff_print.c
1 /*
2 * Copyright (C) the libgit2 contributors. All rights reserved.
3 *
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.
6 */
7 #include "common.h"
8 #include "diff.h"
9 #include "diff_file.h"
10 #include "patch_diff.h"
11 #include "fileops.h"
12 #include "zstream.h"
13 #include "blob.h"
14 #include "delta.h"
15 #include "git2/sys/diff.h"
16
17 typedef struct {
18 git_diff_format_t format;
19 git_diff_line_cb print_cb;
20 void *payload;
21
22 git_buf *buf;
23 git_diff_line line;
24
25 const char *old_prefix;
26 const char *new_prefix;
27 uint32_t flags;
28 int oid_strlen;
29
30 int (*strcomp)(const char *, const char *);
31 } diff_print_info;
32
33 static int diff_print_info_init__common(
34 diff_print_info *pi,
35 git_buf *out,
36 git_repository *repo,
37 git_diff_format_t format,
38 git_diff_line_cb cb,
39 void *payload)
40 {
41 pi->format = format;
42 pi->print_cb = cb;
43 pi->payload = payload;
44 pi->buf = out;
45
46 if (!pi->oid_strlen) {
47 if (!repo)
48 pi->oid_strlen = GIT_ABBREV_DEFAULT;
49 else if (git_repository__cvar(&pi->oid_strlen, repo, GIT_CVAR_ABBREV) < 0)
50 return -1;
51 }
52
53 pi->oid_strlen += 1; /* for NUL byte */
54
55 if (pi->oid_strlen > GIT_OID_HEXSZ + 1)
56 pi->oid_strlen = GIT_OID_HEXSZ + 1;
57
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;
62
63 return 0;
64 }
65
66 static int diff_print_info_init_fromdiff(
67 diff_print_info *pi,
68 git_buf *out,
69 git_diff *diff,
70 git_diff_format_t format,
71 git_diff_line_cb cb,
72 void *payload)
73 {
74 git_repository *repo = diff ? diff->repo : NULL;
75
76 memset(pi, 0, sizeof(diff_print_info));
77
78 if (diff) {
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;
83
84 pi->strcomp = diff->strcomp;
85 }
86
87 return diff_print_info_init__common(pi, out, repo, format, cb, payload);
88 }
89
90 static int diff_print_info_init_frompatch(
91 diff_print_info *pi,
92 git_buf *out,
93 git_patch *patch,
94 git_diff_format_t format,
95 git_diff_line_cb cb,
96 void *payload)
97 {
98 assert(patch);
99
100 memset(pi, 0, sizeof(diff_print_info));
101
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;
106
107 return diff_print_info_init__common(pi, out, patch->repo, format, cb, payload);
108 }
109
110 static char diff_pick_suffix(int mode)
111 {
112 if (S_ISDIR(mode))
113 return '/';
114 else if (GIT_PERMS_IS_EXEC(mode)) /* -V536 */
115 /* in git, modes are very regular, so we must have 0100755 mode */
116 return '*';
117 else
118 return ' ';
119 }
120
121 char git_diff_status_char(git_delta_t status)
122 {
123 char code;
124
125 switch (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;
135 }
136
137 return code;
138 }
139
140 static int diff_print_one_name_only(
141 const git_diff_delta *delta, float progress, void *data)
142 {
143 diff_print_info *pi = data;
144 git_buf *out = pi->buf;
145
146 GIT_UNUSED(progress);
147
148 if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 &&
149 delta->status == GIT_DELTA_UNMODIFIED)
150 return 0;
151
152 git_buf_clear(out);
153 git_buf_puts(out, delta->new_file.path);
154 git_buf_putc(out, '\n');
155 if (git_buf_oom(out))
156 return -1;
157
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);
161
162 return pi->print_cb(delta, NULL, &pi->line, pi->payload);
163 }
164
165 static int diff_print_one_name_status(
166 const git_diff_delta *delta, float progress, void *data)
167 {
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;
173
174 GIT_UNUSED(progress);
175
176 if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ')
177 return 0;
178
179 old_suffix = diff_pick_suffix(delta->old_file.mode);
180 new_suffix = diff_pick_suffix(delta->new_file.mode);
181
182 git_buf_clear(out);
183
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);
194 else
195 git_buf_printf(out, "%c\t%s\n", code, delta->old_file.path);
196 if (git_buf_oom(out))
197 return -1;
198
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);
202
203 return pi->print_cb(delta, NULL, &pi->line, pi->payload);
204 }
205
206 static int diff_print_one_raw(
207 const git_diff_delta *delta, float progress, void *data)
208 {
209 diff_print_info *pi = data;
210 git_buf *out = pi->buf;
211 int id_abbrev;
212 char code = git_diff_status_char(delta->status);
213 char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
214
215 GIT_UNUSED(progress);
216
217 if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ')
218 return 0;
219
220 git_buf_clear(out);
221
222 id_abbrev = delta->old_file.mode ? delta->old_file.id_abbrev :
223 delta->new_file.id_abbrev;
224
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);
229 return -1;
230 }
231
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);
234
235 git_buf_printf(
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);
239
240 if (delta->similarity > 0)
241 git_buf_printf(out, "%03u", delta->similarity);
242
243 if (delta->old_file.path != delta->new_file.path)
244 git_buf_printf(
245 out, "\t%s %s\n", delta->old_file.path, delta->new_file.path);
246 else
247 git_buf_printf(
248 out, "\t%s\n", delta->old_file.path ?
249 delta->old_file.path : delta->new_file.path);
250
251 if (git_buf_oom(out))
252 return -1;
253
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);
257
258 return pi->print_cb(delta, NULL, &pi->line, pi->payload);
259 }
260
261 static int diff_print_oid_range(
262 git_buf *out, const git_diff_delta *delta, int oid_strlen)
263 {
264 char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
265
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);
271 return -1;
272 }
273
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);
279 return -1;
280 }
281
282 git_oid_tostr(start_oid, oid_strlen, &delta->old_file.id);
283 git_oid_tostr(end_oid, oid_strlen, &delta->new_file.id);
284
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);
288 } else {
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);
293 } else {
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);
296 }
297 git_buf_printf(out, "index %s..%s\n", start_oid, end_oid);
298 }
299
300 return git_buf_oom(out) ? -1 : 0;
301 }
302
303 static int diff_delta_format_with_paths(
304 git_buf *out,
305 const git_diff_delta *delta,
306 const char *oldpfx,
307 const char *newpfx,
308 const char *template)
309 {
310 const char *oldpath = delta->old_file.path;
311 const char *newpath = delta->new_file.path;
312
313 if (git_oid_iszero(&delta->old_file.id)) {
314 oldpfx = "";
315 oldpath = "/dev/null";
316 }
317 if (git_oid_iszero(&delta->new_file.id)) {
318 newpfx = "";
319 newpath = "/dev/null";
320 }
321
322 return git_buf_printf(out, template, oldpfx, oldpath, newpfx, newpath);
323 }
324
325 int diff_delta_format_rename_header(
326 git_buf *out,
327 const git_diff_delta *delta)
328 {
329 if (delta->similarity > 100) {
330 giterr_set(GITERR_PATCH, "invalid similarity %d", delta->similarity);
331 return -1;
332 }
333
334 git_buf_printf(out,
335 "similarity index %d%%\n"
336 "rename from %s\n"
337 "rename to %s\n",
338 delta->similarity,
339 delta->old_file.path,
340 delta->new_file.path);
341
342 return git_buf_oom(out) ? -1 : 0;
343 }
344
345 int git_diff_delta__format_file_header(
346 git_buf *out,
347 const git_diff_delta *delta,
348 const char *oldpfx,
349 const char *newpfx,
350 int oid_strlen)
351 {
352 if (!oldpfx)
353 oldpfx = DIFF_OLD_PREFIX_DEFAULT;
354 if (!newpfx)
355 newpfx = DIFF_NEW_PREFIX_DEFAULT;
356 if (!oid_strlen)
357 oid_strlen = GIT_ABBREV_DEFAULT + 1;
358
359 git_buf_clear(out);
360
361 git_buf_printf(out, "diff --git %s%s %s%s\n",
362 oldpfx, delta->old_file.path, newpfx, delta->new_file.path);
363
364 if (delta->status == GIT_DELTA_RENAMED)
365 GITERR_CHECK_ERROR(diff_delta_format_rename_header(out, delta));
366
367 GITERR_CHECK_ERROR(diff_print_oid_range(out, delta, oid_strlen));
368
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");
372
373 return git_buf_oom(out) ? -1 : 0;
374 }
375
376 static int format_binary(
377 diff_print_info *pi,
378 git_diff_binary_t type,
379 const char *data,
380 size_t datalen,
381 size_t inflatedlen)
382 {
383 const char *typename = type == GIT_DIFF_BINARY_DELTA ?
384 "delta" : "literal";
385 const char *scan, *end;
386
387 git_buf_printf(pi->buf, "%s %" PRIuZ "\n", typename, inflatedlen);
388 pi->line.num_lines++;
389
390 for (scan = data, end = data + datalen; scan < end; ) {
391 size_t chunk_len = end - scan;
392 if (chunk_len > 52)
393 chunk_len = 52;
394
395 if (chunk_len <= 26)
396 git_buf_putc(pi->buf, (char)chunk_len + 'A' - 1);
397 else
398 git_buf_putc(pi->buf, (char)chunk_len - 26 + 'a' - 1);
399
400 git_buf_encode_base85(pi->buf, scan, chunk_len);
401 git_buf_putc(pi->buf, '\n');
402
403 if (git_buf_oom(pi->buf))
404 return -1;
405
406 scan += chunk_len;
407 pi->line.num_lines++;
408 }
409 git_buf_putc(pi->buf, '\n');
410
411 return 0;
412 }
413
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)
418 {
419 size_t pre_binary_size;
420 int error;
421
422 if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0)
423 goto noshow;
424
425 if (binary->new_file.datalen == 0 && binary->old_file.datalen == 0)
426 return 0;
427
428 pre_binary_size = pi->buf->size;
429 git_buf_printf(pi->buf, "GIT binary patch\n");
430 pi->line.num_lines++;
431
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) {
436
437 if (error == GIT_EBUFS) {
438 giterr_clear();
439 git_buf_truncate(pi->buf, pre_binary_size);
440 goto noshow;
441 }
442 }
443
444 pi->line.num_lines++;
445 return error;
446
447 noshow:
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");
452 }
453
454 static int diff_print_patch_file(
455 const git_diff_delta *delta, float progress, void *data)
456 {
457 int error;
458 diff_print_info *pi = data;
459 const char *oldpfx =
460 pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT;
461 const char *newpfx =
462 pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT;
463
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;
469
470 GIT_UNUSED(progress);
471
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))
478 return 0;
479
480 if ((error = git_diff_delta__format_file_header(
481 pi->buf, delta, oldpfx, newpfx, oid_strlen)) < 0)
482 return error;
483
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);
487
488 return pi->print_cb(delta, NULL, &pi->line, pi->payload);
489 }
490
491 static int diff_print_patch_binary(
492 const git_diff_delta *delta,
493 const git_diff_binary *binary,
494 void *data)
495 {
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;
501 int error;
502
503 git_buf_clear(pi->buf);
504
505 if ((error = diff_print_patch_file_binary(
506 pi, (git_diff_delta *)delta, old_pfx, new_pfx, binary)) < 0)
507 return error;
508
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);
512
513 return pi->print_cb(delta, NULL, &pi->line, pi->payload);
514 }
515
516 static int diff_print_patch_hunk(
517 const git_diff_delta *d,
518 const git_diff_hunk *h,
519 void *data)
520 {
521 diff_print_info *pi = data;
522
523 if (S_ISDIR(d->new_file.mode))
524 return 0;
525
526 pi->line.origin = GIT_DIFF_LINE_HUNK_HDR;
527 pi->line.content = h->header;
528 pi->line.content_len = h->header_len;
529
530 return pi->print_cb(d, h, &pi->line, pi->payload);
531 }
532
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,
537 void *data)
538 {
539 diff_print_info *pi = data;
540
541 if (S_ISDIR(delta->new_file.mode))
542 return 0;
543
544 return pi->print_cb(delta, hunk, line, pi->payload);
545 }
546
547 /* print a git_diff to an output callback */
548 int git_diff_print(
549 git_diff *diff,
550 git_diff_format_t format,
551 git_diff_line_cb print_cb,
552 void *payload)
553 {
554 int error;
555 git_buf buf = GIT_BUF_INIT;
556 diff_print_info pi;
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;
561
562 switch (format) {
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;
568 break;
569 case GIT_DIFF_FORMAT_PATCH_HEADER:
570 print_file = diff_print_patch_file;
571 break;
572 case GIT_DIFF_FORMAT_RAW:
573 print_file = diff_print_one_raw;
574 break;
575 case GIT_DIFF_FORMAT_NAME_ONLY:
576 print_file = diff_print_one_name_only;
577 break;
578 case GIT_DIFF_FORMAT_NAME_STATUS:
579 print_file = diff_print_one_name_status;
580 break;
581 default:
582 giterr_set(GITERR_INVALID, "Unknown diff output format (%d)", format);
583 return -1;
584 }
585
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);
590
591 if (error) /* make sure error message is set */
592 giterr_set_after_callback_function(error, "git_diff_print");
593 }
594
595 git_buf_free(&buf);
596
597 return error;
598 }
599
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,
604 void *payload)
605 {
606 git_buf *output = payload;
607 GIT_UNUSED(delta); GIT_UNUSED(hunk);
608
609 if (!output) {
610 giterr_set(GITERR_INVALID, "Buffer pointer must be provided");
611 return -1;
612 }
613
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);
618
619 return git_buf_put(output, line->content, line->content_len);
620 }
621
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,
626 void *payload)
627 {
628 FILE *fp = payload ? payload : stdout;
629
630 GIT_UNUSED(delta); GIT_UNUSED(hunk);
631
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);
637 return 0;
638 }
639
640 /* print a git_patch to an output callback */
641 int git_patch_print(
642 git_patch *patch,
643 git_diff_line_cb print_cb,
644 void *payload)
645 {
646 int error;
647 git_buf temp = GIT_BUF_INIT;
648 diff_print_info pi;
649
650 assert(patch && print_cb);
651
652 if (!(error = diff_print_info_init_frompatch(
653 &pi, &temp, patch,
654 GIT_DIFF_FORMAT_PATCH, print_cb, payload)))
655 {
656 error = git_patch__invoke_callbacks(
657 patch,
658 diff_print_patch_file, diff_print_patch_binary,
659 diff_print_patch_hunk, diff_print_patch_line,
660 &pi);
661
662 if (error) /* make sure error message is set */
663 giterr_set_after_callback_function(error, "git_patch_print");
664 }
665
666 git_buf_free(&temp);
667
668 return error;
669 }
670
671 /* print a git_patch to a git_buf */
672 int git_patch_to_buf(git_buf *out, git_patch *patch)
673 {
674 assert(out && patch);
675 git_buf_sanitize(out);
676 return git_patch_print(patch, git_diff_print_callback__to_buf, out);
677 }