]> git.proxmox.com Git - libgit2.git/blame - src/diff_print.c
Add config read fns with controlled error behavior
[libgit2.git] / src / diff_print.c
CommitLineData
7000f3fa
RB
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"
114f5a6c 9#include "diff_patch.h"
f240acce 10#include "fileops.h"
7000f3fa
RB
11
12typedef struct {
3ff1d123 13 git_diff *diff;
10672e3e 14 git_diff_format_t format;
3ff1d123 15 git_diff_line_cb print_cb;
7000f3fa
RB
16 void *payload;
17 git_buf *buf;
10672e3e 18 uint32_t flags;
7000f3fa 19 int oid_strlen;
3b5f7954 20 git_diff_line line;
7000f3fa
RB
21} diff_print_info;
22
23static int diff_print_info_init(
24 diff_print_info *pi,
10672e3e
RB
25 git_buf *out,
26 git_diff *diff,
27 git_diff_format_t format,
28 git_diff_line_cb cb,
29 void *payload)
7000f3fa 30{
7000f3fa 31 pi->diff = diff;
10672e3e 32 pi->format = format;
7000f3fa
RB
33 pi->print_cb = cb;
34 pi->payload = payload;
35 pi->buf = out;
36
10672e3e
RB
37 if (diff)
38 pi->flags = diff->opts.flags;
39
40 if (diff && diff->opts.oid_abbrev != 0)
41 pi->oid_strlen = diff->opts.oid_abbrev;
42 else if (!diff || !diff->repo)
74ded024
RB
43 pi->oid_strlen = GIT_ABBREV_DEFAULT;
44 else if (git_repository__cvar(
45 &pi->oid_strlen, diff->repo, GIT_CVAR_ABBREV) < 0)
7000f3fa
RB
46 return -1;
47
48 pi->oid_strlen += 1; /* for NUL byte */
49
50 if (pi->oid_strlen < 2)
51 pi->oid_strlen = 2;
52 else if (pi->oid_strlen > GIT_OID_HEXSZ + 1)
53 pi->oid_strlen = GIT_OID_HEXSZ + 1;
54
3b5f7954
RB
55 memset(&pi->line, 0, sizeof(pi->line));
56 pi->line.old_lineno = -1;
57 pi->line.new_lineno = -1;
58 pi->line.num_lines = 1;
59
7000f3fa
RB
60 return 0;
61}
62
a1683f28 63static char diff_pick_suffix(int mode)
7000f3fa
RB
64{
65 if (S_ISDIR(mode))
66 return '/';
a7fcc44d 67 else if (GIT_PERMS_IS_EXEC(mode)) /* -V536 */
7000f3fa
RB
68 /* in git, modes are very regular, so we must have 0100755 mode */
69 return '*';
70 else
71 return ' ';
72}
73
74char git_diff_status_char(git_delta_t status)
75{
76 char code;
77
78 switch (status) {
79 case GIT_DELTA_ADDED: code = 'A'; break;
80 case GIT_DELTA_DELETED: code = 'D'; break;
81 case GIT_DELTA_MODIFIED: code = 'M'; break;
82 case GIT_DELTA_RENAMED: code = 'R'; break;
83 case GIT_DELTA_COPIED: code = 'C'; break;
84 case GIT_DELTA_IGNORED: code = 'I'; break;
85 case GIT_DELTA_UNTRACKED: code = '?'; break;
86 default: code = ' '; break;
87 }
88
89 return code;
90}
91
92static int callback_error(void)
93{
94 giterr_clear();
95 return GIT_EUSER;
96}
97
10672e3e
RB
98static int diff_print_one_name_only(
99 const git_diff_delta *delta, float progress, void *data)
100{
101 diff_print_info *pi = data;
102 git_buf *out = pi->buf;
103
104 GIT_UNUSED(progress);
105
106 if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 &&
107 delta->status == GIT_DELTA_UNMODIFIED)
108 return 0;
109
110 git_buf_clear(out);
111
112 if (git_buf_puts(out, delta->new_file.path) < 0 ||
113 git_buf_putc(out, '\n'))
114 return -1;
115
3b5f7954
RB
116 pi->line.origin = GIT_DIFF_LINE_FILE_HDR;
117 pi->line.content = git_buf_cstr(out);
118 pi->line.content_len = git_buf_len(out);
119
120 if (pi->print_cb(delta, NULL, &pi->line, pi->payload))
10672e3e
RB
121 return callback_error();
122
123 return 0;
124}
125
126static int diff_print_one_name_status(
7000f3fa
RB
127 const git_diff_delta *delta, float progress, void *data)
128{
129 diff_print_info *pi = data;
a1683f28 130 git_buf *out = pi->buf;
7000f3fa 131 char old_suffix, new_suffix, code = git_diff_status_char(delta->status);
74ded024
RB
132 int (*strcomp)(const char *, const char *) =
133 pi->diff ? pi->diff->strcomp : git__strcmp;
7000f3fa
RB
134
135 GIT_UNUSED(progress);
136
10672e3e 137 if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ')
7000f3fa
RB
138 return 0;
139
a1683f28
RB
140 old_suffix = diff_pick_suffix(delta->old_file.mode);
141 new_suffix = diff_pick_suffix(delta->new_file.mode);
7000f3fa 142
a1683f28 143 git_buf_clear(out);
7000f3fa
RB
144
145 if (delta->old_file.path != delta->new_file.path &&
74ded024 146 strcomp(delta->old_file.path,delta->new_file.path) != 0)
df40f398 147 git_buf_printf(out, "%c\t%s%c %s%c\n", code,
7000f3fa
RB
148 delta->old_file.path, old_suffix, delta->new_file.path, new_suffix);
149 else if (delta->old_file.mode != delta->new_file.mode &&
150 delta->old_file.mode != 0 && delta->new_file.mode != 0)
df40f398
RB
151 git_buf_printf(out, "%c\t%s%c %s%c\n", code,
152 delta->old_file.path, old_suffix, delta->new_file.path, new_suffix);
7000f3fa 153 else if (old_suffix != ' ')
a1683f28 154 git_buf_printf(out, "%c\t%s%c\n", code, delta->old_file.path, old_suffix);
7000f3fa 155 else
a1683f28 156 git_buf_printf(out, "%c\t%s\n", code, delta->old_file.path);
7000f3fa 157
a1683f28 158 if (git_buf_oom(out))
7000f3fa
RB
159 return -1;
160
3b5f7954
RB
161 pi->line.origin = GIT_DIFF_LINE_FILE_HDR;
162 pi->line.content = git_buf_cstr(out);
163 pi->line.content_len = git_buf_len(out);
164
165 if (pi->print_cb(delta, NULL, &pi->line, pi->payload))
7000f3fa
RB
166 return callback_error();
167
168 return 0;
169}
170
a1683f28 171static int diff_print_one_raw(
7000f3fa
RB
172 const git_diff_delta *delta, float progress, void *data)
173{
174 diff_print_info *pi = data;
a1683f28 175 git_buf *out = pi->buf;
7000f3fa
RB
176 char code = git_diff_status_char(delta->status);
177 char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
178
179 GIT_UNUSED(progress);
180
10672e3e 181 if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ')
7000f3fa
RB
182 return 0;
183
a1683f28 184 git_buf_clear(out);
7000f3fa
RB
185
186 git_oid_tostr(start_oid, pi->oid_strlen, &delta->old_file.oid);
187 git_oid_tostr(end_oid, pi->oid_strlen, &delta->new_file.oid);
188
189 git_buf_printf(
a1683f28 190 out, ":%06o %06o %s... %s... %c",
7000f3fa
RB
191 delta->old_file.mode, delta->new_file.mode, start_oid, end_oid, code);
192
193 if (delta->similarity > 0)
a1683f28 194 git_buf_printf(out, "%03u", delta->similarity);
7000f3fa 195
e4acc3ba 196 if (delta->old_file.path != delta->new_file.path)
7000f3fa 197 git_buf_printf(
a1683f28 198 out, "\t%s %s\n", delta->old_file.path, delta->new_file.path);
7000f3fa
RB
199 else
200 git_buf_printf(
a1683f28 201 out, "\t%s\n", delta->old_file.path ?
7000f3fa
RB
202 delta->old_file.path : delta->new_file.path);
203
a1683f28 204 if (git_buf_oom(out))
7000f3fa
RB
205 return -1;
206
3b5f7954
RB
207 pi->line.origin = GIT_DIFF_LINE_FILE_HDR;
208 pi->line.content = git_buf_cstr(out);
209 pi->line.content_len = git_buf_len(out);
210
211 if (pi->print_cb(delta, NULL, &pi->line, pi->payload))
7000f3fa
RB
212 return callback_error();
213
214 return 0;
215}
216
197b8966
RB
217static int diff_print_oid_range(
218 git_buf *out, const git_diff_delta *delta, int oid_strlen)
7000f3fa
RB
219{
220 char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
221
197b8966
RB
222 git_oid_tostr(start_oid, oid_strlen, &delta->old_file.oid);
223 git_oid_tostr(end_oid, oid_strlen, &delta->new_file.oid);
7000f3fa
RB
224
225 /* TODO: Match git diff more closely */
226 if (delta->old_file.mode == delta->new_file.mode) {
a1683f28 227 git_buf_printf(out, "index %s..%s %o\n",
7000f3fa
RB
228 start_oid, end_oid, delta->old_file.mode);
229 } else {
230 if (delta->old_file.mode == 0) {
a1683f28 231 git_buf_printf(out, "new file mode %o\n", delta->new_file.mode);
7000f3fa 232 } else if (delta->new_file.mode == 0) {
a1683f28 233 git_buf_printf(out, "deleted file mode %o\n", delta->old_file.mode);
7000f3fa 234 } else {
a1683f28
RB
235 git_buf_printf(out, "old mode %o\n", delta->old_file.mode);
236 git_buf_printf(out, "new mode %o\n", delta->new_file.mode);
7000f3fa 237 }
a1683f28 238 git_buf_printf(out, "index %s..%s\n", start_oid, end_oid);
7000f3fa
RB
239 }
240
a1683f28 241 if (git_buf_oom(out))
7000f3fa
RB
242 return -1;
243
244 return 0;
245}
246
eb1c1707 247static int diff_delta_format_with_paths(
197b8966
RB
248 git_buf *out,
249 const git_diff_delta *delta,
250 const char *oldpfx,
251 const char *newpfx,
eb1c1707 252 const char *template)
7000f3fa 253{
7000f3fa 254 const char *oldpath = delta->old_file.path;
7000f3fa 255 const char *newpath = delta->new_file.path;
7000f3fa 256
eb1c1707
RB
257 if (git_oid_iszero(&delta->old_file.oid)) {
258 oldpfx = "";
259 oldpath = "/dev/null";
260 }
261 if (git_oid_iszero(&delta->new_file.oid)) {
262 newpfx = "";
263 newpath = "/dev/null";
264 }
265
266 return git_buf_printf(out, template, oldpfx, oldpath, newpfx, newpath);
267}
268
269int git_diff_delta__format_file_header(
270 git_buf *out,
271 const git_diff_delta *delta,
272 const char *oldpfx,
273 const char *newpfx,
274 int oid_strlen)
275{
7000f3fa
RB
276 if (!oldpfx)
277 oldpfx = DIFF_OLD_PREFIX_DEFAULT;
7000f3fa
RB
278 if (!newpfx)
279 newpfx = DIFF_NEW_PREFIX_DEFAULT;
197b8966
RB
280 if (!oid_strlen)
281 oid_strlen = GIT_ABBREV_DEFAULT + 1;
7000f3fa 282
197b8966
RB
283 git_buf_clear(out);
284
285 git_buf_printf(out, "diff --git %s%s %s%s\n",
eb1c1707 286 oldpfx, delta->old_file.path, newpfx, delta->new_file.path);
7000f3fa 287
197b8966 288 if (diff_print_oid_range(out, delta, oid_strlen) < 0)
7000f3fa
RB
289 return -1;
290
eb1c1707
RB
291 if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0)
292 diff_delta_format_with_paths(
293 out, delta, oldpfx, newpfx, "--- %s%s\n+++ %s%s\n");
7000f3fa 294
197b8966
RB
295 return git_buf_oom(out) ? -1 : 0;
296}
7000f3fa 297
197b8966
RB
298static int diff_print_patch_file(
299 const git_diff_delta *delta, float progress, void *data)
300{
301 diff_print_info *pi = data;
eb1c1707
RB
302 const char *oldpfx =
303 pi->diff ? pi->diff->opts.old_prefix : DIFF_OLD_PREFIX_DEFAULT;
304 const char *newpfx =
305 pi->diff ? pi->diff->opts.new_prefix : DIFF_NEW_PREFIX_DEFAULT;
197b8966
RB
306
307 GIT_UNUSED(progress);
7000f3fa 308
197b8966
RB
309 if (S_ISDIR(delta->new_file.mode) ||
310 delta->status == GIT_DELTA_UNMODIFIED ||
311 delta->status == GIT_DELTA_IGNORED ||
312 (delta->status == GIT_DELTA_UNTRACKED &&
10672e3e 313 (pi->flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) == 0))
7000f3fa
RB
314 return 0;
315
197b8966
RB
316 if (git_diff_delta__format_file_header(
317 pi->buf, delta, oldpfx, newpfx, pi->oid_strlen) < 0)
7000f3fa
RB
318 return -1;
319
3b5f7954
RB
320 pi->line.origin = GIT_DIFF_LINE_FILE_HDR;
321 pi->line.content = git_buf_cstr(pi->buf);
322 pi->line.content_len = git_buf_len(pi->buf);
323
324 if (pi->print_cb(delta, NULL, &pi->line, pi->payload))
eb1c1707
RB
325 return callback_error();
326
327 if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0)
328 return 0;
329
330 git_buf_clear(pi->buf);
331
332 if (diff_delta_format_with_paths(
333 pi->buf, delta, oldpfx, newpfx,
334 "Binary files %s%s and %s%s differ\n") < 0)
335 return -1;
336
3b5f7954
RB
337 pi->line.origin = GIT_DIFF_LINE_BINARY;
338 pi->line.content = git_buf_cstr(pi->buf);
339 pi->line.content_len = git_buf_len(pi->buf);
340 pi->line.num_lines = 1;
341
342 if (pi->print_cb(delta, NULL, &pi->line, pi->payload))
7000f3fa
RB
343 return callback_error();
344
345 return 0;
346}
347
a1683f28 348static int diff_print_patch_hunk(
7000f3fa 349 const git_diff_delta *d,
3b5f7954 350 const git_diff_hunk *h,
7000f3fa
RB
351 void *data)
352{
353 diff_print_info *pi = data;
354
355 if (S_ISDIR(d->new_file.mode))
356 return 0;
357
3b5f7954
RB
358 pi->line.origin = GIT_DIFF_LINE_HUNK_HDR;
359 pi->line.content = h->header;
360 pi->line.content_len = h->header_len;
7000f3fa 361
3b5f7954 362 if (pi->print_cb(d, h, &pi->line, pi->payload))
7000f3fa
RB
363 return callback_error();
364
365 return 0;
366}
367
a1683f28 368static int diff_print_patch_line(
7000f3fa 369 const git_diff_delta *delta,
3b5f7954
RB
370 const git_diff_hunk *hunk,
371 const git_diff_line *line,
7000f3fa
RB
372 void *data)
373{
374 diff_print_info *pi = data;
375
376 if (S_ISDIR(delta->new_file.mode))
377 return 0;
378
3b5f7954 379 if (pi->print_cb(delta, hunk, line, pi->payload))
7000f3fa
RB
380 return callback_error();
381
382 return 0;
383}
384
10672e3e
RB
385/* print a git_diff to an output callback */
386int git_diff_print(
3ff1d123 387 git_diff *diff,
10672e3e 388 git_diff_format_t format,
3ff1d123 389 git_diff_line_cb print_cb,
7000f3fa
RB
390 void *payload)
391{
392 int error;
393 git_buf buf = GIT_BUF_INIT;
394 diff_print_info pi;
10672e3e
RB
395 git_diff_file_cb print_file = NULL;
396 git_diff_hunk_cb print_hunk = NULL;
397 git_diff_line_cb print_line = NULL;
398
399 switch (format) {
400 case GIT_DIFF_FORMAT_PATCH:
401 print_file = diff_print_patch_file;
402 print_hunk = diff_print_patch_hunk;
403 print_line = diff_print_patch_line;
404 break;
405 case GIT_DIFF_FORMAT_PATCH_HEADER:
406 print_file = diff_print_patch_file;
407 break;
408 case GIT_DIFF_FORMAT_RAW:
409 print_file = diff_print_one_raw;
410 break;
411 case GIT_DIFF_FORMAT_NAME_ONLY:
412 print_file = diff_print_one_name_only;
413 break;
414 case GIT_DIFF_FORMAT_NAME_STATUS:
415 print_file = diff_print_one_name_status;
416 break;
417 default:
418 giterr_set(GITERR_INVALID, "Unknown diff output format (%d)", format);
419 return -1;
420 }
7000f3fa 421
10672e3e
RB
422 if (!(error = diff_print_info_init(
423 &pi, &buf, diff, format, print_cb, payload)))
7000f3fa 424 error = git_diff_foreach(
10672e3e 425 diff, print_file, print_hunk, print_line, &pi);
7000f3fa
RB
426
427 git_buf_free(&buf);
428
429 return error;
430}
431
3ff1d123
RB
432/* print a git_patch to an output callback */
433int git_patch_print(
434 git_patch *patch,
435 git_diff_line_cb print_cb,
7000f3fa
RB
436 void *payload)
437{
438 int error;
439 git_buf temp = GIT_BUF_INIT;
440 diff_print_info pi;
7000f3fa
RB
441
442 assert(patch && print_cb);
443
444 if (!(error = diff_print_info_init(
10672e3e
RB
445 &pi, &temp, git_patch__diff(patch),
446 GIT_DIFF_FORMAT_PATCH, print_cb, payload)))
3ff1d123 447 error = git_patch__invoke_callbacks(
a1683f28
RB
448 patch, diff_print_patch_file, diff_print_patch_hunk,
449 diff_print_patch_line, &pi);
7000f3fa
RB
450
451 git_buf_free(&temp);
452
453 return error;
454}
455
a1683f28
RB
456static int diff_print_to_buffer_cb(
457 const git_diff_delta *delta,
3b5f7954
RB
458 const git_diff_hunk *hunk,
459 const git_diff_line *line,
a1683f28
RB
460 void *payload)
461{
462 git_buf *output = payload;
3b5f7954
RB
463 GIT_UNUSED(delta); GIT_UNUSED(hunk);
464
465 if (line->origin == GIT_DIFF_LINE_ADDITION ||
466 line->origin == GIT_DIFF_LINE_DELETION ||
467 line->origin == GIT_DIFF_LINE_CONTEXT)
468 git_buf_putc(output, line->origin);
469
470 return git_buf_put(output, line->content, line->content_len);
a1683f28
RB
471}
472
3ff1d123
RB
473/* print a git_patch to a string buffer */
474int git_patch_to_str(
7000f3fa 475 char **string,
3ff1d123 476 git_patch *patch)
7000f3fa
RB
477{
478 int error;
479 git_buf output = GIT_BUF_INIT;
480
3ff1d123 481 error = git_patch_print(patch, diff_print_to_buffer_cb, &output);
7000f3fa
RB
482
483 /* GIT_EUSER means git_buf_put in print_to_buffer_cb returned -1,
484 * meaning a memory allocation failure, so just map to -1...
485 */
486 if (error == GIT_EUSER)
487 error = -1;
488
489 *string = git_buf_detach(&output);
490
491 return error;
492}