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 "got_compat.h"
19 #include <sys/queue.h>
28 #include "got_object.h"
29 #include "got_repository.h"
30 #include "got_error.h"
33 #include "got_cancel.h"
34 #include "got_worktree.h"
35 #include "got_opentemp.h"
37 #include "got_lib_diff.h"
38 #include "got_lib_delta.h"
39 #include "got_lib_inflate.h"
40 #include "got_lib_object.h"
43 #define MAX(_a,_b) ((_a) > (_b) ? (_a) : (_b))
46 static const struct got_error
*
47 add_line_metadata(struct got_diff_line
**lines
, size_t *nlines
,
48 off_t off
, uint8_t type
)
50 struct got_diff_line
*p
;
52 p
= reallocarray(*lines
, *nlines
+ 1, sizeof(**lines
));
54 return got_error_from_errno("reallocarray");
56 (*lines
)[*nlines
].offset
= off
;
57 (*lines
)[*nlines
].type
= type
;
64 diffstat_field_width(size_t *maxlen
, int *add_cols
, int *rm_cols
, size_t len
,
65 uint32_t add
, uint32_t rm
)
70 *maxlen
= MAX(*maxlen
, len
);
74 *add_cols
= MAX(*add_cols
, d1
);
78 *rm_cols
= MAX(*rm_cols
, d2
);
81 static const struct got_error
*
82 get_diffstat(struct got_diffstat_cb_arg
*ds
, const char *path
,
83 struct diff_result
*r
, int force_text
, int status
)
85 const struct got_error
*err
;
86 struct got_pathlist_entry
*pe
;
87 struct got_diff_changed_path
*change
= NULL
;
88 int flags
= (r
->left
->atomizer_flags
| r
->right
->atomizer_flags
);
89 int isbin
= (flags
& DIFF_ATOMIZER_FOUND_BINARY_DATA
);
92 change
= calloc(1, sizeof(*change
));
94 return got_error_from_errno("calloc");
96 if (!isbin
|| force_text
) {
97 for (i
= 0; i
< r
->chunks
.len
; ++i
) {
101 c
= diff_chunk_get(r
, i
);
102 clc
= diff_chunk_get_left_count(c
);
103 crc
= diff_chunk_get_right_count(c
);
112 change
->status
= status
;
113 ds
->ins
+= change
->add
;
114 ds
->del
+= change
->rm
;
117 err
= got_pathlist_append(ds
->paths
, path
, change
);
123 pe
= TAILQ_LAST(ds
->paths
, got_pathlist_head
);
124 diffstat_field_width(&ds
->max_path_len
, &ds
->add_cols
, &ds
->rm_cols
,
125 pe
->path_len
, change
->add
, change
->rm
);
130 static const struct got_error
*
131 diff_blobs(struct got_diff_line
**lines
, size_t *nlines
,
132 struct got_diffreg_result
**resultp
, struct got_blob_object
*blob1
,
133 struct got_blob_object
*blob2
, FILE *f1
, FILE *f2
,
134 const char *label1
, const char *label2
, mode_t mode1
, mode_t mode2
,
135 int diff_context
, int ignore_whitespace
, int force_text_diff
,
136 struct got_diffstat_cb_arg
*diffstat
, FILE *outfile
,
137 enum got_diff_algorithm diff_algo
)
139 const struct got_error
*err
= NULL
, *free_err
;
140 char hex1
[GOT_OBJECT_ID_HEX_MAXLEN
];
141 char hex2
[GOT_OBJECT_ID_HEX_MAXLEN
];
142 const char *idstr1
= NULL
, *idstr2
= NULL
;
143 char *modestr1
= NULL
, *modestr2
= NULL
;
145 struct got_diffreg_result
*result
= NULL
;
149 if (lines
&& *lines
&& *nlines
> 0)
150 outoff
= (*lines
)[*nlines
- 1].offset
;
152 err
= add_line_metadata(lines
, nlines
, 0, GOT_DIFF_LINE_NONE
);
161 err
= got_opentemp_truncate(f1
);
166 err
= got_opentemp_truncate(f2
);
173 idstr1
= got_object_blob_id_str(blob1
, hex1
, sizeof(hex1
));
174 err
= got_object_blob_dump_to_file(&size1
, NULL
, NULL
, f1
,
179 idstr1
= "/dev/null";
183 idstr2
= got_object_blob_id_str(blob2
, hex2
, sizeof(hex2
));
184 err
= got_object_blob_dump_to_file(&size2
, NULL
, NULL
, f2
,
189 idstr2
= "/dev/null";
194 if (mode1
&& mode1
!= mode2
) {
198 modebits
= (S_IRWXU
| S_IRWXG
| S_IRWXO
);
199 if (asprintf(&modestr1
, " (mode %o)",
200 mode1
& modebits
) == -1) {
201 err
= got_error_from_errno("asprintf");
205 if (mode2
&& mode1
!= mode2
) {
209 modebits
= (S_IRWXU
| S_IRWXG
| S_IRWXO
);
210 if (asprintf(&modestr2
, " (mode %o)",
211 mode2
& modebits
) == -1) {
212 err
= got_error_from_errno("asprintf");
216 n
= fprintf(outfile
, "blob - %s%s\n", idstr1
,
217 modestr1
? modestr1
: "");
222 err
= add_line_metadata(lines
, nlines
, outoff
,
223 GOT_DIFF_LINE_BLOB_MIN
);
228 n
= fprintf(outfile
, "blob + %s%s\n", idstr2
,
229 modestr2
? modestr2
: "");
234 err
= add_line_metadata(lines
, nlines
, outoff
,
235 GOT_DIFF_LINE_BLOB_PLUS
);
241 err
= got_diffreg(&result
, f1
, f2
, diff_algo
, ignore_whitespace
,
248 int status
= GOT_STATUS_NO_CHANGE
;
251 status
= GOT_STATUS_ADD
;
252 else if (blob2
== NULL
)
253 status
= GOT_STATUS_DELETE
;
255 if (strcmp(idstr1
, idstr2
) != 0)
256 status
= GOT_STATUS_MODIFY
;
257 else if (mode1
!= mode2
)
258 status
= GOT_STATUS_MODE_CHANGE
;
261 if (label1
== NULL
&& label2
== NULL
) {
262 /* diffstat of blobs, show hash instead of path */
263 if (asprintf(&path
, "%.10s -> %.10s",
264 idstr1
, idstr2
) == -1) {
265 err
= got_error_from_errno("asprintf");
269 if (label2
!= NULL
&&
270 (status
!= GOT_STATUS_DELETE
|| label1
== NULL
))
271 path
= strdup(label2
);
273 path
= strdup(label1
);
275 err
= got_error_from_errno("strdup");
280 err
= get_diffstat(diffstat
, path
, result
->result
,
281 force_text_diff
, status
);
289 err
= got_diffreg_output(lines
, nlines
, result
,
290 blob1
!= NULL
, blob2
!= NULL
,
291 label1
? label1
: idstr1
,
292 label2
? label2
: idstr2
,
293 GOT_DIFF_OUTPUT_UNIDIFF
, diff_context
, outfile
);
301 if (resultp
&& err
== NULL
)
304 free_err
= got_diffreg_result_free(result
);
305 if (free_err
&& err
== NULL
)
312 const struct got_error
*
313 got_diff_blob_output_unidiff(void *arg
, struct got_blob_object
*blob1
,
314 struct got_blob_object
*blob2
, FILE *f1
, FILE *f2
,
315 struct got_object_id
*id1
, struct got_object_id
*id2
,
316 const char *label1
, const char *label2
, mode_t mode1
, mode_t mode2
,
317 struct got_repository
*repo
)
319 struct got_diff_blob_output_unidiff_arg
*a
= arg
;
321 return diff_blobs(&a
->lines
, &a
->nlines
, NULL
,
322 blob1
, blob2
, f1
, f2
, label1
, label2
, mode1
, mode2
, a
->diff_context
,
323 a
->ignore_whitespace
, a
->force_text_diff
, a
->diffstat
, a
->outfile
,
327 const struct got_error
*
328 got_diff_blob(struct got_diff_line
**lines
, size_t*nlines
,
329 struct got_blob_object
*blob1
, struct got_blob_object
*blob2
,
330 FILE *f1
, FILE *f2
, const char *label1
, const char *label2
,
331 enum got_diff_algorithm diff_algo
, int diff_context
,
332 int ignore_whitespace
, int force_text_diff
,
333 struct got_diffstat_cb_arg
*ds
, FILE *outfile
)
335 return diff_blobs(lines
, nlines
, NULL
, blob1
, blob2
, f1
, f2
,
336 label1
, label2
, 0, 0, diff_context
, ignore_whitespace
,
337 force_text_diff
, ds
, outfile
, diff_algo
);
340 static const struct got_error
*
341 diff_blob_file(struct got_diffreg_result
**resultp
,
342 struct got_blob_object
*blob1
, FILE *f1
, off_t size1
, const char *label1
,
343 FILE *f2
, int f2_exists
, struct stat
*sb2
, const char *label2
,
344 enum got_diff_algorithm diff_algo
, int diff_context
, int ignore_whitespace
,
345 int force_text_diff
, struct got_diffstat_cb_arg
*diffstat
, FILE *outfile
)
347 const struct got_error
*err
= NULL
, *free_err
;
348 char hex1
[GOT_OBJECT_ID_HEX_MAXLEN
];
349 const char *idstr1
= NULL
;
350 struct got_diffreg_result
*result
= NULL
;
356 idstr1
= got_object_blob_id_str(blob1
, hex1
, sizeof(hex1
));
358 idstr1
= "/dev/null";
363 /* display file mode for new added files only */
364 if (f2_exists
&& blob1
== NULL
) {
365 int mmask
= (S_IRWXU
| S_IRWXG
| S_IRWXO
);
367 if (S_ISLNK(sb2
->st_mode
))
369 if (asprintf(&mode
, " (mode %o)",
370 sb2
->st_mode
& mmask
) == -1)
371 return got_error_from_errno("asprintf");
373 fprintf(outfile
, "blob - %s\n", label1
? label1
: idstr1
);
374 fprintf(outfile
, "file + %s%s\n",
375 f2_exists
? label2
: "/dev/null", mode
? mode
: "");
379 err
= got_diffreg(&result
, f1
, f2
, diff_algo
, ignore_whitespace
,
382 char msg
[GOT_ERR_MAX_MSG_SIZE
];
383 if (snprintf(msg
, sizeof(msg
), "%s vs %s: %s",
384 label1
? label1
: idstr1
,
385 f2_exists
? label2
: "/dev/null", err
->msg
) >= 0) {
386 err
= got_error_msg(err
->code
, msg
);
392 err
= got_diffreg_output(NULL
, NULL
, result
,
393 blob1
!= NULL
, f2_exists
,
394 label2
, /* show local file's path, not a blob ID */
395 label2
, GOT_DIFF_OUTPUT_UNIDIFF
,
396 diff_context
, outfile
);
403 int status
= GOT_STATUS_NO_CHANGE
;
406 * Ignore 'm'ode status change: if there's no accompanying
407 * content change, there'll be no diffstat, and if there
408 * are actual changes, 'M'odified takes precedence.
411 status
= GOT_STATUS_ADD
;
413 status
= GOT_STATUS_DELETE
;
415 status
= GOT_STATUS_MODIFY
;
417 if (label2
!= NULL
&&
418 (status
!= GOT_STATUS_DELETE
|| label1
== NULL
))
419 path
= strdup(label2
);
421 path
= strdup(label1
);
423 err
= got_error_from_errno("strdup");
427 err
= get_diffstat(diffstat
, path
, result
->result
,
428 force_text_diff
, status
);
436 if (resultp
&& err
== NULL
)
439 free_err
= got_diffreg_result_free(result
);
440 if (free_err
&& err
== NULL
)
446 const struct got_error
*
447 got_diff_blob_file(struct got_blob_object
*blob1
, FILE *f1
, off_t size1
,
448 const char *label1
, FILE *f2
, int f2_exists
, struct stat
*sb2
,
449 const char *label2
, enum got_diff_algorithm diff_algo
, int diff_context
,
450 int ignore_whitespace
, int force_text_diff
,
451 struct got_diffstat_cb_arg
*ds
, FILE *outfile
)
453 return diff_blob_file(NULL
, blob1
, f1
, size1
, label1
, f2
, f2_exists
,
454 sb2
, label2
, diff_algo
, diff_context
, ignore_whitespace
,
455 force_text_diff
, ds
, outfile
);
458 static const struct got_error
*
459 diff_added_blob(struct got_object_id
*id
, FILE *f1
, FILE *f2
, int fd2
,
460 const char *label
, mode_t mode
, struct got_repository
*repo
,
461 got_diff_blob_cb cb
, void *cb_arg
)
463 const struct got_error
*err
;
464 struct got_blob_object
*blob
= NULL
;
465 struct got_object
*obj
= NULL
;
467 err
= got_object_open(&obj
, repo
, id
);
471 err
= got_object_blob_open(&blob
, repo
, obj
, 8192, fd2
);
474 err
= cb(cb_arg
, NULL
, blob
, f1
, f2
, NULL
, id
,
475 NULL
, label
, 0, mode
, repo
);
477 got_object_close(obj
);
479 got_object_blob_close(blob
);
483 static const struct got_error
*
484 diff_modified_blob(struct got_object_id
*id1
, struct got_object_id
*id2
,
485 FILE *f1
, FILE *f2
, int fd1
, int fd2
,
486 const char *label1
, const char *label2
,
487 mode_t mode1
, mode_t mode2
, struct got_repository
*repo
,
488 got_diff_blob_cb cb
, void *cb_arg
)
490 const struct got_error
*err
;
491 struct got_object
*obj1
= NULL
;
492 struct got_object
*obj2
= NULL
;
493 struct got_blob_object
*blob1
= NULL
;
494 struct got_blob_object
*blob2
= NULL
;
496 err
= got_object_open(&obj1
, repo
, id1
);
500 if (obj1
->type
!= GOT_OBJ_TYPE_BLOB
) {
501 err
= got_error(GOT_ERR_OBJ_TYPE
);
505 err
= got_object_open(&obj2
, repo
, id2
);
508 if (obj2
->type
!= GOT_OBJ_TYPE_BLOB
) {
509 err
= got_error(GOT_ERR_BAD_OBJ_DATA
);
513 err
= got_object_blob_open(&blob1
, repo
, obj1
, 8192, fd1
);
517 err
= got_object_blob_open(&blob2
, repo
, obj2
, 8192, fd2
);
521 err
= cb(cb_arg
, blob1
, blob2
, f1
, f2
, id1
, id2
, label1
, label2
,
525 got_object_close(obj1
);
527 got_object_close(obj2
);
529 got_object_blob_close(blob1
);
531 got_object_blob_close(blob2
);
535 static const struct got_error
*
536 diff_deleted_blob(struct got_object_id
*id
, FILE *f1
, int fd1
,
537 FILE *f2
, const char *label
, mode_t mode
, struct got_repository
*repo
,
538 got_diff_blob_cb cb
, void *cb_arg
)
540 const struct got_error
*err
;
541 struct got_blob_object
*blob
= NULL
;
542 struct got_object
*obj
= NULL
;
544 err
= got_object_open(&obj
, repo
, id
);
548 err
= got_object_blob_open(&blob
, repo
, obj
, 8192, fd1
);
551 err
= cb(cb_arg
, blob
, NULL
, f1
, f2
, id
, NULL
, label
, NULL
,
554 got_object_close(obj
);
556 got_object_blob_close(blob
);
560 static const struct got_error
*
561 diff_added_tree(struct got_object_id
*id
, FILE *f1
, FILE *f2
, int fd2
,
562 const char *label
, struct got_repository
*repo
, got_diff_blob_cb cb
,
563 void *cb_arg
, int diff_content
)
565 const struct got_error
*err
= NULL
;
566 struct got_object
*treeobj
= NULL
;
567 struct got_tree_object
*tree
= NULL
;
569 err
= got_object_open(&treeobj
, repo
, id
);
573 if (treeobj
->type
!= GOT_OBJ_TYPE_TREE
) {
574 err
= got_error(GOT_ERR_OBJ_TYPE
);
578 err
= got_object_tree_open(&tree
, repo
, treeobj
);
582 err
= got_diff_tree(NULL
, tree
, f1
, f2
, -1, fd2
, NULL
, label
,
583 repo
, cb
, cb_arg
, diff_content
);
586 got_object_tree_close(tree
);
588 got_object_close(treeobj
);
592 static const struct got_error
*
593 diff_modified_tree(struct got_object_id
*id1
, struct got_object_id
*id2
,
594 FILE *f1
, FILE *f2
, int fd1
, int fd2
,
595 const char *label1
, const char *label2
,
596 struct got_repository
*repo
, got_diff_blob_cb cb
, void *cb_arg
,
599 const struct got_error
*err
;
600 struct got_object
*treeobj1
= NULL
;
601 struct got_object
*treeobj2
= NULL
;
602 struct got_tree_object
*tree1
= NULL
;
603 struct got_tree_object
*tree2
= NULL
;
605 err
= got_object_open(&treeobj1
, repo
, id1
);
609 if (treeobj1
->type
!= GOT_OBJ_TYPE_TREE
) {
610 err
= got_error(GOT_ERR_OBJ_TYPE
);
614 err
= got_object_open(&treeobj2
, repo
, id2
);
618 if (treeobj2
->type
!= GOT_OBJ_TYPE_TREE
) {
619 err
= got_error(GOT_ERR_OBJ_TYPE
);
623 err
= got_object_tree_open(&tree1
, repo
, treeobj1
);
627 err
= got_object_tree_open(&tree2
, repo
, treeobj2
);
631 err
= got_diff_tree(tree1
, tree2
, f1
, f2
, fd1
, fd2
,
632 label1
, label2
, repo
, cb
, cb_arg
, diff_content
);
636 got_object_tree_close(tree1
);
638 got_object_tree_close(tree2
);
640 got_object_close(treeobj1
);
642 got_object_close(treeobj2
);
646 static const struct got_error
*
647 diff_deleted_tree(struct got_object_id
*id
, FILE *f1
, int fd1
,
648 FILE *f2
, const char *label
, struct got_repository
*repo
,
649 got_diff_blob_cb cb
, void *cb_arg
, int diff_content
)
651 const struct got_error
*err
;
652 struct got_object
*treeobj
= NULL
;
653 struct got_tree_object
*tree
= NULL
;
655 err
= got_object_open(&treeobj
, repo
, id
);
659 if (treeobj
->type
!= GOT_OBJ_TYPE_TREE
) {
660 err
= got_error(GOT_ERR_OBJ_TYPE
);
664 err
= got_object_tree_open(&tree
, repo
, treeobj
);
668 err
= got_diff_tree(tree
, NULL
, f1
, f2
, fd1
, -1, label
, NULL
,
669 repo
, cb
, cb_arg
, diff_content
);
672 got_object_tree_close(tree
);
674 got_object_close(treeobj
);
678 static const struct got_error
*
679 diff_kind_mismatch(struct got_tree_entry
*te1
, struct got_tree_entry
*te2
,
680 FILE *f1
, FILE *f2
, int fd1
, int fd2
,
681 const char *label1
, const char *label2
, struct got_repository
*repo
,
682 got_diff_blob_cb cb
, void *cb_arg
, int diff_content
)
684 const struct got_error
*err
= NULL
;
687 * Handle files changing into directories and vice-versa.
688 * Disregard edge cases with FIFOs, device nodes, etc for now.
690 if (!S_ISDIR(te1
->mode
) && S_ISDIR(te2
->mode
)) {
691 if (S_ISREG(te1
->mode
)) {
693 err
= diff_deleted_blob(&te1
->id
, f1
, fd1
,
694 f2
, label1
, te1
->mode
, repo
, cb
, cb_arg
);
696 err
= cb(cb_arg
, NULL
, NULL
, NULL
, NULL
,
697 &te1
->id
, NULL
, label1
, NULL
,
703 return diff_added_tree(&te2
->id
, f1
, f2
, fd2
, label2
,
704 repo
, cb
, cb_arg
, diff_content
);
705 } else if (S_ISDIR(te1
->mode
) && !S_ISDIR(te2
->mode
)) {
706 err
= diff_deleted_tree(&te1
->id
, f1
, fd1
, f2
,
707 label1
, repo
, cb
, cb_arg
, diff_content
);
710 if (S_ISREG(te2
->mode
)) {
712 err
= diff_added_blob(&te2
->id
, f1
, f2
, fd2
,
713 label2
, te2
->mode
, repo
, cb
, cb_arg
);
715 err
= cb(cb_arg
, NULL
, NULL
, NULL
, NULL
, NULL
,
716 &te2
->id
, NULL
, label2
, 0, te2
->mode
, repo
);
726 static const struct got_error
*
727 diff_entry_old_new(struct got_tree_entry
*te1
, struct got_tree_entry
*te2
,
728 FILE *f1
, FILE *f2
, int fd1
, int fd2
,
729 const char *label1
, const char *label2
,
730 struct got_repository
*repo
, got_diff_blob_cb cb
, void *cb_arg
,
733 const struct got_error
*err
= NULL
;
736 if (got_object_tree_entry_is_submodule(te1
))
740 if (S_ISDIR(te1
->mode
))
741 err
= diff_deleted_tree(&te1
->id
, f1
, fd1
, f2
,
742 label1
, repo
, cb
, cb_arg
, diff_content
);
745 err
= diff_deleted_blob(&te1
->id
, f1
, fd1
,
746 f2
, label1
, te1
->mode
, repo
, cb
, cb_arg
);
748 err
= cb(cb_arg
, NULL
, NULL
, NULL
, NULL
,
749 &te1
->id
, NULL
, label1
, NULL
,
753 } else if (got_object_tree_entry_is_submodule(te2
))
756 id_match
= (got_object_id_cmp(&te1
->id
, &te2
->id
) == 0);
757 if (S_ISDIR(te1
->mode
) && S_ISDIR(te2
->mode
)) {
759 return diff_modified_tree(&te1
->id
, &te2
->id
, f1
, f2
,
760 fd1
, fd2
, label1
, label2
, repo
, cb
, cb_arg
,
762 } else if ((S_ISREG(te1
->mode
) || S_ISLNK(te1
->mode
)) &&
763 (S_ISREG(te2
->mode
) || S_ISLNK(te2
->mode
))) {
765 ((te1
->mode
& (S_IFLNK
| S_IXUSR
))) !=
766 (te2
->mode
& (S_IFLNK
| S_IXUSR
))) {
768 return diff_modified_blob(&te1
->id
, &te2
->id
,
769 f1
, f2
, fd1
, fd2
, label1
, label2
,
770 te1
->mode
, te2
->mode
, repo
, cb
, cb_arg
);
772 return cb(cb_arg
, NULL
, NULL
, NULL
, NULL
,
773 &te1
->id
, &te2
->id
, label1
, label2
,
774 te1
->mode
, te2
->mode
, repo
);
781 return diff_kind_mismatch(te1
, te2
, f1
, f2
, fd1
, fd2
,
782 label1
, label2
, repo
, cb
, cb_arg
, diff_content
);
785 static const struct got_error
*
786 diff_entry_new_old(struct got_tree_entry
*te2
,
787 struct got_tree_entry
*te1
, FILE *f1
, FILE *f2
, int fd2
, const char *label2
,
788 struct got_repository
*repo
, got_diff_blob_cb cb
, void *cb_arg
,
791 if (te1
!= NULL
) /* handled by diff_entry_old_new() */
794 if (got_object_tree_entry_is_submodule(te2
))
797 if (S_ISDIR(te2
->mode
))
798 return diff_added_tree(&te2
->id
, f1
, f2
, fd2
, label2
,
799 repo
, cb
, cb_arg
, diff_content
);
802 return diff_added_blob(&te2
->id
, f1
, f2
, fd2
,
803 label2
, te2
->mode
, repo
, cb
, cb_arg
);
805 return cb(cb_arg
, NULL
, NULL
, NULL
, NULL
, NULL
, &te2
->id
,
806 NULL
, label2
, 0, te2
->mode
, repo
);
809 const struct got_error
*
810 got_diff_tree_compute_diffstat(void *arg
, struct got_blob_object
*blob1
,
811 struct got_blob_object
*blob2
, FILE *f1
, FILE *f2
,
812 struct got_object_id
*id1
, struct got_object_id
*id2
,
813 const char *label1
, const char *label2
,
814 mode_t mode1
, mode_t mode2
, struct got_repository
*repo
)
816 const struct got_error
*err
= NULL
;
817 struct got_diffreg_result
*result
= NULL
;
818 struct got_diffstat_cb_arg
*a
= arg
;
820 int status
= GOT_STATUS_NO_CHANGE
;
822 path
= strdup(label2
? label2
: label1
);
824 return got_error_from_errno("strdup");
827 status
= GOT_STATUS_ADD
;
828 else if (id2
== NULL
)
829 status
= GOT_STATUS_DELETE
;
831 if (got_object_id_cmp(id1
, id2
) != 0)
832 status
= GOT_STATUS_MODIFY
;
833 else if (mode1
!= mode2
)
834 status
= GOT_STATUS_MODE_CHANGE
;
838 err
= got_opentemp_truncate(f1
);
843 err
= got_opentemp_truncate(f2
);
849 err
= got_object_blob_dump_to_file(NULL
, NULL
, NULL
, f1
,
855 err
= got_object_blob_dump_to_file(NULL
, NULL
, NULL
, f2
,
861 err
= got_diffreg(&result
, f1
, f2
, a
->diff_algo
, a
->ignore_ws
,
866 err
= get_diffstat(a
, path
, result
->result
, a
->force_text
, status
);
870 const struct got_error
*free_err
;
872 free_err
= got_diffreg_result_free(result
);
873 if (free_err
&& err
== NULL
)
881 const struct got_error
*
882 got_diff_tree_collect_changed_paths(void *arg
, struct got_blob_object
*blob1
,
883 struct got_blob_object
*blob2
, FILE *f1
, FILE *f2
,
884 struct got_object_id
*id1
, struct got_object_id
*id2
,
885 const char *label1
, const char *label2
,
886 mode_t mode1
, mode_t mode2
, struct got_repository
*repo
)
888 const struct got_error
*err
= NULL
;
889 struct got_pathlist_head
*paths
= arg
;
890 struct got_diff_changed_path
*change
= NULL
;
893 path
= strdup(label2
? label2
: label1
);
895 return got_error_from_errno("strdup");
897 change
= malloc(sizeof(*change
));
898 if (change
== NULL
) {
899 err
= got_error_from_errno("malloc");
903 change
->status
= GOT_STATUS_NO_CHANGE
;
905 change
->status
= GOT_STATUS_ADD
;
906 else if (id2
== NULL
)
907 change
->status
= GOT_STATUS_DELETE
;
909 if (got_object_id_cmp(id1
, id2
) != 0)
910 change
->status
= GOT_STATUS_MODIFY
;
911 else if (mode1
!= mode2
)
912 change
->status
= GOT_STATUS_MODE_CHANGE
;
915 err
= got_pathlist_append(paths
, path
, change
);
924 const struct got_error
*
925 got_diff_tree(struct got_tree_object
*tree1
, struct got_tree_object
*tree2
,
926 FILE *f1
, FILE *f2
, int fd1
, int fd2
,
927 const char *label1
, const char *label2
,
928 struct got_repository
*repo
, got_diff_blob_cb cb
, void *cb_arg
,
931 const struct got_error
*err
= NULL
;
932 struct got_tree_entry
*te1
= NULL
;
933 struct got_tree_entry
*te2
= NULL
;
934 char *l1
= NULL
, *l2
= NULL
;
935 int tidx1
= 0, tidx2
= 0;
938 te1
= got_object_tree_get_entry(tree1
, 0);
939 if (te1
&& asprintf(&l1
, "%s%s%s", label1
, label1
[0] ? "/" : "",
941 return got_error_from_errno("asprintf");
944 te2
= got_object_tree_get_entry(tree2
, 0);
945 if (te2
&& asprintf(&l2
, "%s%s%s", label2
, label2
[0] ? "/" : "",
947 err
= got_error_from_errno("asprintf");
954 struct got_tree_entry
*te
= NULL
;
957 te
= got_object_tree_find_entry(tree2
,
962 if (te
&& asprintf(&l2
, "%s%s%s", label2
,
963 label2
[0] ? "/" : "", te
->name
) == -1) {
964 err
= got_error_from_errno("asprintf");
969 err
= diff_entry_old_new(te1
, te
, f1
, f2
, fd1
, fd2
,
970 l1
, l2
, repo
, cb
, cb_arg
, diff_content
);
976 struct got_tree_entry
*te
= NULL
;
979 te
= got_object_tree_find_entry(tree1
,
985 if (asprintf(&l2
, "%s%s%s", label2
,
986 label2
[0] ? "/" : "", te
->name
) == -1) {
987 err
= got_error_from_errno("asprintf");
991 if (asprintf(&l2
, "%s%s%s", label2
,
992 label2
[0] ? "/" : "", te2
->name
) == -1) {
993 err
= got_error_from_errno("asprintf");
998 err
= diff_entry_new_old(te2
, te
, f1
, f2
, fd2
, l2
,
999 repo
, cb
, cb_arg
, diff_content
);
1008 te1
= got_object_tree_get_entry(tree1
, tidx1
);
1010 asprintf(&l1
, "%s%s%s", label1
,
1011 label1
[0] ? "/" : "", te1
->name
) == -1) {
1012 err
= got_error_from_errno("asprintf");
1021 te2
= got_object_tree_get_entry(tree2
, tidx2
);
1023 asprintf(&l2
, "%s%s%s", label2
,
1024 label2
[0] ? "/" : "", te2
->name
) == -1) {
1025 err
= got_error_from_errno("asprintf");
1029 } while (te1
|| te2
);
1037 const struct got_error
*
1038 got_diff_objects_as_blobs(struct got_diff_line
**lines
, size_t *nlines
,
1039 FILE *f1
, FILE *f2
, int fd1
, int fd2
,
1040 struct got_object_id
*id1
, struct got_object_id
*id2
,
1041 const char *label1
, const char *label2
,
1042 enum got_diff_algorithm diff_algo
, int diff_context
,
1043 int ignore_whitespace
, int force_text_diff
, struct got_diffstat_cb_arg
*ds
,
1044 struct got_repository
*repo
, FILE *outfile
)
1046 const struct got_error
*err
;
1047 struct got_blob_object
*blob1
= NULL
, *blob2
= NULL
;
1049 if (id1
== NULL
&& id2
== NULL
)
1050 return got_error(GOT_ERR_NO_OBJ
);
1053 err
= got_object_open_as_blob(&blob1
, repo
, id1
, 8192, fd1
);
1058 err
= got_object_open_as_blob(&blob2
, repo
, id2
, 8192, fd2
);
1062 err
= got_diff_blob(lines
, nlines
, blob1
, blob2
, f1
, f2
, label1
, label2
,
1063 diff_algo
, diff_context
, ignore_whitespace
, force_text_diff
,
1067 got_object_blob_close(blob1
);
1069 got_object_blob_close(blob2
);
1073 static const struct got_error
*
1074 diff_paths(struct got_tree_object
*tree1
, struct got_tree_object
*tree2
,
1075 FILE *f1
, FILE *f2
, int fd1
, int fd2
, struct got_pathlist_head
*paths
,
1076 struct got_repository
*repo
, got_diff_blob_cb cb
, void *cb_arg
)
1078 const struct got_error
*err
= NULL
;
1079 struct got_pathlist_entry
*pe
;
1080 struct got_object_id
*id1
= NULL
, *id2
= NULL
;
1081 struct got_tree_object
*subtree1
= NULL
, *subtree2
= NULL
;
1082 struct got_blob_object
*blob1
= NULL
, *blob2
= NULL
;
1084 TAILQ_FOREACH(pe
, paths
, entry
) {
1085 int type1
= GOT_OBJ_TYPE_ANY
, type2
= GOT_OBJ_TYPE_ANY
;
1086 mode_t mode1
= 0, mode2
= 0;
1093 got_object_tree_close(subtree1
);
1097 got_object_tree_close(subtree2
);
1101 got_object_blob_close(blob1
);
1105 got_object_blob_close(blob2
);
1109 err
= got_object_tree_find_path(&id1
, &mode1
, repo
,
1111 if (err
&& err
->code
!= GOT_ERR_NO_TREE_ENTRY
)
1115 err
= got_object_tree_find_path(&id2
, &mode2
, repo
,
1117 if (err
&& err
->code
!= GOT_ERR_NO_TREE_ENTRY
)
1120 if (id1
== NULL
&& id2
== NULL
) {
1121 err
= got_error_path(pe
->path
, GOT_ERR_NO_TREE_ENTRY
);
1125 err
= got_object_get_type(&type1
, repo
, id1
);
1130 err
= got_object_get_type(&type2
, repo
, id2
);
1134 if (type1
== GOT_OBJ_TYPE_ANY
&&
1135 type2
== GOT_OBJ_TYPE_ANY
) {
1136 err
= got_error_path(pe
->path
, GOT_ERR_NO_OBJ
);
1138 } else if (type1
!= GOT_OBJ_TYPE_ANY
&&
1139 type2
!= GOT_OBJ_TYPE_ANY
&& type1
!= type2
) {
1140 err
= got_error(GOT_ERR_OBJ_TYPE
);
1144 if (type1
== GOT_OBJ_TYPE_BLOB
||
1145 type2
== GOT_OBJ_TYPE_BLOB
) {
1147 err
= got_object_open_as_blob(&blob1
, repo
,
1153 err
= got_object_open_as_blob(&blob2
, repo
,
1158 err
= cb(cb_arg
, blob1
, blob2
, f1
, f2
, id1
, id2
,
1159 id1
? pe
->path
: "/dev/null",
1160 id2
? pe
->path
: "/dev/null",
1161 mode1
, mode2
, repo
);
1164 } else if (type1
== GOT_OBJ_TYPE_TREE
||
1165 type2
== GOT_OBJ_TYPE_TREE
) {
1167 err
= got_object_open_as_tree(&subtree1
, repo
,
1173 err
= got_object_open_as_tree(&subtree2
, repo
,
1178 err
= got_diff_tree(subtree1
, subtree2
, f1
, f2
,
1180 id1
? pe
->path
: "/dev/null",
1181 id2
? pe
->path
: "/dev/null",
1182 repo
, cb
, cb_arg
, 1);
1186 err
= got_error(GOT_ERR_OBJ_TYPE
);
1194 got_object_tree_close(subtree1
);
1196 got_object_tree_close(subtree2
);
1198 got_object_blob_close(blob1
);
1200 got_object_blob_close(blob2
);
1204 static const struct got_error
*
1205 show_object_id(struct got_diff_line
**lines
, size_t *nlines
,
1206 const char *obj_typestr
, int ch
, const char *id_str
, FILE *outfile
)
1208 const struct got_error
*err
;
1212 n
= fprintf(outfile
, "%s %c %s\n", obj_typestr
, ch
, id_str
);
1214 return got_error_from_errno("fprintf");
1216 if (lines
!= NULL
&& *lines
!= NULL
) {
1218 err
= add_line_metadata(lines
, nlines
, 0,
1219 GOT_DIFF_LINE_META
);
1223 outoff
= (*lines
)[*nlines
- 1].offset
;
1226 err
= add_line_metadata(lines
, nlines
, outoff
,
1227 GOT_DIFF_LINE_META
);
1235 static const struct got_error
*
1236 diff_objects_as_trees(struct got_diff_line
**lines
, size_t *nlines
,
1237 FILE *f1
, FILE *f2
, int fd1
, int fd2
,
1238 struct got_object_id
*id1
, struct got_object_id
*id2
,
1239 struct got_pathlist_head
*paths
, const char *label1
, const char *label2
,
1240 int diff_context
, int ignore_whitespace
, int force_text_diff
,
1241 struct got_diffstat_cb_arg
*dsa
, struct got_repository
*repo
,
1242 FILE *outfile
, enum got_diff_algorithm diff_algo
)
1244 const struct got_error
*err
;
1245 struct got_tree_object
*tree1
= NULL
, *tree2
= NULL
;
1246 struct got_diff_blob_output_unidiff_arg arg
;
1247 int want_linemeta
= (lines
!= NULL
&& *lines
!= NULL
);
1249 if (id1
== NULL
&& id2
== NULL
)
1250 return got_error(GOT_ERR_NO_OBJ
);
1253 err
= got_object_open_as_tree(&tree1
, repo
, id1
);
1258 err
= got_object_open_as_tree(&tree2
, repo
, id2
);
1263 arg
.diff_algo
= diff_algo
;
1264 arg
.diff_context
= diff_context
;
1265 arg
.ignore_whitespace
= ignore_whitespace
;
1266 arg
.force_text_diff
= force_text_diff
;
1268 arg
.outfile
= outfile
;
1269 if (want_linemeta
) {
1271 arg
.nlines
= *nlines
;
1276 if (paths
== NULL
|| TAILQ_EMPTY(paths
))
1277 err
= got_diff_tree(tree1
, tree2
, f1
, f2
, fd1
, fd2
, label1
,
1278 label2
, repo
, got_diff_blob_output_unidiff
, &arg
, 1);
1280 err
= diff_paths(tree1
, tree2
, f1
, f2
, fd1
, fd2
, paths
, repo
,
1281 got_diff_blob_output_unidiff
, &arg
);
1282 if (want_linemeta
) {
1283 *lines
= arg
.lines
; /* was likely re-allocated */
1284 *nlines
= arg
.nlines
;
1288 got_object_tree_close(tree1
);
1290 got_object_tree_close(tree2
);
1294 const struct got_error
*
1295 got_diff_objects_as_trees(struct got_diff_line
**lines
, size_t *nlines
,
1296 FILE *f1
, FILE *f2
, int fd1
, int fd2
,
1297 struct got_object_id
*id1
, struct got_object_id
*id2
,
1298 struct got_pathlist_head
*paths
, const char *label1
, const char *label2
,
1299 enum got_diff_algorithm diff_algo
, int diff_context
, int ignore_whitespace
,
1300 int force_text_diff
, struct got_diffstat_cb_arg
*dsa
,
1301 struct got_repository
*repo
, FILE *outfile
)
1303 const struct got_error
*err
;
1306 if (id1
== NULL
&& id2
== NULL
)
1307 return got_error(GOT_ERR_NO_OBJ
);
1310 err
= got_object_id_str(&idstr
, id1
);
1313 err
= show_object_id(lines
, nlines
, "tree", '-', idstr
, outfile
);
1319 err
= show_object_id(lines
, nlines
, "tree", '-', "/dev/null",
1326 err
= got_object_id_str(&idstr
, id2
);
1329 err
= show_object_id(lines
, nlines
, "tree", '+', idstr
, outfile
);
1335 err
= show_object_id(lines
, nlines
, "tree", '+', "/dev/null",
1341 err
= diff_objects_as_trees(lines
, nlines
, f1
, f2
, fd1
, fd2
, id1
, id2
,
1342 paths
, label1
, label2
, diff_context
, ignore_whitespace
,
1343 force_text_diff
, dsa
, repo
, outfile
, diff_algo
);
1349 const struct got_error
*
1350 got_diff_objects_as_commits(struct got_diff_line
**lines
, size_t *nlines
,
1351 FILE *f1
, FILE *f2
, int fd1
, int fd2
,
1352 struct got_object_id
*id1
, struct got_object_id
*id2
,
1353 struct got_pathlist_head
*paths
, enum got_diff_algorithm diff_algo
,
1354 int diff_context
, int ignore_whitespace
, int force_text_diff
,
1355 struct got_diffstat_cb_arg
*dsa
, struct got_repository
*repo
, FILE *outfile
)
1357 const struct got_error
*err
;
1358 struct got_commit_object
*commit1
= NULL
, *commit2
= NULL
;
1362 return got_error(GOT_ERR_NO_OBJ
);
1365 err
= got_object_open_as_commit(&commit1
, repo
, id1
);
1368 err
= got_object_id_str(&idstr
, id1
);
1371 err
= show_object_id(lines
, nlines
, "commit", '-', idstr
,
1378 err
= show_object_id(lines
, nlines
, "commit", '-', "/dev/null",
1384 err
= got_object_open_as_commit(&commit2
, repo
, id2
);
1388 err
= got_object_id_str(&idstr
, id2
);
1391 err
= show_object_id(lines
, nlines
, "commit", '+', idstr
, outfile
);
1395 err
= diff_objects_as_trees(lines
, nlines
, f1
, f2
, fd1
, fd2
,
1396 commit1
? got_object_commit_get_tree_id(commit1
) : NULL
,
1397 got_object_commit_get_tree_id(commit2
), paths
, "", "",
1398 diff_context
, ignore_whitespace
, force_text_diff
, dsa
, repo
,
1399 outfile
, diff_algo
);
1402 got_object_commit_close(commit1
);
1404 got_object_commit_close(commit2
);
1409 const struct got_error
*
1410 got_diff_files(struct got_diffreg_result
**resultp
,
1411 FILE *f1
, int f1_exists
, const char *label1
, FILE *f2
, int f2_exists
,
1412 const char *label2
, int diff_context
, int ignore_whitespace
,
1413 int force_text_diff
, FILE *outfile
, enum got_diff_algorithm diff_algo
)
1415 const struct got_error
*err
= NULL
;
1416 struct got_diffreg_result
*diffreg_result
= NULL
;
1422 fprintf(outfile
, "file - %s\n",
1423 f1_exists
? label1
: "/dev/null");
1424 fprintf(outfile
, "file + %s\n",
1425 f2_exists
? label2
: "/dev/null");
1428 err
= got_diffreg(&diffreg_result
, f1
, f2
, diff_algo
,
1429 ignore_whitespace
, force_text_diff
);
1434 err
= got_diffreg_output(NULL
, NULL
, diffreg_result
,
1435 f1_exists
, f2_exists
, label1
, label2
,
1436 GOT_DIFF_OUTPUT_UNIDIFF
, diff_context
, outfile
);
1442 if (resultp
&& err
== NULL
)
1443 *resultp
= diffreg_result
;
1444 else if (diffreg_result
) {
1445 const struct got_error
*free_err
;
1447 free_err
= got_diffreg_result_free(diffreg_result
);
1448 if (free_err
&& err
== NULL
)