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 "fileops.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 		GITERR_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 	GITERR_CHECK_ALLOC(out->old_prefix);
103 	GITERR_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 	GITERR_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 		should_skip_binary(patch, patch->ofile.file))
213 		goto cleanup;
214 	if ((error = git_diff_file_content__load(
215 			&patch->nfile, &patch->base.diff_opts)) < 0 ||
216 		should_skip_binary(patch, patch->nfile.file))
217 		goto cleanup;
218 
219 	/* if previously missing an oid, and now that we have it the two sides
220 	 * are the same (and not submodules), update MODIFIED -> UNMODIFIED
221 	 */
222 	if (incomplete_data &&
223 		patch->ofile.file->mode == patch->nfile.file->mode &&
224 		patch->ofile.file->mode != GIT_FILEMODE_COMMIT &&
225 		git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id) &&
226 		patch->base.delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */
227 		patch->base.delta->status = GIT_DELTA_UNMODIFIED;
228 
229 cleanup:
230 	patch_generated_update_binary(patch);
231 
232 	if (!error) {
233 		if (patch_generated_diffable(patch))
234 			patch->flags |= GIT_PATCH_GENERATED_DIFFABLE;
235 
236 		patch->flags |= GIT_PATCH_GENERATED_LOADED;
237 	}
238 
239 	return error;
240 }
241 
patch_generated_invoke_file_callback(git_patch_generated * patch,git_patch_generated_output * output)242 static int patch_generated_invoke_file_callback(
243 	git_patch_generated *patch, git_patch_generated_output *output)
244 {
245 	float progress = patch->diff ?
246 		((float)patch->delta_index / patch->diff->deltas.length) : 1.0f;
247 
248 	if (!output->file_cb)
249 		return 0;
250 
251 	return giterr_set_after_callback_function(
252 		output->file_cb(patch->base.delta, progress, output->payload),
253 		"git_patch");
254 }
255 
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)256 static int create_binary(
257 	git_diff_binary_t *out_type,
258 	char **out_data,
259 	size_t *out_datalen,
260 	size_t *out_inflatedlen,
261 	const char *a_data,
262 	size_t a_datalen,
263 	const char *b_data,
264 	size_t b_datalen)
265 {
266 	git_buf deflate = GIT_BUF_INIT, delta = GIT_BUF_INIT;
267 	size_t delta_data_len = 0;
268 	int error;
269 
270 	/* The git_delta function accepts unsigned long only */
271 	if (!git__is_ulong(a_datalen) || !git__is_ulong(b_datalen))
272 		return GIT_EBUFS;
273 
274 	if ((error = git_zstream_deflatebuf(&deflate, b_data, b_datalen)) < 0)
275 		goto done;
276 
277 	/* The git_delta function accepts unsigned long only */
278 	if (!git__is_ulong(deflate.size)) {
279 		error = GIT_EBUFS;
280 		goto done;
281 	}
282 
283 	if (a_datalen && b_datalen) {
284 		void *delta_data;
285 
286 		error = git_delta(&delta_data, &delta_data_len,
287 			a_data, a_datalen,
288 			b_data, b_datalen,
289 			deflate.size);
290 
291 		if (error == 0) {
292 			error = git_zstream_deflatebuf(
293 				&delta, delta_data, delta_data_len);
294 
295 			git__free(delta_data);
296 		} else if (error == GIT_EBUFS) {
297 			error = 0;
298 		}
299 
300 		if (error < 0)
301 			goto done;
302 	}
303 
304 	if (delta.size && delta.size < deflate.size) {
305 		*out_type = GIT_DIFF_BINARY_DELTA;
306 		*out_datalen = delta.size;
307 		*out_data = git_buf_detach(&delta);
308 		*out_inflatedlen = delta_data_len;
309 	} else {
310 		*out_type = GIT_DIFF_BINARY_LITERAL;
311 		*out_datalen = deflate.size;
312 		*out_data = git_buf_detach(&deflate);
313 		*out_inflatedlen = b_datalen;
314 	}
315 
316 done:
317 	git_buf_free(&deflate);
318 	git_buf_free(&delta);
319 
320 	return error;
321 }
322 
diff_binary(git_patch_generated_output * output,git_patch_generated * patch)323 static int diff_binary(git_patch_generated_output *output, git_patch_generated *patch)
324 {
325 	git_diff_binary binary = {0};
326 	const char *old_data = patch->ofile.map.data;
327 	const char *new_data = patch->nfile.map.data;
328 	size_t old_len = patch->ofile.map.len,
329 		new_len = patch->nfile.map.len;
330 	int error;
331 
332 	/* Only load contents if the user actually wants to diff
333 	 * binary files. */
334 	if (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) {
335 		binary.contains_data = 1;
336 
337 		/* Create the old->new delta (as the "new" side of the patch),
338 		 * and the new->old delta (as the "old" side)
339 		 */
340 		if ((error = create_binary(&binary.old_file.type,
341 				(char **)&binary.old_file.data,
342 				&binary.old_file.datalen,
343 				&binary.old_file.inflatedlen,
344 				new_data, new_len, old_data, old_len)) < 0 ||
345 			(error = create_binary(&binary.new_file.type,
346 				(char **)&binary.new_file.data,
347 				&binary.new_file.datalen,
348 				&binary.new_file.inflatedlen,
349 				old_data, old_len, new_data, new_len)) < 0)
350 			return error;
351 	}
352 
353 	error = giterr_set_after_callback_function(
354 		output->binary_cb(patch->base.delta, &binary, output->payload),
355 		"git_patch");
356 
357 	git__free((char *) binary.old_file.data);
358 	git__free((char *) binary.new_file.data);
359 
360 	return error;
361 }
362 
patch_generated_create(git_patch_generated * patch,git_patch_generated_output * output)363 static int patch_generated_create(
364 	git_patch_generated *patch,
365 	git_patch_generated_output *output)
366 {
367 	int error = 0;
368 
369 	if ((patch->flags & GIT_PATCH_GENERATED_DIFFED) != 0)
370 		return 0;
371 
372 	/* if we are not looking at the binary or text data, don't do the diff */
373 	if (!output->binary_cb && !output->hunk_cb && !output->data_cb)
374 		return 0;
375 
376 	if ((patch->flags & GIT_PATCH_GENERATED_LOADED) == 0 &&
377 		(error = patch_generated_load(patch, output)) < 0)
378 		return error;
379 
380 	if ((patch->flags & GIT_PATCH_GENERATED_DIFFABLE) == 0)
381 		return 0;
382 
383 	if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0) {
384 		if (output->binary_cb)
385 			error = diff_binary(output, patch);
386 	}
387 	else {
388 		if (output->diff_cb)
389 			error = output->diff_cb(output, patch);
390 	}
391 
392 	patch->flags |= GIT_PATCH_GENERATED_DIFFED;
393 	return error;
394 }
395 
diff_required(git_diff * diff,const char * action)396 static int diff_required(git_diff *diff, const char *action)
397 {
398 	if (diff)
399 		return 0;
400 	giterr_set(GITERR_INVALID, "must provide valid diff to %s", action);
401 	return -1;
402 }
403 
404 typedef struct {
405 	git_patch_generated patch;
406 	git_diff_delta delta;
407 	char paths[GIT_FLEX_ARRAY];
408 } patch_generated_with_delta;
409 
diff_single_generate(patch_generated_with_delta * pd,git_xdiff_output * xo)410 static int diff_single_generate(patch_generated_with_delta *pd, git_xdiff_output *xo)
411 {
412 	int error = 0;
413 	git_patch_generated *patch = &pd->patch;
414 	bool has_old = ((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
415 	bool has_new = ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
416 
417 	pd->delta.status = has_new ?
418 		(has_old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) :
419 		(has_old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED);
420 
421 	if (git_oid_equal(&patch->nfile.file->id, &patch->ofile.file->id))
422 		pd->delta.status = GIT_DELTA_UNMODIFIED;
423 
424 	patch->base.delta = &pd->delta;
425 
426 	patch_generated_init_common(patch);
427 
428 	if (pd->delta.status == GIT_DELTA_UNMODIFIED &&
429 		!(patch->ofile.opts_flags & GIT_DIFF_INCLUDE_UNMODIFIED)) {
430 
431 		/* Even empty patches are flagged as binary, and even though
432 		 * there's no difference, we flag this as "containing data"
433 		 * (the data is known to be empty, as opposed to wholly unknown).
434 		 */
435 		if (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY)
436 			patch->base.binary.contains_data = 1;
437 
438 		return error;
439 	}
440 
441 	error = patch_generated_invoke_file_callback(patch, (git_patch_generated_output *)xo);
442 
443 	if (!error)
444 		error = patch_generated_create(patch, (git_patch_generated_output *)xo);
445 
446 	return error;
447 }
448 
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)449 static int patch_generated_from_sources(
450 	patch_generated_with_delta *pd,
451 	git_xdiff_output *xo,
452 	git_diff_file_content_src *oldsrc,
453 	git_diff_file_content_src *newsrc,
454 	const git_diff_options *opts)
455 {
456 	int error = 0;
457 	git_repository *repo =
458 		oldsrc->blob ? git_blob_owner(oldsrc->blob) :
459 		newsrc->blob ? git_blob_owner(newsrc->blob) : NULL;
460 	git_diff_file *lfile = &pd->delta.old_file, *rfile = &pd->delta.new_file;
461 	git_diff_file_content *ldata = &pd->patch.ofile, *rdata = &pd->patch.nfile;
462 
463 	if ((error = patch_generated_normalize_options(&pd->patch.base.diff_opts, opts)) < 0)
464 		return error;
465 
466 	if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) {
467 		void *tmp = lfile; lfile = rfile; rfile = tmp;
468 		tmp = ldata; ldata = rdata; rdata = tmp;
469 	}
470 
471 	pd->patch.base.delta = &pd->delta;
472 
473 	if (!oldsrc->as_path) {
474 		if (newsrc->as_path)
475 			oldsrc->as_path = newsrc->as_path;
476 		else
477 			oldsrc->as_path = newsrc->as_path = "file";
478 	}
479 	else if (!newsrc->as_path)
480 		newsrc->as_path = oldsrc->as_path;
481 
482 	lfile->path = oldsrc->as_path;
483 	rfile->path = newsrc->as_path;
484 
485 	if ((error = git_diff_file_content__init_from_src(
486 			ldata, repo, opts, oldsrc, lfile)) < 0 ||
487 		(error = git_diff_file_content__init_from_src(
488 			rdata, repo, opts, newsrc, rfile)) < 0)
489 		return error;
490 
491 	return diff_single_generate(pd, xo);
492 }
493 
patch_generated_with_delta_alloc(patch_generated_with_delta ** out,const char ** old_path,const char ** new_path)494 static int patch_generated_with_delta_alloc(
495 	patch_generated_with_delta **out,
496 	const char **old_path,
497 	const char **new_path)
498 {
499 	patch_generated_with_delta *pd;
500 	size_t old_len = *old_path ? strlen(*old_path) : 0;
501 	size_t new_len = *new_path ? strlen(*new_path) : 0;
502 	size_t alloc_len;
503 
504 	GITERR_CHECK_ALLOC_ADD(&alloc_len, sizeof(*pd), old_len);
505 	GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, new_len);
506 	GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2);
507 
508 	*out = pd = git__calloc(1, alloc_len);
509 	GITERR_CHECK_ALLOC(pd);
510 
511 	pd->patch.flags = GIT_PATCH_GENERATED_ALLOCATED;
512 
513 	if (*old_path) {
514 		memcpy(&pd->paths[0], *old_path, old_len);
515 		*old_path = &pd->paths[0];
516 	} else if (*new_path)
517 		*old_path = &pd->paths[old_len + 1];
518 
519 	if (*new_path) {
520 		memcpy(&pd->paths[old_len + 1], *new_path, new_len);
521 		*new_path = &pd->paths[old_len + 1];
522 	} else if (*old_path)
523 		*new_path = &pd->paths[0];
524 
525 	return 0;
526 }
527 
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)528 static int diff_from_sources(
529 	git_diff_file_content_src *oldsrc,
530 	git_diff_file_content_src *newsrc,
531 	const git_diff_options *opts,
532 	git_diff_file_cb file_cb,
533 	git_diff_binary_cb binary_cb,
534 	git_diff_hunk_cb hunk_cb,
535 	git_diff_line_cb data_cb,
536 	void *payload)
537 {
538 	int error = 0;
539 	patch_generated_with_delta pd;
540 	git_xdiff_output xo;
541 
542 	memset(&xo, 0, sizeof(xo));
543 	diff_output_init(
544 		&xo.output, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
545 	git_xdiff_init(&xo, opts);
546 
547 	memset(&pd, 0, sizeof(pd));
548 
549 	error = patch_generated_from_sources(&pd, &xo, oldsrc, newsrc, opts);
550 
551 	git_patch_free(&pd.patch.base);
552 
553 	return error;
554 }
555 
patch_from_sources(git_patch ** out,git_diff_file_content_src * oldsrc,git_diff_file_content_src * newsrc,const git_diff_options * opts)556 static int patch_from_sources(
557 	git_patch **out,
558 	git_diff_file_content_src *oldsrc,
559 	git_diff_file_content_src *newsrc,
560 	const git_diff_options *opts)
561 {
562 	int error = 0;
563 	patch_generated_with_delta *pd;
564 	git_xdiff_output xo;
565 
566 	assert(out);
567 	*out = NULL;
568 
569 	if ((error = patch_generated_with_delta_alloc(
570 			&pd, &oldsrc->as_path, &newsrc->as_path)) < 0)
571 		return error;
572 
573 	memset(&xo, 0, sizeof(xo));
574 	diff_output_to_patch(&xo.output, &pd->patch);
575 	git_xdiff_init(&xo, opts);
576 
577 	if (!(error = patch_generated_from_sources(pd, &xo, oldsrc, newsrc, opts)))
578 		*out = (git_patch *)pd;
579 	else
580 		git_patch_free((git_patch *)pd);
581 
582 	return error;
583 }
584 
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)585 int git_diff_blobs(
586 	const git_blob *old_blob,
587 	const char *old_path,
588 	const git_blob *new_blob,
589 	const char *new_path,
590 	const git_diff_options *opts,
591 	git_diff_file_cb file_cb,
592 	git_diff_binary_cb binary_cb,
593 	git_diff_hunk_cb hunk_cb,
594 	git_diff_line_cb data_cb,
595 	void *payload)
596 {
597 	git_diff_file_content_src osrc =
598 		GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
599 	git_diff_file_content_src nsrc =
600 		GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path);
601 	return diff_from_sources(
602 		&osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
603 }
604 
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)605 int git_patch_from_blobs(
606 	git_patch **out,
607 	const git_blob *old_blob,
608 	const char *old_path,
609 	const git_blob *new_blob,
610 	const char *new_path,
611 	const git_diff_options *opts)
612 {
613 	git_diff_file_content_src osrc =
614 		GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
615 	git_diff_file_content_src nsrc =
616 		GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path);
617 	return patch_from_sources(out, &osrc, &nsrc, opts);
618 }
619 
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)620 int git_diff_blob_to_buffer(
621 	const git_blob *old_blob,
622 	const char *old_path,
623 	const char *buf,
624 	size_t buflen,
625 	const char *buf_path,
626 	const git_diff_options *opts,
627 	git_diff_file_cb file_cb,
628 	git_diff_binary_cb binary_cb,
629 	git_diff_hunk_cb hunk_cb,
630 	git_diff_line_cb data_cb,
631 	void *payload)
632 {
633 	git_diff_file_content_src osrc =
634 		GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
635 	git_diff_file_content_src nsrc =
636 		GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path);
637 	return diff_from_sources(
638 		&osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
639 }
640 
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)641 int git_patch_from_blob_and_buffer(
642 	git_patch **out,
643 	const git_blob *old_blob,
644 	const char *old_path,
645 	const void *buf,
646 	size_t buflen,
647 	const char *buf_path,
648 	const git_diff_options *opts)
649 {
650 	git_diff_file_content_src osrc =
651 		GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
652 	git_diff_file_content_src nsrc =
653 		GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path);
654 	return patch_from_sources(out, &osrc, &nsrc, opts);
655 }
656 
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)657 int git_diff_buffers(
658 	const void *old_buf,
659 	size_t old_len,
660 	const char *old_path,
661 	const void *new_buf,
662 	size_t new_len,
663 	const char *new_path,
664 	const git_diff_options *opts,
665 	git_diff_file_cb file_cb,
666 	git_diff_binary_cb binary_cb,
667 	git_diff_hunk_cb hunk_cb,
668 	git_diff_line_cb data_cb,
669 	void *payload)
670 {
671 	git_diff_file_content_src osrc =
672 		GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path);
673 	git_diff_file_content_src nsrc =
674 		GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path);
675 	return diff_from_sources(
676 		&osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
677 }
678 
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)679 int git_patch_from_buffers(
680 	git_patch **out,
681 	const void *old_buf,
682 	size_t old_len,
683 	const char *old_path,
684 	const void *new_buf,
685 	size_t new_len,
686 	const char *new_path,
687 	const git_diff_options *opts)
688 {
689 	git_diff_file_content_src osrc =
690 		GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path);
691 	git_diff_file_content_src nsrc =
692 		GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path);
693 	return patch_from_sources(out, &osrc, &nsrc, opts);
694 }
695 
git_patch_generated_from_diff(git_patch ** patch_ptr,git_diff * diff,size_t idx)696 int git_patch_generated_from_diff(
697 	git_patch **patch_ptr, git_diff *diff, size_t idx)
698 {
699 	int error = 0;
700 	git_xdiff_output xo;
701 	git_diff_delta *delta = NULL;
702 	git_patch_generated *patch = NULL;
703 
704 	if (patch_ptr) *patch_ptr = NULL;
705 
706 	if (diff_required(diff, "git_patch_from_diff") < 0)
707 		return -1;
708 
709 	delta = git_vector_get(&diff->deltas, idx);
710 	if (!delta) {
711 		giterr_set(GITERR_INVALID, "index out of range for delta in diff");
712 		return GIT_ENOTFOUND;
713 	}
714 
715 	if (git_diff_delta__should_skip(&diff->opts, delta))
716 		return 0;
717 
718 	/* don't load the patch data unless we need it for binary check */
719 	if (!patch_ptr &&
720 		((delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0 ||
721 		 (diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0))
722 		return 0;
723 
724 	if ((error = patch_generated_alloc_from_diff(&patch, diff, idx)) < 0)
725 		return error;
726 
727 	memset(&xo, 0, sizeof(xo));
728 	diff_output_to_patch(&xo.output, patch);
729 	git_xdiff_init(&xo, &diff->opts);
730 
731 	error = patch_generated_invoke_file_callback(patch, &xo.output);
732 
733 	if (!error)
734 		error = patch_generated_create(patch, &xo.output);
735 
736 	if (!error) {
737 		/* TODO: if cumulative diff size is < 0.5 total size, flatten patch */
738 		/* TODO: and unload the file content */
739 	}
740 
741 	if (error || !patch_ptr)
742 		git_patch_free(&patch->base);
743 	else
744 		*patch_ptr = &patch->base;
745 
746 	return error;
747 }
748 
git_patch_generated_driver(git_patch_generated * patch)749 git_diff_driver *git_patch_generated_driver(git_patch_generated *patch)
750 {
751 	/* ofile driver is representative for whole patch */
752 	return patch->ofile.driver;
753 }
754 
git_patch_generated_old_data(char ** ptr,size_t * len,git_patch_generated * patch)755 void git_patch_generated_old_data(
756 	char **ptr, size_t *len, git_patch_generated *patch)
757 {
758 	*ptr = patch->ofile.map.data;
759 	*len = patch->ofile.map.len;
760 }
761 
git_patch_generated_new_data(char ** ptr,size_t * len,git_patch_generated * patch)762 void git_patch_generated_new_data(
763 	char **ptr, size_t *len, git_patch_generated *patch)
764 {
765 	*ptr = patch->nfile.map.data;
766 	*len = patch->nfile.map.len;
767 }
768 
patch_generated_file_cb(const git_diff_delta * delta,float progress,void * payload)769 static int patch_generated_file_cb(
770 	const git_diff_delta *delta,
771 	float progress,
772 	void *payload)
773 {
774 	GIT_UNUSED(delta); GIT_UNUSED(progress); GIT_UNUSED(payload);
775 	return 0;
776 }
777 
patch_generated_binary_cb(const git_diff_delta * delta,const git_diff_binary * binary,void * payload)778 static int patch_generated_binary_cb(
779 	const git_diff_delta *delta,
780 	const git_diff_binary *binary,
781 	void *payload)
782 {
783 	git_patch *patch = payload;
784 
785 	GIT_UNUSED(delta);
786 
787 	memcpy(&patch->binary, binary, sizeof(git_diff_binary));
788 
789 	if (binary->old_file.data) {
790 		patch->binary.old_file.data = git__malloc(binary->old_file.datalen);
791 		GITERR_CHECK_ALLOC(patch->binary.old_file.data);
792 
793 		memcpy((char *)patch->binary.old_file.data,
794 			binary->old_file.data, binary->old_file.datalen);
795 	}
796 
797 	if (binary->new_file.data) {
798 		patch->binary.new_file.data = git__malloc(binary->new_file.datalen);
799 		GITERR_CHECK_ALLOC(patch->binary.new_file.data);
800 
801 		memcpy((char *)patch->binary.new_file.data,
802 			binary->new_file.data, binary->new_file.datalen);
803 	}
804 
805 	return 0;
806 }
807 
git_patch_hunk_cb(const git_diff_delta * delta,const git_diff_hunk * hunk_,void * payload)808 static int git_patch_hunk_cb(
809 	const git_diff_delta *delta,
810 	const git_diff_hunk *hunk_,
811 	void *payload)
812 {
813 	git_patch_generated  *patch = payload;
814 	git_patch_hunk *hunk;
815 
816 	GIT_UNUSED(delta);
817 
818 	hunk = git_array_alloc(patch->base.hunks);
819 	GITERR_CHECK_ALLOC(hunk);
820 
821 	memcpy(&hunk->hunk, hunk_, sizeof(hunk->hunk));
822 
823 	patch->base.header_size += hunk_->header_len;
824 
825 	hunk->line_start = git_array_size(patch->base.lines);
826 	hunk->line_count = 0;
827 
828 	return 0;
829 }
830 
patch_generated_line_cb(const git_diff_delta * delta,const git_diff_hunk * hunk_,const git_diff_line * line_,void * payload)831 static int patch_generated_line_cb(
832 	const git_diff_delta *delta,
833 	const git_diff_hunk *hunk_,
834 	const git_diff_line *line_,
835 	void *payload)
836 {
837 	git_patch_generated  *patch = payload;
838 	git_patch_hunk *hunk;
839 	git_diff_line   *line;
840 
841 	GIT_UNUSED(delta);
842 	GIT_UNUSED(hunk_);
843 
844 	hunk = git_array_last(patch->base.hunks);
845 	assert(hunk); /* programmer error if no hunk is available */
846 
847 	line = git_array_alloc(patch->base.lines);
848 	GITERR_CHECK_ALLOC(line);
849 
850 	memcpy(line, line_, sizeof(*line));
851 
852 	/* do some bookkeeping so we can provide old/new line numbers */
853 
854 	patch->base.content_size += line->content_len;
855 
856 	if (line->origin == GIT_DIFF_LINE_ADDITION ||
857 		line->origin == GIT_DIFF_LINE_DELETION)
858 		patch->base.content_size += 1;
859 	else if (line->origin == GIT_DIFF_LINE_CONTEXT) {
860 		patch->base.content_size += 1;
861 		patch->base.context_size += line->content_len + 1;
862 	} else if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL)
863 		patch->base.context_size += line->content_len;
864 
865 	hunk->line_count++;
866 
867 	return 0;
868 }
869 
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)870 static void diff_output_init(
871 	git_patch_generated_output *out,
872 	const git_diff_options *opts,
873 	git_diff_file_cb file_cb,
874 	git_diff_binary_cb binary_cb,
875 	git_diff_hunk_cb hunk_cb,
876 	git_diff_line_cb data_cb,
877 	void *payload)
878 {
879 	GIT_UNUSED(opts);
880 
881 	memset(out, 0, sizeof(*out));
882 
883 	out->file_cb = file_cb;
884 	out->binary_cb = binary_cb;
885 	out->hunk_cb = hunk_cb;
886 	out->data_cb = data_cb;
887 	out->payload = payload;
888 }
889 
diff_output_to_patch(git_patch_generated_output * out,git_patch_generated * patch)890 static void diff_output_to_patch(
891 	git_patch_generated_output *out, git_patch_generated *patch)
892 {
893 	diff_output_init(
894 		out,
895 		NULL,
896 		patch_generated_file_cb,
897 		patch_generated_binary_cb,
898 		git_patch_hunk_cb,
899 		patch_generated_line_cb,
900 		patch);
901 }
902