1/*
2 * Copyright (c) 2017 Stefan Sperling <stsp@openbsd.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17#include <sys/queue.h>
18#include <sys/stat.h>
19
20#include <stdio.h>
21#include <stdlib.h>
22#include <string.h>
23#include <limits.h>
24#include <sha1.h>
25#include <zlib.h>
26
27#include "got_object.h"
28#include "got_repository.h"
29#include "got_error.h"
30#include "got_diff.h"
31#include "got_opentemp.h"
32#include "got_path.h"
33#include "got_cancel.h"
34#include "got_worktree.h"
35
36#include "got_lib_diff.h"
37#include "got_lib_delta.h"
38#include "got_lib_inflate.h"
39#include "got_lib_object.h"
40
41static const struct got_error *
42diff_blobs(struct got_blob_object *blob1, struct got_blob_object *blob2,
43    const char *label1, const char *label2, mode_t mode1, mode_t mode2,
44    int diff_context, int ignore_whitespace, FILE *outfile,
45    struct got_diff_changes *changes)
46{
47	struct got_diff_state ds;
48	struct got_diff_args args;
49	const struct got_error *err = NULL;
50	FILE *f1 = NULL, *f2 = NULL;
51	char hex1[SHA1_DIGEST_STRING_LENGTH];
52	char hex2[SHA1_DIGEST_STRING_LENGTH];
53	char *idstr1 = NULL, *idstr2 = NULL;
54	size_t size1, size2;
55	int res, flags = 0;
56
57	if (blob1) {
58		f1 = got_opentemp();
59		if (f1 == NULL)
60			return got_error_from_errno("got_opentemp");
61	} else
62		flags |= D_EMPTY1;
63
64	if (blob2) {
65		f2 = got_opentemp();
66		if (f2 == NULL) {
67			err = got_error_from_errno("got_opentemp");
68			fclose(f1);
69			return err;
70		}
71	} else
72		flags |= D_EMPTY2;
73
74	size1 = 0;
75	if (blob1) {
76		idstr1 = got_object_blob_id_str(blob1, hex1, sizeof(hex1));
77		err = got_object_blob_dump_to_file(&size1, NULL, NULL, f1,
78		    blob1);
79		if (err)
80			goto done;
81	} else
82		idstr1 = "/dev/null";
83
84	size2 = 0;
85	if (blob2) {
86		idstr2 = got_object_blob_id_str(blob2, hex2, sizeof(hex2));
87		err = got_object_blob_dump_to_file(&size2, NULL, NULL, f2,
88		    blob2);
89		if (err)
90			goto done;
91	} else
92		idstr2 = "/dev/null";
93
94	memset(&ds, 0, sizeof(ds));
95	/* XXX should stat buffers be passed in args instead of ds? */
96	ds.stb1.st_mode = S_IFREG;
97	if (blob1)
98		ds.stb1.st_size = size1;
99	ds.stb1.st_mtime = 0; /* XXX */
100
101	ds.stb2.st_mode = S_IFREG;
102	if (blob2)
103		ds.stb2.st_size = size2;
104	ds.stb2.st_mtime = 0; /* XXX */
105
106	memset(&args, 0, sizeof(args));
107	args.diff_format = D_UNIFIED;
108	args.label[0] = label1 ? label1 : idstr1;
109	args.label[1] = label2 ? label2 : idstr2;
110	args.diff_context = diff_context;
111	flags |= D_PROTOTYPE;
112	if (ignore_whitespace)
113		flags |= D_IGNOREBLANKS;
114
115	if (outfile) {
116		char *modestr1 = NULL, *modestr2 = NULL;
117		int modebits;
118		if (mode1 && mode1 != mode2) {
119			if (S_ISLNK(mode1))
120				modebits = S_IFLNK;
121			else
122				modebits = (S_IRWXU | S_IRWXG | S_IRWXO);
123			if (asprintf(&modestr1, " (mode %o)",
124			    mode1 & modebits) == -1) {
125				err = got_error_from_errno("asprintf");
126				goto done;
127			}
128		}
129		if (mode2 && mode1 != mode2) {
130			if (S_ISLNK(mode2))
131				modebits = S_IFLNK;
132			else
133				modebits = (S_IRWXU | S_IRWXG | S_IRWXO);
134			if (asprintf(&modestr2, " (mode %o)",
135			    mode2 & modebits) == -1) {
136				err = got_error_from_errno("asprintf");
137				goto done;
138			}
139		}
140		fprintf(outfile, "blob - %s%s\n", idstr1,
141		    modestr1 ? modestr1 : "");
142		fprintf(outfile, "blob + %s%s\n", idstr2,
143		    modestr2 ? modestr2 : "");
144		free(modestr1);
145		free(modestr2);
146	}
147	err = got_diffreg(&res, f1, f2, flags, &args, &ds, outfile, changes);
148	got_diff_state_free(&ds);
149done:
150	if (f1 && fclose(f1) != 0 && err == NULL)
151		err = got_error_from_errno("fclose");
152	if (f2 && fclose(f2) != 0 && err == NULL)
153		err = got_error_from_errno("fclose");
154	return err;
155}
156
157const struct got_error *
158got_diff_blob_output_unidiff(void *arg, struct got_blob_object *blob1,
159    struct got_blob_object *blob2, struct got_object_id *id1,
160    struct got_object_id *id2, const char *label1, const char *label2,
161    mode_t mode1, mode_t mode2, struct got_repository *repo)
162{
163	struct got_diff_blob_output_unidiff_arg *a = arg;
164
165	return diff_blobs(blob1, blob2, label1, label2, mode1, mode2,
166	    a->diff_context, a->ignore_whitespace, a->outfile, NULL);
167}
168
169const struct got_error *
170got_diff_blob(struct got_blob_object *blob1, struct got_blob_object *blob2,
171    const char *label1, const char *label2, int diff_context,
172    int ignore_whitespace, FILE *outfile)
173{
174	return diff_blobs(blob1, blob2, label1, label2, 0, 0, diff_context,
175	    ignore_whitespace, outfile, NULL);
176}
177
178static const struct got_error *
179alloc_changes(struct got_diff_changes **changes)
180{
181	*changes = calloc(1, sizeof(**changes));
182	if (*changes == NULL)
183		return got_error_from_errno("calloc");
184	SIMPLEQ_INIT(&(*changes)->entries);
185	return NULL;
186}
187
188static const struct got_error *
189diff_blob_file(struct got_diff_changes **changes,
190    struct got_blob_object *blob1, const char *label1, FILE *f2, size_t size2,
191    const char *label2, int diff_context, int ignore_whitespace, FILE *outfile)
192{
193	struct got_diff_state ds;
194	struct got_diff_args args;
195	const struct got_error *err = NULL;
196	FILE *f1 = NULL;
197	char hex1[SHA1_DIGEST_STRING_LENGTH];
198	char *idstr1 = NULL;
199	size_t size1;
200	int res, flags = 0;
201
202	if (changes)
203		*changes = NULL;
204
205	size1 = 0;
206	if (blob1) {
207		f1 = got_opentemp();
208		if (f1 == NULL)
209			return got_error_from_errno("got_opentemp");
210		idstr1 = got_object_blob_id_str(blob1, hex1, sizeof(hex1));
211		err = got_object_blob_dump_to_file(&size1, NULL, NULL, f1,
212		    blob1);
213		if (err)
214			goto done;
215	} else {
216		flags |= D_EMPTY1;
217		idstr1 = "/dev/null";
218	}
219
220	if (f2 == NULL)
221		flags |= D_EMPTY2;
222
223	memset(&ds, 0, sizeof(ds));
224	/* XXX should stat buffers be passed in args instead of ds? */
225	ds.stb1.st_mode = S_IFREG;
226	if (blob1)
227		ds.stb1.st_size = size1;
228	ds.stb1.st_mtime = 0; /* XXX */
229
230	ds.stb2.st_mode = S_IFREG;
231	ds.stb2.st_size = size2;
232	ds.stb2.st_mtime = 0; /* XXX */
233
234	memset(&args, 0, sizeof(args));
235	args.diff_format = D_UNIFIED;
236	args.label[0] = label2;
237	args.label[1] = label2;
238	args.diff_context = diff_context;
239	flags |= D_PROTOTYPE;
240	if (ignore_whitespace)
241		flags |= D_IGNOREBLANKS;
242
243	if (outfile) {
244		fprintf(outfile, "blob - %s\n", label1 ? label1 : idstr1);
245		fprintf(outfile, "file + %s\n",
246		    f2 == NULL ? "/dev/null" : label2);
247	}
248	if (changes) {
249		err = alloc_changes(changes);
250		if (err)
251			return err;
252	}
253	err = got_diffreg(&res, f1, f2, flags, &args, &ds, outfile,
254	    changes ? *changes : NULL);
255	got_diff_state_free(&ds);
256done:
257	if (f1 && fclose(f1) != 0 && err == NULL)
258		err = got_error_from_errno("fclose");
259	return err;
260}
261
262const struct got_error *
263got_diff_blob_file(struct got_blob_object *blob1, const char *label1,
264    FILE *f2, size_t size2, const char *label2, int diff_context,
265    int ignore_whitespace, FILE *outfile)
266{
267	return diff_blob_file(NULL, blob1, label1, f2, size2, label2,
268	    diff_context, ignore_whitespace, outfile);
269}
270
271const struct got_error *
272got_diff_blob_file_lines_changed(struct got_diff_changes **changes,
273    struct got_blob_object *blob1, FILE *f2, size_t size2)
274{
275	return diff_blob_file(changes, blob1, NULL, f2, size2, NULL,
276	    0, 0, NULL);
277}
278
279const struct got_error *
280got_diff_blob_lines_changed(struct got_diff_changes **changes,
281    struct got_blob_object *blob1, struct got_blob_object *blob2)
282{
283	const struct got_error *err = NULL;
284
285	err = alloc_changes(changes);
286	if (err)
287		return err;
288
289	err = diff_blobs(blob1, blob2, NULL, NULL, 0, 0, 3, 0, NULL, *changes);
290	if (err) {
291		got_diff_free_changes(*changes);
292		*changes = NULL;
293	}
294	return err;
295}
296
297void
298got_diff_free_changes(struct got_diff_changes *changes)
299{
300	struct got_diff_change *change;
301	while (!SIMPLEQ_EMPTY(&changes->entries)) {
302		change = SIMPLEQ_FIRST(&changes->entries);
303		SIMPLEQ_REMOVE_HEAD(&changes->entries, entry);
304		free(change);
305	}
306	free(changes);
307}
308
309static const struct got_error *
310diff_added_blob(struct got_object_id *id, const char *label, mode_t mode,
311    struct got_repository *repo, got_diff_blob_cb cb, void *cb_arg)
312{
313	const struct got_error *err;
314	struct got_blob_object  *blob = NULL;
315	struct got_object *obj = NULL;
316
317	err = got_object_open(&obj, repo, id);
318	if (err)
319		return err;
320
321	err = got_object_blob_open(&blob, repo, obj, 8192);
322	if (err)
323		goto done;
324	err = cb(cb_arg, NULL, blob, NULL, id, NULL, label, 0, mode, repo);
325done:
326	got_object_close(obj);
327	if (blob)
328		got_object_blob_close(blob);
329	return err;
330}
331
332static const struct got_error *
333diff_modified_blob(struct got_object_id *id1, struct got_object_id *id2,
334    const char *label1, const char *label2, mode_t mode1, mode_t mode2,
335    struct got_repository *repo, got_diff_blob_cb cb, void *cb_arg)
336{
337	const struct got_error *err;
338	struct got_object *obj1 = NULL;
339	struct got_object *obj2 = NULL;
340	struct got_blob_object *blob1 = NULL;
341	struct got_blob_object *blob2 = NULL;
342
343	err = got_object_open(&obj1, repo, id1);
344	if (err)
345		return err;
346	if (obj1->type != GOT_OBJ_TYPE_BLOB) {
347		err = got_error(GOT_ERR_OBJ_TYPE);
348		goto done;
349	}
350
351	err = got_object_open(&obj2, repo, id2);
352	if (err)
353		goto done;
354	if (obj2->type != GOT_OBJ_TYPE_BLOB) {
355		err = got_error(GOT_ERR_BAD_OBJ_DATA);
356		goto done;
357	}
358
359	err = got_object_blob_open(&blob1, repo, obj1, 8192);
360	if (err)
361		goto done;
362
363	err = got_object_blob_open(&blob2, repo, obj2, 8192);
364	if (err)
365		goto done;
366
367	err = cb(cb_arg, blob1, blob2, id1, id2, label1, label2, mode1, mode2,
368	    repo);
369done:
370	if (obj1)
371		got_object_close(obj1);
372	if (obj2)
373		got_object_close(obj2);
374	if (blob1)
375		got_object_blob_close(blob1);
376	if (blob2)
377		got_object_blob_close(blob2);
378	return err;
379}
380
381static const struct got_error *
382diff_deleted_blob(struct got_object_id *id, const char *label, mode_t mode,
383    struct got_repository *repo, got_diff_blob_cb cb, void *cb_arg)
384{
385	const struct got_error *err;
386	struct got_blob_object  *blob = NULL;
387	struct got_object *obj = NULL;
388
389	err = got_object_open(&obj, repo, id);
390	if (err)
391		return err;
392
393	err = got_object_blob_open(&blob, repo, obj, 8192);
394	if (err)
395		goto done;
396	err = cb(cb_arg, blob, NULL, id, NULL, label, NULL, mode, 0, repo);
397done:
398	got_object_close(obj);
399	if (blob)
400		got_object_blob_close(blob);
401	return err;
402}
403
404static const struct got_error *
405diff_added_tree(struct got_object_id *id, const char *label,
406    struct got_repository *repo, got_diff_blob_cb cb, void *cb_arg,
407    int diff_content)
408{
409	const struct got_error *err = NULL;
410	struct got_object *treeobj = NULL;
411	struct got_tree_object *tree = NULL;
412
413	err = got_object_open(&treeobj, repo, id);
414	if (err)
415		goto done;
416
417	if (treeobj->type != GOT_OBJ_TYPE_TREE) {
418		err = got_error(GOT_ERR_OBJ_TYPE);
419		goto done;
420	}
421
422	err = got_object_tree_open(&tree, repo, treeobj);
423	if (err)
424		goto done;
425
426	err = got_diff_tree(NULL, tree, NULL, label, repo, cb, cb_arg,
427	    diff_content);
428done:
429	if (tree)
430		got_object_tree_close(tree);
431	if (treeobj)
432		got_object_close(treeobj);
433	return err;
434}
435
436static const struct got_error *
437diff_modified_tree(struct got_object_id *id1, struct got_object_id *id2,
438    const char *label1, const char *label2, struct got_repository *repo,
439    got_diff_blob_cb cb, void *cb_arg, int diff_content)
440{
441	const struct got_error *err;
442	struct got_object *treeobj1 = NULL;
443	struct got_object *treeobj2 = NULL;
444	struct got_tree_object *tree1 = NULL;
445	struct got_tree_object *tree2 = NULL;
446
447	err = got_object_open(&treeobj1, repo, id1);
448	if (err)
449		goto done;
450
451	if (treeobj1->type != GOT_OBJ_TYPE_TREE) {
452		err = got_error(GOT_ERR_OBJ_TYPE);
453		goto done;
454	}
455
456	err = got_object_open(&treeobj2, repo, id2);
457	if (err)
458		goto done;
459
460	if (treeobj2->type != GOT_OBJ_TYPE_TREE) {
461		err = got_error(GOT_ERR_OBJ_TYPE);
462		goto done;
463	}
464
465	err = got_object_tree_open(&tree1, repo, treeobj1);
466	if (err)
467		goto done;
468
469	err = got_object_tree_open(&tree2, repo, treeobj2);
470	if (err)
471		goto done;
472
473	err = got_diff_tree(tree1, tree2, label1, label2, repo, cb, cb_arg,
474	    diff_content);
475
476done:
477	if (tree1)
478		got_object_tree_close(tree1);
479	if (tree2)
480		got_object_tree_close(tree2);
481	if (treeobj1)
482		got_object_close(treeobj1);
483	if (treeobj2)
484		got_object_close(treeobj2);
485	return err;
486}
487
488static const struct got_error *
489diff_deleted_tree(struct got_object_id *id, const char *label,
490    struct got_repository *repo, got_diff_blob_cb cb, void *cb_arg,
491    int diff_content)
492{
493	const struct got_error *err;
494	struct got_object *treeobj = NULL;
495	struct got_tree_object *tree = NULL;
496
497	err = got_object_open(&treeobj, repo, id);
498	if (err)
499		goto done;
500
501	if (treeobj->type != GOT_OBJ_TYPE_TREE) {
502		err = got_error(GOT_ERR_OBJ_TYPE);
503		goto done;
504	}
505
506	err = got_object_tree_open(&tree, repo, treeobj);
507	if (err)
508		goto done;
509
510	err = got_diff_tree(tree, NULL, label, NULL, repo, cb, cb_arg,
511	    diff_content);
512done:
513	if (tree)
514		got_object_tree_close(tree);
515	if (treeobj)
516		got_object_close(treeobj);
517	return err;
518}
519
520static const struct got_error *
521diff_kind_mismatch(struct got_object_id *id1, struct got_object_id *id2,
522    const char *label1, const char *label2, struct got_repository *repo,
523    got_diff_blob_cb cb, void *cb_arg)
524{
525	/* XXX TODO */
526	return NULL;
527}
528
529static const struct got_error *
530diff_entry_old_new(struct got_tree_entry *te1,
531    struct got_tree_entry *te2, const char *label1, const char *label2,
532    struct got_repository *repo, got_diff_blob_cb cb, void *cb_arg,
533    int diff_content)
534{
535	const struct got_error *err = NULL;
536	int id_match;
537
538	if (got_object_tree_entry_is_submodule(te1))
539		return NULL;
540
541	if (te2 == NULL) {
542		if (S_ISDIR(te1->mode))
543			err = diff_deleted_tree(&te1->id, label1, repo,
544			    cb, cb_arg, diff_content);
545		else {
546			if (diff_content)
547				err = diff_deleted_blob(&te1->id, label1,
548				    te1->mode, repo, cb, cb_arg);
549			else
550				err = cb(cb_arg, NULL, NULL, &te1->id, NULL,
551				    label1, NULL, te1->mode, 0, repo);
552		}
553		return err;
554	} else if (got_object_tree_entry_is_submodule(te2))
555		return NULL;
556
557	id_match = (got_object_id_cmp(&te1->id, &te2->id) == 0);
558	if (S_ISDIR(te1->mode) && S_ISDIR(te2->mode)) {
559		if (!id_match)
560			return diff_modified_tree(&te1->id, &te2->id,
561			    label1, label2, repo, cb, cb_arg, diff_content);
562	} else if ((S_ISREG(te1->mode) || S_ISLNK(te1->mode)) &&
563	    (S_ISREG(te2->mode) || S_ISLNK(te2->mode))) {
564		if (!id_match ||
565		    ((te1->mode & (S_IFLNK | S_IXUSR))) !=
566		    (te2->mode & (S_IFLNK | S_IXUSR))) {
567			if (diff_content)
568				return diff_modified_blob(&te1->id, &te2->id,
569				    label1, label2, te1->mode, te2->mode,
570				    repo, cb, cb_arg);
571			else
572				return cb(cb_arg, NULL, NULL, &te1->id,
573				    &te2->id, label1, label2, te1->mode,
574				    te2->mode, repo);
575		}
576	}
577
578	if (id_match)
579		return NULL;
580
581	return diff_kind_mismatch(&te1->id, &te2->id, label1, label2, repo,
582	    cb, cb_arg);
583}
584
585static const struct got_error *
586diff_entry_new_old(struct got_tree_entry *te2,
587    struct got_tree_entry *te1, const char *label2,
588    struct got_repository *repo, got_diff_blob_cb cb, void *cb_arg,
589    int diff_content)
590{
591	if (te1 != NULL) /* handled by diff_entry_old_new() */
592		return NULL;
593
594	if (got_object_tree_entry_is_submodule(te2))
595		return NULL;
596
597	if (S_ISDIR(te2->mode))
598		return diff_added_tree(&te2->id, label2, repo, cb, cb_arg,
599		    diff_content);
600
601	if (diff_content)
602		return diff_added_blob(&te2->id, label2, te2->mode, repo, cb,
603		    cb_arg);
604
605	return cb(cb_arg, NULL, NULL, NULL, &te2->id, NULL, label2, 0,
606	    te2->mode, repo);
607}
608
609const struct got_error *
610got_diff_tree_collect_changed_paths(void *arg, struct got_blob_object *blob1,
611    struct got_blob_object *blob2, struct got_object_id *id1,
612    struct got_object_id *id2, const char *label1, const char *label2,
613    mode_t mode1, mode_t mode2, struct got_repository *repo)
614{
615	const struct got_error *err = NULL;
616	struct got_pathlist_head *paths = arg;
617	struct got_diff_changed_path *change = NULL;
618	char *path = NULL;
619
620	path = strdup(label2 ? label2 : label1);
621	if (path == NULL)
622		return got_error_from_errno("malloc");
623
624	change = malloc(sizeof(*change));
625	if (change == NULL) {
626		err = got_error_from_errno("malloc");
627		goto done;
628	}
629
630	change->status = GOT_STATUS_NO_CHANGE;
631	if (id1 == NULL)
632		change->status = GOT_STATUS_ADD;
633	else if (id2 == NULL)
634		change->status = GOT_STATUS_DELETE;
635	else {
636		if (got_object_id_cmp(id1, id2) != 0)
637			change->status = GOT_STATUS_MODIFY;
638		else if (mode1 != mode2)
639			change->status = GOT_STATUS_MODE_CHANGE;
640	}
641
642	err = got_pathlist_insert(NULL, paths, path, change);
643done:
644	if (err) {
645		free(path);
646		free(change);
647	}
648	return err;
649}
650
651const struct got_error *
652got_diff_tree(struct got_tree_object *tree1, struct got_tree_object *tree2,
653    const char *label1, const char *label2, struct got_repository *repo,
654    got_diff_blob_cb cb, void *cb_arg, int diff_content)
655{
656	const struct got_error *err = NULL;
657	struct got_tree_entry *te1 = NULL;
658	struct got_tree_entry *te2 = NULL;
659	char *l1 = NULL, *l2 = NULL;
660	int tidx1 = 0, tidx2 = 0;
661
662	if (tree1) {
663		te1 = got_object_tree_get_entry(tree1, 0);
664		if (te1 && asprintf(&l1, "%s%s%s", label1, label1[0] ? "/" : "",
665		    te1->name) == -1)
666			return got_error_from_errno("asprintf");
667	}
668	if (tree2) {
669		te2 = got_object_tree_get_entry(tree2, 0);
670		if (te2 && asprintf(&l2, "%s%s%s", label2, label2[0] ? "/" : "",
671		    te2->name) == -1)
672			return got_error_from_errno("asprintf");
673	}
674
675	do {
676		if (te1) {
677			struct got_tree_entry *te = NULL;
678			if (tree2)
679				te = got_object_tree_find_entry(tree2,
680				    te1->name);
681			if (te) {
682				free(l2);
683				l2 = NULL;
684				if (te && asprintf(&l2, "%s%s%s", label2,
685				    label2[0] ? "/" : "", te->name) == -1)
686					return
687					    got_error_from_errno("asprintf");
688			}
689			err = diff_entry_old_new(te1, te, l1, l2, repo, cb,
690			    cb_arg, diff_content);
691			if (err)
692				break;
693		}
694
695		if (te2) {
696			struct got_tree_entry *te = NULL;
697			if (tree1)
698				te = got_object_tree_find_entry(tree1,
699				    te2->name);
700			free(l2);
701			if (te) {
702				if (asprintf(&l2, "%s%s%s", label2,
703				    label2[0] ? "/" : "", te->name) == -1)
704					return
705					    got_error_from_errno("asprintf");
706			} else {
707				if (asprintf(&l2, "%s%s%s", label2,
708				    label2[0] ? "/" : "", te2->name) == -1)
709					return
710					    got_error_from_errno("asprintf");
711			}
712			err = diff_entry_new_old(te2, te, l2, repo,
713			    cb, cb_arg, diff_content);
714			if (err)
715				break;
716		}
717
718		free(l1);
719		l1 = NULL;
720		if (te1) {
721			tidx1++;
722			te1 = got_object_tree_get_entry(tree1, tidx1);
723			if (te1 &&
724			    asprintf(&l1, "%s%s%s", label1,
725			    label1[0] ? "/" : "", te1->name) == -1)
726				return got_error_from_errno("asprintf");
727		}
728		free(l2);
729		l2 = NULL;
730		if (te2) {
731			tidx2++;
732			te2 = got_object_tree_get_entry(tree2, tidx2);
733			if (te2 &&
734			    asprintf(&l2, "%s%s%s", label2,
735			        label2[0] ? "/" : "", te2->name) == -1)
736				return got_error_from_errno("asprintf");
737		}
738	} while (te1 || te2);
739
740	return err;
741}
742
743const struct got_error *
744got_diff_objects_as_blobs(struct got_object_id *id1, struct got_object_id *id2,
745    const char *label1, const char *label2, int diff_context,
746    int ignore_whitespace, struct got_repository *repo, FILE *outfile)
747{
748	const struct got_error *err;
749	struct got_blob_object *blob1 = NULL, *blob2 = NULL;
750
751	if (id1 == NULL && id2 == NULL)
752		return got_error(GOT_ERR_NO_OBJ);
753
754	if (id1) {
755		err = got_object_open_as_blob(&blob1, repo, id1, 8192);
756		if (err)
757			goto done;
758	}
759	if (id2) {
760		err = got_object_open_as_blob(&blob2, repo, id2, 8192);
761		if (err)
762			goto done;
763	}
764	err = got_diff_blob(blob1, blob2, label1, label2, diff_context,
765	    ignore_whitespace, outfile);
766done:
767	if (blob1)
768		got_object_blob_close(blob1);
769	if (blob2)
770		got_object_blob_close(blob2);
771	return err;
772}
773
774const struct got_error *
775got_diff_objects_as_trees(struct got_object_id *id1, struct got_object_id *id2,
776    char *label1, char *label2, int diff_context, int ignore_whitespace,
777    struct got_repository *repo, FILE *outfile)
778{
779	const struct got_error *err;
780	struct got_tree_object *tree1 = NULL, *tree2 = NULL;
781	struct got_diff_blob_output_unidiff_arg arg;
782
783	if (id1 == NULL && id2 == NULL)
784		return got_error(GOT_ERR_NO_OBJ);
785
786	if (id1) {
787		err = got_object_open_as_tree(&tree1, repo, id1);
788		if (err)
789			goto done;
790	}
791	if (id2) {
792		err = got_object_open_as_tree(&tree2, repo, id2);
793		if (err)
794			goto done;
795	}
796	arg.diff_context = diff_context;
797	arg.ignore_whitespace = ignore_whitespace;
798	arg.outfile = outfile;
799	err = got_diff_tree(tree1, tree2, label1, label2, repo,
800	    got_diff_blob_output_unidiff, &arg, 1);
801done:
802	if (tree1)
803		got_object_tree_close(tree1);
804	if (tree2)
805		got_object_tree_close(tree2);
806	return err;
807}
808
809const struct got_error *
810got_diff_objects_as_commits(struct got_object_id *id1,
811    struct got_object_id *id2, int diff_context, int ignore_whitespace,
812    struct got_repository *repo, FILE *outfile)
813{
814	const struct got_error *err;
815	struct got_commit_object *commit1 = NULL, *commit2 = NULL;
816
817	if (id2 == NULL)
818		return got_error(GOT_ERR_NO_OBJ);
819
820	if (id1) {
821		err = got_object_open_as_commit(&commit1, repo, id1);
822		if (err)
823			goto done;
824	}
825
826	err = got_object_open_as_commit(&commit2, repo, id2);
827	if (err)
828		goto done;
829
830	err = got_diff_objects_as_trees(
831	    commit1 ? got_object_commit_get_tree_id(commit1) : NULL,
832	    got_object_commit_get_tree_id(commit2), "", "", diff_context,
833	    ignore_whitespace, repo, outfile);
834done:
835	if (commit1)
836		got_object_commit_close(commit1);
837	if (commit2)
838		got_object_commit_close(commit2);
839	return err;
840}
841
842const struct got_error *
843got_diff_files(struct got_diff_changes **changes,
844    struct got_diff_state **ds,
845    struct got_diff_args **args,
846    int *flags,
847    FILE *f1, size_t size1, const char *label1,
848    FILE *f2, size_t size2, const char *label2,
849    int diff_context, FILE *outfile)
850{
851	const struct got_error *err = NULL;
852	int res;
853
854	*flags = 0;
855	*ds = calloc(1, sizeof(**ds));
856	if (*ds == NULL)
857		return got_error_from_errno("calloc");
858	*args = calloc(1, sizeof(**args));
859	if (*args == NULL) {
860		err = got_error_from_errno("calloc");
861		goto done;
862	}
863
864	if (changes)
865		*changes = NULL;
866
867	if (f1 == NULL)
868		*flags |= D_EMPTY1;
869
870	if (f2 == NULL)
871		*flags |= D_EMPTY2;
872
873	/* XXX should stat buffers be passed in args instead of ds? */
874	(*ds)->stb1.st_mode = S_IFREG;
875	(*ds)->stb1.st_size = size1;
876	(*ds)->stb1.st_mtime = 0; /* XXX */
877
878	(*ds)->stb2.st_mode = S_IFREG;
879	(*ds)->stb2.st_size = size2;
880	(*ds)->stb2.st_mtime = 0; /* XXX */
881
882	(*args)->diff_format = D_UNIFIED;
883	(*args)->label[0] = label1;
884	(*args)->label[1] = label2;
885	(*args)->diff_context = diff_context;
886	*flags |= D_PROTOTYPE;
887
888	if (outfile) {
889		fprintf(outfile, "file - %s\n",
890		    f1 == NULL ? "/dev/null" : label1);
891		fprintf(outfile, "file + %s\n",
892		    f2 == NULL ? "/dev/null" : label2);
893	}
894	if (changes) {
895		err = alloc_changes(changes);
896		if (err)
897			goto done;
898	}
899	err = got_diffreg(&res, f1, f2, *flags, *args, *ds, outfile,
900	    changes ? *changes : NULL);
901done:
902	if (err) {
903		if (*ds) {
904			got_diff_state_free(*ds);
905			free(*ds);
906			*ds = NULL;
907		}
908		if (*args) {
909			free(*args);
910			*args = NULL;
911		}
912		if (changes) {
913			if (*changes)
914				got_diff_free_changes(*changes);
915			*changes = NULL;
916		}
917	}
918	return err;
919}
920