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_repository.h"
28 #include "got_object.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_prefix_errno("got_opentemp");
58 } else
59 flags |= D_EMPTY1;
61 if (blob2) {
62 f2 = got_opentemp();
63 if (f2 == NULL) {
64 err = got_error_prefix_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, f1, blob1);
75 if (err)
76 goto done;
77 } else
78 idstr1 = "/dev/null";
80 size2 = 0;
81 if (blob2) {
82 idstr2 = got_object_blob_id_str(blob2, hex2, sizeof(hex2));
83 err = got_object_blob_dump_to_file(&size2, NULL, f2, blob2);
84 if (err)
85 goto done;
86 } else
87 idstr2 = "/dev/null";
89 memset(&ds, 0, sizeof(ds));
90 /* XXX should stat buffers be passed in args instead of ds? */
91 ds.stb1.st_mode = S_IFREG;
92 if (blob1)
93 ds.stb1.st_size = size1;
94 ds.stb1.st_mtime = 0; /* XXX */
96 ds.stb2.st_mode = S_IFREG;
97 if (blob2)
98 ds.stb2.st_size = size2;
99 ds.stb2.st_mtime = 0; /* XXX */
101 memset(&args, 0, sizeof(args));
102 args.diff_format = D_UNIFIED;
103 args.label[0] = label1 ? label1 : idstr1;
104 args.label[1] = label2 ? label2 : idstr2;
105 args.diff_context = diff_context;
106 flags |= D_PROTOTYPE;
108 if (outfile) {
109 fprintf(outfile, "blob - %s\n", idstr1);
110 fprintf(outfile, "blob + %s\n", idstr2);
112 err = got_diffreg(&res, f1, f2, flags, &args, &ds, outfile, changes);
113 done:
114 if (f1 && fclose(f1) != 0 && err == NULL)
115 err = got_error_prefix_errno("fclose");
116 if (f2 && fclose(f2) != 0 && err == NULL)
117 err = got_error_prefix_errno("fclose");
118 return err;
121 const struct got_error *
122 got_diff_blob(struct got_blob_object *blob1, struct got_blob_object *blob2,
123 const char *label1, const char *label2, int diff_context, FILE *outfile)
125 return diff_blobs(blob1, blob2, label1, label2, diff_context, outfile,
126 NULL);
129 const struct got_error *
130 got_diff_blob_file(struct got_blob_object *blob1, FILE *f2, size_t size2,
131 const char *label2, int diff_context, FILE *outfile)
133 struct got_diff_state ds;
134 struct got_diff_args args;
135 const struct got_error *err = NULL;
136 FILE *f1 = NULL;
137 char hex1[SHA1_DIGEST_STRING_LENGTH];
138 char *idstr1 = NULL;
139 size_t size1;
140 int res, flags = 0;
142 size1 = 0;
143 if (blob1) {
144 f1 = got_opentemp();
145 if (f1 == NULL)
146 return got_error_prefix_errno("got_opentemp");
147 idstr1 = got_object_blob_id_str(blob1, hex1, sizeof(hex1));
148 err = got_object_blob_dump_to_file(&size1, NULL, f1, blob1);
149 if (err)
150 goto done;
151 } else {
152 flags |= D_EMPTY1;
153 idstr1 = "/dev/null";
156 if (f2 == NULL)
157 flags |= D_EMPTY2;
159 memset(&ds, 0, sizeof(ds));
160 /* XXX should stat buffers be passed in args instead of ds? */
161 ds.stb1.st_mode = S_IFREG;
162 if (blob1)
163 ds.stb1.st_size = size1;
164 ds.stb1.st_mtime = 0; /* XXX */
166 ds.stb2.st_mode = S_IFREG;
167 ds.stb2.st_size = size2;
168 ds.stb2.st_mtime = 0; /* XXX */
170 memset(&args, 0, sizeof(args));
171 args.diff_format = D_UNIFIED;
172 args.label[0] = label2;
173 args.label[1] = label2;
174 args.diff_context = diff_context;
175 flags |= D_PROTOTYPE;
177 fprintf(outfile, "blob - %s\n", idstr1);
178 fprintf(outfile, "file + %s\n", f2 == NULL ? "/dev/null" : label2);
179 err = got_diffreg(&res, f1, f2, flags, &args, &ds, outfile, NULL);
180 done:
181 if (f1 && fclose(f1) != 0 && err == NULL)
182 err = got_error_prefix_errno("fclose");
183 return err;
186 const struct got_error *
187 got_diff_blob_lines_changed(struct got_diff_changes **changes,
188 struct got_blob_object *blob1, struct got_blob_object *blob2)
190 const struct got_error *err = NULL;
192 *changes = calloc(1, sizeof(**changes));
193 if (*changes == NULL)
194 return got_error_prefix_errno("calloc");
195 SIMPLEQ_INIT(&(*changes)->entries);
197 err = diff_blobs(blob1, blob2, NULL, NULL, 3, NULL, *changes);
198 if (err) {
199 got_diff_free_changes(*changes);
200 *changes = NULL;
202 return err;
205 void
206 got_diff_free_changes(struct got_diff_changes *changes)
208 struct got_diff_change *change;
209 while (!SIMPLEQ_EMPTY(&changes->entries)) {
210 change = SIMPLEQ_FIRST(&changes->entries);
211 SIMPLEQ_REMOVE_HEAD(&changes->entries, entry);
212 free(change);
214 free(changes);
217 static struct got_tree_entry *
218 match_entry_by_name(struct got_tree_entry *te1, struct got_tree_object *tree2)
220 struct got_tree_entry *te2;
221 const struct got_tree_entries *entries2;
223 entries2 = got_object_tree_get_entries(tree2);
224 SIMPLEQ_FOREACH(te2, &entries2->head, entry) {
225 if (strcmp(te1->name, te2->name) == 0)
226 return te2;
228 return NULL;
231 static const struct got_error *
232 diff_added_blob(struct got_object_id *id, const char *label,
233 int diff_context, struct got_repository *repo, FILE *outfile)
235 const struct got_error *err;
236 struct got_blob_object *blob = NULL;
237 struct got_object *obj = NULL;
239 err = got_object_open(&obj, repo, id);
240 if (err)
241 return err;
243 err = got_object_blob_open(&blob, repo, obj, 8192);
244 if (err)
245 goto done;
246 err = got_diff_blob(NULL, blob, NULL, label, diff_context, outfile);
247 done:
248 got_object_close(obj);
249 if (blob)
250 got_object_blob_close(blob);
251 return err;
254 static const struct got_error *
255 diff_modified_blob(struct got_object_id *id1, struct got_object_id *id2,
256 const char *label1, const char *label2, int diff_context,
257 struct got_repository *repo, FILE *outfile)
259 const struct got_error *err;
260 struct got_object *obj1 = NULL;
261 struct got_object *obj2 = NULL;
262 struct got_blob_object *blob1 = NULL;
263 struct got_blob_object *blob2 = NULL;
265 err = got_object_open(&obj1, repo, id1);
266 if (err)
267 return err;
268 if (obj1->type != GOT_OBJ_TYPE_BLOB) {
269 err = got_error(GOT_ERR_OBJ_TYPE);
270 goto done;
273 err = got_object_open(&obj2, repo, id2);
274 if (err)
275 goto done;
276 if (obj2->type != GOT_OBJ_TYPE_BLOB) {
277 err = got_error(GOT_ERR_BAD_OBJ_DATA);
278 goto done;
281 err = got_object_blob_open(&blob1, repo, obj1, 8192);
282 if (err)
283 goto done;
285 err = got_object_blob_open(&blob2, repo, obj2, 8192);
286 if (err)
287 goto done;
289 err = got_diff_blob(blob1, blob2, label1, label2, diff_context,
290 outfile);
292 done:
293 if (obj1)
294 got_object_close(obj1);
295 if (obj2)
296 got_object_close(obj2);
297 if (blob1)
298 got_object_blob_close(blob1);
299 if (blob2)
300 got_object_blob_close(blob2);
301 return err;
304 static const struct got_error *
305 diff_deleted_blob(struct got_object_id *id, const char *label,
306 int diff_context, struct got_repository *repo, FILE *outfile)
308 const struct got_error *err;
309 struct got_blob_object *blob = NULL;
310 struct got_object *obj = NULL;
312 err = got_object_open(&obj, repo, id);
313 if (err)
314 return err;
316 err = got_object_blob_open(&blob, repo, obj, 8192);
317 if (err)
318 goto done;
319 err = got_diff_blob(blob, NULL, label, NULL, diff_context, outfile);
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_added_tree(struct got_object_id *id, const char *label,
329 int diff_context, struct got_repository *repo, FILE *outfile)
331 const struct got_error *err = NULL;
332 struct got_object *treeobj = NULL;
333 struct got_tree_object *tree = NULL;
335 err = got_object_open(&treeobj, repo, id);
336 if (err)
337 goto done;
339 if (treeobj->type != GOT_OBJ_TYPE_TREE) {
340 err = got_error(GOT_ERR_OBJ_TYPE);
341 goto done;
344 err = got_object_tree_open(&tree, repo, treeobj);
345 if (err)
346 goto done;
348 err = got_diff_tree(NULL, tree, NULL, label, diff_context, repo,
349 outfile);
351 done:
352 if (tree)
353 got_object_tree_close(tree);
354 if (treeobj)
355 got_object_close(treeobj);
356 return err;
359 static const struct got_error *
360 diff_modified_tree(struct got_object_id *id1, struct got_object_id *id2,
361 const char *label1, const char *label2, int diff_context,
362 struct got_repository *repo, FILE *outfile)
364 const struct got_error *err;
365 struct got_object *treeobj1 = NULL;
366 struct got_object *treeobj2 = NULL;
367 struct got_tree_object *tree1 = NULL;
368 struct got_tree_object *tree2 = NULL;
370 err = got_object_open(&treeobj1, repo, id1);
371 if (err)
372 goto done;
374 if (treeobj1->type != GOT_OBJ_TYPE_TREE) {
375 err = got_error(GOT_ERR_OBJ_TYPE);
376 goto done;
379 err = got_object_open(&treeobj2, repo, id2);
380 if (err)
381 goto done;
383 if (treeobj2->type != GOT_OBJ_TYPE_TREE) {
384 err = got_error(GOT_ERR_OBJ_TYPE);
385 goto done;
388 err = got_object_tree_open(&tree1, repo, treeobj1);
389 if (err)
390 goto done;
392 err = got_object_tree_open(&tree2, repo, treeobj2);
393 if (err)
394 goto done;
396 err = got_diff_tree(tree1, tree2, label1, label2, diff_context, repo,
397 outfile);
399 done:
400 if (tree1)
401 got_object_tree_close(tree1);
402 if (tree2)
403 got_object_tree_close(tree2);
404 if (treeobj1)
405 got_object_close(treeobj1);
406 if (treeobj2)
407 got_object_close(treeobj2);
408 return err;
411 static const struct got_error *
412 diff_deleted_tree(struct got_object_id *id, const char *label,
413 int diff_context, struct got_repository *repo, FILE *outfile)
415 const struct got_error *err;
416 struct got_object *treeobj = NULL;
417 struct got_tree_object *tree = NULL;
419 err = got_object_open(&treeobj, repo, id);
420 if (err)
421 goto done;
423 if (treeobj->type != GOT_OBJ_TYPE_TREE) {
424 err = got_error(GOT_ERR_OBJ_TYPE);
425 goto done;
428 err = got_object_tree_open(&tree, repo, treeobj);
429 if (err)
430 goto done;
432 err = got_diff_tree(tree, NULL, label, NULL, diff_context, repo,
433 outfile);
434 done:
435 if (tree)
436 got_object_tree_close(tree);
437 if (treeobj)
438 got_object_close(treeobj);
439 return err;
442 static const struct got_error *
443 diff_kind_mismatch(struct got_object_id *id1, struct got_object_id *id2,
444 const char *label1, const char *label2, FILE *outfile)
446 /* XXX TODO */
447 return NULL;
450 static const struct got_error *
451 diff_entry_old_new(struct got_tree_entry *te1, struct got_tree_entry *te2,
452 const char *label1, const char *label2, int diff_context,
453 struct got_repository *repo, FILE *outfile)
455 const struct got_error *err = NULL;
456 int id_match;
458 if (te2 == NULL) {
459 if (S_ISDIR(te1->mode))
460 err = diff_deleted_tree(te1->id, label1, diff_context,
461 repo, outfile);
462 else
463 err = diff_deleted_blob(te1->id, label1, diff_context,
464 repo, outfile);
465 return err;
468 id_match = (got_object_id_cmp(te1->id, te2->id) == 0);
469 if (S_ISDIR(te1->mode) && S_ISDIR(te2->mode)) {
470 if (!id_match)
471 return diff_modified_tree(te1->id, te2->id,
472 label1, label2, diff_context, repo, outfile);
473 } else if (S_ISREG(te1->mode) && S_ISREG(te2->mode)) {
474 if (!id_match)
475 return diff_modified_blob(te1->id, te2->id,
476 label1, label2, diff_context, repo, outfile);
479 if (id_match)
480 return NULL;
482 return diff_kind_mismatch(te1->id, te2->id, label1, label2, outfile);
485 static const struct got_error *
486 diff_entry_new_old(struct got_tree_entry *te2, struct got_tree_entry *te1,
487 const char *label2, int diff_context, struct got_repository *repo,
488 FILE *outfile)
490 if (te1 != NULL) /* handled by diff_entry_old_new() */
491 return NULL;
493 if (S_ISDIR(te2->mode))
494 return diff_added_tree(te2->id, label2, diff_context, repo,
495 outfile);
497 return diff_added_blob(te2->id, label2, diff_context, repo, outfile);
500 const struct got_error *
501 got_diff_tree(struct got_tree_object *tree1, struct got_tree_object *tree2,
502 const char *label1, const char *label2, int diff_context,
503 struct got_repository *repo, FILE *outfile)
505 const struct got_error *err = NULL;
506 struct got_tree_entry *te1 = NULL;
507 struct got_tree_entry *te2 = NULL;
508 char *l1 = NULL, *l2 = NULL;
510 if (tree1) {
511 const struct got_tree_entries *entries;
512 entries = got_object_tree_get_entries(tree1);
513 te1 = SIMPLEQ_FIRST(&entries->head);
514 if (te1 && asprintf(&l1, "%s%s%s", label1, label1[0] ? "/" : "",
515 te1->name) == -1)
516 return got_error_prefix_errno("asprintf");
518 if (tree2) {
519 const struct got_tree_entries *entries;
520 entries = got_object_tree_get_entries(tree2);
521 te2 = SIMPLEQ_FIRST(&entries->head);
522 if (te2 && asprintf(&l2, "%s%s%s", label2, label2[0] ? "/" : "",
523 te2->name) == -1)
524 return got_error_prefix_errno("asprintf");
527 do {
528 if (te1) {
529 struct got_tree_entry *te = NULL;
530 if (tree2)
531 te = match_entry_by_name(te1, tree2);
532 if (te) {
533 free(l2);
534 l2 = NULL;
535 if (te && asprintf(&l2, "%s%s%s", label2,
536 label2[0] ? "/" : "", te->name) == -1)
537 return
538 got_error_prefix_errno("asprintf");
540 err = diff_entry_old_new(te1, te, l1, l2, diff_context,
541 repo, outfile);
542 if (err)
543 break;
546 if (te2) {
547 struct got_tree_entry *te = NULL;
548 if (tree1)
549 te = match_entry_by_name(te2, tree1);
550 free(l2);
551 if (te) {
552 if (asprintf(&l2, "%s%s%s", label2,
553 label2[0] ? "/" : "", te->name) == -1)
554 return
555 got_error_prefix_errno("asprintf");
556 } else {
557 if (asprintf(&l2, "%s%s%s", label2,
558 label2[0] ? "/" : "", te2->name) == -1)
559 return
560 got_error_prefix_errno("asprintf");
562 err = diff_entry_new_old(te2, te, l2, diff_context,
563 repo, outfile);
564 if (err)
565 break;
568 free(l1);
569 l1 = NULL;
570 if (te1) {
571 te1 = SIMPLEQ_NEXT(te1, entry);
572 if (te1 &&
573 asprintf(&l1, "%s%s%s", label1,
574 label1[0] ? "/" : "", te1->name) == -1)
575 return got_error_prefix_errno("asprintf");
577 free(l2);
578 l2 = NULL;
579 if (te2) {
580 te2 = SIMPLEQ_NEXT(te2, entry);
581 if (te2 &&
582 asprintf(&l2, "%s%s%s", label2,
583 label2[0] ? "/" : "", te2->name) == -1)
584 return got_error_prefix_errno("asprintf");
586 } while (te1 || te2);
588 return err;
591 const struct got_error *
592 got_diff_objects_as_blobs(struct got_object_id *id1, struct got_object_id *id2,
593 const char *label1, const char *label2, int diff_context,
594 struct got_repository *repo, FILE *outfile)
596 const struct got_error *err;
597 struct got_blob_object *blob1 = NULL, *blob2 = NULL;
599 if (id1 == NULL && id2 == NULL)
600 return got_error(GOT_ERR_NO_OBJ);
602 if (id1) {
603 err = got_object_open_as_blob(&blob1, repo, id1, 8192);
604 if (err)
605 goto done;
607 if (id2) {
608 err = got_object_open_as_blob(&blob2, repo, id2, 8192);
609 if (err)
610 goto done;
612 err = got_diff_blob(blob1, blob2, label1, label2, diff_context,
613 outfile);
614 done:
615 if (blob1)
616 got_object_blob_close(blob1);
617 if (blob2)
618 got_object_blob_close(blob2);
619 return err;
622 const struct got_error *
623 got_diff_objects_as_trees(struct got_object_id *id1, struct got_object_id *id2,
624 char *label1, char *label2, int diff_context, struct got_repository *repo,
625 FILE *outfile)
627 const struct got_error *err;
628 struct got_tree_object *tree1 = NULL, *tree2 = NULL;
630 if (id1 == NULL && id2 == NULL)
631 return got_error(GOT_ERR_NO_OBJ);
633 if (id1) {
634 err = got_object_open_as_tree(&tree1, repo, id1);
635 if (err)
636 goto done;
638 if (id2) {
639 err = got_object_open_as_tree(&tree2, repo, id2);
640 if (err)
641 goto done;
643 err = got_diff_tree(tree1, tree2, label1, label2, diff_context,
644 repo, outfile);
645 done:
646 if (tree1)
647 got_object_tree_close(tree1);
648 if (tree2)
649 got_object_tree_close(tree2);
650 return err;
653 const struct got_error *
654 got_diff_objects_as_commits(struct got_object_id *id1,
655 struct got_object_id *id2, int diff_context,
656 struct got_repository *repo, FILE *outfile)
658 const struct got_error *err;
659 struct got_commit_object *commit1 = NULL, *commit2 = NULL;
661 if (id2 == NULL)
662 return got_error(GOT_ERR_NO_OBJ);
664 if (id1) {
665 err = got_object_open_as_commit(&commit1, repo, id1);
666 if (err)
667 goto done;
670 err = got_object_open_as_commit(&commit2, repo, id2);
671 if (err)
672 goto done;
674 err = got_diff_objects_as_trees(
675 commit1 ? got_object_commit_get_tree_id(commit1) : NULL,
676 got_object_commit_get_tree_id(commit2), "", "", diff_context, repo,
677 outfile);
678 done:
679 if (commit1)
680 got_object_commit_close(commit1);
681 if (commit2)
682 got_object_commit_close(commit2);
683 return err;