]> git.proxmox.com Git - libgit2.git/blob - src/diff_print.c
Merge pull request #3219 from libgit2/cmn/racy-diff
[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_patch.h"
10 #include "fileops.h"
11 #include "zstream.h"
12 #include "blob.h"
13 #include "delta.h"
14 #include "git2/sys/diff.h"
15
16 typedef struct {
17 git_diff *diff;
18 git_diff_format_t format;
19 git_diff_line_cb print_cb;
20 void *payload;
21 git_buf *buf;
22 uint32_t flags;
23 int oid_strlen;
24 git_diff_line line;
25 unsigned int
26 content_loaded : 1,
27 content_allocated : 1;
28 git_diff_file_content *ofile;
29 git_diff_file_content *nfile;
30 } diff_print_info;
31
32 static int diff_print_info_init__common(
33 diff_print_info *pi,
34 git_buf *out,
35 git_repository *repo,
36 git_diff_format_t format,
37 git_diff_line_cb cb,
38 void *payload)
39 {
40 pi->format = format;
41 pi->print_cb = cb;
42 pi->payload = payload;
43 pi->buf = out;
44
45 if (!pi->oid_strlen) {
46 if (!repo)
47 pi->oid_strlen = GIT_ABBREV_DEFAULT;
48 else if (git_repository__cvar(&pi->oid_strlen, repo, GIT_CVAR_ABBREV) < 0)
49 return -1;
50 }
51
52 pi->oid_strlen += 1; /* for NUL byte */
53
54 if (pi->oid_strlen > GIT_OID_HEXSZ + 1)
55 pi->oid_strlen = GIT_OID_HEXSZ + 1;
56
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;
61
62 return 0;
63 }
64
65 static int diff_print_info_init_fromdiff(
66 diff_print_info *pi,
67 git_buf *out,
68 git_diff *diff,
69 git_diff_format_t format,
70 git_diff_line_cb cb,
71 void *payload)
72 {
73 git_repository *repo = diff ? diff->repo : NULL;
74
75 memset(pi, 0, sizeof(diff_print_info));
76
77 pi->diff = diff;
78
79 if (diff) {
80 pi->flags = diff->opts.flags;
81 pi->oid_strlen = diff->opts.id_abbrev;
82 }
83
84 return diff_print_info_init__common(pi, out, repo, format, cb, payload);
85 }
86
87 static int diff_print_info_init_frompatch(
88 diff_print_info *pi,
89 git_buf *out,
90 git_patch *patch,
91 git_diff_format_t format,
92 git_diff_line_cb cb,
93 void *payload)
94 {
95 git_repository *repo = patch && patch->diff ? patch->diff->repo : NULL;
96
97 memset(pi, 0, sizeof(diff_print_info));
98
99 pi->diff = patch->diff;
100
101 pi->flags = patch->diff_opts.flags;
102 pi->oid_strlen = patch->diff_opts.id_abbrev;
103
104 pi->content_loaded = 1;
105 pi->ofile = &patch->ofile;
106 pi->nfile = &patch->nfile;
107
108 return diff_print_info_init__common(pi, out, repo, format, cb, payload);
109 }
110
111 static char diff_pick_suffix(int mode)
112 {
113 if (S_ISDIR(mode))
114 return '/';
115 else if (GIT_PERMS_IS_EXEC(mode)) /* -V536 */
116 /* in git, modes are very regular, so we must have 0100755 mode */
117 return '*';
118 else
119 return ' ';
120 }
121
122 char git_diff_status_char(git_delta_t status)
123 {
124 char code;
125
126 switch (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;
136 }
137
138 return code;
139 }
140
141 static int diff_print_one_name_only(
142 const git_diff_delta *delta, float progress, void *data)
143 {
144 diff_print_info *pi = data;
145 git_buf *out = pi->buf;
146
147 GIT_UNUSED(progress);
148
149 if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 &&
150 delta->status == GIT_DELTA_UNMODIFIED)
151 return 0;
152
153 git_buf_clear(out);
154 git_buf_puts(out, delta->new_file.path);
155 git_buf_putc(out, '\n');
156 if (git_buf_oom(out))
157 return -1;
158
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);
162
163 return pi->print_cb(delta, NULL, &pi->line, pi->payload);
164 }
165
166 static int diff_print_one_name_status(
167 const git_diff_delta *delta, float progress, void *data)
168 {
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;
174
175 GIT_UNUSED(progress);
176
177 if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ')
178 return 0;
179
180 old_suffix = diff_pick_suffix(delta->old_file.mode);
181 new_suffix = diff_pick_suffix(delta->new_file.mode);
182
183 git_buf_clear(out);
184
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);
195 else
196 git_buf_printf(out, "%c\t%s\n", code, delta->old_file.path);
197 if (git_buf_oom(out))
198 return -1;
199
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);
203
204 return pi->print_cb(delta, NULL, &pi->line, pi->payload);
205 }
206
207 static int diff_print_one_raw(
208 const git_diff_delta *delta, float progress, void *data)
209 {
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];
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 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);
224
225 git_buf_printf(
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);
229
230 if (delta->similarity > 0)
231 git_buf_printf(out, "%03u", delta->similarity);
232
233 if (delta->old_file.path != delta->new_file.path)
234 git_buf_printf(
235 out, "\t%s %s\n", delta->old_file.path, delta->new_file.path);
236 else
237 git_buf_printf(
238 out, "\t%s\n", delta->old_file.path ?
239 delta->old_file.path : delta->new_file.path);
240
241 if (git_buf_oom(out))
242 return -1;
243
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);
247
248 return pi->print_cb(delta, NULL, &pi->line, pi->payload);
249 }
250
251 static int diff_print_oid_range(
252 git_buf *out, const git_diff_delta *delta, int oid_strlen)
253 {
254 char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
255
256 git_oid_tostr(start_oid, oid_strlen, &delta->old_file.id);
257 git_oid_tostr(end_oid, oid_strlen, &delta->new_file.id);
258
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);
263 } else {
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);
268 } else {
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);
271 }
272 git_buf_printf(out, "index %s..%s\n", start_oid, end_oid);
273 }
274
275 return git_buf_oom(out) ? -1 : 0;
276 }
277
278 static int diff_delta_format_with_paths(
279 git_buf *out,
280 const git_diff_delta *delta,
281 const char *oldpfx,
282 const char *newpfx,
283 const char *template)
284 {
285 const char *oldpath = delta->old_file.path;
286 const char *newpath = delta->new_file.path;
287
288 if (git_oid_iszero(&delta->old_file.id)) {
289 oldpfx = "";
290 oldpath = "/dev/null";
291 }
292 if (git_oid_iszero(&delta->new_file.id)) {
293 newpfx = "";
294 newpath = "/dev/null";
295 }
296
297 return git_buf_printf(out, template, oldpfx, oldpath, newpfx, newpath);
298 }
299
300 int git_diff_delta__format_file_header(
301 git_buf *out,
302 const git_diff_delta *delta,
303 const char *oldpfx,
304 const char *newpfx,
305 int oid_strlen)
306 {
307 if (!oldpfx)
308 oldpfx = DIFF_OLD_PREFIX_DEFAULT;
309 if (!newpfx)
310 newpfx = DIFF_NEW_PREFIX_DEFAULT;
311 if (!oid_strlen)
312 oid_strlen = GIT_ABBREV_DEFAULT + 1;
313
314 git_buf_clear(out);
315
316 git_buf_printf(out, "diff --git %s%s %s%s\n",
317 oldpfx, delta->old_file.path, newpfx, delta->new_file.path);
318
319 GITERR_CHECK_ERROR(diff_print_oid_range(out, delta, oid_strlen));
320
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");
324
325 return git_buf_oom(out) ? -1 : 0;
326 }
327
328 static int format_binary(
329 diff_print_info *pi,
330 git_diff_binary_t type,
331 const char *data,
332 size_t datalen,
333 size_t inflatedlen)
334 {
335 const char *typename = type == GIT_DIFF_BINARY_DELTA ?
336 "delta" : "literal";
337 const char *scan, *end;
338
339 git_buf_printf(pi->buf, "%s %lu\n", typename, inflatedlen);
340 pi->line.num_lines++;
341
342 for (scan = data, end = data + datalen; scan < end; ) {
343 size_t chunk_len = end - scan;
344 if (chunk_len > 52)
345 chunk_len = 52;
346
347 if (chunk_len <= 26)
348 git_buf_putc(pi->buf, (char)chunk_len + 'A' - 1);
349 else
350 git_buf_putc(pi->buf, (char)chunk_len - 26 + 'a' - 1);
351
352 git_buf_encode_base85(pi->buf, scan, chunk_len);
353 git_buf_putc(pi->buf, '\n');
354
355 if (git_buf_oom(pi->buf))
356 return -1;
357
358 scan += chunk_len;
359 pi->line.num_lines++;
360 }
361
362 return 0;
363 }
364
365 static int diff_print_load_content(
366 diff_print_info *pi,
367 git_diff_delta *delta)
368 {
369 git_diff_file_content *ofile, *nfile;
370 int error;
371
372 assert(pi->diff);
373
374 ofile = git__calloc(1, sizeof(git_diff_file_content));
375 nfile = git__calloc(1, sizeof(git_diff_file_content));
376
377 GITERR_CHECK_ALLOC(ofile);
378 GITERR_CHECK_ALLOC(nfile);
379
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) {
384
385 git__free(ofile);
386 git__free(nfile);
387 return error;
388 }
389
390 pi->content_loaded = 1;
391 pi->content_allocated = 1;
392 pi->ofile = ofile;
393 pi->nfile = nfile;
394
395 return 0;
396 }
397
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)
402 {
403 size_t pre_binary_size;
404 int error;
405
406 if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0)
407 goto noshow;
408
409 if (!pi->content_loaded &&
410 (error = diff_print_load_content(pi, delta)) < 0)
411 return error;
412
413 pre_binary_size = pi->buf->size;
414 git_buf_printf(pi->buf, "GIT binary patch\n");
415 pi->line.num_lines++;
416
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) {
422
423 if (error == GIT_EBUFS) {
424 giterr_clear();
425 git_buf_truncate(pi->buf, pre_binary_size);
426 goto noshow;
427 }
428 }
429
430 pi->line.num_lines++;
431 return error;
432
433 noshow:
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");
438 }
439
440 static int diff_print_patch_file(
441 const git_diff_delta *delta, float progress, void *data)
442 {
443 int error;
444 diff_print_info *pi = data;
445 const char *oldpfx =
446 pi->diff ? pi->diff->opts.old_prefix : DIFF_OLD_PREFIX_DEFAULT;
447 const char *newpfx =
448 pi->diff ? pi->diff->opts.new_prefix : DIFF_NEW_PREFIX_DEFAULT;
449
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;
455
456 GIT_UNUSED(progress);
457
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))
464 return 0;
465
466 if ((error = git_diff_delta__format_file_header(
467 pi->buf, delta, oldpfx, newpfx, oid_strlen)) < 0)
468 return error;
469
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);
473
474 return pi->print_cb(delta, NULL, &pi->line, pi->payload);
475 }
476
477 static int diff_print_patch_binary(
478 const git_diff_delta *delta,
479 const git_diff_binary *binary,
480 void *data)
481 {
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;
487 int error;
488
489 git_buf_clear(pi->buf);
490
491 if ((error = diff_print_patch_file_binary(
492 pi, (git_diff_delta *)delta, old_pfx, new_pfx, binary)) < 0)
493 return error;
494
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);
498
499 return pi->print_cb(delta, NULL, &pi->line, pi->payload);
500 }
501
502 static int diff_print_patch_hunk(
503 const git_diff_delta *d,
504 const git_diff_hunk *h,
505 void *data)
506 {
507 diff_print_info *pi = data;
508
509 if (S_ISDIR(d->new_file.mode))
510 return 0;
511
512 pi->line.origin = GIT_DIFF_LINE_HUNK_HDR;
513 pi->line.content = h->header;
514 pi->line.content_len = h->header_len;
515
516 return pi->print_cb(d, h, &pi->line, pi->payload);
517 }
518
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,
523 void *data)
524 {
525 diff_print_info *pi = data;
526
527 if (S_ISDIR(delta->new_file.mode))
528 return 0;
529
530 return pi->print_cb(delta, hunk, line, pi->payload);
531 }
532
533 /* print a git_diff to an output callback */
534 int git_diff_print(
535 git_diff *diff,
536 git_diff_format_t format,
537 git_diff_line_cb print_cb,
538 void *payload)
539 {
540 int error;
541 git_buf buf = GIT_BUF_INIT;
542 diff_print_info pi;
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;
547
548 switch (format) {
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;
554 break;
555 case GIT_DIFF_FORMAT_PATCH_HEADER:
556 print_file = diff_print_patch_file;
557 break;
558 case GIT_DIFF_FORMAT_RAW:
559 print_file = diff_print_one_raw;
560 break;
561 case GIT_DIFF_FORMAT_NAME_ONLY:
562 print_file = diff_print_one_name_only;
563 break;
564 case GIT_DIFF_FORMAT_NAME_STATUS:
565 print_file = diff_print_one_name_status;
566 break;
567 default:
568 giterr_set(GITERR_INVALID, "Unknown diff output format (%d)", format);
569 return -1;
570 }
571
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);
576
577 if (error) /* make sure error message is set */
578 giterr_set_after_callback_function(error, "git_diff_print");
579 }
580
581 git_buf_free(&buf);
582
583 return error;
584 }
585
586 /* print a git_patch to an output callback */
587 int git_patch_print(
588 git_patch *patch,
589 git_diff_line_cb print_cb,
590 void *payload)
591 {
592 int error;
593 git_buf temp = GIT_BUF_INIT;
594 diff_print_info pi;
595
596 assert(patch && print_cb);
597
598 if (!(error = diff_print_info_init_frompatch(
599 &pi, &temp, patch,
600 GIT_DIFF_FORMAT_PATCH, print_cb, payload)))
601 {
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);
605
606 if (error) /* make sure error message is set */
607 giterr_set_after_callback_function(error, "git_patch_print");
608 }
609
610 git_buf_free(&temp);
611
612 return error;
613 }
614
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,
619 void *payload)
620 {
621 git_buf *output = payload;
622 GIT_UNUSED(delta); GIT_UNUSED(hunk);
623
624 if (!output) {
625 giterr_set(GITERR_INVALID, "Buffer pointer must be provided");
626 return -1;
627 }
628
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);
633
634 return git_buf_put(output, line->content, line->content_len);
635 }
636
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,
641 void *payload)
642 {
643 FILE *fp = payload ? payload : stdout;
644
645 GIT_UNUSED(delta); GIT_UNUSED(hunk);
646
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);
652 return 0;
653 }
654
655 /* print a git_patch to a git_buf */
656 int git_patch_to_buf(git_buf *out, git_patch *patch)
657 {
658 assert(out && patch);
659 git_buf_sanitize(out);
660 return git_patch_print(patch, git_diff_print_callback__to_buf, out);
661 }