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"
33 #include "got_lib_diff.h"
34 #include "got_lib_path.h"
36 static const struct got_error *
37 diff_blobs(struct got_blob_object *blob1, struct got_blob_object *blob2,
38 const char *label1, const char *label2, int diff_context, FILE *outfile,
39 struct got_diff_changes *changes)
40 {
41 struct got_diff_state ds;
42 struct got_diff_args args;
43 const struct got_error *err = NULL;
44 FILE *f1 = NULL, *f2 = NULL;
45 char hex1[SHA1_DIGEST_STRING_LENGTH];
46 char hex2[SHA1_DIGEST_STRING_LENGTH];
47 char *idstr1 = NULL, *idstr2 = NULL;
48 size_t size1, size2;
49 int res, flags = 0;
51 if (blob1) {
52 f1 = got_opentemp();
53 if (f1 == NULL)
54 return got_error(GOT_ERR_FILE_OPEN);
55 } else
56 flags |= D_EMPTY1;
58 if (blob2) {
59 f2 = got_opentemp();
60 if (f2 == NULL) {
61 fclose(f1);
62 return got_error(GOT_ERR_FILE_OPEN);
63 }
64 } else
65 flags |= D_EMPTY2;
67 size1 = 0;
68 if (blob1) {
69 idstr1 = got_object_blob_id_str(blob1, hex1, sizeof(hex1));
70 err = got_object_blob_dump_to_file(&size1, NULL, f1, blob1);
71 if (err)
72 goto done;
73 } else
74 idstr1 = "/dev/null";
76 size2 = 0;
77 if (blob2) {
78 idstr2 = got_object_blob_id_str(blob2, hex2, sizeof(hex2));
79 err = got_object_blob_dump_to_file(&size2, NULL, f2, blob2);
80 if (err)
81 goto done;
82 } else
83 idstr2 = "/dev/null";
85 memset(&ds, 0, sizeof(ds));
86 /* XXX should stat buffers be passed in args instead of ds? */
87 ds.stb1.st_mode = S_IFREG;
88 if (blob1)
89 ds.stb1.st_size = size1;
90 ds.stb1.st_mtime = 0; /* XXX */
92 ds.stb2.st_mode = S_IFREG;
93 if (blob2)
94 ds.stb2.st_size = size2;
95 ds.stb2.st_mtime = 0; /* XXX */
97 memset(&args, 0, sizeof(args));
98 args.diff_format = D_UNIFIED;
99 args.label[0] = label1 ? label1 : idstr1;
100 args.label[1] = label2 ? label2 : idstr2;
101 args.diff_context = diff_context;
102 flags |= D_PROTOTYPE;
104 if (label1 && strcmp(label1, idstr1) != 0)
105 fprintf(outfile, "blob - %s\n", idstr1);
106 if (label2 && strcmp(label2, idstr2) != 0)
107 fprintf(outfile, "blob + %s\n", idstr2);
109 err = got_diffreg(&res, f1, f2, flags, &args, &ds, outfile, changes);
110 done:
111 if (f1)
112 fclose(f1);
113 if (f2)
114 fclose(f2);
115 return err;
118 const struct got_error *
119 got_diff_blob(struct got_blob_object *blob1, struct got_blob_object *blob2,
120 const char *label1, const char *label2, int diff_context, FILE *outfile)
122 return diff_blobs(blob1, blob2, label1, label2, diff_context, outfile,
123 NULL);
126 const struct got_error *
127 got_diff_blob_lines_changed(struct got_diff_changes **changes,
128 struct got_blob_object *blob1, struct got_blob_object *blob2)
130 const struct got_error *err = NULL;
132 *changes = calloc(1, sizeof(**changes));
133 if (*changes == NULL)
134 return got_error_from_errno();
135 SIMPLEQ_INIT(&(*changes)->entries);
137 err = diff_blobs(blob1, blob2, NULL, NULL, 3, NULL, *changes);
138 if (err) {
139 got_diff_free_changes(*changes);
140 *changes = NULL;
142 return err;
145 void
146 got_diff_free_changes(struct got_diff_changes *changes)
148 struct got_diff_change *change;
149 while (!SIMPLEQ_EMPTY(&changes->entries)) {
150 change = SIMPLEQ_FIRST(&changes->entries);
151 SIMPLEQ_REMOVE_HEAD(&changes->entries, entry);
152 free(change);
154 free(changes);
157 struct got_tree_entry *
158 match_entry_by_name(struct got_tree_entry *te1, struct got_tree_object *tree2)
160 struct got_tree_entry *te2;
161 const struct got_tree_entries *entries2;
163 entries2 = got_object_tree_get_entries(tree2);
164 SIMPLEQ_FOREACH(te2, &entries2->head, entry) {
165 if (strcmp(te1->name, te2->name) == 0)
166 return te2;
168 return NULL;
171 static const struct got_error *
172 diff_added_blob(struct got_object_id *id, const char *label,
173 int diff_context, struct got_repository *repo, FILE *outfile)
175 const struct got_error *err;
176 struct got_blob_object *blob = NULL;
177 struct got_object *obj = NULL;
179 err = got_object_open(&obj, repo, id);
180 if (err)
181 return err;
183 err = got_object_blob_open(&blob, repo, obj, 8192);
184 if (err)
185 goto done;
186 err = got_diff_blob(NULL, blob, NULL, label, diff_context, outfile);
187 done:
188 got_object_close(obj);
189 if (blob)
190 got_object_blob_close(blob);
191 return err;
194 static const struct got_error *
195 diff_modified_blob(struct got_object_id *id1, struct got_object_id *id2,
196 const char *label1, const char *label2, int diff_context,
197 struct got_repository *repo, FILE *outfile)
199 const struct got_error *err;
200 struct got_object *obj1 = NULL;
201 struct got_object *obj2 = NULL;
202 struct got_blob_object *blob1 = NULL;
203 struct got_blob_object *blob2 = NULL;
205 err = got_object_open(&obj1, repo, id1);
206 if (err)
207 return err;
208 if (got_object_get_type(obj1) != GOT_OBJ_TYPE_BLOB) {
209 err = got_error(GOT_ERR_OBJ_TYPE);
210 goto done;
213 err = got_object_open(&obj2, repo, id2);
214 if (err)
215 goto done;
216 if (got_object_get_type(obj2) != GOT_OBJ_TYPE_BLOB) {
217 err = got_error(GOT_ERR_BAD_OBJ_DATA);
218 goto done;
221 err = got_object_blob_open(&blob1, repo, obj1, 8192);
222 if (err)
223 goto done;
225 err = got_object_blob_open(&blob2, repo, obj2, 8192);
226 if (err)
227 goto done;
229 err = got_diff_blob(blob1, blob2, label1, label2, diff_context,
230 outfile);
232 done:
233 if (obj1)
234 got_object_close(obj1);
235 if (obj2)
236 got_object_close(obj2);
237 if (blob1)
238 got_object_blob_close(blob1);
239 if (blob2)
240 got_object_blob_close(blob2);
241 return err;
244 static const struct got_error *
245 diff_deleted_blob(struct got_object_id *id, const char *label,
246 int diff_context, struct got_repository *repo, FILE *outfile)
248 const struct got_error *err;
249 struct got_blob_object *blob = NULL;
250 struct got_object *obj = NULL;
252 err = got_object_open(&obj, repo, id);
253 if (err)
254 return err;
256 err = got_object_blob_open(&blob, repo, obj, 8192);
257 if (err)
258 goto done;
259 err = got_diff_blob(blob, NULL, label, NULL, diff_context, outfile);
260 done:
261 got_object_close(obj);
262 if (blob)
263 got_object_blob_close(blob);
264 return err;
267 static const struct got_error *
268 diff_added_tree(struct got_object_id *id, const char *label,
269 int diff_context, struct got_repository *repo, FILE *outfile)
271 const struct got_error *err = NULL;
272 struct got_object *treeobj = NULL;
273 struct got_tree_object *tree = NULL;
275 err = got_object_open(&treeobj, repo, id);
276 if (err)
277 goto done;
279 if (got_object_get_type(treeobj) != GOT_OBJ_TYPE_TREE) {
280 err = got_error(GOT_ERR_OBJ_TYPE);
281 goto done;
284 err = got_object_tree_open(&tree, repo, treeobj);
285 if (err)
286 goto done;
288 err = got_diff_tree(NULL, tree, NULL, label, diff_context, repo,
289 outfile);
291 done:
292 if (tree)
293 got_object_tree_close(tree);
294 if (treeobj)
295 got_object_close(treeobj);
296 return err;
299 static const struct got_error *
300 diff_modified_tree(struct got_object_id *id1, struct got_object_id *id2,
301 const char *label1, const char *label2, int diff_context,
302 struct got_repository *repo, FILE *outfile)
304 const struct got_error *err;
305 struct got_object *treeobj1 = NULL;
306 struct got_object *treeobj2 = NULL;
307 struct got_tree_object *tree1 = NULL;
308 struct got_tree_object *tree2 = NULL;
310 err = got_object_open(&treeobj1, repo, id1);
311 if (err)
312 goto done;
314 if (got_object_get_type(treeobj1) != GOT_OBJ_TYPE_TREE) {
315 err = got_error(GOT_ERR_OBJ_TYPE);
316 goto done;
319 err = got_object_open(&treeobj2, repo, id2);
320 if (err)
321 goto done;
323 if (got_object_get_type(treeobj2) != GOT_OBJ_TYPE_TREE) {
324 err = got_error(GOT_ERR_OBJ_TYPE);
325 goto done;
328 err = got_object_tree_open(&tree1, repo, treeobj1);
329 if (err)
330 goto done;
332 err = got_object_tree_open(&tree2, repo, treeobj2);
333 if (err)
334 goto done;
336 err = got_diff_tree(tree1, tree2, label1, label2, diff_context, repo,
337 outfile);
339 done:
340 if (tree1)
341 got_object_tree_close(tree1);
342 if (tree2)
343 got_object_tree_close(tree2);
344 if (treeobj1)
345 got_object_close(treeobj1);
346 if (treeobj2)
347 got_object_close(treeobj2);
348 return err;
351 static const struct got_error *
352 diff_deleted_tree(struct got_object_id *id, const char *label,
353 int diff_context, struct got_repository *repo, FILE *outfile)
355 const struct got_error *err;
356 struct got_object *treeobj = NULL;
357 struct got_tree_object *tree = NULL;
359 err = got_object_open(&treeobj, repo, id);
360 if (err)
361 goto done;
363 if (got_object_get_type(treeobj) != GOT_OBJ_TYPE_TREE) {
364 err = got_error(GOT_ERR_OBJ_TYPE);
365 goto done;
368 err = got_object_tree_open(&tree, repo, treeobj);
369 if (err)
370 goto done;
372 err = got_diff_tree(tree, NULL, label, NULL, diff_context, repo,
373 outfile);
374 done:
375 if (tree)
376 got_object_tree_close(tree);
377 if (treeobj)
378 got_object_close(treeobj);
379 return err;
382 static const struct got_error *
383 diff_kind_mismatch(struct got_object_id *id1, struct got_object_id *id2,
384 const char *label1, const char *label2, FILE *outfile)
386 /* XXX TODO */
387 return NULL;
390 static const struct got_error *
391 diff_entry_old_new(struct got_tree_entry *te1, struct got_tree_entry *te2,
392 const char *label1, const char *label2, int diff_context,
393 struct got_repository *repo, FILE *outfile)
395 const struct got_error *err = NULL;
397 if (te2 == NULL) {
398 if (S_ISDIR(te1->mode))
399 err = diff_deleted_tree(te1->id, label1, diff_context,
400 repo, outfile);
401 else
402 err = diff_deleted_blob(te1->id, label1, diff_context,
403 repo, outfile);
404 return err;
407 if (S_ISDIR(te1->mode) && S_ISDIR(te2->mode)) {
408 if (got_object_id_cmp(te1->id, te2->id) != 0)
409 return diff_modified_tree(te1->id, te2->id,
410 label1, label2, diff_context, repo, outfile);
411 } else if (S_ISREG(te1->mode) && S_ISREG(te2->mode)) {
412 if (got_object_id_cmp(te1->id, te2->id) != 0)
413 return diff_modified_blob(te1->id, te2->id,
414 label1, label2, diff_context, repo, outfile);
417 if (got_object_id_cmp(te1->id, te2->id) == 0)
418 return NULL;
420 return diff_kind_mismatch(te1->id, te2->id, label1, label2, outfile);
423 static const struct got_error *
424 diff_entry_new_old(struct got_tree_entry *te2, struct got_tree_entry *te1,
425 const char *label2, int diff_context, struct got_repository *repo,
426 FILE *outfile)
428 if (te1 != NULL) /* handled by diff_entry_old_new() */
429 return NULL;
431 if (S_ISDIR(te2->mode))
432 return diff_added_tree(te2->id, label2, diff_context, repo,
433 outfile);
435 return diff_added_blob(te2->id, label2, diff_context, repo, outfile);
438 const struct got_error *
439 got_diff_tree(struct got_tree_object *tree1, struct got_tree_object *tree2,
440 const char *label1, const char *label2, int diff_context,
441 struct got_repository *repo, FILE *outfile)
443 const struct got_error *err = NULL;
444 struct got_tree_entry *te1 = NULL;
445 struct got_tree_entry *te2 = NULL;
446 char *l1 = NULL, *l2 = NULL;
448 if (tree1) {
449 const struct got_tree_entries *entries;
450 entries = got_object_tree_get_entries(tree1);
451 te1 = SIMPLEQ_FIRST(&entries->head);
452 if (te1 && asprintf(&l1, "%s%s%s", label1, label1[0] ? "/" : "",
453 te1->name) == -1)
454 return got_error_from_errno();
456 if (tree2) {
457 const struct got_tree_entries *entries;
458 entries = got_object_tree_get_entries(tree2);
459 te2 = SIMPLEQ_FIRST(&entries->head);
460 if (te2 && asprintf(&l2, "%s%s%s", label2, label2[0] ? "/" : "",
461 te2->name) == -1)
462 return got_error_from_errno();
465 do {
466 if (te1) {
467 struct got_tree_entry *te = NULL;
468 if (tree2)
469 te = match_entry_by_name(te1, tree2);
470 if (te) {
471 free(l2);
472 l2 = NULL;
473 if (te && asprintf(&l2, "%s%s%s", label2,
474 label2[0] ? "/" : "", te->name) == -1)
475 return got_error_from_errno();
477 err = diff_entry_old_new(te1, te, l1, l2, diff_context,
478 repo, outfile);
479 if (err)
480 break;
483 if (te2) {
484 struct got_tree_entry *te = NULL;
485 if (tree1)
486 te = match_entry_by_name(te2, tree1);
487 err = diff_entry_new_old(te2, te, l2, diff_context,
488 repo, outfile);
489 if (err)
490 break;
493 free(l1);
494 l1 = NULL;
495 if (te1) {
496 te1 = SIMPLEQ_NEXT(te1, entry);
497 if (te1 &&
498 asprintf(&l1, "%s%s%s", label1,
499 label1[0] ? "/" : "", te1->name) == -1)
500 return got_error_from_errno();
502 free(l2);
503 l2 = NULL;
504 if (te2) {
505 te2 = SIMPLEQ_NEXT(te2, entry);
506 if (te2 &&
507 asprintf(&l2, "%s%s%s", label2,
508 label2[0] ? "/" : "", te2->name) == -1)
509 return got_error_from_errno();
511 } while (te1 || te2);
513 return err;
516 const struct got_error *
517 got_diff_objects_as_blobs(struct got_object *obj1, struct got_object *obj2,
518 const char *label1, const char *label2, int diff_context,
519 struct got_repository *repo, FILE *outfile)
521 const struct got_error *err;
522 struct got_blob_object *blob1 = NULL, *blob2 = NULL;
524 if (obj1 == NULL && obj2 == NULL)
525 return got_error(GOT_ERR_NO_OBJ);
527 if (obj1) {
528 err = got_object_blob_open(&blob1, repo, obj1, 8192);
529 if (err)
530 goto done;
532 if (obj2) {
533 err = got_object_blob_open(&blob2, repo, obj2, 8192);
534 if (err)
535 goto done;
537 err = got_diff_blob(blob1, blob2, label1, label2, diff_context,
538 outfile);
539 done:
540 if (blob1)
541 got_object_blob_close(blob1);
542 if (blob2)
543 got_object_blob_close(blob2);
544 return err;
547 const struct got_error *
548 got_diff_objects_as_trees(struct got_object *obj1, struct got_object *obj2,
549 char *label1, char *label2, int diff_context, struct got_repository *repo,
550 FILE *outfile)
552 const struct got_error *err;
553 struct got_tree_object *tree1 = NULL, *tree2 = NULL;
555 if (obj1 == NULL && obj2 == NULL)
556 return got_error(GOT_ERR_NO_OBJ);
558 if (obj1) {
559 err = got_object_tree_open(&tree1, repo, obj1);
560 if (err)
561 goto done;
563 if (obj2) {
564 err = got_object_tree_open(&tree2, repo, obj2);
565 if (err)
566 goto done;
568 err = got_diff_tree(tree1, tree2, label1, label2, diff_context,
569 repo, outfile);
570 done:
571 if (tree1)
572 got_object_tree_close(tree1);
573 if (tree2)
574 got_object_tree_close(tree2);
575 return err;
578 static char *
579 get_datestr(time_t *time, char *datebuf)
581 char *p, *s = ctime_r(time, datebuf);
582 p = strchr(s, '\n');
583 if (p)
584 *p = '\0';
585 return s;
588 const struct got_error *
589 got_diff_objects_as_commits(struct got_object *obj1, struct got_object *obj2,
590 int diff_context, struct got_repository *repo, FILE *outfile)
592 const struct got_error *err;
593 struct got_commit_object *commit1 = NULL, *commit2 = NULL;
594 struct got_object *tree_obj1 = NULL, *tree_obj2 = NULL;
595 char *id_str;
596 char datebuf[26];
597 time_t time;
599 if (obj2 == NULL)
600 return got_error(GOT_ERR_NO_OBJ);
602 if (obj1) {
603 err = got_object_commit_open(&commit1, repo, obj1);
604 if (err)
605 goto done;
606 err = got_object_open(&tree_obj1, repo, commit1->tree_id);
607 if (err)
608 goto done;
611 err = got_object_commit_open(&commit2, repo, obj2);
612 if (err)
613 goto done;
614 err = got_object_open(&tree_obj2, repo, commit2->tree_id);
615 if (err)
616 goto done;
617 err = got_object_get_id_str(&id_str, obj2);
618 if (err)
619 goto done;
620 if (fprintf(outfile, "commit: %s\n", id_str) < 0) {
621 err = got_error_from_errno();
622 free(id_str);
623 goto done;
625 free(id_str);
626 if (fprintf(outfile, "from: %s\n", commit2->author) < 0) {
627 err = got_error_from_errno();
628 goto done;
630 time = mktime(&commit2->tm_committer);
631 if (fprintf(outfile, "date: %s UTC\n",
632 get_datestr(&time, datebuf)) < 0) {
633 err = got_error_from_errno();
634 goto done;
636 if (strcmp(commit2->author, commit2->committer) != 0 &&
637 fprintf(outfile, "via: %s\n", commit2->committer) < 0) {
638 err = got_error_from_errno();
639 goto done;
641 if (fprintf(outfile, "%s\n", commit2->logmsg) < 0) {
642 err = got_error_from_errno();
643 goto done;
646 err = got_diff_objects_as_trees(tree_obj1, tree_obj2, "", "",
647 diff_context, repo, outfile);
648 done:
649 if (tree_obj1)
650 got_object_close(tree_obj1);
651 if (tree_obj2)
652 got_object_close(tree_obj2);
653 if (commit1)
654 got_object_commit_close(commit1);
655 if (commit2)
656 got_object_commit_close(commit2);
657 return err;