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
) {
151 outoff
= (*lines
)[*nlines
- 1].offset
;
153 err
= add_line_metadata(lines
, nlines
,
154 0, GOT_DIFF_LINE_NONE
);
164 err
= got_opentemp_truncate(f1
);
169 err
= got_opentemp_truncate(f2
);
176 idstr1
= got_object_blob_id_str(blob1
, hex1
, sizeof(hex1
));
177 err
= got_object_blob_dump_to_file(&size1
, NULL
, NULL
, f1
,
182 idstr1
= "/dev/null";
186 idstr2
= got_object_blob_id_str(blob2
, hex2
, sizeof(hex2
));
187 err
= got_object_blob_dump_to_file(&size2
, NULL
, NULL
, f2
,
192 idstr2
= "/dev/null";
197 if (mode1
&& mode1
!= mode2
) {
201 modebits
= (S_IRWXU
| S_IRWXG
| S_IRWXO
);
202 if (asprintf(&modestr1
, " (mode %o)",
203 mode1
& modebits
) == -1) {
204 err
= got_error_from_errno("asprintf");
208 if (mode2
&& mode1
!= mode2
) {
212 modebits
= (S_IRWXU
| S_IRWXG
| S_IRWXO
);
213 if (asprintf(&modestr2
, " (mode %o)",
214 mode2
& modebits
) == -1) {
215 err
= got_error_from_errno("asprintf");
219 n
= fprintf(outfile
, "blob - %s%s\n", idstr1
,
220 modestr1
? modestr1
: "");
224 if (lines
&& *lines
) {
225 err
= add_line_metadata(lines
, nlines
, outoff
,
226 GOT_DIFF_LINE_BLOB_MIN
);
231 n
= fprintf(outfile
, "blob + %s%s\n", idstr2
,
232 modestr2
? modestr2
: "");
236 if (lines
&& *lines
) {
237 err
= add_line_metadata(lines
, nlines
, outoff
,
238 GOT_DIFF_LINE_BLOB_PLUS
);
244 err
= got_diffreg(&result
, f1
, f2
, diff_algo
, ignore_whitespace
,
251 int status
= GOT_STATUS_NO_CHANGE
;
254 status
= GOT_STATUS_ADD
;
255 else if (blob2
== NULL
)
256 status
= GOT_STATUS_DELETE
;
258 if (strcmp(idstr1
, idstr2
) != 0)
259 status
= GOT_STATUS_MODIFY
;
260 else if (mode1
!= mode2
)
261 status
= GOT_STATUS_MODE_CHANGE
;
264 if (label1
== NULL
&& label2
== NULL
) {
265 /* diffstat of blobs, show hash instead of path */
266 if (asprintf(&path
, "%.10s -> %.10s",
267 idstr1
, idstr2
) == -1) {
268 err
= got_error_from_errno("asprintf");
272 if (label2
!= NULL
&&
273 (status
!= GOT_STATUS_DELETE
|| label1
== NULL
))
274 path
= strdup(label2
);
276 path
= strdup(label1
);
278 err
= got_error_from_errno("strdup");
283 err
= get_diffstat(diffstat
, path
, result
->result
,
284 force_text_diff
, status
);
292 err
= got_diffreg_output(lines
, nlines
, result
,
293 blob1
!= NULL
, blob2
!= NULL
,
294 label1
? label1
: idstr1
,
295 label2
? label2
: idstr2
,
296 GOT_DIFF_OUTPUT_UNIDIFF
, diff_context
, outfile
);
304 if (resultp
&& err
== NULL
)
307 free_err
= got_diffreg_result_free(result
);
308 if (free_err
&& err
== NULL
)
315 const struct got_error
*
316 got_diff_blob_output_unidiff(void *arg
, struct got_blob_object
*blob1
,
317 struct got_blob_object
*blob2
, FILE *f1
, FILE *f2
,
318 struct got_object_id
*id1
, struct got_object_id
*id2
,
319 const char *label1
, const char *label2
, mode_t mode1
, mode_t mode2
,
320 struct got_repository
*repo
)
322 struct got_diff_blob_output_unidiff_arg
*a
= arg
;
324 return diff_blobs(&a
->lines
, &a
->nlines
, NULL
,
325 blob1
, blob2
, f1
, f2
, label1
, label2
, mode1
, mode2
, a
->diff_context
,
326 a
->ignore_whitespace
, a
->force_text_diff
, a
->diffstat
, a
->outfile
,
330 const struct got_error
*
331 got_diff_blob(struct got_diff_line
**lines
, size_t*nlines
,
332 struct got_blob_object
*blob1
, struct got_blob_object
*blob2
,
333 FILE *f1
, FILE *f2
, const char *label1
, const char *label2
,
334 enum got_diff_algorithm diff_algo
, int diff_context
,
335 int ignore_whitespace
, int force_text_diff
,
336 struct got_diffstat_cb_arg
*ds
, FILE *outfile
)
338 return diff_blobs(lines
, nlines
, NULL
, blob1
, blob2
, f1
, f2
,
339 label1
, label2
, 0, 0, diff_context
, ignore_whitespace
,
340 force_text_diff
, ds
, outfile
, diff_algo
);
343 static const struct got_error
*
344 diff_blob_file(struct got_diffreg_result
**resultp
,
345 struct got_blob_object
*blob1
, FILE *f1
, off_t size1
, const char *label1
,
346 FILE *f2
, int f2_exists
, struct stat
*sb2
, const char *label2
,
347 enum got_diff_algorithm diff_algo
, int diff_context
, int ignore_whitespace
,
348 int force_text_diff
, struct got_diffstat_cb_arg
*diffstat
, FILE *outfile
)
350 const struct got_error
*err
= NULL
, *free_err
;
351 char hex1
[GOT_OBJECT_ID_HEX_MAXLEN
];
352 const char *idstr1
= NULL
;
353 struct got_diffreg_result
*result
= NULL
;
359 idstr1
= got_object_blob_id_str(blob1
, hex1
, sizeof(hex1
));
361 idstr1
= "/dev/null";
366 /* display file mode for new added files only */
367 if (f2_exists
&& blob1
== NULL
) {
368 int mmask
= (S_IRWXU
| S_IRWXG
| S_IRWXO
);
370 if (S_ISLNK(sb2
->st_mode
))
372 if (asprintf(&mode
, " (mode %o)",
373 sb2
->st_mode
& mmask
) == -1)
374 return got_error_from_errno("asprintf");
376 fprintf(outfile
, "blob - %s\n", label1
? label1
: idstr1
);
377 fprintf(outfile
, "file + %s%s\n",
378 f2_exists
? label2
: "/dev/null", mode
? mode
: "");
382 err
= got_diffreg(&result
, f1
, f2
, diff_algo
, ignore_whitespace
,
385 char msg
[GOT_ERR_MAX_MSG_SIZE
];
386 if (snprintf(msg
, sizeof(msg
), "%s vs %s: %s",
387 label1
? label1
: idstr1
,
388 f2_exists
? label2
: "/dev/null", err
->msg
) >= 0) {
389 err
= got_error_msg(err
->code
, msg
);
395 err
= got_diffreg_output(NULL
, NULL
, result
,
396 blob1
!= NULL
, f2_exists
,
397 label2
, /* show local file's path, not a blob ID */
398 label2
, GOT_DIFF_OUTPUT_UNIDIFF
,
399 diff_context
, outfile
);
406 int status
= GOT_STATUS_NO_CHANGE
;
409 * Ignore 'm'ode status change: if there's no accompanying
410 * content change, there'll be no diffstat, and if there
411 * are actual changes, 'M'odified takes precedence.
414 status
= GOT_STATUS_ADD
;
416 status
= GOT_STATUS_DELETE
;
418 status
= GOT_STATUS_MODIFY
;
420 if (label2
!= NULL
&&
421 (status
!= GOT_STATUS_DELETE
|| label1
== NULL
))
422 path
= strdup(label2
);
424 path
= strdup(label1
);
426 err
= got_error_from_errno("strdup");
430 err
= get_diffstat(diffstat
, path
, result
->result
,
431 force_text_diff
, status
);
439 if (resultp
&& err
== NULL
)
442 free_err
= got_diffreg_result_free(result
);
443 if (free_err
&& err
== NULL
)
449 const struct got_error
*
450 got_diff_blob_file(struct got_blob_object
*blob1
, FILE *f1
, off_t size1
,
451 const char *label1
, FILE *f2
, int f2_exists
, struct stat
*sb2
,
452 const char *label2
, enum got_diff_algorithm diff_algo
, int diff_context
,
453 int ignore_whitespace
, int force_text_diff
,
454 struct got_diffstat_cb_arg
*ds
, FILE *outfile
)
456 return diff_blob_file(NULL
, blob1
, f1
, size1
, label1
, f2
, f2_exists
,
457 sb2
, label2
, diff_algo
, diff_context
, ignore_whitespace
,
458 force_text_diff
, ds
, outfile
);
461 static const struct got_error
*
462 diff_added_blob(struct got_object_id
*id
, FILE *f1
, FILE *f2
, int fd2
,
463 const char *label
, mode_t mode
, struct got_repository
*repo
,
464 got_diff_blob_cb cb
, void *cb_arg
)
466 const struct got_error
*err
;
467 struct got_blob_object
*blob
= NULL
;
468 struct got_object
*obj
= NULL
;
470 err
= got_object_open(&obj
, repo
, id
);
474 err
= got_object_blob_open(&blob
, repo
, obj
, 8192, fd2
);
477 err
= cb(cb_arg
, NULL
, blob
, f1
, f2
, NULL
, id
,
478 NULL
, label
, 0, mode
, repo
);
480 got_object_close(obj
);
482 got_object_blob_close(blob
);
486 static const struct got_error
*
487 diff_modified_blob(struct got_object_id
*id1
, struct got_object_id
*id2
,
488 FILE *f1
, FILE *f2
, int fd1
, int fd2
,
489 const char *label1
, const char *label2
,
490 mode_t mode1
, mode_t mode2
, struct got_repository
*repo
,
491 got_diff_blob_cb cb
, void *cb_arg
)
493 const struct got_error
*err
;
494 struct got_object
*obj1
= NULL
;
495 struct got_object
*obj2
= NULL
;
496 struct got_blob_object
*blob1
= NULL
;
497 struct got_blob_object
*blob2
= NULL
;
499 err
= got_object_open(&obj1
, repo
, id1
);
503 if (obj1
->type
!= GOT_OBJ_TYPE_BLOB
) {
504 err
= got_error(GOT_ERR_OBJ_TYPE
);
508 err
= got_object_open(&obj2
, repo
, id2
);
511 if (obj2
->type
!= GOT_OBJ_TYPE_BLOB
) {
512 err
= got_error(GOT_ERR_OBJ_TYPE
);
516 err
= got_object_blob_open(&blob1
, repo
, obj1
, 8192, fd1
);
520 err
= got_object_blob_open(&blob2
, repo
, obj2
, 8192, fd2
);
524 err
= cb(cb_arg
, blob1
, blob2
, f1
, f2
, id1
, id2
, label1
, label2
,
528 got_object_close(obj1
);
530 got_object_close(obj2
);
532 got_object_blob_close(blob1
);
534 got_object_blob_close(blob2
);
538 static const struct got_error
*
539 diff_deleted_blob(struct got_object_id
*id
, FILE *f1
, int fd1
,
540 FILE *f2
, const char *label
, mode_t mode
, struct got_repository
*repo
,
541 got_diff_blob_cb cb
, void *cb_arg
)
543 const struct got_error
*err
;
544 struct got_blob_object
*blob
= NULL
;
545 struct got_object
*obj
= NULL
;
547 err
= got_object_open(&obj
, repo
, id
);
551 err
= got_object_blob_open(&blob
, repo
, obj
, 8192, fd1
);
554 err
= cb(cb_arg
, blob
, NULL
, f1
, f2
, id
, NULL
, label
, NULL
,
557 got_object_close(obj
);
559 got_object_blob_close(blob
);
563 static const struct got_error
*
564 diff_added_tree(struct got_object_id
*id
, FILE *f1
, FILE *f2
, int fd2
,
565 const char *label
, struct got_repository
*repo
, got_diff_blob_cb cb
,
566 void *cb_arg
, int diff_content
)
568 const struct got_error
*err
= NULL
;
569 struct got_object
*treeobj
= NULL
;
570 struct got_tree_object
*tree
= NULL
;
572 err
= got_object_open(&treeobj
, repo
, id
);
576 if (treeobj
->type
!= GOT_OBJ_TYPE_TREE
) {
577 err
= got_error(GOT_ERR_OBJ_TYPE
);
581 err
= got_object_tree_open(&tree
, repo
, treeobj
);
585 err
= got_diff_tree(NULL
, tree
, f1
, f2
, -1, fd2
, NULL
, label
,
586 repo
, cb
, cb_arg
, diff_content
);
589 got_object_tree_close(tree
);
591 got_object_close(treeobj
);
595 static const struct got_error
*
596 diff_modified_tree(struct got_object_id
*id1
, struct got_object_id
*id2
,
597 FILE *f1
, FILE *f2
, int fd1
, int fd2
,
598 const char *label1
, const char *label2
,
599 struct got_repository
*repo
, got_diff_blob_cb cb
, void *cb_arg
,
602 const struct got_error
*err
;
603 struct got_object
*treeobj1
= NULL
;
604 struct got_object
*treeobj2
= NULL
;
605 struct got_tree_object
*tree1
= NULL
;
606 struct got_tree_object
*tree2
= NULL
;
608 err
= got_object_open(&treeobj1
, repo
, id1
);
612 if (treeobj1
->type
!= GOT_OBJ_TYPE_TREE
) {
613 err
= got_error(GOT_ERR_OBJ_TYPE
);
617 err
= got_object_open(&treeobj2
, repo
, id2
);
621 if (treeobj2
->type
!= GOT_OBJ_TYPE_TREE
) {
622 err
= got_error(GOT_ERR_OBJ_TYPE
);
626 err
= got_object_tree_open(&tree1
, repo
, treeobj1
);
630 err
= got_object_tree_open(&tree2
, repo
, treeobj2
);
634 err
= got_diff_tree(tree1
, tree2
, f1
, f2
, fd1
, fd2
,
635 label1
, label2
, repo
, cb
, cb_arg
, diff_content
);
639 got_object_tree_close(tree1
);
641 got_object_tree_close(tree2
);
643 got_object_close(treeobj1
);
645 got_object_close(treeobj2
);
649 static const struct got_error
*
650 diff_deleted_tree(struct got_object_id
*id
, FILE *f1
, int fd1
,
651 FILE *f2
, const char *label
, struct got_repository
*repo
,
652 got_diff_blob_cb cb
, void *cb_arg
, int diff_content
)
654 const struct got_error
*err
;
655 struct got_object
*treeobj
= NULL
;
656 struct got_tree_object
*tree
= NULL
;
658 err
= got_object_open(&treeobj
, repo
, id
);
662 if (treeobj
->type
!= GOT_OBJ_TYPE_TREE
) {
663 err
= got_error(GOT_ERR_OBJ_TYPE
);
667 err
= got_object_tree_open(&tree
, repo
, treeobj
);
671 err
= got_diff_tree(tree
, NULL
, f1
, f2
, fd1
, -1, label
, NULL
,
672 repo
, cb
, cb_arg
, diff_content
);
675 got_object_tree_close(tree
);
677 got_object_close(treeobj
);
681 static const struct got_error
*
682 diff_kind_mismatch(struct got_tree_entry
*te1
, struct got_tree_entry
*te2
,
683 FILE *f1
, FILE *f2
, int fd1
, int fd2
,
684 const char *label1
, const char *label2
, struct got_repository
*repo
,
685 got_diff_blob_cb cb
, void *cb_arg
, int diff_content
)
687 const struct got_error
*err
= NULL
;
690 * Handle files changing into directories and vice-versa.
691 * Disregard edge cases with FIFOs, device nodes, etc for now.
693 if (!S_ISDIR(te1
->mode
) && S_ISDIR(te2
->mode
)) {
694 if (S_ISREG(te1
->mode
)) {
696 err
= diff_deleted_blob(&te1
->id
, f1
, fd1
,
697 f2
, label1
, te1
->mode
, repo
, cb
, cb_arg
);
699 err
= cb(cb_arg
, NULL
, NULL
, NULL
, NULL
,
700 &te1
->id
, NULL
, label1
, NULL
,
706 return diff_added_tree(&te2
->id
, f1
, f2
, fd2
, label2
,
707 repo
, cb
, cb_arg
, diff_content
);
708 } else if (S_ISDIR(te1
->mode
) && !S_ISDIR(te2
->mode
)) {
709 err
= diff_deleted_tree(&te1
->id
, f1
, fd1
, f2
,
710 label1
, repo
, cb
, cb_arg
, diff_content
);
713 if (S_ISREG(te2
->mode
)) {
715 err
= diff_added_blob(&te2
->id
, f1
, f2
, fd2
,
716 label2
, te2
->mode
, repo
, cb
, cb_arg
);
718 err
= cb(cb_arg
, NULL
, NULL
, NULL
, NULL
, NULL
,
719 &te2
->id
, NULL
, label2
, 0, te2
->mode
, repo
);
729 static const struct got_error
*
730 diff_entry_old_new(struct got_tree_entry
*te1
, struct got_tree_entry
*te2
,
731 FILE *f1
, FILE *f2
, int fd1
, int fd2
,
732 const char *label1
, const char *label2
,
733 struct got_repository
*repo
, got_diff_blob_cb cb
, void *cb_arg
,
736 const struct got_error
*err
= NULL
;
739 if (got_object_tree_entry_is_submodule(te1
))
743 if (S_ISDIR(te1
->mode
))
744 err
= diff_deleted_tree(&te1
->id
, f1
, fd1
, f2
,
745 label1
, repo
, cb
, cb_arg
, diff_content
);
748 err
= diff_deleted_blob(&te1
->id
, f1
, fd1
,
749 f2
, label1
, te1
->mode
, repo
, cb
, cb_arg
);
751 err
= cb(cb_arg
, NULL
, NULL
, NULL
, NULL
,
752 &te1
->id
, NULL
, label1
, NULL
,
756 } else if (got_object_tree_entry_is_submodule(te2
))
759 id_match
= (got_object_id_cmp(&te1
->id
, &te2
->id
) == 0);
760 if (S_ISDIR(te1
->mode
) && S_ISDIR(te2
->mode
)) {
762 return diff_modified_tree(&te1
->id
, &te2
->id
, f1
, f2
,
763 fd1
, fd2
, label1
, label2
, repo
, cb
, cb_arg
,
765 } else if ((S_ISREG(te1
->mode
) || S_ISLNK(te1
->mode
)) &&
766 (S_ISREG(te2
->mode
) || S_ISLNK(te2
->mode
))) {
768 ((te1
->mode
& (S_IFLNK
| S_IXUSR
))) !=
769 (te2
->mode
& (S_IFLNK
| S_IXUSR
))) {
771 return diff_modified_blob(&te1
->id
, &te2
->id
,
772 f1
, f2
, fd1
, fd2
, label1
, label2
,
773 te1
->mode
, te2
->mode
, repo
, cb
, cb_arg
);
775 return cb(cb_arg
, NULL
, NULL
, NULL
, NULL
,
776 &te1
->id
, &te2
->id
, label1
, label2
,
777 te1
->mode
, te2
->mode
, repo
);
784 return diff_kind_mismatch(te1
, te2
, f1
, f2
, fd1
, fd2
,
785 label1
, label2
, repo
, cb
, cb_arg
, diff_content
);
788 static const struct got_error
*
789 diff_entry_new_old(struct got_tree_entry
*te2
,
790 struct got_tree_entry
*te1
, FILE *f1
, FILE *f2
, int fd2
, const char *label2
,
791 struct got_repository
*repo
, got_diff_blob_cb cb
, void *cb_arg
,
794 if (te1
!= NULL
) /* handled by diff_entry_old_new() */
797 if (got_object_tree_entry_is_submodule(te2
))
800 if (S_ISDIR(te2
->mode
))
801 return diff_added_tree(&te2
->id
, f1
, f2
, fd2
, label2
,
802 repo
, cb
, cb_arg
, diff_content
);
805 return diff_added_blob(&te2
->id
, f1
, f2
, fd2
,
806 label2
, te2
->mode
, repo
, cb
, cb_arg
);
808 return cb(cb_arg
, NULL
, NULL
, NULL
, NULL
, NULL
, &te2
->id
,
809 NULL
, label2
, 0, te2
->mode
, repo
);
812 const struct got_error
*
813 got_diff_tree_compute_diffstat(void *arg
, struct got_blob_object
*blob1
,
814 struct got_blob_object
*blob2
, FILE *f1
, FILE *f2
,
815 struct got_object_id
*id1
, struct got_object_id
*id2
,
816 const char *label1
, const char *label2
,
817 mode_t mode1
, mode_t mode2
, struct got_repository
*repo
)
819 const struct got_error
*err
= NULL
;
820 struct got_diffreg_result
*result
= NULL
;
821 struct got_diffstat_cb_arg
*a
= arg
;
823 int status
= GOT_STATUS_NO_CHANGE
;
825 path
= strdup(label2
? label2
: label1
);
827 return got_error_from_errno("strdup");
830 status
= GOT_STATUS_ADD
;
831 else if (id2
== NULL
)
832 status
= GOT_STATUS_DELETE
;
834 if (got_object_id_cmp(id1
, id2
) != 0)
835 status
= GOT_STATUS_MODIFY
;
836 else if (mode1
!= mode2
)
837 status
= GOT_STATUS_MODE_CHANGE
;
841 err
= got_opentemp_truncate(f1
);
846 err
= got_opentemp_truncate(f2
);
852 err
= got_object_blob_dump_to_file(NULL
, NULL
, NULL
, f1
,
858 err
= got_object_blob_dump_to_file(NULL
, NULL
, NULL
, f2
,
864 err
= got_diffreg(&result
, f1
, f2
, a
->diff_algo
, a
->ignore_ws
,
869 err
= get_diffstat(a
, path
, result
->result
, a
->force_text
, status
);
873 const struct got_error
*free_err
;
875 free_err
= got_diffreg_result_free(result
);
876 if (free_err
&& err
== NULL
)
884 const struct got_error
*
885 got_diff_tree_collect_changed_paths(void *arg
, struct got_blob_object
*blob1
,
886 struct got_blob_object
*blob2
, FILE *f1
, FILE *f2
,
887 struct got_object_id
*id1
, struct got_object_id
*id2
,
888 const char *label1
, const char *label2
,
889 mode_t mode1
, mode_t mode2
, struct got_repository
*repo
)
891 const struct got_error
*err
= NULL
;
892 struct got_pathlist_head
*paths
= arg
;
893 struct got_diff_changed_path
*change
= NULL
;
896 path
= strdup(label2
? label2
: label1
);
898 return got_error_from_errno("strdup");
900 change
= malloc(sizeof(*change
));
901 if (change
== NULL
) {
902 err
= got_error_from_errno("malloc");
906 change
->status
= GOT_STATUS_NO_CHANGE
;
908 change
->status
= GOT_STATUS_ADD
;
909 else if (id2
== NULL
)
910 change
->status
= GOT_STATUS_DELETE
;
912 if (got_object_id_cmp(id1
, id2
) != 0)
913 change
->status
= GOT_STATUS_MODIFY
;
914 else if (mode1
!= mode2
)
915 change
->status
= GOT_STATUS_MODE_CHANGE
;
918 err
= got_pathlist_append(paths
, path
, change
);
927 const struct got_error
*
928 got_diff_tree(struct got_tree_object
*tree1
, struct got_tree_object
*tree2
,
929 FILE *f1
, FILE *f2
, int fd1
, int fd2
,
930 const char *label1
, const char *label2
,
931 struct got_repository
*repo
, got_diff_blob_cb cb
, void *cb_arg
,
934 const struct got_error
*err
= NULL
;
935 struct got_tree_entry
*te1
= NULL
;
936 struct got_tree_entry
*te2
= NULL
;
937 char *l1
= NULL
, *l2
= NULL
;
938 int tidx1
= 0, tidx2
= 0;
941 te1
= got_object_tree_get_entry(tree1
, 0);
942 if (te1
&& asprintf(&l1
, "%s%s%s", label1
, label1
[0] ? "/" : "",
944 return got_error_from_errno("asprintf");
947 te2
= got_object_tree_get_entry(tree2
, 0);
948 if (te2
&& asprintf(&l2
, "%s%s%s", label2
, label2
[0] ? "/" : "",
950 err
= got_error_from_errno("asprintf");
957 struct got_tree_entry
*te
= NULL
;
960 te
= got_object_tree_find_entry(tree2
,
965 if (te
&& asprintf(&l2
, "%s%s%s", label2
,
966 label2
[0] ? "/" : "", te
->name
) == -1) {
967 err
= got_error_from_errno("asprintf");
972 err
= diff_entry_old_new(te1
, te
, f1
, f2
, fd1
, fd2
,
973 l1
, l2
, repo
, cb
, cb_arg
, diff_content
);
979 struct got_tree_entry
*te
= NULL
;
982 te
= got_object_tree_find_entry(tree1
,
988 if (asprintf(&l2
, "%s%s%s", label2
,
989 label2
[0] ? "/" : "", te
->name
) == -1) {
990 err
= got_error_from_errno("asprintf");
994 if (asprintf(&l2
, "%s%s%s", label2
,
995 label2
[0] ? "/" : "", te2
->name
) == -1) {
996 err
= got_error_from_errno("asprintf");
1001 err
= diff_entry_new_old(te2
, te
, f1
, f2
, fd2
, l2
,
1002 repo
, cb
, cb_arg
, diff_content
);
1011 te1
= got_object_tree_get_entry(tree1
, tidx1
);
1013 asprintf(&l1
, "%s%s%s", label1
,
1014 label1
[0] ? "/" : "", te1
->name
) == -1) {
1015 err
= got_error_from_errno("asprintf");
1024 te2
= got_object_tree_get_entry(tree2
, tidx2
);
1026 asprintf(&l2
, "%s%s%s", label2
,
1027 label2
[0] ? "/" : "", te2
->name
) == -1) {
1028 err
= got_error_from_errno("asprintf");
1032 } while (te1
|| te2
);
1040 const struct got_error
*
1041 got_diff_objects_as_blobs(struct got_diff_line
**lines
, size_t *nlines
,
1042 FILE *f1
, FILE *f2
, int fd1
, int fd2
,
1043 struct got_object_id
*id1
, struct got_object_id
*id2
,
1044 const char *label1
, const char *label2
,
1045 enum got_diff_algorithm diff_algo
, int diff_context
,
1046 int ignore_whitespace
, int force_text_diff
, struct got_diffstat_cb_arg
*ds
,
1047 struct got_repository
*repo
, FILE *outfile
)
1049 const struct got_error
*err
;
1050 struct got_blob_object
*blob1
= NULL
, *blob2
= NULL
;
1052 if (id1
== NULL
&& id2
== NULL
)
1053 return got_error(GOT_ERR_NO_OBJ
);
1056 err
= got_object_open_as_blob(&blob1
, repo
, id1
, 8192, fd1
);
1061 err
= got_object_open_as_blob(&blob2
, repo
, id2
, 8192, fd2
);
1065 err
= got_diff_blob(lines
, nlines
, blob1
, blob2
, f1
, f2
, label1
, label2
,
1066 diff_algo
, diff_context
, ignore_whitespace
, force_text_diff
,
1070 got_object_blob_close(blob1
);
1072 got_object_blob_close(blob2
);
1076 static const struct got_error
*
1077 diff_paths(struct got_tree_object
*tree1
, struct got_tree_object
*tree2
,
1078 FILE *f1
, FILE *f2
, int fd1
, int fd2
, struct got_pathlist_head
*paths
,
1079 struct got_repository
*repo
, got_diff_blob_cb cb
, void *cb_arg
)
1081 const struct got_error
*err
= NULL
;
1082 struct got_pathlist_entry
*pe
;
1083 struct got_object_id
*id1
= NULL
, *id2
= NULL
;
1084 struct got_tree_object
*subtree1
= NULL
, *subtree2
= NULL
;
1085 struct got_blob_object
*blob1
= NULL
, *blob2
= NULL
;
1087 TAILQ_FOREACH(pe
, paths
, entry
) {
1088 int type1
= GOT_OBJ_TYPE_ANY
, type2
= GOT_OBJ_TYPE_ANY
;
1089 mode_t mode1
= 0, mode2
= 0;
1096 got_object_tree_close(subtree1
);
1100 got_object_tree_close(subtree2
);
1104 got_object_blob_close(blob1
);
1108 got_object_blob_close(blob2
);
1112 err
= got_object_tree_find_path(&id1
, &mode1
, repo
,
1114 if (err
&& err
->code
!= GOT_ERR_NO_TREE_ENTRY
)
1118 err
= got_object_tree_find_path(&id2
, &mode2
, repo
,
1120 if (err
&& err
->code
!= GOT_ERR_NO_TREE_ENTRY
)
1123 if (id1
== NULL
&& id2
== NULL
) {
1124 err
= got_error_path(pe
->path
, GOT_ERR_NO_TREE_ENTRY
);
1128 err
= got_object_get_type(&type1
, repo
, id1
);
1133 err
= got_object_get_type(&type2
, repo
, id2
);
1137 if (type1
== GOT_OBJ_TYPE_ANY
&&
1138 type2
== GOT_OBJ_TYPE_ANY
) {
1139 err
= got_error_path(pe
->path
, GOT_ERR_NO_OBJ
);
1141 } else if (type1
!= GOT_OBJ_TYPE_ANY
&&
1142 type2
!= GOT_OBJ_TYPE_ANY
&& type1
!= type2
) {
1143 err
= got_error(GOT_ERR_OBJ_TYPE
);
1147 if (type1
== GOT_OBJ_TYPE_BLOB
||
1148 type2
== GOT_OBJ_TYPE_BLOB
) {
1150 err
= got_object_open_as_blob(&blob1
, repo
,
1156 err
= got_object_open_as_blob(&blob2
, repo
,
1161 err
= cb(cb_arg
, blob1
, blob2
, f1
, f2
, id1
, id2
,
1162 id1
? pe
->path
: "/dev/null",
1163 id2
? pe
->path
: "/dev/null",
1164 mode1
, mode2
, repo
);
1167 } else if (type1
== GOT_OBJ_TYPE_TREE
||
1168 type2
== GOT_OBJ_TYPE_TREE
) {
1170 err
= got_object_open_as_tree(&subtree1
, repo
,
1176 err
= got_object_open_as_tree(&subtree2
, repo
,
1181 err
= got_diff_tree(subtree1
, subtree2
, f1
, f2
,
1183 id1
? pe
->path
: "/dev/null",
1184 id2
? pe
->path
: "/dev/null",
1185 repo
, cb
, cb_arg
, 1);
1189 err
= got_error(GOT_ERR_OBJ_TYPE
);
1197 got_object_tree_close(subtree1
);
1199 got_object_tree_close(subtree2
);
1201 got_object_blob_close(blob1
);
1203 got_object_blob_close(blob2
);
1207 static const struct got_error
*
1208 show_object_id(struct got_diff_line
**lines
, size_t *nlines
,
1209 const char *obj_typestr
, int ch
, const char *id_str
, FILE *outfile
)
1211 const struct got_error
*err
;
1215 n
= fprintf(outfile
, "%s %c %s\n", obj_typestr
, ch
, id_str
);
1217 return got_error_from_errno("fprintf");
1219 if (lines
!= NULL
&& *lines
!= NULL
) {
1221 err
= add_line_metadata(lines
, nlines
, 0,
1222 GOT_DIFF_LINE_META
);
1226 outoff
= (*lines
)[*nlines
- 1].offset
;
1229 err
= add_line_metadata(lines
, nlines
, outoff
,
1230 GOT_DIFF_LINE_META
);
1238 static const struct got_error
*
1239 diff_objects_as_trees(struct got_diff_line
**lines
, size_t *nlines
,
1240 FILE *f1
, FILE *f2
, int fd1
, int fd2
,
1241 struct got_object_id
*id1
, struct got_object_id
*id2
,
1242 struct got_pathlist_head
*paths
, const char *label1
, const char *label2
,
1243 int diff_context
, int ignore_whitespace
, int force_text_diff
,
1244 struct got_diffstat_cb_arg
*dsa
, struct got_repository
*repo
,
1245 FILE *outfile
, enum got_diff_algorithm diff_algo
)
1247 const struct got_error
*err
;
1248 struct got_tree_object
*tree1
= NULL
, *tree2
= NULL
;
1249 struct got_diff_blob_output_unidiff_arg arg
;
1250 int want_linemeta
= (lines
!= NULL
&& *lines
!= NULL
);
1252 if (id1
== NULL
&& id2
== NULL
)
1253 return got_error(GOT_ERR_NO_OBJ
);
1256 err
= got_object_open_as_tree(&tree1
, repo
, id1
);
1261 err
= got_object_open_as_tree(&tree2
, repo
, id2
);
1266 arg
.diff_algo
= diff_algo
;
1267 arg
.diff_context
= diff_context
;
1268 arg
.ignore_whitespace
= ignore_whitespace
;
1269 arg
.force_text_diff
= force_text_diff
;
1271 arg
.outfile
= outfile
;
1272 if (want_linemeta
) {
1274 arg
.nlines
= *nlines
;
1279 if (paths
== NULL
|| TAILQ_EMPTY(paths
))
1280 err
= got_diff_tree(tree1
, tree2
, f1
, f2
, fd1
, fd2
, label1
,
1281 label2
, repo
, got_diff_blob_output_unidiff
, &arg
, 1);
1283 err
= diff_paths(tree1
, tree2
, f1
, f2
, fd1
, fd2
, paths
, repo
,
1284 got_diff_blob_output_unidiff
, &arg
);
1285 if (want_linemeta
) {
1286 *lines
= arg
.lines
; /* was likely re-allocated */
1287 *nlines
= arg
.nlines
;
1291 got_object_tree_close(tree1
);
1293 got_object_tree_close(tree2
);
1297 const struct got_error
*
1298 got_diff_objects_as_trees(struct got_diff_line
**lines
, size_t *nlines
,
1299 FILE *f1
, FILE *f2
, int fd1
, int fd2
,
1300 struct got_object_id
*id1
, struct got_object_id
*id2
,
1301 struct got_pathlist_head
*paths
, const char *label1
, const char *label2
,
1302 enum got_diff_algorithm diff_algo
, int diff_context
, int ignore_whitespace
,
1303 int force_text_diff
, struct got_diffstat_cb_arg
*dsa
,
1304 struct got_repository
*repo
, FILE *outfile
)
1306 const struct got_error
*err
;
1309 if (id1
== NULL
&& id2
== NULL
)
1310 return got_error(GOT_ERR_NO_OBJ
);
1313 err
= got_object_id_str(&idstr
, id1
);
1316 err
= show_object_id(lines
, nlines
, "tree", '-', idstr
, outfile
);
1322 err
= show_object_id(lines
, nlines
, "tree", '-', "/dev/null",
1329 err
= got_object_id_str(&idstr
, id2
);
1332 err
= show_object_id(lines
, nlines
, "tree", '+', idstr
, outfile
);
1338 err
= show_object_id(lines
, nlines
, "tree", '+', "/dev/null",
1344 err
= diff_objects_as_trees(lines
, nlines
, f1
, f2
, fd1
, fd2
, id1
, id2
,
1345 paths
, label1
, label2
, diff_context
, ignore_whitespace
,
1346 force_text_diff
, dsa
, repo
, outfile
, diff_algo
);
1352 const struct got_error
*
1353 got_diff_objects_as_commits(struct got_diff_line
**lines
, size_t *nlines
,
1354 FILE *f1
, FILE *f2
, int fd1
, int fd2
,
1355 struct got_object_id
*id1
, struct got_object_id
*id2
,
1356 struct got_pathlist_head
*paths
, enum got_diff_algorithm diff_algo
,
1357 int diff_context
, int ignore_whitespace
, int force_text_diff
,
1358 struct got_diffstat_cb_arg
*dsa
, struct got_repository
*repo
, FILE *outfile
)
1360 const struct got_error
*err
;
1361 struct got_commit_object
*commit1
= NULL
, *commit2
= NULL
;
1365 return got_error(GOT_ERR_NO_OBJ
);
1368 err
= got_object_open_as_commit(&commit1
, repo
, id1
);
1371 err
= got_object_id_str(&idstr
, id1
);
1374 err
= show_object_id(lines
, nlines
, "commit", '-', idstr
,
1381 err
= show_object_id(lines
, nlines
, "commit", '-', "/dev/null",
1387 err
= got_object_open_as_commit(&commit2
, repo
, id2
);
1391 err
= got_object_id_str(&idstr
, id2
);
1394 err
= show_object_id(lines
, nlines
, "commit", '+', idstr
, outfile
);
1398 err
= diff_objects_as_trees(lines
, nlines
, f1
, f2
, fd1
, fd2
,
1399 commit1
? got_object_commit_get_tree_id(commit1
) : NULL
,
1400 got_object_commit_get_tree_id(commit2
), paths
, "", "",
1401 diff_context
, ignore_whitespace
, force_text_diff
, dsa
, repo
,
1402 outfile
, diff_algo
);
1405 got_object_commit_close(commit1
);
1407 got_object_commit_close(commit2
);
1412 const struct got_error
*
1413 got_diff_files(struct got_diffreg_result
**resultp
,
1414 FILE *f1
, int f1_exists
, const char *label1
, FILE *f2
, int f2_exists
,
1415 const char *label2
, int diff_context
, int ignore_whitespace
,
1416 int force_text_diff
, FILE *outfile
, enum got_diff_algorithm diff_algo
)
1418 const struct got_error
*err
= NULL
;
1419 struct got_diffreg_result
*diffreg_result
= NULL
;
1425 fprintf(outfile
, "file - %s\n",
1426 f1_exists
? label1
: "/dev/null");
1427 fprintf(outfile
, "file + %s\n",
1428 f2_exists
? label2
: "/dev/null");
1431 err
= got_diffreg(&diffreg_result
, f1
, f2
, diff_algo
,
1432 ignore_whitespace
, force_text_diff
);
1437 err
= got_diffreg_output(NULL
, NULL
, diffreg_result
,
1438 f1_exists
, f2_exists
, label1
, label2
,
1439 GOT_DIFF_OUTPUT_UNIDIFF
, diff_context
, outfile
);
1445 if (resultp
&& err
== NULL
)
1446 *resultp
= diffreg_result
;
1447 else if (diffreg_result
) {
1448 const struct got_error
*free_err
;
1450 free_err
= got_diffreg_result_free(diffreg_result
);
1451 if (free_err
&& err
== NULL
)