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