Blob


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 */
17 #include <sys/queue.h>
18 #include <sys/stat.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <limits.h>
24 #include <sha1.h>
25 #include <zlib.h>
27 #include "got_object.h"
28 #include "got_repository.h"
29 #include "got_error.h"
30 #include "got_diff.h"
31 #include "got_path.h"
32 #include "got_cancel.h"
33 #include "got_worktree.h"
34 #include "got_opentemp.h"
36 #include "got_lib_diff.h"
37 #include "got_lib_delta.h"
38 #include "got_lib_inflate.h"
39 #include "got_lib_object.h"
41 static const struct got_error *
42 add_line_offset(off_t **line_offsets, size_t *nlines, off_t off)
43 {
44 off_t *p;
46 p = reallocarray(*line_offsets, *nlines + 1, sizeof(off_t));
47 if (p == NULL)
48 return got_error_from_errno("reallocarray");
49 *line_offsets = p;
50 (*line_offsets)[*nlines] = off;
51 (*nlines)++;
52 return NULL;
53 }
55 static const struct got_error *
56 diff_blobs(off_t **line_offsets, size_t *nlines,
57 struct got_diffreg_result **resultp, struct got_blob_object *blob1,
58 struct got_blob_object *blob2, FILE *f1, FILE *f2,
59 const char *label1, const char *label2, mode_t mode1, mode_t mode2,
60 int diff_context, int ignore_whitespace, int force_text_diff, FILE *outfile)
61 {
62 const struct got_error *err = NULL, *free_err;
63 char hex1[SHA1_DIGEST_STRING_LENGTH];
64 char hex2[SHA1_DIGEST_STRING_LENGTH];
65 const char *idstr1 = NULL, *idstr2 = NULL;
66 off_t size1, size2;
67 struct got_diffreg_result *result;
68 off_t outoff = 0;
69 int n;
71 if (line_offsets && *line_offsets && *nlines > 0)
72 outoff = (*line_offsets)[*nlines - 1];
73 else if (line_offsets) {
74 err = add_line_offset(line_offsets, nlines, 0);
75 if (err)
76 goto done;
77 }
79 if (resultp)
80 *resultp = NULL;
82 if (f1) {
83 err = got_opentemp_truncate(f1);
84 if (err)
85 goto done;
86 }
87 if (f2) {
88 err = got_opentemp_truncate(f2);
89 if (err)
90 goto done;
91 }
93 size1 = 0;
94 if (blob1) {
95 idstr1 = got_object_blob_id_str(blob1, hex1, sizeof(hex1));
96 err = got_object_blob_dump_to_file(&size1, NULL, NULL, f1,
97 blob1);
98 if (err)
99 goto done;
100 } else
101 idstr1 = "/dev/null";
103 size2 = 0;
104 if (blob2) {
105 idstr2 = got_object_blob_id_str(blob2, hex2, sizeof(hex2));
106 err = got_object_blob_dump_to_file(&size2, NULL, NULL, f2,
107 blob2);
108 if (err)
109 goto done;
110 } else
111 idstr2 = "/dev/null";
113 if (outfile) {
114 char *modestr1 = NULL, *modestr2 = NULL;
115 int modebits;
116 if (mode1 && mode1 != mode2) {
117 if (S_ISLNK(mode1))
118 modebits = S_IFLNK;
119 else
120 modebits = (S_IRWXU | S_IRWXG | S_IRWXO);
121 if (asprintf(&modestr1, " (mode %o)",
122 mode1 & modebits) == -1) {
123 err = got_error_from_errno("asprintf");
124 goto done;
127 if (mode2 && mode1 != mode2) {
128 if (S_ISLNK(mode2))
129 modebits = S_IFLNK;
130 else
131 modebits = (S_IRWXU | S_IRWXG | S_IRWXO);
132 if (asprintf(&modestr2, " (mode %o)",
133 mode2 & modebits) == -1) {
134 err = got_error_from_errno("asprintf");
135 goto done;
138 n = fprintf(outfile, "blob - %s%s\n", idstr1,
139 modestr1 ? modestr1 : "");
140 if (n < 0)
141 goto done;
142 outoff += n;
143 if (line_offsets) {
144 err = add_line_offset(line_offsets, nlines, outoff);
145 if (err)
146 goto done;
149 n = fprintf(outfile, "blob + %s%s\n", idstr2,
150 modestr2 ? modestr2 : "");
151 if (n < 0)
152 goto done;
153 outoff += n;
154 if (line_offsets) {
155 err = add_line_offset(line_offsets, nlines, outoff);
156 if (err)
157 goto done;
160 free(modestr1);
161 free(modestr2);
163 err = got_diffreg(&result, f1, f2, GOT_DIFF_ALGORITHM_PATIENCE,
164 ignore_whitespace, force_text_diff);
165 if (err)
166 goto done;
168 if (outfile) {
169 err = got_diffreg_output(line_offsets, nlines, result,
170 blob1 != NULL, blob2 != NULL,
171 label1 ? label1 : idstr1,
172 label2 ? label2 : idstr2,
173 GOT_DIFF_OUTPUT_UNIDIFF, diff_context, outfile);
174 if (err)
175 goto done;
178 if (resultp && err == NULL)
179 *resultp = result;
180 else {
181 free_err = got_diffreg_result_free(result);
182 if (free_err && err == NULL)
183 err = free_err;
185 done:
186 return err;
189 const struct got_error *
190 got_diff_blob_output_unidiff(void *arg, struct got_blob_object *blob1,
191 struct got_blob_object *blob2, FILE *f1, FILE *f2,
192 struct got_object_id *id1, struct got_object_id *id2,
193 const char *label1, const char *label2, mode_t mode1, mode_t mode2,
194 struct got_repository *repo)
196 struct got_diff_blob_output_unidiff_arg *a = arg;
198 return diff_blobs(&a->line_offsets, &a->nlines, NULL,
199 blob1, blob2, f1, f2, label1, label2, mode1, mode2, a->diff_context,
200 a->ignore_whitespace, a->force_text_diff, a->outfile);
203 const struct got_error *
204 got_diff_blob(off_t **line_offsets, size_t *nlines,
205 struct got_blob_object *blob1, struct got_blob_object *blob2,
206 FILE *f1, FILE *f2, const char *label1, const char *label2,
207 int diff_context, int ignore_whitespace, int force_text_diff,
208 FILE *outfile)
210 return diff_blobs(line_offsets, nlines, NULL, blob1, blob2, f1, f2,
211 label1, label2, 0, 0, diff_context, ignore_whitespace,
212 force_text_diff, outfile);
215 static const struct got_error *
216 diff_blob_file(struct got_diffreg_result **resultp,
217 struct got_blob_object *blob1, FILE *f1, off_t size1, const char *label1,
218 FILE *f2, int f2_exists, size_t size2, const char *label2, int diff_context,
219 int ignore_whitespace, int force_text_diff, FILE *outfile)
221 const struct got_error *err = NULL, *free_err;
222 char hex1[SHA1_DIGEST_STRING_LENGTH];
223 const char *idstr1 = NULL;
224 struct got_diffreg_result *result = NULL;
226 if (resultp)
227 *resultp = NULL;
229 if (blob1)
230 idstr1 = got_object_blob_id_str(blob1, hex1, sizeof(hex1));
231 else
232 idstr1 = "/dev/null";
234 if (outfile) {
235 fprintf(outfile, "blob - %s\n", label1 ? label1 : idstr1);
236 fprintf(outfile, "file + %s\n",
237 f2_exists ? label2 : "/dev/null");
240 err = got_diffreg(&result, f1, f2, GOT_DIFF_ALGORITHM_PATIENCE,
241 ignore_whitespace, force_text_diff);
242 if (err)
243 goto done;
245 if (outfile) {
246 err = got_diffreg_output(NULL, NULL, result,
247 blob1 != NULL, f2_exists,
248 label2, /* show local file's path, not a blob ID */
249 label2, GOT_DIFF_OUTPUT_UNIDIFF,
250 diff_context, outfile);
251 if (err)
252 goto done;
255 if (resultp && err == NULL)
256 *resultp = result;
257 else if (result) {
258 free_err = got_diffreg_result_free(result);
259 if (free_err && err == NULL)
260 err = free_err;
262 done:
263 return err;
266 const struct got_error *
267 got_diff_blob_file(struct got_blob_object *blob1, FILE *f1, off_t size1,
268 const char *label1, FILE *f2, int f2_exists, size_t size2,
269 const char *label2, int diff_context, int ignore_whitespace,
270 int force_text_diff, FILE *outfile)
272 return diff_blob_file(NULL, blob1, f1, size1, label1, f2, f2_exists,
273 size2, label2, diff_context, ignore_whitespace, force_text_diff,
274 outfile);
277 static const struct got_error *
278 diff_added_blob(struct got_object_id *id, FILE *f1, FILE *f2, int fd2,
279 const char *label, mode_t mode, struct got_repository *repo,
280 got_diff_blob_cb cb, void *cb_arg)
282 const struct got_error *err;
283 struct got_blob_object *blob = NULL;
284 struct got_object *obj = NULL;
286 err = got_object_open(&obj, repo, id);
287 if (err)
288 return err;
290 err = got_object_blob_open(&blob, repo, obj, 8192, fd2);
291 if (err)
292 goto done;
293 err = cb(cb_arg, NULL, blob, f1, f2, NULL, id,
294 NULL, label, 0, mode, repo);
295 done:
296 got_object_close(obj);
297 if (blob)
298 got_object_blob_close(blob);
299 return err;
302 static const struct got_error *
303 diff_modified_blob(struct got_object_id *id1, struct got_object_id *id2,
304 FILE *f1, FILE *f2, int fd1, int fd2,
305 const char *label1, const char *label2,
306 mode_t mode1, mode_t mode2, struct got_repository *repo,
307 got_diff_blob_cb cb, void *cb_arg)
309 const struct got_error *err;
310 struct got_object *obj1 = NULL;
311 struct got_object *obj2 = NULL;
312 struct got_blob_object *blob1 = NULL;
313 struct got_blob_object *blob2 = NULL;
315 err = got_object_open(&obj1, repo, id1);
316 if (err)
317 return err;
319 if (obj1->type != GOT_OBJ_TYPE_BLOB) {
320 err = got_error(GOT_ERR_OBJ_TYPE);
321 goto done;
324 err = got_object_open(&obj2, repo, id2);
325 if (err)
326 goto done;
327 if (obj2->type != GOT_OBJ_TYPE_BLOB) {
328 err = got_error(GOT_ERR_BAD_OBJ_DATA);
329 goto done;
332 err = got_object_blob_open(&blob1, repo, obj1, 8192, fd1);
333 if (err)
334 goto done;
336 err = got_object_blob_open(&blob2, repo, obj2, 8192, fd2);
337 if (err)
338 goto done;
340 err = cb(cb_arg, blob1, blob2, f1, f2, id1, id2, label1, label2,
341 mode1, mode2, repo);
342 done:
343 if (obj1)
344 got_object_close(obj1);
345 if (obj2)
346 got_object_close(obj2);
347 if (blob1)
348 got_object_blob_close(blob1);
349 if (blob2)
350 got_object_blob_close(blob2);
351 return err;
354 static const struct got_error *
355 diff_deleted_blob(struct got_object_id *id, FILE *f1, int fd1,
356 FILE *f2, const char *label, mode_t mode, struct got_repository *repo,
357 got_diff_blob_cb cb, void *cb_arg)
359 const struct got_error *err;
360 struct got_blob_object *blob = NULL;
361 struct got_object *obj = NULL;
363 err = got_object_open(&obj, repo, id);
364 if (err)
365 return err;
367 err = got_object_blob_open(&blob, repo, obj, 8192, fd1);
368 if (err)
369 goto done;
370 err = cb(cb_arg, blob, NULL, f1, f2, id, NULL, label, NULL,
371 mode, 0, repo);
372 done:
373 got_object_close(obj);
374 if (blob)
375 got_object_blob_close(blob);
376 return err;
379 static const struct got_error *
380 diff_added_tree(struct got_object_id *id, FILE *f1, FILE *f2, int fd2,
381 const char *label, struct got_repository *repo, got_diff_blob_cb cb,
382 void *cb_arg, int diff_content)
384 const struct got_error *err = NULL;
385 struct got_object *treeobj = NULL;
386 struct got_tree_object *tree = NULL;
388 err = got_object_open(&treeobj, repo, id);
389 if (err)
390 goto done;
392 if (treeobj->type != GOT_OBJ_TYPE_TREE) {
393 err = got_error(GOT_ERR_OBJ_TYPE);
394 goto done;
397 err = got_object_tree_open(&tree, repo, treeobj);
398 if (err)
399 goto done;
401 err = got_diff_tree(NULL, tree, f1, f2, -1, fd2, NULL, label,
402 repo, cb, cb_arg, diff_content);
403 done:
404 if (tree)
405 got_object_tree_close(tree);
406 if (treeobj)
407 got_object_close(treeobj);
408 return err;
411 static const struct got_error *
412 diff_modified_tree(struct got_object_id *id1, struct got_object_id *id2,
413 FILE *f1, FILE *f2, int fd1, int fd2,
414 const char *label1, const char *label2,
415 struct got_repository *repo, got_diff_blob_cb cb, void *cb_arg,
416 int diff_content)
418 const struct got_error *err;
419 struct got_object *treeobj1 = NULL;
420 struct got_object *treeobj2 = NULL;
421 struct got_tree_object *tree1 = NULL;
422 struct got_tree_object *tree2 = NULL;
424 err = got_object_open(&treeobj1, repo, id1);
425 if (err)
426 goto done;
428 if (treeobj1->type != GOT_OBJ_TYPE_TREE) {
429 err = got_error(GOT_ERR_OBJ_TYPE);
430 goto done;
433 err = got_object_open(&treeobj2, repo, id2);
434 if (err)
435 goto done;
437 if (treeobj2->type != GOT_OBJ_TYPE_TREE) {
438 err = got_error(GOT_ERR_OBJ_TYPE);
439 goto done;
442 err = got_object_tree_open(&tree1, repo, treeobj1);
443 if (err)
444 goto done;
446 err = got_object_tree_open(&tree2, repo, treeobj2);
447 if (err)
448 goto done;
450 err = got_diff_tree(tree1, tree2, f1, f2, fd1, fd2,
451 label1, label2, repo, cb, cb_arg, diff_content);
453 done:
454 if (tree1)
455 got_object_tree_close(tree1);
456 if (tree2)
457 got_object_tree_close(tree2);
458 if (treeobj1)
459 got_object_close(treeobj1);
460 if (treeobj2)
461 got_object_close(treeobj2);
462 return err;
465 static const struct got_error *
466 diff_deleted_tree(struct got_object_id *id, FILE *f1, int fd1,
467 FILE *f2, const char *label, struct got_repository *repo,
468 got_diff_blob_cb cb, void *cb_arg, int diff_content)
470 const struct got_error *err;
471 struct got_object *treeobj = NULL;
472 struct got_tree_object *tree = NULL;
474 err = got_object_open(&treeobj, repo, id);
475 if (err)
476 goto done;
478 if (treeobj->type != GOT_OBJ_TYPE_TREE) {
479 err = got_error(GOT_ERR_OBJ_TYPE);
480 goto done;
483 err = got_object_tree_open(&tree, repo, treeobj);
484 if (err)
485 goto done;
487 err = got_diff_tree(tree, NULL, f1, f2, fd1, -1, label, NULL,
488 repo, cb, cb_arg, diff_content);
489 done:
490 if (tree)
491 got_object_tree_close(tree);
492 if (treeobj)
493 got_object_close(treeobj);
494 return err;
497 static const struct got_error *
498 diff_kind_mismatch(struct got_object_id *id1, struct got_object_id *id2,
499 const char *label1, const char *label2, struct got_repository *repo,
500 got_diff_blob_cb cb, void *cb_arg)
502 /* XXX TODO */
503 return NULL;
506 static const struct got_error *
507 diff_entry_old_new(struct got_tree_entry *te1, struct got_tree_entry *te2,
508 FILE *f1, FILE *f2, int fd1, int fd2,
509 const char *label1, const char *label2,
510 struct got_repository *repo, got_diff_blob_cb cb, void *cb_arg,
511 int diff_content)
513 const struct got_error *err = NULL;
514 int id_match;
516 if (got_object_tree_entry_is_submodule(te1))
517 return NULL;
519 if (te2 == NULL) {
520 if (S_ISDIR(te1->mode))
521 err = diff_deleted_tree(&te1->id, f1, fd1, f2,
522 label1, repo, cb, cb_arg, diff_content);
523 else {
524 if (diff_content)
525 err = diff_deleted_blob(&te1->id, f1, fd1,
526 f2, label1, te1->mode, repo, cb, cb_arg);
527 else
528 err = cb(cb_arg, NULL, NULL, NULL, NULL,
529 &te1->id, NULL, label1, NULL,
530 te1->mode, 0, repo);
532 return err;
533 } else if (got_object_tree_entry_is_submodule(te2))
534 return NULL;
536 id_match = (got_object_id_cmp(&te1->id, &te2->id) == 0);
537 if (S_ISDIR(te1->mode) && S_ISDIR(te2->mode)) {
538 if (!id_match)
539 return diff_modified_tree(&te1->id, &te2->id, f1, f2,
540 fd1, fd2, label1, label2, repo, cb, cb_arg,
541 diff_content);
542 } else if ((S_ISREG(te1->mode) || S_ISLNK(te1->mode)) &&
543 (S_ISREG(te2->mode) || S_ISLNK(te2->mode))) {
544 if (!id_match ||
545 ((te1->mode & (S_IFLNK | S_IXUSR))) !=
546 (te2->mode & (S_IFLNK | S_IXUSR))) {
547 if (diff_content)
548 return diff_modified_blob(&te1->id, &te2->id,
549 f1, f2, fd1, fd2, label1, label2,
550 te1->mode, te2->mode, repo, cb, cb_arg);
551 else
552 return cb(cb_arg, NULL, NULL, NULL, NULL,
553 &te1->id, &te2->id, label1, label2,
554 te1->mode, te2->mode, repo);
558 if (id_match)
559 return NULL;
561 return diff_kind_mismatch(&te1->id, &te2->id, label1, label2, repo,
562 cb, cb_arg);
565 static const struct got_error *
566 diff_entry_new_old(struct got_tree_entry *te2,
567 struct got_tree_entry *te1, FILE *f1, FILE *f2, int fd2, const char *label2,
568 struct got_repository *repo, got_diff_blob_cb cb, void *cb_arg,
569 int diff_content)
571 if (te1 != NULL) /* handled by diff_entry_old_new() */
572 return NULL;
574 if (got_object_tree_entry_is_submodule(te2))
575 return NULL;
577 if (S_ISDIR(te2->mode))
578 return diff_added_tree(&te2->id, f1, f2, fd2, label2,
579 repo, cb, cb_arg, diff_content);
581 if (diff_content)
582 return diff_added_blob(&te2->id, f1, f2, fd2,
583 label2, te2->mode, repo, cb, cb_arg);
585 return cb(cb_arg, NULL, NULL, NULL, NULL, NULL, &te2->id,
586 NULL, label2, 0, te2->mode, repo);
589 const struct got_error *
590 got_diff_tree_collect_changed_paths(void *arg, struct got_blob_object *blob1,
591 struct got_blob_object *blob2, FILE *f1, FILE *f2,
592 struct got_object_id *id1, struct got_object_id *id2,
593 const char *label1, const char *label2,
594 mode_t mode1, mode_t mode2, struct got_repository *repo)
596 const struct got_error *err = NULL;
597 struct got_pathlist_head *paths = arg;
598 struct got_diff_changed_path *change = NULL;
599 char *path = NULL;
601 path = strdup(label2 ? label2 : label1);
602 if (path == NULL)
603 return got_error_from_errno("malloc");
605 change = malloc(sizeof(*change));
606 if (change == NULL) {
607 err = got_error_from_errno("malloc");
608 goto done;
611 change->status = GOT_STATUS_NO_CHANGE;
612 if (id1 == NULL)
613 change->status = GOT_STATUS_ADD;
614 else if (id2 == NULL)
615 change->status = GOT_STATUS_DELETE;
616 else {
617 if (got_object_id_cmp(id1, id2) != 0)
618 change->status = GOT_STATUS_MODIFY;
619 else if (mode1 != mode2)
620 change->status = GOT_STATUS_MODE_CHANGE;
623 err = got_pathlist_append(paths, path, change);
624 done:
625 if (err) {
626 free(path);
627 free(change);
629 return err;
632 const struct got_error *
633 got_diff_tree(struct got_tree_object *tree1, struct got_tree_object *tree2,
634 FILE *f1, FILE *f2, int fd1, int fd2,
635 const char *label1, const char *label2,
636 struct got_repository *repo, got_diff_blob_cb cb, void *cb_arg,
637 int diff_content)
639 const struct got_error *err = NULL;
640 struct got_tree_entry *te1 = NULL;
641 struct got_tree_entry *te2 = NULL;
642 char *l1 = NULL, *l2 = NULL;
643 int tidx1 = 0, tidx2 = 0;
645 if (tree1) {
646 te1 = got_object_tree_get_entry(tree1, 0);
647 if (te1 && asprintf(&l1, "%s%s%s", label1, label1[0] ? "/" : "",
648 te1->name) == -1)
649 return got_error_from_errno("asprintf");
651 if (tree2) {
652 te2 = got_object_tree_get_entry(tree2, 0);
653 if (te2 && asprintf(&l2, "%s%s%s", label2, label2[0] ? "/" : "",
654 te2->name) == -1)
655 return got_error_from_errno("asprintf");
658 do {
659 if (te1) {
660 struct got_tree_entry *te = NULL;
661 if (tree2)
662 te = got_object_tree_find_entry(tree2,
663 te1->name);
664 if (te) {
665 free(l2);
666 l2 = NULL;
667 if (te && asprintf(&l2, "%s%s%s", label2,
668 label2[0] ? "/" : "", te->name) == -1)
669 return
670 got_error_from_errno("asprintf");
672 err = diff_entry_old_new(te1, te, f1, f2, fd1, fd2,
673 l1, l2, repo, cb, cb_arg, diff_content);
674 if (err)
675 break;
678 if (te2) {
679 struct got_tree_entry *te = NULL;
680 if (tree1)
681 te = got_object_tree_find_entry(tree1,
682 te2->name);
683 free(l2);
684 if (te) {
685 if (asprintf(&l2, "%s%s%s", label2,
686 label2[0] ? "/" : "", te->name) == -1)
687 return
688 got_error_from_errno("asprintf");
689 } else {
690 if (asprintf(&l2, "%s%s%s", label2,
691 label2[0] ? "/" : "", te2->name) == -1)
692 return
693 got_error_from_errno("asprintf");
695 err = diff_entry_new_old(te2, te, f1, f2, fd2, l2,
696 repo, cb, cb_arg, diff_content);
697 if (err)
698 break;
701 free(l1);
702 l1 = NULL;
703 if (te1) {
704 tidx1++;
705 te1 = got_object_tree_get_entry(tree1, tidx1);
706 if (te1 &&
707 asprintf(&l1, "%s%s%s", label1,
708 label1[0] ? "/" : "", te1->name) == -1)
709 return got_error_from_errno("asprintf");
711 free(l2);
712 l2 = NULL;
713 if (te2) {
714 tidx2++;
715 te2 = got_object_tree_get_entry(tree2, tidx2);
716 if (te2 &&
717 asprintf(&l2, "%s%s%s", label2,
718 label2[0] ? "/" : "", te2->name) == -1)
719 return got_error_from_errno("asprintf");
721 } while (te1 || te2);
723 return err;
726 const struct got_error *
727 got_diff_objects_as_blobs(off_t **line_offsets, size_t *nlines,
728 FILE *f1, FILE *f2, int fd1, int fd2,
729 struct got_object_id *id1, struct got_object_id *id2,
730 const char *label1, const char *label2, int diff_context,
731 int ignore_whitespace, int force_text_diff,
732 struct got_repository *repo, FILE *outfile)
734 const struct got_error *err;
735 struct got_blob_object *blob1 = NULL, *blob2 = NULL;
737 if (id1 == NULL && id2 == NULL)
738 return got_error(GOT_ERR_NO_OBJ);
740 if (id1) {
741 err = got_object_open_as_blob(&blob1, repo, id1, 8192, fd1);
742 if (err)
743 goto done;
745 if (id2) {
746 err = got_object_open_as_blob(&blob2, repo, id2, 8192, fd2);
747 if (err)
748 goto done;
750 err = got_diff_blob(line_offsets, nlines, blob1, blob2, f1, f2,
751 label1, label2, diff_context, ignore_whitespace, force_text_diff,
752 outfile);
753 done:
754 if (blob1)
755 got_object_blob_close(blob1);
756 if (blob2)
757 got_object_blob_close(blob2);
758 return err;
761 static const struct got_error *
762 diff_paths(struct got_tree_object *tree1, struct got_tree_object *tree2,
763 FILE *f1, FILE *f2, int fd1, int fd2, struct got_pathlist_head *paths,
764 struct got_repository *repo, got_diff_blob_cb cb, void *cb_arg)
766 const struct got_error *err = NULL;
767 struct got_pathlist_entry *pe;
768 struct got_object_id *id1 = NULL, *id2 = NULL;
769 struct got_tree_object *subtree1 = NULL, *subtree2 = NULL;
770 struct got_blob_object *blob1 = NULL, *blob2 = NULL;
772 TAILQ_FOREACH(pe, paths, entry) {
773 int type1 = GOT_OBJ_TYPE_ANY, type2 = GOT_OBJ_TYPE_ANY;
774 mode_t mode1 = 0, mode2 = 0;
776 free(id1);
777 id1 = NULL;
778 free(id2);
779 id2 = NULL;
780 if (subtree1) {
781 got_object_tree_close(subtree1);
782 subtree1 = NULL;
784 if (subtree2) {
785 got_object_tree_close(subtree2);
786 subtree2 = NULL;
788 if (blob1) {
789 got_object_blob_close(blob1);
790 blob1 = NULL;
792 if (blob2) {
793 got_object_blob_close(blob2);
794 blob2 = NULL;
797 err = got_object_tree_find_path(&id1, &mode1, repo, tree1,
798 pe->path);
799 if (err && err->code != GOT_ERR_NO_TREE_ENTRY)
800 goto done;
801 err = got_object_tree_find_path(&id2, &mode2, repo, tree2,
802 pe->path);
803 if (err && err->code != GOT_ERR_NO_TREE_ENTRY)
804 goto done;
805 if (id1 == NULL && id2 == NULL) {
806 err = got_error_path(pe->path, GOT_ERR_NO_TREE_ENTRY);
807 goto done;
809 if (id1) {
810 err = got_object_get_type(&type1, repo, id1);
811 if (err)
812 goto done;
814 if (id2) {
815 err = got_object_get_type(&type2, repo, id2);
816 if (err)
817 goto done;
819 if (type1 == GOT_OBJ_TYPE_ANY &&
820 type2 == GOT_OBJ_TYPE_ANY) {
821 err = got_error_path(pe->path, GOT_ERR_NO_OBJ);
822 goto done;
823 } else if (type1 != GOT_OBJ_TYPE_ANY &&
824 type2 != GOT_OBJ_TYPE_ANY && type1 != type2) {
825 err = got_error(GOT_ERR_OBJ_TYPE);
826 goto done;
829 if (type1 == GOT_OBJ_TYPE_BLOB ||
830 type2 == GOT_OBJ_TYPE_BLOB) {
831 if (id1) {
832 err = got_object_open_as_blob(&blob1, repo,
833 id1, 8192, fd1);
834 if (err)
835 goto done;
837 if (id2) {
838 err = got_object_open_as_blob(&blob2, repo,
839 id2, 8192, fd2);
840 if (err)
841 goto done;
843 err = cb(cb_arg, blob1, blob2, f1, f2, id1, id2,
844 id1 ? pe->path : "/dev/null",
845 id2 ? pe->path : "/dev/null",
846 mode1, mode2, repo);
847 if (err)
848 goto done;
849 } else if (type1 == GOT_OBJ_TYPE_TREE ||
850 type2 == GOT_OBJ_TYPE_TREE) {
851 if (id1) {
852 err = got_object_open_as_tree(&subtree1, repo,
853 id1);
854 if (err)
855 goto done;
857 if (id2) {
858 err = got_object_open_as_tree(&subtree2, repo,
859 id2);
860 if (err)
861 goto done;
863 err = got_diff_tree(subtree1, subtree2, f1, f2,
864 fd1, fd2,
865 id1 ? pe->path : "/dev/null",
866 id2 ? pe->path : "/dev/null",
867 repo, cb, cb_arg, 1);
868 if (err)
869 goto done;
870 } else {
871 err = got_error(GOT_ERR_OBJ_TYPE);
872 goto done;
875 done:
876 free(id1);
877 free(id2);
878 if (subtree1)
879 got_object_tree_close(subtree1);
880 if (subtree2)
881 got_object_tree_close(subtree2);
882 if (blob1)
883 got_object_blob_close(blob1);
884 if (blob2)
885 got_object_blob_close(blob2);
886 return err;
889 static const struct got_error *
890 show_object_id(off_t **line_offsets, size_t *nlines, const char *obj_typestr,
891 int ch, const char *id_str, FILE *outfile)
893 const struct got_error *err;
894 int n;
895 off_t outoff = 0;
897 n = fprintf(outfile, "%s %c %s\n", obj_typestr, ch, id_str);
898 if (line_offsets != NULL && *line_offsets != NULL) {
899 if (*nlines == 0) {
900 err = add_line_offset(line_offsets, nlines, 0);
901 if (err)
902 return err;
903 } else
904 outoff = (*line_offsets)[*nlines - 1];
906 outoff += n;
907 err = add_line_offset(line_offsets, nlines, outoff);
908 if (err)
909 return err;
912 return NULL;
915 static const struct got_error *
916 diff_objects_as_trees(off_t **line_offsets, size_t *nlines,
917 FILE *f1, FILE *f2, int fd1, int fd2,
918 struct got_object_id *id1, struct got_object_id *id2,
919 struct got_pathlist_head *paths, const char *label1, const char *label2,
920 int diff_context, int ignore_whitespace, int force_text_diff,
921 struct got_repository *repo, FILE *outfile)
923 const struct got_error *err;
924 struct got_tree_object *tree1 = NULL, *tree2 = NULL;
925 struct got_diff_blob_output_unidiff_arg arg;
926 int want_lineoffsets = (line_offsets != NULL && *line_offsets != NULL);
928 if (id1 == NULL && id2 == NULL)
929 return got_error(GOT_ERR_NO_OBJ);
931 if (id1) {
932 err = got_object_open_as_tree(&tree1, repo, id1);
933 if (err)
934 goto done;
936 if (id2) {
937 err = got_object_open_as_tree(&tree2, repo, id2);
938 if (err)
939 goto done;
942 arg.diff_context = diff_context;
943 arg.ignore_whitespace = ignore_whitespace;
944 arg.force_text_diff = force_text_diff;
945 arg.outfile = outfile;
946 if (want_lineoffsets) {
947 arg.line_offsets = *line_offsets;
948 arg.nlines = *nlines;
949 } else {
950 arg.line_offsets = NULL;
951 arg.nlines = 0;
953 if (paths == NULL || TAILQ_EMPTY(paths)) {
954 err = got_diff_tree(tree1, tree2, f1, f2, fd1, fd2,
955 label1, label2, repo,
956 got_diff_blob_output_unidiff, &arg, 1);
957 } else {
958 err = diff_paths(tree1, tree2, f1, f2, fd1, fd2, paths, repo,
959 got_diff_blob_output_unidiff, &arg);
961 if (want_lineoffsets) {
962 *line_offsets = arg.line_offsets; /* was likely re-allocated */
963 *nlines = arg.nlines;
965 done:
966 if (tree1)
967 got_object_tree_close(tree1);
968 if (tree2)
969 got_object_tree_close(tree2);
970 return err;
973 const struct got_error *
974 got_diff_objects_as_trees(off_t **line_offsets, size_t *nlines,
975 FILE *f1, FILE *f2, int fd1, int fd2,
976 struct got_object_id *id1, struct got_object_id *id2,
977 struct got_pathlist_head *paths, const char *label1, const char *label2,
978 int diff_context, int ignore_whitespace, int force_text_diff,
979 struct got_repository *repo, FILE *outfile)
981 const struct got_error *err;
982 char *idstr = NULL;
984 if (id1 == NULL && id2 == NULL)
985 return got_error(GOT_ERR_NO_OBJ);
987 if (id1) {
988 err = got_object_id_str(&idstr, id1);
989 if (err)
990 goto done;
991 err = show_object_id(line_offsets, nlines, "tree", '-',
992 idstr, outfile);
993 if (err)
994 goto done;
995 free(idstr);
996 idstr = NULL;
997 } else {
998 err = show_object_id(line_offsets, nlines, "tree", '-',
999 "/dev/null", outfile);
1000 if (err)
1001 goto done;
1004 if (id2) {
1005 err = got_object_id_str(&idstr, id2);
1006 if (err)
1007 goto done;
1008 err = show_object_id(line_offsets, nlines, "tree", '+',
1009 idstr, outfile);
1010 if (err)
1011 goto done;
1012 free(idstr);
1013 idstr = NULL;
1014 } else {
1015 err = show_object_id(line_offsets, nlines, "tree", '+',
1016 "/dev/null", outfile);
1017 if (err)
1018 goto done;
1021 err = diff_objects_as_trees(line_offsets, nlines, f1, f2, fd1, fd2,
1022 id1, id2, paths, label1, label2, diff_context, ignore_whitespace,
1023 force_text_diff, repo, outfile);
1024 done:
1025 free(idstr);
1026 return err;
1029 const struct got_error *
1030 got_diff_objects_as_commits(off_t **line_offsets, size_t *nlines,
1031 FILE *f1, FILE *f2, int fd1, int fd2,
1032 struct got_object_id *id1, struct got_object_id *id2,
1033 struct got_pathlist_head *paths,
1034 int diff_context, int ignore_whitespace, int force_text_diff,
1035 struct got_repository *repo, FILE *outfile)
1037 const struct got_error *err;
1038 struct got_commit_object *commit1 = NULL, *commit2 = NULL;
1039 char *idstr = NULL;
1041 if (id2 == NULL)
1042 return got_error(GOT_ERR_NO_OBJ);
1044 if (id1) {
1045 err = got_object_open_as_commit(&commit1, repo, id1);
1046 if (err)
1047 goto done;
1048 err = got_object_id_str(&idstr, id1);
1049 if (err)
1050 goto done;
1051 err = show_object_id(line_offsets, nlines, "commit", '-',
1052 idstr, outfile);
1053 if (err)
1054 goto done;
1055 free(idstr);
1056 idstr = NULL;
1057 } else {
1058 err = show_object_id(line_offsets, nlines, "commit", '-',
1059 "/dev/null", outfile);
1060 if (err)
1061 goto done;
1064 err = got_object_open_as_commit(&commit2, repo, id2);
1065 if (err)
1066 goto done;
1068 err = got_object_id_str(&idstr, id2);
1069 if (err)
1070 goto done;
1071 err = show_object_id(line_offsets, nlines, "commit", '+',
1072 idstr, outfile);
1073 if (err)
1074 goto done;
1076 err = diff_objects_as_trees(line_offsets, nlines, f1, f2, fd1, fd2,
1077 commit1 ? got_object_commit_get_tree_id(commit1) : NULL,
1078 got_object_commit_get_tree_id(commit2), paths, "", "",
1079 diff_context, ignore_whitespace, force_text_diff, repo, outfile);
1080 done:
1081 if (commit1)
1082 got_object_commit_close(commit1);
1083 if (commit2)
1084 got_object_commit_close(commit2);
1085 free(idstr);
1086 return err;
1089 const struct got_error *
1090 got_diff_files(struct got_diffreg_result **resultp,
1091 FILE *f1, int f1_exists, const char *label1, FILE *f2, int f2_exists,
1092 const char *label2, int diff_context, int ignore_whitespace,
1093 int force_text_diff, FILE *outfile)
1095 const struct got_error *err = NULL;
1096 struct got_diffreg_result *diffreg_result = NULL;
1098 if (resultp)
1099 *resultp = NULL;
1101 if (outfile) {
1102 fprintf(outfile, "file - %s\n",
1103 f1_exists ? label1 : "/dev/null");
1104 fprintf(outfile, "file + %s\n",
1105 f2_exists ? label2 : "/dev/null");
1108 err = got_diffreg(&diffreg_result, f1, f2, GOT_DIFF_ALGORITHM_PATIENCE,
1109 ignore_whitespace, force_text_diff);
1110 if (err)
1111 goto done;
1113 if (outfile) {
1114 err = got_diffreg_output(NULL, NULL, diffreg_result,
1115 f1_exists, f2_exists, label1, label2,
1116 GOT_DIFF_OUTPUT_UNIDIFF, diff_context, outfile);
1117 if (err)
1118 goto done;
1121 done:
1122 if (resultp && err == NULL)
1123 *resultp = diffreg_result;
1124 else if (diffreg_result) {
1125 const struct got_error *free_err;
1126 free_err = got_diffreg_result_free(diffreg_result);
1127 if (free_err && err == NULL)
1128 err = free_err;
1131 return err;