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 "patch_generate.h"
9 
10 #include "git2/blob.h"
11 #include "diff.h"
12 #include "diff_generate.h"
13 #include "diff_file.h"
14 #include "diff_driver.h"
15 #include "diff_xdiff.h"
16 #include "delta.h"
17 #include "zstream.h"
18 #include "futils.h"
19 
20 static void diff_output_init(
21 	git_patch_generated_output *, const git_diff_options *, git_diff_file_cb,
22 	git_diff_binary_cb, git_diff_hunk_cb, git_diff_line_cb, void*);
23 
24 static void diff_output_to_patch(
25 	git_patch_generated_output *, git_patch_generated *);
26 
patch_generated_free(git_patch * p)27 static void patch_generated_free(git_patch *p)
28 {
29 	git_patch_generated *patch = (git_patch_generated *)p;
30 
31 	git_array_clear(patch->base.lines);
32 	git_array_clear(patch->base.hunks);
33 
34 	git__free((char *)patch->base.binary.old_file.data);
35 	git__free((char *)patch->base.binary.new_file.data);
36 
37 	git_diff_file_content__clear(&patch->ofile);
38 	git_diff_file_content__clear(&patch->nfile);
39 
40 	git_diff_free(patch->diff); /* decrements refcount */
41 	patch->diff = NULL;
42 
43 	git_pool_clear(&patch->flattened);
44 
45 	git__free((char *)patch->base.diff_opts.old_prefix);
46 	git__free((char *)patch->base.diff_opts.new_prefix);
47 
48 	if (patch->flags & GIT_PATCH_GENERATED_ALLOCATED)
49 		git__free(patch);
50 }
51 
patch_generated_update_binary(git_patch_generated * patch)52 static void patch_generated_update_binary(git_patch_generated *patch)
53 {
54 	if ((patch->base.delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0)
55 		return;
56 
57 	if ((patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0 ||
58 		(patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0)
59 		patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY;
60 
61 	else if (patch->ofile.file->size > GIT_XDIFF_MAX_SIZE ||
62 		patch->nfile.file->size > GIT_XDIFF_MAX_SIZE)
63 		patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY;
64 
65 	else if ((patch->ofile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0 &&
66 		(patch->nfile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0)
67 		patch->base.delta->flags |= GIT_DIFF_FLAG_NOT_BINARY;
68 }
69 
patch_generated_init_common(git_patch_generated * patch)70 static void patch_generated_init_common(git_patch_generated *patch)
71 {
72 	patch->base.free_fn = patch_generated_free;
73 
74 	patch_generated_update_binary(patch);
75 
76 	patch->flags |= GIT_PATCH_GENERATED_INITIALIZED;
77 
78 	if (patch->diff)
79 		git_diff_addref(patch->diff);
80 }
81 
patch_generated_normalize_options(git_diff_options * out,const git_diff_options * opts)82 static int patch_generated_normalize_options(
83 	git_diff_options *out,
84 	const git_diff_options *opts)
85 {
86 	if (opts) {
87 		GIT_ERROR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options");
88 		memcpy(out, opts, sizeof(git_diff_options));
89 	} else {
90 		git_diff_options default_opts = GIT_DIFF_OPTIONS_INIT;
91 		memcpy(out, &default_opts, sizeof(git_diff_options));
92 	}
93 
94 	out->old_prefix = opts && opts->old_prefix ?
95 		git__strdup(opts->old_prefix) :
96 		git__strdup(DIFF_OLD_PREFIX_DEFAULT);
97 
98 	out->new_prefix = opts && opts->new_prefix ?
99 		git__strdup(opts->new_prefix) :
100 		git__strdup(DIFF_NEW_PREFIX_DEFAULT);
101 
102 	GIT_ERROR_CHECK_ALLOC(out->old_prefix);
103 	GIT_ERROR_CHECK_ALLOC(out->new_prefix);
104 
105 	return 0;
106 }
107 
patch_generated_init(git_patch_generated * patch,git_diff * diff,size_t delta_index)108 static int patch_generated_init(
109 	git_patch_generated *patch, git_diff *diff, size_t delta_index)
110 {
111 	int error = 0;
112 
113 	memset(patch, 0, sizeof(*patch));
114 
115 	patch->diff = diff;
116 	patch->base.repo = diff->repo;
117 	patch->base.delta = git_vector_get(&diff->deltas, delta_index);
118 	patch->delta_index = delta_index;
119 
120 	if ((error = patch_generated_normalize_options(
121 			&patch->base.diff_opts, &diff->opts)) < 0 ||
122 		(error = git_diff_file_content__init_from_diff(
123 			&patch->ofile, diff, patch->base.delta, true)) < 0 ||
124 		(error = git_diff_file_content__init_from_diff(
125 			&patch->nfile, diff, patch->base.delta, false)) < 0)
126 		return error;
127 
128 	patch_generated_init_common(patch);
129 
130 	return 0;
131 }
132 
patch_generated_alloc_from_diff(git_patch_generated ** out,git_diff * diff,size_t delta_index)133 static int patch_generated_alloc_from_diff(
134 	git_patch_generated **out, git_diff *diff, size_t delta_index)
135 {
136 	int error;
137 	git_patch_generated *patch = git__calloc(1, sizeof(git_patch_generated));
138 	GIT_ERROR_CHECK_ALLOC(patch);
139 
140 	if (!(error = patch_generated_init(patch, diff, delta_index))) {
141 		patch->flags |= GIT_PATCH_GENERATED_ALLOCATED;
142 		GIT_REFCOUNT_INC(&patch->base);
143 	} else {
144 		git__free(patch);
145 		patch = NULL;
146 	}
147 
148 	*out = patch;
149 	return error;
150 }
151 
should_skip_binary(git_patch_generated * patch,git_diff_file * file)152 GIT_INLINE(bool) should_skip_binary(git_patch_generated *patch, git_diff_file *file)
153 {
154 	if ((patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) != 0)
155 		return false;
156 
157 	return (file->flags & GIT_DIFF_FLAG_BINARY) != 0;
158 }
159 
patch_generated_diffable(git_patch_generated * patch)160 static bool patch_generated_diffable(git_patch_generated *patch)
161 {
162 	size_t olen, nlen;
163 
164 	if (patch->base.delta->status == GIT_DELTA_UNMODIFIED)
165 		return false;
166 
167 	/* if we've determined this to be binary (and we are not showing binary
168 	 * data) then we have skipped loading the map data.  instead, query the
169 	 * file data itself.
170 	 */
171 	if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0 &&
172 		(patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) == 0) {
173 		olen = (size_t)patch->ofile.file->size;
174 		nlen = (size_t)patch->nfile.file->size;
175 	} else {
176 		olen = patch->ofile.map.len;
177 		nlen = patch->nfile.map.len;
178 	}
179 
180 	/* if both sides are empty, files are identical */
181 	if (!olen && !nlen)
182 		return false;
183 
184 	/* otherwise, check the file sizes and the oid */
185 	return (olen != nlen ||
186 		!git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id));
187 }
188 
patch_generated_load(git_patch_generated * patch,git_patch_generated_output * output)189 static int patch_generated_load(git_patch_generated *patch, git_patch_generated_output *output)
190 {
191 	int error = 0;
192 	bool incomplete_data;
193 
194 	if ((patch->flags & GIT_PATCH_GENERATED_LOADED) != 0)
195 		return 0;
196 
197 	/* if no hunk and data callbacks and user doesn't care if data looks
198 	 * binary, then there is no need to actually load the data
199 	 */
200 	if ((patch->ofile.opts_flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0 &&
201 		output && !output->binary_cb && !output->hunk_cb && !output->data_cb)
202 		return 0;
203 
204 	incomplete_data =
205 		(((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 ||
206 		  (patch->ofile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 0) &&
207 		 ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 ||
208 		  (patch->nfile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 0));
209 
210 	if ((error = git_diff_file_content__load(
211 			&patch->ofile, &patch->base.diff_opts)) < 0 ||
212 	    (error = git_diff_file_content__load(
213 			&patch->nfile, &patch->base.diff_opts)) < 0 ||
214 		should_skip_binary(patch, patch->nfile.file))
215 		goto cleanup;
216 
217 	/* if previously missing an oid, and now that we have it the two sides
218 	 * are the same (and not submodules), update MODIFIED -> UNMODIFIED
219 	 */
220 	if (incomplete_data &&
221 		patch->ofile.file->mode == patch->nfile.file->mode &&
222 		patch->ofile.file->mode != GIT_FILEMODE_COMMIT &&
223 		git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id) &&
224 		patch->base.delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */
225 		patch->base.delta->status = GIT_DELTA_UNMODIFIED;
226 
227 cleanup:
228 	patch_generated_update_binary(patch);
229 
230 	if (!error) {
231 		if (patch_generated_diffable(patch))
232 			patch->flags |= GIT_PATCH_GENERATED_DIFFABLE;
233 
234 		patch->flags |= GIT_PATCH_GENERATED_LOADED;
235 	}
236 
237 	return error;
238 }
239 
patch_generated_invoke_file_callback(git_patch_generated * patch,git_patch_generated_output * output)240 static int patch_generated_invoke_file_callback(
241 	git_patch_generated *patch, git_patch_generated_output *output)
242 {
243 	float progress = patch->diff ?
244 		((float)patch->delta_index / patch->diff->deltas.length) : 1.0f;
245 
246 	if (!output->file_cb)
247 		return 0;
248 
249 	return git_error_set_after_callback_function(
250 		output->file_cb(patch->base.delta, progress, output->payload),
251 		"git_patch");
252 }
253 
create_binary(git_diff_binary_t * out_type,char ** out_data,size_t * out_datalen,size_t * out_inflatedlen,const char * a_data,size_t a_datalen,const char * b_data,size_t b_datalen)254 static int create_binary(
255 	git_diff_binary_t *out_type,
256 	char **out_data,
257 	size_t *out_datalen,
258 	size_t *out_inflatedlen,
259 	const char *a_data,
260 	size_t a_datalen,
261 	const char *b_data,
262 	size_t b_datalen)
263 {
264 	git_buf deflate = GIT_BUF_INIT, delta = GIT_BUF_INIT;
265 	size_t delta_data_len = 0;
266 	int error;
267 
268 	/* The git_delta function accepts unsigned long only */
269 	if (!git__is_ulong(a_datalen) || !git__is_ulong(b_datalen))
270 		return GIT_EBUFS;
271 
272 	if ((error = git_zstream_deflatebuf(&deflate, b_data, b_datalen)) < 0)
273 		goto done;
274 
275 	/* The git_delta function accepts unsigned long only */
276 	if (!git__is_ulong(deflate.size)) {
277 		error = GIT_EBUFS;
278 		goto done;
279 	}
280 
281 	if (a_datalen && b_datalen) {
282 		void *delta_data;
283 
284 		error = git_delta(&delta_data, &delta_data_len,
285 			a_data, a_datalen,
286 			b_data, b_datalen,
287 			deflate.size);
288 
289 		if (error == 0) {
290 			error = git_zstream_deflatebuf(
291 				&delta, delta_data, delta_data_len);
292 
293 			git__free(delta_data);
294 		} else if (error == GIT_EBUFS) {
295 			error = 0;
296 		}
297 
298 		if (error < 0)
299 			goto done;
300 	}
301 
302 	if (delta.size && delta.size < deflate.size) {
303 		*out_type = GIT_DIFF_BINARY_DELTA;
304 		*out_datalen = delta.size;
305 		*out_data = git_buf_detach(&delta);
306 		*out_inflatedlen = delta_data_len;
307 	} else {
308 		*out_type = GIT_DIFF_BINARY_LITERAL;
309 		*out_datalen = deflate.size;
310 		*out_data = git_buf_detach(&deflate);
311 		*out_inflatedlen = b_datalen;
312 	}
313 
314 done:
315 	git_buf_dispose(&deflate);
316 	git_buf_dispose(&delta);
317 
318 	return error;
319 }
320 
diff_binary(git_patch_generated_output * output,git_patch_generated * patch)321 static int diff_binary(git_patch_generated_output *output, git_patch_generated *patch)
322 {
323 	git_diff_binary binary = {0};
324 	const char *old_data = patch->ofile.map.data;
325 	const char *new_data = patch->nfile.map.data;
326 	size_t old_len = patch->ofile.map.len,
327 		new_len = patch->nfile.map.len;
328 	int error;
329 
330 	/* Only load contents if the user actually wants to diff
331 	 * binary files. */
332 	if (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) {
333 		binary.contains_data = 1;
334 
335 		/* Create the old->new delta (as the "new" side of the patch),
336 		 * and the new->old delta (as the "old" side)
337 		 */
338 		if ((error = create_binary(&binary.old_file.type,
339 				(char **)&binary.old_file.data,
340 				&binary.old_file.datalen,
341 				&binary.old_file.inflatedlen,
342 				new_data, new_len, old_data, old_len)) < 0 ||
343 			(error = create_binary(&binary.new_file.type,
344 				(char **)&binary.new_file.data,
345 				&binary.new_file.datalen,
346 				&binary.new_file.inflatedlen,
347 				old_data, old_len, new_data, new_len)) < 0)
348 			return error;
349 	}
350 
351 	error = git_error_set_after_callback_function(
352 		output->binary_cb(patch->base.delta, &binary, output->payload),
353 		"git_patch");
354 
355 	git__free((char *) binary.old_file.data);
356 	git__free((char *) binary.new_file.data);
357 
358 	return error;
359 }
360 
patch_generated_create(git_patch_generated * patch,git_patch_generated_output * output)361 static int patch_generated_create(
362 	git_patch_generated *patch,
363 	git_patch_generated_output *output)
364 {
365 	int error = 0;
366 
367 	if ((patch->flags & GIT_PATCH_GENERATED_DIFFED) != 0)
368 		return 0;
369 
370 	/* if we are not looking at the binary or text data, don't do the diff */
371 	if (!output->binary_cb && !output->hunk_cb && !output->data_cb)
372 		return 0;
373 
374 	if ((patch->flags & GIT_PATCH_GENERATED_LOADED) == 0 &&
375 		(error = patch_generated_load(patch, output)) < 0)
376 		return error;
377 
378 	if ((patch->flags & GIT_PATCH_GENERATED_DIFFABLE) == 0)
379 		return 0;
380 
381 	if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0) {
382 		if (output->binary_cb)
383 			error = diff_binary(output, patch);
384 	}
385 	else {
386 		if (output->diff_cb)
387 			error = output->diff_cb(output, patch);
388 	}
389 
390 	patch->flags |= GIT_PATCH_GENERATED_DIFFED;
391 	return error;
392 }
393 
diff_required(git_diff * diff,const char * action)394 static int diff_required(git_diff *diff, const char *action)
395 {
396 	if (diff)
397 		return 0;
398 	git_error_set(GIT_ERROR_INVALID, "must provide valid diff to %s", action);
399 	return -1;
400 }
401 
402 typedef struct {
403 	git_patch_generated patch;
404 	git_diff_delta delta;
405 	char paths[GIT_FLEX_ARRAY];
406 } patch_generated_with_delta;
407 
diff_single_generate(patch_generated_with_delta * pd,git_xdiff_output * xo)408 static int diff_single_generate(patch_generated_with_delta *pd, git_xdiff_output *xo)
409 {
410 	int error = 0;
411 	git_patch_generated *patch = &pd->patch;
412 	bool has_old = ((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
413 	bool has_new = ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
414 
415 	pd->delta.status = has_new ?
416 		(has_old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) :
417 		(has_old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED);
418 
419 	if (git_oid_equal(&patch->nfile.file->id, &patch->ofile.file->id))
420 		pd->delta.status = GIT_DELTA_UNMODIFIED;
421 
422 	patch->base.delta = &pd->delta;
423 
424 	patch_generated_init_common(patch);
425 
426 	if (pd->delta.status == GIT_DELTA_UNMODIFIED &&
427 		!(patch->ofile.opts_flags & GIT_DIFF_INCLUDE_UNMODIFIED)) {
428 
429 		/* Even empty patches are flagged as binary, and even though
430 		 * there's no difference, we flag this as "containing data"
431 		 * (the data is known to be empty, as opposed to wholly unknown).
432 		 */
433 		if (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY)
434 			patch->base.binary.contains_data = 1;
435 
436 		return error;
437 	}
438 
439 	error = patch_generated_invoke_file_callback(patch, (git_patch_generated_output *)xo);
440 
441 	if (!error)
442 		error = patch_generated_create(patch, (git_patch_generated_output *)xo);
443 
444 	return error;
445 }
446 
patch_generated_from_sources(patch_generated_with_delta * pd,git_xdiff_output * xo,git_diff_file_content_src * oldsrc,git_diff_file_content_src * newsrc,const git_diff_options * opts)447 static int patch_generated_from_sources(
448 	patch_generated_with_delta *pd,
449 	git_xdiff_output *xo,
450 	git_diff_file_content_src *oldsrc,
451 	git_diff_file_content_src *newsrc,
452 	const git_diff_options *opts)
453 {
454 	int error = 0;
455 	git_repository *repo =
456 		oldsrc->blob ? git_blob_owner(oldsrc->blob) :
457 		newsrc->blob ? git_blob_owner(newsrc->blob) : NULL;
458 	git_diff_file *lfile = &pd->delta.old_file, *rfile = &pd->delta.new_file;
459 	git_diff_file_content *ldata = &pd->patch.ofile, *rdata = &pd->patch.nfile;
460 
461 	if ((error = patch_generated_normalize_options(&pd->patch.base.diff_opts, opts)) < 0)
462 		return error;
463 
464 	if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) {
465 		void *tmp = lfile; lfile = rfile; rfile = tmp;
466 		tmp = ldata; ldata = rdata; rdata = tmp;
467 	}
468 
469 	pd->patch.base.delta = &pd->delta;
470 
471 	if (!oldsrc->as_path) {
472 		if (newsrc->as_path)
473 			oldsrc->as_path = newsrc->as_path;
474 		else
475 			oldsrc->as_path = newsrc->as_path = "file";
476 	}
477 	else if (!newsrc->as_path)
478 		newsrc->as_path = oldsrc->as_path;
479 
480 	lfile->path = oldsrc->as_path;
481 	rfile->path = newsrc->as_path;
482 
483 	if ((error = git_diff_file_content__init_from_src(
484 			ldata, repo, opts, oldsrc, lfile)) < 0 ||
485 		(error = git_diff_file_content__init_from_src(
486 			rdata, repo, opts, newsrc, rfile)) < 0)
487 		return error;
488 
489 	return diff_single_generate(pd, xo);
490 }
491 
patch_generated_with_delta_alloc(patch_generated_with_delta ** out,const char ** old_path,const char ** new_path)492 static int patch_generated_with_delta_alloc(
493 	patch_generated_with_delta **out,
494 	const char **old_path,
495 	const char **new_path)
496 {
497 	patch_generated_with_delta *pd;
498 	size_t old_len = *old_path ? strlen(*old_path) : 0;
499 	size_t new_len = *new_path ? strlen(*new_path) : 0;
500 	size_t alloc_len;
501 
502 	GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(*pd), old_len);
503 	GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, new_len);
504 	GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2);
505 
506 	*out = pd = git__calloc(1, alloc_len);
507 	GIT_ERROR_CHECK_ALLOC(pd);
508 
509 	pd->patch.flags = GIT_PATCH_GENERATED_ALLOCATED;
510 
511 	if (*old_path) {
512 		memcpy(&pd->paths[0], *old_path, old_len);
513 		*old_path = &pd->paths[0];
514 	} else if (*new_path)
515 		*old_path = &pd->paths[old_len + 1];
516 
517 	if (*new_path) {
518 		memcpy(&pd->paths[old_len + 1], *new_path, new_len);
519 		*new_path = &pd->paths[old_len + 1];
520 	} else if (*old_path)
521 		*new_path = &pd->paths[0];
522 
523 	return 0;
524 }
525 
diff_from_sources(git_diff_file_content_src * oldsrc,git_diff_file_content_src * newsrc,const git_diff_options * opts,git_diff_file_cb file_cb,git_diff_binary_cb binary_cb,git_diff_hunk_cb hunk_cb,git_diff_line_cb data_cb,void * payload)526 static int diff_from_sources(
527 	git_diff_file_content_src *oldsrc,
528 	git_diff_file_content_src *newsrc,
529 	const git_diff_options *opts,
530 	git_diff_file_cb file_cb,
531 	git_diff_binary_cb binary_cb,
532 	git_diff_hunk_cb hunk_cb,
533 	git_diff_line_cb data_cb,
534 	void *payload)
535 {
536 	int error = 0;
537 	patch_generated_with_delta pd;
538 	git_xdiff_output xo;
539 
540 	memset(&xo, 0, sizeof(xo));
541 	diff_output_init(
542 		&xo.output, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
543 	git_xdiff_init(&xo, opts);
544 
545 	memset(&pd, 0, sizeof(pd));
546 
547 	error = patch_generated_from_sources(&pd, &xo, oldsrc, newsrc, opts);
548 
549 	git_patch_free(&pd.patch.base);
550 
551 	return error;
552 }
553 
patch_from_sources(git_patch ** out,git_diff_file_content_src * oldsrc,git_diff_file_content_src * newsrc,const git_diff_options * opts)554 static int patch_from_sources(
555 	git_patch **out,
556 	git_diff_file_content_src *oldsrc,
557 	git_diff_file_content_src *newsrc,
558 	const git_diff_options *opts)
559 {
560 	int error = 0;
561 	patch_generated_with_delta *pd;
562 	git_xdiff_output xo;
563 
564 	GIT_ASSERT_ARG(out);
565 	*out = NULL;
566 
567 	if ((error = patch_generated_with_delta_alloc(
568 			&pd, &oldsrc->as_path, &newsrc->as_path)) < 0)
569 		return error;
570 
571 	memset(&xo, 0, sizeof(xo));
572 	diff_output_to_patch(&xo.output, &pd->patch);
573 	git_xdiff_init(&xo, opts);
574 
575 	if (!(error = patch_generated_from_sources(pd, &xo, oldsrc, newsrc, opts)))
576 		*out = (git_patch *)pd;
577 	else
578 		git_patch_free((git_patch *)pd);
579 
580 	return error;
581 }
582 
git_diff_blobs(const git_blob * old_blob,const char * old_path,const git_blob * new_blob,const char * new_path,const git_diff_options * opts,git_diff_file_cb file_cb,git_diff_binary_cb binary_cb,git_diff_hunk_cb hunk_cb,git_diff_line_cb data_cb,void * payload)583 int git_diff_blobs(
584 	const git_blob *old_blob,
585 	const char *old_path,
586 	const git_blob *new_blob,
587 	const char *new_path,
588 	const git_diff_options *opts,
589 	git_diff_file_cb file_cb,
590 	git_diff_binary_cb binary_cb,
591 	git_diff_hunk_cb hunk_cb,
592 	git_diff_line_cb data_cb,
593 	void *payload)
594 {
595 	git_diff_file_content_src osrc =
596 		GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
597 	git_diff_file_content_src nsrc =
598 		GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path);
599 	return diff_from_sources(
600 		&osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
601 }
602 
git_patch_from_blobs(git_patch ** out,const git_blob * old_blob,const char * old_path,const git_blob * new_blob,const char * new_path,const git_diff_options * opts)603 int git_patch_from_blobs(
604 	git_patch **out,
605 	const git_blob *old_blob,
606 	const char *old_path,
607 	const git_blob *new_blob,
608 	const char *new_path,
609 	const git_diff_options *opts)
610 {
611 	git_diff_file_content_src osrc =
612 		GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
613 	git_diff_file_content_src nsrc =
614 		GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path);
615 	return patch_from_sources(out, &osrc, &nsrc, opts);
616 }
617 
git_diff_blob_to_buffer(const git_blob * old_blob,const char * old_path,const char * buf,size_t buflen,const char * buf_path,const git_diff_options * opts,git_diff_file_cb file_cb,git_diff_binary_cb binary_cb,git_diff_hunk_cb hunk_cb,git_diff_line_cb data_cb,void * payload)618 int git_diff_blob_to_buffer(
619 	const git_blob *old_blob,
620 	const char *old_path,
621 	const char *buf,
622 	size_t buflen,
623 	const char *buf_path,
624 	const git_diff_options *opts,
625 	git_diff_file_cb file_cb,
626 	git_diff_binary_cb binary_cb,
627 	git_diff_hunk_cb hunk_cb,
628 	git_diff_line_cb data_cb,
629 	void *payload)
630 {
631 	git_diff_file_content_src osrc =
632 		GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
633 	git_diff_file_content_src nsrc =
634 		GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path);
635 	return diff_from_sources(
636 		&osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
637 }
638 
git_patch_from_blob_and_buffer(git_patch ** out,const git_blob * old_blob,const char * old_path,const void * buf,size_t buflen,const char * buf_path,const git_diff_options * opts)639 int git_patch_from_blob_and_buffer(
640 	git_patch **out,
641 	const git_blob *old_blob,
642 	const char *old_path,
643 	const void *buf,
644 	size_t buflen,
645 	const char *buf_path,
646 	const git_diff_options *opts)
647 {
648 	git_diff_file_content_src osrc =
649 		GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
650 	git_diff_file_content_src nsrc =
651 		GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path);
652 	return patch_from_sources(out, &osrc, &nsrc, opts);
653 }
654 
git_diff_buffers(const void * old_buf,size_t old_len,const char * old_path,const void * new_buf,size_t new_len,const char * new_path,const git_diff_options * opts,git_diff_file_cb file_cb,git_diff_binary_cb binary_cb,git_diff_hunk_cb hunk_cb,git_diff_line_cb data_cb,void * payload)655 int git_diff_buffers(
656 	const void *old_buf,
657 	size_t old_len,
658 	const char *old_path,
659 	const void *new_buf,
660 	size_t new_len,
661 	const char *new_path,
662 	const git_diff_options *opts,
663 	git_diff_file_cb file_cb,
664 	git_diff_binary_cb binary_cb,
665 	git_diff_hunk_cb hunk_cb,
666 	git_diff_line_cb data_cb,
667 	void *payload)
668 {
669 	git_diff_file_content_src osrc =
670 		GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path);
671 	git_diff_file_content_src nsrc =
672 		GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path);
673 	return diff_from_sources(
674 		&osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
675 }
676 
git_patch_from_buffers(git_patch ** out,const void * old_buf,size_t old_len,const char * old_path,const void * new_buf,size_t new_len,const char * new_path,const git_diff_options * opts)677 int git_patch_from_buffers(
678 	git_patch **out,
679 	const void *old_buf,
680 	size_t old_len,
681 	const char *old_path,
682 	const void *new_buf,
683 	size_t new_len,
684 	const char *new_path,
685 	const git_diff_options *opts)
686 {
687 	git_diff_file_content_src osrc =
688 		GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path);
689 	git_diff_file_content_src nsrc =
690 		GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path);
691 	return patch_from_sources(out, &osrc, &nsrc, opts);
692 }
693 
git_patch_generated_from_diff(git_patch ** patch_ptr,git_diff * diff,size_t idx)694 int git_patch_generated_from_diff(
695 	git_patch **patch_ptr, git_diff *diff, size_t idx)
696 {
697 	int error = 0;
698 	git_xdiff_output xo;
699 	git_diff_delta *delta = NULL;
700 	git_patch_generated *patch = NULL;
701 
702 	if (patch_ptr) *patch_ptr = NULL;
703 
704 	if (diff_required(diff, "git_patch_from_diff") < 0)
705 		return -1;
706 
707 	delta = git_vector_get(&diff->deltas, idx);
708 	if (!delta) {
709 		git_error_set(GIT_ERROR_INVALID, "index out of range for delta in diff");
710 		return GIT_ENOTFOUND;
711 	}
712 
713 	if (git_diff_delta__should_skip(&diff->opts, delta))
714 		return 0;
715 
716 	/* don't load the patch data unless we need it for binary check */
717 	if (!patch_ptr &&
718 		((delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0 ||
719 		 (diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0))
720 		return 0;
721 
722 	if ((error = patch_generated_alloc_from_diff(&patch, diff, idx)) < 0)
723 		return error;
724 
725 	memset(&xo, 0, sizeof(xo));
726 	diff_output_to_patch(&xo.output, patch);
727 	git_xdiff_init(&xo, &diff->opts);
728 
729 	error = patch_generated_invoke_file_callback(patch, &xo.output);
730 
731 	if (!error)
732 		error = patch_generated_create(patch, &xo.output);
733 
734 	if (!error) {
735 		/* TODO: if cumulative diff size is < 0.5 total size, flatten patch */
736 		/* TODO: and unload the file content */
737 	}
738 
739 	if (error || !patch_ptr)
740 		git_patch_free(&patch->base);
741 	else
742 		*patch_ptr = &patch->base;
743 
744 	return error;
745 }
746 
git_patch_generated_driver(git_patch_generated * patch)747 git_diff_driver *git_patch_generated_driver(git_patch_generated *patch)
748 {
749 	/* ofile driver is representative for whole patch */
750 	return patch->ofile.driver;
751 }
752 
git_patch_generated_old_data(char ** ptr,size_t * len,git_patch_generated * patch)753 void git_patch_generated_old_data(
754 	char **ptr, size_t *len, git_patch_generated *patch)
755 {
756 	*ptr = patch->ofile.map.data;
757 	*len = patch->ofile.map.len;
758 }
759 
git_patch_generated_new_data(char ** ptr,size_t * len,git_patch_generated * patch)760 void git_patch_generated_new_data(
761 	char **ptr, size_t *len, git_patch_generated *patch)
762 {
763 	*ptr = patch->nfile.map.data;
764 	*len = patch->nfile.map.len;
765 }
766 
patch_generated_file_cb(const git_diff_delta * delta,float progress,void * payload)767 static int patch_generated_file_cb(
768 	const git_diff_delta *delta,
769 	float progress,
770 	void *payload)
771 {
772 	GIT_UNUSED(delta); GIT_UNUSED(progress); GIT_UNUSED(payload);
773 	return 0;
774 }
775 
patch_generated_binary_cb(const git_diff_delta * delta,const git_diff_binary * binary,void * payload)776 static int patch_generated_binary_cb(
777 	const git_diff_delta *delta,
778 	const git_diff_binary *binary,
779 	void *payload)
780 {
781 	git_patch *patch = payload;
782 
783 	GIT_UNUSED(delta);
784 
785 	memcpy(&patch->binary, binary, sizeof(git_diff_binary));
786 
787 	if (binary->old_file.data) {
788 		patch->binary.old_file.data = git__malloc(binary->old_file.datalen);
789 		GIT_ERROR_CHECK_ALLOC(patch->binary.old_file.data);
790 
791 		memcpy((char *)patch->binary.old_file.data,
792 			binary->old_file.data, binary->old_file.datalen);
793 	}
794 
795 	if (binary->new_file.data) {
796 		patch->binary.new_file.data = git__malloc(binary->new_file.datalen);
797 		GIT_ERROR_CHECK_ALLOC(patch->binary.new_file.data);
798 
799 		memcpy((char *)patch->binary.new_file.data,
800 			binary->new_file.data, binary->new_file.datalen);
801 	}
802 
803 	return 0;
804 }
805 
git_patch_hunk_cb(const git_diff_delta * delta,const git_diff_hunk * hunk_,void * payload)806 static int git_patch_hunk_cb(
807 	const git_diff_delta *delta,
808 	const git_diff_hunk *hunk_,
809 	void *payload)
810 {
811 	git_patch_generated  *patch = payload;
812 	git_patch_hunk *hunk;
813 
814 	GIT_UNUSED(delta);
815 
816 	hunk = git_array_alloc(patch->base.hunks);
817 	GIT_ERROR_CHECK_ALLOC(hunk);
818 
819 	memcpy(&hunk->hunk, hunk_, sizeof(hunk->hunk));
820 
821 	patch->base.header_size += hunk_->header_len;
822 
823 	hunk->line_start = git_array_size(patch->base.lines);
824 	hunk->line_count = 0;
825 
826 	return 0;
827 }
828 
patch_generated_line_cb(const git_diff_delta * delta,const git_diff_hunk * hunk_,const git_diff_line * line_,void * payload)829 static int patch_generated_line_cb(
830 	const git_diff_delta *delta,
831 	const git_diff_hunk *hunk_,
832 	const git_diff_line *line_,
833 	void *payload)
834 {
835 	git_patch_generated  *patch = payload;
836 	git_patch_hunk *hunk;
837 	git_diff_line *line;
838 
839 	GIT_UNUSED(delta);
840 	GIT_UNUSED(hunk_);
841 
842 	hunk = git_array_last(patch->base.hunks);
843 	GIT_ASSERT(hunk); /* programmer error if no hunk is available */
844 
845 	line = git_array_alloc(patch->base.lines);
846 	GIT_ERROR_CHECK_ALLOC(line);
847 
848 	memcpy(line, line_, sizeof(*line));
849 
850 	/* do some bookkeeping so we can provide old/new line numbers */
851 
852 	patch->base.content_size += line->content_len;
853 
854 	if (line->origin == GIT_DIFF_LINE_ADDITION ||
855 		line->origin == GIT_DIFF_LINE_DELETION)
856 		patch->base.content_size += 1;
857 	else if (line->origin == GIT_DIFF_LINE_CONTEXT) {
858 		patch->base.content_size += 1;
859 		patch->base.context_size += line->content_len + 1;
860 	} else if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL)
861 		patch->base.context_size += line->content_len;
862 
863 	hunk->line_count++;
864 
865 	return 0;
866 }
867 
diff_output_init(git_patch_generated_output * out,const git_diff_options * opts,git_diff_file_cb file_cb,git_diff_binary_cb binary_cb,git_diff_hunk_cb hunk_cb,git_diff_line_cb data_cb,void * payload)868 static void diff_output_init(
869 	git_patch_generated_output *out,
870 	const git_diff_options *opts,
871 	git_diff_file_cb file_cb,
872 	git_diff_binary_cb binary_cb,
873 	git_diff_hunk_cb hunk_cb,
874 	git_diff_line_cb data_cb,
875 	void *payload)
876 {
877 	GIT_UNUSED(opts);
878 
879 	memset(out, 0, sizeof(*out));
880 
881 	out->file_cb = file_cb;
882 	out->binary_cb = binary_cb;
883 	out->hunk_cb = hunk_cb;
884 	out->data_cb = data_cb;
885 	out->payload = payload;
886 }
887 
diff_output_to_patch(git_patch_generated_output * out,git_patch_generated * patch)888 static void diff_output_to_patch(
889 	git_patch_generated_output *out, git_patch_generated *patch)
890 {
891 	diff_output_init(
892 		out,
893 		NULL,
894 		patch_generated_file_cb,
895 		patch_generated_binary_cb,
896 		git_patch_hunk_cb,
897 		patch_generated_line_cb,
898 		patch);
899 }
900