]>
Commit | Line | Data |
---|---|---|
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 | ||
8 | #include "common.h" | |
9 | ||
10 | #include "diff.h" | |
11 | #include "diff_file.h" | |
12 | #include "patch_generate.h" | |
13 | #include "futils.h" | |
14 | #include "zstream.h" | |
15 | #include "blob.h" | |
16 | #include "delta.h" | |
17 | #include "git2/sys/diff.h" | |
18 | ||
19 | typedef struct { | |
20 | git_diff_format_t format; | |
21 | git_diff_line_cb print_cb; | |
22 | void *payload; | |
23 | ||
24 | git_buf *buf; | |
25 | git_diff_line line; | |
26 | ||
27 | const char *old_prefix; | |
28 | const char *new_prefix; | |
29 | uint32_t flags; | |
30 | int id_strlen; | |
31 | ||
32 | int (*strcomp)(const char *, const char *); | |
33 | } diff_print_info; | |
34 | ||
35 | static int diff_print_info_init__common( | |
36 | diff_print_info *pi, | |
37 | git_buf *out, | |
38 | git_repository *repo, | |
39 | git_diff_format_t format, | |
40 | git_diff_line_cb cb, | |
41 | void *payload) | |
42 | { | |
43 | pi->format = format; | |
44 | pi->print_cb = cb; | |
45 | pi->payload = payload; | |
46 | pi->buf = out; | |
47 | ||
48 | if (!pi->id_strlen) { | |
49 | if (!repo) | |
50 | pi->id_strlen = GIT_ABBREV_DEFAULT; | |
51 | else if (git_repository__configmap_lookup(&pi->id_strlen, repo, GIT_CONFIGMAP_ABBREV) < 0) | |
52 | return -1; | |
53 | } | |
54 | ||
55 | if (pi->id_strlen > GIT_OID_HEXSZ) | |
56 | pi->id_strlen = GIT_OID_HEXSZ; | |
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->id_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 | GIT_ASSERT_ARG(patch); | |
99 | ||
100 | memset(pi, 0, sizeof(diff_print_info)); | |
101 | ||
102 | pi->flags = patch->diff_opts.flags; | |
103 | pi->id_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_TYPECHANGE: code = 'T'; 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 *) = pi->strcomp ? | |
173 | pi->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 | int id_abbrev; | |
213 | char code = git_diff_status_char(delta->status); | |
214 | char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1]; | |
215 | ||
216 | GIT_UNUSED(progress); | |
217 | ||
218 | if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ') | |
219 | return 0; | |
220 | ||
221 | git_buf_clear(out); | |
222 | ||
223 | id_abbrev = delta->old_file.mode ? delta->old_file.id_abbrev : | |
224 | delta->new_file.id_abbrev; | |
225 | ||
226 | if (pi->id_strlen > id_abbrev) { | |
227 | git_error_set(GIT_ERROR_PATCH, | |
228 | "the patch input contains %d id characters (cannot print %d)", | |
229 | id_abbrev, pi->id_strlen); | |
230 | return -1; | |
231 | } | |
232 | ||
233 | git_oid_tostr(start_oid, pi->id_strlen + 1, &delta->old_file.id); | |
234 | git_oid_tostr(end_oid, pi->id_strlen + 1, &delta->new_file.id); | |
235 | ||
236 | git_buf_printf( | |
237 | out, (pi->id_strlen <= GIT_OID_HEXSZ) ? | |
238 | ":%06o %06o %s... %s... %c" : ":%06o %06o %s %s %c", | |
239 | delta->old_file.mode, delta->new_file.mode, start_oid, end_oid, code); | |
240 | ||
241 | if (delta->similarity > 0) | |
242 | git_buf_printf(out, "%03u", delta->similarity); | |
243 | ||
244 | if (delta->old_file.path != delta->new_file.path) | |
245 | git_buf_printf( | |
246 | out, "\t%s %s\n", delta->old_file.path, delta->new_file.path); | |
247 | else | |
248 | git_buf_printf( | |
249 | out, "\t%s\n", delta->old_file.path ? | |
250 | delta->old_file.path : delta->new_file.path); | |
251 | ||
252 | if (git_buf_oom(out)) | |
253 | return -1; | |
254 | ||
255 | pi->line.origin = GIT_DIFF_LINE_FILE_HDR; | |
256 | pi->line.content = git_buf_cstr(out); | |
257 | pi->line.content_len = git_buf_len(out); | |
258 | ||
259 | return pi->print_cb(delta, NULL, &pi->line, pi->payload); | |
260 | } | |
261 | ||
262 | static int diff_print_modes( | |
263 | git_buf *out, const git_diff_delta *delta) | |
264 | { | |
265 | git_buf_printf(out, "old mode %o\n", delta->old_file.mode); | |
266 | git_buf_printf(out, "new mode %o\n", delta->new_file.mode); | |
267 | ||
268 | return git_buf_oom(out) ? -1 : 0; | |
269 | } | |
270 | ||
271 | static int diff_print_oid_range( | |
272 | git_buf *out, const git_diff_delta *delta, int id_strlen, | |
273 | bool print_index) | |
274 | { | |
275 | char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1]; | |
276 | ||
277 | if (delta->old_file.mode && | |
278 | id_strlen > delta->old_file.id_abbrev) { | |
279 | git_error_set(GIT_ERROR_PATCH, | |
280 | "the patch input contains %d id characters (cannot print %d)", | |
281 | delta->old_file.id_abbrev, id_strlen); | |
282 | return -1; | |
283 | } | |
284 | ||
285 | if ((delta->new_file.mode && | |
286 | id_strlen > delta->new_file.id_abbrev)) { | |
287 | git_error_set(GIT_ERROR_PATCH, | |
288 | "the patch input contains %d id characters (cannot print %d)", | |
289 | delta->new_file.id_abbrev, id_strlen); | |
290 | return -1; | |
291 | } | |
292 | ||
293 | git_oid_tostr(start_oid, id_strlen + 1, &delta->old_file.id); | |
294 | git_oid_tostr(end_oid, id_strlen + 1, &delta->new_file.id); | |
295 | ||
296 | if (delta->old_file.mode == delta->new_file.mode) { | |
297 | if (print_index) | |
298 | git_buf_printf(out, "index %s..%s %o\n", | |
299 | start_oid, end_oid, delta->old_file.mode); | |
300 | } else { | |
301 | if (delta->old_file.mode == 0) | |
302 | git_buf_printf(out, "new file mode %o\n", delta->new_file.mode); | |
303 | else if (delta->new_file.mode == 0) | |
304 | git_buf_printf(out, "deleted file mode %o\n", delta->old_file.mode); | |
305 | else | |
306 | diff_print_modes(out, delta); | |
307 | ||
308 | if (print_index) | |
309 | git_buf_printf(out, "index %s..%s\n", start_oid, end_oid); | |
310 | } | |
311 | ||
312 | return git_buf_oom(out) ? -1 : 0; | |
313 | } | |
314 | ||
315 | static int diff_delta_format_path( | |
316 | git_buf *out, const char *prefix, const char *filename) | |
317 | { | |
318 | if (git_buf_joinpath(out, prefix, filename) < 0) | |
319 | return -1; | |
320 | ||
321 | return git_buf_quote(out); | |
322 | } | |
323 | ||
324 | static int diff_delta_format_with_paths( | |
325 | git_buf *out, | |
326 | const git_diff_delta *delta, | |
327 | const char *template, | |
328 | const char *oldpath, | |
329 | const char *newpath) | |
330 | { | |
331 | if (git_oid_is_zero(&delta->old_file.id)) | |
332 | oldpath = "/dev/null"; | |
333 | ||
334 | if (git_oid_is_zero(&delta->new_file.id)) | |
335 | newpath = "/dev/null"; | |
336 | ||
337 | return git_buf_printf(out, template, oldpath, newpath); | |
338 | } | |
339 | ||
340 | static int diff_delta_format_similarity_header( | |
341 | git_buf *out, | |
342 | const git_diff_delta *delta) | |
343 | { | |
344 | git_buf old_path = GIT_BUF_INIT, new_path = GIT_BUF_INIT; | |
345 | const char *type; | |
346 | int error = 0; | |
347 | ||
348 | if (delta->similarity > 100) { | |
349 | git_error_set(GIT_ERROR_PATCH, "invalid similarity %d", delta->similarity); | |
350 | error = -1; | |
351 | goto done; | |
352 | } | |
353 | ||
354 | GIT_ASSERT(delta->status == GIT_DELTA_RENAMED || delta->status == GIT_DELTA_COPIED); | |
355 | if (delta->status == GIT_DELTA_RENAMED) | |
356 | type = "rename"; | |
357 | else | |
358 | type = "copy"; | |
359 | ||
360 | if ((error = git_buf_puts(&old_path, delta->old_file.path)) < 0 || | |
361 | (error = git_buf_puts(&new_path, delta->new_file.path)) < 0 || | |
362 | (error = git_buf_quote(&old_path)) < 0 || | |
363 | (error = git_buf_quote(&new_path)) < 0) | |
364 | goto done; | |
365 | ||
366 | git_buf_printf(out, | |
367 | "similarity index %d%%\n" | |
368 | "%s from %s\n" | |
369 | "%s to %s\n", | |
370 | delta->similarity, | |
371 | type, old_path.ptr, | |
372 | type, new_path.ptr); | |
373 | ||
374 | if (git_buf_oom(out)) | |
375 | error = -1; | |
376 | ||
377 | done: | |
378 | git_buf_dispose(&old_path); | |
379 | git_buf_dispose(&new_path); | |
380 | ||
381 | return error; | |
382 | } | |
383 | ||
384 | static bool delta_is_unchanged(const git_diff_delta *delta) | |
385 | { | |
386 | if (git_oid_is_zero(&delta->old_file.id) && | |
387 | git_oid_is_zero(&delta->new_file.id)) | |
388 | return true; | |
389 | ||
390 | if (delta->old_file.mode == GIT_FILEMODE_COMMIT || | |
391 | delta->new_file.mode == GIT_FILEMODE_COMMIT) | |
392 | return false; | |
393 | ||
394 | if (git_oid_equal(&delta->old_file.id, &delta->new_file.id)) | |
395 | return true; | |
396 | ||
397 | return false; | |
398 | } | |
399 | ||
400 | int git_diff_delta__format_file_header( | |
401 | git_buf *out, | |
402 | const git_diff_delta *delta, | |
403 | const char *oldpfx, | |
404 | const char *newpfx, | |
405 | int id_strlen, | |
406 | bool print_index) | |
407 | { | |
408 | git_buf old_path = GIT_BUF_INIT, new_path = GIT_BUF_INIT; | |
409 | bool unchanged = delta_is_unchanged(delta); | |
410 | int error = 0; | |
411 | ||
412 | if (!oldpfx) | |
413 | oldpfx = DIFF_OLD_PREFIX_DEFAULT; | |
414 | if (!newpfx) | |
415 | newpfx = DIFF_NEW_PREFIX_DEFAULT; | |
416 | if (!id_strlen) | |
417 | id_strlen = GIT_ABBREV_DEFAULT; | |
418 | ||
419 | if ((error = diff_delta_format_path( | |
420 | &old_path, oldpfx, delta->old_file.path)) < 0 || | |
421 | (error = diff_delta_format_path( | |
422 | &new_path, newpfx, delta->new_file.path)) < 0) | |
423 | goto done; | |
424 | ||
425 | git_buf_clear(out); | |
426 | ||
427 | git_buf_printf(out, "diff --git %s %s\n", | |
428 | old_path.ptr, new_path.ptr); | |
429 | ||
430 | if (unchanged && delta->old_file.mode != delta->new_file.mode) | |
431 | diff_print_modes(out, delta); | |
432 | ||
433 | if (delta->status == GIT_DELTA_RENAMED || | |
434 | (delta->status == GIT_DELTA_COPIED && unchanged)) { | |
435 | if ((error = diff_delta_format_similarity_header(out, delta)) < 0) | |
436 | goto done; | |
437 | } | |
438 | ||
439 | if (!unchanged) { | |
440 | if ((error = diff_print_oid_range(out, delta, | |
441 | id_strlen, print_index)) < 0) | |
442 | goto done; | |
443 | ||
444 | if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0) | |
445 | diff_delta_format_with_paths(out, delta, | |
446 | "--- %s\n+++ %s\n", old_path.ptr, new_path.ptr); | |
447 | } | |
448 | ||
449 | if (git_buf_oom(out)) | |
450 | error = -1; | |
451 | ||
452 | done: | |
453 | git_buf_dispose(&old_path); | |
454 | git_buf_dispose(&new_path); | |
455 | ||
456 | return error; | |
457 | } | |
458 | ||
459 | static int format_binary( | |
460 | diff_print_info *pi, | |
461 | git_diff_binary_t type, | |
462 | const char *data, | |
463 | size_t datalen, | |
464 | size_t inflatedlen) | |
465 | { | |
466 | const char *typename = type == GIT_DIFF_BINARY_DELTA ? | |
467 | "delta" : "literal"; | |
468 | const char *scan, *end; | |
469 | ||
470 | git_buf_printf(pi->buf, "%s %" PRIuZ "\n", typename, inflatedlen); | |
471 | pi->line.num_lines++; | |
472 | ||
473 | for (scan = data, end = data + datalen; scan < end; ) { | |
474 | size_t chunk_len = end - scan; | |
475 | if (chunk_len > 52) | |
476 | chunk_len = 52; | |
477 | ||
478 | if (chunk_len <= 26) | |
479 | git_buf_putc(pi->buf, (char)chunk_len + 'A' - 1); | |
480 | else | |
481 | git_buf_putc(pi->buf, (char)chunk_len - 26 + 'a' - 1); | |
482 | ||
483 | git_buf_encode_base85(pi->buf, scan, chunk_len); | |
484 | git_buf_putc(pi->buf, '\n'); | |
485 | ||
486 | if (git_buf_oom(pi->buf)) | |
487 | return -1; | |
488 | ||
489 | scan += chunk_len; | |
490 | pi->line.num_lines++; | |
491 | } | |
492 | git_buf_putc(pi->buf, '\n'); | |
493 | ||
494 | if (git_buf_oom(pi->buf)) | |
495 | return -1; | |
496 | ||
497 | return 0; | |
498 | } | |
499 | ||
500 | static int diff_print_patch_file_binary_noshow( | |
501 | diff_print_info *pi, git_diff_delta *delta, | |
502 | const char *old_pfx, const char *new_pfx) | |
503 | { | |
504 | git_buf old_path = GIT_BUF_INIT, new_path = GIT_BUF_INIT; | |
505 | int error; | |
506 | ||
507 | if ((error = diff_delta_format_path(&old_path, old_pfx, delta->old_file.path)) < 0 || | |
508 | (error = diff_delta_format_path(&new_path, new_pfx, delta->new_file.path)) < 0 || | |
509 | (error = diff_delta_format_with_paths(pi->buf, delta, "Binary files %s and %s differ\n", | |
510 | old_path.ptr, new_path.ptr)) < 0) | |
511 | goto done; | |
512 | ||
513 | pi->line.num_lines = 1; | |
514 | ||
515 | done: | |
516 | git_buf_dispose(&old_path); | |
517 | git_buf_dispose(&new_path); | |
518 | return error; | |
519 | } | |
520 | ||
521 | static int diff_print_patch_file_binary( | |
522 | diff_print_info *pi, git_diff_delta *delta, | |
523 | const char *old_pfx, const char *new_pfx, | |
524 | const git_diff_binary *binary) | |
525 | { | |
526 | size_t pre_binary_size; | |
527 | int error; | |
528 | ||
529 | if (delta->status == GIT_DELTA_UNMODIFIED) | |
530 | return 0; | |
531 | ||
532 | if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0 || !binary->contains_data) | |
533 | return diff_print_patch_file_binary_noshow( | |
534 | pi, delta, old_pfx, new_pfx); | |
535 | ||
536 | pre_binary_size = pi->buf->size; | |
537 | git_buf_printf(pi->buf, "GIT binary patch\n"); | |
538 | pi->line.num_lines++; | |
539 | ||
540 | if ((error = format_binary(pi, binary->new_file.type, binary->new_file.data, | |
541 | binary->new_file.datalen, binary->new_file.inflatedlen)) < 0 || | |
542 | (error = format_binary(pi, binary->old_file.type, binary->old_file.data, | |
543 | binary->old_file.datalen, binary->old_file.inflatedlen)) < 0) { | |
544 | if (error == GIT_EBUFS) { | |
545 | git_error_clear(); | |
546 | git_buf_truncate(pi->buf, pre_binary_size); | |
547 | ||
548 | return diff_print_patch_file_binary_noshow( | |
549 | pi, delta, old_pfx, new_pfx); | |
550 | } | |
551 | } | |
552 | ||
553 | pi->line.num_lines++; | |
554 | return error; | |
555 | } | |
556 | ||
557 | static int diff_print_patch_file( | |
558 | const git_diff_delta *delta, float progress, void *data) | |
559 | { | |
560 | int error; | |
561 | diff_print_info *pi = data; | |
562 | const char *oldpfx = | |
563 | pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT; | |
564 | const char *newpfx = | |
565 | pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT; | |
566 | ||
567 | bool binary = (delta->flags & GIT_DIFF_FLAG_BINARY) || | |
568 | (pi->flags & GIT_DIFF_FORCE_BINARY); | |
569 | bool show_binary = !!(pi->flags & GIT_DIFF_SHOW_BINARY); | |
570 | int id_strlen = pi->id_strlen; | |
571 | bool print_index = (pi->format != GIT_DIFF_FORMAT_PATCH_ID); | |
572 | ||
573 | if (binary && show_binary) | |
574 | id_strlen = delta->old_file.id_abbrev ? delta->old_file.id_abbrev : | |
575 | delta->new_file.id_abbrev; | |
576 | ||
577 | GIT_UNUSED(progress); | |
578 | ||
579 | if (S_ISDIR(delta->new_file.mode) || | |
580 | delta->status == GIT_DELTA_UNMODIFIED || | |
581 | delta->status == GIT_DELTA_IGNORED || | |
582 | delta->status == GIT_DELTA_UNREADABLE || | |
583 | (delta->status == GIT_DELTA_UNTRACKED && | |
584 | (pi->flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) == 0)) | |
585 | return 0; | |
586 | ||
587 | if ((error = git_diff_delta__format_file_header(pi->buf, delta, oldpfx, newpfx, | |
588 | id_strlen, print_index)) < 0) | |
589 | return error; | |
590 | ||
591 | pi->line.origin = GIT_DIFF_LINE_FILE_HDR; | |
592 | pi->line.content = git_buf_cstr(pi->buf); | |
593 | pi->line.content_len = git_buf_len(pi->buf); | |
594 | ||
595 | return pi->print_cb(delta, NULL, &pi->line, pi->payload); | |
596 | } | |
597 | ||
598 | static int diff_print_patch_binary( | |
599 | const git_diff_delta *delta, | |
600 | const git_diff_binary *binary, | |
601 | void *data) | |
602 | { | |
603 | diff_print_info *pi = data; | |
604 | const char *old_pfx = | |
605 | pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT; | |
606 | const char *new_pfx = | |
607 | pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT; | |
608 | int error; | |
609 | ||
610 | git_buf_clear(pi->buf); | |
611 | ||
612 | if ((error = diff_print_patch_file_binary( | |
613 | pi, (git_diff_delta *)delta, old_pfx, new_pfx, binary)) < 0) | |
614 | return error; | |
615 | ||
616 | pi->line.origin = GIT_DIFF_LINE_BINARY; | |
617 | pi->line.content = git_buf_cstr(pi->buf); | |
618 | pi->line.content_len = git_buf_len(pi->buf); | |
619 | ||
620 | return pi->print_cb(delta, NULL, &pi->line, pi->payload); | |
621 | } | |
622 | ||
623 | static int diff_print_patch_hunk( | |
624 | const git_diff_delta *d, | |
625 | const git_diff_hunk *h, | |
626 | void *data) | |
627 | { | |
628 | diff_print_info *pi = data; | |
629 | ||
630 | if (S_ISDIR(d->new_file.mode)) | |
631 | return 0; | |
632 | ||
633 | pi->line.origin = GIT_DIFF_LINE_HUNK_HDR; | |
634 | pi->line.content = h->header; | |
635 | pi->line.content_len = h->header_len; | |
636 | ||
637 | return pi->print_cb(d, h, &pi->line, pi->payload); | |
638 | } | |
639 | ||
640 | static int diff_print_patch_line( | |
641 | const git_diff_delta *delta, | |
642 | const git_diff_hunk *hunk, | |
643 | const git_diff_line *line, | |
644 | void *data) | |
645 | { | |
646 | diff_print_info *pi = data; | |
647 | ||
648 | if (S_ISDIR(delta->new_file.mode)) | |
649 | return 0; | |
650 | ||
651 | return pi->print_cb(delta, hunk, line, pi->payload); | |
652 | } | |
653 | ||
654 | /* print a git_diff to an output callback */ | |
655 | int git_diff_print( | |
656 | git_diff *diff, | |
657 | git_diff_format_t format, | |
658 | git_diff_line_cb print_cb, | |
659 | void *payload) | |
660 | { | |
661 | int error; | |
662 | git_buf buf = GIT_BUF_INIT; | |
663 | diff_print_info pi; | |
664 | git_diff_file_cb print_file = NULL; | |
665 | git_diff_binary_cb print_binary = NULL; | |
666 | git_diff_hunk_cb print_hunk = NULL; | |
667 | git_diff_line_cb print_line = NULL; | |
668 | ||
669 | switch (format) { | |
670 | case GIT_DIFF_FORMAT_PATCH: | |
671 | print_file = diff_print_patch_file; | |
672 | print_binary = diff_print_patch_binary; | |
673 | print_hunk = diff_print_patch_hunk; | |
674 | print_line = diff_print_patch_line; | |
675 | break; | |
676 | case GIT_DIFF_FORMAT_PATCH_ID: | |
677 | print_file = diff_print_patch_file; | |
678 | print_binary = diff_print_patch_binary; | |
679 | print_line = diff_print_patch_line; | |
680 | break; | |
681 | case GIT_DIFF_FORMAT_PATCH_HEADER: | |
682 | print_file = diff_print_patch_file; | |
683 | break; | |
684 | case GIT_DIFF_FORMAT_RAW: | |
685 | print_file = diff_print_one_raw; | |
686 | break; | |
687 | case GIT_DIFF_FORMAT_NAME_ONLY: | |
688 | print_file = diff_print_one_name_only; | |
689 | break; | |
690 | case GIT_DIFF_FORMAT_NAME_STATUS: | |
691 | print_file = diff_print_one_name_status; | |
692 | break; | |
693 | default: | |
694 | git_error_set(GIT_ERROR_INVALID, "unknown diff output format (%d)", format); | |
695 | return -1; | |
696 | } | |
697 | ||
698 | if ((error = diff_print_info_init_fromdiff(&pi, &buf, diff, format, print_cb, payload)) < 0) | |
699 | goto out; | |
700 | ||
701 | if ((error = git_diff_foreach(diff, print_file, print_binary, print_hunk, print_line, &pi)) != 0) { | |
702 | git_error_set_after_callback_function(error, "git_diff_print"); | |
703 | goto out; | |
704 | } | |
705 | ||
706 | out: | |
707 | git_buf_dispose(&buf); | |
708 | return error; | |
709 | } | |
710 | ||
711 | int git_diff_print_callback__to_buf( | |
712 | const git_diff_delta *delta, | |
713 | const git_diff_hunk *hunk, | |
714 | const git_diff_line *line, | |
715 | void *payload) | |
716 | { | |
717 | git_buf *output = payload; | |
718 | GIT_UNUSED(delta); GIT_UNUSED(hunk); | |
719 | ||
720 | if (!output) { | |
721 | git_error_set(GIT_ERROR_INVALID, "buffer pointer must be provided"); | |
722 | return -1; | |
723 | } | |
724 | ||
725 | if (line->origin == GIT_DIFF_LINE_ADDITION || | |
726 | line->origin == GIT_DIFF_LINE_DELETION || | |
727 | line->origin == GIT_DIFF_LINE_CONTEXT) | |
728 | git_buf_putc(output, line->origin); | |
729 | ||
730 | return git_buf_put(output, line->content, line->content_len); | |
731 | } | |
732 | ||
733 | int git_diff_print_callback__to_file_handle( | |
734 | const git_diff_delta *delta, | |
735 | const git_diff_hunk *hunk, | |
736 | const git_diff_line *line, | |
737 | void *payload) | |
738 | { | |
739 | FILE *fp = payload ? payload : stdout; | |
740 | int error; | |
741 | ||
742 | GIT_UNUSED(delta); | |
743 | GIT_UNUSED(hunk); | |
744 | ||
745 | if (line->origin == GIT_DIFF_LINE_CONTEXT || | |
746 | line->origin == GIT_DIFF_LINE_ADDITION || | |
747 | line->origin == GIT_DIFF_LINE_DELETION) { | |
748 | while ((error = fputc(line->origin, fp)) == EINTR) | |
749 | continue; | |
750 | if (error) { | |
751 | git_error_set(GIT_ERROR_OS, "could not write status"); | |
752 | return -1; | |
753 | } | |
754 | } | |
755 | ||
756 | if (fwrite(line->content, line->content_len, 1, fp) != 1) { | |
757 | git_error_set(GIT_ERROR_OS, "could not write line"); | |
758 | return -1; | |
759 | } | |
760 | ||
761 | return 0; | |
762 | } | |
763 | ||
764 | /* print a git_diff to a git_buf */ | |
765 | int git_diff_to_buf(git_buf *out, git_diff *diff, git_diff_format_t format) | |
766 | { | |
767 | int error; | |
768 | ||
769 | GIT_ASSERT_ARG(out); | |
770 | GIT_ASSERT_ARG(diff); | |
771 | ||
772 | if ((error = git_buf_sanitize(out)) < 0) | |
773 | return error; | |
774 | ||
775 | return git_diff_print(diff, format, git_diff_print_callback__to_buf, out); | |
776 | } | |
777 | ||
778 | /* print a git_patch to an output callback */ | |
779 | int git_patch_print( | |
780 | git_patch *patch, | |
781 | git_diff_line_cb print_cb, | |
782 | void *payload) | |
783 | { | |
784 | git_buf temp = GIT_BUF_INIT; | |
785 | diff_print_info pi; | |
786 | int error; | |
787 | ||
788 | GIT_ASSERT_ARG(patch); | |
789 | GIT_ASSERT_ARG(print_cb); | |
790 | ||
791 | if ((error = diff_print_info_init_frompatch(&pi, &temp, patch, | |
792 | GIT_DIFF_FORMAT_PATCH, print_cb, payload)) < 0) | |
793 | goto out; | |
794 | ||
795 | if ((error = git_patch__invoke_callbacks(patch, diff_print_patch_file, diff_print_patch_binary, | |
796 | diff_print_patch_hunk, diff_print_patch_line, &pi)) < 0) { | |
797 | git_error_set_after_callback_function(error, "git_patch_print"); | |
798 | goto out; | |
799 | } | |
800 | ||
801 | out: | |
802 | git_buf_dispose(&temp); | |
803 | return error; | |
804 | } | |
805 | ||
806 | /* print a git_patch to a git_buf */ | |
807 | int git_patch_to_buf(git_buf *out, git_patch *patch) | |
808 | { | |
809 | int error; | |
810 | ||
811 | GIT_ASSERT_ARG(out); | |
812 | GIT_ASSERT_ARG(patch); | |
813 | ||
814 | if ((error = git_buf_sanitize(out)) < 0) | |
815 | return error; | |
816 | ||
817 | return git_patch_print(patch, git_diff_print_callback__to_buf, out); | |
818 | } |