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>
25 #if defined(__FreeBSD__) || defined(__APPLE__)
26 #define _XOPEN_SOURCE_EXTENDED /* for ncurses wide-character functions */
47 #include "got_version.h"
48 #include "got_error.h"
49 #include "got_object.h"
50 #include "got_reference.h"
51 #include "got_repository.h"
53 #include "got_opentemp.h"
55 #include "got_cancel.h"
56 #include "got_commit_graph.h"
57 #include "got_blame.h"
58 #include "got_privsep.h"
60 #include "got_worktree.h"
63 #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b))
67 #define MAX(_a,_b) ((_a) > (_b) ? (_a) : (_b))
71 #define CTRL(x) ((x) & 0x1f)
75 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
80 const struct got_error
*(*cmd_main
)(int, char *[]);
81 void (*cmd_usage
)(void);
84 __dead
static void usage(int, int);
85 __dead
static void usage_log(void);
86 __dead
static void usage_diff(void);
87 __dead
static void usage_blame(void);
88 __dead
static void usage_tree(void);
89 __dead
static void usage_ref(void);
91 static const struct got_error
* cmd_log(int, char *[]);
92 static const struct got_error
* cmd_diff(int, char *[]);
93 static const struct got_error
* cmd_blame(int, char *[]);
94 static const struct got_error
* cmd_tree(int, char *[]);
95 static const struct got_error
* cmd_ref(int, char *[]);
97 static const struct tog_cmd tog_commands
[] = {
98 { "log", cmd_log
, usage_log
},
99 { "diff", cmd_diff
, usage_diff
},
100 { "blame", cmd_blame
, usage_blame
},
101 { "tree", cmd_tree
, usage_tree
},
102 { "ref", cmd_ref
, usage_ref
},
114 /* Match _DIFF to _HELP with enum tog_view_type TOG_VIEW_* counterparts. */
115 enum tog_keymap_type
{
116 TOG_KEYMAP_KEYS
= -2,
132 #define HSPLIT_SCALE 0.3f /* default horizontal split scale */
134 #define TOG_EOF_STRING "(END)"
136 struct commit_queue_entry
{
137 TAILQ_ENTRY(commit_queue_entry
) entry
;
138 struct got_object_id
*id
;
139 struct got_commit_object
*commit
;
142 TAILQ_HEAD(commit_queue_head
, commit_queue_entry
);
143 struct commit_queue
{
145 struct commit_queue_head head
;
149 STAILQ_ENTRY(tog_color
) entry
;
153 STAILQ_HEAD(tog_colors
, tog_color
);
155 static struct got_reflist_head tog_refs
= TAILQ_HEAD_INITIALIZER(tog_refs
);
156 static struct got_reflist_object_id_map
*tog_refs_idmap
;
157 static enum got_diff_algorithm tog_diff_algo
= GOT_DIFF_ALGORITHM_MYERS
;
159 static const struct got_error
*
160 tog_ref_cmp_by_name(void *arg
, int *cmp
, struct got_reference
*re1
,
161 struct got_reference
* re2
)
163 const char *name1
= got_ref_get_name(re1
);
164 const char *name2
= got_ref_get_name(re2
);
165 int isbackup1
, isbackup2
;
167 /* Sort backup refs towards the bottom of the list. */
168 isbackup1
= strncmp(name1
, "refs/got/backup/", 16) == 0;
169 isbackup2
= strncmp(name2
, "refs/got/backup/", 16) == 0;
170 if (!isbackup1
&& isbackup2
) {
173 } else if (isbackup1
&& !isbackup2
) {
178 *cmp
= got_path_cmp(name1
, name2
, strlen(name1
), strlen(name2
));
182 static const struct got_error
*
183 tog_load_refs(struct got_repository
*repo
, int sort_by_date
)
185 const struct got_error
*err
;
187 err
= got_ref_list(&tog_refs
, repo
, NULL
, sort_by_date
?
188 got_ref_cmp_by_commit_timestamp_descending
: tog_ref_cmp_by_name
,
193 return got_reflist_object_id_map_create(&tog_refs_idmap
, &tog_refs
,
200 if (tog_refs_idmap
) {
201 got_reflist_object_id_map_free(tog_refs_idmap
);
202 tog_refs_idmap
= NULL
;
204 got_ref_list_free(&tog_refs
);
207 static const struct got_error
*
208 add_color(struct tog_colors
*colors
, const char *pattern
,
209 int idx
, short color
)
211 const struct got_error
*err
= NULL
;
212 struct tog_color
*tc
;
215 if (idx
< 1 || idx
> COLOR_PAIRS
- 1)
218 init_pair(idx
, color
, -1);
220 tc
= calloc(1, sizeof(*tc
));
222 return got_error_from_errno("calloc");
223 regerr
= regcomp(&tc
->regex
, pattern
,
224 REG_EXTENDED
| REG_NOSUB
| REG_NEWLINE
);
226 static char regerr_msg
[512];
227 static char err_msg
[512];
228 regerror(regerr
, &tc
->regex
, regerr_msg
,
230 snprintf(err_msg
, sizeof(err_msg
), "regcomp: %s",
232 err
= got_error_msg(GOT_ERR_REGEX
, err_msg
);
237 STAILQ_INSERT_HEAD(colors
, tc
, entry
);
242 free_colors(struct tog_colors
*colors
)
244 struct tog_color
*tc
;
246 while (!STAILQ_EMPTY(colors
)) {
247 tc
= STAILQ_FIRST(colors
);
248 STAILQ_REMOVE_HEAD(colors
, entry
);
254 static struct tog_color
*
255 get_color(struct tog_colors
*colors
, int colorpair
)
257 struct tog_color
*tc
= NULL
;
259 STAILQ_FOREACH(tc
, colors
, entry
) {
260 if (tc
->colorpair
== colorpair
)
268 default_color_value(const char *envvar
)
270 if (strcmp(envvar
, "TOG_COLOR_DIFF_MINUS") == 0)
271 return COLOR_MAGENTA
;
272 if (strcmp(envvar
, "TOG_COLOR_DIFF_PLUS") == 0)
274 if (strcmp(envvar
, "TOG_COLOR_DIFF_CHUNK_HEADER") == 0)
276 if (strcmp(envvar
, "TOG_COLOR_DIFF_META") == 0)
278 if (strcmp(envvar
, "TOG_COLOR_TREE_SUBMODULE") == 0)
279 return COLOR_MAGENTA
;
280 if (strcmp(envvar
, "TOG_COLOR_TREE_SYMLINK") == 0)
281 return COLOR_MAGENTA
;
282 if (strcmp(envvar
, "TOG_COLOR_TREE_DIRECTORY") == 0)
284 if (strcmp(envvar
, "TOG_COLOR_TREE_EXECUTABLE") == 0)
286 if (strcmp(envvar
, "TOG_COLOR_COMMIT") == 0)
288 if (strcmp(envvar
, "TOG_COLOR_AUTHOR") == 0)
290 if (strcmp(envvar
, "TOG_COLOR_DATE") == 0)
292 if (strcmp(envvar
, "TOG_COLOR_REFS_HEADS") == 0)
294 if (strcmp(envvar
, "TOG_COLOR_REFS_TAGS") == 0)
295 return COLOR_MAGENTA
;
296 if (strcmp(envvar
, "TOG_COLOR_REFS_REMOTES") == 0)
298 if (strcmp(envvar
, "TOG_COLOR_REFS_BACKUP") == 0)
305 get_color_value(const char *envvar
)
307 const char *val
= getenv(envvar
);
310 return default_color_value(envvar
);
312 if (strcasecmp(val
, "black") == 0)
314 if (strcasecmp(val
, "red") == 0)
316 if (strcasecmp(val
, "green") == 0)
318 if (strcasecmp(val
, "yellow") == 0)
320 if (strcasecmp(val
, "blue") == 0)
322 if (strcasecmp(val
, "magenta") == 0)
323 return COLOR_MAGENTA
;
324 if (strcasecmp(val
, "cyan") == 0)
326 if (strcasecmp(val
, "white") == 0)
328 if (strcasecmp(val
, "default") == 0)
331 return default_color_value(envvar
);
334 struct tog_diff_view_state
{
335 struct got_object_id
*id1
, *id2
;
336 const char *label1
, *label2
;
340 int first_displayed_line
;
341 int last_displayed_line
;
344 int ignore_whitespace
;
346 struct got_repository
*repo
;
347 struct got_diff_line
*lines
;
352 /* passed from log or blame view; may be NULL */
353 struct tog_view
*parent_view
;
356 pthread_mutex_t tog_mutex
= PTHREAD_MUTEX_INITIALIZER
;
357 static volatile sig_atomic_t tog_thread_error
;
359 struct tog_log_thread_args
{
360 pthread_cond_t need_commits
;
361 pthread_cond_t commit_loaded
;
364 struct got_commit_graph
*graph
;
365 struct commit_queue
*real_commits
;
366 const char *in_repo_path
;
367 struct got_object_id
*start_id
;
368 struct got_repository
*repo
;
372 struct commit_queue_entry
**first_displayed_entry
;
373 struct commit_queue_entry
**selected_entry
;
375 int *search_next_done
;
379 regex_t
*limit_regex
;
380 struct commit_queue
*limit_commits
;
383 struct tog_log_view_state
{
384 struct commit_queue
*commits
;
385 struct commit_queue_entry
*first_displayed_entry
;
386 struct commit_queue_entry
*last_displayed_entry
;
387 struct commit_queue_entry
*selected_entry
;
388 struct commit_queue real_commits
;
393 struct got_repository
*repo
;
394 struct got_object_id
*start_id
;
397 struct tog_log_thread_args thread_args
;
398 struct commit_queue_entry
*matched_entry
;
399 struct commit_queue_entry
*search_entry
;
400 struct tog_colors colors
;
404 struct commit_queue limit_commits
;
407 #define TOG_COLOR_DIFF_MINUS 1
408 #define TOG_COLOR_DIFF_PLUS 2
409 #define TOG_COLOR_DIFF_CHUNK_HEADER 3
410 #define TOG_COLOR_DIFF_META 4
411 #define TOG_COLOR_TREE_SUBMODULE 5
412 #define TOG_COLOR_TREE_SYMLINK 6
413 #define TOG_COLOR_TREE_DIRECTORY 7
414 #define TOG_COLOR_TREE_EXECUTABLE 8
415 #define TOG_COLOR_COMMIT 9
416 #define TOG_COLOR_AUTHOR 10
417 #define TOG_COLOR_DATE 11
418 #define TOG_COLOR_REFS_HEADS 12
419 #define TOG_COLOR_REFS_TAGS 13
420 #define TOG_COLOR_REFS_REMOTES 14
421 #define TOG_COLOR_REFS_BACKUP 15
423 struct tog_blame_cb_args
{
424 struct tog_blame_line
*lines
; /* one per line */
427 struct tog_view
*view
;
428 struct got_object_id
*commit_id
;
432 struct tog_blame_thread_args
{
434 struct got_repository
*repo
;
435 struct tog_blame_cb_args
*cb_args
;
437 got_cancel_cb cancel_cb
;
439 pthread_cond_t blame_complete
;
445 struct tog_blame_line
*lines
;
449 struct tog_blame_thread_args thread_args
;
450 struct tog_blame_cb_args cb_args
;
455 struct tog_blame_view_state
{
456 int first_displayed_line
;
457 int last_displayed_line
;
459 int last_diffed_line
;
463 struct got_object_id_queue blamed_commits
;
464 struct got_object_qid
*blamed_commit
;
466 struct got_repository
*repo
;
467 struct got_object_id
*commit_id
;
468 struct got_object_id
*id_to_log
;
469 struct tog_blame blame
;
471 struct tog_colors colors
;
474 struct tog_parent_tree
{
475 TAILQ_ENTRY(tog_parent_tree
) entry
;
476 struct got_tree_object
*tree
;
477 struct got_tree_entry
*first_displayed_entry
;
478 struct got_tree_entry
*selected_entry
;
482 TAILQ_HEAD(tog_parent_trees
, tog_parent_tree
);
484 struct tog_tree_view_state
{
486 struct got_object_id
*commit_id
;/* commit which this tree belongs to */
487 struct got_tree_object
*root
; /* the commit's root tree entry */
488 struct got_tree_object
*tree
; /* currently displayed (sub-)tree */
489 struct got_tree_entry
*first_displayed_entry
;
490 struct got_tree_entry
*last_displayed_entry
;
491 struct got_tree_entry
*selected_entry
;
492 int ndisplayed
, selected
, show_ids
;
493 struct tog_parent_trees parents
; /* parent trees of current sub-tree */
495 struct got_repository
*repo
;
496 struct got_tree_entry
*matched_entry
;
497 struct tog_colors colors
;
500 struct tog_reflist_entry
{
501 TAILQ_ENTRY(tog_reflist_entry
) entry
;
502 struct got_reference
*ref
;
506 TAILQ_HEAD(tog_reflist_head
, tog_reflist_entry
);
508 struct tog_ref_view_state
{
509 struct tog_reflist_head refs
;
510 struct tog_reflist_entry
*first_displayed_entry
;
511 struct tog_reflist_entry
*last_displayed_entry
;
512 struct tog_reflist_entry
*selected_entry
;
513 int nrefs
, ndisplayed
, selected
, show_date
, show_ids
, sort_by_date
;
514 struct got_repository
*repo
;
515 struct tog_reflist_entry
*matched_entry
;
516 struct tog_colors colors
;
519 struct tog_help_view_state
{
524 int first_displayed_line
;
525 int last_displayed_line
;
530 enum tog_keymap_type type
;
533 #define GENERATE_HELP \
534 KEYMAP_("Global", TOG_KEYMAP_GLOBAL), \
535 KEY_("H F1", "Open view-specific help (double tap for all help)"), \
536 KEY_("k C-p Up", "Move cursor or page up one line"), \
537 KEY_("j C-n Down", "Move cursor or page down one line"), \
538 KEY_("C-b b PgUp", "Scroll the view up one page"), \
539 KEY_("C-f f PgDn Space", "Scroll the view down one page"), \
540 KEY_("C-u u", "Scroll the view up one half page"), \
541 KEY_("C-d d", "Scroll the view down one half page"), \
542 KEY_("g", "Go to line N (default: first line)"), \
543 KEY_("Home =", "Go to the first line"), \
544 KEY_("G", "Go to line N (default: last line)"), \
545 KEY_("End *", "Go to the last line"), \
546 KEY_("l Right", "Scroll the view right"), \
547 KEY_("h Left", "Scroll the view left"), \
548 KEY_("$", "Scroll view to the rightmost position"), \
549 KEY_("0", "Scroll view to the leftmost position"), \
550 KEY_("-", "Decrease size of the focussed split"), \
551 KEY_("+", "Increase size of the focussed split"), \
552 KEY_("Tab", "Switch focus between views"), \
553 KEY_("F", "Toggle fullscreen mode"), \
554 KEY_("S", "Switch split-screen layout"), \
555 KEY_("/", "Open prompt to enter search term"), \
556 KEY_("n", "Find next line/token matching the current search term"), \
557 KEY_("N", "Find previous line/token matching the current search term"),\
558 KEY_("q", "Quit the focussed view; Quit help screen"), \
559 KEY_("Q", "Quit tog"), \
561 KEYMAP_("Log view", TOG_KEYMAP_LOG), \
562 KEY_("< ,", "Move cursor up one commit"), \
563 KEY_("> .", "Move cursor down one commit"), \
564 KEY_("Enter", "Open diff view of the selected commit"), \
565 KEY_("B", "Reload the log view and toggle display of merged commits"), \
566 KEY_("R", "Open ref view of all repository references"), \
567 KEY_("T", "Display tree view of the repository from the selected" \
569 KEY_("@", "Toggle between displaying author and committer name"), \
570 KEY_("&", "Open prompt to enter term to limit commits displayed"), \
571 KEY_("C-g Backspace", "Cancel current search or log operation"), \
572 KEY_("C-l", "Reload the log view with new commits in the repository"), \
574 KEYMAP_("Diff view", TOG_KEYMAP_DIFF), \
575 KEY_("K < ,", "Display diff of next line in the file/log entry"), \
576 KEY_("J > .", "Display diff of previous line in the file/log entry"), \
577 KEY_("A", "Toggle between Myers and Patience diff algorithm"), \
578 KEY_("a", "Toggle treatment of file as ASCII irrespective of binary" \
580 KEY_("(", "Go to the previous file in the diff"), \
581 KEY_(")", "Go to the next file in the diff"), \
582 KEY_("{", "Go to the previous hunk in the diff"), \
583 KEY_("}", "Go to the next hunk in the diff"), \
584 KEY_("[", "Decrease the number of context lines"), \
585 KEY_("]", "Increase the number of context lines"), \
586 KEY_("w", "Toggle ignore whitespace-only changes in the diff"), \
588 KEYMAP_("Blame view", TOG_KEYMAP_BLAME), \
589 KEY_("Enter", "Display diff view of the selected line's commit"), \
590 KEY_("A", "Toggle diff algorithm between Myers and Patience"), \
591 KEY_("L", "Open log view for the currently selected annotated line"), \
592 KEY_("C", "Reload view with the previously blamed commit"), \
593 KEY_("c", "Reload view with the version of the file found in the" \
594 " selected line's commit"), \
595 KEY_("p", "Reload view with the version of the file found in the" \
596 " selected line's parent commit"), \
598 KEYMAP_("Tree view", TOG_KEYMAP_TREE), \
599 KEY_("Enter", "Enter selected directory or open blame view of the" \
601 KEY_("L", "Open log view for the selected entry"), \
602 KEY_("R", "Open ref view of all repository references"), \
603 KEY_("i", "Show object IDs for all tree entries"), \
604 KEY_("Backspace", "Return to the parent directory"), \
606 KEYMAP_("Ref view", TOG_KEYMAP_REF), \
607 KEY_("Enter", "Display log view of the selected reference"), \
608 KEY_("T", "Display tree view of the selected reference"), \
609 KEY_("i", "Toggle display of IDs for all non-symbolic references"), \
610 KEY_("m", "Toggle display of last modified date for each reference"), \
611 KEY_("o", "Toggle reference sort order (name -> timestamp)"), \
612 KEY_("C-l", "Reload view with all repository references")
617 enum tog_keymap_type type
;
620 /* curses io for tog regress */
628 static int using_mock_io
;
630 #define TOG_KEY_SCRDUMP SHRT_MIN
633 * We implement two types of views: parent views and child views.
635 * The 'Tab' key switches focus between a parent view and its child view.
636 * Child views are shown side-by-side to their parent view, provided
637 * there is enough screen estate.
639 * When a new view is opened from within a parent view, this new view
640 * becomes a child view of the parent view, replacing any existing child.
642 * When a new view is opened from within a child view, this new view
643 * becomes a parent view which will obscure the views below until the
644 * user quits the new parent view by typing 'q'.
646 * This list of views contains parent views only.
647 * Child views are only pointed to by their parent view.
649 TAILQ_HEAD(tog_view_list_head
, tog_view
);
652 TAILQ_ENTRY(tog_view
) entry
;
655 int nlines
, ncols
, begin_y
, begin_x
; /* based on split height/width */
656 int resized_y
, resized_x
; /* begin_y/x based on user resizing */
657 int maxx
, x
; /* max column and current start column */
658 int lines
, cols
; /* copies of LINES and COLS */
659 int nscrolled
, offset
; /* lines scrolled and hsplit line offset */
660 int gline
, hiline
; /* navigate to and highlight this nG line */
661 int ch
, count
; /* current keymap and count prefix */
662 int resized
; /* set when in a resize event */
663 int focussed
; /* Only set on one parent or child view at a time. */
665 struct tog_view
*parent
;
666 struct tog_view
*child
;
669 * This flag is initially set on parent views when a new child view
670 * is created. It gets toggled when the 'Tab' key switches focus
671 * between parent and child.
672 * The flag indicates whether focus should be passed on to our child
673 * view if this parent view gets picked for focus after another parent
674 * view was closed. This prevents child views from losing focus in such
679 enum tog_view_mode mode
;
680 /* type-specific state */
681 enum tog_view_type type
;
683 struct tog_diff_view_state diff
;
684 struct tog_log_view_state log
;
685 struct tog_blame_view_state blame
;
686 struct tog_tree_view_state tree
;
687 struct tog_ref_view_state ref
;
688 struct tog_help_view_state help
;
691 const struct got_error
*(*show
)(struct tog_view
*);
692 const struct got_error
*(*input
)(struct tog_view
**,
693 struct tog_view
*, int);
694 const struct got_error
*(*reset
)(struct tog_view
*);
695 const struct got_error
*(*resize
)(struct tog_view
*, int);
696 const struct got_error
*(*close
)(struct tog_view
*);
698 const struct got_error
*(*search_start
)(struct tog_view
*);
699 const struct got_error
*(*search_next
)(struct tog_view
*);
700 void (*search_setup
)(struct tog_view
*, FILE **, off_t
**, size_t *,
701 int **, int **, int **, int **);
704 #define TOG_SEARCH_FORWARD 1
705 #define TOG_SEARCH_BACKWARD 2
706 int search_next_done
;
707 #define TOG_SEARCH_HAVE_MORE 1
708 #define TOG_SEARCH_NO_MORE 2
709 #define TOG_SEARCH_HAVE_NONE 3
715 static const struct got_error
*open_diff_view(struct tog_view
*,
716 struct got_object_id
*, struct got_object_id
*,
717 const char *, const char *, int, int, int, struct tog_view
*,
718 struct got_repository
*);
719 static const struct got_error
*show_diff_view(struct tog_view
*);
720 static const struct got_error
*input_diff_view(struct tog_view
**,
721 struct tog_view
*, int);
722 static const struct got_error
*reset_diff_view(struct tog_view
*);
723 static const struct got_error
* close_diff_view(struct tog_view
*);
724 static const struct got_error
*search_start_diff_view(struct tog_view
*);
725 static void search_setup_diff_view(struct tog_view
*, FILE **, off_t
**,
726 size_t *, int **, int **, int **, int **);
727 static const struct got_error
*search_next_view_match(struct tog_view
*);
729 static const struct got_error
*open_log_view(struct tog_view
*,
730 struct got_object_id
*, struct got_repository
*,
731 const char *, const char *, int);
732 static const struct got_error
* show_log_view(struct tog_view
*);
733 static const struct got_error
*input_log_view(struct tog_view
**,
734 struct tog_view
*, int);
735 static const struct got_error
*resize_log_view(struct tog_view
*, int);
736 static const struct got_error
*close_log_view(struct tog_view
*);
737 static const struct got_error
*search_start_log_view(struct tog_view
*);
738 static const struct got_error
*search_next_log_view(struct tog_view
*);
740 static const struct got_error
*open_blame_view(struct tog_view
*, char *,
741 struct got_object_id
*, struct got_repository
*);
742 static const struct got_error
*show_blame_view(struct tog_view
*);
743 static const struct got_error
*input_blame_view(struct tog_view
**,
744 struct tog_view
*, int);
745 static const struct got_error
*reset_blame_view(struct tog_view
*);
746 static const struct got_error
*close_blame_view(struct tog_view
*);
747 static const struct got_error
*search_start_blame_view(struct tog_view
*);
748 static void search_setup_blame_view(struct tog_view
*, FILE **, off_t
**,
749 size_t *, int **, int **, int **, int **);
751 static const struct got_error
*open_tree_view(struct tog_view
*,
752 struct got_object_id
*, const char *, struct got_repository
*);
753 static const struct got_error
*show_tree_view(struct tog_view
*);
754 static const struct got_error
*input_tree_view(struct tog_view
**,
755 struct tog_view
*, int);
756 static const struct got_error
*close_tree_view(struct tog_view
*);
757 static const struct got_error
*search_start_tree_view(struct tog_view
*);
758 static const struct got_error
*search_next_tree_view(struct tog_view
*);
760 static const struct got_error
*open_ref_view(struct tog_view
*,
761 struct got_repository
*);
762 static const struct got_error
*show_ref_view(struct tog_view
*);
763 static const struct got_error
*input_ref_view(struct tog_view
**,
764 struct tog_view
*, int);
765 static const struct got_error
*close_ref_view(struct tog_view
*);
766 static const struct got_error
*search_start_ref_view(struct tog_view
*);
767 static const struct got_error
*search_next_ref_view(struct tog_view
*);
769 static const struct got_error
*open_help_view(struct tog_view
*,
771 static const struct got_error
*show_help_view(struct tog_view
*);
772 static const struct got_error
*input_help_view(struct tog_view
**,
773 struct tog_view
*, int);
774 static const struct got_error
*reset_help_view(struct tog_view
*);
775 static const struct got_error
* close_help_view(struct tog_view
*);
776 static const struct got_error
*search_start_help_view(struct tog_view
*);
777 static void search_setup_help_view(struct tog_view
*, FILE **, off_t
**,
778 size_t *, int **, int **, int **, int **);
780 static volatile sig_atomic_t tog_sigwinch_received
;
781 static volatile sig_atomic_t tog_sigpipe_received
;
782 static volatile sig_atomic_t tog_sigcont_received
;
783 static volatile sig_atomic_t tog_sigint_received
;
784 static volatile sig_atomic_t tog_sigterm_received
;
787 tog_sigwinch(int signo
)
789 tog_sigwinch_received
= 1;
793 tog_sigpipe(int signo
)
795 tog_sigpipe_received
= 1;
799 tog_sigcont(int signo
)
801 tog_sigcont_received
= 1;
805 tog_sigint(int signo
)
807 tog_sigint_received
= 1;
811 tog_sigterm(int signo
)
813 tog_sigterm_received
= 1;
817 tog_fatal_signal_received(void)
819 return (tog_sigpipe_received
||
820 tog_sigint_received
|| tog_sigterm_received
);
823 static const struct got_error
*
824 view_close(struct tog_view
*view
)
826 const struct got_error
*err
= NULL
, *child_err
= NULL
;
829 child_err
= view_close(view
->child
);
833 err
= view
->close(view
);
835 del_panel(view
->panel
);
837 delwin(view
->window
);
839 return err
? err
: child_err
;
842 static struct tog_view
*
843 view_open(int nlines
, int ncols
, int begin_y
, int begin_x
,
844 enum tog_view_type type
)
846 struct tog_view
*view
= calloc(1, sizeof(*view
));
854 view
->nlines
= nlines
? nlines
: LINES
- begin_y
;
855 view
->ncols
= ncols
? ncols
: COLS
- begin_x
;
856 view
->begin_y
= begin_y
;
857 view
->begin_x
= begin_x
;
858 view
->window
= newwin(nlines
, ncols
, begin_y
, begin_x
);
859 if (view
->window
== NULL
) {
863 view
->panel
= new_panel(view
->window
);
864 if (view
->panel
== NULL
||
865 set_panel_userptr(view
->panel
, view
) != OK
) {
870 keypad(view
->window
, TRUE
);
875 view_split_begin_x(int begin_x
)
877 if (begin_x
> 0 || COLS
< 120)
879 return (COLS
- MAX(COLS
/ 2, 80));
882 /* XXX Stub till we decide what to do. */
884 view_split_begin_y(int lines
)
886 return lines
* HSPLIT_SCALE
;
889 static const struct got_error
*view_resize(struct tog_view
*);
891 static const struct got_error
*
892 view_splitscreen(struct tog_view
*view
)
894 const struct got_error
*err
= NULL
;
896 if (!view
->resized
&& view
->mode
== TOG_VIEW_SPLIT_HRZN
) {
897 if (view
->resized_y
&& view
->resized_y
< view
->lines
)
898 view
->begin_y
= view
->resized_y
;
900 view
->begin_y
= view_split_begin_y(view
->nlines
);
902 } else if (!view
->resized
) {
903 if (view
->resized_x
&& view
->resized_x
< view
->cols
- 1 &&
905 view
->begin_x
= view
->resized_x
;
907 view
->begin_x
= view_split_begin_x(0);
910 view
->nlines
= LINES
- view
->begin_y
;
911 view
->ncols
= COLS
- view
->begin_x
;
914 err
= view_resize(view
);
918 if (view
->parent
&& view
->mode
== TOG_VIEW_SPLIT_HRZN
)
919 view
->parent
->nlines
= view
->begin_y
;
921 if (mvwin(view
->window
, view
->begin_y
, view
->begin_x
) == ERR
)
922 return got_error_from_errno("mvwin");
927 static const struct got_error
*
928 view_fullscreen(struct tog_view
*view
)
930 const struct got_error
*err
= NULL
;
933 view
->begin_y
= view
->resized
? view
->begin_y
: 0;
934 view
->nlines
= view
->resized
? view
->nlines
: LINES
;
938 err
= view_resize(view
);
942 if (mvwin(view
->window
, view
->begin_y
, view
->begin_x
) == ERR
)
943 return got_error_from_errno("mvwin");
949 view_is_parent_view(struct tog_view
*view
)
951 return view
->parent
== NULL
;
955 view_is_splitscreen(struct tog_view
*view
)
957 return view
->begin_x
> 0 || view
->begin_y
> 0;
961 view_is_fullscreen(struct tog_view
*view
)
963 return view
->nlines
== LINES
&& view
->ncols
== COLS
;
967 view_is_hsplit_top(struct tog_view
*view
)
969 return view
->mode
== TOG_VIEW_SPLIT_HRZN
&& view
->child
&&
970 view_is_splitscreen(view
->child
);
974 view_border(struct tog_view
*view
)
977 const struct tog_view
*view_above
;
980 return view_border(view
->parent
);
982 panel
= panel_above(view
->panel
);
986 view_above
= panel_userptr(panel
);
987 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
)
988 mvwhline(view
->window
, view_above
->begin_y
- 1,
989 view
->begin_x
, ACS_HLINE
, view
->ncols
);
991 mvwvline(view
->window
, view
->begin_y
, view_above
->begin_x
- 1,
992 ACS_VLINE
, view
->nlines
);
995 static const struct got_error
*view_init_hsplit(struct tog_view
*, int);
996 static const struct got_error
*request_log_commits(struct tog_view
*);
997 static const struct got_error
*offset_selection_down(struct tog_view
*);
998 static void offset_selection_up(struct tog_view
*);
999 static void view_get_split(struct tog_view
*, int *, int *);
1001 static const struct got_error
*
1002 view_resize(struct tog_view
*view
)
1004 const struct got_error
*err
= NULL
;
1005 int dif
, nlines
, ncols
;
1007 dif
= LINES
- view
->lines
; /* line difference */
1009 if (view
->lines
> LINES
)
1010 nlines
= view
->nlines
- (view
->lines
- LINES
);
1012 nlines
= view
->nlines
+ (LINES
- view
->lines
);
1013 if (view
->cols
> COLS
)
1014 ncols
= view
->ncols
- (view
->cols
- COLS
);
1016 ncols
= view
->ncols
+ (COLS
- view
->cols
);
1019 int hs
= view
->child
->begin_y
;
1021 if (!view_is_fullscreen(view
))
1022 view
->child
->begin_x
= view_split_begin_x(view
->begin_x
);
1023 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
||
1024 view
->child
->begin_x
== 0) {
1027 view_fullscreen(view
->child
);
1028 if (view
->child
->focussed
)
1029 show_panel(view
->child
->panel
);
1031 show_panel(view
->panel
);
1033 ncols
= view
->child
->begin_x
;
1035 view_splitscreen(view
->child
);
1036 show_panel(view
->child
->panel
);
1039 * XXX This is ugly and needs to be moved into the above
1040 * logic but "works" for now and my attempts at moving it
1041 * break either 'tab' or 'F' key maps in horizontal splits.
1044 err
= view_splitscreen(view
->child
);
1047 if (dif
< 0) { /* top split decreased */
1048 err
= offset_selection_down(view
);
1055 show_panel(view
->child
->panel
);
1056 nlines
= view
->nlines
;
1058 } else if (view
->parent
== NULL
)
1061 if (view
->resize
&& dif
> 0) {
1062 err
= view
->resize(view
, dif
);
1067 if (wresize(view
->window
, nlines
, ncols
) == ERR
)
1068 return got_error_from_errno("wresize");
1069 if (replace_panel(view
->panel
, view
->window
) == ERR
)
1070 return got_error_from_errno("replace_panel");
1071 wclear(view
->window
);
1073 view
->nlines
= nlines
;
1074 view
->ncols
= ncols
;
1075 view
->lines
= LINES
;
1081 static const struct got_error
*
1082 resize_log_view(struct tog_view
*view
, int increase
)
1084 struct tog_log_view_state
*s
= &view
->state
.log
;
1085 const struct got_error
*err
= NULL
;
1088 if (s
->selected_entry
)
1089 n
= s
->selected_entry
->idx
+ view
->lines
- s
->selected
;
1092 * Request commits to account for the increased
1093 * height so we have enough to populate the view.
1095 if (s
->commits
->ncommits
< n
) {
1096 view
->nscrolled
= n
- s
->commits
->ncommits
+ increase
+ 1;
1097 err
= request_log_commits(view
);
1104 view_adjust_offset(struct tog_view
*view
, int n
)
1109 if (view
->parent
&& view
->parent
->offset
) {
1110 if (view
->parent
->offset
+ n
>= 0)
1111 view
->parent
->offset
+= n
;
1113 view
->parent
->offset
= 0;
1114 } else if (view
->offset
) {
1115 if (view
->offset
- n
>= 0)
1122 static const struct got_error
*
1123 view_resize_split(struct tog_view
*view
, int resize
)
1125 const struct got_error
*err
= NULL
;
1126 struct tog_view
*v
= NULL
;
1133 if (!v
->child
|| !view_is_splitscreen(v
->child
))
1136 v
->resized
= v
->child
->resized
= resize
; /* lock for resize event */
1138 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
) {
1139 if (v
->child
->resized_y
)
1140 v
->child
->begin_y
= v
->child
->resized_y
;
1142 v
->child
->begin_y
-= resize
;
1144 v
->child
->begin_y
+= resize
;
1145 if (v
->child
->begin_y
< 3) {
1147 v
->child
->begin_y
= 3;
1148 } else if (v
->child
->begin_y
> LINES
- 1) {
1150 v
->child
->begin_y
= LINES
- 1;
1153 v
->child
->ncols
= COLS
;
1154 view_adjust_offset(view
, resize
);
1155 err
= view_init_hsplit(v
, v
->child
->begin_y
);
1158 v
->child
->resized_y
= v
->child
->begin_y
;
1160 if (v
->child
->resized_x
)
1161 v
->child
->begin_x
= v
->child
->resized_x
;
1163 v
->child
->begin_x
-= resize
;
1165 v
->child
->begin_x
+= resize
;
1166 if (v
->child
->begin_x
< 11) {
1168 v
->child
->begin_x
= 11;
1169 } else if (v
->child
->begin_x
> COLS
- 1) {
1171 v
->child
->begin_x
= COLS
- 1;
1173 v
->child
->resized_x
= v
->child
->begin_x
;
1176 v
->child
->mode
= v
->mode
;
1177 v
->child
->nlines
= v
->lines
- v
->child
->begin_y
;
1178 v
->child
->ncols
= v
->cols
- v
->child
->begin_x
;
1181 err
= view_fullscreen(v
);
1184 err
= view_splitscreen(v
->child
);
1188 if (v
->mode
== TOG_VIEW_SPLIT_HRZN
) {
1189 err
= offset_selection_down(v
->child
);
1195 err
= v
->resize(v
, 0);
1196 else if (v
->child
->resize
)
1197 err
= v
->child
->resize(v
->child
, 0);
1199 v
->resized
= v
->child
->resized
= 0;
1205 view_transfer_size(struct tog_view
*dst
, struct tog_view
*src
)
1207 struct tog_view
*v
= src
->child
? src
->child
: src
;
1209 dst
->resized_x
= v
->resized_x
;
1210 dst
->resized_y
= v
->resized_y
;
1213 static const struct got_error
*
1214 view_close_child(struct tog_view
*view
)
1216 const struct got_error
*err
= NULL
;
1218 if (view
->child
== NULL
)
1221 err
= view_close(view
->child
);
1226 static const struct got_error
*
1227 view_set_child(struct tog_view
*view
, struct tog_view
*child
)
1229 const struct got_error
*err
= NULL
;
1231 view
->child
= child
;
1232 child
->parent
= view
;
1234 err
= view_resize(view
);
1238 if (view
->child
->resized_x
|| view
->child
->resized_y
)
1239 err
= view_resize_split(view
, 0);
1244 static const struct got_error
*view_dispatch_request(struct tog_view
**,
1245 struct tog_view
*, enum tog_view_type
, int, int);
1247 static const struct got_error
*
1248 view_request_new(struct tog_view
**requested
, struct tog_view
*view
,
1249 enum tog_view_type request
)
1251 struct tog_view
*new_view
= NULL
;
1252 const struct got_error
*err
;
1257 if (view_is_parent_view(view
) && request
!= TOG_VIEW_HELP
)
1258 view_get_split(view
, &y
, &x
);
1260 err
= view_dispatch_request(&new_view
, view
, request
, y
, x
);
1264 if (view_is_parent_view(view
) && view
->mode
== TOG_VIEW_SPLIT_HRZN
&&
1265 request
!= TOG_VIEW_HELP
) {
1266 err
= view_init_hsplit(view
, y
);
1272 new_view
->focussed
= 1;
1273 new_view
->mode
= view
->mode
;
1274 new_view
->nlines
= request
== TOG_VIEW_HELP
?
1275 view
->lines
: view
->lines
- y
;
1277 if (view_is_parent_view(view
) && request
!= TOG_VIEW_HELP
) {
1278 view_transfer_size(new_view
, view
);
1279 err
= view_close_child(view
);
1282 err
= view_set_child(view
, new_view
);
1285 view
->focus_child
= 1;
1287 *requested
= new_view
;
1293 tog_resizeterm(void)
1296 struct winsize size
;
1298 if (ioctl(STDOUT_FILENO
, TIOCGWINSZ
, &size
) < 0) {
1299 cols
= 80; /* Default */
1303 lines
= size
.ws_row
;
1305 resize_term(lines
, cols
);
1308 static const struct got_error
*
1309 view_search_start(struct tog_view
*view
, int fast_refresh
)
1311 const struct got_error
*err
= NULL
;
1312 struct tog_view
*v
= view
;
1316 if (view
->search_started
) {
1317 regfree(&view
->regex
);
1318 view
->searching
= 0;
1319 memset(&view
->regmatch
, 0, sizeof(view
->regmatch
));
1321 view
->search_started
= 0;
1323 if (view
->nlines
< 1)
1326 if (view_is_hsplit_top(view
))
1328 else if (view
->mode
== TOG_VIEW_SPLIT_VERT
&& view
->parent
)
1331 mvwaddstr(v
->window
, v
->nlines
- 1, 0, "/");
1332 wclrtoeol(v
->window
);
1334 nodelay(v
->window
, FALSE
); /* block for search term input */
1337 ret
= wgetnstr(v
->window
, pattern
, sizeof(pattern
));
1338 wrefresh(v
->window
);
1341 nodelay(v
->window
, TRUE
);
1342 if (!fast_refresh
&& !using_mock_io
)
1347 if (regcomp(&view
->regex
, pattern
, REG_EXTENDED
| REG_NEWLINE
) == 0) {
1348 err
= view
->search_start(view
);
1350 regfree(&view
->regex
);
1353 view
->search_started
= 1;
1354 view
->searching
= TOG_SEARCH_FORWARD
;
1355 view
->search_next_done
= 0;
1356 view
->search_next(view
);
1362 /* Switch split mode. If view is a parent or child, draw the new splitscreen. */
1363 static const struct got_error
*
1364 switch_split(struct tog_view
*view
)
1366 const struct got_error
*err
= NULL
;
1367 struct tog_view
*v
= NULL
;
1374 if (v
->mode
== TOG_VIEW_SPLIT_HRZN
)
1375 v
->mode
= TOG_VIEW_SPLIT_VERT
;
1377 v
->mode
= TOG_VIEW_SPLIT_HRZN
;
1381 else if (v
->mode
== TOG_VIEW_SPLIT_VERT
&& v
->cols
< 120)
1382 v
->mode
= TOG_VIEW_SPLIT_NONE
;
1384 view_get_split(v
, &v
->child
->begin_y
, &v
->child
->begin_x
);
1385 if (v
->mode
== TOG_VIEW_SPLIT_HRZN
&& v
->child
->resized_y
)
1386 v
->child
->begin_y
= v
->child
->resized_y
;
1387 else if (v
->mode
== TOG_VIEW_SPLIT_VERT
&& v
->child
->resized_x
)
1388 v
->child
->begin_x
= v
->child
->resized_x
;
1391 if (v
->mode
== TOG_VIEW_SPLIT_HRZN
) {
1393 v
->child
->ncols
= COLS
;
1394 v
->child
->nscrolled
= LINES
- v
->child
->nlines
;
1396 err
= view_init_hsplit(v
, v
->child
->begin_y
);
1400 v
->child
->mode
= v
->mode
;
1401 v
->child
->nlines
= v
->lines
- v
->child
->begin_y
;
1404 err
= view_fullscreen(v
);
1407 err
= view_splitscreen(v
->child
);
1411 if (v
->mode
== TOG_VIEW_SPLIT_NONE
)
1412 v
->mode
= TOG_VIEW_SPLIT_VERT
;
1413 if (v
->mode
== TOG_VIEW_SPLIT_HRZN
) {
1414 err
= offset_selection_down(v
);
1417 err
= offset_selection_down(v
->child
);
1421 offset_selection_up(v
);
1422 offset_selection_up(v
->child
);
1425 err
= v
->resize(v
, 0);
1426 else if (v
->child
->resize
)
1427 err
= v
->child
->resize(v
->child
, 0);
1433 * Strip trailing whitespace from str starting at byte *n;
1434 * if *n < 0, use strlen(str). Return new str length in *n.
1437 strip_trailing_ws(char *str
, int *n
)
1441 if (str
== NULL
|| *str
== '\0')
1447 while (x
-- > 0 && isspace((unsigned char)str
[x
]))
1454 * Extract visible substring of line y from the curses screen
1455 * and strip trailing whitespace. If vline is set, overwrite
1456 * line[vline] with '|' because the ACS_VLINE character is
1457 * written out as 'x'. Write the line to file f.
1459 static const struct got_error
*
1460 view_write_line(FILE *f
, int y
, int vline
)
1462 char line
[COLS
* MB_LEN_MAX
]; /* allow for multibyte chars */
1465 r
= mvwinnstr(curscr
, y
, 0, line
, sizeof(line
));
1467 return got_error_fmt(GOT_ERR_RANGE
,
1468 "failed to extract line %d", y
);
1471 * In some views, lines are padded with blanks to COLS width.
1472 * Strip them so we can diff without the -b flag when testing.
1474 strip_trailing_ws(line
, &r
);
1479 w
= fprintf(f
, "%s\n", line
);
1480 if (w
!= r
+ 1) /* \n */
1481 return got_ferror(f
, GOT_ERR_IO
);
1487 * Capture the visible curses screen by writing each line to the
1488 * file at the path set via the TOG_SCR_DUMP environment variable.
1490 static const struct got_error
*
1491 screendump(struct tog_view
*view
)
1493 const struct got_error
*err
;
1496 err
= got_opentemp_truncate(tog_io
.sdump
);
1500 if ((view
->child
&& view
->child
->begin_x
) ||
1501 (view
->parent
&& view
->begin_x
)) {
1502 int ncols
= view
->child
? view
->ncols
: view
->parent
->ncols
;
1504 /* vertical splitscreen */
1505 for (i
= 0; i
< view
->nlines
; ++i
) {
1506 err
= view_write_line(tog_io
.sdump
, i
, ncols
- 1);
1513 /* fullscreen or horizontal splitscreen */
1514 if ((view
->child
&& view
->child
->begin_y
) ||
1515 (view
->parent
&& view
->begin_y
)) /* hsplit */
1516 hline
= view
->child
?
1517 view
->child
->begin_y
: view
->begin_y
;
1519 for (i
= 0; i
< view
->lines
; i
++) {
1520 if (hline
&& i
== hline
- 1) {
1523 /* ACS_HLINE writes out as 'q', overwrite it */
1524 for (c
= 0; c
< view
->cols
; ++c
)
1525 fputc('-', tog_io
.sdump
);
1526 fputc('\n', tog_io
.sdump
);
1530 err
= view_write_line(tog_io
.sdump
, i
, 0);
1541 * Compute view->count from numeric input. Assign total to view->count and
1542 * return first non-numeric key entered.
1545 get_compound_key(struct tog_view
*view
, int c
)
1547 struct tog_view
*v
= view
;
1550 if (view_is_hsplit_top(view
))
1552 else if (view
->mode
== TOG_VIEW_SPLIT_VERT
&& view
->parent
)
1556 cbreak(); /* block for input */
1557 nodelay(view
->window
, FALSE
);
1558 wmove(v
->window
, v
->nlines
- 1, 0);
1559 wclrtoeol(v
->window
);
1560 waddch(v
->window
, ':');
1563 x
= getcurx(v
->window
);
1564 if (x
!= ERR
&& x
< view
->ncols
) {
1565 waddch(v
->window
, c
);
1566 wrefresh(v
->window
);
1570 * Don't overflow. Max valid request should be the greatest
1571 * between the longest and total lines; cap at 10 million.
1576 n
= n
* 10 + (c
- '0');
1577 } while (((c
= wgetch(view
->window
))) >= '0' && c
<= '9' && c
!= ERR
);
1579 if (c
== 'G' || c
== 'g') { /* nG key map */
1580 view
->gline
= view
->hiline
= n
;
1585 /* Massage excessive or inapplicable values at the input handler. */
1592 action_report(struct tog_view
*view
)
1594 struct tog_view
*v
= view
;
1596 if (view_is_hsplit_top(view
))
1598 else if (view
->mode
== TOG_VIEW_SPLIT_VERT
&& view
->parent
)
1601 wmove(v
->window
, v
->nlines
- 1, 0);
1602 wclrtoeol(v
->window
);
1603 wprintw(v
->window
, ":%s", view
->action
);
1604 wrefresh(v
->window
);
1607 * Clear action status report. Only clear in blame view
1608 * once annotating is complete, otherwise it's too fast.
1610 if (view
->type
== TOG_VIEW_BLAME
) {
1611 if (view
->state
.blame
.blame_complete
)
1612 view
->action
= NULL
;
1614 view
->action
= NULL
;
1618 * Read the next line from the test script and assign
1619 * key instruction to *ch. If at EOF, set the *done flag.
1621 static const struct got_error
*
1622 tog_read_script_key(FILE *script
, struct tog_view
*view
, int *ch
, int *done
)
1624 const struct got_error
*err
= NULL
;
1628 if (view
->count
&& --view
->count
) {
1634 if (getline(&line
, &linesz
, script
) == -1) {
1639 err
= got_ferror(script
, GOT_ERR_IO
);
1644 if (strncasecmp(line
, "WAIT_FOR_UI", 11) == 0)
1645 tog_io
.wait_for_ui
= 1;
1646 else if (strncasecmp(line
, "KEY_ENTER", 9) == 0)
1648 else if (strncasecmp(line
, "KEY_RIGHT", 9) == 0)
1650 else if (strncasecmp(line
, "KEY_LEFT", 8) == 0)
1652 else if (strncasecmp(line
, "KEY_DOWN", 8) == 0)
1654 else if (strncasecmp(line
, "KEY_UP", 6) == 0)
1656 else if (strncasecmp(line
, "TAB", 3) == 0)
1658 else if (strncasecmp(line
, "SCREENDUMP", 10) == 0)
1659 *ch
= TOG_KEY_SCRDUMP
;
1660 else if (isdigit((unsigned char)*line
)) {
1663 while (isdigit((unsigned char)*t
))
1665 view
->ch
= *ch
= *t
;
1667 /* ignore error, view->count is 0 if instruction is invalid */
1668 view
->count
= strtonum(line
, 0, INT_MAX
, NULL
);
1677 static const struct got_error
*
1678 view_input(struct tog_view
**new, int *done
, struct tog_view
*view
,
1679 struct tog_view_list_head
*views
, int fast_refresh
)
1681 const struct got_error
*err
= NULL
;
1688 action_report(view
);
1690 /* Clear "no matches" indicator. */
1691 if (view
->search_next_done
== TOG_SEARCH_NO_MORE
||
1692 view
->search_next_done
== TOG_SEARCH_HAVE_NONE
) {
1693 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
1697 if (view
->searching
&& !view
->search_next_done
) {
1698 errcode
= pthread_mutex_unlock(&tog_mutex
);
1700 return got_error_set_errno(errcode
,
1701 "pthread_mutex_unlock");
1703 errcode
= pthread_mutex_lock(&tog_mutex
);
1705 return got_error_set_errno(errcode
,
1706 "pthread_mutex_lock");
1707 view
->search_next(view
);
1711 /* Allow threads to make progress while we are waiting for input. */
1712 errcode
= pthread_mutex_unlock(&tog_mutex
);
1714 return got_error_set_errno(errcode
, "pthread_mutex_unlock");
1716 if (using_mock_io
) {
1717 err
= tog_read_script_key(tog_io
.f
, view
, &ch
, done
);
1719 errcode
= pthread_mutex_lock(&tog_mutex
);
1722 } else if (view
->count
&& --view
->count
) {
1724 nodelay(view
->window
, TRUE
);
1725 ch
= wgetch(view
->window
);
1726 /* let C-g or backspace abort unfinished count */
1727 if (ch
== CTRL('g') || ch
== KEY_BACKSPACE
)
1732 ch
= wgetch(view
->window
);
1733 if (ch
>= '1' && ch
<= '9')
1734 view
->ch
= ch
= get_compound_key(view
, ch
);
1736 if (view
->hiline
&& ch
!= ERR
&& ch
!= 0)
1737 view
->hiline
= 0; /* key pressed, clear line highlight */
1738 nodelay(view
->window
, TRUE
);
1739 errcode
= pthread_mutex_lock(&tog_mutex
);
1741 return got_error_set_errno(errcode
, "pthread_mutex_lock");
1743 if (tog_sigwinch_received
|| tog_sigcont_received
) {
1745 tog_sigwinch_received
= 0;
1746 tog_sigcont_received
= 0;
1747 TAILQ_FOREACH(v
, views
, entry
) {
1748 err
= view_resize(v
);
1751 err
= v
->input(new, v
, KEY_RESIZE
);
1755 err
= view_resize(v
->child
);
1758 err
= v
->child
->input(new, v
->child
,
1762 if (v
->child
->resized_x
|| v
->child
->resized_y
) {
1763 err
= view_resize_split(v
, 0);
1775 if (view
->type
== TOG_VIEW_HELP
)
1776 err
= view
->reset(view
);
1778 err
= view_request_new(new, view
, TOG_VIEW_HELP
);
1784 view
->child
->focussed
= 1;
1785 view
->focus_child
= 1;
1786 } else if (view
->parent
) {
1788 view
->parent
->focussed
= 1;
1789 view
->parent
->focus_child
= 0;
1790 if (!view_is_splitscreen(view
)) {
1791 if (view
->parent
->resize
) {
1792 err
= view
->parent
->resize(view
->parent
,
1797 offset_selection_up(view
->parent
);
1798 err
= view_fullscreen(view
->parent
);
1805 if (view
->parent
&& view
->mode
== TOG_VIEW_SPLIT_HRZN
) {
1806 if (view
->parent
->resize
) {
1807 /* might need more commits to fill fullscreen */
1808 err
= view
->parent
->resize(view
->parent
, 0);
1812 offset_selection_up(view
->parent
);
1814 err
= view
->input(new, view
, ch
);
1822 if (view_is_parent_view(view
)) {
1823 if (view
->child
== NULL
)
1825 if (view_is_splitscreen(view
->child
)) {
1827 view
->child
->focussed
= 1;
1828 err
= view_fullscreen(view
->child
);
1830 err
= view_splitscreen(view
->child
);
1832 err
= view_resize_split(view
, 0);
1836 err
= view
->child
->input(new, view
->child
,
1839 if (view_is_splitscreen(view
)) {
1840 view
->parent
->focussed
= 0;
1842 err
= view_fullscreen(view
);
1844 err
= view_splitscreen(view
);
1845 if (!err
&& view
->mode
!= TOG_VIEW_SPLIT_HRZN
)
1846 err
= view_resize(view
->parent
);
1848 err
= view_resize_split(view
, 0);
1852 err
= view
->input(new, view
, KEY_RESIZE
);
1857 err
= view
->resize(view
, 0);
1862 if (view
->parent
->resize
) {
1863 err
= view
->parent
->resize(view
->parent
, 0);
1867 err
= offset_selection_down(view
->parent
);
1871 err
= offset_selection_down(view
);
1875 err
= switch_split(view
);
1878 err
= view_resize_split(view
, -1);
1881 err
= view_resize_split(view
, 1);
1887 if (view
->search_start
)
1888 view_search_start(view
, fast_refresh
);
1890 err
= view
->input(new, view
, ch
);
1894 if (view
->search_started
&& view
->search_next
) {
1895 view
->searching
= (ch
== 'n' ?
1896 TOG_SEARCH_FORWARD
: TOG_SEARCH_BACKWARD
);
1897 view
->search_next_done
= 0;
1898 view
->search_next(view
);
1900 err
= view
->input(new, view
, ch
);
1903 if (tog_diff_algo
== GOT_DIFF_ALGORITHM_MYERS
) {
1904 tog_diff_algo
= GOT_DIFF_ALGORITHM_PATIENCE
;
1905 view
->action
= "Patience diff algorithm";
1907 tog_diff_algo
= GOT_DIFF_ALGORITHM_MYERS
;
1908 view
->action
= "Myers diff algorithm";
1910 TAILQ_FOREACH(v
, views
, entry
) {
1916 if (v
->child
&& v
->child
->reset
) {
1917 err
= v
->child
->reset(v
->child
);
1923 case TOG_KEY_SCRDUMP
:
1924 err
= screendump(view
);
1927 err
= view
->input(new, view
, ch
);
1935 view_needs_focus_indication(struct tog_view
*view
)
1937 if (view_is_parent_view(view
)) {
1938 if (view
->child
== NULL
|| view
->child
->focussed
)
1940 if (!view_is_splitscreen(view
->child
))
1942 } else if (!view_is_splitscreen(view
))
1945 return view
->focussed
;
1948 static const struct got_error
*
1951 const struct got_error
*err
= NULL
;
1953 if (tog_io
.cin
&& fclose(tog_io
.cin
) == EOF
)
1954 err
= got_ferror(tog_io
.cin
, GOT_ERR_IO
);
1955 if (tog_io
.cout
&& fclose(tog_io
.cout
) == EOF
&& err
== NULL
)
1956 err
= got_ferror(tog_io
.cout
, GOT_ERR_IO
);
1957 if (tog_io
.f
&& fclose(tog_io
.f
) == EOF
&& err
== NULL
)
1958 err
= got_ferror(tog_io
.f
, GOT_ERR_IO
);
1959 if (tog_io
.sdump
&& fclose(tog_io
.sdump
) == EOF
&& err
== NULL
)
1960 err
= got_ferror(tog_io
.sdump
, GOT_ERR_IO
);
1965 static const struct got_error
*
1966 view_loop(struct tog_view
*view
)
1968 const struct got_error
*err
= NULL
;
1969 struct tog_view_list_head views
;
1970 struct tog_view
*new_view
;
1972 int fast_refresh
= 10;
1973 int done
= 0, errcode
;
1975 mode
= getenv("TOG_VIEW_SPLIT_MODE");
1976 if (!mode
|| !(*mode
== 'h' || *mode
== 'H'))
1977 view
->mode
= TOG_VIEW_SPLIT_VERT
;
1979 view
->mode
= TOG_VIEW_SPLIT_HRZN
;
1981 errcode
= pthread_mutex_lock(&tog_mutex
);
1983 return got_error_set_errno(errcode
, "pthread_mutex_lock");
1986 TAILQ_INSERT_HEAD(&views
, view
, entry
);
1989 err
= view
->show(view
);
1994 while (!TAILQ_EMPTY(&views
) && !done
&& !tog_thread_error
&&
1995 !tog_fatal_signal_received()) {
1996 /* Refresh fast during initialization, then become slower. */
1997 if (fast_refresh
&& --fast_refresh
== 0 && !using_mock_io
)
1998 halfdelay(10); /* switch to once per second */
2000 err
= view_input(&new_view
, &done
, view
, &views
, fast_refresh
);
2004 if (view
->dying
&& view
== TAILQ_FIRST(&views
) &&
2005 TAILQ_NEXT(view
, entry
) == NULL
)
2011 * When we quit, scroll the screen up a single line
2012 * so we don't lose any information.
2014 TAILQ_FOREACH(v
, &views
, entry
) {
2015 wmove(v
->window
, 0, 0);
2016 wdeleteln(v
->window
);
2017 wnoutrefresh(v
->window
);
2018 if (v
->child
&& !view_is_fullscreen(v
)) {
2019 wmove(v
->child
->window
, 0, 0);
2020 wdeleteln(v
->child
->window
);
2021 wnoutrefresh(v
->child
->window
);
2028 struct tog_view
*v
, *prev
= NULL
;
2030 if (view_is_parent_view(view
))
2031 prev
= TAILQ_PREV(view
, tog_view_list_head
,
2033 else if (view
->parent
)
2034 prev
= view
->parent
;
2037 view
->parent
->child
= NULL
;
2038 view
->parent
->focus_child
= 0;
2039 /* Restore fullscreen line height. */
2040 view
->parent
->nlines
= view
->parent
->lines
;
2041 err
= view_resize(view
->parent
);
2044 /* Make resized splits persist. */
2045 view_transfer_size(view
->parent
, view
);
2047 TAILQ_REMOVE(&views
, view
, entry
);
2049 err
= view_close(view
);
2054 TAILQ_FOREACH(v
, &views
, entry
) {
2058 if (view
== NULL
&& new_view
== NULL
) {
2059 /* No view has focus. Try to pick one. */
2062 else if (!TAILQ_EMPTY(&views
)) {
2063 view
= TAILQ_LAST(&views
,
2064 tog_view_list_head
);
2067 if (view
->focus_child
) {
2068 view
->child
->focussed
= 1;
2076 struct tog_view
*v
, *t
;
2077 /* Only allow one parent view per type. */
2078 TAILQ_FOREACH_SAFE(v
, &views
, entry
, t
) {
2079 if (v
->type
!= new_view
->type
)
2081 TAILQ_REMOVE(&views
, v
, entry
);
2082 err
= view_close(v
);
2087 TAILQ_INSERT_TAIL(&views
, new_view
, entry
);
2090 if (view
&& !done
) {
2091 if (view_is_parent_view(view
)) {
2092 if (view
->child
&& view
->child
->focussed
)
2095 if (view
->parent
&& view
->parent
->focussed
)
2096 view
= view
->parent
;
2098 show_panel(view
->panel
);
2099 if (view
->child
&& view_is_splitscreen(view
->child
))
2100 show_panel(view
->child
->panel
);
2101 if (view
->parent
&& view_is_splitscreen(view
)) {
2102 err
= view
->parent
->show(view
->parent
);
2106 err
= view
->show(view
);
2110 err
= view
->child
->show(view
->child
);
2119 while (!TAILQ_EMPTY(&views
)) {
2120 const struct got_error
*close_err
;
2121 view
= TAILQ_FIRST(&views
);
2122 TAILQ_REMOVE(&views
, view
, entry
);
2123 close_err
= view_close(view
);
2124 if (close_err
&& err
== NULL
)
2128 errcode
= pthread_mutex_unlock(&tog_mutex
);
2129 if (errcode
&& err
== NULL
)
2130 err
= got_error_set_errno(errcode
, "pthread_mutex_unlock");
2140 "usage: %s log [-b] [-c commit] [-r repository-path] [path]\n",
2145 /* Create newly allocated wide-character string equivalent to a byte string. */
2146 static const struct got_error
*
2147 mbs2ws(wchar_t **ws
, size_t *wlen
, const char *s
)
2150 const struct got_error
*err
= NULL
;
2153 *wlen
= mbstowcs(NULL
, s
, 0);
2154 if (*wlen
== (size_t)-1) {
2156 if (errno
!= EILSEQ
)
2157 return got_error_from_errno("mbstowcs");
2159 /* byte string invalid in current encoding; try to "fix" it */
2160 err
= got_mbsavis(&vis
, &vislen
, s
);
2163 *wlen
= mbstowcs(NULL
, vis
, 0);
2164 if (*wlen
== (size_t)-1) {
2165 err
= got_error_from_errno("mbstowcs"); /* give up */
2170 *ws
= calloc(*wlen
+ 1, sizeof(**ws
));
2172 err
= got_error_from_errno("calloc");
2176 if (mbstowcs(*ws
, vis
? vis
: s
, *wlen
) != *wlen
)
2177 err
= got_error_from_errno("mbstowcs");
2188 static const struct got_error
*
2189 expand_tab(char **ptr
, const char *src
)
2192 size_t len
, n
, idx
= 0, sz
= 0;
2195 n
= len
= strlen(src
);
2196 dst
= malloc(n
+ 1);
2198 return got_error_from_errno("malloc");
2200 while (idx
< len
&& src
[idx
]) {
2201 const char c
= src
[idx
];
2204 size_t nb
= TABSIZE
- sz
% TABSIZE
;
2207 p
= realloc(dst
, n
+ nb
);
2210 return got_error_from_errno("realloc");
2215 memset(dst
+ sz
, ' ', nb
);
2218 dst
[sz
++] = src
[idx
];
2228 * Advance at most n columns from wline starting at offset off.
2229 * Return the index to the first character after the span operation.
2230 * Return the combined column width of all spanned wide characters in
2234 span_wline(int *rcol
, int off
, wchar_t *wline
, int n
, int col_tab_align
)
2236 int width
, i
, cols
= 0;
2243 for (i
= off
; wline
[i
] != L
'\0'; ++i
) {
2244 if (wline
[i
] == L
'\t')
2245 width
= TABSIZE
- ((cols
+ col_tab_align
) % TABSIZE
);
2247 width
= wcwidth(wline
[i
]);
2254 if (cols
+ width
> n
)
2264 * Format a line for display, ensuring that it won't overflow a width limit.
2265 * With scrolling, the width returned refers to the scrolled version of the
2266 * line, which starts at (*wlinep)[*scrollxp]. The caller must free *wlinep.
2268 static const struct got_error
*
2269 format_line(wchar_t **wlinep
, int *widthp
, int *scrollxp
,
2270 const char *line
, int nscroll
, int wlimit
, int col_tab_align
, int expand
)
2272 const struct got_error
*err
= NULL
;
2274 wchar_t *wline
= NULL
;
2283 err
= expand_tab(&exstr
, line
);
2288 err
= mbs2ws(&wline
, &wlen
, expand
? exstr
: line
);
2293 scrollx
= span_wline(&cols
, 0, wline
, nscroll
, col_tab_align
);
2295 if (wlen
> 0 && wline
[wlen
- 1] == L
'\n') {
2296 wline
[wlen
- 1] = L
'\0';
2299 if (wlen
> 0 && wline
[wlen
- 1] == L
'\r') {
2300 wline
[wlen
- 1] = L
'\0';
2304 i
= span_wline(&cols
, scrollx
, wline
, wlimit
, col_tab_align
);
2310 *scrollxp
= scrollx
;
2318 static const struct got_error
*
2319 build_refs_str(char **refs_str
, struct got_reflist_head
*refs
,
2320 struct got_object_id
*id
, struct got_repository
*repo
)
2322 static const struct got_error
*err
= NULL
;
2323 struct got_reflist_entry
*re
;
2332 TAILQ_FOREACH(re
, refs
, entry
) {
2333 struct got_tag_object
*tag
= NULL
;
2334 struct got_object_id
*ref_id
;
2337 name
= got_ref_get_name(re
->ref
);
2338 if (strcmp(name
, GOT_REF_HEAD
) == 0)
2340 if (strncmp(name
, "refs/", 5) == 0)
2342 if (strncmp(name
, "got/", 4) == 0)
2344 if (strncmp(name
, "heads/", 6) == 0)
2346 if (strncmp(name
, "remotes/", 8) == 0) {
2348 s
= strstr(name
, "/" GOT_REF_HEAD
);
2349 if (s
!= NULL
&& strcmp(s
, "/" GOT_REF_HEAD
) == 0)
2352 err
= got_ref_resolve(&ref_id
, repo
, re
->ref
);
2355 if (strncmp(name
, "tags/", 5) == 0) {
2356 err
= got_object_open_as_tag(&tag
, repo
, ref_id
);
2358 if (err
->code
!= GOT_ERR_OBJ_TYPE
) {
2362 /* Ref points at something other than a tag. */
2367 cmp
= got_object_id_cmp(tag
?
2368 got_object_tag_get_object_id(tag
) : ref_id
, id
);
2371 got_object_tag_close(tag
);
2375 if (asprintf(refs_str
, "%s%s%s", s
? s
: "",
2376 s
? ", " : "", name
) == -1) {
2377 err
= got_error_from_errno("asprintf");
2388 static const struct got_error
*
2389 format_author(wchar_t **wauthor
, int *author_width
, char *author
, int limit
,
2394 smallerthan
= strchr(author
, '<');
2395 if (smallerthan
&& smallerthan
[1] != '\0')
2396 author
= smallerthan
+ 1;
2397 author
[strcspn(author
, "@>")] = '\0';
2398 return format_line(wauthor
, author_width
, NULL
, author
, 0, limit
,
2402 static const struct got_error
*
2403 draw_commit(struct tog_view
*view
, struct got_commit_object
*commit
,
2404 struct got_object_id
*id
, const size_t date_display_cols
,
2405 int author_display_cols
)
2407 struct tog_log_view_state
*s
= &view
->state
.log
;
2408 const struct got_error
*err
= NULL
;
2409 char datebuf
[12]; /* YYYY-MM-DD + SPACE + NUL */
2410 char *refs_str
= NULL
;
2411 char *logmsg0
= NULL
, *logmsg
= NULL
;
2412 char *author
= NULL
;
2413 wchar_t *wrefstr
= NULL
, *wlogmsg
= NULL
, *wauthor
= NULL
;
2414 int author_width
, refstr_width
, logmsg_width
;
2415 char *newline
, *line
= NULL
;
2416 int col
, limit
, scrollx
, logmsg_x
;
2417 const int avail
= view
->ncols
;
2419 time_t committer_time
;
2420 struct tog_color
*tc
;
2421 struct got_reflist_head
*refs
;
2423 committer_time
= got_object_commit_get_committer_time(commit
);
2424 if (gmtime_r(&committer_time
, &tm
) == NULL
)
2425 return got_error_from_errno("gmtime_r");
2426 if (strftime(datebuf
, sizeof(datebuf
), "%G-%m-%d ", &tm
) == 0)
2427 return got_error(GOT_ERR_NO_SPACE
);
2429 if (avail
<= date_display_cols
)
2430 limit
= MIN(sizeof(datebuf
) - 1, avail
);
2432 limit
= MIN(date_display_cols
, sizeof(datebuf
) - 1);
2433 tc
= get_color(&s
->colors
, TOG_COLOR_DATE
);
2435 wattr_on(view
->window
,
2436 COLOR_PAIR(tc
->colorpair
), NULL
);
2437 waddnstr(view
->window
, datebuf
, limit
);
2439 wattr_off(view
->window
,
2440 COLOR_PAIR(tc
->colorpair
), NULL
);
2447 err
= got_object_id_str(&id_str
, id
);
2450 tc
= get_color(&s
->colors
, TOG_COLOR_COMMIT
);
2452 wattr_on(view
->window
,
2453 COLOR_PAIR(tc
->colorpair
), NULL
);
2454 wprintw(view
->window
, "%.8s ", id_str
);
2456 wattr_off(view
->window
,
2457 COLOR_PAIR(tc
->colorpair
), NULL
);
2464 if (s
->use_committer
)
2465 author
= strdup(got_object_commit_get_committer(commit
));
2467 author
= strdup(got_object_commit_get_author(commit
));
2468 if (author
== NULL
) {
2469 err
= got_error_from_errno("strdup");
2472 err
= format_author(&wauthor
, &author_width
, author
, avail
- col
, col
);
2475 tc
= get_color(&s
->colors
, TOG_COLOR_AUTHOR
);
2477 wattr_on(view
->window
,
2478 COLOR_PAIR(tc
->colorpair
), NULL
);
2479 waddwstr(view
->window
, wauthor
);
2480 col
+= author_width
;
2481 while (col
< avail
&& author_width
< author_display_cols
+ 2) {
2482 waddch(view
->window
, ' ');
2487 wattr_off(view
->window
,
2488 COLOR_PAIR(tc
->colorpair
), NULL
);
2492 err
= got_object_commit_get_logmsg(&logmsg0
, commit
);
2496 while (*logmsg
== '\n')
2498 newline
= strchr(logmsg
, '\n');
2502 limit
= avail
- col
;
2503 if (view
->child
&& !view_is_hsplit_top(view
) && limit
> 0)
2504 limit
--; /* for the border */
2506 /* Prepend reference labels to log message if possible .*/
2507 refs
= got_reflist_object_id_map_lookup(tog_refs_idmap
, id
);
2508 err
= build_refs_str(&refs_str
, refs
, id
, s
->repo
);
2514 if (asprintf(&rs
, "[%s]", refs_str
) == -1) {
2515 err
= got_error_from_errno("asprintf");
2518 err
= format_line(&wrefstr
, &refstr_width
,
2519 &scrollx
, rs
, view
->x
, limit
, col
, 1);
2523 tc
= get_color(&s
->colors
, TOG_COLOR_COMMIT
);
2525 wattr_on(view
->window
,
2526 COLOR_PAIR(tc
->colorpair
), NULL
);
2527 waddwstr(view
->window
, &wrefstr
[scrollx
]);
2529 wattr_off(view
->window
,
2530 COLOR_PAIR(tc
->colorpair
), NULL
);
2531 col
+= MAX(refstr_width
, 0);
2536 waddch(view
->window
, ' ');
2540 if (refstr_width
> 0)
2543 int unscrolled_refstr_width
;
2544 size_t len
= wcslen(wrefstr
);
2547 * No need to check for -1 return value here since
2548 * unprintables have been replaced by span_wline().
2550 unscrolled_refstr_width
= wcswidth(wrefstr
, len
);
2551 unscrolled_refstr_width
+= 1; /* trailing space */
2552 logmsg_x
= view
->x
- unscrolled_refstr_width
;
2555 limit
= avail
- col
;
2556 if (view
->child
&& !view_is_hsplit_top(view
) && limit
> 0)
2557 limit
--; /* for the border */
2561 err
= format_line(&wlogmsg
, &logmsg_width
, &scrollx
, logmsg
, logmsg_x
,
2565 waddwstr(view
->window
, &wlogmsg
[scrollx
]);
2566 col
+= MAX(logmsg_width
, 0);
2567 while (col
< avail
) {
2568 waddch(view
->window
, ' ');
2582 static struct commit_queue_entry
*
2583 alloc_commit_queue_entry(struct got_commit_object
*commit
,
2584 struct got_object_id
*id
)
2586 struct commit_queue_entry
*entry
;
2587 struct got_object_id
*dup
;
2589 entry
= calloc(1, sizeof(*entry
));
2593 dup
= got_object_id_dup(id
);
2600 entry
->commit
= commit
;
2605 pop_commit(struct commit_queue
*commits
)
2607 struct commit_queue_entry
*entry
;
2609 entry
= TAILQ_FIRST(&commits
->head
);
2610 TAILQ_REMOVE(&commits
->head
, entry
, entry
);
2611 got_object_commit_close(entry
->commit
);
2612 commits
->ncommits
--;
2618 free_commits(struct commit_queue
*commits
)
2620 while (!TAILQ_EMPTY(&commits
->head
))
2621 pop_commit(commits
);
2624 static const struct got_error
*
2625 match_commit(int *have_match
, struct got_object_id
*id
,
2626 struct got_commit_object
*commit
, regex_t
*regex
)
2628 const struct got_error
*err
= NULL
;
2629 regmatch_t regmatch
;
2630 char *id_str
= NULL
, *logmsg
= NULL
;
2634 err
= got_object_id_str(&id_str
, id
);
2638 err
= got_object_commit_get_logmsg(&logmsg
, commit
);
2642 if (regexec(regex
, got_object_commit_get_author(commit
), 1,
2643 ®match
, 0) == 0 ||
2644 regexec(regex
, got_object_commit_get_committer(commit
), 1,
2645 ®match
, 0) == 0 ||
2646 regexec(regex
, id_str
, 1, ®match
, 0) == 0 ||
2647 regexec(regex
, logmsg
, 1, ®match
, 0) == 0)
2655 static const struct got_error
*
2656 queue_commits(struct tog_log_thread_args
*a
)
2658 const struct got_error
*err
= NULL
;
2661 * We keep all commits open throughout the lifetime of the log
2662 * view in order to avoid having to re-fetch commits from disk
2663 * while updating the display.
2666 struct got_object_id id
;
2667 struct got_commit_object
*commit
;
2668 struct commit_queue_entry
*entry
;
2669 int limit_match
= 0;
2672 err
= got_commit_graph_iter_next(&id
, a
->graph
, a
->repo
,
2677 err
= got_object_open_as_commit(&commit
, a
->repo
, &id
);
2680 entry
= alloc_commit_queue_entry(commit
, &id
);
2681 if (entry
== NULL
) {
2682 err
= got_error_from_errno("alloc_commit_queue_entry");
2686 errcode
= pthread_mutex_lock(&tog_mutex
);
2688 err
= got_error_set_errno(errcode
,
2689 "pthread_mutex_lock");
2693 entry
->idx
= a
->real_commits
->ncommits
;
2694 TAILQ_INSERT_TAIL(&a
->real_commits
->head
, entry
, entry
);
2695 a
->real_commits
->ncommits
++;
2698 err
= match_commit(&limit_match
, &id
, commit
,
2704 struct commit_queue_entry
*matched
;
2706 matched
= alloc_commit_queue_entry(
2707 entry
->commit
, entry
->id
);
2708 if (matched
== NULL
) {
2709 err
= got_error_from_errno(
2710 "alloc_commit_queue_entry");
2713 matched
->commit
= entry
->commit
;
2714 got_object_commit_retain(entry
->commit
);
2716 matched
->idx
= a
->limit_commits
->ncommits
;
2717 TAILQ_INSERT_TAIL(&a
->limit_commits
->head
,
2719 a
->limit_commits
->ncommits
++;
2723 * This is how we signal log_thread() that we
2724 * have found a match, and that it should be
2725 * counted as a new entry for the view.
2727 a
->limit_match
= limit_match
;
2730 if (*a
->searching
== TOG_SEARCH_FORWARD
&&
2731 !*a
->search_next_done
) {
2733 err
= match_commit(&have_match
, &id
, commit
, a
->regex
);
2738 if (limit_match
&& have_match
)
2739 *a
->search_next_done
=
2740 TOG_SEARCH_HAVE_MORE
;
2741 } else if (have_match
)
2742 *a
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
2745 errcode
= pthread_mutex_unlock(&tog_mutex
);
2746 if (errcode
&& err
== NULL
)
2747 err
= got_error_set_errno(errcode
,
2748 "pthread_mutex_unlock");
2751 } while (*a
->searching
== TOG_SEARCH_FORWARD
&& !*a
->search_next_done
);
2757 select_commit(struct tog_log_view_state
*s
)
2759 struct commit_queue_entry
*entry
;
2762 entry
= s
->first_displayed_entry
;
2764 if (ncommits
== s
->selected
) {
2765 s
->selected_entry
= entry
;
2768 entry
= TAILQ_NEXT(entry
, entry
);
2773 static const struct got_error
*
2774 draw_commits(struct tog_view
*view
)
2776 const struct got_error
*err
= NULL
;
2777 struct tog_log_view_state
*s
= &view
->state
.log
;
2778 struct commit_queue_entry
*entry
= s
->selected_entry
;
2779 int limit
= view
->nlines
;
2781 int ncommits
, author_cols
= 4, refstr_cols
;
2782 char *id_str
= NULL
, *header
= NULL
, *ncommits_str
= NULL
;
2783 char *refs_str
= NULL
;
2785 struct tog_color
*tc
;
2786 static const size_t date_display_cols
= 12;
2787 struct got_reflist_head
*refs
;
2789 if (view_is_hsplit_top(view
))
2790 --limit
; /* account for border */
2792 if (s
->selected_entry
&&
2793 !(view
->searching
&& view
->search_next_done
== 0)) {
2794 err
= got_object_id_str(&id_str
, s
->selected_entry
->id
);
2797 refs
= got_reflist_object_id_map_lookup(tog_refs_idmap
,
2798 s
->selected_entry
->id
);
2799 err
= build_refs_str(&refs_str
, refs
, s
->selected_entry
->id
,
2805 if (s
->thread_args
.commits_needed
== 0 && !using_mock_io
)
2806 halfdelay(10); /* disable fast refresh */
2808 if (s
->thread_args
.commits_needed
> 0 || s
->thread_args
.load_all
) {
2809 if (asprintf(&ncommits_str
, " [%d/%d] %s",
2810 entry
? entry
->idx
+ 1 : 0, s
->commits
->ncommits
,
2811 (view
->searching
&& !view
->search_next_done
) ?
2812 "searching..." : "loading...") == -1) {
2813 err
= got_error_from_errno("asprintf");
2817 const char *search_str
= NULL
;
2818 const char *limit_str
= NULL
;
2820 if (view
->searching
) {
2821 if (view
->search_next_done
== TOG_SEARCH_NO_MORE
)
2822 search_str
= "no more matches";
2823 else if (view
->search_next_done
== TOG_SEARCH_HAVE_NONE
)
2824 search_str
= "no matches found";
2825 else if (!view
->search_next_done
)
2826 search_str
= "searching...";
2829 if (s
->limit_view
&& s
->commits
->ncommits
== 0)
2830 limit_str
= "no matches found";
2832 if (asprintf(&ncommits_str
, " [%d/%d] %s %s",
2833 entry
? entry
->idx
+ 1 : 0, s
->commits
->ncommits
,
2834 search_str
? search_str
: (refs_str
? refs_str
: ""),
2835 limit_str
? limit_str
: "") == -1) {
2836 err
= got_error_from_errno("asprintf");
2844 if (s
->in_repo_path
&& strcmp(s
->in_repo_path
, "/") != 0) {
2845 if (asprintf(&header
, "commit %s %s%s", id_str
? id_str
:
2846 "........................................",
2847 s
->in_repo_path
, ncommits_str
) == -1) {
2848 err
= got_error_from_errno("asprintf");
2852 } else if (asprintf(&header
, "commit %s%s",
2853 id_str
? id_str
: "........................................",
2854 ncommits_str
) == -1) {
2855 err
= got_error_from_errno("asprintf");
2859 err
= format_line(&wline
, &width
, NULL
, header
, 0, view
->ncols
, 0, 0);
2863 werase(view
->window
);
2865 if (view_needs_focus_indication(view
))
2866 wstandout(view
->window
);
2867 tc
= get_color(&s
->colors
, TOG_COLOR_COMMIT
);
2869 wattr_on(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2870 waddwstr(view
->window
, wline
);
2871 while (width
< view
->ncols
) {
2872 waddch(view
->window
, ' ');
2876 wattr_off(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2877 if (view_needs_focus_indication(view
))
2878 wstandend(view
->window
);
2883 /* Grow author column size if necessary, and set view->maxx. */
2884 entry
= s
->first_displayed_entry
;
2888 struct got_commit_object
*c
= entry
->commit
;
2889 char *author
, *eol
, *msg
, *msg0
;
2890 wchar_t *wauthor
, *wmsg
;
2892 if (ncommits
>= limit
- 1)
2894 if (s
->use_committer
)
2895 author
= strdup(got_object_commit_get_committer(c
));
2897 author
= strdup(got_object_commit_get_author(c
));
2898 if (author
== NULL
) {
2899 err
= got_error_from_errno("strdup");
2902 err
= format_author(&wauthor
, &width
, author
, COLS
,
2904 if (author_cols
< width
)
2905 author_cols
= width
;
2910 refs
= got_reflist_object_id_map_lookup(tog_refs_idmap
,
2912 err
= build_refs_str(&refs_str
, refs
, entry
->id
, s
->repo
);
2917 err
= format_line(&ws
, &width
, NULL
, refs_str
,
2918 0, INT_MAX
, date_display_cols
+ author_cols
, 0);
2924 refstr_cols
= width
+ 3; /* account for [ ] + space */
2927 err
= got_object_commit_get_logmsg(&msg0
, c
);
2931 while (*msg
== '\n')
2933 if ((eol
= strchr(msg
, '\n')))
2935 err
= format_line(&wmsg
, &width
, NULL
, msg
, 0, INT_MAX
,
2936 date_display_cols
+ author_cols
+ refstr_cols
, 0);
2939 view
->maxx
= MAX(view
->maxx
, width
+ refstr_cols
);
2943 entry
= TAILQ_NEXT(entry
, entry
);
2946 entry
= s
->first_displayed_entry
;
2947 s
->last_displayed_entry
= s
->first_displayed_entry
;
2950 if (ncommits
>= limit
- 1)
2952 if (ncommits
== s
->selected
)
2953 wstandout(view
->window
);
2954 err
= draw_commit(view
, entry
->commit
, entry
->id
,
2955 date_display_cols
, author_cols
);
2956 if (ncommits
== s
->selected
)
2957 wstandend(view
->window
);
2961 s
->last_displayed_entry
= entry
;
2962 entry
= TAILQ_NEXT(entry
, entry
);
2975 log_scroll_up(struct tog_log_view_state
*s
, int maxscroll
)
2977 struct commit_queue_entry
*entry
;
2980 entry
= TAILQ_FIRST(&s
->commits
->head
);
2981 if (s
->first_displayed_entry
== entry
)
2984 entry
= s
->first_displayed_entry
;
2985 while (entry
&& nscrolled
< maxscroll
) {
2986 entry
= TAILQ_PREV(entry
, commit_queue_head
, entry
);
2988 s
->first_displayed_entry
= entry
;
2994 static const struct got_error
*
2995 trigger_log_thread(struct tog_view
*view
, int wait
)
2997 struct tog_log_thread_args
*ta
= &view
->state
.log
.thread_args
;
3001 halfdelay(1); /* fast refresh while loading commits */
3003 while (!ta
->log_complete
&& !tog_thread_error
&&
3004 (ta
->commits_needed
> 0 || ta
->load_all
)) {
3005 /* Wake the log thread. */
3006 errcode
= pthread_cond_signal(&ta
->need_commits
);
3008 return got_error_set_errno(errcode
,
3009 "pthread_cond_signal");
3012 * The mutex will be released while the view loop waits
3013 * in wgetch(), at which time the log thread will run.
3018 /* Display progress update in log view. */
3019 show_log_view(view
);
3023 /* Wait right here while next commit is being loaded. */
3024 errcode
= pthread_cond_wait(&ta
->commit_loaded
, &tog_mutex
);
3026 return got_error_set_errno(errcode
,
3027 "pthread_cond_wait");
3029 /* Display progress update in log view. */
3030 show_log_view(view
);
3038 static const struct got_error
*
3039 request_log_commits(struct tog_view
*view
)
3041 struct tog_log_view_state
*state
= &view
->state
.log
;
3042 const struct got_error
*err
= NULL
;
3044 if (state
->thread_args
.log_complete
)
3047 state
->thread_args
.commits_needed
+= view
->nscrolled
;
3048 err
= trigger_log_thread(view
, 1);
3049 view
->nscrolled
= 0;
3054 static const struct got_error
*
3055 log_scroll_down(struct tog_view
*view
, int maxscroll
)
3057 struct tog_log_view_state
*s
= &view
->state
.log
;
3058 const struct got_error
*err
= NULL
;
3059 struct commit_queue_entry
*pentry
;
3060 int nscrolled
= 0, ncommits_needed
;
3062 if (s
->last_displayed_entry
== NULL
)
3065 ncommits_needed
= s
->last_displayed_entry
->idx
+ 1 + maxscroll
;
3066 if (s
->commits
->ncommits
< ncommits_needed
&&
3067 !s
->thread_args
.log_complete
) {
3069 * Ask the log thread for required amount of commits.
3071 s
->thread_args
.commits_needed
+=
3072 ncommits_needed
- s
->commits
->ncommits
;
3073 err
= trigger_log_thread(view
, 1);
3079 pentry
= TAILQ_NEXT(s
->last_displayed_entry
, entry
);
3080 if (pentry
== NULL
&& view
->mode
!= TOG_VIEW_SPLIT_HRZN
)
3083 s
->last_displayed_entry
= pentry
?
3084 pentry
: s
->last_displayed_entry
;
3086 pentry
= TAILQ_NEXT(s
->first_displayed_entry
, entry
);
3089 s
->first_displayed_entry
= pentry
;
3090 } while (++nscrolled
< maxscroll
);
3092 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
&& !s
->thread_args
.log_complete
)
3093 view
->nscrolled
+= nscrolled
;
3095 view
->nscrolled
= 0;
3100 static const struct got_error
*
3101 open_diff_view_for_commit(struct tog_view
**new_view
, int begin_y
, int begin_x
,
3102 struct got_commit_object
*commit
, struct got_object_id
*commit_id
,
3103 struct tog_view
*log_view
, struct got_repository
*repo
)
3105 const struct got_error
*err
;
3106 struct got_object_qid
*parent_id
;
3107 struct tog_view
*diff_view
;
3109 diff_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_DIFF
);
3110 if (diff_view
== NULL
)
3111 return got_error_from_errno("view_open");
3113 parent_id
= STAILQ_FIRST(got_object_commit_get_parent_ids(commit
));
3114 err
= open_diff_view(diff_view
, parent_id
? &parent_id
->id
: NULL
,
3115 commit_id
, NULL
, NULL
, 3, 0, 0, log_view
, repo
);
3117 *new_view
= diff_view
;
3121 static const struct got_error
*
3122 tree_view_visit_subtree(struct tog_tree_view_state
*s
,
3123 struct got_tree_object
*subtree
)
3125 struct tog_parent_tree
*parent
;
3127 parent
= calloc(1, sizeof(*parent
));
3129 return got_error_from_errno("calloc");
3131 parent
->tree
= s
->tree
;
3132 parent
->first_displayed_entry
= s
->first_displayed_entry
;
3133 parent
->selected_entry
= s
->selected_entry
;
3134 parent
->selected
= s
->selected
;
3135 TAILQ_INSERT_HEAD(&s
->parents
, parent
, entry
);
3138 s
->first_displayed_entry
= NULL
;
3142 static const struct got_error
*
3143 tree_view_walk_path(struct tog_tree_view_state
*s
,
3144 struct got_commit_object
*commit
, const char *path
)
3146 const struct got_error
*err
= NULL
;
3147 struct got_tree_object
*tree
= NULL
;
3149 char *slash
, *subpath
= NULL
;
3151 /* Walk the path and open corresponding tree objects. */
3154 struct got_tree_entry
*te
;
3155 struct got_object_id
*tree_id
;
3161 /* Ensure the correct subtree entry is selected. */
3162 slash
= strchr(p
, '/');
3164 te_name
= strdup(p
);
3166 te_name
= strndup(p
, slash
- p
);
3167 if (te_name
== NULL
) {
3168 err
= got_error_from_errno("strndup");
3171 te
= got_object_tree_find_entry(s
->tree
, te_name
);
3173 err
= got_error_path(te_name
, GOT_ERR_NO_TREE_ENTRY
);
3178 s
->first_displayed_entry
= s
->selected_entry
= te
;
3180 if (!S_ISDIR(got_tree_entry_get_mode(s
->selected_entry
)))
3181 break; /* jump to this file's entry */
3183 slash
= strchr(p
, '/');
3185 subpath
= strndup(path
, slash
- path
);
3187 subpath
= strdup(path
);
3188 if (subpath
== NULL
) {
3189 err
= got_error_from_errno("strdup");
3193 err
= got_object_id_by_path(&tree_id
, s
->repo
, commit
,
3198 err
= got_object_open_as_tree(&tree
, s
->repo
, tree_id
);
3203 err
= tree_view_visit_subtree(s
, tree
);
3205 got_object_tree_close(tree
);
3219 static const struct got_error
*
3220 browse_commit_tree(struct tog_view
**new_view
, int begin_y
, int begin_x
,
3221 struct commit_queue_entry
*entry
, const char *path
,
3222 const char *head_ref_name
, struct got_repository
*repo
)
3224 const struct got_error
*err
= NULL
;
3225 struct tog_tree_view_state
*s
;
3226 struct tog_view
*tree_view
;
3228 tree_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_TREE
);
3229 if (tree_view
== NULL
)
3230 return got_error_from_errno("view_open");
3232 err
= open_tree_view(tree_view
, entry
->id
, head_ref_name
, repo
);
3235 s
= &tree_view
->state
.tree
;
3237 *new_view
= tree_view
;
3239 if (got_path_is_root_dir(path
))
3242 return tree_view_walk_path(s
, entry
->commit
, path
);
3245 static const struct got_error
*
3246 block_signals_used_by_main_thread(void)
3251 if (sigemptyset(&sigset
) == -1)
3252 return got_error_from_errno("sigemptyset");
3254 /* tog handles SIGWINCH, SIGCONT, SIGINT, SIGTERM */
3255 if (sigaddset(&sigset
, SIGWINCH
) == -1)
3256 return got_error_from_errno("sigaddset");
3257 if (sigaddset(&sigset
, SIGCONT
) == -1)
3258 return got_error_from_errno("sigaddset");
3259 if (sigaddset(&sigset
, SIGINT
) == -1)
3260 return got_error_from_errno("sigaddset");
3261 if (sigaddset(&sigset
, SIGTERM
) == -1)
3262 return got_error_from_errno("sigaddset");
3264 /* ncurses handles SIGTSTP */
3265 if (sigaddset(&sigset
, SIGTSTP
) == -1)
3266 return got_error_from_errno("sigaddset");
3268 errcode
= pthread_sigmask(SIG_BLOCK
, &sigset
, NULL
);
3270 return got_error_set_errno(errcode
, "pthread_sigmask");
3276 log_thread(void *arg
)
3278 const struct got_error
*err
= NULL
;
3280 struct tog_log_thread_args
*a
= arg
;
3284 * Sync startup with main thread such that we begin our
3285 * work once view_input() has released the mutex.
3287 errcode
= pthread_mutex_lock(&tog_mutex
);
3289 err
= got_error_set_errno(errcode
, "pthread_mutex_lock");
3293 err
= block_signals_used_by_main_thread();
3295 pthread_mutex_unlock(&tog_mutex
);
3299 while (!done
&& !err
&& !tog_fatal_signal_received()) {
3300 errcode
= pthread_mutex_unlock(&tog_mutex
);
3302 err
= got_error_set_errno(errcode
,
3303 "pthread_mutex_unlock");
3306 err
= queue_commits(a
);
3308 if (err
->code
!= GOT_ERR_ITER_COMPLETED
)
3312 } else if (a
->commits_needed
> 0 && !a
->load_all
) {
3315 a
->commits_needed
--;
3317 a
->commits_needed
--;
3320 errcode
= pthread_mutex_lock(&tog_mutex
);
3322 err
= got_error_set_errno(errcode
,
3323 "pthread_mutex_lock");
3325 } else if (*a
->quit
)
3327 else if (*a
->limiting
&& *a
->first_displayed_entry
== NULL
) {
3328 *a
->first_displayed_entry
=
3329 TAILQ_FIRST(&a
->limit_commits
->head
);
3330 *a
->selected_entry
= *a
->first_displayed_entry
;
3331 } else if (*a
->first_displayed_entry
== NULL
) {
3332 *a
->first_displayed_entry
=
3333 TAILQ_FIRST(&a
->real_commits
->head
);
3334 *a
->selected_entry
= *a
->first_displayed_entry
;
3337 errcode
= pthread_cond_signal(&a
->commit_loaded
);
3339 err
= got_error_set_errno(errcode
,
3340 "pthread_cond_signal");
3341 pthread_mutex_unlock(&tog_mutex
);
3346 a
->commits_needed
= 0;
3348 if (a
->commits_needed
== 0 && !a
->load_all
) {
3349 errcode
= pthread_cond_wait(&a
->need_commits
,
3352 err
= got_error_set_errno(errcode
,
3353 "pthread_cond_wait");
3354 pthread_mutex_unlock(&tog_mutex
);
3362 a
->log_complete
= 1;
3363 errcode
= pthread_mutex_unlock(&tog_mutex
);
3365 err
= got_error_set_errno(errcode
, "pthread_mutex_unlock");
3368 tog_thread_error
= 1;
3369 pthread_cond_signal(&a
->commit_loaded
);
3374 static const struct got_error
*
3375 stop_log_thread(struct tog_log_view_state
*s
)
3377 const struct got_error
*err
= NULL
, *thread_err
= NULL
;
3382 errcode
= pthread_cond_signal(&s
->thread_args
.need_commits
);
3384 return got_error_set_errno(errcode
,
3385 "pthread_cond_signal");
3386 errcode
= pthread_mutex_unlock(&tog_mutex
);
3388 return got_error_set_errno(errcode
,
3389 "pthread_mutex_unlock");
3390 errcode
= pthread_join(s
->thread
, (void **)&thread_err
);
3392 return got_error_set_errno(errcode
, "pthread_join");
3393 errcode
= pthread_mutex_lock(&tog_mutex
);
3395 return got_error_set_errno(errcode
,
3396 "pthread_mutex_lock");
3397 s
->thread
= 0; //NULL;
3400 if (s
->thread_args
.repo
) {
3401 err
= got_repo_close(s
->thread_args
.repo
);
3402 s
->thread_args
.repo
= NULL
;
3405 if (s
->thread_args
.pack_fds
) {
3406 const struct got_error
*pack_err
=
3407 got_repo_pack_fds_close(s
->thread_args
.pack_fds
);
3410 s
->thread_args
.pack_fds
= NULL
;
3413 if (s
->thread_args
.graph
) {
3414 got_commit_graph_close(s
->thread_args
.graph
);
3415 s
->thread_args
.graph
= NULL
;
3418 return err
? err
: thread_err
;
3421 static const struct got_error
*
3422 close_log_view(struct tog_view
*view
)
3424 const struct got_error
*err
= NULL
;
3425 struct tog_log_view_state
*s
= &view
->state
.log
;
3428 err
= stop_log_thread(s
);
3430 errcode
= pthread_cond_destroy(&s
->thread_args
.need_commits
);
3431 if (errcode
&& err
== NULL
)
3432 err
= got_error_set_errno(errcode
, "pthread_cond_destroy");
3434 errcode
= pthread_cond_destroy(&s
->thread_args
.commit_loaded
);
3435 if (errcode
&& err
== NULL
)
3436 err
= got_error_set_errno(errcode
, "pthread_cond_destroy");
3438 free_commits(&s
->limit_commits
);
3439 free_commits(&s
->real_commits
);
3440 free(s
->in_repo_path
);
3441 s
->in_repo_path
= NULL
;
3444 free(s
->head_ref_name
);
3445 s
->head_ref_name
= NULL
;
3450 * We use two queues to implement the limit feature: first consists of
3451 * commits matching the current limit_regex; second is the real queue
3452 * of all known commits (real_commits). When the user starts limiting,
3453 * we swap queues such that all movement and displaying functionality
3454 * works with very slight change.
3456 static const struct got_error
*
3457 limit_log_view(struct tog_view
*view
)
3459 struct tog_log_view_state
*s
= &view
->state
.log
;
3460 struct commit_queue_entry
*entry
;
3461 struct tog_view
*v
= view
;
3462 const struct got_error
*err
= NULL
;
3466 if (view_is_hsplit_top(view
))
3468 else if (view
->mode
== TOG_VIEW_SPLIT_VERT
&& view
->parent
)
3471 /* Get the pattern */
3472 wmove(v
->window
, v
->nlines
- 1, 0);
3473 wclrtoeol(v
->window
);
3474 mvwaddstr(v
->window
, v
->nlines
- 1, 0, "&/");
3475 nodelay(v
->window
, FALSE
);
3478 ret
= wgetnstr(v
->window
, pattern
, sizeof(pattern
));
3481 nodelay(v
->window
, TRUE
);
3485 if (*pattern
== '\0') {
3487 * Safety measure for the situation where the user
3488 * resets limit without previously limiting anything.
3494 * User could have pressed Ctrl+L, which refreshed the
3495 * commit queues, it means we can't save previously
3496 * (before limit took place) displayed entries,
3497 * because they would point to already free'ed memory,
3498 * so we are forced to always select first entry of
3501 s
->commits
= &s
->real_commits
;
3502 s
->first_displayed_entry
= TAILQ_FIRST(&s
->real_commits
.head
);
3503 s
->selected_entry
= s
->first_displayed_entry
;
3510 if (regcomp(&s
->limit_regex
, pattern
, REG_EXTENDED
| REG_NEWLINE
))
3515 /* Clear the screen while loading limit view */
3516 s
->first_displayed_entry
= NULL
;
3517 s
->last_displayed_entry
= NULL
;
3518 s
->selected_entry
= NULL
;
3519 s
->commits
= &s
->limit_commits
;
3521 /* Prepare limit queue for new search */
3522 free_commits(&s
->limit_commits
);
3523 s
->limit_commits
.ncommits
= 0;
3525 /* First process commits, which are in queue already */
3526 TAILQ_FOREACH(entry
, &s
->real_commits
.head
, entry
) {
3529 err
= match_commit(&have_match
, entry
->id
,
3530 entry
->commit
, &s
->limit_regex
);
3535 struct commit_queue_entry
*matched
;
3537 matched
= alloc_commit_queue_entry(entry
->commit
,
3539 if (matched
== NULL
) {
3540 err
= got_error_from_errno(
3541 "alloc_commit_queue_entry");
3544 matched
->commit
= entry
->commit
;
3545 got_object_commit_retain(entry
->commit
);
3547 matched
->idx
= s
->limit_commits
.ncommits
;
3548 TAILQ_INSERT_TAIL(&s
->limit_commits
.head
,
3550 s
->limit_commits
.ncommits
++;
3554 /* Second process all the commits, until we fill the screen */
3555 if (s
->limit_commits
.ncommits
< view
->nlines
- 1 &&
3556 !s
->thread_args
.log_complete
) {
3557 s
->thread_args
.commits_needed
+=
3558 view
->nlines
- s
->limit_commits
.ncommits
- 1;
3559 err
= trigger_log_thread(view
, 1);
3564 s
->first_displayed_entry
= TAILQ_FIRST(&s
->commits
->head
);
3565 s
->selected_entry
= TAILQ_FIRST(&s
->commits
->head
);
3571 static const struct got_error
*
3572 search_start_log_view(struct tog_view
*view
)
3574 struct tog_log_view_state
*s
= &view
->state
.log
;
3576 s
->matched_entry
= NULL
;
3577 s
->search_entry
= NULL
;
3581 static const struct got_error
*
3582 search_next_log_view(struct tog_view
*view
)
3584 const struct got_error
*err
= NULL
;
3585 struct tog_log_view_state
*s
= &view
->state
.log
;
3586 struct commit_queue_entry
*entry
;
3588 /* Display progress update in log view. */
3589 show_log_view(view
);
3593 if (s
->search_entry
) {
3595 errcode
= pthread_mutex_unlock(&tog_mutex
);
3597 return got_error_set_errno(errcode
,
3598 "pthread_mutex_unlock");
3599 ch
= wgetch(view
->window
);
3600 errcode
= pthread_mutex_lock(&tog_mutex
);
3602 return got_error_set_errno(errcode
,
3603 "pthread_mutex_lock");
3604 if (ch
== CTRL('g') || ch
== KEY_BACKSPACE
) {
3605 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
3608 if (view
->searching
== TOG_SEARCH_FORWARD
)
3609 entry
= TAILQ_NEXT(s
->search_entry
, entry
);
3611 entry
= TAILQ_PREV(s
->search_entry
,
3612 commit_queue_head
, entry
);
3613 } else if (s
->matched_entry
) {
3615 * If the user has moved the cursor after we hit a match,
3616 * the position from where we should continue searching
3617 * might have changed.
3619 if (view
->searching
== TOG_SEARCH_FORWARD
)
3620 entry
= TAILQ_NEXT(s
->selected_entry
, entry
);
3622 entry
= TAILQ_PREV(s
->selected_entry
, commit_queue_head
,
3625 entry
= s
->selected_entry
;
3631 if (entry
== NULL
) {
3632 if (s
->thread_args
.log_complete
||
3633 view
->searching
== TOG_SEARCH_BACKWARD
) {
3634 view
->search_next_done
=
3635 (s
->matched_entry
== NULL
?
3636 TOG_SEARCH_HAVE_NONE
: TOG_SEARCH_NO_MORE
);
3637 s
->search_entry
= NULL
;
3641 * Poke the log thread for more commits and return,
3642 * allowing the main loop to make progress. Search
3643 * will resume at s->search_entry once we come back.
3645 s
->thread_args
.commits_needed
++;
3646 return trigger_log_thread(view
, 0);
3649 err
= match_commit(&have_match
, entry
->id
, entry
->commit
,
3654 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
3655 s
->matched_entry
= entry
;
3659 s
->search_entry
= entry
;
3660 if (view
->searching
== TOG_SEARCH_FORWARD
)
3661 entry
= TAILQ_NEXT(entry
, entry
);
3663 entry
= TAILQ_PREV(entry
, commit_queue_head
, entry
);
3666 if (s
->matched_entry
) {
3667 int cur
= s
->selected_entry
->idx
;
3668 while (cur
< s
->matched_entry
->idx
) {
3669 err
= input_log_view(NULL
, view
, KEY_DOWN
);
3674 while (cur
> s
->matched_entry
->idx
) {
3675 err
= input_log_view(NULL
, view
, KEY_UP
);
3682 s
->search_entry
= NULL
;
3687 static const struct got_error
*
3688 open_log_view(struct tog_view
*view
, struct got_object_id
*start_id
,
3689 struct got_repository
*repo
, const char *head_ref_name
,
3690 const char *in_repo_path
, int log_branches
)
3692 const struct got_error
*err
= NULL
;
3693 struct tog_log_view_state
*s
= &view
->state
.log
;
3694 struct got_repository
*thread_repo
= NULL
;
3695 struct got_commit_graph
*thread_graph
= NULL
;
3698 if (in_repo_path
!= s
->in_repo_path
) {
3699 free(s
->in_repo_path
);
3700 s
->in_repo_path
= strdup(in_repo_path
);
3701 if (s
->in_repo_path
== NULL
) {
3702 err
= got_error_from_errno("strdup");
3707 /* The commit queue only contains commits being displayed. */
3708 TAILQ_INIT(&s
->real_commits
.head
);
3709 s
->real_commits
.ncommits
= 0;
3710 s
->commits
= &s
->real_commits
;
3712 TAILQ_INIT(&s
->limit_commits
.head
);
3714 s
->limit_commits
.ncommits
= 0;
3717 if (head_ref_name
) {
3718 s
->head_ref_name
= strdup(head_ref_name
);
3719 if (s
->head_ref_name
== NULL
) {
3720 err
= got_error_from_errno("strdup");
3724 s
->start_id
= got_object_id_dup(start_id
);
3725 if (s
->start_id
== NULL
) {
3726 err
= got_error_from_errno("got_object_id_dup");
3729 s
->log_branches
= log_branches
;
3730 s
->use_committer
= 1;
3732 STAILQ_INIT(&s
->colors
);
3733 if (has_colors() && getenv("TOG_COLORS") != NULL
) {
3734 err
= add_color(&s
->colors
, "^$", TOG_COLOR_COMMIT
,
3735 get_color_value("TOG_COLOR_COMMIT"));
3738 err
= add_color(&s
->colors
, "^$", TOG_COLOR_AUTHOR
,
3739 get_color_value("TOG_COLOR_AUTHOR"));
3741 free_colors(&s
->colors
);
3744 err
= add_color(&s
->colors
, "^$", TOG_COLOR_DATE
,
3745 get_color_value("TOG_COLOR_DATE"));
3747 free_colors(&s
->colors
);
3752 view
->show
= show_log_view
;
3753 view
->input
= input_log_view
;
3754 view
->resize
= resize_log_view
;
3755 view
->close
= close_log_view
;
3756 view
->search_start
= search_start_log_view
;
3757 view
->search_next
= search_next_log_view
;
3759 if (s
->thread_args
.pack_fds
== NULL
) {
3760 err
= got_repo_pack_fds_open(&s
->thread_args
.pack_fds
);
3764 err
= got_repo_open(&thread_repo
, got_repo_get_path(repo
), NULL
,
3765 s
->thread_args
.pack_fds
);
3768 err
= got_commit_graph_open(&thread_graph
, s
->in_repo_path
,
3772 err
= got_commit_graph_iter_start(thread_graph
, s
->start_id
,
3773 s
->repo
, NULL
, NULL
);
3777 errcode
= pthread_cond_init(&s
->thread_args
.need_commits
, NULL
);
3779 err
= got_error_set_errno(errcode
, "pthread_cond_init");
3782 errcode
= pthread_cond_init(&s
->thread_args
.commit_loaded
, NULL
);
3784 err
= got_error_set_errno(errcode
, "pthread_cond_init");
3788 s
->thread_args
.commits_needed
= view
->nlines
;
3789 s
->thread_args
.graph
= thread_graph
;
3790 s
->thread_args
.real_commits
= &s
->real_commits
;
3791 s
->thread_args
.limit_commits
= &s
->limit_commits
;
3792 s
->thread_args
.in_repo_path
= s
->in_repo_path
;
3793 s
->thread_args
.start_id
= s
->start_id
;
3794 s
->thread_args
.repo
= thread_repo
;
3795 s
->thread_args
.log_complete
= 0;
3796 s
->thread_args
.quit
= &s
->quit
;
3797 s
->thread_args
.first_displayed_entry
= &s
->first_displayed_entry
;
3798 s
->thread_args
.selected_entry
= &s
->selected_entry
;
3799 s
->thread_args
.searching
= &view
->searching
;
3800 s
->thread_args
.search_next_done
= &view
->search_next_done
;
3801 s
->thread_args
.regex
= &view
->regex
;
3802 s
->thread_args
.limiting
= &s
->limit_view
;
3803 s
->thread_args
.limit_regex
= &s
->limit_regex
;
3804 s
->thread_args
.limit_commits
= &s
->limit_commits
;
3807 if (view
->close
== NULL
)
3808 close_log_view(view
);
3814 static const struct got_error
*
3815 show_log_view(struct tog_view
*view
)
3817 const struct got_error
*err
;
3818 struct tog_log_view_state
*s
= &view
->state
.log
;
3820 if (s
->thread
== 0) { //NULL) {
3821 int errcode
= pthread_create(&s
->thread
, NULL
, log_thread
,
3824 return got_error_set_errno(errcode
, "pthread_create");
3825 if (s
->thread_args
.commits_needed
> 0) {
3826 err
= trigger_log_thread(view
, 1);
3832 return draw_commits(view
);
3836 log_move_cursor_up(struct tog_view
*view
, int page
, int home
)
3838 struct tog_log_view_state
*s
= &view
->state
.log
;
3840 if (s
->first_displayed_entry
== NULL
)
3842 if (s
->selected_entry
->idx
== 0)
3845 if ((page
&& TAILQ_FIRST(&s
->commits
->head
) == s
->first_displayed_entry
)
3847 s
->selected
= home
? 0 : MAX(0, s
->selected
- page
- 1);
3849 if (!page
&& !home
&& s
->selected
> 0)
3852 log_scroll_up(s
, home
? s
->commits
->ncommits
: MAX(page
, 1));
3858 static const struct got_error
*
3859 log_move_cursor_down(struct tog_view
*view
, int page
)
3861 struct tog_log_view_state
*s
= &view
->state
.log
;
3862 const struct got_error
*err
= NULL
;
3863 int eos
= view
->nlines
- 2;
3865 if (s
->first_displayed_entry
== NULL
)
3868 if (s
->thread_args
.log_complete
&&
3869 s
->selected_entry
->idx
>= s
->commits
->ncommits
- 1)
3872 if (view_is_hsplit_top(view
))
3873 --eos
; /* border consumes the last line */
3876 if (s
->selected
< MIN(eos
, s
->commits
->ncommits
- 1))
3879 err
= log_scroll_down(view
, 1);
3880 } else if (s
->thread_args
.load_all
&& s
->thread_args
.log_complete
) {
3881 struct commit_queue_entry
*entry
;
3885 entry
= TAILQ_LAST(&s
->commits
->head
, commit_queue_head
);
3886 s
->last_displayed_entry
= entry
;
3887 for (n
= 0; n
<= eos
; n
++) {
3890 s
->first_displayed_entry
= entry
;
3891 entry
= TAILQ_PREV(entry
, commit_queue_head
, entry
);
3894 s
->selected
= n
- 1;
3896 if (s
->last_displayed_entry
->idx
== s
->commits
->ncommits
- 1 &&
3897 s
->thread_args
.log_complete
)
3898 s
->selected
+= MIN(page
,
3899 s
->commits
->ncommits
- s
->selected_entry
->idx
- 1);
3901 err
= log_scroll_down(view
, page
);
3907 * We might necessarily overshoot in horizontal
3908 * splits; if so, select the last displayed commit.
3910 if (s
->first_displayed_entry
&& s
->last_displayed_entry
) {
3911 s
->selected
= MIN(s
->selected
,
3912 s
->last_displayed_entry
->idx
-
3913 s
->first_displayed_entry
->idx
);
3918 if (s
->thread_args
.log_complete
&&
3919 s
->selected_entry
->idx
== s
->commits
->ncommits
- 1)
3926 view_get_split(struct tog_view
*view
, int *y
, int *x
)
3931 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
) {
3932 if (view
->child
&& view
->child
->resized_y
)
3933 *y
= view
->child
->resized_y
;
3934 else if (view
->resized_y
)
3935 *y
= view
->resized_y
;
3937 *y
= view_split_begin_y(view
->lines
);
3938 } else if (view
->mode
== TOG_VIEW_SPLIT_VERT
) {
3939 if (view
->child
&& view
->child
->resized_x
)
3940 *x
= view
->child
->resized_x
;
3941 else if (view
->resized_x
)
3942 *x
= view
->resized_x
;
3944 *x
= view_split_begin_x(view
->begin_x
);
3948 /* Split view horizontally at y and offset view->state->selected line. */
3949 static const struct got_error
*
3950 view_init_hsplit(struct tog_view
*view
, int y
)
3952 const struct got_error
*err
= NULL
;
3956 err
= view_resize(view
);
3960 err
= offset_selection_down(view
);
3965 static const struct got_error
*
3966 log_goto_line(struct tog_view
*view
, int nlines
)
3968 const struct got_error
*err
= NULL
;
3969 struct tog_log_view_state
*s
= &view
->state
.log
;
3970 int g
, idx
= s
->selected_entry
->idx
;
3972 if (s
->first_displayed_entry
== NULL
|| s
->last_displayed_entry
== NULL
)
3978 if (g
>= s
->first_displayed_entry
->idx
+ 1 &&
3979 g
<= s
->last_displayed_entry
->idx
+ 1 &&
3980 g
- s
->first_displayed_entry
->idx
- 1 < nlines
) {
3981 s
->selected
= g
- s
->first_displayed_entry
->idx
- 1;
3987 err
= log_move_cursor_down(view
, g
- idx
- 1);
3988 if (!err
&& g
> s
->selected_entry
->idx
+ 1)
3989 err
= log_move_cursor_down(view
,
3990 g
- s
->first_displayed_entry
->idx
- 1);
3993 } else if (idx
+ 1 > g
)
3994 log_move_cursor_up(view
, idx
- g
+ 1, 0);
3996 if (g
< nlines
&& s
->first_displayed_entry
->idx
== 0)
3997 s
->selected
= g
- 1;
4005 horizontal_scroll_input(struct tog_view
*view
, int ch
)
4011 view
->x
-= MIN(view
->x
, 2);
4017 if (view
->x
+ view
->ncols
/ 2 < view
->maxx
)
4026 view
->x
= MAX(view
->maxx
- view
->ncols
/ 2, 0);
4034 static const struct got_error
*
4035 input_log_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
4037 const struct got_error
*err
= NULL
;
4038 struct tog_log_view_state
*s
= &view
->state
.log
;
4041 if (s
->thread_args
.load_all
) {
4042 if (ch
== CTRL('g') || ch
== KEY_BACKSPACE
)
4043 s
->thread_args
.load_all
= 0;
4044 else if (s
->thread_args
.log_complete
) {
4045 err
= log_move_cursor_down(view
, s
->commits
->ncommits
);
4046 s
->thread_args
.load_all
= 0;
4052 eos
= nscroll
= view
->nlines
- 1;
4053 if (view_is_hsplit_top(view
))
4057 return log_goto_line(view
, eos
);
4061 err
= limit_log_view(view
);
4072 horizontal_scroll_input(view
, ch
);
4079 log_move_cursor_up(view
, 0, 0);
4084 log_move_cursor_up(view
, 0, 1);
4094 log_move_cursor_up(view
, nscroll
, 0);
4101 err
= log_move_cursor_down(view
, 0);
4104 s
->use_committer
= !s
->use_committer
;
4105 view
->action
= s
->use_committer
?
4106 "show committer" : "show commit author";
4111 /* We don't know yet how many commits, so we're forced to
4112 * traverse them all. */
4114 s
->thread_args
.load_all
= 1;
4115 if (!s
->thread_args
.log_complete
)
4116 return trigger_log_thread(view
, 0);
4117 err
= log_move_cursor_down(view
, s
->commits
->ncommits
);
4118 s
->thread_args
.load_all
= 0;
4129 err
= log_move_cursor_down(view
, nscroll
);
4132 if (s
->selected
> view
->nlines
- 2)
4133 s
->selected
= view
->nlines
- 2;
4134 if (s
->selected
> s
->commits
->ncommits
- 1)
4135 s
->selected
= s
->commits
->ncommits
- 1;
4137 if (s
->commits
->ncommits
< view
->nlines
- 1 &&
4138 !s
->thread_args
.log_complete
) {
4139 s
->thread_args
.commits_needed
+= (view
->nlines
- 1) -
4140 s
->commits
->ncommits
;
4141 err
= trigger_log_thread(view
, 1);
4147 if (s
->selected_entry
== NULL
)
4149 err
= view_request_new(new_view
, view
, TOG_VIEW_DIFF
);
4153 if (s
->selected_entry
== NULL
)
4155 err
= view_request_new(new_view
, view
, TOG_VIEW_TREE
);
4161 if (ch
== KEY_BACKSPACE
&&
4162 got_path_is_root_dir(s
->in_repo_path
))
4164 err
= stop_log_thread(s
);
4167 if (ch
== KEY_BACKSPACE
) {
4169 err
= got_path_dirname(&parent_path
, s
->in_repo_path
);
4172 free(s
->in_repo_path
);
4173 s
->in_repo_path
= parent_path
;
4174 s
->thread_args
.in_repo_path
= s
->in_repo_path
;
4175 } else if (ch
== CTRL('l')) {
4176 struct got_object_id
*start_id
;
4177 err
= got_repo_match_object_id(&start_id
, NULL
,
4178 s
->head_ref_name
? s
->head_ref_name
: GOT_REF_HEAD
,
4179 GOT_OBJ_TYPE_COMMIT
, &tog_refs
, s
->repo
);
4181 if (s
->head_ref_name
== NULL
||
4182 err
->code
!= GOT_ERR_NOT_REF
)
4184 /* Try to cope with deleted references. */
4185 free(s
->head_ref_name
);
4186 s
->head_ref_name
= NULL
;
4187 err
= got_repo_match_object_id(&start_id
,
4188 NULL
, GOT_REF_HEAD
, GOT_OBJ_TYPE_COMMIT
,
4189 &tog_refs
, s
->repo
);
4194 s
->start_id
= start_id
;
4195 s
->thread_args
.start_id
= s
->start_id
;
4197 s
->log_branches
= !s
->log_branches
;
4199 if (s
->thread_args
.pack_fds
== NULL
) {
4200 err
= got_repo_pack_fds_open(&s
->thread_args
.pack_fds
);
4204 err
= got_repo_open(&s
->thread_args
.repo
,
4205 got_repo_get_path(s
->repo
), NULL
,
4206 s
->thread_args
.pack_fds
);
4210 err
= tog_load_refs(s
->repo
, 0);
4213 err
= got_commit_graph_open(&s
->thread_args
.graph
,
4214 s
->in_repo_path
, !s
->log_branches
);
4217 err
= got_commit_graph_iter_start(s
->thread_args
.graph
,
4218 s
->start_id
, s
->repo
, NULL
, NULL
);
4221 free_commits(&s
->real_commits
);
4222 free_commits(&s
->limit_commits
);
4223 s
->first_displayed_entry
= NULL
;
4224 s
->last_displayed_entry
= NULL
;
4225 s
->selected_entry
= NULL
;
4227 s
->thread_args
.log_complete
= 0;
4229 s
->thread_args
.commits_needed
= view
->lines
;
4230 s
->matched_entry
= NULL
;
4231 s
->search_entry
= NULL
;
4236 err
= view_request_new(new_view
, view
, TOG_VIEW_REF
);
4246 static const struct got_error
*
4247 apply_unveil(const char *repo_path
, const char *worktree_path
)
4249 const struct got_error
*error
;
4252 if (unveil("gmon.out", "rwc") != 0)
4253 return got_error_from_errno2("unveil", "gmon.out");
4255 if (repo_path
&& unveil(repo_path
, "r") != 0)
4256 return got_error_from_errno2("unveil", repo_path
);
4258 if (worktree_path
&& unveil(worktree_path
, "rwc") != 0)
4259 return got_error_from_errno2("unveil", worktree_path
);
4261 if (unveil(GOT_TMPDIR_STR
, "rwc") != 0)
4262 return got_error_from_errno2("unveil", GOT_TMPDIR_STR
);
4264 error
= got_privsep_unveil_exec_helpers();
4268 if (unveil(NULL
, NULL
) != 0)
4269 return got_error_from_errno("unveil");
4274 static const struct got_error
*
4275 init_mock_term(const char *test_script_path
)
4277 const struct got_error
*err
= NULL
;
4278 const char *screen_dump_path
;
4281 if (test_script_path
== NULL
|| *test_script_path
== '\0')
4282 return got_error_msg(GOT_ERR_IO
, "TOG_TEST_SCRIPT not defined");
4284 tog_io
.f
= fopen(test_script_path
, "re");
4285 if (tog_io
.f
== NULL
) {
4286 err
= got_error_from_errno_fmt("fopen: %s",
4291 /* test mode, we don't want any output */
4292 tog_io
.cout
= fopen("/dev/null", "w+");
4293 if (tog_io
.cout
== NULL
) {
4294 err
= got_error_from_errno2("fopen", "/dev/null");
4298 in
= dup(fileno(tog_io
.cout
));
4300 err
= got_error_from_errno("dup");
4303 tog_io
.cin
= fdopen(in
, "r");
4304 if (tog_io
.cin
== NULL
) {
4305 err
= got_error_from_errno("fdopen");
4310 screen_dump_path
= getenv("TOG_SCR_DUMP");
4311 if (screen_dump_path
== NULL
|| *screen_dump_path
== '\0')
4312 return got_error_msg(GOT_ERR_IO
, "TOG_SCR_DUMP not defined");
4313 tog_io
.sdump
= fopen(screen_dump_path
, "wex");
4314 if (tog_io
.sdump
== NULL
) {
4315 err
= got_error_from_errno2("fopen", screen_dump_path
);
4319 if (fseeko(tog_io
.f
, 0L, SEEK_SET
) == -1) {
4320 err
= got_error_from_errno("fseeko");
4324 if (newterm(NULL
, tog_io
.cout
, tog_io
.cin
) == NULL
)
4325 err
= got_error_msg(GOT_ERR_IO
,
4326 "newterm: failed to initialise curses");
4340 * Override default signal handlers before starting ncurses.
4341 * This should prevent ncurses from installing its own
4342 * broken cleanup() signal handler.
4344 signal(SIGWINCH
, tog_sigwinch
);
4345 signal(SIGPIPE
, tog_sigpipe
);
4346 signal(SIGCONT
, tog_sigcont
);
4347 signal(SIGINT
, tog_sigint
);
4348 signal(SIGTERM
, tog_sigterm
);
4350 if (using_mock_io
) /* In test mode we use a fake terminal */
4356 halfdelay(1); /* Fast refresh while initial view is loading. */
4359 intrflush(stdscr
, FALSE
);
4360 keypad(stdscr
, TRUE
);
4362 if (getenv("TOG_COLORS") != NULL
) {
4364 use_default_colors();
4370 static const struct got_error
*
4371 get_in_repo_path_from_argv0(char **in_repo_path
, int argc
, char *argv
[],
4372 struct got_repository
*repo
, struct got_worktree
*worktree
)
4374 const struct got_error
*err
= NULL
;
4377 *in_repo_path
= strdup("/");
4378 if (*in_repo_path
== NULL
)
4379 return got_error_from_errno("strdup");
4384 const char *prefix
= got_worktree_get_path_prefix(worktree
);
4387 err
= got_worktree_resolve_path(&p
, worktree
, argv
[0]);
4390 if (asprintf(in_repo_path
, "%s%s%s", prefix
,
4391 (p
[0] != '\0' && !got_path_is_root_dir(prefix
)) ? "/" : "",
4393 err
= got_error_from_errno("asprintf");
4394 *in_repo_path
= NULL
;
4398 err
= got_repo_map_path(in_repo_path
, repo
, argv
[0]);
4403 static const struct got_error
*
4404 cmd_log(int argc
, char *argv
[])
4406 const struct got_error
*error
;
4407 struct got_repository
*repo
= NULL
;
4408 struct got_worktree
*worktree
= NULL
;
4409 struct got_object_id
*start_id
= NULL
;
4410 char *in_repo_path
= NULL
, *repo_path
= NULL
, *cwd
= NULL
;
4411 char *start_commit
= NULL
, *label
= NULL
;
4412 struct got_reference
*ref
= NULL
;
4413 const char *head_ref_name
= NULL
;
4414 int ch
, log_branches
= 0;
4415 struct tog_view
*view
;
4416 int *pack_fds
= NULL
;
4418 while ((ch
= getopt(argc
, argv
, "bc:r:")) != -1) {
4424 start_commit
= optarg
;
4427 repo_path
= realpath(optarg
, NULL
);
4428 if (repo_path
== NULL
)
4429 return got_error_from_errno2("realpath",
4444 error
= got_repo_pack_fds_open(&pack_fds
);
4448 if (repo_path
== NULL
) {
4449 cwd
= getcwd(NULL
, 0);
4451 return got_error_from_errno("getcwd");
4452 error
= got_worktree_open(&worktree
, cwd
);
4453 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
4457 strdup(got_worktree_get_repo_path(worktree
));
4459 repo_path
= strdup(cwd
);
4460 if (repo_path
== NULL
) {
4461 error
= got_error_from_errno("strdup");
4466 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
4470 error
= get_in_repo_path_from_argv0(&in_repo_path
, argc
, argv
,
4477 error
= apply_unveil(got_repo_get_path(repo
),
4478 worktree
? got_worktree_get_root_path(worktree
) : NULL
);
4482 /* already loaded by tog_log_with_path()? */
4483 if (TAILQ_EMPTY(&tog_refs
)) {
4484 error
= tog_load_refs(repo
, 0);
4489 if (start_commit
== NULL
) {
4490 error
= got_repo_match_object_id(&start_id
, &label
,
4491 worktree
? got_worktree_get_head_ref_name(worktree
) :
4492 GOT_REF_HEAD
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
4495 head_ref_name
= label
;
4497 error
= got_ref_open(&ref
, repo
, start_commit
, 0);
4499 head_ref_name
= got_ref_get_name(ref
);
4500 else if (error
->code
!= GOT_ERR_NOT_REF
)
4502 error
= got_repo_match_object_id(&start_id
, NULL
,
4503 start_commit
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
4508 view
= view_open(0, 0, 0, 0, TOG_VIEW_LOG
);
4510 error
= got_error_from_errno("view_open");
4513 error
= open_log_view(view
, start_id
, repo
, head_ref_name
,
4514 in_repo_path
, log_branches
);
4518 /* Release work tree lock. */
4519 got_worktree_close(worktree
);
4522 error
= view_loop(view
);
4532 const struct got_error
*close_err
= got_repo_close(repo
);
4537 got_worktree_close(worktree
);
4539 const struct got_error
*pack_err
=
4540 got_repo_pack_fds_close(pack_fds
);
4552 fprintf(stderr
, "usage: %s diff [-aw] [-C number] [-r repository-path] "
4553 "object1 object2\n", getprogname());
4558 match_line(const char *line
, regex_t
*regex
, size_t nmatch
,
4559 regmatch_t
*regmatch
)
4561 return regexec(regex
, line
, nmatch
, regmatch
, 0) == 0;
4564 static struct tog_color
*
4565 match_color(struct tog_colors
*colors
, const char *line
)
4567 struct tog_color
*tc
= NULL
;
4569 STAILQ_FOREACH(tc
, colors
, entry
) {
4570 if (match_line(line
, &tc
->regex
, 0, NULL
))
4577 static const struct got_error
*
4578 add_matched_line(int *wtotal
, const char *line
, int wlimit
, int col_tab_align
,
4579 WINDOW
*window
, int skipcol
, regmatch_t
*regmatch
)
4581 const struct got_error
*err
= NULL
;
4583 wchar_t *wline
= NULL
;
4584 int rme
, rms
, n
, width
, scrollx
;
4585 int width0
= 0, width1
= 0, width2
= 0;
4586 char *seg0
= NULL
, *seg1
= NULL
, *seg2
= NULL
;
4590 rms
= regmatch
->rm_so
;
4591 rme
= regmatch
->rm_eo
;
4593 err
= expand_tab(&exstr
, line
);
4597 /* Split the line into 3 segments, according to match offsets. */
4598 seg0
= strndup(exstr
, rms
);
4600 err
= got_error_from_errno("strndup");
4603 seg1
= strndup(exstr
+ rms
, rme
- rms
);
4605 err
= got_error_from_errno("strndup");
4608 seg2
= strdup(exstr
+ rme
);
4610 err
= got_error_from_errno("strndup");
4614 /* draw up to matched token if we haven't scrolled past it */
4615 err
= format_line(&wline
, &width0
, NULL
, seg0
, 0, wlimit
,
4619 n
= MAX(width0
- skipcol
, 0);
4622 err
= format_line(&wline
, &width
, &scrollx
, seg0
, skipcol
,
4623 wlimit
, col_tab_align
, 1);
4626 waddwstr(window
, &wline
[scrollx
]);
4636 err
= format_line(&wline
, &width1
, NULL
, seg1
, 0, wlimit
,
4640 wlen
= wcslen(wline
);
4642 width
= wcwidth(wline
[i
]);
4644 /* should not happen, tabs are expanded */
4645 err
= got_error(GOT_ERR_RANGE
);
4648 if (width0
+ w
+ width
> skipcol
)
4653 /* draw (visible part of) matched token (if scrolled into it) */
4654 if (width1
- w
> 0) {
4655 wattron(window
, A_STANDOUT
);
4656 waddwstr(window
, &wline
[i
]);
4657 wattroff(window
, A_STANDOUT
);
4658 wlimit
-= (width1
- w
);
4659 *wtotal
+= (width1
- w
);
4663 if (wlimit
> 0) { /* draw rest of line */
4665 if (skipcol
> width0
+ width1
) {
4666 err
= format_line(&wline
, &width2
, &scrollx
, seg2
,
4667 skipcol
- (width0
+ width1
), wlimit
,
4671 waddwstr(window
, &wline
[scrollx
]);
4673 err
= format_line(&wline
, &width2
, NULL
, seg2
, 0,
4674 wlimit
, col_tab_align
, 1);
4677 waddwstr(window
, wline
);
4691 gotoline(struct tog_view
*view
, int *lineno
, int *nprinted
)
4694 int *eof
, *first
, *selected
;
4696 if (view
->type
== TOG_VIEW_DIFF
) {
4697 struct tog_diff_view_state
*s
= &view
->state
.diff
;
4699 first
= &s
->first_displayed_line
;
4703 } else if (view
->type
== TOG_VIEW_HELP
) {
4704 struct tog_help_view_state
*s
= &view
->state
.help
;
4706 first
= &s
->first_displayed_line
;
4710 } else if (view
->type
== TOG_VIEW_BLAME
) {
4711 struct tog_blame_view_state
*s
= &view
->state
.blame
;
4713 first
= &s
->first_displayed_line
;
4714 selected
= &s
->selected_line
;
4720 /* Center gline in the middle of the page like vi(1). */
4721 if (*lineno
< view
->gline
- (view
->nlines
- 3) / 2)
4723 if (*first
!= 1 && (*lineno
> view
->gline
- (view
->nlines
- 3) / 2)) {
4732 *selected
= view
->gline
<= (view
->nlines
- 3) / 2 ?
4733 view
->gline
: (view
->nlines
- 3) / 2 + 1;
4739 static const struct got_error
*
4740 draw_file(struct tog_view
*view
, const char *header
)
4742 struct tog_diff_view_state
*s
= &view
->state
.diff
;
4743 regmatch_t
*regmatch
= &view
->regmatch
;
4744 const struct got_error
*err
;
4747 size_t linesize
= 0;
4751 int max_lines
= view
->nlines
;
4752 int nlines
= s
->nlines
;
4755 s
->lineno
= s
->first_displayed_line
- 1;
4756 line_offset
= s
->lines
[s
->first_displayed_line
- 1].offset
;
4757 if (fseeko(s
->f
, line_offset
, SEEK_SET
) == -1)
4758 return got_error_from_errno("fseek");
4760 werase(view
->window
);
4762 if (view
->gline
> s
->nlines
- 1)
4763 view
->gline
= s
->nlines
- 1;
4766 int ln
= view
->gline
? view
->gline
<= (view
->nlines
- 3) / 2 ?
4767 1 : view
->gline
- (view
->nlines
- 3) / 2 :
4768 s
->lineno
+ s
->selected_line
;
4770 if (asprintf(&line
, "[%d/%d] %s", ln
, nlines
, header
) == -1)
4771 return got_error_from_errno("asprintf");
4772 err
= format_line(&wline
, &width
, NULL
, line
, 0, view
->ncols
,
4778 if (view_needs_focus_indication(view
))
4779 wstandout(view
->window
);
4780 waddwstr(view
->window
, wline
);
4783 while (width
++ < view
->ncols
)
4784 waddch(view
->window
, ' ');
4785 if (view_needs_focus_indication(view
))
4786 wstandend(view
->window
);
4796 while (max_lines
> 0 && nprinted
< max_lines
) {
4797 enum got_diff_line_type linetype
;
4800 linelen
= getline(&line
, &linesize
, s
->f
);
4801 if (linelen
== -1) {
4807 return got_ferror(s
->f
, GOT_ERR_IO
);
4810 if (++s
->lineno
< s
->first_displayed_line
)
4812 if (view
->gline
&& !gotoline(view
, &s
->lineno
, &nprinted
))
4814 if (s
->lineno
== view
->hiline
)
4817 /* Set view->maxx based on full line length. */
4818 err
= format_line(&wline
, &width
, NULL
, line
, 0, INT_MAX
, 0,
4824 view
->maxx
= MAX(view
->maxx
, width
);
4828 linetype
= s
->lines
[s
->lineno
].type
;
4829 if (linetype
> GOT_DIFF_LINE_LOGMSG
&&
4830 linetype
< GOT_DIFF_LINE_CONTEXT
)
4831 attr
|= COLOR_PAIR(linetype
);
4833 wattron(view
->window
, attr
);
4834 if (s
->first_displayed_line
+ nprinted
== s
->matched_line
&&
4835 regmatch
->rm_so
>= 0 && regmatch
->rm_so
< regmatch
->rm_eo
) {
4836 err
= add_matched_line(&width
, line
, view
->ncols
, 0,
4837 view
->window
, view
->x
, regmatch
);
4844 err
= format_line(&wline
, &width
, &skip
, line
,
4845 view
->x
, view
->ncols
, 0, view
->x
? 1 : 0);
4850 waddwstr(view
->window
, &wline
[skip
]);
4854 if (s
->lineno
== view
->hiline
) {
4855 /* highlight full gline length */
4856 while (width
++ < view
->ncols
)
4857 waddch(view
->window
, ' ');
4859 if (width
<= view
->ncols
- 1)
4860 waddch(view
->window
, '\n');
4863 wattroff(view
->window
, attr
);
4864 if (++nprinted
== 1)
4865 s
->first_displayed_line
= s
->lineno
;
4869 s
->last_displayed_line
= s
->first_displayed_line
+
4872 s
->last_displayed_line
= s
->first_displayed_line
;
4877 while (nprinted
< view
->nlines
) {
4878 waddch(view
->window
, '\n');
4882 err
= format_line(&wline
, &width
, NULL
, TOG_EOF_STRING
, 0,
4888 wstandout(view
->window
);
4889 waddwstr(view
->window
, wline
);
4892 wstandend(view
->window
);
4899 get_datestr(time_t *time
, char *datebuf
)
4901 struct tm mytm
, *tm
;
4904 tm
= gmtime_r(time
, &mytm
);
4907 s
= asctime_r(tm
, datebuf
);
4910 p
= strchr(s
, '\n');
4916 static const struct got_error
*
4917 add_line_metadata(struct got_diff_line
**lines
, size_t *nlines
,
4918 off_t off
, uint8_t type
)
4920 struct got_diff_line
*p
;
4922 p
= reallocarray(*lines
, *nlines
+ 1, sizeof(**lines
));
4924 return got_error_from_errno("reallocarray");
4926 (*lines
)[*nlines
].offset
= off
;
4927 (*lines
)[*nlines
].type
= type
;
4933 static const struct got_error
*
4934 cat_diff(FILE *dst
, FILE *src
, struct got_diff_line
**d_lines
, size_t *d_nlines
,
4935 struct got_diff_line
*s_lines
, size_t s_nlines
)
4937 struct got_diff_line
*p
;
4941 if (fseeko(src
, 0L, SEEK_SET
) == -1)
4942 return got_error_from_errno("fseeko");
4945 r
= fread(buf
, 1, sizeof(buf
), src
);
4948 return got_error_from_errno("fread");
4952 if (fwrite(buf
, 1, r
, dst
) != r
)
4953 return got_ferror(dst
, GOT_ERR_IO
);
4956 if (s_nlines
== 0 && *d_nlines
== 0)
4960 * If commit info was in dst, increment line offsets
4961 * of the appended diff content, but skip s_lines[0]
4962 * because offset zero is already in *d_lines.
4964 if (*d_nlines
> 0) {
4965 for (i
= 1; i
< s_nlines
; ++i
)
4966 s_lines
[i
].offset
+= (*d_lines
)[*d_nlines
- 1].offset
;
4974 p
= reallocarray(*d_lines
, *d_nlines
+ s_nlines
, sizeof(*p
));
4976 /* d_lines is freed in close_diff_view() */
4977 return got_error_from_errno("reallocarray");
4982 memcpy(*d_lines
+ *d_nlines
, s_lines
, s_nlines
* sizeof(*s_lines
));
4983 *d_nlines
+= s_nlines
;
4988 static const struct got_error
*
4989 write_commit_info(struct got_diff_line
**lines
, size_t *nlines
,
4990 struct got_object_id
*commit_id
, struct got_reflist_head
*refs
,
4991 struct got_repository
*repo
, int ignore_ws
, int force_text_diff
,
4992 struct got_diffstat_cb_arg
*dsa
, FILE *outfile
)
4994 const struct got_error
*err
= NULL
;
4995 char datebuf
[26], *datestr
;
4996 struct got_commit_object
*commit
;
4997 char *id_str
= NULL
, *logmsg
= NULL
, *s
= NULL
, *line
;
4998 time_t committer_time
;
4999 const char *author
, *committer
;
5000 char *refs_str
= NULL
;
5001 struct got_pathlist_entry
*pe
;
5005 err
= build_refs_str(&refs_str
, refs
, commit_id
, repo
);
5009 err
= got_object_open_as_commit(&commit
, repo
, commit_id
);
5013 err
= got_object_id_str(&id_str
, commit_id
);
5015 err
= got_error_from_errno("got_object_id_str");
5019 err
= add_line_metadata(lines
, nlines
, 0, GOT_DIFF_LINE_NONE
);
5023 n
= fprintf(outfile
, "commit %s%s%s%s\n", id_str
, refs_str
? " (" : "",
5024 refs_str
? refs_str
: "", refs_str
? ")" : "");
5026 err
= got_error_from_errno("fprintf");
5030 err
= add_line_metadata(lines
, nlines
, outoff
, GOT_DIFF_LINE_META
);
5034 n
= fprintf(outfile
, "from: %s\n",
5035 got_object_commit_get_author(commit
));
5037 err
= got_error_from_errno("fprintf");
5041 err
= add_line_metadata(lines
, nlines
, outoff
, GOT_DIFF_LINE_AUTHOR
);
5045 author
= got_object_commit_get_author(commit
);
5046 committer
= got_object_commit_get_committer(commit
);
5047 if (strcmp(author
, committer
) != 0) {
5048 n
= fprintf(outfile
, "via: %s\n", committer
);
5050 err
= got_error_from_errno("fprintf");
5054 err
= add_line_metadata(lines
, nlines
, outoff
,
5055 GOT_DIFF_LINE_AUTHOR
);
5059 committer_time
= got_object_commit_get_committer_time(commit
);
5060 datestr
= get_datestr(&committer_time
, datebuf
);
5062 n
= fprintf(outfile
, "date: %s UTC\n", datestr
);
5064 err
= got_error_from_errno("fprintf");
5068 err
= add_line_metadata(lines
, nlines
, outoff
,
5069 GOT_DIFF_LINE_DATE
);
5073 if (got_object_commit_get_nparents(commit
) > 1) {
5074 const struct got_object_id_queue
*parent_ids
;
5075 struct got_object_qid
*qid
;
5077 parent_ids
= got_object_commit_get_parent_ids(commit
);
5078 STAILQ_FOREACH(qid
, parent_ids
, entry
) {
5079 err
= got_object_id_str(&id_str
, &qid
->id
);
5082 n
= fprintf(outfile
, "parent %d: %s\n", pn
++, id_str
);
5084 err
= got_error_from_errno("fprintf");
5088 err
= add_line_metadata(lines
, nlines
, outoff
,
5089 GOT_DIFF_LINE_META
);
5097 err
= got_object_commit_get_logmsg(&logmsg
, commit
);
5101 while ((line
= strsep(&s
, "\n")) != NULL
) {
5102 n
= fprintf(outfile
, "%s\n", line
);
5104 err
= got_error_from_errno("fprintf");
5108 err
= add_line_metadata(lines
, nlines
, outoff
,
5109 GOT_DIFF_LINE_LOGMSG
);
5114 TAILQ_FOREACH(pe
, dsa
->paths
, entry
) {
5115 struct got_diff_changed_path
*cp
= pe
->data
;
5116 int pad
= dsa
->max_path_len
- pe
->path_len
+ 1;
5118 n
= fprintf(outfile
, "%c %s%*c | %*d+ %*d-\n", cp
->status
,
5119 pe
->path
, pad
, ' ', dsa
->add_cols
+ 1, cp
->add
,
5120 dsa
->rm_cols
+ 1, cp
->rm
);
5122 err
= got_error_from_errno("fprintf");
5126 err
= add_line_metadata(lines
, nlines
, outoff
,
5127 GOT_DIFF_LINE_CHANGES
);
5132 fputc('\n', outfile
);
5134 err
= add_line_metadata(lines
, nlines
, outoff
, GOT_DIFF_LINE_NONE
);
5138 n
= fprintf(outfile
,
5139 "%d file%s changed, %d insertion%s(+), %d deletion%s(-)\n",
5140 dsa
->nfiles
, dsa
->nfiles
> 1 ? "s" : "", dsa
->ins
,
5141 dsa
->ins
!= 1 ? "s" : "", dsa
->del
, dsa
->del
!= 1 ? "s" : "");
5143 err
= got_error_from_errno("fprintf");
5147 err
= add_line_metadata(lines
, nlines
, outoff
, GOT_DIFF_LINE_NONE
);
5151 fputc('\n', outfile
);
5153 err
= add_line_metadata(lines
, nlines
, outoff
, GOT_DIFF_LINE_NONE
);
5158 got_object_commit_close(commit
);
5167 static const struct got_error
*
5168 create_diff(struct tog_diff_view_state
*s
)
5170 const struct got_error
*err
= NULL
;
5171 FILE *f
= NULL
, *tmp_diff_file
= NULL
;
5173 struct got_diff_line
*lines
= NULL
;
5174 struct got_pathlist_head changed_paths
;
5176 TAILQ_INIT(&changed_paths
);
5179 s
->lines
= malloc(sizeof(*s
->lines
));
5180 if (s
->lines
== NULL
)
5181 return got_error_from_errno("malloc");
5186 err
= got_error_from_errno("got_opentemp");
5189 tmp_diff_file
= got_opentemp();
5190 if (tmp_diff_file
== NULL
) {
5191 err
= got_error_from_errno("got_opentemp");
5194 if (s
->f
&& fclose(s
->f
) == EOF
) {
5195 err
= got_error_from_errno("fclose");
5201 err
= got_object_get_type(&obj_type
, s
->repo
, s
->id1
);
5203 err
= got_object_get_type(&obj_type
, s
->repo
, s
->id2
);
5208 case GOT_OBJ_TYPE_BLOB
:
5209 err
= got_diff_objects_as_blobs(&s
->lines
, &s
->nlines
,
5210 s
->f1
, s
->f2
, s
->fd1
, s
->fd2
, s
->id1
, s
->id2
,
5211 s
->label1
, s
->label2
, tog_diff_algo
, s
->diff_context
,
5212 s
->ignore_whitespace
, s
->force_text_diff
, NULL
, s
->repo
,
5215 case GOT_OBJ_TYPE_TREE
:
5216 err
= got_diff_objects_as_trees(&s
->lines
, &s
->nlines
,
5217 s
->f1
, s
->f2
, s
->fd1
, s
->fd2
, s
->id1
, s
->id2
, NULL
, "", "",
5218 tog_diff_algo
, s
->diff_context
, s
->ignore_whitespace
,
5219 s
->force_text_diff
, NULL
, s
->repo
, s
->f
);
5221 case GOT_OBJ_TYPE_COMMIT
: {
5222 const struct got_object_id_queue
*parent_ids
;
5223 struct got_object_qid
*pid
;
5224 struct got_commit_object
*commit2
;
5225 struct got_reflist_head
*refs
;
5227 struct got_diffstat_cb_arg dsa
= {
5230 s
->ignore_whitespace
,
5235 lines
= malloc(sizeof(*lines
));
5236 if (lines
== NULL
) {
5237 err
= got_error_from_errno("malloc");
5241 /* build diff first in tmp file then append to commit info */
5242 err
= got_diff_objects_as_commits(&lines
, &nlines
,
5243 s
->f1
, s
->f2
, s
->fd1
, s
->fd2
, s
->id1
, s
->id2
, NULL
,
5244 tog_diff_algo
, s
->diff_context
, s
->ignore_whitespace
,
5245 s
->force_text_diff
, &dsa
, s
->repo
, tmp_diff_file
);
5249 err
= got_object_open_as_commit(&commit2
, s
->repo
, s
->id2
);
5252 refs
= got_reflist_object_id_map_lookup(tog_refs_idmap
, s
->id2
);
5253 /* Show commit info if we're diffing to a parent/root commit. */
5254 if (s
->id1
== NULL
) {
5255 err
= write_commit_info(&s
->lines
, &s
->nlines
, s
->id2
,
5256 refs
, s
->repo
, s
->ignore_whitespace
,
5257 s
->force_text_diff
, &dsa
, s
->f
);
5261 parent_ids
= got_object_commit_get_parent_ids(commit2
);
5262 STAILQ_FOREACH(pid
, parent_ids
, entry
) {
5263 if (got_object_id_cmp(s
->id1
, &pid
->id
) == 0) {
5264 err
= write_commit_info(&s
->lines
,
5265 &s
->nlines
, s
->id2
, refs
, s
->repo
,
5266 s
->ignore_whitespace
,
5267 s
->force_text_diff
, &dsa
, s
->f
);
5274 got_object_commit_close(commit2
);
5276 err
= cat_diff(s
->f
, tmp_diff_file
, &s
->lines
, &s
->nlines
,
5281 err
= got_error(GOT_ERR_OBJ_TYPE
);
5286 got_pathlist_free(&changed_paths
, GOT_PATHLIST_FREE_ALL
);
5287 if (s
->f
&& fflush(s
->f
) != 0 && err
== NULL
)
5288 err
= got_error_from_errno("fflush");
5289 if (tmp_diff_file
&& fclose(tmp_diff_file
) == EOF
&& err
== NULL
)
5290 err
= got_error_from_errno("fclose");
5295 diff_view_indicate_progress(struct tog_view
*view
)
5297 mvwaddstr(view
->window
, 0, 0, "diffing...");
5302 static const struct got_error
*
5303 search_start_diff_view(struct tog_view
*view
)
5305 struct tog_diff_view_state
*s
= &view
->state
.diff
;
5307 s
->matched_line
= 0;
5312 search_setup_diff_view(struct tog_view
*view
, FILE **f
, off_t
**line_offsets
,
5313 size_t *nlines
, int **first
, int **last
, int **match
, int **selected
)
5315 struct tog_diff_view_state
*s
= &view
->state
.diff
;
5318 *nlines
= s
->nlines
;
5319 *line_offsets
= NULL
;
5320 *match
= &s
->matched_line
;
5321 *first
= &s
->first_displayed_line
;
5322 *last
= &s
->last_displayed_line
;
5323 *selected
= &s
->selected_line
;
5326 static const struct got_error
*
5327 search_next_view_match(struct tog_view
*view
)
5329 const struct got_error
*err
= NULL
;
5333 size_t linesize
= 0;
5335 off_t
*line_offsets
;
5337 int *first
, *last
, *match
, *selected
;
5339 if (!view
->search_setup
)
5340 return got_error_msg(GOT_ERR_NOT_IMPL
,
5341 "view search not supported");
5342 view
->search_setup(view
, &f
, &line_offsets
, &nlines
, &first
, &last
,
5345 if (!view
->searching
) {
5346 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
5351 if (view
->searching
== TOG_SEARCH_FORWARD
)
5352 lineno
= *first
+ 1;
5354 lineno
= *first
- 1;
5356 lineno
= *first
- 1 + *selected
;
5361 if (lineno
<= 0 || lineno
> nlines
) {
5363 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
5367 if (view
->searching
== TOG_SEARCH_FORWARD
)
5373 offset
= view
->type
== TOG_VIEW_DIFF
?
5374 view
->state
.diff
.lines
[lineno
- 1].offset
:
5375 line_offsets
[lineno
- 1];
5376 if (fseeko(f
, offset
, SEEK_SET
) != 0) {
5378 return got_error_from_errno("fseeko");
5380 linelen
= getline(&line
, &linesize
, f
);
5381 if (linelen
!= -1) {
5383 err
= expand_tab(&exstr
, line
);
5386 if (match_line(exstr
, &view
->regex
, 1,
5388 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
5395 if (view
->searching
== TOG_SEARCH_FORWARD
)
5410 static const struct got_error
*
5411 close_diff_view(struct tog_view
*view
)
5413 const struct got_error
*err
= NULL
;
5414 struct tog_diff_view_state
*s
= &view
->state
.diff
;
5420 if (s
->f
&& fclose(s
->f
) == EOF
)
5421 err
= got_error_from_errno("fclose");
5423 if (s
->f1
&& fclose(s
->f1
) == EOF
&& err
== NULL
)
5424 err
= got_error_from_errno("fclose");
5426 if (s
->f2
&& fclose(s
->f2
) == EOF
&& err
== NULL
)
5427 err
= got_error_from_errno("fclose");
5429 if (s
->fd1
!= -1 && close(s
->fd1
) == -1 && err
== NULL
)
5430 err
= got_error_from_errno("close");
5432 if (s
->fd2
!= -1 && close(s
->fd2
) == -1 && err
== NULL
)
5433 err
= got_error_from_errno("close");
5441 static const struct got_error
*
5442 open_diff_view(struct tog_view
*view
, struct got_object_id
*id1
,
5443 struct got_object_id
*id2
, const char *label1
, const char *label2
,
5444 int diff_context
, int ignore_whitespace
, int force_text_diff
,
5445 struct tog_view
*parent_view
, struct got_repository
*repo
)
5447 const struct got_error
*err
;
5448 struct tog_diff_view_state
*s
= &view
->state
.diff
;
5450 memset(s
, 0, sizeof(*s
));
5454 if (id1
!= NULL
&& id2
!= NULL
) {
5457 err
= got_object_get_type(&type1
, repo
, id1
);
5460 err
= got_object_get_type(&type2
, repo
, id2
);
5464 if (type1
!= type2
) {
5465 err
= got_error(GOT_ERR_OBJ_TYPE
);
5469 s
->first_displayed_line
= 1;
5470 s
->last_displayed_line
= view
->nlines
;
5471 s
->selected_line
= 1;
5479 s
->id1
= got_object_id_dup(id1
);
5480 if (s
->id1
== NULL
) {
5481 err
= got_error_from_errno("got_object_id_dup");
5487 s
->id2
= got_object_id_dup(id2
);
5488 if (s
->id2
== NULL
) {
5489 err
= got_error_from_errno("got_object_id_dup");
5493 s
->f1
= got_opentemp();
5494 if (s
->f1
== NULL
) {
5495 err
= got_error_from_errno("got_opentemp");
5499 s
->f2
= got_opentemp();
5500 if (s
->f2
== NULL
) {
5501 err
= got_error_from_errno("got_opentemp");
5505 s
->fd1
= got_opentempfd();
5507 err
= got_error_from_errno("got_opentempfd");
5511 s
->fd2
= got_opentempfd();
5513 err
= got_error_from_errno("got_opentempfd");
5517 s
->diff_context
= diff_context
;
5518 s
->ignore_whitespace
= ignore_whitespace
;
5519 s
->force_text_diff
= force_text_diff
;
5520 s
->parent_view
= parent_view
;
5523 if (has_colors() && getenv("TOG_COLORS") != NULL
&& !using_mock_io
) {
5526 rc
= init_pair(GOT_DIFF_LINE_MINUS
,
5527 get_color_value("TOG_COLOR_DIFF_MINUS"), -1);
5529 rc
= init_pair(GOT_DIFF_LINE_PLUS
,
5530 get_color_value("TOG_COLOR_DIFF_PLUS"), -1);
5532 rc
= init_pair(GOT_DIFF_LINE_HUNK
,
5533 get_color_value("TOG_COLOR_DIFF_CHUNK_HEADER"), -1);
5535 rc
= init_pair(GOT_DIFF_LINE_META
,
5536 get_color_value("TOG_COLOR_DIFF_META"), -1);
5538 rc
= init_pair(GOT_DIFF_LINE_CHANGES
,
5539 get_color_value("TOG_COLOR_DIFF_META"), -1);
5541 rc
= init_pair(GOT_DIFF_LINE_BLOB_MIN
,
5542 get_color_value("TOG_COLOR_DIFF_META"), -1);
5544 rc
= init_pair(GOT_DIFF_LINE_BLOB_PLUS
,
5545 get_color_value("TOG_COLOR_DIFF_META"), -1);
5547 rc
= init_pair(GOT_DIFF_LINE_AUTHOR
,
5548 get_color_value("TOG_COLOR_AUTHOR"), -1);
5550 rc
= init_pair(GOT_DIFF_LINE_DATE
,
5551 get_color_value("TOG_COLOR_DATE"), -1);
5553 err
= got_error(GOT_ERR_RANGE
);
5558 if (parent_view
&& parent_view
->type
== TOG_VIEW_LOG
&&
5559 view_is_splitscreen(view
))
5560 show_log_view(parent_view
); /* draw border */
5561 diff_view_indicate_progress(view
);
5563 err
= create_diff(s
);
5565 view
->show
= show_diff_view
;
5566 view
->input
= input_diff_view
;
5567 view
->reset
= reset_diff_view
;
5568 view
->close
= close_diff_view
;
5569 view
->search_start
= search_start_diff_view
;
5570 view
->search_setup
= search_setup_diff_view
;
5571 view
->search_next
= search_next_view_match
;
5574 if (view
->close
== NULL
)
5575 close_diff_view(view
);
5581 static const struct got_error
*
5582 show_diff_view(struct tog_view
*view
)
5584 const struct got_error
*err
;
5585 struct tog_diff_view_state
*s
= &view
->state
.diff
;
5586 char *id_str1
= NULL
, *id_str2
, *header
;
5587 const char *label1
, *label2
;
5590 err
= got_object_id_str(&id_str1
, s
->id1
);
5593 label1
= s
->label1
? s
->label1
: id_str1
;
5595 label1
= "/dev/null";
5597 err
= got_object_id_str(&id_str2
, s
->id2
);
5600 label2
= s
->label2
? s
->label2
: id_str2
;
5602 if (asprintf(&header
, "diff %s %s", label1
, label2
) == -1) {
5603 err
= got_error_from_errno("asprintf");
5611 err
= draw_file(view
, header
);
5616 static const struct got_error
*
5617 set_selected_commit(struct tog_diff_view_state
*s
,
5618 struct commit_queue_entry
*entry
)
5620 const struct got_error
*err
;
5621 const struct got_object_id_queue
*parent_ids
;
5622 struct got_commit_object
*selected_commit
;
5623 struct got_object_qid
*pid
;
5626 s
->id2
= got_object_id_dup(entry
->id
);
5628 return got_error_from_errno("got_object_id_dup");
5630 err
= got_object_open_as_commit(&selected_commit
, s
->repo
, entry
->id
);
5633 parent_ids
= got_object_commit_get_parent_ids(selected_commit
);
5635 pid
= STAILQ_FIRST(parent_ids
);
5636 s
->id1
= pid
? got_object_id_dup(&pid
->id
) : NULL
;
5637 got_object_commit_close(selected_commit
);
5641 static const struct got_error
*
5642 reset_diff_view(struct tog_view
*view
)
5644 struct tog_diff_view_state
*s
= &view
->state
.diff
;
5647 wclear(view
->window
);
5648 s
->first_displayed_line
= 1;
5649 s
->last_displayed_line
= view
->nlines
;
5650 s
->matched_line
= 0;
5651 diff_view_indicate_progress(view
);
5652 return create_diff(s
);
5656 diff_prev_index(struct tog_diff_view_state
*s
, enum got_diff_line_type type
)
5660 i
= start
= s
->first_displayed_line
- 1;
5662 while (s
->lines
[i
].type
!= type
) {
5666 return; /* do nothing, requested type not in file */
5669 s
->selected_line
= 1;
5670 s
->first_displayed_line
= i
;
5674 diff_next_index(struct tog_diff_view_state
*s
, enum got_diff_line_type type
)
5678 i
= start
= s
->first_displayed_line
+ 1;
5680 while (s
->lines
[i
].type
!= type
) {
5681 if (i
== s
->nlines
- 1)
5684 return; /* do nothing, requested type not in file */
5687 s
->selected_line
= 1;
5688 s
->first_displayed_line
= i
;
5691 static struct got_object_id
*get_selected_commit_id(struct tog_blame_line
*,
5693 static struct got_object_id
*get_annotation_for_line(struct tog_blame_line
*,
5696 static const struct got_error
*
5697 input_diff_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
5699 const struct got_error
*err
= NULL
;
5700 struct tog_diff_view_state
*s
= &view
->state
.diff
;
5701 struct tog_log_view_state
*ls
;
5702 struct commit_queue_entry
*old_selected_entry
;
5704 size_t linesize
= 0;
5706 int i
, nscroll
= view
->nlines
- 1, up
= 0;
5708 s
->lineno
= s
->first_displayed_line
- 1 + s
->selected_line
;
5717 horizontal_scroll_input(view
, ch
);
5722 s
->force_text_diff
= !s
->force_text_diff
;
5723 view
->action
= s
->force_text_diff
?
5724 "force ASCII text enabled" :
5725 "force ASCII text disabled";
5727 else if (ch
== 'w') {
5728 s
->ignore_whitespace
= !s
->ignore_whitespace
;
5729 view
->action
= s
->ignore_whitespace
?
5730 "ignore whitespace enabled" :
5731 "ignore whitespace disabled";
5733 err
= reset_diff_view(view
);
5737 s
->first_displayed_line
= 1;
5746 s
->first_displayed_line
= (s
->nlines
- view
->nlines
) + 2;
5752 if (s
->first_displayed_line
> 1)
5753 s
->first_displayed_line
--;
5764 if (s
->first_displayed_line
== 1) {
5769 while (i
++ < nscroll
&& s
->first_displayed_line
> 1)
5770 s
->first_displayed_line
--;
5776 s
->first_displayed_line
++;
5793 while (!s
->eof
&& i
++ < nscroll
) {
5794 linelen
= getline(&line
, &linesize
, s
->f
);
5795 s
->first_displayed_line
++;
5796 if (linelen
== -1) {
5800 err
= got_ferror(s
->f
, GOT_ERR_IO
);
5807 diff_prev_index(s
, GOT_DIFF_LINE_BLOB_MIN
);
5810 diff_next_index(s
, GOT_DIFF_LINE_BLOB_MIN
);
5813 diff_prev_index(s
, GOT_DIFF_LINE_HUNK
);
5816 diff_next_index(s
, GOT_DIFF_LINE_HUNK
);
5819 if (s
->diff_context
> 0) {
5821 s
->matched_line
= 0;
5822 diff_view_indicate_progress(view
);
5823 err
= create_diff(s
);
5824 if (s
->first_displayed_line
+ view
->nlines
- 1 >
5826 s
->first_displayed_line
= 1;
5827 s
->last_displayed_line
= view
->nlines
;
5833 if (s
->diff_context
< GOT_DIFF_MAX_CONTEXT
) {
5835 s
->matched_line
= 0;
5836 diff_view_indicate_progress(view
);
5837 err
= create_diff(s
);
5849 if (s
->parent_view
== NULL
) {
5853 s
->parent_view
->count
= view
->count
;
5855 if (s
->parent_view
->type
== TOG_VIEW_LOG
) {
5856 ls
= &s
->parent_view
->state
.log
;
5857 old_selected_entry
= ls
->selected_entry
;
5859 err
= input_log_view(NULL
, s
->parent_view
,
5860 up
? KEY_UP
: KEY_DOWN
);
5863 view
->count
= s
->parent_view
->count
;
5865 if (old_selected_entry
== ls
->selected_entry
)
5868 err
= set_selected_commit(s
, ls
->selected_entry
);
5871 } else if (s
->parent_view
->type
== TOG_VIEW_BLAME
) {
5872 struct tog_blame_view_state
*bs
;
5873 struct got_object_id
*id
, *prev_id
;
5875 bs
= &s
->parent_view
->state
.blame
;
5876 prev_id
= get_annotation_for_line(bs
->blame
.lines
,
5877 bs
->blame
.nlines
, bs
->last_diffed_line
);
5879 err
= input_blame_view(&view
, s
->parent_view
,
5880 up
? KEY_UP
: KEY_DOWN
);
5883 view
->count
= s
->parent_view
->count
;
5885 if (prev_id
== NULL
)
5887 id
= get_selected_commit_id(bs
->blame
.lines
,
5888 bs
->blame
.nlines
, bs
->first_displayed_line
,
5893 if (!got_object_id_cmp(prev_id
, id
))
5896 err
= input_blame_view(&view
, s
->parent_view
, KEY_ENTER
);
5900 s
->first_displayed_line
= 1;
5901 s
->last_displayed_line
= view
->nlines
;
5902 s
->matched_line
= 0;
5905 diff_view_indicate_progress(view
);
5906 err
= create_diff(s
);
5916 static const struct got_error
*
5917 cmd_diff(int argc
, char *argv
[])
5919 const struct got_error
*error
;
5920 struct got_repository
*repo
= NULL
;
5921 struct got_worktree
*worktree
= NULL
;
5922 struct got_object_id
*id1
= NULL
, *id2
= NULL
;
5923 char *repo_path
= NULL
, *cwd
= NULL
;
5924 char *id_str1
= NULL
, *id_str2
= NULL
;
5925 char *label1
= NULL
, *label2
= NULL
;
5926 int diff_context
= 3, ignore_whitespace
= 0;
5927 int ch
, force_text_diff
= 0;
5929 struct tog_view
*view
;
5930 int *pack_fds
= NULL
;
5932 while ((ch
= getopt(argc
, argv
, "aC:r:w")) != -1) {
5935 force_text_diff
= 1;
5938 diff_context
= strtonum(optarg
, 0, GOT_DIFF_MAX_CONTEXT
,
5941 errx(1, "number of context lines is %s: %s",
5945 repo_path
= realpath(optarg
, NULL
);
5946 if (repo_path
== NULL
)
5947 return got_error_from_errno2("realpath",
5949 got_path_strip_trailing_slashes(repo_path
);
5952 ignore_whitespace
= 1;
5964 usage_diff(); /* TODO show local worktree changes */
5965 } else if (argc
== 2) {
5971 error
= got_repo_pack_fds_open(&pack_fds
);
5975 if (repo_path
== NULL
) {
5976 cwd
= getcwd(NULL
, 0);
5978 return got_error_from_errno("getcwd");
5979 error
= got_worktree_open(&worktree
, cwd
);
5980 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
5984 strdup(got_worktree_get_repo_path(worktree
));
5986 repo_path
= strdup(cwd
);
5987 if (repo_path
== NULL
) {
5988 error
= got_error_from_errno("strdup");
5993 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
5999 error
= apply_unveil(got_repo_get_path(repo
), NULL
);
6003 error
= tog_load_refs(repo
, 0);
6007 error
= got_repo_match_object_id(&id1
, &label1
, id_str1
,
6008 GOT_OBJ_TYPE_ANY
, &tog_refs
, repo
);
6012 error
= got_repo_match_object_id(&id2
, &label2
, id_str2
,
6013 GOT_OBJ_TYPE_ANY
, &tog_refs
, repo
);
6017 view
= view_open(0, 0, 0, 0, TOG_VIEW_DIFF
);
6019 error
= got_error_from_errno("view_open");
6022 error
= open_diff_view(view
, id1
, id2
, label1
, label2
, diff_context
,
6023 ignore_whitespace
, force_text_diff
, NULL
, repo
);
6026 error
= view_loop(view
);
6033 const struct got_error
*close_err
= got_repo_close(repo
);
6038 got_worktree_close(worktree
);
6040 const struct got_error
*pack_err
=
6041 got_repo_pack_fds_close(pack_fds
);
6054 "usage: %s blame [-c commit] [-r repository-path] path\n",
6059 struct tog_blame_line
{
6061 struct got_object_id
*id
;
6064 static const struct got_error
*
6065 draw_blame(struct tog_view
*view
)
6067 struct tog_blame_view_state
*s
= &view
->state
.blame
;
6068 struct tog_blame
*blame
= &s
->blame
;
6069 regmatch_t
*regmatch
= &view
->regmatch
;
6070 const struct got_error
*err
;
6071 int lineno
= 0, nprinted
= 0;
6073 size_t linesize
= 0;
6077 struct tog_blame_line
*blame_line
;
6078 struct got_object_id
*prev_id
= NULL
;
6080 struct tog_color
*tc
;
6082 err
= got_object_id_str(&id_str
, &s
->blamed_commit
->id
);
6087 werase(view
->window
);
6089 if (asprintf(&line
, "commit %s", id_str
) == -1) {
6090 err
= got_error_from_errno("asprintf");
6095 err
= format_line(&wline
, &width
, NULL
, line
, 0, view
->ncols
, 0, 0);
6100 if (view_needs_focus_indication(view
))
6101 wstandout(view
->window
);
6102 tc
= get_color(&s
->colors
, TOG_COLOR_COMMIT
);
6104 wattr_on(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
6105 waddwstr(view
->window
, wline
);
6106 while (width
++ < view
->ncols
)
6107 waddch(view
->window
, ' ');
6109 wattr_off(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
6110 if (view_needs_focus_indication(view
))
6111 wstandend(view
->window
);
6115 if (view
->gline
> blame
->nlines
)
6116 view
->gline
= blame
->nlines
;
6118 if (tog_io
.wait_for_ui
) {
6119 struct tog_blame_thread_args
*bta
= &s
->blame
.thread_args
;
6122 rc
= pthread_cond_wait(&bta
->blame_complete
, &tog_mutex
);
6124 return got_error_set_errno(rc
, "pthread_cond_wait");
6125 tog_io
.wait_for_ui
= 0;
6128 if (asprintf(&line
, "[%d/%d] %s%s", view
->gline
? view
->gline
:
6129 s
->first_displayed_line
- 1 + s
->selected_line
, blame
->nlines
,
6130 s
->blame_complete
? "" : "annotating... ", s
->path
) == -1) {
6132 return got_error_from_errno("asprintf");
6135 err
= format_line(&wline
, &width
, NULL
, line
, 0, view
->ncols
, 0, 0);
6140 waddwstr(view
->window
, wline
);
6143 if (width
< view
->ncols
- 1)
6144 waddch(view
->window
, '\n');
6148 while (nprinted
< view
->nlines
- 2) {
6149 linelen
= getline(&line
, &linesize
, blame
->f
);
6150 if (linelen
== -1) {
6151 if (feof(blame
->f
)) {
6156 return got_ferror(blame
->f
, GOT_ERR_IO
);
6158 if (++lineno
< s
->first_displayed_line
)
6160 if (view
->gline
&& !gotoline(view
, &lineno
, &nprinted
))
6163 /* Set view->maxx based on full line length. */
6164 err
= format_line(&wline
, &width
, NULL
, line
, 0, INT_MAX
, 9, 1);
6171 view
->maxx
= MAX(view
->maxx
, width
);
6173 if (nprinted
== s
->selected_line
- 1)
6174 wstandout(view
->window
);
6176 if (blame
->nlines
> 0) {
6177 blame_line
= &blame
->lines
[lineno
- 1];
6178 if (blame_line
->annotated
&& prev_id
&&
6179 got_object_id_cmp(prev_id
, blame_line
->id
) == 0 &&
6180 !(nprinted
== s
->selected_line
- 1)) {
6181 waddstr(view
->window
, " ");
6182 } else if (blame_line
->annotated
) {
6184 err
= got_object_id_str(&id_str
,
6190 tc
= get_color(&s
->colors
, TOG_COLOR_COMMIT
);
6192 wattr_on(view
->window
,
6193 COLOR_PAIR(tc
->colorpair
), NULL
);
6194 wprintw(view
->window
, "%.8s", id_str
);
6196 wattr_off(view
->window
,
6197 COLOR_PAIR(tc
->colorpair
), NULL
);
6199 prev_id
= blame_line
->id
;
6201 waddstr(view
->window
, "........");
6205 waddstr(view
->window
, "........");
6209 if (nprinted
== s
->selected_line
- 1)
6210 wstandend(view
->window
);
6211 waddstr(view
->window
, " ");
6213 if (view
->ncols
<= 9) {
6215 } else if (s
->first_displayed_line
+ nprinted
==
6217 regmatch
->rm_so
>= 0 && regmatch
->rm_so
< regmatch
->rm_eo
) {
6218 err
= add_matched_line(&width
, line
, view
->ncols
- 9, 9,
6219 view
->window
, view
->x
, regmatch
);
6227 err
= format_line(&wline
, &width
, &skip
, line
,
6228 view
->x
, view
->ncols
- 9, 9, 1);
6233 waddwstr(view
->window
, &wline
[skip
]);
6239 if (width
<= view
->ncols
- 1)
6240 waddch(view
->window
, '\n');
6241 if (++nprinted
== 1)
6242 s
->first_displayed_line
= lineno
;
6245 s
->last_displayed_line
= lineno
;
6252 static const struct got_error
*
6253 blame_cb(void *arg
, int nlines
, int lineno
,
6254 struct got_commit_object
*commit
, struct got_object_id
*id
)
6256 const struct got_error
*err
= NULL
;
6257 struct tog_blame_cb_args
*a
= arg
;
6258 struct tog_blame_line
*line
;
6261 if (nlines
!= a
->nlines
||
6262 (lineno
!= -1 && lineno
< 1) || lineno
> a
->nlines
)
6263 return got_error(GOT_ERR_RANGE
);
6265 errcode
= pthread_mutex_lock(&tog_mutex
);
6267 return got_error_set_errno(errcode
, "pthread_mutex_lock");
6269 if (*a
->quit
) { /* user has quit the blame view */
6270 err
= got_error(GOT_ERR_ITER_COMPLETED
);
6275 goto done
; /* no change in this commit */
6277 line
= &a
->lines
[lineno
- 1];
6278 if (line
->annotated
)
6281 line
->id
= got_object_id_dup(id
);
6282 if (line
->id
== NULL
) {
6283 err
= got_error_from_errno("got_object_id_dup");
6286 line
->annotated
= 1;
6288 errcode
= pthread_mutex_unlock(&tog_mutex
);
6290 err
= got_error_set_errno(errcode
, "pthread_mutex_unlock");
6295 blame_thread(void *arg
)
6297 const struct got_error
*err
, *close_err
;
6298 struct tog_blame_thread_args
*ta
= arg
;
6299 struct tog_blame_cb_args
*a
= ta
->cb_args
;
6300 int errcode
, fd1
= -1, fd2
= -1;
6301 FILE *f1
= NULL
, *f2
= NULL
;
6303 fd1
= got_opentempfd();
6305 return (void *)got_error_from_errno("got_opentempfd");
6307 fd2
= got_opentempfd();
6309 err
= got_error_from_errno("got_opentempfd");
6313 f1
= got_opentemp();
6315 err
= (void *)got_error_from_errno("got_opentemp");
6318 f2
= got_opentemp();
6320 err
= (void *)got_error_from_errno("got_opentemp");
6324 err
= block_signals_used_by_main_thread();
6328 err
= got_blame(ta
->path
, a
->commit_id
, ta
->repo
,
6329 tog_diff_algo
, blame_cb
, ta
->cb_args
,
6330 ta
->cancel_cb
, ta
->cancel_arg
, fd1
, fd2
, f1
, f2
);
6331 if (err
&& err
->code
== GOT_ERR_CANCELLED
)
6334 errcode
= pthread_mutex_lock(&tog_mutex
);
6336 err
= got_error_set_errno(errcode
, "pthread_mutex_lock");
6340 close_err
= got_repo_close(ta
->repo
);
6346 if (tog_io
.wait_for_ui
) {
6347 errcode
= pthread_cond_signal(&ta
->blame_complete
);
6348 if (errcode
&& err
== NULL
)
6349 err
= got_error_set_errno(errcode
,
6350 "pthread_cond_signal");
6353 errcode
= pthread_mutex_unlock(&tog_mutex
);
6354 if (errcode
&& err
== NULL
)
6355 err
= got_error_set_errno(errcode
, "pthread_mutex_unlock");
6358 if (fd1
!= -1 && close(fd1
) == -1 && err
== NULL
)
6359 err
= got_error_from_errno("close");
6360 if (fd2
!= -1 && close(fd2
) == -1 && err
== NULL
)
6361 err
= got_error_from_errno("close");
6362 if (f1
&& fclose(f1
) == EOF
&& err
== NULL
)
6363 err
= got_error_from_errno("fclose");
6364 if (f2
&& fclose(f2
) == EOF
&& err
== NULL
)
6365 err
= got_error_from_errno("fclose");
6370 static struct got_object_id
*
6371 get_selected_commit_id(struct tog_blame_line
*lines
, int nlines
,
6372 int first_displayed_line
, int selected_line
)
6374 struct tog_blame_line
*line
;
6379 line
= &lines
[first_displayed_line
- 1 + selected_line
- 1];
6380 if (!line
->annotated
)
6386 static struct got_object_id
*
6387 get_annotation_for_line(struct tog_blame_line
*lines
, int nlines
,
6390 struct tog_blame_line
*line
;
6392 if (nlines
<= 0 || lineno
>= nlines
)
6395 line
= &lines
[lineno
- 1];
6396 if (!line
->annotated
)
6402 static const struct got_error
*
6403 stop_blame(struct tog_blame
*blame
)
6405 const struct got_error
*err
= NULL
;
6408 if (blame
->thread
) {
6410 errcode
= pthread_mutex_unlock(&tog_mutex
);
6412 return got_error_set_errno(errcode
,
6413 "pthread_mutex_unlock");
6414 errcode
= pthread_join(blame
->thread
, (void **)&err
);
6416 return got_error_set_errno(errcode
, "pthread_join");
6417 errcode
= pthread_mutex_lock(&tog_mutex
);
6419 return got_error_set_errno(errcode
,
6420 "pthread_mutex_lock");
6421 if (err
&& err
->code
== GOT_ERR_ITER_COMPLETED
)
6423 blame
->thread
= 0; //NULL;
6425 if (blame
->thread_args
.repo
) {
6426 const struct got_error
*close_err
;
6427 close_err
= got_repo_close(blame
->thread_args
.repo
);
6430 blame
->thread_args
.repo
= NULL
;
6433 if (fclose(blame
->f
) == EOF
&& err
== NULL
)
6434 err
= got_error_from_errno("fclose");
6438 for (i
= 0; i
< blame
->nlines
; i
++)
6439 free(blame
->lines
[i
].id
);
6441 blame
->lines
= NULL
;
6443 free(blame
->cb_args
.commit_id
);
6444 blame
->cb_args
.commit_id
= NULL
;
6445 if (blame
->pack_fds
) {
6446 const struct got_error
*pack_err
=
6447 got_repo_pack_fds_close(blame
->pack_fds
);
6450 blame
->pack_fds
= NULL
;
6455 static const struct got_error
*
6456 cancel_blame_view(void *arg
)
6458 const struct got_error
*err
= NULL
;
6462 errcode
= pthread_mutex_lock(&tog_mutex
);
6464 return got_error_set_errno(errcode
,
6465 "pthread_mutex_unlock");
6468 err
= got_error(GOT_ERR_CANCELLED
);
6470 errcode
= pthread_mutex_unlock(&tog_mutex
);
6472 return got_error_set_errno(errcode
,
6473 "pthread_mutex_lock");
6478 static const struct got_error
*
6479 run_blame(struct tog_view
*view
)
6481 struct tog_blame_view_state
*s
= &view
->state
.blame
;
6482 struct tog_blame
*blame
= &s
->blame
;
6483 const struct got_error
*err
= NULL
;
6484 struct got_commit_object
*commit
= NULL
;
6485 struct got_blob_object
*blob
= NULL
;
6486 struct got_repository
*thread_repo
= NULL
;
6487 struct got_object_id
*obj_id
= NULL
;
6488 int obj_type
, fd
= -1;
6489 int *pack_fds
= NULL
;
6491 err
= got_object_open_as_commit(&commit
, s
->repo
,
6492 &s
->blamed_commit
->id
);
6496 fd
= got_opentempfd();
6498 err
= got_error_from_errno("got_opentempfd");
6502 err
= got_object_id_by_path(&obj_id
, s
->repo
, commit
, s
->path
);
6506 err
= got_object_get_type(&obj_type
, s
->repo
, obj_id
);
6510 if (obj_type
!= GOT_OBJ_TYPE_BLOB
) {
6511 err
= got_error(GOT_ERR_OBJ_TYPE
);
6515 err
= got_object_open_as_blob(&blob
, s
->repo
, obj_id
, 8192, fd
);
6518 blame
->f
= got_opentemp();
6519 if (blame
->f
== NULL
) {
6520 err
= got_error_from_errno("got_opentemp");
6523 err
= got_object_blob_dump_to_file(&blame
->filesize
, &blame
->nlines
,
6524 &blame
->line_offsets
, blame
->f
, blob
);
6527 if (blame
->nlines
== 0) {
6528 s
->blame_complete
= 1;
6532 /* Don't include \n at EOF in the blame line count. */
6533 if (blame
->line_offsets
[blame
->nlines
- 1] == blame
->filesize
)
6536 blame
->lines
= calloc(blame
->nlines
, sizeof(*blame
->lines
));
6537 if (blame
->lines
== NULL
) {
6538 err
= got_error_from_errno("calloc");
6542 err
= got_repo_pack_fds_open(&pack_fds
);
6545 err
= got_repo_open(&thread_repo
, got_repo_get_path(s
->repo
), NULL
,
6550 blame
->pack_fds
= pack_fds
;
6551 blame
->cb_args
.view
= view
;
6552 blame
->cb_args
.lines
= blame
->lines
;
6553 blame
->cb_args
.nlines
= blame
->nlines
;
6554 blame
->cb_args
.commit_id
= got_object_id_dup(&s
->blamed_commit
->id
);
6555 if (blame
->cb_args
.commit_id
== NULL
) {
6556 err
= got_error_from_errno("got_object_id_dup");
6559 blame
->cb_args
.quit
= &s
->done
;
6561 blame
->thread_args
.path
= s
->path
;
6562 blame
->thread_args
.repo
= thread_repo
;
6563 blame
->thread_args
.cb_args
= &blame
->cb_args
;
6564 blame
->thread_args
.complete
= &s
->blame_complete
;
6565 blame
->thread_args
.cancel_cb
= cancel_blame_view
;
6566 blame
->thread_args
.cancel_arg
= &s
->done
;
6567 s
->blame_complete
= 0;
6569 if (s
->first_displayed_line
+ view
->nlines
- 1 > blame
->nlines
) {
6570 s
->first_displayed_line
= 1;
6571 s
->last_displayed_line
= view
->nlines
;
6572 s
->selected_line
= 1;
6574 s
->matched_line
= 0;
6578 got_object_commit_close(commit
);
6579 if (fd
!= -1 && close(fd
) == -1 && err
== NULL
)
6580 err
= got_error_from_errno("close");
6582 got_object_blob_close(blob
);
6589 static const struct got_error
*
6590 open_blame_view(struct tog_view
*view
, char *path
,
6591 struct got_object_id
*commit_id
, struct got_repository
*repo
)
6593 const struct got_error
*err
= NULL
;
6594 struct tog_blame_view_state
*s
= &view
->state
.blame
;
6596 STAILQ_INIT(&s
->blamed_commits
);
6598 s
->path
= strdup(path
);
6599 if (s
->path
== NULL
)
6600 return got_error_from_errno("strdup");
6602 err
= got_object_qid_alloc(&s
->blamed_commit
, commit_id
);
6608 STAILQ_INSERT_HEAD(&s
->blamed_commits
, s
->blamed_commit
, entry
);
6609 s
->first_displayed_line
= 1;
6610 s
->last_displayed_line
= view
->nlines
;
6611 s
->selected_line
= 1;
6612 s
->blame_complete
= 0;
6614 s
->commit_id
= commit_id
;
6615 memset(&s
->blame
, 0, sizeof(s
->blame
));
6617 STAILQ_INIT(&s
->colors
);
6618 if (has_colors() && getenv("TOG_COLORS") != NULL
) {
6619 err
= add_color(&s
->colors
, "^", TOG_COLOR_COMMIT
,
6620 get_color_value("TOG_COLOR_COMMIT"));
6625 view
->show
= show_blame_view
;
6626 view
->input
= input_blame_view
;
6627 view
->reset
= reset_blame_view
;
6628 view
->close
= close_blame_view
;
6629 view
->search_start
= search_start_blame_view
;
6630 view
->search_setup
= search_setup_blame_view
;
6631 view
->search_next
= search_next_view_match
;
6633 if (using_mock_io
) {
6634 struct tog_blame_thread_args
*bta
= &s
->blame
.thread_args
;
6637 rc
= pthread_cond_init(&bta
->blame_complete
, NULL
);
6639 return got_error_set_errno(rc
, "pthread_cond_init");
6642 return run_blame(view
);
6645 static const struct got_error
*
6646 close_blame_view(struct tog_view
*view
)
6648 const struct got_error
*err
= NULL
;
6649 struct tog_blame_view_state
*s
= &view
->state
.blame
;
6651 if (s
->blame
.thread
)
6652 err
= stop_blame(&s
->blame
);
6654 while (!STAILQ_EMPTY(&s
->blamed_commits
)) {
6655 struct got_object_qid
*blamed_commit
;
6656 blamed_commit
= STAILQ_FIRST(&s
->blamed_commits
);
6657 STAILQ_REMOVE_HEAD(&s
->blamed_commits
, entry
);
6658 got_object_qid_free(blamed_commit
);
6661 if (using_mock_io
) {
6662 struct tog_blame_thread_args
*bta
= &s
->blame
.thread_args
;
6665 rc
= pthread_cond_destroy(&bta
->blame_complete
);
6666 if (rc
&& err
== NULL
)
6667 err
= got_error_set_errno(rc
, "pthread_cond_destroy");
6671 free_colors(&s
->colors
);
6675 static const struct got_error
*
6676 search_start_blame_view(struct tog_view
*view
)
6678 struct tog_blame_view_state
*s
= &view
->state
.blame
;
6680 s
->matched_line
= 0;
6685 search_setup_blame_view(struct tog_view
*view
, FILE **f
, off_t
**line_offsets
,
6686 size_t *nlines
, int **first
, int **last
, int **match
, int **selected
)
6688 struct tog_blame_view_state
*s
= &view
->state
.blame
;
6691 *nlines
= s
->blame
.nlines
;
6692 *line_offsets
= s
->blame
.line_offsets
;
6693 *match
= &s
->matched_line
;
6694 *first
= &s
->first_displayed_line
;
6695 *last
= &s
->last_displayed_line
;
6696 *selected
= &s
->selected_line
;
6699 static const struct got_error
*
6700 show_blame_view(struct tog_view
*view
)
6702 const struct got_error
*err
= NULL
;
6703 struct tog_blame_view_state
*s
= &view
->state
.blame
;
6706 if (s
->blame
.thread
== 0 && !s
->blame_complete
) {
6707 errcode
= pthread_create(&s
->blame
.thread
, NULL
, blame_thread
,
6708 &s
->blame
.thread_args
);
6710 return got_error_set_errno(errcode
, "pthread_create");
6713 halfdelay(1); /* fast refresh while annotating */
6716 if (s
->blame_complete
&& !using_mock_io
)
6717 halfdelay(10); /* disable fast refresh */
6719 err
= draw_blame(view
);
6725 static const struct got_error
*
6726 log_annotated_line(struct tog_view
**new_view
, int begin_y
, int begin_x
,
6727 struct got_repository
*repo
, struct got_object_id
*id
)
6729 struct tog_view
*log_view
;
6730 const struct got_error
*err
= NULL
;
6734 log_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_LOG
);
6735 if (log_view
== NULL
)
6736 return got_error_from_errno("view_open");
6738 err
= open_log_view(log_view
, id
, repo
, GOT_REF_HEAD
, "", 0);
6740 view_close(log_view
);
6742 *new_view
= log_view
;
6747 static const struct got_error
*
6748 input_blame_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
6750 const struct got_error
*err
= NULL
, *thread_err
= NULL
;
6751 struct tog_view
*diff_view
;
6752 struct tog_blame_view_state
*s
= &view
->state
.blame
;
6753 int eos
, nscroll
, begin_y
= 0, begin_x
= 0;
6755 eos
= nscroll
= view
->nlines
- 2;
6756 if (view_is_hsplit_top(view
))
6766 horizontal_scroll_input(view
, ch
);
6773 s
->selected_line
= 1;
6774 s
->first_displayed_line
= 1;
6779 if (s
->blame
.nlines
< eos
) {
6780 s
->selected_line
= s
->blame
.nlines
;
6781 s
->first_displayed_line
= 1;
6783 s
->selected_line
= eos
;
6784 s
->first_displayed_line
= s
->blame
.nlines
- (eos
- 1);
6791 if (s
->selected_line
> 1)
6793 else if (s
->selected_line
== 1 &&
6794 s
->first_displayed_line
> 1)
6795 s
->first_displayed_line
--;
6806 if (s
->first_displayed_line
== 1) {
6807 if (view
->count
> 1)
6809 s
->selected_line
= MAX(1, s
->selected_line
- nscroll
);
6813 if (s
->first_displayed_line
> nscroll
)
6814 s
->first_displayed_line
-= nscroll
;
6816 s
->first_displayed_line
= 1;
6821 if (s
->selected_line
< eos
&& s
->first_displayed_line
+
6822 s
->selected_line
<= s
->blame
.nlines
)
6824 else if (s
->first_displayed_line
< s
->blame
.nlines
- (eos
- 1))
6825 s
->first_displayed_line
++;
6831 struct got_object_id
*id
= NULL
;
6834 id
= get_selected_commit_id(s
->blame
.lines
, s
->blame
.nlines
,
6835 s
->first_displayed_line
, s
->selected_line
);
6839 struct got_commit_object
*commit
, *pcommit
;
6840 struct got_object_qid
*pid
;
6841 struct got_object_id
*blob_id
= NULL
;
6843 err
= got_object_open_as_commit(&commit
,
6848 got_object_commit_get_parent_ids(commit
));
6850 got_object_commit_close(commit
);
6853 /* Check if path history ends here. */
6854 err
= got_object_open_as_commit(&pcommit
,
6858 err
= got_object_id_by_path(&blob_id
, s
->repo
,
6860 got_object_commit_close(pcommit
);
6862 if (err
->code
== GOT_ERR_NO_TREE_ENTRY
)
6864 got_object_commit_close(commit
);
6867 err
= got_object_get_type(&obj_type
, s
->repo
,
6870 /* Can't blame non-blob type objects. */
6871 if (obj_type
!= GOT_OBJ_TYPE_BLOB
) {
6872 got_object_commit_close(commit
);
6875 err
= got_object_qid_alloc(&s
->blamed_commit
,
6877 got_object_commit_close(commit
);
6879 if (got_object_id_cmp(id
,
6880 &s
->blamed_commit
->id
) == 0)
6882 err
= got_object_qid_alloc(&s
->blamed_commit
,
6888 thread_err
= stop_blame(&s
->blame
);
6892 STAILQ_INSERT_HEAD(&s
->blamed_commits
,
6893 s
->blamed_commit
, entry
);
6894 err
= run_blame(view
);
6900 struct got_object_qid
*first
;
6903 first
= STAILQ_FIRST(&s
->blamed_commits
);
6904 if (!got_object_id_cmp(&first
->id
, s
->commit_id
))
6907 thread_err
= stop_blame(&s
->blame
);
6911 STAILQ_REMOVE_HEAD(&s
->blamed_commits
, entry
);
6912 got_object_qid_free(s
->blamed_commit
);
6914 STAILQ_FIRST(&s
->blamed_commits
);
6915 err
= run_blame(view
);
6922 s
->id_to_log
= get_selected_commit_id(s
->blame
.lines
,
6923 s
->blame
.nlines
, s
->first_displayed_line
, s
->selected_line
);
6925 err
= view_request_new(new_view
, view
, TOG_VIEW_LOG
);
6929 struct got_object_id
*id
= NULL
;
6930 struct got_object_qid
*pid
;
6931 struct got_commit_object
*commit
= NULL
;
6934 id
= get_selected_commit_id(s
->blame
.lines
, s
->blame
.nlines
,
6935 s
->first_displayed_line
, s
->selected_line
);
6938 err
= got_object_open_as_commit(&commit
, s
->repo
, id
);
6941 pid
= STAILQ_FIRST(got_object_commit_get_parent_ids(commit
));
6943 /* traversed from diff view, release diff resources */
6944 err
= close_diff_view(*new_view
);
6947 diff_view
= *new_view
;
6949 if (view_is_parent_view(view
))
6950 view_get_split(view
, &begin_y
, &begin_x
);
6952 diff_view
= view_open(0, 0, begin_y
, begin_x
,
6954 if (diff_view
== NULL
) {
6955 got_object_commit_close(commit
);
6956 err
= got_error_from_errno("view_open");
6960 err
= open_diff_view(diff_view
, pid
? &pid
->id
: NULL
,
6961 id
, NULL
, NULL
, 3, 0, 0, view
, s
->repo
);
6962 got_object_commit_close(commit
);
6964 view_close(diff_view
);
6967 s
->last_diffed_line
= s
->first_displayed_line
- 1 +
6970 break; /* still open from active diff view */
6971 if (view_is_parent_view(view
) &&
6972 view
->mode
== TOG_VIEW_SPLIT_HRZN
) {
6973 err
= view_init_hsplit(view
, begin_y
);
6979 diff_view
->focussed
= 1;
6980 diff_view
->mode
= view
->mode
;
6981 diff_view
->nlines
= view
->lines
- begin_y
;
6982 if (view_is_parent_view(view
)) {
6983 view_transfer_size(diff_view
, view
);
6984 err
= view_close_child(view
);
6987 err
= view_set_child(view
, diff_view
);
6990 view
->focus_child
= 1;
6992 *new_view
= diff_view
;
7005 if (s
->last_displayed_line
>= s
->blame
.nlines
&&
7006 s
->selected_line
>= MIN(s
->blame
.nlines
,
7007 view
->nlines
- 2)) {
7011 if (s
->last_displayed_line
>= s
->blame
.nlines
&&
7012 s
->selected_line
< view
->nlines
- 2) {
7014 MIN(nscroll
, s
->last_displayed_line
-
7015 s
->first_displayed_line
- s
->selected_line
+ 1);
7017 if (s
->last_displayed_line
+ nscroll
<= s
->blame
.nlines
)
7018 s
->first_displayed_line
+= nscroll
;
7020 s
->first_displayed_line
=
7021 s
->blame
.nlines
- (view
->nlines
- 3);
7024 if (s
->selected_line
> view
->nlines
- 2) {
7025 s
->selected_line
= MIN(s
->blame
.nlines
,
7033 return thread_err
? thread_err
: err
;
7036 static const struct got_error
*
7037 reset_blame_view(struct tog_view
*view
)
7039 const struct got_error
*err
;
7040 struct tog_blame_view_state
*s
= &view
->state
.blame
;
7044 err
= stop_blame(&s
->blame
);
7048 return run_blame(view
);
7051 static const struct got_error
*
7052 cmd_blame(int argc
, char *argv
[])
7054 const struct got_error
*error
;
7055 struct got_repository
*repo
= NULL
;
7056 struct got_worktree
*worktree
= NULL
;
7057 char *cwd
= NULL
, *repo_path
= NULL
, *in_repo_path
= NULL
;
7058 char *link_target
= NULL
;
7059 struct got_object_id
*commit_id
= NULL
;
7060 struct got_commit_object
*commit
= NULL
;
7061 char *commit_id_str
= NULL
;
7063 struct tog_view
*view
= NULL
;
7064 int *pack_fds
= NULL
;
7066 while ((ch
= getopt(argc
, argv
, "c:r:")) != -1) {
7069 commit_id_str
= optarg
;
7072 repo_path
= realpath(optarg
, NULL
);
7073 if (repo_path
== NULL
)
7074 return got_error_from_errno2("realpath",
7089 error
= got_repo_pack_fds_open(&pack_fds
);
7093 if (repo_path
== NULL
) {
7094 cwd
= getcwd(NULL
, 0);
7096 return got_error_from_errno("getcwd");
7097 error
= got_worktree_open(&worktree
, cwd
);
7098 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
7102 strdup(got_worktree_get_repo_path(worktree
));
7104 repo_path
= strdup(cwd
);
7105 if (repo_path
== NULL
) {
7106 error
= got_error_from_errno("strdup");
7111 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
7115 error
= get_in_repo_path_from_argv0(&in_repo_path
, argc
, argv
, repo
,
7122 error
= apply_unveil(got_repo_get_path(repo
), NULL
);
7126 error
= tog_load_refs(repo
, 0);
7130 if (commit_id_str
== NULL
) {
7131 struct got_reference
*head_ref
;
7132 error
= got_ref_open(&head_ref
, repo
, worktree
?
7133 got_worktree_get_head_ref_name(worktree
) : GOT_REF_HEAD
, 0);
7136 error
= got_ref_resolve(&commit_id
, repo
, head_ref
);
7137 got_ref_close(head_ref
);
7139 error
= got_repo_match_object_id(&commit_id
, NULL
,
7140 commit_id_str
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
7145 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
7149 error
= got_object_resolve_symlinks(&link_target
, in_repo_path
,
7154 view
= view_open(0, 0, 0, 0, TOG_VIEW_BLAME
);
7156 error
= got_error_from_errno("view_open");
7159 error
= open_blame_view(view
, link_target
? link_target
: in_repo_path
,
7161 if (error
!= NULL
) {
7162 if (view
->close
== NULL
)
7163 close_blame_view(view
);
7168 /* Release work tree lock. */
7169 got_worktree_close(worktree
);
7172 error
= view_loop(view
);
7180 got_object_commit_close(commit
);
7182 got_worktree_close(worktree
);
7184 const struct got_error
*close_err
= got_repo_close(repo
);
7189 const struct got_error
*pack_err
=
7190 got_repo_pack_fds_close(pack_fds
);
7198 static const struct got_error
*
7199 draw_tree_entries(struct tog_view
*view
, const char *parent_path
)
7201 struct tog_tree_view_state
*s
= &view
->state
.tree
;
7202 const struct got_error
*err
= NULL
;
7203 struct got_tree_entry
*te
;
7206 struct tog_color
*tc
;
7207 int width
, n
, nentries
, scrollx
, i
= 1;
7208 int limit
= view
->nlines
;
7211 if (view_is_hsplit_top(view
))
7212 --limit
; /* border */
7214 werase(view
->window
);
7219 err
= format_line(&wline
, &width
, NULL
, s
->tree_label
, 0, view
->ncols
,
7223 if (view_needs_focus_indication(view
))
7224 wstandout(view
->window
);
7225 tc
= get_color(&s
->colors
, TOG_COLOR_COMMIT
);
7227 wattr_on(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
7228 waddwstr(view
->window
, wline
);
7231 while (width
++ < view
->ncols
)
7232 waddch(view
->window
, ' ');
7234 wattr_off(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
7235 if (view_needs_focus_indication(view
))
7236 wstandend(view
->window
);
7241 if (s
->first_displayed_entry
) {
7242 i
+= got_tree_entry_get_index(s
->first_displayed_entry
);
7243 if (s
->tree
!= s
->root
)
7244 ++i
; /* account for ".." entry */
7246 nentries
= got_object_tree_get_nentries(s
->tree
);
7247 if (asprintf(&index
, "[%d/%d] %s",
7248 i
, nentries
+ (s
->tree
== s
->root
? 0 : 1), parent_path
) == -1)
7249 return got_error_from_errno("asprintf");
7250 err
= format_line(&wline
, &width
, NULL
, index
, 0, view
->ncols
, 0, 0);
7254 waddwstr(view
->window
, wline
);
7257 if (width
< view
->ncols
- 1)
7258 waddch(view
->window
, '\n');
7261 waddch(view
->window
, '\n');
7265 if (s
->first_displayed_entry
== NULL
) {
7266 te
= got_object_tree_get_first_entry(s
->tree
);
7267 if (s
->selected
== 0) {
7269 wstandout(view
->window
);
7270 s
->selected_entry
= NULL
;
7272 waddstr(view
->window
, " ..\n"); /* parent directory */
7273 if (s
->selected
== 0 && view
->focussed
)
7274 wstandend(view
->window
);
7281 te
= s
->first_displayed_entry
;
7285 for (i
= got_tree_entry_get_index(te
); i
< nentries
; i
++) {
7286 char *line
= NULL
, *id_str
= NULL
, *link_target
= NULL
;
7287 const char *modestr
= "";
7290 te
= got_object_tree_get_entry(s
->tree
, i
);
7291 mode
= got_tree_entry_get_mode(te
);
7294 err
= got_object_id_str(&id_str
,
7295 got_tree_entry_get_id(te
));
7297 return got_error_from_errno(
7298 "got_object_id_str");
7300 if (got_object_tree_entry_is_submodule(te
))
7302 else if (S_ISLNK(mode
)) {
7305 err
= got_tree_entry_get_symlink_target(&link_target
,
7311 for (i
= 0; link_target
[i
] != '\0'; i
++) {
7312 if (!isprint((unsigned char)link_target
[i
]))
7313 link_target
[i
] = '?';
7317 else if (S_ISDIR(mode
))
7319 else if (mode
& S_IXUSR
)
7321 if (asprintf(&line
, "%s %s%s%s%s", id_str
? id_str
: "",
7322 got_tree_entry_get_name(te
), modestr
,
7323 link_target
? " -> ": "",
7324 link_target
? link_target
: "") == -1) {
7327 return got_error_from_errno("asprintf");
7332 /* use full line width to determine view->maxx */
7333 err
= format_line(&wline
, &width
, NULL
, line
, 0, INT_MAX
, 0, 0);
7338 view
->maxx
= MAX(view
->maxx
, width
);
7342 err
= format_line(&wline
, &width
, &scrollx
, line
, view
->x
,
7348 if (n
== s
->selected
) {
7350 wstandout(view
->window
);
7351 s
->selected_entry
= te
;
7353 tc
= match_color(&s
->colors
, line
);
7355 wattr_on(view
->window
,
7356 COLOR_PAIR(tc
->colorpair
), NULL
);
7357 waddwstr(view
->window
, &wline
[scrollx
]);
7359 wattr_off(view
->window
,
7360 COLOR_PAIR(tc
->colorpair
), NULL
);
7361 if (width
< view
->ncols
)
7362 waddch(view
->window
, '\n');
7363 if (n
== s
->selected
&& view
->focussed
)
7364 wstandend(view
->window
);
7370 s
->last_displayed_entry
= te
;
7379 tree_scroll_up(struct tog_tree_view_state
*s
, int maxscroll
)
7381 struct got_tree_entry
*te
;
7382 int isroot
= s
->tree
== s
->root
;
7385 if (s
->first_displayed_entry
== NULL
)
7388 te
= got_tree_entry_get_prev(s
->tree
, s
->first_displayed_entry
);
7389 while (i
++ < maxscroll
) {
7392 s
->first_displayed_entry
= NULL
;
7395 s
->first_displayed_entry
= te
;
7396 te
= got_tree_entry_get_prev(s
->tree
, te
);
7400 static const struct got_error
*
7401 tree_scroll_down(struct tog_view
*view
, int maxscroll
)
7403 struct tog_tree_view_state
*s
= &view
->state
.tree
;
7404 struct got_tree_entry
*next
, *last
;
7407 if (s
->first_displayed_entry
)
7408 next
= got_tree_entry_get_next(s
->tree
,
7409 s
->first_displayed_entry
);
7411 next
= got_object_tree_get_first_entry(s
->tree
);
7413 last
= s
->last_displayed_entry
;
7414 while (next
&& n
++ < maxscroll
) {
7416 s
->last_displayed_entry
= last
;
7417 last
= got_tree_entry_get_next(s
->tree
, last
);
7419 if (last
|| (view
->mode
== TOG_VIEW_SPLIT_HRZN
&& next
)) {
7420 s
->first_displayed_entry
= next
;
7421 next
= got_tree_entry_get_next(s
->tree
, next
);
7428 static const struct got_error
*
7429 tree_entry_path(char **path
, struct tog_parent_trees
*parents
,
7430 struct got_tree_entry
*te
)
7432 const struct got_error
*err
= NULL
;
7433 struct tog_parent_tree
*pt
;
7434 size_t len
= 2; /* for leading slash and NUL */
7436 TAILQ_FOREACH(pt
, parents
, entry
)
7437 len
+= strlen(got_tree_entry_get_name(pt
->selected_entry
))
7440 len
+= strlen(got_tree_entry_get_name(te
));
7442 *path
= calloc(1, len
);
7444 return got_error_from_errno("calloc");
7447 pt
= TAILQ_LAST(parents
, tog_parent_trees
);
7449 const char *name
= got_tree_entry_get_name(pt
->selected_entry
);
7450 if (strlcat(*path
, name
, len
) >= len
) {
7451 err
= got_error(GOT_ERR_NO_SPACE
);
7454 if (strlcat(*path
, "/", len
) >= len
) {
7455 err
= got_error(GOT_ERR_NO_SPACE
);
7458 pt
= TAILQ_PREV(pt
, tog_parent_trees
, entry
);
7461 if (strlcat(*path
, got_tree_entry_get_name(te
), len
) >= len
) {
7462 err
= got_error(GOT_ERR_NO_SPACE
);
7474 static const struct got_error
*
7475 blame_tree_entry(struct tog_view
**new_view
, int begin_y
, int begin_x
,
7476 struct got_tree_entry
*te
, struct tog_parent_trees
*parents
,
7477 struct got_object_id
*commit_id
, struct got_repository
*repo
)
7479 const struct got_error
*err
= NULL
;
7481 struct tog_view
*blame_view
;
7485 err
= tree_entry_path(&path
, parents
, te
);
7489 blame_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_BLAME
);
7490 if (blame_view
== NULL
) {
7491 err
= got_error_from_errno("view_open");
7495 err
= open_blame_view(blame_view
, path
, commit_id
, repo
);
7497 if (err
->code
== GOT_ERR_CANCELLED
)
7499 view_close(blame_view
);
7501 *new_view
= blame_view
;
7507 static const struct got_error
*
7508 log_selected_tree_entry(struct tog_view
**new_view
, int begin_y
, int begin_x
,
7509 struct tog_tree_view_state
*s
)
7511 struct tog_view
*log_view
;
7512 const struct got_error
*err
= NULL
;
7517 log_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_LOG
);
7518 if (log_view
== NULL
)
7519 return got_error_from_errno("view_open");
7521 err
= tree_entry_path(&path
, &s
->parents
, s
->selected_entry
);
7525 err
= open_log_view(log_view
, s
->commit_id
, s
->repo
, s
->head_ref_name
,
7528 view_close(log_view
);
7530 *new_view
= log_view
;
7535 static const struct got_error
*
7536 open_tree_view(struct tog_view
*view
, struct got_object_id
*commit_id
,
7537 const char *head_ref_name
, struct got_repository
*repo
)
7539 const struct got_error
*err
= NULL
;
7540 char *commit_id_str
= NULL
;
7541 struct tog_tree_view_state
*s
= &view
->state
.tree
;
7542 struct got_commit_object
*commit
= NULL
;
7544 TAILQ_INIT(&s
->parents
);
7545 STAILQ_INIT(&s
->colors
);
7547 s
->commit_id
= got_object_id_dup(commit_id
);
7548 if (s
->commit_id
== NULL
) {
7549 err
= got_error_from_errno("got_object_id_dup");
7553 err
= got_object_open_as_commit(&commit
, repo
, commit_id
);
7558 * The root is opened here and will be closed when the view is closed.
7559 * Any visited subtrees and their path-wise parents are opened and
7562 err
= got_object_open_as_tree(&s
->root
, repo
,
7563 got_object_commit_get_tree_id(commit
));
7568 err
= got_object_id_str(&commit_id_str
, commit_id
);
7572 if (asprintf(&s
->tree_label
, "commit %s", commit_id_str
) == -1) {
7573 err
= got_error_from_errno("asprintf");
7577 s
->first_displayed_entry
= got_object_tree_get_entry(s
->tree
, 0);
7578 s
->selected_entry
= got_object_tree_get_entry(s
->tree
, 0);
7579 if (head_ref_name
) {
7580 s
->head_ref_name
= strdup(head_ref_name
);
7581 if (s
->head_ref_name
== NULL
) {
7582 err
= got_error_from_errno("strdup");
7588 if (has_colors() && getenv("TOG_COLORS") != NULL
) {
7589 err
= add_color(&s
->colors
, "\\$$",
7590 TOG_COLOR_TREE_SUBMODULE
,
7591 get_color_value("TOG_COLOR_TREE_SUBMODULE"));
7594 err
= add_color(&s
->colors
, "@$", TOG_COLOR_TREE_SYMLINK
,
7595 get_color_value("TOG_COLOR_TREE_SYMLINK"));
7598 err
= add_color(&s
->colors
, "/$",
7599 TOG_COLOR_TREE_DIRECTORY
,
7600 get_color_value("TOG_COLOR_TREE_DIRECTORY"));
7604 err
= add_color(&s
->colors
, "\\*$",
7605 TOG_COLOR_TREE_EXECUTABLE
,
7606 get_color_value("TOG_COLOR_TREE_EXECUTABLE"));
7610 err
= add_color(&s
->colors
, "^$", TOG_COLOR_COMMIT
,
7611 get_color_value("TOG_COLOR_COMMIT"));
7616 view
->show
= show_tree_view
;
7617 view
->input
= input_tree_view
;
7618 view
->close
= close_tree_view
;
7619 view
->search_start
= search_start_tree_view
;
7620 view
->search_next
= search_next_tree_view
;
7622 free(commit_id_str
);
7624 got_object_commit_close(commit
);
7626 if (view
->close
== NULL
)
7627 close_tree_view(view
);
7633 static const struct got_error
*
7634 close_tree_view(struct tog_view
*view
)
7636 struct tog_tree_view_state
*s
= &view
->state
.tree
;
7638 free_colors(&s
->colors
);
7639 free(s
->tree_label
);
7640 s
->tree_label
= NULL
;
7642 s
->commit_id
= NULL
;
7643 free(s
->head_ref_name
);
7644 s
->head_ref_name
= NULL
;
7645 while (!TAILQ_EMPTY(&s
->parents
)) {
7646 struct tog_parent_tree
*parent
;
7647 parent
= TAILQ_FIRST(&s
->parents
);
7648 TAILQ_REMOVE(&s
->parents
, parent
, entry
);
7649 if (parent
->tree
!= s
->root
)
7650 got_object_tree_close(parent
->tree
);
7654 if (s
->tree
!= NULL
&& s
->tree
!= s
->root
)
7655 got_object_tree_close(s
->tree
);
7657 got_object_tree_close(s
->root
);
7661 static const struct got_error
*
7662 search_start_tree_view(struct tog_view
*view
)
7664 struct tog_tree_view_state
*s
= &view
->state
.tree
;
7666 s
->matched_entry
= NULL
;
7671 match_tree_entry(struct got_tree_entry
*te
, regex_t
*regex
)
7673 regmatch_t regmatch
;
7675 return regexec(regex
, got_tree_entry_get_name(te
), 1, ®match
,
7679 static const struct got_error
*
7680 search_next_tree_view(struct tog_view
*view
)
7682 struct tog_tree_view_state
*s
= &view
->state
.tree
;
7683 struct got_tree_entry
*te
= NULL
;
7685 if (!view
->searching
) {
7686 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
7690 if (s
->matched_entry
) {
7691 if (view
->searching
== TOG_SEARCH_FORWARD
) {
7692 if (s
->selected_entry
)
7693 te
= got_tree_entry_get_next(s
->tree
,
7696 te
= got_object_tree_get_first_entry(s
->tree
);
7698 if (s
->selected_entry
== NULL
)
7699 te
= got_object_tree_get_last_entry(s
->tree
);
7701 te
= got_tree_entry_get_prev(s
->tree
,
7705 if (s
->selected_entry
)
7706 te
= s
->selected_entry
;
7707 else if (view
->searching
== TOG_SEARCH_FORWARD
)
7708 te
= got_object_tree_get_first_entry(s
->tree
);
7710 te
= got_object_tree_get_last_entry(s
->tree
);
7715 if (s
->matched_entry
== NULL
) {
7716 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
7719 if (view
->searching
== TOG_SEARCH_FORWARD
)
7720 te
= got_object_tree_get_first_entry(s
->tree
);
7722 te
= got_object_tree_get_last_entry(s
->tree
);
7725 if (match_tree_entry(te
, &view
->regex
)) {
7726 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
7727 s
->matched_entry
= te
;
7731 if (view
->searching
== TOG_SEARCH_FORWARD
)
7732 te
= got_tree_entry_get_next(s
->tree
, te
);
7734 te
= got_tree_entry_get_prev(s
->tree
, te
);
7737 if (s
->matched_entry
) {
7738 s
->first_displayed_entry
= s
->matched_entry
;
7745 static const struct got_error
*
7746 show_tree_view(struct tog_view
*view
)
7748 const struct got_error
*err
= NULL
;
7749 struct tog_tree_view_state
*s
= &view
->state
.tree
;
7752 err
= tree_entry_path(&parent_path
, &s
->parents
, NULL
);
7756 err
= draw_tree_entries(view
, parent_path
);
7763 static const struct got_error
*
7764 tree_goto_line(struct tog_view
*view
, int nlines
)
7766 const struct got_error
*err
= NULL
;
7767 struct tog_tree_view_state
*s
= &view
->state
.tree
;
7768 struct got_tree_entry
**fte
, **lte
, **ste
;
7769 int g
, last
, first
= 1, i
= 1;
7770 int root
= s
->tree
== s
->root
;
7771 int off
= root
? 1 : 2;
7778 else if (g
> got_object_tree_get_nentries(s
->tree
))
7779 g
= got_object_tree_get_nentries(s
->tree
) + (root
? 0 : 1);
7781 fte
= &s
->first_displayed_entry
;
7782 lte
= &s
->last_displayed_entry
;
7783 ste
= &s
->selected_entry
;
7786 first
= got_tree_entry_get_index(*fte
);
7787 first
+= off
; /* account for ".." */
7789 last
= got_tree_entry_get_index(*lte
);
7792 if (g
>= first
&& g
<= last
&& g
- first
< nlines
) {
7793 s
->selected
= g
- first
;
7794 return NULL
; /* gline is on the current page */
7798 i
= got_tree_entry_get_index(*ste
);
7803 err
= tree_scroll_down(view
, g
- i
);
7806 if (got_tree_entry_get_index(*lte
) >=
7807 got_object_tree_get_nentries(s
->tree
) - 1 &&
7808 first
+ s
->selected
< g
&&
7809 s
->selected
< s
->ndisplayed
- 1) {
7810 first
= got_tree_entry_get_index(*fte
);
7812 s
->selected
= g
- first
;
7815 tree_scroll_up(s
, i
- g
);
7818 (*fte
== NULL
|| (root
&& !got_tree_entry_get_index(*fte
))))
7819 s
->selected
= g
- 1;
7824 static const struct got_error
*
7825 input_tree_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
7827 const struct got_error
*err
= NULL
;
7828 struct tog_tree_view_state
*s
= &view
->state
.tree
;
7829 struct got_tree_entry
*te
;
7830 int n
, nscroll
= view
->nlines
- 3;
7833 return tree_goto_line(view
, nscroll
);
7842 horizontal_scroll_input(view
, ch
);
7845 s
->show_ids
= !s
->show_ids
;
7850 if (!s
->selected_entry
)
7852 err
= view_request_new(new_view
, view
, TOG_VIEW_LOG
);
7856 err
= view_request_new(new_view
, view
, TOG_VIEW_REF
);
7863 if (s
->tree
== s
->root
)
7864 s
->first_displayed_entry
=
7865 got_object_tree_get_first_entry(s
->tree
);
7867 s
->first_displayed_entry
= NULL
;
7872 int eos
= view
->nlines
- 3;
7874 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
)
7878 te
= got_object_tree_get_last_entry(s
->tree
);
7879 for (n
= 0; n
< eos
; n
++) {
7881 if (s
->tree
!= s
->root
) {
7882 s
->first_displayed_entry
= NULL
;
7887 s
->first_displayed_entry
= te
;
7888 te
= got_tree_entry_get_prev(s
->tree
, te
);
7891 s
->selected
= n
- 1;
7897 if (s
->selected
> 0) {
7901 tree_scroll_up(s
, 1);
7902 if (s
->selected_entry
== NULL
||
7903 (s
->tree
== s
->root
&& s
->selected_entry
==
7904 got_object_tree_get_first_entry(s
->tree
)))
7914 if (s
->tree
== s
->root
) {
7915 if (got_object_tree_get_first_entry(s
->tree
) ==
7916 s
->first_displayed_entry
)
7917 s
->selected
-= MIN(s
->selected
, nscroll
);
7919 if (s
->first_displayed_entry
== NULL
)
7920 s
->selected
-= MIN(s
->selected
, nscroll
);
7922 tree_scroll_up(s
, MAX(0, nscroll
));
7923 if (s
->selected_entry
== NULL
||
7924 (s
->tree
== s
->root
&& s
->selected_entry
==
7925 got_object_tree_get_first_entry(s
->tree
)))
7931 if (s
->selected
< s
->ndisplayed
- 1) {
7935 if (got_tree_entry_get_next(s
->tree
, s
->last_displayed_entry
)
7937 /* can't scroll any further */
7941 tree_scroll_down(view
, 1);
7951 if (got_tree_entry_get_next(s
->tree
, s
->last_displayed_entry
)
7953 /* can't scroll any further; move cursor down */
7954 if (s
->selected
< s
->ndisplayed
- 1)
7955 s
->selected
+= MIN(nscroll
,
7956 s
->ndisplayed
- s
->selected
- 1);
7961 tree_scroll_down(view
, nscroll
);
7966 if (s
->selected_entry
== NULL
|| ch
== KEY_BACKSPACE
) {
7967 struct tog_parent_tree
*parent
;
7968 /* user selected '..' */
7969 if (s
->tree
== s
->root
) {
7973 parent
= TAILQ_FIRST(&s
->parents
);
7974 TAILQ_REMOVE(&s
->parents
, parent
,
7976 got_object_tree_close(s
->tree
);
7977 s
->tree
= parent
->tree
;
7978 s
->first_displayed_entry
=
7979 parent
->first_displayed_entry
;
7981 parent
->selected_entry
;
7982 s
->selected
= parent
->selected
;
7983 if (s
->selected
> view
->nlines
- 3) {
7984 err
= offset_selection_down(view
);
7989 } else if (S_ISDIR(got_tree_entry_get_mode(
7990 s
->selected_entry
))) {
7991 struct got_tree_object
*subtree
;
7993 err
= got_object_open_as_tree(&subtree
, s
->repo
,
7994 got_tree_entry_get_id(s
->selected_entry
));
7997 err
= tree_view_visit_subtree(s
, subtree
);
7999 got_object_tree_close(subtree
);
8002 } else if (S_ISREG(got_tree_entry_get_mode(s
->selected_entry
)))
8003 err
= view_request_new(new_view
, view
, TOG_VIEW_BLAME
);
8006 if (view
->nlines
>= 4 && s
->selected
>= view
->nlines
- 3)
8007 s
->selected
= view
->nlines
- 4;
8023 "usage: %s tree [-c commit] [-r repository-path] [path]\n",
8028 static const struct got_error
*
8029 cmd_tree(int argc
, char *argv
[])
8031 const struct got_error
*error
;
8032 struct got_repository
*repo
= NULL
;
8033 struct got_worktree
*worktree
= NULL
;
8034 char *cwd
= NULL
, *repo_path
= NULL
, *in_repo_path
= NULL
;
8035 struct got_object_id
*commit_id
= NULL
;
8036 struct got_commit_object
*commit
= NULL
;
8037 const char *commit_id_arg
= NULL
;
8039 struct got_reference
*ref
= NULL
;
8040 const char *head_ref_name
= NULL
;
8042 struct tog_view
*view
;
8043 int *pack_fds
= NULL
;
8045 while ((ch
= getopt(argc
, argv
, "c:r:")) != -1) {
8048 commit_id_arg
= optarg
;
8051 repo_path
= realpath(optarg
, NULL
);
8052 if (repo_path
== NULL
)
8053 return got_error_from_errno2("realpath",
8068 error
= got_repo_pack_fds_open(&pack_fds
);
8072 if (repo_path
== NULL
) {
8073 cwd
= getcwd(NULL
, 0);
8075 return got_error_from_errno("getcwd");
8076 error
= got_worktree_open(&worktree
, cwd
);
8077 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
8081 strdup(got_worktree_get_repo_path(worktree
));
8083 repo_path
= strdup(cwd
);
8084 if (repo_path
== NULL
) {
8085 error
= got_error_from_errno("strdup");
8090 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
8094 error
= get_in_repo_path_from_argv0(&in_repo_path
, argc
, argv
,
8101 error
= apply_unveil(got_repo_get_path(repo
), NULL
);
8105 error
= tog_load_refs(repo
, 0);
8109 if (commit_id_arg
== NULL
) {
8110 error
= got_repo_match_object_id(&commit_id
, &label
,
8111 worktree
? got_worktree_get_head_ref_name(worktree
) :
8112 GOT_REF_HEAD
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
8115 head_ref_name
= label
;
8117 error
= got_ref_open(&ref
, repo
, commit_id_arg
, 0);
8119 head_ref_name
= got_ref_get_name(ref
);
8120 else if (error
->code
!= GOT_ERR_NOT_REF
)
8122 error
= got_repo_match_object_id(&commit_id
, NULL
,
8123 commit_id_arg
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
8128 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
8132 view
= view_open(0, 0, 0, 0, TOG_VIEW_TREE
);
8134 error
= got_error_from_errno("view_open");
8137 error
= open_tree_view(view
, commit_id
, head_ref_name
, repo
);
8140 if (!got_path_is_root_dir(in_repo_path
)) {
8141 error
= tree_view_walk_path(&view
->state
.tree
, commit
,
8148 /* Release work tree lock. */
8149 got_worktree_close(worktree
);
8152 error
= view_loop(view
);
8161 const struct got_error
*close_err
= got_repo_close(repo
);
8166 const struct got_error
*pack_err
=
8167 got_repo_pack_fds_close(pack_fds
);
8175 static const struct got_error
*
8176 ref_view_load_refs(struct tog_ref_view_state
*s
)
8178 struct got_reflist_entry
*sre
;
8179 struct tog_reflist_entry
*re
;
8182 TAILQ_FOREACH(sre
, &tog_refs
, entry
) {
8183 if (strncmp(got_ref_get_name(sre
->ref
),
8184 "refs/got/", 9) == 0 &&
8185 strncmp(got_ref_get_name(sre
->ref
),
8186 "refs/got/backup/", 16) != 0)
8189 re
= malloc(sizeof(*re
));
8191 return got_error_from_errno("malloc");
8193 re
->ref
= got_ref_dup(sre
->ref
);
8194 if (re
->ref
== NULL
)
8195 return got_error_from_errno("got_ref_dup");
8196 re
->idx
= s
->nrefs
++;
8197 TAILQ_INSERT_TAIL(&s
->refs
, re
, entry
);
8200 s
->first_displayed_entry
= TAILQ_FIRST(&s
->refs
);
8205 ref_view_free_refs(struct tog_ref_view_state
*s
)
8207 struct tog_reflist_entry
*re
;
8209 while (!TAILQ_EMPTY(&s
->refs
)) {
8210 re
= TAILQ_FIRST(&s
->refs
);
8211 TAILQ_REMOVE(&s
->refs
, re
, entry
);
8212 got_ref_close(re
->ref
);
8217 static const struct got_error
*
8218 open_ref_view(struct tog_view
*view
, struct got_repository
*repo
)
8220 const struct got_error
*err
= NULL
;
8221 struct tog_ref_view_state
*s
= &view
->state
.ref
;
8223 s
->selected_entry
= 0;
8226 TAILQ_INIT(&s
->refs
);
8227 STAILQ_INIT(&s
->colors
);
8229 err
= ref_view_load_refs(s
);
8233 if (has_colors() && getenv("TOG_COLORS") != NULL
) {
8234 err
= add_color(&s
->colors
, "^refs/heads/",
8235 TOG_COLOR_REFS_HEADS
,
8236 get_color_value("TOG_COLOR_REFS_HEADS"));
8240 err
= add_color(&s
->colors
, "^refs/tags/",
8241 TOG_COLOR_REFS_TAGS
,
8242 get_color_value("TOG_COLOR_REFS_TAGS"));
8246 err
= add_color(&s
->colors
, "^refs/remotes/",
8247 TOG_COLOR_REFS_REMOTES
,
8248 get_color_value("TOG_COLOR_REFS_REMOTES"));
8252 err
= add_color(&s
->colors
, "^refs/got/backup/",
8253 TOG_COLOR_REFS_BACKUP
,
8254 get_color_value("TOG_COLOR_REFS_BACKUP"));
8259 view
->show
= show_ref_view
;
8260 view
->input
= input_ref_view
;
8261 view
->close
= close_ref_view
;
8262 view
->search_start
= search_start_ref_view
;
8263 view
->search_next
= search_next_ref_view
;
8266 if (view
->close
== NULL
)
8267 close_ref_view(view
);
8273 static const struct got_error
*
8274 close_ref_view(struct tog_view
*view
)
8276 struct tog_ref_view_state
*s
= &view
->state
.ref
;
8278 ref_view_free_refs(s
);
8279 free_colors(&s
->colors
);
8284 static const struct got_error
*
8285 resolve_reflist_entry(struct got_object_id
**commit_id
,
8286 struct tog_reflist_entry
*re
, struct got_repository
*repo
)
8288 const struct got_error
*err
= NULL
;
8289 struct got_object_id
*obj_id
;
8290 struct got_tag_object
*tag
= NULL
;
8295 err
= got_ref_resolve(&obj_id
, repo
, re
->ref
);
8299 err
= got_object_get_type(&obj_type
, repo
, obj_id
);
8304 case GOT_OBJ_TYPE_COMMIT
:
8305 *commit_id
= obj_id
;
8307 case GOT_OBJ_TYPE_TAG
:
8308 err
= got_object_open_as_tag(&tag
, repo
, obj_id
);
8312 err
= got_object_get_type(&obj_type
, repo
,
8313 got_object_tag_get_object_id(tag
));
8316 if (obj_type
!= GOT_OBJ_TYPE_COMMIT
) {
8317 err
= got_error(GOT_ERR_OBJ_TYPE
);
8320 *commit_id
= got_object_id_dup(
8321 got_object_tag_get_object_id(tag
));
8322 if (*commit_id
== NULL
) {
8323 err
= got_error_from_errno("got_object_id_dup");
8328 err
= got_error(GOT_ERR_OBJ_TYPE
);
8334 got_object_tag_close(tag
);
8342 static const struct got_error
*
8343 log_ref_entry(struct tog_view
**new_view
, int begin_y
, int begin_x
,
8344 struct tog_reflist_entry
*re
, struct got_repository
*repo
)
8346 struct tog_view
*log_view
;
8347 const struct got_error
*err
= NULL
;
8348 struct got_object_id
*commit_id
= NULL
;
8352 err
= resolve_reflist_entry(&commit_id
, re
, repo
);
8354 if (err
->code
!= GOT_ERR_OBJ_TYPE
)
8360 log_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_LOG
);
8361 if (log_view
== NULL
) {
8362 err
= got_error_from_errno("view_open");
8366 err
= open_log_view(log_view
, commit_id
, repo
,
8367 got_ref_get_name(re
->ref
), "", 0);
8370 view_close(log_view
);
8372 *new_view
= log_view
;
8378 ref_scroll_up(struct tog_ref_view_state
*s
, int maxscroll
)
8380 struct tog_reflist_entry
*re
;
8383 if (s
->first_displayed_entry
== TAILQ_FIRST(&s
->refs
))
8386 re
= TAILQ_PREV(s
->first_displayed_entry
, tog_reflist_head
, entry
);
8387 while (i
++ < maxscroll
) {
8390 s
->first_displayed_entry
= re
;
8391 re
= TAILQ_PREV(re
, tog_reflist_head
, entry
);
8395 static const struct got_error
*
8396 ref_scroll_down(struct tog_view
*view
, int maxscroll
)
8398 struct tog_ref_view_state
*s
= &view
->state
.ref
;
8399 struct tog_reflist_entry
*next
, *last
;
8402 if (s
->first_displayed_entry
)
8403 next
= TAILQ_NEXT(s
->first_displayed_entry
, entry
);
8405 next
= TAILQ_FIRST(&s
->refs
);
8407 last
= s
->last_displayed_entry
;
8408 while (next
&& n
++ < maxscroll
) {
8410 s
->last_displayed_entry
= last
;
8411 last
= TAILQ_NEXT(last
, entry
);
8413 if (last
|| (view
->mode
== TOG_VIEW_SPLIT_HRZN
)) {
8414 s
->first_displayed_entry
= next
;
8415 next
= TAILQ_NEXT(next
, entry
);
8422 static const struct got_error
*
8423 search_start_ref_view(struct tog_view
*view
)
8425 struct tog_ref_view_state
*s
= &view
->state
.ref
;
8427 s
->matched_entry
= NULL
;
8432 match_reflist_entry(struct tog_reflist_entry
*re
, regex_t
*regex
)
8434 regmatch_t regmatch
;
8436 return regexec(regex
, got_ref_get_name(re
->ref
), 1, ®match
,
8440 static const struct got_error
*
8441 search_next_ref_view(struct tog_view
*view
)
8443 struct tog_ref_view_state
*s
= &view
->state
.ref
;
8444 struct tog_reflist_entry
*re
= NULL
;
8446 if (!view
->searching
) {
8447 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
8451 if (s
->matched_entry
) {
8452 if (view
->searching
== TOG_SEARCH_FORWARD
) {
8453 if (s
->selected_entry
)
8454 re
= TAILQ_NEXT(s
->selected_entry
, entry
);
8456 re
= TAILQ_PREV(s
->selected_entry
,
8457 tog_reflist_head
, entry
);
8459 if (s
->selected_entry
== NULL
)
8460 re
= TAILQ_LAST(&s
->refs
, tog_reflist_head
);
8462 re
= TAILQ_PREV(s
->selected_entry
,
8463 tog_reflist_head
, entry
);
8466 if (s
->selected_entry
)
8467 re
= s
->selected_entry
;
8468 else if (view
->searching
== TOG_SEARCH_FORWARD
)
8469 re
= TAILQ_FIRST(&s
->refs
);
8471 re
= TAILQ_LAST(&s
->refs
, tog_reflist_head
);
8476 if (s
->matched_entry
== NULL
) {
8477 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
8480 if (view
->searching
== TOG_SEARCH_FORWARD
)
8481 re
= TAILQ_FIRST(&s
->refs
);
8483 re
= TAILQ_LAST(&s
->refs
, tog_reflist_head
);
8486 if (match_reflist_entry(re
, &view
->regex
)) {
8487 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
8488 s
->matched_entry
= re
;
8492 if (view
->searching
== TOG_SEARCH_FORWARD
)
8493 re
= TAILQ_NEXT(re
, entry
);
8495 re
= TAILQ_PREV(re
, tog_reflist_head
, entry
);
8498 if (s
->matched_entry
) {
8499 s
->first_displayed_entry
= s
->matched_entry
;
8506 static const struct got_error
*
8507 show_ref_view(struct tog_view
*view
)
8509 const struct got_error
*err
= NULL
;
8510 struct tog_ref_view_state
*s
= &view
->state
.ref
;
8511 struct tog_reflist_entry
*re
;
8514 struct tog_color
*tc
;
8515 int width
, n
, scrollx
;
8516 int limit
= view
->nlines
;
8518 werase(view
->window
);
8521 if (view_is_hsplit_top(view
))
8522 --limit
; /* border */
8527 re
= s
->first_displayed_entry
;
8529 if (asprintf(&line
, "references [%d/%d]", re
->idx
+ s
->selected
+ 1,
8531 return got_error_from_errno("asprintf");
8533 err
= format_line(&wline
, &width
, NULL
, line
, 0, view
->ncols
, 0, 0);
8538 if (view_needs_focus_indication(view
))
8539 wstandout(view
->window
);
8540 waddwstr(view
->window
, wline
);
8541 while (width
++ < view
->ncols
)
8542 waddch(view
->window
, ' ');
8543 if (view_needs_focus_indication(view
))
8544 wstandend(view
->window
);
8554 while (re
&& limit
> 0) {
8556 char ymd
[13]; /* YYYY-MM-DD + " " + NUL */
8559 struct got_commit_object
*ci
;
8560 struct got_tag_object
*tag
;
8561 struct got_object_id
*id
;
8565 err
= got_ref_resolve(&id
, s
->repo
, re
->ref
);
8568 err
= got_object_open_as_tag(&tag
, s
->repo
, id
);
8570 if (err
->code
!= GOT_ERR_OBJ_TYPE
) {
8574 err
= got_object_open_as_commit(&ci
, s
->repo
,
8580 t
= got_object_commit_get_committer_time(ci
);
8581 got_object_commit_close(ci
);
8583 t
= got_object_tag_get_tagger_time(tag
);
8584 got_object_tag_close(tag
);
8587 if (gmtime_r(&t
, &tm
) == NULL
)
8588 return got_error_from_errno("gmtime_r");
8589 if (strftime(ymd
, sizeof(ymd
), "%G-%m-%d ", &tm
) == 0)
8590 return got_error(GOT_ERR_NO_SPACE
);
8592 if (got_ref_is_symbolic(re
->ref
)) {
8593 if (asprintf(&line
, "%s%s -> %s", s
->show_date
?
8594 ymd
: "", got_ref_get_name(re
->ref
),
8595 got_ref_get_symref_target(re
->ref
)) == -1)
8596 return got_error_from_errno("asprintf");
8597 } else if (s
->show_ids
) {
8598 struct got_object_id
*id
;
8600 err
= got_ref_resolve(&id
, s
->repo
, re
->ref
);
8603 err
= got_object_id_str(&id_str
, id
);
8608 if (asprintf(&line
, "%s%s: %s", s
->show_date
? ymd
: "",
8609 got_ref_get_name(re
->ref
), id_str
) == -1) {
8610 err
= got_error_from_errno("asprintf");
8617 } else if (asprintf(&line
, "%s%s", s
->show_date
? ymd
: "",
8618 got_ref_get_name(re
->ref
)) == -1)
8619 return got_error_from_errno("asprintf");
8621 /* use full line width to determine view->maxx */
8622 err
= format_line(&wline
, &width
, NULL
, line
, 0, INT_MAX
, 0, 0);
8627 view
->maxx
= MAX(view
->maxx
, width
);
8631 err
= format_line(&wline
, &width
, &scrollx
, line
, view
->x
,
8637 if (n
== s
->selected
) {
8639 wstandout(view
->window
);
8640 s
->selected_entry
= re
;
8642 tc
= match_color(&s
->colors
, got_ref_get_name(re
->ref
));
8644 wattr_on(view
->window
,
8645 COLOR_PAIR(tc
->colorpair
), NULL
);
8646 waddwstr(view
->window
, &wline
[scrollx
]);
8648 wattr_off(view
->window
,
8649 COLOR_PAIR(tc
->colorpair
), NULL
);
8650 if (width
< view
->ncols
)
8651 waddch(view
->window
, '\n');
8652 if (n
== s
->selected
&& view
->focussed
)
8653 wstandend(view
->window
);
8659 s
->last_displayed_entry
= re
;
8662 re
= TAILQ_NEXT(re
, entry
);
8669 static const struct got_error
*
8670 browse_ref_tree(struct tog_view
**new_view
, int begin_y
, int begin_x
,
8671 struct tog_reflist_entry
*re
, struct got_repository
*repo
)
8673 const struct got_error
*err
= NULL
;
8674 struct got_object_id
*commit_id
= NULL
;
8675 struct tog_view
*tree_view
;
8679 err
= resolve_reflist_entry(&commit_id
, re
, repo
);
8681 if (err
->code
!= GOT_ERR_OBJ_TYPE
)
8688 tree_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_TREE
);
8689 if (tree_view
== NULL
) {
8690 err
= got_error_from_errno("view_open");
8694 err
= open_tree_view(tree_view
, commit_id
,
8695 got_ref_get_name(re
->ref
), repo
);
8699 *new_view
= tree_view
;
8705 static const struct got_error
*
8706 ref_goto_line(struct tog_view
*view
, int nlines
)
8708 const struct got_error
*err
= NULL
;
8709 struct tog_ref_view_state
*s
= &view
->state
.ref
;
8710 int g
, idx
= s
->selected_entry
->idx
;
8717 else if (g
> s
->nrefs
)
8720 if (g
>= s
->first_displayed_entry
->idx
+ 1 &&
8721 g
<= s
->last_displayed_entry
->idx
+ 1 &&
8722 g
- s
->first_displayed_entry
->idx
- 1 < nlines
) {
8723 s
->selected
= g
- s
->first_displayed_entry
->idx
- 1;
8728 err
= ref_scroll_down(view
, g
- idx
- 1);
8731 if (TAILQ_NEXT(s
->last_displayed_entry
, entry
) == NULL
&&
8732 s
->first_displayed_entry
->idx
+ s
->selected
< g
&&
8733 s
->selected
< s
->ndisplayed
- 1)
8734 s
->selected
= g
- s
->first_displayed_entry
->idx
- 1;
8735 } else if (idx
+ 1 > g
)
8736 ref_scroll_up(s
, idx
- g
+ 1);
8738 if (g
< nlines
&& s
->first_displayed_entry
->idx
== 0)
8739 s
->selected
= g
- 1;
8745 static const struct got_error
*
8746 input_ref_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
8748 const struct got_error
*err
= NULL
;
8749 struct tog_ref_view_state
*s
= &view
->state
.ref
;
8750 struct tog_reflist_entry
*re
;
8751 int n
, nscroll
= view
->nlines
- 1;
8754 return ref_goto_line(view
, nscroll
);
8763 horizontal_scroll_input(view
, ch
);
8766 s
->show_ids
= !s
->show_ids
;
8770 s
->show_date
= !s
->show_date
;
8774 s
->sort_by_date
= !s
->sort_by_date
;
8775 view
->action
= s
->sort_by_date
? "sort by date" : "sort by name";
8777 err
= got_reflist_sort(&tog_refs
, s
->sort_by_date
?
8778 got_ref_cmp_by_commit_timestamp_descending
:
8779 tog_ref_cmp_by_name
, s
->repo
);
8782 got_reflist_object_id_map_free(tog_refs_idmap
);
8783 err
= got_reflist_object_id_map_create(&tog_refs_idmap
,
8784 &tog_refs
, s
->repo
);
8787 ref_view_free_refs(s
);
8788 err
= ref_view_load_refs(s
);
8793 if (!s
->selected_entry
)
8795 err
= view_request_new(new_view
, view
, TOG_VIEW_LOG
);
8799 if (!s
->selected_entry
)
8801 err
= view_request_new(new_view
, view
, TOG_VIEW_TREE
);
8808 s
->first_displayed_entry
= TAILQ_FIRST(&s
->refs
);
8813 int eos
= view
->nlines
- 1;
8815 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
)
8819 re
= TAILQ_LAST(&s
->refs
, tog_reflist_head
);
8820 for (n
= 0; n
< eos
; n
++) {
8823 s
->first_displayed_entry
= re
;
8824 re
= TAILQ_PREV(re
, tog_reflist_head
, entry
);
8827 s
->selected
= n
- 1;
8833 if (s
->selected
> 0) {
8837 ref_scroll_up(s
, 1);
8838 if (s
->selected_entry
== TAILQ_FIRST(&s
->refs
))
8848 if (s
->first_displayed_entry
== TAILQ_FIRST(&s
->refs
))
8849 s
->selected
-= MIN(nscroll
, s
->selected
);
8850 ref_scroll_up(s
, MAX(0, nscroll
));
8851 if (s
->selected_entry
== TAILQ_FIRST(&s
->refs
))
8857 if (s
->selected
< s
->ndisplayed
- 1) {
8861 if (TAILQ_NEXT(s
->last_displayed_entry
, entry
) == NULL
) {
8862 /* can't scroll any further */
8866 ref_scroll_down(view
, 1);
8876 if (TAILQ_NEXT(s
->last_displayed_entry
, entry
) == NULL
) {
8877 /* can't scroll any further; move cursor down */
8878 if (s
->selected
< s
->ndisplayed
- 1)
8879 s
->selected
+= MIN(nscroll
,
8880 s
->ndisplayed
- s
->selected
- 1);
8881 if (view
->count
> 1 && s
->selected
< s
->ndisplayed
- 1)
8882 s
->selected
+= s
->ndisplayed
- s
->selected
- 1;
8886 ref_scroll_down(view
, nscroll
);
8891 err
= tog_load_refs(s
->repo
, s
->sort_by_date
);
8894 ref_view_free_refs(s
);
8895 err
= ref_view_load_refs(s
);
8898 if (view
->nlines
>= 2 && s
->selected
>= view
->nlines
- 1)
8899 s
->selected
= view
->nlines
- 2;
8913 fprintf(stderr
, "usage: %s ref [-r repository-path]\n",
8918 static const struct got_error
*
8919 cmd_ref(int argc
, char *argv
[])
8921 const struct got_error
*error
;
8922 struct got_repository
*repo
= NULL
;
8923 struct got_worktree
*worktree
= NULL
;
8924 char *cwd
= NULL
, *repo_path
= NULL
;
8926 struct tog_view
*view
;
8927 int *pack_fds
= NULL
;
8929 while ((ch
= getopt(argc
, argv
, "r:")) != -1) {
8932 repo_path
= realpath(optarg
, NULL
);
8933 if (repo_path
== NULL
)
8934 return got_error_from_errno2("realpath",
8949 error
= got_repo_pack_fds_open(&pack_fds
);
8953 if (repo_path
== NULL
) {
8954 cwd
= getcwd(NULL
, 0);
8956 return got_error_from_errno("getcwd");
8957 error
= got_worktree_open(&worktree
, cwd
);
8958 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
8962 strdup(got_worktree_get_repo_path(worktree
));
8964 repo_path
= strdup(cwd
);
8965 if (repo_path
== NULL
) {
8966 error
= got_error_from_errno("strdup");
8971 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
8977 error
= apply_unveil(got_repo_get_path(repo
), NULL
);
8981 error
= tog_load_refs(repo
, 0);
8985 view
= view_open(0, 0, 0, 0, TOG_VIEW_REF
);
8987 error
= got_error_from_errno("view_open");
8991 error
= open_ref_view(view
, repo
);
8996 /* Release work tree lock. */
8997 got_worktree_close(worktree
);
9000 error
= view_loop(view
);
9005 const struct got_error
*close_err
= got_repo_close(repo
);
9010 const struct got_error
*pack_err
=
9011 got_repo_pack_fds_close(pack_fds
);
9019 static const struct got_error
*
9020 win_draw_center(WINDOW
*win
, size_t y
, size_t x
, size_t maxx
, int focus
,
9029 x
= x
? x
: maxx
> len
? (maxx
- len
) / 2 : 0;
9033 if (mvwprintw(win
, y
, x
, "%s", str
) == ERR
)
9034 return got_error_msg(GOT_ERR_RANGE
, "mvwprintw");
9041 static const struct got_error
*
9042 add_line_offset(off_t
**line_offsets
, size_t *nlines
, off_t off
)
9046 p
= reallocarray(*line_offsets
, *nlines
+ 1, sizeof(off_t
));
9048 free(*line_offsets
);
9049 *line_offsets
= NULL
;
9050 return got_error_from_errno("reallocarray");
9054 (*line_offsets
)[*nlines
] = off
;
9059 static const struct got_error
*
9060 max_key_str(int *ret
, const struct tog_key_map
*km
, size_t n
)
9064 for (;n
> 0; --n
, ++km
) {
9068 if (km
->keys
== NULL
)
9071 t
= t0
= strdup(km
->keys
);
9073 return got_error_from_errno("strdup");
9076 while ((k
= strsep(&t
, " ")) != NULL
)
9077 len
+= strlen(k
) > 1 ? 2 : 0;
9079 *ret
= MAX(*ret
, len
);
9086 * Write keymap section headers, keys, and key info in km to f.
9087 * Save line offset to *off. If terminal has UTF8 encoding enabled,
9088 * wrap control and symbolic keys in guillemets, else use <>.
9090 static const struct got_error
*
9091 format_help_line(off_t
*off
, FILE *f
, const struct tog_key_map
*km
, int width
)
9096 static const char *u8_glyph
[] = {
9097 "\xe2\x80\xb9", /* U+2039 (utf8 <) */
9098 "\xe2\x80\xba" /* U+203A (utf8 >) */
9101 int cs
, s
, first
= 1;
9103 cs
= got_locale_is_utf8();
9105 t
= t0
= strdup(km
->keys
);
9107 return got_error_from_errno("strdup");
9109 len
= strlen(km
->keys
);
9110 while ((k
= strsep(&t
, " ")) != NULL
) {
9111 s
= strlen(k
) > 1; /* control or symbolic key */
9112 n
= fprintf(f
, "%s%s%s%s%s", first
? " " : "",
9113 cs
&& s
? u8_glyph
[0] : s
? "<" : "", k
,
9114 cs
&& s
? u8_glyph
[1] : s
? ">" : "", t
? " " : "");
9117 return got_error_from_errno("fprintf");
9125 n
= fprintf(f
, "%*s%s\n", width
- len
, width
- len
? " " : "", km
->info
);
9127 return got_error_from_errno("fprintf");
9133 static const struct got_error
*
9134 format_help(struct tog_help_view_state
*s
)
9136 const struct got_error
*err
= NULL
;
9138 int i
, max
, n
, show
= s
->all
;
9139 static const struct tog_key_map km
[] = {
9140 #define KEYMAP_(info, type) { NULL, (info), type }
9141 #define KEY_(keys, info) { (keys), (info), TOG_KEYMAP_KEYS }
9147 err
= add_line_offset(&s
->line_offsets
, &s
->nlines
, 0);
9152 err
= max_key_str(&max
, km
, n
);
9156 for (i
= 0; i
< n
; ++i
) {
9157 if (km
[i
].keys
== NULL
) {
9159 if (km
[i
].type
== TOG_KEYMAP_GLOBAL
||
9160 km
[i
].type
== s
->type
|| s
->all
)
9164 err
= format_help_line(&off
, s
->f
, &km
[i
], max
);
9167 err
= add_line_offset(&s
->line_offsets
, &s
->nlines
, off
);
9174 err
= add_line_offset(&s
->line_offsets
, &s
->nlines
, off
);
9178 static const struct got_error
*
9179 create_help(struct tog_help_view_state
*s
)
9182 const struct got_error
*err
;
9184 free(s
->line_offsets
);
9185 s
->line_offsets
= NULL
;
9190 return got_error_from_errno("got_opentemp");
9193 err
= format_help(s
);
9197 if (s
->f
&& fflush(s
->f
) != 0)
9198 return got_error_from_errno("fflush");
9203 static const struct got_error
*
9204 search_start_help_view(struct tog_view
*view
)
9206 view
->state
.help
.matched_line
= 0;
9211 search_setup_help_view(struct tog_view
*view
, FILE **f
, off_t
**line_offsets
,
9212 size_t *nlines
, int **first
, int **last
, int **match
, int **selected
)
9214 struct tog_help_view_state
*s
= &view
->state
.help
;
9217 *nlines
= s
->nlines
;
9218 *line_offsets
= s
->line_offsets
;
9219 *match
= &s
->matched_line
;
9220 *first
= &s
->first_displayed_line
;
9221 *last
= &s
->last_displayed_line
;
9222 *selected
= &s
->selected_line
;
9225 static const struct got_error
*
9226 show_help_view(struct tog_view
*view
)
9228 struct tog_help_view_state
*s
= &view
->state
.help
;
9229 const struct got_error
*err
;
9230 regmatch_t
*regmatch
= &view
->regmatch
;
9235 int width
, nprinted
= 0, rc
= 0;
9236 int eos
= view
->nlines
;
9238 if (view_is_hsplit_top(view
))
9239 --eos
; /* account for border */
9243 werase(view
->window
);
9245 if (view
->gline
> s
->nlines
- 1)
9246 view
->gline
= s
->nlines
- 1;
9248 err
= win_draw_center(view
->window
, 0, 0, view
->ncols
,
9249 view_needs_focus_indication(view
),
9250 "tog help (press q to return to tog)");
9255 waddstr(view
->window
, "\n\n");
9261 while (eos
> 0 && nprinted
< eos
) {
9264 linelen
= getline(&line
, &linesz
, s
->f
);
9265 if (linelen
== -1) {
9268 return got_ferror(s
->f
, GOT_ERR_IO
);
9273 if (++s
->lineno
< s
->first_displayed_line
)
9275 if (view
->gline
&& !gotoline(view
, &s
->lineno
, &nprinted
))
9277 if (s
->lineno
== view
->hiline
)
9280 err
= format_line(&wline
, &width
, NULL
, line
, 0, INT_MAX
, 0,
9286 view
->maxx
= MAX(view
->maxx
, width
);
9291 wattron(view
->window
, attr
);
9292 if (s
->first_displayed_line
+ nprinted
== s
->matched_line
&&
9293 regmatch
->rm_so
>= 0 && regmatch
->rm_so
< regmatch
->rm_eo
) {
9294 err
= add_matched_line(&width
, line
, view
->ncols
- 1, 0,
9295 view
->window
, view
->x
, regmatch
);
9303 err
= format_line(&wline
, &width
, &skip
, line
,
9304 view
->x
, view
->ncols
, 0, view
->x
? 1 : 0);
9309 waddwstr(view
->window
, &wline
[skip
]);
9313 if (s
->lineno
== view
->hiline
) {
9314 while (width
++ < view
->ncols
)
9315 waddch(view
->window
, ' ');
9317 if (width
< view
->ncols
)
9318 waddch(view
->window
, '\n');
9321 wattroff(view
->window
, attr
);
9322 if (++nprinted
== 1)
9323 s
->first_displayed_line
= s
->lineno
;
9327 s
->last_displayed_line
= s
->first_displayed_line
+ nprinted
- 1;
9329 s
->last_displayed_line
= s
->first_displayed_line
;
9334 rc
= waddnstr(view
->window
,
9335 "See the tog(1) manual page for full documentation",
9338 return got_error_msg(GOT_ERR_RANGE
, "waddnstr");
9340 wmove(view
->window
, view
->nlines
- 1, 0);
9341 wclrtoeol(view
->window
);
9342 wstandout(view
->window
);
9343 rc
= waddnstr(view
->window
, "scroll down for more...",
9346 return got_error_msg(GOT_ERR_RANGE
, "waddnstr");
9347 if (getcurx(view
->window
) < view
->ncols
- 6) {
9348 rc
= wprintw(view
->window
, "[%.0f%%]",
9349 100.00 * s
->last_displayed_line
/ s
->nlines
);
9351 return got_error_msg(GOT_ERR_IO
, "wprintw");
9353 wstandend(view
->window
);
9359 static const struct got_error
*
9360 input_help_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
9362 struct tog_help_view_state
*s
= &view
->state
.help
;
9363 const struct got_error
*err
= NULL
;
9369 eos
= nscroll
= view
->nlines
;
9370 if (view_is_hsplit_top(view
))
9373 s
->lineno
= s
->first_displayed_line
- 1 + s
->selected_line
;
9382 horizontal_scroll_input(view
, ch
);
9386 s
->first_displayed_line
= 1;
9394 s
->first_displayed_line
= (s
->nlines
- eos
) + 3;
9399 if (s
->first_displayed_line
> 1)
9400 --s
->first_displayed_line
;
9411 if (s
->first_displayed_line
== 1) {
9415 while (--nscroll
> 0 && s
->first_displayed_line
> 1)
9416 s
->first_displayed_line
--;
9422 ++s
->first_displayed_line
;
9438 while (!s
->eof
&& --nscroll
> 0) {
9439 linelen
= getline(&line
, &linesz
, s
->f
);
9440 s
->first_displayed_line
++;
9441 if (linelen
== -1) {
9445 err
= got_ferror(s
->f
, GOT_ERR_IO
);
9459 static const struct got_error
*
9460 close_help_view(struct tog_view
*view
)
9462 struct tog_help_view_state
*s
= &view
->state
.help
;
9464 free(s
->line_offsets
);
9465 s
->line_offsets
= NULL
;
9466 if (fclose(s
->f
) == EOF
)
9467 return got_error_from_errno("fclose");
9472 static const struct got_error
*
9473 reset_help_view(struct tog_view
*view
)
9475 struct tog_help_view_state
*s
= &view
->state
.help
;
9478 if (s
->f
&& fclose(s
->f
) == EOF
)
9479 return got_error_from_errno("fclose");
9481 wclear(view
->window
);
9485 s
->first_displayed_line
= 1;
9486 s
->last_displayed_line
= view
->nlines
;
9487 s
->matched_line
= 0;
9489 return create_help(s
);
9492 static const struct got_error
*
9493 open_help_view(struct tog_view
*view
, struct tog_view
*parent
)
9495 const struct got_error
*err
= NULL
;
9496 struct tog_help_view_state
*s
= &view
->state
.help
;
9498 s
->type
= (enum tog_keymap_type
)parent
->type
;
9499 s
->first_displayed_line
= 1;
9500 s
->last_displayed_line
= view
->nlines
;
9501 s
->selected_line
= 1;
9503 view
->show
= show_help_view
;
9504 view
->input
= input_help_view
;
9505 view
->reset
= reset_help_view
;
9506 view
->close
= close_help_view
;
9507 view
->search_start
= search_start_help_view
;
9508 view
->search_setup
= search_setup_help_view
;
9509 view
->search_next
= search_next_view_match
;
9511 err
= create_help(s
);
9515 static const struct got_error
*
9516 view_dispatch_request(struct tog_view
**new_view
, struct tog_view
*view
,
9517 enum tog_view_type request
, int y
, int x
)
9519 const struct got_error
*err
= NULL
;
9525 if (view
->type
== TOG_VIEW_LOG
) {
9526 struct tog_log_view_state
*s
= &view
->state
.log
;
9528 err
= open_diff_view_for_commit(new_view
, y
, x
,
9529 s
->selected_entry
->commit
, s
->selected_entry
->id
,
9532 return got_error_msg(GOT_ERR_NOT_IMPL
,
9533 "parent/child view pair not supported");
9535 case TOG_VIEW_BLAME
:
9536 if (view
->type
== TOG_VIEW_TREE
) {
9537 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9539 err
= blame_tree_entry(new_view
, y
, x
,
9540 s
->selected_entry
, &s
->parents
, s
->commit_id
,
9543 return got_error_msg(GOT_ERR_NOT_IMPL
,
9544 "parent/child view pair not supported");
9547 if (view
->type
== TOG_VIEW_BLAME
)
9548 err
= log_annotated_line(new_view
, y
, x
,
9549 view
->state
.blame
.repo
, view
->state
.blame
.id_to_log
);
9550 else if (view
->type
== TOG_VIEW_TREE
)
9551 err
= log_selected_tree_entry(new_view
, y
, x
,
9553 else if (view
->type
== TOG_VIEW_REF
)
9554 err
= log_ref_entry(new_view
, y
, x
,
9555 view
->state
.ref
.selected_entry
,
9556 view
->state
.ref
.repo
);
9558 return got_error_msg(GOT_ERR_NOT_IMPL
,
9559 "parent/child view pair not supported");
9562 if (view
->type
== TOG_VIEW_LOG
)
9563 err
= browse_commit_tree(new_view
, y
, x
,
9564 view
->state
.log
.selected_entry
,
9565 view
->state
.log
.in_repo_path
,
9566 view
->state
.log
.head_ref_name
,
9567 view
->state
.log
.repo
);
9568 else if (view
->type
== TOG_VIEW_REF
)
9569 err
= browse_ref_tree(new_view
, y
, x
,
9570 view
->state
.ref
.selected_entry
,
9571 view
->state
.ref
.repo
);
9573 return got_error_msg(GOT_ERR_NOT_IMPL
,
9574 "parent/child view pair not supported");
9577 *new_view
= view_open(0, 0, y
, x
, TOG_VIEW_REF
);
9578 if (*new_view
== NULL
)
9579 return got_error_from_errno("view_open");
9580 if (view
->type
== TOG_VIEW_LOG
)
9581 err
= open_ref_view(*new_view
, view
->state
.log
.repo
);
9582 else if (view
->type
== TOG_VIEW_TREE
)
9583 err
= open_ref_view(*new_view
, view
->state
.tree
.repo
);
9585 err
= got_error_msg(GOT_ERR_NOT_IMPL
,
9586 "parent/child view pair not supported");
9588 view_close(*new_view
);
9591 *new_view
= view_open(0, 0, 0, 0, TOG_VIEW_HELP
);
9592 if (*new_view
== NULL
)
9593 return got_error_from_errno("view_open");
9594 err
= open_help_view(*new_view
, view
);
9596 view_close(*new_view
);
9599 return got_error_msg(GOT_ERR_NOT_IMPL
, "invalid view");
9606 * If view was scrolled down to move the selected line into view when opening a
9607 * horizontal split, scroll back up when closing the split/toggling fullscreen.
9610 offset_selection_up(struct tog_view
*view
)
9612 switch (view
->type
) {
9613 case TOG_VIEW_BLAME
: {
9614 struct tog_blame_view_state
*s
= &view
->state
.blame
;
9615 if (s
->first_displayed_line
== 1) {
9616 s
->selected_line
= MAX(s
->selected_line
- view
->offset
,
9620 if (s
->first_displayed_line
> view
->offset
)
9621 s
->first_displayed_line
-= view
->offset
;
9623 s
->first_displayed_line
= 1;
9624 s
->selected_line
+= view
->offset
;
9628 log_scroll_up(&view
->state
.log
, view
->offset
);
9629 view
->state
.log
.selected
+= view
->offset
;
9632 ref_scroll_up(&view
->state
.ref
, view
->offset
);
9633 view
->state
.ref
.selected
+= view
->offset
;
9636 tree_scroll_up(&view
->state
.tree
, view
->offset
);
9637 view
->state
.tree
.selected
+= view
->offset
;
9647 * If the selected line is in the section of screen covered by the bottom split,
9648 * scroll down offset lines to move it into view and index its new position.
9650 static const struct got_error
*
9651 offset_selection_down(struct tog_view
*view
)
9653 const struct got_error
*err
= NULL
;
9654 const struct got_error
*(*scrolld
)(struct tog_view
*, int);
9655 int *selected
= NULL
;
9658 switch (view
->type
) {
9659 case TOG_VIEW_BLAME
: {
9660 struct tog_blame_view_state
*s
= &view
->state
.blame
;
9663 if (s
->selected_line
> view
->nlines
- header
) {
9664 offset
= abs(view
->nlines
- s
->selected_line
- header
);
9665 s
->first_displayed_line
+= offset
;
9666 s
->selected_line
-= offset
;
9667 view
->offset
= offset
;
9671 case TOG_VIEW_LOG
: {
9672 struct tog_log_view_state
*s
= &view
->state
.log
;
9673 scrolld
= &log_scroll_down
;
9674 header
= view_is_parent_view(view
) ? 3 : 2;
9675 selected
= &s
->selected
;
9678 case TOG_VIEW_REF
: {
9679 struct tog_ref_view_state
*s
= &view
->state
.ref
;
9680 scrolld
= &ref_scroll_down
;
9682 selected
= &s
->selected
;
9685 case TOG_VIEW_TREE
: {
9686 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9687 scrolld
= &tree_scroll_down
;
9689 selected
= &s
->selected
;
9699 if (selected
&& *selected
> view
->nlines
- header
) {
9700 offset
= abs(view
->nlines
- *selected
- header
);
9701 view
->offset
= offset
;
9702 if (scrolld
&& offset
) {
9703 err
= scrolld(view
, offset
);
9704 *selected
-= offset
;
9712 list_commands(FILE *fp
)
9716 fprintf(fp
, "commands:");
9717 for (i
= 0; i
< nitems(tog_commands
); i
++) {
9718 const struct tog_cmd
*cmd
= &tog_commands
[i
];
9719 fprintf(fp
, " %s", cmd
->name
);
9725 usage(int hflag
, int status
)
9727 FILE *fp
= (status
== 0) ? stdout
: stderr
;
9729 fprintf(fp
, "usage: %s [-hV] command [arg ...]\n",
9732 fprintf(fp
, "lazy usage: %s path\n", getprogname());
9739 make_argv(int argc
, ...)
9747 argv
= calloc(argc
, sizeof(char *));
9750 for (i
= 0; i
< argc
; i
++) {
9751 argv
[i
] = strdup(va_arg(ap
, char *));
9752 if (argv
[i
] == NULL
)
9761 * Try to convert 'tog path' into a 'tog log path' command.
9762 * The user could simply have mistyped the command rather than knowingly
9763 * provided a path. So check whether argv[0] can in fact be resolved
9764 * to a path in the HEAD commit and print a special error if not.
9765 * This hack is for mpi@ <3
9767 static const struct got_error
*
9768 tog_log_with_path(int argc
, char *argv
[])
9770 const struct got_error
*error
= NULL
, *close_err
;
9771 const struct tog_cmd
*cmd
= NULL
;
9772 struct got_repository
*repo
= NULL
;
9773 struct got_worktree
*worktree
= NULL
;
9774 struct got_object_id
*commit_id
= NULL
, *id
= NULL
;
9775 struct got_commit_object
*commit
= NULL
;
9776 char *cwd
= NULL
, *repo_path
= NULL
, *in_repo_path
= NULL
;
9777 char *commit_id_str
= NULL
, **cmd_argv
= NULL
;
9778 int *pack_fds
= NULL
;
9780 cwd
= getcwd(NULL
, 0);
9782 return got_error_from_errno("getcwd");
9784 error
= got_repo_pack_fds_open(&pack_fds
);
9788 error
= got_worktree_open(&worktree
, cwd
);
9789 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
9793 repo_path
= strdup(got_worktree_get_repo_path(worktree
));
9795 repo_path
= strdup(cwd
);
9796 if (repo_path
== NULL
) {
9797 error
= got_error_from_errno("strdup");
9801 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
9805 error
= get_in_repo_path_from_argv0(&in_repo_path
, argc
, argv
,
9810 error
= tog_load_refs(repo
, 0);
9813 error
= got_repo_match_object_id(&commit_id
, NULL
, worktree
?
9814 got_worktree_get_head_ref_name(worktree
) : GOT_REF_HEAD
,
9815 GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
9820 got_worktree_close(worktree
);
9824 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
9828 error
= got_object_id_by_path(&id
, repo
, commit
, in_repo_path
);
9830 if (error
->code
!= GOT_ERR_NO_TREE_ENTRY
)
9832 fprintf(stderr
, "%s: '%s' is no known command or path\n",
9833 getprogname(), argv
[0]);
9838 error
= got_object_id_str(&commit_id_str
, commit_id
);
9842 cmd
= &tog_commands
[0]; /* log */
9844 cmd_argv
= make_argv(argc
, cmd
->name
, "-c", commit_id_str
, argv
[0]);
9845 error
= cmd
->cmd_main(argc
, cmd_argv
);
9848 close_err
= got_repo_close(repo
);
9853 got_object_commit_close(commit
);
9855 got_worktree_close(worktree
);
9857 const struct got_error
*pack_err
=
9858 got_repo_pack_fds_close(pack_fds
);
9863 free(commit_id_str
);
9870 for (i
= 0; i
< argc
; i
++)
9879 main(int argc
, char *argv
[])
9881 const struct got_error
*io_err
, *error
= NULL
;
9882 const struct tog_cmd
*cmd
= NULL
;
9883 int ch
, hflag
= 0, Vflag
= 0;
9884 char **cmd_argv
= NULL
;
9885 static const struct option longopts
[] = {
9886 { "version", no_argument
, NULL
, 'V' },
9889 char *diff_algo_str
= NULL
;
9890 const char *test_script_path
;
9892 setlocale(LC_CTYPE
, "");
9895 * Test mode init must happen before pledge() because "tty" will
9896 * not allow TTY-related ioctls to occur via regular files.
9898 test_script_path
= getenv("TOG_TEST_SCRIPT");
9899 if (test_script_path
!= NULL
) {
9900 error
= init_mock_term(test_script_path
);
9902 fprintf(stderr
, "%s: %s\n", getprogname(), error
->msg
);
9905 } else if (!isatty(STDIN_FILENO
))
9906 errx(1, "standard input is not a tty");
9908 #if !defined(PROFILE)
9909 if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd unveil",
9914 while ((ch
= getopt_long(argc
, argv
, "+hV", longopts
, NULL
)) != -1) {
9934 got_version_print_str();
9941 /* Build an argument vector which runs a default command. */
9942 cmd
= &tog_commands
[0];
9944 cmd_argv
= make_argv(argc
, cmd
->name
);
9948 /* Did the user specify a command? */
9949 for (i
= 0; i
< nitems(tog_commands
); i
++) {
9950 if (strncmp(tog_commands
[i
].name
, argv
[0],
9951 strlen(argv
[0])) == 0) {
9952 cmd
= &tog_commands
[i
];
9958 diff_algo_str
= getenv("TOG_DIFF_ALGORITHM");
9959 if (diff_algo_str
) {
9960 if (strcasecmp(diff_algo_str
, "patience") == 0)
9961 tog_diff_algo
= GOT_DIFF_ALGORITHM_PATIENCE
;
9962 if (strcasecmp(diff_algo_str
, "myers") == 0)
9963 tog_diff_algo
= GOT_DIFF_ALGORITHM_MYERS
;
9969 /* No command specified; try log with a path */
9970 error
= tog_log_with_path(argc
, argv
);
9975 error
= cmd
->cmd_main(argc
, cmd_argv
? cmd_argv
: argv
);
9978 if (using_mock_io
) {
9979 io_err
= tog_io_close();
9986 for (i
= 0; i
< argc
; i
++)
9991 if (error
&& error
->code
!= GOT_ERR_CANCELLED
&&
9992 error
->code
!= GOT_ERR_EOF
&&
9993 error
->code
!= GOT_ERR_PRIVSEP_EXIT
&&
9994 error
->code
!= GOT_ERR_PRIVSEP_PIPE
&&
9995 !(error
->code
== GOT_ERR_ERRNO
&& errno
== EINTR
))
9996 fprintf(stderr
, "%s: %s\n", getprogname(), error
->msg
);