2 * Copyright (c) 2018, 2019, 2020 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>
21 #include <sys/ioctl.h>
26 #if defined(__FreeBSD__) || defined(__APPLE__)
27 #define _XOPEN_SOURCE_EXTENDED /* for ncurses wide-character functions */
48 #include "got_version.h"
49 #include "got_error.h"
50 #include "got_object.h"
51 #include "got_reference.h"
52 #include "got_repository.h"
53 #include "got_gotconfig.h"
55 #include "got_opentemp.h"
57 #include "got_cancel.h"
58 #include "got_commit_graph.h"
59 #include "got_blame.h"
60 #include "got_privsep.h"
62 #include "got_worktree.h"
63 #include "got_keyword.h"
66 #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b))
70 #define MAX(_a,_b) ((_a) > (_b) ? (_a) : (_b))
74 #define CTRL(x) ((x) & 0x1f)
78 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
83 const struct got_error
*(*cmd_main
)(int, char *[]);
84 void (*cmd_usage
)(void);
87 __dead
static void usage(int, int);
88 __dead
static void usage_log(void);
89 __dead
static void usage_diff(void);
90 __dead
static void usage_blame(void);
91 __dead
static void usage_tree(void);
92 __dead
static void usage_ref(void);
94 static const struct got_error
* cmd_log(int, char *[]);
95 static const struct got_error
* cmd_diff(int, char *[]);
96 static const struct got_error
* cmd_blame(int, char *[]);
97 static const struct got_error
* cmd_tree(int, char *[]);
98 static const struct got_error
* cmd_ref(int, char *[]);
100 static const struct tog_cmd tog_commands
[] = {
101 { "log", cmd_log
, usage_log
},
102 { "diff", cmd_diff
, usage_diff
},
103 { "blame", cmd_blame
, usage_blame
},
104 { "tree", cmd_tree
, usage_tree
},
105 { "ref", cmd_ref
, usage_ref
},
117 /* Match _DIFF to _HELP with enum tog_view_type TOG_VIEW_* counterparts. */
118 enum tog_keymap_type
{
119 TOG_KEYMAP_KEYS
= -2,
135 #define HSPLIT_SCALE 0.3f /* default horizontal split scale */
137 #define TOG_EOF_STRING "(END)"
139 struct commit_queue_entry
{
140 TAILQ_ENTRY(commit_queue_entry
) entry
;
141 struct got_object_id
*id
;
142 struct got_commit_object
*commit
;
146 TAILQ_HEAD(commit_queue_head
, commit_queue_entry
);
147 struct commit_queue
{
149 struct commit_queue_head head
;
153 STAILQ_ENTRY(tog_color
) entry
;
157 STAILQ_HEAD(tog_colors
, tog_color
);
159 static struct got_reflist_head tog_refs
= TAILQ_HEAD_INITIALIZER(tog_refs
);
160 static struct got_reflist_object_id_map
*tog_refs_idmap
;
162 struct got_object_id
*id
;
166 static enum got_diff_algorithm tog_diff_algo
= GOT_DIFF_ALGORITHM_PATIENCE
;
168 static const struct got_error
*
169 tog_ref_cmp_by_name(void *arg
, int *cmp
, struct got_reference
*re1
,
170 struct got_reference
* re2
)
172 const char *name1
= got_ref_get_name(re1
);
173 const char *name2
= got_ref_get_name(re2
);
174 int isbackup1
, isbackup2
;
176 /* Sort backup refs towards the bottom of the list. */
177 isbackup1
= strncmp(name1
, "refs/got/backup/", 16) == 0;
178 isbackup2
= strncmp(name2
, "refs/got/backup/", 16) == 0;
179 if (!isbackup1
&& isbackup2
) {
182 } else if (isbackup1
&& !isbackup2
) {
187 *cmp
= got_path_cmp(name1
, name2
, strlen(name1
), strlen(name2
));
191 static const struct got_error
*
192 tog_load_refs(struct got_repository
*repo
, int sort_by_date
)
194 const struct got_error
*err
;
196 err
= got_ref_list(&tog_refs
, repo
, NULL
, sort_by_date
?
197 got_ref_cmp_by_commit_timestamp_descending
: tog_ref_cmp_by_name
,
202 return got_reflist_object_id_map_create(&tog_refs_idmap
, &tog_refs
,
209 if (tog_refs_idmap
) {
210 got_reflist_object_id_map_free(tog_refs_idmap
);
211 tog_refs_idmap
= NULL
;
213 got_ref_list_free(&tog_refs
);
216 static const struct got_error
*
217 add_color(struct tog_colors
*colors
, const char *pattern
,
218 int idx
, short color
)
220 const struct got_error
*err
= NULL
;
221 struct tog_color
*tc
;
224 if (idx
< 1 || idx
> COLOR_PAIRS
- 1)
227 init_pair(idx
, color
, -1);
229 tc
= calloc(1, sizeof(*tc
));
231 return got_error_from_errno("calloc");
232 regerr
= regcomp(&tc
->regex
, pattern
,
233 REG_EXTENDED
| REG_NOSUB
| REG_NEWLINE
);
235 static char regerr_msg
[512];
236 static char err_msg
[512];
237 regerror(regerr
, &tc
->regex
, regerr_msg
,
239 snprintf(err_msg
, sizeof(err_msg
), "regcomp: %s",
241 err
= got_error_msg(GOT_ERR_REGEX
, err_msg
);
246 STAILQ_INSERT_HEAD(colors
, tc
, entry
);
251 free_colors(struct tog_colors
*colors
)
253 struct tog_color
*tc
;
255 while (!STAILQ_EMPTY(colors
)) {
256 tc
= STAILQ_FIRST(colors
);
257 STAILQ_REMOVE_HEAD(colors
, entry
);
263 static struct tog_color
*
264 get_color(struct tog_colors
*colors
, int colorpair
)
266 struct tog_color
*tc
= NULL
;
268 STAILQ_FOREACH(tc
, colors
, entry
) {
269 if (tc
->colorpair
== colorpair
)
277 default_color_value(const char *envvar
)
279 if (strcmp(envvar
, "TOG_COLOR_DIFF_MINUS") == 0)
280 return COLOR_MAGENTA
;
281 if (strcmp(envvar
, "TOG_COLOR_DIFF_PLUS") == 0)
283 if (strcmp(envvar
, "TOG_COLOR_DIFF_CHUNK_HEADER") == 0)
285 if (strcmp(envvar
, "TOG_COLOR_DIFF_META") == 0)
287 if (strcmp(envvar
, "TOG_COLOR_TREE_SUBMODULE") == 0)
288 return COLOR_MAGENTA
;
289 if (strcmp(envvar
, "TOG_COLOR_TREE_SYMLINK") == 0)
290 return COLOR_MAGENTA
;
291 if (strcmp(envvar
, "TOG_COLOR_TREE_DIRECTORY") == 0)
293 if (strcmp(envvar
, "TOG_COLOR_TREE_EXECUTABLE") == 0)
295 if (strcmp(envvar
, "TOG_COLOR_COMMIT") == 0)
297 if (strcmp(envvar
, "TOG_COLOR_AUTHOR") == 0)
299 if (strcmp(envvar
, "TOG_COLOR_DATE") == 0)
301 if (strcmp(envvar
, "TOG_COLOR_REFS_HEADS") == 0)
303 if (strcmp(envvar
, "TOG_COLOR_REFS_TAGS") == 0)
304 return COLOR_MAGENTA
;
305 if (strcmp(envvar
, "TOG_COLOR_REFS_REMOTES") == 0)
307 if (strcmp(envvar
, "TOG_COLOR_REFS_BACKUP") == 0)
314 get_color_value(const char *envvar
)
316 const char *val
= getenv(envvar
);
319 return default_color_value(envvar
);
321 if (strcasecmp(val
, "black") == 0)
323 if (strcasecmp(val
, "red") == 0)
325 if (strcasecmp(val
, "green") == 0)
327 if (strcasecmp(val
, "yellow") == 0)
329 if (strcasecmp(val
, "blue") == 0)
331 if (strcasecmp(val
, "magenta") == 0)
332 return COLOR_MAGENTA
;
333 if (strcasecmp(val
, "cyan") == 0)
335 if (strcasecmp(val
, "white") == 0)
337 if (strcasecmp(val
, "default") == 0)
340 return default_color_value(envvar
);
343 struct diff_worktree_arg
{
344 struct got_repository
*repo
;
345 struct got_worktree
*worktree
;
346 struct got_diff_line
**lines
;
347 struct got_diffstat_cb_arg
*diffstat
;
356 int ignore_whitespace
;
358 enum got_diff_algorithm diff_algo
;
361 struct tog_diff_view_state
{
362 struct got_object_id
*id1
, *id2
;
363 const char *label1
, *label2
;
364 const char *worktree_root
;
369 int first_displayed_line
;
370 int last_displayed_line
;
373 int ignore_whitespace
;
377 struct got_repository
*repo
;
378 struct got_pathlist_head
*paths
;
379 struct got_diff_line
*lines
;
384 /* passed from log or blame view; may be NULL */
385 struct tog_view
*parent_view
;
388 #define TOG_WORKTREE_CHANGES_LOCAL_MSG "work tree changes"
389 #define TOG_WORKTREE_CHANGES_STAGED_MSG "staged work tree changes"
391 #define TOG_WORKTREE_CHANGES_LOCAL (1 << 0)
392 #define TOG_WORKTREE_CHANGES_STAGED (1 << 1)
393 #define TOG_WORKTREE_CHANGES_ALL \
394 (TOG_WORKTREE_CHANGES_LOCAL | TOG_WORKTREE_CHANGES_STAGED)
396 struct tog_worktree_ctx
{
404 pthread_mutex_t tog_mutex
= PTHREAD_MUTEX_INITIALIZER
;
405 static volatile sig_atomic_t tog_thread_error
;
407 struct tog_log_thread_args
{
408 pthread_cond_t need_commits
;
409 pthread_cond_t commit_loaded
;
412 struct got_commit_graph
*graph
;
413 struct commit_queue
*real_commits
;
414 struct tog_worktree_ctx wctx
;
415 const char *in_repo_path
;
416 struct got_object_id
*start_id
;
417 struct got_repository
*repo
;
420 pthread_cond_t log_loaded
;
422 struct commit_queue_entry
**first_displayed_entry
;
423 struct commit_queue_entry
**last_displayed_entry
;
424 struct commit_queue_entry
**selected_entry
;
427 int *search_next_done
;
431 regex_t
*limit_regex
;
432 struct commit_queue
*limit_commits
;
433 struct got_worktree
*worktree
;
434 int need_commit_marker
;
439 struct tog_log_view_state
{
440 struct commit_queue
*commits
;
441 struct commit_queue_entry
*first_displayed_entry
;
442 struct commit_queue_entry
*last_displayed_entry
;
443 struct commit_queue_entry
*selected_entry
;
444 struct commit_queue_entry
*marked_entry
;
445 struct commit_queue real_commits
;
450 struct got_repository
*repo
;
451 struct got_object_id
*start_id
;
454 struct tog_log_thread_args thread_args
;
455 struct commit_queue_entry
*matched_entry
;
456 struct commit_queue_entry
*search_entry
;
457 struct tog_colors colors
;
461 struct commit_queue limit_commits
;
464 #define TOG_COLOR_DIFF_MINUS 1
465 #define TOG_COLOR_DIFF_PLUS 2
466 #define TOG_COLOR_DIFF_CHUNK_HEADER 3
467 #define TOG_COLOR_DIFF_META 4
468 #define TOG_COLOR_TREE_SUBMODULE 5
469 #define TOG_COLOR_TREE_SYMLINK 6
470 #define TOG_COLOR_TREE_DIRECTORY 7
471 #define TOG_COLOR_TREE_EXECUTABLE 8
472 #define TOG_COLOR_COMMIT 9
473 #define TOG_COLOR_AUTHOR 10
474 #define TOG_COLOR_DATE 11
475 #define TOG_COLOR_REFS_HEADS 12
476 #define TOG_COLOR_REFS_TAGS 13
477 #define TOG_COLOR_REFS_REMOTES 14
478 #define TOG_COLOR_REFS_BACKUP 15
480 struct tog_blame_cb_args
{
481 struct tog_blame_line
*lines
; /* one per line */
484 struct tog_view
*view
;
485 struct got_object_id
*commit_id
;
489 struct tog_blame_thread_args
{
491 struct got_repository
*repo
;
492 struct tog_blame_cb_args
*cb_args
;
494 got_cancel_cb cancel_cb
;
496 pthread_cond_t blame_complete
;
502 struct tog_blame_line
*lines
;
506 struct tog_blame_thread_args thread_args
;
507 struct tog_blame_cb_args cb_args
;
512 struct tog_blame_view_state
{
513 int first_displayed_line
;
514 int last_displayed_line
;
516 int last_diffed_line
;
520 struct got_object_id_queue blamed_commits
;
521 struct got_object_qid
*blamed_commit
;
523 struct got_repository
*repo
;
524 struct got_object_id
*commit_id
;
525 struct got_object_id
*id_to_log
;
526 struct tog_blame blame
;
528 struct tog_colors colors
;
531 struct tog_parent_tree
{
532 TAILQ_ENTRY(tog_parent_tree
) entry
;
533 struct got_tree_object
*tree
;
534 struct got_tree_entry
*first_displayed_entry
;
535 struct got_tree_entry
*selected_entry
;
539 TAILQ_HEAD(tog_parent_trees
, tog_parent_tree
);
541 struct tog_tree_view_state
{
543 struct got_object_id
*commit_id
;/* commit which this tree belongs to */
544 struct got_tree_object
*root
; /* the commit's root tree entry */
545 struct got_tree_object
*tree
; /* currently displayed (sub-)tree */
546 struct got_tree_entry
*first_displayed_entry
;
547 struct got_tree_entry
*last_displayed_entry
;
548 struct got_tree_entry
*selected_entry
;
549 int ndisplayed
, selected
, show_ids
;
550 struct tog_parent_trees parents
; /* parent trees of current sub-tree */
552 struct got_repository
*repo
;
553 struct got_tree_entry
*matched_entry
;
554 struct tog_colors colors
;
557 struct tog_reflist_entry
{
558 TAILQ_ENTRY(tog_reflist_entry
) entry
;
559 struct got_reference
*ref
;
563 TAILQ_HEAD(tog_reflist_head
, tog_reflist_entry
);
565 struct tog_ref_view_state
{
566 struct tog_reflist_head refs
;
567 struct tog_reflist_entry
*first_displayed_entry
;
568 struct tog_reflist_entry
*last_displayed_entry
;
569 struct tog_reflist_entry
*selected_entry
;
570 int nrefs
, ndisplayed
, selected
, show_date
, show_ids
, sort_by_date
;
571 struct got_repository
*repo
;
572 struct tog_reflist_entry
*matched_entry
;
573 struct tog_colors colors
;
576 struct tog_help_view_state
{
581 int first_displayed_line
;
582 int last_displayed_line
;
587 enum tog_keymap_type type
;
590 #define GENERATE_HELP \
591 KEYMAP_("Global", TOG_KEYMAP_GLOBAL), \
592 KEY_("H F1", "Open view-specific help (double tap for all help)"), \
593 KEY_("k C-p Up", "Move cursor or page up one line"), \
594 KEY_("j C-n Down", "Move cursor or page down one line"), \
595 KEY_("C-b b PgUp", "Scroll the view up one page"), \
596 KEY_("C-f f PgDn Space", "Scroll the view down one page"), \
597 KEY_("C-u u", "Scroll the view up one half page"), \
598 KEY_("C-d d", "Scroll the view down one half page"), \
599 KEY_("g", "Go to line N (default: first line)"), \
600 KEY_("Home =", "Go to the first line"), \
601 KEY_("G", "Go to line N (default: last line)"), \
602 KEY_("End *", "Go to the last line"), \
603 KEY_("l Right", "Scroll the view right"), \
604 KEY_("h Left", "Scroll the view left"), \
605 KEY_("$", "Scroll view to the rightmost position"), \
606 KEY_("0", "Scroll view to the leftmost position"), \
607 KEY_("-", "Decrease size of the focussed split"), \
608 KEY_("+", "Increase size of the focussed split"), \
609 KEY_("Tab", "Switch focus between views"), \
610 KEY_("F", "Toggle fullscreen mode"), \
611 KEY_("S", "Switch split-screen layout"), \
612 KEY_("/", "Open prompt to enter search term"), \
613 KEY_("n", "Find next line/token matching the current search term"), \
614 KEY_("N", "Find previous line/token matching the current search term"),\
615 KEY_("q", "Quit the focussed view; Quit help screen"), \
616 KEY_("Q", "Quit tog"), \
618 KEYMAP_("Log view", TOG_KEYMAP_LOG), \
619 KEY_("< ,", "Move cursor up one commit"), \
620 KEY_("> .", "Move cursor down one commit"), \
621 KEY_("Enter", "Open diff view of the selected commit"), \
622 KEY_("B", "Reload the log view and toggle display of merged commits"), \
623 KEY_("R", "Open ref view of all repository references"), \
624 KEY_("T", "Display tree view of the repository from the selected" \
626 KEY_("m", "Mark or unmark the selected entry for diffing with the " \
627 "next selected commit"), \
628 KEY_("@", "Toggle between displaying author and committer name"), \
629 KEY_("&", "Open prompt to enter term to limit commits displayed"), \
630 KEY_("C-g Backspace", "Cancel current search or log operation"), \
631 KEY_("C-l", "Reload the log view with new repository commits or " \
632 "work tree changes"), \
634 KEYMAP_("Diff view", TOG_KEYMAP_DIFF), \
635 KEY_("K < ,", "Display diff of next line in the file/log entry"), \
636 KEY_("J > .", "Display diff of previous line in the file/log entry"), \
637 KEY_("A", "Toggle between Myers and Patience diff algorithm"), \
638 KEY_("a", "Toggle treatment of file as ASCII irrespective of binary" \
640 KEY_("p", "Write diff to a patch file in /tmp"), \
641 KEY_("(", "Go to the previous file in the diff"), \
642 KEY_(")", "Go to the next file in the diff"), \
643 KEY_("{", "Go to the previous hunk in the diff"), \
644 KEY_("}", "Go to the next hunk in the diff"), \
645 KEY_("[", "Decrease the number of context lines"), \
646 KEY_("]", "Increase the number of context lines"), \
647 KEY_("w", "Toggle ignore whitespace-only changes in the diff"), \
649 KEYMAP_("Blame view", TOG_KEYMAP_BLAME), \
650 KEY_("Enter", "Display diff view of the selected line's commit"), \
651 KEY_("A", "Toggle diff algorithm between Myers and Patience"), \
652 KEY_("L", "Open log view for the currently selected annotated line"), \
653 KEY_("C", "Reload view with the previously blamed commit"), \
654 KEY_("c", "Reload view with the version of the file found in the" \
655 " selected line's commit"), \
656 KEY_("p", "Reload view with the version of the file found in the" \
657 " selected line's parent commit"), \
659 KEYMAP_("Tree view", TOG_KEYMAP_TREE), \
660 KEY_("Enter", "Enter selected directory or open blame view of the" \
662 KEY_("L", "Open log view for the selected entry"), \
663 KEY_("R", "Open ref view of all repository references"), \
664 KEY_("i", "Show object IDs for all tree entries"), \
665 KEY_("Backspace", "Return to the parent directory"), \
667 KEYMAP_("Ref view", TOG_KEYMAP_REF), \
668 KEY_("Enter", "Display log view of the selected reference"), \
669 KEY_("T", "Display tree view of the selected reference"), \
670 KEY_("i", "Toggle display of IDs for all non-symbolic references"), \
671 KEY_("m", "Toggle display of last modified date for each reference"), \
672 KEY_("o", "Toggle reference sort order (name -> timestamp)"), \
673 KEY_("C-l", "Reload view with all repository references")
678 enum tog_keymap_type type
;
681 /* curses io for tog regress */
690 static int using_mock_io
;
692 #define TOG_KEY_SCRDUMP SHRT_MIN
695 * We implement two types of views: parent views and child views.
697 * The 'Tab' key switches focus between a parent view and its child view.
698 * Child views are shown side-by-side to their parent view, provided
699 * there is enough screen estate.
701 * When a new view is opened from within a parent view, this new view
702 * becomes a child view of the parent view, replacing any existing child.
704 * When a new view is opened from within a child view, this new view
705 * becomes a parent view which will obscure the views below until the
706 * user quits the new parent view by typing 'q'.
708 * This list of views contains parent views only.
709 * Child views are only pointed to by their parent view.
711 TAILQ_HEAD(tog_view_list_head
, tog_view
);
714 TAILQ_ENTRY(tog_view
) entry
;
717 int nlines
, ncols
, begin_y
, begin_x
; /* based on split height/width */
718 int resized_y
, resized_x
; /* begin_y/x based on user resizing */
719 int maxx
, x
; /* max column and current start column */
720 int lines
, cols
; /* copies of LINES and COLS */
721 int nscrolled
, offset
; /* lines scrolled and hsplit line offset */
722 int gline
, hiline
; /* navigate to and highlight this nG line */
723 int ch
, count
; /* current keymap and count prefix */
724 int resized
; /* set when in a resize event */
725 int focussed
; /* Only set on one parent or child view at a time. */
727 struct tog_view
*parent
;
728 struct tog_view
*child
;
731 * This flag is initially set on parent views when a new child view
732 * is created. It gets toggled when the 'Tab' key switches focus
733 * between parent and child.
734 * The flag indicates whether focus should be passed on to our child
735 * view if this parent view gets picked for focus after another parent
736 * view was closed. This prevents child views from losing focus in such
741 enum tog_view_mode mode
;
742 /* type-specific state */
743 enum tog_view_type type
;
745 struct tog_diff_view_state diff
;
746 struct tog_log_view_state log
;
747 struct tog_blame_view_state blame
;
748 struct tog_tree_view_state tree
;
749 struct tog_ref_view_state ref
;
750 struct tog_help_view_state help
;
753 const struct got_error
*(*show
)(struct tog_view
*);
754 const struct got_error
*(*input
)(struct tog_view
**,
755 struct tog_view
*, int);
756 const struct got_error
*(*reset
)(struct tog_view
*);
757 const struct got_error
*(*resize
)(struct tog_view
*, int);
758 const struct got_error
*(*close
)(struct tog_view
*);
760 const struct got_error
*(*search_start
)(struct tog_view
*);
761 const struct got_error
*(*search_next
)(struct tog_view
*);
762 void (*search_setup
)(struct tog_view
*, FILE **, off_t
**, size_t *,
763 int **, int **, int **, int **);
766 #define TOG_SEARCH_FORWARD 1
767 #define TOG_SEARCH_BACKWARD 2
768 int search_next_done
;
769 #define TOG_SEARCH_HAVE_MORE 1
770 #define TOG_SEARCH_NO_MORE 2
771 #define TOG_SEARCH_HAVE_NONE 3
777 static const struct got_error
*open_diff_view(struct tog_view
*,
778 struct got_object_id
*, struct got_object_id
*, const char *, const char *,
779 int, int, int, int, int, const char *, struct tog_view
*,
780 struct got_repository
*, struct got_pathlist_head
*);
781 static const struct got_error
*show_diff_view(struct tog_view
*);
782 static const struct got_error
*input_diff_view(struct tog_view
**,
783 struct tog_view
*, int);
784 static const struct got_error
*reset_diff_view(struct tog_view
*);
785 static const struct got_error
* close_diff_view(struct tog_view
*);
786 static const struct got_error
*search_start_diff_view(struct tog_view
*);
787 static void search_setup_diff_view(struct tog_view
*, FILE **, off_t
**,
788 size_t *, int **, int **, int **, int **);
789 static const struct got_error
*search_next_view_match(struct tog_view
*);
791 static const struct got_error
*open_log_view(struct tog_view
*,
792 struct got_object_id
*, struct got_repository
*,
793 const char *, const char *, int, struct got_worktree
*);
794 static const struct got_error
* show_log_view(struct tog_view
*);
795 static const struct got_error
*input_log_view(struct tog_view
**,
796 struct tog_view
*, int);
797 static const struct got_error
*resize_log_view(struct tog_view
*, int);
798 static const struct got_error
*close_log_view(struct tog_view
*);
799 static const struct got_error
*search_start_log_view(struct tog_view
*);
800 static const struct got_error
*search_next_log_view(struct tog_view
*);
802 static const struct got_error
*open_blame_view(struct tog_view
*, char *,
803 struct got_object_id
*, struct got_repository
*);
804 static const struct got_error
*show_blame_view(struct tog_view
*);
805 static const struct got_error
*input_blame_view(struct tog_view
**,
806 struct tog_view
*, int);
807 static const struct got_error
*reset_blame_view(struct tog_view
*);
808 static const struct got_error
*close_blame_view(struct tog_view
*);
809 static const struct got_error
*search_start_blame_view(struct tog_view
*);
810 static void search_setup_blame_view(struct tog_view
*, FILE **, off_t
**,
811 size_t *, int **, int **, int **, int **);
813 static const struct got_error
*open_tree_view(struct tog_view
*,
814 struct got_object_id
*, const char *, struct got_repository
*);
815 static const struct got_error
*show_tree_view(struct tog_view
*);
816 static const struct got_error
*input_tree_view(struct tog_view
**,
817 struct tog_view
*, int);
818 static const struct got_error
*close_tree_view(struct tog_view
*);
819 static const struct got_error
*search_start_tree_view(struct tog_view
*);
820 static const struct got_error
*search_next_tree_view(struct tog_view
*);
822 static const struct got_error
*open_ref_view(struct tog_view
*,
823 struct got_repository
*);
824 static const struct got_error
*show_ref_view(struct tog_view
*);
825 static const struct got_error
*input_ref_view(struct tog_view
**,
826 struct tog_view
*, int);
827 static const struct got_error
*close_ref_view(struct tog_view
*);
828 static const struct got_error
*search_start_ref_view(struct tog_view
*);
829 static const struct got_error
*search_next_ref_view(struct tog_view
*);
831 static const struct got_error
*open_help_view(struct tog_view
*,
833 static const struct got_error
*show_help_view(struct tog_view
*);
834 static const struct got_error
*input_help_view(struct tog_view
**,
835 struct tog_view
*, int);
836 static const struct got_error
*reset_help_view(struct tog_view
*);
837 static const struct got_error
* close_help_view(struct tog_view
*);
838 static const struct got_error
*search_start_help_view(struct tog_view
*);
839 static void search_setup_help_view(struct tog_view
*, FILE **, off_t
**,
840 size_t *, int **, int **, int **, int **);
842 static volatile sig_atomic_t tog_sigwinch_received
;
843 static volatile sig_atomic_t tog_sigpipe_received
;
844 static volatile sig_atomic_t tog_sigcont_received
;
845 static volatile sig_atomic_t tog_sigint_received
;
846 static volatile sig_atomic_t tog_sigterm_received
;
849 tog_sigwinch(int signo
)
851 tog_sigwinch_received
= 1;
855 tog_sigpipe(int signo
)
857 tog_sigpipe_received
= 1;
861 tog_sigcont(int signo
)
863 tog_sigcont_received
= 1;
867 tog_sigint(int signo
)
869 tog_sigint_received
= 1;
873 tog_sigterm(int signo
)
875 tog_sigterm_received
= 1;
879 tog_fatal_signal_received(void)
881 return (tog_sigpipe_received
||
882 tog_sigint_received
|| tog_sigterm_received
);
885 static const struct got_error
*
886 view_close(struct tog_view
*view
)
888 const struct got_error
*err
= NULL
, *child_err
= NULL
;
891 child_err
= view_close(view
->child
);
895 err
= view
->close(view
);
897 del_panel(view
->panel
);
901 delwin(view
->window
);
905 return err
? err
: child_err
;
908 static struct tog_view
*
909 view_open(int nlines
, int ncols
, int begin_y
, int begin_x
,
910 enum tog_view_type type
)
912 struct tog_view
*view
= calloc(1, sizeof(*view
));
920 view
->nlines
= nlines
? nlines
: LINES
- begin_y
;
921 view
->ncols
= ncols
? ncols
: COLS
- begin_x
;
922 view
->begin_y
= begin_y
;
923 view
->begin_x
= begin_x
;
924 view
->window
= newwin(nlines
, ncols
, begin_y
, begin_x
);
925 if (view
->window
== NULL
) {
929 view
->panel
= new_panel(view
->window
);
930 if (view
->panel
== NULL
||
931 set_panel_userptr(view
->panel
, view
) != OK
) {
936 keypad(view
->window
, TRUE
);
941 view_split_begin_x(int begin_x
)
943 if (begin_x
> 0 || COLS
< 120)
945 return (COLS
- MAX(COLS
/ 2, 80));
948 /* XXX Stub till we decide what to do. */
950 view_split_begin_y(int lines
)
952 return lines
* HSPLIT_SCALE
;
955 static const struct got_error
*view_resize(struct tog_view
*);
957 static const struct got_error
*
958 view_splitscreen(struct tog_view
*view
)
960 const struct got_error
*err
= NULL
;
962 if (!view
->resized
&& view
->mode
== TOG_VIEW_SPLIT_HRZN
) {
963 if (view
->resized_y
&& view
->resized_y
< view
->lines
)
964 view
->begin_y
= view
->resized_y
;
966 view
->begin_y
= view_split_begin_y(view
->nlines
);
968 } else if (!view
->resized
) {
969 if (view
->resized_x
&& view
->resized_x
< view
->cols
- 1 &&
971 view
->begin_x
= view
->resized_x
;
973 view
->begin_x
= view_split_begin_x(0);
976 view
->nlines
= LINES
- view
->begin_y
;
977 view
->ncols
= COLS
- view
->begin_x
;
980 err
= view_resize(view
);
984 if (view
->parent
&& view
->mode
== TOG_VIEW_SPLIT_HRZN
)
985 view
->parent
->nlines
= view
->begin_y
;
987 if (mvwin(view
->window
, view
->begin_y
, view
->begin_x
) == ERR
)
988 return got_error_from_errno("mvwin");
993 static const struct got_error
*
994 view_fullscreen(struct tog_view
*view
)
996 const struct got_error
*err
= NULL
;
999 view
->begin_y
= view
->resized
? view
->begin_y
: 0;
1000 view
->nlines
= view
->resized
? view
->nlines
: LINES
;
1002 view
->lines
= LINES
;
1004 err
= view_resize(view
);
1008 if (mvwin(view
->window
, view
->begin_y
, view
->begin_x
) == ERR
)
1009 return got_error_from_errno("mvwin");
1015 view_is_parent_view(struct tog_view
*view
)
1017 return view
->parent
== NULL
;
1021 view_is_splitscreen(struct tog_view
*view
)
1023 return view
->begin_x
> 0 || view
->begin_y
> 0;
1027 view_is_fullscreen(struct tog_view
*view
)
1029 return view
->nlines
== LINES
&& view
->ncols
== COLS
;
1033 view_is_hsplit_top(struct tog_view
*view
)
1035 return view
->mode
== TOG_VIEW_SPLIT_HRZN
&& view
->child
&&
1036 view_is_splitscreen(view
->child
);
1040 view_border(struct tog_view
*view
)
1043 const struct tog_view
*view_above
;
1046 return view_border(view
->parent
);
1048 panel
= panel_above(view
->panel
);
1052 view_above
= panel_userptr(panel
);
1053 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
)
1054 mvwhline(view
->window
, view_above
->begin_y
- 1,
1055 view
->begin_x
, ACS_HLINE
, view
->ncols
);
1057 mvwvline(view
->window
, view
->begin_y
, view_above
->begin_x
- 1,
1058 ACS_VLINE
, view
->nlines
);
1061 static const struct got_error
*view_init_hsplit(struct tog_view
*, int);
1062 static const struct got_error
*request_log_commits(struct tog_view
*);
1063 static const struct got_error
*offset_selection_down(struct tog_view
*);
1064 static void offset_selection_up(struct tog_view
*);
1065 static void view_get_split(struct tog_view
*, int *, int *);
1067 static const struct got_error
*
1068 view_resize(struct tog_view
*view
)
1070 const struct got_error
*err
= NULL
;
1071 int dif
, nlines
, ncols
;
1073 dif
= LINES
- view
->lines
; /* line difference */
1075 if (view
->lines
> LINES
)
1076 nlines
= view
->nlines
- (view
->lines
- LINES
);
1078 nlines
= view
->nlines
+ (LINES
- view
->lines
);
1079 if (view
->cols
> COLS
)
1080 ncols
= view
->ncols
- (view
->cols
- COLS
);
1082 ncols
= view
->ncols
+ (COLS
- view
->cols
);
1085 int hs
= view
->child
->begin_y
;
1087 if (!view_is_fullscreen(view
))
1088 view
->child
->begin_x
= view_split_begin_x(view
->begin_x
);
1089 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
||
1090 view
->child
->begin_x
== 0) {
1093 view_fullscreen(view
->child
);
1094 if (view
->child
->focussed
)
1095 show_panel(view
->child
->panel
);
1097 show_panel(view
->panel
);
1099 ncols
= view
->child
->begin_x
;
1101 view_splitscreen(view
->child
);
1102 show_panel(view
->child
->panel
);
1105 * XXX This is ugly and needs to be moved into the above
1106 * logic but "works" for now and my attempts at moving it
1107 * break either 'tab' or 'F' key maps in horizontal splits.
1110 err
= view_splitscreen(view
->child
);
1113 if (dif
< 0) { /* top split decreased */
1114 err
= offset_selection_down(view
);
1121 show_panel(view
->child
->panel
);
1122 nlines
= view
->nlines
;
1124 } else if (view
->parent
== NULL
)
1127 if (view
->resize
&& dif
> 0) {
1128 err
= view
->resize(view
, dif
);
1133 if (wresize(view
->window
, nlines
, ncols
) == ERR
)
1134 return got_error_from_errno("wresize");
1135 if (replace_panel(view
->panel
, view
->window
) == ERR
)
1136 return got_error_from_errno("replace_panel");
1137 wclear(view
->window
);
1139 view
->nlines
= nlines
;
1140 view
->ncols
= ncols
;
1141 view
->lines
= LINES
;
1147 static const struct got_error
*
1148 resize_log_view(struct tog_view
*view
, int increase
)
1150 struct tog_log_view_state
*s
= &view
->state
.log
;
1151 const struct got_error
*err
= NULL
;
1154 if (s
->selected_entry
)
1155 n
= s
->selected_entry
->idx
+ view
->lines
- s
->selected
;
1158 * Request commits to account for the increased
1159 * height so we have enough to populate the view.
1161 if (s
->commits
->ncommits
< n
) {
1162 view
->nscrolled
= n
- s
->commits
->ncommits
+ increase
+ 1;
1163 err
= request_log_commits(view
);
1170 view_adjust_offset(struct tog_view
*view
, int n
)
1175 if (view
->parent
&& view
->parent
->offset
) {
1176 if (view
->parent
->offset
+ n
>= 0)
1177 view
->parent
->offset
+= n
;
1179 view
->parent
->offset
= 0;
1180 } else if (view
->offset
) {
1181 if (view
->offset
- n
>= 0)
1188 static const struct got_error
*
1189 view_resize_split(struct tog_view
*view
, int resize
)
1191 const struct got_error
*err
= NULL
;
1192 struct tog_view
*v
= NULL
;
1199 if (!v
->child
|| !view_is_splitscreen(v
->child
))
1202 v
->resized
= v
->child
->resized
= resize
; /* lock for resize event */
1204 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
) {
1205 if (v
->child
->resized_y
)
1206 v
->child
->begin_y
= v
->child
->resized_y
;
1208 v
->child
->begin_y
-= resize
;
1210 v
->child
->begin_y
+= resize
;
1211 if (v
->child
->begin_y
< 3) {
1213 v
->child
->begin_y
= 3;
1214 } else if (v
->child
->begin_y
> LINES
- 1) {
1216 v
->child
->begin_y
= LINES
- 1;
1219 v
->child
->ncols
= COLS
;
1220 view_adjust_offset(view
, resize
);
1221 err
= view_init_hsplit(v
, v
->child
->begin_y
);
1224 v
->child
->resized_y
= v
->child
->begin_y
;
1226 if (v
->child
->resized_x
)
1227 v
->child
->begin_x
= v
->child
->resized_x
;
1229 v
->child
->begin_x
-= resize
;
1231 v
->child
->begin_x
+= resize
;
1232 if (v
->child
->begin_x
< 11) {
1234 v
->child
->begin_x
= 11;
1235 } else if (v
->child
->begin_x
> COLS
- 1) {
1237 v
->child
->begin_x
= COLS
- 1;
1239 v
->child
->resized_x
= v
->child
->begin_x
;
1242 v
->child
->mode
= v
->mode
;
1243 v
->child
->nlines
= v
->lines
- v
->child
->begin_y
;
1244 v
->child
->ncols
= v
->cols
- v
->child
->begin_x
;
1247 err
= view_fullscreen(v
);
1250 err
= view_splitscreen(v
->child
);
1254 if (v
->mode
== TOG_VIEW_SPLIT_HRZN
) {
1255 err
= offset_selection_down(v
->child
);
1261 err
= v
->resize(v
, 0);
1262 else if (v
->child
->resize
)
1263 err
= v
->child
->resize(v
->child
, 0);
1265 v
->resized
= v
->child
->resized
= 0;
1271 view_transfer_size(struct tog_view
*dst
, struct tog_view
*src
)
1273 struct tog_view
*v
= src
->child
? src
->child
: src
;
1275 dst
->resized_x
= v
->resized_x
;
1276 dst
->resized_y
= v
->resized_y
;
1279 static const struct got_error
*
1280 view_close_child(struct tog_view
*view
)
1282 const struct got_error
*err
= NULL
;
1284 if (view
->child
== NULL
)
1287 err
= view_close(view
->child
);
1292 static const struct got_error
*
1293 view_set_child(struct tog_view
*view
, struct tog_view
*child
)
1295 const struct got_error
*err
= NULL
;
1297 view
->child
= child
;
1298 child
->parent
= view
;
1300 err
= view_resize(view
);
1304 if (view
->child
->resized_x
|| view
->child
->resized_y
)
1305 err
= view_resize_split(view
, 0);
1310 static const struct got_error
*view_dispatch_request(struct tog_view
**,
1311 struct tog_view
*, enum tog_view_type
, int, int);
1313 static const struct got_error
*
1314 view_request_new(struct tog_view
**requested
, struct tog_view
*view
,
1315 enum tog_view_type request
)
1317 struct tog_view
*new_view
= NULL
;
1318 const struct got_error
*err
;
1323 if (view_is_parent_view(view
) && request
!= TOG_VIEW_HELP
)
1324 view_get_split(view
, &y
, &x
);
1326 err
= view_dispatch_request(&new_view
, view
, request
, y
, x
);
1330 if (view_is_parent_view(view
) && view
->mode
== TOG_VIEW_SPLIT_HRZN
&&
1331 request
!= TOG_VIEW_HELP
) {
1332 err
= view_init_hsplit(view
, y
);
1338 new_view
->focussed
= 1;
1339 new_view
->mode
= view
->mode
;
1340 new_view
->nlines
= request
== TOG_VIEW_HELP
?
1341 view
->lines
: view
->lines
- y
;
1343 if (view_is_parent_view(view
) && request
!= TOG_VIEW_HELP
) {
1344 view_transfer_size(new_view
, view
);
1345 err
= view_close_child(view
);
1348 err
= view_set_child(view
, new_view
);
1351 view
->focus_child
= 1;
1353 *requested
= new_view
;
1359 tog_resizeterm(void)
1362 struct winsize size
;
1364 if (ioctl(STDOUT_FILENO
, TIOCGWINSZ
, &size
) < 0) {
1365 cols
= 80; /* Default */
1369 lines
= size
.ws_row
;
1371 resize_term(lines
, cols
);
1374 static const struct got_error
*
1375 view_search_start(struct tog_view
*view
, int fast_refresh
)
1377 const struct got_error
*err
= NULL
;
1378 struct tog_view
*v
= view
;
1382 if (view
->search_started
) {
1383 regfree(&view
->regex
);
1384 view
->searching
= 0;
1385 memset(&view
->regmatch
, 0, sizeof(view
->regmatch
));
1387 view
->search_started
= 0;
1389 if (view
->nlines
< 1)
1392 if (view_is_hsplit_top(view
))
1394 else if (view
->mode
== TOG_VIEW_SPLIT_VERT
&& view
->parent
)
1397 if (tog_io
.input_str
!= NULL
) {
1398 if (strlcpy(pattern
, tog_io
.input_str
, sizeof(pattern
)) >=
1400 return got_error(GOT_ERR_NO_SPACE
);
1402 mvwaddstr(v
->window
, v
->nlines
- 1, 0, "/");
1403 wclrtoeol(v
->window
);
1404 nodelay(v
->window
, FALSE
); /* block for search term input */
1407 ret
= wgetnstr(v
->window
, pattern
, sizeof(pattern
));
1408 wrefresh(v
->window
);
1411 nodelay(v
->window
, TRUE
);
1412 if (!fast_refresh
&& !using_mock_io
)
1418 if (regcomp(&view
->regex
, pattern
, REG_EXTENDED
| REG_NEWLINE
) == 0) {
1419 err
= view
->search_start(view
);
1421 regfree(&view
->regex
);
1424 view
->search_started
= 1;
1425 view
->searching
= TOG_SEARCH_FORWARD
;
1426 view
->search_next_done
= 0;
1427 view
->search_next(view
);
1433 /* Switch split mode. If view is a parent or child, draw the new splitscreen. */
1434 static const struct got_error
*
1435 switch_split(struct tog_view
*view
)
1437 const struct got_error
*err
= NULL
;
1438 struct tog_view
*v
= NULL
;
1445 if (v
->mode
== TOG_VIEW_SPLIT_HRZN
)
1446 v
->mode
= TOG_VIEW_SPLIT_VERT
;
1448 v
->mode
= TOG_VIEW_SPLIT_HRZN
;
1452 else if (v
->mode
== TOG_VIEW_SPLIT_VERT
&& v
->cols
< 120)
1453 v
->mode
= TOG_VIEW_SPLIT_NONE
;
1455 view_get_split(v
, &v
->child
->begin_y
, &v
->child
->begin_x
);
1456 if (v
->mode
== TOG_VIEW_SPLIT_HRZN
&& v
->child
->resized_y
)
1457 v
->child
->begin_y
= v
->child
->resized_y
;
1458 else if (v
->mode
== TOG_VIEW_SPLIT_VERT
&& v
->child
->resized_x
)
1459 v
->child
->begin_x
= v
->child
->resized_x
;
1462 if (v
->mode
== TOG_VIEW_SPLIT_HRZN
) {
1464 v
->child
->ncols
= COLS
;
1465 v
->child
->nscrolled
= LINES
- v
->child
->nlines
;
1467 err
= view_init_hsplit(v
, v
->child
->begin_y
);
1471 v
->child
->mode
= v
->mode
;
1472 v
->child
->nlines
= v
->lines
- v
->child
->begin_y
;
1475 err
= view_fullscreen(v
);
1478 err
= view_splitscreen(v
->child
);
1482 if (v
->mode
== TOG_VIEW_SPLIT_NONE
)
1483 v
->mode
= TOG_VIEW_SPLIT_VERT
;
1484 if (v
->mode
== TOG_VIEW_SPLIT_HRZN
) {
1485 err
= offset_selection_down(v
);
1488 err
= offset_selection_down(v
->child
);
1492 offset_selection_up(v
);
1493 offset_selection_up(v
->child
);
1496 err
= v
->resize(v
, 0);
1497 else if (v
->child
->resize
)
1498 err
= v
->child
->resize(v
->child
, 0);
1504 * Strip trailing whitespace from str starting at byte *n;
1505 * if *n < 0, use strlen(str). Return new str length in *n.
1508 strip_trailing_ws(char *str
, int *n
)
1512 if (str
== NULL
|| *str
== '\0')
1518 while (x
-- > 0 && isspace((unsigned char)str
[x
]))
1525 * Extract visible substring of line y from the curses screen
1526 * and strip trailing whitespace. If vline is set, overwrite
1527 * line[vline] with '|' because the ACS_VLINE character is
1528 * written out as 'x'. Write the line to file f.
1530 static const struct got_error
*
1531 view_write_line(FILE *f
, int y
, int vline
)
1533 char line
[COLS
* MB_LEN_MAX
]; /* allow for multibyte chars */
1536 r
= mvwinnstr(curscr
, y
, 0, line
, sizeof(line
));
1538 return got_error_fmt(GOT_ERR_RANGE
,
1539 "failed to extract line %d", y
);
1542 * In some views, lines are padded with blanks to COLS width.
1543 * Strip them so we can diff without the -b flag when testing.
1545 strip_trailing_ws(line
, &r
);
1550 w
= fprintf(f
, "%s\n", line
);
1551 if (w
!= r
+ 1) /* \n */
1552 return got_ferror(f
, GOT_ERR_IO
);
1558 * Capture the visible curses screen by writing each line to the
1559 * file at the path set via the TOG_SCR_DUMP environment variable.
1561 static const struct got_error
*
1562 screendump(struct tog_view
*view
)
1564 const struct got_error
*err
;
1567 err
= got_opentemp_truncate(tog_io
.sdump
);
1571 if ((view
->child
&& view
->child
->begin_x
) ||
1572 (view
->parent
&& view
->begin_x
)) {
1573 int ncols
= view
->child
? view
->ncols
: view
->parent
->ncols
;
1575 /* vertical splitscreen */
1576 for (i
= 0; i
< view
->nlines
; ++i
) {
1577 err
= view_write_line(tog_io
.sdump
, i
, ncols
- 1);
1584 /* fullscreen or horizontal splitscreen */
1585 if ((view
->child
&& view
->child
->begin_y
) ||
1586 (view
->parent
&& view
->begin_y
)) /* hsplit */
1587 hline
= view
->child
?
1588 view
->child
->begin_y
: view
->begin_y
;
1590 for (i
= 0; i
< view
->lines
; i
++) {
1591 if (hline
&& i
== hline
- 1) {
1594 /* ACS_HLINE writes out as 'q', overwrite it */
1595 for (c
= 0; c
< view
->cols
; ++c
)
1596 fputc('-', tog_io
.sdump
);
1597 fputc('\n', tog_io
.sdump
);
1601 err
= view_write_line(tog_io
.sdump
, i
, 0);
1612 * Compute view->count from numeric input. Assign total to view->count and
1613 * return first non-numeric key entered.
1616 get_compound_key(struct tog_view
*view
, int c
)
1618 struct tog_view
*v
= view
;
1621 if (view_is_hsplit_top(view
))
1623 else if (view
->mode
== TOG_VIEW_SPLIT_VERT
&& view
->parent
)
1627 cbreak(); /* block for input */
1628 nodelay(view
->window
, FALSE
);
1629 wmove(v
->window
, v
->nlines
- 1, 0);
1630 wclrtoeol(v
->window
);
1631 waddch(v
->window
, ':');
1634 x
= getcurx(v
->window
);
1635 if (x
!= ERR
&& x
< view
->ncols
) {
1636 waddch(v
->window
, c
);
1637 wrefresh(v
->window
);
1641 * Don't overflow. Max valid request should be the greatest
1642 * between the longest and total lines; cap at 10 million.
1647 n
= n
* 10 + (c
- '0');
1648 } while (((c
= wgetch(view
->window
))) >= '0' && c
<= '9' && c
!= ERR
);
1650 if (c
== 'G' || c
== 'g') { /* nG key map */
1651 view
->gline
= view
->hiline
= n
;
1656 /* Massage excessive or inapplicable values at the input handler. */
1663 action_report(struct tog_view
*view
)
1665 struct tog_view
*v
= view
;
1667 if (view_is_hsplit_top(view
))
1669 else if (view
->mode
== TOG_VIEW_SPLIT_VERT
&& view
->parent
)
1672 wmove(v
->window
, v
->nlines
- 1, 0);
1673 wclrtoeol(v
->window
);
1674 wprintw(v
->window
, ":%s", view
->action
);
1675 wrefresh(v
->window
);
1678 * Clear action status report. Only clear in blame view
1679 * once annotating is complete, otherwise it's too fast.
1680 * In diff view, let its state control view->action lifetime.
1682 if (view
->type
== TOG_VIEW_BLAME
) {
1683 if (view
->state
.blame
.blame_complete
)
1684 view
->action
= NULL
;
1685 } else if (view
->type
== TOG_VIEW_DIFF
) {
1686 view
->action
= view
->state
.diff
.action
;
1688 view
->action
= NULL
;
1692 * Read the next line from the test script and assign
1693 * key instruction to *ch. If at EOF, set the *done flag.
1695 static const struct got_error
*
1696 tog_read_script_key(FILE *script
, struct tog_view
*view
, int *ch
, int *done
)
1698 const struct got_error
*err
= NULL
;
1704 if (view
->count
&& --view
->count
) {
1710 if ((n
= getline(&line
, &linesz
, script
)) == -1) {
1715 err
= got_ferror(script
, GOT_ERR_IO
);
1720 if (strncasecmp(line
, "WAIT_FOR_UI", 11) == 0)
1721 tog_io
.wait_for_ui
= 1;
1722 else if (strncasecmp(line
, "KEY_ENTER", 9) == 0)
1724 else if (strncasecmp(line
, "KEY_RIGHT", 9) == 0)
1726 else if (strncasecmp(line
, "KEY_LEFT", 8) == 0)
1728 else if (strncasecmp(line
, "KEY_DOWN", 8) == 0)
1730 else if (strncasecmp(line
, "KEY_UP", 6) == 0)
1732 else if (strncasecmp(line
, "TAB", 3) == 0)
1734 else if (strncasecmp(line
, "SCREENDUMP", 10) == 0)
1735 *ch
= TOG_KEY_SCRDUMP
;
1736 else if (isdigit((unsigned char)*line
)) {
1739 while (isdigit((unsigned char)*t
))
1741 view
->ch
= *ch
= *t
;
1743 /* ignore error, view->count is 0 if instruction is invalid */
1744 view
->count
= strtonum(line
, 0, INT_MAX
, NULL
);
1747 if (n
> 2 && (*ch
== '/' || *ch
== '&')) {
1748 /* skip leading keymap and trim trailing newline */
1749 tog_io
.input_str
= strndup(line
+ 1, n
- 2);
1750 if (tog_io
.input_str
== NULL
) {
1751 err
= got_error_from_errno("strndup");
1763 log_mark_clear(struct tog_log_view_state
*s
)
1765 s
->marked_entry
= NULL
;
1768 static const struct got_error
*
1769 view_input(struct tog_view
**new, int *done
, struct tog_view
*view
,
1770 struct tog_view_list_head
*views
, int fast_refresh
)
1772 const struct got_error
*err
= NULL
;
1779 action_report(view
);
1781 /* Clear "no matches" indicator. */
1782 if (view
->search_next_done
== TOG_SEARCH_NO_MORE
||
1783 view
->search_next_done
== TOG_SEARCH_HAVE_NONE
) {
1784 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
1788 if (view
->searching
&& !view
->search_next_done
) {
1789 errcode
= pthread_mutex_unlock(&tog_mutex
);
1791 return got_error_set_errno(errcode
,
1792 "pthread_mutex_unlock");
1794 errcode
= pthread_mutex_lock(&tog_mutex
);
1796 return got_error_set_errno(errcode
,
1797 "pthread_mutex_lock");
1798 view
->search_next(view
);
1802 /* Allow threads to make progress while we are waiting for input. */
1803 errcode
= pthread_mutex_unlock(&tog_mutex
);
1805 return got_error_set_errno(errcode
, "pthread_mutex_unlock");
1807 if (using_mock_io
) {
1808 err
= tog_read_script_key(tog_io
.f
, view
, &ch
, done
);
1810 errcode
= pthread_mutex_lock(&tog_mutex
);
1813 } else if (view
->count
&& --view
->count
) {
1815 nodelay(view
->window
, TRUE
);
1816 ch
= wgetch(view
->window
);
1817 /* let C-g or backspace abort unfinished count */
1818 if (ch
== CTRL('g') || ch
== KEY_BACKSPACE
)
1823 ch
= wgetch(view
->window
);
1824 if (ch
>= '1' && ch
<= '9')
1825 view
->ch
= ch
= get_compound_key(view
, ch
);
1827 if (view
->hiline
&& ch
!= ERR
&& ch
!= 0)
1828 view
->hiline
= 0; /* key pressed, clear line highlight */
1829 nodelay(view
->window
, TRUE
);
1830 errcode
= pthread_mutex_lock(&tog_mutex
);
1832 return got_error_set_errno(errcode
, "pthread_mutex_lock");
1834 if (tog_sigwinch_received
|| tog_sigcont_received
) {
1836 tog_sigwinch_received
= 0;
1837 tog_sigcont_received
= 0;
1838 TAILQ_FOREACH(v
, views
, entry
) {
1839 err
= view_resize(v
);
1842 err
= v
->input(new, v
, KEY_RESIZE
);
1846 err
= view_resize(v
->child
);
1849 err
= v
->child
->input(new, v
->child
,
1853 if (v
->child
->resized_x
|| v
->child
->resized_y
) {
1854 err
= view_resize_split(v
, 0);
1866 if (view
->type
== TOG_VIEW_HELP
)
1867 err
= view
->reset(view
);
1869 err
= view_request_new(new, view
, TOG_VIEW_HELP
);
1875 view
->child
->focussed
= 1;
1876 view
->focus_child
= 1;
1877 } else if (view
->parent
) {
1879 view
->parent
->focussed
= 1;
1880 view
->parent
->focus_child
= 0;
1881 if (!view_is_splitscreen(view
)) {
1882 if (view
->parent
->resize
) {
1883 err
= view
->parent
->resize(view
->parent
,
1888 offset_selection_up(view
->parent
);
1889 err
= view_fullscreen(view
->parent
);
1896 if (view
->parent
!= NULL
) {
1897 if (view
->parent
->type
== TOG_VIEW_LOG
)
1898 log_mark_clear(&view
->parent
->state
.log
);
1900 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
) {
1901 if (view
->parent
->resize
) {
1903 * Might need more commits
1904 * to fill fullscreen.
1906 err
= view
->parent
->resize(
1911 offset_selection_up(view
->parent
);
1914 err
= view
->input(new, view
, ch
);
1922 if (view_is_parent_view(view
)) {
1923 if (view
->child
== NULL
)
1925 if (view_is_splitscreen(view
->child
)) {
1927 view
->child
->focussed
= 1;
1928 err
= view_fullscreen(view
->child
);
1930 err
= view_splitscreen(view
->child
);
1932 err
= view_resize_split(view
, 0);
1936 err
= view
->child
->input(new, view
->child
,
1939 if (view_is_splitscreen(view
)) {
1940 view
->parent
->focussed
= 0;
1942 err
= view_fullscreen(view
);
1944 err
= view_splitscreen(view
);
1945 if (!err
&& view
->mode
!= TOG_VIEW_SPLIT_HRZN
)
1946 err
= view_resize(view
->parent
);
1948 err
= view_resize_split(view
, 0);
1952 err
= view
->input(new, view
, KEY_RESIZE
);
1957 err
= view
->resize(view
, 0);
1962 if (view
->parent
->resize
) {
1963 err
= view
->parent
->resize(view
->parent
, 0);
1967 err
= offset_selection_down(view
->parent
);
1971 err
= offset_selection_down(view
);
1975 err
= switch_split(view
);
1978 err
= view_resize_split(view
, -1);
1981 err
= view_resize_split(view
, 1);
1987 if (view
->search_start
)
1988 view_search_start(view
, fast_refresh
);
1990 err
= view
->input(new, view
, ch
);
1994 if (view
->search_started
&& view
->search_next
) {
1995 view
->searching
= (ch
== 'n' ?
1996 TOG_SEARCH_FORWARD
: TOG_SEARCH_BACKWARD
);
1997 view
->search_next_done
= 0;
1998 view
->search_next(view
);
2000 err
= view
->input(new, view
, ch
);
2003 if (tog_diff_algo
== GOT_DIFF_ALGORITHM_MYERS
) {
2004 tog_diff_algo
= GOT_DIFF_ALGORITHM_PATIENCE
;
2005 view
->action
= "Patience diff algorithm";
2007 tog_diff_algo
= GOT_DIFF_ALGORITHM_MYERS
;
2008 view
->action
= "Myers diff algorithm";
2010 TAILQ_FOREACH(v
, views
, entry
) {
2016 if (v
->child
&& v
->child
->reset
) {
2017 err
= v
->child
->reset(v
->child
);
2023 case TOG_KEY_SCRDUMP
:
2024 err
= screendump(view
);
2027 err
= view
->input(new, view
, ch
);
2035 view_needs_focus_indication(struct tog_view
*view
)
2037 if (view_is_parent_view(view
)) {
2038 if (view
->child
== NULL
|| view
->child
->focussed
)
2040 if (!view_is_splitscreen(view
->child
))
2042 } else if (!view_is_splitscreen(view
))
2045 return view
->focussed
;
2048 static const struct got_error
*
2051 const struct got_error
*err
= NULL
;
2053 if (tog_io
.cin
&& fclose(tog_io
.cin
) == EOF
)
2054 err
= got_ferror(tog_io
.cin
, GOT_ERR_IO
);
2055 if (tog_io
.cout
&& fclose(tog_io
.cout
) == EOF
&& err
== NULL
)
2056 err
= got_ferror(tog_io
.cout
, GOT_ERR_IO
);
2057 if (tog_io
.f
&& fclose(tog_io
.f
) == EOF
&& err
== NULL
)
2058 err
= got_ferror(tog_io
.f
, GOT_ERR_IO
);
2059 if (tog_io
.sdump
&& fclose(tog_io
.sdump
) == EOF
&& err
== NULL
)
2060 err
= got_ferror(tog_io
.sdump
, GOT_ERR_IO
);
2061 if (tog_io
.input_str
!= NULL
)
2062 free(tog_io
.input_str
);
2067 static const struct got_error
*
2068 view_loop(struct tog_view
*view
)
2070 const struct got_error
*err
= NULL
;
2071 struct tog_view_list_head views
;
2072 struct tog_view
*new_view
;
2074 int fast_refresh
= 10;
2075 int done
= 0, errcode
;
2077 mode
= getenv("TOG_VIEW_SPLIT_MODE");
2078 if (!mode
|| !(*mode
== 'h' || *mode
== 'H'))
2079 view
->mode
= TOG_VIEW_SPLIT_VERT
;
2081 view
->mode
= TOG_VIEW_SPLIT_HRZN
;
2083 errcode
= pthread_mutex_lock(&tog_mutex
);
2085 return got_error_set_errno(errcode
, "pthread_mutex_lock");
2088 TAILQ_INSERT_HEAD(&views
, view
, entry
);
2091 err
= view
->show(view
);
2096 while (!TAILQ_EMPTY(&views
) && !done
&& !tog_thread_error
&&
2097 !tog_fatal_signal_received()) {
2098 /* Refresh fast during initialization, then become slower. */
2099 if (fast_refresh
&& --fast_refresh
== 0 && !using_mock_io
)
2100 halfdelay(10); /* switch to once per second */
2102 err
= view_input(&new_view
, &done
, view
, &views
, fast_refresh
);
2106 if (view
->dying
&& view
== TAILQ_FIRST(&views
) &&
2107 TAILQ_NEXT(view
, entry
) == NULL
)
2113 * When we quit, scroll the screen up a single line
2114 * so we don't lose any information.
2116 TAILQ_FOREACH(v
, &views
, entry
) {
2117 wmove(v
->window
, 0, 0);
2118 wdeleteln(v
->window
);
2119 wnoutrefresh(v
->window
);
2120 if (v
->child
&& !view_is_fullscreen(v
)) {
2121 wmove(v
->child
->window
, 0, 0);
2122 wdeleteln(v
->child
->window
);
2123 wnoutrefresh(v
->child
->window
);
2130 struct tog_view
*v
, *prev
= NULL
;
2132 if (view_is_parent_view(view
))
2133 prev
= TAILQ_PREV(view
, tog_view_list_head
,
2135 else if (view
->parent
)
2136 prev
= view
->parent
;
2139 view
->parent
->child
= NULL
;
2140 view
->parent
->focus_child
= 0;
2141 /* Restore fullscreen line height. */
2142 view
->parent
->nlines
= view
->parent
->lines
;
2143 err
= view_resize(view
->parent
);
2146 /* Make resized splits persist. */
2147 view_transfer_size(view
->parent
, view
);
2149 TAILQ_REMOVE(&views
, view
, entry
);
2151 err
= view_close(view
);
2156 TAILQ_FOREACH(v
, &views
, entry
) {
2160 if (view
== NULL
&& new_view
== NULL
) {
2161 /* No view has focus. Try to pick one. */
2164 else if (!TAILQ_EMPTY(&views
)) {
2165 view
= TAILQ_LAST(&views
,
2166 tog_view_list_head
);
2169 if (view
->focus_child
) {
2170 view
->child
->focussed
= 1;
2178 struct tog_view
*v
, *t
;
2179 /* Only allow one parent view per type. */
2180 TAILQ_FOREACH_SAFE(v
, &views
, entry
, t
) {
2181 if (v
->type
!= new_view
->type
)
2183 TAILQ_REMOVE(&views
, v
, entry
);
2184 err
= view_close(v
);
2189 TAILQ_INSERT_TAIL(&views
, new_view
, entry
);
2192 if (view
&& !done
) {
2193 if (view_is_parent_view(view
)) {
2194 if (view
->child
&& view
->child
->focussed
)
2197 if (view
->parent
&& view
->parent
->focussed
)
2198 view
= view
->parent
;
2200 show_panel(view
->panel
);
2201 if (view
->child
&& view_is_splitscreen(view
->child
))
2202 show_panel(view
->child
->panel
);
2203 if (view
->parent
&& view_is_splitscreen(view
)) {
2204 err
= view
->parent
->show(view
->parent
);
2208 err
= view
->show(view
);
2212 err
= view
->child
->show(view
->child
);
2221 while (!TAILQ_EMPTY(&views
)) {
2222 const struct got_error
*close_err
;
2223 view
= TAILQ_FIRST(&views
);
2224 TAILQ_REMOVE(&views
, view
, entry
);
2225 close_err
= view_close(view
);
2226 if (close_err
&& err
== NULL
)
2230 errcode
= pthread_mutex_unlock(&tog_mutex
);
2231 if (errcode
&& err
== NULL
)
2232 err
= got_error_set_errno(errcode
, "pthread_mutex_unlock");
2242 "usage: %s log [-b] [-c commit] [-r repository-path] [path]\n",
2247 /* Create newly allocated wide-character string equivalent to a byte string. */
2248 static const struct got_error
*
2249 mbs2ws(wchar_t **ws
, size_t *wlen
, const char *s
)
2252 const struct got_error
*err
= NULL
;
2255 *wlen
= mbstowcs(NULL
, s
, 0);
2256 if (*wlen
== (size_t)-1) {
2258 if (errno
!= EILSEQ
)
2259 return got_error_from_errno("mbstowcs");
2261 /* byte string invalid in current encoding; try to "fix" it */
2262 err
= got_mbsavis(&vis
, &vislen
, s
);
2265 *wlen
= mbstowcs(NULL
, vis
, 0);
2266 if (*wlen
== (size_t)-1) {
2267 err
= got_error_from_errno("mbstowcs"); /* give up */
2272 *ws
= calloc(*wlen
+ 1, sizeof(**ws
));
2274 err
= got_error_from_errno("calloc");
2278 if (mbstowcs(*ws
, vis
? vis
: s
, *wlen
) != *wlen
)
2279 err
= got_error_from_errno("mbstowcs");
2290 static const struct got_error
*
2291 expand_tab(char **ptr
, const char *src
)
2294 size_t len
, n
, idx
= 0, sz
= 0;
2297 n
= len
= strlen(src
);
2298 dst
= malloc(n
+ 1);
2300 return got_error_from_errno("malloc");
2302 while (idx
< len
&& src
[idx
]) {
2303 const char c
= src
[idx
];
2306 size_t nb
= TABSIZE
- sz
% TABSIZE
;
2309 p
= realloc(dst
, n
+ nb
);
2312 return got_error_from_errno("realloc");
2317 memset(dst
+ sz
, ' ', nb
);
2320 dst
[sz
++] = src
[idx
];
2330 * Advance at most n columns from wline starting at offset off.
2331 * Return the index to the first character after the span operation.
2332 * Return the combined column width of all spanned wide characters in
2336 span_wline(int *rcol
, int off
, wchar_t *wline
, int n
, int col_tab_align
)
2338 int width
, i
, cols
= 0;
2345 for (i
= off
; wline
[i
] != L
'\0'; ++i
) {
2346 if (wline
[i
] == L
'\t')
2347 width
= TABSIZE
- ((cols
+ col_tab_align
) % TABSIZE
);
2349 width
= wcwidth(wline
[i
]);
2356 if (cols
+ width
> n
)
2366 * Format a line for display, ensuring that it won't overflow a width limit.
2367 * With scrolling, the width returned refers to the scrolled version of the
2368 * line, which starts at (*wlinep)[*scrollxp]. The caller must free *wlinep.
2370 static const struct got_error
*
2371 format_line(wchar_t **wlinep
, int *widthp
, int *scrollxp
,
2372 const char *line
, int nscroll
, int wlimit
, int col_tab_align
, int expand
)
2374 const struct got_error
*err
= NULL
;
2376 wchar_t *wline
= NULL
;
2385 err
= expand_tab(&exstr
, line
);
2390 err
= mbs2ws(&wline
, &wlen
, expand
? exstr
: line
);
2395 if (wlen
> 0 && wline
[wlen
- 1] == L
'\n') {
2396 wline
[wlen
- 1] = L
'\0';
2399 if (wlen
> 0 && wline
[wlen
- 1] == L
'\r') {
2400 wline
[wlen
- 1] = L
'\0';
2404 scrollx
= span_wline(&cols
, 0, wline
, nscroll
, col_tab_align
);
2406 i
= span_wline(&cols
, scrollx
, wline
, wlimit
, col_tab_align
);
2412 *scrollxp
= scrollx
;
2420 static const struct got_error
*
2421 build_refs_str(char **refs_str
, struct got_reflist_head
*refs
,
2422 struct got_object_id
*id
, struct got_repository
*repo
)
2424 static const struct got_error
*err
= NULL
;
2425 struct got_reflist_entry
*re
;
2434 TAILQ_FOREACH(re
, refs
, entry
) {
2435 struct got_tag_object
*tag
= NULL
;
2436 struct got_object_id
*ref_id
;
2439 name
= got_ref_get_name(re
->ref
);
2440 if (strcmp(name
, GOT_REF_HEAD
) == 0)
2442 if (strncmp(name
, "refs/", 5) == 0)
2444 if (strncmp(name
, "got/", 4) == 0)
2446 if (strncmp(name
, "heads/", 6) == 0)
2448 if (strncmp(name
, "remotes/", 8) == 0) {
2450 s
= strstr(name
, "/" GOT_REF_HEAD
);
2451 if (s
!= NULL
&& strcmp(s
, "/" GOT_REF_HEAD
) == 0)
2454 err
= got_ref_resolve(&ref_id
, repo
, re
->ref
);
2457 if (strncmp(name
, "tags/", 5) == 0) {
2458 err
= got_object_open_as_tag(&tag
, repo
, ref_id
);
2460 if (err
->code
!= GOT_ERR_OBJ_TYPE
) {
2464 /* Ref points at something other than a tag. */
2469 cmp
= got_object_id_cmp(tag
?
2470 got_object_tag_get_object_id(tag
) : ref_id
, id
);
2473 got_object_tag_close(tag
);
2477 if (asprintf(refs_str
, "%s%s%s", s
? s
: "",
2478 s
? ", " : "", name
) == -1) {
2479 err
= got_error_from_errno("asprintf");
2490 static const struct got_error
*
2491 format_author(wchar_t **wauthor
, int *author_width
, char *author
, int limit
,
2496 smallerthan
= strchr(author
, '<');
2497 if (smallerthan
&& smallerthan
[1] != '\0')
2498 author
= smallerthan
+ 1;
2499 author
[strcspn(author
, "@>")] = '\0';
2500 return format_line(wauthor
, author_width
, NULL
, author
, 0, limit
,
2504 static const struct got_error
*
2505 draw_commit_marker(struct tog_view
*view
, char c
)
2507 struct tog_color
*tc
;
2509 if (view
->type
!= TOG_VIEW_LOG
)
2510 return got_error_msg(GOT_ERR_NOT_IMPL
, "view not supported");
2512 tc
= get_color(&view
->state
.log
.colors
, TOG_COLOR_COMMIT
);
2514 wattr_on(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2515 if (waddch(view
->window
, c
) == ERR
)
2516 return got_error_msg(GOT_ERR_IO
, "waddch");
2518 wattr_off(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2524 tog_waddwstr(struct tog_view
*view
, wchar_t *wstr
, int width
,
2525 int *col
, int color
, int toeol
)
2527 struct tog_color
*tc
;
2530 x
= col
!= NULL
? *col
: getcurx(view
->window
);
2531 tc
= color
> 0 ? get_color(&view
->state
.log
.colors
, color
) : NULL
;
2534 wattr_on(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2535 waddwstr(view
->window
, wstr
);
2538 while (x
< view
->ncols
) {
2539 waddch(view
->window
, ' ');
2544 wattr_off(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2550 tog_waddnstr(struct tog_view
*view
, const char *str
, int limit
, int color
)
2552 struct tog_color
*tc
;
2555 limit
= view
->ncols
- getcurx(view
->window
);
2557 tc
= get_color(&view
->state
.log
.colors
, color
);
2559 wattr_on(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2560 waddnstr(view
->window
, str
, limit
);
2562 wattr_off(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2565 static const struct got_error
*
2566 draw_author(struct tog_view
*view
, char *author
, int author_display_cols
,
2567 int limit
, int *col
, int color
, int marker_column
,
2568 struct commit_queue_entry
*entry
)
2570 const struct got_error
*err
;
2571 struct tog_log_view_state
*s
= &view
->state
.log
;
2572 struct tog_color
*tc
;
2576 err
= format_author(&wauthor
, &author_width
, author
, limit
, *col
);
2579 if ((tc
= get_color(&s
->colors
, color
)) != NULL
)
2580 wattr_on(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2581 waddwstr(view
->window
, wauthor
);
2584 *col
+= author_width
;
2585 while (*col
< limit
&& author_width
< author_display_cols
+ 2) {
2586 if (entry
!= NULL
&& s
->marked_entry
== entry
&&
2587 author_width
== marker_column
) {
2588 err
= draw_commit_marker(view
, '>');
2591 } else if (entry
!= NULL
&&
2592 tog_base_commit
.marker
!= GOT_WORKTREE_STATE_UNKNOWN
&&
2593 author_width
== marker_column
&&
2594 entry
->idx
== tog_base_commit
.idx
&& !s
->limit_view
) {
2595 err
= draw_commit_marker(view
, tog_base_commit
.marker
);
2599 waddch(view
->window
, ' ');
2604 wattr_off(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2609 static const struct got_error
*
2610 draw_idstr(struct tog_view
*view
, const char *id_str
, int color
)
2614 if (strlen(id_str
) > 9 && asprintf(&str
, "%.8s ", id_str
) == -1)
2615 return got_error_from_errno("asprintf");
2617 tog_waddnstr(view
, str
!= NULL
? str
: id_str
, 0, color
);
2622 static const struct got_error
*
2623 draw_ymd(struct tog_view
*view
, time_t t
, int *limit
, int avail
,
2624 int date_display_cols
)
2627 char datebuf
[12]; /* YYYY-MM-DD + SPACE + NUL */
2629 if (gmtime_r(&t
, &tm
) == NULL
)
2630 return got_error_from_errno("gmtime_r");
2631 if (strftime(datebuf
, sizeof(datebuf
), "%F ", &tm
) == 0)
2632 return got_error(GOT_ERR_NO_SPACE
);
2634 if (avail
<= date_display_cols
)
2635 *limit
= MIN(sizeof(datebuf
) - 1, avail
);
2637 *limit
= MIN(date_display_cols
, sizeof(datebuf
) - 1);
2639 tog_waddnstr(view
, datebuf
, *limit
, TOG_COLOR_DATE
);
2643 static const struct got_error
*
2644 draw_worktree_entry(struct tog_view
*view
, int wt_entry
,
2645 const size_t date_display_cols
, int author_display_cols
)
2647 const struct got_error
*err
= NULL
;
2648 struct tog_log_view_state
*s
= &view
->state
.log
;
2649 wchar_t *wmsg
= NULL
;
2650 char *author
, *msg
= NULL
;
2651 char *base_commit_id
= NULL
;
2652 const char *p
= TOG_WORKTREE_CHANGES_LOCAL_MSG
;
2653 int col
, limit
, scrollx
, width
;
2654 const int avail
= view
->ncols
;
2656 err
= draw_ymd(view
, time(NULL
), &col
, avail
, date_display_cols
);
2662 err
= draw_idstr(view
, "........ ", TOG_COLOR_COMMIT
);
2670 author
= strdup(s
->thread_args
.wctx
.wt_author
);
2672 return got_error_from_errno("strdup");
2674 err
= draw_author(view
, author
, author_display_cols
, avail
- col
,
2675 &col
, TOG_COLOR_AUTHOR
, 0, NULL
);
2681 err
= got_object_id_str(&base_commit_id
, tog_base_commit
.id
);
2684 if (wt_entry
& TOG_WORKTREE_CHANGES_STAGED
)
2685 p
= TOG_WORKTREE_CHANGES_STAGED_MSG
;
2686 if (asprintf(&msg
, "%s based on [%.10s]", p
, base_commit_id
) == -1) {
2687 err
= got_error_from_errno("asprintf");
2691 limit
= avail
- col
;
2692 if (view
->child
!= NULL
&& !view_is_hsplit_top(view
) && limit
> 0)
2693 limit
--; /* for the border */
2695 err
= format_line(&wmsg
, &width
, &scrollx
, msg
, view
->x
, limit
, col
, 1);
2698 tog_waddwstr(view
, &wmsg
[scrollx
], width
, &col
, 0, 1);
2704 free(base_commit_id
);
2708 static const struct got_error
*
2709 draw_commit(struct tog_view
*view
, struct commit_queue_entry
*entry
,
2710 const size_t date_display_cols
, int author_display_cols
)
2712 struct tog_log_view_state
*s
= &view
->state
.log
;
2713 const struct got_error
*err
= NULL
;
2714 struct got_commit_object
*commit
= entry
->commit
;
2715 struct got_object_id
*id
= entry
->id
;
2716 char *author
, *newline
, *logmsg
, *logmsg0
= NULL
, *refs_str
= NULL
;
2717 wchar_t *wrefstr
= NULL
, *wlogmsg
= NULL
;
2718 int refstr_width
, logmsg_width
, col
, limit
, scrollx
, logmsg_x
;
2719 const int avail
= view
->ncols
, marker_column
= author_display_cols
+ 1;
2720 time_t committer_time
;
2721 struct got_reflist_head
*refs
;
2723 if (tog_base_commit
.id
!= NULL
&& tog_base_commit
.idx
== -1 &&
2724 got_object_id_cmp(id
, tog_base_commit
.id
) == 0)
2725 tog_base_commit
.idx
= entry
->idx
;
2726 if (tog_io
.wait_for_ui
&& s
->thread_args
.need_commit_marker
) {
2729 rc
= pthread_cond_wait(&s
->thread_args
.log_loaded
, &tog_mutex
);
2731 return got_error_set_errno(rc
, "pthread_cond_wait");
2734 committer_time
= got_object_commit_get_committer_time(commit
);
2735 err
= draw_ymd(view
, committer_time
, &col
, avail
, date_display_cols
);
2744 err
= got_object_id_str(&id_str
, id
);
2747 err
= draw_idstr(view
, id_str
, TOG_COLOR_COMMIT
);
2756 if (s
->use_committer
)
2757 author
= strdup(got_object_commit_get_committer(commit
));
2759 author
= strdup(got_object_commit_get_author(commit
));
2761 return got_error_from_errno("strdup");
2763 err
= draw_author(view
, author
, author_display_cols
,
2764 avail
- col
, &col
, TOG_COLOR_AUTHOR
, marker_column
, entry
);
2770 err
= got_object_commit_get_logmsg(&logmsg0
, commit
);
2774 while (*logmsg
== '\n')
2776 newline
= strchr(logmsg
, '\n');
2780 limit
= avail
- col
;
2781 if (view
->child
&& !view_is_hsplit_top(view
) && limit
> 0)
2782 limit
--; /* for the border */
2784 /* Prepend reference labels to log message if possible .*/
2785 refs
= got_reflist_object_id_map_lookup(tog_refs_idmap
, id
);
2786 err
= build_refs_str(&refs_str
, refs
, id
, s
->repo
);
2792 if (asprintf(&rs
, "[%s]", refs_str
) == -1) {
2793 err
= got_error_from_errno("asprintf");
2796 err
= format_line(&wrefstr
, &refstr_width
,
2797 &scrollx
, rs
, view
->x
, limit
, col
, 1);
2801 tog_waddwstr(view
, &wrefstr
[scrollx
], refstr_width
,
2802 &col
, TOG_COLOR_COMMIT
, 0);
2807 waddch(view
->window
, ' ');
2811 if (refstr_width
> 0)
2814 int unscrolled_refstr_width
;
2815 size_t len
= wcslen(wrefstr
);
2818 * No need to check for -1 return value here since
2819 * unprintables have been replaced by span_wline().
2821 unscrolled_refstr_width
= wcswidth(wrefstr
, len
);
2822 unscrolled_refstr_width
+= 1; /* trailing space */
2823 logmsg_x
= view
->x
- unscrolled_refstr_width
;
2826 limit
= avail
- col
;
2827 if (view
->child
&& !view_is_hsplit_top(view
) && limit
> 0)
2828 limit
--; /* for the border */
2832 err
= format_line(&wlogmsg
, &logmsg_width
, &scrollx
, logmsg
, logmsg_x
,
2836 tog_waddwstr(view
, &wlogmsg
[scrollx
], logmsg_width
, &col
, 0, 1);
2847 static struct commit_queue_entry
*
2848 alloc_commit_queue_entry(struct got_commit_object
*commit
,
2849 struct got_object_id
*id
)
2851 struct commit_queue_entry
*entry
;
2852 struct got_object_id
*dup
;
2854 entry
= calloc(1, sizeof(*entry
));
2858 dup
= got_object_id_dup(id
);
2865 entry
->commit
= commit
;
2870 pop_commit(struct commit_queue
*commits
)
2872 struct commit_queue_entry
*entry
;
2874 entry
= TAILQ_FIRST(&commits
->head
);
2875 TAILQ_REMOVE(&commits
->head
, entry
, entry
);
2876 if (entry
->worktree_entry
== 0)
2877 got_object_commit_close(entry
->commit
);
2878 commits
->ncommits
--;
2884 free_commits(struct commit_queue
*commits
)
2886 while (!TAILQ_EMPTY(&commits
->head
))
2887 pop_commit(commits
);
2890 static const struct got_error
*
2891 match_commit(int *have_match
, struct got_object_id
*id
,
2892 struct got_commit_object
*commit
, regex_t
*regex
)
2894 const struct got_error
*err
= NULL
;
2895 regmatch_t regmatch
;
2896 char *id_str
= NULL
, *logmsg
= NULL
;
2900 err
= got_object_id_str(&id_str
, id
);
2904 err
= got_object_commit_get_logmsg(&logmsg
, commit
);
2908 if (regexec(regex
, got_object_commit_get_author(commit
), 1,
2909 ®match
, 0) == 0 ||
2910 regexec(regex
, got_object_commit_get_committer(commit
), 1,
2911 ®match
, 0) == 0 ||
2912 regexec(regex
, id_str
, 1, ®match
, 0) == 0 ||
2913 regexec(regex
, logmsg
, 1, ®match
, 0) == 0)
2921 static const struct got_error
*
2922 queue_commits(struct tog_log_thread_args
*a
)
2924 const struct got_error
*err
= NULL
;
2927 * We keep all commits open throughout the lifetime of the log
2928 * view in order to avoid having to re-fetch commits from disk
2929 * while updating the display.
2932 struct got_object_id id
;
2933 struct got_commit_object
*commit
;
2934 struct commit_queue_entry
*entry
;
2935 int limit_match
= 0;
2938 err
= got_commit_graph_iter_next(&id
, a
->graph
, a
->repo
,
2943 err
= got_object_open_as_commit(&commit
, a
->repo
, &id
);
2946 entry
= alloc_commit_queue_entry(commit
, &id
);
2947 if (entry
== NULL
) {
2948 err
= got_error_from_errno("alloc_commit_queue_entry");
2952 errcode
= pthread_mutex_lock(&tog_mutex
);
2954 err
= got_error_set_errno(errcode
,
2955 "pthread_mutex_lock");
2959 entry
->idx
= a
->real_commits
->ncommits
;
2960 TAILQ_INSERT_TAIL(&a
->real_commits
->head
, entry
, entry
);
2961 a
->real_commits
->ncommits
++;
2964 err
= match_commit(&limit_match
, &id
, commit
,
2970 struct commit_queue_entry
*matched
;
2972 matched
= alloc_commit_queue_entry(
2973 entry
->commit
, entry
->id
);
2974 if (matched
== NULL
) {
2975 err
= got_error_from_errno(
2976 "alloc_commit_queue_entry");
2979 matched
->commit
= entry
->commit
;
2980 got_object_commit_retain(entry
->commit
);
2982 matched
->idx
= a
->limit_commits
->ncommits
;
2983 TAILQ_INSERT_TAIL(&a
->limit_commits
->head
,
2985 a
->limit_commits
->ncommits
++;
2989 * This is how we signal log_thread() that we
2990 * have found a match, and that it should be
2991 * counted as a new entry for the view.
2993 a
->limit_match
= limit_match
;
2996 if (*a
->searching
== TOG_SEARCH_FORWARD
&&
2997 !*a
->search_next_done
) {
2999 err
= match_commit(&have_match
, &id
, commit
, a
->regex
);
3004 if (limit_match
&& have_match
)
3005 *a
->search_next_done
=
3006 TOG_SEARCH_HAVE_MORE
;
3007 } else if (have_match
)
3008 *a
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
3011 errcode
= pthread_mutex_unlock(&tog_mutex
);
3012 if (errcode
&& err
== NULL
)
3013 err
= got_error_set_errno(errcode
,
3014 "pthread_mutex_unlock");
3017 } while (*a
->searching
== TOG_SEARCH_FORWARD
&& !*a
->search_next_done
);
3023 select_commit(struct tog_log_view_state
*s
)
3025 struct commit_queue_entry
*entry
;
3028 entry
= s
->first_displayed_entry
;
3030 if (ncommits
== s
->selected
) {
3031 s
->selected_entry
= entry
;
3034 entry
= TAILQ_NEXT(entry
, entry
);
3039 /* lifted from got.c:652 (TODO make lib routine) */
3040 static const struct got_error
*
3041 get_author(char **author
, struct got_repository
*repo
,
3042 struct got_worktree
*worktree
)
3044 const struct got_error
*err
= NULL
;
3045 const char *got_author
= NULL
, *name
, *email
;
3046 const struct got_gotconfig
*worktree_conf
= NULL
, *repo_conf
= NULL
;
3051 worktree_conf
= got_worktree_get_gotconfig(worktree
);
3052 repo_conf
= got_repo_get_gotconfig(repo
);
3055 * Priority of potential author information sources, from most
3056 * significant to least significant:
3057 * 1) work tree's .got/got.conf file
3058 * 2) repository's got.conf file
3059 * 3) repository's git config file
3060 * 4) environment variables
3061 * 5) global git config files (in user's home directory or /etc)
3065 got_author
= got_gotconfig_get_author(worktree_conf
);
3066 if (got_author
== NULL
)
3067 got_author
= got_gotconfig_get_author(repo_conf
);
3068 if (got_author
== NULL
) {
3069 name
= got_repo_get_gitconfig_author_name(repo
);
3070 email
= got_repo_get_gitconfig_author_email(repo
);
3071 if (name
&& email
) {
3072 if (asprintf(author
, "%s <%s>", name
, email
) == -1)
3073 return got_error_from_errno("asprintf");
3077 got_author
= getenv("GOT_AUTHOR");
3078 if (got_author
== NULL
) {
3079 name
= got_repo_get_global_gitconfig_author_name(repo
);
3080 email
= got_repo_get_global_gitconfig_author_email(
3082 if (name
&& email
) {
3083 if (asprintf(author
, "%s <%s>", name
, email
)
3085 return got_error_from_errno("asprintf");
3088 /* TODO: Look up user in password database? */
3089 return got_error(GOT_ERR_COMMIT_NO_AUTHOR
);
3093 *author
= strdup(got_author
);
3094 if (*author
== NULL
)
3095 err
= got_error_from_errno("strdup");
3099 static const struct got_error
*
3100 push_worktree_entry(struct tog_log_thread_args
*ta
, int wt_entry
,
3101 struct got_worktree
*worktree
)
3103 struct commit_queue_entry
*e
, *entry
;
3106 entry
= calloc(1, sizeof(*entry
));
3108 return got_error_from_errno("calloc");
3111 entry
->worktree_entry
= wt_entry
;
3113 rc
= pthread_mutex_lock(&tog_mutex
);
3116 return got_error_set_errno(rc
, "pthread_mutex_lock");
3119 TAILQ_FOREACH(e
, &ta
->real_commits
->head
, entry
)
3122 TAILQ_INSERT_HEAD(&ta
->real_commits
->head
, entry
, entry
);
3123 ta
->wctx
.wt_state
|= wt_entry
;
3124 ++ta
->real_commits
->ncommits
;
3125 ++tog_base_commit
.idx
;
3127 rc
= pthread_mutex_unlock(&tog_mutex
);
3129 return got_error_set_errno(rc
, "pthread_mutex_unlock");
3134 static const struct got_error
*
3135 check_cancelled(void *arg
)
3137 if (tog_sigint_received
|| tog_sigpipe_received
)
3138 return got_error(GOT_ERR_CANCELLED
);
3142 static const struct got_error
*
3143 check_local_changes(void *arg
, unsigned char status
,
3144 unsigned char staged_status
, const char *path
,
3145 struct got_object_id
*blob_id
, struct got_object_id
*staged_blob_id
,
3146 struct got_object_id
*commit_id
, int dirfd
, const char *de_name
)
3148 int *have_local_changes
= arg
;
3151 case GOT_STATUS_ADD
:
3152 case GOT_STATUS_DELETE
:
3153 case GOT_STATUS_MODIFY
:
3154 case GOT_STATUS_CONFLICT
:
3155 *have_local_changes
|= TOG_WORKTREE_CHANGES_LOCAL
;
3160 switch (staged_status
) {
3161 case GOT_STATUS_ADD
:
3162 case GOT_STATUS_DELETE
:
3163 case GOT_STATUS_MODIFY
:
3164 *have_local_changes
|= TOG_WORKTREE_CHANGES_STAGED
;
3172 static const struct got_error
*
3173 tog_worktree_status(struct tog_log_thread_args
*ta
)
3175 const struct got_error
*err
, *close_err
;
3176 struct tog_worktree_ctx
*wctx
= &ta
->wctx
;
3177 struct got_worktree
*wt
= ta
->worktree
;
3178 struct got_pathlist_head paths
;
3185 cwd
= getcwd(NULL
, 0);
3187 return got_error_from_errno("getcwd");
3189 err
= got_worktree_open(&wt
, cwd
, NULL
);
3191 if (err
->code
== GOT_ERR_NOT_WORKTREE
) {
3193 * Shouldn't happen; this routine should only
3194 * be called if tog is invoked in a worktree.
3198 } else if (err
->code
== GOT_ERR_WORKTREE_BUSY
)
3199 err
= NULL
; /* retry next redraw */
3204 err
= got_pathlist_insert(NULL
, &paths
, "", NULL
);
3208 err
= got_worktree_status(wt
, &paths
, ta
->repo
, 0,
3209 check_local_changes
, &wt_state
, check_cancelled
, NULL
);
3211 if (err
->code
!= GOT_ERR_CANCELLED
)
3216 if (wt_state
!= 0) {
3217 err
= get_author(&wctx
->wt_author
, ta
->repo
, wt
);
3219 if (err
->code
!= GOT_ERR_COMMIT_NO_AUTHOR
)
3221 if ((wctx
->wt_author
= strdup("")) == NULL
) {
3222 err
= got_error_from_errno("strdup");
3227 wctx
->wt_root
= strdup(got_worktree_get_root_path(wt
));
3228 if (wctx
->wt_root
== NULL
) {
3229 err
= got_error_from_errno("strdup");
3233 wctx
->wt_ref
= strdup(got_worktree_get_head_ref_name(wt
));
3234 if (wctx
->wt_ref
== NULL
) {
3235 err
= got_error_from_errno("strdup");
3241 * Push staged entry first so it's the second log entry
3242 * if there are both staged and unstaged work tree changes.
3244 if (wt_state
& TOG_WORKTREE_CHANGES_STAGED
&&
3245 (wctx
->wt_state
& TOG_WORKTREE_CHANGES_STAGED
) == 0) {
3246 err
= push_worktree_entry(ta
, TOG_WORKTREE_CHANGES_STAGED
, wt
);
3250 if (wt_state
& TOG_WORKTREE_CHANGES_LOCAL
&&
3251 (wctx
->wt_state
& TOG_WORKTREE_CHANGES_LOCAL
) == 0) {
3252 err
= push_worktree_entry(ta
, TOG_WORKTREE_CHANGES_LOCAL
, wt
);
3258 got_pathlist_free(&paths
, GOT_PATHLIST_FREE_NONE
);
3259 if (ta
->worktree
== NULL
&& wt
!= NULL
) {
3260 close_err
= got_worktree_close(wt
);
3261 if (close_err
!= NULL
&& err
== NULL
)
3268 static const struct got_error
*
3269 worktree_headref_str(char **ret
, const char *ref
)
3271 if (strncmp(ref
, "refs/heads/", 11) == 0)
3272 *ret
= strdup(ref
+ 11);
3276 return got_error_from_errno("strdup");
3281 static const struct got_error
*
3282 fmtindex(char **index
, int *ncommits
, int wt_state
,
3283 struct commit_queue_entry
*entry
, int limit_view
)
3288 if (*ncommits
> 0 && wt_state
& TOG_WORKTREE_CHANGES_LOCAL
)
3290 if (*ncommits
> 0 && wt_state
& TOG_WORKTREE_CHANGES_STAGED
)
3294 if (entry
!= NULL
&& entry
->worktree_entry
== 0) {
3296 * Display 1-based index of selected commit entries only.
3297 * If a work tree entry is selected, show an index of 0.
3300 if (wt_state
== 0 || limit_view
)
3302 else if (wt_state
> TOG_WORKTREE_CHANGES_STAGED
)
3305 if (asprintf(index
, " [%d/%d] ", idx
, *ncommits
) == -1) {
3307 return got_error_from_errno("asprintf");
3313 static const struct got_error
*
3314 fmtheader(char **header
, int *ncommits
, struct commit_queue_entry
*entry
,
3315 struct tog_view
*view
)
3317 const struct got_error
*err
;
3318 struct tog_log_view_state
*s
= &view
->state
.log
;
3319 struct tog_worktree_ctx
*wctx
= &s
->thread_args
.wctx
;
3320 struct got_reflist_head
*refs
;
3321 char *id_str
= NULL
, *index
= NULL
;
3322 char *wthdr
= NULL
, *ncommits_str
= NULL
;
3323 char *refs_str
= NULL
;
3327 wt_entry
= entry
!= NULL
? entry
->worktree_entry
: 0;
3329 if (entry
&& !(view
->searching
&& view
->search_next_done
== 0)) {
3330 if (entry
->worktree_entry
== 0) {
3331 err
= got_object_id_str(&id_str
, entry
->id
);
3334 refs
= got_reflist_object_id_map_lookup(tog_refs_idmap
,
3336 err
= build_refs_str(&refs_str
, refs
,
3337 entry
->id
, s
->repo
);
3341 err
= worktree_headref_str(&refs_str
, wctx
->wt_ref
);
3347 err
= fmtindex(&index
, ncommits
, wctx
->wt_state
, entry
, s
->limit_view
);
3351 if (s
->thread_args
.commits_needed
> 0 || s
->thread_args
.load_all
) {
3352 if (asprintf(&ncommits_str
, "%s%s", index
,
3353 (view
->searching
&& !view
->search_next_done
) ?
3354 "searching..." : "loading...") == -1) {
3355 err
= got_error_from_errno("asprintf");
3359 const char *search_str
= NULL
;
3360 const char *limit_str
= NULL
;
3362 if (view
->searching
) {
3363 if (view
->search_next_done
== TOG_SEARCH_NO_MORE
)
3364 search_str
= "no more matches";
3365 else if (view
->search_next_done
== TOG_SEARCH_HAVE_NONE
)
3366 search_str
= "no matches found";
3367 else if (!view
->search_next_done
)
3368 search_str
= "searching...";
3371 if (s
->limit_view
&& ncommits
== 0)
3372 limit_str
= "no matches found";
3374 if (asprintf(&ncommits_str
, "%s%s %s", index
,
3375 search_str
? search_str
: (refs_str
? refs_str
: ""),
3376 limit_str
? limit_str
: "") == -1) {
3377 err
= got_error_from_errno("asprintf");
3382 if (wt_entry
!= 0) {
3383 const char *t
= "", *p
= TOG_WORKTREE_CHANGES_LOCAL_MSG
;
3385 if (wt_entry
== TOG_WORKTREE_CHANGES_STAGED
) {
3386 p
= TOG_WORKTREE_CHANGES_STAGED_MSG
;
3389 if (asprintf(&wthdr
, "%s%s (%s)", t
, wctx
->wt_root
, p
) == -1) {
3390 err
= got_error_from_errno("asprintf");
3395 if (s
->in_repo_path
!= NULL
&& strcmp(s
->in_repo_path
, "/") != 0) {
3396 if (asprintf(header
, "%s%s %s%s",
3397 wt_entry
== 0 ? "commit " : "diff ",
3398 wt_entry
== 0 ? id_str
? id_str
:
3399 "........................................" :
3400 wthdr
!= NULL
? wthdr
: "", s
->in_repo_path
,
3401 ncommits_str
) == -1)
3402 err
= got_error_from_errno("asprintf");
3403 } else if (asprintf(header
, "%s%s%s",
3404 wt_entry
== 0 ? "commit " : "diff ",
3405 wt_entry
== 0 ? id_str
? id_str
:
3406 "........................................" :
3407 wthdr
!= NULL
? wthdr
: "", ncommits_str
) == -1)
3408 err
= got_error_from_errno("asprintf");
3421 static const struct got_error
*
3422 draw_commits(struct tog_view
*view
)
3424 const struct got_error
*err
;
3425 struct tog_log_view_state
*s
= &view
->state
.log
;
3426 struct commit_queue_entry
*entry
= s
->selected_entry
;
3427 int width
, limit
= view
->nlines
;
3428 int ncommits
= s
->commits
->ncommits
, author_cols
= 4, refstr_cols
;
3431 static const size_t date_display_cols
= 12;
3433 if (view_is_hsplit_top(view
))
3434 --limit
; /* account for border */
3436 if (s
->thread_args
.commits_needed
== 0 &&
3437 s
->thread_args
.need_wt_status
== 0 &&
3438 s
->thread_args
.need_commit_marker
== 0 && !using_mock_io
)
3439 halfdelay(10); /* disable fast refresh */
3441 err
= fmtheader(&header
, &ncommits
, entry
, view
);
3445 err
= format_line(&wline
, &width
, NULL
, header
, 0, view
->ncols
, 0, 0);
3450 werase(view
->window
);
3452 if (view_needs_focus_indication(view
))
3453 wstandout(view
->window
);
3454 tog_waddwstr(view
, wline
, width
, NULL
, TOG_COLOR_COMMIT
, 1);
3455 if (view_needs_focus_indication(view
))
3456 wstandend(view
->window
);
3461 /* Grow author column size if necessary, and set view->maxx. */
3462 entry
= s
->first_displayed_entry
;
3466 struct got_reflist_head
*refs
;
3467 struct got_commit_object
*c
= entry
->commit
;
3468 char *author
, *eol
, *msg
, *msg0
, *refs_str
;
3469 wchar_t *wauthor
, *wmsg
;
3472 if (ncommits
>= limit
- 1)
3474 if (entry
->worktree_entry
!= 0)
3475 author
= strdup(s
->thread_args
.wctx
.wt_author
);
3476 else if (s
->use_committer
)
3477 author
= strdup(got_object_commit_get_committer(c
));
3479 author
= strdup(got_object_commit_get_author(c
));
3481 return got_error_from_errno("strdup");
3483 err
= format_author(&wauthor
, &width
, author
, COLS
,
3485 if (author_cols
< width
)
3486 author_cols
= width
;
3491 if (entry
->worktree_entry
!= 0) {
3492 if (entry
->worktree_entry
== TOG_WORKTREE_CHANGES_LOCAL
)
3493 width
= sizeof(TOG_WORKTREE_CHANGES_LOCAL_MSG
);
3495 width
= sizeof(TOG_WORKTREE_CHANGES_STAGED_MSG
);
3496 view
->maxx
= MAX(view
->maxx
, width
- 1);
3497 entry
= TAILQ_NEXT(entry
, entry
);
3501 refs
= got_reflist_object_id_map_lookup(tog_refs_idmap
,
3503 err
= build_refs_str(&refs_str
, refs
, entry
->id
, s
->repo
);
3509 err
= format_line(&ws
, &width
, NULL
, refs_str
,
3510 0, INT_MAX
, date_display_cols
+ author_cols
, 0);
3516 refstr_cols
= width
+ 3; /* account for [ ] + space */
3519 err
= got_object_commit_get_logmsg(&msg0
, c
);
3523 while (*msg
== '\n')
3525 if ((eol
= strchr(msg
, '\n')))
3527 err
= format_line(&wmsg
, &width
, NULL
, msg
, 0, INT_MAX
,
3528 date_display_cols
+ author_cols
+ refstr_cols
, 0);
3533 view
->maxx
= MAX(view
->maxx
, width
+ refstr_cols
);
3535 entry
= TAILQ_NEXT(entry
, entry
);
3538 entry
= s
->first_displayed_entry
;
3539 s
->last_displayed_entry
= s
->first_displayed_entry
;
3542 if (ncommits
>= limit
- 1)
3544 if (ncommits
== s
->selected
)
3545 wstandout(view
->window
);
3546 if (entry
->worktree_entry
== 0)
3547 err
= draw_commit(view
, entry
,
3548 date_display_cols
, author_cols
);
3550 err
= draw_worktree_entry(view
, entry
->worktree_entry
,
3551 date_display_cols
, author_cols
);
3552 if (ncommits
== s
->selected
)
3553 wstandend(view
->window
);
3557 s
->last_displayed_entry
= entry
;
3558 entry
= TAILQ_NEXT(entry
, entry
);
3566 log_scroll_up(struct tog_log_view_state
*s
, int maxscroll
)
3568 struct commit_queue_entry
*entry
;
3571 entry
= TAILQ_FIRST(&s
->commits
->head
);
3572 if (s
->first_displayed_entry
== entry
)
3575 entry
= s
->first_displayed_entry
;
3576 while (entry
&& nscrolled
< maxscroll
) {
3577 entry
= TAILQ_PREV(entry
, commit_queue_head
, entry
);
3579 s
->first_displayed_entry
= entry
;
3585 static const struct got_error
*
3586 trigger_log_thread(struct tog_view
*view
, int wait
)
3588 const struct got_error
*err
;
3589 struct tog_log_thread_args
*ta
= &view
->state
.log
.thread_args
;
3593 halfdelay(1); /* fast refresh while loading commits */
3595 while (!ta
->log_complete
&& !tog_thread_error
&&
3596 (ta
->commits_needed
> 0 || ta
->load_all
)) {
3597 /* Wake the log thread. */
3598 errcode
= pthread_cond_signal(&ta
->need_commits
);
3600 return got_error_set_errno(errcode
,
3601 "pthread_cond_signal");
3604 * The mutex will be released while the view loop waits
3605 * in wgetch(), at which time the log thread will run.
3610 /* Display progress update in log view. */
3611 err
= show_log_view(view
);
3617 /* Wait right here while next commit is being loaded. */
3618 errcode
= pthread_cond_wait(&ta
->commit_loaded
, &tog_mutex
);
3620 return got_error_set_errno(errcode
,
3621 "pthread_cond_wait");
3623 /* Display progress update in log view. */
3624 err
= show_log_view(view
);
3634 static const struct got_error
*
3635 request_log_commits(struct tog_view
*view
)
3637 struct tog_log_view_state
*state
= &view
->state
.log
;
3638 const struct got_error
*err
= NULL
;
3640 if (state
->thread_args
.log_complete
)
3643 state
->thread_args
.commits_needed
+= view
->nscrolled
;
3644 err
= trigger_log_thread(view
, 1);
3645 view
->nscrolled
= 0;
3650 static const struct got_error
*
3651 log_scroll_down(struct tog_view
*view
, int maxscroll
)
3653 struct tog_log_view_state
*s
= &view
->state
.log
;
3654 const struct got_error
*err
= NULL
;
3655 struct commit_queue_entry
*pentry
;
3656 int nscrolled
= 0, ncommits_needed
;
3658 if (s
->last_displayed_entry
== NULL
)
3661 ncommits_needed
= s
->last_displayed_entry
->idx
+ 2 + maxscroll
;
3662 if (s
->commits
->ncommits
< ncommits_needed
&&
3663 !s
->thread_args
.log_complete
) {
3665 * Ask the log thread for required amount of commits.
3667 s
->thread_args
.commits_needed
+=
3668 ncommits_needed
- s
->commits
->ncommits
;
3669 err
= trigger_log_thread(view
, 1);
3675 pentry
= TAILQ_NEXT(s
->last_displayed_entry
, entry
);
3676 if (pentry
== NULL
&& view
->mode
!= TOG_VIEW_SPLIT_HRZN
)
3679 s
->last_displayed_entry
= pentry
?
3680 pentry
: s
->last_displayed_entry
;
3682 pentry
= TAILQ_NEXT(s
->first_displayed_entry
, entry
);
3685 s
->first_displayed_entry
= pentry
;
3686 } while (++nscrolled
< maxscroll
);
3688 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
&& !s
->thread_args
.log_complete
)
3689 view
->nscrolled
+= nscrolled
;
3691 view
->nscrolled
= 0;
3696 static const struct got_error
*
3697 open_diff_view_for_commit(struct tog_view
**new_view
, int begin_y
, int begin_x
,
3698 struct commit_queue_entry
*entry
, struct tog_view
*log_view
,
3699 struct got_repository
*repo
)
3701 const struct got_error
*err
;
3702 struct got_object_qid
*p
;
3703 struct got_object_id
*parent_id
;
3704 struct tog_view
*diff_view
;
3705 struct tog_log_view_state
*ls
= NULL
;
3706 const char *worktree_root
= NULL
;
3708 diff_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_DIFF
);
3709 if (diff_view
== NULL
)
3710 return got_error_from_errno("view_open");
3712 if (log_view
!= NULL
) {
3713 ls
= &log_view
->state
.log
;
3714 worktree_root
= ls
->thread_args
.wctx
.wt_root
;
3717 if (ls
!= NULL
&& ls
->marked_entry
!= NULL
&&
3718 ls
->marked_entry
!= ls
->selected_entry
)
3719 parent_id
= ls
->marked_entry
->id
;
3720 else if (entry
->worktree_entry
== 0 &&
3721 (p
= STAILQ_FIRST(got_object_commit_get_parent_ids(entry
->commit
))))
3726 err
= open_diff_view(diff_view
, parent_id
, entry
->id
, NULL
, NULL
, 3, 0,
3727 0, 0, entry
->worktree_entry
, worktree_root
, log_view
, repo
, NULL
);
3729 *new_view
= diff_view
;
3733 static const struct got_error
*
3734 tree_view_visit_subtree(struct tog_tree_view_state
*s
,
3735 struct got_tree_object
*subtree
)
3737 struct tog_parent_tree
*parent
;
3739 parent
= calloc(1, sizeof(*parent
));
3741 return got_error_from_errno("calloc");
3743 parent
->tree
= s
->tree
;
3744 parent
->first_displayed_entry
= s
->first_displayed_entry
;
3745 parent
->selected_entry
= s
->selected_entry
;
3746 parent
->selected
= s
->selected
;
3747 TAILQ_INSERT_HEAD(&s
->parents
, parent
, entry
);
3750 s
->first_displayed_entry
= NULL
;
3754 static const struct got_error
*
3755 tree_view_walk_path(struct tog_tree_view_state
*s
,
3756 struct got_commit_object
*commit
, const char *path
)
3758 const struct got_error
*err
= NULL
;
3759 struct got_tree_object
*tree
= NULL
;
3761 char *slash
, *subpath
= NULL
;
3763 /* Walk the path and open corresponding tree objects. */
3766 struct got_tree_entry
*te
;
3767 struct got_object_id
*tree_id
;
3773 /* Ensure the correct subtree entry is selected. */
3774 slash
= strchr(p
, '/');
3776 te_name
= strdup(p
);
3778 te_name
= strndup(p
, slash
- p
);
3779 if (te_name
== NULL
) {
3780 err
= got_error_from_errno("strndup");
3783 te
= got_object_tree_find_entry(s
->tree
, te_name
);
3785 err
= got_error_path(te_name
, GOT_ERR_NO_TREE_ENTRY
);
3790 s
->first_displayed_entry
= s
->selected_entry
= te
;
3792 if (!S_ISDIR(got_tree_entry_get_mode(s
->selected_entry
)))
3793 break; /* jump to this file's entry */
3795 slash
= strchr(p
, '/');
3797 subpath
= strndup(path
, slash
- path
);
3799 subpath
= strdup(path
);
3800 if (subpath
== NULL
) {
3801 err
= got_error_from_errno("strdup");
3805 err
= got_object_id_by_path(&tree_id
, s
->repo
, commit
,
3810 err
= got_object_open_as_tree(&tree
, s
->repo
, tree_id
);
3815 err
= tree_view_visit_subtree(s
, tree
);
3817 got_object_tree_close(tree
);
3831 static const struct got_error
*
3832 browse_commit_tree(struct tog_view
**new_view
, int begin_y
, int begin_x
,
3833 struct commit_queue_entry
*entry
, const char *path
,
3834 const char *head_ref_name
, struct got_repository
*repo
)
3836 const struct got_error
*err
= NULL
;
3837 struct tog_tree_view_state
*s
;
3838 struct tog_view
*tree_view
;
3840 tree_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_TREE
);
3841 if (tree_view
== NULL
)
3842 return got_error_from_errno("view_open");
3844 err
= open_tree_view(tree_view
, entry
->id
, head_ref_name
, repo
);
3847 s
= &tree_view
->state
.tree
;
3849 *new_view
= tree_view
;
3851 if (got_path_is_root_dir(path
))
3854 return tree_view_walk_path(s
, entry
->commit
, path
);
3858 * If work tree entries have been pushed onto the commit queue and the
3859 * first commit entry is still displayed, scroll the view so the new
3860 * work tree entries are visible. If the selection cursor is still on
3861 * the first commit entry, keep the cursor in place such that the first
3862 * work tree entry is selected, otherwise move the selection cursor so
3863 * the currently selected commit stays selected if it remains on screen.
3866 worktree_entries_reveal(struct tog_log_thread_args
*a
)
3868 struct commit_queue_entry
**first
= a
->first_displayed_entry
;
3869 struct commit_queue_entry
**select
= a
->selected_entry
;
3870 int *cursor
= a
->selected
;
3871 int wts
= a
->wctx
.wt_state
;
3873 #define select_worktree_entry(_first, _selected) do { \
3874 *_first = TAILQ_FIRST(&a->real_commits->head); \
3875 *_selected = *_first; \
3879 select_worktree_entry(first
, select
);
3880 else if (*select
== *first
) {
3881 if (wts
== TOG_WORKTREE_CHANGES_LOCAL
&& (*first
)->idx
== 1)
3882 select_worktree_entry(first
, select
);
3883 else if (wts
== TOG_WORKTREE_CHANGES_STAGED
&&
3885 select_worktree_entry(first
, select
);
3886 else if (wts
& TOG_WORKTREE_CHANGES_ALL
&& (*first
)->idx
== 2)
3887 select_worktree_entry(first
, select
);
3888 } else if (wts
& TOG_WORKTREE_CHANGES_ALL
&& (*first
)->idx
== 2) {
3889 *first
= TAILQ_FIRST(&a
->real_commits
->head
);
3890 if (*cursor
+ 2 < *a
->view_nlines
- 1)
3892 else if (*cursor
+ 1 < *a
->view_nlines
- 1) {
3893 *select
= TAILQ_PREV(*select
, commit_queue_head
, entry
);
3896 *select
= TAILQ_PREV(*select
, commit_queue_head
, entry
);
3897 *select
= TAILQ_PREV(*select
, commit_queue_head
, entry
);
3899 } else if (wts
!= 0 && (*first
)->idx
== 1) {
3900 *first
= TAILQ_FIRST(&a
->real_commits
->head
);
3901 if (*cursor
+ 1 < *a
->view_nlines
- 1)
3904 *select
= TAILQ_PREV(*select
, commit_queue_head
, entry
);
3906 #undef select_worktree_entry
3909 static const struct got_error
*
3910 block_signals_used_by_main_thread(void)
3915 if (sigemptyset(&sigset
) == -1)
3916 return got_error_from_errno("sigemptyset");
3918 /* tog handles SIGWINCH, SIGCONT, SIGINT, SIGTERM */
3919 if (sigaddset(&sigset
, SIGWINCH
) == -1)
3920 return got_error_from_errno("sigaddset");
3921 if (sigaddset(&sigset
, SIGCONT
) == -1)
3922 return got_error_from_errno("sigaddset");
3923 if (sigaddset(&sigset
, SIGINT
) == -1)
3924 return got_error_from_errno("sigaddset");
3925 if (sigaddset(&sigset
, SIGTERM
) == -1)
3926 return got_error_from_errno("sigaddset");
3928 /* ncurses handles SIGTSTP */
3929 if (sigaddset(&sigset
, SIGTSTP
) == -1)
3930 return got_error_from_errno("sigaddset");
3932 errcode
= pthread_sigmask(SIG_BLOCK
, &sigset
, NULL
);
3934 return got_error_set_errno(errcode
, "pthread_sigmask");
3940 log_thread(void *arg
)
3942 const struct got_error
*err
= NULL
;
3944 struct tog_log_thread_args
*a
= arg
;
3948 * Sync startup with main thread such that we begin our
3949 * work once view_input() has released the mutex.
3951 errcode
= pthread_mutex_lock(&tog_mutex
);
3953 err
= got_error_set_errno(errcode
, "pthread_mutex_lock");
3957 err
= block_signals_used_by_main_thread();
3959 pthread_mutex_unlock(&tog_mutex
);
3963 while (!done
&& !err
&& !tog_fatal_signal_received()) {
3964 errcode
= pthread_mutex_unlock(&tog_mutex
);
3966 err
= got_error_set_errno(errcode
,
3967 "pthread_mutex_unlock");
3970 err
= queue_commits(a
);
3972 if (err
->code
!= GOT_ERR_ITER_COMPLETED
)
3976 a
->commits_needed
= 0;
3977 } else if (a
->commits_needed
> 0 && !a
->load_all
) {
3980 a
->commits_needed
--;
3982 a
->commits_needed
--;
3985 errcode
= pthread_mutex_lock(&tog_mutex
);
3987 err
= got_error_set_errno(errcode
,
3988 "pthread_mutex_lock");
3990 } else if (*a
->quit
)
3992 else if (*a
->limiting
&& *a
->first_displayed_entry
== NULL
) {
3993 *a
->first_displayed_entry
=
3994 TAILQ_FIRST(&a
->limit_commits
->head
);
3995 *a
->selected_entry
= *a
->first_displayed_entry
;
3996 } else if (*a
->first_displayed_entry
== NULL
) {
3997 *a
->first_displayed_entry
=
3998 TAILQ_FIRST(&a
->real_commits
->head
);
3999 *a
->selected_entry
= *a
->first_displayed_entry
;
4002 errcode
= pthread_cond_signal(&a
->commit_loaded
);
4004 err
= got_error_set_errno(errcode
,
4005 "pthread_cond_signal");
4006 pthread_mutex_unlock(&tog_mutex
);
4010 if (a
->commits_needed
== 0 && a
->need_wt_status
) {
4011 errcode
= pthread_mutex_unlock(&tog_mutex
);
4013 err
= got_error_set_errno(errcode
,
4014 "pthread_mutex_unlock");
4017 err
= tog_worktree_status(a
);
4020 errcode
= pthread_mutex_lock(&tog_mutex
);
4022 err
= got_error_set_errno(errcode
,
4023 "pthread_mutex_lock");
4026 if (a
->wctx
.wt_state
!= 0)
4027 worktree_entries_reveal(a
);
4028 a
->need_wt_status
= 0;
4031 if (a
->commits_needed
== 0 &&
4032 a
->need_commit_marker
&& a
->worktree
) {
4033 errcode
= pthread_mutex_unlock(&tog_mutex
);
4035 err
= got_error_set_errno(errcode
,
4036 "pthread_mutex_unlock");
4039 err
= got_worktree_get_state(&tog_base_commit
.marker
,
4040 a
->repo
, a
->worktree
, NULL
, NULL
);
4043 errcode
= pthread_mutex_lock(&tog_mutex
);
4045 err
= got_error_set_errno(errcode
,
4046 "pthread_mutex_lock");
4049 a
->need_commit_marker
= 0;
4051 * The main thread did not close this
4052 * work tree yet. Close it now.
4054 got_worktree_close(a
->worktree
);
4062 a
->commits_needed
= 0;
4064 if (a
->commits_needed
== 0 && !a
->load_all
) {
4065 if (tog_io
.wait_for_ui
) {
4066 errcode
= pthread_cond_signal(
4069 err
= got_error_set_errno(
4071 "pthread_cond_signal");
4072 pthread_mutex_unlock(
4078 errcode
= pthread_cond_wait(&a
->need_commits
,
4081 err
= got_error_set_errno(errcode
,
4082 "pthread_cond_wait");
4083 pthread_mutex_unlock(&tog_mutex
);
4091 a
->log_complete
= 1;
4092 if (tog_io
.wait_for_ui
) {
4093 errcode
= pthread_cond_signal(&a
->log_loaded
);
4094 if (errcode
&& err
== NULL
)
4095 err
= got_error_set_errno(errcode
,
4096 "pthread_cond_signal");
4099 errcode
= pthread_mutex_unlock(&tog_mutex
);
4101 err
= got_error_set_errno(errcode
, "pthread_mutex_unlock");
4104 tog_thread_error
= 1;
4105 pthread_cond_signal(&a
->commit_loaded
);
4107 got_worktree_close(a
->worktree
);
4114 static const struct got_error
*
4115 stop_log_thread(struct tog_log_view_state
*s
)
4117 const struct got_error
*err
= NULL
, *thread_err
= NULL
;
4122 errcode
= pthread_cond_signal(&s
->thread_args
.need_commits
);
4124 return got_error_set_errno(errcode
,
4125 "pthread_cond_signal");
4126 errcode
= pthread_mutex_unlock(&tog_mutex
);
4128 return got_error_set_errno(errcode
,
4129 "pthread_mutex_unlock");
4130 errcode
= pthread_join(s
->thread
, (void **)&thread_err
);
4132 return got_error_set_errno(errcode
, "pthread_join");
4133 errcode
= pthread_mutex_lock(&tog_mutex
);
4135 return got_error_set_errno(errcode
,
4136 "pthread_mutex_lock");
4137 s
->thread
= 0; //NULL;
4140 if (s
->thread_args
.repo
) {
4141 err
= got_repo_close(s
->thread_args
.repo
);
4142 s
->thread_args
.repo
= NULL
;
4145 if (s
->thread_args
.pack_fds
) {
4146 const struct got_error
*pack_err
=
4147 got_repo_pack_fds_close(s
->thread_args
.pack_fds
);
4150 s
->thread_args
.pack_fds
= NULL
;
4153 if (s
->thread_args
.graph
) {
4154 got_commit_graph_close(s
->thread_args
.graph
);
4155 s
->thread_args
.graph
= NULL
;
4158 return err
? err
: thread_err
;
4162 worktree_ctx_close(struct tog_log_thread_args
*ta
)
4164 struct tog_worktree_ctx
*wctx
= &ta
->wctx
;
4167 free(wctx
->wt_author
);
4168 wctx
->wt_author
= NULL
;
4169 free(wctx
->wt_root
);
4170 wctx
->wt_root
= NULL
;
4172 wctx
->wt_ref
= NULL
;
4174 ta
->need_wt_status
= 1;
4178 static const struct got_error
*
4179 close_log_view(struct tog_view
*view
)
4181 const struct got_error
*err
= NULL
;
4182 struct tog_log_view_state
*s
= &view
->state
.log
;
4187 err
= stop_log_thread(s
);
4189 errcode
= pthread_cond_destroy(&s
->thread_args
.need_commits
);
4190 if (errcode
&& err
== NULL
)
4191 err
= got_error_set_errno(errcode
, "pthread_cond_destroy");
4193 errcode
= pthread_cond_destroy(&s
->thread_args
.commit_loaded
);
4194 if (errcode
&& err
== NULL
)
4195 err
= got_error_set_errno(errcode
, "pthread_cond_destroy");
4197 if (using_mock_io
) {
4198 errcode
= pthread_cond_destroy(&s
->thread_args
.log_loaded
);
4199 if (errcode
&& err
== NULL
)
4200 err
= got_error_set_errno(errcode
,
4201 "pthread_cond_destroy");
4204 free_commits(&s
->limit_commits
);
4205 free_commits(&s
->real_commits
);
4206 free_colors(&s
->colors
);
4207 free(s
->in_repo_path
);
4208 s
->in_repo_path
= NULL
;
4211 free(s
->head_ref_name
);
4212 s
->head_ref_name
= NULL
;
4213 worktree_ctx_close(&s
->thread_args
);
4218 * We use two queues to implement the limit feature: first consists of
4219 * commits matching the current limit_regex; second is the real queue
4220 * of all known commits (real_commits). When the user starts limiting,
4221 * we swap queues such that all movement and displaying functionality
4222 * works with very slight change.
4224 static const struct got_error
*
4225 limit_log_view(struct tog_view
*view
)
4227 struct tog_log_view_state
*s
= &view
->state
.log
;
4228 struct commit_queue_entry
*entry
;
4229 struct tog_view
*v
= view
;
4230 const struct got_error
*err
= NULL
;
4234 if (view_is_hsplit_top(view
))
4236 else if (view
->mode
== TOG_VIEW_SPLIT_VERT
&& view
->parent
)
4239 if (tog_io
.input_str
!= NULL
) {
4240 if (strlcpy(pattern
, tog_io
.input_str
, sizeof(pattern
)) >=
4242 return got_error(GOT_ERR_NO_SPACE
);
4244 wmove(v
->window
, v
->nlines
- 1, 0);
4245 wclrtoeol(v
->window
);
4246 mvwaddstr(v
->window
, v
->nlines
- 1, 0, "&/");
4247 nodelay(v
->window
, FALSE
);
4250 ret
= wgetnstr(v
->window
, pattern
, sizeof(pattern
));
4253 nodelay(v
->window
, TRUE
);
4258 if (*pattern
== '\0') {
4260 * Safety measure for the situation where the user
4261 * resets limit without previously limiting anything.
4267 * User could have pressed Ctrl+L, which refreshed the
4268 * commit queues, it means we can't save previously
4269 * (before limit took place) displayed entries,
4270 * because they would point to already free'ed memory,
4271 * so we are forced to always select first entry of
4274 s
->commits
= &s
->real_commits
;
4275 s
->first_displayed_entry
= TAILQ_FIRST(&s
->real_commits
.head
);
4276 s
->selected_entry
= s
->first_displayed_entry
;
4283 if (regcomp(&s
->limit_regex
, pattern
, REG_EXTENDED
| REG_NEWLINE
))
4288 /* Clear the screen while loading limit view */
4289 s
->first_displayed_entry
= NULL
;
4290 s
->last_displayed_entry
= NULL
;
4291 s
->selected_entry
= NULL
;
4292 s
->commits
= &s
->limit_commits
;
4294 /* Prepare limit queue for new search */
4295 free_commits(&s
->limit_commits
);
4296 s
->limit_commits
.ncommits
= 0;
4298 /* First process commits, which are in queue already */
4299 TAILQ_FOREACH(entry
, &s
->real_commits
.head
, entry
) {
4302 if (entry
->worktree_entry
== 0) {
4303 err
= match_commit(&have_match
, entry
->id
,
4304 entry
->commit
, &s
->limit_regex
);
4310 struct commit_queue_entry
*matched
;
4312 matched
= alloc_commit_queue_entry(entry
->commit
,
4314 if (matched
== NULL
) {
4315 err
= got_error_from_errno(
4316 "alloc_commit_queue_entry");
4319 matched
->commit
= entry
->commit
;
4320 got_object_commit_retain(entry
->commit
);
4322 matched
->idx
= s
->limit_commits
.ncommits
;
4323 TAILQ_INSERT_TAIL(&s
->limit_commits
.head
,
4325 s
->limit_commits
.ncommits
++;
4329 /* Second process all the commits, until we fill the screen */
4330 if (s
->limit_commits
.ncommits
< view
->nlines
- 1 &&
4331 !s
->thread_args
.log_complete
) {
4332 s
->thread_args
.commits_needed
+=
4333 view
->nlines
- s
->limit_commits
.ncommits
- 1;
4334 err
= trigger_log_thread(view
, 1);
4339 s
->first_displayed_entry
= TAILQ_FIRST(&s
->commits
->head
);
4340 s
->selected_entry
= TAILQ_FIRST(&s
->commits
->head
);
4346 static const struct got_error
*
4347 search_start_log_view(struct tog_view
*view
)
4349 struct tog_log_view_state
*s
= &view
->state
.log
;
4351 s
->matched_entry
= NULL
;
4352 s
->search_entry
= NULL
;
4356 static const struct got_error
*
4357 search_next_log_view(struct tog_view
*view
)
4359 const struct got_error
*err
= NULL
;
4360 struct tog_log_view_state
*s
= &view
->state
.log
;
4361 struct commit_queue_entry
*entry
;
4363 /* Display progress update in log view. */
4364 err
= show_log_view(view
);
4370 if (s
->search_entry
) {
4371 if (!using_mock_io
) {
4374 errcode
= pthread_mutex_unlock(&tog_mutex
);
4376 return got_error_set_errno(errcode
,
4377 "pthread_mutex_unlock");
4378 ch
= wgetch(view
->window
);
4379 errcode
= pthread_mutex_lock(&tog_mutex
);
4381 return got_error_set_errno(errcode
,
4382 "pthread_mutex_lock");
4383 if (ch
== CTRL('g') || ch
== KEY_BACKSPACE
) {
4384 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
4388 if (view
->searching
== TOG_SEARCH_FORWARD
)
4389 entry
= TAILQ_NEXT(s
->search_entry
, entry
);
4391 entry
= TAILQ_PREV(s
->search_entry
,
4392 commit_queue_head
, entry
);
4393 } else if (s
->matched_entry
) {
4395 * If the user has moved the cursor after we hit a match,
4396 * the position from where we should continue searching
4397 * might have changed.
4399 if (view
->searching
== TOG_SEARCH_FORWARD
)
4400 entry
= TAILQ_NEXT(s
->selected_entry
, entry
);
4402 entry
= TAILQ_PREV(s
->selected_entry
, commit_queue_head
,
4405 entry
= s
->selected_entry
;
4411 if (entry
== NULL
) {
4412 if (s
->thread_args
.log_complete
||
4413 view
->searching
== TOG_SEARCH_BACKWARD
) {
4414 view
->search_next_done
=
4415 (s
->matched_entry
== NULL
?
4416 TOG_SEARCH_HAVE_NONE
: TOG_SEARCH_NO_MORE
);
4417 s
->search_entry
= NULL
;
4421 * Poke the log thread for more commits and return,
4422 * allowing the main loop to make progress. Search
4423 * will resume at s->search_entry once we come back.
4425 s
->search_entry
= s
->selected_entry
;
4426 s
->thread_args
.commits_needed
++;
4427 return trigger_log_thread(view
, 0);
4430 if (entry
->worktree_entry
== 0) {
4431 err
= match_commit(&have_match
, entry
->id
,
4432 entry
->commit
, &view
->regex
);
4436 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
4437 s
->matched_entry
= entry
;
4442 s
->search_entry
= entry
;
4443 if (view
->searching
== TOG_SEARCH_FORWARD
)
4444 entry
= TAILQ_NEXT(entry
, entry
);
4446 entry
= TAILQ_PREV(entry
, commit_queue_head
, entry
);
4449 if (s
->matched_entry
) {
4450 int cur
= s
->selected_entry
->idx
;
4451 while (cur
< s
->matched_entry
->idx
) {
4452 err
= input_log_view(NULL
, view
, KEY_DOWN
);
4457 while (cur
> s
->matched_entry
->idx
) {
4458 err
= input_log_view(NULL
, view
, KEY_UP
);
4465 s
->search_entry
= NULL
;
4470 static const struct got_error
*
4471 open_log_view(struct tog_view
*view
, struct got_object_id
*start_id
,
4472 struct got_repository
*repo
, const char *head_ref_name
,
4473 const char *in_repo_path
, int log_branches
,
4474 struct got_worktree
*worktree
)
4476 const struct got_error
*err
= NULL
;
4477 struct tog_log_view_state
*s
= &view
->state
.log
;
4478 struct got_repository
*thread_repo
= NULL
;
4479 struct got_commit_graph
*thread_graph
= NULL
;
4482 if (in_repo_path
!= s
->in_repo_path
) {
4483 free(s
->in_repo_path
);
4484 s
->in_repo_path
= strdup(in_repo_path
);
4485 if (s
->in_repo_path
== NULL
) {
4486 err
= got_error_from_errno("strdup");
4491 /* The commit queue only contains commits being displayed. */
4492 TAILQ_INIT(&s
->real_commits
.head
);
4493 s
->real_commits
.ncommits
= 0;
4494 s
->commits
= &s
->real_commits
;
4496 TAILQ_INIT(&s
->limit_commits
.head
);
4498 s
->limit_commits
.ncommits
= 0;
4501 if (head_ref_name
) {
4502 s
->head_ref_name
= strdup(head_ref_name
);
4503 if (s
->head_ref_name
== NULL
) {
4504 err
= got_error_from_errno("strdup");
4508 s
->start_id
= got_object_id_dup(start_id
);
4509 if (s
->start_id
== NULL
) {
4510 err
= got_error_from_errno("got_object_id_dup");
4513 s
->log_branches
= log_branches
;
4514 s
->use_committer
= 1;
4516 STAILQ_INIT(&s
->colors
);
4517 if (has_colors() && getenv("TOG_COLORS") != NULL
) {
4518 err
= add_color(&s
->colors
, "^$", TOG_COLOR_COMMIT
,
4519 get_color_value("TOG_COLOR_COMMIT"));
4522 err
= add_color(&s
->colors
, "^$", TOG_COLOR_AUTHOR
,
4523 get_color_value("TOG_COLOR_AUTHOR"));
4526 err
= add_color(&s
->colors
, "^$", TOG_COLOR_DATE
,
4527 get_color_value("TOG_COLOR_DATE"));
4532 view
->show
= show_log_view
;
4533 view
->input
= input_log_view
;
4534 view
->resize
= resize_log_view
;
4535 view
->close
= close_log_view
;
4536 view
->search_start
= search_start_log_view
;
4537 view
->search_next
= search_next_log_view
;
4539 if (s
->thread_args
.pack_fds
== NULL
) {
4540 err
= got_repo_pack_fds_open(&s
->thread_args
.pack_fds
);
4544 err
= got_repo_open(&thread_repo
, got_repo_get_path(repo
), NULL
,
4545 s
->thread_args
.pack_fds
);
4548 err
= got_commit_graph_open(&thread_graph
, s
->in_repo_path
,
4552 err
= got_commit_graph_bfsort(thread_graph
, s
->start_id
,
4553 s
->repo
, NULL
, NULL
);
4557 errcode
= pthread_cond_init(&s
->thread_args
.need_commits
, NULL
);
4559 err
= got_error_set_errno(errcode
, "pthread_cond_init");
4562 errcode
= pthread_cond_init(&s
->thread_args
.commit_loaded
, NULL
);
4564 err
= got_error_set_errno(errcode
, "pthread_cond_init");
4568 if (using_mock_io
) {
4571 rc
= pthread_cond_init(&s
->thread_args
.log_loaded
, NULL
);
4573 return got_error_set_errno(rc
, "pthread_cond_init");
4576 s
->thread_args
.view_nlines
= &view
->nlines
;
4577 s
->thread_args
.commits_needed
= view
->nlines
;
4578 s
->thread_args
.graph
= thread_graph
;
4579 s
->thread_args
.real_commits
= &s
->real_commits
;
4580 s
->thread_args
.limit_commits
= &s
->limit_commits
;
4581 s
->thread_args
.in_repo_path
= s
->in_repo_path
;
4582 s
->thread_args
.start_id
= s
->start_id
;
4583 s
->thread_args
.repo
= thread_repo
;
4584 s
->thread_args
.log_complete
= 0;
4585 s
->thread_args
.quit
= &s
->quit
;
4586 s
->thread_args
.first_displayed_entry
= &s
->first_displayed_entry
;
4587 s
->thread_args
.last_displayed_entry
= &s
->last_displayed_entry
;
4588 s
->thread_args
.selected_entry
= &s
->selected_entry
;
4589 s
->thread_args
.selected
= &s
->selected
;
4590 s
->thread_args
.searching
= &view
->searching
;
4591 s
->thread_args
.search_next_done
= &view
->search_next_done
;
4592 s
->thread_args
.regex
= &view
->regex
;
4593 s
->thread_args
.limiting
= &s
->limit_view
;
4594 s
->thread_args
.limit_regex
= &s
->limit_regex
;
4595 s
->thread_args
.limit_commits
= &s
->limit_commits
;
4596 s
->thread_args
.worktree
= worktree
;
4598 s
->thread_args
.wctx
.active
= 1;
4599 s
->thread_args
.need_wt_status
= 1;
4600 s
->thread_args
.need_commit_marker
= 1;
4605 if (view
->close
== NULL
)
4606 close_log_view(view
);
4612 static const struct got_error
*
4613 show_log_view(struct tog_view
*view
)
4615 const struct got_error
*err
;
4616 struct tog_log_view_state
*s
= &view
->state
.log
;
4618 if (s
->thread
== 0) { //NULL) {
4619 int errcode
= pthread_create(&s
->thread
, NULL
, log_thread
,
4622 return got_error_set_errno(errcode
, "pthread_create");
4623 if (s
->thread_args
.commits_needed
> 0) {
4624 err
= trigger_log_thread(view
, 1);
4630 return draw_commits(view
);
4634 log_move_cursor_up(struct tog_view
*view
, int page
, int home
)
4636 struct tog_log_view_state
*s
= &view
->state
.log
;
4638 if (s
->first_displayed_entry
== NULL
)
4640 if (s
->selected_entry
->idx
== 0)
4643 if ((page
&& TAILQ_FIRST(&s
->commits
->head
) == s
->first_displayed_entry
)
4645 s
->selected
= home
? 0 : MAX(0, s
->selected
- page
- 1);
4647 if (!page
&& !home
&& s
->selected
> 0)
4650 log_scroll_up(s
, home
? s
->commits
->ncommits
: MAX(page
, 1));
4656 static const struct got_error
*
4657 log_move_cursor_down(struct tog_view
*view
, int page
)
4659 struct tog_log_view_state
*s
= &view
->state
.log
;
4660 const struct got_error
*err
= NULL
;
4661 int eos
= view
->nlines
- 2;
4663 if (s
->first_displayed_entry
== NULL
)
4666 if (s
->thread_args
.log_complete
&&
4667 s
->selected_entry
->idx
>= s
->commits
->ncommits
- 1)
4670 if (view_is_hsplit_top(view
))
4671 --eos
; /* border consumes the last line */
4674 if (s
->selected
< MIN(eos
, s
->commits
->ncommits
- 1))
4677 err
= log_scroll_down(view
, 1);
4678 } else if (s
->thread_args
.load_all
&& s
->thread_args
.log_complete
) {
4679 struct commit_queue_entry
*entry
;
4683 entry
= TAILQ_LAST(&s
->commits
->head
, commit_queue_head
);
4684 s
->last_displayed_entry
= entry
;
4685 for (n
= 0; n
<= eos
; n
++) {
4688 s
->first_displayed_entry
= entry
;
4689 entry
= TAILQ_PREV(entry
, commit_queue_head
, entry
);
4692 s
->selected
= n
- 1;
4694 if (s
->last_displayed_entry
->idx
== s
->commits
->ncommits
- 1 &&
4695 s
->thread_args
.log_complete
)
4696 s
->selected
+= MIN(page
,
4697 s
->commits
->ncommits
- s
->selected_entry
->idx
- 1);
4699 err
= log_scroll_down(view
, page
);
4705 * We might necessarily overshoot in horizontal
4706 * splits; if so, select the last displayed commit.
4708 if (view_is_hsplit_top(view
) && s
->first_displayed_entry
&&
4709 s
->last_displayed_entry
) {
4710 s
->selected
= MIN(s
->selected
,
4711 s
->last_displayed_entry
->idx
-
4712 s
->first_displayed_entry
->idx
);
4717 if (s
->thread_args
.log_complete
&&
4718 s
->selected_entry
->idx
== s
->commits
->ncommits
- 1)
4725 view_get_split(struct tog_view
*view
, int *y
, int *x
)
4730 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
) {
4731 if (view
->child
&& view
->child
->resized_y
)
4732 *y
= view
->child
->resized_y
;
4733 else if (view
->resized_y
)
4734 *y
= view
->resized_y
;
4736 *y
= view_split_begin_y(view
->lines
);
4737 } else if (view
->mode
== TOG_VIEW_SPLIT_VERT
) {
4738 if (view
->child
&& view
->child
->resized_x
)
4739 *x
= view
->child
->resized_x
;
4740 else if (view
->resized_x
)
4741 *x
= view
->resized_x
;
4743 *x
= view_split_begin_x(view
->begin_x
);
4747 /* Split view horizontally at y and offset view->state->selected line. */
4748 static const struct got_error
*
4749 view_init_hsplit(struct tog_view
*view
, int y
)
4751 const struct got_error
*err
= NULL
;
4755 err
= view_resize(view
);
4759 err
= offset_selection_down(view
);
4764 static const struct got_error
*
4765 log_goto_line(struct tog_view
*view
, int nlines
)
4767 const struct got_error
*err
= NULL
;
4768 struct tog_log_view_state
*s
= &view
->state
.log
;
4769 int g
, idx
= s
->selected_entry
->idx
;
4771 if (s
->first_displayed_entry
== NULL
|| s
->last_displayed_entry
== NULL
)
4777 if (g
>= s
->first_displayed_entry
->idx
+ 1 &&
4778 g
<= s
->last_displayed_entry
->idx
+ 1 &&
4779 g
- s
->first_displayed_entry
->idx
- 1 < nlines
) {
4780 s
->selected
= g
- s
->first_displayed_entry
->idx
- 1;
4786 err
= log_move_cursor_down(view
, g
- idx
- 1);
4787 if (!err
&& g
> s
->selected_entry
->idx
+ 1)
4788 err
= log_move_cursor_down(view
,
4789 g
- s
->first_displayed_entry
->idx
- 1);
4792 } else if (idx
+ 1 > g
)
4793 log_move_cursor_up(view
, idx
- g
+ 1, 0);
4795 if (g
< nlines
&& s
->first_displayed_entry
->idx
== 0)
4796 s
->selected
= g
- 1;
4804 horizontal_scroll_input(struct tog_view
*view
, int ch
)
4810 view
->x
-= MIN(view
->x
, 2);
4816 if (view
->x
+ view
->ncols
/ 2 < view
->maxx
)
4825 view
->x
= MAX(view
->maxx
- view
->ncols
/ 2, 0);
4834 log_mark_commit(struct tog_log_view_state
*s
)
4836 if (s
->selected_entry
== s
->marked_entry
)
4837 s
->marked_entry
= NULL
;
4839 s
->marked_entry
= s
->selected_entry
;
4842 static const struct got_error
*
4843 input_log_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
4845 const struct got_error
*err
= NULL
;
4846 struct tog_log_view_state
*s
= &view
->state
.log
;
4849 if (s
->thread_args
.load_all
) {
4850 if (ch
== CTRL('g') || ch
== KEY_BACKSPACE
)
4851 s
->thread_args
.load_all
= 0;
4852 else if (s
->thread_args
.log_complete
) {
4853 err
= log_move_cursor_down(view
, s
->commits
->ncommits
);
4854 s
->thread_args
.load_all
= 0;
4860 eos
= nscroll
= view
->nlines
- 1;
4861 if (view_is_hsplit_top(view
))
4865 return log_goto_line(view
, eos
);
4869 err
= limit_log_view(view
);
4880 horizontal_scroll_input(view
, ch
);
4887 log_move_cursor_up(view
, 0, 0);
4892 log_move_cursor_up(view
, 0, 1);
4902 log_move_cursor_up(view
, nscroll
, 0);
4909 err
= log_move_cursor_down(view
, 0);
4912 s
->use_committer
= !s
->use_committer
;
4913 view
->action
= s
->use_committer
?
4914 "show committer" : "show commit author";
4919 /* We don't know yet how many commits, so we're forced to
4920 * traverse them all. */
4922 s
->thread_args
.load_all
= 1;
4923 if (!s
->thread_args
.log_complete
)
4924 return trigger_log_thread(view
, 0);
4925 err
= log_move_cursor_down(view
, s
->commits
->ncommits
);
4926 s
->thread_args
.load_all
= 0;
4937 err
= log_move_cursor_down(view
, nscroll
);
4940 if (s
->selected
> view
->nlines
- 2)
4941 s
->selected
= view
->nlines
- 2;
4942 if (s
->selected
> s
->commits
->ncommits
- 1)
4943 s
->selected
= s
->commits
->ncommits
- 1;
4945 if (s
->commits
->ncommits
< view
->nlines
- 1 &&
4946 !s
->thread_args
.log_complete
) {
4947 s
->thread_args
.commits_needed
+= (view
->nlines
- 1) -
4948 s
->commits
->ncommits
;
4949 err
= trigger_log_thread(view
, 1);
4955 if (s
->selected_entry
== NULL
)
4957 err
= view_request_new(new_view
, view
, TOG_VIEW_DIFF
);
4961 if (s
->selected_entry
== NULL
)
4963 err
= view_request_new(new_view
, view
, TOG_VIEW_TREE
);
4969 if (ch
== KEY_BACKSPACE
&&
4970 got_path_is_root_dir(s
->in_repo_path
))
4972 err
= stop_log_thread(s
);
4975 if (ch
== KEY_BACKSPACE
) {
4977 err
= got_path_dirname(&parent_path
, s
->in_repo_path
);
4980 free(s
->in_repo_path
);
4981 s
->in_repo_path
= parent_path
;
4982 s
->thread_args
.in_repo_path
= s
->in_repo_path
;
4983 } else if (ch
== CTRL('l')) {
4984 struct got_object_id
*start_id
;
4985 err
= got_repo_match_object_id(&start_id
, NULL
,
4986 s
->head_ref_name
? s
->head_ref_name
: GOT_REF_HEAD
,
4987 GOT_OBJ_TYPE_COMMIT
, &tog_refs
, s
->repo
);
4989 if (s
->head_ref_name
== NULL
||
4990 err
->code
!= GOT_ERR_NOT_REF
)
4992 /* Try to cope with deleted references. */
4993 free(s
->head_ref_name
);
4994 s
->head_ref_name
= NULL
;
4995 err
= got_repo_match_object_id(&start_id
,
4996 NULL
, GOT_REF_HEAD
, GOT_OBJ_TYPE_COMMIT
,
4997 &tog_refs
, s
->repo
);
5002 s
->start_id
= start_id
;
5003 s
->thread_args
.start_id
= s
->start_id
;
5005 s
->log_branches
= !s
->log_branches
;
5007 if (s
->thread_args
.pack_fds
== NULL
) {
5008 err
= got_repo_pack_fds_open(&s
->thread_args
.pack_fds
);
5012 err
= got_repo_open(&s
->thread_args
.repo
,
5013 got_repo_get_path(s
->repo
), NULL
,
5014 s
->thread_args
.pack_fds
);
5018 err
= tog_load_refs(s
->repo
, 0);
5021 err
= got_commit_graph_open(&s
->thread_args
.graph
,
5022 s
->in_repo_path
, !s
->log_branches
);
5025 err
= got_commit_graph_bfsort(s
->thread_args
.graph
,
5026 s
->start_id
, s
->repo
, NULL
, NULL
);
5029 free_commits(&s
->real_commits
);
5030 free_commits(&s
->limit_commits
);
5031 s
->first_displayed_entry
= NULL
;
5032 s
->last_displayed_entry
= NULL
;
5033 s
->selected_entry
= NULL
;
5035 s
->thread_args
.log_complete
= 0;
5037 s
->thread_args
.commits_needed
= view
->lines
;
5038 s
->matched_entry
= NULL
;
5039 s
->search_entry
= NULL
;
5040 tog_base_commit
.idx
= -1;
5041 worktree_ctx_close(&s
->thread_args
);
5045 if (s
->selected_entry
->worktree_entry
== 0)
5050 err
= view_request_new(new_view
, view
, TOG_VIEW_REF
);
5060 static const struct got_error
*
5061 apply_unveil(const char *repo_path
, const char *worktree_path
)
5063 const struct got_error
*error
;
5066 if (unveil("gmon.out", "rwc") != 0)
5067 return got_error_from_errno2("unveil", "gmon.out");
5069 if (repo_path
&& unveil(repo_path
, "r") != 0)
5070 return got_error_from_errno2("unveil", repo_path
);
5072 if (worktree_path
&& unveil(worktree_path
, "rwc") != 0)
5073 return got_error_from_errno2("unveil", worktree_path
);
5075 if (unveil(GOT_TMPDIR_STR
, "rwc") != 0)
5076 return got_error_from_errno2("unveil", GOT_TMPDIR_STR
);
5078 error
= got_privsep_unveil_exec_helpers();
5082 if (unveil(NULL
, NULL
) != 0)
5083 return got_error_from_errno("unveil");
5088 static const struct got_error
*
5089 init_mock_term(const char *test_script_path
)
5091 const struct got_error
*err
= NULL
;
5092 const char *screen_dump_path
;
5095 if (test_script_path
== NULL
|| *test_script_path
== '\0')
5096 return got_error_msg(GOT_ERR_IO
, "TOG_TEST_SCRIPT not defined");
5098 tog_io
.f
= fopen(test_script_path
, "re");
5099 if (tog_io
.f
== NULL
) {
5100 err
= got_error_from_errno_fmt("fopen: %s",
5105 /* test mode, we don't want any output */
5106 tog_io
.cout
= fopen("/dev/null", "w+");
5107 if (tog_io
.cout
== NULL
) {
5108 err
= got_error_from_errno2("fopen", "/dev/null");
5112 in
= dup(fileno(tog_io
.cout
));
5114 err
= got_error_from_errno("dup");
5117 tog_io
.cin
= fdopen(in
, "r");
5118 if (tog_io
.cin
== NULL
) {
5119 err
= got_error_from_errno("fdopen");
5124 screen_dump_path
= getenv("TOG_SCR_DUMP");
5125 if (screen_dump_path
== NULL
|| *screen_dump_path
== '\0')
5126 return got_error_msg(GOT_ERR_IO
, "TOG_SCR_DUMP not defined");
5127 tog_io
.sdump
= fopen(screen_dump_path
, "we");
5128 if (tog_io
.sdump
== NULL
) {
5129 err
= got_error_from_errno2("fopen", screen_dump_path
);
5133 if (fseeko(tog_io
.f
, 0L, SEEK_SET
) == -1) {
5134 err
= got_error_from_errno("fseeko");
5138 if (newterm(NULL
, tog_io
.cout
, tog_io
.cin
) == NULL
)
5139 err
= got_error_msg(GOT_ERR_IO
,
5140 "newterm: failed to initialise curses");
5153 if (using_mock_io
) /* In test mode we use a fake terminal */
5159 halfdelay(1); /* Fast refresh while initial view is loading. */
5162 intrflush(stdscr
, FALSE
);
5163 keypad(stdscr
, TRUE
);
5165 if (getenv("TOG_COLORS") != NULL
) {
5167 use_default_colors();
5173 static const struct got_error
*
5174 set_tog_base_commit(struct got_repository
*repo
, struct got_worktree
*worktree
)
5176 tog_base_commit
.id
= got_object_id_dup(
5177 got_worktree_get_base_commit_id(worktree
));
5178 if (tog_base_commit
.id
== NULL
)
5179 return got_error_from_errno( "got_object_id_dup");
5184 static const struct got_error
*
5185 get_in_repo_path_from_argv0(char **in_repo_path
, int argc
, char *argv
[],
5186 struct got_repository
*repo
, struct got_worktree
*worktree
)
5188 const struct got_error
*err
= NULL
;
5191 *in_repo_path
= strdup("/");
5192 if (*in_repo_path
== NULL
)
5193 return got_error_from_errno("strdup");
5198 const char *prefix
= got_worktree_get_path_prefix(worktree
);
5201 err
= got_worktree_resolve_path(&p
, worktree
, argv
[0]);
5204 if (asprintf(in_repo_path
, "%s%s%s", prefix
,
5205 (p
[0] != '\0' && !got_path_is_root_dir(prefix
)) ? "/" : "",
5207 err
= got_error_from_errno("asprintf");
5208 *in_repo_path
= NULL
;
5212 err
= got_repo_map_path(in_repo_path
, repo
, argv
[0]);
5217 static const struct got_error
*
5218 cmd_log(int argc
, char *argv
[])
5220 const struct got_error
*error
;
5221 struct got_repository
*repo
= NULL
;
5222 struct got_worktree
*worktree
= NULL
;
5223 struct got_object_id
*start_id
= NULL
;
5224 char *in_repo_path
= NULL
, *repo_path
= NULL
, *cwd
= NULL
;
5225 char *keyword_idstr
= NULL
, *start_commit
= NULL
, *label
= NULL
;
5226 struct got_reference
*ref
= NULL
;
5227 const char *head_ref_name
= NULL
;
5228 int ch
, log_branches
= 0;
5229 struct tog_view
*view
;
5230 int *pack_fds
= NULL
;
5232 while ((ch
= getopt(argc
, argv
, "bc:r:")) != -1) {
5238 start_commit
= optarg
;
5241 repo_path
= realpath(optarg
, NULL
);
5242 if (repo_path
== NULL
)
5243 return got_error_from_errno2("realpath",
5258 error
= got_repo_pack_fds_open(&pack_fds
);
5262 if (repo_path
== NULL
) {
5263 cwd
= getcwd(NULL
, 0);
5265 error
= got_error_from_errno("getcwd");
5268 error
= got_worktree_open(&worktree
, cwd
, NULL
);
5269 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
5273 strdup(got_worktree_get_repo_path(worktree
));
5275 repo_path
= strdup(cwd
);
5276 if (repo_path
== NULL
) {
5277 error
= got_error_from_errno("strdup");
5282 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
5286 error
= get_in_repo_path_from_argv0(&in_repo_path
, argc
, argv
,
5293 error
= apply_unveil(got_repo_get_path(repo
),
5294 worktree
? got_worktree_get_root_path(worktree
) : NULL
);
5298 /* already loaded by tog_log_with_path()? */
5299 if (TAILQ_EMPTY(&tog_refs
)) {
5300 error
= tog_load_refs(repo
, 0);
5305 if (start_commit
== NULL
) {
5306 error
= got_repo_match_object_id(&start_id
, &label
,
5307 worktree
? got_worktree_get_head_ref_name(worktree
) :
5308 GOT_REF_HEAD
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
5311 head_ref_name
= label
;
5313 error
= got_keyword_to_idstr(&keyword_idstr
, start_commit
,
5317 if (keyword_idstr
!= NULL
)
5318 start_commit
= keyword_idstr
;
5320 error
= got_ref_open(&ref
, repo
, start_commit
, 0);
5322 head_ref_name
= got_ref_get_name(ref
);
5323 else if (error
->code
!= GOT_ERR_NOT_REF
)
5325 error
= got_repo_match_object_id(&start_id
, NULL
,
5326 start_commit
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
5331 view
= view_open(0, 0, 0, 0, TOG_VIEW_LOG
);
5333 error
= got_error_from_errno("view_open");
5338 error
= set_tog_base_commit(repo
, worktree
);
5343 error
= open_log_view(view
, start_id
, repo
, head_ref_name
,
5344 in_repo_path
, log_branches
, worktree
);
5349 /* The work tree will be closed by the log thread. */
5353 error
= view_loop(view
);
5356 free(tog_base_commit
.id
);
5357 free(keyword_idstr
);
5366 const struct got_error
*close_err
= got_repo_close(repo
);
5371 got_worktree_close(worktree
);
5373 const struct got_error
*pack_err
=
5374 got_repo_pack_fds_close(pack_fds
);
5386 fprintf(stderr
, "usage: %s diff [-asw] [-C number] [-c commit] "
5387 "[-r repository-path] [object1 object2 | path ...]\n",
5393 match_line(const char *line
, regex_t
*regex
, size_t nmatch
,
5394 regmatch_t
*regmatch
)
5396 return regexec(regex
, line
, nmatch
, regmatch
, 0) == 0;
5399 static struct tog_color
*
5400 match_color(struct tog_colors
*colors
, const char *line
)
5402 struct tog_color
*tc
= NULL
;
5404 STAILQ_FOREACH(tc
, colors
, entry
) {
5405 if (match_line(line
, &tc
->regex
, 0, NULL
))
5412 static const struct got_error
*
5413 add_matched_line(int *wtotal
, const char *line
, int wlimit
, int col_tab_align
,
5414 WINDOW
*window
, int skipcol
, regmatch_t
*regmatch
)
5416 const struct got_error
*err
= NULL
;
5418 wchar_t *wline
= NULL
;
5419 int rme
, rms
, n
, width
, scrollx
;
5420 int width0
= 0, width1
= 0, width2
= 0;
5421 char *seg0
= NULL
, *seg1
= NULL
, *seg2
= NULL
;
5425 rms
= regmatch
->rm_so
;
5426 rme
= regmatch
->rm_eo
;
5428 err
= expand_tab(&exstr
, line
);
5432 /* Split the line into 3 segments, according to match offsets. */
5433 seg0
= strndup(exstr
, rms
);
5435 err
= got_error_from_errno("strndup");
5438 seg1
= strndup(exstr
+ rms
, rme
- rms
);
5440 err
= got_error_from_errno("strndup");
5443 seg2
= strdup(exstr
+ rme
);
5445 err
= got_error_from_errno("strndup");
5449 /* draw up to matched token if we haven't scrolled past it */
5450 err
= format_line(&wline
, &width0
, NULL
, seg0
, 0, wlimit
,
5454 n
= MAX(width0
- skipcol
, 0);
5457 err
= format_line(&wline
, &width
, &scrollx
, seg0
, skipcol
,
5458 wlimit
, col_tab_align
, 1);
5461 waddwstr(window
, &wline
[scrollx
]);
5471 err
= format_line(&wline
, &width1
, NULL
, seg1
, 0, wlimit
,
5475 wlen
= wcslen(wline
);
5477 width
= wcwidth(wline
[i
]);
5479 /* should not happen, tabs are expanded */
5480 err
= got_error(GOT_ERR_RANGE
);
5483 if (width0
+ w
+ width
> skipcol
)
5488 /* draw (visible part of) matched token (if scrolled into it) */
5489 if (width1
- w
> 0) {
5490 wattron(window
, A_STANDOUT
);
5491 waddwstr(window
, &wline
[i
]);
5492 wattroff(window
, A_STANDOUT
);
5493 wlimit
-= (width1
- w
);
5494 *wtotal
+= (width1
- w
);
5498 if (wlimit
> 0) { /* draw rest of line */
5500 if (skipcol
> width0
+ width1
) {
5501 err
= format_line(&wline
, &width2
, &scrollx
, seg2
,
5502 skipcol
- (width0
+ width1
), wlimit
,
5506 waddwstr(window
, &wline
[scrollx
]);
5508 err
= format_line(&wline
, &width2
, NULL
, seg2
, 0,
5509 wlimit
, col_tab_align
, 1);
5512 waddwstr(window
, wline
);
5526 gotoline(struct tog_view
*view
, int *lineno
, int *nprinted
)
5529 int *eof
, *first
, *selected
;
5531 if (view
->type
== TOG_VIEW_DIFF
) {
5532 struct tog_diff_view_state
*s
= &view
->state
.diff
;
5534 first
= &s
->first_displayed_line
;
5538 } else if (view
->type
== TOG_VIEW_HELP
) {
5539 struct tog_help_view_state
*s
= &view
->state
.help
;
5541 first
= &s
->first_displayed_line
;
5545 } else if (view
->type
== TOG_VIEW_BLAME
) {
5546 struct tog_blame_view_state
*s
= &view
->state
.blame
;
5548 first
= &s
->first_displayed_line
;
5549 selected
= &s
->selected_line
;
5555 /* Center gline in the middle of the page like vi(1). */
5556 if (*lineno
< view
->gline
- (view
->nlines
- 3) / 2)
5558 if (*first
!= 1 && (*lineno
> view
->gline
- (view
->nlines
- 3) / 2)) {
5567 *selected
= view
->gline
<= (view
->nlines
- 3) / 2 ?
5568 view
->gline
: (view
->nlines
- 3) / 2 + 1;
5574 static const struct got_error
*
5575 draw_file(struct tog_view
*view
, const char *header
)
5577 struct tog_diff_view_state
*s
= &view
->state
.diff
;
5578 regmatch_t
*regmatch
= &view
->regmatch
;
5579 const struct got_error
*err
;
5582 size_t linesize
= 0;
5586 int max_lines
= view
->nlines
;
5587 int nlines
= s
->nlines
;
5590 s
->lineno
= s
->first_displayed_line
- 1;
5591 line_offset
= s
->lines
[s
->first_displayed_line
- 1].offset
;
5592 if (fseeko(s
->f
, line_offset
, SEEK_SET
) == -1)
5593 return got_error_from_errno("fseek");
5595 werase(view
->window
);
5597 if (view
->gline
> s
->nlines
- 1)
5598 view
->gline
= s
->nlines
- 1;
5601 int ln
= view
->gline
? view
->gline
<= (view
->nlines
- 3) / 2 ?
5602 1 : view
->gline
- (view
->nlines
- 3) / 2 :
5603 s
->lineno
+ s
->selected_line
;
5605 if (asprintf(&line
, "[%d/%d] %s", ln
, nlines
, header
) == -1)
5606 return got_error_from_errno("asprintf");
5607 err
= format_line(&wline
, &width
, NULL
, line
, 0, view
->ncols
,
5613 if (view_needs_focus_indication(view
))
5614 wstandout(view
->window
);
5615 waddwstr(view
->window
, wline
);
5618 while (width
++ < view
->ncols
)
5619 waddch(view
->window
, ' ');
5620 if (view_needs_focus_indication(view
))
5621 wstandend(view
->window
);
5631 while (max_lines
> 0 && nprinted
< max_lines
) {
5632 enum got_diff_line_type linetype
;
5635 linelen
= getline(&line
, &linesize
, s
->f
);
5636 if (linelen
== -1) {
5642 return got_ferror(s
->f
, GOT_ERR_IO
);
5645 if (++s
->lineno
< s
->first_displayed_line
)
5647 if (view
->gline
&& !gotoline(view
, &s
->lineno
, &nprinted
))
5649 if (s
->lineno
== view
->hiline
)
5652 /* Set view->maxx based on full line length. */
5653 err
= format_line(&wline
, &width
, NULL
, line
, 0, INT_MAX
, 0,
5659 view
->maxx
= MAX(view
->maxx
, width
);
5663 linetype
= s
->lines
[s
->lineno
].type
;
5664 if (linetype
> GOT_DIFF_LINE_LOGMSG
&&
5665 linetype
< GOT_DIFF_LINE_CONTEXT
)
5666 attr
|= COLOR_PAIR(linetype
);
5668 wattron(view
->window
, attr
);
5669 if (s
->first_displayed_line
+ nprinted
== s
->matched_line
&&
5670 regmatch
->rm_so
>= 0 && regmatch
->rm_so
< regmatch
->rm_eo
) {
5671 err
= add_matched_line(&width
, line
, view
->ncols
, 0,
5672 view
->window
, view
->x
, regmatch
);
5679 err
= format_line(&wline
, &width
, &skip
, line
,
5680 view
->x
, view
->ncols
, 0, view
->x
? 1 : 0);
5685 waddwstr(view
->window
, &wline
[skip
]);
5689 if (s
->lineno
== view
->hiline
) {
5690 /* highlight full gline length */
5691 while (width
++ < view
->ncols
)
5692 waddch(view
->window
, ' ');
5694 if (width
<= view
->ncols
- 1)
5695 waddch(view
->window
, '\n');
5698 wattroff(view
->window
, attr
);
5699 if (++nprinted
== 1)
5700 s
->first_displayed_line
= s
->lineno
;
5704 s
->last_displayed_line
= s
->first_displayed_line
+
5707 s
->last_displayed_line
= s
->first_displayed_line
;
5712 while (nprinted
< view
->nlines
) {
5713 waddch(view
->window
, '\n');
5717 err
= format_line(&wline
, &width
, NULL
, TOG_EOF_STRING
, 0,
5723 wstandout(view
->window
);
5724 waddwstr(view
->window
, wline
);
5727 wstandend(view
->window
);
5734 get_datestr(time_t *time
, char *datebuf
)
5736 struct tm mytm
, *tm
;
5739 tm
= gmtime_r(time
, &mytm
);
5742 s
= asctime_r(tm
, datebuf
);
5745 p
= strchr(s
, '\n');
5751 static const struct got_error
*
5752 add_line_metadata(struct got_diff_line
**lines
, size_t *nlines
,
5753 off_t off
, uint8_t type
)
5755 struct got_diff_line
*p
;
5757 p
= reallocarray(*lines
, *nlines
+ 1, sizeof(**lines
));
5759 return got_error_from_errno("reallocarray");
5761 (*lines
)[*nlines
].offset
= off
;
5762 (*lines
)[*nlines
].type
= type
;
5768 static const struct got_error
*
5769 cat_diff(FILE *dst
, FILE *src
, struct got_diff_line
**d_lines
, size_t *d_nlines
,
5770 struct got_diff_line
*s_lines
, size_t s_nlines
)
5772 struct got_diff_line
*p
;
5776 if (fseeko(src
, 0L, SEEK_SET
) == -1)
5777 return got_error_from_errno("fseeko");
5780 r
= fread(buf
, 1, sizeof(buf
), src
);
5783 return got_error_from_errno("fread");
5787 if (fwrite(buf
, 1, r
, dst
) != r
)
5788 return got_ferror(dst
, GOT_ERR_IO
);
5791 if (s_nlines
== 0 && *d_nlines
== 0)
5795 * If commit info was in dst, increment line offsets
5796 * of the appended diff content, but skip s_lines[0]
5797 * because offset zero is already in *d_lines.
5799 if (*d_nlines
> 0) {
5800 for (i
= 1; i
< s_nlines
; ++i
)
5801 s_lines
[i
].offset
+= (*d_lines
)[*d_nlines
- 1].offset
;
5809 p
= reallocarray(*d_lines
, *d_nlines
+ s_nlines
, sizeof(*p
));
5811 /* d_lines is freed in close_diff_view() */
5812 return got_error_from_errno("reallocarray");
5817 memcpy(*d_lines
+ *d_nlines
, s_lines
, s_nlines
* sizeof(*s_lines
));
5818 *d_nlines
+= s_nlines
;
5823 static const struct got_error
*
5824 write_diffstat(FILE *outfile
, struct got_diff_line
**lines
, size_t *nlines
,
5825 struct got_diffstat_cb_arg
*dsa
)
5827 const struct got_error
*err
;
5828 struct got_pathlist_entry
*pe
;
5833 err
= add_line_metadata(lines
, nlines
, 0, GOT_DIFF_LINE_NONE
);
5838 offset
= (*lines
)[*nlines
- 1].offset
;
5840 TAILQ_FOREACH(pe
, dsa
->paths
, entry
) {
5841 struct got_diff_changed_path
*cp
= pe
->data
;
5842 int pad
= dsa
->max_path_len
- pe
->path_len
+ 1;
5844 n
= fprintf(outfile
, "%c %s%*c | %*d+ %*d-\n", cp
->status
,
5845 pe
->path
, pad
, ' ', dsa
->add_cols
+ 1, cp
->add
,
5846 dsa
->rm_cols
+ 1, cp
->rm
);
5848 return got_error_from_errno("fprintf");
5851 err
= add_line_metadata(lines
, nlines
, offset
,
5852 GOT_DIFF_LINE_CHANGES
);
5857 if (fputc('\n', outfile
) == EOF
)
5858 return got_error_from_errno("fputc");
5861 err
= add_line_metadata(lines
, nlines
, offset
, GOT_DIFF_LINE_NONE
);
5865 n
= fprintf(outfile
,
5866 "%d file%s changed, %d insertion%s(+), %d deletion%s(-)\n",
5867 dsa
->nfiles
, dsa
->nfiles
> 1 ? "s" : "", dsa
->ins
,
5868 dsa
->ins
!= 1 ? "s" : "", dsa
->del
, dsa
->del
!= 1 ? "s" : "");
5870 return got_error_from_errno("fprintf");
5873 err
= add_line_metadata(lines
, nlines
, offset
, GOT_DIFF_LINE_NONE
);
5877 if (fputc('\n', outfile
) == EOF
)
5878 return got_error_from_errno("fputc");
5881 return add_line_metadata(lines
, nlines
, offset
, GOT_DIFF_LINE_NONE
);
5884 static const struct got_error
*
5885 write_commit_info(struct got_diff_line
**lines
, size_t *nlines
,
5886 struct got_object_id
*commit_id
, struct got_reflist_head
*refs
,
5887 struct got_repository
*repo
, int ignore_ws
, int force_text_diff
,
5888 struct got_diffstat_cb_arg
*dsa
, FILE *outfile
)
5890 const struct got_error
*err
= NULL
;
5891 char datebuf
[26], *datestr
;
5892 struct got_commit_object
*commit
;
5893 char *id_str
= NULL
, *logmsg
= NULL
, *s
= NULL
, *line
;
5894 time_t committer_time
;
5895 const char *author
, *committer
;
5896 char *refs_str
= NULL
;
5900 err
= build_refs_str(&refs_str
, refs
, commit_id
, repo
);
5904 err
= got_object_open_as_commit(&commit
, repo
, commit_id
);
5908 err
= got_object_id_str(&id_str
, commit_id
);
5910 err
= got_error_from_errno("got_object_id_str");
5914 err
= add_line_metadata(lines
, nlines
, 0, GOT_DIFF_LINE_NONE
);
5918 n
= fprintf(outfile
, "commit %s%s%s%s\n", id_str
, refs_str
? " (" : "",
5919 refs_str
? refs_str
: "", refs_str
? ")" : "");
5921 err
= got_error_from_errno("fprintf");
5925 err
= add_line_metadata(lines
, nlines
, outoff
, GOT_DIFF_LINE_META
);
5929 n
= fprintf(outfile
, "from: %s\n",
5930 got_object_commit_get_author(commit
));
5932 err
= got_error_from_errno("fprintf");
5936 err
= add_line_metadata(lines
, nlines
, outoff
, GOT_DIFF_LINE_AUTHOR
);
5940 author
= got_object_commit_get_author(commit
);
5941 committer
= got_object_commit_get_committer(commit
);
5942 if (strcmp(author
, committer
) != 0) {
5943 n
= fprintf(outfile
, "via: %s\n", committer
);
5945 err
= got_error_from_errno("fprintf");
5949 err
= add_line_metadata(lines
, nlines
, outoff
,
5950 GOT_DIFF_LINE_AUTHOR
);
5954 committer_time
= got_object_commit_get_committer_time(commit
);
5955 datestr
= get_datestr(&committer_time
, datebuf
);
5957 n
= fprintf(outfile
, "date: %s UTC\n", datestr
);
5959 err
= got_error_from_errno("fprintf");
5963 err
= add_line_metadata(lines
, nlines
, outoff
,
5964 GOT_DIFF_LINE_DATE
);
5968 if (got_object_commit_get_nparents(commit
) > 1) {
5969 const struct got_object_id_queue
*parent_ids
;
5970 struct got_object_qid
*qid
;
5972 parent_ids
= got_object_commit_get_parent_ids(commit
);
5973 STAILQ_FOREACH(qid
, parent_ids
, entry
) {
5974 err
= got_object_id_str(&id_str
, &qid
->id
);
5977 n
= fprintf(outfile
, "parent %d: %s\n", pn
++, id_str
);
5979 err
= got_error_from_errno("fprintf");
5983 err
= add_line_metadata(lines
, nlines
, outoff
,
5984 GOT_DIFF_LINE_META
);
5992 err
= got_object_commit_get_logmsg(&logmsg
, commit
);
5996 while ((line
= strsep(&s
, "\n")) != NULL
) {
5997 n
= fprintf(outfile
, "%s\n", line
);
5999 err
= got_error_from_errno("fprintf");
6003 err
= add_line_metadata(lines
, nlines
, outoff
,
6004 GOT_DIFF_LINE_LOGMSG
);
6013 got_object_commit_close(commit
);
6018 evict_worktree_entry(struct tog_log_thread_args
*ta
, int victim
)
6020 struct commit_queue_entry
*e
, *v
= *ta
->selected_entry
;
6023 return; /* paranoid check */
6025 if (v
->worktree_entry
!= victim
) {
6026 TAILQ_FOREACH(v
, &ta
->real_commits
->head
, entry
) {
6027 if (v
->worktree_entry
== victim
)
6034 ta
->wctx
.wt_state
&= ~victim
;
6036 if (*ta
->selected_entry
== v
)
6037 *ta
->selected_entry
= TAILQ_NEXT(v
, entry
);
6038 if (*ta
->first_displayed_entry
== v
)
6039 *ta
->first_displayed_entry
= TAILQ_NEXT(v
, entry
);
6040 if (*ta
->last_displayed_entry
== v
)
6041 *ta
->last_displayed_entry
= TAILQ_NEXT(v
, entry
);
6043 for (e
= TAILQ_NEXT(v
, entry
); e
!= NULL
; e
= TAILQ_NEXT(e
, entry
))
6046 --tog_base_commit
.idx
;
6047 --ta
->real_commits
->ncommits
;
6049 TAILQ_REMOVE(&ta
->real_commits
->head
, v
, entry
);
6054 * Create a file which contains the target path of a symlink so we can feed
6055 * it as content to the diff engine.
6057 static const struct got_error
*
6058 get_symlink_target_file(int *fd
, int dirfd
, const char *de_name
,
6059 const char *abspath
)
6061 const struct got_error
*err
= NULL
;
6062 char target_path
[PATH_MAX
];
6063 ssize_t target_len
, outlen
;
6068 target_len
= readlinkat(dirfd
, de_name
, target_path
, PATH_MAX
);
6069 if (target_len
== -1)
6070 return got_error_from_errno2("readlinkat", abspath
);
6072 target_len
= readlink(abspath
, target_path
, PATH_MAX
);
6073 if (target_len
== -1)
6074 return got_error_from_errno2("readlink", abspath
);
6077 *fd
= got_opentempfd();
6079 return got_error_from_errno("got_opentempfd");
6081 outlen
= write(*fd
, target_path
, target_len
);
6083 err
= got_error_from_errno("got_opentempfd");
6087 if (lseek(*fd
, 0, SEEK_SET
) == -1) {
6088 err
= got_error_from_errno2("lseek", abspath
);
6100 static const struct got_error
*
6101 emit_base_commit_header(FILE *f
, struct got_diff_line
**lines
, size_t *nlines
,
6102 struct got_object_id
*commit_id
, struct got_worktree
*worktree
)
6104 const struct got_error
*err
;
6105 struct got_object_id
*base_commit_id
;
6106 char *base_commit_idstr
;
6109 if (worktree
== NULL
) /* shouldn't happen */
6110 return got_error(GOT_ERR_NOT_WORKTREE
);
6112 base_commit_id
= got_worktree_get_base_commit_id(worktree
);
6114 if (commit_id
!= NULL
) {
6115 if (got_object_id_cmp(commit_id
, base_commit_id
) != 0)
6116 base_commit_id
= commit_id
;
6119 err
= got_object_id_str(&base_commit_idstr
, base_commit_id
);
6123 if ((n
= fprintf(f
, "commit - %s\n", base_commit_idstr
)) < 0)
6124 err
= got_error_from_errno("fprintf");
6125 free(base_commit_idstr
);
6129 return add_line_metadata(lines
, nlines
,
6130 (*lines
)[*nlines
- 1].offset
+ n
, GOT_DIFF_LINE_META
);
6133 static const struct got_error
*
6134 tog_worktree_diff(void *arg
, unsigned char status
, unsigned char staged_status
,
6135 const char *path
, struct got_object_id
*blob_id
,
6136 struct got_object_id
*staged_blob_id
, struct got_object_id
*commit_id
,
6137 int dirfd
, const char *de_name
)
6139 const struct got_error
*err
= NULL
;
6140 struct diff_worktree_arg
*a
= arg
;
6141 struct got_blob_object
*blob1
= NULL
;
6144 char *abspath
= NULL
, *label1
= NULL
;
6147 int fd
= -1, fd1
= -1, fd2
= -1;
6148 int n
, f2_exists
= 1;
6150 if (a
->diff_staged
) {
6151 if (staged_status
!= GOT_STATUS_MODIFY
&&
6152 staged_status
!= GOT_STATUS_ADD
&&
6153 staged_status
!= GOT_STATUS_DELETE
)
6156 if (staged_status
== GOT_STATUS_DELETE
)
6158 if (status
== GOT_STATUS_NONEXISTENT
)
6159 return got_error_set_errno(ENOENT
, path
);
6160 if (status
!= GOT_STATUS_MODIFY
&&
6161 status
!= GOT_STATUS_ADD
&&
6162 status
!= GOT_STATUS_DELETE
&&
6163 status
!= GOT_STATUS_CONFLICT
)
6167 err
= got_opentemp_truncate(a
->f1
);
6169 return got_error_from_errno("got_opentemp_truncate");
6170 err
= got_opentemp_truncate(a
->f2
);
6172 return got_error_from_errno("got_opentemp_truncate");
6174 if (!a
->header_shown
) {
6175 n
= fprintf(a
->outfile
, "path + %s%s\n",
6176 got_worktree_get_root_path(a
->worktree
),
6177 a
->diff_staged
? " (staged changes)" : "");
6179 return got_error_from_errno("fprintf");
6182 err
= add_line_metadata(a
->lines
, a
->nlines
, outoff
,
6183 GOT_DIFF_LINE_META
);
6187 a
->header_shown
= 1;
6190 err
= emit_base_commit_header(a
->outfile
,
6191 a
->lines
, a
->nlines
, commit_id
, a
->worktree
);
6195 if (a
->diff_staged
) {
6196 const char *label1
= NULL
, *label2
= NULL
;
6198 switch (staged_status
) {
6199 case GOT_STATUS_MODIFY
:
6203 case GOT_STATUS_ADD
:
6206 case GOT_STATUS_DELETE
:
6210 return got_error(GOT_ERR_FILE_STATUS
);
6213 fd1
= got_opentempfd();
6215 return got_error_from_errno("got_opentempfd");
6217 fd2
= got_opentempfd();
6219 err
= got_error_from_errno("got_opentempfd");
6223 err
= got_diff_objects_as_blobs(a
->lines
, a
->nlines
,
6224 a
->f1
, a
->f2
, fd1
, fd2
, blob_id
, staged_blob_id
,
6225 label1
, label2
, a
->diff_algo
, a
->diff_context
,
6226 a
->ignore_whitespace
, a
->force_text_diff
,
6227 a
->diffstat
, a
->repo
, a
->outfile
);
6231 fd1
= got_opentempfd();
6233 return got_error_from_errno("got_opentempfd");
6235 if (staged_status
== GOT_STATUS_ADD
||
6236 staged_status
== GOT_STATUS_MODIFY
) {
6239 err
= got_object_open_as_blob(&blob1
,
6240 a
->repo
, staged_blob_id
, 8192, fd1
);
6243 err
= got_object_id_str(&id_str
, staged_blob_id
);
6246 if (asprintf(&label1
, "%s (staged)", id_str
) == -1) {
6247 err
= got_error_from_errno("asprintf");
6252 } else if (status
!= GOT_STATUS_ADD
) {
6253 err
= got_object_open_as_blob(&blob1
,
6254 a
->repo
, blob_id
, 8192, fd1
);
6259 if (status
!= GOT_STATUS_DELETE
) {
6260 if (asprintf(&abspath
, "%s/%s",
6261 got_worktree_get_root_path(a
->worktree
), path
) == -1) {
6262 err
= got_error_from_errno("asprintf");
6267 fd
= openat(dirfd
, de_name
,
6268 O_RDONLY
| O_NOFOLLOW
| O_CLOEXEC
);
6270 if (!got_err_open_nofollow_on_symlink()) {
6271 err
= got_error_from_errno2("openat",
6275 err
= get_symlink_target_file(&fd
,
6276 dirfd
, de_name
, abspath
);
6281 fd
= open(abspath
, O_RDONLY
| O_NOFOLLOW
| O_CLOEXEC
);
6283 if (!got_err_open_nofollow_on_symlink()) {
6284 err
= got_error_from_errno2("open",
6288 err
= get_symlink_target_file(&fd
,
6289 dirfd
, de_name
, abspath
);
6294 if (fstat(fd
, &sb
) == -1) {
6295 err
= got_error_from_errno2("fstat", abspath
);
6298 f2
= fdopen(fd
, "r");
6300 err
= got_error_from_errno2("fdopen", abspath
);
6309 if (blob1
!= NULL
) {
6310 err
= got_object_blob_dump_to_file(&size1
,
6311 NULL
, NULL
, a
->f1
, blob1
);
6316 err
= got_diff_blob_file(a
->lines
, a
->nlines
, blob1
, a
->f1
, size1
,
6317 label1
, f2
!= NULL
? f2
: a
->f2
, f2_exists
, &sb
, path
,
6318 tog_diff_algo
, a
->diff_context
, a
->ignore_whitespace
,
6319 a
->force_text_diff
, a
->diffstat
, a
->outfile
);
6322 if (fd
!= -1 && close(fd
) == -1 && err
== NULL
)
6323 err
= got_error_from_errno("close");
6324 if (fd1
!= -1 && close(fd1
) == -1 && err
== NULL
)
6325 err
= got_error_from_errno("close");
6326 if (fd2
!= -1 && close(fd2
) == -1 && err
== NULL
)
6327 err
= got_error_from_errno("close");
6329 got_object_blob_close(blob1
);
6330 if (f2
!= NULL
&& fclose(f2
) == EOF
&& err
== NULL
)
6331 err
= got_error_from_errno("fclose");
6337 static const struct got_error
*
6338 tog_diff_worktree(struct tog_diff_view_state
*s
, FILE *f
,
6339 struct got_diff_line
**lines
, size_t *nlines
,
6340 struct got_diffstat_cb_arg
*dsa
)
6342 const struct got_error
*close_err
, *err
;
6343 struct got_worktree
*worktree
= NULL
;
6344 struct diff_worktree_arg arg
;
6345 struct got_pathlist_head pathlist
;
6346 char *cwd
, *id_str
= NULL
;
6348 TAILQ_INIT(&pathlist
);
6350 cwd
= getcwd(NULL
, 0);
6352 return got_error_from_errno("getcwd");
6354 err
= add_line_metadata(lines
, nlines
, 0, GOT_DIFF_LINE_NONE
);
6358 err
= got_worktree_open(&worktree
, cwd
, NULL
);
6360 if (err
->code
== GOT_ERR_WORKTREE_BUSY
) {
6363 if ((n
= fprintf(f
, "%s\n", err
->msg
)) < 0) {
6364 err
= got_ferror(f
, GOT_ERR_IO
);
6367 err
= add_line_metadata(lines
, nlines
, n
,
6368 GOT_DIFF_LINE_META
);
6371 err
= got_error(GOT_ERR_DIFF_NOCHANGES
);
6376 err
= got_object_id_str(&id_str
,
6377 got_worktree_get_base_commit_id(worktree
));
6381 err
= got_repo_match_object_id(&s
->id1
, NULL
, id_str
,
6382 GOT_OBJ_TYPE_ANY
, &tog_refs
, s
->repo
);
6386 arg
.id_str
= id_str
;
6387 arg
.diff_algo
= tog_diff_algo
;
6389 arg
.worktree
= worktree
;
6391 arg
.diff_context
= s
->diff_context
;
6392 arg
.diff_staged
= s
->diff_staged
;
6393 arg
.ignore_whitespace
= s
->ignore_whitespace
;
6394 arg
.force_text_diff
= s
->force_text_diff
;
6395 arg
.header_shown
= 0;
6397 arg
.nlines
= nlines
;
6402 if (s
->paths
== NULL
) {
6403 err
= got_pathlist_insert(NULL
, &pathlist
, "", NULL
);
6408 err
= got_worktree_status(worktree
, s
->paths
? s
->paths
: &pathlist
,
6409 s
->repo
, 0, tog_worktree_diff
, &arg
, NULL
, NULL
);
6414 const char *msg
= TOG_WORKTREE_CHANGES_LOCAL_MSG
;
6415 int n
, victim
= TOG_WORKTREE_CHANGES_LOCAL
;
6417 if (s
->diff_staged
) {
6418 victim
= TOG_WORKTREE_CHANGES_STAGED
;
6419 msg
= TOG_WORKTREE_CHANGES_STAGED_MSG
;
6421 if ((n
= fprintf(f
, "no %s\n", msg
)) < 0) {
6422 err
= got_ferror(f
, GOT_ERR_IO
);
6425 err
= add_line_metadata(lines
, nlines
, n
, GOT_DIFF_LINE_META
);
6428 if (s
->parent_view
&& s
->parent_view
->type
== TOG_VIEW_LOG
)
6429 evict_worktree_entry(
6430 &s
->parent_view
->state
.log
.thread_args
, victim
);
6431 err
= got_error(GOT_ERR_DIFF_NOCHANGES
);
6437 got_pathlist_free(&pathlist
, GOT_PATHLIST_FREE_NONE
);
6438 if (worktree
!= NULL
) {
6439 if ((close_err
= got_worktree_close(worktree
)) != NULL
) {
6440 if (err
== NULL
|| err
->code
== GOT_ERR_DIFF_NOCHANGES
)
6447 static const struct got_error
*
6448 tog_diff_objects(struct tog_diff_view_state
*s
, FILE *f
,
6449 struct got_diff_line
**lines
, size_t *nlines
,
6450 struct got_diffstat_cb_arg
*dsa
)
6452 const struct got_error
*err
;
6456 err
= got_object_get_type(&obj_type
, s
->repo
, s
->id1
);
6458 err
= got_object_get_type(&obj_type
, s
->repo
, s
->id2
);
6463 case GOT_OBJ_TYPE_BLOB
:
6464 err
= got_diff_objects_as_blobs(lines
, nlines
, s
->f1
, s
->f2
,
6465 s
->fd1
, s
->fd2
, s
->id1
, s
->id2
, NULL
, NULL
, tog_diff_algo
,
6466 s
->diff_context
, s
->ignore_whitespace
, s
->force_text_diff
,
6471 case GOT_OBJ_TYPE_TREE
:
6472 err
= got_diff_objects_as_trees(lines
, nlines
,
6473 s
->f1
, s
->f2
, s
->fd1
, s
->fd2
, s
->id1
, s
->id2
,
6474 s
->paths
, "", "", tog_diff_algo
, s
->diff_context
,
6475 s
->ignore_whitespace
, s
->force_text_diff
, dsa
, s
->repo
, f
);
6479 case GOT_OBJ_TYPE_COMMIT
: {
6480 const struct got_object_id_queue
*parent_ids
;
6481 struct got_commit_object
*commit2
;
6482 struct got_object_qid
*pid
;
6483 struct got_reflist_head
*refs
;
6485 err
= got_diff_objects_as_commits(lines
, nlines
, s
->f1
, s
->f2
,
6486 s
->fd1
, s
->fd2
, s
->id1
, s
->id2
, s
->paths
, tog_diff_algo
,
6487 s
->diff_context
, s
->ignore_whitespace
, s
->force_text_diff
,
6492 refs
= got_reflist_object_id_map_lookup(tog_refs_idmap
, s
->id2
);
6493 /* Show commit info if we're diffing to a parent/root commit. */
6495 return write_commit_info(&s
->lines
, &s
->nlines
, s
->id2
,
6496 refs
, s
->repo
, s
->ignore_whitespace
,
6497 s
->force_text_diff
, dsa
, s
->f
);
6499 err
= got_object_open_as_commit(&commit2
, s
->repo
,
6504 parent_ids
= got_object_commit_get_parent_ids(commit2
);
6505 STAILQ_FOREACH(pid
, parent_ids
, entry
) {
6506 if (got_object_id_cmp(s
->id1
, &pid
->id
) == 0) {
6507 err
= write_commit_info(&s
->lines
, &s
->nlines
,
6508 s
->id2
, refs
, s
->repo
, s
->ignore_whitespace
,
6509 s
->force_text_diff
, dsa
, s
->f
);
6513 if (commit2
!= NULL
)
6514 got_object_commit_close(commit2
);
6520 return got_error(GOT_ERR_OBJ_TYPE
);
6526 static const struct got_error
*
6527 create_diff(struct tog_diff_view_state
*s
)
6529 const struct got_error
*err
= NULL
;
6530 FILE *tmp_diff_file
= NULL
;
6531 struct got_diff_line
*lines
= NULL
;
6532 struct got_pathlist_head changed_paths
;
6533 struct got_diffstat_cb_arg dsa
;
6536 TAILQ_INIT(&changed_paths
);
6537 memset(&dsa
, 0, sizeof(dsa
));
6538 dsa
.paths
= &changed_paths
;
6539 dsa
.diff_algo
= tog_diff_algo
;
6540 dsa
.force_text
= s
->force_text_diff
;
6541 dsa
.ignore_ws
= s
->ignore_whitespace
;
6544 s
->lines
= malloc(sizeof(*s
->lines
));
6545 if (s
->lines
== NULL
)
6546 return got_error_from_errno("malloc");
6549 if (s
->f
&& fclose(s
->f
) == EOF
) {
6551 return got_error_from_errno("fclose");
6554 s
->f
= got_opentemp();
6556 return got_error_from_errno("got_opentemp");
6559 * The diffstat requires the diff to be built first, but we want the
6560 * diffstat to precede the diff when displayed. Build the diff first
6561 * in the temporary file and write the diffstat and/or commit info to
6562 * the persistent file (s->f) from which views are drawn, then append
6563 * the diff from the temp file to the diffstat/commit info in s->f.
6565 tmp_diff_file
= got_opentemp();
6566 if (tmp_diff_file
== NULL
)
6567 return got_error_from_errno("got_opentemp");
6569 lines
= malloc(sizeof(*lines
));
6570 if (lines
== NULL
) {
6571 err
= got_error_from_errno("malloc");
6575 if (s
->parent_view
!= NULL
&& s
->parent_view
->type
== TOG_VIEW_LOG
) {
6576 struct tog_log_view_state
*ls
= &s
->parent_view
->state
.log
;
6577 struct commit_queue_entry
*cqe
= ls
->selected_entry
;
6579 if (cqe
->worktree_entry
!= 0) {
6580 if (cqe
->worktree_entry
== TOG_WORKTREE_CHANGES_STAGED
)
6582 s
->diff_worktree
= 1;
6586 if (s
->diff_worktree
)
6587 err
= tog_diff_worktree(s
, tmp_diff_file
,
6588 &lines
, &nlines
, &dsa
);
6590 err
= tog_diff_objects(s
, tmp_diff_file
,
6591 &lines
, &nlines
, &dsa
);
6593 if (err
->code
!= GOT_ERR_DIFF_NOCHANGES
)
6596 err
= write_diffstat(s
->f
, &s
->lines
, &s
->nlines
, &dsa
);
6601 err
= cat_diff(s
->f
, tmp_diff_file
, &s
->lines
, &s
->nlines
,
6606 got_pathlist_free(&changed_paths
, GOT_PATHLIST_FREE_ALL
);
6607 if (s
->f
&& fflush(s
->f
) != 0 && err
== NULL
)
6608 err
= got_error_from_errno("fflush");
6609 if (tmp_diff_file
&& fclose(tmp_diff_file
) == EOF
&& err
== NULL
)
6610 err
= got_error_from_errno("fclose");
6615 diff_view_indicate_progress(struct tog_view
*view
)
6617 mvwaddstr(view
->window
, 0, 0, "diffing...");
6622 static const struct got_error
*
6623 search_start_diff_view(struct tog_view
*view
)
6625 struct tog_diff_view_state
*s
= &view
->state
.diff
;
6627 s
->matched_line
= 0;
6632 search_setup_diff_view(struct tog_view
*view
, FILE **f
, off_t
**line_offsets
,
6633 size_t *nlines
, int **first
, int **last
, int **match
, int **selected
)
6635 struct tog_diff_view_state
*s
= &view
->state
.diff
;
6638 *nlines
= s
->nlines
;
6639 *line_offsets
= NULL
;
6640 *match
= &s
->matched_line
;
6641 *first
= &s
->first_displayed_line
;
6642 *last
= &s
->last_displayed_line
;
6643 *selected
= &s
->selected_line
;
6646 static const struct got_error
*
6647 search_next_view_match(struct tog_view
*view
)
6649 const struct got_error
*err
= NULL
;
6653 size_t linesize
= 0;
6655 off_t
*line_offsets
;
6657 int *first
, *last
, *match
, *selected
;
6659 if (!view
->search_setup
)
6660 return got_error_msg(GOT_ERR_NOT_IMPL
,
6661 "view search not supported");
6662 view
->search_setup(view
, &f
, &line_offsets
, &nlines
, &first
, &last
,
6665 if (!view
->searching
) {
6666 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
6671 if (view
->searching
== TOG_SEARCH_FORWARD
)
6672 lineno
= *first
+ 1;
6674 lineno
= *first
- 1;
6676 lineno
= *first
- 1 + *selected
;
6681 if (lineno
<= 0 || lineno
> nlines
) {
6683 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
6687 if (view
->searching
== TOG_SEARCH_FORWARD
)
6693 offset
= view
->type
== TOG_VIEW_DIFF
?
6694 view
->state
.diff
.lines
[lineno
- 1].offset
:
6695 line_offsets
[lineno
- 1];
6696 if (fseeko(f
, offset
, SEEK_SET
) != 0) {
6698 return got_error_from_errno("fseeko");
6700 linelen
= getline(&line
, &linesize
, f
);
6701 if (linelen
!= -1) {
6703 err
= expand_tab(&exstr
, line
);
6706 if (match_line(exstr
, &view
->regex
, 1,
6708 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
6715 if (view
->searching
== TOG_SEARCH_FORWARD
)
6730 static const struct got_error
*
6731 close_diff_view(struct tog_view
*view
)
6733 const struct got_error
*err
= NULL
;
6734 struct tog_diff_view_state
*s
= &view
->state
.diff
;
6742 if (s
->f
&& fclose(s
->f
) == EOF
)
6743 err
= got_error_from_errno("fclose");
6745 if (s
->f1
&& fclose(s
->f1
) == EOF
&& err
== NULL
)
6746 err
= got_error_from_errno("fclose");
6748 if (s
->f2
&& fclose(s
->f2
) == EOF
&& err
== NULL
)
6749 err
= got_error_from_errno("fclose");
6751 if (s
->fd1
!= -1 && close(s
->fd1
) == -1 && err
== NULL
)
6752 err
= got_error_from_errno("close");
6754 if (s
->fd2
!= -1 && close(s
->fd2
) == -1 && err
== NULL
)
6755 err
= got_error_from_errno("close");
6763 static const struct got_error
*
6764 open_diff_view(struct tog_view
*view
, struct got_object_id
*id1
,
6765 struct got_object_id
*id2
, const char *label1
, const char *label2
,
6766 int diff_context
, int ignore_whitespace
, int force_text_diff
,
6767 int diff_staged
, int diff_worktree
, const char *worktree_root
,
6768 struct tog_view
*parent_view
, struct got_repository
*repo
,
6769 struct got_pathlist_head
*paths
)
6771 const struct got_error
*err
;
6772 struct tog_diff_view_state
*s
= &view
->state
.diff
;
6774 memset(s
, 0, sizeof(*s
));
6778 if (id1
!= NULL
&& id2
!= NULL
) {
6781 err
= got_object_get_type(&type1
, repo
, id1
);
6784 err
= got_object_get_type(&type2
, repo
, id2
);
6788 if (type1
!= type2
) {
6789 err
= got_error(GOT_ERR_OBJ_TYPE
);
6794 if (diff_worktree
== 0) {
6796 s
->id1
= got_object_id_dup(id1
);
6797 if (s
->id1
== NULL
) {
6798 err
= got_error_from_errno("got_object_id_dup");
6804 s
->id2
= got_object_id_dup(id2
);
6805 if (s
->id2
== NULL
) {
6806 err
= got_error_from_errno("got_object_id_dup");
6811 s
->f1
= got_opentemp();
6812 if (s
->f1
== NULL
) {
6813 err
= got_error_from_errno("got_opentemp");
6817 s
->f2
= got_opentemp();
6818 if (s
->f2
== NULL
) {
6819 err
= got_error_from_errno("got_opentemp");
6823 s
->fd1
= got_opentempfd();
6825 err
= got_error_from_errno("got_opentempfd");
6829 s
->fd2
= got_opentempfd();
6831 err
= got_error_from_errno("got_opentempfd");
6835 s
->first_displayed_line
= 1;
6836 s
->last_displayed_line
= view
->nlines
;
6837 s
->selected_line
= 1;
6840 s
->diff_context
= diff_context
;
6841 s
->ignore_whitespace
= ignore_whitespace
;
6842 s
->force_text_diff
= force_text_diff
;
6843 s
->diff_worktree
= diff_worktree
;
6844 s
->diff_staged
= diff_staged
;
6845 s
->parent_view
= parent_view
;
6848 s
->worktree_root
= worktree_root
;
6850 if (has_colors() && getenv("TOG_COLORS") != NULL
&& !using_mock_io
) {
6853 rc
= init_pair(GOT_DIFF_LINE_MINUS
,
6854 get_color_value("TOG_COLOR_DIFF_MINUS"), -1);
6856 rc
= init_pair(GOT_DIFF_LINE_PLUS
,
6857 get_color_value("TOG_COLOR_DIFF_PLUS"), -1);
6859 rc
= init_pair(GOT_DIFF_LINE_HUNK
,
6860 get_color_value("TOG_COLOR_DIFF_CHUNK_HEADER"), -1);
6862 rc
= init_pair(GOT_DIFF_LINE_META
,
6863 get_color_value("TOG_COLOR_DIFF_META"), -1);
6865 rc
= init_pair(GOT_DIFF_LINE_CHANGES
,
6866 get_color_value("TOG_COLOR_DIFF_META"), -1);
6868 rc
= init_pair(GOT_DIFF_LINE_BLOB_MIN
,
6869 get_color_value("TOG_COLOR_DIFF_META"), -1);
6871 rc
= init_pair(GOT_DIFF_LINE_BLOB_PLUS
,
6872 get_color_value("TOG_COLOR_DIFF_META"), -1);
6874 rc
= init_pair(GOT_DIFF_LINE_AUTHOR
,
6875 get_color_value("TOG_COLOR_AUTHOR"), -1);
6877 rc
= init_pair(GOT_DIFF_LINE_DATE
,
6878 get_color_value("TOG_COLOR_DATE"), -1);
6880 err
= got_error(GOT_ERR_RANGE
);
6885 if (parent_view
&& parent_view
->type
== TOG_VIEW_LOG
&&
6886 view_is_splitscreen(view
)) {
6887 err
= show_log_view(parent_view
); /* draw border */
6891 diff_view_indicate_progress(view
);
6893 err
= create_diff(s
);
6895 view
->show
= show_diff_view
;
6896 view
->input
= input_diff_view
;
6897 view
->reset
= reset_diff_view
;
6898 view
->close
= close_diff_view
;
6899 view
->search_start
= search_start_diff_view
;
6900 view
->search_setup
= search_setup_diff_view
;
6901 view
->search_next
= search_next_view_match
;
6904 if (view
->close
== NULL
)
6905 close_diff_view(view
);
6911 static const struct got_error
*
6912 show_diff_view(struct tog_view
*view
)
6914 const struct got_error
*err
;
6915 struct tog_diff_view_state
*s
= &view
->state
.diff
;
6918 if (s
->diff_worktree
) {
6919 if (asprintf(&header
, "diff %s%s",
6920 s
->diff_staged
? "-s " : "", s
->worktree_root
) == -1)
6921 return got_error_from_errno("asprintf");
6923 char *id_str2
, *id_str1
= NULL
;
6924 const char *label1
, *label2
;
6927 err
= got_object_id_str(&id_str1
, s
->id1
);
6930 label1
= s
->label1
? s
->label1
: id_str1
;
6932 label1
= "/dev/null";
6934 err
= got_object_id_str(&id_str2
, s
->id2
);
6937 label2
= s
->label2
? s
->label2
: id_str2
;
6939 if (asprintf(&header
, "diff %s %s", label1
, label2
) == -1) {
6940 err
= got_error_from_errno("asprintf");
6949 err
= draw_file(view
, header
);
6954 static const struct got_error
*
6955 diff_write_patch(struct tog_view
*view
)
6957 const struct got_error
*err
;
6958 struct tog_diff_view_state
*s
= &view
->state
.diff
;
6959 struct got_object_id
*id2
= s
->id2
;
6961 char buf
[BUFSIZ
], pathbase
[PATH_MAX
];
6962 char *idstr1
, *idstr2
= NULL
, *path
= NULL
;
6967 if (s
->action
!= NULL
) {
6974 return got_error_from_errno("ftello");
6975 if (fseeko(s
->f
, 0L, SEEK_SET
) == -1)
6976 return got_error_from_errno("fseeko");
6978 if (s
->id1
!= NULL
) {
6979 err
= got_object_id_str(&idstr1
, s
->id1
);
6984 if (s
->diff_worktree
== 0 || tog_base_commit
.id
== NULL
) {
6985 /* illegal state that should not be possible */
6986 err
= got_error(GOT_ERR_NOT_WORKTREE
);
6989 id2
= tog_base_commit
.id
;
6991 err
= got_object_id_str(&idstr2
, id2
);
6995 rc
= snprintf(pathbase
, sizeof(pathbase
), "%s/tog-%.8s-%.8s",
6996 GOT_TMPDIR_STR
, idstr1
!= NULL
? idstr1
: "empty", idstr2
);
6997 if (rc
< 0 || (size_t)rc
>= sizeof(pathbase
)) {
6998 err
= got_error(rc
< 0 ? GOT_ERR_IO
: GOT_ERR_NO_SPACE
);
7002 err
= got_opentemp_named(&path
, &f
, pathbase
, ".diff");
7006 while ((r
= fread(buf
, 1, sizeof(buf
), s
->f
)) > 0) {
7007 if (fwrite(buf
, 1, r
, f
) != r
) {
7008 err
= got_ferror(f
, GOT_ERR_IO
);
7014 err
= got_error_from_errno("fread");
7017 if (fseeko(s
->f
, pos
, SEEK_SET
) == -1) {
7018 err
= got_error_from_errno("fseeko");
7022 if (fflush(f
) == EOF
) {
7023 err
= got_error_from_errno2("fflush", path
);
7027 if (asprintf(&s
->action
, "patch file written to %s", path
) == -1) {
7028 err
= got_error_from_errno("asprintf");
7032 view
->action
= s
->action
;
7035 if (f
!= NULL
&& fclose(f
) == EOF
&& err
== NULL
)
7036 err
= got_error_from_errno2("fclose", path
);
7043 static const struct got_error
*
7044 set_selected_commit(struct tog_diff_view_state
*s
,
7045 struct commit_queue_entry
*entry
)
7047 const struct got_error
*err
;
7048 const struct got_object_id_queue
*parent_ids
;
7049 struct got_commit_object
*selected_commit
;
7050 struct got_object_qid
*pid
;
7057 if (entry
->worktree_entry
== 0) {
7058 s
->id2
= got_object_id_dup(entry
->id
);
7060 return got_error_from_errno("got_object_id_dup");
7062 err
= got_object_open_as_commit(&selected_commit
,
7063 s
->repo
, entry
->id
);
7066 parent_ids
= got_object_commit_get_parent_ids(selected_commit
);
7067 pid
= STAILQ_FIRST(parent_ids
);
7068 s
->id1
= pid
? got_object_id_dup(&pid
->id
) : NULL
;
7069 got_object_commit_close(selected_commit
);
7075 static const struct got_error
*
7076 reset_diff_view(struct tog_view
*view
)
7078 struct tog_diff_view_state
*s
= &view
->state
.diff
;
7081 wclear(view
->window
);
7082 s
->first_displayed_line
= 1;
7083 s
->last_displayed_line
= view
->nlines
;
7084 s
->matched_line
= 0;
7085 if (s
->action
!= NULL
) {
7089 diff_view_indicate_progress(view
);
7090 return create_diff(s
);
7094 diff_prev_index(struct tog_diff_view_state
*s
, enum got_diff_line_type type
)
7098 i
= start
= s
->first_displayed_line
- 1;
7100 while (s
->lines
[i
].type
!= type
) {
7104 return; /* do nothing, requested type not in file */
7107 s
->selected_line
= 1;
7108 s
->first_displayed_line
= i
;
7112 diff_next_index(struct tog_diff_view_state
*s
, enum got_diff_line_type type
)
7116 i
= start
= s
->first_displayed_line
+ 1;
7118 while (s
->lines
[i
].type
!= type
) {
7119 if (i
== s
->nlines
- 1)
7122 return; /* do nothing, requested type not in file */
7125 s
->selected_line
= 1;
7126 s
->first_displayed_line
= i
;
7129 static struct got_object_id
*get_selected_commit_id(struct tog_blame_line
*,
7131 static struct got_object_id
*get_annotation_for_line(struct tog_blame_line
*,
7134 static const struct got_error
*
7135 input_diff_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
7137 const struct got_error
*err
= NULL
;
7138 struct tog_diff_view_state
*s
= &view
->state
.diff
;
7139 struct tog_log_view_state
*ls
;
7140 struct commit_queue_entry
*old_selected_entry
;
7142 size_t linesize
= 0;
7144 int i
, nscroll
= view
->nlines
- 1, up
= 0;
7146 s
->lineno
= s
->first_displayed_line
- 1 + s
->selected_line
;
7148 if (s
->action
!= NULL
&& ch
!= ERR
) {
7151 view
->action
= NULL
;
7161 horizontal_scroll_input(view
, ch
);
7166 s
->force_text_diff
= !s
->force_text_diff
;
7167 view
->action
= s
->force_text_diff
?
7168 "force ASCII text enabled" :
7169 "force ASCII text disabled";
7171 else if (ch
== 'w') {
7172 s
->ignore_whitespace
= !s
->ignore_whitespace
;
7173 view
->action
= s
->ignore_whitespace
?
7174 "ignore whitespace enabled" :
7175 "ignore whitespace disabled";
7177 err
= reset_diff_view(view
);
7181 s
->first_displayed_line
= 1;
7190 s
->first_displayed_line
= (s
->nlines
- view
->nlines
) + 2;
7196 if (s
->first_displayed_line
> 1)
7197 s
->first_displayed_line
--;
7208 if (s
->first_displayed_line
== 1) {
7213 while (i
++ < nscroll
&& s
->first_displayed_line
> 1)
7214 s
->first_displayed_line
--;
7220 s
->first_displayed_line
++;
7237 while (!s
->eof
&& i
++ < nscroll
) {
7238 linelen
= getline(&line
, &linesize
, s
->f
);
7239 s
->first_displayed_line
++;
7240 if (linelen
== -1) {
7244 err
= got_ferror(s
->f
, GOT_ERR_IO
);
7251 diff_prev_index(s
, GOT_DIFF_LINE_BLOB_MIN
);
7254 diff_next_index(s
, GOT_DIFF_LINE_BLOB_MIN
);
7257 diff_prev_index(s
, GOT_DIFF_LINE_HUNK
);
7260 diff_next_index(s
, GOT_DIFF_LINE_HUNK
);
7263 if (s
->diff_context
> 0) {
7265 s
->matched_line
= 0;
7266 diff_view_indicate_progress(view
);
7267 err
= create_diff(s
);
7268 if (s
->first_displayed_line
+ view
->nlines
- 1 >
7270 s
->first_displayed_line
= 1;
7271 s
->last_displayed_line
= view
->nlines
;
7277 if (s
->diff_context
< GOT_DIFF_MAX_CONTEXT
) {
7279 s
->matched_line
= 0;
7280 diff_view_indicate_progress(view
);
7281 err
= create_diff(s
);
7293 if (s
->parent_view
== NULL
) {
7297 s
->parent_view
->count
= view
->count
;
7299 if (s
->parent_view
->type
== TOG_VIEW_LOG
) {
7300 ls
= &s
->parent_view
->state
.log
;
7301 old_selected_entry
= ls
->selected_entry
;
7303 err
= input_log_view(NULL
, s
->parent_view
,
7304 up
? KEY_UP
: KEY_DOWN
);
7307 view
->count
= s
->parent_view
->count
;
7309 if (old_selected_entry
== ls
->selected_entry
)
7314 err
= set_selected_commit(s
, ls
->selected_entry
);
7318 if (s
->worktree_root
== NULL
)
7319 s
->worktree_root
= ls
->thread_args
.wctx
.wt_root
;
7320 } else if (s
->parent_view
->type
== TOG_VIEW_BLAME
) {
7321 struct tog_blame_view_state
*bs
;
7322 struct got_object_id
*id
, *prev_id
;
7324 bs
= &s
->parent_view
->state
.blame
;
7325 prev_id
= get_annotation_for_line(bs
->blame
.lines
,
7326 bs
->blame
.nlines
, bs
->last_diffed_line
);
7328 err
= input_blame_view(&view
, s
->parent_view
,
7329 up
? KEY_UP
: KEY_DOWN
);
7332 view
->count
= s
->parent_view
->count
;
7334 if (prev_id
== NULL
)
7336 id
= get_selected_commit_id(bs
->blame
.lines
,
7337 bs
->blame
.nlines
, bs
->first_displayed_line
,
7342 if (!got_object_id_cmp(prev_id
, id
))
7345 err
= input_blame_view(&view
, s
->parent_view
, KEY_ENTER
);
7350 s
->diff_worktree
= 0;
7351 s
->first_displayed_line
= 1;
7352 s
->last_displayed_line
= view
->nlines
;
7353 s
->matched_line
= 0;
7356 diff_view_indicate_progress(view
);
7357 err
= create_diff(s
);
7360 err
= diff_write_patch(view
);
7370 static const struct got_error
*
7371 get_worktree_paths_from_argv(struct got_pathlist_head
*paths
, int argc
,
7372 char *argv
[], struct got_worktree
*worktree
)
7374 const struct got_error
*err
= NULL
;
7376 struct got_pathlist_entry
*new;
7382 return got_error_from_errno("strdup");
7383 return got_pathlist_insert(NULL
, paths
, path
, NULL
);
7386 for (i
= 0; i
< argc
; i
++) {
7387 err
= got_worktree_resolve_path(&path
, worktree
, argv
[i
]);
7390 err
= got_pathlist_insert(&new, paths
, path
, NULL
);
7391 if (err
!= NULL
|| new == NULL
) {
7401 static const struct got_error
*
7402 cmd_diff(int argc
, char *argv
[])
7404 const struct got_error
*error
;
7405 struct got_repository
*repo
= NULL
;
7406 struct got_worktree
*worktree
= NULL
;
7407 struct got_pathlist_head paths
;
7408 struct got_object_id
*ids
[2] = { NULL
, NULL
};
7409 const char *commit_args
[2] = { NULL
, NULL
};
7410 char *labels
[2] = { NULL
, NULL
};
7411 char *repo_path
= NULL
, *worktree_path
= NULL
, *cwd
= NULL
;
7412 int type1
= GOT_OBJ_TYPE_ANY
, type2
= GOT_OBJ_TYPE_ANY
;
7413 int i
, ncommit_args
= 0, diff_context
= 3, ignore_whitespace
= 0;
7414 int ch
, diff_staged
= 0, diff_worktree
= 0, force_text_diff
= 0;
7416 struct tog_view
*view
;
7417 int *pack_fds
= NULL
;
7421 while ((ch
= getopt(argc
, argv
, "aC:c:r:sw")) != -1) {
7424 force_text_diff
= 1;
7427 diff_context
= strtonum(optarg
, 0, GOT_DIFF_MAX_CONTEXT
,
7430 errx(1, "number of context lines is %s: %s",
7434 if (ncommit_args
>= 2)
7435 errx(1, "too many -c options used");
7436 commit_args
[ncommit_args
++] = optarg
;
7439 repo_path
= realpath(optarg
, NULL
);
7440 if (repo_path
== NULL
)
7441 return got_error_from_errno2("realpath",
7443 got_path_strip_trailing_slashes(repo_path
);
7449 ignore_whitespace
= 1;
7460 error
= got_repo_pack_fds_open(&pack_fds
);
7464 if (repo_path
== NULL
) {
7465 cwd
= getcwd(NULL
, 0);
7467 return got_error_from_errno("getcwd");
7468 error
= got_worktree_open(&worktree
, cwd
, NULL
);
7469 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
7473 strdup(got_worktree_get_repo_path(worktree
));
7475 repo_path
= strdup(cwd
);
7476 if (repo_path
== NULL
) {
7477 error
= got_error_from_errno("strdup");
7482 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
7486 if (diff_staged
&& (worktree
== NULL
|| ncommit_args
> 0)) {
7487 error
= got_error_msg(GOT_ERR_BAD_OPTION
,
7488 "-s can only be used when diffing a work tree");
7494 error
= apply_unveil(got_repo_get_path(repo
),
7495 worktree
!= NULL
? got_worktree_get_root_path(worktree
) : NULL
);
7499 if (argc
== 2 || ncommit_args
> 0) {
7500 int obj_type
= (ncommit_args
> 0 ?
7501 GOT_OBJ_TYPE_COMMIT
: GOT_OBJ_TYPE_ANY
);
7503 error
= tog_load_refs(repo
, 0);
7507 for (i
= 0; i
< (ncommit_args
> 0 ? ncommit_args
: argc
); ++i
) {
7509 char *keyword_idstr
= NULL
;
7511 if (ncommit_args
> 0)
7512 arg
= commit_args
[i
];
7516 error
= got_keyword_to_idstr(&keyword_idstr
, arg
,
7520 if (keyword_idstr
!= NULL
)
7521 arg
= keyword_idstr
;
7523 error
= got_repo_match_object_id(&ids
[i
], &labels
[i
],
7524 arg
, obj_type
, &tog_refs
, repo
);
7525 free(keyword_idstr
);
7526 if (error
!= NULL
) {
7527 if (error
->code
!= GOT_ERR_NOT_REF
&&
7528 error
->code
!= GOT_ERR_NO_OBJ
)
7530 if (ncommit_args
> 0)
7538 if (diff_staged
&& ids
[0] != NULL
) {
7539 error
= got_error_msg(GOT_ERR_BAD_OPTION
,
7540 "-s can only be used when diffing a work tree");
7544 if (ncommit_args
== 0 && (ids
[0] == NULL
|| ids
[1] == NULL
)) {
7545 if (worktree
== NULL
) {
7546 if (argc
== 2 && ids
[0] == NULL
) {
7547 error
= got_error_path(argv
[0], GOT_ERR_NO_OBJ
);
7549 } else if (argc
== 2 && ids
[1] == NULL
) {
7550 error
= got_error_path(argv
[1], GOT_ERR_NO_OBJ
);
7552 } else if (argc
> 0) {
7553 error
= got_error_fmt(GOT_ERR_NOT_WORKTREE
,
7554 "%s", "specified paths cannot be resolved");
7557 error
= got_error(GOT_ERR_NOT_WORKTREE
);
7562 error
= get_worktree_paths_from_argv(&paths
, argc
, argv
,
7567 worktree_path
= strdup(got_worktree_get_root_path(worktree
));
7568 if (worktree_path
== NULL
) {
7569 error
= got_error_from_errno("strdup");
7575 if (ncommit_args
== 1) { /* diff commit against its first parent */
7576 struct got_commit_object
*commit
;
7578 error
= got_object_open_as_commit(&commit
, repo
, ids
[0]);
7582 labels
[1] = labels
[0];
7584 if (got_object_commit_get_nparents(commit
) > 0) {
7585 const struct got_object_id_queue
*pids
;
7586 struct got_object_qid
*pid
;
7588 pids
= got_object_commit_get_parent_ids(commit
);
7589 pid
= STAILQ_FIRST(pids
);
7590 ids
[0] = got_object_id_dup(&pid
->id
);
7591 if (ids
[0] == NULL
) {
7592 error
= got_error_from_errno(
7593 "got_object_id_dup");
7594 got_object_commit_close(commit
);
7597 error
= got_object_id_str(&labels
[0], ids
[0]);
7598 if (error
!= NULL
) {
7599 got_object_commit_close(commit
);
7604 labels
[0] = strdup("/dev/null");
7605 if (labels
[0] == NULL
) {
7606 error
= got_error_from_errno("strdup");
7607 got_object_commit_close(commit
);
7612 got_object_commit_close(commit
);
7615 if (ncommit_args
== 0 && argc
> 2) {
7616 error
= got_error_msg(GOT_ERR_BAD_PATH
,
7617 "path arguments cannot be used when diffing two objects");
7622 error
= got_object_get_type(&type1
, repo
, ids
[0]);
7627 if (diff_worktree
== 0) {
7628 error
= got_object_get_type(&type2
, repo
, ids
[1]);
7631 if (type1
!= GOT_OBJ_TYPE_ANY
&& type1
!= type2
) {
7632 error
= got_error(GOT_ERR_OBJ_TYPE
);
7635 if (type1
== GOT_OBJ_TYPE_BLOB
&& argc
> 2) {
7636 error
= got_error_msg(GOT_ERR_OBJ_TYPE
,
7637 "path arguments cannot be used when diffing blobs");
7642 for (i
= 0; ncommit_args
> 0 && i
< argc
; i
++) {
7644 struct got_pathlist_entry
*new;
7650 error
= got_worktree_resolve_path(&p
, worktree
,
7654 prefix
= got_worktree_get_path_prefix(worktree
);
7655 while (prefix
[0] == '/')
7657 if (asprintf(&in_repo_path
, "%s%s%s", prefix
,
7658 (p
[0] != '\0' && prefix
[0] != '\0') ? "/" : "",
7660 error
= got_error_from_errno("asprintf");
7666 char *mapped_path
, *s
;
7668 error
= got_repo_map_path(&mapped_path
, repo
, argv
[i
]);
7674 in_repo_path
= strdup(s
);
7675 if (in_repo_path
== NULL
) {
7676 error
= got_error_from_errno("asprintf");
7683 error
= got_pathlist_insert(&new, &paths
, in_repo_path
, NULL
);
7684 if (error
!= NULL
|| new == NULL
)
7690 view
= view_open(0, 0, 0, 0, TOG_VIEW_DIFF
);
7692 error
= got_error_from_errno("view_open");
7697 error
= set_tog_base_commit(repo
, worktree
);
7701 /* Release work tree lock. */
7702 got_worktree_close(worktree
);
7706 error
= open_diff_view(view
, ids
[0], ids
[1], labels
[0], labels
[1],
7707 diff_context
, ignore_whitespace
, force_text_diff
, diff_staged
,
7708 diff_worktree
, worktree_path
, NULL
, repo
, &paths
);
7712 error
= view_loop(view
);
7715 got_pathlist_free(&paths
, GOT_PATHLIST_FREE_PATH
);
7716 free(tog_base_commit
.id
);
7717 free(worktree_path
);
7725 const struct got_error
*close_err
= got_repo_close(repo
);
7730 got_worktree_close(worktree
);
7732 const struct got_error
*pack_err
=
7733 got_repo_pack_fds_close(pack_fds
);
7746 "usage: %s blame [-c commit] [-r repository-path] path\n",
7751 struct tog_blame_line
{
7753 struct got_object_id
*id
;
7756 static const struct got_error
*
7757 draw_blame(struct tog_view
*view
)
7759 struct tog_blame_view_state
*s
= &view
->state
.blame
;
7760 struct tog_blame
*blame
= &s
->blame
;
7761 regmatch_t
*regmatch
= &view
->regmatch
;
7762 const struct got_error
*err
;
7763 int lineno
= 0, nprinted
= 0;
7765 size_t linesize
= 0;
7769 struct tog_blame_line
*blame_line
;
7770 struct got_object_id
*prev_id
= NULL
;
7772 struct tog_color
*tc
;
7774 err
= got_object_id_str(&id_str
, &s
->blamed_commit
->id
);
7779 werase(view
->window
);
7781 if (asprintf(&line
, "commit %s", id_str
) == -1) {
7782 err
= got_error_from_errno("asprintf");
7787 err
= format_line(&wline
, &width
, NULL
, line
, 0, view
->ncols
, 0, 0);
7792 if (view_needs_focus_indication(view
))
7793 wstandout(view
->window
);
7794 tc
= get_color(&s
->colors
, TOG_COLOR_COMMIT
);
7796 wattr_on(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
7797 waddwstr(view
->window
, wline
);
7798 while (width
++ < view
->ncols
)
7799 waddch(view
->window
, ' ');
7801 wattr_off(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
7802 if (view_needs_focus_indication(view
))
7803 wstandend(view
->window
);
7807 if (view
->gline
> blame
->nlines
)
7808 view
->gline
= blame
->nlines
;
7810 if (tog_io
.wait_for_ui
) {
7811 struct tog_blame_thread_args
*bta
= &s
->blame
.thread_args
;
7814 rc
= pthread_cond_wait(&bta
->blame_complete
, &tog_mutex
);
7816 return got_error_set_errno(rc
, "pthread_cond_wait");
7817 tog_io
.wait_for_ui
= 0;
7820 if (asprintf(&line
, "[%d/%d] %s%s", view
->gline
? view
->gline
:
7821 s
->first_displayed_line
- 1 + s
->selected_line
, blame
->nlines
,
7822 s
->blame_complete
? "" : "annotating... ", s
->path
) == -1) {
7824 return got_error_from_errno("asprintf");
7827 err
= format_line(&wline
, &width
, NULL
, line
, 0, view
->ncols
, 0, 0);
7832 waddwstr(view
->window
, wline
);
7835 if (width
< view
->ncols
- 1)
7836 waddch(view
->window
, '\n');
7840 while (nprinted
< view
->nlines
- 2) {
7841 linelen
= getline(&line
, &linesize
, blame
->f
);
7842 if (linelen
== -1) {
7843 if (feof(blame
->f
)) {
7848 return got_ferror(blame
->f
, GOT_ERR_IO
);
7850 if (++lineno
< s
->first_displayed_line
)
7852 if (view
->gline
&& !gotoline(view
, &lineno
, &nprinted
))
7855 /* Set view->maxx based on full line length. */
7856 err
= format_line(&wline
, &width
, NULL
, line
, 0, INT_MAX
, 9, 1);
7863 view
->maxx
= MAX(view
->maxx
, width
);
7865 if (nprinted
== s
->selected_line
- 1)
7866 wstandout(view
->window
);
7868 if (blame
->nlines
> 0) {
7869 blame_line
= &blame
->lines
[lineno
- 1];
7870 if (blame_line
->annotated
&& prev_id
&&
7871 got_object_id_cmp(prev_id
, blame_line
->id
) == 0 &&
7872 !(nprinted
== s
->selected_line
- 1)) {
7873 waddstr(view
->window
, " ");
7874 } else if (blame_line
->annotated
) {
7876 err
= got_object_id_str(&id_str
,
7882 tc
= get_color(&s
->colors
, TOG_COLOR_COMMIT
);
7884 wattr_on(view
->window
,
7885 COLOR_PAIR(tc
->colorpair
), NULL
);
7886 wprintw(view
->window
, "%.8s", id_str
);
7888 wattr_off(view
->window
,
7889 COLOR_PAIR(tc
->colorpair
), NULL
);
7891 prev_id
= blame_line
->id
;
7893 waddstr(view
->window
, "........");
7897 waddstr(view
->window
, "........");
7901 if (nprinted
== s
->selected_line
- 1)
7902 wstandend(view
->window
);
7903 waddstr(view
->window
, " ");
7905 if (view
->ncols
<= 9) {
7907 } else if (s
->first_displayed_line
+ nprinted
==
7909 regmatch
->rm_so
>= 0 && regmatch
->rm_so
< regmatch
->rm_eo
) {
7910 err
= add_matched_line(&width
, line
, view
->ncols
- 9, 9,
7911 view
->window
, view
->x
, regmatch
);
7919 err
= format_line(&wline
, &width
, &skip
, line
,
7920 view
->x
, view
->ncols
- 9, 9, 1);
7925 waddwstr(view
->window
, &wline
[skip
]);
7931 if (width
<= view
->ncols
- 1)
7932 waddch(view
->window
, '\n');
7933 if (++nprinted
== 1)
7934 s
->first_displayed_line
= lineno
;
7937 s
->last_displayed_line
= lineno
;
7944 static const struct got_error
*
7945 blame_cb(void *arg
, int nlines
, int lineno
,
7946 struct got_commit_object
*commit
, struct got_object_id
*id
)
7948 const struct got_error
*err
= NULL
;
7949 struct tog_blame_cb_args
*a
= arg
;
7950 struct tog_blame_line
*line
;
7953 if (nlines
!= a
->nlines
||
7954 (lineno
!= -1 && lineno
< 1) || lineno
> a
->nlines
)
7955 return got_error(GOT_ERR_RANGE
);
7957 errcode
= pthread_mutex_lock(&tog_mutex
);
7959 return got_error_set_errno(errcode
, "pthread_mutex_lock");
7961 if (*a
->quit
) { /* user has quit the blame view */
7962 err
= got_error(GOT_ERR_ITER_COMPLETED
);
7967 goto done
; /* no change in this commit */
7969 line
= &a
->lines
[lineno
- 1];
7970 if (line
->annotated
)
7973 line
->id
= got_object_id_dup(id
);
7974 if (line
->id
== NULL
) {
7975 err
= got_error_from_errno("got_object_id_dup");
7978 line
->annotated
= 1;
7980 errcode
= pthread_mutex_unlock(&tog_mutex
);
7982 err
= got_error_set_errno(errcode
, "pthread_mutex_unlock");
7987 blame_thread(void *arg
)
7989 const struct got_error
*err
, *close_err
;
7990 struct tog_blame_thread_args
*ta
= arg
;
7991 struct tog_blame_cb_args
*a
= ta
->cb_args
;
7992 int errcode
, fd1
= -1, fd2
= -1;
7993 FILE *f1
= NULL
, *f2
= NULL
;
7995 fd1
= got_opentempfd();
7997 return (void *)got_error_from_errno("got_opentempfd");
7999 fd2
= got_opentempfd();
8001 err
= got_error_from_errno("got_opentempfd");
8005 f1
= got_opentemp();
8007 err
= (void *)got_error_from_errno("got_opentemp");
8010 f2
= got_opentemp();
8012 err
= (void *)got_error_from_errno("got_opentemp");
8016 err
= block_signals_used_by_main_thread();
8020 err
= got_blame(ta
->path
, a
->commit_id
, ta
->repo
,
8021 tog_diff_algo
, blame_cb
, ta
->cb_args
,
8022 ta
->cancel_cb
, ta
->cancel_arg
, fd1
, fd2
, f1
, f2
);
8023 if (err
&& err
->code
== GOT_ERR_CANCELLED
)
8026 errcode
= pthread_mutex_lock(&tog_mutex
);
8028 err
= got_error_set_errno(errcode
, "pthread_mutex_lock");
8032 close_err
= got_repo_close(ta
->repo
);
8038 if (tog_io
.wait_for_ui
) {
8039 errcode
= pthread_cond_signal(&ta
->blame_complete
);
8040 if (errcode
&& err
== NULL
)
8041 err
= got_error_set_errno(errcode
,
8042 "pthread_cond_signal");
8045 errcode
= pthread_mutex_unlock(&tog_mutex
);
8046 if (errcode
&& err
== NULL
)
8047 err
= got_error_set_errno(errcode
, "pthread_mutex_unlock");
8050 if (fd1
!= -1 && close(fd1
) == -1 && err
== NULL
)
8051 err
= got_error_from_errno("close");
8052 if (fd2
!= -1 && close(fd2
) == -1 && err
== NULL
)
8053 err
= got_error_from_errno("close");
8054 if (f1
&& fclose(f1
) == EOF
&& err
== NULL
)
8055 err
= got_error_from_errno("fclose");
8056 if (f2
&& fclose(f2
) == EOF
&& err
== NULL
)
8057 err
= got_error_from_errno("fclose");
8062 static struct got_object_id
*
8063 get_selected_commit_id(struct tog_blame_line
*lines
, int nlines
,
8064 int first_displayed_line
, int selected_line
)
8066 struct tog_blame_line
*line
;
8071 line
= &lines
[first_displayed_line
- 1 + selected_line
- 1];
8072 if (!line
->annotated
)
8078 static struct got_object_id
*
8079 get_annotation_for_line(struct tog_blame_line
*lines
, int nlines
,
8082 struct tog_blame_line
*line
;
8084 if (nlines
<= 0 || lineno
>= nlines
)
8087 line
= &lines
[lineno
- 1];
8088 if (!line
->annotated
)
8094 static const struct got_error
*
8095 stop_blame(struct tog_blame
*blame
)
8097 const struct got_error
*err
= NULL
;
8100 if (blame
->thread
) {
8102 errcode
= pthread_mutex_unlock(&tog_mutex
);
8104 return got_error_set_errno(errcode
,
8105 "pthread_mutex_unlock");
8106 errcode
= pthread_join(blame
->thread
, (void **)&err
);
8108 return got_error_set_errno(errcode
, "pthread_join");
8109 errcode
= pthread_mutex_lock(&tog_mutex
);
8111 return got_error_set_errno(errcode
,
8112 "pthread_mutex_lock");
8113 if (err
&& err
->code
== GOT_ERR_ITER_COMPLETED
)
8115 blame
->thread
= 0; //NULL;
8117 if (blame
->thread_args
.repo
) {
8118 const struct got_error
*close_err
;
8119 close_err
= got_repo_close(blame
->thread_args
.repo
);
8122 blame
->thread_args
.repo
= NULL
;
8125 if (fclose(blame
->f
) == EOF
&& err
== NULL
)
8126 err
= got_error_from_errno("fclose");
8130 for (i
= 0; i
< blame
->nlines
; i
++)
8131 free(blame
->lines
[i
].id
);
8133 blame
->lines
= NULL
;
8135 free(blame
->cb_args
.commit_id
);
8136 blame
->cb_args
.commit_id
= NULL
;
8137 if (blame
->pack_fds
) {
8138 const struct got_error
*pack_err
=
8139 got_repo_pack_fds_close(blame
->pack_fds
);
8142 blame
->pack_fds
= NULL
;
8144 free(blame
->line_offsets
);
8145 blame
->line_offsets
= NULL
;
8149 static const struct got_error
*
8150 cancel_blame_view(void *arg
)
8152 const struct got_error
*err
= NULL
;
8156 errcode
= pthread_mutex_lock(&tog_mutex
);
8158 return got_error_set_errno(errcode
, "pthread_mutex_lock");
8161 err
= got_error(GOT_ERR_CANCELLED
);
8163 errcode
= pthread_mutex_unlock(&tog_mutex
);
8165 return got_error_set_errno(errcode
, "pthread_mutex_unlock");
8170 static const struct got_error
*
8171 run_blame(struct tog_view
*view
)
8173 struct tog_blame_view_state
*s
= &view
->state
.blame
;
8174 struct tog_blame
*blame
= &s
->blame
;
8175 const struct got_error
*err
= NULL
;
8176 struct got_commit_object
*commit
= NULL
;
8177 struct got_blob_object
*blob
= NULL
;
8178 struct got_repository
*thread_repo
= NULL
;
8179 struct got_object_id
*obj_id
= NULL
;
8180 int obj_type
, fd
= -1;
8181 int *pack_fds
= NULL
;
8183 err
= got_object_open_as_commit(&commit
, s
->repo
,
8184 &s
->blamed_commit
->id
);
8188 fd
= got_opentempfd();
8190 err
= got_error_from_errno("got_opentempfd");
8194 err
= got_object_id_by_path(&obj_id
, s
->repo
, commit
, s
->path
);
8198 err
= got_object_get_type(&obj_type
, s
->repo
, obj_id
);
8202 if (obj_type
!= GOT_OBJ_TYPE_BLOB
) {
8203 err
= got_error(GOT_ERR_OBJ_TYPE
);
8207 err
= got_object_open_as_blob(&blob
, s
->repo
, obj_id
, 8192, fd
);
8210 blame
->f
= got_opentemp();
8211 if (blame
->f
== NULL
) {
8212 err
= got_error_from_errno("got_opentemp");
8215 err
= got_object_blob_dump_to_file(&blame
->filesize
, &blame
->nlines
,
8216 &blame
->line_offsets
, blame
->f
, blob
);
8219 if (blame
->nlines
== 0) {
8220 s
->blame_complete
= 1;
8224 /* Don't include \n at EOF in the blame line count. */
8225 if (blame
->line_offsets
[blame
->nlines
- 1] == blame
->filesize
)
8228 blame
->lines
= calloc(blame
->nlines
, sizeof(*blame
->lines
));
8229 if (blame
->lines
== NULL
) {
8230 err
= got_error_from_errno("calloc");
8234 err
= got_repo_pack_fds_open(&pack_fds
);
8237 err
= got_repo_open(&thread_repo
, got_repo_get_path(s
->repo
), NULL
,
8242 blame
->pack_fds
= pack_fds
;
8243 blame
->cb_args
.view
= view
;
8244 blame
->cb_args
.lines
= blame
->lines
;
8245 blame
->cb_args
.nlines
= blame
->nlines
;
8246 blame
->cb_args
.commit_id
= got_object_id_dup(&s
->blamed_commit
->id
);
8247 if (blame
->cb_args
.commit_id
== NULL
) {
8248 err
= got_error_from_errno("got_object_id_dup");
8251 blame
->cb_args
.quit
= &s
->done
;
8253 blame
->thread_args
.path
= s
->path
;
8254 blame
->thread_args
.repo
= thread_repo
;
8255 blame
->thread_args
.cb_args
= &blame
->cb_args
;
8256 blame
->thread_args
.complete
= &s
->blame_complete
;
8257 blame
->thread_args
.cancel_cb
= cancel_blame_view
;
8258 blame
->thread_args
.cancel_arg
= &s
->done
;
8259 s
->blame_complete
= 0;
8261 if (s
->first_displayed_line
+ view
->nlines
- 1 > blame
->nlines
) {
8262 s
->first_displayed_line
= 1;
8263 s
->last_displayed_line
= view
->nlines
;
8264 s
->selected_line
= 1;
8266 s
->matched_line
= 0;
8270 got_object_commit_close(commit
);
8271 if (fd
!= -1 && close(fd
) == -1 && err
== NULL
)
8272 err
= got_error_from_errno("close");
8274 got_object_blob_close(blob
);
8281 static const struct got_error
*
8282 open_blame_view(struct tog_view
*view
, char *path
,
8283 struct got_object_id
*commit_id
, struct got_repository
*repo
)
8285 const struct got_error
*err
= NULL
;
8286 struct tog_blame_view_state
*s
= &view
->state
.blame
;
8288 STAILQ_INIT(&s
->blamed_commits
);
8290 s
->path
= strdup(path
);
8291 if (s
->path
== NULL
)
8292 return got_error_from_errno("strdup");
8294 err
= got_object_qid_alloc(&s
->blamed_commit
, commit_id
);
8300 STAILQ_INSERT_HEAD(&s
->blamed_commits
, s
->blamed_commit
, entry
);
8301 s
->first_displayed_line
= 1;
8302 s
->last_displayed_line
= view
->nlines
;
8303 s
->selected_line
= 1;
8304 s
->blame_complete
= 0;
8306 s
->commit_id
= commit_id
;
8307 memset(&s
->blame
, 0, sizeof(s
->blame
));
8309 STAILQ_INIT(&s
->colors
);
8310 if (has_colors() && getenv("TOG_COLORS") != NULL
) {
8311 err
= add_color(&s
->colors
, "^", TOG_COLOR_COMMIT
,
8312 get_color_value("TOG_COLOR_COMMIT"));
8317 view
->show
= show_blame_view
;
8318 view
->input
= input_blame_view
;
8319 view
->reset
= reset_blame_view
;
8320 view
->close
= close_blame_view
;
8321 view
->search_start
= search_start_blame_view
;
8322 view
->search_setup
= search_setup_blame_view
;
8323 view
->search_next
= search_next_view_match
;
8325 if (using_mock_io
) {
8326 struct tog_blame_thread_args
*bta
= &s
->blame
.thread_args
;
8329 rc
= pthread_cond_init(&bta
->blame_complete
, NULL
);
8331 return got_error_set_errno(rc
, "pthread_cond_init");
8334 return run_blame(view
);
8337 static const struct got_error
*
8338 close_blame_view(struct tog_view
*view
)
8340 const struct got_error
*err
= NULL
;
8341 struct tog_blame_view_state
*s
= &view
->state
.blame
;
8343 if (s
->blame
.thread
)
8344 err
= stop_blame(&s
->blame
);
8346 while (!STAILQ_EMPTY(&s
->blamed_commits
)) {
8347 struct got_object_qid
*blamed_commit
;
8348 blamed_commit
= STAILQ_FIRST(&s
->blamed_commits
);
8349 STAILQ_REMOVE_HEAD(&s
->blamed_commits
, entry
);
8350 got_object_qid_free(blamed_commit
);
8353 if (using_mock_io
) {
8354 struct tog_blame_thread_args
*bta
= &s
->blame
.thread_args
;
8357 rc
= pthread_cond_destroy(&bta
->blame_complete
);
8358 if (rc
&& err
== NULL
)
8359 err
= got_error_set_errno(rc
, "pthread_cond_destroy");
8363 free_colors(&s
->colors
);
8367 static const struct got_error
*
8368 search_start_blame_view(struct tog_view
*view
)
8370 struct tog_blame_view_state
*s
= &view
->state
.blame
;
8372 s
->matched_line
= 0;
8377 search_setup_blame_view(struct tog_view
*view
, FILE **f
, off_t
**line_offsets
,
8378 size_t *nlines
, int **first
, int **last
, int **match
, int **selected
)
8380 struct tog_blame_view_state
*s
= &view
->state
.blame
;
8383 *nlines
= s
->blame
.nlines
;
8384 *line_offsets
= s
->blame
.line_offsets
;
8385 *match
= &s
->matched_line
;
8386 *first
= &s
->first_displayed_line
;
8387 *last
= &s
->last_displayed_line
;
8388 *selected
= &s
->selected_line
;
8391 static const struct got_error
*
8392 show_blame_view(struct tog_view
*view
)
8394 const struct got_error
*err
= NULL
;
8395 struct tog_blame_view_state
*s
= &view
->state
.blame
;
8398 if (s
->blame
.thread
== 0 && !s
->blame_complete
) {
8399 errcode
= pthread_create(&s
->blame
.thread
, NULL
, blame_thread
,
8400 &s
->blame
.thread_args
);
8402 return got_error_set_errno(errcode
, "pthread_create");
8405 halfdelay(1); /* fast refresh while annotating */
8408 if (s
->blame_complete
&& !using_mock_io
)
8409 halfdelay(10); /* disable fast refresh */
8411 err
= draw_blame(view
);
8417 static const struct got_error
*
8418 log_annotated_line(struct tog_view
**new_view
, int begin_y
, int begin_x
,
8419 struct got_repository
*repo
, struct got_object_id
*id
)
8421 struct tog_view
*log_view
;
8422 const struct got_error
*err
= NULL
;
8426 log_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_LOG
);
8427 if (log_view
== NULL
)
8428 return got_error_from_errno("view_open");
8430 err
= open_log_view(log_view
, id
, repo
, GOT_REF_HEAD
, "", 0, NULL
);
8432 view_close(log_view
);
8434 *new_view
= log_view
;
8439 static const struct got_error
*
8440 input_blame_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
8442 const struct got_error
*err
= NULL
, *thread_err
= NULL
;
8443 struct tog_view
*diff_view
;
8444 struct tog_blame_view_state
*s
= &view
->state
.blame
;
8445 int eos
, nscroll
, begin_y
= 0, begin_x
= 0;
8447 eos
= nscroll
= view
->nlines
- 2;
8448 if (view_is_hsplit_top(view
))
8458 horizontal_scroll_input(view
, ch
);
8465 s
->selected_line
= 1;
8466 s
->first_displayed_line
= 1;
8471 if (s
->blame
.nlines
< eos
) {
8472 s
->selected_line
= s
->blame
.nlines
;
8473 s
->first_displayed_line
= 1;
8475 s
->selected_line
= eos
;
8476 s
->first_displayed_line
= s
->blame
.nlines
- (eos
- 1);
8483 if (s
->selected_line
> 1)
8485 else if (s
->selected_line
== 1 &&
8486 s
->first_displayed_line
> 1)
8487 s
->first_displayed_line
--;
8498 if (s
->first_displayed_line
== 1) {
8499 if (view
->count
> 1)
8501 s
->selected_line
= MAX(1, s
->selected_line
- nscroll
);
8505 if (s
->first_displayed_line
> nscroll
)
8506 s
->first_displayed_line
-= nscroll
;
8508 s
->first_displayed_line
= 1;
8513 if (s
->selected_line
< eos
&& s
->first_displayed_line
+
8514 s
->selected_line
<= s
->blame
.nlines
)
8516 else if (s
->first_displayed_line
< s
->blame
.nlines
- (eos
- 1))
8517 s
->first_displayed_line
++;
8523 struct got_object_id
*id
= NULL
;
8526 id
= get_selected_commit_id(s
->blame
.lines
, s
->blame
.nlines
,
8527 s
->first_displayed_line
, s
->selected_line
);
8531 struct got_commit_object
*commit
, *pcommit
;
8532 struct got_object_qid
*pid
;
8533 struct got_object_id
*blob_id
= NULL
;
8535 err
= got_object_open_as_commit(&commit
,
8540 got_object_commit_get_parent_ids(commit
));
8542 got_object_commit_close(commit
);
8545 /* Check if path history ends here. */
8546 err
= got_object_open_as_commit(&pcommit
,
8550 err
= got_object_id_by_path(&blob_id
, s
->repo
,
8552 got_object_commit_close(pcommit
);
8554 if (err
->code
== GOT_ERR_NO_TREE_ENTRY
)
8556 got_object_commit_close(commit
);
8559 err
= got_object_get_type(&obj_type
, s
->repo
,
8562 /* Can't blame non-blob type objects. */
8563 if (obj_type
!= GOT_OBJ_TYPE_BLOB
) {
8564 got_object_commit_close(commit
);
8567 err
= got_object_qid_alloc(&s
->blamed_commit
,
8569 got_object_commit_close(commit
);
8571 if (got_object_id_cmp(id
,
8572 &s
->blamed_commit
->id
) == 0)
8574 err
= got_object_qid_alloc(&s
->blamed_commit
,
8580 thread_err
= stop_blame(&s
->blame
);
8584 STAILQ_INSERT_HEAD(&s
->blamed_commits
,
8585 s
->blamed_commit
, entry
);
8586 err
= run_blame(view
);
8592 struct got_object_qid
*first
;
8595 first
= STAILQ_FIRST(&s
->blamed_commits
);
8596 if (!got_object_id_cmp(&first
->id
, s
->commit_id
))
8599 thread_err
= stop_blame(&s
->blame
);
8603 STAILQ_REMOVE_HEAD(&s
->blamed_commits
, entry
);
8604 got_object_qid_free(s
->blamed_commit
);
8606 STAILQ_FIRST(&s
->blamed_commits
);
8607 err
= run_blame(view
);
8614 s
->id_to_log
= get_selected_commit_id(s
->blame
.lines
,
8615 s
->blame
.nlines
, s
->first_displayed_line
, s
->selected_line
);
8617 err
= view_request_new(new_view
, view
, TOG_VIEW_LOG
);
8621 struct got_object_id
*id
= NULL
;
8622 struct got_object_qid
*pid
;
8623 struct got_commit_object
*commit
= NULL
;
8626 id
= get_selected_commit_id(s
->blame
.lines
, s
->blame
.nlines
,
8627 s
->first_displayed_line
, s
->selected_line
);
8630 err
= got_object_open_as_commit(&commit
, s
->repo
, id
);
8633 pid
= STAILQ_FIRST(got_object_commit_get_parent_ids(commit
));
8635 /* traversed from diff view, release diff resources */
8636 err
= close_diff_view(*new_view
);
8639 diff_view
= *new_view
;
8641 if (view_is_parent_view(view
))
8642 view_get_split(view
, &begin_y
, &begin_x
);
8644 diff_view
= view_open(0, 0, begin_y
, begin_x
,
8646 if (diff_view
== NULL
) {
8647 got_object_commit_close(commit
);
8648 err
= got_error_from_errno("view_open");
8652 err
= open_diff_view(diff_view
, pid
? &pid
->id
: NULL
,
8653 id
, NULL
, NULL
, 3, 0, 0, 0, 0, NULL
, view
, s
->repo
, NULL
);
8654 got_object_commit_close(commit
);
8657 s
->last_diffed_line
= s
->first_displayed_line
- 1 +
8660 break; /* still open from active diff view */
8661 if (view_is_parent_view(view
) &&
8662 view
->mode
== TOG_VIEW_SPLIT_HRZN
) {
8663 err
= view_init_hsplit(view
, begin_y
);
8669 diff_view
->focussed
= 1;
8670 diff_view
->mode
= view
->mode
;
8671 diff_view
->nlines
= view
->lines
- begin_y
;
8672 if (view_is_parent_view(view
)) {
8673 view_transfer_size(diff_view
, view
);
8674 err
= view_close_child(view
);
8677 err
= view_set_child(view
, diff_view
);
8680 view
->focus_child
= 1;
8682 *new_view
= diff_view
;
8695 if (s
->last_displayed_line
>= s
->blame
.nlines
&&
8696 s
->selected_line
>= MIN(s
->blame
.nlines
,
8697 view
->nlines
- 2)) {
8701 if (s
->last_displayed_line
>= s
->blame
.nlines
&&
8702 s
->selected_line
< view
->nlines
- 2) {
8704 MIN(nscroll
, s
->last_displayed_line
-
8705 s
->first_displayed_line
- s
->selected_line
+ 1);
8707 if (s
->last_displayed_line
+ nscroll
<= s
->blame
.nlines
)
8708 s
->first_displayed_line
+= nscroll
;
8710 s
->first_displayed_line
=
8711 s
->blame
.nlines
- (view
->nlines
- 3);
8714 if (s
->selected_line
> view
->nlines
- 2) {
8715 s
->selected_line
= MIN(s
->blame
.nlines
,
8723 return thread_err
? thread_err
: err
;
8726 static const struct got_error
*
8727 reset_blame_view(struct tog_view
*view
)
8729 const struct got_error
*err
;
8730 struct tog_blame_view_state
*s
= &view
->state
.blame
;
8734 err
= stop_blame(&s
->blame
);
8738 return run_blame(view
);
8741 static const struct got_error
*
8742 cmd_blame(int argc
, char *argv
[])
8744 const struct got_error
*error
;
8745 struct got_repository
*repo
= NULL
;
8746 struct got_worktree
*worktree
= NULL
;
8747 char *cwd
= NULL
, *repo_path
= NULL
, *in_repo_path
= NULL
;
8748 char *link_target
= NULL
;
8749 struct got_object_id
*commit_id
= NULL
;
8750 struct got_commit_object
*commit
= NULL
;
8751 char *keyword_idstr
= NULL
, *commit_id_str
= NULL
;
8753 struct tog_view
*view
= NULL
;
8754 int *pack_fds
= NULL
;
8756 while ((ch
= getopt(argc
, argv
, "c:r:")) != -1) {
8759 commit_id_str
= optarg
;
8762 repo_path
= realpath(optarg
, NULL
);
8763 if (repo_path
== NULL
)
8764 return got_error_from_errno2("realpath",
8779 error
= got_repo_pack_fds_open(&pack_fds
);
8783 if (repo_path
== NULL
) {
8784 cwd
= getcwd(NULL
, 0);
8786 return got_error_from_errno("getcwd");
8787 error
= got_worktree_open(&worktree
, cwd
, NULL
);
8788 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
8792 strdup(got_worktree_get_repo_path(worktree
));
8794 repo_path
= strdup(cwd
);
8795 if (repo_path
== NULL
) {
8796 error
= got_error_from_errno("strdup");
8801 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
8805 error
= get_in_repo_path_from_argv0(&in_repo_path
, argc
, argv
, repo
,
8812 error
= apply_unveil(got_repo_get_path(repo
), NULL
);
8816 error
= tog_load_refs(repo
, 0);
8820 if (commit_id_str
== NULL
) {
8821 struct got_reference
*head_ref
;
8822 error
= got_ref_open(&head_ref
, repo
, worktree
?
8823 got_worktree_get_head_ref_name(worktree
) : GOT_REF_HEAD
, 0);
8826 error
= got_ref_resolve(&commit_id
, repo
, head_ref
);
8827 got_ref_close(head_ref
);
8829 error
= got_keyword_to_idstr(&keyword_idstr
, commit_id_str
,
8833 if (keyword_idstr
!= NULL
)
8834 commit_id_str
= keyword_idstr
;
8836 error
= got_repo_match_object_id(&commit_id
, NULL
,
8837 commit_id_str
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
8842 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
8846 error
= got_object_resolve_symlinks(&link_target
, in_repo_path
,
8851 view
= view_open(0, 0, 0, 0, TOG_VIEW_BLAME
);
8853 error
= got_error_from_errno("view_open");
8856 error
= open_blame_view(view
, link_target
? link_target
: in_repo_path
,
8858 if (error
!= NULL
) {
8859 if (view
->close
== NULL
)
8860 close_blame_view(view
);
8866 error
= set_tog_base_commit(repo
, worktree
);
8870 /* Release work tree lock. */
8871 got_worktree_close(worktree
);
8875 error
= view_loop(view
);
8878 free(tog_base_commit
.id
);
8884 free(keyword_idstr
);
8886 got_object_commit_close(commit
);
8888 got_worktree_close(worktree
);
8890 const struct got_error
*close_err
= got_repo_close(repo
);
8895 const struct got_error
*pack_err
=
8896 got_repo_pack_fds_close(pack_fds
);
8904 static const struct got_error
*
8905 draw_tree_entries(struct tog_view
*view
, const char *parent_path
)
8907 struct tog_tree_view_state
*s
= &view
->state
.tree
;
8908 const struct got_error
*err
= NULL
;
8909 struct got_tree_entry
*te
;
8912 struct tog_color
*tc
;
8913 int width
, n
, nentries
, scrollx
, i
= 1;
8914 int limit
= view
->nlines
;
8917 if (view_is_hsplit_top(view
))
8918 --limit
; /* border */
8920 werase(view
->window
);
8925 err
= format_line(&wline
, &width
, NULL
, s
->tree_label
, 0, view
->ncols
,
8929 if (view_needs_focus_indication(view
))
8930 wstandout(view
->window
);
8931 tc
= get_color(&s
->colors
, TOG_COLOR_COMMIT
);
8933 wattr_on(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
8934 waddwstr(view
->window
, wline
);
8937 while (width
++ < view
->ncols
)
8938 waddch(view
->window
, ' ');
8940 wattr_off(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
8941 if (view_needs_focus_indication(view
))
8942 wstandend(view
->window
);
8947 if (s
->first_displayed_entry
) {
8948 i
+= got_tree_entry_get_index(s
->first_displayed_entry
);
8949 if (s
->tree
!= s
->root
)
8950 ++i
; /* account for ".." entry */
8952 nentries
= got_object_tree_get_nentries(s
->tree
);
8953 if (asprintf(&index
, "[%d/%d] %s",
8954 i
, nentries
+ (s
->tree
== s
->root
? 0 : 1), parent_path
) == -1)
8955 return got_error_from_errno("asprintf");
8956 err
= format_line(&wline
, &width
, NULL
, index
, 0, view
->ncols
, 0, 0);
8960 waddwstr(view
->window
, wline
);
8963 if (width
< view
->ncols
- 1)
8964 waddch(view
->window
, '\n');
8967 waddch(view
->window
, '\n');
8971 if (s
->first_displayed_entry
== NULL
) {
8972 te
= got_object_tree_get_first_entry(s
->tree
);
8973 if (s
->selected
== 0) {
8975 wstandout(view
->window
);
8976 s
->selected_entry
= NULL
;
8978 waddstr(view
->window
, " ..\n"); /* parent directory */
8979 if (s
->selected
== 0 && view
->focussed
)
8980 wstandend(view
->window
);
8987 te
= s
->first_displayed_entry
;
8991 for (i
= got_tree_entry_get_index(te
); i
< nentries
; i
++) {
8992 char *line
= NULL
, *id_str
= NULL
, *link_target
= NULL
;
8993 const char *modestr
= "";
8996 te
= got_object_tree_get_entry(s
->tree
, i
);
8997 mode
= got_tree_entry_get_mode(te
);
9000 err
= got_object_id_str(&id_str
,
9001 got_tree_entry_get_id(te
));
9003 return got_error_from_errno(
9004 "got_object_id_str");
9006 if (got_object_tree_entry_is_submodule(te
))
9008 else if (S_ISLNK(mode
)) {
9011 err
= got_tree_entry_get_symlink_target(&link_target
,
9017 for (i
= 0; link_target
[i
] != '\0'; i
++) {
9018 if (!isprint((unsigned char)link_target
[i
]))
9019 link_target
[i
] = '?';
9023 else if (S_ISDIR(mode
))
9025 else if (mode
& S_IXUSR
)
9027 if (asprintf(&line
, "%s %s%s%s%s", id_str
? id_str
: "",
9028 got_tree_entry_get_name(te
), modestr
,
9029 link_target
? " -> ": "",
9030 link_target
? link_target
: "") == -1) {
9033 return got_error_from_errno("asprintf");
9038 /* use full line width to determine view->maxx */
9039 err
= format_line(&wline
, &width
, NULL
, line
, 0, INT_MAX
, 0, 0);
9044 view
->maxx
= MAX(view
->maxx
, width
);
9048 err
= format_line(&wline
, &width
, &scrollx
, line
, view
->x
,
9054 if (n
== s
->selected
) {
9056 wstandout(view
->window
);
9057 s
->selected_entry
= te
;
9059 tc
= match_color(&s
->colors
, line
);
9061 wattr_on(view
->window
,
9062 COLOR_PAIR(tc
->colorpair
), NULL
);
9063 waddwstr(view
->window
, &wline
[scrollx
]);
9065 wattr_off(view
->window
,
9066 COLOR_PAIR(tc
->colorpair
), NULL
);
9067 if (width
< view
->ncols
)
9068 waddch(view
->window
, '\n');
9069 if (n
== s
->selected
&& view
->focussed
)
9070 wstandend(view
->window
);
9076 s
->last_displayed_entry
= te
;
9085 tree_scroll_up(struct tog_tree_view_state
*s
, int maxscroll
)
9087 struct got_tree_entry
*te
;
9088 int isroot
= s
->tree
== s
->root
;
9091 if (s
->first_displayed_entry
== NULL
)
9094 te
= got_tree_entry_get_prev(s
->tree
, s
->first_displayed_entry
);
9095 while (i
++ < maxscroll
) {
9098 s
->first_displayed_entry
= NULL
;
9101 s
->first_displayed_entry
= te
;
9102 te
= got_tree_entry_get_prev(s
->tree
, te
);
9106 static const struct got_error
*
9107 tree_scroll_down(struct tog_view
*view
, int maxscroll
)
9109 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9110 struct got_tree_entry
*next
, *last
;
9113 if (s
->first_displayed_entry
)
9114 next
= got_tree_entry_get_next(s
->tree
,
9115 s
->first_displayed_entry
);
9117 next
= got_object_tree_get_first_entry(s
->tree
);
9119 last
= s
->last_displayed_entry
;
9120 while (next
&& n
++ < maxscroll
) {
9122 s
->last_displayed_entry
= last
;
9123 last
= got_tree_entry_get_next(s
->tree
, last
);
9125 if (last
|| (view
->mode
== TOG_VIEW_SPLIT_HRZN
&& next
)) {
9126 s
->first_displayed_entry
= next
;
9127 next
= got_tree_entry_get_next(s
->tree
, next
);
9134 static const struct got_error
*
9135 tree_entry_path(char **path
, struct tog_parent_trees
*parents
,
9136 struct got_tree_entry
*te
)
9138 const struct got_error
*err
= NULL
;
9139 struct tog_parent_tree
*pt
;
9140 size_t len
= 2; /* for leading slash and NUL */
9142 TAILQ_FOREACH(pt
, parents
, entry
)
9143 len
+= strlen(got_tree_entry_get_name(pt
->selected_entry
))
9146 len
+= strlen(got_tree_entry_get_name(te
));
9148 *path
= calloc(1, len
);
9150 return got_error_from_errno("calloc");
9153 pt
= TAILQ_LAST(parents
, tog_parent_trees
);
9155 const char *name
= got_tree_entry_get_name(pt
->selected_entry
);
9156 if (strlcat(*path
, name
, len
) >= len
) {
9157 err
= got_error(GOT_ERR_NO_SPACE
);
9160 if (strlcat(*path
, "/", len
) >= len
) {
9161 err
= got_error(GOT_ERR_NO_SPACE
);
9164 pt
= TAILQ_PREV(pt
, tog_parent_trees
, entry
);
9167 if (strlcat(*path
, got_tree_entry_get_name(te
), len
) >= len
) {
9168 err
= got_error(GOT_ERR_NO_SPACE
);
9180 static const struct got_error
*
9181 blame_tree_entry(struct tog_view
**new_view
, int begin_y
, int begin_x
,
9182 struct got_tree_entry
*te
, struct tog_parent_trees
*parents
,
9183 struct got_object_id
*commit_id
, struct got_repository
*repo
)
9185 const struct got_error
*err
= NULL
;
9187 struct tog_view
*blame_view
;
9191 err
= tree_entry_path(&path
, parents
, te
);
9195 blame_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_BLAME
);
9196 if (blame_view
== NULL
) {
9197 err
= got_error_from_errno("view_open");
9201 err
= open_blame_view(blame_view
, path
, commit_id
, repo
);
9203 if (err
->code
== GOT_ERR_CANCELLED
)
9205 view_close(blame_view
);
9207 *new_view
= blame_view
;
9213 static const struct got_error
*
9214 log_selected_tree_entry(struct tog_view
**new_view
, int begin_y
, int begin_x
,
9215 struct tog_tree_view_state
*s
)
9217 struct tog_view
*log_view
;
9218 const struct got_error
*err
= NULL
;
9223 log_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_LOG
);
9224 if (log_view
== NULL
)
9225 return got_error_from_errno("view_open");
9227 err
= tree_entry_path(&path
, &s
->parents
, s
->selected_entry
);
9231 err
= open_log_view(log_view
, s
->commit_id
, s
->repo
, s
->head_ref_name
,
9234 view_close(log_view
);
9236 *new_view
= log_view
;
9241 static const struct got_error
*
9242 open_tree_view(struct tog_view
*view
, struct got_object_id
*commit_id
,
9243 const char *head_ref_name
, struct got_repository
*repo
)
9245 const struct got_error
*err
= NULL
;
9246 char *commit_id_str
= NULL
;
9247 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9248 struct got_commit_object
*commit
= NULL
;
9250 TAILQ_INIT(&s
->parents
);
9251 STAILQ_INIT(&s
->colors
);
9253 s
->commit_id
= got_object_id_dup(commit_id
);
9254 if (s
->commit_id
== NULL
) {
9255 err
= got_error_from_errno("got_object_id_dup");
9259 err
= got_object_open_as_commit(&commit
, repo
, commit_id
);
9264 * The root is opened here and will be closed when the view is closed.
9265 * Any visited subtrees and their path-wise parents are opened and
9268 err
= got_object_open_as_tree(&s
->root
, repo
,
9269 got_object_commit_get_tree_id(commit
));
9274 err
= got_object_id_str(&commit_id_str
, commit_id
);
9278 if (asprintf(&s
->tree_label
, "commit %s", commit_id_str
) == -1) {
9279 err
= got_error_from_errno("asprintf");
9283 s
->first_displayed_entry
= got_object_tree_get_entry(s
->tree
, 0);
9284 s
->selected_entry
= got_object_tree_get_entry(s
->tree
, 0);
9285 if (head_ref_name
) {
9286 s
->head_ref_name
= strdup(head_ref_name
);
9287 if (s
->head_ref_name
== NULL
) {
9288 err
= got_error_from_errno("strdup");
9294 if (has_colors() && getenv("TOG_COLORS") != NULL
) {
9295 err
= add_color(&s
->colors
, "\\$$",
9296 TOG_COLOR_TREE_SUBMODULE
,
9297 get_color_value("TOG_COLOR_TREE_SUBMODULE"));
9300 err
= add_color(&s
->colors
, "@$", TOG_COLOR_TREE_SYMLINK
,
9301 get_color_value("TOG_COLOR_TREE_SYMLINK"));
9304 err
= add_color(&s
->colors
, "/$",
9305 TOG_COLOR_TREE_DIRECTORY
,
9306 get_color_value("TOG_COLOR_TREE_DIRECTORY"));
9310 err
= add_color(&s
->colors
, "\\*$",
9311 TOG_COLOR_TREE_EXECUTABLE
,
9312 get_color_value("TOG_COLOR_TREE_EXECUTABLE"));
9316 err
= add_color(&s
->colors
, "^$", TOG_COLOR_COMMIT
,
9317 get_color_value("TOG_COLOR_COMMIT"));
9322 view
->show
= show_tree_view
;
9323 view
->input
= input_tree_view
;
9324 view
->close
= close_tree_view
;
9325 view
->search_start
= search_start_tree_view
;
9326 view
->search_next
= search_next_tree_view
;
9328 free(commit_id_str
);
9330 got_object_commit_close(commit
);
9332 if (view
->close
== NULL
)
9333 close_tree_view(view
);
9339 static const struct got_error
*
9340 close_tree_view(struct tog_view
*view
)
9342 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9344 free_colors(&s
->colors
);
9345 free(s
->tree_label
);
9346 s
->tree_label
= NULL
;
9348 s
->commit_id
= NULL
;
9349 free(s
->head_ref_name
);
9350 s
->head_ref_name
= NULL
;
9351 while (!TAILQ_EMPTY(&s
->parents
)) {
9352 struct tog_parent_tree
*parent
;
9353 parent
= TAILQ_FIRST(&s
->parents
);
9354 TAILQ_REMOVE(&s
->parents
, parent
, entry
);
9355 if (parent
->tree
!= s
->root
)
9356 got_object_tree_close(parent
->tree
);
9360 if (s
->tree
!= NULL
&& s
->tree
!= s
->root
)
9361 got_object_tree_close(s
->tree
);
9363 got_object_tree_close(s
->root
);
9367 static const struct got_error
*
9368 search_start_tree_view(struct tog_view
*view
)
9370 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9372 s
->matched_entry
= NULL
;
9377 match_tree_entry(struct got_tree_entry
*te
, regex_t
*regex
)
9379 regmatch_t regmatch
;
9381 return regexec(regex
, got_tree_entry_get_name(te
), 1, ®match
,
9385 static const struct got_error
*
9386 search_next_tree_view(struct tog_view
*view
)
9388 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9389 struct got_tree_entry
*te
= NULL
;
9391 if (!view
->searching
) {
9392 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
9396 if (s
->matched_entry
) {
9397 if (view
->searching
== TOG_SEARCH_FORWARD
) {
9398 if (s
->selected_entry
)
9399 te
= got_tree_entry_get_next(s
->tree
,
9402 te
= got_object_tree_get_first_entry(s
->tree
);
9404 if (s
->selected_entry
== NULL
)
9405 te
= got_object_tree_get_last_entry(s
->tree
);
9407 te
= got_tree_entry_get_prev(s
->tree
,
9411 if (s
->selected_entry
)
9412 te
= s
->selected_entry
;
9413 else if (view
->searching
== TOG_SEARCH_FORWARD
)
9414 te
= got_object_tree_get_first_entry(s
->tree
);
9416 te
= got_object_tree_get_last_entry(s
->tree
);
9421 if (s
->matched_entry
== NULL
) {
9422 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
9425 if (view
->searching
== TOG_SEARCH_FORWARD
)
9426 te
= got_object_tree_get_first_entry(s
->tree
);
9428 te
= got_object_tree_get_last_entry(s
->tree
);
9431 if (match_tree_entry(te
, &view
->regex
)) {
9432 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
9433 s
->matched_entry
= te
;
9437 if (view
->searching
== TOG_SEARCH_FORWARD
)
9438 te
= got_tree_entry_get_next(s
->tree
, te
);
9440 te
= got_tree_entry_get_prev(s
->tree
, te
);
9443 if (s
->matched_entry
) {
9444 s
->first_displayed_entry
= s
->matched_entry
;
9451 static const struct got_error
*
9452 show_tree_view(struct tog_view
*view
)
9454 const struct got_error
*err
= NULL
;
9455 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9458 err
= tree_entry_path(&parent_path
, &s
->parents
, NULL
);
9462 err
= draw_tree_entries(view
, parent_path
);
9469 static const struct got_error
*
9470 tree_goto_line(struct tog_view
*view
, int nlines
)
9472 const struct got_error
*err
= NULL
;
9473 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9474 struct got_tree_entry
**fte
, **lte
, **ste
;
9475 int g
, last
, first
= 1, i
= 1;
9476 int root
= s
->tree
== s
->root
;
9477 int off
= root
? 1 : 2;
9484 else if (g
> got_object_tree_get_nentries(s
->tree
))
9485 g
= got_object_tree_get_nentries(s
->tree
) + (root
? 0 : 1);
9487 fte
= &s
->first_displayed_entry
;
9488 lte
= &s
->last_displayed_entry
;
9489 ste
= &s
->selected_entry
;
9492 first
= got_tree_entry_get_index(*fte
);
9493 first
+= off
; /* account for ".." */
9495 last
= got_tree_entry_get_index(*lte
);
9498 if (g
>= first
&& g
<= last
&& g
- first
< nlines
) {
9499 s
->selected
= g
- first
;
9500 return NULL
; /* gline is on the current page */
9504 i
= got_tree_entry_get_index(*ste
);
9509 err
= tree_scroll_down(view
, g
- i
);
9512 if (got_tree_entry_get_index(*lte
) >=
9513 got_object_tree_get_nentries(s
->tree
) - 1 &&
9514 first
+ s
->selected
< g
&&
9515 s
->selected
< s
->ndisplayed
- 1) {
9516 first
= got_tree_entry_get_index(*fte
);
9518 s
->selected
= g
- first
;
9521 tree_scroll_up(s
, i
- g
);
9524 (*fte
== NULL
|| (root
&& !got_tree_entry_get_index(*fte
))))
9525 s
->selected
= g
- 1;
9530 static const struct got_error
*
9531 input_tree_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
9533 const struct got_error
*err
= NULL
;
9534 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9535 struct got_tree_entry
*te
;
9536 int n
, nscroll
= view
->nlines
- 3;
9539 return tree_goto_line(view
, nscroll
);
9548 horizontal_scroll_input(view
, ch
);
9551 s
->show_ids
= !s
->show_ids
;
9556 if (!s
->selected_entry
)
9558 err
= view_request_new(new_view
, view
, TOG_VIEW_LOG
);
9562 err
= view_request_new(new_view
, view
, TOG_VIEW_REF
);
9569 if (s
->tree
== s
->root
)
9570 s
->first_displayed_entry
=
9571 got_object_tree_get_first_entry(s
->tree
);
9573 s
->first_displayed_entry
= NULL
;
9578 int eos
= view
->nlines
- 3;
9580 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
)
9584 te
= got_object_tree_get_last_entry(s
->tree
);
9585 for (n
= 0; n
< eos
; n
++) {
9587 if (s
->tree
!= s
->root
) {
9588 s
->first_displayed_entry
= NULL
;
9593 s
->first_displayed_entry
= te
;
9594 te
= got_tree_entry_get_prev(s
->tree
, te
);
9597 s
->selected
= n
- 1;
9603 if (s
->selected
> 0) {
9607 tree_scroll_up(s
, 1);
9608 if (s
->selected_entry
== NULL
||
9609 (s
->tree
== s
->root
&& s
->selected_entry
==
9610 got_object_tree_get_first_entry(s
->tree
)))
9620 if (s
->tree
== s
->root
) {
9621 if (got_object_tree_get_first_entry(s
->tree
) ==
9622 s
->first_displayed_entry
)
9623 s
->selected
-= MIN(s
->selected
, nscroll
);
9625 if (s
->first_displayed_entry
== NULL
)
9626 s
->selected
-= MIN(s
->selected
, nscroll
);
9628 tree_scroll_up(s
, MAX(0, nscroll
));
9629 if (s
->selected_entry
== NULL
||
9630 (s
->tree
== s
->root
&& s
->selected_entry
==
9631 got_object_tree_get_first_entry(s
->tree
)))
9637 if (s
->selected
< s
->ndisplayed
- 1) {
9641 if (got_tree_entry_get_next(s
->tree
, s
->last_displayed_entry
)
9643 /* can't scroll any further */
9647 tree_scroll_down(view
, 1);
9657 if (got_tree_entry_get_next(s
->tree
, s
->last_displayed_entry
)
9659 /* can't scroll any further; move cursor down */
9660 if (s
->selected
< s
->ndisplayed
- 1)
9661 s
->selected
+= MIN(nscroll
,
9662 s
->ndisplayed
- s
->selected
- 1);
9667 tree_scroll_down(view
, nscroll
);
9672 if (s
->selected_entry
== NULL
|| ch
== KEY_BACKSPACE
) {
9673 struct tog_parent_tree
*parent
;
9674 /* user selected '..' */
9675 if (s
->tree
== s
->root
) {
9679 parent
= TAILQ_FIRST(&s
->parents
);
9680 TAILQ_REMOVE(&s
->parents
, parent
,
9682 got_object_tree_close(s
->tree
);
9683 s
->tree
= parent
->tree
;
9684 s
->first_displayed_entry
=
9685 parent
->first_displayed_entry
;
9687 parent
->selected_entry
;
9688 s
->selected
= parent
->selected
;
9689 if (s
->selected
> view
->nlines
- 3) {
9690 err
= offset_selection_down(view
);
9695 } else if (S_ISDIR(got_tree_entry_get_mode(
9696 s
->selected_entry
))) {
9697 struct got_tree_object
*subtree
;
9699 err
= got_object_open_as_tree(&subtree
, s
->repo
,
9700 got_tree_entry_get_id(s
->selected_entry
));
9703 err
= tree_view_visit_subtree(s
, subtree
);
9705 got_object_tree_close(subtree
);
9708 } else if (S_ISREG(got_tree_entry_get_mode(s
->selected_entry
)))
9709 err
= view_request_new(new_view
, view
, TOG_VIEW_BLAME
);
9712 if (view
->nlines
>= 4 && s
->selected
>= view
->nlines
- 3)
9713 s
->selected
= view
->nlines
- 4;
9729 "usage: %s tree [-c commit] [-r repository-path] [path]\n",
9734 static const struct got_error
*
9735 cmd_tree(int argc
, char *argv
[])
9737 const struct got_error
*error
;
9738 struct got_repository
*repo
= NULL
;
9739 struct got_worktree
*worktree
= NULL
;
9740 char *cwd
= NULL
, *repo_path
= NULL
, *in_repo_path
= NULL
;
9741 struct got_object_id
*commit_id
= NULL
;
9742 struct got_commit_object
*commit
= NULL
;
9743 const char *commit_id_arg
= NULL
;
9744 char *keyword_idstr
= NULL
, *label
= NULL
;
9745 struct got_reference
*ref
= NULL
;
9746 const char *head_ref_name
= NULL
;
9748 struct tog_view
*view
;
9749 int *pack_fds
= NULL
;
9751 while ((ch
= getopt(argc
, argv
, "c:r:")) != -1) {
9754 commit_id_arg
= optarg
;
9757 repo_path
= realpath(optarg
, NULL
);
9758 if (repo_path
== NULL
)
9759 return got_error_from_errno2("realpath",
9774 error
= got_repo_pack_fds_open(&pack_fds
);
9778 if (repo_path
== NULL
) {
9779 cwd
= getcwd(NULL
, 0);
9781 return got_error_from_errno("getcwd");
9782 error
= got_worktree_open(&worktree
, cwd
, NULL
);
9783 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
9787 strdup(got_worktree_get_repo_path(worktree
));
9789 repo_path
= strdup(cwd
);
9790 if (repo_path
== NULL
) {
9791 error
= got_error_from_errno("strdup");
9796 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
9800 error
= get_in_repo_path_from_argv0(&in_repo_path
, argc
, argv
,
9807 error
= apply_unveil(got_repo_get_path(repo
), NULL
);
9811 error
= tog_load_refs(repo
, 0);
9815 if (commit_id_arg
== NULL
) {
9816 error
= got_repo_match_object_id(&commit_id
, &label
,
9817 worktree
? got_worktree_get_head_ref_name(worktree
) :
9818 GOT_REF_HEAD
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
9821 head_ref_name
= label
;
9823 error
= got_keyword_to_idstr(&keyword_idstr
, commit_id_arg
,
9827 if (keyword_idstr
!= NULL
)
9828 commit_id_arg
= keyword_idstr
;
9830 error
= got_ref_open(&ref
, repo
, commit_id_arg
, 0);
9832 head_ref_name
= got_ref_get_name(ref
);
9833 else if (error
->code
!= GOT_ERR_NOT_REF
)
9835 error
= got_repo_match_object_id(&commit_id
, NULL
,
9836 commit_id_arg
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
9841 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
9845 view
= view_open(0, 0, 0, 0, TOG_VIEW_TREE
);
9847 error
= got_error_from_errno("view_open");
9850 error
= open_tree_view(view
, commit_id
, head_ref_name
, repo
);
9853 if (!got_path_is_root_dir(in_repo_path
)) {
9854 error
= tree_view_walk_path(&view
->state
.tree
, commit
,
9861 error
= set_tog_base_commit(repo
, worktree
);
9865 /* Release work tree lock. */
9866 got_worktree_close(worktree
);
9870 error
= view_loop(view
);
9873 free(tog_base_commit
.id
);
9874 free(keyword_idstr
);
9880 got_object_commit_close(commit
);
9883 if (worktree
!= NULL
)
9884 got_worktree_close(worktree
);
9886 const struct got_error
*close_err
= got_repo_close(repo
);
9891 const struct got_error
*pack_err
=
9892 got_repo_pack_fds_close(pack_fds
);
9900 static const struct got_error
*
9901 ref_view_load_refs(struct tog_ref_view_state
*s
)
9903 struct got_reflist_entry
*sre
;
9904 struct tog_reflist_entry
*re
;
9907 TAILQ_FOREACH(sre
, &tog_refs
, entry
) {
9908 if (strncmp(got_ref_get_name(sre
->ref
),
9909 "refs/got/", 9) == 0 &&
9910 strncmp(got_ref_get_name(sre
->ref
),
9911 "refs/got/backup/", 16) != 0)
9914 re
= malloc(sizeof(*re
));
9916 return got_error_from_errno("malloc");
9918 re
->ref
= got_ref_dup(sre
->ref
);
9919 if (re
->ref
== NULL
)
9920 return got_error_from_errno("got_ref_dup");
9921 re
->idx
= s
->nrefs
++;
9922 TAILQ_INSERT_TAIL(&s
->refs
, re
, entry
);
9925 s
->first_displayed_entry
= TAILQ_FIRST(&s
->refs
);
9930 ref_view_free_refs(struct tog_ref_view_state
*s
)
9932 struct tog_reflist_entry
*re
;
9934 while (!TAILQ_EMPTY(&s
->refs
)) {
9935 re
= TAILQ_FIRST(&s
->refs
);
9936 TAILQ_REMOVE(&s
->refs
, re
, entry
);
9937 got_ref_close(re
->ref
);
9942 static const struct got_error
*
9943 open_ref_view(struct tog_view
*view
, struct got_repository
*repo
)
9945 const struct got_error
*err
= NULL
;
9946 struct tog_ref_view_state
*s
= &view
->state
.ref
;
9948 s
->selected_entry
= 0;
9951 TAILQ_INIT(&s
->refs
);
9952 STAILQ_INIT(&s
->colors
);
9954 err
= ref_view_load_refs(s
);
9958 if (has_colors() && getenv("TOG_COLORS") != NULL
) {
9959 err
= add_color(&s
->colors
, "^refs/heads/",
9960 TOG_COLOR_REFS_HEADS
,
9961 get_color_value("TOG_COLOR_REFS_HEADS"));
9965 err
= add_color(&s
->colors
, "^refs/tags/",
9966 TOG_COLOR_REFS_TAGS
,
9967 get_color_value("TOG_COLOR_REFS_TAGS"));
9971 err
= add_color(&s
->colors
, "^refs/remotes/",
9972 TOG_COLOR_REFS_REMOTES
,
9973 get_color_value("TOG_COLOR_REFS_REMOTES"));
9977 err
= add_color(&s
->colors
, "^refs/got/backup/",
9978 TOG_COLOR_REFS_BACKUP
,
9979 get_color_value("TOG_COLOR_REFS_BACKUP"));
9984 view
->show
= show_ref_view
;
9985 view
->input
= input_ref_view
;
9986 view
->close
= close_ref_view
;
9987 view
->search_start
= search_start_ref_view
;
9988 view
->search_next
= search_next_ref_view
;
9991 if (view
->close
== NULL
)
9992 close_ref_view(view
);
9998 static const struct got_error
*
9999 close_ref_view(struct tog_view
*view
)
10001 struct tog_ref_view_state
*s
= &view
->state
.ref
;
10003 ref_view_free_refs(s
);
10004 free_colors(&s
->colors
);
10009 static const struct got_error
*
10010 resolve_reflist_entry(struct got_object_id
**commit_id
,
10011 struct tog_reflist_entry
*re
, struct got_repository
*repo
)
10013 const struct got_error
*err
= NULL
;
10014 struct got_object_id
*obj_id
;
10015 struct got_tag_object
*tag
= NULL
;
10020 err
= got_ref_resolve(&obj_id
, repo
, re
->ref
);
10024 err
= got_object_get_type(&obj_type
, repo
, obj_id
);
10028 switch (obj_type
) {
10029 case GOT_OBJ_TYPE_COMMIT
:
10030 *commit_id
= obj_id
;
10032 case GOT_OBJ_TYPE_TAG
:
10033 err
= got_object_open_as_tag(&tag
, repo
, obj_id
);
10037 err
= got_object_get_type(&obj_type
, repo
,
10038 got_object_tag_get_object_id(tag
));
10041 if (obj_type
!= GOT_OBJ_TYPE_COMMIT
) {
10042 err
= got_error(GOT_ERR_OBJ_TYPE
);
10045 *commit_id
= got_object_id_dup(
10046 got_object_tag_get_object_id(tag
));
10047 if (*commit_id
== NULL
) {
10048 err
= got_error_from_errno("got_object_id_dup");
10053 err
= got_error(GOT_ERR_OBJ_TYPE
);
10059 got_object_tag_close(tag
);
10067 static const struct got_error
*
10068 log_ref_entry(struct tog_view
**new_view
, int begin_y
, int begin_x
,
10069 struct tog_reflist_entry
*re
, struct got_repository
*repo
)
10071 struct tog_view
*log_view
;
10072 const struct got_error
*err
= NULL
;
10073 struct got_object_id
*commit_id
= NULL
;
10077 err
= resolve_reflist_entry(&commit_id
, re
, repo
);
10081 log_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_LOG
);
10082 if (log_view
== NULL
) {
10083 err
= got_error_from_errno("view_open");
10087 err
= open_log_view(log_view
, commit_id
, repo
,
10088 got_ref_get_name(re
->ref
), "", 0, NULL
);
10091 view_close(log_view
);
10093 *new_view
= log_view
;
10099 ref_scroll_up(struct tog_ref_view_state
*s
, int maxscroll
)
10101 struct tog_reflist_entry
*re
;
10104 if (s
->first_displayed_entry
== TAILQ_FIRST(&s
->refs
))
10107 re
= TAILQ_PREV(s
->first_displayed_entry
, tog_reflist_head
, entry
);
10108 while (i
++ < maxscroll
) {
10111 s
->first_displayed_entry
= re
;
10112 re
= TAILQ_PREV(re
, tog_reflist_head
, entry
);
10116 static const struct got_error
*
10117 ref_scroll_down(struct tog_view
*view
, int maxscroll
)
10119 struct tog_ref_view_state
*s
= &view
->state
.ref
;
10120 struct tog_reflist_entry
*next
, *last
;
10123 if (s
->first_displayed_entry
)
10124 next
= TAILQ_NEXT(s
->first_displayed_entry
, entry
);
10126 next
= TAILQ_FIRST(&s
->refs
);
10128 last
= s
->last_displayed_entry
;
10129 while (next
&& n
++ < maxscroll
) {
10131 s
->last_displayed_entry
= last
;
10132 last
= TAILQ_NEXT(last
, entry
);
10134 if (last
|| (view
->mode
== TOG_VIEW_SPLIT_HRZN
)) {
10135 s
->first_displayed_entry
= next
;
10136 next
= TAILQ_NEXT(next
, entry
);
10143 static const struct got_error
*
10144 search_start_ref_view(struct tog_view
*view
)
10146 struct tog_ref_view_state
*s
= &view
->state
.ref
;
10148 s
->matched_entry
= NULL
;
10153 match_reflist_entry(struct tog_reflist_entry
*re
, regex_t
*regex
)
10155 regmatch_t regmatch
;
10157 return regexec(regex
, got_ref_get_name(re
->ref
), 1, ®match
,
10161 static const struct got_error
*
10162 search_next_ref_view(struct tog_view
*view
)
10164 struct tog_ref_view_state
*s
= &view
->state
.ref
;
10165 struct tog_reflist_entry
*re
= NULL
;
10167 if (!view
->searching
) {
10168 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
10172 if (s
->matched_entry
) {
10173 if (view
->searching
== TOG_SEARCH_FORWARD
) {
10174 if (s
->selected_entry
)
10175 re
= TAILQ_NEXT(s
->selected_entry
, entry
);
10177 re
= TAILQ_PREV(s
->selected_entry
,
10178 tog_reflist_head
, entry
);
10180 if (s
->selected_entry
== NULL
)
10181 re
= TAILQ_LAST(&s
->refs
, tog_reflist_head
);
10183 re
= TAILQ_PREV(s
->selected_entry
,
10184 tog_reflist_head
, entry
);
10187 if (s
->selected_entry
)
10188 re
= s
->selected_entry
;
10189 else if (view
->searching
== TOG_SEARCH_FORWARD
)
10190 re
= TAILQ_FIRST(&s
->refs
);
10192 re
= TAILQ_LAST(&s
->refs
, tog_reflist_head
);
10197 if (s
->matched_entry
== NULL
) {
10198 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
10201 if (view
->searching
== TOG_SEARCH_FORWARD
)
10202 re
= TAILQ_FIRST(&s
->refs
);
10204 re
= TAILQ_LAST(&s
->refs
, tog_reflist_head
);
10207 if (match_reflist_entry(re
, &view
->regex
)) {
10208 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
10209 s
->matched_entry
= re
;
10213 if (view
->searching
== TOG_SEARCH_FORWARD
)
10214 re
= TAILQ_NEXT(re
, entry
);
10216 re
= TAILQ_PREV(re
, tog_reflist_head
, entry
);
10219 if (s
->matched_entry
) {
10220 s
->first_displayed_entry
= s
->matched_entry
;
10227 static const struct got_error
*
10228 show_ref_view(struct tog_view
*view
)
10230 const struct got_error
*err
= NULL
;
10231 struct tog_ref_view_state
*s
= &view
->state
.ref
;
10232 struct tog_reflist_entry
*re
;
10235 struct tog_color
*tc
;
10236 int width
, n
, scrollx
;
10237 int limit
= view
->nlines
;
10239 werase(view
->window
);
10242 if (view_is_hsplit_top(view
))
10243 --limit
; /* border */
10248 re
= s
->first_displayed_entry
;
10250 if (asprintf(&line
, "references [%d/%d]", re
->idx
+ s
->selected
+ 1,
10252 return got_error_from_errno("asprintf");
10254 err
= format_line(&wline
, &width
, NULL
, line
, 0, view
->ncols
, 0, 0);
10259 if (view_needs_focus_indication(view
))
10260 wstandout(view
->window
);
10261 waddwstr(view
->window
, wline
);
10262 while (width
++ < view
->ncols
)
10263 waddch(view
->window
, ' ');
10264 if (view_needs_focus_indication(view
))
10265 wstandend(view
->window
);
10275 while (re
&& limit
> 0) {
10277 char ymd
[13]; /* YYYY-MM-DD + " " + NUL */
10279 if (s
->show_date
) {
10280 struct got_commit_object
*ci
;
10281 struct got_tag_object
*tag
;
10282 struct got_object_id
*id
;
10286 err
= got_ref_resolve(&id
, s
->repo
, re
->ref
);
10289 err
= got_object_open_as_tag(&tag
, s
->repo
, id
);
10291 if (err
->code
!= GOT_ERR_OBJ_TYPE
) {
10295 err
= got_object_open_as_commit(&ci
, s
->repo
,
10301 t
= got_object_commit_get_committer_time(ci
);
10302 got_object_commit_close(ci
);
10304 t
= got_object_tag_get_tagger_time(tag
);
10305 got_object_tag_close(tag
);
10308 if (gmtime_r(&t
, &tm
) == NULL
)
10309 return got_error_from_errno("gmtime_r");
10310 if (strftime(ymd
, sizeof(ymd
), "%F ", &tm
) == 0)
10311 return got_error(GOT_ERR_NO_SPACE
);
10313 if (got_ref_is_symbolic(re
->ref
)) {
10314 if (asprintf(&line
, "%s%s -> %s", s
->show_date
?
10315 ymd
: "", got_ref_get_name(re
->ref
),
10316 got_ref_get_symref_target(re
->ref
)) == -1)
10317 return got_error_from_errno("asprintf");
10318 } else if (s
->show_ids
) {
10319 struct got_object_id
*id
;
10321 err
= got_ref_resolve(&id
, s
->repo
, re
->ref
);
10324 err
= got_object_id_str(&id_str
, id
);
10329 if (asprintf(&line
, "%s%s: %s", s
->show_date
? ymd
: "",
10330 got_ref_get_name(re
->ref
), id_str
) == -1) {
10331 err
= got_error_from_errno("asprintf");
10338 } else if (asprintf(&line
, "%s%s", s
->show_date
? ymd
: "",
10339 got_ref_get_name(re
->ref
)) == -1)
10340 return got_error_from_errno("asprintf");
10342 /* use full line width to determine view->maxx */
10343 err
= format_line(&wline
, &width
, NULL
, line
, 0, INT_MAX
, 0, 0);
10348 view
->maxx
= MAX(view
->maxx
, width
);
10352 err
= format_line(&wline
, &width
, &scrollx
, line
, view
->x
,
10353 view
->ncols
, 0, 0);
10358 if (n
== s
->selected
) {
10359 if (view
->focussed
)
10360 wstandout(view
->window
);
10361 s
->selected_entry
= re
;
10363 tc
= match_color(&s
->colors
, got_ref_get_name(re
->ref
));
10365 wattr_on(view
->window
,
10366 COLOR_PAIR(tc
->colorpair
), NULL
);
10367 waddwstr(view
->window
, &wline
[scrollx
]);
10369 wattr_off(view
->window
,
10370 COLOR_PAIR(tc
->colorpair
), NULL
);
10371 if (width
< view
->ncols
)
10372 waddch(view
->window
, '\n');
10373 if (n
== s
->selected
&& view
->focussed
)
10374 wstandend(view
->window
);
10380 s
->last_displayed_entry
= re
;
10383 re
= TAILQ_NEXT(re
, entry
);
10390 static const struct got_error
*
10391 browse_ref_tree(struct tog_view
**new_view
, int begin_y
, int begin_x
,
10392 struct tog_reflist_entry
*re
, struct got_repository
*repo
)
10394 const struct got_error
*err
= NULL
;
10395 struct got_object_id
*commit_id
= NULL
;
10396 struct tog_view
*tree_view
;
10400 err
= resolve_reflist_entry(&commit_id
, re
, repo
);
10404 tree_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_TREE
);
10405 if (tree_view
== NULL
) {
10406 err
= got_error_from_errno("view_open");
10410 err
= open_tree_view(tree_view
, commit_id
,
10411 got_ref_get_name(re
->ref
), repo
);
10415 *new_view
= tree_view
;
10421 static const struct got_error
*
10422 ref_goto_line(struct tog_view
*view
, int nlines
)
10424 const struct got_error
*err
= NULL
;
10425 struct tog_ref_view_state
*s
= &view
->state
.ref
;
10426 int g
, idx
= s
->selected_entry
->idx
;
10433 else if (g
> s
->nrefs
)
10436 if (g
>= s
->first_displayed_entry
->idx
+ 1 &&
10437 g
<= s
->last_displayed_entry
->idx
+ 1 &&
10438 g
- s
->first_displayed_entry
->idx
- 1 < nlines
) {
10439 s
->selected
= g
- s
->first_displayed_entry
->idx
- 1;
10444 err
= ref_scroll_down(view
, g
- idx
- 1);
10447 if (TAILQ_NEXT(s
->last_displayed_entry
, entry
) == NULL
&&
10448 s
->first_displayed_entry
->idx
+ s
->selected
< g
&&
10449 s
->selected
< s
->ndisplayed
- 1)
10450 s
->selected
= g
- s
->first_displayed_entry
->idx
- 1;
10451 } else if (idx
+ 1 > g
)
10452 ref_scroll_up(s
, idx
- g
+ 1);
10454 if (g
< nlines
&& s
->first_displayed_entry
->idx
== 0)
10455 s
->selected
= g
- 1;
10461 static const struct got_error
*
10462 input_ref_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
10464 const struct got_error
*err
= NULL
;
10465 struct tog_ref_view_state
*s
= &view
->state
.ref
;
10466 struct tog_reflist_entry
*re
;
10467 int n
, nscroll
= view
->nlines
- 1;
10470 return ref_goto_line(view
, nscroll
);
10479 horizontal_scroll_input(view
, ch
);
10482 s
->show_ids
= !s
->show_ids
;
10486 s
->show_date
= !s
->show_date
;
10490 s
->sort_by_date
= !s
->sort_by_date
;
10491 view
->action
= s
->sort_by_date
? "sort by date" : "sort by name";
10493 err
= got_reflist_sort(&tog_refs
, s
->sort_by_date
?
10494 got_ref_cmp_by_commit_timestamp_descending
:
10495 tog_ref_cmp_by_name
, s
->repo
);
10498 got_reflist_object_id_map_free(tog_refs_idmap
);
10499 err
= got_reflist_object_id_map_create(&tog_refs_idmap
,
10500 &tog_refs
, s
->repo
);
10503 ref_view_free_refs(s
);
10504 err
= ref_view_load_refs(s
);
10509 if (!s
->selected_entry
)
10511 err
= view_request_new(new_view
, view
, TOG_VIEW_LOG
);
10515 if (!s
->selected_entry
)
10517 err
= view_request_new(new_view
, view
, TOG_VIEW_TREE
);
10524 s
->first_displayed_entry
= TAILQ_FIRST(&s
->refs
);
10529 int eos
= view
->nlines
- 1;
10531 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
)
10532 --eos
; /* border */
10535 re
= TAILQ_LAST(&s
->refs
, tog_reflist_head
);
10536 for (n
= 0; n
< eos
; n
++) {
10539 s
->first_displayed_entry
= re
;
10540 re
= TAILQ_PREV(re
, tog_reflist_head
, entry
);
10543 s
->selected
= n
- 1;
10549 if (s
->selected
> 0) {
10553 ref_scroll_up(s
, 1);
10554 if (s
->selected_entry
== TAILQ_FIRST(&s
->refs
))
10564 if (s
->first_displayed_entry
== TAILQ_FIRST(&s
->refs
))
10565 s
->selected
-= MIN(nscroll
, s
->selected
);
10566 ref_scroll_up(s
, MAX(0, nscroll
));
10567 if (s
->selected_entry
== TAILQ_FIRST(&s
->refs
))
10573 if (s
->selected
< s
->ndisplayed
- 1) {
10577 if (TAILQ_NEXT(s
->last_displayed_entry
, entry
) == NULL
) {
10578 /* can't scroll any further */
10582 ref_scroll_down(view
, 1);
10592 if (TAILQ_NEXT(s
->last_displayed_entry
, entry
) == NULL
) {
10593 /* can't scroll any further; move cursor down */
10594 if (s
->selected
< s
->ndisplayed
- 1)
10595 s
->selected
+= MIN(nscroll
,
10596 s
->ndisplayed
- s
->selected
- 1);
10597 if (view
->count
> 1 && s
->selected
< s
->ndisplayed
- 1)
10598 s
->selected
+= s
->ndisplayed
- s
->selected
- 1;
10602 ref_scroll_down(view
, nscroll
);
10607 err
= tog_load_refs(s
->repo
, s
->sort_by_date
);
10610 ref_view_free_refs(s
);
10611 err
= ref_view_load_refs(s
);
10614 if (view
->nlines
>= 2 && s
->selected
>= view
->nlines
- 1)
10615 s
->selected
= view
->nlines
- 2;
10629 fprintf(stderr
, "usage: %s ref [-r repository-path]\n",
10634 static const struct got_error
*
10635 cmd_ref(int argc
, char *argv
[])
10637 const struct got_error
*error
;
10638 struct got_repository
*repo
= NULL
;
10639 struct got_worktree
*worktree
= NULL
;
10640 char *cwd
= NULL
, *repo_path
= NULL
;
10642 struct tog_view
*view
;
10643 int *pack_fds
= NULL
;
10645 while ((ch
= getopt(argc
, argv
, "r:")) != -1) {
10648 repo_path
= realpath(optarg
, NULL
);
10649 if (repo_path
== NULL
)
10650 return got_error_from_errno2("realpath",
10665 error
= got_repo_pack_fds_open(&pack_fds
);
10669 if (repo_path
== NULL
) {
10670 cwd
= getcwd(NULL
, 0);
10672 return got_error_from_errno("getcwd");
10673 error
= got_worktree_open(&worktree
, cwd
, NULL
);
10674 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
10678 strdup(got_worktree_get_repo_path(worktree
));
10680 repo_path
= strdup(cwd
);
10681 if (repo_path
== NULL
) {
10682 error
= got_error_from_errno("strdup");
10687 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
10693 error
= apply_unveil(got_repo_get_path(repo
), NULL
);
10697 error
= tog_load_refs(repo
, 0);
10701 view
= view_open(0, 0, 0, 0, TOG_VIEW_REF
);
10702 if (view
== NULL
) {
10703 error
= got_error_from_errno("view_open");
10707 error
= open_ref_view(view
, repo
);
10712 error
= set_tog_base_commit(repo
, worktree
);
10716 /* Release work tree lock. */
10717 got_worktree_close(worktree
);
10721 error
= view_loop(view
);
10724 free(tog_base_commit
.id
);
10727 if (worktree
!= NULL
)
10728 got_worktree_close(worktree
);
10730 const struct got_error
*close_err
= got_repo_close(repo
);
10735 const struct got_error
*pack_err
=
10736 got_repo_pack_fds_close(pack_fds
);
10744 static const struct got_error
*
10745 win_draw_center(WINDOW
*win
, size_t y
, size_t x
, size_t maxx
, int focus
,
10754 x
= x
? x
: maxx
> len
? (maxx
- len
) / 2 : 0;
10758 if (mvwprintw(win
, y
, x
, "%s", str
) == ERR
)
10759 return got_error_msg(GOT_ERR_RANGE
, "mvwprintw");
10766 static const struct got_error
*
10767 add_line_offset(off_t
**line_offsets
, size_t *nlines
, off_t off
)
10771 p
= reallocarray(*line_offsets
, *nlines
+ 1, sizeof(off_t
));
10773 free(*line_offsets
);
10774 *line_offsets
= NULL
;
10775 return got_error_from_errno("reallocarray");
10779 (*line_offsets
)[*nlines
] = off
;
10784 static const struct got_error
*
10785 max_key_str(int *ret
, const struct tog_key_map
*km
, size_t n
)
10789 for (;n
> 0; --n
, ++km
) {
10793 if (km
->keys
== NULL
)
10796 t
= t0
= strdup(km
->keys
);
10798 return got_error_from_errno("strdup");
10801 while ((k
= strsep(&t
, " ")) != NULL
)
10802 len
+= strlen(k
) > 1 ? 2 : 0;
10804 *ret
= MAX(*ret
, len
);
10811 * Write keymap section headers, keys, and key info in km to f.
10812 * Save line offset to *off. If terminal has UTF8 encoding enabled,
10813 * wrap control and symbolic keys in guillemets, else use <>.
10815 static const struct got_error
*
10816 format_help_line(off_t
*off
, FILE *f
, const struct tog_key_map
*km
, int width
)
10818 int n
, len
= width
;
10821 static const char *u8_glyph
[] = {
10822 "\xe2\x80\xb9", /* U+2039 (utf8 <) */
10823 "\xe2\x80\xba" /* U+203A (utf8 >) */
10826 int cs
, s
, first
= 1;
10828 cs
= got_locale_is_utf8();
10830 t
= t0
= strdup(km
->keys
);
10832 return got_error_from_errno("strdup");
10834 len
= strlen(km
->keys
);
10835 while ((k
= strsep(&t
, " ")) != NULL
) {
10836 s
= strlen(k
) > 1; /* control or symbolic key */
10837 n
= fprintf(f
, "%s%s%s%s%s", first
? " " : "",
10838 cs
&& s
? u8_glyph
[0] : s
? "<" : "", k
,
10839 cs
&& s
? u8_glyph
[1] : s
? ">" : "", t
? " " : "");
10842 return got_error_from_errno("fprintf");
10850 n
= fprintf(f
, "%*s%s\n", width
- len
, width
- len
? " " : "", km
->info
);
10852 return got_error_from_errno("fprintf");
10858 static const struct got_error
*
10859 format_help(struct tog_help_view_state
*s
)
10861 const struct got_error
*err
= NULL
;
10863 int i
, max
, n
, show
= s
->all
;
10864 static const struct tog_key_map km
[] = {
10865 #define KEYMAP_(info, type) { NULL, (info), type }
10866 #define KEY_(keys, info) { (keys), (info), TOG_KEYMAP_KEYS }
10872 err
= add_line_offset(&s
->line_offsets
, &s
->nlines
, 0);
10877 err
= max_key_str(&max
, km
, n
);
10881 for (i
= 0; i
< n
; ++i
) {
10882 if (km
[i
].keys
== NULL
) {
10884 if (km
[i
].type
== TOG_KEYMAP_GLOBAL
||
10885 km
[i
].type
== s
->type
|| s
->all
)
10889 err
= format_help_line(&off
, s
->f
, &km
[i
], max
);
10892 err
= add_line_offset(&s
->line_offsets
, &s
->nlines
, off
);
10899 err
= add_line_offset(&s
->line_offsets
, &s
->nlines
, off
);
10903 static const struct got_error
*
10904 create_help(struct tog_help_view_state
*s
)
10907 const struct got_error
*err
;
10909 free(s
->line_offsets
);
10910 s
->line_offsets
= NULL
;
10913 f
= got_opentemp();
10915 return got_error_from_errno("got_opentemp");
10918 err
= format_help(s
);
10922 if (s
->f
&& fflush(s
->f
) != 0)
10923 return got_error_from_errno("fflush");
10928 static const struct got_error
*
10929 search_start_help_view(struct tog_view
*view
)
10931 view
->state
.help
.matched_line
= 0;
10936 search_setup_help_view(struct tog_view
*view
, FILE **f
, off_t
**line_offsets
,
10937 size_t *nlines
, int **first
, int **last
, int **match
, int **selected
)
10939 struct tog_help_view_state
*s
= &view
->state
.help
;
10942 *nlines
= s
->nlines
;
10943 *line_offsets
= s
->line_offsets
;
10944 *match
= &s
->matched_line
;
10945 *first
= &s
->first_displayed_line
;
10946 *last
= &s
->last_displayed_line
;
10947 *selected
= &s
->selected_line
;
10950 static const struct got_error
*
10951 show_help_view(struct tog_view
*view
)
10953 struct tog_help_view_state
*s
= &view
->state
.help
;
10954 const struct got_error
*err
;
10955 regmatch_t
*regmatch
= &view
->regmatch
;
10960 int width
, nprinted
= 0, rc
= 0;
10961 int eos
= view
->nlines
;
10963 if (view_is_hsplit_top(view
))
10964 --eos
; /* account for border */
10968 werase(view
->window
);
10970 if (view
->gline
> s
->nlines
- 1)
10971 view
->gline
= s
->nlines
- 1;
10973 err
= win_draw_center(view
->window
, 0, 0, view
->ncols
,
10974 view_needs_focus_indication(view
),
10975 "tog help (press q to return to tog)");
10980 waddstr(view
->window
, "\n\n");
10986 while (eos
> 0 && nprinted
< eos
) {
10989 linelen
= getline(&line
, &linesz
, s
->f
);
10990 if (linelen
== -1) {
10993 return got_ferror(s
->f
, GOT_ERR_IO
);
10998 if (++s
->lineno
< s
->first_displayed_line
)
11000 if (view
->gline
&& !gotoline(view
, &s
->lineno
, &nprinted
))
11002 if (s
->lineno
== view
->hiline
)
11005 err
= format_line(&wline
, &width
, NULL
, line
, 0, INT_MAX
, 0,
11011 view
->maxx
= MAX(view
->maxx
, width
);
11016 wattron(view
->window
, attr
);
11017 if (s
->first_displayed_line
+ nprinted
== s
->matched_line
&&
11018 regmatch
->rm_so
>= 0 && regmatch
->rm_so
< regmatch
->rm_eo
) {
11019 err
= add_matched_line(&width
, line
, view
->ncols
- 1, 0,
11020 view
->window
, view
->x
, regmatch
);
11028 err
= format_line(&wline
, &width
, &skip
, line
,
11029 view
->x
, view
->ncols
, 0, view
->x
? 1 : 0);
11034 waddwstr(view
->window
, &wline
[skip
]);
11038 if (s
->lineno
== view
->hiline
) {
11039 while (width
++ < view
->ncols
)
11040 waddch(view
->window
, ' ');
11042 if (width
< view
->ncols
)
11043 waddch(view
->window
, '\n');
11046 wattroff(view
->window
, attr
);
11047 if (++nprinted
== 1)
11048 s
->first_displayed_line
= s
->lineno
;
11052 s
->last_displayed_line
= s
->first_displayed_line
+ nprinted
- 1;
11054 s
->last_displayed_line
= s
->first_displayed_line
;
11059 rc
= waddnstr(view
->window
,
11060 "See the tog(1) manual page for full documentation",
11063 return got_error_msg(GOT_ERR_RANGE
, "waddnstr");
11065 wmove(view
->window
, view
->nlines
- 1, 0);
11066 wclrtoeol(view
->window
);
11067 wstandout(view
->window
);
11068 rc
= waddnstr(view
->window
, "scroll down for more...",
11071 return got_error_msg(GOT_ERR_RANGE
, "waddnstr");
11072 if (getcurx(view
->window
) < view
->ncols
- 6) {
11073 rc
= wprintw(view
->window
, "[%.0f%%]",
11074 100.00 * s
->last_displayed_line
/ s
->nlines
);
11076 return got_error_msg(GOT_ERR_IO
, "wprintw");
11078 wstandend(view
->window
);
11084 static const struct got_error
*
11085 input_help_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
11087 struct tog_help_view_state
*s
= &view
->state
.help
;
11088 const struct got_error
*err
= NULL
;
11094 eos
= nscroll
= view
->nlines
;
11095 if (view_is_hsplit_top(view
))
11096 --eos
; /* border */
11098 s
->lineno
= s
->first_displayed_line
- 1 + s
->selected_line
;
11107 horizontal_scroll_input(view
, ch
);
11111 s
->first_displayed_line
= 1;
11119 s
->first_displayed_line
= (s
->nlines
- eos
) + 3;
11124 if (s
->first_displayed_line
> 1)
11125 --s
->first_displayed_line
;
11136 if (s
->first_displayed_line
== 1) {
11140 while (--nscroll
> 0 && s
->first_displayed_line
> 1)
11141 s
->first_displayed_line
--;
11147 ++s
->first_displayed_line
;
11163 while (!s
->eof
&& --nscroll
> 0) {
11164 linelen
= getline(&line
, &linesz
, s
->f
);
11165 s
->first_displayed_line
++;
11166 if (linelen
== -1) {
11170 err
= got_ferror(s
->f
, GOT_ERR_IO
);
11184 static const struct got_error
*
11185 close_help_view(struct tog_view
*view
)
11187 struct tog_help_view_state
*s
= &view
->state
.help
;
11189 free(s
->line_offsets
);
11190 s
->line_offsets
= NULL
;
11191 if (fclose(s
->f
) == EOF
)
11192 return got_error_from_errno("fclose");
11197 static const struct got_error
*
11198 reset_help_view(struct tog_view
*view
)
11200 struct tog_help_view_state
*s
= &view
->state
.help
;
11203 if (s
->f
&& fclose(s
->f
) == EOF
)
11204 return got_error_from_errno("fclose");
11206 wclear(view
->window
);
11210 s
->first_displayed_line
= 1;
11211 s
->last_displayed_line
= view
->nlines
;
11212 s
->matched_line
= 0;
11214 return create_help(s
);
11217 static const struct got_error
*
11218 open_help_view(struct tog_view
*view
, struct tog_view
*parent
)
11220 const struct got_error
*err
= NULL
;
11221 struct tog_help_view_state
*s
= &view
->state
.help
;
11223 s
->type
= (enum tog_keymap_type
)parent
->type
;
11224 s
->first_displayed_line
= 1;
11225 s
->last_displayed_line
= view
->nlines
;
11226 s
->selected_line
= 1;
11228 view
->show
= show_help_view
;
11229 view
->input
= input_help_view
;
11230 view
->reset
= reset_help_view
;
11231 view
->close
= close_help_view
;
11232 view
->search_start
= search_start_help_view
;
11233 view
->search_setup
= search_setup_help_view
;
11234 view
->search_next
= search_next_view_match
;
11236 err
= create_help(s
);
11240 static const struct got_error
*
11241 view_dispatch_request(struct tog_view
**new_view
, struct tog_view
*view
,
11242 enum tog_view_type request
, int y
, int x
)
11244 const struct got_error
*err
= NULL
;
11249 case TOG_VIEW_DIFF
:
11250 if (view
->type
== TOG_VIEW_LOG
) {
11251 struct tog_log_view_state
*s
= &view
->state
.log
;
11253 err
= open_diff_view_for_commit(new_view
, y
, x
,
11254 s
->selected_entry
, view
, s
->repo
);
11256 return got_error_msg(GOT_ERR_NOT_IMPL
,
11257 "parent/child view pair not supported");
11259 case TOG_VIEW_BLAME
:
11260 if (view
->type
== TOG_VIEW_TREE
) {
11261 struct tog_tree_view_state
*s
= &view
->state
.tree
;
11263 err
= blame_tree_entry(new_view
, y
, x
,
11264 s
->selected_entry
, &s
->parents
, s
->commit_id
,
11267 return got_error_msg(GOT_ERR_NOT_IMPL
,
11268 "parent/child view pair not supported");
11271 if (view
->type
== TOG_VIEW_BLAME
)
11272 err
= log_annotated_line(new_view
, y
, x
,
11273 view
->state
.blame
.repo
, view
->state
.blame
.id_to_log
);
11274 else if (view
->type
== TOG_VIEW_TREE
)
11275 err
= log_selected_tree_entry(new_view
, y
, x
,
11276 &view
->state
.tree
);
11277 else if (view
->type
== TOG_VIEW_REF
)
11278 err
= log_ref_entry(new_view
, y
, x
,
11279 view
->state
.ref
.selected_entry
,
11280 view
->state
.ref
.repo
);
11282 return got_error_msg(GOT_ERR_NOT_IMPL
,
11283 "parent/child view pair not supported");
11285 case TOG_VIEW_TREE
:
11286 if (view
->type
== TOG_VIEW_LOG
)
11287 err
= browse_commit_tree(new_view
, y
, x
,
11288 view
->state
.log
.selected_entry
,
11289 view
->state
.log
.in_repo_path
,
11290 view
->state
.log
.head_ref_name
,
11291 view
->state
.log
.repo
);
11292 else if (view
->type
== TOG_VIEW_REF
)
11293 err
= browse_ref_tree(new_view
, y
, x
,
11294 view
->state
.ref
.selected_entry
,
11295 view
->state
.ref
.repo
);
11297 return got_error_msg(GOT_ERR_NOT_IMPL
,
11298 "parent/child view pair not supported");
11301 *new_view
= view_open(0, 0, y
, x
, TOG_VIEW_REF
);
11302 if (*new_view
== NULL
)
11303 return got_error_from_errno("view_open");
11304 if (view
->type
== TOG_VIEW_LOG
)
11305 err
= open_ref_view(*new_view
, view
->state
.log
.repo
);
11306 else if (view
->type
== TOG_VIEW_TREE
)
11307 err
= open_ref_view(*new_view
, view
->state
.tree
.repo
);
11309 err
= got_error_msg(GOT_ERR_NOT_IMPL
,
11310 "parent/child view pair not supported");
11312 view_close(*new_view
);
11314 case TOG_VIEW_HELP
:
11315 *new_view
= view_open(0, 0, 0, 0, TOG_VIEW_HELP
);
11316 if (*new_view
== NULL
)
11317 return got_error_from_errno("view_open");
11318 err
= open_help_view(*new_view
, view
);
11320 view_close(*new_view
);
11323 return got_error_msg(GOT_ERR_NOT_IMPL
, "invalid view");
11330 * If view was scrolled down to move the selected line into view when opening a
11331 * horizontal split, scroll back up when closing the split/toggling fullscreen.
11334 offset_selection_up(struct tog_view
*view
)
11336 switch (view
->type
) {
11337 case TOG_VIEW_BLAME
: {
11338 struct tog_blame_view_state
*s
= &view
->state
.blame
;
11339 if (s
->first_displayed_line
== 1) {
11340 s
->selected_line
= MAX(s
->selected_line
- view
->offset
,
11344 if (s
->first_displayed_line
> view
->offset
)
11345 s
->first_displayed_line
-= view
->offset
;
11347 s
->first_displayed_line
= 1;
11348 s
->selected_line
+= view
->offset
;
11352 log_scroll_up(&view
->state
.log
, view
->offset
);
11353 view
->state
.log
.selected
+= view
->offset
;
11356 ref_scroll_up(&view
->state
.ref
, view
->offset
);
11357 view
->state
.ref
.selected
+= view
->offset
;
11359 case TOG_VIEW_TREE
:
11360 tree_scroll_up(&view
->state
.tree
, view
->offset
);
11361 view
->state
.tree
.selected
+= view
->offset
;
11371 * If the selected line is in the section of screen covered by the bottom split,
11372 * scroll down offset lines to move it into view and index its new position.
11374 static const struct got_error
*
11375 offset_selection_down(struct tog_view
*view
)
11377 const struct got_error
*err
= NULL
;
11378 const struct got_error
*(*scrolld
)(struct tog_view
*, int);
11379 int *selected
= NULL
;
11380 int header
, offset
;
11382 switch (view
->type
) {
11383 case TOG_VIEW_BLAME
: {
11384 struct tog_blame_view_state
*s
= &view
->state
.blame
;
11387 if (s
->selected_line
> view
->nlines
- header
) {
11388 offset
= abs(view
->nlines
- s
->selected_line
- header
);
11389 s
->first_displayed_line
+= offset
;
11390 s
->selected_line
-= offset
;
11391 view
->offset
= offset
;
11395 case TOG_VIEW_LOG
: {
11396 struct tog_log_view_state
*s
= &view
->state
.log
;
11397 scrolld
= &log_scroll_down
;
11398 header
= view_is_parent_view(view
) ? 3 : 2;
11399 selected
= &s
->selected
;
11402 case TOG_VIEW_REF
: {
11403 struct tog_ref_view_state
*s
= &view
->state
.ref
;
11404 scrolld
= &ref_scroll_down
;
11406 selected
= &s
->selected
;
11409 case TOG_VIEW_TREE
: {
11410 struct tog_tree_view_state
*s
= &view
->state
.tree
;
11411 scrolld
= &tree_scroll_down
;
11413 selected
= &s
->selected
;
11423 if (selected
&& *selected
> view
->nlines
- header
) {
11424 offset
= abs(view
->nlines
- *selected
- header
);
11425 view
->offset
= offset
;
11426 if (scrolld
&& offset
) {
11427 err
= scrolld(view
, offset
);
11428 *selected
-= offset
;
11436 list_commands(FILE *fp
)
11440 fprintf(fp
, "commands:");
11441 for (i
= 0; i
< nitems(tog_commands
); i
++) {
11442 const struct tog_cmd
*cmd
= &tog_commands
[i
];
11443 fprintf(fp
, " %s", cmd
->name
);
11449 usage(int hflag
, int status
)
11451 FILE *fp
= (status
== 0) ? stdout
: stderr
;
11453 fprintf(fp
, "usage: %s [-hV] command [arg ...]\n",
11456 fprintf(fp
, "lazy usage: %s path\n", getprogname());
11463 make_argv(int argc
, ...)
11469 va_start(ap
, argc
);
11471 argv
= calloc(argc
, sizeof(char *));
11474 for (i
= 0; i
< argc
; i
++) {
11475 argv
[i
] = strdup(va_arg(ap
, char *));
11476 if (argv
[i
] == NULL
)
11485 * Try to convert 'tog path' into a 'tog log path' command.
11486 * The user could simply have mistyped the command rather than knowingly
11487 * provided a path. So check whether argv[0] can in fact be resolved
11488 * to a path in the HEAD commit and print a special error if not.
11489 * This hack is for mpi@ <3
11491 static const struct got_error
*
11492 tog_log_with_path(int argc
, char *argv
[])
11494 const struct got_error
*error
= NULL
, *close_err
;
11495 const struct tog_cmd
*cmd
= NULL
;
11496 struct got_repository
*repo
= NULL
;
11497 struct got_worktree
*worktree
= NULL
;
11498 struct got_object_id
*commit_id
= NULL
, *id
= NULL
;
11499 struct got_commit_object
*commit
= NULL
;
11500 char *cwd
= NULL
, *repo_path
= NULL
, *in_repo_path
= NULL
;
11501 char *commit_id_str
= NULL
, **cmd_argv
= NULL
;
11502 int *pack_fds
= NULL
;
11504 cwd
= getcwd(NULL
, 0);
11506 return got_error_from_errno("getcwd");
11508 error
= got_repo_pack_fds_open(&pack_fds
);
11512 error
= got_worktree_open(&worktree
, cwd
, NULL
);
11513 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
11517 repo_path
= strdup(got_worktree_get_repo_path(worktree
));
11519 repo_path
= strdup(cwd
);
11520 if (repo_path
== NULL
) {
11521 error
= got_error_from_errno("strdup");
11525 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
11529 error
= get_in_repo_path_from_argv0(&in_repo_path
, argc
, argv
,
11534 error
= tog_load_refs(repo
, 0);
11537 error
= got_repo_match_object_id(&commit_id
, NULL
, worktree
?
11538 got_worktree_get_head_ref_name(worktree
) : GOT_REF_HEAD
,
11539 GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
11544 got_worktree_close(worktree
);
11548 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
11552 error
= got_object_id_by_path(&id
, repo
, commit
, in_repo_path
);
11554 if (error
->code
!= GOT_ERR_NO_TREE_ENTRY
)
11556 fprintf(stderr
, "%s: '%s' is no known command or path\n",
11557 getprogname(), argv
[0]);
11562 error
= got_object_id_str(&commit_id_str
, commit_id
);
11566 cmd
= &tog_commands
[0]; /* log */
11568 cmd_argv
= make_argv(argc
, cmd
->name
, "-c", commit_id_str
, argv
[0]);
11569 error
= cmd
->cmd_main(argc
, cmd_argv
);
11572 close_err
= got_repo_close(repo
);
11577 got_object_commit_close(commit
);
11579 got_worktree_close(worktree
);
11581 const struct got_error
*pack_err
=
11582 got_repo_pack_fds_close(pack_fds
);
11587 free(commit_id_str
);
11591 free(in_repo_path
);
11594 for (i
= 0; i
< argc
; i
++)
11603 main(int argc
, char *argv
[])
11605 const struct got_error
*io_err
, *error
= NULL
;
11606 const struct tog_cmd
*cmd
= NULL
;
11607 int ch
, hflag
= 0, Vflag
= 0;
11608 char **cmd_argv
= NULL
;
11609 static const struct option longopts
[] = {
11610 { "version", no_argument
, NULL
, 'V' },
11611 { NULL
, 0, NULL
, 0}
11613 char *diff_algo_str
= NULL
;
11614 const char *test_script_path
;
11616 setlocale(LC_CTYPE
, "");
11619 * Override default signal handlers before starting ncurses.
11620 * This should prevent ncurses from installing its own
11621 * broken cleanup() signal handler.
11623 signal(SIGWINCH
, tog_sigwinch
);
11624 signal(SIGPIPE
, tog_sigpipe
);
11625 signal(SIGCONT
, tog_sigcont
);
11626 signal(SIGINT
, tog_sigint
);
11627 signal(SIGTERM
, tog_sigterm
);
11630 * Test mode init must happen before pledge() because "tty" will
11631 * not allow TTY-related ioctls to occur via regular files.
11633 test_script_path
= getenv("TOG_TEST_SCRIPT");
11634 if (test_script_path
!= NULL
) {
11635 error
= init_mock_term(test_script_path
);
11637 fprintf(stderr
, "%s: %s\n", getprogname(), error
->msg
);
11640 } else if (!isatty(STDIN_FILENO
))
11641 errx(1, "standard input is not a tty");
11643 #if !defined(PROFILE)
11644 if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd unveil",
11649 while ((ch
= getopt_long(argc
, argv
, "+hV", longopts
, NULL
)) != -1) {
11669 got_version_print_str();
11676 /* Build an argument vector which runs a default command. */
11677 cmd
= &tog_commands
[0];
11679 cmd_argv
= make_argv(argc
, cmd
->name
);
11683 /* Did the user specify a command? */
11684 for (i
= 0; i
< nitems(tog_commands
); i
++) {
11685 if (strncmp(tog_commands
[i
].name
, argv
[0],
11686 strlen(argv
[0])) == 0) {
11687 cmd
= &tog_commands
[i
];
11693 diff_algo_str
= getenv("TOG_DIFF_ALGORITHM");
11694 if (diff_algo_str
) {
11695 if (strcasecmp(diff_algo_str
, "patience") == 0)
11696 tog_diff_algo
= GOT_DIFF_ALGORITHM_PATIENCE
;
11697 if (strcasecmp(diff_algo_str
, "myers") == 0)
11698 tog_diff_algo
= GOT_DIFF_ALGORITHM_MYERS
;
11701 tog_base_commit
.idx
= -1;
11702 tog_base_commit
.marker
= GOT_WORKTREE_STATE_UNKNOWN
;
11707 /* No command specified; try log with a path */
11708 error
= tog_log_with_path(argc
, argv
);
11713 error
= cmd
->cmd_main(argc
, cmd_argv
? cmd_argv
: argv
);
11716 if (using_mock_io
) {
11717 io_err
= tog_io_close();
11724 for (i
= 0; i
< argc
; i
++)
11729 if (error
&& error
->code
!= GOT_ERR_CANCELLED
&&
11730 error
->code
!= GOT_ERR_EOF
&&
11731 error
->code
!= GOT_ERR_PRIVSEP_EXIT
&&
11732 error
->code
!= GOT_ERR_PRIVSEP_PIPE
&&
11733 !(error
->code
== GOT_ERR_ERRNO
&& errno
== EINTR
)) {
11734 fprintf(stderr
, "%s: %s\n", getprogname(), error
->msg
);