2 * Copyright (c) 2017 Stefan Sperling <stsp@openbsd.org>
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.
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.
17 #include <sys/queue.h>
27 #include "got_repository.h"
28 #include "got_object.h"
29 #include "got_error.h"
31 #include "got_opentemp.h"
33 #include "got_lib_diff.h"
34 #include "got_lib_path.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)
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;
57 return got_error_from_errno();
64 err = got_error_from_errno();
73 idstr1 = got_object_blob_id_str(blob1, hex1, sizeof(hex1));
74 err = got_object_blob_dump_to_file(&size1, NULL, f1, blob1);
82 idstr2 = got_object_blob_id_str(blob2, hex2, sizeof(hex2));
83 err = got_object_blob_dump_to_file(&size2, NULL, f2, blob2);
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;
93 ds.stb1.st_size = size1;
94 ds.stb1.st_mtime = 0; /* XXX */
96 ds.stb2.st_mode = S_IFREG;
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;
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);
114 if (f1 && fclose(f1) != 0 && err == NULL)
115 err = got_error_from_errno();
116 if (f2 && fclose(f2) != 0 && err == NULL)
117 err = got_error_from_errno();
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,
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;
137 char hex1[SHA1_DIGEST_STRING_LENGTH];
146 return got_error_from_errno();
147 idstr1 = got_object_blob_id_str(blob1, hex1, sizeof(hex1));
148 err = got_object_blob_dump_to_file(&size1, NULL, f1, blob1);
153 idstr1 = "/dev/null";
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;
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", label2);
179 err = got_diffreg(&res, f1, f2, flags, &args, &ds, outfile, NULL);
181 if (f1 && fclose(f1) != 0 && err == NULL)
182 err = got_error_from_errno();
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_from_errno();
195 SIMPLEQ_INIT(&(*changes)->entries);
197 err = diff_blobs(blob1, blob2, NULL, NULL, 3, NULL, *changes);
199 got_diff_free_changes(*changes);
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);
217 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)
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);
243 err = got_object_blob_open(&blob, repo, obj, 8192);
246 err = got_diff_blob(NULL, blob, NULL, label, diff_context, outfile);
248 got_object_close(obj);
250 got_object_blob_close(blob);
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);
268 if (obj1->type != GOT_OBJ_TYPE_BLOB) {
269 err = got_error(GOT_ERR_OBJ_TYPE);
273 err = got_object_open(&obj2, repo, id2);
276 if (obj2->type != GOT_OBJ_TYPE_BLOB) {
277 err = got_error(GOT_ERR_BAD_OBJ_DATA);
281 err = got_object_blob_open(&blob1, repo, obj1, 8192);
285 err = got_object_blob_open(&blob2, repo, obj2, 8192);
289 err = got_diff_blob(blob1, blob2, label1, label2, diff_context,
294 got_object_close(obj1);
296 got_object_close(obj2);
298 got_object_blob_close(blob1);
300 got_object_blob_close(blob2);
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);
316 err = got_object_blob_open(&blob, repo, obj, 8192);
319 err = got_diff_blob(blob, NULL, label, NULL, diff_context, outfile);
321 got_object_close(obj);
323 got_object_blob_close(blob);
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);
339 if (treeobj->type != GOT_OBJ_TYPE_TREE) {
340 err = got_error(GOT_ERR_OBJ_TYPE);
344 err = got_object_tree_open(&tree, repo, treeobj);
348 err = got_diff_tree(NULL, tree, NULL, label, diff_context, repo,
353 got_object_tree_close(tree);
355 got_object_close(treeobj);
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);
374 if (treeobj1->type != GOT_OBJ_TYPE_TREE) {
375 err = got_error(GOT_ERR_OBJ_TYPE);
379 err = got_object_open(&treeobj2, repo, id2);
383 if (treeobj2->type != GOT_OBJ_TYPE_TREE) {
384 err = got_error(GOT_ERR_OBJ_TYPE);
388 err = got_object_tree_open(&tree1, repo, treeobj1);
392 err = got_object_tree_open(&tree2, repo, treeobj2);
396 err = got_diff_tree(tree1, tree2, label1, label2, diff_context, repo,
401 got_object_tree_close(tree1);
403 got_object_tree_close(tree2);
405 got_object_close(treeobj1);
407 got_object_close(treeobj2);
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);
423 if (treeobj->type != GOT_OBJ_TYPE_TREE) {
424 err = got_error(GOT_ERR_OBJ_TYPE);
428 err = got_object_tree_open(&tree, repo, treeobj);
432 err = got_diff_tree(tree, NULL, label, NULL, diff_context, repo,
436 got_object_tree_close(tree);
438 got_object_close(treeobj);
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)
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;
459 if (S_ISDIR(te1->mode))
460 err = diff_deleted_tree(te1->id, label1, diff_context,
463 err = diff_deleted_blob(te1->id, label1, diff_context,
468 id_match = (got_object_id_cmp(te1->id, te2->id) == 0);
469 if (S_ISDIR(te1->mode) && S_ISDIR(te2->mode)) {
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)) {
475 return diff_modified_blob(te1->id, te2->id,
476 label1, label2, diff_context, repo, outfile);
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,
490 if (te1 != NULL) /* handled by diff_entry_old_new() */
493 if (S_ISDIR(te2->mode))
494 return diff_added_tree(te2->id, label2, diff_context, repo,
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;
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] ? "/" : "",
516 return got_error_from_errno();
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] ? "/" : "",
524 return got_error_from_errno();
529 struct got_tree_entry *te = NULL;
531 te = match_entry_by_name(te1, tree2);
535 if (te && asprintf(&l2, "%s%s%s", label2,
536 label2[0] ? "/" : "", te->name) == -1)
537 return got_error_from_errno();
539 err = diff_entry_old_new(te1, te, l1, l2, diff_context,
546 struct got_tree_entry *te = NULL;
548 te = match_entry_by_name(te2, tree1);
551 if (asprintf(&l2, "%s%s%s", label2,
552 label2[0] ? "/" : "", te->name) == -1)
553 return got_error_from_errno();
555 if (asprintf(&l2, "%s%s%s", label2,
556 label2[0] ? "/" : "", te2->name) == -1)
557 return got_error_from_errno();
559 err = diff_entry_new_old(te2, te, l2, diff_context,
568 te1 = SIMPLEQ_NEXT(te1, entry);
570 asprintf(&l1, "%s%s%s", label1,
571 label1[0] ? "/" : "", te1->name) == -1)
572 return got_error_from_errno();
577 te2 = SIMPLEQ_NEXT(te2, entry);
579 asprintf(&l2, "%s%s%s", label2,
580 label2[0] ? "/" : "", te2->name) == -1)
581 return got_error_from_errno();
583 } while (te1 || te2);
588 const struct got_error *
589 got_diff_objects_as_blobs(struct got_object_id *id1, struct got_object_id *id2,
590 const char *label1, const char *label2, int diff_context,
591 struct got_repository *repo, FILE *outfile)
593 const struct got_error *err;
594 struct got_blob_object *blob1 = NULL, *blob2 = NULL;
596 if (id1 == NULL && id2 == NULL)
597 return got_error(GOT_ERR_NO_OBJ);
600 err = got_object_open_as_blob(&blob1, repo, id1, 8192);
605 err = got_object_open_as_blob(&blob2, repo, id2, 8192);
609 err = got_diff_blob(blob1, blob2, label1, label2, diff_context,
613 got_object_blob_close(blob1);
615 got_object_blob_close(blob2);
619 const struct got_error *
620 got_diff_objects_as_trees(struct got_object_id *id1, struct got_object_id *id2,
621 char *label1, char *label2, int diff_context, struct got_repository *repo,
624 const struct got_error *err;
625 struct got_tree_object *tree1 = NULL, *tree2 = NULL;
627 if (id1 == NULL && id2 == NULL)
628 return got_error(GOT_ERR_NO_OBJ);
631 err = got_object_open_as_tree(&tree1, repo, id1);
636 err = got_object_open_as_tree(&tree2, repo, id2);
640 err = got_diff_tree(tree1, tree2, label1, label2, diff_context,
644 got_object_tree_close(tree1);
646 got_object_tree_close(tree2);
650 const struct got_error *
651 got_diff_objects_as_commits(struct got_object_id *id1,
652 struct got_object_id *id2, int diff_context,
653 struct got_repository *repo, FILE *outfile)
655 const struct got_error *err;
656 struct got_commit_object *commit1 = NULL, *commit2 = NULL;
659 return got_error(GOT_ERR_NO_OBJ);
662 err = got_object_open_as_commit(&commit1, repo, id1);
667 err = got_object_open_as_commit(&commit2, repo, id2);
671 err = got_diff_objects_as_trees(
672 commit1 ? got_object_commit_get_tree_id(commit1) : NULL,
673 got_object_commit_get_tree_id(commit2), "", "", diff_context, repo,
677 got_object_commit_close(commit1);
679 got_object_commit_close(commit2);