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 #ifndef MAX
42 #define MAX(_a,_b) ((_a) > (_b) ? (_a) : (_b))
43 #endif
45 static const struct got_error *
46 add_line_metadata(struct got_diff_line **lines, size_t *nlines,
47 off_t off, uint8_t type)
48 {
49 struct got_diff_line *p;
51 p = reallocarray(*lines, *nlines + 1, sizeof(**lines));
52 if (p == NULL)
53 return got_error_from_errno("reallocarray");
54 *lines = p;
55 (*lines)[*nlines].offset = off;
56 (*lines)[*nlines].type = type;
57 (*nlines)++;
59 return NULL;
60 }
62 static const struct got_error *
63 diff_blobs(struct got_diff_line **lines, size_t *nlines,
64 struct got_diffreg_result **resultp, struct got_blob_object *blob1,
65 struct got_blob_object *blob2, FILE *f1, FILE *f2,
66 const char *label1, const char *label2, mode_t mode1, mode_t mode2,
67 int diff_context, int ignore_whitespace, int force_text_diff, FILE *outfile,
68 enum got_diff_algorithm diff_algo)
69 {
70 const struct got_error *err = NULL, *free_err;
71 char hex1[SHA1_DIGEST_STRING_LENGTH];
72 char hex2[SHA1_DIGEST_STRING_LENGTH];
73 const char *idstr1 = NULL, *idstr2 = NULL;
74 off_t size1, size2;
75 struct got_diffreg_result *result = NULL;
76 off_t outoff = 0;
77 int n;
79 if (lines && *lines && *nlines > 0)
80 outoff = (*lines)[*nlines - 1].offset;
81 else if (lines) {
82 err = add_line_metadata(lines, nlines, 0, GOT_DIFF_LINE_NONE);
83 if (err)
84 goto done;
85 }
87 if (resultp)
88 *resultp = NULL;
90 if (f1) {
91 err = got_opentemp_truncate(f1);
92 if (err)
93 goto done;
94 }
95 if (f2) {
96 err = got_opentemp_truncate(f2);
97 if (err)
98 goto done;
99 }
101 size1 = 0;
102 if (blob1) {
103 idstr1 = got_object_blob_id_str(blob1, hex1, sizeof(hex1));
104 err = got_object_blob_dump_to_file(&size1, NULL, NULL, f1,
105 blob1);
106 if (err)
107 goto done;
108 } else
109 idstr1 = "/dev/null";
111 size2 = 0;
112 if (blob2) {
113 idstr2 = got_object_blob_id_str(blob2, hex2, sizeof(hex2));
114 err = got_object_blob_dump_to_file(&size2, NULL, NULL, f2,
115 blob2);
116 if (err)
117 goto done;
118 } else
119 idstr2 = "/dev/null";
121 if (outfile) {
122 char *modestr1 = NULL, *modestr2 = NULL;
123 int modebits;
124 if (mode1 && mode1 != mode2) {
125 if (S_ISLNK(mode1))
126 modebits = S_IFLNK;
127 else
128 modebits = (S_IRWXU | S_IRWXG | S_IRWXO);
129 if (asprintf(&modestr1, " (mode %o)",
130 mode1 & modebits) == -1) {
131 err = got_error_from_errno("asprintf");
132 goto done;
135 if (mode2 && mode1 != mode2) {
136 if (S_ISLNK(mode2))
137 modebits = S_IFLNK;
138 else
139 modebits = (S_IRWXU | S_IRWXG | S_IRWXO);
140 if (asprintf(&modestr2, " (mode %o)",
141 mode2 & modebits) == -1) {
142 err = got_error_from_errno("asprintf");
143 goto done;
146 n = fprintf(outfile, "blob - %s%s\n", idstr1,
147 modestr1 ? modestr1 : "");
148 if (n < 0)
149 goto done;
150 outoff += n;
151 if (lines) {
152 err = add_line_metadata(lines, nlines, outoff,
153 GOT_DIFF_LINE_BLOB_MIN);
154 if (err)
155 goto done;
158 n = fprintf(outfile, "blob + %s%s\n", idstr2,
159 modestr2 ? modestr2 : "");
160 if (n < 0)
161 goto done;
162 outoff += n;
163 if (lines) {
164 err = add_line_metadata(lines, nlines, outoff,
165 GOT_DIFF_LINE_BLOB_PLUS);
166 if (err)
167 goto done;
170 free(modestr1);
171 free(modestr2);
173 err = got_diffreg(&result, f1, f2, diff_algo, ignore_whitespace,
174 force_text_diff);
175 if (err)
176 goto done;
178 if (outfile) {
179 err = got_diffreg_output(lines, nlines, result,
180 blob1 != NULL, blob2 != NULL,
181 label1 ? label1 : idstr1,
182 label2 ? label2 : idstr2,
183 GOT_DIFF_OUTPUT_UNIDIFF, diff_context, outfile);
184 if (err)
185 goto done;
188 done:
189 if (resultp && err == NULL)
190 *resultp = result;
191 else if (result) {
192 free_err = got_diffreg_result_free(result);
193 if (free_err && err == NULL)
194 err = free_err;
197 return err;
200 const struct got_error *
201 got_diff_blob_output_unidiff(void *arg, struct got_blob_object *blob1,
202 struct got_blob_object *blob2, FILE *f1, FILE *f2,
203 struct got_object_id *id1, struct got_object_id *id2,
204 const char *label1, const char *label2, mode_t mode1, mode_t mode2,
205 struct got_repository *repo)
207 struct got_diff_blob_output_unidiff_arg *a = arg;
209 return diff_blobs(&a->lines, &a->nlines, NULL,
210 blob1, blob2, f1, f2, label1, label2, mode1, mode2, a->diff_context,
211 a->ignore_whitespace, a->force_text_diff, a->outfile, a->diff_algo);
214 const struct got_error *
215 got_diff_blob(struct got_diff_line **lines, size_t*nlines,
216 struct got_blob_object *blob1, struct got_blob_object *blob2,
217 FILE *f1, FILE *f2, const char *label1, const char *label2,
218 enum got_diff_algorithm diff_algo, int diff_context,
219 int ignore_whitespace, int force_text_diff, FILE *outfile)
221 return diff_blobs(lines, nlines, NULL, blob1, blob2, f1, f2,
222 label1, label2, 0, 0, diff_context, ignore_whitespace,
223 force_text_diff, outfile, diff_algo);
226 static const struct got_error *
227 diff_blob_file(struct got_diffreg_result **resultp,
228 struct got_blob_object *blob1, FILE *f1, off_t size1, const char *label1,
229 FILE *f2, int f2_exists, struct stat *sb2, const char *label2,
230 enum got_diff_algorithm diff_algo, int diff_context, int ignore_whitespace,
231 int force_text_diff, FILE *outfile)
233 const struct got_error *err = NULL, *free_err;
234 char hex1[SHA1_DIGEST_STRING_LENGTH];
235 const char *idstr1 = NULL;
236 struct got_diffreg_result *result = NULL;
238 if (resultp)
239 *resultp = NULL;
241 if (blob1)
242 idstr1 = got_object_blob_id_str(blob1, hex1, sizeof(hex1));
243 else
244 idstr1 = "/dev/null";
246 if (outfile) {
247 char *mode = NULL;
249 /* display file mode for new added files only */
250 if (f2_exists && blob1 == NULL) {
251 int mmask = (S_IRWXU | S_IRWXG | S_IRWXO);
253 if (S_ISLNK(sb2->st_mode))
254 mmask = S_IFLNK;
255 if (asprintf(&mode, " (mode %o)",
256 sb2->st_mode & mmask) == -1)
257 return got_error_from_errno("asprintf");
259 fprintf(outfile, "blob - %s\n", label1 ? label1 : idstr1);
260 fprintf(outfile, "file + %s%s\n",
261 f2_exists ? label2 : "/dev/null", mode ? mode : "");
262 free(mode);
265 err = got_diffreg(&result, f1, f2, diff_algo, ignore_whitespace,
266 force_text_diff);
267 if (err)
268 goto done;
270 if (outfile) {
271 err = got_diffreg_output(NULL, NULL, result,
272 blob1 != NULL, f2_exists,
273 label2, /* show local file's path, not a blob ID */
274 label2, GOT_DIFF_OUTPUT_UNIDIFF,
275 diff_context, outfile);
276 if (err)
277 goto done;
280 done:
281 if (resultp && err == NULL)
282 *resultp = result;
283 else if (result) {
284 free_err = got_diffreg_result_free(result);
285 if (free_err && err == NULL)
286 err = free_err;
288 return err;
291 const struct got_error *
292 got_diff_blob_file(struct got_blob_object *blob1, FILE *f1, off_t size1,
293 const char *label1, FILE *f2, int f2_exists, struct stat *sb2,
294 const char *label2, enum got_diff_algorithm diff_algo, int diff_context,
295 int ignore_whitespace, int force_text_diff, FILE *outfile)
297 return diff_blob_file(NULL, blob1, f1, size1, label1, f2, f2_exists,
298 sb2, label2, diff_algo, diff_context, ignore_whitespace,
299 force_text_diff, outfile);
302 static const struct got_error *
303 diff_added_blob(struct got_object_id *id, FILE *f1, FILE *f2, int fd2,
304 const char *label, mode_t mode, struct got_repository *repo,
305 got_diff_blob_cb cb, void *cb_arg)
307 const struct got_error *err;
308 struct got_blob_object *blob = NULL;
309 struct got_object *obj = NULL;
311 err = got_object_open(&obj, repo, id);
312 if (err)
313 return err;
315 err = got_object_blob_open(&blob, repo, obj, 8192, fd2);
316 if (err)
317 goto done;
318 err = cb(cb_arg, NULL, blob, f1, f2, NULL, id,
319 NULL, label, 0, mode, repo);
320 done:
321 got_object_close(obj);
322 if (blob)
323 got_object_blob_close(blob);
324 return err;
327 static const struct got_error *
328 diff_modified_blob(struct got_object_id *id1, struct got_object_id *id2,
329 FILE *f1, FILE *f2, int fd1, int fd2,
330 const char *label1, const char *label2,
331 mode_t mode1, mode_t mode2, struct got_repository *repo,
332 got_diff_blob_cb cb, void *cb_arg)
334 const struct got_error *err;
335 struct got_object *obj1 = NULL;
336 struct got_object *obj2 = NULL;
337 struct got_blob_object *blob1 = NULL;
338 struct got_blob_object *blob2 = NULL;
340 err = got_object_open(&obj1, repo, id1);
341 if (err)
342 return err;
344 if (obj1->type != GOT_OBJ_TYPE_BLOB) {
345 err = got_error(GOT_ERR_OBJ_TYPE);
346 goto done;
349 err = got_object_open(&obj2, repo, id2);
350 if (err)
351 goto done;
352 if (obj2->type != GOT_OBJ_TYPE_BLOB) {
353 err = got_error(GOT_ERR_BAD_OBJ_DATA);
354 goto done;
357 err = got_object_blob_open(&blob1, repo, obj1, 8192, fd1);
358 if (err)
359 goto done;
361 err = got_object_blob_open(&blob2, repo, obj2, 8192, fd2);
362 if (err)
363 goto done;
365 err = cb(cb_arg, blob1, blob2, f1, f2, id1, id2, label1, label2,
366 mode1, mode2, repo);
367 done:
368 if (obj1)
369 got_object_close(obj1);
370 if (obj2)
371 got_object_close(obj2);
372 if (blob1)
373 got_object_blob_close(blob1);
374 if (blob2)
375 got_object_blob_close(blob2);
376 return err;
379 static const struct got_error *
380 diff_deleted_blob(struct got_object_id *id, FILE *f1, int fd1,
381 FILE *f2, const char *label, mode_t mode, struct got_repository *repo,
382 got_diff_blob_cb cb, void *cb_arg)
384 const struct got_error *err;
385 struct got_blob_object *blob = NULL;
386 struct got_object *obj = NULL;
388 err = got_object_open(&obj, repo, id);
389 if (err)
390 return err;
392 err = got_object_blob_open(&blob, repo, obj, 8192, fd1);
393 if (err)
394 goto done;
395 err = cb(cb_arg, blob, NULL, f1, f2, id, NULL, label, NULL,
396 mode, 0, repo);
397 done:
398 got_object_close(obj);
399 if (blob)
400 got_object_blob_close(blob);
401 return err;
404 static const struct got_error *
405 diff_added_tree(struct got_object_id *id, FILE *f1, FILE *f2, int fd2,
406 const char *label, struct got_repository *repo, got_diff_blob_cb cb,
407 void *cb_arg, int diff_content)
409 const struct got_error *err = NULL;
410 struct got_object *treeobj = NULL;
411 struct got_tree_object *tree = NULL;
413 err = got_object_open(&treeobj, repo, id);
414 if (err)
415 goto done;
417 if (treeobj->type != GOT_OBJ_TYPE_TREE) {
418 err = got_error(GOT_ERR_OBJ_TYPE);
419 goto done;
422 err = got_object_tree_open(&tree, repo, treeobj);
423 if (err)
424 goto done;
426 err = got_diff_tree(NULL, tree, f1, f2, -1, fd2, NULL, label,
427 repo, cb, cb_arg, diff_content);
428 done:
429 if (tree)
430 got_object_tree_close(tree);
431 if (treeobj)
432 got_object_close(treeobj);
433 return err;
436 static const struct got_error *
437 diff_modified_tree(struct got_object_id *id1, struct got_object_id *id2,
438 FILE *f1, FILE *f2, int fd1, int fd2,
439 const char *label1, const char *label2,
440 struct got_repository *repo, got_diff_blob_cb cb, void *cb_arg,
441 int diff_content)
443 const struct got_error *err;
444 struct got_object *treeobj1 = NULL;
445 struct got_object *treeobj2 = NULL;
446 struct got_tree_object *tree1 = NULL;
447 struct got_tree_object *tree2 = NULL;
449 err = got_object_open(&treeobj1, repo, id1);
450 if (err)
451 goto done;
453 if (treeobj1->type != GOT_OBJ_TYPE_TREE) {
454 err = got_error(GOT_ERR_OBJ_TYPE);
455 goto done;
458 err = got_object_open(&treeobj2, repo, id2);
459 if (err)
460 goto done;
462 if (treeobj2->type != GOT_OBJ_TYPE_TREE) {
463 err = got_error(GOT_ERR_OBJ_TYPE);
464 goto done;
467 err = got_object_tree_open(&tree1, repo, treeobj1);
468 if (err)
469 goto done;
471 err = got_object_tree_open(&tree2, repo, treeobj2);
472 if (err)
473 goto done;
475 err = got_diff_tree(tree1, tree2, f1, f2, fd1, fd2,
476 label1, label2, repo, cb, cb_arg, diff_content);
478 done:
479 if (tree1)
480 got_object_tree_close(tree1);
481 if (tree2)
482 got_object_tree_close(tree2);
483 if (treeobj1)
484 got_object_close(treeobj1);
485 if (treeobj2)
486 got_object_close(treeobj2);
487 return err;
490 static const struct got_error *
491 diff_deleted_tree(struct got_object_id *id, FILE *f1, int fd1,
492 FILE *f2, const char *label, struct got_repository *repo,
493 got_diff_blob_cb cb, void *cb_arg, int diff_content)
495 const struct got_error *err;
496 struct got_object *treeobj = NULL;
497 struct got_tree_object *tree = NULL;
499 err = got_object_open(&treeobj, repo, id);
500 if (err)
501 goto done;
503 if (treeobj->type != GOT_OBJ_TYPE_TREE) {
504 err = got_error(GOT_ERR_OBJ_TYPE);
505 goto done;
508 err = got_object_tree_open(&tree, repo, treeobj);
509 if (err)
510 goto done;
512 err = got_diff_tree(tree, NULL, f1, f2, fd1, -1, label, NULL,
513 repo, cb, cb_arg, diff_content);
514 done:
515 if (tree)
516 got_object_tree_close(tree);
517 if (treeobj)
518 got_object_close(treeobj);
519 return err;
522 static const struct got_error *
523 diff_kind_mismatch(struct got_object_id *id1, struct got_object_id *id2,
524 const char *label1, const char *label2, struct got_repository *repo,
525 got_diff_blob_cb cb, void *cb_arg)
527 /* XXX TODO */
528 return NULL;
531 static const struct got_error *
532 diff_entry_old_new(struct got_tree_entry *te1, struct got_tree_entry *te2,
533 FILE *f1, FILE *f2, int fd1, int fd2,
534 const char *label1, const char *label2,
535 struct got_repository *repo, got_diff_blob_cb cb, void *cb_arg,
536 int diff_content)
538 const struct got_error *err = NULL;
539 int id_match;
541 if (got_object_tree_entry_is_submodule(te1))
542 return NULL;
544 if (te2 == NULL) {
545 if (S_ISDIR(te1->mode))
546 err = diff_deleted_tree(&te1->id, f1, fd1, f2,
547 label1, repo, cb, cb_arg, diff_content);
548 else {
549 if (diff_content)
550 err = diff_deleted_blob(&te1->id, f1, fd1,
551 f2, label1, te1->mode, repo, cb, cb_arg);
552 else
553 err = cb(cb_arg, NULL, NULL, NULL, NULL,
554 &te1->id, NULL, label1, NULL,
555 te1->mode, 0, repo);
557 return err;
558 } else if (got_object_tree_entry_is_submodule(te2))
559 return NULL;
561 id_match = (got_object_id_cmp(&te1->id, &te2->id) == 0);
562 if (S_ISDIR(te1->mode) && S_ISDIR(te2->mode)) {
563 if (!id_match)
564 return diff_modified_tree(&te1->id, &te2->id, f1, f2,
565 fd1, fd2, label1, label2, repo, cb, cb_arg,
566 diff_content);
567 } else if ((S_ISREG(te1->mode) || S_ISLNK(te1->mode)) &&
568 (S_ISREG(te2->mode) || S_ISLNK(te2->mode))) {
569 if (!id_match ||
570 ((te1->mode & (S_IFLNK | S_IXUSR))) !=
571 (te2->mode & (S_IFLNK | S_IXUSR))) {
572 if (diff_content)
573 return diff_modified_blob(&te1->id, &te2->id,
574 f1, f2, fd1, fd2, label1, label2,
575 te1->mode, te2->mode, repo, cb, cb_arg);
576 else
577 return cb(cb_arg, NULL, NULL, NULL, NULL,
578 &te1->id, &te2->id, label1, label2,
579 te1->mode, te2->mode, repo);
583 if (id_match)
584 return NULL;
586 return diff_kind_mismatch(&te1->id, &te2->id, label1, label2, repo,
587 cb, cb_arg);
590 static const struct got_error *
591 diff_entry_new_old(struct got_tree_entry *te2,
592 struct got_tree_entry *te1, FILE *f1, FILE *f2, int fd2, const char *label2,
593 struct got_repository *repo, got_diff_blob_cb cb, void *cb_arg,
594 int diff_content)
596 if (te1 != NULL) /* handled by diff_entry_old_new() */
597 return NULL;
599 if (got_object_tree_entry_is_submodule(te2))
600 return NULL;
602 if (S_ISDIR(te2->mode))
603 return diff_added_tree(&te2->id, f1, f2, fd2, label2,
604 repo, cb, cb_arg, diff_content);
606 if (diff_content)
607 return diff_added_blob(&te2->id, f1, f2, fd2,
608 label2, te2->mode, repo, cb, cb_arg);
610 return cb(cb_arg, NULL, NULL, NULL, NULL, NULL, &te2->id,
611 NULL, label2, 0, te2->mode, repo);
614 static void
615 diffstat_field_width(size_t *maxlen, int *add_cols, int *rm_cols, size_t len,
616 uint32_t add, uint32_t rm)
618 int d1 = 1, d2 = 1;
620 *maxlen = MAX(*maxlen, len);
622 while (add /= 10)
623 ++d1;
624 *add_cols = MAX(*add_cols, d1);
626 while (rm /= 10)
627 ++d2;
628 *rm_cols = MAX(*rm_cols, d2);
631 const struct got_error *
632 got_diff_tree_compute_diffstat(void *arg, struct got_blob_object *blob1,
633 struct got_blob_object *blob2, FILE *f1, FILE *f2,
634 struct got_object_id *id1, struct got_object_id *id2,
635 const char *label1, const char *label2,
636 mode_t mode1, mode_t mode2, struct got_repository *repo)
638 const struct got_error *err = NULL;
639 struct got_diffreg_result *result = NULL;
640 struct diff_result *r;
641 struct got_diff_changed_path *change = NULL;
642 struct got_diffstat_cb_arg *a = arg;
643 struct got_pathlist_entry *pe;
644 char *path = NULL;
645 int i;
647 path = strdup(label2 ? label2 : label1);
648 if (path == NULL)
649 return got_error_from_errno("malloc");
651 change = malloc(sizeof(*change));
652 if (change == NULL) {
653 err = got_error_from_errno("malloc");
654 goto done;
657 change->add = 0;
658 change->rm = 0;
659 change->status = GOT_STATUS_NO_CHANGE;
660 if (id1 == NULL)
661 change->status = GOT_STATUS_ADD;
662 else if (id2 == NULL)
663 change->status = GOT_STATUS_DELETE;
664 else {
665 if (got_object_id_cmp(id1, id2) != 0)
666 change->status = GOT_STATUS_MODIFY;
667 else if (mode1 != mode2)
668 change->status = GOT_STATUS_MODE_CHANGE;
671 if (f1) {
672 err = got_opentemp_truncate(f1);
673 if (err)
674 goto done;
676 if (f2) {
677 err = got_opentemp_truncate(f2);
678 if (err)
679 goto done;
682 if (blob1) {
683 err = got_object_blob_dump_to_file(NULL, NULL, NULL, f1,
684 blob1);
685 if (err)
686 goto done;
688 if (blob2) {
689 err = got_object_blob_dump_to_file(NULL, NULL, NULL, f2,
690 blob2);
691 if (err)
692 goto done;
695 err = got_diffreg(&result, f1, f2, a->diff_algo, a->ignore_ws,
696 a->force_text);
697 if (err)
698 goto done;
700 for (i = 0, r = result->result; i < r->chunks.len; ++i) {
701 int flags = (r->left->atomizer_flags | r->right->atomizer_flags);
702 int isbin = (flags & DIFF_ATOMIZER_FOUND_BINARY_DATA);
704 if (!isbin || a->force_text) {
705 struct diff_chunk *c;
706 int clc, crc;
708 c = diff_chunk_get(r, i);
709 clc = diff_chunk_get_left_count(c);
710 crc = diff_chunk_get_right_count(c);
712 if (clc && !crc)
713 change->rm += clc;
714 else if (crc && !clc)
715 change->add += crc;
719 err = got_pathlist_append(a->paths, path, change);
720 if (err)
721 goto done;
723 pe = TAILQ_LAST(a->paths, got_pathlist_head);
724 diffstat_field_width(&a->max_path_len, &a->add_cols, &a->rm_cols,
725 pe->path_len, change->add, change->rm);
726 a->ins += change->add;
727 a->del += change->rm;
728 ++a->nfiles;
730 done:
731 if (result) {
732 const struct got_error *free_err;
734 free_err = got_diffreg_result_free(result);
735 if (free_err && err == NULL)
736 err = free_err;
738 if (err) {
739 free(path);
740 free(change);
742 return err;
745 const struct got_error *
746 got_diff_tree_collect_changed_paths(void *arg, struct got_blob_object *blob1,
747 struct got_blob_object *blob2, FILE *f1, FILE *f2,
748 struct got_object_id *id1, struct got_object_id *id2,
749 const char *label1, const char *label2,
750 mode_t mode1, mode_t mode2, struct got_repository *repo)
752 const struct got_error *err = NULL;
753 struct got_pathlist_head *paths = arg;
754 struct got_diff_changed_path *change = NULL;
755 char *path = NULL;
757 path = strdup(label2 ? label2 : label1);
758 if (path == NULL)
759 return got_error_from_errno("malloc");
761 change = malloc(sizeof(*change));
762 if (change == NULL) {
763 err = got_error_from_errno("malloc");
764 goto done;
767 change->status = GOT_STATUS_NO_CHANGE;
768 if (id1 == NULL)
769 change->status = GOT_STATUS_ADD;
770 else if (id2 == NULL)
771 change->status = GOT_STATUS_DELETE;
772 else {
773 if (got_object_id_cmp(id1, id2) != 0)
774 change->status = GOT_STATUS_MODIFY;
775 else if (mode1 != mode2)
776 change->status = GOT_STATUS_MODE_CHANGE;
779 err = got_pathlist_append(paths, path, change);
780 done:
781 if (err) {
782 free(path);
783 free(change);
785 return err;
788 const struct got_error *
789 got_diff_tree(struct got_tree_object *tree1, struct got_tree_object *tree2,
790 FILE *f1, FILE *f2, int fd1, int fd2,
791 const char *label1, const char *label2,
792 struct got_repository *repo, got_diff_blob_cb cb, void *cb_arg,
793 int diff_content)
795 const struct got_error *err = NULL;
796 struct got_tree_entry *te1 = NULL;
797 struct got_tree_entry *te2 = NULL;
798 char *l1 = NULL, *l2 = NULL;
799 int tidx1 = 0, tidx2 = 0;
801 if (tree1) {
802 te1 = got_object_tree_get_entry(tree1, 0);
803 if (te1 && asprintf(&l1, "%s%s%s", label1, label1[0] ? "/" : "",
804 te1->name) == -1)
805 return got_error_from_errno("asprintf");
807 if (tree2) {
808 te2 = got_object_tree_get_entry(tree2, 0);
809 if (te2 && asprintf(&l2, "%s%s%s", label2, label2[0] ? "/" : "",
810 te2->name) == -1)
811 return got_error_from_errno("asprintf");
814 do {
815 if (te1) {
816 struct got_tree_entry *te = NULL;
817 if (tree2)
818 te = got_object_tree_find_entry(tree2,
819 te1->name);
820 if (te) {
821 free(l2);
822 l2 = NULL;
823 if (te && asprintf(&l2, "%s%s%s", label2,
824 label2[0] ? "/" : "", te->name) == -1)
825 return
826 got_error_from_errno("asprintf");
828 err = diff_entry_old_new(te1, te, f1, f2, fd1, fd2,
829 l1, l2, repo, cb, cb_arg, diff_content);
830 if (err)
831 break;
834 if (te2) {
835 struct got_tree_entry *te = NULL;
836 if (tree1)
837 te = got_object_tree_find_entry(tree1,
838 te2->name);
839 free(l2);
840 if (te) {
841 if (asprintf(&l2, "%s%s%s", label2,
842 label2[0] ? "/" : "", te->name) == -1)
843 return
844 got_error_from_errno("asprintf");
845 } else {
846 if (asprintf(&l2, "%s%s%s", label2,
847 label2[0] ? "/" : "", te2->name) == -1)
848 return
849 got_error_from_errno("asprintf");
851 err = diff_entry_new_old(te2, te, f1, f2, fd2, l2,
852 repo, cb, cb_arg, diff_content);
853 if (err)
854 break;
857 free(l1);
858 l1 = NULL;
859 if (te1) {
860 tidx1++;
861 te1 = got_object_tree_get_entry(tree1, tidx1);
862 if (te1 &&
863 asprintf(&l1, "%s%s%s", label1,
864 label1[0] ? "/" : "", te1->name) == -1)
865 return got_error_from_errno("asprintf");
867 free(l2);
868 l2 = NULL;
869 if (te2) {
870 tidx2++;
871 te2 = got_object_tree_get_entry(tree2, tidx2);
872 if (te2 &&
873 asprintf(&l2, "%s%s%s", label2,
874 label2[0] ? "/" : "", te2->name) == -1)
875 return got_error_from_errno("asprintf");
877 } while (te1 || te2);
879 return err;
882 const struct got_error *
883 got_diff_objects_as_blobs(struct got_diff_line **lines, size_t *nlines,
884 FILE *f1, FILE *f2, int fd1, int fd2,
885 struct got_object_id *id1, struct got_object_id *id2,
886 const char *label1, const char *label2,
887 enum got_diff_algorithm diff_algo, int diff_context,
888 int ignore_whitespace, int force_text_diff,
889 struct got_repository *repo, FILE *outfile)
891 const struct got_error *err;
892 struct got_blob_object *blob1 = NULL, *blob2 = NULL;
894 if (id1 == NULL && id2 == NULL)
895 return got_error(GOT_ERR_NO_OBJ);
897 if (id1) {
898 err = got_object_open_as_blob(&blob1, repo, id1, 8192, fd1);
899 if (err)
900 goto done;
902 if (id2) {
903 err = got_object_open_as_blob(&blob2, repo, id2, 8192, fd2);
904 if (err)
905 goto done;
907 err = got_diff_blob(lines, nlines, blob1, blob2, f1, f2, label1, label2,
908 diff_algo, diff_context, ignore_whitespace, force_text_diff,
909 outfile);
910 done:
911 if (blob1)
912 got_object_blob_close(blob1);
913 if (blob2)
914 got_object_blob_close(blob2);
915 return err;
918 static const struct got_error *
919 diff_paths(struct got_tree_object *tree1, struct got_tree_object *tree2,
920 FILE *f1, FILE *f2, int fd1, int fd2, struct got_pathlist_head *paths,
921 struct got_repository *repo, got_diff_blob_cb cb, void *cb_arg)
923 const struct got_error *err = NULL;
924 struct got_pathlist_entry *pe;
925 struct got_object_id *id1 = NULL, *id2 = NULL;
926 struct got_tree_object *subtree1 = NULL, *subtree2 = NULL;
927 struct got_blob_object *blob1 = NULL, *blob2 = NULL;
929 TAILQ_FOREACH(pe, paths, entry) {
930 int type1 = GOT_OBJ_TYPE_ANY, type2 = GOT_OBJ_TYPE_ANY;
931 mode_t mode1 = 0, mode2 = 0;
933 free(id1);
934 id1 = NULL;
935 free(id2);
936 id2 = NULL;
937 if (subtree1) {
938 got_object_tree_close(subtree1);
939 subtree1 = NULL;
941 if (subtree2) {
942 got_object_tree_close(subtree2);
943 subtree2 = NULL;
945 if (blob1) {
946 got_object_blob_close(blob1);
947 blob1 = NULL;
949 if (blob2) {
950 got_object_blob_close(blob2);
951 blob2 = NULL;
954 err = got_object_tree_find_path(&id1, &mode1, repo, tree1,
955 pe->path);
956 if (err && err->code != GOT_ERR_NO_TREE_ENTRY)
957 goto done;
958 err = got_object_tree_find_path(&id2, &mode2, repo, tree2,
959 pe->path);
960 if (err && err->code != GOT_ERR_NO_TREE_ENTRY)
961 goto done;
962 if (id1 == NULL && id2 == NULL) {
963 err = got_error_path(pe->path, GOT_ERR_NO_TREE_ENTRY);
964 goto done;
966 if (id1) {
967 err = got_object_get_type(&type1, repo, id1);
968 if (err)
969 goto done;
971 if (id2) {
972 err = got_object_get_type(&type2, repo, id2);
973 if (err)
974 goto done;
976 if (type1 == GOT_OBJ_TYPE_ANY &&
977 type2 == GOT_OBJ_TYPE_ANY) {
978 err = got_error_path(pe->path, GOT_ERR_NO_OBJ);
979 goto done;
980 } else if (type1 != GOT_OBJ_TYPE_ANY &&
981 type2 != GOT_OBJ_TYPE_ANY && type1 != type2) {
982 err = got_error(GOT_ERR_OBJ_TYPE);
983 goto done;
986 if (type1 == GOT_OBJ_TYPE_BLOB ||
987 type2 == GOT_OBJ_TYPE_BLOB) {
988 if (id1) {
989 err = got_object_open_as_blob(&blob1, repo,
990 id1, 8192, fd1);
991 if (err)
992 goto done;
994 if (id2) {
995 err = got_object_open_as_blob(&blob2, repo,
996 id2, 8192, fd2);
997 if (err)
998 goto done;
1000 err = cb(cb_arg, blob1, blob2, f1, f2, id1, id2,
1001 id1 ? pe->path : "/dev/null",
1002 id2 ? pe->path : "/dev/null",
1003 mode1, mode2, repo);
1004 if (err)
1005 goto done;
1006 } else if (type1 == GOT_OBJ_TYPE_TREE ||
1007 type2 == GOT_OBJ_TYPE_TREE) {
1008 if (id1) {
1009 err = got_object_open_as_tree(&subtree1, repo,
1010 id1);
1011 if (err)
1012 goto done;
1014 if (id2) {
1015 err = got_object_open_as_tree(&subtree2, repo,
1016 id2);
1017 if (err)
1018 goto done;
1020 err = got_diff_tree(subtree1, subtree2, f1, f2,
1021 fd1, fd2,
1022 id1 ? pe->path : "/dev/null",
1023 id2 ? pe->path : "/dev/null",
1024 repo, cb, cb_arg, 1);
1025 if (err)
1026 goto done;
1027 } else {
1028 err = got_error(GOT_ERR_OBJ_TYPE);
1029 goto done;
1032 done:
1033 free(id1);
1034 free(id2);
1035 if (subtree1)
1036 got_object_tree_close(subtree1);
1037 if (subtree2)
1038 got_object_tree_close(subtree2);
1039 if (blob1)
1040 got_object_blob_close(blob1);
1041 if (blob2)
1042 got_object_blob_close(blob2);
1043 return err;
1046 static const struct got_error *
1047 show_object_id(struct got_diff_line **lines, size_t *nlines,
1048 const char *obj_typestr, int ch, const char *id_str, FILE *outfile)
1050 const struct got_error *err;
1051 int n;
1052 off_t outoff = 0;
1054 n = fprintf(outfile, "%s %c %s\n", obj_typestr, ch, id_str);
1055 if (n < 0)
1056 return got_error_from_errno("fprintf");
1058 if (lines != NULL && *lines != NULL) {
1059 if (*nlines == 0) {
1060 err = add_line_metadata(lines, nlines, 0,
1061 GOT_DIFF_LINE_META);
1062 if (err)
1063 return err;
1064 } else
1065 outoff = (*lines)[*nlines - 1].offset;
1067 outoff += n;
1068 err = add_line_metadata(lines, nlines, outoff,
1069 GOT_DIFF_LINE_META);
1070 if (err)
1071 return err;
1074 return NULL;
1077 static const struct got_error *
1078 diff_objects_as_trees(struct got_diff_line **lines, size_t *nlines,
1079 FILE *f1, FILE *f2, int fd1, int fd2,
1080 struct got_object_id *id1, struct got_object_id *id2,
1081 struct got_pathlist_head *paths, const char *label1, const char *label2,
1082 int diff_context, int ignore_whitespace, int force_text_diff,
1083 struct got_repository *repo, FILE *outfile,
1084 enum got_diff_algorithm diff_algo)
1086 const struct got_error *err;
1087 struct got_tree_object *tree1 = NULL, *tree2 = NULL;
1088 struct got_diff_blob_output_unidiff_arg arg;
1089 int want_linemeta = (lines != NULL && *lines != NULL);
1091 if (id1 == NULL && id2 == NULL)
1092 return got_error(GOT_ERR_NO_OBJ);
1094 if (id1) {
1095 err = got_object_open_as_tree(&tree1, repo, id1);
1096 if (err)
1097 goto done;
1099 if (id2) {
1100 err = got_object_open_as_tree(&tree2, repo, id2);
1101 if (err)
1102 goto done;
1105 arg.diff_algo = diff_algo;
1106 arg.diff_context = diff_context;
1107 arg.ignore_whitespace = ignore_whitespace;
1108 arg.force_text_diff = force_text_diff;
1109 arg.outfile = outfile;
1110 if (want_linemeta) {
1111 arg.lines = *lines;
1112 arg.nlines = *nlines;
1113 } else {
1114 arg.lines = NULL;
1115 arg.nlines = 0;
1117 if (paths == NULL || TAILQ_EMPTY(paths)) {
1118 err = got_diff_tree(tree1, tree2, f1, f2, fd1, fd2,
1119 label1, label2, repo,
1120 got_diff_blob_output_unidiff, &arg, 1);
1121 } else {
1122 err = diff_paths(tree1, tree2, f1, f2, fd1, fd2, paths, repo,
1123 got_diff_blob_output_unidiff, &arg);
1125 if (want_linemeta) {
1126 *lines = arg.lines; /* was likely re-allocated */
1127 *nlines = arg.nlines;
1129 done:
1130 if (tree1)
1131 got_object_tree_close(tree1);
1132 if (tree2)
1133 got_object_tree_close(tree2);
1134 return err;
1137 const struct got_error *
1138 got_diff_objects_as_trees(struct got_diff_line **lines, size_t *nlines,
1139 FILE *f1, FILE *f2, int fd1, int fd2,
1140 struct got_object_id *id1, struct got_object_id *id2,
1141 struct got_pathlist_head *paths, const char *label1, const char *label2,
1142 enum got_diff_algorithm diff_algo, int diff_context, int ignore_whitespace,
1143 int force_text_diff, struct got_repository *repo, FILE *outfile)
1145 const struct got_error *err;
1146 char *idstr = NULL;
1148 if (id1 == NULL && id2 == NULL)
1149 return got_error(GOT_ERR_NO_OBJ);
1151 if (id1) {
1152 err = got_object_id_str(&idstr, id1);
1153 if (err)
1154 goto done;
1155 err = show_object_id(lines, nlines, "tree", '-', idstr, outfile);
1156 if (err)
1157 goto done;
1158 free(idstr);
1159 idstr = NULL;
1160 } else {
1161 err = show_object_id(lines, nlines, "tree", '-', "/dev/null",
1162 outfile);
1163 if (err)
1164 goto done;
1167 if (id2) {
1168 err = got_object_id_str(&idstr, id2);
1169 if (err)
1170 goto done;
1171 err = show_object_id(lines, nlines, "tree", '+', idstr, outfile);
1172 if (err)
1173 goto done;
1174 free(idstr);
1175 idstr = NULL;
1176 } else {
1177 err = show_object_id(lines, nlines, "tree", '+', "/dev/null",
1178 outfile);
1179 if (err)
1180 goto done;
1183 err = diff_objects_as_trees(lines, nlines, f1, f2, fd1, fd2, id1, id2,
1184 paths, label1, label2, diff_context, ignore_whitespace,
1185 force_text_diff, repo, outfile, diff_algo);
1186 done:
1187 free(idstr);
1188 return err;
1191 const struct got_error *
1192 got_diff_objects_as_commits(struct got_diff_line **lines, size_t *nlines,
1193 FILE *f1, FILE *f2, int fd1, int fd2,
1194 struct got_object_id *id1, struct got_object_id *id2,
1195 struct got_pathlist_head *paths, enum got_diff_algorithm diff_algo,
1196 int diff_context, int ignore_whitespace, int force_text_diff,
1197 struct got_repository *repo, FILE *outfile)
1199 const struct got_error *err;
1200 struct got_commit_object *commit1 = NULL, *commit2 = NULL;
1201 char *idstr = NULL;
1203 if (id2 == NULL)
1204 return got_error(GOT_ERR_NO_OBJ);
1206 if (id1) {
1207 err = got_object_open_as_commit(&commit1, repo, id1);
1208 if (err)
1209 goto done;
1210 err = got_object_id_str(&idstr, id1);
1211 if (err)
1212 goto done;
1213 err = show_object_id(lines, nlines, "commit", '-', idstr,
1214 outfile);
1215 if (err)
1216 goto done;
1217 free(idstr);
1218 idstr = NULL;
1219 } else {
1220 err = show_object_id(lines, nlines, "commit", '-', "/dev/null",
1221 outfile);
1222 if (err)
1223 goto done;
1226 err = got_object_open_as_commit(&commit2, repo, id2);
1227 if (err)
1228 goto done;
1230 err = got_object_id_str(&idstr, id2);
1231 if (err)
1232 goto done;
1233 err = show_object_id(lines, nlines, "commit", '+', idstr, outfile);
1234 if (err)
1235 goto done;
1237 err = diff_objects_as_trees(lines, nlines, f1, f2, fd1, fd2,
1238 commit1 ? got_object_commit_get_tree_id(commit1) : NULL,
1239 got_object_commit_get_tree_id(commit2), paths, "", "",
1240 diff_context, ignore_whitespace, force_text_diff, repo, outfile,
1241 diff_algo);
1242 done:
1243 if (commit1)
1244 got_object_commit_close(commit1);
1245 if (commit2)
1246 got_object_commit_close(commit2);
1247 free(idstr);
1248 return err;
1251 const struct got_error *
1252 got_diff_files(struct got_diffreg_result **resultp,
1253 FILE *f1, int f1_exists, const char *label1, FILE *f2, int f2_exists,
1254 const char *label2, int diff_context, int ignore_whitespace,
1255 int force_text_diff, FILE *outfile, enum got_diff_algorithm diff_algo)
1257 const struct got_error *err = NULL;
1258 struct got_diffreg_result *diffreg_result = NULL;
1260 if (resultp)
1261 *resultp = NULL;
1263 if (outfile) {
1264 fprintf(outfile, "file - %s\n",
1265 f1_exists ? label1 : "/dev/null");
1266 fprintf(outfile, "file + %s\n",
1267 f2_exists ? label2 : "/dev/null");
1270 err = got_diffreg(&diffreg_result, f1, f2, diff_algo,
1271 ignore_whitespace, force_text_diff);
1272 if (err)
1273 goto done;
1275 if (outfile) {
1276 err = got_diffreg_output(NULL, NULL, diffreg_result,
1277 f1_exists, f2_exists, label1, label2,
1278 GOT_DIFF_OUTPUT_UNIDIFF, diff_context, outfile);
1279 if (err)
1280 goto done;
1283 done:
1284 if (resultp && err == NULL)
1285 *resultp = diffreg_result;
1286 else if (diffreg_result) {
1287 const struct got_error *free_err;
1288 free_err = got_diffreg_result_free(diffreg_result);
1289 if (free_err && err == NULL)
1290 err = free_err;
1293 return err;