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"
61 #include "got_keyword.h"
64 #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b))
68 #define MAX(_a,_b) ((_a) > (_b) ? (_a) : (_b))
72 #define CTRL(x) ((x) & 0x1f)
76 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
81 const struct got_error
*(*cmd_main
)(int, char *[]);
82 void (*cmd_usage
)(void);
85 __dead
static void usage(int, int);
86 __dead
static void usage_log(void);
87 __dead
static void usage_diff(void);
88 __dead
static void usage_blame(void);
89 __dead
static void usage_tree(void);
90 __dead
static void usage_ref(void);
92 static const struct got_error
* cmd_log(int, char *[]);
93 static const struct got_error
* cmd_diff(int, char *[]);
94 static const struct got_error
* cmd_blame(int, char *[]);
95 static const struct got_error
* cmd_tree(int, char *[]);
96 static const struct got_error
* cmd_ref(int, char *[]);
98 static const struct tog_cmd tog_commands
[] = {
99 { "log", cmd_log
, usage_log
},
100 { "diff", cmd_diff
, usage_diff
},
101 { "blame", cmd_blame
, usage_blame
},
102 { "tree", cmd_tree
, usage_tree
},
103 { "ref", cmd_ref
, usage_ref
},
115 /* Match _DIFF to _HELP with enum tog_view_type TOG_VIEW_* counterparts. */
116 enum tog_keymap_type
{
117 TOG_KEYMAP_KEYS
= -2,
133 #define HSPLIT_SCALE 0.3f /* default horizontal split scale */
135 #define TOG_EOF_STRING "(END)"
137 struct commit_queue_entry
{
138 TAILQ_ENTRY(commit_queue_entry
) entry
;
139 struct got_object_id
*id
;
140 struct got_commit_object
*commit
;
143 TAILQ_HEAD(commit_queue_head
, commit_queue_entry
);
144 struct commit_queue
{
146 struct commit_queue_head head
;
150 STAILQ_ENTRY(tog_color
) entry
;
154 STAILQ_HEAD(tog_colors
, tog_color
);
156 static struct got_reflist_head tog_refs
= TAILQ_HEAD_INITIALIZER(tog_refs
);
157 static struct got_reflist_object_id_map
*tog_refs_idmap
;
159 struct got_object_id
*id
;
163 static enum got_diff_algorithm tog_diff_algo
= GOT_DIFF_ALGORITHM_MYERS
;
165 static const struct got_error
*
166 tog_ref_cmp_by_name(void *arg
, int *cmp
, struct got_reference
*re1
,
167 struct got_reference
* re2
)
169 const char *name1
= got_ref_get_name(re1
);
170 const char *name2
= got_ref_get_name(re2
);
171 int isbackup1
, isbackup2
;
173 /* Sort backup refs towards the bottom of the list. */
174 isbackup1
= strncmp(name1
, "refs/got/backup/", 16) == 0;
175 isbackup2
= strncmp(name2
, "refs/got/backup/", 16) == 0;
176 if (!isbackup1
&& isbackup2
) {
179 } else if (isbackup1
&& !isbackup2
) {
184 *cmp
= got_path_cmp(name1
, name2
, strlen(name1
), strlen(name2
));
188 static const struct got_error
*
189 tog_load_refs(struct got_repository
*repo
, int sort_by_date
)
191 const struct got_error
*err
;
193 err
= got_ref_list(&tog_refs
, repo
, NULL
, sort_by_date
?
194 got_ref_cmp_by_commit_timestamp_descending
: tog_ref_cmp_by_name
,
199 return got_reflist_object_id_map_create(&tog_refs_idmap
, &tog_refs
,
206 if (tog_refs_idmap
) {
207 got_reflist_object_id_map_free(tog_refs_idmap
);
208 tog_refs_idmap
= NULL
;
210 got_ref_list_free(&tog_refs
);
213 static const struct got_error
*
214 add_color(struct tog_colors
*colors
, const char *pattern
,
215 int idx
, short color
)
217 const struct got_error
*err
= NULL
;
218 struct tog_color
*tc
;
221 if (idx
< 1 || idx
> COLOR_PAIRS
- 1)
224 init_pair(idx
, color
, -1);
226 tc
= calloc(1, sizeof(*tc
));
228 return got_error_from_errno("calloc");
229 regerr
= regcomp(&tc
->regex
, pattern
,
230 REG_EXTENDED
| REG_NOSUB
| REG_NEWLINE
);
232 static char regerr_msg
[512];
233 static char err_msg
[512];
234 regerror(regerr
, &tc
->regex
, regerr_msg
,
236 snprintf(err_msg
, sizeof(err_msg
), "regcomp: %s",
238 err
= got_error_msg(GOT_ERR_REGEX
, err_msg
);
243 STAILQ_INSERT_HEAD(colors
, tc
, entry
);
248 free_colors(struct tog_colors
*colors
)
250 struct tog_color
*tc
;
252 while (!STAILQ_EMPTY(colors
)) {
253 tc
= STAILQ_FIRST(colors
);
254 STAILQ_REMOVE_HEAD(colors
, entry
);
260 static struct tog_color
*
261 get_color(struct tog_colors
*colors
, int colorpair
)
263 struct tog_color
*tc
= NULL
;
265 STAILQ_FOREACH(tc
, colors
, entry
) {
266 if (tc
->colorpair
== colorpair
)
274 default_color_value(const char *envvar
)
276 if (strcmp(envvar
, "TOG_COLOR_DIFF_MINUS") == 0)
277 return COLOR_MAGENTA
;
278 if (strcmp(envvar
, "TOG_COLOR_DIFF_PLUS") == 0)
280 if (strcmp(envvar
, "TOG_COLOR_DIFF_CHUNK_HEADER") == 0)
282 if (strcmp(envvar
, "TOG_COLOR_DIFF_META") == 0)
284 if (strcmp(envvar
, "TOG_COLOR_TREE_SUBMODULE") == 0)
285 return COLOR_MAGENTA
;
286 if (strcmp(envvar
, "TOG_COLOR_TREE_SYMLINK") == 0)
287 return COLOR_MAGENTA
;
288 if (strcmp(envvar
, "TOG_COLOR_TREE_DIRECTORY") == 0)
290 if (strcmp(envvar
, "TOG_COLOR_TREE_EXECUTABLE") == 0)
292 if (strcmp(envvar
, "TOG_COLOR_COMMIT") == 0)
294 if (strcmp(envvar
, "TOG_COLOR_AUTHOR") == 0)
296 if (strcmp(envvar
, "TOG_COLOR_DATE") == 0)
298 if (strcmp(envvar
, "TOG_COLOR_REFS_HEADS") == 0)
300 if (strcmp(envvar
, "TOG_COLOR_REFS_TAGS") == 0)
301 return COLOR_MAGENTA
;
302 if (strcmp(envvar
, "TOG_COLOR_REFS_REMOTES") == 0)
304 if (strcmp(envvar
, "TOG_COLOR_REFS_BACKUP") == 0)
311 get_color_value(const char *envvar
)
313 const char *val
= getenv(envvar
);
316 return default_color_value(envvar
);
318 if (strcasecmp(val
, "black") == 0)
320 if (strcasecmp(val
, "red") == 0)
322 if (strcasecmp(val
, "green") == 0)
324 if (strcasecmp(val
, "yellow") == 0)
326 if (strcasecmp(val
, "blue") == 0)
328 if (strcasecmp(val
, "magenta") == 0)
329 return COLOR_MAGENTA
;
330 if (strcasecmp(val
, "cyan") == 0)
332 if (strcasecmp(val
, "white") == 0)
334 if (strcasecmp(val
, "default") == 0)
337 return default_color_value(envvar
);
340 struct tog_diff_view_state
{
341 struct got_object_id
*id1
, *id2
;
342 const char *label1
, *label2
;
346 int first_displayed_line
;
347 int last_displayed_line
;
350 int ignore_whitespace
;
352 struct got_repository
*repo
;
353 struct got_diff_line
*lines
;
358 /* passed from log or blame view; may be NULL */
359 struct tog_view
*parent_view
;
362 pthread_mutex_t tog_mutex
= PTHREAD_MUTEX_INITIALIZER
;
363 static volatile sig_atomic_t tog_thread_error
;
365 struct tog_log_thread_args
{
366 pthread_cond_t need_commits
;
367 pthread_cond_t commit_loaded
;
370 struct got_commit_graph
*graph
;
371 struct commit_queue
*real_commits
;
372 const char *in_repo_path
;
373 struct got_object_id
*start_id
;
374 struct got_repository
*repo
;
377 pthread_cond_t log_loaded
;
379 struct commit_queue_entry
**first_displayed_entry
;
380 struct commit_queue_entry
**selected_entry
;
382 int *search_next_done
;
386 regex_t
*limit_regex
;
387 struct commit_queue
*limit_commits
;
388 struct got_worktree
*worktree
;
389 int need_commit_marker
;
392 struct tog_log_view_state
{
393 struct commit_queue
*commits
;
394 struct commit_queue_entry
*first_displayed_entry
;
395 struct commit_queue_entry
*last_displayed_entry
;
396 struct commit_queue_entry
*selected_entry
;
397 struct commit_queue real_commits
;
402 struct got_repository
*repo
;
403 struct got_object_id
*start_id
;
406 struct tog_log_thread_args thread_args
;
407 struct commit_queue_entry
*matched_entry
;
408 struct commit_queue_entry
*search_entry
;
409 struct tog_colors colors
;
413 struct commit_queue limit_commits
;
416 #define TOG_COLOR_DIFF_MINUS 1
417 #define TOG_COLOR_DIFF_PLUS 2
418 #define TOG_COLOR_DIFF_CHUNK_HEADER 3
419 #define TOG_COLOR_DIFF_META 4
420 #define TOG_COLOR_TREE_SUBMODULE 5
421 #define TOG_COLOR_TREE_SYMLINK 6
422 #define TOG_COLOR_TREE_DIRECTORY 7
423 #define TOG_COLOR_TREE_EXECUTABLE 8
424 #define TOG_COLOR_COMMIT 9
425 #define TOG_COLOR_AUTHOR 10
426 #define TOG_COLOR_DATE 11
427 #define TOG_COLOR_REFS_HEADS 12
428 #define TOG_COLOR_REFS_TAGS 13
429 #define TOG_COLOR_REFS_REMOTES 14
430 #define TOG_COLOR_REFS_BACKUP 15
432 struct tog_blame_cb_args
{
433 struct tog_blame_line
*lines
; /* one per line */
436 struct tog_view
*view
;
437 struct got_object_id
*commit_id
;
441 struct tog_blame_thread_args
{
443 struct got_repository
*repo
;
444 struct tog_blame_cb_args
*cb_args
;
446 got_cancel_cb cancel_cb
;
448 pthread_cond_t blame_complete
;
454 struct tog_blame_line
*lines
;
458 struct tog_blame_thread_args thread_args
;
459 struct tog_blame_cb_args cb_args
;
464 struct tog_blame_view_state
{
465 int first_displayed_line
;
466 int last_displayed_line
;
468 int last_diffed_line
;
472 struct got_object_id_queue blamed_commits
;
473 struct got_object_qid
*blamed_commit
;
475 struct got_repository
*repo
;
476 struct got_object_id
*commit_id
;
477 struct got_object_id
*id_to_log
;
478 struct tog_blame blame
;
480 struct tog_colors colors
;
483 struct tog_parent_tree
{
484 TAILQ_ENTRY(tog_parent_tree
) entry
;
485 struct got_tree_object
*tree
;
486 struct got_tree_entry
*first_displayed_entry
;
487 struct got_tree_entry
*selected_entry
;
491 TAILQ_HEAD(tog_parent_trees
, tog_parent_tree
);
493 struct tog_tree_view_state
{
495 struct got_object_id
*commit_id
;/* commit which this tree belongs to */
496 struct got_tree_object
*root
; /* the commit's root tree entry */
497 struct got_tree_object
*tree
; /* currently displayed (sub-)tree */
498 struct got_tree_entry
*first_displayed_entry
;
499 struct got_tree_entry
*last_displayed_entry
;
500 struct got_tree_entry
*selected_entry
;
501 int ndisplayed
, selected
, show_ids
;
502 struct tog_parent_trees parents
; /* parent trees of current sub-tree */
504 struct got_repository
*repo
;
505 struct got_tree_entry
*matched_entry
;
506 struct tog_colors colors
;
509 struct tog_reflist_entry
{
510 TAILQ_ENTRY(tog_reflist_entry
) entry
;
511 struct got_reference
*ref
;
515 TAILQ_HEAD(tog_reflist_head
, tog_reflist_entry
);
517 struct tog_ref_view_state
{
518 struct tog_reflist_head refs
;
519 struct tog_reflist_entry
*first_displayed_entry
;
520 struct tog_reflist_entry
*last_displayed_entry
;
521 struct tog_reflist_entry
*selected_entry
;
522 int nrefs
, ndisplayed
, selected
, show_date
, show_ids
, sort_by_date
;
523 struct got_repository
*repo
;
524 struct tog_reflist_entry
*matched_entry
;
525 struct tog_colors colors
;
528 struct tog_help_view_state
{
533 int first_displayed_line
;
534 int last_displayed_line
;
539 enum tog_keymap_type type
;
542 #define GENERATE_HELP \
543 KEYMAP_("Global", TOG_KEYMAP_GLOBAL), \
544 KEY_("H F1", "Open view-specific help (double tap for all help)"), \
545 KEY_("k C-p Up", "Move cursor or page up one line"), \
546 KEY_("j C-n Down", "Move cursor or page down one line"), \
547 KEY_("C-b b PgUp", "Scroll the view up one page"), \
548 KEY_("C-f f PgDn Space", "Scroll the view down one page"), \
549 KEY_("C-u u", "Scroll the view up one half page"), \
550 KEY_("C-d d", "Scroll the view down one half page"), \
551 KEY_("g", "Go to line N (default: first line)"), \
552 KEY_("Home =", "Go to the first line"), \
553 KEY_("G", "Go to line N (default: last line)"), \
554 KEY_("End *", "Go to the last line"), \
555 KEY_("l Right", "Scroll the view right"), \
556 KEY_("h Left", "Scroll the view left"), \
557 KEY_("$", "Scroll view to the rightmost position"), \
558 KEY_("0", "Scroll view to the leftmost position"), \
559 KEY_("-", "Decrease size of the focussed split"), \
560 KEY_("+", "Increase size of the focussed split"), \
561 KEY_("Tab", "Switch focus between views"), \
562 KEY_("F", "Toggle fullscreen mode"), \
563 KEY_("S", "Switch split-screen layout"), \
564 KEY_("/", "Open prompt to enter search term"), \
565 KEY_("n", "Find next line/token matching the current search term"), \
566 KEY_("N", "Find previous line/token matching the current search term"),\
567 KEY_("q", "Quit the focussed view; Quit help screen"), \
568 KEY_("Q", "Quit tog"), \
570 KEYMAP_("Log view", TOG_KEYMAP_LOG), \
571 KEY_("< ,", "Move cursor up one commit"), \
572 KEY_("> .", "Move cursor down one commit"), \
573 KEY_("Enter", "Open diff view of the selected commit"), \
574 KEY_("B", "Reload the log view and toggle display of merged commits"), \
575 KEY_("R", "Open ref view of all repository references"), \
576 KEY_("T", "Display tree view of the repository from the selected" \
578 KEY_("@", "Toggle between displaying author and committer name"), \
579 KEY_("&", "Open prompt to enter term to limit commits displayed"), \
580 KEY_("C-g Backspace", "Cancel current search or log operation"), \
581 KEY_("C-l", "Reload the log view with new commits in the repository"), \
583 KEYMAP_("Diff view", TOG_KEYMAP_DIFF), \
584 KEY_("K < ,", "Display diff of next line in the file/log entry"), \
585 KEY_("J > .", "Display diff of previous line in the file/log entry"), \
586 KEY_("A", "Toggle between Myers and Patience diff algorithm"), \
587 KEY_("a", "Toggle treatment of file as ASCII irrespective of binary" \
589 KEY_("(", "Go to the previous file in the diff"), \
590 KEY_(")", "Go to the next file in the diff"), \
591 KEY_("{", "Go to the previous hunk in the diff"), \
592 KEY_("}", "Go to the next hunk in the diff"), \
593 KEY_("[", "Decrease the number of context lines"), \
594 KEY_("]", "Increase the number of context lines"), \
595 KEY_("w", "Toggle ignore whitespace-only changes in the diff"), \
597 KEYMAP_("Blame view", TOG_KEYMAP_BLAME), \
598 KEY_("Enter", "Display diff view of the selected line's commit"), \
599 KEY_("A", "Toggle diff algorithm between Myers and Patience"), \
600 KEY_("L", "Open log view for the currently selected annotated line"), \
601 KEY_("C", "Reload view with the previously blamed commit"), \
602 KEY_("c", "Reload view with the version of the file found in the" \
603 " selected line's commit"), \
604 KEY_("p", "Reload view with the version of the file found in the" \
605 " selected line's parent commit"), \
607 KEYMAP_("Tree view", TOG_KEYMAP_TREE), \
608 KEY_("Enter", "Enter selected directory or open blame view of the" \
610 KEY_("L", "Open log view for the selected entry"), \
611 KEY_("R", "Open ref view of all repository references"), \
612 KEY_("i", "Show object IDs for all tree entries"), \
613 KEY_("Backspace", "Return to the parent directory"), \
615 KEYMAP_("Ref view", TOG_KEYMAP_REF), \
616 KEY_("Enter", "Display log view of the selected reference"), \
617 KEY_("T", "Display tree view of the selected reference"), \
618 KEY_("i", "Toggle display of IDs for all non-symbolic references"), \
619 KEY_("m", "Toggle display of last modified date for each reference"), \
620 KEY_("o", "Toggle reference sort order (name -> timestamp)"), \
621 KEY_("C-l", "Reload view with all repository references")
626 enum tog_keymap_type type
;
629 /* curses io for tog regress */
638 static int using_mock_io
;
640 #define TOG_KEY_SCRDUMP SHRT_MIN
643 * We implement two types of views: parent views and child views.
645 * The 'Tab' key switches focus between a parent view and its child view.
646 * Child views are shown side-by-side to their parent view, provided
647 * there is enough screen estate.
649 * When a new view is opened from within a parent view, this new view
650 * becomes a child view of the parent view, replacing any existing child.
652 * When a new view is opened from within a child view, this new view
653 * becomes a parent view which will obscure the views below until the
654 * user quits the new parent view by typing 'q'.
656 * This list of views contains parent views only.
657 * Child views are only pointed to by their parent view.
659 TAILQ_HEAD(tog_view_list_head
, tog_view
);
662 TAILQ_ENTRY(tog_view
) entry
;
665 int nlines
, ncols
, begin_y
, begin_x
; /* based on split height/width */
666 int resized_y
, resized_x
; /* begin_y/x based on user resizing */
667 int maxx
, x
; /* max column and current start column */
668 int lines
, cols
; /* copies of LINES and COLS */
669 int nscrolled
, offset
; /* lines scrolled and hsplit line offset */
670 int gline
, hiline
; /* navigate to and highlight this nG line */
671 int ch
, count
; /* current keymap and count prefix */
672 int resized
; /* set when in a resize event */
673 int focussed
; /* Only set on one parent or child view at a time. */
675 struct tog_view
*parent
;
676 struct tog_view
*child
;
679 * This flag is initially set on parent views when a new child view
680 * is created. It gets toggled when the 'Tab' key switches focus
681 * between parent and child.
682 * The flag indicates whether focus should be passed on to our child
683 * view if this parent view gets picked for focus after another parent
684 * view was closed. This prevents child views from losing focus in such
689 enum tog_view_mode mode
;
690 /* type-specific state */
691 enum tog_view_type type
;
693 struct tog_diff_view_state diff
;
694 struct tog_log_view_state log
;
695 struct tog_blame_view_state blame
;
696 struct tog_tree_view_state tree
;
697 struct tog_ref_view_state ref
;
698 struct tog_help_view_state help
;
701 const struct got_error
*(*show
)(struct tog_view
*);
702 const struct got_error
*(*input
)(struct tog_view
**,
703 struct tog_view
*, int);
704 const struct got_error
*(*reset
)(struct tog_view
*);
705 const struct got_error
*(*resize
)(struct tog_view
*, int);
706 const struct got_error
*(*close
)(struct tog_view
*);
708 const struct got_error
*(*search_start
)(struct tog_view
*);
709 const struct got_error
*(*search_next
)(struct tog_view
*);
710 void (*search_setup
)(struct tog_view
*, FILE **, off_t
**, size_t *,
711 int **, int **, int **, int **);
714 #define TOG_SEARCH_FORWARD 1
715 #define TOG_SEARCH_BACKWARD 2
716 int search_next_done
;
717 #define TOG_SEARCH_HAVE_MORE 1
718 #define TOG_SEARCH_NO_MORE 2
719 #define TOG_SEARCH_HAVE_NONE 3
725 static const struct got_error
*open_diff_view(struct tog_view
*,
726 struct got_object_id
*, struct got_object_id
*,
727 const char *, const char *, int, int, int, struct tog_view
*,
728 struct got_repository
*);
729 static const struct got_error
*show_diff_view(struct tog_view
*);
730 static const struct got_error
*input_diff_view(struct tog_view
**,
731 struct tog_view
*, int);
732 static const struct got_error
*reset_diff_view(struct tog_view
*);
733 static const struct got_error
* close_diff_view(struct tog_view
*);
734 static const struct got_error
*search_start_diff_view(struct tog_view
*);
735 static void search_setup_diff_view(struct tog_view
*, FILE **, off_t
**,
736 size_t *, int **, int **, int **, int **);
737 static const struct got_error
*search_next_view_match(struct tog_view
*);
739 static const struct got_error
*open_log_view(struct tog_view
*,
740 struct got_object_id
*, struct got_repository
*,
741 const char *, const char *, int, struct got_worktree
*);
742 static const struct got_error
* show_log_view(struct tog_view
*);
743 static const struct got_error
*input_log_view(struct tog_view
**,
744 struct tog_view
*, int);
745 static const struct got_error
*resize_log_view(struct tog_view
*, int);
746 static const struct got_error
*close_log_view(struct tog_view
*);
747 static const struct got_error
*search_start_log_view(struct tog_view
*);
748 static const struct got_error
*search_next_log_view(struct tog_view
*);
750 static const struct got_error
*open_blame_view(struct tog_view
*, char *,
751 struct got_object_id
*, struct got_repository
*);
752 static const struct got_error
*show_blame_view(struct tog_view
*);
753 static const struct got_error
*input_blame_view(struct tog_view
**,
754 struct tog_view
*, int);
755 static const struct got_error
*reset_blame_view(struct tog_view
*);
756 static const struct got_error
*close_blame_view(struct tog_view
*);
757 static const struct got_error
*search_start_blame_view(struct tog_view
*);
758 static void search_setup_blame_view(struct tog_view
*, FILE **, off_t
**,
759 size_t *, int **, int **, int **, int **);
761 static const struct got_error
*open_tree_view(struct tog_view
*,
762 struct got_object_id
*, const char *, struct got_repository
*);
763 static const struct got_error
*show_tree_view(struct tog_view
*);
764 static const struct got_error
*input_tree_view(struct tog_view
**,
765 struct tog_view
*, int);
766 static const struct got_error
*close_tree_view(struct tog_view
*);
767 static const struct got_error
*search_start_tree_view(struct tog_view
*);
768 static const struct got_error
*search_next_tree_view(struct tog_view
*);
770 static const struct got_error
*open_ref_view(struct tog_view
*,
771 struct got_repository
*);
772 static const struct got_error
*show_ref_view(struct tog_view
*);
773 static const struct got_error
*input_ref_view(struct tog_view
**,
774 struct tog_view
*, int);
775 static const struct got_error
*close_ref_view(struct tog_view
*);
776 static const struct got_error
*search_start_ref_view(struct tog_view
*);
777 static const struct got_error
*search_next_ref_view(struct tog_view
*);
779 static const struct got_error
*open_help_view(struct tog_view
*,
781 static const struct got_error
*show_help_view(struct tog_view
*);
782 static const struct got_error
*input_help_view(struct tog_view
**,
783 struct tog_view
*, int);
784 static const struct got_error
*reset_help_view(struct tog_view
*);
785 static const struct got_error
* close_help_view(struct tog_view
*);
786 static const struct got_error
*search_start_help_view(struct tog_view
*);
787 static void search_setup_help_view(struct tog_view
*, FILE **, off_t
**,
788 size_t *, int **, int **, int **, int **);
790 static volatile sig_atomic_t tog_sigwinch_received
;
791 static volatile sig_atomic_t tog_sigpipe_received
;
792 static volatile sig_atomic_t tog_sigcont_received
;
793 static volatile sig_atomic_t tog_sigint_received
;
794 static volatile sig_atomic_t tog_sigterm_received
;
797 tog_sigwinch(int signo
)
799 tog_sigwinch_received
= 1;
803 tog_sigpipe(int signo
)
805 tog_sigpipe_received
= 1;
809 tog_sigcont(int signo
)
811 tog_sigcont_received
= 1;
815 tog_sigint(int signo
)
817 tog_sigint_received
= 1;
821 tog_sigterm(int signo
)
823 tog_sigterm_received
= 1;
827 tog_fatal_signal_received(void)
829 return (tog_sigpipe_received
||
830 tog_sigint_received
|| tog_sigterm_received
);
833 static const struct got_error
*
834 view_close(struct tog_view
*view
)
836 const struct got_error
*err
= NULL
, *child_err
= NULL
;
839 child_err
= view_close(view
->child
);
843 err
= view
->close(view
);
845 del_panel(view
->panel
);
847 delwin(view
->window
);
849 return err
? err
: child_err
;
852 static struct tog_view
*
853 view_open(int nlines
, int ncols
, int begin_y
, int begin_x
,
854 enum tog_view_type type
)
856 struct tog_view
*view
= calloc(1, sizeof(*view
));
864 view
->nlines
= nlines
? nlines
: LINES
- begin_y
;
865 view
->ncols
= ncols
? ncols
: COLS
- begin_x
;
866 view
->begin_y
= begin_y
;
867 view
->begin_x
= begin_x
;
868 view
->window
= newwin(nlines
, ncols
, begin_y
, begin_x
);
869 if (view
->window
== NULL
) {
873 view
->panel
= new_panel(view
->window
);
874 if (view
->panel
== NULL
||
875 set_panel_userptr(view
->panel
, view
) != OK
) {
880 keypad(view
->window
, TRUE
);
885 view_split_begin_x(int begin_x
)
887 if (begin_x
> 0 || COLS
< 120)
889 return (COLS
- MAX(COLS
/ 2, 80));
892 /* XXX Stub till we decide what to do. */
894 view_split_begin_y(int lines
)
896 return lines
* HSPLIT_SCALE
;
899 static const struct got_error
*view_resize(struct tog_view
*);
901 static const struct got_error
*
902 view_splitscreen(struct tog_view
*view
)
904 const struct got_error
*err
= NULL
;
906 if (!view
->resized
&& view
->mode
== TOG_VIEW_SPLIT_HRZN
) {
907 if (view
->resized_y
&& view
->resized_y
< view
->lines
)
908 view
->begin_y
= view
->resized_y
;
910 view
->begin_y
= view_split_begin_y(view
->nlines
);
912 } else if (!view
->resized
) {
913 if (view
->resized_x
&& view
->resized_x
< view
->cols
- 1 &&
915 view
->begin_x
= view
->resized_x
;
917 view
->begin_x
= view_split_begin_x(0);
920 view
->nlines
= LINES
- view
->begin_y
;
921 view
->ncols
= COLS
- view
->begin_x
;
924 err
= view_resize(view
);
928 if (view
->parent
&& view
->mode
== TOG_VIEW_SPLIT_HRZN
)
929 view
->parent
->nlines
= view
->begin_y
;
931 if (mvwin(view
->window
, view
->begin_y
, view
->begin_x
) == ERR
)
932 return got_error_from_errno("mvwin");
937 static const struct got_error
*
938 view_fullscreen(struct tog_view
*view
)
940 const struct got_error
*err
= NULL
;
943 view
->begin_y
= view
->resized
? view
->begin_y
: 0;
944 view
->nlines
= view
->resized
? view
->nlines
: LINES
;
948 err
= view_resize(view
);
952 if (mvwin(view
->window
, view
->begin_y
, view
->begin_x
) == ERR
)
953 return got_error_from_errno("mvwin");
959 view_is_parent_view(struct tog_view
*view
)
961 return view
->parent
== NULL
;
965 view_is_splitscreen(struct tog_view
*view
)
967 return view
->begin_x
> 0 || view
->begin_y
> 0;
971 view_is_fullscreen(struct tog_view
*view
)
973 return view
->nlines
== LINES
&& view
->ncols
== COLS
;
977 view_is_hsplit_top(struct tog_view
*view
)
979 return view
->mode
== TOG_VIEW_SPLIT_HRZN
&& view
->child
&&
980 view_is_splitscreen(view
->child
);
984 view_border(struct tog_view
*view
)
987 const struct tog_view
*view_above
;
990 return view_border(view
->parent
);
992 panel
= panel_above(view
->panel
);
996 view_above
= panel_userptr(panel
);
997 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
)
998 mvwhline(view
->window
, view_above
->begin_y
- 1,
999 view
->begin_x
, ACS_HLINE
, view
->ncols
);
1001 mvwvline(view
->window
, view
->begin_y
, view_above
->begin_x
- 1,
1002 ACS_VLINE
, view
->nlines
);
1005 static const struct got_error
*view_init_hsplit(struct tog_view
*, int);
1006 static const struct got_error
*request_log_commits(struct tog_view
*);
1007 static const struct got_error
*offset_selection_down(struct tog_view
*);
1008 static void offset_selection_up(struct tog_view
*);
1009 static void view_get_split(struct tog_view
*, int *, int *);
1011 static const struct got_error
*
1012 view_resize(struct tog_view
*view
)
1014 const struct got_error
*err
= NULL
;
1015 int dif
, nlines
, ncols
;
1017 dif
= LINES
- view
->lines
; /* line difference */
1019 if (view
->lines
> LINES
)
1020 nlines
= view
->nlines
- (view
->lines
- LINES
);
1022 nlines
= view
->nlines
+ (LINES
- view
->lines
);
1023 if (view
->cols
> COLS
)
1024 ncols
= view
->ncols
- (view
->cols
- COLS
);
1026 ncols
= view
->ncols
+ (COLS
- view
->cols
);
1029 int hs
= view
->child
->begin_y
;
1031 if (!view_is_fullscreen(view
))
1032 view
->child
->begin_x
= view_split_begin_x(view
->begin_x
);
1033 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
||
1034 view
->child
->begin_x
== 0) {
1037 view_fullscreen(view
->child
);
1038 if (view
->child
->focussed
)
1039 show_panel(view
->child
->panel
);
1041 show_panel(view
->panel
);
1043 ncols
= view
->child
->begin_x
;
1045 view_splitscreen(view
->child
);
1046 show_panel(view
->child
->panel
);
1049 * XXX This is ugly and needs to be moved into the above
1050 * logic but "works" for now and my attempts at moving it
1051 * break either 'tab' or 'F' key maps in horizontal splits.
1054 err
= view_splitscreen(view
->child
);
1057 if (dif
< 0) { /* top split decreased */
1058 err
= offset_selection_down(view
);
1065 show_panel(view
->child
->panel
);
1066 nlines
= view
->nlines
;
1068 } else if (view
->parent
== NULL
)
1071 if (view
->resize
&& dif
> 0) {
1072 err
= view
->resize(view
, dif
);
1077 if (wresize(view
->window
, nlines
, ncols
) == ERR
)
1078 return got_error_from_errno("wresize");
1079 if (replace_panel(view
->panel
, view
->window
) == ERR
)
1080 return got_error_from_errno("replace_panel");
1081 wclear(view
->window
);
1083 view
->nlines
= nlines
;
1084 view
->ncols
= ncols
;
1085 view
->lines
= LINES
;
1091 static const struct got_error
*
1092 resize_log_view(struct tog_view
*view
, int increase
)
1094 struct tog_log_view_state
*s
= &view
->state
.log
;
1095 const struct got_error
*err
= NULL
;
1098 if (s
->selected_entry
)
1099 n
= s
->selected_entry
->idx
+ view
->lines
- s
->selected
;
1102 * Request commits to account for the increased
1103 * height so we have enough to populate the view.
1105 if (s
->commits
->ncommits
< n
) {
1106 view
->nscrolled
= n
- s
->commits
->ncommits
+ increase
+ 1;
1107 err
= request_log_commits(view
);
1114 view_adjust_offset(struct tog_view
*view
, int n
)
1119 if (view
->parent
&& view
->parent
->offset
) {
1120 if (view
->parent
->offset
+ n
>= 0)
1121 view
->parent
->offset
+= n
;
1123 view
->parent
->offset
= 0;
1124 } else if (view
->offset
) {
1125 if (view
->offset
- n
>= 0)
1132 static const struct got_error
*
1133 view_resize_split(struct tog_view
*view
, int resize
)
1135 const struct got_error
*err
= NULL
;
1136 struct tog_view
*v
= NULL
;
1143 if (!v
->child
|| !view_is_splitscreen(v
->child
))
1146 v
->resized
= v
->child
->resized
= resize
; /* lock for resize event */
1148 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
) {
1149 if (v
->child
->resized_y
)
1150 v
->child
->begin_y
= v
->child
->resized_y
;
1152 v
->child
->begin_y
-= resize
;
1154 v
->child
->begin_y
+= resize
;
1155 if (v
->child
->begin_y
< 3) {
1157 v
->child
->begin_y
= 3;
1158 } else if (v
->child
->begin_y
> LINES
- 1) {
1160 v
->child
->begin_y
= LINES
- 1;
1163 v
->child
->ncols
= COLS
;
1164 view_adjust_offset(view
, resize
);
1165 err
= view_init_hsplit(v
, v
->child
->begin_y
);
1168 v
->child
->resized_y
= v
->child
->begin_y
;
1170 if (v
->child
->resized_x
)
1171 v
->child
->begin_x
= v
->child
->resized_x
;
1173 v
->child
->begin_x
-= resize
;
1175 v
->child
->begin_x
+= resize
;
1176 if (v
->child
->begin_x
< 11) {
1178 v
->child
->begin_x
= 11;
1179 } else if (v
->child
->begin_x
> COLS
- 1) {
1181 v
->child
->begin_x
= COLS
- 1;
1183 v
->child
->resized_x
= v
->child
->begin_x
;
1186 v
->child
->mode
= v
->mode
;
1187 v
->child
->nlines
= v
->lines
- v
->child
->begin_y
;
1188 v
->child
->ncols
= v
->cols
- v
->child
->begin_x
;
1191 err
= view_fullscreen(v
);
1194 err
= view_splitscreen(v
->child
);
1198 if (v
->mode
== TOG_VIEW_SPLIT_HRZN
) {
1199 err
= offset_selection_down(v
->child
);
1205 err
= v
->resize(v
, 0);
1206 else if (v
->child
->resize
)
1207 err
= v
->child
->resize(v
->child
, 0);
1209 v
->resized
= v
->child
->resized
= 0;
1215 view_transfer_size(struct tog_view
*dst
, struct tog_view
*src
)
1217 struct tog_view
*v
= src
->child
? src
->child
: src
;
1219 dst
->resized_x
= v
->resized_x
;
1220 dst
->resized_y
= v
->resized_y
;
1223 static const struct got_error
*
1224 view_close_child(struct tog_view
*view
)
1226 const struct got_error
*err
= NULL
;
1228 if (view
->child
== NULL
)
1231 err
= view_close(view
->child
);
1236 static const struct got_error
*
1237 view_set_child(struct tog_view
*view
, struct tog_view
*child
)
1239 const struct got_error
*err
= NULL
;
1241 view
->child
= child
;
1242 child
->parent
= view
;
1244 err
= view_resize(view
);
1248 if (view
->child
->resized_x
|| view
->child
->resized_y
)
1249 err
= view_resize_split(view
, 0);
1254 static const struct got_error
*view_dispatch_request(struct tog_view
**,
1255 struct tog_view
*, enum tog_view_type
, int, int);
1257 static const struct got_error
*
1258 view_request_new(struct tog_view
**requested
, struct tog_view
*view
,
1259 enum tog_view_type request
)
1261 struct tog_view
*new_view
= NULL
;
1262 const struct got_error
*err
;
1267 if (view_is_parent_view(view
) && request
!= TOG_VIEW_HELP
)
1268 view_get_split(view
, &y
, &x
);
1270 err
= view_dispatch_request(&new_view
, view
, request
, y
, x
);
1274 if (view_is_parent_view(view
) && view
->mode
== TOG_VIEW_SPLIT_HRZN
&&
1275 request
!= TOG_VIEW_HELP
) {
1276 err
= view_init_hsplit(view
, y
);
1282 new_view
->focussed
= 1;
1283 new_view
->mode
= view
->mode
;
1284 new_view
->nlines
= request
== TOG_VIEW_HELP
?
1285 view
->lines
: view
->lines
- y
;
1287 if (view_is_parent_view(view
) && request
!= TOG_VIEW_HELP
) {
1288 view_transfer_size(new_view
, view
);
1289 err
= view_close_child(view
);
1292 err
= view_set_child(view
, new_view
);
1295 view
->focus_child
= 1;
1297 *requested
= new_view
;
1303 tog_resizeterm(void)
1306 struct winsize size
;
1308 if (ioctl(STDOUT_FILENO
, TIOCGWINSZ
, &size
) < 0) {
1309 cols
= 80; /* Default */
1313 lines
= size
.ws_row
;
1315 resize_term(lines
, cols
);
1318 static const struct got_error
*
1319 view_search_start(struct tog_view
*view
, int fast_refresh
)
1321 const struct got_error
*err
= NULL
;
1322 struct tog_view
*v
= view
;
1326 if (view
->search_started
) {
1327 regfree(&view
->regex
);
1328 view
->searching
= 0;
1329 memset(&view
->regmatch
, 0, sizeof(view
->regmatch
));
1331 view
->search_started
= 0;
1333 if (view
->nlines
< 1)
1336 if (view_is_hsplit_top(view
))
1338 else if (view
->mode
== TOG_VIEW_SPLIT_VERT
&& view
->parent
)
1341 if (tog_io
.input_str
!= NULL
) {
1342 if (strlcpy(pattern
, tog_io
.input_str
, sizeof(pattern
)) >=
1344 return got_error(GOT_ERR_NO_SPACE
);
1346 mvwaddstr(v
->window
, v
->nlines
- 1, 0, "/");
1347 wclrtoeol(v
->window
);
1348 nodelay(v
->window
, FALSE
); /* block for search term input */
1351 ret
= wgetnstr(v
->window
, pattern
, sizeof(pattern
));
1352 wrefresh(v
->window
);
1355 nodelay(v
->window
, TRUE
);
1356 if (!fast_refresh
&& !using_mock_io
)
1362 if (regcomp(&view
->regex
, pattern
, REG_EXTENDED
| REG_NEWLINE
) == 0) {
1363 err
= view
->search_start(view
);
1365 regfree(&view
->regex
);
1368 view
->search_started
= 1;
1369 view
->searching
= TOG_SEARCH_FORWARD
;
1370 view
->search_next_done
= 0;
1371 view
->search_next(view
);
1377 /* Switch split mode. If view is a parent or child, draw the new splitscreen. */
1378 static const struct got_error
*
1379 switch_split(struct tog_view
*view
)
1381 const struct got_error
*err
= NULL
;
1382 struct tog_view
*v
= NULL
;
1389 if (v
->mode
== TOG_VIEW_SPLIT_HRZN
)
1390 v
->mode
= TOG_VIEW_SPLIT_VERT
;
1392 v
->mode
= TOG_VIEW_SPLIT_HRZN
;
1396 else if (v
->mode
== TOG_VIEW_SPLIT_VERT
&& v
->cols
< 120)
1397 v
->mode
= TOG_VIEW_SPLIT_NONE
;
1399 view_get_split(v
, &v
->child
->begin_y
, &v
->child
->begin_x
);
1400 if (v
->mode
== TOG_VIEW_SPLIT_HRZN
&& v
->child
->resized_y
)
1401 v
->child
->begin_y
= v
->child
->resized_y
;
1402 else if (v
->mode
== TOG_VIEW_SPLIT_VERT
&& v
->child
->resized_x
)
1403 v
->child
->begin_x
= v
->child
->resized_x
;
1406 if (v
->mode
== TOG_VIEW_SPLIT_HRZN
) {
1408 v
->child
->ncols
= COLS
;
1409 v
->child
->nscrolled
= LINES
- v
->child
->nlines
;
1411 err
= view_init_hsplit(v
, v
->child
->begin_y
);
1415 v
->child
->mode
= v
->mode
;
1416 v
->child
->nlines
= v
->lines
- v
->child
->begin_y
;
1419 err
= view_fullscreen(v
);
1422 err
= view_splitscreen(v
->child
);
1426 if (v
->mode
== TOG_VIEW_SPLIT_NONE
)
1427 v
->mode
= TOG_VIEW_SPLIT_VERT
;
1428 if (v
->mode
== TOG_VIEW_SPLIT_HRZN
) {
1429 err
= offset_selection_down(v
);
1432 err
= offset_selection_down(v
->child
);
1436 offset_selection_up(v
);
1437 offset_selection_up(v
->child
);
1440 err
= v
->resize(v
, 0);
1441 else if (v
->child
->resize
)
1442 err
= v
->child
->resize(v
->child
, 0);
1448 * Strip trailing whitespace from str starting at byte *n;
1449 * if *n < 0, use strlen(str). Return new str length in *n.
1452 strip_trailing_ws(char *str
, int *n
)
1456 if (str
== NULL
|| *str
== '\0')
1462 while (x
-- > 0 && isspace((unsigned char)str
[x
]))
1469 * Extract visible substring of line y from the curses screen
1470 * and strip trailing whitespace. If vline is set, overwrite
1471 * line[vline] with '|' because the ACS_VLINE character is
1472 * written out as 'x'. Write the line to file f.
1474 static const struct got_error
*
1475 view_write_line(FILE *f
, int y
, int vline
)
1477 char line
[COLS
* MB_LEN_MAX
]; /* allow for multibyte chars */
1480 r
= mvwinnstr(curscr
, y
, 0, line
, sizeof(line
));
1482 return got_error_fmt(GOT_ERR_RANGE
,
1483 "failed to extract line %d", y
);
1486 * In some views, lines are padded with blanks to COLS width.
1487 * Strip them so we can diff without the -b flag when testing.
1489 strip_trailing_ws(line
, &r
);
1494 w
= fprintf(f
, "%s\n", line
);
1495 if (w
!= r
+ 1) /* \n */
1496 return got_ferror(f
, GOT_ERR_IO
);
1502 * Capture the visible curses screen by writing each line to the
1503 * file at the path set via the TOG_SCR_DUMP environment variable.
1505 static const struct got_error
*
1506 screendump(struct tog_view
*view
)
1508 const struct got_error
*err
;
1511 err
= got_opentemp_truncate(tog_io
.sdump
);
1515 if ((view
->child
&& view
->child
->begin_x
) ||
1516 (view
->parent
&& view
->begin_x
)) {
1517 int ncols
= view
->child
? view
->ncols
: view
->parent
->ncols
;
1519 /* vertical splitscreen */
1520 for (i
= 0; i
< view
->nlines
; ++i
) {
1521 err
= view_write_line(tog_io
.sdump
, i
, ncols
- 1);
1528 /* fullscreen or horizontal splitscreen */
1529 if ((view
->child
&& view
->child
->begin_y
) ||
1530 (view
->parent
&& view
->begin_y
)) /* hsplit */
1531 hline
= view
->child
?
1532 view
->child
->begin_y
: view
->begin_y
;
1534 for (i
= 0; i
< view
->lines
; i
++) {
1535 if (hline
&& i
== hline
- 1) {
1538 /* ACS_HLINE writes out as 'q', overwrite it */
1539 for (c
= 0; c
< view
->cols
; ++c
)
1540 fputc('-', tog_io
.sdump
);
1541 fputc('\n', tog_io
.sdump
);
1545 err
= view_write_line(tog_io
.sdump
, i
, 0);
1556 * Compute view->count from numeric input. Assign total to view->count and
1557 * return first non-numeric key entered.
1560 get_compound_key(struct tog_view
*view
, int c
)
1562 struct tog_view
*v
= view
;
1565 if (view_is_hsplit_top(view
))
1567 else if (view
->mode
== TOG_VIEW_SPLIT_VERT
&& view
->parent
)
1571 cbreak(); /* block for input */
1572 nodelay(view
->window
, FALSE
);
1573 wmove(v
->window
, v
->nlines
- 1, 0);
1574 wclrtoeol(v
->window
);
1575 waddch(v
->window
, ':');
1578 x
= getcurx(v
->window
);
1579 if (x
!= ERR
&& x
< view
->ncols
) {
1580 waddch(v
->window
, c
);
1581 wrefresh(v
->window
);
1585 * Don't overflow. Max valid request should be the greatest
1586 * between the longest and total lines; cap at 10 million.
1591 n
= n
* 10 + (c
- '0');
1592 } while (((c
= wgetch(view
->window
))) >= '0' && c
<= '9' && c
!= ERR
);
1594 if (c
== 'G' || c
== 'g') { /* nG key map */
1595 view
->gline
= view
->hiline
= n
;
1600 /* Massage excessive or inapplicable values at the input handler. */
1607 action_report(struct tog_view
*view
)
1609 struct tog_view
*v
= view
;
1611 if (view_is_hsplit_top(view
))
1613 else if (view
->mode
== TOG_VIEW_SPLIT_VERT
&& view
->parent
)
1616 wmove(v
->window
, v
->nlines
- 1, 0);
1617 wclrtoeol(v
->window
);
1618 wprintw(v
->window
, ":%s", view
->action
);
1619 wrefresh(v
->window
);
1622 * Clear action status report. Only clear in blame view
1623 * once annotating is complete, otherwise it's too fast.
1625 if (view
->type
== TOG_VIEW_BLAME
) {
1626 if (view
->state
.blame
.blame_complete
)
1627 view
->action
= NULL
;
1629 view
->action
= NULL
;
1633 * Read the next line from the test script and assign
1634 * key instruction to *ch. If at EOF, set the *done flag.
1636 static const struct got_error
*
1637 tog_read_script_key(FILE *script
, struct tog_view
*view
, int *ch
, int *done
)
1639 const struct got_error
*err
= NULL
;
1645 if (view
->count
&& --view
->count
) {
1651 if ((n
= getline(&line
, &linesz
, script
)) == -1) {
1656 err
= got_ferror(script
, GOT_ERR_IO
);
1661 if (strncasecmp(line
, "WAIT_FOR_UI", 11) == 0)
1662 tog_io
.wait_for_ui
= 1;
1663 else if (strncasecmp(line
, "KEY_ENTER", 9) == 0)
1665 else if (strncasecmp(line
, "KEY_RIGHT", 9) == 0)
1667 else if (strncasecmp(line
, "KEY_LEFT", 8) == 0)
1669 else if (strncasecmp(line
, "KEY_DOWN", 8) == 0)
1671 else if (strncasecmp(line
, "KEY_UP", 6) == 0)
1673 else if (strncasecmp(line
, "TAB", 3) == 0)
1675 else if (strncasecmp(line
, "SCREENDUMP", 10) == 0)
1676 *ch
= TOG_KEY_SCRDUMP
;
1677 else if (isdigit((unsigned char)*line
)) {
1680 while (isdigit((unsigned char)*t
))
1682 view
->ch
= *ch
= *t
;
1684 /* ignore error, view->count is 0 if instruction is invalid */
1685 view
->count
= strtonum(line
, 0, INT_MAX
, NULL
);
1688 if (n
> 2 && (*ch
== '/' || *ch
== '&')) {
1689 /* skip leading keymap and trim trailing newline */
1690 tog_io
.input_str
= strndup(line
+ 1, n
- 2);
1691 if (tog_io
.input_str
== NULL
) {
1692 err
= got_error_from_errno("strndup");
1703 static const struct got_error
*
1704 view_input(struct tog_view
**new, int *done
, struct tog_view
*view
,
1705 struct tog_view_list_head
*views
, int fast_refresh
)
1707 const struct got_error
*err
= NULL
;
1714 action_report(view
);
1716 /* Clear "no matches" indicator. */
1717 if (view
->search_next_done
== TOG_SEARCH_NO_MORE
||
1718 view
->search_next_done
== TOG_SEARCH_HAVE_NONE
) {
1719 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
1723 if (view
->searching
&& !view
->search_next_done
) {
1724 errcode
= pthread_mutex_unlock(&tog_mutex
);
1726 return got_error_set_errno(errcode
,
1727 "pthread_mutex_unlock");
1729 errcode
= pthread_mutex_lock(&tog_mutex
);
1731 return got_error_set_errno(errcode
,
1732 "pthread_mutex_lock");
1733 view
->search_next(view
);
1737 /* Allow threads to make progress while we are waiting for input. */
1738 errcode
= pthread_mutex_unlock(&tog_mutex
);
1740 return got_error_set_errno(errcode
, "pthread_mutex_unlock");
1742 if (using_mock_io
) {
1743 err
= tog_read_script_key(tog_io
.f
, view
, &ch
, done
);
1745 errcode
= pthread_mutex_lock(&tog_mutex
);
1748 } else if (view
->count
&& --view
->count
) {
1750 nodelay(view
->window
, TRUE
);
1751 ch
= wgetch(view
->window
);
1752 /* let C-g or backspace abort unfinished count */
1753 if (ch
== CTRL('g') || ch
== KEY_BACKSPACE
)
1758 ch
= wgetch(view
->window
);
1759 if (ch
>= '1' && ch
<= '9')
1760 view
->ch
= ch
= get_compound_key(view
, ch
);
1762 if (view
->hiline
&& ch
!= ERR
&& ch
!= 0)
1763 view
->hiline
= 0; /* key pressed, clear line highlight */
1764 nodelay(view
->window
, TRUE
);
1765 errcode
= pthread_mutex_lock(&tog_mutex
);
1767 return got_error_set_errno(errcode
, "pthread_mutex_lock");
1769 if (tog_sigwinch_received
|| tog_sigcont_received
) {
1771 tog_sigwinch_received
= 0;
1772 tog_sigcont_received
= 0;
1773 TAILQ_FOREACH(v
, views
, entry
) {
1774 err
= view_resize(v
);
1777 err
= v
->input(new, v
, KEY_RESIZE
);
1781 err
= view_resize(v
->child
);
1784 err
= v
->child
->input(new, v
->child
,
1788 if (v
->child
->resized_x
|| v
->child
->resized_y
) {
1789 err
= view_resize_split(v
, 0);
1801 if (view
->type
== TOG_VIEW_HELP
)
1802 err
= view
->reset(view
);
1804 err
= view_request_new(new, view
, TOG_VIEW_HELP
);
1810 view
->child
->focussed
= 1;
1811 view
->focus_child
= 1;
1812 } else if (view
->parent
) {
1814 view
->parent
->focussed
= 1;
1815 view
->parent
->focus_child
= 0;
1816 if (!view_is_splitscreen(view
)) {
1817 if (view
->parent
->resize
) {
1818 err
= view
->parent
->resize(view
->parent
,
1823 offset_selection_up(view
->parent
);
1824 err
= view_fullscreen(view
->parent
);
1831 if (view
->parent
&& view
->mode
== TOG_VIEW_SPLIT_HRZN
) {
1832 if (view
->parent
->resize
) {
1833 /* might need more commits to fill fullscreen */
1834 err
= view
->parent
->resize(view
->parent
, 0);
1838 offset_selection_up(view
->parent
);
1840 err
= view
->input(new, view
, ch
);
1848 if (view_is_parent_view(view
)) {
1849 if (view
->child
== NULL
)
1851 if (view_is_splitscreen(view
->child
)) {
1853 view
->child
->focussed
= 1;
1854 err
= view_fullscreen(view
->child
);
1856 err
= view_splitscreen(view
->child
);
1858 err
= view_resize_split(view
, 0);
1862 err
= view
->child
->input(new, view
->child
,
1865 if (view_is_splitscreen(view
)) {
1866 view
->parent
->focussed
= 0;
1868 err
= view_fullscreen(view
);
1870 err
= view_splitscreen(view
);
1871 if (!err
&& view
->mode
!= TOG_VIEW_SPLIT_HRZN
)
1872 err
= view_resize(view
->parent
);
1874 err
= view_resize_split(view
, 0);
1878 err
= view
->input(new, view
, KEY_RESIZE
);
1883 err
= view
->resize(view
, 0);
1888 if (view
->parent
->resize
) {
1889 err
= view
->parent
->resize(view
->parent
, 0);
1893 err
= offset_selection_down(view
->parent
);
1897 err
= offset_selection_down(view
);
1901 err
= switch_split(view
);
1904 err
= view_resize_split(view
, -1);
1907 err
= view_resize_split(view
, 1);
1913 if (view
->search_start
)
1914 view_search_start(view
, fast_refresh
);
1916 err
= view
->input(new, view
, ch
);
1920 if (view
->search_started
&& view
->search_next
) {
1921 view
->searching
= (ch
== 'n' ?
1922 TOG_SEARCH_FORWARD
: TOG_SEARCH_BACKWARD
);
1923 view
->search_next_done
= 0;
1924 view
->search_next(view
);
1926 err
= view
->input(new, view
, ch
);
1929 if (tog_diff_algo
== GOT_DIFF_ALGORITHM_MYERS
) {
1930 tog_diff_algo
= GOT_DIFF_ALGORITHM_PATIENCE
;
1931 view
->action
= "Patience diff algorithm";
1933 tog_diff_algo
= GOT_DIFF_ALGORITHM_MYERS
;
1934 view
->action
= "Myers diff algorithm";
1936 TAILQ_FOREACH(v
, views
, entry
) {
1942 if (v
->child
&& v
->child
->reset
) {
1943 err
= v
->child
->reset(v
->child
);
1949 case TOG_KEY_SCRDUMP
:
1950 err
= screendump(view
);
1953 err
= view
->input(new, view
, ch
);
1961 view_needs_focus_indication(struct tog_view
*view
)
1963 if (view_is_parent_view(view
)) {
1964 if (view
->child
== NULL
|| view
->child
->focussed
)
1966 if (!view_is_splitscreen(view
->child
))
1968 } else if (!view_is_splitscreen(view
))
1971 return view
->focussed
;
1974 static const struct got_error
*
1977 const struct got_error
*err
= NULL
;
1979 if (tog_io
.cin
&& fclose(tog_io
.cin
) == EOF
)
1980 err
= got_ferror(tog_io
.cin
, GOT_ERR_IO
);
1981 if (tog_io
.cout
&& fclose(tog_io
.cout
) == EOF
&& err
== NULL
)
1982 err
= got_ferror(tog_io
.cout
, GOT_ERR_IO
);
1983 if (tog_io
.f
&& fclose(tog_io
.f
) == EOF
&& err
== NULL
)
1984 err
= got_ferror(tog_io
.f
, GOT_ERR_IO
);
1985 if (tog_io
.sdump
&& fclose(tog_io
.sdump
) == EOF
&& err
== NULL
)
1986 err
= got_ferror(tog_io
.sdump
, GOT_ERR_IO
);
1987 if (tog_io
.input_str
!= NULL
)
1988 free(tog_io
.input_str
);
1993 static const struct got_error
*
1994 view_loop(struct tog_view
*view
)
1996 const struct got_error
*err
= NULL
;
1997 struct tog_view_list_head views
;
1998 struct tog_view
*new_view
;
2000 int fast_refresh
= 10;
2001 int done
= 0, errcode
;
2003 mode
= getenv("TOG_VIEW_SPLIT_MODE");
2004 if (!mode
|| !(*mode
== 'h' || *mode
== 'H'))
2005 view
->mode
= TOG_VIEW_SPLIT_VERT
;
2007 view
->mode
= TOG_VIEW_SPLIT_HRZN
;
2009 errcode
= pthread_mutex_lock(&tog_mutex
);
2011 return got_error_set_errno(errcode
, "pthread_mutex_lock");
2014 TAILQ_INSERT_HEAD(&views
, view
, entry
);
2017 err
= view
->show(view
);
2022 while (!TAILQ_EMPTY(&views
) && !done
&& !tog_thread_error
&&
2023 !tog_fatal_signal_received()) {
2024 /* Refresh fast during initialization, then become slower. */
2025 if (fast_refresh
&& --fast_refresh
== 0 && !using_mock_io
)
2026 halfdelay(10); /* switch to once per second */
2028 err
= view_input(&new_view
, &done
, view
, &views
, fast_refresh
);
2032 if (view
->dying
&& view
== TAILQ_FIRST(&views
) &&
2033 TAILQ_NEXT(view
, entry
) == NULL
)
2039 * When we quit, scroll the screen up a single line
2040 * so we don't lose any information.
2042 TAILQ_FOREACH(v
, &views
, entry
) {
2043 wmove(v
->window
, 0, 0);
2044 wdeleteln(v
->window
);
2045 wnoutrefresh(v
->window
);
2046 if (v
->child
&& !view_is_fullscreen(v
)) {
2047 wmove(v
->child
->window
, 0, 0);
2048 wdeleteln(v
->child
->window
);
2049 wnoutrefresh(v
->child
->window
);
2056 struct tog_view
*v
, *prev
= NULL
;
2058 if (view_is_parent_view(view
))
2059 prev
= TAILQ_PREV(view
, tog_view_list_head
,
2061 else if (view
->parent
)
2062 prev
= view
->parent
;
2065 view
->parent
->child
= NULL
;
2066 view
->parent
->focus_child
= 0;
2067 /* Restore fullscreen line height. */
2068 view
->parent
->nlines
= view
->parent
->lines
;
2069 err
= view_resize(view
->parent
);
2072 /* Make resized splits persist. */
2073 view_transfer_size(view
->parent
, view
);
2075 TAILQ_REMOVE(&views
, view
, entry
);
2077 err
= view_close(view
);
2082 TAILQ_FOREACH(v
, &views
, entry
) {
2086 if (view
== NULL
&& new_view
== NULL
) {
2087 /* No view has focus. Try to pick one. */
2090 else if (!TAILQ_EMPTY(&views
)) {
2091 view
= TAILQ_LAST(&views
,
2092 tog_view_list_head
);
2095 if (view
->focus_child
) {
2096 view
->child
->focussed
= 1;
2104 struct tog_view
*v
, *t
;
2105 /* Only allow one parent view per type. */
2106 TAILQ_FOREACH_SAFE(v
, &views
, entry
, t
) {
2107 if (v
->type
!= new_view
->type
)
2109 TAILQ_REMOVE(&views
, v
, entry
);
2110 err
= view_close(v
);
2115 TAILQ_INSERT_TAIL(&views
, new_view
, entry
);
2118 if (view
&& !done
) {
2119 if (view_is_parent_view(view
)) {
2120 if (view
->child
&& view
->child
->focussed
)
2123 if (view
->parent
&& view
->parent
->focussed
)
2124 view
= view
->parent
;
2126 show_panel(view
->panel
);
2127 if (view
->child
&& view_is_splitscreen(view
->child
))
2128 show_panel(view
->child
->panel
);
2129 if (view
->parent
&& view_is_splitscreen(view
)) {
2130 err
= view
->parent
->show(view
->parent
);
2134 err
= view
->show(view
);
2138 err
= view
->child
->show(view
->child
);
2147 while (!TAILQ_EMPTY(&views
)) {
2148 const struct got_error
*close_err
;
2149 view
= TAILQ_FIRST(&views
);
2150 TAILQ_REMOVE(&views
, view
, entry
);
2151 close_err
= view_close(view
);
2152 if (close_err
&& err
== NULL
)
2156 errcode
= pthread_mutex_unlock(&tog_mutex
);
2157 if (errcode
&& err
== NULL
)
2158 err
= got_error_set_errno(errcode
, "pthread_mutex_unlock");
2168 "usage: %s log [-b] [-c commit] [-r repository-path] [path]\n",
2173 /* Create newly allocated wide-character string equivalent to a byte string. */
2174 static const struct got_error
*
2175 mbs2ws(wchar_t **ws
, size_t *wlen
, const char *s
)
2178 const struct got_error
*err
= NULL
;
2181 *wlen
= mbstowcs(NULL
, s
, 0);
2182 if (*wlen
== (size_t)-1) {
2184 if (errno
!= EILSEQ
)
2185 return got_error_from_errno("mbstowcs");
2187 /* byte string invalid in current encoding; try to "fix" it */
2188 err
= got_mbsavis(&vis
, &vislen
, s
);
2191 *wlen
= mbstowcs(NULL
, vis
, 0);
2192 if (*wlen
== (size_t)-1) {
2193 err
= got_error_from_errno("mbstowcs"); /* give up */
2198 *ws
= calloc(*wlen
+ 1, sizeof(**ws
));
2200 err
= got_error_from_errno("calloc");
2204 if (mbstowcs(*ws
, vis
? vis
: s
, *wlen
) != *wlen
)
2205 err
= got_error_from_errno("mbstowcs");
2216 static const struct got_error
*
2217 expand_tab(char **ptr
, const char *src
)
2220 size_t len
, n
, idx
= 0, sz
= 0;
2223 n
= len
= strlen(src
);
2224 dst
= malloc(n
+ 1);
2226 return got_error_from_errno("malloc");
2228 while (idx
< len
&& src
[idx
]) {
2229 const char c
= src
[idx
];
2232 size_t nb
= TABSIZE
- sz
% TABSIZE
;
2235 p
= realloc(dst
, n
+ nb
);
2238 return got_error_from_errno("realloc");
2243 memset(dst
+ sz
, ' ', nb
);
2246 dst
[sz
++] = src
[idx
];
2256 * Advance at most n columns from wline starting at offset off.
2257 * Return the index to the first character after the span operation.
2258 * Return the combined column width of all spanned wide characters in
2262 span_wline(int *rcol
, int off
, wchar_t *wline
, int n
, int col_tab_align
)
2264 int width
, i
, cols
= 0;
2271 for (i
= off
; wline
[i
] != L
'\0'; ++i
) {
2272 if (wline
[i
] == L
'\t')
2273 width
= TABSIZE
- ((cols
+ col_tab_align
) % TABSIZE
);
2275 width
= wcwidth(wline
[i
]);
2282 if (cols
+ width
> n
)
2292 * Format a line for display, ensuring that it won't overflow a width limit.
2293 * With scrolling, the width returned refers to the scrolled version of the
2294 * line, which starts at (*wlinep)[*scrollxp]. The caller must free *wlinep.
2296 static const struct got_error
*
2297 format_line(wchar_t **wlinep
, int *widthp
, int *scrollxp
,
2298 const char *line
, int nscroll
, int wlimit
, int col_tab_align
, int expand
)
2300 const struct got_error
*err
= NULL
;
2302 wchar_t *wline
= NULL
;
2311 err
= expand_tab(&exstr
, line
);
2316 err
= mbs2ws(&wline
, &wlen
, expand
? exstr
: line
);
2321 scrollx
= span_wline(&cols
, 0, wline
, nscroll
, col_tab_align
);
2323 if (wlen
> 0 && wline
[wlen
- 1] == L
'\n') {
2324 wline
[wlen
- 1] = L
'\0';
2327 if (wlen
> 0 && wline
[wlen
- 1] == L
'\r') {
2328 wline
[wlen
- 1] = L
'\0';
2332 i
= span_wline(&cols
, scrollx
, wline
, wlimit
, col_tab_align
);
2338 *scrollxp
= scrollx
;
2346 static const struct got_error
*
2347 build_refs_str(char **refs_str
, struct got_reflist_head
*refs
,
2348 struct got_object_id
*id
, struct got_repository
*repo
)
2350 static const struct got_error
*err
= NULL
;
2351 struct got_reflist_entry
*re
;
2360 TAILQ_FOREACH(re
, refs
, entry
) {
2361 struct got_tag_object
*tag
= NULL
;
2362 struct got_object_id
*ref_id
;
2365 name
= got_ref_get_name(re
->ref
);
2366 if (strcmp(name
, GOT_REF_HEAD
) == 0)
2368 if (strncmp(name
, "refs/", 5) == 0)
2370 if (strncmp(name
, "got/", 4) == 0)
2372 if (strncmp(name
, "heads/", 6) == 0)
2374 if (strncmp(name
, "remotes/", 8) == 0) {
2376 s
= strstr(name
, "/" GOT_REF_HEAD
);
2377 if (s
!= NULL
&& strcmp(s
, "/" GOT_REF_HEAD
) == 0)
2380 err
= got_ref_resolve(&ref_id
, repo
, re
->ref
);
2383 if (strncmp(name
, "tags/", 5) == 0) {
2384 err
= got_object_open_as_tag(&tag
, repo
, ref_id
);
2386 if (err
->code
!= GOT_ERR_OBJ_TYPE
) {
2390 /* Ref points at something other than a tag. */
2395 cmp
= got_object_id_cmp(tag
?
2396 got_object_tag_get_object_id(tag
) : ref_id
, id
);
2399 got_object_tag_close(tag
);
2403 if (asprintf(refs_str
, "%s%s%s", s
? s
: "",
2404 s
? ", " : "", name
) == -1) {
2405 err
= got_error_from_errno("asprintf");
2416 static const struct got_error
*
2417 format_author(wchar_t **wauthor
, int *author_width
, char *author
, int limit
,
2422 smallerthan
= strchr(author
, '<');
2423 if (smallerthan
&& smallerthan
[1] != '\0')
2424 author
= smallerthan
+ 1;
2425 author
[strcspn(author
, "@>")] = '\0';
2426 return format_line(wauthor
, author_width
, NULL
, author
, 0, limit
,
2430 static const struct got_error
*
2431 draw_commit(struct tog_view
*view
, struct commit_queue_entry
*entry
,
2432 const size_t date_display_cols
, int author_display_cols
)
2434 struct tog_log_view_state
*s
= &view
->state
.log
;
2435 const struct got_error
*err
= NULL
;
2436 struct got_commit_object
*commit
= entry
->commit
;
2437 struct got_object_id
*id
= entry
->id
;
2438 char datebuf
[12]; /* YYYY-MM-DD + SPACE + NUL */
2439 char *refs_str
= NULL
;
2440 char *logmsg0
= NULL
, *logmsg
= NULL
;
2441 char *author
= NULL
;
2442 wchar_t *wrefstr
= NULL
, *wlogmsg
= NULL
, *wauthor
= NULL
;
2443 int author_width
, refstr_width
, logmsg_width
;
2444 char *newline
, *line
= NULL
;
2445 int col
, limit
, scrollx
, logmsg_x
;
2446 const int avail
= view
->ncols
, marker_column
= author_display_cols
+ 1;
2448 time_t committer_time
;
2449 struct tog_color
*tc
;
2450 struct got_reflist_head
*refs
;
2452 if (tog_base_commit
.id
!= NULL
&& tog_base_commit
.idx
== -1 &&
2453 got_object_id_cmp(id
, tog_base_commit
.id
) == 0)
2454 tog_base_commit
.idx
= entry
->idx
;
2455 if (tog_io
.wait_for_ui
&& s
->thread_args
.need_commit_marker
) {
2458 rc
= pthread_cond_wait(&s
->thread_args
.log_loaded
, &tog_mutex
);
2460 return got_error_set_errno(rc
, "pthread_cond_wait");
2463 committer_time
= got_object_commit_get_committer_time(commit
);
2464 if (gmtime_r(&committer_time
, &tm
) == NULL
)
2465 return got_error_from_errno("gmtime_r");
2466 if (strftime(datebuf
, sizeof(datebuf
), "%G-%m-%d ", &tm
) == 0)
2467 return got_error(GOT_ERR_NO_SPACE
);
2469 if (avail
<= date_display_cols
)
2470 limit
= MIN(sizeof(datebuf
) - 1, avail
);
2472 limit
= MIN(date_display_cols
, sizeof(datebuf
) - 1);
2473 tc
= get_color(&s
->colors
, TOG_COLOR_DATE
);
2475 wattr_on(view
->window
,
2476 COLOR_PAIR(tc
->colorpair
), NULL
);
2477 waddnstr(view
->window
, datebuf
, limit
);
2479 wattr_off(view
->window
,
2480 COLOR_PAIR(tc
->colorpair
), NULL
);
2487 err
= got_object_id_str(&id_str
, id
);
2490 tc
= get_color(&s
->colors
, TOG_COLOR_COMMIT
);
2492 wattr_on(view
->window
,
2493 COLOR_PAIR(tc
->colorpair
), NULL
);
2494 wprintw(view
->window
, "%.8s ", id_str
);
2496 wattr_off(view
->window
,
2497 COLOR_PAIR(tc
->colorpair
), NULL
);
2504 if (s
->use_committer
)
2505 author
= strdup(got_object_commit_get_committer(commit
));
2507 author
= strdup(got_object_commit_get_author(commit
));
2508 if (author
== NULL
) {
2509 err
= got_error_from_errno("strdup");
2512 err
= format_author(&wauthor
, &author_width
, author
, avail
- col
, col
);
2515 tc
= get_color(&s
->colors
, TOG_COLOR_AUTHOR
);
2517 wattr_on(view
->window
,
2518 COLOR_PAIR(tc
->colorpair
), NULL
);
2519 waddwstr(view
->window
, wauthor
);
2520 col
+= author_width
;
2521 while (col
< avail
&& author_width
< author_display_cols
+ 2) {
2522 if (tog_base_commit
.marker
!= GOT_WORKTREE_STATE_UNKNOWN
&&
2523 author_width
== marker_column
&&
2524 entry
->idx
== tog_base_commit
.idx
&& !s
->limit_view
) {
2525 tc
= get_color(&s
->colors
, TOG_COLOR_COMMIT
);
2527 wattr_on(view
->window
,
2528 COLOR_PAIR(tc
->colorpair
), NULL
);
2529 waddch(view
->window
, tog_base_commit
.marker
);
2531 wattr_off(view
->window
,
2532 COLOR_PAIR(tc
->colorpair
), NULL
);
2534 waddch(view
->window
, ' ');
2539 wattr_off(view
->window
,
2540 COLOR_PAIR(tc
->colorpair
), NULL
);
2544 err
= got_object_commit_get_logmsg(&logmsg0
, commit
);
2548 while (*logmsg
== '\n')
2550 newline
= strchr(logmsg
, '\n');
2554 limit
= avail
- col
;
2555 if (view
->child
&& !view_is_hsplit_top(view
) && limit
> 0)
2556 limit
--; /* for the border */
2558 /* Prepend reference labels to log message if possible .*/
2559 refs
= got_reflist_object_id_map_lookup(tog_refs_idmap
, id
);
2560 err
= build_refs_str(&refs_str
, refs
, id
, s
->repo
);
2566 if (asprintf(&rs
, "[%s]", refs_str
) == -1) {
2567 err
= got_error_from_errno("asprintf");
2570 err
= format_line(&wrefstr
, &refstr_width
,
2571 &scrollx
, rs
, view
->x
, limit
, col
, 1);
2575 tc
= get_color(&s
->colors
, TOG_COLOR_COMMIT
);
2577 wattr_on(view
->window
,
2578 COLOR_PAIR(tc
->colorpair
), NULL
);
2579 waddwstr(view
->window
, &wrefstr
[scrollx
]);
2581 wattr_off(view
->window
,
2582 COLOR_PAIR(tc
->colorpair
), NULL
);
2583 col
+= MAX(refstr_width
, 0);
2588 waddch(view
->window
, ' ');
2592 if (refstr_width
> 0)
2595 int unscrolled_refstr_width
;
2596 size_t len
= wcslen(wrefstr
);
2599 * No need to check for -1 return value here since
2600 * unprintables have been replaced by span_wline().
2602 unscrolled_refstr_width
= wcswidth(wrefstr
, len
);
2603 unscrolled_refstr_width
+= 1; /* trailing space */
2604 logmsg_x
= view
->x
- unscrolled_refstr_width
;
2607 limit
= avail
- col
;
2608 if (view
->child
&& !view_is_hsplit_top(view
) && limit
> 0)
2609 limit
--; /* for the border */
2613 err
= format_line(&wlogmsg
, &logmsg_width
, &scrollx
, logmsg
, logmsg_x
,
2617 waddwstr(view
->window
, &wlogmsg
[scrollx
]);
2618 col
+= MAX(logmsg_width
, 0);
2619 while (col
< avail
) {
2620 waddch(view
->window
, ' ');
2634 static struct commit_queue_entry
*
2635 alloc_commit_queue_entry(struct got_commit_object
*commit
,
2636 struct got_object_id
*id
)
2638 struct commit_queue_entry
*entry
;
2639 struct got_object_id
*dup
;
2641 entry
= calloc(1, sizeof(*entry
));
2645 dup
= got_object_id_dup(id
);
2652 entry
->commit
= commit
;
2657 pop_commit(struct commit_queue
*commits
)
2659 struct commit_queue_entry
*entry
;
2661 entry
= TAILQ_FIRST(&commits
->head
);
2662 TAILQ_REMOVE(&commits
->head
, entry
, entry
);
2663 got_object_commit_close(entry
->commit
);
2664 commits
->ncommits
--;
2670 free_commits(struct commit_queue
*commits
)
2672 while (!TAILQ_EMPTY(&commits
->head
))
2673 pop_commit(commits
);
2676 static const struct got_error
*
2677 match_commit(int *have_match
, struct got_object_id
*id
,
2678 struct got_commit_object
*commit
, regex_t
*regex
)
2680 const struct got_error
*err
= NULL
;
2681 regmatch_t regmatch
;
2682 char *id_str
= NULL
, *logmsg
= NULL
;
2686 err
= got_object_id_str(&id_str
, id
);
2690 err
= got_object_commit_get_logmsg(&logmsg
, commit
);
2694 if (regexec(regex
, got_object_commit_get_author(commit
), 1,
2695 ®match
, 0) == 0 ||
2696 regexec(regex
, got_object_commit_get_committer(commit
), 1,
2697 ®match
, 0) == 0 ||
2698 regexec(regex
, id_str
, 1, ®match
, 0) == 0 ||
2699 regexec(regex
, logmsg
, 1, ®match
, 0) == 0)
2707 static const struct got_error
*
2708 queue_commits(struct tog_log_thread_args
*a
)
2710 const struct got_error
*err
= NULL
;
2713 * We keep all commits open throughout the lifetime of the log
2714 * view in order to avoid having to re-fetch commits from disk
2715 * while updating the display.
2718 struct got_object_id id
;
2719 struct got_commit_object
*commit
;
2720 struct commit_queue_entry
*entry
;
2721 int limit_match
= 0;
2724 err
= got_commit_graph_iter_next(&id
, a
->graph
, a
->repo
,
2729 err
= got_object_open_as_commit(&commit
, a
->repo
, &id
);
2732 entry
= alloc_commit_queue_entry(commit
, &id
);
2733 if (entry
== NULL
) {
2734 err
= got_error_from_errno("alloc_commit_queue_entry");
2738 errcode
= pthread_mutex_lock(&tog_mutex
);
2740 err
= got_error_set_errno(errcode
,
2741 "pthread_mutex_lock");
2745 entry
->idx
= a
->real_commits
->ncommits
;
2746 TAILQ_INSERT_TAIL(&a
->real_commits
->head
, entry
, entry
);
2747 a
->real_commits
->ncommits
++;
2750 err
= match_commit(&limit_match
, &id
, commit
,
2756 struct commit_queue_entry
*matched
;
2758 matched
= alloc_commit_queue_entry(
2759 entry
->commit
, entry
->id
);
2760 if (matched
== NULL
) {
2761 err
= got_error_from_errno(
2762 "alloc_commit_queue_entry");
2765 matched
->commit
= entry
->commit
;
2766 got_object_commit_retain(entry
->commit
);
2768 matched
->idx
= a
->limit_commits
->ncommits
;
2769 TAILQ_INSERT_TAIL(&a
->limit_commits
->head
,
2771 a
->limit_commits
->ncommits
++;
2775 * This is how we signal log_thread() that we
2776 * have found a match, and that it should be
2777 * counted as a new entry for the view.
2779 a
->limit_match
= limit_match
;
2782 if (*a
->searching
== TOG_SEARCH_FORWARD
&&
2783 !*a
->search_next_done
) {
2785 err
= match_commit(&have_match
, &id
, commit
, a
->regex
);
2790 if (limit_match
&& have_match
)
2791 *a
->search_next_done
=
2792 TOG_SEARCH_HAVE_MORE
;
2793 } else if (have_match
)
2794 *a
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
2797 errcode
= pthread_mutex_unlock(&tog_mutex
);
2798 if (errcode
&& err
== NULL
)
2799 err
= got_error_set_errno(errcode
,
2800 "pthread_mutex_unlock");
2803 } while (*a
->searching
== TOG_SEARCH_FORWARD
&& !*a
->search_next_done
);
2809 select_commit(struct tog_log_view_state
*s
)
2811 struct commit_queue_entry
*entry
;
2814 entry
= s
->first_displayed_entry
;
2816 if (ncommits
== s
->selected
) {
2817 s
->selected_entry
= entry
;
2820 entry
= TAILQ_NEXT(entry
, entry
);
2825 static const struct got_error
*
2826 draw_commits(struct tog_view
*view
)
2828 const struct got_error
*err
= NULL
;
2829 struct tog_log_view_state
*s
= &view
->state
.log
;
2830 struct commit_queue_entry
*entry
= s
->selected_entry
;
2831 int limit
= view
->nlines
;
2833 int ncommits
, author_cols
= 4, refstr_cols
;
2834 char *id_str
= NULL
, *header
= NULL
, *ncommits_str
= NULL
;
2835 char *refs_str
= NULL
;
2837 struct tog_color
*tc
;
2838 static const size_t date_display_cols
= 12;
2839 struct got_reflist_head
*refs
;
2841 if (view_is_hsplit_top(view
))
2842 --limit
; /* account for border */
2844 if (s
->selected_entry
&&
2845 !(view
->searching
&& view
->search_next_done
== 0)) {
2846 err
= got_object_id_str(&id_str
, s
->selected_entry
->id
);
2849 refs
= got_reflist_object_id_map_lookup(tog_refs_idmap
,
2850 s
->selected_entry
->id
);
2851 err
= build_refs_str(&refs_str
, refs
, s
->selected_entry
->id
,
2857 if (s
->thread_args
.commits_needed
== 0 && !using_mock_io
)
2858 halfdelay(10); /* disable fast refresh */
2860 if (s
->thread_args
.commits_needed
> 0 || s
->thread_args
.load_all
) {
2861 if (asprintf(&ncommits_str
, " [%d/%d] %s",
2862 entry
? entry
->idx
+ 1 : 0, s
->commits
->ncommits
,
2863 (view
->searching
&& !view
->search_next_done
) ?
2864 "searching..." : "loading...") == -1) {
2865 err
= got_error_from_errno("asprintf");
2869 const char *search_str
= NULL
;
2870 const char *limit_str
= NULL
;
2872 if (view
->searching
) {
2873 if (view
->search_next_done
== TOG_SEARCH_NO_MORE
)
2874 search_str
= "no more matches";
2875 else if (view
->search_next_done
== TOG_SEARCH_HAVE_NONE
)
2876 search_str
= "no matches found";
2877 else if (!view
->search_next_done
)
2878 search_str
= "searching...";
2881 if (s
->limit_view
&& s
->commits
->ncommits
== 0)
2882 limit_str
= "no matches found";
2884 if (asprintf(&ncommits_str
, " [%d/%d] %s %s",
2885 entry
? entry
->idx
+ 1 : 0, s
->commits
->ncommits
,
2886 search_str
? search_str
: (refs_str
? refs_str
: ""),
2887 limit_str
? limit_str
: "") == -1) {
2888 err
= got_error_from_errno("asprintf");
2896 if (s
->in_repo_path
&& strcmp(s
->in_repo_path
, "/") != 0) {
2897 if (asprintf(&header
, "commit %s %s%s", id_str
? id_str
:
2898 "........................................",
2899 s
->in_repo_path
, ncommits_str
) == -1) {
2900 err
= got_error_from_errno("asprintf");
2904 } else if (asprintf(&header
, "commit %s%s",
2905 id_str
? id_str
: "........................................",
2906 ncommits_str
) == -1) {
2907 err
= got_error_from_errno("asprintf");
2911 err
= format_line(&wline
, &width
, NULL
, header
, 0, view
->ncols
, 0, 0);
2915 werase(view
->window
);
2917 if (view_needs_focus_indication(view
))
2918 wstandout(view
->window
);
2919 tc
= get_color(&s
->colors
, TOG_COLOR_COMMIT
);
2921 wattr_on(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2922 waddwstr(view
->window
, wline
);
2923 while (width
< view
->ncols
) {
2924 waddch(view
->window
, ' ');
2928 wattr_off(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2929 if (view_needs_focus_indication(view
))
2930 wstandend(view
->window
);
2935 /* Grow author column size if necessary, and set view->maxx. */
2936 entry
= s
->first_displayed_entry
;
2940 struct got_commit_object
*c
= entry
->commit
;
2941 char *author
, *eol
, *msg
, *msg0
;
2942 wchar_t *wauthor
, *wmsg
;
2944 if (ncommits
>= limit
- 1)
2946 if (s
->use_committer
)
2947 author
= strdup(got_object_commit_get_committer(c
));
2949 author
= strdup(got_object_commit_get_author(c
));
2950 if (author
== NULL
) {
2951 err
= got_error_from_errno("strdup");
2954 err
= format_author(&wauthor
, &width
, author
, COLS
,
2956 if (author_cols
< width
)
2957 author_cols
= width
;
2962 refs
= got_reflist_object_id_map_lookup(tog_refs_idmap
,
2964 err
= build_refs_str(&refs_str
, refs
, entry
->id
, s
->repo
);
2969 err
= format_line(&ws
, &width
, NULL
, refs_str
,
2970 0, INT_MAX
, date_display_cols
+ author_cols
, 0);
2976 refstr_cols
= width
+ 3; /* account for [ ] + space */
2979 err
= got_object_commit_get_logmsg(&msg0
, c
);
2983 while (*msg
== '\n')
2985 if ((eol
= strchr(msg
, '\n')))
2987 err
= format_line(&wmsg
, &width
, NULL
, msg
, 0, INT_MAX
,
2988 date_display_cols
+ author_cols
+ refstr_cols
, 0);
2991 view
->maxx
= MAX(view
->maxx
, width
+ refstr_cols
);
2995 entry
= TAILQ_NEXT(entry
, entry
);
2998 entry
= s
->first_displayed_entry
;
2999 s
->last_displayed_entry
= s
->first_displayed_entry
;
3002 if (ncommits
>= limit
- 1)
3004 if (ncommits
== s
->selected
)
3005 wstandout(view
->window
);
3006 err
= draw_commit(view
, entry
, date_display_cols
, author_cols
);
3007 if (ncommits
== s
->selected
)
3008 wstandend(view
->window
);
3012 s
->last_displayed_entry
= entry
;
3013 entry
= TAILQ_NEXT(entry
, entry
);
3026 log_scroll_up(struct tog_log_view_state
*s
, int maxscroll
)
3028 struct commit_queue_entry
*entry
;
3031 entry
= TAILQ_FIRST(&s
->commits
->head
);
3032 if (s
->first_displayed_entry
== entry
)
3035 entry
= s
->first_displayed_entry
;
3036 while (entry
&& nscrolled
< maxscroll
) {
3037 entry
= TAILQ_PREV(entry
, commit_queue_head
, entry
);
3039 s
->first_displayed_entry
= entry
;
3045 static const struct got_error
*
3046 trigger_log_thread(struct tog_view
*view
, int wait
)
3048 struct tog_log_thread_args
*ta
= &view
->state
.log
.thread_args
;
3052 halfdelay(1); /* fast refresh while loading commits */
3054 while (!ta
->log_complete
&& !tog_thread_error
&&
3055 (ta
->commits_needed
> 0 || ta
->load_all
)) {
3056 /* Wake the log thread. */
3057 errcode
= pthread_cond_signal(&ta
->need_commits
);
3059 return got_error_set_errno(errcode
,
3060 "pthread_cond_signal");
3063 * The mutex will be released while the view loop waits
3064 * in wgetch(), at which time the log thread will run.
3069 /* Display progress update in log view. */
3070 show_log_view(view
);
3074 /* Wait right here while next commit is being loaded. */
3075 errcode
= pthread_cond_wait(&ta
->commit_loaded
, &tog_mutex
);
3077 return got_error_set_errno(errcode
,
3078 "pthread_cond_wait");
3080 /* Display progress update in log view. */
3081 show_log_view(view
);
3089 static const struct got_error
*
3090 request_log_commits(struct tog_view
*view
)
3092 struct tog_log_view_state
*state
= &view
->state
.log
;
3093 const struct got_error
*err
= NULL
;
3095 if (state
->thread_args
.log_complete
)
3098 state
->thread_args
.commits_needed
+= view
->nscrolled
;
3099 err
= trigger_log_thread(view
, 1);
3100 view
->nscrolled
= 0;
3105 static const struct got_error
*
3106 log_scroll_down(struct tog_view
*view
, int maxscroll
)
3108 struct tog_log_view_state
*s
= &view
->state
.log
;
3109 const struct got_error
*err
= NULL
;
3110 struct commit_queue_entry
*pentry
;
3111 int nscrolled
= 0, ncommits_needed
;
3113 if (s
->last_displayed_entry
== NULL
)
3116 ncommits_needed
= s
->last_displayed_entry
->idx
+ 1 + maxscroll
;
3117 if (s
->commits
->ncommits
< ncommits_needed
&&
3118 !s
->thread_args
.log_complete
) {
3120 * Ask the log thread for required amount of commits.
3122 s
->thread_args
.commits_needed
+=
3123 ncommits_needed
- s
->commits
->ncommits
;
3124 err
= trigger_log_thread(view
, 1);
3130 pentry
= TAILQ_NEXT(s
->last_displayed_entry
, entry
);
3131 if (pentry
== NULL
&& view
->mode
!= TOG_VIEW_SPLIT_HRZN
)
3134 s
->last_displayed_entry
= pentry
?
3135 pentry
: s
->last_displayed_entry
;
3137 pentry
= TAILQ_NEXT(s
->first_displayed_entry
, entry
);
3140 s
->first_displayed_entry
= pentry
;
3141 } while (++nscrolled
< maxscroll
);
3143 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
&& !s
->thread_args
.log_complete
)
3144 view
->nscrolled
+= nscrolled
;
3146 view
->nscrolled
= 0;
3151 static const struct got_error
*
3152 open_diff_view_for_commit(struct tog_view
**new_view
, int begin_y
, int begin_x
,
3153 struct got_commit_object
*commit
, struct got_object_id
*commit_id
,
3154 struct tog_view
*log_view
, struct got_repository
*repo
)
3156 const struct got_error
*err
;
3157 struct got_object_qid
*parent_id
;
3158 struct tog_view
*diff_view
;
3160 diff_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_DIFF
);
3161 if (diff_view
== NULL
)
3162 return got_error_from_errno("view_open");
3164 parent_id
= STAILQ_FIRST(got_object_commit_get_parent_ids(commit
));
3165 err
= open_diff_view(diff_view
, parent_id
? &parent_id
->id
: NULL
,
3166 commit_id
, NULL
, NULL
, 3, 0, 0, log_view
, repo
);
3168 *new_view
= diff_view
;
3172 static const struct got_error
*
3173 tree_view_visit_subtree(struct tog_tree_view_state
*s
,
3174 struct got_tree_object
*subtree
)
3176 struct tog_parent_tree
*parent
;
3178 parent
= calloc(1, sizeof(*parent
));
3180 return got_error_from_errno("calloc");
3182 parent
->tree
= s
->tree
;
3183 parent
->first_displayed_entry
= s
->first_displayed_entry
;
3184 parent
->selected_entry
= s
->selected_entry
;
3185 parent
->selected
= s
->selected
;
3186 TAILQ_INSERT_HEAD(&s
->parents
, parent
, entry
);
3189 s
->first_displayed_entry
= NULL
;
3193 static const struct got_error
*
3194 tree_view_walk_path(struct tog_tree_view_state
*s
,
3195 struct got_commit_object
*commit
, const char *path
)
3197 const struct got_error
*err
= NULL
;
3198 struct got_tree_object
*tree
= NULL
;
3200 char *slash
, *subpath
= NULL
;
3202 /* Walk the path and open corresponding tree objects. */
3205 struct got_tree_entry
*te
;
3206 struct got_object_id
*tree_id
;
3212 /* Ensure the correct subtree entry is selected. */
3213 slash
= strchr(p
, '/');
3215 te_name
= strdup(p
);
3217 te_name
= strndup(p
, slash
- p
);
3218 if (te_name
== NULL
) {
3219 err
= got_error_from_errno("strndup");
3222 te
= got_object_tree_find_entry(s
->tree
, te_name
);
3224 err
= got_error_path(te_name
, GOT_ERR_NO_TREE_ENTRY
);
3229 s
->first_displayed_entry
= s
->selected_entry
= te
;
3231 if (!S_ISDIR(got_tree_entry_get_mode(s
->selected_entry
)))
3232 break; /* jump to this file's entry */
3234 slash
= strchr(p
, '/');
3236 subpath
= strndup(path
, slash
- path
);
3238 subpath
= strdup(path
);
3239 if (subpath
== NULL
) {
3240 err
= got_error_from_errno("strdup");
3244 err
= got_object_id_by_path(&tree_id
, s
->repo
, commit
,
3249 err
= got_object_open_as_tree(&tree
, s
->repo
, tree_id
);
3254 err
= tree_view_visit_subtree(s
, tree
);
3256 got_object_tree_close(tree
);
3270 static const struct got_error
*
3271 browse_commit_tree(struct tog_view
**new_view
, int begin_y
, int begin_x
,
3272 struct commit_queue_entry
*entry
, const char *path
,
3273 const char *head_ref_name
, struct got_repository
*repo
)
3275 const struct got_error
*err
= NULL
;
3276 struct tog_tree_view_state
*s
;
3277 struct tog_view
*tree_view
;
3279 tree_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_TREE
);
3280 if (tree_view
== NULL
)
3281 return got_error_from_errno("view_open");
3283 err
= open_tree_view(tree_view
, entry
->id
, head_ref_name
, repo
);
3286 s
= &tree_view
->state
.tree
;
3288 *new_view
= tree_view
;
3290 if (got_path_is_root_dir(path
))
3293 return tree_view_walk_path(s
, entry
->commit
, path
);
3296 static const struct got_error
*
3297 block_signals_used_by_main_thread(void)
3302 if (sigemptyset(&sigset
) == -1)
3303 return got_error_from_errno("sigemptyset");
3305 /* tog handles SIGWINCH, SIGCONT, SIGINT, SIGTERM */
3306 if (sigaddset(&sigset
, SIGWINCH
) == -1)
3307 return got_error_from_errno("sigaddset");
3308 if (sigaddset(&sigset
, SIGCONT
) == -1)
3309 return got_error_from_errno("sigaddset");
3310 if (sigaddset(&sigset
, SIGINT
) == -1)
3311 return got_error_from_errno("sigaddset");
3312 if (sigaddset(&sigset
, SIGTERM
) == -1)
3313 return got_error_from_errno("sigaddset");
3315 /* ncurses handles SIGTSTP */
3316 if (sigaddset(&sigset
, SIGTSTP
) == -1)
3317 return got_error_from_errno("sigaddset");
3319 errcode
= pthread_sigmask(SIG_BLOCK
, &sigset
, NULL
);
3321 return got_error_set_errno(errcode
, "pthread_sigmask");
3327 log_thread(void *arg
)
3329 const struct got_error
*err
= NULL
;
3331 struct tog_log_thread_args
*a
= arg
;
3335 * Sync startup with main thread such that we begin our
3336 * work once view_input() has released the mutex.
3338 errcode
= pthread_mutex_lock(&tog_mutex
);
3340 err
= got_error_set_errno(errcode
, "pthread_mutex_lock");
3344 err
= block_signals_used_by_main_thread();
3346 pthread_mutex_unlock(&tog_mutex
);
3350 while (!done
&& !err
&& !tog_fatal_signal_received()) {
3351 errcode
= pthread_mutex_unlock(&tog_mutex
);
3353 err
= got_error_set_errno(errcode
,
3354 "pthread_mutex_unlock");
3357 err
= queue_commits(a
);
3359 if (err
->code
!= GOT_ERR_ITER_COMPLETED
)
3363 a
->commits_needed
= 0;
3364 } else if (a
->commits_needed
> 0 && !a
->load_all
) {
3367 a
->commits_needed
--;
3369 a
->commits_needed
--;
3372 errcode
= pthread_mutex_lock(&tog_mutex
);
3374 err
= got_error_set_errno(errcode
,
3375 "pthread_mutex_lock");
3377 } else if (*a
->quit
)
3379 else if (*a
->limiting
&& *a
->first_displayed_entry
== NULL
) {
3380 *a
->first_displayed_entry
=
3381 TAILQ_FIRST(&a
->limit_commits
->head
);
3382 *a
->selected_entry
= *a
->first_displayed_entry
;
3383 } else if (*a
->first_displayed_entry
== NULL
) {
3384 *a
->first_displayed_entry
=
3385 TAILQ_FIRST(&a
->real_commits
->head
);
3386 *a
->selected_entry
= *a
->first_displayed_entry
;
3389 errcode
= pthread_cond_signal(&a
->commit_loaded
);
3391 err
= got_error_set_errno(errcode
,
3392 "pthread_cond_signal");
3393 pthread_mutex_unlock(&tog_mutex
);
3397 if (a
->commits_needed
== 0 &&
3398 a
->need_commit_marker
&& a
->worktree
) {
3399 errcode
= pthread_mutex_unlock(&tog_mutex
);
3401 err
= got_error_set_errno(errcode
,
3402 "pthread_mutex_unlock");
3405 err
= got_worktree_get_state(&tog_base_commit
.marker
,
3406 a
->repo
, a
->worktree
, NULL
, NULL
);
3409 errcode
= pthread_mutex_lock(&tog_mutex
);
3411 err
= got_error_set_errno(errcode
,
3412 "pthread_mutex_lock");
3415 a
->need_commit_marker
= 0;
3417 * The main thread did not close this
3418 * work tree yet. Close it now.
3420 got_worktree_close(a
->worktree
);
3428 a
->commits_needed
= 0;
3430 if (a
->commits_needed
== 0 && !a
->load_all
) {
3431 if (tog_io
.wait_for_ui
) {
3432 errcode
= pthread_cond_signal(
3434 if (errcode
&& err
== NULL
)
3435 err
= got_error_set_errno(
3437 "pthread_cond_signal");
3440 errcode
= pthread_cond_wait(&a
->need_commits
,
3443 err
= got_error_set_errno(errcode
,
3444 "pthread_cond_wait");
3445 pthread_mutex_unlock(&tog_mutex
);
3453 a
->log_complete
= 1;
3454 if (tog_io
.wait_for_ui
) {
3455 errcode
= pthread_cond_signal(&a
->log_loaded
);
3456 if (errcode
&& err
== NULL
)
3457 err
= got_error_set_errno(errcode
,
3458 "pthread_cond_signal");
3461 errcode
= pthread_mutex_unlock(&tog_mutex
);
3463 err
= got_error_set_errno(errcode
, "pthread_mutex_unlock");
3466 tog_thread_error
= 1;
3467 pthread_cond_signal(&a
->commit_loaded
);
3469 got_worktree_close(a
->worktree
);
3476 static const struct got_error
*
3477 stop_log_thread(struct tog_log_view_state
*s
)
3479 const struct got_error
*err
= NULL
, *thread_err
= NULL
;
3484 errcode
= pthread_cond_signal(&s
->thread_args
.need_commits
);
3486 return got_error_set_errno(errcode
,
3487 "pthread_cond_signal");
3488 errcode
= pthread_mutex_unlock(&tog_mutex
);
3490 return got_error_set_errno(errcode
,
3491 "pthread_mutex_unlock");
3492 errcode
= pthread_join(s
->thread
, (void **)&thread_err
);
3494 return got_error_set_errno(errcode
, "pthread_join");
3495 errcode
= pthread_mutex_lock(&tog_mutex
);
3497 return got_error_set_errno(errcode
,
3498 "pthread_mutex_lock");
3499 s
->thread
= 0; //NULL;
3502 if (s
->thread_args
.repo
) {
3503 err
= got_repo_close(s
->thread_args
.repo
);
3504 s
->thread_args
.repo
= NULL
;
3507 if (s
->thread_args
.pack_fds
) {
3508 const struct got_error
*pack_err
=
3509 got_repo_pack_fds_close(s
->thread_args
.pack_fds
);
3512 s
->thread_args
.pack_fds
= NULL
;
3515 if (s
->thread_args
.graph
) {
3516 got_commit_graph_close(s
->thread_args
.graph
);
3517 s
->thread_args
.graph
= NULL
;
3520 return err
? err
: thread_err
;
3523 static const struct got_error
*
3524 close_log_view(struct tog_view
*view
)
3526 const struct got_error
*err
= NULL
;
3527 struct tog_log_view_state
*s
= &view
->state
.log
;
3530 err
= stop_log_thread(s
);
3532 errcode
= pthread_cond_destroy(&s
->thread_args
.need_commits
);
3533 if (errcode
&& err
== NULL
)
3534 err
= got_error_set_errno(errcode
, "pthread_cond_destroy");
3536 errcode
= pthread_cond_destroy(&s
->thread_args
.commit_loaded
);
3537 if (errcode
&& err
== NULL
)
3538 err
= got_error_set_errno(errcode
, "pthread_cond_destroy");
3540 free_commits(&s
->limit_commits
);
3541 free_commits(&s
->real_commits
);
3542 free(s
->in_repo_path
);
3543 s
->in_repo_path
= NULL
;
3546 free(s
->head_ref_name
);
3547 s
->head_ref_name
= NULL
;
3552 * We use two queues to implement the limit feature: first consists of
3553 * commits matching the current limit_regex; second is the real queue
3554 * of all known commits (real_commits). When the user starts limiting,
3555 * we swap queues such that all movement and displaying functionality
3556 * works with very slight change.
3558 static const struct got_error
*
3559 limit_log_view(struct tog_view
*view
)
3561 struct tog_log_view_state
*s
= &view
->state
.log
;
3562 struct commit_queue_entry
*entry
;
3563 struct tog_view
*v
= view
;
3564 const struct got_error
*err
= NULL
;
3568 if (view_is_hsplit_top(view
))
3570 else if (view
->mode
== TOG_VIEW_SPLIT_VERT
&& view
->parent
)
3573 if (tog_io
.input_str
!= NULL
) {
3574 if (strlcpy(pattern
, tog_io
.input_str
, sizeof(pattern
)) >=
3576 return got_error(GOT_ERR_NO_SPACE
);
3578 wmove(v
->window
, v
->nlines
- 1, 0);
3579 wclrtoeol(v
->window
);
3580 mvwaddstr(v
->window
, v
->nlines
- 1, 0, "&/");
3581 nodelay(v
->window
, FALSE
);
3584 ret
= wgetnstr(v
->window
, pattern
, sizeof(pattern
));
3587 nodelay(v
->window
, TRUE
);
3592 if (*pattern
== '\0') {
3594 * Safety measure for the situation where the user
3595 * resets limit without previously limiting anything.
3601 * User could have pressed Ctrl+L, which refreshed the
3602 * commit queues, it means we can't save previously
3603 * (before limit took place) displayed entries,
3604 * because they would point to already free'ed memory,
3605 * so we are forced to always select first entry of
3608 s
->commits
= &s
->real_commits
;
3609 s
->first_displayed_entry
= TAILQ_FIRST(&s
->real_commits
.head
);
3610 s
->selected_entry
= s
->first_displayed_entry
;
3617 if (regcomp(&s
->limit_regex
, pattern
, REG_EXTENDED
| REG_NEWLINE
))
3622 /* Clear the screen while loading limit view */
3623 s
->first_displayed_entry
= NULL
;
3624 s
->last_displayed_entry
= NULL
;
3625 s
->selected_entry
= NULL
;
3626 s
->commits
= &s
->limit_commits
;
3628 /* Prepare limit queue for new search */
3629 free_commits(&s
->limit_commits
);
3630 s
->limit_commits
.ncommits
= 0;
3632 /* First process commits, which are in queue already */
3633 TAILQ_FOREACH(entry
, &s
->real_commits
.head
, entry
) {
3636 err
= match_commit(&have_match
, entry
->id
,
3637 entry
->commit
, &s
->limit_regex
);
3642 struct commit_queue_entry
*matched
;
3644 matched
= alloc_commit_queue_entry(entry
->commit
,
3646 if (matched
== NULL
) {
3647 err
= got_error_from_errno(
3648 "alloc_commit_queue_entry");
3651 matched
->commit
= entry
->commit
;
3652 got_object_commit_retain(entry
->commit
);
3654 matched
->idx
= s
->limit_commits
.ncommits
;
3655 TAILQ_INSERT_TAIL(&s
->limit_commits
.head
,
3657 s
->limit_commits
.ncommits
++;
3661 /* Second process all the commits, until we fill the screen */
3662 if (s
->limit_commits
.ncommits
< view
->nlines
- 1 &&
3663 !s
->thread_args
.log_complete
) {
3664 s
->thread_args
.commits_needed
+=
3665 view
->nlines
- s
->limit_commits
.ncommits
- 1;
3666 err
= trigger_log_thread(view
, 1);
3671 s
->first_displayed_entry
= TAILQ_FIRST(&s
->commits
->head
);
3672 s
->selected_entry
= TAILQ_FIRST(&s
->commits
->head
);
3678 static const struct got_error
*
3679 search_start_log_view(struct tog_view
*view
)
3681 struct tog_log_view_state
*s
= &view
->state
.log
;
3683 s
->matched_entry
= NULL
;
3684 s
->search_entry
= NULL
;
3688 static const struct got_error
*
3689 search_next_log_view(struct tog_view
*view
)
3691 const struct got_error
*err
= NULL
;
3692 struct tog_log_view_state
*s
= &view
->state
.log
;
3693 struct commit_queue_entry
*entry
;
3695 /* Display progress update in log view. */
3696 show_log_view(view
);
3700 if (s
->search_entry
) {
3701 if (!using_mock_io
) {
3704 errcode
= pthread_mutex_unlock(&tog_mutex
);
3706 return got_error_set_errno(errcode
,
3707 "pthread_mutex_unlock");
3708 ch
= wgetch(view
->window
);
3709 errcode
= pthread_mutex_lock(&tog_mutex
);
3711 return got_error_set_errno(errcode
,
3712 "pthread_mutex_lock");
3713 if (ch
== CTRL('g') || ch
== KEY_BACKSPACE
) {
3714 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
3718 if (view
->searching
== TOG_SEARCH_FORWARD
)
3719 entry
= TAILQ_NEXT(s
->search_entry
, entry
);
3721 entry
= TAILQ_PREV(s
->search_entry
,
3722 commit_queue_head
, entry
);
3723 } else if (s
->matched_entry
) {
3725 * If the user has moved the cursor after we hit a match,
3726 * the position from where we should continue searching
3727 * might have changed.
3729 if (view
->searching
== TOG_SEARCH_FORWARD
)
3730 entry
= TAILQ_NEXT(s
->selected_entry
, entry
);
3732 entry
= TAILQ_PREV(s
->selected_entry
, commit_queue_head
,
3735 entry
= s
->selected_entry
;
3741 if (entry
== NULL
) {
3742 if (s
->thread_args
.log_complete
||
3743 view
->searching
== TOG_SEARCH_BACKWARD
) {
3744 view
->search_next_done
=
3745 (s
->matched_entry
== NULL
?
3746 TOG_SEARCH_HAVE_NONE
: TOG_SEARCH_NO_MORE
);
3747 s
->search_entry
= NULL
;
3751 * Poke the log thread for more commits and return,
3752 * allowing the main loop to make progress. Search
3753 * will resume at s->search_entry once we come back.
3755 s
->search_entry
= s
->selected_entry
;
3756 s
->thread_args
.commits_needed
++;
3757 return trigger_log_thread(view
, 0);
3760 err
= match_commit(&have_match
, entry
->id
, entry
->commit
,
3765 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
3766 s
->matched_entry
= entry
;
3770 s
->search_entry
= entry
;
3771 if (view
->searching
== TOG_SEARCH_FORWARD
)
3772 entry
= TAILQ_NEXT(entry
, entry
);
3774 entry
= TAILQ_PREV(entry
, commit_queue_head
, entry
);
3777 if (s
->matched_entry
) {
3778 int cur
= s
->selected_entry
->idx
;
3779 while (cur
< s
->matched_entry
->idx
) {
3780 err
= input_log_view(NULL
, view
, KEY_DOWN
);
3785 while (cur
> s
->matched_entry
->idx
) {
3786 err
= input_log_view(NULL
, view
, KEY_UP
);
3793 s
->search_entry
= NULL
;
3798 static const struct got_error
*
3799 open_log_view(struct tog_view
*view
, struct got_object_id
*start_id
,
3800 struct got_repository
*repo
, const char *head_ref_name
,
3801 const char *in_repo_path
, int log_branches
,
3802 struct got_worktree
*worktree
)
3804 const struct got_error
*err
= NULL
;
3805 struct tog_log_view_state
*s
= &view
->state
.log
;
3806 struct got_repository
*thread_repo
= NULL
;
3807 struct got_commit_graph
*thread_graph
= NULL
;
3810 if (in_repo_path
!= s
->in_repo_path
) {
3811 free(s
->in_repo_path
);
3812 s
->in_repo_path
= strdup(in_repo_path
);
3813 if (s
->in_repo_path
== NULL
) {
3814 err
= got_error_from_errno("strdup");
3819 /* The commit queue only contains commits being displayed. */
3820 TAILQ_INIT(&s
->real_commits
.head
);
3821 s
->real_commits
.ncommits
= 0;
3822 s
->commits
= &s
->real_commits
;
3824 TAILQ_INIT(&s
->limit_commits
.head
);
3826 s
->limit_commits
.ncommits
= 0;
3829 if (head_ref_name
) {
3830 s
->head_ref_name
= strdup(head_ref_name
);
3831 if (s
->head_ref_name
== NULL
) {
3832 err
= got_error_from_errno("strdup");
3836 s
->start_id
= got_object_id_dup(start_id
);
3837 if (s
->start_id
== NULL
) {
3838 err
= got_error_from_errno("got_object_id_dup");
3841 s
->log_branches
= log_branches
;
3842 s
->use_committer
= 1;
3844 STAILQ_INIT(&s
->colors
);
3845 if (has_colors() && getenv("TOG_COLORS") != NULL
) {
3846 err
= add_color(&s
->colors
, "^$", TOG_COLOR_COMMIT
,
3847 get_color_value("TOG_COLOR_COMMIT"));
3850 err
= add_color(&s
->colors
, "^$", TOG_COLOR_AUTHOR
,
3851 get_color_value("TOG_COLOR_AUTHOR"));
3853 free_colors(&s
->colors
);
3856 err
= add_color(&s
->colors
, "^$", TOG_COLOR_DATE
,
3857 get_color_value("TOG_COLOR_DATE"));
3859 free_colors(&s
->colors
);
3864 view
->show
= show_log_view
;
3865 view
->input
= input_log_view
;
3866 view
->resize
= resize_log_view
;
3867 view
->close
= close_log_view
;
3868 view
->search_start
= search_start_log_view
;
3869 view
->search_next
= search_next_log_view
;
3871 if (s
->thread_args
.pack_fds
== NULL
) {
3872 err
= got_repo_pack_fds_open(&s
->thread_args
.pack_fds
);
3876 err
= got_repo_open(&thread_repo
, got_repo_get_path(repo
), NULL
,
3877 s
->thread_args
.pack_fds
);
3880 err
= got_commit_graph_open(&thread_graph
, s
->in_repo_path
,
3884 err
= got_commit_graph_iter_start(thread_graph
, s
->start_id
,
3885 s
->repo
, NULL
, NULL
);
3889 errcode
= pthread_cond_init(&s
->thread_args
.need_commits
, NULL
);
3891 err
= got_error_set_errno(errcode
, "pthread_cond_init");
3894 errcode
= pthread_cond_init(&s
->thread_args
.commit_loaded
, NULL
);
3896 err
= got_error_set_errno(errcode
, "pthread_cond_init");
3900 if (using_mock_io
) {
3903 rc
= pthread_cond_init(&s
->thread_args
.log_loaded
, NULL
);
3905 return got_error_set_errno(rc
, "pthread_cond_init");
3908 s
->thread_args
.commits_needed
= view
->nlines
;
3909 s
->thread_args
.graph
= thread_graph
;
3910 s
->thread_args
.real_commits
= &s
->real_commits
;
3911 s
->thread_args
.limit_commits
= &s
->limit_commits
;
3912 s
->thread_args
.in_repo_path
= s
->in_repo_path
;
3913 s
->thread_args
.start_id
= s
->start_id
;
3914 s
->thread_args
.repo
= thread_repo
;
3915 s
->thread_args
.log_complete
= 0;
3916 s
->thread_args
.quit
= &s
->quit
;
3917 s
->thread_args
.first_displayed_entry
= &s
->first_displayed_entry
;
3918 s
->thread_args
.selected_entry
= &s
->selected_entry
;
3919 s
->thread_args
.searching
= &view
->searching
;
3920 s
->thread_args
.search_next_done
= &view
->search_next_done
;
3921 s
->thread_args
.regex
= &view
->regex
;
3922 s
->thread_args
.limiting
= &s
->limit_view
;
3923 s
->thread_args
.limit_regex
= &s
->limit_regex
;
3924 s
->thread_args
.limit_commits
= &s
->limit_commits
;
3925 s
->thread_args
.worktree
= worktree
;
3927 s
->thread_args
.need_commit_marker
= 1;
3930 if (view
->close
== NULL
)
3931 close_log_view(view
);
3937 static const struct got_error
*
3938 show_log_view(struct tog_view
*view
)
3940 const struct got_error
*err
;
3941 struct tog_log_view_state
*s
= &view
->state
.log
;
3943 if (s
->thread
== 0) { //NULL) {
3944 int errcode
= pthread_create(&s
->thread
, NULL
, log_thread
,
3947 return got_error_set_errno(errcode
, "pthread_create");
3948 if (s
->thread_args
.commits_needed
> 0) {
3949 err
= trigger_log_thread(view
, 1);
3955 return draw_commits(view
);
3959 log_move_cursor_up(struct tog_view
*view
, int page
, int home
)
3961 struct tog_log_view_state
*s
= &view
->state
.log
;
3963 if (s
->first_displayed_entry
== NULL
)
3965 if (s
->selected_entry
->idx
== 0)
3968 if ((page
&& TAILQ_FIRST(&s
->commits
->head
) == s
->first_displayed_entry
)
3970 s
->selected
= home
? 0 : MAX(0, s
->selected
- page
- 1);
3972 if (!page
&& !home
&& s
->selected
> 0)
3975 log_scroll_up(s
, home
? s
->commits
->ncommits
: MAX(page
, 1));
3981 static const struct got_error
*
3982 log_move_cursor_down(struct tog_view
*view
, int page
)
3984 struct tog_log_view_state
*s
= &view
->state
.log
;
3985 const struct got_error
*err
= NULL
;
3986 int eos
= view
->nlines
- 2;
3988 if (s
->first_displayed_entry
== NULL
)
3991 if (s
->thread_args
.log_complete
&&
3992 s
->selected_entry
->idx
>= s
->commits
->ncommits
- 1)
3995 if (view_is_hsplit_top(view
))
3996 --eos
; /* border consumes the last line */
3999 if (s
->selected
< MIN(eos
, s
->commits
->ncommits
- 1))
4002 err
= log_scroll_down(view
, 1);
4003 } else if (s
->thread_args
.load_all
&& s
->thread_args
.log_complete
) {
4004 struct commit_queue_entry
*entry
;
4008 entry
= TAILQ_LAST(&s
->commits
->head
, commit_queue_head
);
4009 s
->last_displayed_entry
= entry
;
4010 for (n
= 0; n
<= eos
; n
++) {
4013 s
->first_displayed_entry
= entry
;
4014 entry
= TAILQ_PREV(entry
, commit_queue_head
, entry
);
4017 s
->selected
= n
- 1;
4019 if (s
->last_displayed_entry
->idx
== s
->commits
->ncommits
- 1 &&
4020 s
->thread_args
.log_complete
)
4021 s
->selected
+= MIN(page
,
4022 s
->commits
->ncommits
- s
->selected_entry
->idx
- 1);
4024 err
= log_scroll_down(view
, page
);
4030 * We might necessarily overshoot in horizontal
4031 * splits; if so, select the last displayed commit.
4033 if (s
->first_displayed_entry
&& s
->last_displayed_entry
) {
4034 s
->selected
= MIN(s
->selected
,
4035 s
->last_displayed_entry
->idx
-
4036 s
->first_displayed_entry
->idx
);
4041 if (s
->thread_args
.log_complete
&&
4042 s
->selected_entry
->idx
== s
->commits
->ncommits
- 1)
4049 view_get_split(struct tog_view
*view
, int *y
, int *x
)
4054 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
) {
4055 if (view
->child
&& view
->child
->resized_y
)
4056 *y
= view
->child
->resized_y
;
4057 else if (view
->resized_y
)
4058 *y
= view
->resized_y
;
4060 *y
= view_split_begin_y(view
->lines
);
4061 } else if (view
->mode
== TOG_VIEW_SPLIT_VERT
) {
4062 if (view
->child
&& view
->child
->resized_x
)
4063 *x
= view
->child
->resized_x
;
4064 else if (view
->resized_x
)
4065 *x
= view
->resized_x
;
4067 *x
= view_split_begin_x(view
->begin_x
);
4071 /* Split view horizontally at y and offset view->state->selected line. */
4072 static const struct got_error
*
4073 view_init_hsplit(struct tog_view
*view
, int y
)
4075 const struct got_error
*err
= NULL
;
4079 err
= view_resize(view
);
4083 err
= offset_selection_down(view
);
4088 static const struct got_error
*
4089 log_goto_line(struct tog_view
*view
, int nlines
)
4091 const struct got_error
*err
= NULL
;
4092 struct tog_log_view_state
*s
= &view
->state
.log
;
4093 int g
, idx
= s
->selected_entry
->idx
;
4095 if (s
->first_displayed_entry
== NULL
|| s
->last_displayed_entry
== NULL
)
4101 if (g
>= s
->first_displayed_entry
->idx
+ 1 &&
4102 g
<= s
->last_displayed_entry
->idx
+ 1 &&
4103 g
- s
->first_displayed_entry
->idx
- 1 < nlines
) {
4104 s
->selected
= g
- s
->first_displayed_entry
->idx
- 1;
4110 err
= log_move_cursor_down(view
, g
- idx
- 1);
4111 if (!err
&& g
> s
->selected_entry
->idx
+ 1)
4112 err
= log_move_cursor_down(view
,
4113 g
- s
->first_displayed_entry
->idx
- 1);
4116 } else if (idx
+ 1 > g
)
4117 log_move_cursor_up(view
, idx
- g
+ 1, 0);
4119 if (g
< nlines
&& s
->first_displayed_entry
->idx
== 0)
4120 s
->selected
= g
- 1;
4128 horizontal_scroll_input(struct tog_view
*view
, int ch
)
4134 view
->x
-= MIN(view
->x
, 2);
4140 if (view
->x
+ view
->ncols
/ 2 < view
->maxx
)
4149 view
->x
= MAX(view
->maxx
- view
->ncols
/ 2, 0);
4157 static const struct got_error
*
4158 input_log_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
4160 const struct got_error
*err
= NULL
;
4161 struct tog_log_view_state
*s
= &view
->state
.log
;
4164 if (s
->thread_args
.load_all
) {
4165 if (ch
== CTRL('g') || ch
== KEY_BACKSPACE
)
4166 s
->thread_args
.load_all
= 0;
4167 else if (s
->thread_args
.log_complete
) {
4168 err
= log_move_cursor_down(view
, s
->commits
->ncommits
);
4169 s
->thread_args
.load_all
= 0;
4175 eos
= nscroll
= view
->nlines
- 1;
4176 if (view_is_hsplit_top(view
))
4180 return log_goto_line(view
, eos
);
4184 err
= limit_log_view(view
);
4195 horizontal_scroll_input(view
, ch
);
4202 log_move_cursor_up(view
, 0, 0);
4207 log_move_cursor_up(view
, 0, 1);
4217 log_move_cursor_up(view
, nscroll
, 0);
4224 err
= log_move_cursor_down(view
, 0);
4227 s
->use_committer
= !s
->use_committer
;
4228 view
->action
= s
->use_committer
?
4229 "show committer" : "show commit author";
4234 /* We don't know yet how many commits, so we're forced to
4235 * traverse them all. */
4237 s
->thread_args
.load_all
= 1;
4238 if (!s
->thread_args
.log_complete
)
4239 return trigger_log_thread(view
, 0);
4240 err
= log_move_cursor_down(view
, s
->commits
->ncommits
);
4241 s
->thread_args
.load_all
= 0;
4252 err
= log_move_cursor_down(view
, nscroll
);
4255 if (s
->selected
> view
->nlines
- 2)
4256 s
->selected
= view
->nlines
- 2;
4257 if (s
->selected
> s
->commits
->ncommits
- 1)
4258 s
->selected
= s
->commits
->ncommits
- 1;
4260 if (s
->commits
->ncommits
< view
->nlines
- 1 &&
4261 !s
->thread_args
.log_complete
) {
4262 s
->thread_args
.commits_needed
+= (view
->nlines
- 1) -
4263 s
->commits
->ncommits
;
4264 err
= trigger_log_thread(view
, 1);
4270 if (s
->selected_entry
== NULL
)
4272 err
= view_request_new(new_view
, view
, TOG_VIEW_DIFF
);
4276 if (s
->selected_entry
== NULL
)
4278 err
= view_request_new(new_view
, view
, TOG_VIEW_TREE
);
4284 if (ch
== KEY_BACKSPACE
&&
4285 got_path_is_root_dir(s
->in_repo_path
))
4287 err
= stop_log_thread(s
);
4290 if (ch
== KEY_BACKSPACE
) {
4292 err
= got_path_dirname(&parent_path
, s
->in_repo_path
);
4295 free(s
->in_repo_path
);
4296 s
->in_repo_path
= parent_path
;
4297 s
->thread_args
.in_repo_path
= s
->in_repo_path
;
4298 } else if (ch
== CTRL('l')) {
4299 struct got_object_id
*start_id
;
4300 err
= got_repo_match_object_id(&start_id
, NULL
,
4301 s
->head_ref_name
? s
->head_ref_name
: GOT_REF_HEAD
,
4302 GOT_OBJ_TYPE_COMMIT
, &tog_refs
, s
->repo
);
4304 if (s
->head_ref_name
== NULL
||
4305 err
->code
!= GOT_ERR_NOT_REF
)
4307 /* Try to cope with deleted references. */
4308 free(s
->head_ref_name
);
4309 s
->head_ref_name
= NULL
;
4310 err
= got_repo_match_object_id(&start_id
,
4311 NULL
, GOT_REF_HEAD
, GOT_OBJ_TYPE_COMMIT
,
4312 &tog_refs
, s
->repo
);
4317 s
->start_id
= start_id
;
4318 s
->thread_args
.start_id
= s
->start_id
;
4320 s
->log_branches
= !s
->log_branches
;
4322 if (s
->thread_args
.pack_fds
== NULL
) {
4323 err
= got_repo_pack_fds_open(&s
->thread_args
.pack_fds
);
4327 err
= got_repo_open(&s
->thread_args
.repo
,
4328 got_repo_get_path(s
->repo
), NULL
,
4329 s
->thread_args
.pack_fds
);
4333 err
= tog_load_refs(s
->repo
, 0);
4336 err
= got_commit_graph_open(&s
->thread_args
.graph
,
4337 s
->in_repo_path
, !s
->log_branches
);
4340 err
= got_commit_graph_iter_start(s
->thread_args
.graph
,
4341 s
->start_id
, s
->repo
, NULL
, NULL
);
4344 free_commits(&s
->real_commits
);
4345 free_commits(&s
->limit_commits
);
4346 s
->first_displayed_entry
= NULL
;
4347 s
->last_displayed_entry
= NULL
;
4348 s
->selected_entry
= NULL
;
4350 s
->thread_args
.log_complete
= 0;
4352 s
->thread_args
.commits_needed
= view
->lines
;
4353 s
->matched_entry
= NULL
;
4354 s
->search_entry
= NULL
;
4359 err
= view_request_new(new_view
, view
, TOG_VIEW_REF
);
4369 static const struct got_error
*
4370 apply_unveil(const char *repo_path
, const char *worktree_path
)
4372 const struct got_error
*error
;
4375 if (unveil("gmon.out", "rwc") != 0)
4376 return got_error_from_errno2("unveil", "gmon.out");
4378 if (repo_path
&& unveil(repo_path
, "r") != 0)
4379 return got_error_from_errno2("unveil", repo_path
);
4381 if (worktree_path
&& unveil(worktree_path
, "rwc") != 0)
4382 return got_error_from_errno2("unveil", worktree_path
);
4384 if (unveil(GOT_TMPDIR_STR
, "rwc") != 0)
4385 return got_error_from_errno2("unveil", GOT_TMPDIR_STR
);
4387 error
= got_privsep_unveil_exec_helpers();
4391 if (unveil(NULL
, NULL
) != 0)
4392 return got_error_from_errno("unveil");
4397 static const struct got_error
*
4398 init_mock_term(const char *test_script_path
)
4400 const struct got_error
*err
= NULL
;
4401 const char *screen_dump_path
;
4404 if (test_script_path
== NULL
|| *test_script_path
== '\0')
4405 return got_error_msg(GOT_ERR_IO
, "TOG_TEST_SCRIPT not defined");
4407 tog_io
.f
= fopen(test_script_path
, "re");
4408 if (tog_io
.f
== NULL
) {
4409 err
= got_error_from_errno_fmt("fopen: %s",
4414 /* test mode, we don't want any output */
4415 tog_io
.cout
= fopen("/dev/null", "w+");
4416 if (tog_io
.cout
== NULL
) {
4417 err
= got_error_from_errno2("fopen", "/dev/null");
4421 in
= dup(fileno(tog_io
.cout
));
4423 err
= got_error_from_errno("dup");
4426 tog_io
.cin
= fdopen(in
, "r");
4427 if (tog_io
.cin
== NULL
) {
4428 err
= got_error_from_errno("fdopen");
4433 screen_dump_path
= getenv("TOG_SCR_DUMP");
4434 if (screen_dump_path
== NULL
|| *screen_dump_path
== '\0')
4435 return got_error_msg(GOT_ERR_IO
, "TOG_SCR_DUMP not defined");
4436 tog_io
.sdump
= fopen(screen_dump_path
, "we");
4437 if (tog_io
.sdump
== NULL
) {
4438 err
= got_error_from_errno2("fopen", screen_dump_path
);
4442 if (fseeko(tog_io
.f
, 0L, SEEK_SET
) == -1) {
4443 err
= got_error_from_errno("fseeko");
4447 if (newterm(NULL
, tog_io
.cout
, tog_io
.cin
) == NULL
)
4448 err
= got_error_msg(GOT_ERR_IO
,
4449 "newterm: failed to initialise curses");
4462 if (using_mock_io
) /* In test mode we use a fake terminal */
4468 halfdelay(1); /* Fast refresh while initial view is loading. */
4471 intrflush(stdscr
, FALSE
);
4472 keypad(stdscr
, TRUE
);
4474 if (getenv("TOG_COLORS") != NULL
) {
4476 use_default_colors();
4482 static const struct got_error
*
4483 set_tog_base_commit(struct got_repository
*repo
, struct got_worktree
*worktree
)
4485 tog_base_commit
.id
= got_object_id_dup(
4486 got_worktree_get_base_commit_id(worktree
));
4487 if (tog_base_commit
.id
== NULL
)
4488 return got_error_from_errno( "got_object_id_dup");
4493 static const struct got_error
*
4494 get_in_repo_path_from_argv0(char **in_repo_path
, int argc
, char *argv
[],
4495 struct got_repository
*repo
, struct got_worktree
*worktree
)
4497 const struct got_error
*err
= NULL
;
4500 *in_repo_path
= strdup("/");
4501 if (*in_repo_path
== NULL
)
4502 return got_error_from_errno("strdup");
4507 const char *prefix
= got_worktree_get_path_prefix(worktree
);
4510 err
= got_worktree_resolve_path(&p
, worktree
, argv
[0]);
4513 if (asprintf(in_repo_path
, "%s%s%s", prefix
,
4514 (p
[0] != '\0' && !got_path_is_root_dir(prefix
)) ? "/" : "",
4516 err
= got_error_from_errno("asprintf");
4517 *in_repo_path
= NULL
;
4521 err
= got_repo_map_path(in_repo_path
, repo
, argv
[0]);
4526 static const struct got_error
*
4527 cmd_log(int argc
, char *argv
[])
4529 const struct got_error
*error
;
4530 struct got_repository
*repo
= NULL
;
4531 struct got_worktree
*worktree
= NULL
;
4532 struct got_object_id
*start_id
= NULL
;
4533 char *in_repo_path
= NULL
, *repo_path
= NULL
, *cwd
= NULL
;
4534 char *keyword_idstr
= NULL
, *start_commit
= NULL
, *label
= NULL
;
4535 struct got_reference
*ref
= NULL
;
4536 const char *head_ref_name
= NULL
;
4537 int ch
, log_branches
= 0;
4538 struct tog_view
*view
;
4539 int *pack_fds
= NULL
;
4541 while ((ch
= getopt(argc
, argv
, "bc:r:")) != -1) {
4547 start_commit
= optarg
;
4550 repo_path
= realpath(optarg
, NULL
);
4551 if (repo_path
== NULL
)
4552 return got_error_from_errno2("realpath",
4567 error
= got_repo_pack_fds_open(&pack_fds
);
4571 if (repo_path
== NULL
) {
4572 cwd
= getcwd(NULL
, 0);
4574 error
= got_error_from_errno("getcwd");
4577 error
= got_worktree_open(&worktree
, cwd
, NULL
);
4578 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
4582 strdup(got_worktree_get_repo_path(worktree
));
4584 repo_path
= strdup(cwd
);
4585 if (repo_path
== NULL
) {
4586 error
= got_error_from_errno("strdup");
4591 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
4595 error
= get_in_repo_path_from_argv0(&in_repo_path
, argc
, argv
,
4602 error
= apply_unveil(got_repo_get_path(repo
),
4603 worktree
? got_worktree_get_root_path(worktree
) : NULL
);
4607 /* already loaded by tog_log_with_path()? */
4608 if (TAILQ_EMPTY(&tog_refs
)) {
4609 error
= tog_load_refs(repo
, 0);
4614 if (start_commit
== NULL
) {
4615 error
= got_repo_match_object_id(&start_id
, &label
,
4616 worktree
? got_worktree_get_head_ref_name(worktree
) :
4617 GOT_REF_HEAD
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
4620 head_ref_name
= label
;
4622 error
= got_keyword_to_idstr(&keyword_idstr
, start_commit
,
4626 if (keyword_idstr
!= NULL
)
4627 start_commit
= keyword_idstr
;
4629 error
= got_ref_open(&ref
, repo
, start_commit
, 0);
4631 head_ref_name
= got_ref_get_name(ref
);
4632 else if (error
->code
!= GOT_ERR_NOT_REF
)
4634 error
= got_repo_match_object_id(&start_id
, NULL
,
4635 start_commit
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
4640 view
= view_open(0, 0, 0, 0, TOG_VIEW_LOG
);
4642 error
= got_error_from_errno("view_open");
4647 error
= set_tog_base_commit(repo
, worktree
);
4652 error
= open_log_view(view
, start_id
, repo
, head_ref_name
,
4653 in_repo_path
, log_branches
, worktree
);
4658 /* The work tree will be closed by the log thread. */
4662 error
= view_loop(view
);
4665 free(tog_base_commit
.id
);
4666 free(keyword_idstr
);
4675 const struct got_error
*close_err
= got_repo_close(repo
);
4680 got_worktree_close(worktree
);
4682 const struct got_error
*pack_err
=
4683 got_repo_pack_fds_close(pack_fds
);
4695 fprintf(stderr
, "usage: %s diff [-aw] [-C number] [-r repository-path] "
4696 "object1 object2\n", getprogname());
4701 match_line(const char *line
, regex_t
*regex
, size_t nmatch
,
4702 regmatch_t
*regmatch
)
4704 return regexec(regex
, line
, nmatch
, regmatch
, 0) == 0;
4707 static struct tog_color
*
4708 match_color(struct tog_colors
*colors
, const char *line
)
4710 struct tog_color
*tc
= NULL
;
4712 STAILQ_FOREACH(tc
, colors
, entry
) {
4713 if (match_line(line
, &tc
->regex
, 0, NULL
))
4720 static const struct got_error
*
4721 add_matched_line(int *wtotal
, const char *line
, int wlimit
, int col_tab_align
,
4722 WINDOW
*window
, int skipcol
, regmatch_t
*regmatch
)
4724 const struct got_error
*err
= NULL
;
4726 wchar_t *wline
= NULL
;
4727 int rme
, rms
, n
, width
, scrollx
;
4728 int width0
= 0, width1
= 0, width2
= 0;
4729 char *seg0
= NULL
, *seg1
= NULL
, *seg2
= NULL
;
4733 rms
= regmatch
->rm_so
;
4734 rme
= regmatch
->rm_eo
;
4736 err
= expand_tab(&exstr
, line
);
4740 /* Split the line into 3 segments, according to match offsets. */
4741 seg0
= strndup(exstr
, rms
);
4743 err
= got_error_from_errno("strndup");
4746 seg1
= strndup(exstr
+ rms
, rme
- rms
);
4748 err
= got_error_from_errno("strndup");
4751 seg2
= strdup(exstr
+ rme
);
4753 err
= got_error_from_errno("strndup");
4757 /* draw up to matched token if we haven't scrolled past it */
4758 err
= format_line(&wline
, &width0
, NULL
, seg0
, 0, wlimit
,
4762 n
= MAX(width0
- skipcol
, 0);
4765 err
= format_line(&wline
, &width
, &scrollx
, seg0
, skipcol
,
4766 wlimit
, col_tab_align
, 1);
4769 waddwstr(window
, &wline
[scrollx
]);
4779 err
= format_line(&wline
, &width1
, NULL
, seg1
, 0, wlimit
,
4783 wlen
= wcslen(wline
);
4785 width
= wcwidth(wline
[i
]);
4787 /* should not happen, tabs are expanded */
4788 err
= got_error(GOT_ERR_RANGE
);
4791 if (width0
+ w
+ width
> skipcol
)
4796 /* draw (visible part of) matched token (if scrolled into it) */
4797 if (width1
- w
> 0) {
4798 wattron(window
, A_STANDOUT
);
4799 waddwstr(window
, &wline
[i
]);
4800 wattroff(window
, A_STANDOUT
);
4801 wlimit
-= (width1
- w
);
4802 *wtotal
+= (width1
- w
);
4806 if (wlimit
> 0) { /* draw rest of line */
4808 if (skipcol
> width0
+ width1
) {
4809 err
= format_line(&wline
, &width2
, &scrollx
, seg2
,
4810 skipcol
- (width0
+ width1
), wlimit
,
4814 waddwstr(window
, &wline
[scrollx
]);
4816 err
= format_line(&wline
, &width2
, NULL
, seg2
, 0,
4817 wlimit
, col_tab_align
, 1);
4820 waddwstr(window
, wline
);
4834 gotoline(struct tog_view
*view
, int *lineno
, int *nprinted
)
4837 int *eof
, *first
, *selected
;
4839 if (view
->type
== TOG_VIEW_DIFF
) {
4840 struct tog_diff_view_state
*s
= &view
->state
.diff
;
4842 first
= &s
->first_displayed_line
;
4846 } else if (view
->type
== TOG_VIEW_HELP
) {
4847 struct tog_help_view_state
*s
= &view
->state
.help
;
4849 first
= &s
->first_displayed_line
;
4853 } else if (view
->type
== TOG_VIEW_BLAME
) {
4854 struct tog_blame_view_state
*s
= &view
->state
.blame
;
4856 first
= &s
->first_displayed_line
;
4857 selected
= &s
->selected_line
;
4863 /* Center gline in the middle of the page like vi(1). */
4864 if (*lineno
< view
->gline
- (view
->nlines
- 3) / 2)
4866 if (*first
!= 1 && (*lineno
> view
->gline
- (view
->nlines
- 3) / 2)) {
4875 *selected
= view
->gline
<= (view
->nlines
- 3) / 2 ?
4876 view
->gline
: (view
->nlines
- 3) / 2 + 1;
4882 static const struct got_error
*
4883 draw_file(struct tog_view
*view
, const char *header
)
4885 struct tog_diff_view_state
*s
= &view
->state
.diff
;
4886 regmatch_t
*regmatch
= &view
->regmatch
;
4887 const struct got_error
*err
;
4890 size_t linesize
= 0;
4894 int max_lines
= view
->nlines
;
4895 int nlines
= s
->nlines
;
4898 s
->lineno
= s
->first_displayed_line
- 1;
4899 line_offset
= s
->lines
[s
->first_displayed_line
- 1].offset
;
4900 if (fseeko(s
->f
, line_offset
, SEEK_SET
) == -1)
4901 return got_error_from_errno("fseek");
4903 werase(view
->window
);
4905 if (view
->gline
> s
->nlines
- 1)
4906 view
->gline
= s
->nlines
- 1;
4909 int ln
= view
->gline
? view
->gline
<= (view
->nlines
- 3) / 2 ?
4910 1 : view
->gline
- (view
->nlines
- 3) / 2 :
4911 s
->lineno
+ s
->selected_line
;
4913 if (asprintf(&line
, "[%d/%d] %s", ln
, nlines
, header
) == -1)
4914 return got_error_from_errno("asprintf");
4915 err
= format_line(&wline
, &width
, NULL
, line
, 0, view
->ncols
,
4921 if (view_needs_focus_indication(view
))
4922 wstandout(view
->window
);
4923 waddwstr(view
->window
, wline
);
4926 while (width
++ < view
->ncols
)
4927 waddch(view
->window
, ' ');
4928 if (view_needs_focus_indication(view
))
4929 wstandend(view
->window
);
4939 while (max_lines
> 0 && nprinted
< max_lines
) {
4940 enum got_diff_line_type linetype
;
4943 linelen
= getline(&line
, &linesize
, s
->f
);
4944 if (linelen
== -1) {
4950 return got_ferror(s
->f
, GOT_ERR_IO
);
4953 if (++s
->lineno
< s
->first_displayed_line
)
4955 if (view
->gline
&& !gotoline(view
, &s
->lineno
, &nprinted
))
4957 if (s
->lineno
== view
->hiline
)
4960 /* Set view->maxx based on full line length. */
4961 err
= format_line(&wline
, &width
, NULL
, line
, 0, INT_MAX
, 0,
4967 view
->maxx
= MAX(view
->maxx
, width
);
4971 linetype
= s
->lines
[s
->lineno
].type
;
4972 if (linetype
> GOT_DIFF_LINE_LOGMSG
&&
4973 linetype
< GOT_DIFF_LINE_CONTEXT
)
4974 attr
|= COLOR_PAIR(linetype
);
4976 wattron(view
->window
, attr
);
4977 if (s
->first_displayed_line
+ nprinted
== s
->matched_line
&&
4978 regmatch
->rm_so
>= 0 && regmatch
->rm_so
< regmatch
->rm_eo
) {
4979 err
= add_matched_line(&width
, line
, view
->ncols
, 0,
4980 view
->window
, view
->x
, regmatch
);
4987 err
= format_line(&wline
, &width
, &skip
, line
,
4988 view
->x
, view
->ncols
, 0, view
->x
? 1 : 0);
4993 waddwstr(view
->window
, &wline
[skip
]);
4997 if (s
->lineno
== view
->hiline
) {
4998 /* highlight full gline length */
4999 while (width
++ < view
->ncols
)
5000 waddch(view
->window
, ' ');
5002 if (width
<= view
->ncols
- 1)
5003 waddch(view
->window
, '\n');
5006 wattroff(view
->window
, attr
);
5007 if (++nprinted
== 1)
5008 s
->first_displayed_line
= s
->lineno
;
5012 s
->last_displayed_line
= s
->first_displayed_line
+
5015 s
->last_displayed_line
= s
->first_displayed_line
;
5020 while (nprinted
< view
->nlines
) {
5021 waddch(view
->window
, '\n');
5025 err
= format_line(&wline
, &width
, NULL
, TOG_EOF_STRING
, 0,
5031 wstandout(view
->window
);
5032 waddwstr(view
->window
, wline
);
5035 wstandend(view
->window
);
5042 get_datestr(time_t *time
, char *datebuf
)
5044 struct tm mytm
, *tm
;
5047 tm
= gmtime_r(time
, &mytm
);
5050 s
= asctime_r(tm
, datebuf
);
5053 p
= strchr(s
, '\n');
5059 static const struct got_error
*
5060 add_line_metadata(struct got_diff_line
**lines
, size_t *nlines
,
5061 off_t off
, uint8_t type
)
5063 struct got_diff_line
*p
;
5065 p
= reallocarray(*lines
, *nlines
+ 1, sizeof(**lines
));
5067 return got_error_from_errno("reallocarray");
5069 (*lines
)[*nlines
].offset
= off
;
5070 (*lines
)[*nlines
].type
= type
;
5076 static const struct got_error
*
5077 cat_diff(FILE *dst
, FILE *src
, struct got_diff_line
**d_lines
, size_t *d_nlines
,
5078 struct got_diff_line
*s_lines
, size_t s_nlines
)
5080 struct got_diff_line
*p
;
5084 if (fseeko(src
, 0L, SEEK_SET
) == -1)
5085 return got_error_from_errno("fseeko");
5088 r
= fread(buf
, 1, sizeof(buf
), src
);
5091 return got_error_from_errno("fread");
5095 if (fwrite(buf
, 1, r
, dst
) != r
)
5096 return got_ferror(dst
, GOT_ERR_IO
);
5099 if (s_nlines
== 0 && *d_nlines
== 0)
5103 * If commit info was in dst, increment line offsets
5104 * of the appended diff content, but skip s_lines[0]
5105 * because offset zero is already in *d_lines.
5107 if (*d_nlines
> 0) {
5108 for (i
= 1; i
< s_nlines
; ++i
)
5109 s_lines
[i
].offset
+= (*d_lines
)[*d_nlines
- 1].offset
;
5117 p
= reallocarray(*d_lines
, *d_nlines
+ s_nlines
, sizeof(*p
));
5119 /* d_lines is freed in close_diff_view() */
5120 return got_error_from_errno("reallocarray");
5125 memcpy(*d_lines
+ *d_nlines
, s_lines
, s_nlines
* sizeof(*s_lines
));
5126 *d_nlines
+= s_nlines
;
5131 static const struct got_error
*
5132 write_commit_info(struct got_diff_line
**lines
, size_t *nlines
,
5133 struct got_object_id
*commit_id
, struct got_reflist_head
*refs
,
5134 struct got_repository
*repo
, int ignore_ws
, int force_text_diff
,
5135 struct got_diffstat_cb_arg
*dsa
, FILE *outfile
)
5137 const struct got_error
*err
= NULL
;
5138 char datebuf
[26], *datestr
;
5139 struct got_commit_object
*commit
;
5140 char *id_str
= NULL
, *logmsg
= NULL
, *s
= NULL
, *line
;
5141 time_t committer_time
;
5142 const char *author
, *committer
;
5143 char *refs_str
= NULL
;
5144 struct got_pathlist_entry
*pe
;
5148 err
= build_refs_str(&refs_str
, refs
, commit_id
, repo
);
5152 err
= got_object_open_as_commit(&commit
, repo
, commit_id
);
5156 err
= got_object_id_str(&id_str
, commit_id
);
5158 err
= got_error_from_errno("got_object_id_str");
5162 err
= add_line_metadata(lines
, nlines
, 0, GOT_DIFF_LINE_NONE
);
5166 n
= fprintf(outfile
, "commit %s%s%s%s\n", id_str
, refs_str
? " (" : "",
5167 refs_str
? refs_str
: "", refs_str
? ")" : "");
5169 err
= got_error_from_errno("fprintf");
5173 err
= add_line_metadata(lines
, nlines
, outoff
, GOT_DIFF_LINE_META
);
5177 n
= fprintf(outfile
, "from: %s\n",
5178 got_object_commit_get_author(commit
));
5180 err
= got_error_from_errno("fprintf");
5184 err
= add_line_metadata(lines
, nlines
, outoff
, GOT_DIFF_LINE_AUTHOR
);
5188 author
= got_object_commit_get_author(commit
);
5189 committer
= got_object_commit_get_committer(commit
);
5190 if (strcmp(author
, committer
) != 0) {
5191 n
= fprintf(outfile
, "via: %s\n", committer
);
5193 err
= got_error_from_errno("fprintf");
5197 err
= add_line_metadata(lines
, nlines
, outoff
,
5198 GOT_DIFF_LINE_AUTHOR
);
5202 committer_time
= got_object_commit_get_committer_time(commit
);
5203 datestr
= get_datestr(&committer_time
, datebuf
);
5205 n
= fprintf(outfile
, "date: %s UTC\n", datestr
);
5207 err
= got_error_from_errno("fprintf");
5211 err
= add_line_metadata(lines
, nlines
, outoff
,
5212 GOT_DIFF_LINE_DATE
);
5216 if (got_object_commit_get_nparents(commit
) > 1) {
5217 const struct got_object_id_queue
*parent_ids
;
5218 struct got_object_qid
*qid
;
5220 parent_ids
= got_object_commit_get_parent_ids(commit
);
5221 STAILQ_FOREACH(qid
, parent_ids
, entry
) {
5222 err
= got_object_id_str(&id_str
, &qid
->id
);
5225 n
= fprintf(outfile
, "parent %d: %s\n", pn
++, id_str
);
5227 err
= got_error_from_errno("fprintf");
5231 err
= add_line_metadata(lines
, nlines
, outoff
,
5232 GOT_DIFF_LINE_META
);
5240 err
= got_object_commit_get_logmsg(&logmsg
, commit
);
5244 while ((line
= strsep(&s
, "\n")) != NULL
) {
5245 n
= fprintf(outfile
, "%s\n", line
);
5247 err
= got_error_from_errno("fprintf");
5251 err
= add_line_metadata(lines
, nlines
, outoff
,
5252 GOT_DIFF_LINE_LOGMSG
);
5257 TAILQ_FOREACH(pe
, dsa
->paths
, entry
) {
5258 struct got_diff_changed_path
*cp
= pe
->data
;
5259 int pad
= dsa
->max_path_len
- pe
->path_len
+ 1;
5261 n
= fprintf(outfile
, "%c %s%*c | %*d+ %*d-\n", cp
->status
,
5262 pe
->path
, pad
, ' ', dsa
->add_cols
+ 1, cp
->add
,
5263 dsa
->rm_cols
+ 1, cp
->rm
);
5265 err
= got_error_from_errno("fprintf");
5269 err
= add_line_metadata(lines
, nlines
, outoff
,
5270 GOT_DIFF_LINE_CHANGES
);
5275 fputc('\n', outfile
);
5277 err
= add_line_metadata(lines
, nlines
, outoff
, GOT_DIFF_LINE_NONE
);
5281 n
= fprintf(outfile
,
5282 "%d file%s changed, %d insertion%s(+), %d deletion%s(-)\n",
5283 dsa
->nfiles
, dsa
->nfiles
> 1 ? "s" : "", dsa
->ins
,
5284 dsa
->ins
!= 1 ? "s" : "", dsa
->del
, dsa
->del
!= 1 ? "s" : "");
5286 err
= got_error_from_errno("fprintf");
5290 err
= add_line_metadata(lines
, nlines
, outoff
, GOT_DIFF_LINE_NONE
);
5294 fputc('\n', outfile
);
5296 err
= add_line_metadata(lines
, nlines
, outoff
, GOT_DIFF_LINE_NONE
);
5301 got_object_commit_close(commit
);
5310 static const struct got_error
*
5311 create_diff(struct tog_diff_view_state
*s
)
5313 const struct got_error
*err
= NULL
;
5314 FILE *f
= NULL
, *tmp_diff_file
= NULL
;
5316 struct got_diff_line
*lines
= NULL
;
5317 struct got_pathlist_head changed_paths
;
5319 TAILQ_INIT(&changed_paths
);
5322 s
->lines
= malloc(sizeof(*s
->lines
));
5323 if (s
->lines
== NULL
)
5324 return got_error_from_errno("malloc");
5329 err
= got_error_from_errno("got_opentemp");
5332 tmp_diff_file
= got_opentemp();
5333 if (tmp_diff_file
== NULL
) {
5334 err
= got_error_from_errno("got_opentemp");
5337 if (s
->f
&& fclose(s
->f
) == EOF
) {
5338 err
= got_error_from_errno("fclose");
5344 err
= got_object_get_type(&obj_type
, s
->repo
, s
->id1
);
5346 err
= got_object_get_type(&obj_type
, s
->repo
, s
->id2
);
5351 case GOT_OBJ_TYPE_BLOB
:
5352 err
= got_diff_objects_as_blobs(&s
->lines
, &s
->nlines
,
5353 s
->f1
, s
->f2
, s
->fd1
, s
->fd2
, s
->id1
, s
->id2
,
5354 s
->label1
, s
->label2
, tog_diff_algo
, s
->diff_context
,
5355 s
->ignore_whitespace
, s
->force_text_diff
, NULL
, s
->repo
,
5358 case GOT_OBJ_TYPE_TREE
:
5359 err
= got_diff_objects_as_trees(&s
->lines
, &s
->nlines
,
5360 s
->f1
, s
->f2
, s
->fd1
, s
->fd2
, s
->id1
, s
->id2
, NULL
, "", "",
5361 tog_diff_algo
, s
->diff_context
, s
->ignore_whitespace
,
5362 s
->force_text_diff
, NULL
, s
->repo
, s
->f
);
5364 case GOT_OBJ_TYPE_COMMIT
: {
5365 const struct got_object_id_queue
*parent_ids
;
5366 struct got_object_qid
*pid
;
5367 struct got_commit_object
*commit2
;
5368 struct got_reflist_head
*refs
;
5370 struct got_diffstat_cb_arg dsa
= {
5373 s
->ignore_whitespace
,
5378 lines
= malloc(sizeof(*lines
));
5379 if (lines
== NULL
) {
5380 err
= got_error_from_errno("malloc");
5384 /* build diff first in tmp file then append to commit info */
5385 err
= got_diff_objects_as_commits(&lines
, &nlines
,
5386 s
->f1
, s
->f2
, s
->fd1
, s
->fd2
, s
->id1
, s
->id2
, NULL
,
5387 tog_diff_algo
, s
->diff_context
, s
->ignore_whitespace
,
5388 s
->force_text_diff
, &dsa
, s
->repo
, tmp_diff_file
);
5392 err
= got_object_open_as_commit(&commit2
, s
->repo
, s
->id2
);
5395 refs
= got_reflist_object_id_map_lookup(tog_refs_idmap
, s
->id2
);
5396 /* Show commit info if we're diffing to a parent/root commit. */
5397 if (s
->id1
== NULL
) {
5398 err
= write_commit_info(&s
->lines
, &s
->nlines
, s
->id2
,
5399 refs
, s
->repo
, s
->ignore_whitespace
,
5400 s
->force_text_diff
, &dsa
, s
->f
);
5404 parent_ids
= got_object_commit_get_parent_ids(commit2
);
5405 STAILQ_FOREACH(pid
, parent_ids
, entry
) {
5406 if (got_object_id_cmp(s
->id1
, &pid
->id
) == 0) {
5407 err
= write_commit_info(&s
->lines
,
5408 &s
->nlines
, s
->id2
, refs
, s
->repo
,
5409 s
->ignore_whitespace
,
5410 s
->force_text_diff
, &dsa
, s
->f
);
5417 got_object_commit_close(commit2
);
5419 err
= cat_diff(s
->f
, tmp_diff_file
, &s
->lines
, &s
->nlines
,
5424 err
= got_error(GOT_ERR_OBJ_TYPE
);
5429 got_pathlist_free(&changed_paths
, GOT_PATHLIST_FREE_ALL
);
5430 if (s
->f
&& fflush(s
->f
) != 0 && err
== NULL
)
5431 err
= got_error_from_errno("fflush");
5432 if (tmp_diff_file
&& fclose(tmp_diff_file
) == EOF
&& err
== NULL
)
5433 err
= got_error_from_errno("fclose");
5438 diff_view_indicate_progress(struct tog_view
*view
)
5440 mvwaddstr(view
->window
, 0, 0, "diffing...");
5445 static const struct got_error
*
5446 search_start_diff_view(struct tog_view
*view
)
5448 struct tog_diff_view_state
*s
= &view
->state
.diff
;
5450 s
->matched_line
= 0;
5455 search_setup_diff_view(struct tog_view
*view
, FILE **f
, off_t
**line_offsets
,
5456 size_t *nlines
, int **first
, int **last
, int **match
, int **selected
)
5458 struct tog_diff_view_state
*s
= &view
->state
.diff
;
5461 *nlines
= s
->nlines
;
5462 *line_offsets
= NULL
;
5463 *match
= &s
->matched_line
;
5464 *first
= &s
->first_displayed_line
;
5465 *last
= &s
->last_displayed_line
;
5466 *selected
= &s
->selected_line
;
5469 static const struct got_error
*
5470 search_next_view_match(struct tog_view
*view
)
5472 const struct got_error
*err
= NULL
;
5476 size_t linesize
= 0;
5478 off_t
*line_offsets
;
5480 int *first
, *last
, *match
, *selected
;
5482 if (!view
->search_setup
)
5483 return got_error_msg(GOT_ERR_NOT_IMPL
,
5484 "view search not supported");
5485 view
->search_setup(view
, &f
, &line_offsets
, &nlines
, &first
, &last
,
5488 if (!view
->searching
) {
5489 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
5494 if (view
->searching
== TOG_SEARCH_FORWARD
)
5495 lineno
= *first
+ 1;
5497 lineno
= *first
- 1;
5499 lineno
= *first
- 1 + *selected
;
5504 if (lineno
<= 0 || lineno
> nlines
) {
5506 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
5510 if (view
->searching
== TOG_SEARCH_FORWARD
)
5516 offset
= view
->type
== TOG_VIEW_DIFF
?
5517 view
->state
.diff
.lines
[lineno
- 1].offset
:
5518 line_offsets
[lineno
- 1];
5519 if (fseeko(f
, offset
, SEEK_SET
) != 0) {
5521 return got_error_from_errno("fseeko");
5523 linelen
= getline(&line
, &linesize
, f
);
5524 if (linelen
!= -1) {
5526 err
= expand_tab(&exstr
, line
);
5529 if (match_line(exstr
, &view
->regex
, 1,
5531 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
5538 if (view
->searching
== TOG_SEARCH_FORWARD
)
5553 static const struct got_error
*
5554 close_diff_view(struct tog_view
*view
)
5556 const struct got_error
*err
= NULL
;
5557 struct tog_diff_view_state
*s
= &view
->state
.diff
;
5563 if (s
->f
&& fclose(s
->f
) == EOF
)
5564 err
= got_error_from_errno("fclose");
5566 if (s
->f1
&& fclose(s
->f1
) == EOF
&& err
== NULL
)
5567 err
= got_error_from_errno("fclose");
5569 if (s
->f2
&& fclose(s
->f2
) == EOF
&& err
== NULL
)
5570 err
= got_error_from_errno("fclose");
5572 if (s
->fd1
!= -1 && close(s
->fd1
) == -1 && err
== NULL
)
5573 err
= got_error_from_errno("close");
5575 if (s
->fd2
!= -1 && close(s
->fd2
) == -1 && err
== NULL
)
5576 err
= got_error_from_errno("close");
5584 static const struct got_error
*
5585 open_diff_view(struct tog_view
*view
, struct got_object_id
*id1
,
5586 struct got_object_id
*id2
, const char *label1
, const char *label2
,
5587 int diff_context
, int ignore_whitespace
, int force_text_diff
,
5588 struct tog_view
*parent_view
, struct got_repository
*repo
)
5590 const struct got_error
*err
;
5591 struct tog_diff_view_state
*s
= &view
->state
.diff
;
5593 memset(s
, 0, sizeof(*s
));
5597 if (id1
!= NULL
&& id2
!= NULL
) {
5600 err
= got_object_get_type(&type1
, repo
, id1
);
5603 err
= got_object_get_type(&type2
, repo
, id2
);
5607 if (type1
!= type2
) {
5608 err
= got_error(GOT_ERR_OBJ_TYPE
);
5612 s
->first_displayed_line
= 1;
5613 s
->last_displayed_line
= view
->nlines
;
5614 s
->selected_line
= 1;
5622 s
->id1
= got_object_id_dup(id1
);
5623 if (s
->id1
== NULL
) {
5624 err
= got_error_from_errno("got_object_id_dup");
5630 s
->id2
= got_object_id_dup(id2
);
5631 if (s
->id2
== NULL
) {
5632 err
= got_error_from_errno("got_object_id_dup");
5636 s
->f1
= got_opentemp();
5637 if (s
->f1
== NULL
) {
5638 err
= got_error_from_errno("got_opentemp");
5642 s
->f2
= got_opentemp();
5643 if (s
->f2
== NULL
) {
5644 err
= got_error_from_errno("got_opentemp");
5648 s
->fd1
= got_opentempfd();
5650 err
= got_error_from_errno("got_opentempfd");
5654 s
->fd2
= got_opentempfd();
5656 err
= got_error_from_errno("got_opentempfd");
5660 s
->diff_context
= diff_context
;
5661 s
->ignore_whitespace
= ignore_whitespace
;
5662 s
->force_text_diff
= force_text_diff
;
5663 s
->parent_view
= parent_view
;
5666 if (has_colors() && getenv("TOG_COLORS") != NULL
&& !using_mock_io
) {
5669 rc
= init_pair(GOT_DIFF_LINE_MINUS
,
5670 get_color_value("TOG_COLOR_DIFF_MINUS"), -1);
5672 rc
= init_pair(GOT_DIFF_LINE_PLUS
,
5673 get_color_value("TOG_COLOR_DIFF_PLUS"), -1);
5675 rc
= init_pair(GOT_DIFF_LINE_HUNK
,
5676 get_color_value("TOG_COLOR_DIFF_CHUNK_HEADER"), -1);
5678 rc
= init_pair(GOT_DIFF_LINE_META
,
5679 get_color_value("TOG_COLOR_DIFF_META"), -1);
5681 rc
= init_pair(GOT_DIFF_LINE_CHANGES
,
5682 get_color_value("TOG_COLOR_DIFF_META"), -1);
5684 rc
= init_pair(GOT_DIFF_LINE_BLOB_MIN
,
5685 get_color_value("TOG_COLOR_DIFF_META"), -1);
5687 rc
= init_pair(GOT_DIFF_LINE_BLOB_PLUS
,
5688 get_color_value("TOG_COLOR_DIFF_META"), -1);
5690 rc
= init_pair(GOT_DIFF_LINE_AUTHOR
,
5691 get_color_value("TOG_COLOR_AUTHOR"), -1);
5693 rc
= init_pair(GOT_DIFF_LINE_DATE
,
5694 get_color_value("TOG_COLOR_DATE"), -1);
5696 err
= got_error(GOT_ERR_RANGE
);
5701 if (parent_view
&& parent_view
->type
== TOG_VIEW_LOG
&&
5702 view_is_splitscreen(view
))
5703 show_log_view(parent_view
); /* draw border */
5704 diff_view_indicate_progress(view
);
5706 err
= create_diff(s
);
5708 view
->show
= show_diff_view
;
5709 view
->input
= input_diff_view
;
5710 view
->reset
= reset_diff_view
;
5711 view
->close
= close_diff_view
;
5712 view
->search_start
= search_start_diff_view
;
5713 view
->search_setup
= search_setup_diff_view
;
5714 view
->search_next
= search_next_view_match
;
5717 if (view
->close
== NULL
)
5718 close_diff_view(view
);
5724 static const struct got_error
*
5725 show_diff_view(struct tog_view
*view
)
5727 const struct got_error
*err
;
5728 struct tog_diff_view_state
*s
= &view
->state
.diff
;
5729 char *id_str1
= NULL
, *id_str2
, *header
;
5730 const char *label1
, *label2
;
5733 err
= got_object_id_str(&id_str1
, s
->id1
);
5736 label1
= s
->label1
? s
->label1
: id_str1
;
5738 label1
= "/dev/null";
5740 err
= got_object_id_str(&id_str2
, s
->id2
);
5743 label2
= s
->label2
? s
->label2
: id_str2
;
5745 if (asprintf(&header
, "diff %s %s", label1
, label2
) == -1) {
5746 err
= got_error_from_errno("asprintf");
5754 err
= draw_file(view
, header
);
5759 static const struct got_error
*
5760 set_selected_commit(struct tog_diff_view_state
*s
,
5761 struct commit_queue_entry
*entry
)
5763 const struct got_error
*err
;
5764 const struct got_object_id_queue
*parent_ids
;
5765 struct got_commit_object
*selected_commit
;
5766 struct got_object_qid
*pid
;
5769 s
->id2
= got_object_id_dup(entry
->id
);
5771 return got_error_from_errno("got_object_id_dup");
5773 err
= got_object_open_as_commit(&selected_commit
, s
->repo
, entry
->id
);
5776 parent_ids
= got_object_commit_get_parent_ids(selected_commit
);
5778 pid
= STAILQ_FIRST(parent_ids
);
5779 s
->id1
= pid
? got_object_id_dup(&pid
->id
) : NULL
;
5780 got_object_commit_close(selected_commit
);
5784 static const struct got_error
*
5785 reset_diff_view(struct tog_view
*view
)
5787 struct tog_diff_view_state
*s
= &view
->state
.diff
;
5790 wclear(view
->window
);
5791 s
->first_displayed_line
= 1;
5792 s
->last_displayed_line
= view
->nlines
;
5793 s
->matched_line
= 0;
5794 diff_view_indicate_progress(view
);
5795 return create_diff(s
);
5799 diff_prev_index(struct tog_diff_view_state
*s
, enum got_diff_line_type type
)
5803 i
= start
= s
->first_displayed_line
- 1;
5805 while (s
->lines
[i
].type
!= type
) {
5809 return; /* do nothing, requested type not in file */
5812 s
->selected_line
= 1;
5813 s
->first_displayed_line
= i
;
5817 diff_next_index(struct tog_diff_view_state
*s
, enum got_diff_line_type type
)
5821 i
= start
= s
->first_displayed_line
+ 1;
5823 while (s
->lines
[i
].type
!= type
) {
5824 if (i
== s
->nlines
- 1)
5827 return; /* do nothing, requested type not in file */
5830 s
->selected_line
= 1;
5831 s
->first_displayed_line
= i
;
5834 static struct got_object_id
*get_selected_commit_id(struct tog_blame_line
*,
5836 static struct got_object_id
*get_annotation_for_line(struct tog_blame_line
*,
5839 static const struct got_error
*
5840 input_diff_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
5842 const struct got_error
*err
= NULL
;
5843 struct tog_diff_view_state
*s
= &view
->state
.diff
;
5844 struct tog_log_view_state
*ls
;
5845 struct commit_queue_entry
*old_selected_entry
;
5847 size_t linesize
= 0;
5849 int i
, nscroll
= view
->nlines
- 1, up
= 0;
5851 s
->lineno
= s
->first_displayed_line
- 1 + s
->selected_line
;
5860 horizontal_scroll_input(view
, ch
);
5865 s
->force_text_diff
= !s
->force_text_diff
;
5866 view
->action
= s
->force_text_diff
?
5867 "force ASCII text enabled" :
5868 "force ASCII text disabled";
5870 else if (ch
== 'w') {
5871 s
->ignore_whitespace
= !s
->ignore_whitespace
;
5872 view
->action
= s
->ignore_whitespace
?
5873 "ignore whitespace enabled" :
5874 "ignore whitespace disabled";
5876 err
= reset_diff_view(view
);
5880 s
->first_displayed_line
= 1;
5889 s
->first_displayed_line
= (s
->nlines
- view
->nlines
) + 2;
5895 if (s
->first_displayed_line
> 1)
5896 s
->first_displayed_line
--;
5907 if (s
->first_displayed_line
== 1) {
5912 while (i
++ < nscroll
&& s
->first_displayed_line
> 1)
5913 s
->first_displayed_line
--;
5919 s
->first_displayed_line
++;
5936 while (!s
->eof
&& i
++ < nscroll
) {
5937 linelen
= getline(&line
, &linesize
, s
->f
);
5938 s
->first_displayed_line
++;
5939 if (linelen
== -1) {
5943 err
= got_ferror(s
->f
, GOT_ERR_IO
);
5950 diff_prev_index(s
, GOT_DIFF_LINE_BLOB_MIN
);
5953 diff_next_index(s
, GOT_DIFF_LINE_BLOB_MIN
);
5956 diff_prev_index(s
, GOT_DIFF_LINE_HUNK
);
5959 diff_next_index(s
, GOT_DIFF_LINE_HUNK
);
5962 if (s
->diff_context
> 0) {
5964 s
->matched_line
= 0;
5965 diff_view_indicate_progress(view
);
5966 err
= create_diff(s
);
5967 if (s
->first_displayed_line
+ view
->nlines
- 1 >
5969 s
->first_displayed_line
= 1;
5970 s
->last_displayed_line
= view
->nlines
;
5976 if (s
->diff_context
< GOT_DIFF_MAX_CONTEXT
) {
5978 s
->matched_line
= 0;
5979 diff_view_indicate_progress(view
);
5980 err
= create_diff(s
);
5992 if (s
->parent_view
== NULL
) {
5996 s
->parent_view
->count
= view
->count
;
5998 if (s
->parent_view
->type
== TOG_VIEW_LOG
) {
5999 ls
= &s
->parent_view
->state
.log
;
6000 old_selected_entry
= ls
->selected_entry
;
6002 err
= input_log_view(NULL
, s
->parent_view
,
6003 up
? KEY_UP
: KEY_DOWN
);
6006 view
->count
= s
->parent_view
->count
;
6008 if (old_selected_entry
== ls
->selected_entry
)
6011 err
= set_selected_commit(s
, ls
->selected_entry
);
6014 } else if (s
->parent_view
->type
== TOG_VIEW_BLAME
) {
6015 struct tog_blame_view_state
*bs
;
6016 struct got_object_id
*id
, *prev_id
;
6018 bs
= &s
->parent_view
->state
.blame
;
6019 prev_id
= get_annotation_for_line(bs
->blame
.lines
,
6020 bs
->blame
.nlines
, bs
->last_diffed_line
);
6022 err
= input_blame_view(&view
, s
->parent_view
,
6023 up
? KEY_UP
: KEY_DOWN
);
6026 view
->count
= s
->parent_view
->count
;
6028 if (prev_id
== NULL
)
6030 id
= get_selected_commit_id(bs
->blame
.lines
,
6031 bs
->blame
.nlines
, bs
->first_displayed_line
,
6036 if (!got_object_id_cmp(prev_id
, id
))
6039 err
= input_blame_view(&view
, s
->parent_view
, KEY_ENTER
);
6043 s
->first_displayed_line
= 1;
6044 s
->last_displayed_line
= view
->nlines
;
6045 s
->matched_line
= 0;
6048 diff_view_indicate_progress(view
);
6049 err
= create_diff(s
);
6059 static const struct got_error
*
6060 cmd_diff(int argc
, char *argv
[])
6062 const struct got_error
*error
;
6063 struct got_repository
*repo
= NULL
;
6064 struct got_worktree
*worktree
= NULL
;
6065 struct got_object_id
*id1
= NULL
, *id2
= NULL
;
6066 char *repo_path
= NULL
, *cwd
= NULL
;
6067 char *id_str1
= NULL
, *id_str2
= NULL
;
6068 char *keyword_idstr1
= NULL
, *keyword_idstr2
= NULL
;
6069 char *label1
= NULL
, *label2
= NULL
;
6070 int diff_context
= 3, ignore_whitespace
= 0;
6071 int ch
, force_text_diff
= 0;
6073 struct tog_view
*view
;
6074 int *pack_fds
= NULL
;
6076 while ((ch
= getopt(argc
, argv
, "aC:r:w")) != -1) {
6079 force_text_diff
= 1;
6082 diff_context
= strtonum(optarg
, 0, GOT_DIFF_MAX_CONTEXT
,
6085 errx(1, "number of context lines is %s: %s",
6089 repo_path
= realpath(optarg
, NULL
);
6090 if (repo_path
== NULL
)
6091 return got_error_from_errno2("realpath",
6093 got_path_strip_trailing_slashes(repo_path
);
6096 ignore_whitespace
= 1;
6108 usage_diff(); /* TODO show local worktree changes */
6109 } else if (argc
== 2) {
6115 error
= got_repo_pack_fds_open(&pack_fds
);
6119 if (repo_path
== NULL
) {
6120 cwd
= getcwd(NULL
, 0);
6122 return got_error_from_errno("getcwd");
6123 error
= got_worktree_open(&worktree
, cwd
, NULL
);
6124 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
6128 strdup(got_worktree_get_repo_path(worktree
));
6130 repo_path
= strdup(cwd
);
6131 if (repo_path
== NULL
) {
6132 error
= got_error_from_errno("strdup");
6137 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
6143 error
= apply_unveil(got_repo_get_path(repo
), NULL
);
6147 error
= tog_load_refs(repo
, 0);
6151 if (id_str1
!= NULL
) {
6152 error
= got_keyword_to_idstr(&keyword_idstr1
, id_str1
,
6156 if (keyword_idstr1
!= NULL
)
6157 id_str1
= keyword_idstr1
;
6159 if (id_str2
!= NULL
) {
6160 error
= got_keyword_to_idstr(&keyword_idstr2
, id_str2
,
6164 if (keyword_idstr2
!= NULL
)
6165 id_str2
= keyword_idstr2
;
6168 error
= got_repo_match_object_id(&id1
, &label1
, id_str1
,
6169 GOT_OBJ_TYPE_ANY
, &tog_refs
, repo
);
6173 error
= got_repo_match_object_id(&id2
, &label2
, id_str2
,
6174 GOT_OBJ_TYPE_ANY
, &tog_refs
, repo
);
6178 view
= view_open(0, 0, 0, 0, TOG_VIEW_DIFF
);
6180 error
= got_error_from_errno("view_open");
6183 error
= open_diff_view(view
, id1
, id2
, label1
, label2
, diff_context
,
6184 ignore_whitespace
, force_text_diff
, NULL
, repo
);
6189 error
= set_tog_base_commit(repo
, worktree
);
6194 error
= view_loop(view
);
6197 free(tog_base_commit
.id
);
6198 free(keyword_idstr1
);
6199 free(keyword_idstr2
);
6205 const struct got_error
*close_err
= got_repo_close(repo
);
6210 got_worktree_close(worktree
);
6212 const struct got_error
*pack_err
=
6213 got_repo_pack_fds_close(pack_fds
);
6226 "usage: %s blame [-c commit] [-r repository-path] path\n",
6231 struct tog_blame_line
{
6233 struct got_object_id
*id
;
6236 static const struct got_error
*
6237 draw_blame(struct tog_view
*view
)
6239 struct tog_blame_view_state
*s
= &view
->state
.blame
;
6240 struct tog_blame
*blame
= &s
->blame
;
6241 regmatch_t
*regmatch
= &view
->regmatch
;
6242 const struct got_error
*err
;
6243 int lineno
= 0, nprinted
= 0;
6245 size_t linesize
= 0;
6249 struct tog_blame_line
*blame_line
;
6250 struct got_object_id
*prev_id
= NULL
;
6252 struct tog_color
*tc
;
6254 err
= got_object_id_str(&id_str
, &s
->blamed_commit
->id
);
6259 werase(view
->window
);
6261 if (asprintf(&line
, "commit %s", id_str
) == -1) {
6262 err
= got_error_from_errno("asprintf");
6267 err
= format_line(&wline
, &width
, NULL
, line
, 0, view
->ncols
, 0, 0);
6272 if (view_needs_focus_indication(view
))
6273 wstandout(view
->window
);
6274 tc
= get_color(&s
->colors
, TOG_COLOR_COMMIT
);
6276 wattr_on(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
6277 waddwstr(view
->window
, wline
);
6278 while (width
++ < view
->ncols
)
6279 waddch(view
->window
, ' ');
6281 wattr_off(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
6282 if (view_needs_focus_indication(view
))
6283 wstandend(view
->window
);
6287 if (view
->gline
> blame
->nlines
)
6288 view
->gline
= blame
->nlines
;
6290 if (tog_io
.wait_for_ui
) {
6291 struct tog_blame_thread_args
*bta
= &s
->blame
.thread_args
;
6294 rc
= pthread_cond_wait(&bta
->blame_complete
, &tog_mutex
);
6296 return got_error_set_errno(rc
, "pthread_cond_wait");
6297 tog_io
.wait_for_ui
= 0;
6300 if (asprintf(&line
, "[%d/%d] %s%s", view
->gline
? view
->gline
:
6301 s
->first_displayed_line
- 1 + s
->selected_line
, blame
->nlines
,
6302 s
->blame_complete
? "" : "annotating... ", s
->path
) == -1) {
6304 return got_error_from_errno("asprintf");
6307 err
= format_line(&wline
, &width
, NULL
, line
, 0, view
->ncols
, 0, 0);
6312 waddwstr(view
->window
, wline
);
6315 if (width
< view
->ncols
- 1)
6316 waddch(view
->window
, '\n');
6320 while (nprinted
< view
->nlines
- 2) {
6321 linelen
= getline(&line
, &linesize
, blame
->f
);
6322 if (linelen
== -1) {
6323 if (feof(blame
->f
)) {
6328 return got_ferror(blame
->f
, GOT_ERR_IO
);
6330 if (++lineno
< s
->first_displayed_line
)
6332 if (view
->gline
&& !gotoline(view
, &lineno
, &nprinted
))
6335 /* Set view->maxx based on full line length. */
6336 err
= format_line(&wline
, &width
, NULL
, line
, 0, INT_MAX
, 9, 1);
6343 view
->maxx
= MAX(view
->maxx
, width
);
6345 if (nprinted
== s
->selected_line
- 1)
6346 wstandout(view
->window
);
6348 if (blame
->nlines
> 0) {
6349 blame_line
= &blame
->lines
[lineno
- 1];
6350 if (blame_line
->annotated
&& prev_id
&&
6351 got_object_id_cmp(prev_id
, blame_line
->id
) == 0 &&
6352 !(nprinted
== s
->selected_line
- 1)) {
6353 waddstr(view
->window
, " ");
6354 } else if (blame_line
->annotated
) {
6356 err
= got_object_id_str(&id_str
,
6362 tc
= get_color(&s
->colors
, TOG_COLOR_COMMIT
);
6364 wattr_on(view
->window
,
6365 COLOR_PAIR(tc
->colorpair
), NULL
);
6366 wprintw(view
->window
, "%.8s", id_str
);
6368 wattr_off(view
->window
,
6369 COLOR_PAIR(tc
->colorpair
), NULL
);
6371 prev_id
= blame_line
->id
;
6373 waddstr(view
->window
, "........");
6377 waddstr(view
->window
, "........");
6381 if (nprinted
== s
->selected_line
- 1)
6382 wstandend(view
->window
);
6383 waddstr(view
->window
, " ");
6385 if (view
->ncols
<= 9) {
6387 } else if (s
->first_displayed_line
+ nprinted
==
6389 regmatch
->rm_so
>= 0 && regmatch
->rm_so
< regmatch
->rm_eo
) {
6390 err
= add_matched_line(&width
, line
, view
->ncols
- 9, 9,
6391 view
->window
, view
->x
, regmatch
);
6399 err
= format_line(&wline
, &width
, &skip
, line
,
6400 view
->x
, view
->ncols
- 9, 9, 1);
6405 waddwstr(view
->window
, &wline
[skip
]);
6411 if (width
<= view
->ncols
- 1)
6412 waddch(view
->window
, '\n');
6413 if (++nprinted
== 1)
6414 s
->first_displayed_line
= lineno
;
6417 s
->last_displayed_line
= lineno
;
6424 static const struct got_error
*
6425 blame_cb(void *arg
, int nlines
, int lineno
,
6426 struct got_commit_object
*commit
, struct got_object_id
*id
)
6428 const struct got_error
*err
= NULL
;
6429 struct tog_blame_cb_args
*a
= arg
;
6430 struct tog_blame_line
*line
;
6433 if (nlines
!= a
->nlines
||
6434 (lineno
!= -1 && lineno
< 1) || lineno
> a
->nlines
)
6435 return got_error(GOT_ERR_RANGE
);
6437 errcode
= pthread_mutex_lock(&tog_mutex
);
6439 return got_error_set_errno(errcode
, "pthread_mutex_lock");
6441 if (*a
->quit
) { /* user has quit the blame view */
6442 err
= got_error(GOT_ERR_ITER_COMPLETED
);
6447 goto done
; /* no change in this commit */
6449 line
= &a
->lines
[lineno
- 1];
6450 if (line
->annotated
)
6453 line
->id
= got_object_id_dup(id
);
6454 if (line
->id
== NULL
) {
6455 err
= got_error_from_errno("got_object_id_dup");
6458 line
->annotated
= 1;
6460 errcode
= pthread_mutex_unlock(&tog_mutex
);
6462 err
= got_error_set_errno(errcode
, "pthread_mutex_unlock");
6467 blame_thread(void *arg
)
6469 const struct got_error
*err
, *close_err
;
6470 struct tog_blame_thread_args
*ta
= arg
;
6471 struct tog_blame_cb_args
*a
= ta
->cb_args
;
6472 int errcode
, fd1
= -1, fd2
= -1;
6473 FILE *f1
= NULL
, *f2
= NULL
;
6475 fd1
= got_opentempfd();
6477 return (void *)got_error_from_errno("got_opentempfd");
6479 fd2
= got_opentempfd();
6481 err
= got_error_from_errno("got_opentempfd");
6485 f1
= got_opentemp();
6487 err
= (void *)got_error_from_errno("got_opentemp");
6490 f2
= got_opentemp();
6492 err
= (void *)got_error_from_errno("got_opentemp");
6496 err
= block_signals_used_by_main_thread();
6500 err
= got_blame(ta
->path
, a
->commit_id
, ta
->repo
,
6501 tog_diff_algo
, blame_cb
, ta
->cb_args
,
6502 ta
->cancel_cb
, ta
->cancel_arg
, fd1
, fd2
, f1
, f2
);
6503 if (err
&& err
->code
== GOT_ERR_CANCELLED
)
6506 errcode
= pthread_mutex_lock(&tog_mutex
);
6508 err
= got_error_set_errno(errcode
, "pthread_mutex_lock");
6512 close_err
= got_repo_close(ta
->repo
);
6518 if (tog_io
.wait_for_ui
) {
6519 errcode
= pthread_cond_signal(&ta
->blame_complete
);
6520 if (errcode
&& err
== NULL
)
6521 err
= got_error_set_errno(errcode
,
6522 "pthread_cond_signal");
6525 errcode
= pthread_mutex_unlock(&tog_mutex
);
6526 if (errcode
&& err
== NULL
)
6527 err
= got_error_set_errno(errcode
, "pthread_mutex_unlock");
6530 if (fd1
!= -1 && close(fd1
) == -1 && err
== NULL
)
6531 err
= got_error_from_errno("close");
6532 if (fd2
!= -1 && close(fd2
) == -1 && err
== NULL
)
6533 err
= got_error_from_errno("close");
6534 if (f1
&& fclose(f1
) == EOF
&& err
== NULL
)
6535 err
= got_error_from_errno("fclose");
6536 if (f2
&& fclose(f2
) == EOF
&& err
== NULL
)
6537 err
= got_error_from_errno("fclose");
6542 static struct got_object_id
*
6543 get_selected_commit_id(struct tog_blame_line
*lines
, int nlines
,
6544 int first_displayed_line
, int selected_line
)
6546 struct tog_blame_line
*line
;
6551 line
= &lines
[first_displayed_line
- 1 + selected_line
- 1];
6552 if (!line
->annotated
)
6558 static struct got_object_id
*
6559 get_annotation_for_line(struct tog_blame_line
*lines
, int nlines
,
6562 struct tog_blame_line
*line
;
6564 if (nlines
<= 0 || lineno
>= nlines
)
6567 line
= &lines
[lineno
- 1];
6568 if (!line
->annotated
)
6574 static const struct got_error
*
6575 stop_blame(struct tog_blame
*blame
)
6577 const struct got_error
*err
= NULL
;
6580 if (blame
->thread
) {
6582 errcode
= pthread_mutex_unlock(&tog_mutex
);
6584 return got_error_set_errno(errcode
,
6585 "pthread_mutex_unlock");
6586 errcode
= pthread_join(blame
->thread
, (void **)&err
);
6588 return got_error_set_errno(errcode
, "pthread_join");
6589 errcode
= pthread_mutex_lock(&tog_mutex
);
6591 return got_error_set_errno(errcode
,
6592 "pthread_mutex_lock");
6593 if (err
&& err
->code
== GOT_ERR_ITER_COMPLETED
)
6595 blame
->thread
= 0; //NULL;
6597 if (blame
->thread_args
.repo
) {
6598 const struct got_error
*close_err
;
6599 close_err
= got_repo_close(blame
->thread_args
.repo
);
6602 blame
->thread_args
.repo
= NULL
;
6605 if (fclose(blame
->f
) == EOF
&& err
== NULL
)
6606 err
= got_error_from_errno("fclose");
6610 for (i
= 0; i
< blame
->nlines
; i
++)
6611 free(blame
->lines
[i
].id
);
6613 blame
->lines
= NULL
;
6615 free(blame
->cb_args
.commit_id
);
6616 blame
->cb_args
.commit_id
= NULL
;
6617 if (blame
->pack_fds
) {
6618 const struct got_error
*pack_err
=
6619 got_repo_pack_fds_close(blame
->pack_fds
);
6622 blame
->pack_fds
= NULL
;
6624 free(blame
->line_offsets
);
6625 blame
->line_offsets
= NULL
;
6629 static const struct got_error
*
6630 cancel_blame_view(void *arg
)
6632 const struct got_error
*err
= NULL
;
6636 errcode
= pthread_mutex_lock(&tog_mutex
);
6638 return got_error_set_errno(errcode
,
6639 "pthread_mutex_unlock");
6642 err
= got_error(GOT_ERR_CANCELLED
);
6644 errcode
= pthread_mutex_unlock(&tog_mutex
);
6646 return got_error_set_errno(errcode
,
6647 "pthread_mutex_lock");
6652 static const struct got_error
*
6653 run_blame(struct tog_view
*view
)
6655 struct tog_blame_view_state
*s
= &view
->state
.blame
;
6656 struct tog_blame
*blame
= &s
->blame
;
6657 const struct got_error
*err
= NULL
;
6658 struct got_commit_object
*commit
= NULL
;
6659 struct got_blob_object
*blob
= NULL
;
6660 struct got_repository
*thread_repo
= NULL
;
6661 struct got_object_id
*obj_id
= NULL
;
6662 int obj_type
, fd
= -1;
6663 int *pack_fds
= NULL
;
6665 err
= got_object_open_as_commit(&commit
, s
->repo
,
6666 &s
->blamed_commit
->id
);
6670 fd
= got_opentempfd();
6672 err
= got_error_from_errno("got_opentempfd");
6676 err
= got_object_id_by_path(&obj_id
, s
->repo
, commit
, s
->path
);
6680 err
= got_object_get_type(&obj_type
, s
->repo
, obj_id
);
6684 if (obj_type
!= GOT_OBJ_TYPE_BLOB
) {
6685 err
= got_error(GOT_ERR_OBJ_TYPE
);
6689 err
= got_object_open_as_blob(&blob
, s
->repo
, obj_id
, 8192, fd
);
6692 blame
->f
= got_opentemp();
6693 if (blame
->f
== NULL
) {
6694 err
= got_error_from_errno("got_opentemp");
6697 err
= got_object_blob_dump_to_file(&blame
->filesize
, &blame
->nlines
,
6698 &blame
->line_offsets
, blame
->f
, blob
);
6701 if (blame
->nlines
== 0) {
6702 s
->blame_complete
= 1;
6706 /* Don't include \n at EOF in the blame line count. */
6707 if (blame
->line_offsets
[blame
->nlines
- 1] == blame
->filesize
)
6710 blame
->lines
= calloc(blame
->nlines
, sizeof(*blame
->lines
));
6711 if (blame
->lines
== NULL
) {
6712 err
= got_error_from_errno("calloc");
6716 err
= got_repo_pack_fds_open(&pack_fds
);
6719 err
= got_repo_open(&thread_repo
, got_repo_get_path(s
->repo
), NULL
,
6724 blame
->pack_fds
= pack_fds
;
6725 blame
->cb_args
.view
= view
;
6726 blame
->cb_args
.lines
= blame
->lines
;
6727 blame
->cb_args
.nlines
= blame
->nlines
;
6728 blame
->cb_args
.commit_id
= got_object_id_dup(&s
->blamed_commit
->id
);
6729 if (blame
->cb_args
.commit_id
== NULL
) {
6730 err
= got_error_from_errno("got_object_id_dup");
6733 blame
->cb_args
.quit
= &s
->done
;
6735 blame
->thread_args
.path
= s
->path
;
6736 blame
->thread_args
.repo
= thread_repo
;
6737 blame
->thread_args
.cb_args
= &blame
->cb_args
;
6738 blame
->thread_args
.complete
= &s
->blame_complete
;
6739 blame
->thread_args
.cancel_cb
= cancel_blame_view
;
6740 blame
->thread_args
.cancel_arg
= &s
->done
;
6741 s
->blame_complete
= 0;
6743 if (s
->first_displayed_line
+ view
->nlines
- 1 > blame
->nlines
) {
6744 s
->first_displayed_line
= 1;
6745 s
->last_displayed_line
= view
->nlines
;
6746 s
->selected_line
= 1;
6748 s
->matched_line
= 0;
6752 got_object_commit_close(commit
);
6753 if (fd
!= -1 && close(fd
) == -1 && err
== NULL
)
6754 err
= got_error_from_errno("close");
6756 got_object_blob_close(blob
);
6763 static const struct got_error
*
6764 open_blame_view(struct tog_view
*view
, char *path
,
6765 struct got_object_id
*commit_id
, struct got_repository
*repo
)
6767 const struct got_error
*err
= NULL
;
6768 struct tog_blame_view_state
*s
= &view
->state
.blame
;
6770 STAILQ_INIT(&s
->blamed_commits
);
6772 s
->path
= strdup(path
);
6773 if (s
->path
== NULL
)
6774 return got_error_from_errno("strdup");
6776 err
= got_object_qid_alloc(&s
->blamed_commit
, commit_id
);
6782 STAILQ_INSERT_HEAD(&s
->blamed_commits
, s
->blamed_commit
, entry
);
6783 s
->first_displayed_line
= 1;
6784 s
->last_displayed_line
= view
->nlines
;
6785 s
->selected_line
= 1;
6786 s
->blame_complete
= 0;
6788 s
->commit_id
= commit_id
;
6789 memset(&s
->blame
, 0, sizeof(s
->blame
));
6791 STAILQ_INIT(&s
->colors
);
6792 if (has_colors() && getenv("TOG_COLORS") != NULL
) {
6793 err
= add_color(&s
->colors
, "^", TOG_COLOR_COMMIT
,
6794 get_color_value("TOG_COLOR_COMMIT"));
6799 view
->show
= show_blame_view
;
6800 view
->input
= input_blame_view
;
6801 view
->reset
= reset_blame_view
;
6802 view
->close
= close_blame_view
;
6803 view
->search_start
= search_start_blame_view
;
6804 view
->search_setup
= search_setup_blame_view
;
6805 view
->search_next
= search_next_view_match
;
6807 if (using_mock_io
) {
6808 struct tog_blame_thread_args
*bta
= &s
->blame
.thread_args
;
6811 rc
= pthread_cond_init(&bta
->blame_complete
, NULL
);
6813 return got_error_set_errno(rc
, "pthread_cond_init");
6816 return run_blame(view
);
6819 static const struct got_error
*
6820 close_blame_view(struct tog_view
*view
)
6822 const struct got_error
*err
= NULL
;
6823 struct tog_blame_view_state
*s
= &view
->state
.blame
;
6825 if (s
->blame
.thread
)
6826 err
= stop_blame(&s
->blame
);
6828 while (!STAILQ_EMPTY(&s
->blamed_commits
)) {
6829 struct got_object_qid
*blamed_commit
;
6830 blamed_commit
= STAILQ_FIRST(&s
->blamed_commits
);
6831 STAILQ_REMOVE_HEAD(&s
->blamed_commits
, entry
);
6832 got_object_qid_free(blamed_commit
);
6835 if (using_mock_io
) {
6836 struct tog_blame_thread_args
*bta
= &s
->blame
.thread_args
;
6839 rc
= pthread_cond_destroy(&bta
->blame_complete
);
6840 if (rc
&& err
== NULL
)
6841 err
= got_error_set_errno(rc
, "pthread_cond_destroy");
6845 free_colors(&s
->colors
);
6849 static const struct got_error
*
6850 search_start_blame_view(struct tog_view
*view
)
6852 struct tog_blame_view_state
*s
= &view
->state
.blame
;
6854 s
->matched_line
= 0;
6859 search_setup_blame_view(struct tog_view
*view
, FILE **f
, off_t
**line_offsets
,
6860 size_t *nlines
, int **first
, int **last
, int **match
, int **selected
)
6862 struct tog_blame_view_state
*s
= &view
->state
.blame
;
6865 *nlines
= s
->blame
.nlines
;
6866 *line_offsets
= s
->blame
.line_offsets
;
6867 *match
= &s
->matched_line
;
6868 *first
= &s
->first_displayed_line
;
6869 *last
= &s
->last_displayed_line
;
6870 *selected
= &s
->selected_line
;
6873 static const struct got_error
*
6874 show_blame_view(struct tog_view
*view
)
6876 const struct got_error
*err
= NULL
;
6877 struct tog_blame_view_state
*s
= &view
->state
.blame
;
6880 if (s
->blame
.thread
== 0 && !s
->blame_complete
) {
6881 errcode
= pthread_create(&s
->blame
.thread
, NULL
, blame_thread
,
6882 &s
->blame
.thread_args
);
6884 return got_error_set_errno(errcode
, "pthread_create");
6887 halfdelay(1); /* fast refresh while annotating */
6890 if (s
->blame_complete
&& !using_mock_io
)
6891 halfdelay(10); /* disable fast refresh */
6893 err
= draw_blame(view
);
6899 static const struct got_error
*
6900 log_annotated_line(struct tog_view
**new_view
, int begin_y
, int begin_x
,
6901 struct got_repository
*repo
, struct got_object_id
*id
)
6903 struct tog_view
*log_view
;
6904 const struct got_error
*err
= NULL
;
6908 log_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_LOG
);
6909 if (log_view
== NULL
)
6910 return got_error_from_errno("view_open");
6912 err
= open_log_view(log_view
, id
, repo
, GOT_REF_HEAD
, "", 0, NULL
);
6914 view_close(log_view
);
6916 *new_view
= log_view
;
6921 static const struct got_error
*
6922 input_blame_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
6924 const struct got_error
*err
= NULL
, *thread_err
= NULL
;
6925 struct tog_view
*diff_view
;
6926 struct tog_blame_view_state
*s
= &view
->state
.blame
;
6927 int eos
, nscroll
, begin_y
= 0, begin_x
= 0;
6929 eos
= nscroll
= view
->nlines
- 2;
6930 if (view_is_hsplit_top(view
))
6940 horizontal_scroll_input(view
, ch
);
6947 s
->selected_line
= 1;
6948 s
->first_displayed_line
= 1;
6953 if (s
->blame
.nlines
< eos
) {
6954 s
->selected_line
= s
->blame
.nlines
;
6955 s
->first_displayed_line
= 1;
6957 s
->selected_line
= eos
;
6958 s
->first_displayed_line
= s
->blame
.nlines
- (eos
- 1);
6965 if (s
->selected_line
> 1)
6967 else if (s
->selected_line
== 1 &&
6968 s
->first_displayed_line
> 1)
6969 s
->first_displayed_line
--;
6980 if (s
->first_displayed_line
== 1) {
6981 if (view
->count
> 1)
6983 s
->selected_line
= MAX(1, s
->selected_line
- nscroll
);
6987 if (s
->first_displayed_line
> nscroll
)
6988 s
->first_displayed_line
-= nscroll
;
6990 s
->first_displayed_line
= 1;
6995 if (s
->selected_line
< eos
&& s
->first_displayed_line
+
6996 s
->selected_line
<= s
->blame
.nlines
)
6998 else if (s
->first_displayed_line
< s
->blame
.nlines
- (eos
- 1))
6999 s
->first_displayed_line
++;
7005 struct got_object_id
*id
= NULL
;
7008 id
= get_selected_commit_id(s
->blame
.lines
, s
->blame
.nlines
,
7009 s
->first_displayed_line
, s
->selected_line
);
7013 struct got_commit_object
*commit
, *pcommit
;
7014 struct got_object_qid
*pid
;
7015 struct got_object_id
*blob_id
= NULL
;
7017 err
= got_object_open_as_commit(&commit
,
7022 got_object_commit_get_parent_ids(commit
));
7024 got_object_commit_close(commit
);
7027 /* Check if path history ends here. */
7028 err
= got_object_open_as_commit(&pcommit
,
7032 err
= got_object_id_by_path(&blob_id
, s
->repo
,
7034 got_object_commit_close(pcommit
);
7036 if (err
->code
== GOT_ERR_NO_TREE_ENTRY
)
7038 got_object_commit_close(commit
);
7041 err
= got_object_get_type(&obj_type
, s
->repo
,
7044 /* Can't blame non-blob type objects. */
7045 if (obj_type
!= GOT_OBJ_TYPE_BLOB
) {
7046 got_object_commit_close(commit
);
7049 err
= got_object_qid_alloc(&s
->blamed_commit
,
7051 got_object_commit_close(commit
);
7053 if (got_object_id_cmp(id
,
7054 &s
->blamed_commit
->id
) == 0)
7056 err
= got_object_qid_alloc(&s
->blamed_commit
,
7062 thread_err
= stop_blame(&s
->blame
);
7066 STAILQ_INSERT_HEAD(&s
->blamed_commits
,
7067 s
->blamed_commit
, entry
);
7068 err
= run_blame(view
);
7074 struct got_object_qid
*first
;
7077 first
= STAILQ_FIRST(&s
->blamed_commits
);
7078 if (!got_object_id_cmp(&first
->id
, s
->commit_id
))
7081 thread_err
= stop_blame(&s
->blame
);
7085 STAILQ_REMOVE_HEAD(&s
->blamed_commits
, entry
);
7086 got_object_qid_free(s
->blamed_commit
);
7088 STAILQ_FIRST(&s
->blamed_commits
);
7089 err
= run_blame(view
);
7096 s
->id_to_log
= get_selected_commit_id(s
->blame
.lines
,
7097 s
->blame
.nlines
, s
->first_displayed_line
, s
->selected_line
);
7099 err
= view_request_new(new_view
, view
, TOG_VIEW_LOG
);
7103 struct got_object_id
*id
= NULL
;
7104 struct got_object_qid
*pid
;
7105 struct got_commit_object
*commit
= NULL
;
7108 id
= get_selected_commit_id(s
->blame
.lines
, s
->blame
.nlines
,
7109 s
->first_displayed_line
, s
->selected_line
);
7112 err
= got_object_open_as_commit(&commit
, s
->repo
, id
);
7115 pid
= STAILQ_FIRST(got_object_commit_get_parent_ids(commit
));
7117 /* traversed from diff view, release diff resources */
7118 err
= close_diff_view(*new_view
);
7121 diff_view
= *new_view
;
7123 if (view_is_parent_view(view
))
7124 view_get_split(view
, &begin_y
, &begin_x
);
7126 diff_view
= view_open(0, 0, begin_y
, begin_x
,
7128 if (diff_view
== NULL
) {
7129 got_object_commit_close(commit
);
7130 err
= got_error_from_errno("view_open");
7134 err
= open_diff_view(diff_view
, pid
? &pid
->id
: NULL
,
7135 id
, NULL
, NULL
, 3, 0, 0, view
, s
->repo
);
7136 got_object_commit_close(commit
);
7139 s
->last_diffed_line
= s
->first_displayed_line
- 1 +
7142 break; /* still open from active diff view */
7143 if (view_is_parent_view(view
) &&
7144 view
->mode
== TOG_VIEW_SPLIT_HRZN
) {
7145 err
= view_init_hsplit(view
, begin_y
);
7151 diff_view
->focussed
= 1;
7152 diff_view
->mode
= view
->mode
;
7153 diff_view
->nlines
= view
->lines
- begin_y
;
7154 if (view_is_parent_view(view
)) {
7155 view_transfer_size(diff_view
, view
);
7156 err
= view_close_child(view
);
7159 err
= view_set_child(view
, diff_view
);
7162 view
->focus_child
= 1;
7164 *new_view
= diff_view
;
7177 if (s
->last_displayed_line
>= s
->blame
.nlines
&&
7178 s
->selected_line
>= MIN(s
->blame
.nlines
,
7179 view
->nlines
- 2)) {
7183 if (s
->last_displayed_line
>= s
->blame
.nlines
&&
7184 s
->selected_line
< view
->nlines
- 2) {
7186 MIN(nscroll
, s
->last_displayed_line
-
7187 s
->first_displayed_line
- s
->selected_line
+ 1);
7189 if (s
->last_displayed_line
+ nscroll
<= s
->blame
.nlines
)
7190 s
->first_displayed_line
+= nscroll
;
7192 s
->first_displayed_line
=
7193 s
->blame
.nlines
- (view
->nlines
- 3);
7196 if (s
->selected_line
> view
->nlines
- 2) {
7197 s
->selected_line
= MIN(s
->blame
.nlines
,
7205 return thread_err
? thread_err
: err
;
7208 static const struct got_error
*
7209 reset_blame_view(struct tog_view
*view
)
7211 const struct got_error
*err
;
7212 struct tog_blame_view_state
*s
= &view
->state
.blame
;
7216 err
= stop_blame(&s
->blame
);
7220 return run_blame(view
);
7223 static const struct got_error
*
7224 cmd_blame(int argc
, char *argv
[])
7226 const struct got_error
*error
;
7227 struct got_repository
*repo
= NULL
;
7228 struct got_worktree
*worktree
= NULL
;
7229 char *cwd
= NULL
, *repo_path
= NULL
, *in_repo_path
= NULL
;
7230 char *link_target
= NULL
;
7231 struct got_object_id
*commit_id
= NULL
;
7232 struct got_commit_object
*commit
= NULL
;
7233 char *keyword_idstr
= NULL
, *commit_id_str
= NULL
;
7235 struct tog_view
*view
= NULL
;
7236 int *pack_fds
= NULL
;
7238 while ((ch
= getopt(argc
, argv
, "c:r:")) != -1) {
7241 commit_id_str
= optarg
;
7244 repo_path
= realpath(optarg
, NULL
);
7245 if (repo_path
== NULL
)
7246 return got_error_from_errno2("realpath",
7261 error
= got_repo_pack_fds_open(&pack_fds
);
7265 if (repo_path
== NULL
) {
7266 cwd
= getcwd(NULL
, 0);
7268 return got_error_from_errno("getcwd");
7269 error
= got_worktree_open(&worktree
, cwd
, NULL
);
7270 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
7274 strdup(got_worktree_get_repo_path(worktree
));
7276 repo_path
= strdup(cwd
);
7277 if (repo_path
== NULL
) {
7278 error
= got_error_from_errno("strdup");
7283 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
7287 error
= get_in_repo_path_from_argv0(&in_repo_path
, argc
, argv
, repo
,
7294 error
= apply_unveil(got_repo_get_path(repo
), NULL
);
7298 error
= tog_load_refs(repo
, 0);
7302 if (commit_id_str
== NULL
) {
7303 struct got_reference
*head_ref
;
7304 error
= got_ref_open(&head_ref
, repo
, worktree
?
7305 got_worktree_get_head_ref_name(worktree
) : GOT_REF_HEAD
, 0);
7308 error
= got_ref_resolve(&commit_id
, repo
, head_ref
);
7309 got_ref_close(head_ref
);
7311 error
= got_keyword_to_idstr(&keyword_idstr
, commit_id_str
,
7315 if (keyword_idstr
!= NULL
)
7316 commit_id_str
= keyword_idstr
;
7318 error
= got_repo_match_object_id(&commit_id
, NULL
,
7319 commit_id_str
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
7324 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
7328 error
= got_object_resolve_symlinks(&link_target
, in_repo_path
,
7333 view
= view_open(0, 0, 0, 0, TOG_VIEW_BLAME
);
7335 error
= got_error_from_errno("view_open");
7338 error
= open_blame_view(view
, link_target
? link_target
: in_repo_path
,
7340 if (error
!= NULL
) {
7341 if (view
->close
== NULL
)
7342 close_blame_view(view
);
7348 error
= set_tog_base_commit(repo
, worktree
);
7352 /* Release work tree lock. */
7353 got_worktree_close(worktree
);
7357 error
= view_loop(view
);
7360 free(tog_base_commit
.id
);
7366 free(keyword_idstr
);
7368 got_object_commit_close(commit
);
7370 got_worktree_close(worktree
);
7372 const struct got_error
*close_err
= got_repo_close(repo
);
7377 const struct got_error
*pack_err
=
7378 got_repo_pack_fds_close(pack_fds
);
7386 static const struct got_error
*
7387 draw_tree_entries(struct tog_view
*view
, const char *parent_path
)
7389 struct tog_tree_view_state
*s
= &view
->state
.tree
;
7390 const struct got_error
*err
= NULL
;
7391 struct got_tree_entry
*te
;
7394 struct tog_color
*tc
;
7395 int width
, n
, nentries
, scrollx
, i
= 1;
7396 int limit
= view
->nlines
;
7399 if (view_is_hsplit_top(view
))
7400 --limit
; /* border */
7402 werase(view
->window
);
7407 err
= format_line(&wline
, &width
, NULL
, s
->tree_label
, 0, view
->ncols
,
7411 if (view_needs_focus_indication(view
))
7412 wstandout(view
->window
);
7413 tc
= get_color(&s
->colors
, TOG_COLOR_COMMIT
);
7415 wattr_on(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
7416 waddwstr(view
->window
, wline
);
7419 while (width
++ < view
->ncols
)
7420 waddch(view
->window
, ' ');
7422 wattr_off(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
7423 if (view_needs_focus_indication(view
))
7424 wstandend(view
->window
);
7429 if (s
->first_displayed_entry
) {
7430 i
+= got_tree_entry_get_index(s
->first_displayed_entry
);
7431 if (s
->tree
!= s
->root
)
7432 ++i
; /* account for ".." entry */
7434 nentries
= got_object_tree_get_nentries(s
->tree
);
7435 if (asprintf(&index
, "[%d/%d] %s",
7436 i
, nentries
+ (s
->tree
== s
->root
? 0 : 1), parent_path
) == -1)
7437 return got_error_from_errno("asprintf");
7438 err
= format_line(&wline
, &width
, NULL
, index
, 0, view
->ncols
, 0, 0);
7442 waddwstr(view
->window
, wline
);
7445 if (width
< view
->ncols
- 1)
7446 waddch(view
->window
, '\n');
7449 waddch(view
->window
, '\n');
7453 if (s
->first_displayed_entry
== NULL
) {
7454 te
= got_object_tree_get_first_entry(s
->tree
);
7455 if (s
->selected
== 0) {
7457 wstandout(view
->window
);
7458 s
->selected_entry
= NULL
;
7460 waddstr(view
->window
, " ..\n"); /* parent directory */
7461 if (s
->selected
== 0 && view
->focussed
)
7462 wstandend(view
->window
);
7469 te
= s
->first_displayed_entry
;
7473 for (i
= got_tree_entry_get_index(te
); i
< nentries
; i
++) {
7474 char *line
= NULL
, *id_str
= NULL
, *link_target
= NULL
;
7475 const char *modestr
= "";
7478 te
= got_object_tree_get_entry(s
->tree
, i
);
7479 mode
= got_tree_entry_get_mode(te
);
7482 err
= got_object_id_str(&id_str
,
7483 got_tree_entry_get_id(te
));
7485 return got_error_from_errno(
7486 "got_object_id_str");
7488 if (got_object_tree_entry_is_submodule(te
))
7490 else if (S_ISLNK(mode
)) {
7493 err
= got_tree_entry_get_symlink_target(&link_target
,
7499 for (i
= 0; link_target
[i
] != '\0'; i
++) {
7500 if (!isprint((unsigned char)link_target
[i
]))
7501 link_target
[i
] = '?';
7505 else if (S_ISDIR(mode
))
7507 else if (mode
& S_IXUSR
)
7509 if (asprintf(&line
, "%s %s%s%s%s", id_str
? id_str
: "",
7510 got_tree_entry_get_name(te
), modestr
,
7511 link_target
? " -> ": "",
7512 link_target
? link_target
: "") == -1) {
7515 return got_error_from_errno("asprintf");
7520 /* use full line width to determine view->maxx */
7521 err
= format_line(&wline
, &width
, NULL
, line
, 0, INT_MAX
, 0, 0);
7526 view
->maxx
= MAX(view
->maxx
, width
);
7530 err
= format_line(&wline
, &width
, &scrollx
, line
, view
->x
,
7536 if (n
== s
->selected
) {
7538 wstandout(view
->window
);
7539 s
->selected_entry
= te
;
7541 tc
= match_color(&s
->colors
, line
);
7543 wattr_on(view
->window
,
7544 COLOR_PAIR(tc
->colorpair
), NULL
);
7545 waddwstr(view
->window
, &wline
[scrollx
]);
7547 wattr_off(view
->window
,
7548 COLOR_PAIR(tc
->colorpair
), NULL
);
7549 if (width
< view
->ncols
)
7550 waddch(view
->window
, '\n');
7551 if (n
== s
->selected
&& view
->focussed
)
7552 wstandend(view
->window
);
7558 s
->last_displayed_entry
= te
;
7567 tree_scroll_up(struct tog_tree_view_state
*s
, int maxscroll
)
7569 struct got_tree_entry
*te
;
7570 int isroot
= s
->tree
== s
->root
;
7573 if (s
->first_displayed_entry
== NULL
)
7576 te
= got_tree_entry_get_prev(s
->tree
, s
->first_displayed_entry
);
7577 while (i
++ < maxscroll
) {
7580 s
->first_displayed_entry
= NULL
;
7583 s
->first_displayed_entry
= te
;
7584 te
= got_tree_entry_get_prev(s
->tree
, te
);
7588 static const struct got_error
*
7589 tree_scroll_down(struct tog_view
*view
, int maxscroll
)
7591 struct tog_tree_view_state
*s
= &view
->state
.tree
;
7592 struct got_tree_entry
*next
, *last
;
7595 if (s
->first_displayed_entry
)
7596 next
= got_tree_entry_get_next(s
->tree
,
7597 s
->first_displayed_entry
);
7599 next
= got_object_tree_get_first_entry(s
->tree
);
7601 last
= s
->last_displayed_entry
;
7602 while (next
&& n
++ < maxscroll
) {
7604 s
->last_displayed_entry
= last
;
7605 last
= got_tree_entry_get_next(s
->tree
, last
);
7607 if (last
|| (view
->mode
== TOG_VIEW_SPLIT_HRZN
&& next
)) {
7608 s
->first_displayed_entry
= next
;
7609 next
= got_tree_entry_get_next(s
->tree
, next
);
7616 static const struct got_error
*
7617 tree_entry_path(char **path
, struct tog_parent_trees
*parents
,
7618 struct got_tree_entry
*te
)
7620 const struct got_error
*err
= NULL
;
7621 struct tog_parent_tree
*pt
;
7622 size_t len
= 2; /* for leading slash and NUL */
7624 TAILQ_FOREACH(pt
, parents
, entry
)
7625 len
+= strlen(got_tree_entry_get_name(pt
->selected_entry
))
7628 len
+= strlen(got_tree_entry_get_name(te
));
7630 *path
= calloc(1, len
);
7632 return got_error_from_errno("calloc");
7635 pt
= TAILQ_LAST(parents
, tog_parent_trees
);
7637 const char *name
= got_tree_entry_get_name(pt
->selected_entry
);
7638 if (strlcat(*path
, name
, len
) >= len
) {
7639 err
= got_error(GOT_ERR_NO_SPACE
);
7642 if (strlcat(*path
, "/", len
) >= len
) {
7643 err
= got_error(GOT_ERR_NO_SPACE
);
7646 pt
= TAILQ_PREV(pt
, tog_parent_trees
, entry
);
7649 if (strlcat(*path
, got_tree_entry_get_name(te
), len
) >= len
) {
7650 err
= got_error(GOT_ERR_NO_SPACE
);
7662 static const struct got_error
*
7663 blame_tree_entry(struct tog_view
**new_view
, int begin_y
, int begin_x
,
7664 struct got_tree_entry
*te
, struct tog_parent_trees
*parents
,
7665 struct got_object_id
*commit_id
, struct got_repository
*repo
)
7667 const struct got_error
*err
= NULL
;
7669 struct tog_view
*blame_view
;
7673 err
= tree_entry_path(&path
, parents
, te
);
7677 blame_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_BLAME
);
7678 if (blame_view
== NULL
) {
7679 err
= got_error_from_errno("view_open");
7683 err
= open_blame_view(blame_view
, path
, commit_id
, repo
);
7685 if (err
->code
== GOT_ERR_CANCELLED
)
7687 view_close(blame_view
);
7689 *new_view
= blame_view
;
7695 static const struct got_error
*
7696 log_selected_tree_entry(struct tog_view
**new_view
, int begin_y
, int begin_x
,
7697 struct tog_tree_view_state
*s
)
7699 struct tog_view
*log_view
;
7700 const struct got_error
*err
= NULL
;
7705 log_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_LOG
);
7706 if (log_view
== NULL
)
7707 return got_error_from_errno("view_open");
7709 err
= tree_entry_path(&path
, &s
->parents
, s
->selected_entry
);
7713 err
= open_log_view(log_view
, s
->commit_id
, s
->repo
, s
->head_ref_name
,
7716 view_close(log_view
);
7718 *new_view
= log_view
;
7723 static const struct got_error
*
7724 open_tree_view(struct tog_view
*view
, struct got_object_id
*commit_id
,
7725 const char *head_ref_name
, struct got_repository
*repo
)
7727 const struct got_error
*err
= NULL
;
7728 char *commit_id_str
= NULL
;
7729 struct tog_tree_view_state
*s
= &view
->state
.tree
;
7730 struct got_commit_object
*commit
= NULL
;
7732 TAILQ_INIT(&s
->parents
);
7733 STAILQ_INIT(&s
->colors
);
7735 s
->commit_id
= got_object_id_dup(commit_id
);
7736 if (s
->commit_id
== NULL
) {
7737 err
= got_error_from_errno("got_object_id_dup");
7741 err
= got_object_open_as_commit(&commit
, repo
, commit_id
);
7746 * The root is opened here and will be closed when the view is closed.
7747 * Any visited subtrees and their path-wise parents are opened and
7750 err
= got_object_open_as_tree(&s
->root
, repo
,
7751 got_object_commit_get_tree_id(commit
));
7756 err
= got_object_id_str(&commit_id_str
, commit_id
);
7760 if (asprintf(&s
->tree_label
, "commit %s", commit_id_str
) == -1) {
7761 err
= got_error_from_errno("asprintf");
7765 s
->first_displayed_entry
= got_object_tree_get_entry(s
->tree
, 0);
7766 s
->selected_entry
= got_object_tree_get_entry(s
->tree
, 0);
7767 if (head_ref_name
) {
7768 s
->head_ref_name
= strdup(head_ref_name
);
7769 if (s
->head_ref_name
== NULL
) {
7770 err
= got_error_from_errno("strdup");
7776 if (has_colors() && getenv("TOG_COLORS") != NULL
) {
7777 err
= add_color(&s
->colors
, "\\$$",
7778 TOG_COLOR_TREE_SUBMODULE
,
7779 get_color_value("TOG_COLOR_TREE_SUBMODULE"));
7782 err
= add_color(&s
->colors
, "@$", TOG_COLOR_TREE_SYMLINK
,
7783 get_color_value("TOG_COLOR_TREE_SYMLINK"));
7786 err
= add_color(&s
->colors
, "/$",
7787 TOG_COLOR_TREE_DIRECTORY
,
7788 get_color_value("TOG_COLOR_TREE_DIRECTORY"));
7792 err
= add_color(&s
->colors
, "\\*$",
7793 TOG_COLOR_TREE_EXECUTABLE
,
7794 get_color_value("TOG_COLOR_TREE_EXECUTABLE"));
7798 err
= add_color(&s
->colors
, "^$", TOG_COLOR_COMMIT
,
7799 get_color_value("TOG_COLOR_COMMIT"));
7804 view
->show
= show_tree_view
;
7805 view
->input
= input_tree_view
;
7806 view
->close
= close_tree_view
;
7807 view
->search_start
= search_start_tree_view
;
7808 view
->search_next
= search_next_tree_view
;
7810 free(commit_id_str
);
7812 got_object_commit_close(commit
);
7814 if (view
->close
== NULL
)
7815 close_tree_view(view
);
7821 static const struct got_error
*
7822 close_tree_view(struct tog_view
*view
)
7824 struct tog_tree_view_state
*s
= &view
->state
.tree
;
7826 free_colors(&s
->colors
);
7827 free(s
->tree_label
);
7828 s
->tree_label
= NULL
;
7830 s
->commit_id
= NULL
;
7831 free(s
->head_ref_name
);
7832 s
->head_ref_name
= NULL
;
7833 while (!TAILQ_EMPTY(&s
->parents
)) {
7834 struct tog_parent_tree
*parent
;
7835 parent
= TAILQ_FIRST(&s
->parents
);
7836 TAILQ_REMOVE(&s
->parents
, parent
, entry
);
7837 if (parent
->tree
!= s
->root
)
7838 got_object_tree_close(parent
->tree
);
7842 if (s
->tree
!= NULL
&& s
->tree
!= s
->root
)
7843 got_object_tree_close(s
->tree
);
7845 got_object_tree_close(s
->root
);
7849 static const struct got_error
*
7850 search_start_tree_view(struct tog_view
*view
)
7852 struct tog_tree_view_state
*s
= &view
->state
.tree
;
7854 s
->matched_entry
= NULL
;
7859 match_tree_entry(struct got_tree_entry
*te
, regex_t
*regex
)
7861 regmatch_t regmatch
;
7863 return regexec(regex
, got_tree_entry_get_name(te
), 1, ®match
,
7867 static const struct got_error
*
7868 search_next_tree_view(struct tog_view
*view
)
7870 struct tog_tree_view_state
*s
= &view
->state
.tree
;
7871 struct got_tree_entry
*te
= NULL
;
7873 if (!view
->searching
) {
7874 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
7878 if (s
->matched_entry
) {
7879 if (view
->searching
== TOG_SEARCH_FORWARD
) {
7880 if (s
->selected_entry
)
7881 te
= got_tree_entry_get_next(s
->tree
,
7884 te
= got_object_tree_get_first_entry(s
->tree
);
7886 if (s
->selected_entry
== NULL
)
7887 te
= got_object_tree_get_last_entry(s
->tree
);
7889 te
= got_tree_entry_get_prev(s
->tree
,
7893 if (s
->selected_entry
)
7894 te
= s
->selected_entry
;
7895 else if (view
->searching
== TOG_SEARCH_FORWARD
)
7896 te
= got_object_tree_get_first_entry(s
->tree
);
7898 te
= got_object_tree_get_last_entry(s
->tree
);
7903 if (s
->matched_entry
== NULL
) {
7904 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
7907 if (view
->searching
== TOG_SEARCH_FORWARD
)
7908 te
= got_object_tree_get_first_entry(s
->tree
);
7910 te
= got_object_tree_get_last_entry(s
->tree
);
7913 if (match_tree_entry(te
, &view
->regex
)) {
7914 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
7915 s
->matched_entry
= te
;
7919 if (view
->searching
== TOG_SEARCH_FORWARD
)
7920 te
= got_tree_entry_get_next(s
->tree
, te
);
7922 te
= got_tree_entry_get_prev(s
->tree
, te
);
7925 if (s
->matched_entry
) {
7926 s
->first_displayed_entry
= s
->matched_entry
;
7933 static const struct got_error
*
7934 show_tree_view(struct tog_view
*view
)
7936 const struct got_error
*err
= NULL
;
7937 struct tog_tree_view_state
*s
= &view
->state
.tree
;
7940 err
= tree_entry_path(&parent_path
, &s
->parents
, NULL
);
7944 err
= draw_tree_entries(view
, parent_path
);
7951 static const struct got_error
*
7952 tree_goto_line(struct tog_view
*view
, int nlines
)
7954 const struct got_error
*err
= NULL
;
7955 struct tog_tree_view_state
*s
= &view
->state
.tree
;
7956 struct got_tree_entry
**fte
, **lte
, **ste
;
7957 int g
, last
, first
= 1, i
= 1;
7958 int root
= s
->tree
== s
->root
;
7959 int off
= root
? 1 : 2;
7966 else if (g
> got_object_tree_get_nentries(s
->tree
))
7967 g
= got_object_tree_get_nentries(s
->tree
) + (root
? 0 : 1);
7969 fte
= &s
->first_displayed_entry
;
7970 lte
= &s
->last_displayed_entry
;
7971 ste
= &s
->selected_entry
;
7974 first
= got_tree_entry_get_index(*fte
);
7975 first
+= off
; /* account for ".." */
7977 last
= got_tree_entry_get_index(*lte
);
7980 if (g
>= first
&& g
<= last
&& g
- first
< nlines
) {
7981 s
->selected
= g
- first
;
7982 return NULL
; /* gline is on the current page */
7986 i
= got_tree_entry_get_index(*ste
);
7991 err
= tree_scroll_down(view
, g
- i
);
7994 if (got_tree_entry_get_index(*lte
) >=
7995 got_object_tree_get_nentries(s
->tree
) - 1 &&
7996 first
+ s
->selected
< g
&&
7997 s
->selected
< s
->ndisplayed
- 1) {
7998 first
= got_tree_entry_get_index(*fte
);
8000 s
->selected
= g
- first
;
8003 tree_scroll_up(s
, i
- g
);
8006 (*fte
== NULL
|| (root
&& !got_tree_entry_get_index(*fte
))))
8007 s
->selected
= g
- 1;
8012 static const struct got_error
*
8013 input_tree_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
8015 const struct got_error
*err
= NULL
;
8016 struct tog_tree_view_state
*s
= &view
->state
.tree
;
8017 struct got_tree_entry
*te
;
8018 int n
, nscroll
= view
->nlines
- 3;
8021 return tree_goto_line(view
, nscroll
);
8030 horizontal_scroll_input(view
, ch
);
8033 s
->show_ids
= !s
->show_ids
;
8038 if (!s
->selected_entry
)
8040 err
= view_request_new(new_view
, view
, TOG_VIEW_LOG
);
8044 err
= view_request_new(new_view
, view
, TOG_VIEW_REF
);
8051 if (s
->tree
== s
->root
)
8052 s
->first_displayed_entry
=
8053 got_object_tree_get_first_entry(s
->tree
);
8055 s
->first_displayed_entry
= NULL
;
8060 int eos
= view
->nlines
- 3;
8062 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
)
8066 te
= got_object_tree_get_last_entry(s
->tree
);
8067 for (n
= 0; n
< eos
; n
++) {
8069 if (s
->tree
!= s
->root
) {
8070 s
->first_displayed_entry
= NULL
;
8075 s
->first_displayed_entry
= te
;
8076 te
= got_tree_entry_get_prev(s
->tree
, te
);
8079 s
->selected
= n
- 1;
8085 if (s
->selected
> 0) {
8089 tree_scroll_up(s
, 1);
8090 if (s
->selected_entry
== NULL
||
8091 (s
->tree
== s
->root
&& s
->selected_entry
==
8092 got_object_tree_get_first_entry(s
->tree
)))
8102 if (s
->tree
== s
->root
) {
8103 if (got_object_tree_get_first_entry(s
->tree
) ==
8104 s
->first_displayed_entry
)
8105 s
->selected
-= MIN(s
->selected
, nscroll
);
8107 if (s
->first_displayed_entry
== NULL
)
8108 s
->selected
-= MIN(s
->selected
, nscroll
);
8110 tree_scroll_up(s
, MAX(0, nscroll
));
8111 if (s
->selected_entry
== NULL
||
8112 (s
->tree
== s
->root
&& s
->selected_entry
==
8113 got_object_tree_get_first_entry(s
->tree
)))
8119 if (s
->selected
< s
->ndisplayed
- 1) {
8123 if (got_tree_entry_get_next(s
->tree
, s
->last_displayed_entry
)
8125 /* can't scroll any further */
8129 tree_scroll_down(view
, 1);
8139 if (got_tree_entry_get_next(s
->tree
, s
->last_displayed_entry
)
8141 /* can't scroll any further; move cursor down */
8142 if (s
->selected
< s
->ndisplayed
- 1)
8143 s
->selected
+= MIN(nscroll
,
8144 s
->ndisplayed
- s
->selected
- 1);
8149 tree_scroll_down(view
, nscroll
);
8154 if (s
->selected_entry
== NULL
|| ch
== KEY_BACKSPACE
) {
8155 struct tog_parent_tree
*parent
;
8156 /* user selected '..' */
8157 if (s
->tree
== s
->root
) {
8161 parent
= TAILQ_FIRST(&s
->parents
);
8162 TAILQ_REMOVE(&s
->parents
, parent
,
8164 got_object_tree_close(s
->tree
);
8165 s
->tree
= parent
->tree
;
8166 s
->first_displayed_entry
=
8167 parent
->first_displayed_entry
;
8169 parent
->selected_entry
;
8170 s
->selected
= parent
->selected
;
8171 if (s
->selected
> view
->nlines
- 3) {
8172 err
= offset_selection_down(view
);
8177 } else if (S_ISDIR(got_tree_entry_get_mode(
8178 s
->selected_entry
))) {
8179 struct got_tree_object
*subtree
;
8181 err
= got_object_open_as_tree(&subtree
, s
->repo
,
8182 got_tree_entry_get_id(s
->selected_entry
));
8185 err
= tree_view_visit_subtree(s
, subtree
);
8187 got_object_tree_close(subtree
);
8190 } else if (S_ISREG(got_tree_entry_get_mode(s
->selected_entry
)))
8191 err
= view_request_new(new_view
, view
, TOG_VIEW_BLAME
);
8194 if (view
->nlines
>= 4 && s
->selected
>= view
->nlines
- 3)
8195 s
->selected
= view
->nlines
- 4;
8211 "usage: %s tree [-c commit] [-r repository-path] [path]\n",
8216 static const struct got_error
*
8217 cmd_tree(int argc
, char *argv
[])
8219 const struct got_error
*error
;
8220 struct got_repository
*repo
= NULL
;
8221 struct got_worktree
*worktree
= NULL
;
8222 char *cwd
= NULL
, *repo_path
= NULL
, *in_repo_path
= NULL
;
8223 struct got_object_id
*commit_id
= NULL
;
8224 struct got_commit_object
*commit
= NULL
;
8225 const char *commit_id_arg
= NULL
;
8226 char *keyword_idstr
= NULL
, *label
= NULL
;
8227 struct got_reference
*ref
= NULL
;
8228 const char *head_ref_name
= NULL
;
8230 struct tog_view
*view
;
8231 int *pack_fds
= NULL
;
8233 while ((ch
= getopt(argc
, argv
, "c:r:")) != -1) {
8236 commit_id_arg
= optarg
;
8239 repo_path
= realpath(optarg
, NULL
);
8240 if (repo_path
== NULL
)
8241 return got_error_from_errno2("realpath",
8256 error
= got_repo_pack_fds_open(&pack_fds
);
8260 if (repo_path
== NULL
) {
8261 cwd
= getcwd(NULL
, 0);
8263 return got_error_from_errno("getcwd");
8264 error
= got_worktree_open(&worktree
, cwd
, NULL
);
8265 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
8269 strdup(got_worktree_get_repo_path(worktree
));
8271 repo_path
= strdup(cwd
);
8272 if (repo_path
== NULL
) {
8273 error
= got_error_from_errno("strdup");
8278 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
8282 error
= get_in_repo_path_from_argv0(&in_repo_path
, argc
, argv
,
8289 error
= apply_unveil(got_repo_get_path(repo
), NULL
);
8293 error
= tog_load_refs(repo
, 0);
8297 if (commit_id_arg
== NULL
) {
8298 error
= got_repo_match_object_id(&commit_id
, &label
,
8299 worktree
? got_worktree_get_head_ref_name(worktree
) :
8300 GOT_REF_HEAD
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
8303 head_ref_name
= label
;
8305 error
= got_keyword_to_idstr(&keyword_idstr
, commit_id_arg
,
8309 if (keyword_idstr
!= NULL
)
8310 commit_id_arg
= keyword_idstr
;
8312 error
= got_ref_open(&ref
, repo
, commit_id_arg
, 0);
8314 head_ref_name
= got_ref_get_name(ref
);
8315 else if (error
->code
!= GOT_ERR_NOT_REF
)
8317 error
= got_repo_match_object_id(&commit_id
, NULL
,
8318 commit_id_arg
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
8323 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
8327 view
= view_open(0, 0, 0, 0, TOG_VIEW_TREE
);
8329 error
= got_error_from_errno("view_open");
8332 error
= open_tree_view(view
, commit_id
, head_ref_name
, repo
);
8335 if (!got_path_is_root_dir(in_repo_path
)) {
8336 error
= tree_view_walk_path(&view
->state
.tree
, commit
,
8343 error
= set_tog_base_commit(repo
, worktree
);
8347 /* Release work tree lock. */
8348 got_worktree_close(worktree
);
8352 error
= view_loop(view
);
8355 free(tog_base_commit
.id
);
8356 free(keyword_idstr
);
8363 if (worktree
!= NULL
)
8364 got_worktree_close(worktree
);
8366 const struct got_error
*close_err
= got_repo_close(repo
);
8371 const struct got_error
*pack_err
=
8372 got_repo_pack_fds_close(pack_fds
);
8380 static const struct got_error
*
8381 ref_view_load_refs(struct tog_ref_view_state
*s
)
8383 struct got_reflist_entry
*sre
;
8384 struct tog_reflist_entry
*re
;
8387 TAILQ_FOREACH(sre
, &tog_refs
, entry
) {
8388 if (strncmp(got_ref_get_name(sre
->ref
),
8389 "refs/got/", 9) == 0 &&
8390 strncmp(got_ref_get_name(sre
->ref
),
8391 "refs/got/backup/", 16) != 0)
8394 re
= malloc(sizeof(*re
));
8396 return got_error_from_errno("malloc");
8398 re
->ref
= got_ref_dup(sre
->ref
);
8399 if (re
->ref
== NULL
)
8400 return got_error_from_errno("got_ref_dup");
8401 re
->idx
= s
->nrefs
++;
8402 TAILQ_INSERT_TAIL(&s
->refs
, re
, entry
);
8405 s
->first_displayed_entry
= TAILQ_FIRST(&s
->refs
);
8410 ref_view_free_refs(struct tog_ref_view_state
*s
)
8412 struct tog_reflist_entry
*re
;
8414 while (!TAILQ_EMPTY(&s
->refs
)) {
8415 re
= TAILQ_FIRST(&s
->refs
);
8416 TAILQ_REMOVE(&s
->refs
, re
, entry
);
8417 got_ref_close(re
->ref
);
8422 static const struct got_error
*
8423 open_ref_view(struct tog_view
*view
, struct got_repository
*repo
)
8425 const struct got_error
*err
= NULL
;
8426 struct tog_ref_view_state
*s
= &view
->state
.ref
;
8428 s
->selected_entry
= 0;
8431 TAILQ_INIT(&s
->refs
);
8432 STAILQ_INIT(&s
->colors
);
8434 err
= ref_view_load_refs(s
);
8438 if (has_colors() && getenv("TOG_COLORS") != NULL
) {
8439 err
= add_color(&s
->colors
, "^refs/heads/",
8440 TOG_COLOR_REFS_HEADS
,
8441 get_color_value("TOG_COLOR_REFS_HEADS"));
8445 err
= add_color(&s
->colors
, "^refs/tags/",
8446 TOG_COLOR_REFS_TAGS
,
8447 get_color_value("TOG_COLOR_REFS_TAGS"));
8451 err
= add_color(&s
->colors
, "^refs/remotes/",
8452 TOG_COLOR_REFS_REMOTES
,
8453 get_color_value("TOG_COLOR_REFS_REMOTES"));
8457 err
= add_color(&s
->colors
, "^refs/got/backup/",
8458 TOG_COLOR_REFS_BACKUP
,
8459 get_color_value("TOG_COLOR_REFS_BACKUP"));
8464 view
->show
= show_ref_view
;
8465 view
->input
= input_ref_view
;
8466 view
->close
= close_ref_view
;
8467 view
->search_start
= search_start_ref_view
;
8468 view
->search_next
= search_next_ref_view
;
8471 if (view
->close
== NULL
)
8472 close_ref_view(view
);
8478 static const struct got_error
*
8479 close_ref_view(struct tog_view
*view
)
8481 struct tog_ref_view_state
*s
= &view
->state
.ref
;
8483 ref_view_free_refs(s
);
8484 free_colors(&s
->colors
);
8489 static const struct got_error
*
8490 resolve_reflist_entry(struct got_object_id
**commit_id
,
8491 struct tog_reflist_entry
*re
, struct got_repository
*repo
)
8493 const struct got_error
*err
= NULL
;
8494 struct got_object_id
*obj_id
;
8495 struct got_tag_object
*tag
= NULL
;
8500 err
= got_ref_resolve(&obj_id
, repo
, re
->ref
);
8504 err
= got_object_get_type(&obj_type
, repo
, obj_id
);
8509 case GOT_OBJ_TYPE_COMMIT
:
8510 *commit_id
= obj_id
;
8512 case GOT_OBJ_TYPE_TAG
:
8513 err
= got_object_open_as_tag(&tag
, repo
, obj_id
);
8517 err
= got_object_get_type(&obj_type
, repo
,
8518 got_object_tag_get_object_id(tag
));
8521 if (obj_type
!= GOT_OBJ_TYPE_COMMIT
) {
8522 err
= got_error(GOT_ERR_OBJ_TYPE
);
8525 *commit_id
= got_object_id_dup(
8526 got_object_tag_get_object_id(tag
));
8527 if (*commit_id
== NULL
) {
8528 err
= got_error_from_errno("got_object_id_dup");
8533 err
= got_error(GOT_ERR_OBJ_TYPE
);
8539 got_object_tag_close(tag
);
8547 static const struct got_error
*
8548 log_ref_entry(struct tog_view
**new_view
, int begin_y
, int begin_x
,
8549 struct tog_reflist_entry
*re
, struct got_repository
*repo
)
8551 struct tog_view
*log_view
;
8552 const struct got_error
*err
= NULL
;
8553 struct got_object_id
*commit_id
= NULL
;
8557 err
= resolve_reflist_entry(&commit_id
, re
, repo
);
8559 if (err
->code
!= GOT_ERR_OBJ_TYPE
)
8565 log_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_LOG
);
8566 if (log_view
== NULL
) {
8567 err
= got_error_from_errno("view_open");
8571 err
= open_log_view(log_view
, commit_id
, repo
,
8572 got_ref_get_name(re
->ref
), "", 0, NULL
);
8575 view_close(log_view
);
8577 *new_view
= log_view
;
8583 ref_scroll_up(struct tog_ref_view_state
*s
, int maxscroll
)
8585 struct tog_reflist_entry
*re
;
8588 if (s
->first_displayed_entry
== TAILQ_FIRST(&s
->refs
))
8591 re
= TAILQ_PREV(s
->first_displayed_entry
, tog_reflist_head
, entry
);
8592 while (i
++ < maxscroll
) {
8595 s
->first_displayed_entry
= re
;
8596 re
= TAILQ_PREV(re
, tog_reflist_head
, entry
);
8600 static const struct got_error
*
8601 ref_scroll_down(struct tog_view
*view
, int maxscroll
)
8603 struct tog_ref_view_state
*s
= &view
->state
.ref
;
8604 struct tog_reflist_entry
*next
, *last
;
8607 if (s
->first_displayed_entry
)
8608 next
= TAILQ_NEXT(s
->first_displayed_entry
, entry
);
8610 next
= TAILQ_FIRST(&s
->refs
);
8612 last
= s
->last_displayed_entry
;
8613 while (next
&& n
++ < maxscroll
) {
8615 s
->last_displayed_entry
= last
;
8616 last
= TAILQ_NEXT(last
, entry
);
8618 if (last
|| (view
->mode
== TOG_VIEW_SPLIT_HRZN
)) {
8619 s
->first_displayed_entry
= next
;
8620 next
= TAILQ_NEXT(next
, entry
);
8627 static const struct got_error
*
8628 search_start_ref_view(struct tog_view
*view
)
8630 struct tog_ref_view_state
*s
= &view
->state
.ref
;
8632 s
->matched_entry
= NULL
;
8637 match_reflist_entry(struct tog_reflist_entry
*re
, regex_t
*regex
)
8639 regmatch_t regmatch
;
8641 return regexec(regex
, got_ref_get_name(re
->ref
), 1, ®match
,
8645 static const struct got_error
*
8646 search_next_ref_view(struct tog_view
*view
)
8648 struct tog_ref_view_state
*s
= &view
->state
.ref
;
8649 struct tog_reflist_entry
*re
= NULL
;
8651 if (!view
->searching
) {
8652 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
8656 if (s
->matched_entry
) {
8657 if (view
->searching
== TOG_SEARCH_FORWARD
) {
8658 if (s
->selected_entry
)
8659 re
= TAILQ_NEXT(s
->selected_entry
, entry
);
8661 re
= TAILQ_PREV(s
->selected_entry
,
8662 tog_reflist_head
, entry
);
8664 if (s
->selected_entry
== NULL
)
8665 re
= TAILQ_LAST(&s
->refs
, tog_reflist_head
);
8667 re
= TAILQ_PREV(s
->selected_entry
,
8668 tog_reflist_head
, entry
);
8671 if (s
->selected_entry
)
8672 re
= s
->selected_entry
;
8673 else if (view
->searching
== TOG_SEARCH_FORWARD
)
8674 re
= TAILQ_FIRST(&s
->refs
);
8676 re
= TAILQ_LAST(&s
->refs
, tog_reflist_head
);
8681 if (s
->matched_entry
== NULL
) {
8682 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
8685 if (view
->searching
== TOG_SEARCH_FORWARD
)
8686 re
= TAILQ_FIRST(&s
->refs
);
8688 re
= TAILQ_LAST(&s
->refs
, tog_reflist_head
);
8691 if (match_reflist_entry(re
, &view
->regex
)) {
8692 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
8693 s
->matched_entry
= re
;
8697 if (view
->searching
== TOG_SEARCH_FORWARD
)
8698 re
= TAILQ_NEXT(re
, entry
);
8700 re
= TAILQ_PREV(re
, tog_reflist_head
, entry
);
8703 if (s
->matched_entry
) {
8704 s
->first_displayed_entry
= s
->matched_entry
;
8711 static const struct got_error
*
8712 show_ref_view(struct tog_view
*view
)
8714 const struct got_error
*err
= NULL
;
8715 struct tog_ref_view_state
*s
= &view
->state
.ref
;
8716 struct tog_reflist_entry
*re
;
8719 struct tog_color
*tc
;
8720 int width
, n
, scrollx
;
8721 int limit
= view
->nlines
;
8723 werase(view
->window
);
8726 if (view_is_hsplit_top(view
))
8727 --limit
; /* border */
8732 re
= s
->first_displayed_entry
;
8734 if (asprintf(&line
, "references [%d/%d]", re
->idx
+ s
->selected
+ 1,
8736 return got_error_from_errno("asprintf");
8738 err
= format_line(&wline
, &width
, NULL
, line
, 0, view
->ncols
, 0, 0);
8743 if (view_needs_focus_indication(view
))
8744 wstandout(view
->window
);
8745 waddwstr(view
->window
, wline
);
8746 while (width
++ < view
->ncols
)
8747 waddch(view
->window
, ' ');
8748 if (view_needs_focus_indication(view
))
8749 wstandend(view
->window
);
8759 while (re
&& limit
> 0) {
8761 char ymd
[13]; /* YYYY-MM-DD + " " + NUL */
8764 struct got_commit_object
*ci
;
8765 struct got_tag_object
*tag
;
8766 struct got_object_id
*id
;
8770 err
= got_ref_resolve(&id
, s
->repo
, re
->ref
);
8773 err
= got_object_open_as_tag(&tag
, s
->repo
, id
);
8775 if (err
->code
!= GOT_ERR_OBJ_TYPE
) {
8779 err
= got_object_open_as_commit(&ci
, s
->repo
,
8785 t
= got_object_commit_get_committer_time(ci
);
8786 got_object_commit_close(ci
);
8788 t
= got_object_tag_get_tagger_time(tag
);
8789 got_object_tag_close(tag
);
8792 if (gmtime_r(&t
, &tm
) == NULL
)
8793 return got_error_from_errno("gmtime_r");
8794 if (strftime(ymd
, sizeof(ymd
), "%G-%m-%d ", &tm
) == 0)
8795 return got_error(GOT_ERR_NO_SPACE
);
8797 if (got_ref_is_symbolic(re
->ref
)) {
8798 if (asprintf(&line
, "%s%s -> %s", s
->show_date
?
8799 ymd
: "", got_ref_get_name(re
->ref
),
8800 got_ref_get_symref_target(re
->ref
)) == -1)
8801 return got_error_from_errno("asprintf");
8802 } else if (s
->show_ids
) {
8803 struct got_object_id
*id
;
8805 err
= got_ref_resolve(&id
, s
->repo
, re
->ref
);
8808 err
= got_object_id_str(&id_str
, id
);
8813 if (asprintf(&line
, "%s%s: %s", s
->show_date
? ymd
: "",
8814 got_ref_get_name(re
->ref
), id_str
) == -1) {
8815 err
= got_error_from_errno("asprintf");
8822 } else if (asprintf(&line
, "%s%s", s
->show_date
? ymd
: "",
8823 got_ref_get_name(re
->ref
)) == -1)
8824 return got_error_from_errno("asprintf");
8826 /* use full line width to determine view->maxx */
8827 err
= format_line(&wline
, &width
, NULL
, line
, 0, INT_MAX
, 0, 0);
8832 view
->maxx
= MAX(view
->maxx
, width
);
8836 err
= format_line(&wline
, &width
, &scrollx
, line
, view
->x
,
8842 if (n
== s
->selected
) {
8844 wstandout(view
->window
);
8845 s
->selected_entry
= re
;
8847 tc
= match_color(&s
->colors
, got_ref_get_name(re
->ref
));
8849 wattr_on(view
->window
,
8850 COLOR_PAIR(tc
->colorpair
), NULL
);
8851 waddwstr(view
->window
, &wline
[scrollx
]);
8853 wattr_off(view
->window
,
8854 COLOR_PAIR(tc
->colorpair
), NULL
);
8855 if (width
< view
->ncols
)
8856 waddch(view
->window
, '\n');
8857 if (n
== s
->selected
&& view
->focussed
)
8858 wstandend(view
->window
);
8864 s
->last_displayed_entry
= re
;
8867 re
= TAILQ_NEXT(re
, entry
);
8874 static const struct got_error
*
8875 browse_ref_tree(struct tog_view
**new_view
, int begin_y
, int begin_x
,
8876 struct tog_reflist_entry
*re
, struct got_repository
*repo
)
8878 const struct got_error
*err
= NULL
;
8879 struct got_object_id
*commit_id
= NULL
;
8880 struct tog_view
*tree_view
;
8884 err
= resolve_reflist_entry(&commit_id
, re
, repo
);
8886 if (err
->code
!= GOT_ERR_OBJ_TYPE
)
8893 tree_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_TREE
);
8894 if (tree_view
== NULL
) {
8895 err
= got_error_from_errno("view_open");
8899 err
= open_tree_view(tree_view
, commit_id
,
8900 got_ref_get_name(re
->ref
), repo
);
8904 *new_view
= tree_view
;
8910 static const struct got_error
*
8911 ref_goto_line(struct tog_view
*view
, int nlines
)
8913 const struct got_error
*err
= NULL
;
8914 struct tog_ref_view_state
*s
= &view
->state
.ref
;
8915 int g
, idx
= s
->selected_entry
->idx
;
8922 else if (g
> s
->nrefs
)
8925 if (g
>= s
->first_displayed_entry
->idx
+ 1 &&
8926 g
<= s
->last_displayed_entry
->idx
+ 1 &&
8927 g
- s
->first_displayed_entry
->idx
- 1 < nlines
) {
8928 s
->selected
= g
- s
->first_displayed_entry
->idx
- 1;
8933 err
= ref_scroll_down(view
, g
- idx
- 1);
8936 if (TAILQ_NEXT(s
->last_displayed_entry
, entry
) == NULL
&&
8937 s
->first_displayed_entry
->idx
+ s
->selected
< g
&&
8938 s
->selected
< s
->ndisplayed
- 1)
8939 s
->selected
= g
- s
->first_displayed_entry
->idx
- 1;
8940 } else if (idx
+ 1 > g
)
8941 ref_scroll_up(s
, idx
- g
+ 1);
8943 if (g
< nlines
&& s
->first_displayed_entry
->idx
== 0)
8944 s
->selected
= g
- 1;
8950 static const struct got_error
*
8951 input_ref_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
8953 const struct got_error
*err
= NULL
;
8954 struct tog_ref_view_state
*s
= &view
->state
.ref
;
8955 struct tog_reflist_entry
*re
;
8956 int n
, nscroll
= view
->nlines
- 1;
8959 return ref_goto_line(view
, nscroll
);
8968 horizontal_scroll_input(view
, ch
);
8971 s
->show_ids
= !s
->show_ids
;
8975 s
->show_date
= !s
->show_date
;
8979 s
->sort_by_date
= !s
->sort_by_date
;
8980 view
->action
= s
->sort_by_date
? "sort by date" : "sort by name";
8982 err
= got_reflist_sort(&tog_refs
, s
->sort_by_date
?
8983 got_ref_cmp_by_commit_timestamp_descending
:
8984 tog_ref_cmp_by_name
, s
->repo
);
8987 got_reflist_object_id_map_free(tog_refs_idmap
);
8988 err
= got_reflist_object_id_map_create(&tog_refs_idmap
,
8989 &tog_refs
, s
->repo
);
8992 ref_view_free_refs(s
);
8993 err
= ref_view_load_refs(s
);
8998 if (!s
->selected_entry
)
9000 err
= view_request_new(new_view
, view
, TOG_VIEW_LOG
);
9004 if (!s
->selected_entry
)
9006 err
= view_request_new(new_view
, view
, TOG_VIEW_TREE
);
9013 s
->first_displayed_entry
= TAILQ_FIRST(&s
->refs
);
9018 int eos
= view
->nlines
- 1;
9020 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
)
9024 re
= TAILQ_LAST(&s
->refs
, tog_reflist_head
);
9025 for (n
= 0; n
< eos
; n
++) {
9028 s
->first_displayed_entry
= re
;
9029 re
= TAILQ_PREV(re
, tog_reflist_head
, entry
);
9032 s
->selected
= n
- 1;
9038 if (s
->selected
> 0) {
9042 ref_scroll_up(s
, 1);
9043 if (s
->selected_entry
== TAILQ_FIRST(&s
->refs
))
9053 if (s
->first_displayed_entry
== TAILQ_FIRST(&s
->refs
))
9054 s
->selected
-= MIN(nscroll
, s
->selected
);
9055 ref_scroll_up(s
, MAX(0, nscroll
));
9056 if (s
->selected_entry
== TAILQ_FIRST(&s
->refs
))
9062 if (s
->selected
< s
->ndisplayed
- 1) {
9066 if (TAILQ_NEXT(s
->last_displayed_entry
, entry
) == NULL
) {
9067 /* can't scroll any further */
9071 ref_scroll_down(view
, 1);
9081 if (TAILQ_NEXT(s
->last_displayed_entry
, entry
) == NULL
) {
9082 /* can't scroll any further; move cursor down */
9083 if (s
->selected
< s
->ndisplayed
- 1)
9084 s
->selected
+= MIN(nscroll
,
9085 s
->ndisplayed
- s
->selected
- 1);
9086 if (view
->count
> 1 && s
->selected
< s
->ndisplayed
- 1)
9087 s
->selected
+= s
->ndisplayed
- s
->selected
- 1;
9091 ref_scroll_down(view
, nscroll
);
9096 err
= tog_load_refs(s
->repo
, s
->sort_by_date
);
9099 ref_view_free_refs(s
);
9100 err
= ref_view_load_refs(s
);
9103 if (view
->nlines
>= 2 && s
->selected
>= view
->nlines
- 1)
9104 s
->selected
= view
->nlines
- 2;
9118 fprintf(stderr
, "usage: %s ref [-r repository-path]\n",
9123 static const struct got_error
*
9124 cmd_ref(int argc
, char *argv
[])
9126 const struct got_error
*error
;
9127 struct got_repository
*repo
= NULL
;
9128 struct got_worktree
*worktree
= NULL
;
9129 char *cwd
= NULL
, *repo_path
= NULL
;
9131 struct tog_view
*view
;
9132 int *pack_fds
= NULL
;
9134 while ((ch
= getopt(argc
, argv
, "r:")) != -1) {
9137 repo_path
= realpath(optarg
, NULL
);
9138 if (repo_path
== NULL
)
9139 return got_error_from_errno2("realpath",
9154 error
= got_repo_pack_fds_open(&pack_fds
);
9158 if (repo_path
== NULL
) {
9159 cwd
= getcwd(NULL
, 0);
9161 return got_error_from_errno("getcwd");
9162 error
= got_worktree_open(&worktree
, cwd
, NULL
);
9163 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
9167 strdup(got_worktree_get_repo_path(worktree
));
9169 repo_path
= strdup(cwd
);
9170 if (repo_path
== NULL
) {
9171 error
= got_error_from_errno("strdup");
9176 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
9182 error
= apply_unveil(got_repo_get_path(repo
), NULL
);
9186 error
= tog_load_refs(repo
, 0);
9190 view
= view_open(0, 0, 0, 0, TOG_VIEW_REF
);
9192 error
= got_error_from_errno("view_open");
9196 error
= open_ref_view(view
, repo
);
9201 error
= set_tog_base_commit(repo
, worktree
);
9205 /* Release work tree lock. */
9206 got_worktree_close(worktree
);
9210 error
= view_loop(view
);
9213 free(tog_base_commit
.id
);
9216 if (worktree
!= NULL
)
9217 got_worktree_close(worktree
);
9219 const struct got_error
*close_err
= got_repo_close(repo
);
9224 const struct got_error
*pack_err
=
9225 got_repo_pack_fds_close(pack_fds
);
9233 static const struct got_error
*
9234 win_draw_center(WINDOW
*win
, size_t y
, size_t x
, size_t maxx
, int focus
,
9243 x
= x
? x
: maxx
> len
? (maxx
- len
) / 2 : 0;
9247 if (mvwprintw(win
, y
, x
, "%s", str
) == ERR
)
9248 return got_error_msg(GOT_ERR_RANGE
, "mvwprintw");
9255 static const struct got_error
*
9256 add_line_offset(off_t
**line_offsets
, size_t *nlines
, off_t off
)
9260 p
= reallocarray(*line_offsets
, *nlines
+ 1, sizeof(off_t
));
9262 free(*line_offsets
);
9263 *line_offsets
= NULL
;
9264 return got_error_from_errno("reallocarray");
9268 (*line_offsets
)[*nlines
] = off
;
9273 static const struct got_error
*
9274 max_key_str(int *ret
, const struct tog_key_map
*km
, size_t n
)
9278 for (;n
> 0; --n
, ++km
) {
9282 if (km
->keys
== NULL
)
9285 t
= t0
= strdup(km
->keys
);
9287 return got_error_from_errno("strdup");
9290 while ((k
= strsep(&t
, " ")) != NULL
)
9291 len
+= strlen(k
) > 1 ? 2 : 0;
9293 *ret
= MAX(*ret
, len
);
9300 * Write keymap section headers, keys, and key info in km to f.
9301 * Save line offset to *off. If terminal has UTF8 encoding enabled,
9302 * wrap control and symbolic keys in guillemets, else use <>.
9304 static const struct got_error
*
9305 format_help_line(off_t
*off
, FILE *f
, const struct tog_key_map
*km
, int width
)
9310 static const char *u8_glyph
[] = {
9311 "\xe2\x80\xb9", /* U+2039 (utf8 <) */
9312 "\xe2\x80\xba" /* U+203A (utf8 >) */
9315 int cs
, s
, first
= 1;
9317 cs
= got_locale_is_utf8();
9319 t
= t0
= strdup(km
->keys
);
9321 return got_error_from_errno("strdup");
9323 len
= strlen(km
->keys
);
9324 while ((k
= strsep(&t
, " ")) != NULL
) {
9325 s
= strlen(k
) > 1; /* control or symbolic key */
9326 n
= fprintf(f
, "%s%s%s%s%s", first
? " " : "",
9327 cs
&& s
? u8_glyph
[0] : s
? "<" : "", k
,
9328 cs
&& s
? u8_glyph
[1] : s
? ">" : "", t
? " " : "");
9331 return got_error_from_errno("fprintf");
9339 n
= fprintf(f
, "%*s%s\n", width
- len
, width
- len
? " " : "", km
->info
);
9341 return got_error_from_errno("fprintf");
9347 static const struct got_error
*
9348 format_help(struct tog_help_view_state
*s
)
9350 const struct got_error
*err
= NULL
;
9352 int i
, max
, n
, show
= s
->all
;
9353 static const struct tog_key_map km
[] = {
9354 #define KEYMAP_(info, type) { NULL, (info), type }
9355 #define KEY_(keys, info) { (keys), (info), TOG_KEYMAP_KEYS }
9361 err
= add_line_offset(&s
->line_offsets
, &s
->nlines
, 0);
9366 err
= max_key_str(&max
, km
, n
);
9370 for (i
= 0; i
< n
; ++i
) {
9371 if (km
[i
].keys
== NULL
) {
9373 if (km
[i
].type
== TOG_KEYMAP_GLOBAL
||
9374 km
[i
].type
== s
->type
|| s
->all
)
9378 err
= format_help_line(&off
, s
->f
, &km
[i
], max
);
9381 err
= add_line_offset(&s
->line_offsets
, &s
->nlines
, off
);
9388 err
= add_line_offset(&s
->line_offsets
, &s
->nlines
, off
);
9392 static const struct got_error
*
9393 create_help(struct tog_help_view_state
*s
)
9396 const struct got_error
*err
;
9398 free(s
->line_offsets
);
9399 s
->line_offsets
= NULL
;
9404 return got_error_from_errno("got_opentemp");
9407 err
= format_help(s
);
9411 if (s
->f
&& fflush(s
->f
) != 0)
9412 return got_error_from_errno("fflush");
9417 static const struct got_error
*
9418 search_start_help_view(struct tog_view
*view
)
9420 view
->state
.help
.matched_line
= 0;
9425 search_setup_help_view(struct tog_view
*view
, FILE **f
, off_t
**line_offsets
,
9426 size_t *nlines
, int **first
, int **last
, int **match
, int **selected
)
9428 struct tog_help_view_state
*s
= &view
->state
.help
;
9431 *nlines
= s
->nlines
;
9432 *line_offsets
= s
->line_offsets
;
9433 *match
= &s
->matched_line
;
9434 *first
= &s
->first_displayed_line
;
9435 *last
= &s
->last_displayed_line
;
9436 *selected
= &s
->selected_line
;
9439 static const struct got_error
*
9440 show_help_view(struct tog_view
*view
)
9442 struct tog_help_view_state
*s
= &view
->state
.help
;
9443 const struct got_error
*err
;
9444 regmatch_t
*regmatch
= &view
->regmatch
;
9449 int width
, nprinted
= 0, rc
= 0;
9450 int eos
= view
->nlines
;
9452 if (view_is_hsplit_top(view
))
9453 --eos
; /* account for border */
9457 werase(view
->window
);
9459 if (view
->gline
> s
->nlines
- 1)
9460 view
->gline
= s
->nlines
- 1;
9462 err
= win_draw_center(view
->window
, 0, 0, view
->ncols
,
9463 view_needs_focus_indication(view
),
9464 "tog help (press q to return to tog)");
9469 waddstr(view
->window
, "\n\n");
9475 while (eos
> 0 && nprinted
< eos
) {
9478 linelen
= getline(&line
, &linesz
, s
->f
);
9479 if (linelen
== -1) {
9482 return got_ferror(s
->f
, GOT_ERR_IO
);
9487 if (++s
->lineno
< s
->first_displayed_line
)
9489 if (view
->gline
&& !gotoline(view
, &s
->lineno
, &nprinted
))
9491 if (s
->lineno
== view
->hiline
)
9494 err
= format_line(&wline
, &width
, NULL
, line
, 0, INT_MAX
, 0,
9500 view
->maxx
= MAX(view
->maxx
, width
);
9505 wattron(view
->window
, attr
);
9506 if (s
->first_displayed_line
+ nprinted
== s
->matched_line
&&
9507 regmatch
->rm_so
>= 0 && regmatch
->rm_so
< regmatch
->rm_eo
) {
9508 err
= add_matched_line(&width
, line
, view
->ncols
- 1, 0,
9509 view
->window
, view
->x
, regmatch
);
9517 err
= format_line(&wline
, &width
, &skip
, line
,
9518 view
->x
, view
->ncols
, 0, view
->x
? 1 : 0);
9523 waddwstr(view
->window
, &wline
[skip
]);
9527 if (s
->lineno
== view
->hiline
) {
9528 while (width
++ < view
->ncols
)
9529 waddch(view
->window
, ' ');
9531 if (width
< view
->ncols
)
9532 waddch(view
->window
, '\n');
9535 wattroff(view
->window
, attr
);
9536 if (++nprinted
== 1)
9537 s
->first_displayed_line
= s
->lineno
;
9541 s
->last_displayed_line
= s
->first_displayed_line
+ nprinted
- 1;
9543 s
->last_displayed_line
= s
->first_displayed_line
;
9548 rc
= waddnstr(view
->window
,
9549 "See the tog(1) manual page for full documentation",
9552 return got_error_msg(GOT_ERR_RANGE
, "waddnstr");
9554 wmove(view
->window
, view
->nlines
- 1, 0);
9555 wclrtoeol(view
->window
);
9556 wstandout(view
->window
);
9557 rc
= waddnstr(view
->window
, "scroll down for more...",
9560 return got_error_msg(GOT_ERR_RANGE
, "waddnstr");
9561 if (getcurx(view
->window
) < view
->ncols
- 6) {
9562 rc
= wprintw(view
->window
, "[%.0f%%]",
9563 100.00 * s
->last_displayed_line
/ s
->nlines
);
9565 return got_error_msg(GOT_ERR_IO
, "wprintw");
9567 wstandend(view
->window
);
9573 static const struct got_error
*
9574 input_help_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
9576 struct tog_help_view_state
*s
= &view
->state
.help
;
9577 const struct got_error
*err
= NULL
;
9583 eos
= nscroll
= view
->nlines
;
9584 if (view_is_hsplit_top(view
))
9587 s
->lineno
= s
->first_displayed_line
- 1 + s
->selected_line
;
9596 horizontal_scroll_input(view
, ch
);
9600 s
->first_displayed_line
= 1;
9608 s
->first_displayed_line
= (s
->nlines
- eos
) + 3;
9613 if (s
->first_displayed_line
> 1)
9614 --s
->first_displayed_line
;
9625 if (s
->first_displayed_line
== 1) {
9629 while (--nscroll
> 0 && s
->first_displayed_line
> 1)
9630 s
->first_displayed_line
--;
9636 ++s
->first_displayed_line
;
9652 while (!s
->eof
&& --nscroll
> 0) {
9653 linelen
= getline(&line
, &linesz
, s
->f
);
9654 s
->first_displayed_line
++;
9655 if (linelen
== -1) {
9659 err
= got_ferror(s
->f
, GOT_ERR_IO
);
9673 static const struct got_error
*
9674 close_help_view(struct tog_view
*view
)
9676 struct tog_help_view_state
*s
= &view
->state
.help
;
9678 free(s
->line_offsets
);
9679 s
->line_offsets
= NULL
;
9680 if (fclose(s
->f
) == EOF
)
9681 return got_error_from_errno("fclose");
9686 static const struct got_error
*
9687 reset_help_view(struct tog_view
*view
)
9689 struct tog_help_view_state
*s
= &view
->state
.help
;
9692 if (s
->f
&& fclose(s
->f
) == EOF
)
9693 return got_error_from_errno("fclose");
9695 wclear(view
->window
);
9699 s
->first_displayed_line
= 1;
9700 s
->last_displayed_line
= view
->nlines
;
9701 s
->matched_line
= 0;
9703 return create_help(s
);
9706 static const struct got_error
*
9707 open_help_view(struct tog_view
*view
, struct tog_view
*parent
)
9709 const struct got_error
*err
= NULL
;
9710 struct tog_help_view_state
*s
= &view
->state
.help
;
9712 s
->type
= (enum tog_keymap_type
)parent
->type
;
9713 s
->first_displayed_line
= 1;
9714 s
->last_displayed_line
= view
->nlines
;
9715 s
->selected_line
= 1;
9717 view
->show
= show_help_view
;
9718 view
->input
= input_help_view
;
9719 view
->reset
= reset_help_view
;
9720 view
->close
= close_help_view
;
9721 view
->search_start
= search_start_help_view
;
9722 view
->search_setup
= search_setup_help_view
;
9723 view
->search_next
= search_next_view_match
;
9725 err
= create_help(s
);
9729 static const struct got_error
*
9730 view_dispatch_request(struct tog_view
**new_view
, struct tog_view
*view
,
9731 enum tog_view_type request
, int y
, int x
)
9733 const struct got_error
*err
= NULL
;
9739 if (view
->type
== TOG_VIEW_LOG
) {
9740 struct tog_log_view_state
*s
= &view
->state
.log
;
9742 err
= open_diff_view_for_commit(new_view
, y
, x
,
9743 s
->selected_entry
->commit
, s
->selected_entry
->id
,
9746 return got_error_msg(GOT_ERR_NOT_IMPL
,
9747 "parent/child view pair not supported");
9749 case TOG_VIEW_BLAME
:
9750 if (view
->type
== TOG_VIEW_TREE
) {
9751 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9753 err
= blame_tree_entry(new_view
, y
, x
,
9754 s
->selected_entry
, &s
->parents
, s
->commit_id
,
9757 return got_error_msg(GOT_ERR_NOT_IMPL
,
9758 "parent/child view pair not supported");
9761 if (view
->type
== TOG_VIEW_BLAME
)
9762 err
= log_annotated_line(new_view
, y
, x
,
9763 view
->state
.blame
.repo
, view
->state
.blame
.id_to_log
);
9764 else if (view
->type
== TOG_VIEW_TREE
)
9765 err
= log_selected_tree_entry(new_view
, y
, x
,
9767 else if (view
->type
== TOG_VIEW_REF
)
9768 err
= log_ref_entry(new_view
, y
, x
,
9769 view
->state
.ref
.selected_entry
,
9770 view
->state
.ref
.repo
);
9772 return got_error_msg(GOT_ERR_NOT_IMPL
,
9773 "parent/child view pair not supported");
9776 if (view
->type
== TOG_VIEW_LOG
)
9777 err
= browse_commit_tree(new_view
, y
, x
,
9778 view
->state
.log
.selected_entry
,
9779 view
->state
.log
.in_repo_path
,
9780 view
->state
.log
.head_ref_name
,
9781 view
->state
.log
.repo
);
9782 else if (view
->type
== TOG_VIEW_REF
)
9783 err
= browse_ref_tree(new_view
, y
, x
,
9784 view
->state
.ref
.selected_entry
,
9785 view
->state
.ref
.repo
);
9787 return got_error_msg(GOT_ERR_NOT_IMPL
,
9788 "parent/child view pair not supported");
9791 *new_view
= view_open(0, 0, y
, x
, TOG_VIEW_REF
);
9792 if (*new_view
== NULL
)
9793 return got_error_from_errno("view_open");
9794 if (view
->type
== TOG_VIEW_LOG
)
9795 err
= open_ref_view(*new_view
, view
->state
.log
.repo
);
9796 else if (view
->type
== TOG_VIEW_TREE
)
9797 err
= open_ref_view(*new_view
, view
->state
.tree
.repo
);
9799 err
= got_error_msg(GOT_ERR_NOT_IMPL
,
9800 "parent/child view pair not supported");
9802 view_close(*new_view
);
9805 *new_view
= view_open(0, 0, 0, 0, TOG_VIEW_HELP
);
9806 if (*new_view
== NULL
)
9807 return got_error_from_errno("view_open");
9808 err
= open_help_view(*new_view
, view
);
9810 view_close(*new_view
);
9813 return got_error_msg(GOT_ERR_NOT_IMPL
, "invalid view");
9820 * If view was scrolled down to move the selected line into view when opening a
9821 * horizontal split, scroll back up when closing the split/toggling fullscreen.
9824 offset_selection_up(struct tog_view
*view
)
9826 switch (view
->type
) {
9827 case TOG_VIEW_BLAME
: {
9828 struct tog_blame_view_state
*s
= &view
->state
.blame
;
9829 if (s
->first_displayed_line
== 1) {
9830 s
->selected_line
= MAX(s
->selected_line
- view
->offset
,
9834 if (s
->first_displayed_line
> view
->offset
)
9835 s
->first_displayed_line
-= view
->offset
;
9837 s
->first_displayed_line
= 1;
9838 s
->selected_line
+= view
->offset
;
9842 log_scroll_up(&view
->state
.log
, view
->offset
);
9843 view
->state
.log
.selected
+= view
->offset
;
9846 ref_scroll_up(&view
->state
.ref
, view
->offset
);
9847 view
->state
.ref
.selected
+= view
->offset
;
9850 tree_scroll_up(&view
->state
.tree
, view
->offset
);
9851 view
->state
.tree
.selected
+= view
->offset
;
9861 * If the selected line is in the section of screen covered by the bottom split,
9862 * scroll down offset lines to move it into view and index its new position.
9864 static const struct got_error
*
9865 offset_selection_down(struct tog_view
*view
)
9867 const struct got_error
*err
= NULL
;
9868 const struct got_error
*(*scrolld
)(struct tog_view
*, int);
9869 int *selected
= NULL
;
9872 switch (view
->type
) {
9873 case TOG_VIEW_BLAME
: {
9874 struct tog_blame_view_state
*s
= &view
->state
.blame
;
9877 if (s
->selected_line
> view
->nlines
- header
) {
9878 offset
= abs(view
->nlines
- s
->selected_line
- header
);
9879 s
->first_displayed_line
+= offset
;
9880 s
->selected_line
-= offset
;
9881 view
->offset
= offset
;
9885 case TOG_VIEW_LOG
: {
9886 struct tog_log_view_state
*s
= &view
->state
.log
;
9887 scrolld
= &log_scroll_down
;
9888 header
= view_is_parent_view(view
) ? 3 : 2;
9889 selected
= &s
->selected
;
9892 case TOG_VIEW_REF
: {
9893 struct tog_ref_view_state
*s
= &view
->state
.ref
;
9894 scrolld
= &ref_scroll_down
;
9896 selected
= &s
->selected
;
9899 case TOG_VIEW_TREE
: {
9900 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9901 scrolld
= &tree_scroll_down
;
9903 selected
= &s
->selected
;
9913 if (selected
&& *selected
> view
->nlines
- header
) {
9914 offset
= abs(view
->nlines
- *selected
- header
);
9915 view
->offset
= offset
;
9916 if (scrolld
&& offset
) {
9917 err
= scrolld(view
, offset
);
9918 *selected
-= offset
;
9926 list_commands(FILE *fp
)
9930 fprintf(fp
, "commands:");
9931 for (i
= 0; i
< nitems(tog_commands
); i
++) {
9932 const struct tog_cmd
*cmd
= &tog_commands
[i
];
9933 fprintf(fp
, " %s", cmd
->name
);
9939 usage(int hflag
, int status
)
9941 FILE *fp
= (status
== 0) ? stdout
: stderr
;
9943 fprintf(fp
, "usage: %s [-hV] command [arg ...]\n",
9946 fprintf(fp
, "lazy usage: %s path\n", getprogname());
9953 make_argv(int argc
, ...)
9961 argv
= calloc(argc
, sizeof(char *));
9964 for (i
= 0; i
< argc
; i
++) {
9965 argv
[i
] = strdup(va_arg(ap
, char *));
9966 if (argv
[i
] == NULL
)
9975 * Try to convert 'tog path' into a 'tog log path' command.
9976 * The user could simply have mistyped the command rather than knowingly
9977 * provided a path. So check whether argv[0] can in fact be resolved
9978 * to a path in the HEAD commit and print a special error if not.
9979 * This hack is for mpi@ <3
9981 static const struct got_error
*
9982 tog_log_with_path(int argc
, char *argv
[])
9984 const struct got_error
*error
= NULL
, *close_err
;
9985 const struct tog_cmd
*cmd
= NULL
;
9986 struct got_repository
*repo
= NULL
;
9987 struct got_worktree
*worktree
= NULL
;
9988 struct got_object_id
*commit_id
= NULL
, *id
= NULL
;
9989 struct got_commit_object
*commit
= NULL
;
9990 char *cwd
= NULL
, *repo_path
= NULL
, *in_repo_path
= NULL
;
9991 char *commit_id_str
= NULL
, **cmd_argv
= NULL
;
9992 int *pack_fds
= NULL
;
9994 cwd
= getcwd(NULL
, 0);
9996 return got_error_from_errno("getcwd");
9998 error
= got_repo_pack_fds_open(&pack_fds
);
10002 error
= got_worktree_open(&worktree
, cwd
, NULL
);
10003 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
10007 repo_path
= strdup(got_worktree_get_repo_path(worktree
));
10009 repo_path
= strdup(cwd
);
10010 if (repo_path
== NULL
) {
10011 error
= got_error_from_errno("strdup");
10015 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
10019 error
= get_in_repo_path_from_argv0(&in_repo_path
, argc
, argv
,
10024 error
= tog_load_refs(repo
, 0);
10027 error
= got_repo_match_object_id(&commit_id
, NULL
, worktree
?
10028 got_worktree_get_head_ref_name(worktree
) : GOT_REF_HEAD
,
10029 GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
10034 got_worktree_close(worktree
);
10038 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
10042 error
= got_object_id_by_path(&id
, repo
, commit
, in_repo_path
);
10044 if (error
->code
!= GOT_ERR_NO_TREE_ENTRY
)
10046 fprintf(stderr
, "%s: '%s' is no known command or path\n",
10047 getprogname(), argv
[0]);
10052 error
= got_object_id_str(&commit_id_str
, commit_id
);
10056 cmd
= &tog_commands
[0]; /* log */
10058 cmd_argv
= make_argv(argc
, cmd
->name
, "-c", commit_id_str
, argv
[0]);
10059 error
= cmd
->cmd_main(argc
, cmd_argv
);
10062 close_err
= got_repo_close(repo
);
10067 got_object_commit_close(commit
);
10069 got_worktree_close(worktree
);
10071 const struct got_error
*pack_err
=
10072 got_repo_pack_fds_close(pack_fds
);
10077 free(commit_id_str
);
10081 free(in_repo_path
);
10084 for (i
= 0; i
< argc
; i
++)
10093 main(int argc
, char *argv
[])
10095 const struct got_error
*io_err
, *error
= NULL
;
10096 const struct tog_cmd
*cmd
= NULL
;
10097 int ch
, hflag
= 0, Vflag
= 0;
10098 char **cmd_argv
= NULL
;
10099 static const struct option longopts
[] = {
10100 { "version", no_argument
, NULL
, 'V' },
10101 { NULL
, 0, NULL
, 0}
10103 char *diff_algo_str
= NULL
;
10104 const char *test_script_path
;
10106 setlocale(LC_CTYPE
, "");
10109 * Override default signal handlers before starting ncurses.
10110 * This should prevent ncurses from installing its own
10111 * broken cleanup() signal handler.
10113 signal(SIGWINCH
, tog_sigwinch
);
10114 signal(SIGPIPE
, tog_sigpipe
);
10115 signal(SIGCONT
, tog_sigcont
);
10116 signal(SIGINT
, tog_sigint
);
10117 signal(SIGTERM
, tog_sigterm
);
10120 * Test mode init must happen before pledge() because "tty" will
10121 * not allow TTY-related ioctls to occur via regular files.
10123 test_script_path
= getenv("TOG_TEST_SCRIPT");
10124 if (test_script_path
!= NULL
) {
10125 error
= init_mock_term(test_script_path
);
10127 fprintf(stderr
, "%s: %s\n", getprogname(), error
->msg
);
10130 } else if (!isatty(STDIN_FILENO
))
10131 errx(1, "standard input is not a tty");
10133 #if !defined(PROFILE)
10134 if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd unveil",
10139 while ((ch
= getopt_long(argc
, argv
, "+hV", longopts
, NULL
)) != -1) {
10159 got_version_print_str();
10166 /* Build an argument vector which runs a default command. */
10167 cmd
= &tog_commands
[0];
10169 cmd_argv
= make_argv(argc
, cmd
->name
);
10173 /* Did the user specify a command? */
10174 for (i
= 0; i
< nitems(tog_commands
); i
++) {
10175 if (strncmp(tog_commands
[i
].name
, argv
[0],
10176 strlen(argv
[0])) == 0) {
10177 cmd
= &tog_commands
[i
];
10183 diff_algo_str
= getenv("TOG_DIFF_ALGORITHM");
10184 if (diff_algo_str
) {
10185 if (strcasecmp(diff_algo_str
, "patience") == 0)
10186 tog_diff_algo
= GOT_DIFF_ALGORITHM_PATIENCE
;
10187 if (strcasecmp(diff_algo_str
, "myers") == 0)
10188 tog_diff_algo
= GOT_DIFF_ALGORITHM_MYERS
;
10191 tog_base_commit
.idx
= -1;
10192 tog_base_commit
.marker
= GOT_WORKTREE_STATE_UNKNOWN
;
10197 /* No command specified; try log with a path */
10198 error
= tog_log_with_path(argc
, argv
);
10203 error
= cmd
->cmd_main(argc
, cmd_argv
? cmd_argv
: argv
);
10206 if (using_mock_io
) {
10207 io_err
= tog_io_close();
10214 for (i
= 0; i
< argc
; i
++)
10219 if (error
&& error
->code
!= GOT_ERR_CANCELLED
&&
10220 error
->code
!= GOT_ERR_EOF
&&
10221 error
->code
!= GOT_ERR_PRIVSEP_EXIT
&&
10222 error
->code
!= GOT_ERR_PRIVSEP_PIPE
&&
10223 !(error
->code
== GOT_ERR_ERRNO
&& errno
== EINTR
)) {
10224 fprintf(stderr
, "%s: %s\n", getprogname(), error
->msg
);