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 
diff_print_info_init__common(diff_print_info * pi,git_buf * out,git_repository * repo,git_diff_format_t format,git_diff_line_cb cb,void * payload)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 
diff_print_info_init_fromdiff(diff_print_info * pi,git_buf * out,git_diff * diff,git_diff_format_t format,git_diff_line_cb cb,void * payload)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 
diff_print_info_init_frompatch(diff_print_info * pi,git_buf * out,git_patch * patch,git_diff_format_t format,git_diff_line_cb cb,void * payload)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->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 
diff_pick_suffix(int mode)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 
git_diff_status_char(git_delta_t status)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 
diff_print_one_name_only(const git_diff_delta * delta,float progress,void * data)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 
diff_print_one_name_status(const git_diff_delta * delta,float progress,void * data)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 
diff_print_one_raw(const git_diff_delta * delta,float progress,void * data)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 
diff_print_modes(git_buf * out,const git_diff_delta * delta)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 
diff_print_oid_range(git_buf * out,const git_diff_delta * delta,int id_strlen,bool print_index)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 
diff_delta_format_path(git_buf * out,const char * prefix,const char * filename)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 
diff_delta_format_with_paths(git_buf * out,const git_diff_delta * delta,const char * template,const char * oldpath,const char * newpath)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 
diff_delta_format_similarity_header(git_buf * out,const git_diff_delta * delta)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 
delta_is_unchanged(const git_diff_delta * delta)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 
git_diff_delta__format_file_header(git_buf * out,const git_diff_delta * delta,const char * oldpfx,const char * newpfx,int id_strlen,bool print_index)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 
format_binary(diff_print_info * pi,git_diff_binary_t type,const char * data,size_t datalen,size_t inflatedlen)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 
diff_print_patch_file_binary_noshow(diff_print_info * pi,git_diff_delta * delta,const char * old_pfx,const char * new_pfx)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 
diff_print_patch_file_binary(diff_print_info * pi,git_diff_delta * delta,const char * old_pfx,const char * new_pfx,const git_diff_binary * binary)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 
diff_print_patch_file(const git_diff_delta * delta,float progress,void * data)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 
diff_print_patch_binary(const git_diff_delta * delta,const git_diff_binary * binary,void * data)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 
diff_print_patch_hunk(const git_diff_delta * d,const git_diff_hunk * h,void * data)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 
diff_print_patch_line(const git_diff_delta * delta,const git_diff_hunk * hunk,const git_diff_line * line,void * data)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 */
git_diff_print(git_diff * diff,git_diff_format_t format,git_diff_line_cb print_cb,void * payload)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 
git_diff_print_callback__to_buf(const git_diff_delta * delta,const git_diff_hunk * hunk,const git_diff_line * line,void * payload)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 
git_diff_print_callback__to_file_handle(const git_diff_delta * delta,const git_diff_hunk * hunk,const git_diff_line * line,void * payload)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 */
git_diff_to_buf(git_buf * out,git_diff * diff,git_diff_format_t format)765 int git_diff_to_buf(git_buf *out, git_diff *diff, git_diff_format_t format)
766 {
767 	assert(out && diff);
768 	git_buf_sanitize(out);
769 	return git_diff_print(diff, format, git_diff_print_callback__to_buf, out);
770 }
771 
772 /* print a git_patch to an output callback */
git_patch_print(git_patch * patch,git_diff_line_cb print_cb,void * payload)773 int git_patch_print(
774 	git_patch *patch,
775 	git_diff_line_cb print_cb,
776 	void *payload)
777 {
778 	git_buf temp = GIT_BUF_INIT;
779 	diff_print_info pi;
780 	int error;
781 
782 	assert(patch && print_cb);
783 
784 	if ((error = diff_print_info_init_frompatch(&pi, &temp, patch,
785 						    GIT_DIFF_FORMAT_PATCH, print_cb, payload)) < 0)
786 		goto out;
787 
788 	if ((error = git_patch__invoke_callbacks(patch, diff_print_patch_file, diff_print_patch_binary,
789 						 diff_print_patch_hunk, diff_print_patch_line, &pi)) < 0) {
790 		git_error_set_after_callback_function(error, "git_patch_print");
791 		goto out;
792 	}
793 
794 out:
795 	git_buf_dispose(&temp);
796 	return error;
797 }
798 
799 /* print a git_patch to a git_buf */
git_patch_to_buf(git_buf * out,git_patch * patch)800 int git_patch_to_buf(git_buf *out, git_patch *patch)
801 {
802 	assert(out && patch);
803 	git_buf_sanitize(out);
804 	return git_patch_print(patch, git_diff_print_callback__to_buf, out);
805 }
806