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 <sys/queue.h>
19 #include <sys/ioctl.h>
23 #if defined(__FreeBSD__) || defined(__APPLE__)
24 #define _XOPEN_SOURCE_EXTENDED /* for ncurses wide-character functions */
45 #include "got_compat.h"
47 #include "got_version.h"
48 #include "got_error.h"
49 #include "got_object.h"
50 #include "got_reference.h"
51 #include "got_repository.h"
53 #include "got_opentemp.h"
55 #include "got_cancel.h"
56 #include "got_commit_graph.h"
57 #include "got_blame.h"
58 #include "got_privsep.h"
60 #include "got_worktree.h"
63 #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b))
67 #define MAX(_a,_b) ((_a) > (_b) ? (_a) : (_b))
71 #define CTRL(x) ((x) & 0x1f)
75 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
80 const struct got_error
*(*cmd_main
)(int, char *[]);
81 void (*cmd_usage
)(void);
84 __dead
static void usage(int, int);
85 __dead
static void usage_log(void);
86 __dead
static void usage_diff(void);
87 __dead
static void usage_blame(void);
88 __dead
static void usage_tree(void);
89 __dead
static void usage_ref(void);
91 static const struct got_error
* cmd_log(int, char *[]);
92 static const struct got_error
* cmd_diff(int, char *[]);
93 static const struct got_error
* cmd_blame(int, char *[]);
94 static const struct got_error
* cmd_tree(int, char *[]);
95 static const struct got_error
* cmd_ref(int, char *[]);
97 static const struct tog_cmd tog_commands
[] = {
98 { "log", cmd_log
, usage_log
},
99 { "diff", cmd_diff
, usage_diff
},
100 { "blame", cmd_blame
, usage_blame
},
101 { "tree", cmd_tree
, usage_tree
},
102 { "ref", cmd_ref
, usage_ref
},
114 /* Match _DIFF to _HELP with enum tog_view_type TOG_VIEW_* counterparts. */
115 enum tog_keymap_type
{
116 TOG_KEYMAP_KEYS
= -2,
132 #define HSPLIT_SCALE 0.3 /* default horizontal split scale */
134 #define TOG_EOF_STRING "(END)"
136 struct commit_queue_entry
{
137 TAILQ_ENTRY(commit_queue_entry
) entry
;
138 struct got_object_id
*id
;
139 struct got_commit_object
*commit
;
142 TAILQ_HEAD(commit_queue_head
, commit_queue_entry
);
143 struct commit_queue
{
145 struct commit_queue_head head
;
149 STAILQ_ENTRY(tog_color
) entry
;
153 STAILQ_HEAD(tog_colors
, tog_color
);
155 static struct got_reflist_head tog_refs
= TAILQ_HEAD_INITIALIZER(tog_refs
);
156 static struct got_reflist_object_id_map
*tog_refs_idmap
;
157 static enum got_diff_algorithm tog_diff_algo
= GOT_DIFF_ALGORITHM_MYERS
;
159 static const struct got_error
*
160 tog_ref_cmp_by_name(void *arg
, int *cmp
, struct got_reference
*re1
,
161 struct got_reference
* re2
)
163 const char *name1
= got_ref_get_name(re1
);
164 const char *name2
= got_ref_get_name(re2
);
165 int isbackup1
, isbackup2
;
167 /* Sort backup refs towards the bottom of the list. */
168 isbackup1
= strncmp(name1
, "refs/got/backup/", 16) == 0;
169 isbackup2
= strncmp(name2
, "refs/got/backup/", 16) == 0;
170 if (!isbackup1
&& isbackup2
) {
173 } else if (isbackup1
&& !isbackup2
) {
178 *cmp
= got_path_cmp(name1
, name2
, strlen(name1
), strlen(name2
));
182 static const struct got_error
*
183 tog_load_refs(struct got_repository
*repo
, int sort_by_date
)
185 const struct got_error
*err
;
187 err
= got_ref_list(&tog_refs
, repo
, NULL
, sort_by_date
?
188 got_ref_cmp_by_commit_timestamp_descending
: tog_ref_cmp_by_name
,
193 return got_reflist_object_id_map_create(&tog_refs_idmap
, &tog_refs
,
200 if (tog_refs_idmap
) {
201 got_reflist_object_id_map_free(tog_refs_idmap
);
202 tog_refs_idmap
= NULL
;
204 got_ref_list_free(&tog_refs
);
207 static const struct got_error
*
208 add_color(struct tog_colors
*colors
, const char *pattern
,
209 int idx
, short color
)
211 const struct got_error
*err
= NULL
;
212 struct tog_color
*tc
;
215 if (idx
< 1 || idx
> COLOR_PAIRS
- 1)
218 init_pair(idx
, color
, -1);
220 tc
= calloc(1, sizeof(*tc
));
222 return got_error_from_errno("calloc");
223 regerr
= regcomp(&tc
->regex
, pattern
,
224 REG_EXTENDED
| REG_NOSUB
| REG_NEWLINE
);
226 static char regerr_msg
[512];
227 static char err_msg
[512];
228 regerror(regerr
, &tc
->regex
, regerr_msg
,
230 snprintf(err_msg
, sizeof(err_msg
), "regcomp: %s",
232 err
= got_error_msg(GOT_ERR_REGEX
, err_msg
);
237 STAILQ_INSERT_HEAD(colors
, tc
, entry
);
242 free_colors(struct tog_colors
*colors
)
244 struct tog_color
*tc
;
246 while (!STAILQ_EMPTY(colors
)) {
247 tc
= STAILQ_FIRST(colors
);
248 STAILQ_REMOVE_HEAD(colors
, entry
);
254 static struct tog_color
*
255 get_color(struct tog_colors
*colors
, int colorpair
)
257 struct tog_color
*tc
= NULL
;
259 STAILQ_FOREACH(tc
, colors
, entry
) {
260 if (tc
->colorpair
== colorpair
)
268 default_color_value(const char *envvar
)
270 if (strcmp(envvar
, "TOG_COLOR_DIFF_MINUS") == 0)
271 return COLOR_MAGENTA
;
272 if (strcmp(envvar
, "TOG_COLOR_DIFF_PLUS") == 0)
274 if (strcmp(envvar
, "TOG_COLOR_DIFF_CHUNK_HEADER") == 0)
276 if (strcmp(envvar
, "TOG_COLOR_DIFF_META") == 0)
278 if (strcmp(envvar
, "TOG_COLOR_TREE_SUBMODULE") == 0)
279 return COLOR_MAGENTA
;
280 if (strcmp(envvar
, "TOG_COLOR_TREE_SYMLINK") == 0)
281 return COLOR_MAGENTA
;
282 if (strcmp(envvar
, "TOG_COLOR_TREE_DIRECTORY") == 0)
284 if (strcmp(envvar
, "TOG_COLOR_TREE_EXECUTABLE") == 0)
286 if (strcmp(envvar
, "TOG_COLOR_COMMIT") == 0)
288 if (strcmp(envvar
, "TOG_COLOR_AUTHOR") == 0)
290 if (strcmp(envvar
, "TOG_COLOR_DATE") == 0)
292 if (strcmp(envvar
, "TOG_COLOR_REFS_HEADS") == 0)
294 if (strcmp(envvar
, "TOG_COLOR_REFS_TAGS") == 0)
295 return COLOR_MAGENTA
;
296 if (strcmp(envvar
, "TOG_COLOR_REFS_REMOTES") == 0)
298 if (strcmp(envvar
, "TOG_COLOR_REFS_BACKUP") == 0)
305 get_color_value(const char *envvar
)
307 const char *val
= getenv(envvar
);
310 return default_color_value(envvar
);
312 if (strcasecmp(val
, "black") == 0)
314 if (strcasecmp(val
, "red") == 0)
316 if (strcasecmp(val
, "green") == 0)
318 if (strcasecmp(val
, "yellow") == 0)
320 if (strcasecmp(val
, "blue") == 0)
322 if (strcasecmp(val
, "magenta") == 0)
323 return COLOR_MAGENTA
;
324 if (strcasecmp(val
, "cyan") == 0)
326 if (strcasecmp(val
, "white") == 0)
328 if (strcasecmp(val
, "default") == 0)
331 return default_color_value(envvar
);
334 struct tog_diff_view_state
{
335 struct got_object_id
*id1
, *id2
;
336 const char *label1
, *label2
;
340 int first_displayed_line
;
341 int last_displayed_line
;
344 int ignore_whitespace
;
346 struct got_repository
*repo
;
347 struct got_diff_line
*lines
;
352 /* passed from log or blame view; may be NULL */
353 struct tog_view
*parent_view
;
356 pthread_mutex_t tog_mutex
= PTHREAD_MUTEX_INITIALIZER
;
357 static volatile sig_atomic_t tog_thread_error
;
359 struct tog_log_thread_args
{
360 pthread_cond_t need_commits
;
361 pthread_cond_t commit_loaded
;
364 struct got_commit_graph
*graph
;
365 struct commit_queue
*real_commits
;
366 const char *in_repo_path
;
367 struct got_object_id
*start_id
;
368 struct got_repository
*repo
;
372 struct commit_queue_entry
**first_displayed_entry
;
373 struct commit_queue_entry
**selected_entry
;
375 int *search_next_done
;
379 regex_t
*limit_regex
;
380 struct commit_queue
*limit_commits
;
383 struct tog_log_view_state
{
384 struct commit_queue
*commits
;
385 struct commit_queue_entry
*first_displayed_entry
;
386 struct commit_queue_entry
*last_displayed_entry
;
387 struct commit_queue_entry
*selected_entry
;
388 struct commit_queue real_commits
;
393 struct got_repository
*repo
;
394 struct got_object_id
*start_id
;
397 struct tog_log_thread_args thread_args
;
398 struct commit_queue_entry
*matched_entry
;
399 struct commit_queue_entry
*search_entry
;
400 struct tog_colors colors
;
404 struct commit_queue limit_commits
;
407 #define TOG_COLOR_DIFF_MINUS 1
408 #define TOG_COLOR_DIFF_PLUS 2
409 #define TOG_COLOR_DIFF_CHUNK_HEADER 3
410 #define TOG_COLOR_DIFF_META 4
411 #define TOG_COLOR_TREE_SUBMODULE 5
412 #define TOG_COLOR_TREE_SYMLINK 6
413 #define TOG_COLOR_TREE_DIRECTORY 7
414 #define TOG_COLOR_TREE_EXECUTABLE 8
415 #define TOG_COLOR_COMMIT 9
416 #define TOG_COLOR_AUTHOR 10
417 #define TOG_COLOR_DATE 11
418 #define TOG_COLOR_REFS_HEADS 12
419 #define TOG_COLOR_REFS_TAGS 13
420 #define TOG_COLOR_REFS_REMOTES 14
421 #define TOG_COLOR_REFS_BACKUP 15
423 struct tog_blame_cb_args
{
424 struct tog_blame_line
*lines
; /* one per line */
427 struct tog_view
*view
;
428 struct got_object_id
*commit_id
;
432 struct tog_blame_thread_args
{
434 struct got_repository
*repo
;
435 struct tog_blame_cb_args
*cb_args
;
437 got_cancel_cb cancel_cb
;
444 struct tog_blame_line
*lines
;
448 struct tog_blame_thread_args thread_args
;
449 struct tog_blame_cb_args cb_args
;
454 struct tog_blame_view_state
{
455 int first_displayed_line
;
456 int last_displayed_line
;
458 int last_diffed_line
;
462 struct got_object_id_queue blamed_commits
;
463 struct got_object_qid
*blamed_commit
;
465 struct got_repository
*repo
;
466 struct got_object_id
*commit_id
;
467 struct got_object_id
*id_to_log
;
468 struct tog_blame blame
;
470 struct tog_colors colors
;
473 struct tog_parent_tree
{
474 TAILQ_ENTRY(tog_parent_tree
) entry
;
475 struct got_tree_object
*tree
;
476 struct got_tree_entry
*first_displayed_entry
;
477 struct got_tree_entry
*selected_entry
;
481 TAILQ_HEAD(tog_parent_trees
, tog_parent_tree
);
483 struct tog_tree_view_state
{
485 struct got_object_id
*commit_id
;/* commit which this tree belongs to */
486 struct got_tree_object
*root
; /* the commit's root tree entry */
487 struct got_tree_object
*tree
; /* currently displayed (sub-)tree */
488 struct got_tree_entry
*first_displayed_entry
;
489 struct got_tree_entry
*last_displayed_entry
;
490 struct got_tree_entry
*selected_entry
;
491 int ndisplayed
, selected
, show_ids
;
492 struct tog_parent_trees parents
; /* parent trees of current sub-tree */
494 struct got_repository
*repo
;
495 struct got_tree_entry
*matched_entry
;
496 struct tog_colors colors
;
499 struct tog_reflist_entry
{
500 TAILQ_ENTRY(tog_reflist_entry
) entry
;
501 struct got_reference
*ref
;
505 TAILQ_HEAD(tog_reflist_head
, tog_reflist_entry
);
507 struct tog_ref_view_state
{
508 struct tog_reflist_head refs
;
509 struct tog_reflist_entry
*first_displayed_entry
;
510 struct tog_reflist_entry
*last_displayed_entry
;
511 struct tog_reflist_entry
*selected_entry
;
512 int nrefs
, ndisplayed
, selected
, show_date
, show_ids
, sort_by_date
;
513 struct got_repository
*repo
;
514 struct tog_reflist_entry
*matched_entry
;
515 struct tog_colors colors
;
518 struct tog_help_view_state
{
523 int first_displayed_line
;
524 int last_displayed_line
;
529 enum tog_keymap_type type
;
532 #define GENERATE_HELP \
533 KEYMAP_("Global", TOG_KEYMAP_GLOBAL), \
534 KEY_("H F1", "Open view-specific help (double tap for all help)"), \
535 KEY_("k C-p Up", "Move cursor or page up one line"), \
536 KEY_("j C-n Down", "Move cursor or page down one line"), \
537 KEY_("C-b b PgUp", "Scroll the view up one page"), \
538 KEY_("C-f f PgDn Space", "Scroll the view down one page"), \
539 KEY_("C-u u", "Scroll the view up one half page"), \
540 KEY_("C-d d", "Scroll the view down one half page"), \
541 KEY_("g Home", "Go to line N (default: first line)"), \
542 KEY_("G End", "Go to line N (default: last line)"), \
543 KEY_("l Right", "Scroll the view right"), \
544 KEY_("h Left", "Scroll the view left"), \
545 KEY_("$", "Scroll view to the rightmost position"), \
546 KEY_("0", "Scroll view to the leftmost position"), \
547 KEY_("-", "Decrease size of the focussed split"), \
548 KEY_("+", "Increase size of the focussed split"), \
549 KEY_("Tab", "Switch focus between views"), \
550 KEY_("F", "Toggle fullscreen mode"), \
551 KEY_("/", "Open prompt to enter search term"), \
552 KEY_("n", "Find next line/token matching the current search term"), \
553 KEY_("N", "Find previous line/token matching the current search term"),\
554 KEY_("q", "Quit the focussed view; Quit help screen"), \
555 KEY_("Q", "Quit tog"), \
557 KEYMAP_("Log view", TOG_KEYMAP_LOG), \
558 KEY_("< ,", "Move cursor up one commit"), \
559 KEY_("> .", "Move cursor down one commit"), \
560 KEY_("Enter", "Open diff view of the selected commit"), \
561 KEY_("B", "Reload the log view and toggle display of merged commits"), \
562 KEY_("R", "Open ref view of all repository references"), \
563 KEY_("T", "Display tree view of the repository from the selected" \
565 KEY_("@", "Toggle between displaying author and committer name"), \
566 KEY_("&", "Open prompt to enter term to limit commits displayed"), \
567 KEY_("C-g Backspace", "Cancel current search or log operation"), \
568 KEY_("C-l", "Reload the log view with new commits in the repository"), \
570 KEYMAP_("Diff view", TOG_KEYMAP_DIFF), \
571 KEY_("K < ,", "Display diff of next line in the file/log entry"), \
572 KEY_("J > .", "Display diff of previous line in the file/log entry"), \
573 KEY_("A", "Toggle between Myers and Patience diff algorithm"), \
574 KEY_("a", "Toggle treatment of file as ASCII irrespective of binary" \
576 KEY_("(", "Go to the previous file in the diff"), \
577 KEY_(")", "Go to the next file in the diff"), \
578 KEY_("{", "Go to the previous hunk in the diff"), \
579 KEY_("}", "Go to the next hunk in the diff"), \
580 KEY_("[", "Decrease the number of context lines"), \
581 KEY_("]", "Increase the number of context lines"), \
582 KEY_("w", "Toggle ignore whitespace-only changes in the diff"), \
584 KEYMAP_("Blame view", TOG_KEYMAP_BLAME), \
585 KEY_("Enter", "Display diff view of the selected line's commit"), \
586 KEY_("A", "Toggle diff algorithm between Myers and Patience"), \
587 KEY_("L", "Open log view for the currently selected annotated line"), \
588 KEY_("C", "Reload view with the previously blamed commit"), \
589 KEY_("c", "Reload view with the version of the file found in the" \
590 " selected line's commit"), \
591 KEY_("p", "Reload view with the version of the file found in the" \
592 " selected line's parent commit"), \
594 KEYMAP_("Tree view", TOG_KEYMAP_TREE), \
595 KEY_("Enter", "Enter selected directory or open blame view of the" \
597 KEY_("L", "Open log view for the selected entry"), \
598 KEY_("R", "Open ref view of all repository references"), \
599 KEY_("i", "Show object IDs for all tree entries"), \
600 KEY_("Backspace", "Return to the parent directory"), \
602 KEYMAP_("Ref view", TOG_KEYMAP_REF), \
603 KEY_("Enter", "Display log view of the selected reference"), \
604 KEY_("T", "Display tree view of the selected reference"), \
605 KEY_("i", "Toggle display of IDs for all non-symbolic references"), \
606 KEY_("m", "Toggle display of last modified date for each reference"), \
607 KEY_("o", "Toggle reference sort order (name -> timestamp)"), \
608 KEY_("C-l", "Reload view with all repository references")
613 enum tog_keymap_type type
;
617 * We implement two types of views: parent views and child views.
619 * The 'Tab' key switches focus between a parent view and its child view.
620 * Child views are shown side-by-side to their parent view, provided
621 * there is enough screen estate.
623 * When a new view is opened from within a parent view, this new view
624 * becomes a child view of the parent view, replacing any existing child.
626 * When a new view is opened from within a child view, this new view
627 * becomes a parent view which will obscure the views below until the
628 * user quits the new parent view by typing 'q'.
630 * This list of views contains parent views only.
631 * Child views are only pointed to by their parent view.
633 TAILQ_HEAD(tog_view_list_head
, tog_view
);
636 TAILQ_ENTRY(tog_view
) entry
;
639 int nlines
, ncols
, begin_y
, begin_x
; /* based on split height/width */
640 int resized_y
, resized_x
; /* begin_y/x based on user resizing */
641 int maxx
, x
; /* max column and current start column */
642 int lines
, cols
; /* copies of LINES and COLS */
643 int nscrolled
, offset
; /* lines scrolled and hsplit line offset */
644 int gline
, hiline
; /* navigate to and highlight this nG line */
645 int ch
, count
; /* current keymap and count prefix */
646 int resized
; /* set when in a resize event */
647 int focussed
; /* Only set on one parent or child view at a time. */
649 struct tog_view
*parent
;
650 struct tog_view
*child
;
653 * This flag is initially set on parent views when a new child view
654 * is created. It gets toggled when the 'Tab' key switches focus
655 * between parent and child.
656 * The flag indicates whether focus should be passed on to our child
657 * view if this parent view gets picked for focus after another parent
658 * view was closed. This prevents child views from losing focus in such
663 enum tog_view_mode mode
;
664 /* type-specific state */
665 enum tog_view_type type
;
667 struct tog_diff_view_state diff
;
668 struct tog_log_view_state log
;
669 struct tog_blame_view_state blame
;
670 struct tog_tree_view_state tree
;
671 struct tog_ref_view_state ref
;
672 struct tog_help_view_state help
;
675 const struct got_error
*(*show
)(struct tog_view
*);
676 const struct got_error
*(*input
)(struct tog_view
**,
677 struct tog_view
*, int);
678 const struct got_error
*(*reset
)(struct tog_view
*);
679 const struct got_error
*(*resize
)(struct tog_view
*, int);
680 const struct got_error
*(*close
)(struct tog_view
*);
682 const struct got_error
*(*search_start
)(struct tog_view
*);
683 const struct got_error
*(*search_next
)(struct tog_view
*);
684 void (*search_setup
)(struct tog_view
*, FILE **, off_t
**, size_t *,
685 int **, int **, int **, int **);
688 #define TOG_SEARCH_FORWARD 1
689 #define TOG_SEARCH_BACKWARD 2
690 int search_next_done
;
691 #define TOG_SEARCH_HAVE_MORE 1
692 #define TOG_SEARCH_NO_MORE 2
693 #define TOG_SEARCH_HAVE_NONE 3
698 static const struct got_error
*open_diff_view(struct tog_view
*,
699 struct got_object_id
*, struct got_object_id
*,
700 const char *, const char *, int, int, int, struct tog_view
*,
701 struct got_repository
*);
702 static const struct got_error
*show_diff_view(struct tog_view
*);
703 static const struct got_error
*input_diff_view(struct tog_view
**,
704 struct tog_view
*, int);
705 static const struct got_error
*reset_diff_view(struct tog_view
*);
706 static const struct got_error
* close_diff_view(struct tog_view
*);
707 static const struct got_error
*search_start_diff_view(struct tog_view
*);
708 static void search_setup_diff_view(struct tog_view
*, FILE **, off_t
**,
709 size_t *, int **, int **, int **, int **);
710 static const struct got_error
*search_next_view_match(struct tog_view
*);
712 static const struct got_error
*open_log_view(struct tog_view
*,
713 struct got_object_id
*, struct got_repository
*,
714 const char *, const char *, int);
715 static const struct got_error
* show_log_view(struct tog_view
*);
716 static const struct got_error
*input_log_view(struct tog_view
**,
717 struct tog_view
*, int);
718 static const struct got_error
*resize_log_view(struct tog_view
*, int);
719 static const struct got_error
*close_log_view(struct tog_view
*);
720 static const struct got_error
*search_start_log_view(struct tog_view
*);
721 static const struct got_error
*search_next_log_view(struct tog_view
*);
723 static const struct got_error
*open_blame_view(struct tog_view
*, char *,
724 struct got_object_id
*, struct got_repository
*);
725 static const struct got_error
*show_blame_view(struct tog_view
*);
726 static const struct got_error
*input_blame_view(struct tog_view
**,
727 struct tog_view
*, int);
728 static const struct got_error
*reset_blame_view(struct tog_view
*);
729 static const struct got_error
*close_blame_view(struct tog_view
*);
730 static const struct got_error
*search_start_blame_view(struct tog_view
*);
731 static void search_setup_blame_view(struct tog_view
*, FILE **, off_t
**,
732 size_t *, int **, int **, int **, int **);
734 static const struct got_error
*open_tree_view(struct tog_view
*,
735 struct got_object_id
*, const char *, struct got_repository
*);
736 static const struct got_error
*show_tree_view(struct tog_view
*);
737 static const struct got_error
*input_tree_view(struct tog_view
**,
738 struct tog_view
*, int);
739 static const struct got_error
*close_tree_view(struct tog_view
*);
740 static const struct got_error
*search_start_tree_view(struct tog_view
*);
741 static const struct got_error
*search_next_tree_view(struct tog_view
*);
743 static const struct got_error
*open_ref_view(struct tog_view
*,
744 struct got_repository
*);
745 static const struct got_error
*show_ref_view(struct tog_view
*);
746 static const struct got_error
*input_ref_view(struct tog_view
**,
747 struct tog_view
*, int);
748 static const struct got_error
*close_ref_view(struct tog_view
*);
749 static const struct got_error
*search_start_ref_view(struct tog_view
*);
750 static const struct got_error
*search_next_ref_view(struct tog_view
*);
752 static const struct got_error
*open_help_view(struct tog_view
*,
754 static const struct got_error
*show_help_view(struct tog_view
*);
755 static const struct got_error
*input_help_view(struct tog_view
**,
756 struct tog_view
*, int);
757 static const struct got_error
*reset_help_view(struct tog_view
*);
758 static const struct got_error
* close_help_view(struct tog_view
*);
759 static const struct got_error
*search_start_help_view(struct tog_view
*);
760 static void search_setup_help_view(struct tog_view
*, FILE **, off_t
**,
761 size_t *, int **, int **, int **, int **);
763 static volatile sig_atomic_t tog_sigwinch_received
;
764 static volatile sig_atomic_t tog_sigpipe_received
;
765 static volatile sig_atomic_t tog_sigcont_received
;
766 static volatile sig_atomic_t tog_sigint_received
;
767 static volatile sig_atomic_t tog_sigterm_received
;
770 tog_sigwinch(int signo
)
772 tog_sigwinch_received
= 1;
776 tog_sigpipe(int signo
)
778 tog_sigpipe_received
= 1;
782 tog_sigcont(int signo
)
784 tog_sigcont_received
= 1;
788 tog_sigint(int signo
)
790 tog_sigint_received
= 1;
794 tog_sigterm(int signo
)
796 tog_sigterm_received
= 1;
800 tog_fatal_signal_received(void)
802 return (tog_sigpipe_received
||
803 tog_sigint_received
|| tog_sigterm_received
);
806 static const struct got_error
*
807 view_close(struct tog_view
*view
)
809 const struct got_error
*err
= NULL
, *child_err
= NULL
;
812 child_err
= view_close(view
->child
);
816 err
= view
->close(view
);
818 del_panel(view
->panel
);
820 delwin(view
->window
);
822 return err
? err
: child_err
;
825 static struct tog_view
*
826 view_open(int nlines
, int ncols
, int begin_y
, int begin_x
,
827 enum tog_view_type type
)
829 struct tog_view
*view
= calloc(1, sizeof(*view
));
837 view
->nlines
= nlines
? nlines
: LINES
- begin_y
;
838 view
->ncols
= ncols
? ncols
: COLS
- begin_x
;
839 view
->begin_y
= begin_y
;
840 view
->begin_x
= begin_x
;
841 view
->window
= newwin(nlines
, ncols
, begin_y
, begin_x
);
842 if (view
->window
== NULL
) {
846 view
->panel
= new_panel(view
->window
);
847 if (view
->panel
== NULL
||
848 set_panel_userptr(view
->panel
, view
) != OK
) {
853 keypad(view
->window
, TRUE
);
858 view_split_begin_x(int begin_x
)
860 if (begin_x
> 0 || COLS
< 120)
862 return (COLS
- MAX(COLS
/ 2, 80));
865 /* XXX Stub till we decide what to do. */
867 view_split_begin_y(int lines
)
869 return lines
* HSPLIT_SCALE
;
872 static const struct got_error
*view_resize(struct tog_view
*);
874 static const struct got_error
*
875 view_splitscreen(struct tog_view
*view
)
877 const struct got_error
*err
= NULL
;
879 if (!view
->resized
&& view
->mode
== TOG_VIEW_SPLIT_HRZN
) {
880 if (view
->resized_y
&& view
->resized_y
< view
->lines
)
881 view
->begin_y
= view
->resized_y
;
883 view
->begin_y
= view_split_begin_y(view
->nlines
);
885 } else if (!view
->resized
) {
886 if (view
->resized_x
&& view
->resized_x
< view
->cols
- 1 &&
888 view
->begin_x
= view
->resized_x
;
890 view
->begin_x
= view_split_begin_x(0);
893 view
->nlines
= LINES
- view
->begin_y
;
894 view
->ncols
= COLS
- view
->begin_x
;
897 err
= view_resize(view
);
901 if (view
->parent
&& view
->mode
== TOG_VIEW_SPLIT_HRZN
)
902 view
->parent
->nlines
= view
->begin_y
;
904 if (mvwin(view
->window
, view
->begin_y
, view
->begin_x
) == ERR
)
905 return got_error_from_errno("mvwin");
910 static const struct got_error
*
911 view_fullscreen(struct tog_view
*view
)
913 const struct got_error
*err
= NULL
;
916 view
->begin_y
= view
->resized
? view
->begin_y
: 0;
917 view
->nlines
= view
->resized
? view
->nlines
: LINES
;
921 err
= view_resize(view
);
925 if (mvwin(view
->window
, view
->begin_y
, view
->begin_x
) == ERR
)
926 return got_error_from_errno("mvwin");
932 view_is_parent_view(struct tog_view
*view
)
934 return view
->parent
== NULL
;
938 view_is_splitscreen(struct tog_view
*view
)
940 return view
->begin_x
> 0 || view
->begin_y
> 0;
944 view_is_fullscreen(struct tog_view
*view
)
946 return view
->nlines
== LINES
&& view
->ncols
== COLS
;
950 view_is_hsplit_top(struct tog_view
*view
)
952 return view
->mode
== TOG_VIEW_SPLIT_HRZN
&& view
->child
&&
953 view_is_splitscreen(view
->child
);
957 view_border(struct tog_view
*view
)
960 const struct tog_view
*view_above
;
963 return view_border(view
->parent
);
965 panel
= panel_above(view
->panel
);
969 view_above
= panel_userptr(panel
);
970 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
)
971 mvwhline(view
->window
, view_above
->begin_y
- 1,
972 view
->begin_x
, got_locale_is_utf8() ?
973 ACS_HLINE
: '-', view
->ncols
);
975 mvwvline(view
->window
, view
->begin_y
, view_above
->begin_x
- 1,
976 got_locale_is_utf8() ? ACS_VLINE
: '|', view
->nlines
);
979 static const struct got_error
*view_init_hsplit(struct tog_view
*, int);
980 static const struct got_error
*request_log_commits(struct tog_view
*);
981 static const struct got_error
*offset_selection_down(struct tog_view
*);
982 static void offset_selection_up(struct tog_view
*);
983 static void view_get_split(struct tog_view
*, int *, int *);
985 static const struct got_error
*
986 view_resize(struct tog_view
*view
)
988 const struct got_error
*err
= NULL
;
989 int dif
, nlines
, ncols
;
991 dif
= LINES
- view
->lines
; /* line difference */
993 if (view
->lines
> LINES
)
994 nlines
= view
->nlines
- (view
->lines
- LINES
);
996 nlines
= view
->nlines
+ (LINES
- view
->lines
);
997 if (view
->cols
> COLS
)
998 ncols
= view
->ncols
- (view
->cols
- COLS
);
1000 ncols
= view
->ncols
+ (COLS
- view
->cols
);
1003 int hs
= view
->child
->begin_y
;
1005 if (!view_is_fullscreen(view
))
1006 view
->child
->begin_x
= view_split_begin_x(view
->begin_x
);
1007 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
||
1008 view
->child
->begin_x
== 0) {
1011 view_fullscreen(view
->child
);
1012 if (view
->child
->focussed
)
1013 show_panel(view
->child
->panel
);
1015 show_panel(view
->panel
);
1017 ncols
= view
->child
->begin_x
;
1019 view_splitscreen(view
->child
);
1020 show_panel(view
->child
->panel
);
1023 * XXX This is ugly and needs to be moved into the above
1024 * logic but "works" for now and my attempts at moving it
1025 * break either 'tab' or 'F' key maps in horizontal splits.
1028 err
= view_splitscreen(view
->child
);
1031 if (dif
< 0) { /* top split decreased */
1032 err
= offset_selection_down(view
);
1039 show_panel(view
->child
->panel
);
1040 nlines
= view
->nlines
;
1042 } else if (view
->parent
== NULL
)
1045 if (view
->resize
&& dif
> 0) {
1046 err
= view
->resize(view
, dif
);
1051 if (wresize(view
->window
, nlines
, ncols
) == ERR
)
1052 return got_error_from_errno("wresize");
1053 if (replace_panel(view
->panel
, view
->window
) == ERR
)
1054 return got_error_from_errno("replace_panel");
1055 wclear(view
->window
);
1057 view
->nlines
= nlines
;
1058 view
->ncols
= ncols
;
1059 view
->lines
= LINES
;
1065 static const struct got_error
*
1066 resize_log_view(struct tog_view
*view
, int increase
)
1068 struct tog_log_view_state
*s
= &view
->state
.log
;
1069 const struct got_error
*err
= NULL
;
1072 if (s
->selected_entry
)
1073 n
= s
->selected_entry
->idx
+ view
->lines
- s
->selected
;
1076 * Request commits to account for the increased
1077 * height so we have enough to populate the view.
1079 if (s
->commits
->ncommits
< n
) {
1080 view
->nscrolled
= n
- s
->commits
->ncommits
+ increase
+ 1;
1081 err
= request_log_commits(view
);
1088 view_adjust_offset(struct tog_view
*view
, int n
)
1093 if (view
->parent
&& view
->parent
->offset
) {
1094 if (view
->parent
->offset
+ n
>= 0)
1095 view
->parent
->offset
+= n
;
1097 view
->parent
->offset
= 0;
1098 } else if (view
->offset
) {
1099 if (view
->offset
- n
>= 0)
1106 static const struct got_error
*
1107 view_resize_split(struct tog_view
*view
, int resize
)
1109 const struct got_error
*err
= NULL
;
1110 struct tog_view
*v
= NULL
;
1117 if (!v
->child
|| !view_is_splitscreen(v
->child
))
1120 v
->resized
= v
->child
->resized
= resize
; /* lock for resize event */
1122 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
) {
1123 if (v
->child
->resized_y
)
1124 v
->child
->begin_y
= v
->child
->resized_y
;
1126 v
->child
->begin_y
-= resize
;
1128 v
->child
->begin_y
+= resize
;
1129 if (v
->child
->begin_y
< 3) {
1131 v
->child
->begin_y
= 3;
1132 } else if (v
->child
->begin_y
> LINES
- 1) {
1134 v
->child
->begin_y
= LINES
- 1;
1137 v
->child
->ncols
= COLS
;
1138 view_adjust_offset(view
, resize
);
1139 err
= view_init_hsplit(v
, v
->child
->begin_y
);
1142 v
->child
->resized_y
= v
->child
->begin_y
;
1144 if (v
->child
->resized_x
)
1145 v
->child
->begin_x
= v
->child
->resized_x
;
1147 v
->child
->begin_x
-= resize
;
1149 v
->child
->begin_x
+= resize
;
1150 if (v
->child
->begin_x
< 11) {
1152 v
->child
->begin_x
= 11;
1153 } else if (v
->child
->begin_x
> COLS
- 1) {
1155 v
->child
->begin_x
= COLS
- 1;
1157 v
->child
->resized_x
= v
->child
->begin_x
;
1160 v
->child
->mode
= v
->mode
;
1161 v
->child
->nlines
= v
->lines
- v
->child
->begin_y
;
1162 v
->child
->ncols
= v
->cols
- v
->child
->begin_x
;
1165 err
= view_fullscreen(v
);
1168 err
= view_splitscreen(v
->child
);
1172 if (v
->mode
== TOG_VIEW_SPLIT_HRZN
) {
1173 err
= offset_selection_down(v
->child
);
1179 err
= v
->resize(v
, 0);
1180 else if (v
->child
->resize
)
1181 err
= v
->child
->resize(v
->child
, 0);
1183 v
->resized
= v
->child
->resized
= 0;
1189 view_transfer_size(struct tog_view
*dst
, struct tog_view
*src
)
1191 struct tog_view
*v
= src
->child
? src
->child
: src
;
1193 dst
->resized_x
= v
->resized_x
;
1194 dst
->resized_y
= v
->resized_y
;
1197 static const struct got_error
*
1198 view_close_child(struct tog_view
*view
)
1200 const struct got_error
*err
= NULL
;
1202 if (view
->child
== NULL
)
1205 err
= view_close(view
->child
);
1210 static const struct got_error
*
1211 view_set_child(struct tog_view
*view
, struct tog_view
*child
)
1213 const struct got_error
*err
= NULL
;
1215 view
->child
= child
;
1216 child
->parent
= view
;
1218 err
= view_resize(view
);
1222 if (view
->child
->resized_x
|| view
->child
->resized_y
)
1223 err
= view_resize_split(view
, 0);
1228 static const struct got_error
*view_dispatch_request(struct tog_view
**,
1229 struct tog_view
*, enum tog_view_type
, int, int);
1231 static const struct got_error
*
1232 view_request_new(struct tog_view
**requested
, struct tog_view
*view
,
1233 enum tog_view_type request
)
1235 struct tog_view
*new_view
= NULL
;
1236 const struct got_error
*err
;
1241 if (view_is_parent_view(view
) && request
!= TOG_VIEW_HELP
)
1242 view_get_split(view
, &y
, &x
);
1244 err
= view_dispatch_request(&new_view
, view
, request
, y
, x
);
1248 if (view_is_parent_view(view
) && view
->mode
== TOG_VIEW_SPLIT_HRZN
&&
1249 request
!= TOG_VIEW_HELP
) {
1250 err
= view_init_hsplit(view
, y
);
1256 new_view
->focussed
= 1;
1257 new_view
->mode
= view
->mode
;
1258 new_view
->nlines
= request
== TOG_VIEW_HELP
?
1259 view
->lines
: view
->lines
- y
;
1261 if (view_is_parent_view(view
) && request
!= TOG_VIEW_HELP
) {
1262 view_transfer_size(new_view
, view
);
1263 err
= view_close_child(view
);
1266 err
= view_set_child(view
, new_view
);
1269 view
->focus_child
= 1;
1271 *requested
= new_view
;
1277 tog_resizeterm(void)
1280 struct winsize size
;
1282 if (ioctl(STDOUT_FILENO
, TIOCGWINSZ
, &size
) < 0) {
1283 cols
= 80; /* Default */
1287 lines
= size
.ws_row
;
1289 resize_term(lines
, cols
);
1292 static const struct got_error
*
1293 view_search_start(struct tog_view
*view
)
1295 const struct got_error
*err
= NULL
;
1296 struct tog_view
*v
= view
;
1300 if (view
->search_started
) {
1301 regfree(&view
->regex
);
1302 view
->searching
= 0;
1303 memset(&view
->regmatch
, 0, sizeof(view
->regmatch
));
1305 view
->search_started
= 0;
1307 if (view
->nlines
< 1)
1310 if (view_is_hsplit_top(view
))
1312 else if (view
->mode
== TOG_VIEW_SPLIT_VERT
&& view
->parent
)
1315 mvwaddstr(v
->window
, v
->nlines
- 1, 0, "/");
1316 wclrtoeol(v
->window
);
1318 nodelay(v
->window
, FALSE
); /* block for search term input */
1321 ret
= wgetnstr(v
->window
, pattern
, sizeof(pattern
));
1322 wrefresh(v
->window
);
1325 nodelay(v
->window
, TRUE
);
1329 if (regcomp(&view
->regex
, pattern
, REG_EXTENDED
| REG_NEWLINE
) == 0) {
1330 err
= view
->search_start(view
);
1332 regfree(&view
->regex
);
1335 view
->search_started
= 1;
1336 view
->searching
= TOG_SEARCH_FORWARD
;
1337 view
->search_next_done
= 0;
1338 view
->search_next(view
);
1344 /* Switch split mode. If view is a parent or child, draw the new splitscreen. */
1345 static const struct got_error
*
1346 switch_split(struct tog_view
*view
)
1348 const struct got_error
*err
= NULL
;
1349 struct tog_view
*v
= NULL
;
1356 if (v
->mode
== TOG_VIEW_SPLIT_HRZN
)
1357 v
->mode
= TOG_VIEW_SPLIT_VERT
;
1359 v
->mode
= TOG_VIEW_SPLIT_HRZN
;
1363 else if (v
->mode
== TOG_VIEW_SPLIT_VERT
&& v
->cols
< 120)
1364 v
->mode
= TOG_VIEW_SPLIT_NONE
;
1366 view_get_split(v
, &v
->child
->begin_y
, &v
->child
->begin_x
);
1367 if (v
->mode
== TOG_VIEW_SPLIT_HRZN
&& v
->child
->resized_y
)
1368 v
->child
->begin_y
= v
->child
->resized_y
;
1369 else if (v
->mode
== TOG_VIEW_SPLIT_VERT
&& v
->child
->resized_x
)
1370 v
->child
->begin_x
= v
->child
->resized_x
;
1373 if (v
->mode
== TOG_VIEW_SPLIT_HRZN
) {
1375 v
->child
->ncols
= COLS
;
1376 v
->child
->nscrolled
= LINES
- v
->child
->nlines
;
1378 err
= view_init_hsplit(v
, v
->child
->begin_y
);
1382 v
->child
->mode
= v
->mode
;
1383 v
->child
->nlines
= v
->lines
- v
->child
->begin_y
;
1386 err
= view_fullscreen(v
);
1389 err
= view_splitscreen(v
->child
);
1393 if (v
->mode
== TOG_VIEW_SPLIT_NONE
)
1394 v
->mode
= TOG_VIEW_SPLIT_VERT
;
1395 if (v
->mode
== TOG_VIEW_SPLIT_HRZN
) {
1396 err
= offset_selection_down(v
);
1399 err
= offset_selection_down(v
->child
);
1403 offset_selection_up(v
);
1404 offset_selection_up(v
->child
);
1407 err
= v
->resize(v
, 0);
1408 else if (v
->child
->resize
)
1409 err
= v
->child
->resize(v
->child
, 0);
1415 * Compute view->count from numeric input. Assign total to view->count and
1416 * return first non-numeric key entered.
1419 get_compound_key(struct tog_view
*view
, int c
)
1421 struct tog_view
*v
= view
;
1424 if (view_is_hsplit_top(view
))
1426 else if (view
->mode
== TOG_VIEW_SPLIT_VERT
&& view
->parent
)
1430 cbreak(); /* block for input */
1431 nodelay(view
->window
, FALSE
);
1432 wmove(v
->window
, v
->nlines
- 1, 0);
1433 wclrtoeol(v
->window
);
1434 waddch(v
->window
, ':');
1437 x
= getcurx(v
->window
);
1438 if (x
!= ERR
&& x
< view
->ncols
) {
1439 waddch(v
->window
, c
);
1440 wrefresh(v
->window
);
1444 * Don't overflow. Max valid request should be the greatest
1445 * between the longest and total lines; cap at 10 million.
1450 n
= n
* 10 + (c
- '0');
1451 } while (((c
= wgetch(view
->window
))) >= '0' && c
<= '9' && c
!= ERR
);
1453 if (c
== 'G' || c
== 'g') { /* nG key map */
1454 view
->gline
= view
->hiline
= n
;
1459 /* Massage excessive or inapplicable values at the input handler. */
1465 static const struct got_error
*
1466 view_input(struct tog_view
**new, int *done
, struct tog_view
*view
,
1467 struct tog_view_list_head
*views
)
1469 const struct got_error
*err
= NULL
;
1475 /* Clear "no matches" indicator. */
1476 if (view
->search_next_done
== TOG_SEARCH_NO_MORE
||
1477 view
->search_next_done
== TOG_SEARCH_HAVE_NONE
) {
1478 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
1482 if (view
->searching
&& !view
->search_next_done
) {
1483 errcode
= pthread_mutex_unlock(&tog_mutex
);
1485 return got_error_set_errno(errcode
,
1486 "pthread_mutex_unlock");
1488 errcode
= pthread_mutex_lock(&tog_mutex
);
1490 return got_error_set_errno(errcode
,
1491 "pthread_mutex_lock");
1492 view
->search_next(view
);
1496 /* Allow threads to make progress while we are waiting for input. */
1497 errcode
= pthread_mutex_unlock(&tog_mutex
);
1499 return got_error_set_errno(errcode
, "pthread_mutex_unlock");
1500 /* If we have an unfinished count, let C-g or backspace abort. */
1501 if (view
->count
&& --view
->count
) {
1503 nodelay(view
->window
, TRUE
);
1504 ch
= wgetch(view
->window
);
1505 if (ch
== CTRL('g') || ch
== KEY_BACKSPACE
)
1510 ch
= wgetch(view
->window
);
1511 if (ch
>= '1' && ch
<= '9')
1512 view
->ch
= ch
= get_compound_key(view
, ch
);
1514 if (view
->hiline
&& ch
!= ERR
&& ch
!= 0)
1515 view
->hiline
= 0; /* key pressed, clear line highlight */
1516 nodelay(view
->window
, TRUE
);
1517 errcode
= pthread_mutex_lock(&tog_mutex
);
1519 return got_error_set_errno(errcode
, "pthread_mutex_lock");
1521 if (tog_sigwinch_received
|| tog_sigcont_received
) {
1523 tog_sigwinch_received
= 0;
1524 tog_sigcont_received
= 0;
1525 TAILQ_FOREACH(v
, views
, entry
) {
1526 err
= view_resize(v
);
1529 err
= v
->input(new, v
, KEY_RESIZE
);
1533 err
= view_resize(v
->child
);
1536 err
= v
->child
->input(new, v
->child
,
1540 if (v
->child
->resized_x
|| v
->child
->resized_y
) {
1541 err
= view_resize_split(v
, 0);
1553 if (view
->type
== TOG_VIEW_HELP
)
1554 err
= view
->reset(view
);
1556 err
= view_request_new(new, view
, TOG_VIEW_HELP
);
1562 view
->child
->focussed
= 1;
1563 view
->focus_child
= 1;
1564 } else if (view
->parent
) {
1566 view
->parent
->focussed
= 1;
1567 view
->parent
->focus_child
= 0;
1568 if (!view_is_splitscreen(view
)) {
1569 if (view
->parent
->resize
) {
1570 err
= view
->parent
->resize(view
->parent
,
1575 offset_selection_up(view
->parent
);
1576 err
= view_fullscreen(view
->parent
);
1583 if (view
->parent
&& view
->mode
== TOG_VIEW_SPLIT_HRZN
) {
1584 if (view
->parent
->resize
) {
1585 /* might need more commits to fill fullscreen */
1586 err
= view
->parent
->resize(view
->parent
, 0);
1590 offset_selection_up(view
->parent
);
1592 err
= view
->input(new, view
, ch
);
1600 if (view_is_parent_view(view
)) {
1601 if (view
->child
== NULL
)
1603 if (view_is_splitscreen(view
->child
)) {
1605 view
->child
->focussed
= 1;
1606 err
= view_fullscreen(view
->child
);
1608 err
= view_splitscreen(view
->child
);
1610 err
= view_resize_split(view
, 0);
1614 err
= view
->child
->input(new, view
->child
,
1617 if (view_is_splitscreen(view
)) {
1618 view
->parent
->focussed
= 0;
1620 err
= view_fullscreen(view
);
1622 err
= view_splitscreen(view
);
1623 if (!err
&& view
->mode
!= TOG_VIEW_SPLIT_HRZN
)
1624 err
= view_resize(view
->parent
);
1626 err
= view_resize_split(view
, 0);
1630 err
= view
->input(new, view
, KEY_RESIZE
);
1635 err
= view
->resize(view
, 0);
1640 err
= offset_selection_down(view
->parent
);
1642 err
= offset_selection_down(view
);
1646 err
= switch_split(view
);
1649 err
= view_resize_split(view
, -1);
1652 err
= view_resize_split(view
, 1);
1658 if (view
->search_start
)
1659 view_search_start(view
);
1661 err
= view
->input(new, view
, ch
);
1665 if (view
->search_started
&& view
->search_next
) {
1666 view
->searching
= (ch
== 'n' ?
1667 TOG_SEARCH_FORWARD
: TOG_SEARCH_BACKWARD
);
1668 view
->search_next_done
= 0;
1669 view
->search_next(view
);
1671 err
= view
->input(new, view
, ch
);
1674 if (tog_diff_algo
== GOT_DIFF_ALGORITHM_MYERS
)
1675 tog_diff_algo
= GOT_DIFF_ALGORITHM_PATIENCE
;
1677 tog_diff_algo
= GOT_DIFF_ALGORITHM_MYERS
;
1678 TAILQ_FOREACH(v
, views
, entry
) {
1684 if (v
->child
&& v
->child
->reset
) {
1685 err
= v
->child
->reset(v
->child
);
1692 err
= view
->input(new, view
, ch
);
1700 view_needs_focus_indication(struct tog_view
*view
)
1702 if (view_is_parent_view(view
)) {
1703 if (view
->child
== NULL
|| view
->child
->focussed
)
1705 if (!view_is_splitscreen(view
->child
))
1707 } else if (!view_is_splitscreen(view
))
1710 return view
->focussed
;
1713 static const struct got_error
*
1714 view_loop(struct tog_view
*view
)
1716 const struct got_error
*err
= NULL
;
1717 struct tog_view_list_head views
;
1718 struct tog_view
*new_view
;
1720 int fast_refresh
= 10;
1721 int done
= 0, errcode
;
1723 mode
= getenv("TOG_VIEW_SPLIT_MODE");
1724 if (!mode
|| !(*mode
== 'h' || *mode
== 'H'))
1725 view
->mode
= TOG_VIEW_SPLIT_VERT
;
1727 view
->mode
= TOG_VIEW_SPLIT_HRZN
;
1729 errcode
= pthread_mutex_lock(&tog_mutex
);
1731 return got_error_set_errno(errcode
, "pthread_mutex_lock");
1734 TAILQ_INSERT_HEAD(&views
, view
, entry
);
1737 err
= view
->show(view
);
1742 while (!TAILQ_EMPTY(&views
) && !done
&& !tog_thread_error
&&
1743 !tog_fatal_signal_received()) {
1744 /* Refresh fast during initialization, then become slower. */
1745 if (fast_refresh
&& fast_refresh
-- == 0)
1746 halfdelay(10); /* switch to once per second */
1748 err
= view_input(&new_view
, &done
, view
, &views
);
1752 struct tog_view
*v
, *prev
= NULL
;
1754 if (view_is_parent_view(view
))
1755 prev
= TAILQ_PREV(view
, tog_view_list_head
,
1757 else if (view
->parent
)
1758 prev
= view
->parent
;
1761 view
->parent
->child
= NULL
;
1762 view
->parent
->focus_child
= 0;
1763 /* Restore fullscreen line height. */
1764 view
->parent
->nlines
= view
->parent
->lines
;
1765 err
= view_resize(view
->parent
);
1768 /* Make resized splits persist. */
1769 view_transfer_size(view
->parent
, view
);
1771 TAILQ_REMOVE(&views
, view
, entry
);
1773 err
= view_close(view
);
1778 TAILQ_FOREACH(v
, &views
, entry
) {
1782 if (view
== NULL
&& new_view
== NULL
) {
1783 /* No view has focus. Try to pick one. */
1786 else if (!TAILQ_EMPTY(&views
)) {
1787 view
= TAILQ_LAST(&views
,
1788 tog_view_list_head
);
1791 if (view
->focus_child
) {
1792 view
->child
->focussed
= 1;
1800 struct tog_view
*v
, *t
;
1801 /* Only allow one parent view per type. */
1802 TAILQ_FOREACH_SAFE(v
, &views
, entry
, t
) {
1803 if (v
->type
!= new_view
->type
)
1805 TAILQ_REMOVE(&views
, v
, entry
);
1806 err
= view_close(v
);
1811 TAILQ_INSERT_TAIL(&views
, new_view
, entry
);
1815 if (view_is_parent_view(view
)) {
1816 if (view
->child
&& view
->child
->focussed
)
1819 if (view
->parent
&& view
->parent
->focussed
)
1820 view
= view
->parent
;
1822 show_panel(view
->panel
);
1823 if (view
->child
&& view_is_splitscreen(view
->child
))
1824 show_panel(view
->child
->panel
);
1825 if (view
->parent
&& view_is_splitscreen(view
)) {
1826 err
= view
->parent
->show(view
->parent
);
1830 err
= view
->show(view
);
1834 err
= view
->child
->show(view
->child
);
1843 while (!TAILQ_EMPTY(&views
)) {
1844 const struct got_error
*close_err
;
1845 view
= TAILQ_FIRST(&views
);
1846 TAILQ_REMOVE(&views
, view
, entry
);
1847 close_err
= view_close(view
);
1848 if (close_err
&& err
== NULL
)
1852 errcode
= pthread_mutex_unlock(&tog_mutex
);
1853 if (errcode
&& err
== NULL
)
1854 err
= got_error_set_errno(errcode
, "pthread_mutex_unlock");
1864 "usage: %s log [-b] [-c commit] [-r repository-path] [path]\n",
1869 /* Create newly allocated wide-character string equivalent to a byte string. */
1870 static const struct got_error
*
1871 mbs2ws(wchar_t **ws
, size_t *wlen
, const char *s
)
1874 const struct got_error
*err
= NULL
;
1877 *wlen
= mbstowcs(NULL
, s
, 0);
1878 if (*wlen
== (size_t)-1) {
1880 if (errno
!= EILSEQ
)
1881 return got_error_from_errno("mbstowcs");
1883 /* byte string invalid in current encoding; try to "fix" it */
1884 err
= got_mbsavis(&vis
, &vislen
, s
);
1887 *wlen
= mbstowcs(NULL
, vis
, 0);
1888 if (*wlen
== (size_t)-1) {
1889 err
= got_error_from_errno("mbstowcs"); /* give up */
1894 *ws
= calloc(*wlen
+ 1, sizeof(**ws
));
1896 err
= got_error_from_errno("calloc");
1900 if (mbstowcs(*ws
, vis
? vis
: s
, *wlen
) != *wlen
)
1901 err
= got_error_from_errno("mbstowcs");
1912 static const struct got_error
*
1913 expand_tab(char **ptr
, const char *src
)
1916 size_t len
, n
, idx
= 0, sz
= 0;
1919 n
= len
= strlen(src
);
1920 dst
= malloc(n
+ 1);
1922 return got_error_from_errno("malloc");
1924 while (idx
< len
&& src
[idx
]) {
1925 const char c
= src
[idx
];
1928 size_t nb
= TABSIZE
- sz
% TABSIZE
;
1931 p
= realloc(dst
, n
+ nb
);
1934 return got_error_from_errno("realloc");
1939 memset(dst
+ sz
, ' ', nb
);
1942 dst
[sz
++] = src
[idx
];
1952 * Advance at most n columns from wline starting at offset off.
1953 * Return the index to the first character after the span operation.
1954 * Return the combined column width of all spanned wide character in
1958 span_wline(int *rcol
, int off
, wchar_t *wline
, int n
, int col_tab_align
)
1960 int width
, i
, cols
= 0;
1967 for (i
= off
; wline
[i
] != L
'\0'; ++i
) {
1968 if (wline
[i
] == L
'\t')
1969 width
= TABSIZE
- ((cols
+ col_tab_align
) % TABSIZE
);
1971 width
= wcwidth(wline
[i
]);
1978 if (cols
+ width
> n
)
1988 * Format a line for display, ensuring that it won't overflow a width limit.
1989 * With scrolling, the width returned refers to the scrolled version of the
1990 * line, which starts at (*wlinep)[*scrollxp]. The caller must free *wlinep.
1992 static const struct got_error
*
1993 format_line(wchar_t **wlinep
, int *widthp
, int *scrollxp
,
1994 const char *line
, int nscroll
, int wlimit
, int col_tab_align
, int expand
)
1996 const struct got_error
*err
= NULL
;
1998 wchar_t *wline
= NULL
;
2007 err
= expand_tab(&exstr
, line
);
2012 err
= mbs2ws(&wline
, &wlen
, expand
? exstr
: line
);
2017 scrollx
= span_wline(&cols
, 0, wline
, nscroll
, col_tab_align
);
2019 if (wlen
> 0 && wline
[wlen
- 1] == L
'\n') {
2020 wline
[wlen
- 1] = L
'\0';
2023 if (wlen
> 0 && wline
[wlen
- 1] == L
'\r') {
2024 wline
[wlen
- 1] = L
'\0';
2028 i
= span_wline(&cols
, scrollx
, wline
, wlimit
, col_tab_align
);
2034 *scrollxp
= scrollx
;
2042 static const struct got_error
*
2043 build_refs_str(char **refs_str
, struct got_reflist_head
*refs
,
2044 struct got_object_id
*id
, struct got_repository
*repo
)
2046 static const struct got_error
*err
= NULL
;
2047 struct got_reflist_entry
*re
;
2053 TAILQ_FOREACH(re
, refs
, entry
) {
2054 struct got_tag_object
*tag
= NULL
;
2055 struct got_object_id
*ref_id
;
2058 name
= got_ref_get_name(re
->ref
);
2059 if (strcmp(name
, GOT_REF_HEAD
) == 0)
2061 if (strncmp(name
, "refs/", 5) == 0)
2063 if (strncmp(name
, "got/", 4) == 0 &&
2064 strncmp(name
, "got/backup/", 11) != 0)
2066 if (strncmp(name
, "heads/", 6) == 0)
2068 if (strncmp(name
, "remotes/", 8) == 0) {
2070 s
= strstr(name
, "/" GOT_REF_HEAD
);
2071 if (s
!= NULL
&& s
[strlen(s
)] == '\0')
2074 err
= got_ref_resolve(&ref_id
, repo
, re
->ref
);
2077 if (strncmp(name
, "tags/", 5) == 0) {
2078 err
= got_object_open_as_tag(&tag
, repo
, ref_id
);
2080 if (err
->code
!= GOT_ERR_OBJ_TYPE
) {
2084 /* Ref points at something other than a tag. */
2089 cmp
= got_object_id_cmp(tag
?
2090 got_object_tag_get_object_id(tag
) : ref_id
, id
);
2093 got_object_tag_close(tag
);
2097 if (asprintf(refs_str
, "%s%s%s", s
? s
: "",
2098 s
? ", " : "", name
) == -1) {
2099 err
= got_error_from_errno("asprintf");
2110 static const struct got_error
*
2111 format_author(wchar_t **wauthor
, int *author_width
, char *author
, int limit
,
2116 smallerthan
= strchr(author
, '<');
2117 if (smallerthan
&& smallerthan
[1] != '\0')
2118 author
= smallerthan
+ 1;
2119 author
[strcspn(author
, "@>")] = '\0';
2120 return format_line(wauthor
, author_width
, NULL
, author
, 0, limit
,
2124 static const struct got_error
*
2125 draw_commit(struct tog_view
*view
, struct got_commit_object
*commit
,
2126 struct got_object_id
*id
, const size_t date_display_cols
,
2127 int author_display_cols
)
2129 struct tog_log_view_state
*s
= &view
->state
.log
;
2130 const struct got_error
*err
= NULL
;
2131 char datebuf
[12]; /* YYYY-MM-DD + SPACE + NUL */
2132 char *logmsg0
= NULL
, *logmsg
= NULL
;
2133 char *author
= NULL
;
2134 wchar_t *wlogmsg
= NULL
, *wauthor
= NULL
;
2135 int author_width
, logmsg_width
;
2136 char *newline
, *line
= NULL
;
2137 int col
, limit
, scrollx
;
2138 const int avail
= view
->ncols
;
2140 time_t committer_time
;
2141 struct tog_color
*tc
;
2143 committer_time
= got_object_commit_get_committer_time(commit
);
2144 if (gmtime_r(&committer_time
, &tm
) == NULL
)
2145 return got_error_from_errno("gmtime_r");
2146 if (strftime(datebuf
, sizeof(datebuf
), "%G-%m-%d ", &tm
) == 0)
2147 return got_error(GOT_ERR_NO_SPACE
);
2149 if (avail
<= date_display_cols
)
2150 limit
= MIN(sizeof(datebuf
) - 1, avail
);
2152 limit
= MIN(date_display_cols
, sizeof(datebuf
) - 1);
2153 tc
= get_color(&s
->colors
, TOG_COLOR_DATE
);
2155 wattr_on(view
->window
,
2156 COLOR_PAIR(tc
->colorpair
), NULL
);
2157 waddnstr(view
->window
, datebuf
, limit
);
2159 wattr_off(view
->window
,
2160 COLOR_PAIR(tc
->colorpair
), NULL
);
2167 err
= got_object_id_str(&id_str
, id
);
2170 tc
= get_color(&s
->colors
, TOG_COLOR_COMMIT
);
2172 wattr_on(view
->window
,
2173 COLOR_PAIR(tc
->colorpair
), NULL
);
2174 wprintw(view
->window
, "%.8s ", id_str
);
2176 wattr_off(view
->window
,
2177 COLOR_PAIR(tc
->colorpair
), NULL
);
2184 if (s
->use_committer
)
2185 author
= strdup(got_object_commit_get_committer(commit
));
2187 author
= strdup(got_object_commit_get_author(commit
));
2188 if (author
== NULL
) {
2189 err
= got_error_from_errno("strdup");
2192 err
= format_author(&wauthor
, &author_width
, author
, avail
- col
, col
);
2195 tc
= get_color(&s
->colors
, TOG_COLOR_AUTHOR
);
2197 wattr_on(view
->window
,
2198 COLOR_PAIR(tc
->colorpair
), NULL
);
2199 waddwstr(view
->window
, wauthor
);
2200 col
+= author_width
;
2201 while (col
< avail
&& author_width
< author_display_cols
+ 2) {
2202 waddch(view
->window
, ' ');
2207 wattr_off(view
->window
,
2208 COLOR_PAIR(tc
->colorpair
), NULL
);
2212 err
= got_object_commit_get_logmsg(&logmsg0
, commit
);
2216 while (*logmsg
== '\n')
2218 newline
= strchr(logmsg
, '\n');
2221 limit
= avail
- col
;
2222 if (view
->child
&& !view_is_hsplit_top(view
) && limit
> 0)
2223 limit
--; /* for the border */
2224 err
= format_line(&wlogmsg
, &logmsg_width
, &scrollx
, logmsg
, view
->x
,
2228 waddwstr(view
->window
, &wlogmsg
[scrollx
]);
2229 col
+= MAX(logmsg_width
, 0);
2230 while (col
< avail
) {
2231 waddch(view
->window
, ' ');
2243 static struct commit_queue_entry
*
2244 alloc_commit_queue_entry(struct got_commit_object
*commit
,
2245 struct got_object_id
*id
)
2247 struct commit_queue_entry
*entry
;
2248 struct got_object_id
*dup
;
2250 entry
= calloc(1, sizeof(*entry
));
2254 dup
= got_object_id_dup(id
);
2261 entry
->commit
= commit
;
2266 pop_commit(struct commit_queue
*commits
)
2268 struct commit_queue_entry
*entry
;
2270 entry
= TAILQ_FIRST(&commits
->head
);
2271 TAILQ_REMOVE(&commits
->head
, entry
, entry
);
2272 got_object_commit_close(entry
->commit
);
2273 commits
->ncommits
--;
2279 free_commits(struct commit_queue
*commits
)
2281 while (!TAILQ_EMPTY(&commits
->head
))
2282 pop_commit(commits
);
2285 static const struct got_error
*
2286 match_commit(int *have_match
, struct got_object_id
*id
,
2287 struct got_commit_object
*commit
, regex_t
*regex
)
2289 const struct got_error
*err
= NULL
;
2290 regmatch_t regmatch
;
2291 char *id_str
= NULL
, *logmsg
= NULL
;
2295 err
= got_object_id_str(&id_str
, id
);
2299 err
= got_object_commit_get_logmsg(&logmsg
, commit
);
2303 if (regexec(regex
, got_object_commit_get_author(commit
), 1,
2304 ®match
, 0) == 0 ||
2305 regexec(regex
, got_object_commit_get_committer(commit
), 1,
2306 ®match
, 0) == 0 ||
2307 regexec(regex
, id_str
, 1, ®match
, 0) == 0 ||
2308 regexec(regex
, logmsg
, 1, ®match
, 0) == 0)
2316 static const struct got_error
*
2317 queue_commits(struct tog_log_thread_args
*a
)
2319 const struct got_error
*err
= NULL
;
2322 * We keep all commits open throughout the lifetime of the log
2323 * view in order to avoid having to re-fetch commits from disk
2324 * while updating the display.
2327 struct got_object_id id
;
2328 struct got_commit_object
*commit
;
2329 struct commit_queue_entry
*entry
;
2330 int limit_match
= 0;
2333 err
= got_commit_graph_iter_next(&id
, a
->graph
, a
->repo
,
2338 err
= got_object_open_as_commit(&commit
, a
->repo
, &id
);
2341 entry
= alloc_commit_queue_entry(commit
, &id
);
2342 if (entry
== NULL
) {
2343 err
= got_error_from_errno("alloc_commit_queue_entry");
2347 errcode
= pthread_mutex_lock(&tog_mutex
);
2349 err
= got_error_set_errno(errcode
,
2350 "pthread_mutex_lock");
2354 entry
->idx
= a
->real_commits
->ncommits
;
2355 TAILQ_INSERT_TAIL(&a
->real_commits
->head
, entry
, entry
);
2356 a
->real_commits
->ncommits
++;
2359 err
= match_commit(&limit_match
, &id
, commit
,
2365 struct commit_queue_entry
*matched
;
2367 matched
= alloc_commit_queue_entry(
2368 entry
->commit
, entry
->id
);
2369 if (matched
== NULL
) {
2370 err
= got_error_from_errno(
2371 "alloc_commit_queue_entry");
2374 matched
->commit
= entry
->commit
;
2375 got_object_commit_retain(entry
->commit
);
2377 matched
->idx
= a
->limit_commits
->ncommits
;
2378 TAILQ_INSERT_TAIL(&a
->limit_commits
->head
,
2380 a
->limit_commits
->ncommits
++;
2384 * This is how we signal log_thread() that we
2385 * have found a match, and that it should be
2386 * counted as a new entry for the view.
2388 a
->limit_match
= limit_match
;
2391 if (*a
->searching
== TOG_SEARCH_FORWARD
&&
2392 !*a
->search_next_done
) {
2394 err
= match_commit(&have_match
, &id
, commit
, a
->regex
);
2399 if (limit_match
&& have_match
)
2400 *a
->search_next_done
=
2401 TOG_SEARCH_HAVE_MORE
;
2402 } else if (have_match
)
2403 *a
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
2406 errcode
= pthread_mutex_unlock(&tog_mutex
);
2407 if (errcode
&& err
== NULL
)
2408 err
= got_error_set_errno(errcode
,
2409 "pthread_mutex_unlock");
2412 } while (*a
->searching
== TOG_SEARCH_FORWARD
&& !*a
->search_next_done
);
2418 select_commit(struct tog_log_view_state
*s
)
2420 struct commit_queue_entry
*entry
;
2423 entry
= s
->first_displayed_entry
;
2425 if (ncommits
== s
->selected
) {
2426 s
->selected_entry
= entry
;
2429 entry
= TAILQ_NEXT(entry
, entry
);
2434 static const struct got_error
*
2435 draw_commits(struct tog_view
*view
)
2437 const struct got_error
*err
= NULL
;
2438 struct tog_log_view_state
*s
= &view
->state
.log
;
2439 struct commit_queue_entry
*entry
= s
->selected_entry
;
2440 int limit
= view
->nlines
;
2442 int ncommits
, author_cols
= 4;
2443 char *id_str
= NULL
, *header
= NULL
, *ncommits_str
= NULL
;
2444 char *refs_str
= NULL
;
2446 struct tog_color
*tc
;
2447 static const size_t date_display_cols
= 12;
2449 if (view_is_hsplit_top(view
))
2450 --limit
; /* account for border */
2452 if (s
->selected_entry
&&
2453 !(view
->searching
&& view
->search_next_done
== 0)) {
2454 struct got_reflist_head
*refs
;
2455 err
= got_object_id_str(&id_str
, s
->selected_entry
->id
);
2458 refs
= got_reflist_object_id_map_lookup(tog_refs_idmap
,
2459 s
->selected_entry
->id
);
2461 err
= build_refs_str(&refs_str
, refs
,
2462 s
->selected_entry
->id
, s
->repo
);
2468 if (s
->thread_args
.commits_needed
== 0)
2469 halfdelay(10); /* disable fast refresh */
2471 if (s
->thread_args
.commits_needed
> 0 || s
->thread_args
.load_all
) {
2472 if (asprintf(&ncommits_str
, " [%d/%d] %s",
2473 entry
? entry
->idx
+ 1 : 0, s
->commits
->ncommits
,
2474 (view
->searching
&& !view
->search_next_done
) ?
2475 "searching..." : "loading...") == -1) {
2476 err
= got_error_from_errno("asprintf");
2480 const char *search_str
= NULL
;
2481 const char *limit_str
= NULL
;
2483 if (view
->searching
) {
2484 if (view
->search_next_done
== TOG_SEARCH_NO_MORE
)
2485 search_str
= "no more matches";
2486 else if (view
->search_next_done
== TOG_SEARCH_HAVE_NONE
)
2487 search_str
= "no matches found";
2488 else if (!view
->search_next_done
)
2489 search_str
= "searching...";
2492 if (s
->limit_view
&& s
->commits
->ncommits
== 0)
2493 limit_str
= "no matches found";
2495 if (asprintf(&ncommits_str
, " [%d/%d] %s %s",
2496 entry
? entry
->idx
+ 1 : 0, s
->commits
->ncommits
,
2497 search_str
? search_str
: (refs_str
? refs_str
: ""),
2498 limit_str
? limit_str
: "") == -1) {
2499 err
= got_error_from_errno("asprintf");
2504 if (s
->in_repo_path
&& strcmp(s
->in_repo_path
, "/") != 0) {
2505 if (asprintf(&header
, "commit %s %s%s", id_str
? id_str
:
2506 "........................................",
2507 s
->in_repo_path
, ncommits_str
) == -1) {
2508 err
= got_error_from_errno("asprintf");
2512 } else if (asprintf(&header
, "commit %s%s",
2513 id_str
? id_str
: "........................................",
2514 ncommits_str
) == -1) {
2515 err
= got_error_from_errno("asprintf");
2519 err
= format_line(&wline
, &width
, NULL
, header
, 0, view
->ncols
, 0, 0);
2523 werase(view
->window
);
2525 if (view_needs_focus_indication(view
))
2526 wstandout(view
->window
);
2527 tc
= get_color(&s
->colors
, TOG_COLOR_COMMIT
);
2529 wattr_on(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2530 waddwstr(view
->window
, wline
);
2531 while (width
< view
->ncols
) {
2532 waddch(view
->window
, ' ');
2536 wattr_off(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2537 if (view_needs_focus_indication(view
))
2538 wstandend(view
->window
);
2543 /* Grow author column size if necessary, and set view->maxx. */
2544 entry
= s
->first_displayed_entry
;
2548 struct got_commit_object
*c
= entry
->commit
;
2549 char *author
, *eol
, *msg
, *msg0
;
2550 wchar_t *wauthor
, *wmsg
;
2552 if (ncommits
>= limit
- 1)
2554 if (s
->use_committer
)
2555 author
= strdup(got_object_commit_get_committer(c
));
2557 author
= strdup(got_object_commit_get_author(c
));
2558 if (author
== NULL
) {
2559 err
= got_error_from_errno("strdup");
2562 err
= format_author(&wauthor
, &width
, author
, COLS
,
2564 if (author_cols
< width
)
2565 author_cols
= width
;
2570 err
= got_object_commit_get_logmsg(&msg0
, c
);
2574 while (*msg
== '\n')
2576 if ((eol
= strchr(msg
, '\n')))
2578 err
= format_line(&wmsg
, &width
, NULL
, msg
, 0, INT_MAX
,
2579 date_display_cols
+ author_cols
, 0);
2582 view
->maxx
= MAX(view
->maxx
, width
);
2586 entry
= TAILQ_NEXT(entry
, entry
);
2589 entry
= s
->first_displayed_entry
;
2590 s
->last_displayed_entry
= s
->first_displayed_entry
;
2593 if (ncommits
>= limit
- 1)
2595 if (ncommits
== s
->selected
)
2596 wstandout(view
->window
);
2597 err
= draw_commit(view
, entry
->commit
, entry
->id
,
2598 date_display_cols
, author_cols
);
2599 if (ncommits
== s
->selected
)
2600 wstandend(view
->window
);
2604 s
->last_displayed_entry
= entry
;
2605 entry
= TAILQ_NEXT(entry
, entry
);
2618 log_scroll_up(struct tog_log_view_state
*s
, int maxscroll
)
2620 struct commit_queue_entry
*entry
;
2623 entry
= TAILQ_FIRST(&s
->commits
->head
);
2624 if (s
->first_displayed_entry
== entry
)
2627 entry
= s
->first_displayed_entry
;
2628 while (entry
&& nscrolled
< maxscroll
) {
2629 entry
= TAILQ_PREV(entry
, commit_queue_head
, entry
);
2631 s
->first_displayed_entry
= entry
;
2637 static const struct got_error
*
2638 trigger_log_thread(struct tog_view
*view
, int wait
)
2640 struct tog_log_thread_args
*ta
= &view
->state
.log
.thread_args
;
2643 halfdelay(1); /* fast refresh while loading commits */
2645 while (!ta
->log_complete
&& !tog_thread_error
&&
2646 (ta
->commits_needed
> 0 || ta
->load_all
)) {
2647 /* Wake the log thread. */
2648 errcode
= pthread_cond_signal(&ta
->need_commits
);
2650 return got_error_set_errno(errcode
,
2651 "pthread_cond_signal");
2654 * The mutex will be released while the view loop waits
2655 * in wgetch(), at which time the log thread will run.
2660 /* Display progress update in log view. */
2661 show_log_view(view
);
2665 /* Wait right here while next commit is being loaded. */
2666 errcode
= pthread_cond_wait(&ta
->commit_loaded
, &tog_mutex
);
2668 return got_error_set_errno(errcode
,
2669 "pthread_cond_wait");
2671 /* Display progress update in log view. */
2672 show_log_view(view
);
2680 static const struct got_error
*
2681 request_log_commits(struct tog_view
*view
)
2683 struct tog_log_view_state
*state
= &view
->state
.log
;
2684 const struct got_error
*err
= NULL
;
2686 if (state
->thread_args
.log_complete
)
2689 state
->thread_args
.commits_needed
+= view
->nscrolled
;
2690 err
= trigger_log_thread(view
, 1);
2691 view
->nscrolled
= 0;
2696 static const struct got_error
*
2697 log_scroll_down(struct tog_view
*view
, int maxscroll
)
2699 struct tog_log_view_state
*s
= &view
->state
.log
;
2700 const struct got_error
*err
= NULL
;
2701 struct commit_queue_entry
*pentry
;
2702 int nscrolled
= 0, ncommits_needed
;
2704 if (s
->last_displayed_entry
== NULL
)
2707 ncommits_needed
= s
->last_displayed_entry
->idx
+ 1 + maxscroll
;
2708 if (s
->commits
->ncommits
< ncommits_needed
&&
2709 !s
->thread_args
.log_complete
) {
2711 * Ask the log thread for required amount of commits.
2713 s
->thread_args
.commits_needed
+=
2714 ncommits_needed
- s
->commits
->ncommits
;
2715 err
= trigger_log_thread(view
, 1);
2721 pentry
= TAILQ_NEXT(s
->last_displayed_entry
, entry
);
2722 if (pentry
== NULL
&& view
->mode
!= TOG_VIEW_SPLIT_HRZN
)
2725 s
->last_displayed_entry
= pentry
?
2726 pentry
: s
->last_displayed_entry
;;
2728 pentry
= TAILQ_NEXT(s
->first_displayed_entry
, entry
);
2731 s
->first_displayed_entry
= pentry
;
2732 } while (++nscrolled
< maxscroll
);
2734 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
&& !s
->thread_args
.log_complete
)
2735 view
->nscrolled
+= nscrolled
;
2737 view
->nscrolled
= 0;
2742 static const struct got_error
*
2743 open_diff_view_for_commit(struct tog_view
**new_view
, int begin_y
, int begin_x
,
2744 struct got_commit_object
*commit
, struct got_object_id
*commit_id
,
2745 struct tog_view
*log_view
, struct got_repository
*repo
)
2747 const struct got_error
*err
;
2748 struct got_object_qid
*parent_id
;
2749 struct tog_view
*diff_view
;
2751 diff_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_DIFF
);
2752 if (diff_view
== NULL
)
2753 return got_error_from_errno("view_open");
2755 parent_id
= STAILQ_FIRST(got_object_commit_get_parent_ids(commit
));
2756 err
= open_diff_view(diff_view
, parent_id
? &parent_id
->id
: NULL
,
2757 commit_id
, NULL
, NULL
, 3, 0, 0, log_view
, repo
);
2759 *new_view
= diff_view
;
2763 static const struct got_error
*
2764 tree_view_visit_subtree(struct tog_tree_view_state
*s
,
2765 struct got_tree_object
*subtree
)
2767 struct tog_parent_tree
*parent
;
2769 parent
= calloc(1, sizeof(*parent
));
2771 return got_error_from_errno("calloc");
2773 parent
->tree
= s
->tree
;
2774 parent
->first_displayed_entry
= s
->first_displayed_entry
;
2775 parent
->selected_entry
= s
->selected_entry
;
2776 parent
->selected
= s
->selected
;
2777 TAILQ_INSERT_HEAD(&s
->parents
, parent
, entry
);
2780 s
->first_displayed_entry
= NULL
;
2784 static const struct got_error
*
2785 tree_view_walk_path(struct tog_tree_view_state
*s
,
2786 struct got_commit_object
*commit
, const char *path
)
2788 const struct got_error
*err
= NULL
;
2789 struct got_tree_object
*tree
= NULL
;
2791 char *slash
, *subpath
= NULL
;
2793 /* Walk the path and open corresponding tree objects. */
2796 struct got_tree_entry
*te
;
2797 struct got_object_id
*tree_id
;
2803 /* Ensure the correct subtree entry is selected. */
2804 slash
= strchr(p
, '/');
2806 te_name
= strdup(p
);
2808 te_name
= strndup(p
, slash
- p
);
2809 if (te_name
== NULL
) {
2810 err
= got_error_from_errno("strndup");
2813 te
= got_object_tree_find_entry(s
->tree
, te_name
);
2815 err
= got_error_path(te_name
, GOT_ERR_NO_TREE_ENTRY
);
2820 s
->first_displayed_entry
= s
->selected_entry
= te
;
2822 if (!S_ISDIR(got_tree_entry_get_mode(s
->selected_entry
)))
2823 break; /* jump to this file's entry */
2825 slash
= strchr(p
, '/');
2827 subpath
= strndup(path
, slash
- path
);
2829 subpath
= strdup(path
);
2830 if (subpath
== NULL
) {
2831 err
= got_error_from_errno("strdup");
2835 err
= got_object_id_by_path(&tree_id
, s
->repo
, commit
,
2840 err
= got_object_open_as_tree(&tree
, s
->repo
, tree_id
);
2845 err
= tree_view_visit_subtree(s
, tree
);
2847 got_object_tree_close(tree
);
2861 static const struct got_error
*
2862 browse_commit_tree(struct tog_view
**new_view
, int begin_y
, int begin_x
,
2863 struct commit_queue_entry
*entry
, const char *path
,
2864 const char *head_ref_name
, struct got_repository
*repo
)
2866 const struct got_error
*err
= NULL
;
2867 struct tog_tree_view_state
*s
;
2868 struct tog_view
*tree_view
;
2870 tree_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_TREE
);
2871 if (tree_view
== NULL
)
2872 return got_error_from_errno("view_open");
2874 err
= open_tree_view(tree_view
, entry
->id
, head_ref_name
, repo
);
2877 s
= &tree_view
->state
.tree
;
2879 *new_view
= tree_view
;
2881 if (got_path_is_root_dir(path
))
2884 return tree_view_walk_path(s
, entry
->commit
, path
);
2887 static const struct got_error
*
2888 block_signals_used_by_main_thread(void)
2893 if (sigemptyset(&sigset
) == -1)
2894 return got_error_from_errno("sigemptyset");
2896 /* tog handles SIGWINCH, SIGCONT, SIGINT, SIGTERM */
2897 if (sigaddset(&sigset
, SIGWINCH
) == -1)
2898 return got_error_from_errno("sigaddset");
2899 if (sigaddset(&sigset
, SIGCONT
) == -1)
2900 return got_error_from_errno("sigaddset");
2901 if (sigaddset(&sigset
, SIGINT
) == -1)
2902 return got_error_from_errno("sigaddset");
2903 if (sigaddset(&sigset
, SIGTERM
) == -1)
2904 return got_error_from_errno("sigaddset");
2906 /* ncurses handles SIGTSTP */
2907 if (sigaddset(&sigset
, SIGTSTP
) == -1)
2908 return got_error_from_errno("sigaddset");
2910 errcode
= pthread_sigmask(SIG_BLOCK
, &sigset
, NULL
);
2912 return got_error_set_errno(errcode
, "pthread_sigmask");
2918 log_thread(void *arg
)
2920 const struct got_error
*err
= NULL
;
2922 struct tog_log_thread_args
*a
= arg
;
2926 * Sync startup with main thread such that we begin our
2927 * work once view_input() has released the mutex.
2929 errcode
= pthread_mutex_lock(&tog_mutex
);
2931 err
= got_error_set_errno(errcode
, "pthread_mutex_lock");
2935 err
= block_signals_used_by_main_thread();
2937 pthread_mutex_unlock(&tog_mutex
);
2941 while (!done
&& !err
&& !tog_fatal_signal_received()) {
2942 errcode
= pthread_mutex_unlock(&tog_mutex
);
2944 err
= got_error_set_errno(errcode
,
2945 "pthread_mutex_unlock");
2948 err
= queue_commits(a
);
2950 if (err
->code
!= GOT_ERR_ITER_COMPLETED
)
2954 } else if (a
->commits_needed
> 0 && !a
->load_all
) {
2957 a
->commits_needed
--;
2959 a
->commits_needed
--;
2962 errcode
= pthread_mutex_lock(&tog_mutex
);
2964 err
= got_error_set_errno(errcode
,
2965 "pthread_mutex_lock");
2967 } else if (*a
->quit
)
2969 else if (*a
->limiting
&& *a
->first_displayed_entry
== NULL
) {
2970 *a
->first_displayed_entry
=
2971 TAILQ_FIRST(&a
->limit_commits
->head
);
2972 *a
->selected_entry
= *a
->first_displayed_entry
;
2973 } else if (*a
->first_displayed_entry
== NULL
) {
2974 *a
->first_displayed_entry
=
2975 TAILQ_FIRST(&a
->real_commits
->head
);
2976 *a
->selected_entry
= *a
->first_displayed_entry
;
2979 errcode
= pthread_cond_signal(&a
->commit_loaded
);
2981 err
= got_error_set_errno(errcode
,
2982 "pthread_cond_signal");
2983 pthread_mutex_unlock(&tog_mutex
);
2988 a
->commits_needed
= 0;
2990 if (a
->commits_needed
== 0 && !a
->load_all
) {
2991 errcode
= pthread_cond_wait(&a
->need_commits
,
2994 err
= got_error_set_errno(errcode
,
2995 "pthread_cond_wait");
2996 pthread_mutex_unlock(&tog_mutex
);
3004 a
->log_complete
= 1;
3005 errcode
= pthread_mutex_unlock(&tog_mutex
);
3007 err
= got_error_set_errno(errcode
, "pthread_mutex_unlock");
3010 tog_thread_error
= 1;
3011 pthread_cond_signal(&a
->commit_loaded
);
3016 static const struct got_error
*
3017 stop_log_thread(struct tog_log_view_state
*s
)
3019 const struct got_error
*err
= NULL
, *thread_err
= NULL
;
3024 errcode
= pthread_cond_signal(&s
->thread_args
.need_commits
);
3026 return got_error_set_errno(errcode
,
3027 "pthread_cond_signal");
3028 errcode
= pthread_mutex_unlock(&tog_mutex
);
3030 return got_error_set_errno(errcode
,
3031 "pthread_mutex_unlock");
3032 errcode
= pthread_join(s
->thread
, (void **)&thread_err
);
3034 return got_error_set_errno(errcode
, "pthread_join");
3035 errcode
= pthread_mutex_lock(&tog_mutex
);
3037 return got_error_set_errno(errcode
,
3038 "pthread_mutex_lock");
3039 s
->thread
= 0; //NULL;
3042 if (s
->thread_args
.repo
) {
3043 err
= got_repo_close(s
->thread_args
.repo
);
3044 s
->thread_args
.repo
= NULL
;
3047 if (s
->thread_args
.pack_fds
) {
3048 const struct got_error
*pack_err
=
3049 got_repo_pack_fds_close(s
->thread_args
.pack_fds
);
3052 s
->thread_args
.pack_fds
= NULL
;
3055 if (s
->thread_args
.graph
) {
3056 got_commit_graph_close(s
->thread_args
.graph
);
3057 s
->thread_args
.graph
= NULL
;
3060 return err
? err
: thread_err
;
3063 static const struct got_error
*
3064 close_log_view(struct tog_view
*view
)
3066 const struct got_error
*err
= NULL
;
3067 struct tog_log_view_state
*s
= &view
->state
.log
;
3070 err
= stop_log_thread(s
);
3072 errcode
= pthread_cond_destroy(&s
->thread_args
.need_commits
);
3073 if (errcode
&& err
== NULL
)
3074 err
= got_error_set_errno(errcode
, "pthread_cond_destroy");
3076 errcode
= pthread_cond_destroy(&s
->thread_args
.commit_loaded
);
3077 if (errcode
&& err
== NULL
)
3078 err
= got_error_set_errno(errcode
, "pthread_cond_destroy");
3080 free_commits(&s
->limit_commits
);
3081 free_commits(&s
->real_commits
);
3082 free(s
->in_repo_path
);
3083 s
->in_repo_path
= NULL
;
3086 free(s
->head_ref_name
);
3087 s
->head_ref_name
= NULL
;
3092 * We use two queues to implement the limit feature: first consists of
3093 * commits matching the current limit_regex; second is the real queue
3094 * of all known commits (real_commits). When the user starts limiting,
3095 * we swap queues such that all movement and displaying functionality
3096 * works with very slight change.
3098 static const struct got_error
*
3099 limit_log_view(struct tog_view
*view
)
3101 struct tog_log_view_state
*s
= &view
->state
.log
;
3102 struct commit_queue_entry
*entry
;
3103 struct tog_view
*v
= view
;
3104 const struct got_error
*err
= NULL
;
3108 if (view_is_hsplit_top(view
))
3110 else if (view
->mode
== TOG_VIEW_SPLIT_VERT
&& view
->parent
)
3113 /* Get the pattern */
3114 wmove(v
->window
, v
->nlines
- 1, 0);
3115 wclrtoeol(v
->window
);
3116 mvwaddstr(v
->window
, v
->nlines
- 1, 0, "&/");
3117 nodelay(v
->window
, FALSE
);
3120 ret
= wgetnstr(v
->window
, pattern
, sizeof(pattern
));
3123 nodelay(v
->window
, TRUE
);
3127 if (*pattern
== '\0') {
3129 * Safety measure for the situation where the user
3130 * resets limit without previously limiting anything.
3136 * User could have pressed Ctrl+L, which refreshed the
3137 * commit queues, it means we can't save previously
3138 * (before limit took place) displayed entries,
3139 * because they would point to already free'ed memory,
3140 * so we are forced to always select first entry of
3143 s
->commits
= &s
->real_commits
;
3144 s
->first_displayed_entry
= TAILQ_FIRST(&s
->real_commits
.head
);
3145 s
->selected_entry
= s
->first_displayed_entry
;
3152 if (regcomp(&s
->limit_regex
, pattern
, REG_EXTENDED
| REG_NEWLINE
))
3157 /* Clear the screen while loading limit view */
3158 s
->first_displayed_entry
= NULL
;
3159 s
->last_displayed_entry
= NULL
;
3160 s
->selected_entry
= NULL
;
3161 s
->commits
= &s
->limit_commits
;
3163 /* Prepare limit queue for new search */
3164 free_commits(&s
->limit_commits
);
3165 s
->limit_commits
.ncommits
= 0;
3167 /* First process commits, which are in queue already */
3168 TAILQ_FOREACH(entry
, &s
->real_commits
.head
, entry
) {
3171 err
= match_commit(&have_match
, entry
->id
,
3172 entry
->commit
, &s
->limit_regex
);
3177 struct commit_queue_entry
*matched
;
3179 matched
= alloc_commit_queue_entry(entry
->commit
,
3181 if (matched
== NULL
) {
3182 err
= got_error_from_errno(
3183 "alloc_commit_queue_entry");
3186 matched
->commit
= entry
->commit
;
3187 got_object_commit_retain(entry
->commit
);
3189 matched
->idx
= s
->limit_commits
.ncommits
;
3190 TAILQ_INSERT_TAIL(&s
->limit_commits
.head
,
3192 s
->limit_commits
.ncommits
++;
3196 /* Second process all the commits, until we fill the screen */
3197 if (s
->limit_commits
.ncommits
< view
->nlines
- 1 &&
3198 !s
->thread_args
.log_complete
) {
3199 s
->thread_args
.commits_needed
+=
3200 view
->nlines
- s
->limit_commits
.ncommits
- 1;
3201 err
= trigger_log_thread(view
, 1);
3206 s
->first_displayed_entry
= TAILQ_FIRST(&s
->commits
->head
);
3207 s
->selected_entry
= TAILQ_FIRST(&s
->commits
->head
);
3213 static const struct got_error
*
3214 search_start_log_view(struct tog_view
*view
)
3216 struct tog_log_view_state
*s
= &view
->state
.log
;
3218 s
->matched_entry
= NULL
;
3219 s
->search_entry
= NULL
;
3223 static const struct got_error
*
3224 search_next_log_view(struct tog_view
*view
)
3226 const struct got_error
*err
= NULL
;
3227 struct tog_log_view_state
*s
= &view
->state
.log
;
3228 struct commit_queue_entry
*entry
;
3230 /* Display progress update in log view. */
3231 show_log_view(view
);
3235 if (s
->search_entry
) {
3237 errcode
= pthread_mutex_unlock(&tog_mutex
);
3239 return got_error_set_errno(errcode
,
3240 "pthread_mutex_unlock");
3241 ch
= wgetch(view
->window
);
3242 errcode
= pthread_mutex_lock(&tog_mutex
);
3244 return got_error_set_errno(errcode
,
3245 "pthread_mutex_lock");
3246 if (ch
== CTRL('g') || ch
== KEY_BACKSPACE
) {
3247 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
3250 if (view
->searching
== TOG_SEARCH_FORWARD
)
3251 entry
= TAILQ_NEXT(s
->search_entry
, entry
);
3253 entry
= TAILQ_PREV(s
->search_entry
,
3254 commit_queue_head
, entry
);
3255 } else if (s
->matched_entry
) {
3257 * If the user has moved the cursor after we hit a match,
3258 * the position from where we should continue searching
3259 * might have changed.
3261 if (view
->searching
== TOG_SEARCH_FORWARD
)
3262 entry
= TAILQ_NEXT(s
->selected_entry
, entry
);
3264 entry
= TAILQ_PREV(s
->selected_entry
, commit_queue_head
,
3267 entry
= s
->selected_entry
;
3273 if (entry
== NULL
) {
3274 if (s
->thread_args
.log_complete
||
3275 view
->searching
== TOG_SEARCH_BACKWARD
) {
3276 view
->search_next_done
=
3277 (s
->matched_entry
== NULL
?
3278 TOG_SEARCH_HAVE_NONE
: TOG_SEARCH_NO_MORE
);
3279 s
->search_entry
= NULL
;
3283 * Poke the log thread for more commits and return,
3284 * allowing the main loop to make progress. Search
3285 * will resume at s->search_entry once we come back.
3287 s
->thread_args
.commits_needed
++;
3288 return trigger_log_thread(view
, 0);
3291 err
= match_commit(&have_match
, entry
->id
, entry
->commit
,
3296 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
3297 s
->matched_entry
= entry
;
3301 s
->search_entry
= entry
;
3302 if (view
->searching
== TOG_SEARCH_FORWARD
)
3303 entry
= TAILQ_NEXT(entry
, entry
);
3305 entry
= TAILQ_PREV(entry
, commit_queue_head
, entry
);
3308 if (s
->matched_entry
) {
3309 int cur
= s
->selected_entry
->idx
;
3310 while (cur
< s
->matched_entry
->idx
) {
3311 err
= input_log_view(NULL
, view
, KEY_DOWN
);
3316 while (cur
> s
->matched_entry
->idx
) {
3317 err
= input_log_view(NULL
, view
, KEY_UP
);
3324 s
->search_entry
= NULL
;
3329 static const struct got_error
*
3330 open_log_view(struct tog_view
*view
, struct got_object_id
*start_id
,
3331 struct got_repository
*repo
, const char *head_ref_name
,
3332 const char *in_repo_path
, int log_branches
)
3334 const struct got_error
*err
= NULL
;
3335 struct tog_log_view_state
*s
= &view
->state
.log
;
3336 struct got_repository
*thread_repo
= NULL
;
3337 struct got_commit_graph
*thread_graph
= NULL
;
3340 if (in_repo_path
!= s
->in_repo_path
) {
3341 free(s
->in_repo_path
);
3342 s
->in_repo_path
= strdup(in_repo_path
);
3343 if (s
->in_repo_path
== NULL
)
3344 return got_error_from_errno("strdup");
3347 /* The commit queue only contains commits being displayed. */
3348 TAILQ_INIT(&s
->real_commits
.head
);
3349 s
->real_commits
.ncommits
= 0;
3350 s
->commits
= &s
->real_commits
;
3352 TAILQ_INIT(&s
->limit_commits
.head
);
3354 s
->limit_commits
.ncommits
= 0;
3357 if (head_ref_name
) {
3358 s
->head_ref_name
= strdup(head_ref_name
);
3359 if (s
->head_ref_name
== NULL
) {
3360 err
= got_error_from_errno("strdup");
3364 s
->start_id
= got_object_id_dup(start_id
);
3365 if (s
->start_id
== NULL
) {
3366 err
= got_error_from_errno("got_object_id_dup");
3369 s
->log_branches
= log_branches
;
3370 s
->use_committer
= 1;
3372 STAILQ_INIT(&s
->colors
);
3373 if (has_colors() && getenv("TOG_COLORS") != NULL
) {
3374 err
= add_color(&s
->colors
, "^$", TOG_COLOR_COMMIT
,
3375 get_color_value("TOG_COLOR_COMMIT"));
3378 err
= add_color(&s
->colors
, "^$", TOG_COLOR_AUTHOR
,
3379 get_color_value("TOG_COLOR_AUTHOR"));
3381 free_colors(&s
->colors
);
3384 err
= add_color(&s
->colors
, "^$", TOG_COLOR_DATE
,
3385 get_color_value("TOG_COLOR_DATE"));
3387 free_colors(&s
->colors
);
3392 view
->show
= show_log_view
;
3393 view
->input
= input_log_view
;
3394 view
->resize
= resize_log_view
;
3395 view
->close
= close_log_view
;
3396 view
->search_start
= search_start_log_view
;
3397 view
->search_next
= search_next_log_view
;
3399 if (s
->thread_args
.pack_fds
== NULL
) {
3400 err
= got_repo_pack_fds_open(&s
->thread_args
.pack_fds
);
3404 err
= got_repo_open(&thread_repo
, got_repo_get_path(repo
), NULL
,
3405 s
->thread_args
.pack_fds
);
3408 err
= got_commit_graph_open(&thread_graph
, s
->in_repo_path
,
3412 err
= got_commit_graph_iter_start(thread_graph
, s
->start_id
,
3413 s
->repo
, NULL
, NULL
);
3417 errcode
= pthread_cond_init(&s
->thread_args
.need_commits
, NULL
);
3419 err
= got_error_set_errno(errcode
, "pthread_cond_init");
3422 errcode
= pthread_cond_init(&s
->thread_args
.commit_loaded
, NULL
);
3424 err
= got_error_set_errno(errcode
, "pthread_cond_init");
3428 s
->thread_args
.commits_needed
= view
->nlines
;
3429 s
->thread_args
.graph
= thread_graph
;
3430 s
->thread_args
.real_commits
= &s
->real_commits
;
3431 s
->thread_args
.limit_commits
= &s
->limit_commits
;
3432 s
->thread_args
.in_repo_path
= s
->in_repo_path
;
3433 s
->thread_args
.start_id
= s
->start_id
;
3434 s
->thread_args
.repo
= thread_repo
;
3435 s
->thread_args
.log_complete
= 0;
3436 s
->thread_args
.quit
= &s
->quit
;
3437 s
->thread_args
.first_displayed_entry
= &s
->first_displayed_entry
;
3438 s
->thread_args
.selected_entry
= &s
->selected_entry
;
3439 s
->thread_args
.searching
= &view
->searching
;
3440 s
->thread_args
.search_next_done
= &view
->search_next_done
;
3441 s
->thread_args
.regex
= &view
->regex
;
3442 s
->thread_args
.limiting
= &s
->limit_view
;
3443 s
->thread_args
.limit_regex
= &s
->limit_regex
;
3444 s
->thread_args
.limit_commits
= &s
->limit_commits
;
3447 close_log_view(view
);
3451 static const struct got_error
*
3452 show_log_view(struct tog_view
*view
)
3454 const struct got_error
*err
;
3455 struct tog_log_view_state
*s
= &view
->state
.log
;
3457 if (s
->thread
== 0) { //NULL) {
3458 int errcode
= pthread_create(&s
->thread
, NULL
, log_thread
,
3461 return got_error_set_errno(errcode
, "pthread_create");
3462 if (s
->thread_args
.commits_needed
> 0) {
3463 err
= trigger_log_thread(view
, 1);
3469 return draw_commits(view
);
3473 log_move_cursor_up(struct tog_view
*view
, int page
, int home
)
3475 struct tog_log_view_state
*s
= &view
->state
.log
;
3477 if (s
->first_displayed_entry
== NULL
)
3479 if (s
->selected_entry
->idx
== 0)
3482 if ((page
&& TAILQ_FIRST(&s
->commits
->head
) == s
->first_displayed_entry
)
3484 s
->selected
= home
? 0 : MAX(0, s
->selected
- page
- 1);
3486 if (!page
&& !home
&& s
->selected
> 0)
3489 log_scroll_up(s
, home
? s
->commits
->ncommits
: MAX(page
, 1));
3495 static const struct got_error
*
3496 log_move_cursor_down(struct tog_view
*view
, int page
)
3498 struct tog_log_view_state
*s
= &view
->state
.log
;
3499 const struct got_error
*err
= NULL
;
3500 int eos
= view
->nlines
- 2;
3502 if (s
->first_displayed_entry
== NULL
)
3505 if (s
->thread_args
.log_complete
&&
3506 s
->selected_entry
->idx
>= s
->commits
->ncommits
- 1)
3509 if (view_is_hsplit_top(view
))
3510 --eos
; /* border consumes the last line */
3513 if (s
->selected
< MIN(eos
, s
->commits
->ncommits
- 1))
3516 err
= log_scroll_down(view
, 1);
3517 } else if (s
->thread_args
.load_all
&& s
->thread_args
.log_complete
) {
3518 struct commit_queue_entry
*entry
;
3522 entry
= TAILQ_LAST(&s
->commits
->head
, commit_queue_head
);
3523 s
->last_displayed_entry
= entry
;
3524 for (n
= 0; n
<= eos
; n
++) {
3527 s
->first_displayed_entry
= entry
;
3528 entry
= TAILQ_PREV(entry
, commit_queue_head
, entry
);
3531 s
->selected
= n
- 1;
3533 if (s
->last_displayed_entry
->idx
== s
->commits
->ncommits
- 1 &&
3534 s
->thread_args
.log_complete
)
3535 s
->selected
+= MIN(page
,
3536 s
->commits
->ncommits
- s
->selected_entry
->idx
- 1);
3538 err
= log_scroll_down(view
, page
);
3544 * We might necessarily overshoot in horizontal
3545 * splits; if so, select the last displayed commit.
3547 if (s
->first_displayed_entry
&& s
->last_displayed_entry
) {
3548 s
->selected
= MIN(s
->selected
,
3549 s
->last_displayed_entry
->idx
-
3550 s
->first_displayed_entry
->idx
);
3555 if (s
->thread_args
.log_complete
&&
3556 s
->selected_entry
->idx
== s
->commits
->ncommits
- 1)
3563 view_get_split(struct tog_view
*view
, int *y
, int *x
)
3568 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
) {
3569 if (view
->child
&& view
->child
->resized_y
)
3570 *y
= view
->child
->resized_y
;
3571 else if (view
->resized_y
)
3572 *y
= view
->resized_y
;
3574 *y
= view_split_begin_y(view
->lines
);
3575 } else if (view
->mode
== TOG_VIEW_SPLIT_VERT
) {
3576 if (view
->child
&& view
->child
->resized_x
)
3577 *x
= view
->child
->resized_x
;
3578 else if (view
->resized_x
)
3579 *x
= view
->resized_x
;
3581 *x
= view_split_begin_x(view
->begin_x
);
3585 /* Split view horizontally at y and offset view->state->selected line. */
3586 static const struct got_error
*
3587 view_init_hsplit(struct tog_view
*view
, int y
)
3589 const struct got_error
*err
= NULL
;
3593 err
= view_resize(view
);
3597 err
= offset_selection_down(view
);
3602 static const struct got_error
*
3603 log_goto_line(struct tog_view
*view
, int nlines
)
3605 const struct got_error
*err
= NULL
;
3606 struct tog_log_view_state
*s
= &view
->state
.log
;
3607 int g
, idx
= s
->selected_entry
->idx
;
3609 if (s
->first_displayed_entry
== NULL
|| s
->last_displayed_entry
== NULL
)
3615 if (g
>= s
->first_displayed_entry
->idx
+ 1 &&
3616 g
<= s
->last_displayed_entry
->idx
+ 1 &&
3617 g
- s
->first_displayed_entry
->idx
- 1 < nlines
) {
3618 s
->selected
= g
- s
->first_displayed_entry
->idx
- 1;
3624 err
= log_move_cursor_down(view
, g
- idx
- 1);
3625 if (!err
&& g
> s
->selected_entry
->idx
+ 1)
3626 err
= log_move_cursor_down(view
,
3627 g
- s
->first_displayed_entry
->idx
- 1);
3630 } else if (idx
+ 1 > g
)
3631 log_move_cursor_up(view
, idx
- g
+ 1, 0);
3633 if (g
< nlines
&& s
->first_displayed_entry
->idx
== 0)
3634 s
->selected
= g
- 1;
3641 static const struct got_error
*
3642 input_log_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
3644 const struct got_error
*err
= NULL
;
3645 struct tog_log_view_state
*s
= &view
->state
.log
;
3648 if (s
->thread_args
.load_all
) {
3649 if (ch
== CTRL('g') || ch
== KEY_BACKSPACE
)
3650 s
->thread_args
.load_all
= 0;
3651 else if (s
->thread_args
.log_complete
) {
3652 err
= log_move_cursor_down(view
, s
->commits
->ncommits
);
3653 s
->thread_args
.load_all
= 0;
3659 eos
= nscroll
= view
->nlines
- 1;
3660 if (view_is_hsplit_top(view
))
3664 return log_goto_line(view
, eos
);
3668 err
= limit_log_view(view
);
3677 view
->x
= MAX(view
->maxx
- view
->ncols
/ 2, 0);
3682 if (view
->x
+ view
->ncols
/ 2 < view
->maxx
)
3683 view
->x
+= 2; /* move two columns right */
3689 view
->x
-= MIN(view
->x
, 2); /* move two columns back */
3698 log_move_cursor_up(view
, 0, 0);
3702 log_move_cursor_up(view
, 0, 1);
3712 log_move_cursor_up(view
, nscroll
, 0);
3719 err
= log_move_cursor_down(view
, 0);
3722 s
->use_committer
= !s
->use_committer
;
3726 /* We don't know yet how many commits, so we're forced to
3727 * traverse them all. */
3729 s
->thread_args
.load_all
= 1;
3730 if (!s
->thread_args
.log_complete
)
3731 return trigger_log_thread(view
, 0);
3732 err
= log_move_cursor_down(view
, s
->commits
->ncommits
);
3733 s
->thread_args
.load_all
= 0;
3744 err
= log_move_cursor_down(view
, nscroll
);
3747 if (s
->selected
> view
->nlines
- 2)
3748 s
->selected
= view
->nlines
- 2;
3749 if (s
->selected
> s
->commits
->ncommits
- 1)
3750 s
->selected
= s
->commits
->ncommits
- 1;
3752 if (s
->commits
->ncommits
< view
->nlines
- 1 &&
3753 !s
->thread_args
.log_complete
) {
3754 s
->thread_args
.commits_needed
+= (view
->nlines
- 1) -
3755 s
->commits
->ncommits
;
3756 err
= trigger_log_thread(view
, 1);
3762 if (s
->selected_entry
== NULL
)
3764 err
= view_request_new(new_view
, view
, TOG_VIEW_DIFF
);
3768 if (s
->selected_entry
== NULL
)
3770 err
= view_request_new(new_view
, view
, TOG_VIEW_TREE
);
3776 if (ch
== KEY_BACKSPACE
&&
3777 got_path_is_root_dir(s
->in_repo_path
))
3779 err
= stop_log_thread(s
);
3782 if (ch
== KEY_BACKSPACE
) {
3784 err
= got_path_dirname(&parent_path
, s
->in_repo_path
);
3787 free(s
->in_repo_path
);
3788 s
->in_repo_path
= parent_path
;
3789 s
->thread_args
.in_repo_path
= s
->in_repo_path
;
3790 } else if (ch
== CTRL('l')) {
3791 struct got_object_id
*start_id
;
3792 err
= got_repo_match_object_id(&start_id
, NULL
,
3793 s
->head_ref_name
? s
->head_ref_name
: GOT_REF_HEAD
,
3794 GOT_OBJ_TYPE_COMMIT
, &tog_refs
, s
->repo
);
3796 if (s
->head_ref_name
== NULL
||
3797 err
->code
!= GOT_ERR_NOT_REF
)
3799 /* Try to cope with deleted references. */
3800 free(s
->head_ref_name
);
3801 s
->head_ref_name
= NULL
;
3802 err
= got_repo_match_object_id(&start_id
,
3803 NULL
, GOT_REF_HEAD
, GOT_OBJ_TYPE_COMMIT
,
3804 &tog_refs
, s
->repo
);
3809 s
->start_id
= start_id
;
3810 s
->thread_args
.start_id
= s
->start_id
;
3812 s
->log_branches
= !s
->log_branches
;
3814 if (s
->thread_args
.pack_fds
== NULL
) {
3815 err
= got_repo_pack_fds_open(&s
->thread_args
.pack_fds
);
3819 err
= got_repo_open(&s
->thread_args
.repo
,
3820 got_repo_get_path(s
->repo
), NULL
,
3821 s
->thread_args
.pack_fds
);
3825 err
= tog_load_refs(s
->repo
, 0);
3828 err
= got_commit_graph_open(&s
->thread_args
.graph
,
3829 s
->in_repo_path
, !s
->log_branches
);
3832 err
= got_commit_graph_iter_start(s
->thread_args
.graph
,
3833 s
->start_id
, s
->repo
, NULL
, NULL
);
3836 free_commits(&s
->real_commits
);
3837 free_commits(&s
->limit_commits
);
3838 s
->first_displayed_entry
= NULL
;
3839 s
->last_displayed_entry
= NULL
;
3840 s
->selected_entry
= NULL
;
3842 s
->thread_args
.log_complete
= 0;
3844 s
->thread_args
.commits_needed
= view
->lines
;
3845 s
->matched_entry
= NULL
;
3846 s
->search_entry
= NULL
;
3851 err
= view_request_new(new_view
, view
, TOG_VIEW_REF
);
3861 static const struct got_error
*
3862 apply_unveil(const char *repo_path
, const char *worktree_path
)
3864 const struct got_error
*error
;
3867 if (unveil("gmon.out", "rwc") != 0)
3868 return got_error_from_errno2("unveil", "gmon.out");
3870 if (repo_path
&& unveil(repo_path
, "r") != 0)
3871 return got_error_from_errno2("unveil", repo_path
);
3873 if (worktree_path
&& unveil(worktree_path
, "rwc") != 0)
3874 return got_error_from_errno2("unveil", worktree_path
);
3876 if (unveil(GOT_TMPDIR_STR
, "rwc") != 0)
3877 return got_error_from_errno2("unveil", GOT_TMPDIR_STR
);
3879 error
= got_privsep_unveil_exec_helpers();
3883 if (unveil(NULL
, NULL
) != 0)
3884 return got_error_from_errno("unveil");
3893 * Override default signal handlers before starting ncurses.
3894 * This should prevent ncurses from installing its own
3895 * broken cleanup() signal handler.
3897 signal(SIGWINCH
, tog_sigwinch
);
3898 signal(SIGPIPE
, tog_sigpipe
);
3899 signal(SIGCONT
, tog_sigcont
);
3900 signal(SIGINT
, tog_sigint
);
3901 signal(SIGTERM
, tog_sigterm
);
3905 halfdelay(1); /* Do fast refresh while initial view is loading. */
3908 intrflush(stdscr
, FALSE
);
3909 keypad(stdscr
, TRUE
);
3911 if (getenv("TOG_COLORS") != NULL
) {
3913 use_default_colors();
3917 static const struct got_error
*
3918 get_in_repo_path_from_argv0(char **in_repo_path
, int argc
, char *argv
[],
3919 struct got_repository
*repo
, struct got_worktree
*worktree
)
3921 const struct got_error
*err
= NULL
;
3924 *in_repo_path
= strdup("/");
3925 if (*in_repo_path
== NULL
)
3926 return got_error_from_errno("strdup");
3931 const char *prefix
= got_worktree_get_path_prefix(worktree
);
3934 err
= got_worktree_resolve_path(&p
, worktree
, argv
[0]);
3937 if (asprintf(in_repo_path
, "%s%s%s", prefix
,
3938 (p
[0] != '\0' && !got_path_is_root_dir(prefix
)) ? "/" : "",
3940 err
= got_error_from_errno("asprintf");
3941 *in_repo_path
= NULL
;
3945 err
= got_repo_map_path(in_repo_path
, repo
, argv
[0]);
3950 static const struct got_error
*
3951 cmd_log(int argc
, char *argv
[])
3953 const struct got_error
*error
;
3954 struct got_repository
*repo
= NULL
;
3955 struct got_worktree
*worktree
= NULL
;
3956 struct got_object_id
*start_id
= NULL
;
3957 char *in_repo_path
= NULL
, *repo_path
= NULL
, *cwd
= NULL
;
3958 char *start_commit
= NULL
, *label
= NULL
;
3959 struct got_reference
*ref
= NULL
;
3960 const char *head_ref_name
= NULL
;
3961 int ch
, log_branches
= 0;
3962 struct tog_view
*view
;
3963 int *pack_fds
= NULL
;
3965 while ((ch
= getopt(argc
, argv
, "bc:r:")) != -1) {
3971 start_commit
= optarg
;
3974 repo_path
= realpath(optarg
, NULL
);
3975 if (repo_path
== NULL
)
3976 return got_error_from_errno2("realpath",
3991 error
= got_repo_pack_fds_open(&pack_fds
);
3995 if (repo_path
== NULL
) {
3996 cwd
= getcwd(NULL
, 0);
3998 return got_error_from_errno("getcwd");
3999 error
= got_worktree_open(&worktree
, cwd
);
4000 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
4004 strdup(got_worktree_get_repo_path(worktree
));
4006 repo_path
= strdup(cwd
);
4007 if (repo_path
== NULL
) {
4008 error
= got_error_from_errno("strdup");
4013 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
4017 error
= get_in_repo_path_from_argv0(&in_repo_path
, argc
, argv
,
4024 error
= apply_unveil(got_repo_get_path(repo
),
4025 worktree
? got_worktree_get_root_path(worktree
) : NULL
);
4029 /* already loaded by tog_log_with_path()? */
4030 if (TAILQ_EMPTY(&tog_refs
)) {
4031 error
= tog_load_refs(repo
, 0);
4036 if (start_commit
== NULL
) {
4037 error
= got_repo_match_object_id(&start_id
, &label
,
4038 worktree
? got_worktree_get_head_ref_name(worktree
) :
4039 GOT_REF_HEAD
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
4042 head_ref_name
= label
;
4044 error
= got_ref_open(&ref
, repo
, start_commit
, 0);
4046 head_ref_name
= got_ref_get_name(ref
);
4047 else if (error
->code
!= GOT_ERR_NOT_REF
)
4049 error
= got_repo_match_object_id(&start_id
, NULL
,
4050 start_commit
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
4055 view
= view_open(0, 0, 0, 0, TOG_VIEW_LOG
);
4057 error
= got_error_from_errno("view_open");
4060 error
= open_log_view(view
, start_id
, repo
, head_ref_name
,
4061 in_repo_path
, log_branches
);
4065 /* Release work tree lock. */
4066 got_worktree_close(worktree
);
4069 error
= view_loop(view
);
4079 const struct got_error
*close_err
= got_repo_close(repo
);
4084 got_worktree_close(worktree
);
4086 const struct got_error
*pack_err
=
4087 got_repo_pack_fds_close(pack_fds
);
4099 fprintf(stderr
, "usage: %s diff [-aw] [-C number] [-r repository-path] "
4100 "object1 object2\n", getprogname());
4105 match_line(const char *line
, regex_t
*regex
, size_t nmatch
,
4106 regmatch_t
*regmatch
)
4108 return regexec(regex
, line
, nmatch
, regmatch
, 0) == 0;
4111 static struct tog_color
*
4112 match_color(struct tog_colors
*colors
, const char *line
)
4114 struct tog_color
*tc
= NULL
;
4116 STAILQ_FOREACH(tc
, colors
, entry
) {
4117 if (match_line(line
, &tc
->regex
, 0, NULL
))
4124 static const struct got_error
*
4125 add_matched_line(int *wtotal
, const char *line
, int wlimit
, int col_tab_align
,
4126 WINDOW
*window
, int skipcol
, regmatch_t
*regmatch
)
4128 const struct got_error
*err
= NULL
;
4130 wchar_t *wline
= NULL
;
4131 int rme
, rms
, n
, width
, scrollx
;
4132 int width0
= 0, width1
= 0, width2
= 0;
4133 char *seg0
= NULL
, *seg1
= NULL
, *seg2
= NULL
;
4137 rms
= regmatch
->rm_so
;
4138 rme
= regmatch
->rm_eo
;
4140 err
= expand_tab(&exstr
, line
);
4144 /* Split the line into 3 segments, according to match offsets. */
4145 seg0
= strndup(exstr
, rms
);
4147 err
= got_error_from_errno("strndup");
4150 seg1
= strndup(exstr
+ rms
, rme
- rms
);
4152 err
= got_error_from_errno("strndup");
4155 seg2
= strdup(exstr
+ rme
);
4157 err
= got_error_from_errno("strndup");
4161 /* draw up to matched token if we haven't scrolled past it */
4162 err
= format_line(&wline
, &width0
, NULL
, seg0
, 0, wlimit
,
4166 n
= MAX(width0
- skipcol
, 0);
4169 err
= format_line(&wline
, &width
, &scrollx
, seg0
, skipcol
,
4170 wlimit
, col_tab_align
, 1);
4173 waddwstr(window
, &wline
[scrollx
]);
4183 err
= format_line(&wline
, &width1
, NULL
, seg1
, 0, wlimit
,
4187 wlen
= wcslen(wline
);
4189 width
= wcwidth(wline
[i
]);
4191 /* should not happen, tabs are expanded */
4192 err
= got_error(GOT_ERR_RANGE
);
4195 if (width0
+ w
+ width
> skipcol
)
4200 /* draw (visible part of) matched token (if scrolled into it) */
4201 if (width1
- w
> 0) {
4202 wattron(window
, A_STANDOUT
);
4203 waddwstr(window
, &wline
[i
]);
4204 wattroff(window
, A_STANDOUT
);
4205 wlimit
-= (width1
- w
);
4206 *wtotal
+= (width1
- w
);
4210 if (wlimit
> 0) { /* draw rest of line */
4212 if (skipcol
> width0
+ width1
) {
4213 err
= format_line(&wline
, &width2
, &scrollx
, seg2
,
4214 skipcol
- (width0
+ width1
), wlimit
,
4218 waddwstr(window
, &wline
[scrollx
]);
4220 err
= format_line(&wline
, &width2
, NULL
, seg2
, 0,
4221 wlimit
, col_tab_align
, 1);
4224 waddwstr(window
, wline
);
4238 gotoline(struct tog_view
*view
, int *lineno
, int *nprinted
)
4241 int *eof
, *first
, *selected
;
4243 if (view
->type
== TOG_VIEW_DIFF
) {
4244 struct tog_diff_view_state
*s
= &view
->state
.diff
;
4246 first
= &s
->first_displayed_line
;
4250 } else if (view
->type
== TOG_VIEW_HELP
) {
4251 struct tog_help_view_state
*s
= &view
->state
.help
;
4253 first
= &s
->first_displayed_line
;
4257 } else if (view
->type
== TOG_VIEW_BLAME
) {
4258 struct tog_blame_view_state
*s
= &view
->state
.blame
;
4260 first
= &s
->first_displayed_line
;
4261 selected
= &s
->selected_line
;
4267 /* Center gline in the middle of the page like vi(1). */
4268 if (*lineno
< view
->gline
- (view
->nlines
- 3) / 2)
4270 if (*first
!= 1 && (*lineno
> view
->gline
- (view
->nlines
- 3) / 2)) {
4279 *selected
= view
->gline
<= (view
->nlines
- 3) / 2 ?
4280 view
->gline
: (view
->nlines
- 3) / 2 + 1;
4286 static const struct got_error
*
4287 draw_file(struct tog_view
*view
, const char *header
)
4289 struct tog_diff_view_state
*s
= &view
->state
.diff
;
4290 regmatch_t
*regmatch
= &view
->regmatch
;
4291 const struct got_error
*err
;
4294 size_t linesize
= 0;
4298 int max_lines
= view
->nlines
;
4299 int nlines
= s
->nlines
;
4302 s
->lineno
= s
->first_displayed_line
- 1;
4303 line_offset
= s
->lines
[s
->first_displayed_line
- 1].offset
;
4304 if (fseeko(s
->f
, line_offset
, SEEK_SET
) == -1)
4305 return got_error_from_errno("fseek");
4307 werase(view
->window
);
4309 if (view
->gline
> s
->nlines
- 1)
4310 view
->gline
= s
->nlines
- 1;
4313 int ln
= view
->gline
? view
->gline
<= (view
->nlines
- 3) / 2 ?
4314 1 : view
->gline
- (view
->nlines
- 3) / 2 :
4315 s
->lineno
+ s
->selected_line
;
4317 if (asprintf(&line
, "[%d/%d] %s", ln
, nlines
, header
) == -1)
4318 return got_error_from_errno("asprintf");
4319 err
= format_line(&wline
, &width
, NULL
, line
, 0, view
->ncols
,
4325 if (view_needs_focus_indication(view
))
4326 wstandout(view
->window
);
4327 waddwstr(view
->window
, wline
);
4330 while (width
++ < view
->ncols
)
4331 waddch(view
->window
, ' ');
4332 if (view_needs_focus_indication(view
))
4333 wstandend(view
->window
);
4343 while (max_lines
> 0 && nprinted
< max_lines
) {
4344 enum got_diff_line_type linetype
;
4347 linelen
= getline(&line
, &linesize
, s
->f
);
4348 if (linelen
== -1) {
4354 return got_ferror(s
->f
, GOT_ERR_IO
);
4357 if (++s
->lineno
< s
->first_displayed_line
)
4359 if (view
->gline
&& !gotoline(view
, &s
->lineno
, &nprinted
))
4361 if (s
->lineno
== view
->hiline
)
4364 /* Set view->maxx based on full line length. */
4365 err
= format_line(&wline
, &width
, NULL
, line
, 0, INT_MAX
, 0,
4371 view
->maxx
= MAX(view
->maxx
, width
);
4375 linetype
= s
->lines
[s
->lineno
].type
;
4376 if (linetype
> GOT_DIFF_LINE_LOGMSG
&&
4377 linetype
< GOT_DIFF_LINE_CONTEXT
)
4378 attr
|= COLOR_PAIR(linetype
);
4380 wattron(view
->window
, attr
);
4381 if (s
->first_displayed_line
+ nprinted
== s
->matched_line
&&
4382 regmatch
->rm_so
>= 0 && regmatch
->rm_so
< regmatch
->rm_eo
) {
4383 err
= add_matched_line(&width
, line
, view
->ncols
, 0,
4384 view
->window
, view
->x
, regmatch
);
4391 err
= format_line(&wline
, &width
, &skip
, line
,
4392 view
->x
, view
->ncols
, 0, view
->x
? 1 : 0);
4397 waddwstr(view
->window
, &wline
[skip
]);
4401 if (s
->lineno
== view
->hiline
) {
4402 /* highlight full gline length */
4403 while (width
++ < view
->ncols
)
4404 waddch(view
->window
, ' ');
4406 if (width
<= view
->ncols
- 1)
4407 waddch(view
->window
, '\n');
4410 wattroff(view
->window
, attr
);
4411 if (++nprinted
== 1)
4412 s
->first_displayed_line
= s
->lineno
;
4416 s
->last_displayed_line
= s
->first_displayed_line
+
4419 s
->last_displayed_line
= s
->first_displayed_line
;
4424 while (nprinted
< view
->nlines
) {
4425 waddch(view
->window
, '\n');
4429 err
= format_line(&wline
, &width
, NULL
, TOG_EOF_STRING
, 0,
4435 wstandout(view
->window
);
4436 waddwstr(view
->window
, wline
);
4439 wstandend(view
->window
);
4446 get_datestr(time_t *time
, char *datebuf
)
4448 struct tm mytm
, *tm
;
4451 tm
= gmtime_r(time
, &mytm
);
4454 s
= asctime_r(tm
, datebuf
);
4457 p
= strchr(s
, '\n');
4463 static const struct got_error
*
4464 get_changed_paths(struct got_pathlist_head
*paths
,
4465 struct got_commit_object
*commit
, struct got_repository
*repo
,
4466 struct got_diffstat_cb_arg
*dsa
)
4468 const struct got_error
*err
= NULL
;
4469 struct got_object_id
*tree_id1
= NULL
, *tree_id2
= NULL
;
4470 struct got_tree_object
*tree1
= NULL
, *tree2
= NULL
;
4471 struct got_object_qid
*qid
;
4472 FILE *f1
= NULL
, *f2
= NULL
;
4473 int fd1
= -1, fd2
= -1;
4475 f1
= got_opentemp();
4477 err
= got_error_from_errno("got_opentemp");
4480 f2
= got_opentemp();
4482 err
= got_error_from_errno("got_opentemp");
4486 fd1
= got_opentempfd();
4488 err
= got_error_from_errno("got_opentempfd");
4491 fd2
= got_opentempfd();
4493 err
= got_error_from_errno("got_opentempfd");
4497 qid
= STAILQ_FIRST(got_object_commit_get_parent_ids(commit
));
4499 struct got_commit_object
*pcommit
;
4500 err
= got_object_open_as_commit(&pcommit
, repo
,
4505 tree_id1
= got_object_id_dup(
4506 got_object_commit_get_tree_id(pcommit
));
4507 if (tree_id1
== NULL
) {
4508 got_object_commit_close(pcommit
);
4509 return got_error_from_errno("got_object_id_dup");
4511 got_object_commit_close(pcommit
);
4516 err
= got_object_open_as_tree(&tree1
, repo
, tree_id1
);
4521 tree_id2
= got_object_commit_get_tree_id(commit
);
4522 err
= got_object_open_as_tree(&tree2
, repo
, tree_id2
);
4526 err
= got_diff_tree(tree1
, tree2
, f1
, f2
, fd1
, fd2
, "", "", repo
,
4527 got_diff_tree_compute_diffstat
, dsa
, 1);
4530 got_object_tree_close(tree1
);
4532 got_object_tree_close(tree2
);
4533 if (fd1
!= -1 && close(fd1
) == -1 && err
== NULL
)
4534 err
= got_error_from_errno("close");
4535 if (fd2
!= -1 && close(fd2
) == -1 && err
== NULL
)
4536 err
= got_error_from_errno("close");
4537 if (f1
&& fclose(f1
) == EOF
&& err
== NULL
)
4538 err
= got_error_from_errno("fclose");
4539 if (f2
&& fclose(f2
) == EOF
&& err
== NULL
)
4540 err
= got_error_from_errno("fclose");
4545 static const struct got_error
*
4546 add_line_metadata(struct got_diff_line
**lines
, size_t *nlines
,
4547 off_t off
, uint8_t type
)
4549 struct got_diff_line
*p
;
4551 p
= reallocarray(*lines
, *nlines
+ 1, sizeof(**lines
));
4553 return got_error_from_errno("reallocarray");
4555 (*lines
)[*nlines
].offset
= off
;
4556 (*lines
)[*nlines
].type
= type
;
4562 static const struct got_error
*
4563 write_commit_info(struct got_diff_line
**lines
, size_t *nlines
,
4564 struct got_object_id
*commit_id
, struct got_reflist_head
*refs
,
4565 struct got_repository
*repo
, int ignore_ws
, int force_text_diff
,
4568 const struct got_error
*err
= NULL
;
4569 char datebuf
[26], *datestr
;
4570 struct got_commit_object
*commit
;
4571 char *id_str
= NULL
, *logmsg
= NULL
, *s
= NULL
, *line
;
4572 time_t committer_time
;
4573 const char *author
, *committer
;
4574 char *refs_str
= NULL
;
4575 struct got_pathlist_head changed_paths
;
4576 struct got_pathlist_entry
*pe
;
4577 struct got_diffstat_cb_arg dsa
= { 0, 0, 0, 0, 0, 0, &changed_paths
,
4578 ignore_ws
, force_text_diff
, tog_diff_algo
};
4582 TAILQ_INIT(&changed_paths
);
4585 err
= build_refs_str(&refs_str
, refs
, commit_id
, repo
);
4590 err
= got_object_open_as_commit(&commit
, repo
, commit_id
);
4594 err
= got_object_id_str(&id_str
, commit_id
);
4596 err
= got_error_from_errno("got_object_id_str");
4600 err
= add_line_metadata(lines
, nlines
, 0, GOT_DIFF_LINE_NONE
);
4604 n
= fprintf(outfile
, "commit %s%s%s%s\n", id_str
, refs_str
? " (" : "",
4605 refs_str
? refs_str
: "", refs_str
? ")" : "");
4607 err
= got_error_from_errno("fprintf");
4611 err
= add_line_metadata(lines
, nlines
, outoff
, GOT_DIFF_LINE_META
);
4615 n
= fprintf(outfile
, "from: %s\n",
4616 got_object_commit_get_author(commit
));
4618 err
= got_error_from_errno("fprintf");
4622 err
= add_line_metadata(lines
, nlines
, outoff
, GOT_DIFF_LINE_AUTHOR
);
4626 committer_time
= got_object_commit_get_committer_time(commit
);
4627 datestr
= get_datestr(&committer_time
, datebuf
);
4629 n
= fprintf(outfile
, "date: %s UTC\n", datestr
);
4631 err
= got_error_from_errno("fprintf");
4635 err
= add_line_metadata(lines
, nlines
, outoff
,
4636 GOT_DIFF_LINE_DATE
);
4640 author
= got_object_commit_get_author(commit
);
4641 committer
= got_object_commit_get_committer(commit
);
4642 if (strcmp(author
, committer
) != 0) {
4643 n
= fprintf(outfile
, "via: %s\n", committer
);
4645 err
= got_error_from_errno("fprintf");
4649 err
= add_line_metadata(lines
, nlines
, outoff
,
4650 GOT_DIFF_LINE_AUTHOR
);
4654 if (got_object_commit_get_nparents(commit
) > 1) {
4655 const struct got_object_id_queue
*parent_ids
;
4656 struct got_object_qid
*qid
;
4658 parent_ids
= got_object_commit_get_parent_ids(commit
);
4659 STAILQ_FOREACH(qid
, parent_ids
, entry
) {
4660 err
= got_object_id_str(&id_str
, &qid
->id
);
4663 n
= fprintf(outfile
, "parent %d: %s\n", pn
++, id_str
);
4665 err
= got_error_from_errno("fprintf");
4669 err
= add_line_metadata(lines
, nlines
, outoff
,
4670 GOT_DIFF_LINE_META
);
4678 err
= got_object_commit_get_logmsg(&logmsg
, commit
);
4682 while ((line
= strsep(&s
, "\n")) != NULL
) {
4683 n
= fprintf(outfile
, "%s\n", line
);
4685 err
= got_error_from_errno("fprintf");
4689 err
= add_line_metadata(lines
, nlines
, outoff
,
4690 GOT_DIFF_LINE_LOGMSG
);
4695 err
= get_changed_paths(&changed_paths
, commit
, repo
, &dsa
);
4699 TAILQ_FOREACH(pe
, &changed_paths
, entry
) {
4700 struct got_diff_changed_path
*cp
= pe
->data
;
4701 int pad
= dsa
.max_path_len
- pe
->path_len
+ 1;
4703 n
= fprintf(outfile
, "%c %s%*c | %*d+ %*d-\n", cp
->status
,
4704 pe
->path
, pad
, ' ', dsa
.add_cols
+ 1, cp
->add
,
4705 dsa
.rm_cols
+ 1, cp
->rm
);
4707 err
= got_error_from_errno("fprintf");
4711 err
= add_line_metadata(lines
, nlines
, outoff
,
4712 GOT_DIFF_LINE_CHANGES
);
4715 free((char *)pe
->path
);
4719 fputc('\n', outfile
);
4721 err
= add_line_metadata(lines
, nlines
, outoff
, GOT_DIFF_LINE_NONE
);
4725 n
= fprintf(outfile
,
4726 "%d file%s changed, %d insertions(+), %d deletions(-)\n",
4727 dsa
.nfiles
, dsa
.nfiles
> 1 ? "s" : "", dsa
.ins
, dsa
.del
);
4729 err
= got_error_from_errno("fprintf");
4733 err
= add_line_metadata(lines
, nlines
, outoff
, GOT_DIFF_LINE_NONE
);
4737 fputc('\n', outfile
);
4739 err
= add_line_metadata(lines
, nlines
, outoff
, GOT_DIFF_LINE_NONE
);
4741 got_pathlist_free(&changed_paths
);
4745 got_object_commit_close(commit
);
4754 static const struct got_error
*
4755 create_diff(struct tog_diff_view_state
*s
)
4757 const struct got_error
*err
= NULL
;
4762 s
->lines
= malloc(sizeof(*s
->lines
));
4763 if (s
->lines
== NULL
)
4764 return got_error_from_errno("malloc");
4769 err
= got_error_from_errno("got_opentemp");
4772 if (s
->f
&& fclose(s
->f
) == EOF
) {
4773 err
= got_error_from_errno("fclose");
4779 err
= got_object_get_type(&obj_type
, s
->repo
, s
->id1
);
4781 err
= got_object_get_type(&obj_type
, s
->repo
, s
->id2
);
4786 case GOT_OBJ_TYPE_BLOB
:
4787 err
= got_diff_objects_as_blobs(&s
->lines
, &s
->nlines
,
4788 s
->f1
, s
->f2
, s
->fd1
, s
->fd2
, s
->id1
, s
->id2
,
4789 s
->label1
, s
->label2
, tog_diff_algo
, s
->diff_context
,
4790 s
->ignore_whitespace
, s
->force_text_diff
, s
->repo
, s
->f
);
4792 case GOT_OBJ_TYPE_TREE
:
4793 err
= got_diff_objects_as_trees(&s
->lines
, &s
->nlines
,
4794 s
->f1
, s
->f2
, s
->fd1
, s
->fd2
, s
->id1
, s
->id2
, NULL
, "", "",
4795 tog_diff_algo
, s
->diff_context
, s
->ignore_whitespace
,
4796 s
->force_text_diff
, s
->repo
, s
->f
);
4798 case GOT_OBJ_TYPE_COMMIT
: {
4799 const struct got_object_id_queue
*parent_ids
;
4800 struct got_object_qid
*pid
;
4801 struct got_commit_object
*commit2
;
4802 struct got_reflist_head
*refs
;
4804 err
= got_object_open_as_commit(&commit2
, s
->repo
, s
->id2
);
4807 refs
= got_reflist_object_id_map_lookup(tog_refs_idmap
, s
->id2
);
4808 /* Show commit info if we're diffing to a parent/root commit. */
4809 if (s
->id1
== NULL
) {
4810 err
= write_commit_info(&s
->lines
, &s
->nlines
, s
->id2
,
4811 refs
, s
->repo
, s
->ignore_whitespace
,
4812 s
->force_text_diff
, s
->f
);
4816 parent_ids
= got_object_commit_get_parent_ids(commit2
);
4817 STAILQ_FOREACH(pid
, parent_ids
, entry
) {
4818 if (got_object_id_cmp(s
->id1
, &pid
->id
) == 0) {
4819 err
= write_commit_info(&s
->lines
,
4820 &s
->nlines
, s
->id2
, refs
, s
->repo
,
4821 s
->ignore_whitespace
,
4822 s
->force_text_diff
, s
->f
);
4829 got_object_commit_close(commit2
);
4831 err
= got_diff_objects_as_commits(&s
->lines
, &s
->nlines
,
4832 s
->f1
, s
->f2
, s
->fd1
, s
->fd2
, s
->id1
, s
->id2
, NULL
,
4833 tog_diff_algo
, s
->diff_context
, s
->ignore_whitespace
,
4834 s
->force_text_diff
, s
->repo
, s
->f
);
4838 err
= got_error(GOT_ERR_OBJ_TYPE
);
4842 if (s
->f
&& fflush(s
->f
) != 0 && err
== NULL
)
4843 err
= got_error_from_errno("fflush");
4848 diff_view_indicate_progress(struct tog_view
*view
)
4850 mvwaddstr(view
->window
, 0, 0, "diffing...");
4855 static const struct got_error
*
4856 search_start_diff_view(struct tog_view
*view
)
4858 struct tog_diff_view_state
*s
= &view
->state
.diff
;
4860 s
->matched_line
= 0;
4865 search_setup_diff_view(struct tog_view
*view
, FILE **f
, off_t
**line_offsets
,
4866 size_t *nlines
, int **first
, int **last
, int **match
, int **selected
)
4868 struct tog_diff_view_state
*s
= &view
->state
.diff
;
4871 *nlines
= s
->nlines
;
4872 *line_offsets
= NULL
;
4873 *match
= &s
->matched_line
;
4874 *first
= &s
->first_displayed_line
;
4875 *last
= &s
->last_displayed_line
;
4876 *selected
= &s
->selected_line
;
4879 static const struct got_error
*
4880 search_next_view_match(struct tog_view
*view
)
4882 const struct got_error
*err
= NULL
;
4886 size_t linesize
= 0;
4888 off_t
*line_offsets
;
4890 int *first
, *last
, *match
, *selected
;
4892 if (!view
->search_setup
)
4893 return got_error_msg(GOT_ERR_NOT_IMPL
,
4894 "view search not supported");
4895 view
->search_setup(view
, &f
, &line_offsets
, &nlines
, &first
, &last
,
4898 if (!view
->searching
) {
4899 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
4904 if (view
->searching
== TOG_SEARCH_FORWARD
)
4905 lineno
= *match
+ 1;
4907 lineno
= *match
- 1;
4909 lineno
= *first
- 1 + *selected
;
4914 if (lineno
<= 0 || lineno
> nlines
) {
4916 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
4920 if (view
->searching
== TOG_SEARCH_FORWARD
)
4926 offset
= view
->type
== TOG_VIEW_DIFF
?
4927 view
->state
.diff
.lines
[lineno
- 1].offset
:
4928 line_offsets
[lineno
- 1];
4929 if (fseeko(f
, offset
, SEEK_SET
) != 0) {
4931 return got_error_from_errno("fseeko");
4933 linelen
= getline(&line
, &linesize
, f
);
4934 if (linelen
!= -1) {
4936 err
= expand_tab(&exstr
, line
);
4939 if (match_line(exstr
, &view
->regex
, 1,
4941 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
4948 if (view
->searching
== TOG_SEARCH_FORWARD
)
4963 static const struct got_error
*
4964 close_diff_view(struct tog_view
*view
)
4966 const struct got_error
*err
= NULL
;
4967 struct tog_diff_view_state
*s
= &view
->state
.diff
;
4973 if (s
->f
&& fclose(s
->f
) == EOF
)
4974 err
= got_error_from_errno("fclose");
4976 if (s
->f1
&& fclose(s
->f1
) == EOF
&& err
== NULL
)
4977 err
= got_error_from_errno("fclose");
4979 if (s
->f2
&& fclose(s
->f2
) == EOF
&& err
== NULL
)
4980 err
= got_error_from_errno("fclose");
4982 if (s
->fd1
!= -1 && close(s
->fd1
) == -1 && err
== NULL
)
4983 err
= got_error_from_errno("close");
4985 if (s
->fd2
!= -1 && close(s
->fd2
) == -1 && err
== NULL
)
4986 err
= got_error_from_errno("close");
4994 static const struct got_error
*
4995 open_diff_view(struct tog_view
*view
, struct got_object_id
*id1
,
4996 struct got_object_id
*id2
, const char *label1
, const char *label2
,
4997 int diff_context
, int ignore_whitespace
, int force_text_diff
,
4998 struct tog_view
*parent_view
, struct got_repository
*repo
)
5000 const struct got_error
*err
;
5001 struct tog_diff_view_state
*s
= &view
->state
.diff
;
5003 memset(s
, 0, sizeof(*s
));
5007 if (id1
!= NULL
&& id2
!= NULL
) {
5009 err
= got_object_get_type(&type1
, repo
, id1
);
5012 err
= got_object_get_type(&type2
, repo
, id2
);
5017 return got_error(GOT_ERR_OBJ_TYPE
);
5019 s
->first_displayed_line
= 1;
5020 s
->last_displayed_line
= view
->nlines
;
5021 s
->selected_line
= 1;
5029 s
->id1
= got_object_id_dup(id1
);
5031 return got_error_from_errno("got_object_id_dup");
5035 s
->id2
= got_object_id_dup(id2
);
5036 if (s
->id2
== NULL
) {
5037 err
= got_error_from_errno("got_object_id_dup");
5041 s
->f1
= got_opentemp();
5042 if (s
->f1
== NULL
) {
5043 err
= got_error_from_errno("got_opentemp");
5047 s
->f2
= got_opentemp();
5048 if (s
->f2
== NULL
) {
5049 err
= got_error_from_errno("got_opentemp");
5053 s
->fd1
= got_opentempfd();
5055 err
= got_error_from_errno("got_opentempfd");
5059 s
->fd2
= got_opentempfd();
5061 err
= got_error_from_errno("got_opentempfd");
5065 s
->first_displayed_line
= 1;
5066 s
->last_displayed_line
= view
->nlines
;
5067 s
->diff_context
= diff_context
;
5068 s
->ignore_whitespace
= ignore_whitespace
;
5069 s
->force_text_diff
= force_text_diff
;
5070 s
->parent_view
= parent_view
;
5073 if (has_colors() && getenv("TOG_COLORS") != NULL
) {
5076 rc
= init_pair(GOT_DIFF_LINE_MINUS
,
5077 get_color_value("TOG_COLOR_DIFF_MINUS"), -1);
5079 rc
= init_pair(GOT_DIFF_LINE_PLUS
,
5080 get_color_value("TOG_COLOR_DIFF_PLUS"), -1);
5082 rc
= init_pair(GOT_DIFF_LINE_HUNK
,
5083 get_color_value("TOG_COLOR_DIFF_CHUNK_HEADER"), -1);
5085 rc
= init_pair(GOT_DIFF_LINE_META
,
5086 get_color_value("TOG_COLOR_DIFF_META"), -1);
5088 rc
= init_pair(GOT_DIFF_LINE_CHANGES
,
5089 get_color_value("TOG_COLOR_DIFF_META"), -1);
5091 rc
= init_pair(GOT_DIFF_LINE_BLOB_MIN
,
5092 get_color_value("TOG_COLOR_DIFF_META"), -1);
5094 rc
= init_pair(GOT_DIFF_LINE_BLOB_PLUS
,
5095 get_color_value("TOG_COLOR_DIFF_META"), -1);
5097 rc
= init_pair(GOT_DIFF_LINE_AUTHOR
,
5098 get_color_value("TOG_COLOR_AUTHOR"), -1);
5100 rc
= init_pair(GOT_DIFF_LINE_DATE
,
5101 get_color_value("TOG_COLOR_DATE"), -1);
5103 err
= got_error(GOT_ERR_RANGE
);
5108 if (parent_view
&& parent_view
->type
== TOG_VIEW_LOG
&&
5109 view_is_splitscreen(view
))
5110 show_log_view(parent_view
); /* draw border */
5111 diff_view_indicate_progress(view
);
5113 err
= create_diff(s
);
5115 view
->show
= show_diff_view
;
5116 view
->input
= input_diff_view
;
5117 view
->reset
= reset_diff_view
;
5118 view
->close
= close_diff_view
;
5119 view
->search_start
= search_start_diff_view
;
5120 view
->search_setup
= search_setup_diff_view
;
5121 view
->search_next
= search_next_view_match
;
5124 close_diff_view(view
);
5128 static const struct got_error
*
5129 show_diff_view(struct tog_view
*view
)
5131 const struct got_error
*err
;
5132 struct tog_diff_view_state
*s
= &view
->state
.diff
;
5133 char *id_str1
= NULL
, *id_str2
, *header
;
5134 const char *label1
, *label2
;
5137 err
= got_object_id_str(&id_str1
, s
->id1
);
5140 label1
= s
->label1
? s
->label1
: id_str1
;
5142 label1
= "/dev/null";
5144 err
= got_object_id_str(&id_str2
, s
->id2
);
5147 label2
= s
->label2
? s
->label2
: id_str2
;
5149 if (asprintf(&header
, "diff %s %s", label1
, label2
) == -1) {
5150 err
= got_error_from_errno("asprintf");
5158 err
= draw_file(view
, header
);
5163 static const struct got_error
*
5164 set_selected_commit(struct tog_diff_view_state
*s
,
5165 struct commit_queue_entry
*entry
)
5167 const struct got_error
*err
;
5168 const struct got_object_id_queue
*parent_ids
;
5169 struct got_commit_object
*selected_commit
;
5170 struct got_object_qid
*pid
;
5173 s
->id2
= got_object_id_dup(entry
->id
);
5175 return got_error_from_errno("got_object_id_dup");
5177 err
= got_object_open_as_commit(&selected_commit
, s
->repo
, entry
->id
);
5180 parent_ids
= got_object_commit_get_parent_ids(selected_commit
);
5182 pid
= STAILQ_FIRST(parent_ids
);
5183 s
->id1
= pid
? got_object_id_dup(&pid
->id
) : NULL
;
5184 got_object_commit_close(selected_commit
);
5188 static const struct got_error
*
5189 reset_diff_view(struct tog_view
*view
)
5191 struct tog_diff_view_state
*s
= &view
->state
.diff
;
5194 wclear(view
->window
);
5195 s
->first_displayed_line
= 1;
5196 s
->last_displayed_line
= view
->nlines
;
5197 s
->matched_line
= 0;
5198 diff_view_indicate_progress(view
);
5199 return create_diff(s
);
5203 diff_prev_index(struct tog_diff_view_state
*s
, enum got_diff_line_type type
)
5207 i
= start
= s
->first_displayed_line
- 1;
5209 while (s
->lines
[i
].type
!= type
) {
5213 return; /* do nothing, requested type not in file */
5216 s
->selected_line
= 1;
5217 s
->first_displayed_line
= i
;
5221 diff_next_index(struct tog_diff_view_state
*s
, enum got_diff_line_type type
)
5225 i
= start
= s
->first_displayed_line
+ 1;
5227 while (s
->lines
[i
].type
!= type
) {
5228 if (i
== s
->nlines
- 1)
5231 return; /* do nothing, requested type not in file */
5234 s
->selected_line
= 1;
5235 s
->first_displayed_line
= i
;
5238 static struct got_object_id
*get_selected_commit_id(struct tog_blame_line
*,
5240 static struct got_object_id
*get_annotation_for_line(struct tog_blame_line
*,
5243 static const struct got_error
*
5244 input_diff_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
5246 const struct got_error
*err
= NULL
;
5247 struct tog_diff_view_state
*s
= &view
->state
.diff
;
5248 struct tog_log_view_state
*ls
;
5249 struct commit_queue_entry
*old_selected_entry
;
5251 size_t linesize
= 0;
5253 int i
, nscroll
= view
->nlines
- 1, up
= 0;
5255 s
->lineno
= s
->first_displayed_line
- 1 + s
->selected_line
;
5262 view
->x
= MAX(view
->maxx
- view
->ncols
/ 3, 0);
5267 if (view
->x
+ view
->ncols
/ 3 < view
->maxx
)
5268 view
->x
+= 2; /* move two columns right */
5274 view
->x
-= MIN(view
->x
, 2); /* move two columns back */
5281 s
->force_text_diff
= !s
->force_text_diff
;
5283 s
->ignore_whitespace
= !s
->ignore_whitespace
;
5284 err
= reset_diff_view(view
);
5288 s
->first_displayed_line
= 1;
5297 s
->first_displayed_line
= (s
->nlines
- view
->nlines
) + 2;
5303 if (s
->first_displayed_line
> 1)
5304 s
->first_displayed_line
--;
5315 if (s
->first_displayed_line
== 1) {
5320 while (i
++ < nscroll
&& s
->first_displayed_line
> 1)
5321 s
->first_displayed_line
--;
5327 s
->first_displayed_line
++;
5344 while (!s
->eof
&& i
++ < nscroll
) {
5345 linelen
= getline(&line
, &linesize
, s
->f
);
5346 s
->first_displayed_line
++;
5347 if (linelen
== -1) {
5351 err
= got_ferror(s
->f
, GOT_ERR_IO
);
5358 diff_prev_index(s
, GOT_DIFF_LINE_BLOB_MIN
);
5361 diff_next_index(s
, GOT_DIFF_LINE_BLOB_MIN
);
5364 diff_prev_index(s
, GOT_DIFF_LINE_HUNK
);
5367 diff_next_index(s
, GOT_DIFF_LINE_HUNK
);
5370 if (s
->diff_context
> 0) {
5372 s
->matched_line
= 0;
5373 diff_view_indicate_progress(view
);
5374 err
= create_diff(s
);
5375 if (s
->first_displayed_line
+ view
->nlines
- 1 >
5377 s
->first_displayed_line
= 1;
5378 s
->last_displayed_line
= view
->nlines
;
5384 if (s
->diff_context
< GOT_DIFF_MAX_CONTEXT
) {
5386 s
->matched_line
= 0;
5387 diff_view_indicate_progress(view
);
5388 err
= create_diff(s
);
5400 if (s
->parent_view
== NULL
) {
5404 s
->parent_view
->count
= view
->count
;
5406 if (s
->parent_view
->type
== TOG_VIEW_LOG
) {
5407 ls
= &s
->parent_view
->state
.log
;
5408 old_selected_entry
= ls
->selected_entry
;
5410 err
= input_log_view(NULL
, s
->parent_view
,
5411 up
? KEY_UP
: KEY_DOWN
);
5414 view
->count
= s
->parent_view
->count
;
5416 if (old_selected_entry
== ls
->selected_entry
)
5419 err
= set_selected_commit(s
, ls
->selected_entry
);
5422 } else if (s
->parent_view
->type
== TOG_VIEW_BLAME
) {
5423 struct tog_blame_view_state
*bs
;
5424 struct got_object_id
*id
, *prev_id
;
5426 bs
= &s
->parent_view
->state
.blame
;
5427 prev_id
= get_annotation_for_line(bs
->blame
.lines
,
5428 bs
->blame
.nlines
, bs
->last_diffed_line
);
5430 err
= input_blame_view(&view
, s
->parent_view
,
5431 up
? KEY_UP
: KEY_DOWN
);
5434 view
->count
= s
->parent_view
->count
;
5436 if (prev_id
== NULL
)
5438 id
= get_selected_commit_id(bs
->blame
.lines
,
5439 bs
->blame
.nlines
, bs
->first_displayed_line
,
5444 if (!got_object_id_cmp(prev_id
, id
))
5447 err
= input_blame_view(&view
, s
->parent_view
, KEY_ENTER
);
5451 s
->first_displayed_line
= 1;
5452 s
->last_displayed_line
= view
->nlines
;
5453 s
->matched_line
= 0;
5456 diff_view_indicate_progress(view
);
5457 err
= create_diff(s
);
5467 static const struct got_error
*
5468 cmd_diff(int argc
, char *argv
[])
5470 const struct got_error
*error
= NULL
;
5471 struct got_repository
*repo
= NULL
;
5472 struct got_worktree
*worktree
= NULL
;
5473 struct got_object_id
*id1
= NULL
, *id2
= NULL
;
5474 char *repo_path
= NULL
, *cwd
= NULL
;
5475 char *id_str1
= NULL
, *id_str2
= NULL
;
5476 char *label1
= NULL
, *label2
= NULL
;
5477 int diff_context
= 3, ignore_whitespace
= 0;
5478 int ch
, force_text_diff
= 0;
5480 struct tog_view
*view
;
5481 int *pack_fds
= NULL
;
5483 while ((ch
= getopt(argc
, argv
, "aC:r:w")) != -1) {
5486 force_text_diff
= 1;
5489 diff_context
= strtonum(optarg
, 0, GOT_DIFF_MAX_CONTEXT
,
5492 errx(1, "number of context lines is %s: %s",
5496 repo_path
= realpath(optarg
, NULL
);
5497 if (repo_path
== NULL
)
5498 return got_error_from_errno2("realpath",
5500 got_path_strip_trailing_slashes(repo_path
);
5503 ignore_whitespace
= 1;
5515 usage_diff(); /* TODO show local worktree changes */
5516 } else if (argc
== 2) {
5522 error
= got_repo_pack_fds_open(&pack_fds
);
5526 if (repo_path
== NULL
) {
5527 cwd
= getcwd(NULL
, 0);
5529 return got_error_from_errno("getcwd");
5530 error
= got_worktree_open(&worktree
, cwd
);
5531 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
5535 strdup(got_worktree_get_repo_path(worktree
));
5537 repo_path
= strdup(cwd
);
5538 if (repo_path
== NULL
) {
5539 error
= got_error_from_errno("strdup");
5544 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
5550 error
= apply_unveil(got_repo_get_path(repo
), NULL
);
5554 error
= tog_load_refs(repo
, 0);
5558 error
= got_repo_match_object_id(&id1
, &label1
, id_str1
,
5559 GOT_OBJ_TYPE_ANY
, &tog_refs
, repo
);
5563 error
= got_repo_match_object_id(&id2
, &label2
, id_str2
,
5564 GOT_OBJ_TYPE_ANY
, &tog_refs
, repo
);
5568 view
= view_open(0, 0, 0, 0, TOG_VIEW_DIFF
);
5570 error
= got_error_from_errno("view_open");
5573 error
= open_diff_view(view
, id1
, id2
, label1
, label2
, diff_context
,
5574 ignore_whitespace
, force_text_diff
, NULL
, repo
);
5577 error
= view_loop(view
);
5584 const struct got_error
*close_err
= got_repo_close(repo
);
5589 got_worktree_close(worktree
);
5591 const struct got_error
*pack_err
=
5592 got_repo_pack_fds_close(pack_fds
);
5605 "usage: %s blame [-c commit] [-r repository-path] path\n",
5610 struct tog_blame_line
{
5612 struct got_object_id
*id
;
5615 static const struct got_error
*
5616 draw_blame(struct tog_view
*view
)
5618 struct tog_blame_view_state
*s
= &view
->state
.blame
;
5619 struct tog_blame
*blame
= &s
->blame
;
5620 regmatch_t
*regmatch
= &view
->regmatch
;
5621 const struct got_error
*err
;
5622 int lineno
= 0, nprinted
= 0;
5624 size_t linesize
= 0;
5628 struct tog_blame_line
*blame_line
;
5629 struct got_object_id
*prev_id
= NULL
;
5631 struct tog_color
*tc
;
5633 err
= got_object_id_str(&id_str
, &s
->blamed_commit
->id
);
5638 werase(view
->window
);
5640 if (asprintf(&line
, "commit %s", id_str
) == -1) {
5641 err
= got_error_from_errno("asprintf");
5646 err
= format_line(&wline
, &width
, NULL
, line
, 0, view
->ncols
, 0, 0);
5651 if (view_needs_focus_indication(view
))
5652 wstandout(view
->window
);
5653 tc
= get_color(&s
->colors
, TOG_COLOR_COMMIT
);
5655 wattr_on(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
5656 waddwstr(view
->window
, wline
);
5657 while (width
++ < view
->ncols
)
5658 waddch(view
->window
, ' ');
5660 wattr_off(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
5661 if (view_needs_focus_indication(view
))
5662 wstandend(view
->window
);
5666 if (view
->gline
> blame
->nlines
)
5667 view
->gline
= blame
->nlines
;
5669 if (asprintf(&line
, "[%d/%d] %s%s", view
->gline
? view
->gline
:
5670 s
->first_displayed_line
- 1 + s
->selected_line
, blame
->nlines
,
5671 s
->blame_complete
? "" : "annotating... ", s
->path
) == -1) {
5673 return got_error_from_errno("asprintf");
5676 err
= format_line(&wline
, &width
, NULL
, line
, 0, view
->ncols
, 0, 0);
5681 waddwstr(view
->window
, wline
);
5684 if (width
< view
->ncols
- 1)
5685 waddch(view
->window
, '\n');
5689 while (nprinted
< view
->nlines
- 2) {
5690 linelen
= getline(&line
, &linesize
, blame
->f
);
5691 if (linelen
== -1) {
5692 if (feof(blame
->f
)) {
5697 return got_ferror(blame
->f
, GOT_ERR_IO
);
5699 if (++lineno
< s
->first_displayed_line
)
5701 if (view
->gline
&& !gotoline(view
, &lineno
, &nprinted
))
5704 /* Set view->maxx based on full line length. */
5705 err
= format_line(&wline
, &width
, NULL
, line
, 0, INT_MAX
, 9, 1);
5712 view
->maxx
= MAX(view
->maxx
, width
);
5714 if (nprinted
== s
->selected_line
- 1)
5715 wstandout(view
->window
);
5717 if (blame
->nlines
> 0) {
5718 blame_line
= &blame
->lines
[lineno
- 1];
5719 if (blame_line
->annotated
&& prev_id
&&
5720 got_object_id_cmp(prev_id
, blame_line
->id
) == 0 &&
5721 !(nprinted
== s
->selected_line
- 1)) {
5722 waddstr(view
->window
, " ");
5723 } else if (blame_line
->annotated
) {
5725 err
= got_object_id_str(&id_str
,
5731 tc
= get_color(&s
->colors
, TOG_COLOR_COMMIT
);
5733 wattr_on(view
->window
,
5734 COLOR_PAIR(tc
->colorpair
), NULL
);
5735 wprintw(view
->window
, "%.8s", id_str
);
5737 wattr_off(view
->window
,
5738 COLOR_PAIR(tc
->colorpair
), NULL
);
5740 prev_id
= blame_line
->id
;
5742 waddstr(view
->window
, "........");
5746 waddstr(view
->window
, "........");
5750 if (nprinted
== s
->selected_line
- 1)
5751 wstandend(view
->window
);
5752 waddstr(view
->window
, " ");
5754 if (view
->ncols
<= 9) {
5756 } else if (s
->first_displayed_line
+ nprinted
==
5758 regmatch
->rm_so
>= 0 && regmatch
->rm_so
< regmatch
->rm_eo
) {
5759 err
= add_matched_line(&width
, line
, view
->ncols
- 9, 9,
5760 view
->window
, view
->x
, regmatch
);
5768 err
= format_line(&wline
, &width
, &skip
, line
,
5769 view
->x
, view
->ncols
- 9, 9, 1);
5774 waddwstr(view
->window
, &wline
[skip
]);
5780 if (width
<= view
->ncols
- 1)
5781 waddch(view
->window
, '\n');
5782 if (++nprinted
== 1)
5783 s
->first_displayed_line
= lineno
;
5786 s
->last_displayed_line
= lineno
;
5793 static const struct got_error
*
5794 blame_cb(void *arg
, int nlines
, int lineno
,
5795 struct got_commit_object
*commit
, struct got_object_id
*id
)
5797 const struct got_error
*err
= NULL
;
5798 struct tog_blame_cb_args
*a
= arg
;
5799 struct tog_blame_line
*line
;
5802 if (nlines
!= a
->nlines
||
5803 (lineno
!= -1 && lineno
< 1) || lineno
> a
->nlines
)
5804 return got_error(GOT_ERR_RANGE
);
5806 errcode
= pthread_mutex_lock(&tog_mutex
);
5808 return got_error_set_errno(errcode
, "pthread_mutex_lock");
5810 if (*a
->quit
) { /* user has quit the blame view */
5811 err
= got_error(GOT_ERR_ITER_COMPLETED
);
5816 goto done
; /* no change in this commit */
5818 line
= &a
->lines
[lineno
- 1];
5819 if (line
->annotated
)
5822 line
->id
= got_object_id_dup(id
);
5823 if (line
->id
== NULL
) {
5824 err
= got_error_from_errno("got_object_id_dup");
5827 line
->annotated
= 1;
5829 errcode
= pthread_mutex_unlock(&tog_mutex
);
5831 err
= got_error_set_errno(errcode
, "pthread_mutex_unlock");
5836 blame_thread(void *arg
)
5838 const struct got_error
*err
, *close_err
;
5839 struct tog_blame_thread_args
*ta
= arg
;
5840 struct tog_blame_cb_args
*a
= ta
->cb_args
;
5841 int errcode
, fd1
= -1, fd2
= -1;
5842 FILE *f1
= NULL
, *f2
= NULL
;
5844 fd1
= got_opentempfd();
5846 return (void *)got_error_from_errno("got_opentempfd");
5848 fd2
= got_opentempfd();
5850 err
= got_error_from_errno("got_opentempfd");
5854 f1
= got_opentemp();
5856 err
= (void *)got_error_from_errno("got_opentemp");
5859 f2
= got_opentemp();
5861 err
= (void *)got_error_from_errno("got_opentemp");
5865 err
= block_signals_used_by_main_thread();
5869 err
= got_blame(ta
->path
, a
->commit_id
, ta
->repo
,
5870 tog_diff_algo
, blame_cb
, ta
->cb_args
,
5871 ta
->cancel_cb
, ta
->cancel_arg
, fd1
, fd2
, f1
, f2
);
5872 if (err
&& err
->code
== GOT_ERR_CANCELLED
)
5875 errcode
= pthread_mutex_lock(&tog_mutex
);
5877 err
= got_error_set_errno(errcode
, "pthread_mutex_lock");
5881 close_err
= got_repo_close(ta
->repo
);
5887 errcode
= pthread_mutex_unlock(&tog_mutex
);
5888 if (errcode
&& err
== NULL
)
5889 err
= got_error_set_errno(errcode
, "pthread_mutex_unlock");
5892 if (fd1
!= -1 && close(fd1
) == -1 && err
== NULL
)
5893 err
= got_error_from_errno("close");
5894 if (fd2
!= -1 && close(fd2
) == -1 && err
== NULL
)
5895 err
= got_error_from_errno("close");
5896 if (f1
&& fclose(f1
) == EOF
&& err
== NULL
)
5897 err
= got_error_from_errno("fclose");
5898 if (f2
&& fclose(f2
) == EOF
&& err
== NULL
)
5899 err
= got_error_from_errno("fclose");
5904 static struct got_object_id
*
5905 get_selected_commit_id(struct tog_blame_line
*lines
, int nlines
,
5906 int first_displayed_line
, int selected_line
)
5908 struct tog_blame_line
*line
;
5913 line
= &lines
[first_displayed_line
- 1 + selected_line
- 1];
5914 if (!line
->annotated
)
5920 static struct got_object_id
*
5921 get_annotation_for_line(struct tog_blame_line
*lines
, int nlines
,
5924 struct tog_blame_line
*line
;
5926 if (nlines
<= 0 || lineno
>= nlines
)
5929 line
= &lines
[lineno
- 1];
5930 if (!line
->annotated
)
5936 static const struct got_error
*
5937 stop_blame(struct tog_blame
*blame
)
5939 const struct got_error
*err
= NULL
;
5942 if (blame
->thread
) {
5944 errcode
= pthread_mutex_unlock(&tog_mutex
);
5946 return got_error_set_errno(errcode
,
5947 "pthread_mutex_unlock");
5948 errcode
= pthread_join(blame
->thread
, (void **)&err
);
5950 return got_error_set_errno(errcode
, "pthread_join");
5951 errcode
= pthread_mutex_lock(&tog_mutex
);
5953 return got_error_set_errno(errcode
,
5954 "pthread_mutex_lock");
5955 if (err
&& err
->code
== GOT_ERR_ITER_COMPLETED
)
5957 blame
->thread
= 0; //NULL;
5959 if (blame
->thread_args
.repo
) {
5960 const struct got_error
*close_err
;
5961 close_err
= got_repo_close(blame
->thread_args
.repo
);
5964 blame
->thread_args
.repo
= NULL
;
5967 if (fclose(blame
->f
) == EOF
&& err
== NULL
)
5968 err
= got_error_from_errno("fclose");
5972 for (i
= 0; i
< blame
->nlines
; i
++)
5973 free(blame
->lines
[i
].id
);
5975 blame
->lines
= NULL
;
5977 free(blame
->cb_args
.commit_id
);
5978 blame
->cb_args
.commit_id
= NULL
;
5979 if (blame
->pack_fds
) {
5980 const struct got_error
*pack_err
=
5981 got_repo_pack_fds_close(blame
->pack_fds
);
5984 blame
->pack_fds
= NULL
;
5989 static const struct got_error
*
5990 cancel_blame_view(void *arg
)
5992 const struct got_error
*err
= NULL
;
5996 errcode
= pthread_mutex_lock(&tog_mutex
);
5998 return got_error_set_errno(errcode
,
5999 "pthread_mutex_unlock");
6002 err
= got_error(GOT_ERR_CANCELLED
);
6004 errcode
= pthread_mutex_unlock(&tog_mutex
);
6006 return got_error_set_errno(errcode
,
6007 "pthread_mutex_lock");
6012 static const struct got_error
*
6013 run_blame(struct tog_view
*view
)
6015 struct tog_blame_view_state
*s
= &view
->state
.blame
;
6016 struct tog_blame
*blame
= &s
->blame
;
6017 const struct got_error
*err
= NULL
;
6018 struct got_commit_object
*commit
= NULL
;
6019 struct got_blob_object
*blob
= NULL
;
6020 struct got_repository
*thread_repo
= NULL
;
6021 struct got_object_id
*obj_id
= NULL
;
6022 int obj_type
, fd
= -1;
6023 int *pack_fds
= NULL
;
6025 err
= got_object_open_as_commit(&commit
, s
->repo
,
6026 &s
->blamed_commit
->id
);
6030 fd
= got_opentempfd();
6032 err
= got_error_from_errno("got_opentempfd");
6036 err
= got_object_id_by_path(&obj_id
, s
->repo
, commit
, s
->path
);
6040 err
= got_object_get_type(&obj_type
, s
->repo
, obj_id
);
6044 if (obj_type
!= GOT_OBJ_TYPE_BLOB
) {
6045 err
= got_error(GOT_ERR_OBJ_TYPE
);
6049 err
= got_object_open_as_blob(&blob
, s
->repo
, obj_id
, 8192, fd
);
6052 blame
->f
= got_opentemp();
6053 if (blame
->f
== NULL
) {
6054 err
= got_error_from_errno("got_opentemp");
6057 err
= got_object_blob_dump_to_file(&blame
->filesize
, &blame
->nlines
,
6058 &blame
->line_offsets
, blame
->f
, blob
);
6061 if (blame
->nlines
== 0) {
6062 s
->blame_complete
= 1;
6066 /* Don't include \n at EOF in the blame line count. */
6067 if (blame
->line_offsets
[blame
->nlines
- 1] == blame
->filesize
)
6070 blame
->lines
= calloc(blame
->nlines
, sizeof(*blame
->lines
));
6071 if (blame
->lines
== NULL
) {
6072 err
= got_error_from_errno("calloc");
6076 err
= got_repo_pack_fds_open(&pack_fds
);
6079 err
= got_repo_open(&thread_repo
, got_repo_get_path(s
->repo
), NULL
,
6084 blame
->pack_fds
= pack_fds
;
6085 blame
->cb_args
.view
= view
;
6086 blame
->cb_args
.lines
= blame
->lines
;
6087 blame
->cb_args
.nlines
= blame
->nlines
;
6088 blame
->cb_args
.commit_id
= got_object_id_dup(&s
->blamed_commit
->id
);
6089 if (blame
->cb_args
.commit_id
== NULL
) {
6090 err
= got_error_from_errno("got_object_id_dup");
6093 blame
->cb_args
.quit
= &s
->done
;
6095 blame
->thread_args
.path
= s
->path
;
6096 blame
->thread_args
.repo
= thread_repo
;
6097 blame
->thread_args
.cb_args
= &blame
->cb_args
;
6098 blame
->thread_args
.complete
= &s
->blame_complete
;
6099 blame
->thread_args
.cancel_cb
= cancel_blame_view
;
6100 blame
->thread_args
.cancel_arg
= &s
->done
;
6101 s
->blame_complete
= 0;
6103 if (s
->first_displayed_line
+ view
->nlines
- 1 > blame
->nlines
) {
6104 s
->first_displayed_line
= 1;
6105 s
->last_displayed_line
= view
->nlines
;
6106 s
->selected_line
= 1;
6108 s
->matched_line
= 0;
6112 got_object_commit_close(commit
);
6113 if (fd
!= -1 && close(fd
) == -1 && err
== NULL
)
6114 err
= got_error_from_errno("close");
6116 got_object_blob_close(blob
);
6123 static const struct got_error
*
6124 open_blame_view(struct tog_view
*view
, char *path
,
6125 struct got_object_id
*commit_id
, struct got_repository
*repo
)
6127 const struct got_error
*err
= NULL
;
6128 struct tog_blame_view_state
*s
= &view
->state
.blame
;
6130 STAILQ_INIT(&s
->blamed_commits
);
6132 s
->path
= strdup(path
);
6133 if (s
->path
== NULL
)
6134 return got_error_from_errno("strdup");
6136 err
= got_object_qid_alloc(&s
->blamed_commit
, commit_id
);
6142 STAILQ_INSERT_HEAD(&s
->blamed_commits
, s
->blamed_commit
, entry
);
6143 s
->first_displayed_line
= 1;
6144 s
->last_displayed_line
= view
->nlines
;
6145 s
->selected_line
= 1;
6146 s
->blame_complete
= 0;
6148 s
->commit_id
= commit_id
;
6149 memset(&s
->blame
, 0, sizeof(s
->blame
));
6151 STAILQ_INIT(&s
->colors
);
6152 if (has_colors() && getenv("TOG_COLORS") != NULL
) {
6153 err
= add_color(&s
->colors
, "^", TOG_COLOR_COMMIT
,
6154 get_color_value("TOG_COLOR_COMMIT"));
6159 view
->show
= show_blame_view
;
6160 view
->input
= input_blame_view
;
6161 view
->reset
= reset_blame_view
;
6162 view
->close
= close_blame_view
;
6163 view
->search_start
= search_start_blame_view
;
6164 view
->search_setup
= search_setup_blame_view
;
6165 view
->search_next
= search_next_view_match
;
6167 return run_blame(view
);
6170 static const struct got_error
*
6171 close_blame_view(struct tog_view
*view
)
6173 const struct got_error
*err
= NULL
;
6174 struct tog_blame_view_state
*s
= &view
->state
.blame
;
6176 if (s
->blame
.thread
)
6177 err
= stop_blame(&s
->blame
);
6179 while (!STAILQ_EMPTY(&s
->blamed_commits
)) {
6180 struct got_object_qid
*blamed_commit
;
6181 blamed_commit
= STAILQ_FIRST(&s
->blamed_commits
);
6182 STAILQ_REMOVE_HEAD(&s
->blamed_commits
, entry
);
6183 got_object_qid_free(blamed_commit
);
6187 free_colors(&s
->colors
);
6191 static const struct got_error
*
6192 search_start_blame_view(struct tog_view
*view
)
6194 struct tog_blame_view_state
*s
= &view
->state
.blame
;
6196 s
->matched_line
= 0;
6201 search_setup_blame_view(struct tog_view
*view
, FILE **f
, off_t
**line_offsets
,
6202 size_t *nlines
, int **first
, int **last
, int **match
, int **selected
)
6204 struct tog_blame_view_state
*s
= &view
->state
.blame
;
6207 *nlines
= s
->blame
.nlines
;
6208 *line_offsets
= s
->blame
.line_offsets
;
6209 *match
= &s
->matched_line
;
6210 *first
= &s
->first_displayed_line
;
6211 *last
= &s
->last_displayed_line
;
6212 *selected
= &s
->selected_line
;
6215 static const struct got_error
*
6216 show_blame_view(struct tog_view
*view
)
6218 const struct got_error
*err
= NULL
;
6219 struct tog_blame_view_state
*s
= &view
->state
.blame
;
6222 if (s
->blame
.thread
== 0 && !s
->blame_complete
) {
6223 errcode
= pthread_create(&s
->blame
.thread
, NULL
, blame_thread
,
6224 &s
->blame
.thread_args
);
6226 return got_error_set_errno(errcode
, "pthread_create");
6228 halfdelay(1); /* fast refresh while annotating */
6231 if (s
->blame_complete
)
6232 halfdelay(10); /* disable fast refresh */
6234 err
= draw_blame(view
);
6240 static const struct got_error
*
6241 log_annotated_line(struct tog_view
**new_view
, int begin_y
, int begin_x
,
6242 struct got_repository
*repo
, struct got_object_id
*id
)
6244 struct tog_view
*log_view
;
6245 const struct got_error
*err
= NULL
;
6249 log_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_LOG
);
6250 if (log_view
== NULL
)
6251 return got_error_from_errno("view_open");
6253 err
= open_log_view(log_view
, id
, repo
, GOT_REF_HEAD
, "", 0);
6255 view_close(log_view
);
6257 *new_view
= log_view
;
6262 static const struct got_error
*
6263 input_blame_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
6265 const struct got_error
*err
= NULL
, *thread_err
= NULL
;
6266 struct tog_view
*diff_view
;
6267 struct tog_blame_view_state
*s
= &view
->state
.blame
;
6268 int eos
, nscroll
, begin_y
= 0, begin_x
= 0;
6270 eos
= nscroll
= view
->nlines
- 2;
6271 if (view_is_hsplit_top(view
))
6279 view
->x
= MAX(view
->maxx
- view
->ncols
/ 3, 0);
6284 if (view
->x
+ view
->ncols
/ 3 < view
->maxx
)
6285 view
->x
+= 2; /* move two columns right */
6291 view
->x
-= MIN(view
->x
, 2); /* move two columns back */
6300 s
->selected_line
= 1;
6301 s
->first_displayed_line
= 1;
6306 if (s
->blame
.nlines
< eos
) {
6307 s
->selected_line
= s
->blame
.nlines
;
6308 s
->first_displayed_line
= 1;
6310 s
->selected_line
= eos
;
6311 s
->first_displayed_line
= s
->blame
.nlines
- (eos
- 1);
6318 if (s
->selected_line
> 1)
6320 else if (s
->selected_line
== 1 &&
6321 s
->first_displayed_line
> 1)
6322 s
->first_displayed_line
--;
6333 if (s
->first_displayed_line
== 1) {
6334 if (view
->count
> 1)
6336 s
->selected_line
= MAX(1, s
->selected_line
- nscroll
);
6340 if (s
->first_displayed_line
> nscroll
)
6341 s
->first_displayed_line
-= nscroll
;
6343 s
->first_displayed_line
= 1;
6348 if (s
->selected_line
< eos
&& s
->first_displayed_line
+
6349 s
->selected_line
<= s
->blame
.nlines
)
6351 else if (s
->first_displayed_line
< s
->blame
.nlines
- (eos
- 1))
6352 s
->first_displayed_line
++;
6358 struct got_object_id
*id
= NULL
;
6361 id
= get_selected_commit_id(s
->blame
.lines
, s
->blame
.nlines
,
6362 s
->first_displayed_line
, s
->selected_line
);
6366 struct got_commit_object
*commit
, *pcommit
;
6367 struct got_object_qid
*pid
;
6368 struct got_object_id
*blob_id
= NULL
;
6370 err
= got_object_open_as_commit(&commit
,
6375 got_object_commit_get_parent_ids(commit
));
6377 got_object_commit_close(commit
);
6380 /* Check if path history ends here. */
6381 err
= got_object_open_as_commit(&pcommit
,
6385 err
= got_object_id_by_path(&blob_id
, s
->repo
,
6387 got_object_commit_close(pcommit
);
6389 if (err
->code
== GOT_ERR_NO_TREE_ENTRY
)
6391 got_object_commit_close(commit
);
6394 err
= got_object_get_type(&obj_type
, s
->repo
,
6397 /* Can't blame non-blob type objects. */
6398 if (obj_type
!= GOT_OBJ_TYPE_BLOB
) {
6399 got_object_commit_close(commit
);
6402 err
= got_object_qid_alloc(&s
->blamed_commit
,
6404 got_object_commit_close(commit
);
6406 if (got_object_id_cmp(id
,
6407 &s
->blamed_commit
->id
) == 0)
6409 err
= got_object_qid_alloc(&s
->blamed_commit
,
6415 thread_err
= stop_blame(&s
->blame
);
6419 STAILQ_INSERT_HEAD(&s
->blamed_commits
,
6420 s
->blamed_commit
, entry
);
6421 err
= run_blame(view
);
6427 struct got_object_qid
*first
;
6430 first
= STAILQ_FIRST(&s
->blamed_commits
);
6431 if (!got_object_id_cmp(&first
->id
, s
->commit_id
))
6434 thread_err
= stop_blame(&s
->blame
);
6438 STAILQ_REMOVE_HEAD(&s
->blamed_commits
, entry
);
6439 got_object_qid_free(s
->blamed_commit
);
6441 STAILQ_FIRST(&s
->blamed_commits
);
6442 err
= run_blame(view
);
6449 s
->id_to_log
= get_selected_commit_id(s
->blame
.lines
,
6450 s
->blame
.nlines
, s
->first_displayed_line
, s
->selected_line
);
6452 err
= view_request_new(new_view
, view
, TOG_VIEW_LOG
);
6456 struct got_object_id
*id
= NULL
;
6457 struct got_object_qid
*pid
;
6458 struct got_commit_object
*commit
= NULL
;
6461 id
= get_selected_commit_id(s
->blame
.lines
, s
->blame
.nlines
,
6462 s
->first_displayed_line
, s
->selected_line
);
6465 err
= got_object_open_as_commit(&commit
, s
->repo
, id
);
6468 pid
= STAILQ_FIRST(got_object_commit_get_parent_ids(commit
));
6470 /* traversed from diff view, release diff resources */
6471 err
= close_diff_view(*new_view
);
6474 diff_view
= *new_view
;
6476 if (view_is_parent_view(view
))
6477 view_get_split(view
, &begin_y
, &begin_x
);
6479 diff_view
= view_open(0, 0, begin_y
, begin_x
,
6481 if (diff_view
== NULL
) {
6482 got_object_commit_close(commit
);
6483 err
= got_error_from_errno("view_open");
6487 err
= open_diff_view(diff_view
, pid
? &pid
->id
: NULL
,
6488 id
, NULL
, NULL
, 3, 0, 0, view
, s
->repo
);
6489 got_object_commit_close(commit
);
6491 view_close(diff_view
);
6494 s
->last_diffed_line
= s
->first_displayed_line
- 1 +
6497 break; /* still open from active diff view */
6498 if (view_is_parent_view(view
) &&
6499 view
->mode
== TOG_VIEW_SPLIT_HRZN
) {
6500 err
= view_init_hsplit(view
, begin_y
);
6506 diff_view
->focussed
= 1;
6507 diff_view
->mode
= view
->mode
;
6508 diff_view
->nlines
= view
->lines
- begin_y
;
6509 if (view_is_parent_view(view
)) {
6510 view_transfer_size(diff_view
, view
);
6511 err
= view_close_child(view
);
6514 err
= view_set_child(view
, diff_view
);
6517 view
->focus_child
= 1;
6519 *new_view
= diff_view
;
6532 if (s
->last_displayed_line
>= s
->blame
.nlines
&&
6533 s
->selected_line
>= MIN(s
->blame
.nlines
,
6534 view
->nlines
- 2)) {
6538 if (s
->last_displayed_line
>= s
->blame
.nlines
&&
6539 s
->selected_line
< view
->nlines
- 2) {
6541 MIN(nscroll
, s
->last_displayed_line
-
6542 s
->first_displayed_line
- s
->selected_line
+ 1);
6544 if (s
->last_displayed_line
+ nscroll
<= s
->blame
.nlines
)
6545 s
->first_displayed_line
+= nscroll
;
6547 s
->first_displayed_line
=
6548 s
->blame
.nlines
- (view
->nlines
- 3);
6551 if (s
->selected_line
> view
->nlines
- 2) {
6552 s
->selected_line
= MIN(s
->blame
.nlines
,
6560 return thread_err
? thread_err
: err
;
6563 static const struct got_error
*
6564 reset_blame_view(struct tog_view
*view
)
6566 const struct got_error
*err
;
6567 struct tog_blame_view_state
*s
= &view
->state
.blame
;
6571 err
= stop_blame(&s
->blame
);
6575 return run_blame(view
);
6578 static const struct got_error
*
6579 cmd_blame(int argc
, char *argv
[])
6581 const struct got_error
*error
;
6582 struct got_repository
*repo
= NULL
;
6583 struct got_worktree
*worktree
= NULL
;
6584 char *cwd
= NULL
, *repo_path
= NULL
, *in_repo_path
= NULL
;
6585 char *link_target
= NULL
;
6586 struct got_object_id
*commit_id
= NULL
;
6587 struct got_commit_object
*commit
= NULL
;
6588 char *commit_id_str
= NULL
;
6590 struct tog_view
*view
;
6591 int *pack_fds
= NULL
;
6593 while ((ch
= getopt(argc
, argv
, "c:r:")) != -1) {
6596 commit_id_str
= optarg
;
6599 repo_path
= realpath(optarg
, NULL
);
6600 if (repo_path
== NULL
)
6601 return got_error_from_errno2("realpath",
6616 error
= got_repo_pack_fds_open(&pack_fds
);
6620 if (repo_path
== NULL
) {
6621 cwd
= getcwd(NULL
, 0);
6623 return got_error_from_errno("getcwd");
6624 error
= got_worktree_open(&worktree
, cwd
);
6625 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
6629 strdup(got_worktree_get_repo_path(worktree
));
6631 repo_path
= strdup(cwd
);
6632 if (repo_path
== NULL
) {
6633 error
= got_error_from_errno("strdup");
6638 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
6642 error
= get_in_repo_path_from_argv0(&in_repo_path
, argc
, argv
, repo
,
6649 error
= apply_unveil(got_repo_get_path(repo
), NULL
);
6653 error
= tog_load_refs(repo
, 0);
6657 if (commit_id_str
== NULL
) {
6658 struct got_reference
*head_ref
;
6659 error
= got_ref_open(&head_ref
, repo
, worktree
?
6660 got_worktree_get_head_ref_name(worktree
) : GOT_REF_HEAD
, 0);
6663 error
= got_ref_resolve(&commit_id
, repo
, head_ref
);
6664 got_ref_close(head_ref
);
6666 error
= got_repo_match_object_id(&commit_id
, NULL
,
6667 commit_id_str
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
6672 view
= view_open(0, 0, 0, 0, TOG_VIEW_BLAME
);
6674 error
= got_error_from_errno("view_open");
6678 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
6682 error
= got_object_resolve_symlinks(&link_target
, in_repo_path
,
6687 error
= open_blame_view(view
, link_target
? link_target
: in_repo_path
,
6692 /* Release work tree lock. */
6693 got_worktree_close(worktree
);
6696 error
= view_loop(view
);
6704 got_object_commit_close(commit
);
6706 got_worktree_close(worktree
);
6708 const struct got_error
*close_err
= got_repo_close(repo
);
6713 const struct got_error
*pack_err
=
6714 got_repo_pack_fds_close(pack_fds
);
6722 static const struct got_error
*
6723 draw_tree_entries(struct tog_view
*view
, const char *parent_path
)
6725 struct tog_tree_view_state
*s
= &view
->state
.tree
;
6726 const struct got_error
*err
= NULL
;
6727 struct got_tree_entry
*te
;
6730 struct tog_color
*tc
;
6731 int width
, n
, nentries
, i
= 1;
6732 int limit
= view
->nlines
;
6735 if (view_is_hsplit_top(view
))
6736 --limit
; /* border */
6738 werase(view
->window
);
6743 err
= format_line(&wline
, &width
, NULL
, s
->tree_label
, 0, view
->ncols
,
6747 if (view_needs_focus_indication(view
))
6748 wstandout(view
->window
);
6749 tc
= get_color(&s
->colors
, TOG_COLOR_COMMIT
);
6751 wattr_on(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
6752 waddwstr(view
->window
, wline
);
6755 while (width
++ < view
->ncols
)
6756 waddch(view
->window
, ' ');
6758 wattr_off(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
6759 if (view_needs_focus_indication(view
))
6760 wstandend(view
->window
);
6765 if (s
->first_displayed_entry
) {
6766 i
+= got_tree_entry_get_index(s
->first_displayed_entry
);
6767 if (s
->tree
!= s
->root
)
6768 ++i
; /* account for ".." entry */
6770 nentries
= got_object_tree_get_nentries(s
->tree
);
6771 if (asprintf(&index
, "[%d/%d] %s",
6772 i
, nentries
+ (s
->tree
== s
->root
? 0 : 1), parent_path
) == -1)
6773 return got_error_from_errno("asprintf");
6774 err
= format_line(&wline
, &width
, NULL
, index
, 0, view
->ncols
, 0, 0);
6778 waddwstr(view
->window
, wline
);
6781 if (width
< view
->ncols
- 1)
6782 waddch(view
->window
, '\n');
6785 waddch(view
->window
, '\n');
6789 if (s
->first_displayed_entry
== NULL
) {
6790 te
= got_object_tree_get_first_entry(s
->tree
);
6791 if (s
->selected
== 0) {
6793 wstandout(view
->window
);
6794 s
->selected_entry
= NULL
;
6796 waddstr(view
->window
, " ..\n"); /* parent directory */
6797 if (s
->selected
== 0 && view
->focussed
)
6798 wstandend(view
->window
);
6805 te
= s
->first_displayed_entry
;
6808 for (i
= got_tree_entry_get_index(te
); i
< nentries
; i
++) {
6809 char *line
= NULL
, *id_str
= NULL
, *link_target
= NULL
;
6810 const char *modestr
= "";
6813 te
= got_object_tree_get_entry(s
->tree
, i
);
6814 mode
= got_tree_entry_get_mode(te
);
6817 err
= got_object_id_str(&id_str
,
6818 got_tree_entry_get_id(te
));
6820 return got_error_from_errno(
6821 "got_object_id_str");
6823 if (got_object_tree_entry_is_submodule(te
))
6825 else if (S_ISLNK(mode
)) {
6828 err
= got_tree_entry_get_symlink_target(&link_target
,
6834 for (i
= 0; i
< strlen(link_target
); i
++) {
6835 if (!isprint((unsigned char)link_target
[i
]))
6836 link_target
[i
] = '?';
6840 else if (S_ISDIR(mode
))
6842 else if (mode
& S_IXUSR
)
6844 if (asprintf(&line
, "%s %s%s%s%s", id_str
? id_str
: "",
6845 got_tree_entry_get_name(te
), modestr
,
6846 link_target
? " -> ": "",
6847 link_target
? link_target
: "") == -1) {
6850 return got_error_from_errno("asprintf");
6854 err
= format_line(&wline
, &width
, NULL
, line
, 0, view
->ncols
,
6860 if (n
== s
->selected
) {
6862 wstandout(view
->window
);
6863 s
->selected_entry
= te
;
6865 tc
= match_color(&s
->colors
, line
);
6867 wattr_on(view
->window
,
6868 COLOR_PAIR(tc
->colorpair
), NULL
);
6869 waddwstr(view
->window
, wline
);
6871 wattr_off(view
->window
,
6872 COLOR_PAIR(tc
->colorpair
), NULL
);
6873 if (width
< view
->ncols
- 1)
6874 waddch(view
->window
, '\n');
6875 if (n
== s
->selected
&& view
->focussed
)
6876 wstandend(view
->window
);
6882 s
->last_displayed_entry
= te
;
6891 tree_scroll_up(struct tog_tree_view_state
*s
, int maxscroll
)
6893 struct got_tree_entry
*te
;
6894 int isroot
= s
->tree
== s
->root
;
6897 if (s
->first_displayed_entry
== NULL
)
6900 te
= got_tree_entry_get_prev(s
->tree
, s
->first_displayed_entry
);
6901 while (i
++ < maxscroll
) {
6904 s
->first_displayed_entry
= NULL
;
6907 s
->first_displayed_entry
= te
;
6908 te
= got_tree_entry_get_prev(s
->tree
, te
);
6912 static const struct got_error
*
6913 tree_scroll_down(struct tog_view
*view
, int maxscroll
)
6915 struct tog_tree_view_state
*s
= &view
->state
.tree
;
6916 struct got_tree_entry
*next
, *last
;
6919 if (s
->first_displayed_entry
)
6920 next
= got_tree_entry_get_next(s
->tree
,
6921 s
->first_displayed_entry
);
6923 next
= got_object_tree_get_first_entry(s
->tree
);
6925 last
= s
->last_displayed_entry
;
6926 while (next
&& n
++ < maxscroll
) {
6928 s
->last_displayed_entry
= last
;
6929 last
= got_tree_entry_get_next(s
->tree
, last
);
6931 if (last
|| (view
->mode
== TOG_VIEW_SPLIT_HRZN
&& next
)) {
6932 s
->first_displayed_entry
= next
;
6933 next
= got_tree_entry_get_next(s
->tree
, next
);
6940 static const struct got_error
*
6941 tree_entry_path(char **path
, struct tog_parent_trees
*parents
,
6942 struct got_tree_entry
*te
)
6944 const struct got_error
*err
= NULL
;
6945 struct tog_parent_tree
*pt
;
6946 size_t len
= 2; /* for leading slash and NUL */
6948 TAILQ_FOREACH(pt
, parents
, entry
)
6949 len
+= strlen(got_tree_entry_get_name(pt
->selected_entry
))
6952 len
+= strlen(got_tree_entry_get_name(te
));
6954 *path
= calloc(1, len
);
6956 return got_error_from_errno("calloc");
6959 pt
= TAILQ_LAST(parents
, tog_parent_trees
);
6961 const char *name
= got_tree_entry_get_name(pt
->selected_entry
);
6962 if (strlcat(*path
, name
, len
) >= len
) {
6963 err
= got_error(GOT_ERR_NO_SPACE
);
6966 if (strlcat(*path
, "/", len
) >= len
) {
6967 err
= got_error(GOT_ERR_NO_SPACE
);
6970 pt
= TAILQ_PREV(pt
, tog_parent_trees
, entry
);
6973 if (strlcat(*path
, got_tree_entry_get_name(te
), len
) >= len
) {
6974 err
= got_error(GOT_ERR_NO_SPACE
);
6986 static const struct got_error
*
6987 blame_tree_entry(struct tog_view
**new_view
, int begin_y
, int begin_x
,
6988 struct got_tree_entry
*te
, struct tog_parent_trees
*parents
,
6989 struct got_object_id
*commit_id
, struct got_repository
*repo
)
6991 const struct got_error
*err
= NULL
;
6993 struct tog_view
*blame_view
;
6997 err
= tree_entry_path(&path
, parents
, te
);
7001 blame_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_BLAME
);
7002 if (blame_view
== NULL
) {
7003 err
= got_error_from_errno("view_open");
7007 err
= open_blame_view(blame_view
, path
, commit_id
, repo
);
7009 if (err
->code
== GOT_ERR_CANCELLED
)
7011 view_close(blame_view
);
7013 *new_view
= blame_view
;
7019 static const struct got_error
*
7020 log_selected_tree_entry(struct tog_view
**new_view
, int begin_y
, int begin_x
,
7021 struct tog_tree_view_state
*s
)
7023 struct tog_view
*log_view
;
7024 const struct got_error
*err
= NULL
;
7029 log_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_LOG
);
7030 if (log_view
== NULL
)
7031 return got_error_from_errno("view_open");
7033 err
= tree_entry_path(&path
, &s
->parents
, s
->selected_entry
);
7037 err
= open_log_view(log_view
, s
->commit_id
, s
->repo
, s
->head_ref_name
,
7040 view_close(log_view
);
7042 *new_view
= log_view
;
7047 static const struct got_error
*
7048 open_tree_view(struct tog_view
*view
, struct got_object_id
*commit_id
,
7049 const char *head_ref_name
, struct got_repository
*repo
)
7051 const struct got_error
*err
= NULL
;
7052 char *commit_id_str
= NULL
;
7053 struct tog_tree_view_state
*s
= &view
->state
.tree
;
7054 struct got_commit_object
*commit
= NULL
;
7056 TAILQ_INIT(&s
->parents
);
7057 STAILQ_INIT(&s
->colors
);
7059 s
->commit_id
= got_object_id_dup(commit_id
);
7060 if (s
->commit_id
== NULL
)
7061 return got_error_from_errno("got_object_id_dup");
7063 err
= got_object_open_as_commit(&commit
, repo
, commit_id
);
7068 * The root is opened here and will be closed when the view is closed.
7069 * Any visited subtrees and their path-wise parents are opened and
7072 err
= got_object_open_as_tree(&s
->root
, repo
,
7073 got_object_commit_get_tree_id(commit
));
7078 err
= got_object_id_str(&commit_id_str
, commit_id
);
7082 if (asprintf(&s
->tree_label
, "commit %s", commit_id_str
) == -1) {
7083 err
= got_error_from_errno("asprintf");
7087 s
->first_displayed_entry
= got_object_tree_get_entry(s
->tree
, 0);
7088 s
->selected_entry
= got_object_tree_get_entry(s
->tree
, 0);
7089 if (head_ref_name
) {
7090 s
->head_ref_name
= strdup(head_ref_name
);
7091 if (s
->head_ref_name
== NULL
) {
7092 err
= got_error_from_errno("strdup");
7098 if (has_colors() && getenv("TOG_COLORS") != NULL
) {
7099 err
= add_color(&s
->colors
, "\\$$",
7100 TOG_COLOR_TREE_SUBMODULE
,
7101 get_color_value("TOG_COLOR_TREE_SUBMODULE"));
7104 err
= add_color(&s
->colors
, "@$", TOG_COLOR_TREE_SYMLINK
,
7105 get_color_value("TOG_COLOR_TREE_SYMLINK"));
7108 err
= add_color(&s
->colors
, "/$",
7109 TOG_COLOR_TREE_DIRECTORY
,
7110 get_color_value("TOG_COLOR_TREE_DIRECTORY"));
7114 err
= add_color(&s
->colors
, "\\*$",
7115 TOG_COLOR_TREE_EXECUTABLE
,
7116 get_color_value("TOG_COLOR_TREE_EXECUTABLE"));
7120 err
= add_color(&s
->colors
, "^$", TOG_COLOR_COMMIT
,
7121 get_color_value("TOG_COLOR_COMMIT"));
7126 view
->show
= show_tree_view
;
7127 view
->input
= input_tree_view
;
7128 view
->close
= close_tree_view
;
7129 view
->search_start
= search_start_tree_view
;
7130 view
->search_next
= search_next_tree_view
;
7132 free(commit_id_str
);
7134 got_object_commit_close(commit
);
7136 close_tree_view(view
);
7140 static const struct got_error
*
7141 close_tree_view(struct tog_view
*view
)
7143 struct tog_tree_view_state
*s
= &view
->state
.tree
;
7145 free_colors(&s
->colors
);
7146 free(s
->tree_label
);
7147 s
->tree_label
= NULL
;
7149 s
->commit_id
= NULL
;
7150 free(s
->head_ref_name
);
7151 s
->head_ref_name
= NULL
;
7152 while (!TAILQ_EMPTY(&s
->parents
)) {
7153 struct tog_parent_tree
*parent
;
7154 parent
= TAILQ_FIRST(&s
->parents
);
7155 TAILQ_REMOVE(&s
->parents
, parent
, entry
);
7156 if (parent
->tree
!= s
->root
)
7157 got_object_tree_close(parent
->tree
);
7161 if (s
->tree
!= NULL
&& s
->tree
!= s
->root
)
7162 got_object_tree_close(s
->tree
);
7164 got_object_tree_close(s
->root
);
7168 static const struct got_error
*
7169 search_start_tree_view(struct tog_view
*view
)
7171 struct tog_tree_view_state
*s
= &view
->state
.tree
;
7173 s
->matched_entry
= NULL
;
7178 match_tree_entry(struct got_tree_entry
*te
, regex_t
*regex
)
7180 regmatch_t regmatch
;
7182 return regexec(regex
, got_tree_entry_get_name(te
), 1, ®match
,
7186 static const struct got_error
*
7187 search_next_tree_view(struct tog_view
*view
)
7189 struct tog_tree_view_state
*s
= &view
->state
.tree
;
7190 struct got_tree_entry
*te
= NULL
;
7192 if (!view
->searching
) {
7193 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
7197 if (s
->matched_entry
) {
7198 if (view
->searching
== TOG_SEARCH_FORWARD
) {
7199 if (s
->selected_entry
)
7200 te
= got_tree_entry_get_next(s
->tree
,
7203 te
= got_object_tree_get_first_entry(s
->tree
);
7205 if (s
->selected_entry
== NULL
)
7206 te
= got_object_tree_get_last_entry(s
->tree
);
7208 te
= got_tree_entry_get_prev(s
->tree
,
7212 if (s
->selected_entry
)
7213 te
= s
->selected_entry
;
7214 else if (view
->searching
== TOG_SEARCH_FORWARD
)
7215 te
= got_object_tree_get_first_entry(s
->tree
);
7217 te
= got_object_tree_get_last_entry(s
->tree
);
7222 if (s
->matched_entry
== NULL
) {
7223 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
7226 if (view
->searching
== TOG_SEARCH_FORWARD
)
7227 te
= got_object_tree_get_first_entry(s
->tree
);
7229 te
= got_object_tree_get_last_entry(s
->tree
);
7232 if (match_tree_entry(te
, &view
->regex
)) {
7233 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
7234 s
->matched_entry
= te
;
7238 if (view
->searching
== TOG_SEARCH_FORWARD
)
7239 te
= got_tree_entry_get_next(s
->tree
, te
);
7241 te
= got_tree_entry_get_prev(s
->tree
, te
);
7244 if (s
->matched_entry
) {
7245 s
->first_displayed_entry
= s
->matched_entry
;
7252 static const struct got_error
*
7253 show_tree_view(struct tog_view
*view
)
7255 const struct got_error
*err
= NULL
;
7256 struct tog_tree_view_state
*s
= &view
->state
.tree
;
7259 err
= tree_entry_path(&parent_path
, &s
->parents
, NULL
);
7263 err
= draw_tree_entries(view
, parent_path
);
7270 static const struct got_error
*
7271 tree_goto_line(struct tog_view
*view
, int nlines
)
7273 const struct got_error
*err
= NULL
;
7274 struct tog_tree_view_state
*s
= &view
->state
.tree
;
7275 struct got_tree_entry
**fte
, **lte
, **ste
;
7276 int g
, last
, first
= 1, i
= 1;
7277 int root
= s
->tree
== s
->root
;
7278 int off
= root
? 1 : 2;
7285 else if (g
> got_object_tree_get_nentries(s
->tree
))
7286 g
= got_object_tree_get_nentries(s
->tree
) + (root
? 0 : 1);
7288 fte
= &s
->first_displayed_entry
;
7289 lte
= &s
->last_displayed_entry
;
7290 ste
= &s
->selected_entry
;
7293 first
= got_tree_entry_get_index(*fte
);
7294 first
+= off
; /* account for ".." */
7296 last
= got_tree_entry_get_index(*lte
);
7299 if (g
>= first
&& g
<= last
&& g
- first
< nlines
) {
7300 s
->selected
= g
- first
;
7301 return NULL
; /* gline is on the current page */
7305 i
= got_tree_entry_get_index(*ste
);
7310 err
= tree_scroll_down(view
, g
- i
);
7313 if (got_tree_entry_get_index(*lte
) >=
7314 got_object_tree_get_nentries(s
->tree
) - 1 &&
7315 first
+ s
->selected
< g
&&
7316 s
->selected
< s
->ndisplayed
- 1) {
7317 first
= got_tree_entry_get_index(*fte
);
7319 s
->selected
= g
- first
;
7322 tree_scroll_up(s
, i
- g
);
7325 (*fte
== NULL
|| (root
&& !got_tree_entry_get_index(*fte
))))
7326 s
->selected
= g
- 1;
7331 static const struct got_error
*
7332 input_tree_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
7334 const struct got_error
*err
= NULL
;
7335 struct tog_tree_view_state
*s
= &view
->state
.tree
;
7336 struct got_tree_entry
*te
;
7337 int n
, nscroll
= view
->nlines
- 3;
7340 return tree_goto_line(view
, nscroll
);
7344 s
->show_ids
= !s
->show_ids
;
7349 if (!s
->selected_entry
)
7351 err
= view_request_new(new_view
, view
, TOG_VIEW_LOG
);
7355 err
= view_request_new(new_view
, view
, TOG_VIEW_REF
);
7361 if (s
->tree
== s
->root
)
7362 s
->first_displayed_entry
=
7363 got_object_tree_get_first_entry(s
->tree
);
7365 s
->first_displayed_entry
= NULL
;
7369 int eos
= view
->nlines
- 3;
7371 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
)
7375 te
= got_object_tree_get_last_entry(s
->tree
);
7376 for (n
= 0; n
< eos
; n
++) {
7378 if (s
->tree
!= s
->root
) {
7379 s
->first_displayed_entry
= NULL
;
7384 s
->first_displayed_entry
= te
;
7385 te
= got_tree_entry_get_prev(s
->tree
, te
);
7388 s
->selected
= n
- 1;
7394 if (s
->selected
> 0) {
7398 tree_scroll_up(s
, 1);
7399 if (s
->selected_entry
== NULL
||
7400 (s
->tree
== s
->root
&& s
->selected_entry
==
7401 got_object_tree_get_first_entry(s
->tree
)))
7411 if (s
->tree
== s
->root
) {
7412 if (got_object_tree_get_first_entry(s
->tree
) ==
7413 s
->first_displayed_entry
)
7414 s
->selected
-= MIN(s
->selected
, nscroll
);
7416 if (s
->first_displayed_entry
== NULL
)
7417 s
->selected
-= MIN(s
->selected
, nscroll
);
7419 tree_scroll_up(s
, MAX(0, nscroll
));
7420 if (s
->selected_entry
== NULL
||
7421 (s
->tree
== s
->root
&& s
->selected_entry
==
7422 got_object_tree_get_first_entry(s
->tree
)))
7428 if (s
->selected
< s
->ndisplayed
- 1) {
7432 if (got_tree_entry_get_next(s
->tree
, s
->last_displayed_entry
)
7434 /* can't scroll any further */
7438 tree_scroll_down(view
, 1);
7448 if (got_tree_entry_get_next(s
->tree
, s
->last_displayed_entry
)
7450 /* can't scroll any further; move cursor down */
7451 if (s
->selected
< s
->ndisplayed
- 1)
7452 s
->selected
+= MIN(nscroll
,
7453 s
->ndisplayed
- s
->selected
- 1);
7458 tree_scroll_down(view
, nscroll
);
7463 if (s
->selected_entry
== NULL
|| ch
== KEY_BACKSPACE
) {
7464 struct tog_parent_tree
*parent
;
7465 /* user selected '..' */
7466 if (s
->tree
== s
->root
) {
7470 parent
= TAILQ_FIRST(&s
->parents
);
7471 TAILQ_REMOVE(&s
->parents
, parent
,
7473 got_object_tree_close(s
->tree
);
7474 s
->tree
= parent
->tree
;
7475 s
->first_displayed_entry
=
7476 parent
->first_displayed_entry
;
7478 parent
->selected_entry
;
7479 s
->selected
= parent
->selected
;
7480 if (s
->selected
> view
->nlines
- 3) {
7481 err
= offset_selection_down(view
);
7486 } else if (S_ISDIR(got_tree_entry_get_mode(
7487 s
->selected_entry
))) {
7488 struct got_tree_object
*subtree
;
7490 err
= got_object_open_as_tree(&subtree
, s
->repo
,
7491 got_tree_entry_get_id(s
->selected_entry
));
7494 err
= tree_view_visit_subtree(s
, subtree
);
7496 got_object_tree_close(subtree
);
7499 } else if (S_ISREG(got_tree_entry_get_mode(s
->selected_entry
)))
7500 err
= view_request_new(new_view
, view
, TOG_VIEW_BLAME
);
7503 if (view
->nlines
>= 4 && s
->selected
>= view
->nlines
- 3)
7504 s
->selected
= view
->nlines
- 4;
7520 "usage: %s tree [-c commit] [-r repository-path] [path]\n",
7525 static const struct got_error
*
7526 cmd_tree(int argc
, char *argv
[])
7528 const struct got_error
*error
;
7529 struct got_repository
*repo
= NULL
;
7530 struct got_worktree
*worktree
= NULL
;
7531 char *cwd
= NULL
, *repo_path
= NULL
, *in_repo_path
= NULL
;
7532 struct got_object_id
*commit_id
= NULL
;
7533 struct got_commit_object
*commit
= NULL
;
7534 const char *commit_id_arg
= NULL
;
7536 struct got_reference
*ref
= NULL
;
7537 const char *head_ref_name
= NULL
;
7539 struct tog_view
*view
;
7540 int *pack_fds
= NULL
;
7542 while ((ch
= getopt(argc
, argv
, "c:r:")) != -1) {
7545 commit_id_arg
= optarg
;
7548 repo_path
= realpath(optarg
, NULL
);
7549 if (repo_path
== NULL
)
7550 return got_error_from_errno2("realpath",
7565 error
= got_repo_pack_fds_open(&pack_fds
);
7569 if (repo_path
== NULL
) {
7570 cwd
= getcwd(NULL
, 0);
7572 return got_error_from_errno("getcwd");
7573 error
= got_worktree_open(&worktree
, cwd
);
7574 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
7578 strdup(got_worktree_get_repo_path(worktree
));
7580 repo_path
= strdup(cwd
);
7581 if (repo_path
== NULL
) {
7582 error
= got_error_from_errno("strdup");
7587 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
7591 error
= get_in_repo_path_from_argv0(&in_repo_path
, argc
, argv
,
7598 error
= apply_unveil(got_repo_get_path(repo
), NULL
);
7602 error
= tog_load_refs(repo
, 0);
7606 if (commit_id_arg
== NULL
) {
7607 error
= got_repo_match_object_id(&commit_id
, &label
,
7608 worktree
? got_worktree_get_head_ref_name(worktree
) :
7609 GOT_REF_HEAD
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
7612 head_ref_name
= label
;
7614 error
= got_ref_open(&ref
, repo
, commit_id_arg
, 0);
7616 head_ref_name
= got_ref_get_name(ref
);
7617 else if (error
->code
!= GOT_ERR_NOT_REF
)
7619 error
= got_repo_match_object_id(&commit_id
, NULL
,
7620 commit_id_arg
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
7625 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
7629 view
= view_open(0, 0, 0, 0, TOG_VIEW_TREE
);
7631 error
= got_error_from_errno("view_open");
7634 error
= open_tree_view(view
, commit_id
, head_ref_name
, repo
);
7637 if (!got_path_is_root_dir(in_repo_path
)) {
7638 error
= tree_view_walk_path(&view
->state
.tree
, commit
,
7645 /* Release work tree lock. */
7646 got_worktree_close(worktree
);
7649 error
= view_loop(view
);
7658 const struct got_error
*close_err
= got_repo_close(repo
);
7663 const struct got_error
*pack_err
=
7664 got_repo_pack_fds_close(pack_fds
);
7672 static const struct got_error
*
7673 ref_view_load_refs(struct tog_ref_view_state
*s
)
7675 struct got_reflist_entry
*sre
;
7676 struct tog_reflist_entry
*re
;
7679 TAILQ_FOREACH(sre
, &tog_refs
, entry
) {
7680 if (strncmp(got_ref_get_name(sre
->ref
),
7681 "refs/got/", 9) == 0 &&
7682 strncmp(got_ref_get_name(sre
->ref
),
7683 "refs/got/backup/", 16) != 0)
7686 re
= malloc(sizeof(*re
));
7688 return got_error_from_errno("malloc");
7690 re
->ref
= got_ref_dup(sre
->ref
);
7691 if (re
->ref
== NULL
)
7692 return got_error_from_errno("got_ref_dup");
7693 re
->idx
= s
->nrefs
++;
7694 TAILQ_INSERT_TAIL(&s
->refs
, re
, entry
);
7697 s
->first_displayed_entry
= TAILQ_FIRST(&s
->refs
);
7702 ref_view_free_refs(struct tog_ref_view_state
*s
)
7704 struct tog_reflist_entry
*re
;
7706 while (!TAILQ_EMPTY(&s
->refs
)) {
7707 re
= TAILQ_FIRST(&s
->refs
);
7708 TAILQ_REMOVE(&s
->refs
, re
, entry
);
7709 got_ref_close(re
->ref
);
7714 static const struct got_error
*
7715 open_ref_view(struct tog_view
*view
, struct got_repository
*repo
)
7717 const struct got_error
*err
= NULL
;
7718 struct tog_ref_view_state
*s
= &view
->state
.ref
;
7720 s
->selected_entry
= 0;
7723 TAILQ_INIT(&s
->refs
);
7724 STAILQ_INIT(&s
->colors
);
7726 err
= ref_view_load_refs(s
);
7730 if (has_colors() && getenv("TOG_COLORS") != NULL
) {
7731 err
= add_color(&s
->colors
, "^refs/heads/",
7732 TOG_COLOR_REFS_HEADS
,
7733 get_color_value("TOG_COLOR_REFS_HEADS"));
7737 err
= add_color(&s
->colors
, "^refs/tags/",
7738 TOG_COLOR_REFS_TAGS
,
7739 get_color_value("TOG_COLOR_REFS_TAGS"));
7743 err
= add_color(&s
->colors
, "^refs/remotes/",
7744 TOG_COLOR_REFS_REMOTES
,
7745 get_color_value("TOG_COLOR_REFS_REMOTES"));
7749 err
= add_color(&s
->colors
, "^refs/got/backup/",
7750 TOG_COLOR_REFS_BACKUP
,
7751 get_color_value("TOG_COLOR_REFS_BACKUP"));
7756 view
->show
= show_ref_view
;
7757 view
->input
= input_ref_view
;
7758 view
->close
= close_ref_view
;
7759 view
->search_start
= search_start_ref_view
;
7760 view
->search_next
= search_next_ref_view
;
7763 free_colors(&s
->colors
);
7767 static const struct got_error
*
7768 close_ref_view(struct tog_view
*view
)
7770 struct tog_ref_view_state
*s
= &view
->state
.ref
;
7772 ref_view_free_refs(s
);
7773 free_colors(&s
->colors
);
7778 static const struct got_error
*
7779 resolve_reflist_entry(struct got_object_id
**commit_id
,
7780 struct tog_reflist_entry
*re
, struct got_repository
*repo
)
7782 const struct got_error
*err
= NULL
;
7783 struct got_object_id
*obj_id
;
7784 struct got_tag_object
*tag
= NULL
;
7789 err
= got_ref_resolve(&obj_id
, repo
, re
->ref
);
7793 err
= got_object_get_type(&obj_type
, repo
, obj_id
);
7798 case GOT_OBJ_TYPE_COMMIT
:
7799 *commit_id
= obj_id
;
7801 case GOT_OBJ_TYPE_TAG
:
7802 err
= got_object_open_as_tag(&tag
, repo
, obj_id
);
7806 err
= got_object_get_type(&obj_type
, repo
,
7807 got_object_tag_get_object_id(tag
));
7810 if (obj_type
!= GOT_OBJ_TYPE_COMMIT
) {
7811 err
= got_error(GOT_ERR_OBJ_TYPE
);
7814 *commit_id
= got_object_id_dup(
7815 got_object_tag_get_object_id(tag
));
7816 if (*commit_id
== NULL
) {
7817 err
= got_error_from_errno("got_object_id_dup");
7822 err
= got_error(GOT_ERR_OBJ_TYPE
);
7828 got_object_tag_close(tag
);
7836 static const struct got_error
*
7837 log_ref_entry(struct tog_view
**new_view
, int begin_y
, int begin_x
,
7838 struct tog_reflist_entry
*re
, struct got_repository
*repo
)
7840 struct tog_view
*log_view
;
7841 const struct got_error
*err
= NULL
;
7842 struct got_object_id
*commit_id
= NULL
;
7846 err
= resolve_reflist_entry(&commit_id
, re
, repo
);
7848 if (err
->code
!= GOT_ERR_OBJ_TYPE
)
7854 log_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_LOG
);
7855 if (log_view
== NULL
) {
7856 err
= got_error_from_errno("view_open");
7860 err
= open_log_view(log_view
, commit_id
, repo
,
7861 got_ref_get_name(re
->ref
), "", 0);
7864 view_close(log_view
);
7866 *new_view
= log_view
;
7872 ref_scroll_up(struct tog_ref_view_state
*s
, int maxscroll
)
7874 struct tog_reflist_entry
*re
;
7877 if (s
->first_displayed_entry
== TAILQ_FIRST(&s
->refs
))
7880 re
= TAILQ_PREV(s
->first_displayed_entry
, tog_reflist_head
, entry
);
7881 while (i
++ < maxscroll
) {
7884 s
->first_displayed_entry
= re
;
7885 re
= TAILQ_PREV(re
, tog_reflist_head
, entry
);
7889 static const struct got_error
*
7890 ref_scroll_down(struct tog_view
*view
, int maxscroll
)
7892 struct tog_ref_view_state
*s
= &view
->state
.ref
;
7893 struct tog_reflist_entry
*next
, *last
;
7896 if (s
->first_displayed_entry
)
7897 next
= TAILQ_NEXT(s
->first_displayed_entry
, entry
);
7899 next
= TAILQ_FIRST(&s
->refs
);
7901 last
= s
->last_displayed_entry
;
7902 while (next
&& n
++ < maxscroll
) {
7904 s
->last_displayed_entry
= last
;
7905 last
= TAILQ_NEXT(last
, entry
);
7907 if (last
|| (view
->mode
== TOG_VIEW_SPLIT_HRZN
)) {
7908 s
->first_displayed_entry
= next
;
7909 next
= TAILQ_NEXT(next
, entry
);
7916 static const struct got_error
*
7917 search_start_ref_view(struct tog_view
*view
)
7919 struct tog_ref_view_state
*s
= &view
->state
.ref
;
7921 s
->matched_entry
= NULL
;
7926 match_reflist_entry(struct tog_reflist_entry
*re
, regex_t
*regex
)
7928 regmatch_t regmatch
;
7930 return regexec(regex
, got_ref_get_name(re
->ref
), 1, ®match
,
7934 static const struct got_error
*
7935 search_next_ref_view(struct tog_view
*view
)
7937 struct tog_ref_view_state
*s
= &view
->state
.ref
;
7938 struct tog_reflist_entry
*re
= NULL
;
7940 if (!view
->searching
) {
7941 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
7945 if (s
->matched_entry
) {
7946 if (view
->searching
== TOG_SEARCH_FORWARD
) {
7947 if (s
->selected_entry
)
7948 re
= TAILQ_NEXT(s
->selected_entry
, entry
);
7950 re
= TAILQ_PREV(s
->selected_entry
,
7951 tog_reflist_head
, entry
);
7953 if (s
->selected_entry
== NULL
)
7954 re
= TAILQ_LAST(&s
->refs
, tog_reflist_head
);
7956 re
= TAILQ_PREV(s
->selected_entry
,
7957 tog_reflist_head
, entry
);
7960 if (s
->selected_entry
)
7961 re
= s
->selected_entry
;
7962 else if (view
->searching
== TOG_SEARCH_FORWARD
)
7963 re
= TAILQ_FIRST(&s
->refs
);
7965 re
= TAILQ_LAST(&s
->refs
, tog_reflist_head
);
7970 if (s
->matched_entry
== NULL
) {
7971 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
7974 if (view
->searching
== TOG_SEARCH_FORWARD
)
7975 re
= TAILQ_FIRST(&s
->refs
);
7977 re
= TAILQ_LAST(&s
->refs
, tog_reflist_head
);
7980 if (match_reflist_entry(re
, &view
->regex
)) {
7981 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
7982 s
->matched_entry
= re
;
7986 if (view
->searching
== TOG_SEARCH_FORWARD
)
7987 re
= TAILQ_NEXT(re
, entry
);
7989 re
= TAILQ_PREV(re
, tog_reflist_head
, entry
);
7992 if (s
->matched_entry
) {
7993 s
->first_displayed_entry
= s
->matched_entry
;
8000 static const struct got_error
*
8001 show_ref_view(struct tog_view
*view
)
8003 const struct got_error
*err
= NULL
;
8004 struct tog_ref_view_state
*s
= &view
->state
.ref
;
8005 struct tog_reflist_entry
*re
;
8008 struct tog_color
*tc
;
8010 int limit
= view
->nlines
;
8012 werase(view
->window
);
8015 if (view_is_hsplit_top(view
))
8016 --limit
; /* border */
8021 re
= s
->first_displayed_entry
;
8023 if (asprintf(&line
, "references [%d/%d]", re
->idx
+ s
->selected
+ 1,
8025 return got_error_from_errno("asprintf");
8027 err
= format_line(&wline
, &width
, NULL
, line
, 0, view
->ncols
, 0, 0);
8032 if (view_needs_focus_indication(view
))
8033 wstandout(view
->window
);
8034 waddwstr(view
->window
, wline
);
8035 while (width
++ < view
->ncols
)
8036 waddch(view
->window
, ' ');
8037 if (view_needs_focus_indication(view
))
8038 wstandend(view
->window
);
8047 while (re
&& limit
> 0) {
8049 char ymd
[13]; /* YYYY-MM-DD + " " + NUL */
8052 struct got_commit_object
*ci
;
8053 struct got_tag_object
*tag
;
8054 struct got_object_id
*id
;
8058 err
= got_ref_resolve(&id
, s
->repo
, re
->ref
);
8061 err
= got_object_open_as_tag(&tag
, s
->repo
, id
);
8063 if (err
->code
!= GOT_ERR_OBJ_TYPE
) {
8067 err
= got_object_open_as_commit(&ci
, s
->repo
,
8073 t
= got_object_commit_get_committer_time(ci
);
8074 got_object_commit_close(ci
);
8076 t
= got_object_tag_get_tagger_time(tag
);
8077 got_object_tag_close(tag
);
8080 if (gmtime_r(&t
, &tm
) == NULL
)
8081 return got_error_from_errno("gmtime_r");
8082 if (strftime(ymd
, sizeof(ymd
), "%G-%m-%d ", &tm
) == 0)
8083 return got_error(GOT_ERR_NO_SPACE
);
8085 if (got_ref_is_symbolic(re
->ref
)) {
8086 if (asprintf(&line
, "%s%s -> %s", s
->show_date
?
8087 ymd
: "", got_ref_get_name(re
->ref
),
8088 got_ref_get_symref_target(re
->ref
)) == -1)
8089 return got_error_from_errno("asprintf");
8090 } else if (s
->show_ids
) {
8091 struct got_object_id
*id
;
8093 err
= got_ref_resolve(&id
, s
->repo
, re
->ref
);
8096 err
= got_object_id_str(&id_str
, id
);
8101 if (asprintf(&line
, "%s%s: %s", s
->show_date
? ymd
: "",
8102 got_ref_get_name(re
->ref
), id_str
) == -1) {
8103 err
= got_error_from_errno("asprintf");
8110 } else if (asprintf(&line
, "%s%s", s
->show_date
? ymd
: "",
8111 got_ref_get_name(re
->ref
)) == -1)
8112 return got_error_from_errno("asprintf");
8114 err
= format_line(&wline
, &width
, NULL
, line
, 0, view
->ncols
,
8120 if (n
== s
->selected
) {
8122 wstandout(view
->window
);
8123 s
->selected_entry
= re
;
8125 tc
= match_color(&s
->colors
, got_ref_get_name(re
->ref
));
8127 wattr_on(view
->window
,
8128 COLOR_PAIR(tc
->colorpair
), NULL
);
8129 waddwstr(view
->window
, wline
);
8131 wattr_off(view
->window
,
8132 COLOR_PAIR(tc
->colorpair
), NULL
);
8133 if (width
< view
->ncols
- 1)
8134 waddch(view
->window
, '\n');
8135 if (n
== s
->selected
&& view
->focussed
)
8136 wstandend(view
->window
);
8142 s
->last_displayed_entry
= re
;
8145 re
= TAILQ_NEXT(re
, entry
);
8152 static const struct got_error
*
8153 browse_ref_tree(struct tog_view
**new_view
, int begin_y
, int begin_x
,
8154 struct tog_reflist_entry
*re
, struct got_repository
*repo
)
8156 const struct got_error
*err
= NULL
;
8157 struct got_object_id
*commit_id
= NULL
;
8158 struct tog_view
*tree_view
;
8162 err
= resolve_reflist_entry(&commit_id
, re
, repo
);
8164 if (err
->code
!= GOT_ERR_OBJ_TYPE
)
8171 tree_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_TREE
);
8172 if (tree_view
== NULL
) {
8173 err
= got_error_from_errno("view_open");
8177 err
= open_tree_view(tree_view
, commit_id
,
8178 got_ref_get_name(re
->ref
), repo
);
8182 *new_view
= tree_view
;
8188 static const struct got_error
*
8189 ref_goto_line(struct tog_view
*view
, int nlines
)
8191 const struct got_error
*err
= NULL
;
8192 struct tog_ref_view_state
*s
= &view
->state
.ref
;
8193 int g
, idx
= s
->selected_entry
->idx
;
8200 else if (g
> s
->nrefs
)
8203 if (g
>= s
->first_displayed_entry
->idx
+ 1 &&
8204 g
<= s
->last_displayed_entry
->idx
+ 1 &&
8205 g
- s
->first_displayed_entry
->idx
- 1 < nlines
) {
8206 s
->selected
= g
- s
->first_displayed_entry
->idx
- 1;
8211 err
= ref_scroll_down(view
, g
- idx
- 1);
8214 if (TAILQ_NEXT(s
->last_displayed_entry
, entry
) == NULL
&&
8215 s
->first_displayed_entry
->idx
+ s
->selected
< g
&&
8216 s
->selected
< s
->ndisplayed
- 1)
8217 s
->selected
= g
- s
->first_displayed_entry
->idx
- 1;
8218 } else if (idx
+ 1 > g
)
8219 ref_scroll_up(s
, idx
- g
+ 1);
8221 if (g
< nlines
&& s
->first_displayed_entry
->idx
== 0)
8222 s
->selected
= g
- 1;
8228 static const struct got_error
*
8229 input_ref_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
8231 const struct got_error
*err
= NULL
;
8232 struct tog_ref_view_state
*s
= &view
->state
.ref
;
8233 struct tog_reflist_entry
*re
;
8234 int n
, nscroll
= view
->nlines
- 1;
8237 return ref_goto_line(view
, nscroll
);
8241 s
->show_ids
= !s
->show_ids
;
8245 s
->show_date
= !s
->show_date
;
8249 s
->sort_by_date
= !s
->sort_by_date
;
8251 err
= got_reflist_sort(&tog_refs
, s
->sort_by_date
?
8252 got_ref_cmp_by_commit_timestamp_descending
:
8253 tog_ref_cmp_by_name
, s
->repo
);
8256 got_reflist_object_id_map_free(tog_refs_idmap
);
8257 err
= got_reflist_object_id_map_create(&tog_refs_idmap
,
8258 &tog_refs
, s
->repo
);
8261 ref_view_free_refs(s
);
8262 err
= ref_view_load_refs(s
);
8267 if (!s
->selected_entry
)
8269 err
= view_request_new(new_view
, view
, TOG_VIEW_LOG
);
8273 if (!s
->selected_entry
)
8275 err
= view_request_new(new_view
, view
, TOG_VIEW_TREE
);
8281 s
->first_displayed_entry
= TAILQ_FIRST(&s
->refs
);
8285 int eos
= view
->nlines
- 1;
8287 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
)
8291 re
= TAILQ_LAST(&s
->refs
, tog_reflist_head
);
8292 for (n
= 0; n
< eos
; n
++) {
8295 s
->first_displayed_entry
= re
;
8296 re
= TAILQ_PREV(re
, tog_reflist_head
, entry
);
8299 s
->selected
= n
- 1;
8305 if (s
->selected
> 0) {
8309 ref_scroll_up(s
, 1);
8310 if (s
->selected_entry
== TAILQ_FIRST(&s
->refs
))
8320 if (s
->first_displayed_entry
== TAILQ_FIRST(&s
->refs
))
8321 s
->selected
-= MIN(nscroll
, s
->selected
);
8322 ref_scroll_up(s
, MAX(0, nscroll
));
8323 if (s
->selected_entry
== TAILQ_FIRST(&s
->refs
))
8329 if (s
->selected
< s
->ndisplayed
- 1) {
8333 if (TAILQ_NEXT(s
->last_displayed_entry
, entry
) == NULL
) {
8334 /* can't scroll any further */
8338 ref_scroll_down(view
, 1);
8348 if (TAILQ_NEXT(s
->last_displayed_entry
, entry
) == NULL
) {
8349 /* can't scroll any further; move cursor down */
8350 if (s
->selected
< s
->ndisplayed
- 1)
8351 s
->selected
+= MIN(nscroll
,
8352 s
->ndisplayed
- s
->selected
- 1);
8353 if (view
->count
> 1 && s
->selected
< s
->ndisplayed
- 1)
8354 s
->selected
+= s
->ndisplayed
- s
->selected
- 1;
8358 ref_scroll_down(view
, nscroll
);
8363 err
= tog_load_refs(s
->repo
, s
->sort_by_date
);
8366 ref_view_free_refs(s
);
8367 err
= ref_view_load_refs(s
);
8370 if (view
->nlines
>= 2 && s
->selected
>= view
->nlines
- 1)
8371 s
->selected
= view
->nlines
- 2;
8385 fprintf(stderr
, "usage: %s ref [-r repository-path]\n",
8390 static const struct got_error
*
8391 cmd_ref(int argc
, char *argv
[])
8393 const struct got_error
*error
;
8394 struct got_repository
*repo
= NULL
;
8395 struct got_worktree
*worktree
= NULL
;
8396 char *cwd
= NULL
, *repo_path
= NULL
;
8398 struct tog_view
*view
;
8399 int *pack_fds
= NULL
;
8401 while ((ch
= getopt(argc
, argv
, "r:")) != -1) {
8404 repo_path
= realpath(optarg
, NULL
);
8405 if (repo_path
== NULL
)
8406 return got_error_from_errno2("realpath",
8421 error
= got_repo_pack_fds_open(&pack_fds
);
8425 if (repo_path
== NULL
) {
8426 cwd
= getcwd(NULL
, 0);
8428 return got_error_from_errno("getcwd");
8429 error
= got_worktree_open(&worktree
, cwd
);
8430 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
8434 strdup(got_worktree_get_repo_path(worktree
));
8436 repo_path
= strdup(cwd
);
8437 if (repo_path
== NULL
) {
8438 error
= got_error_from_errno("strdup");
8443 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
8449 error
= apply_unveil(got_repo_get_path(repo
), NULL
);
8453 error
= tog_load_refs(repo
, 0);
8457 view
= view_open(0, 0, 0, 0, TOG_VIEW_REF
);
8459 error
= got_error_from_errno("view_open");
8463 error
= open_ref_view(view
, repo
);
8468 /* Release work tree lock. */
8469 got_worktree_close(worktree
);
8472 error
= view_loop(view
);
8477 const struct got_error
*close_err
= got_repo_close(repo
);
8482 const struct got_error
*pack_err
=
8483 got_repo_pack_fds_close(pack_fds
);
8491 static const struct got_error
*
8492 win_draw_center(WINDOW
*win
, size_t y
, size_t x
, size_t maxx
, int focus
,
8501 x
= x
? x
: maxx
> len
? (maxx
- len
) / 2 : 0;
8505 if (mvwprintw(win
, y
, x
, "%s", str
) == ERR
)
8506 return got_error_msg(GOT_ERR_RANGE
, "mvwprintw");
8513 static const struct got_error
*
8514 add_line_offset(off_t
**line_offsets
, size_t *nlines
, off_t off
)
8518 p
= reallocarray(*line_offsets
, *nlines
+ 1, sizeof(off_t
));
8520 free(*line_offsets
);
8521 *line_offsets
= NULL
;
8522 return got_error_from_errno("reallocarray");
8526 (*line_offsets
)[*nlines
] = off
;
8531 static const struct got_error
*
8532 max_key_str(int *ret
, const struct tog_key_map
*km
, size_t n
)
8536 for (;n
> 0; --n
, ++km
) {
8540 if (km
->keys
== NULL
)
8543 t
= t0
= strdup(km
->keys
);
8545 return got_error_from_errno("strdup");
8548 while ((k
= strsep(&t
, " ")) != NULL
)
8549 len
+= strlen(k
) > 1 ? 2 : 0;
8551 *ret
= MAX(*ret
, len
);
8558 * Write keymap section headers, keys, and key info in km to f.
8559 * Save line offset to *off. If terminal has UTF8 encoding enabled,
8560 * wrap control and symbolic keys in guillemets, else use <>.
8562 static const struct got_error
*
8563 format_help_line(off_t
*off
, FILE *f
, const struct tog_key_map
*km
, int width
)
8568 static const char *u8_glyph
[] = {
8569 "\xe2\x80\xb9", /* U+2039 (utf8 <) */
8570 "\xe2\x80\xba" /* U+203A (utf8 >) */
8573 int cs
, s
, first
= 1;
8575 cs
= got_locale_is_utf8();
8577 t
= t0
= strdup(km
->keys
);
8579 return got_error_from_errno("strdup");
8581 len
= strlen(km
->keys
);
8582 while ((k
= strsep(&t
, " ")) != NULL
) {
8583 s
= strlen(k
) > 1; /* control or symbolic key */
8584 n
= fprintf(f
, "%s%s%s%s%s", first
? " " : "",
8585 cs
&& s
? u8_glyph
[0] : s
? "<" : "", k
,
8586 cs
&& s
? u8_glyph
[1] : s
? ">" : "", t
? " " : "");
8589 return got_error_from_errno("fprintf");
8597 n
= fprintf(f
, "%*s%s\n", width
- len
, width
- len
? " " : "", km
->info
);
8599 return got_error_from_errno("fprintf");
8605 static const struct got_error
*
8606 format_help(struct tog_help_view_state
*s
)
8608 const struct got_error
*err
= NULL
;
8610 int i
, max
, n
, show
= s
->all
;
8611 static const struct tog_key_map km
[] = {
8612 #define KEYMAP_(info, type) { NULL, (info), type }
8613 #define KEY_(keys, info) { (keys), (info), TOG_KEYMAP_KEYS }
8619 err
= add_line_offset(&s
->line_offsets
, &s
->nlines
, 0);
8624 err
= max_key_str(&max
, km
, n
);
8628 for (i
= 0; i
< n
; ++i
) {
8629 if (km
[i
].keys
== NULL
) {
8631 if (km
[i
].type
== TOG_KEYMAP_GLOBAL
||
8632 km
[i
].type
== s
->type
|| s
->all
)
8636 err
= format_help_line(&off
, s
->f
, &km
[i
], max
);
8639 err
= add_line_offset(&s
->line_offsets
, &s
->nlines
, off
);
8646 err
= add_line_offset(&s
->line_offsets
, &s
->nlines
, off
);
8650 static const struct got_error
*
8651 create_help(struct tog_help_view_state
*s
)
8654 const struct got_error
*err
;
8656 free(s
->line_offsets
);
8657 s
->line_offsets
= NULL
;
8662 return got_error_from_errno("got_opentemp");
8665 err
= format_help(s
);
8669 if (s
->f
&& fflush(s
->f
) != 0)
8670 return got_error_from_errno("fflush");
8675 static const struct got_error
*
8676 search_start_help_view(struct tog_view
*view
)
8678 view
->state
.help
.matched_line
= 0;
8683 search_setup_help_view(struct tog_view
*view
, FILE **f
, off_t
**line_offsets
,
8684 size_t *nlines
, int **first
, int **last
, int **match
, int **selected
)
8686 struct tog_help_view_state
*s
= &view
->state
.help
;
8689 *nlines
= s
->nlines
;
8690 *line_offsets
= s
->line_offsets
;
8691 *match
= &s
->matched_line
;
8692 *first
= &s
->first_displayed_line
;
8693 *last
= &s
->last_displayed_line
;
8694 *selected
= &s
->selected_line
;
8697 static const struct got_error
*
8698 show_help_view(struct tog_view
*view
)
8700 struct tog_help_view_state
*s
= &view
->state
.help
;
8701 const struct got_error
*err
;
8702 regmatch_t
*regmatch
= &view
->regmatch
;
8707 int width
, nprinted
= 0, rc
= 0;
8708 int eos
= view
->nlines
;
8710 if (view_is_hsplit_top(view
))
8711 --eos
; /* account for border */
8715 werase(view
->window
);
8717 if (view
->gline
> s
->nlines
- 1)
8718 view
->gline
= s
->nlines
- 1;
8720 err
= win_draw_center(view
->window
, 0, 0, view
->ncols
,
8721 view_needs_focus_indication(view
),
8722 "tog help (press q to return to tog)");
8727 waddstr(view
->window
, "\n\n");
8733 while (eos
> 0 && nprinted
< eos
) {
8736 linelen
= getline(&line
, &linesz
, s
->f
);
8737 if (linelen
== -1) {
8740 return got_ferror(s
->f
, GOT_ERR_IO
);
8745 if (++s
->lineno
< s
->first_displayed_line
)
8747 if (view
->gline
&& !gotoline(view
, &s
->lineno
, &nprinted
))
8749 if (s
->lineno
== view
->hiline
)
8752 err
= format_line(&wline
, &width
, NULL
, line
, 0, INT_MAX
, 0,
8758 view
->maxx
= MAX(view
->maxx
, width
);
8763 wattron(view
->window
, attr
);
8764 if (s
->first_displayed_line
+ nprinted
== s
->matched_line
&&
8765 regmatch
->rm_so
>= 0 && regmatch
->rm_so
< regmatch
->rm_eo
) {
8766 err
= add_matched_line(&width
, line
, view
->ncols
- 1, 0,
8767 view
->window
, view
->x
, regmatch
);
8775 err
= format_line(&wline
, &width
, &skip
, line
,
8776 view
->x
, view
->ncols
- 1, 0, view
->x
? 1 : 0);
8781 rc
= waddwstr(view
->window
, &wline
[skip
]);
8785 return got_error_msg(GOT_ERR_IO
, "waddwstr");
8787 if (s
->lineno
== view
->hiline
) {
8788 while (width
++ < view
->ncols
)
8789 waddch(view
->window
, ' ');
8791 if (width
<= view
->ncols
)
8792 waddch(view
->window
, '\n');
8795 wattroff(view
->window
, attr
);
8796 if (++nprinted
== 1)
8797 s
->first_displayed_line
= s
->lineno
;
8801 s
->last_displayed_line
= s
->first_displayed_line
+ nprinted
- 1;
8803 s
->last_displayed_line
= s
->first_displayed_line
;
8808 rc
= waddnstr(view
->window
,
8809 "See the tog(1) manual page for full documentation",
8812 return got_error_msg(GOT_ERR_RANGE
, "waddnstr");
8814 wmove(view
->window
, view
->nlines
- 1, 0);
8815 wclrtoeol(view
->window
);
8816 wstandout(view
->window
);
8817 rc
= waddnstr(view
->window
, "scroll down for more...",
8820 return got_error_msg(GOT_ERR_RANGE
, "waddnstr");
8821 if (getcurx(view
->window
) < view
->ncols
- 6) {
8822 rc
= wprintw(view
->window
, "[%.0f%%]",
8823 100.00 * s
->last_displayed_line
/ s
->nlines
);
8825 return got_error_msg(GOT_ERR_IO
, "wprintw");
8827 wstandend(view
->window
);
8833 static const struct got_error
*
8834 input_help_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
8836 struct tog_help_view_state
*s
= &view
->state
.help
;
8837 const struct got_error
*err
= NULL
;
8843 eos
= nscroll
= view
->nlines
;
8844 if (view_is_hsplit_top(view
))
8847 s
->lineno
= s
->first_displayed_line
- 1 + s
->selected_line
;
8854 view
->x
= MAX(view
->maxx
- view
->ncols
/ 3, 0);
8859 if (view
->x
+ view
->ncols
/ 3 < view
->maxx
)
8866 view
->x
-= MIN(view
->x
, 2);
8872 s
->first_displayed_line
= 1;
8880 s
->first_displayed_line
= (s
->nlines
- eos
) + 3;
8885 if (s
->first_displayed_line
> 1)
8886 --s
->first_displayed_line
;
8897 if (s
->first_displayed_line
== 1) {
8901 while (--nscroll
> 0 && s
->first_displayed_line
> 1)
8902 s
->first_displayed_line
--;
8908 ++s
->first_displayed_line
;
8924 while (!s
->eof
&& --nscroll
> 0) {
8925 linelen
= getline(&line
, &linesz
, s
->f
);
8926 s
->first_displayed_line
++;
8927 if (linelen
== -1) {
8931 err
= got_ferror(s
->f
, GOT_ERR_IO
);
8945 static const struct got_error
*
8946 close_help_view(struct tog_view
*view
)
8948 struct tog_help_view_state
*s
= &view
->state
.help
;
8950 free(s
->line_offsets
);
8951 s
->line_offsets
= NULL
;
8952 if (fclose(s
->f
) == EOF
)
8953 return got_error_from_errno("fclose");
8958 static const struct got_error
*
8959 reset_help_view(struct tog_view
*view
)
8961 struct tog_help_view_state
*s
= &view
->state
.help
;
8964 if (s
->f
&& fclose(s
->f
) == EOF
)
8965 return got_error_from_errno("fclose");
8967 wclear(view
->window
);
8971 s
->first_displayed_line
= 1;
8972 s
->last_displayed_line
= view
->nlines
;
8973 s
->matched_line
= 0;
8975 return create_help(s
);
8978 static const struct got_error
*
8979 open_help_view(struct tog_view
*view
, struct tog_view
*parent
)
8981 const struct got_error
*err
= NULL
;
8982 struct tog_help_view_state
*s
= &view
->state
.help
;
8984 s
->type
= (enum tog_keymap_type
)parent
->type
;
8985 s
->first_displayed_line
= 1;
8986 s
->last_displayed_line
= view
->nlines
;
8987 s
->selected_line
= 1;
8989 view
->show
= show_help_view
;
8990 view
->input
= input_help_view
;
8991 view
->reset
= reset_help_view
;
8992 view
->close
= close_help_view
;
8993 view
->search_start
= search_start_help_view
;
8994 view
->search_setup
= search_setup_help_view
;
8995 view
->search_next
= search_next_view_match
;
8997 err
= create_help(s
);
9001 static const struct got_error
*
9002 view_dispatch_request(struct tog_view
**new_view
, struct tog_view
*view
,
9003 enum tog_view_type request
, int y
, int x
)
9005 const struct got_error
*err
= NULL
;
9011 if (view
->type
== TOG_VIEW_LOG
) {
9012 struct tog_log_view_state
*s
= &view
->state
.log
;
9014 err
= open_diff_view_for_commit(new_view
, y
, x
,
9015 s
->selected_entry
->commit
, s
->selected_entry
->id
,
9018 return got_error_msg(GOT_ERR_NOT_IMPL
,
9019 "parent/child view pair not supported");
9021 case TOG_VIEW_BLAME
:
9022 if (view
->type
== TOG_VIEW_TREE
) {
9023 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9025 err
= blame_tree_entry(new_view
, y
, x
,
9026 s
->selected_entry
, &s
->parents
, s
->commit_id
,
9029 return got_error_msg(GOT_ERR_NOT_IMPL
,
9030 "parent/child view pair not supported");
9033 if (view
->type
== TOG_VIEW_BLAME
)
9034 err
= log_annotated_line(new_view
, y
, x
,
9035 view
->state
.blame
.repo
, view
->state
.blame
.id_to_log
);
9036 else if (view
->type
== TOG_VIEW_TREE
)
9037 err
= log_selected_tree_entry(new_view
, y
, x
,
9039 else if (view
->type
== TOG_VIEW_REF
)
9040 err
= log_ref_entry(new_view
, y
, x
,
9041 view
->state
.ref
.selected_entry
,
9042 view
->state
.ref
.repo
);
9044 return got_error_msg(GOT_ERR_NOT_IMPL
,
9045 "parent/child view pair not supported");
9048 if (view
->type
== TOG_VIEW_LOG
)
9049 err
= browse_commit_tree(new_view
, y
, x
,
9050 view
->state
.log
.selected_entry
,
9051 view
->state
.log
.in_repo_path
,
9052 view
->state
.log
.head_ref_name
,
9053 view
->state
.log
.repo
);
9054 else if (view
->type
== TOG_VIEW_REF
)
9055 err
= browse_ref_tree(new_view
, y
, x
,
9056 view
->state
.ref
.selected_entry
,
9057 view
->state
.ref
.repo
);
9059 return got_error_msg(GOT_ERR_NOT_IMPL
,
9060 "parent/child view pair not supported");
9063 *new_view
= view_open(0, 0, y
, x
, TOG_VIEW_REF
);
9064 if (*new_view
== NULL
)
9065 return got_error_from_errno("view_open");
9066 if (view
->type
== TOG_VIEW_LOG
)
9067 err
= open_ref_view(*new_view
, view
->state
.log
.repo
);
9068 else if (view
->type
== TOG_VIEW_TREE
)
9069 err
= open_ref_view(*new_view
, view
->state
.tree
.repo
);
9071 err
= got_error_msg(GOT_ERR_NOT_IMPL
,
9072 "parent/child view pair not supported");
9074 view_close(*new_view
);
9077 *new_view
= view_open(0, 0, 0, 0, TOG_VIEW_HELP
);
9078 if (*new_view
== NULL
)
9079 return got_error_from_errno("view_open");
9080 err
= open_help_view(*new_view
, view
);
9082 view_close(*new_view
);
9085 return got_error_msg(GOT_ERR_NOT_IMPL
, "invalid view");
9092 * If view was scrolled down to move the selected line into view when opening a
9093 * horizontal split, scroll back up when closing the split/toggling fullscreen.
9096 offset_selection_up(struct tog_view
*view
)
9098 switch (view
->type
) {
9099 case TOG_VIEW_BLAME
: {
9100 struct tog_blame_view_state
*s
= &view
->state
.blame
;
9101 if (s
->first_displayed_line
== 1) {
9102 s
->selected_line
= MAX(s
->selected_line
- view
->offset
,
9106 if (s
->first_displayed_line
> view
->offset
)
9107 s
->first_displayed_line
-= view
->offset
;
9109 s
->first_displayed_line
= 1;
9110 s
->selected_line
+= view
->offset
;
9114 log_scroll_up(&view
->state
.log
, view
->offset
);
9115 view
->state
.log
.selected
+= view
->offset
;
9118 ref_scroll_up(&view
->state
.ref
, view
->offset
);
9119 view
->state
.ref
.selected
+= view
->offset
;
9122 tree_scroll_up(&view
->state
.tree
, view
->offset
);
9123 view
->state
.tree
.selected
+= view
->offset
;
9133 * If the selected line is in the section of screen covered by the bottom split,
9134 * scroll down offset lines to move it into view and index its new position.
9136 static const struct got_error
*
9137 offset_selection_down(struct tog_view
*view
)
9139 const struct got_error
*err
= NULL
;
9140 const struct got_error
*(*scrolld
)(struct tog_view
*, int);
9141 int *selected
= NULL
;
9144 switch (view
->type
) {
9145 case TOG_VIEW_BLAME
: {
9146 struct tog_blame_view_state
*s
= &view
->state
.blame
;
9149 if (s
->selected_line
> view
->nlines
- header
) {
9150 offset
= abs(view
->nlines
- s
->selected_line
- header
);
9151 s
->first_displayed_line
+= offset
;
9152 s
->selected_line
-= offset
;
9153 view
->offset
= offset
;
9157 case TOG_VIEW_LOG
: {
9158 struct tog_log_view_state
*s
= &view
->state
.log
;
9159 scrolld
= &log_scroll_down
;
9160 header
= view_is_parent_view(view
) ? 3 : 2;
9161 selected
= &s
->selected
;
9164 case TOG_VIEW_REF
: {
9165 struct tog_ref_view_state
*s
= &view
->state
.ref
;
9166 scrolld
= &ref_scroll_down
;
9168 selected
= &s
->selected
;
9171 case TOG_VIEW_TREE
: {
9172 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9173 scrolld
= &tree_scroll_down
;
9175 selected
= &s
->selected
;
9185 if (selected
&& *selected
> view
->nlines
- header
) {
9186 offset
= abs(view
->nlines
- *selected
- header
);
9187 view
->offset
= offset
;
9188 if (scrolld
&& offset
) {
9189 err
= scrolld(view
, offset
);
9190 *selected
-= offset
;
9198 list_commands(FILE *fp
)
9202 fprintf(fp
, "commands:");
9203 for (i
= 0; i
< nitems(tog_commands
); i
++) {
9204 const struct tog_cmd
*cmd
= &tog_commands
[i
];
9205 fprintf(fp
, " %s", cmd
->name
);
9211 usage(int hflag
, int status
)
9213 FILE *fp
= (status
== 0) ? stdout
: stderr
;
9215 fprintf(fp
, "usage: %s [-hV] command [arg ...]\n",
9218 fprintf(fp
, "lazy usage: %s path\n", getprogname());
9225 make_argv(int argc
, ...)
9233 argv
= calloc(argc
, sizeof(char *));
9236 for (i
= 0; i
< argc
; i
++) {
9237 argv
[i
] = strdup(va_arg(ap
, char *));
9238 if (argv
[i
] == NULL
)
9247 * Try to convert 'tog path' into a 'tog log path' command.
9248 * The user could simply have mistyped the command rather than knowingly
9249 * provided a path. So check whether argv[0] can in fact be resolved
9250 * to a path in the HEAD commit and print a special error if not.
9251 * This hack is for mpi@ <3
9253 static const struct got_error
*
9254 tog_log_with_path(int argc
, char *argv
[])
9256 const struct got_error
*error
= NULL
, *close_err
;
9257 const struct tog_cmd
*cmd
= NULL
;
9258 struct got_repository
*repo
= NULL
;
9259 struct got_worktree
*worktree
= NULL
;
9260 struct got_object_id
*commit_id
= NULL
, *id
= NULL
;
9261 struct got_commit_object
*commit
= NULL
;
9262 char *cwd
= NULL
, *repo_path
= NULL
, *in_repo_path
= NULL
;
9263 char *commit_id_str
= NULL
, **cmd_argv
= NULL
;
9264 int *pack_fds
= NULL
;
9266 cwd
= getcwd(NULL
, 0);
9268 return got_error_from_errno("getcwd");
9270 error
= got_repo_pack_fds_open(&pack_fds
);
9274 error
= got_worktree_open(&worktree
, cwd
);
9275 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
9279 repo_path
= strdup(got_worktree_get_repo_path(worktree
));
9281 repo_path
= strdup(cwd
);
9282 if (repo_path
== NULL
) {
9283 error
= got_error_from_errno("strdup");
9287 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
9291 error
= get_in_repo_path_from_argv0(&in_repo_path
, argc
, argv
,
9296 error
= tog_load_refs(repo
, 0);
9299 error
= got_repo_match_object_id(&commit_id
, NULL
, worktree
?
9300 got_worktree_get_head_ref_name(worktree
) : GOT_REF_HEAD
,
9301 GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
9306 got_worktree_close(worktree
);
9310 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
9314 error
= got_object_id_by_path(&id
, repo
, commit
, in_repo_path
);
9316 if (error
->code
!= GOT_ERR_NO_TREE_ENTRY
)
9318 fprintf(stderr
, "%s: '%s' is no known command or path\n",
9319 getprogname(), argv
[0]);
9324 error
= got_object_id_str(&commit_id_str
, commit_id
);
9328 cmd
= &tog_commands
[0]; /* log */
9330 cmd_argv
= make_argv(argc
, cmd
->name
, "-c", commit_id_str
, argv
[0]);
9331 error
= cmd
->cmd_main(argc
, cmd_argv
);
9334 close_err
= got_repo_close(repo
);
9339 got_object_commit_close(commit
);
9341 got_worktree_close(worktree
);
9343 const struct got_error
*pack_err
=
9344 got_repo_pack_fds_close(pack_fds
);
9349 free(commit_id_str
);
9356 for (i
= 0; i
< argc
; i
++)
9365 main(int argc
, char *argv
[])
9367 const struct got_error
*error
= NULL
;
9368 const struct tog_cmd
*cmd
= NULL
;
9369 int ch
, hflag
= 0, Vflag
= 0;
9370 char **cmd_argv
= NULL
;
9371 static const struct option longopts
[] = {
9372 { "version", no_argument
, NULL
, 'V' },
9375 char *diff_algo_str
= NULL
;
9377 if (!isatty(STDIN_FILENO
))
9378 errx(1, "standard input is not a tty");
9380 setlocale(LC_CTYPE
, "");
9382 while ((ch
= getopt_long(argc
, argv
, "+hV", longopts
, NULL
)) != -1) {
9402 got_version_print_str();
9407 if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd unveil",
9415 /* Build an argument vector which runs a default command. */
9416 cmd
= &tog_commands
[0];
9418 cmd_argv
= make_argv(argc
, cmd
->name
);
9422 /* Did the user specify a command? */
9423 for (i
= 0; i
< nitems(tog_commands
); i
++) {
9424 if (strncmp(tog_commands
[i
].name
, argv
[0],
9425 strlen(argv
[0])) == 0) {
9426 cmd
= &tog_commands
[i
];
9432 diff_algo_str
= getenv("TOG_DIFF_ALGORITHM");
9433 if (diff_algo_str
) {
9434 if (strcasecmp(diff_algo_str
, "patience") == 0)
9435 tog_diff_algo
= GOT_DIFF_ALGORITHM_PATIENCE
;
9436 if (strcasecmp(diff_algo_str
, "myers") == 0)
9437 tog_diff_algo
= GOT_DIFF_ALGORITHM_MYERS
;
9443 /* No command specified; try log with a path */
9444 error
= tog_log_with_path(argc
, argv
);
9449 error
= cmd
->cmd_main(argc
, cmd_argv
? cmd_argv
: argv
);
9456 for (i
= 0; i
< argc
; i
++)
9461 if (error
&& error
->code
!= GOT_ERR_CANCELLED
&&
9462 error
->code
!= GOT_ERR_EOF
&&
9463 error
->code
!= GOT_ERR_PRIVSEP_EXIT
&&
9464 error
->code
!= GOT_ERR_PRIVSEP_PIPE
&&
9465 !(error
->code
== GOT_ERR_ERRNO
&& errno
== EINTR
))
9466 fprintf(stderr
, "%s: %s\n", getprogname(), error
->msg
);