]> git.proxmox.com Git - libgit2.git/blob - src/diff_print.c
diff: include oid length in deltas
[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 /* TODO: Match git diff more closely */
286 if (delta->old_file.mode == delta->new_file.mode) {
287 git_buf_printf(out, "index %s..%s %o\n",
288 start_oid, end_oid, delta->old_file.mode);
289 } else {
290 if (delta->old_file.mode == 0) {
291 git_buf_printf(out, "new file mode %o\n", delta->new_file.mode);
292 } else if (delta->new_file.mode == 0) {
293 git_buf_printf(out, "deleted file mode %o\n", delta->old_file.mode);
294 } else {
295 git_buf_printf(out, "old mode %o\n", delta->old_file.mode);
296 git_buf_printf(out, "new mode %o\n", delta->new_file.mode);
297 }
298 git_buf_printf(out, "index %s..%s\n", start_oid, end_oid);
299 }
300
301 return git_buf_oom(out) ? -1 : 0;
302 }
303
304 static int diff_delta_format_with_paths(
305 git_buf *out,
306 const git_diff_delta *delta,
307 const char *oldpfx,
308 const char *newpfx,
309 const char *template)
310 {
311 const char *oldpath = delta->old_file.path;
312 const char *newpath = delta->new_file.path;
313
314 if (git_oid_iszero(&delta->old_file.id)) {
315 oldpfx = "";
316 oldpath = "/dev/null";
317 }
318 if (git_oid_iszero(&delta->new_file.id)) {
319 newpfx = "";
320 newpath = "/dev/null";
321 }
322
323 return git_buf_printf(out, template, oldpfx, oldpath, newpfx, newpath);
324 }
325
326 int git_diff_delta__format_file_header(
327 git_buf *out,
328 const git_diff_delta *delta,
329 const char *oldpfx,
330 const char *newpfx,
331 int oid_strlen)
332 {
333 if (!oldpfx)
334 oldpfx = DIFF_OLD_PREFIX_DEFAULT;
335 if (!newpfx)
336 newpfx = DIFF_NEW_PREFIX_DEFAULT;
337 if (!oid_strlen)
338 oid_strlen = GIT_ABBREV_DEFAULT + 1;
339
340 git_buf_clear(out);
341
342 git_buf_printf(out, "diff --git %s%s %s%s\n",
343 oldpfx, delta->old_file.path, newpfx, delta->new_file.path);
344
345 GITERR_CHECK_ERROR(diff_print_oid_range(out, delta, oid_strlen));
346
347 if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0)
348 diff_delta_format_with_paths(
349 out, delta, oldpfx, newpfx, "--- %s%s\n+++ %s%s\n");
350
351 return git_buf_oom(out) ? -1 : 0;
352 }
353
354 static int format_binary(
355 diff_print_info *pi,
356 git_diff_binary_t type,
357 const char *data,
358 size_t datalen,
359 size_t inflatedlen)
360 {
361 const char *typename = type == GIT_DIFF_BINARY_DELTA ?
362 "delta" : "literal";
363 const char *scan, *end;
364
365 git_buf_printf(pi->buf, "%s %" PRIuZ "\n", typename, inflatedlen);
366 pi->line.num_lines++;
367
368 for (scan = data, end = data + datalen; scan < end; ) {
369 size_t chunk_len = end - scan;
370 if (chunk_len > 52)
371 chunk_len = 52;
372
373 if (chunk_len <= 26)
374 git_buf_putc(pi->buf, (char)chunk_len + 'A' - 1);
375 else
376 git_buf_putc(pi->buf, (char)chunk_len - 26 + 'a' - 1);
377
378 git_buf_encode_base85(pi->buf, scan, chunk_len);
379 git_buf_putc(pi->buf, '\n');
380
381 if (git_buf_oom(pi->buf))
382 return -1;
383
384 scan += chunk_len;
385 pi->line.num_lines++;
386 }
387 git_buf_putc(pi->buf, '\n');
388
389 return 0;
390 }
391
392 static int diff_print_patch_file_binary(
393 diff_print_info *pi, git_diff_delta *delta,
394 const char *old_pfx, const char *new_pfx,
395 const git_diff_binary *binary)
396 {
397 size_t pre_binary_size;
398 int error;
399
400 if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0)
401 goto noshow;
402
403 if (binary->new_file.datalen == 0 && binary->old_file.datalen == 0)
404 return 0;
405
406 pre_binary_size = pi->buf->size;
407 git_buf_printf(pi->buf, "GIT binary patch\n");
408 pi->line.num_lines++;
409
410 if ((error = format_binary(pi, binary->new_file.type, binary->new_file.data,
411 binary->new_file.datalen, binary->new_file.inflatedlen)) < 0 ||
412 (error = format_binary(pi, binary->old_file.type, binary->old_file.data,
413 binary->old_file.datalen, binary->old_file.inflatedlen)) < 0) {
414
415 if (error == GIT_EBUFS) {
416 giterr_clear();
417 git_buf_truncate(pi->buf, pre_binary_size);
418 goto noshow;
419 }
420 }
421
422 pi->line.num_lines++;
423 return error;
424
425 noshow:
426 pi->line.num_lines = 1;
427 return diff_delta_format_with_paths(
428 pi->buf, delta, old_pfx, new_pfx,
429 "Binary files %s%s and %s%s differ\n");
430 }
431
432 static int diff_print_patch_file(
433 const git_diff_delta *delta, float progress, void *data)
434 {
435 int error;
436 diff_print_info *pi = data;
437 const char *oldpfx =
438 pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT;
439 const char *newpfx =
440 pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT;
441
442 bool binary = (delta->flags & GIT_DIFF_FLAG_BINARY) ||
443 (pi->flags & GIT_DIFF_FORCE_BINARY);
444 bool show_binary = !!(pi->flags & GIT_DIFF_SHOW_BINARY);
445 int oid_strlen = binary && show_binary ?
446 GIT_OID_HEXSZ + 1 : pi->oid_strlen;
447
448 GIT_UNUSED(progress);
449
450 if (S_ISDIR(delta->new_file.mode) ||
451 delta->status == GIT_DELTA_UNMODIFIED ||
452 delta->status == GIT_DELTA_IGNORED ||
453 delta->status == GIT_DELTA_UNREADABLE ||
454 (delta->status == GIT_DELTA_UNTRACKED &&
455 (pi->flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) == 0))
456 return 0;
457
458 if ((error = git_diff_delta__format_file_header(
459 pi->buf, delta, oldpfx, newpfx, oid_strlen)) < 0)
460 return error;
461
462 pi->line.origin = GIT_DIFF_LINE_FILE_HDR;
463 pi->line.content = git_buf_cstr(pi->buf);
464 pi->line.content_len = git_buf_len(pi->buf);
465
466 return pi->print_cb(delta, NULL, &pi->line, pi->payload);
467 }
468
469 static int diff_print_patch_binary(
470 const git_diff_delta *delta,
471 const git_diff_binary *binary,
472 void *data)
473 {
474 diff_print_info *pi = data;
475 const char *old_pfx =
476 pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT;
477 const char *new_pfx =
478 pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT;
479 int error;
480
481 git_buf_clear(pi->buf);
482
483 if ((error = diff_print_patch_file_binary(
484 pi, (git_diff_delta *)delta, old_pfx, new_pfx, binary)) < 0)
485 return error;
486
487 pi->line.origin = GIT_DIFF_LINE_BINARY;
488 pi->line.content = git_buf_cstr(pi->buf);
489 pi->line.content_len = git_buf_len(pi->buf);
490
491 return pi->print_cb(delta, NULL, &pi->line, pi->payload);
492 }
493
494 static int diff_print_patch_hunk(
495 const git_diff_delta *d,
496 const git_diff_hunk *h,
497 void *data)
498 {
499 diff_print_info *pi = data;
500
501 if (S_ISDIR(d->new_file.mode))
502 return 0;
503
504 pi->line.origin = GIT_DIFF_LINE_HUNK_HDR;
505 pi->line.content = h->header;
506 pi->line.content_len = h->header_len;
507
508 return pi->print_cb(d, h, &pi->line, pi->payload);
509 }
510
511 static int diff_print_patch_line(
512 const git_diff_delta *delta,
513 const git_diff_hunk *hunk,
514 const git_diff_line *line,
515 void *data)
516 {
517 diff_print_info *pi = data;
518
519 if (S_ISDIR(delta->new_file.mode))
520 return 0;
521
522 return pi->print_cb(delta, hunk, line, pi->payload);
523 }
524
525 /* print a git_diff to an output callback */
526 int git_diff_print(
527 git_diff *diff,
528 git_diff_format_t format,
529 git_diff_line_cb print_cb,
530 void *payload)
531 {
532 int error;
533 git_buf buf = GIT_BUF_INIT;
534 diff_print_info pi;
535 git_diff_file_cb print_file = NULL;
536 git_diff_binary_cb print_binary = NULL;
537 git_diff_hunk_cb print_hunk = NULL;
538 git_diff_line_cb print_line = NULL;
539
540 switch (format) {
541 case GIT_DIFF_FORMAT_PATCH:
542 print_file = diff_print_patch_file;
543 print_binary = diff_print_patch_binary;
544 print_hunk = diff_print_patch_hunk;
545 print_line = diff_print_patch_line;
546 break;
547 case GIT_DIFF_FORMAT_PATCH_HEADER:
548 print_file = diff_print_patch_file;
549 break;
550 case GIT_DIFF_FORMAT_RAW:
551 print_file = diff_print_one_raw;
552 break;
553 case GIT_DIFF_FORMAT_NAME_ONLY:
554 print_file = diff_print_one_name_only;
555 break;
556 case GIT_DIFF_FORMAT_NAME_STATUS:
557 print_file = diff_print_one_name_status;
558 break;
559 default:
560 giterr_set(GITERR_INVALID, "Unknown diff output format (%d)", format);
561 return -1;
562 }
563
564 if (!(error = diff_print_info_init_fromdiff(
565 &pi, &buf, diff, format, print_cb, payload))) {
566 error = git_diff_foreach(
567 diff, print_file, print_binary, print_hunk, print_line, &pi);
568
569 if (error) /* make sure error message is set */
570 giterr_set_after_callback_function(error, "git_diff_print");
571 }
572
573 git_buf_free(&buf);
574
575 return error;
576 }
577
578 int git_diff_print_callback__to_buf(
579 const git_diff_delta *delta,
580 const git_diff_hunk *hunk,
581 const git_diff_line *line,
582 void *payload)
583 {
584 git_buf *output = payload;
585 GIT_UNUSED(delta); GIT_UNUSED(hunk);
586
587 if (!output) {
588 giterr_set(GITERR_INVALID, "Buffer pointer must be provided");
589 return -1;
590 }
591
592 if (line->origin == GIT_DIFF_LINE_ADDITION ||
593 line->origin == GIT_DIFF_LINE_DELETION ||
594 line->origin == GIT_DIFF_LINE_CONTEXT)
595 git_buf_putc(output, line->origin);
596
597 return git_buf_put(output, line->content, line->content_len);
598 }
599
600 int git_diff_print_callback__to_file_handle(
601 const git_diff_delta *delta,
602 const git_diff_hunk *hunk,
603 const git_diff_line *line,
604 void *payload)
605 {
606 FILE *fp = payload ? payload : stdout;
607
608 GIT_UNUSED(delta); GIT_UNUSED(hunk);
609
610 if (line->origin == GIT_DIFF_LINE_CONTEXT ||
611 line->origin == GIT_DIFF_LINE_ADDITION ||
612 line->origin == GIT_DIFF_LINE_DELETION)
613 fputc(line->origin, fp);
614 fwrite(line->content, 1, line->content_len, fp);
615 return 0;
616 }
617
618 /* print a git_patch to an output callback */
619 int git_patch_print(
620 git_patch *patch,
621 git_diff_line_cb print_cb,
622 void *payload)
623 {
624 int error;
625 git_buf temp = GIT_BUF_INIT;
626 diff_print_info pi;
627
628 assert(patch && print_cb);
629
630 if (!(error = diff_print_info_init_frompatch(
631 &pi, &temp, patch,
632 GIT_DIFF_FORMAT_PATCH, print_cb, payload)))
633 {
634 error = git_patch__invoke_callbacks(
635 patch,
636 diff_print_patch_file, diff_print_patch_binary,
637 diff_print_patch_hunk, diff_print_patch_line,
638 &pi);
639
640 if (error) /* make sure error message is set */
641 giterr_set_after_callback_function(error, "git_patch_print");
642 }
643
644 git_buf_free(&temp);
645
646 return error;
647 }
648
649 /* print a git_patch to a git_buf */
650 int git_patch_to_buf(git_buf *out, git_patch *patch)
651 {
652 assert(out && patch);
653 git_buf_sanitize(out);
654 return git_patch_print(patch, git_diff_print_callback__to_buf, out);
655 }