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_opentemp.h"
32 #include "got_path.h"
34 #include "got_lib_diff.h"
35 #include "got_lib_delta.h"
36 #include "got_lib_inflate.h"
37 #include "got_lib_object.h"
39 static const struct got_error *
40 diff_blobs(struct got_blob_object *blob1, struct got_blob_object *blob2,
41 const char *label1, const char *label2, int diff_context, FILE *outfile,
42 struct got_diff_changes *changes)
43 {
44 struct got_diff_state ds;
45 struct got_diff_args args;
46 const struct got_error *err = NULL;
47 FILE *f1 = NULL, *f2 = NULL;
48 char hex1[SHA1_DIGEST_STRING_LENGTH];
49 char hex2[SHA1_DIGEST_STRING_LENGTH];
50 char *idstr1 = NULL, *idstr2 = NULL;
51 size_t size1, size2;
52 int res, flags = 0;
54 if (blob1) {
55 f1 = got_opentemp();
56 if (f1 == NULL)
57 return got_error_from_errno("got_opentemp");
58 } else
59 flags |= D_EMPTY1;
61 if (blob2) {
62 f2 = got_opentemp();
63 if (f2 == NULL) {
64 err = got_error_from_errno("got_opentemp");
65 fclose(f1);
66 return err;
67 }
68 } else
69 flags |= D_EMPTY2;
71 size1 = 0;
72 if (blob1) {
73 idstr1 = got_object_blob_id_str(blob1, hex1, sizeof(hex1));
74 err = got_object_blob_dump_to_file(&size1, NULL, NULL, f1,
75 blob1);
76 if (err)
77 goto done;
78 } else
79 idstr1 = "/dev/null";
81 size2 = 0;
82 if (blob2) {
83 idstr2 = got_object_blob_id_str(blob2, hex2, sizeof(hex2));
84 err = got_object_blob_dump_to_file(&size2, NULL, NULL, f2,
85 blob2);
86 if (err)
87 goto done;
88 } else
89 idstr2 = "/dev/null";
91 memset(&ds, 0, sizeof(ds));
92 /* XXX should stat buffers be passed in args instead of ds? */
93 ds.stb1.st_mode = S_IFREG;
94 if (blob1)
95 ds.stb1.st_size = size1;
96 ds.stb1.st_mtime = 0; /* XXX */
98 ds.stb2.st_mode = S_IFREG;
99 if (blob2)
100 ds.stb2.st_size = size2;
101 ds.stb2.st_mtime = 0; /* XXX */
103 memset(&args, 0, sizeof(args));
104 args.diff_format = D_UNIFIED;
105 args.label[0] = label1 ? label1 : idstr1;
106 args.label[1] = label2 ? label2 : idstr2;
107 args.diff_context = diff_context;
108 flags |= D_PROTOTYPE;
110 if (outfile) {
111 fprintf(outfile, "blob - %s\n", idstr1);
112 fprintf(outfile, "blob + %s\n", idstr2);
114 err = got_diffreg(&res, f1, f2, flags, &args, &ds, outfile, changes);
115 done:
116 if (f1 && fclose(f1) != 0 && err == NULL)
117 err = got_error_from_errno("fclose");
118 if (f2 && fclose(f2) != 0 && err == NULL)
119 err = got_error_from_errno("fclose");
120 return err;
123 const struct got_error *
124 got_diff_blob_output_unidiff(void *arg, struct got_blob_object *blob1,
125 struct got_blob_object *blob2, struct got_object_id *id1,
126 struct got_object_id *id2, const char *label1, const char *label2,
127 struct got_repository *repo)
129 struct got_diff_blob_output_unidiff_arg *a = arg;
131 return diff_blobs(blob1, blob2, label1, label2, a->diff_context,
132 a->outfile, NULL);
135 const struct got_error *
136 got_diff_blob(struct got_blob_object *blob1, struct got_blob_object *blob2,
137 const char *label1, const char *label2, int diff_context, FILE *outfile)
139 return diff_blobs(blob1, blob2, label1, label2, diff_context, outfile,
140 NULL);
143 static const struct got_error *
144 alloc_changes(struct got_diff_changes **changes)
146 *changes = calloc(1, sizeof(**changes));
147 if (*changes == NULL)
148 return got_error_from_errno("calloc");
149 SIMPLEQ_INIT(&(*changes)->entries);
150 return NULL;
153 static const struct got_error *
154 diff_blob_file(struct got_diff_changes **changes,
155 struct got_blob_object *blob1, FILE *f2, size_t size2,
156 const char *label2, int diff_context, FILE *outfile)
158 struct got_diff_state ds;
159 struct got_diff_args args;
160 const struct got_error *err = NULL;
161 FILE *f1 = NULL;
162 char hex1[SHA1_DIGEST_STRING_LENGTH];
163 char *idstr1 = NULL;
164 size_t size1;
165 int res, flags = 0;
167 if (changes)
168 *changes = NULL;
170 size1 = 0;
171 if (blob1) {
172 f1 = got_opentemp();
173 if (f1 == NULL)
174 return got_error_from_errno("got_opentemp");
175 idstr1 = got_object_blob_id_str(blob1, hex1, sizeof(hex1));
176 err = got_object_blob_dump_to_file(&size1, NULL, NULL, f1,
177 blob1);
178 if (err)
179 goto done;
180 } else {
181 flags |= D_EMPTY1;
182 idstr1 = "/dev/null";
185 if (f2 == NULL)
186 flags |= D_EMPTY2;
188 memset(&ds, 0, sizeof(ds));
189 /* XXX should stat buffers be passed in args instead of ds? */
190 ds.stb1.st_mode = S_IFREG;
191 if (blob1)
192 ds.stb1.st_size = size1;
193 ds.stb1.st_mtime = 0; /* XXX */
195 ds.stb2.st_mode = S_IFREG;
196 ds.stb2.st_size = size2;
197 ds.stb2.st_mtime = 0; /* XXX */
199 memset(&args, 0, sizeof(args));
200 args.diff_format = D_UNIFIED;
201 args.label[0] = label2;
202 args.label[1] = label2;
203 args.diff_context = diff_context;
204 flags |= D_PROTOTYPE;
206 if (outfile) {
207 fprintf(outfile, "blob - %s\n", idstr1);
208 fprintf(outfile, "file + %s\n",
209 f2 == NULL ? "/dev/null" : label2);
211 if (changes) {
212 err = alloc_changes(changes);
213 if (err)
214 return err;
216 err = got_diffreg(&res, f1, f2, flags, &args, &ds, outfile,
217 changes ? *changes : NULL);
218 done:
219 if (f1 && fclose(f1) != 0 && err == NULL)
220 err = got_error_from_errno("fclose");
221 return err;
224 const struct got_error *
225 got_diff_blob_file(struct got_blob_object *blob1, FILE *f2, size_t size2,
226 const char *label2, int diff_context, FILE *outfile)
228 return diff_blob_file(NULL, blob1, f2, size2, label2, diff_context,
229 outfile);
232 const struct got_error *
233 got_diff_blob_file_lines_changed(struct got_diff_changes **changes,
234 struct got_blob_object *blob1, FILE *f2, size_t size2,
235 const char *label2, int diff_context)
237 return diff_blob_file(changes, blob1, f2, size2, label2, diff_context,
238 NULL);
241 const struct got_error *
242 got_diff_blob_lines_changed(struct got_diff_changes **changes,
243 struct got_blob_object *blob1, struct got_blob_object *blob2)
245 const struct got_error *err = NULL;
247 err = alloc_changes(changes);
248 if (err)
249 return err;
251 err = diff_blobs(blob1, blob2, NULL, NULL, 3, NULL, *changes);
252 if (err) {
253 got_diff_free_changes(*changes);
254 *changes = NULL;
256 return err;
259 void
260 got_diff_free_changes(struct got_diff_changes *changes)
262 struct got_diff_change *change;
263 while (!SIMPLEQ_EMPTY(&changes->entries)) {
264 change = SIMPLEQ_FIRST(&changes->entries);
265 SIMPLEQ_REMOVE_HEAD(&changes->entries, entry);
266 free(change);
268 free(changes);
271 static const struct got_error *
272 diff_added_blob(struct got_object_id *id, const char *label,
273 struct got_repository *repo, got_diff_blob_cb cb, void *cb_arg)
275 const struct got_error *err;
276 struct got_blob_object *blob = NULL;
277 struct got_object *obj = NULL;
279 err = got_object_open(&obj, repo, id);
280 if (err)
281 return err;
283 err = got_object_blob_open(&blob, repo, obj, 8192);
284 if (err)
285 goto done;
286 err = cb(cb_arg, NULL, blob, NULL, id, NULL, label, repo);
287 done:
288 got_object_close(obj);
289 if (blob)
290 got_object_blob_close(blob);
291 return err;
294 static const struct got_error *
295 diff_modified_blob(struct got_object_id *id1, struct got_object_id *id2,
296 const char *label1, const char *label2, struct got_repository *repo,
297 got_diff_blob_cb cb, void *cb_arg)
299 const struct got_error *err;
300 struct got_object *obj1 = NULL;
301 struct got_object *obj2 = NULL;
302 struct got_blob_object *blob1 = NULL;
303 struct got_blob_object *blob2 = NULL;
305 err = got_object_open(&obj1, repo, id1);
306 if (err)
307 return err;
308 if (obj1->type != GOT_OBJ_TYPE_BLOB) {
309 err = got_error(GOT_ERR_OBJ_TYPE);
310 goto done;
313 err = got_object_open(&obj2, repo, id2);
314 if (err)
315 goto done;
316 if (obj2->type != GOT_OBJ_TYPE_BLOB) {
317 err = got_error(GOT_ERR_BAD_OBJ_DATA);
318 goto done;
321 err = got_object_blob_open(&blob1, repo, obj1, 8192);
322 if (err)
323 goto done;
325 err = got_object_blob_open(&blob2, repo, obj2, 8192);
326 if (err)
327 goto done;
329 err = cb(cb_arg, blob1, blob2, id1, id2, label1, label2, repo);
330 done:
331 if (obj1)
332 got_object_close(obj1);
333 if (obj2)
334 got_object_close(obj2);
335 if (blob1)
336 got_object_blob_close(blob1);
337 if (blob2)
338 got_object_blob_close(blob2);
339 return err;
342 static const struct got_error *
343 diff_deleted_blob(struct got_object_id *id, const char *label,
344 struct got_repository *repo, got_diff_blob_cb cb, void *cb_arg)
346 const struct got_error *err;
347 struct got_blob_object *blob = NULL;
348 struct got_object *obj = NULL;
350 err = got_object_open(&obj, repo, id);
351 if (err)
352 return err;
354 err = got_object_blob_open(&blob, repo, obj, 8192);
355 if (err)
356 goto done;
357 err = cb(cb_arg, blob, NULL, id, NULL, label, NULL, repo);
358 done:
359 got_object_close(obj);
360 if (blob)
361 got_object_blob_close(blob);
362 return err;
365 static const struct got_error *
366 diff_added_tree(struct got_object_id *id, const char *label,
367 struct got_repository *repo, got_diff_blob_cb cb, void *cb_arg,
368 int diff_content)
370 const struct got_error *err = NULL;
371 struct got_object *treeobj = NULL;
372 struct got_tree_object *tree = NULL;
374 err = got_object_open(&treeobj, repo, id);
375 if (err)
376 goto done;
378 if (treeobj->type != GOT_OBJ_TYPE_TREE) {
379 err = got_error(GOT_ERR_OBJ_TYPE);
380 goto done;
383 err = got_object_tree_open(&tree, repo, treeobj);
384 if (err)
385 goto done;
387 err = got_diff_tree(NULL, tree, NULL, label, repo, cb, cb_arg,
388 diff_content);
389 done:
390 if (tree)
391 got_object_tree_close(tree);
392 if (treeobj)
393 got_object_close(treeobj);
394 return err;
397 static const struct got_error *
398 diff_modified_tree(struct got_object_id *id1, struct got_object_id *id2,
399 const char *label1, const char *label2, struct got_repository *repo,
400 got_diff_blob_cb cb, void *cb_arg, int diff_content)
402 const struct got_error *err;
403 struct got_object *treeobj1 = NULL;
404 struct got_object *treeobj2 = NULL;
405 struct got_tree_object *tree1 = NULL;
406 struct got_tree_object *tree2 = NULL;
408 err = got_object_open(&treeobj1, repo, id1);
409 if (err)
410 goto done;
412 if (treeobj1->type != GOT_OBJ_TYPE_TREE) {
413 err = got_error(GOT_ERR_OBJ_TYPE);
414 goto done;
417 err = got_object_open(&treeobj2, repo, id2);
418 if (err)
419 goto done;
421 if (treeobj2->type != GOT_OBJ_TYPE_TREE) {
422 err = got_error(GOT_ERR_OBJ_TYPE);
423 goto done;
426 err = got_object_tree_open(&tree1, repo, treeobj1);
427 if (err)
428 goto done;
430 err = got_object_tree_open(&tree2, repo, treeobj2);
431 if (err)
432 goto done;
434 err = got_diff_tree(tree1, tree2, label1, label2, repo, cb, cb_arg,
435 diff_content);
437 done:
438 if (tree1)
439 got_object_tree_close(tree1);
440 if (tree2)
441 got_object_tree_close(tree2);
442 if (treeobj1)
443 got_object_close(treeobj1);
444 if (treeobj2)
445 got_object_close(treeobj2);
446 return err;
449 static const struct got_error *
450 diff_deleted_tree(struct got_object_id *id, const char *label,
451 struct got_repository *repo, got_diff_blob_cb cb, void *cb_arg,
452 int diff_content)
454 const struct got_error *err;
455 struct got_object *treeobj = NULL;
456 struct got_tree_object *tree = NULL;
458 err = got_object_open(&treeobj, repo, id);
459 if (err)
460 goto done;
462 if (treeobj->type != GOT_OBJ_TYPE_TREE) {
463 err = got_error(GOT_ERR_OBJ_TYPE);
464 goto done;
467 err = got_object_tree_open(&tree, repo, treeobj);
468 if (err)
469 goto done;
471 err = got_diff_tree(tree, NULL, label, NULL, repo, cb, cb_arg,
472 diff_content);
473 done:
474 if (tree)
475 got_object_tree_close(tree);
476 if (treeobj)
477 got_object_close(treeobj);
478 return err;
481 static const struct got_error *
482 diff_kind_mismatch(struct got_object_id *id1, struct got_object_id *id2,
483 const char *label1, const char *label2, struct got_repository *repo,
484 got_diff_blob_cb cb, void *cb_arg)
486 /* XXX TODO */
487 return NULL;
490 static const struct got_error *
491 diff_entry_old_new(const struct got_tree_entry *te1,
492 const struct got_tree_entry *te2, const char *label1, const char *label2,
493 struct got_repository *repo, got_diff_blob_cb cb, void *cb_arg,
494 int diff_content)
496 const struct got_error *err = NULL;
497 int id_match;
499 if (te2 == NULL) {
500 if (S_ISDIR(te1->mode))
501 err = diff_deleted_tree(te1->id, label1, repo,
502 cb, cb_arg, diff_content);
503 else {
504 if (diff_content)
505 err = diff_deleted_blob(te1->id, label1, repo,
506 cb, cb_arg);
507 else
508 err = cb(cb_arg, NULL, NULL, te1->id, NULL,
509 label1, NULL, repo);
511 return err;
514 id_match = (got_object_id_cmp(te1->id, te2->id) == 0);
515 if (S_ISDIR(te1->mode) && S_ISDIR(te2->mode)) {
516 if (!id_match)
517 return diff_modified_tree(te1->id, te2->id,
518 label1, label2, repo, cb, cb_arg, diff_content);
519 } else if (S_ISREG(te1->mode) && S_ISREG(te2->mode)) {
520 if (!id_match) {
521 if (diff_content)
522 return diff_modified_blob(te1->id, te2->id,
523 label1, label2, repo, cb, cb_arg);
524 else
525 return cb(cb_arg, NULL, NULL, te1->id,
526 te2->id, label1, label2, repo);
530 if (id_match)
531 return NULL;
533 return diff_kind_mismatch(te1->id, te2->id, label1, label2, repo,
534 cb, cb_arg);
537 static const struct got_error *
538 diff_entry_new_old(const struct got_tree_entry *te2,
539 const struct got_tree_entry *te1, const char *label2,
540 struct got_repository *repo, got_diff_blob_cb cb, void *cb_arg,
541 int diff_content)
543 if (te1 != NULL) /* handled by diff_entry_old_new() */
544 return NULL;
546 if (S_ISDIR(te2->mode))
547 return diff_added_tree(te2->id, label2, repo, cb, cb_arg,
548 diff_content);
550 if (diff_content)
551 return diff_added_blob(te2->id, label2, repo, cb, cb_arg);
553 return cb(cb_arg, NULL, NULL, NULL, te2->id, NULL, label2, repo);
556 const struct got_error *
557 got_diff_tree(struct got_tree_object *tree1, struct got_tree_object *tree2,
558 const char *label1, const char *label2, struct got_repository *repo,
559 got_diff_blob_cb cb, void *cb_arg, int diff_content)
561 const struct got_error *err = NULL;
562 struct got_tree_entry *te1 = NULL;
563 struct got_tree_entry *te2 = NULL;
564 char *l1 = NULL, *l2 = NULL;
566 if (tree1) {
567 const struct got_tree_entries *entries;
568 entries = got_object_tree_get_entries(tree1);
569 te1 = SIMPLEQ_FIRST(&entries->head);
570 if (te1 && asprintf(&l1, "%s%s%s", label1, label1[0] ? "/" : "",
571 te1->name) == -1)
572 return got_error_from_errno("asprintf");
574 if (tree2) {
575 const struct got_tree_entries *entries;
576 entries = got_object_tree_get_entries(tree2);
577 te2 = SIMPLEQ_FIRST(&entries->head);
578 if (te2 && asprintf(&l2, "%s%s%s", label2, label2[0] ? "/" : "",
579 te2->name) == -1)
580 return got_error_from_errno("asprintf");
583 do {
584 if (te1) {
585 const struct got_tree_entry *te = NULL;
586 if (tree2)
587 te = got_object_tree_find_entry(tree2,
588 te1->name);
589 if (te) {
590 free(l2);
591 l2 = NULL;
592 if (te && asprintf(&l2, "%s%s%s", label2,
593 label2[0] ? "/" : "", te->name) == -1)
594 return
595 got_error_from_errno("asprintf");
597 err = diff_entry_old_new(te1, te, l1, l2, repo, cb,
598 cb_arg, diff_content);
599 if (err)
600 break;
603 if (te2) {
604 const struct got_tree_entry *te = NULL;
605 if (tree1)
606 te = got_object_tree_find_entry(tree1,
607 te2->name);
608 free(l2);
609 if (te) {
610 if (asprintf(&l2, "%s%s%s", label2,
611 label2[0] ? "/" : "", te->name) == -1)
612 return
613 got_error_from_errno("asprintf");
614 } else {
615 if (asprintf(&l2, "%s%s%s", label2,
616 label2[0] ? "/" : "", te2->name) == -1)
617 return
618 got_error_from_errno("asprintf");
620 err = diff_entry_new_old(te2, te, l2, repo,
621 cb, cb_arg, diff_content);
622 if (err)
623 break;
626 free(l1);
627 l1 = NULL;
628 if (te1) {
629 te1 = SIMPLEQ_NEXT(te1, entry);
630 if (te1 &&
631 asprintf(&l1, "%s%s%s", label1,
632 label1[0] ? "/" : "", te1->name) == -1)
633 return got_error_from_errno("asprintf");
635 free(l2);
636 l2 = NULL;
637 if (te2) {
638 te2 = SIMPLEQ_NEXT(te2, entry);
639 if (te2 &&
640 asprintf(&l2, "%s%s%s", label2,
641 label2[0] ? "/" : "", te2->name) == -1)
642 return got_error_from_errno("asprintf");
644 } while (te1 || te2);
646 return err;
649 const struct got_error *
650 got_diff_objects_as_blobs(struct got_object_id *id1, struct got_object_id *id2,
651 const char *label1, const char *label2, int diff_context,
652 struct got_repository *repo, FILE *outfile)
654 const struct got_error *err;
655 struct got_blob_object *blob1 = NULL, *blob2 = NULL;
657 if (id1 == NULL && id2 == NULL)
658 return got_error(GOT_ERR_NO_OBJ);
660 if (id1) {
661 err = got_object_open_as_blob(&blob1, repo, id1, 8192);
662 if (err)
663 goto done;
665 if (id2) {
666 err = got_object_open_as_blob(&blob2, repo, id2, 8192);
667 if (err)
668 goto done;
670 err = got_diff_blob(blob1, blob2, label1, label2, diff_context,
671 outfile);
672 done:
673 if (blob1)
674 got_object_blob_close(blob1);
675 if (blob2)
676 got_object_blob_close(blob2);
677 return err;
680 const struct got_error *
681 got_diff_objects_as_trees(struct got_object_id *id1, struct got_object_id *id2,
682 char *label1, char *label2, int diff_context, struct got_repository *repo,
683 FILE *outfile)
685 const struct got_error *err;
686 struct got_tree_object *tree1 = NULL, *tree2 = NULL;
687 struct got_diff_blob_output_unidiff_arg arg;
689 if (id1 == NULL && id2 == NULL)
690 return got_error(GOT_ERR_NO_OBJ);
692 if (id1) {
693 err = got_object_open_as_tree(&tree1, repo, id1);
694 if (err)
695 goto done;
697 if (id2) {
698 err = got_object_open_as_tree(&tree2, repo, id2);
699 if (err)
700 goto done;
702 arg.diff_context = diff_context;
703 arg.outfile = outfile;
704 err = got_diff_tree(tree1, tree2, label1, label2, repo,
705 got_diff_blob_output_unidiff, &arg, 1);
706 done:
707 if (tree1)
708 got_object_tree_close(tree1);
709 if (tree2)
710 got_object_tree_close(tree2);
711 return err;
714 const struct got_error *
715 got_diff_objects_as_commits(struct got_object_id *id1,
716 struct got_object_id *id2, int diff_context,
717 struct got_repository *repo, FILE *outfile)
719 const struct got_error *err;
720 struct got_commit_object *commit1 = NULL, *commit2 = NULL;
722 if (id2 == NULL)
723 return got_error(GOT_ERR_NO_OBJ);
725 if (id1) {
726 err = got_object_open_as_commit(&commit1, repo, id1);
727 if (err)
728 goto done;
731 err = got_object_open_as_commit(&commit2, repo, id2);
732 if (err)
733 goto done;
735 err = got_diff_objects_as_trees(
736 commit1 ? got_object_commit_get_tree_id(commit1) : NULL,
737 got_object_commit_get_tree_id(commit2), "", "", diff_context, repo,
738 outfile);
739 done:
740 if (commit1)
741 got_object_commit_close(commit1);
742 if (commit2)
743 got_object_commit_close(commit2);
744 return err;