2 * Copyright (c) 2018, 2019, 2020 Stefan Sperling <stsp@openbsd.org>
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 #include "got_compat.h"
19 #include <sys/queue.h>
21 #include <sys/ioctl.h>
26 #if defined(__FreeBSD__) || defined(__APPLE__)
27 #define _XOPEN_SOURCE_EXTENDED /* for ncurses wide-character functions */
48 #include "got_version.h"
49 #include "got_error.h"
50 #include "got_object.h"
51 #include "got_reference.h"
52 #include "got_repository.h"
53 #include "got_gotconfig.h"
55 #include "got_opentemp.h"
57 #include "got_cancel.h"
58 #include "got_commit_graph.h"
59 #include "got_blame.h"
60 #include "got_privsep.h"
62 #include "got_worktree.h"
63 #include "got_keyword.h"
66 #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b))
70 #define MAX(_a,_b) ((_a) > (_b) ? (_a) : (_b))
74 #define CTRL(x) ((x) & 0x1f)
78 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
83 const struct got_error
*(*cmd_main
)(int, char *[]);
84 void (*cmd_usage
)(void);
87 __dead
static void usage(int, int);
88 __dead
static void usage_log(void);
89 __dead
static void usage_diff(void);
90 __dead
static void usage_blame(void);
91 __dead
static void usage_tree(void);
92 __dead
static void usage_ref(void);
94 static const struct got_error
* cmd_log(int, char *[]);
95 static const struct got_error
* cmd_diff(int, char *[]);
96 static const struct got_error
* cmd_blame(int, char *[]);
97 static const struct got_error
* cmd_tree(int, char *[]);
98 static const struct got_error
* cmd_ref(int, char *[]);
100 static const struct tog_cmd tog_commands
[] = {
101 { "log", cmd_log
, usage_log
},
102 { "diff", cmd_diff
, usage_diff
},
103 { "blame", cmd_blame
, usage_blame
},
104 { "tree", cmd_tree
, usage_tree
},
105 { "ref", cmd_ref
, usage_ref
},
117 /* Match _DIFF to _HELP with enum tog_view_type TOG_VIEW_* counterparts. */
118 enum tog_keymap_type
{
119 TOG_KEYMAP_KEYS
= -2,
135 #define HSPLIT_SCALE 0.3f /* default horizontal split scale */
137 #define TOG_EOF_STRING "(END)"
139 struct commit_queue_entry
{
140 TAILQ_ENTRY(commit_queue_entry
) entry
;
141 struct got_object_id
*id
;
142 struct got_commit_object
*commit
;
146 TAILQ_HEAD(commit_queue_head
, commit_queue_entry
);
147 struct commit_queue
{
149 struct commit_queue_head head
;
153 STAILQ_ENTRY(tog_color
) entry
;
157 STAILQ_HEAD(tog_colors
, tog_color
);
159 static struct got_reflist_head tog_refs
= TAILQ_HEAD_INITIALIZER(tog_refs
);
160 static struct got_reflist_object_id_map
*tog_refs_idmap
;
162 struct got_object_id
*id
;
166 static enum got_diff_algorithm tog_diff_algo
= GOT_DIFF_ALGORITHM_PATIENCE
;
168 static const struct got_error
*
169 tog_ref_cmp_by_name(void *arg
, int *cmp
, struct got_reference
*re1
,
170 struct got_reference
* re2
)
172 const char *name1
= got_ref_get_name(re1
);
173 const char *name2
= got_ref_get_name(re2
);
174 int isbackup1
, isbackup2
;
176 /* Sort backup refs towards the bottom of the list. */
177 isbackup1
= strncmp(name1
, "refs/got/backup/", 16) == 0;
178 isbackup2
= strncmp(name2
, "refs/got/backup/", 16) == 0;
179 if (!isbackup1
&& isbackup2
) {
182 } else if (isbackup1
&& !isbackup2
) {
187 *cmp
= got_path_cmp(name1
, name2
, strlen(name1
), strlen(name2
));
191 static const struct got_error
*
192 tog_load_refs(struct got_repository
*repo
, int sort_by_date
)
194 const struct got_error
*err
;
196 err
= got_ref_list(&tog_refs
, repo
, NULL
, sort_by_date
?
197 got_ref_cmp_by_commit_timestamp_descending
: tog_ref_cmp_by_name
,
202 return got_reflist_object_id_map_create(&tog_refs_idmap
, &tog_refs
,
209 if (tog_refs_idmap
) {
210 got_reflist_object_id_map_free(tog_refs_idmap
);
211 tog_refs_idmap
= NULL
;
213 got_ref_list_free(&tog_refs
);
216 static const struct got_error
*
217 add_color(struct tog_colors
*colors
, const char *pattern
,
218 int idx
, short color
)
220 const struct got_error
*err
= NULL
;
221 struct tog_color
*tc
;
224 if (idx
< 1 || idx
> COLOR_PAIRS
- 1)
227 init_pair(idx
, color
, -1);
229 tc
= calloc(1, sizeof(*tc
));
231 return got_error_from_errno("calloc");
232 regerr
= regcomp(&tc
->regex
, pattern
,
233 REG_EXTENDED
| REG_NOSUB
| REG_NEWLINE
);
235 static char regerr_msg
[512];
236 static char err_msg
[512];
237 regerror(regerr
, &tc
->regex
, regerr_msg
,
239 snprintf(err_msg
, sizeof(err_msg
), "regcomp: %s",
241 err
= got_error_msg(GOT_ERR_REGEX
, err_msg
);
246 STAILQ_INSERT_HEAD(colors
, tc
, entry
);
251 free_colors(struct tog_colors
*colors
)
253 struct tog_color
*tc
;
255 while (!STAILQ_EMPTY(colors
)) {
256 tc
= STAILQ_FIRST(colors
);
257 STAILQ_REMOVE_HEAD(colors
, entry
);
263 static struct tog_color
*
264 get_color(struct tog_colors
*colors
, int colorpair
)
266 struct tog_color
*tc
= NULL
;
268 STAILQ_FOREACH(tc
, colors
, entry
) {
269 if (tc
->colorpair
== colorpair
)
277 default_color_value(const char *envvar
)
279 if (strcmp(envvar
, "TOG_COLOR_DIFF_MINUS") == 0)
280 return COLOR_MAGENTA
;
281 if (strcmp(envvar
, "TOG_COLOR_DIFF_PLUS") == 0)
283 if (strcmp(envvar
, "TOG_COLOR_DIFF_CHUNK_HEADER") == 0)
285 if (strcmp(envvar
, "TOG_COLOR_DIFF_META") == 0)
287 if (strcmp(envvar
, "TOG_COLOR_TREE_SUBMODULE") == 0)
288 return COLOR_MAGENTA
;
289 if (strcmp(envvar
, "TOG_COLOR_TREE_SYMLINK") == 0)
290 return COLOR_MAGENTA
;
291 if (strcmp(envvar
, "TOG_COLOR_TREE_DIRECTORY") == 0)
293 if (strcmp(envvar
, "TOG_COLOR_TREE_EXECUTABLE") == 0)
295 if (strcmp(envvar
, "TOG_COLOR_COMMIT") == 0)
297 if (strcmp(envvar
, "TOG_COLOR_AUTHOR") == 0)
299 if (strcmp(envvar
, "TOG_COLOR_DATE") == 0)
301 if (strcmp(envvar
, "TOG_COLOR_REFS_HEADS") == 0)
303 if (strcmp(envvar
, "TOG_COLOR_REFS_TAGS") == 0)
304 return COLOR_MAGENTA
;
305 if (strcmp(envvar
, "TOG_COLOR_REFS_REMOTES") == 0)
307 if (strcmp(envvar
, "TOG_COLOR_REFS_BACKUP") == 0)
314 get_color_value(const char *envvar
)
316 const char *val
= getenv(envvar
);
319 return default_color_value(envvar
);
321 if (strcasecmp(val
, "black") == 0)
323 if (strcasecmp(val
, "red") == 0)
325 if (strcasecmp(val
, "green") == 0)
327 if (strcasecmp(val
, "yellow") == 0)
329 if (strcasecmp(val
, "blue") == 0)
331 if (strcasecmp(val
, "magenta") == 0)
332 return COLOR_MAGENTA
;
333 if (strcasecmp(val
, "cyan") == 0)
335 if (strcasecmp(val
, "white") == 0)
337 if (strcasecmp(val
, "default") == 0)
340 return default_color_value(envvar
);
343 struct diff_worktree_arg
{
344 struct got_repository
*repo
;
345 struct got_worktree
*worktree
;
346 struct got_diff_line
**lines
;
347 struct got_diffstat_cb_arg
*diffstat
;
356 int ignore_whitespace
;
358 enum got_diff_algorithm diff_algo
;
361 struct tog_diff_view_state
{
362 struct got_object_id
*id1
, *id2
;
363 const char *label1
, *label2
;
364 const char *worktree_root
;
369 int first_displayed_line
;
370 int last_displayed_line
;
373 int ignore_whitespace
;
377 struct got_repository
*repo
;
378 struct got_pathlist_head
*paths
;
379 struct got_diff_line
*lines
;
384 /* passed from log or blame view; may be NULL */
385 struct tog_view
*parent_view
;
388 #define TOG_WORKTREE_CHANGES_LOCAL_MSG "work tree changes"
389 #define TOG_WORKTREE_CHANGES_STAGED_MSG "staged work tree changes"
391 #define TOG_WORKTREE_CHANGES_LOCAL (1 << 0)
392 #define TOG_WORKTREE_CHANGES_STAGED (1 << 1)
393 #define TOG_WORKTREE_CHANGES_ALL \
394 (TOG_WORKTREE_CHANGES_LOCAL | TOG_WORKTREE_CHANGES_STAGED)
396 struct tog_worktree_ctx
{
404 pthread_mutex_t tog_mutex
= PTHREAD_MUTEX_INITIALIZER
;
405 static volatile sig_atomic_t tog_thread_error
;
407 struct tog_log_thread_args
{
408 pthread_cond_t need_commits
;
409 pthread_cond_t commit_loaded
;
412 struct got_commit_graph
*graph
;
413 struct commit_queue
*real_commits
;
414 struct tog_worktree_ctx wctx
;
415 const char *in_repo_path
;
416 struct got_object_id
*start_id
;
417 struct got_repository
*repo
;
420 pthread_cond_t log_loaded
;
422 struct commit_queue_entry
**first_displayed_entry
;
423 struct commit_queue_entry
**last_displayed_entry
;
424 struct commit_queue_entry
**selected_entry
;
427 int *search_next_done
;
431 regex_t
*limit_regex
;
432 struct commit_queue
*limit_commits
;
433 struct got_worktree
*worktree
;
434 int need_commit_marker
;
439 struct tog_log_view_state
{
440 struct commit_queue
*commits
;
441 struct commit_queue_entry
*first_displayed_entry
;
442 struct commit_queue_entry
*last_displayed_entry
;
443 struct commit_queue_entry
*selected_entry
;
444 struct commit_queue_entry
*marked_entry
;
445 struct commit_queue real_commits
;
450 struct got_repository
*repo
;
451 struct got_object_id
*start_id
;
454 struct tog_log_thread_args thread_args
;
455 struct commit_queue_entry
*matched_entry
;
456 struct commit_queue_entry
*search_entry
;
457 struct tog_colors colors
;
461 struct commit_queue limit_commits
;
464 #define TOG_COLOR_DIFF_MINUS 1
465 #define TOG_COLOR_DIFF_PLUS 2
466 #define TOG_COLOR_DIFF_CHUNK_HEADER 3
467 #define TOG_COLOR_DIFF_META 4
468 #define TOG_COLOR_TREE_SUBMODULE 5
469 #define TOG_COLOR_TREE_SYMLINK 6
470 #define TOG_COLOR_TREE_DIRECTORY 7
471 #define TOG_COLOR_TREE_EXECUTABLE 8
472 #define TOG_COLOR_COMMIT 9
473 #define TOG_COLOR_AUTHOR 10
474 #define TOG_COLOR_DATE 11
475 #define TOG_COLOR_REFS_HEADS 12
476 #define TOG_COLOR_REFS_TAGS 13
477 #define TOG_COLOR_REFS_REMOTES 14
478 #define TOG_COLOR_REFS_BACKUP 15
480 struct tog_blame_cb_args
{
481 struct tog_blame_line
*lines
; /* one per line */
484 struct tog_view
*view
;
485 struct got_object_id
*commit_id
;
489 struct tog_blame_thread_args
{
491 struct got_repository
*repo
;
492 struct tog_blame_cb_args
*cb_args
;
494 got_cancel_cb cancel_cb
;
496 pthread_cond_t blame_complete
;
502 struct tog_blame_line
*lines
;
506 struct tog_blame_thread_args thread_args
;
507 struct tog_blame_cb_args cb_args
;
512 struct tog_blame_view_state
{
513 int first_displayed_line
;
514 int last_displayed_line
;
516 int last_diffed_line
;
520 struct got_object_id_queue blamed_commits
;
521 struct got_object_qid
*blamed_commit
;
523 struct got_repository
*repo
;
524 struct got_object_id
*commit_id
;
525 struct got_object_id
*id_to_log
;
526 struct tog_blame blame
;
528 struct tog_colors colors
;
531 struct tog_parent_tree
{
532 TAILQ_ENTRY(tog_parent_tree
) entry
;
533 struct got_tree_object
*tree
;
534 struct got_tree_entry
*first_displayed_entry
;
535 struct got_tree_entry
*selected_entry
;
539 TAILQ_HEAD(tog_parent_trees
, tog_parent_tree
);
541 struct tog_tree_view_state
{
543 struct got_object_id
*commit_id
;/* commit which this tree belongs to */
544 struct got_tree_object
*root
; /* the commit's root tree entry */
545 struct got_tree_object
*tree
; /* currently displayed (sub-)tree */
546 struct got_tree_entry
*first_displayed_entry
;
547 struct got_tree_entry
*last_displayed_entry
;
548 struct got_tree_entry
*selected_entry
;
549 int ndisplayed
, selected
, show_ids
;
550 struct tog_parent_trees parents
; /* parent trees of current sub-tree */
552 struct got_repository
*repo
;
553 struct got_tree_entry
*matched_entry
;
554 struct tog_colors colors
;
557 struct tog_reflist_entry
{
558 TAILQ_ENTRY(tog_reflist_entry
) entry
;
559 struct got_reference
*ref
;
563 TAILQ_HEAD(tog_reflist_head
, tog_reflist_entry
);
565 struct tog_ref_view_state
{
566 struct tog_reflist_head refs
;
567 struct tog_reflist_entry
*first_displayed_entry
;
568 struct tog_reflist_entry
*last_displayed_entry
;
569 struct tog_reflist_entry
*selected_entry
;
570 int nrefs
, ndisplayed
, selected
, show_date
, show_ids
, sort_by_date
;
571 struct got_repository
*repo
;
572 struct tog_reflist_entry
*matched_entry
;
573 struct tog_colors colors
;
576 struct tog_help_view_state
{
581 int first_displayed_line
;
582 int last_displayed_line
;
587 enum tog_keymap_type type
;
590 #define GENERATE_HELP \
591 KEYMAP_("Global", TOG_KEYMAP_GLOBAL), \
592 KEY_("H F1", "Open view-specific help (double tap for all help)"), \
593 KEY_("k C-p Up", "Move cursor or page up one line"), \
594 KEY_("j C-n Down", "Move cursor or page down one line"), \
595 KEY_("C-b b PgUp", "Scroll the view up one page"), \
596 KEY_("C-f f PgDn Space", "Scroll the view down one page"), \
597 KEY_("C-u u", "Scroll the view up one half page"), \
598 KEY_("C-d d", "Scroll the view down one half page"), \
599 KEY_("g", "Go to line N (default: first line)"), \
600 KEY_("Home =", "Go to the first line"), \
601 KEY_("G", "Go to line N (default: last line)"), \
602 KEY_("End *", "Go to the last line"), \
603 KEY_("l Right", "Scroll the view right"), \
604 KEY_("h Left", "Scroll the view left"), \
605 KEY_("$", "Scroll view to the rightmost position"), \
606 KEY_("0", "Scroll view to the leftmost position"), \
607 KEY_("-", "Decrease size of the focussed split"), \
608 KEY_("+", "Increase size of the focussed split"), \
609 KEY_("Tab", "Switch focus between views"), \
610 KEY_("F", "Toggle fullscreen mode"), \
611 KEY_("S", "Switch split-screen layout"), \
612 KEY_("/", "Open prompt to enter search term"), \
613 KEY_("n", "Find next line/token matching the current search term"), \
614 KEY_("N", "Find previous line/token matching the current search term"),\
615 KEY_("q", "Quit the focussed view; Quit help screen"), \
616 KEY_("Q", "Quit tog"), \
618 KEYMAP_("Log view", TOG_KEYMAP_LOG), \
619 KEY_("< ,", "Move cursor up one commit"), \
620 KEY_("> .", "Move cursor down one commit"), \
621 KEY_("Enter", "Open diff view of the selected commit"), \
622 KEY_("B", "Reload the log view and toggle display of merged commits"), \
623 KEY_("R", "Open ref view of all repository references"), \
624 KEY_("T", "Display tree view of the repository from the selected" \
626 KEY_("m", "Mark or unmark the selected entry for diffing with the " \
627 "next selected commit"), \
628 KEY_("@", "Toggle between displaying author and committer name"), \
629 KEY_("&", "Open prompt to enter term to limit commits displayed"), \
630 KEY_("C-g Backspace", "Cancel current search or log operation"), \
631 KEY_("C-l", "Reload the log view with new repository commits or " \
632 "work tree changes"), \
634 KEYMAP_("Diff view", TOG_KEYMAP_DIFF), \
635 KEY_("K < ,", "Display diff of next line in the file/log entry"), \
636 KEY_("J > .", "Display diff of previous line in the file/log entry"), \
637 KEY_("A", "Toggle between Myers and Patience diff algorithm"), \
638 KEY_("a", "Toggle treatment of file as ASCII irrespective of binary" \
640 KEY_("p", "Write diff to a patch file in /tmp"), \
641 KEY_("(", "Go to the previous file in the diff"), \
642 KEY_(")", "Go to the next file in the diff"), \
643 KEY_("{", "Go to the previous hunk in the diff"), \
644 KEY_("}", "Go to the next hunk in the diff"), \
645 KEY_("[", "Decrease the number of context lines"), \
646 KEY_("]", "Increase the number of context lines"), \
647 KEY_("w", "Toggle ignore whitespace-only changes in the diff"), \
649 KEYMAP_("Blame view", TOG_KEYMAP_BLAME), \
650 KEY_("Enter", "Display diff view of the selected line's commit"), \
651 KEY_("A", "Toggle diff algorithm between Myers and Patience"), \
652 KEY_("L", "Open log view for the currently selected annotated line"), \
653 KEY_("C", "Reload view with the previously blamed commit"), \
654 KEY_("c", "Reload view with the version of the file found in the" \
655 " selected line's commit"), \
656 KEY_("p", "Reload view with the version of the file found in the" \
657 " selected line's parent commit"), \
659 KEYMAP_("Tree view", TOG_KEYMAP_TREE), \
660 KEY_("Enter", "Enter selected directory or open blame view of the" \
662 KEY_("L", "Open log view for the selected entry"), \
663 KEY_("R", "Open ref view of all repository references"), \
664 KEY_("i", "Show object IDs for all tree entries"), \
665 KEY_("Backspace", "Return to the parent directory"), \
667 KEYMAP_("Ref view", TOG_KEYMAP_REF), \
668 KEY_("Enter", "Display log view of the selected reference"), \
669 KEY_("T", "Display tree view of the selected reference"), \
670 KEY_("i", "Toggle display of IDs for all non-symbolic references"), \
671 KEY_("m", "Toggle display of last modified date for each reference"), \
672 KEY_("o", "Toggle reference sort order (name -> timestamp)"), \
673 KEY_("C-l", "Reload view with all repository references")
678 enum tog_keymap_type type
;
681 /* curses io for tog regress */
690 static int using_mock_io
;
692 #define TOG_KEY_SCRDUMP SHRT_MIN
695 * We implement two types of views: parent views and child views.
697 * The 'Tab' key switches focus between a parent view and its child view.
698 * Child views are shown side-by-side to their parent view, provided
699 * there is enough screen estate.
701 * When a new view is opened from within a parent view, this new view
702 * becomes a child view of the parent view, replacing any existing child.
704 * When a new view is opened from within a child view, this new view
705 * becomes a parent view which will obscure the views below until the
706 * user quits the new parent view by typing 'q'.
708 * This list of views contains parent views only.
709 * Child views are only pointed to by their parent view.
711 TAILQ_HEAD(tog_view_list_head
, tog_view
);
714 TAILQ_ENTRY(tog_view
) entry
;
717 int nlines
, ncols
, begin_y
, begin_x
; /* based on split height/width */
718 int resized_y
, resized_x
; /* begin_y/x based on user resizing */
719 int maxx
, x
; /* max column and current start column */
720 int lines
, cols
; /* copies of LINES and COLS */
721 int nscrolled
, offset
; /* lines scrolled and hsplit line offset */
722 int gline
, hiline
; /* navigate to and highlight this nG line */
723 int ch
, count
; /* current keymap and count prefix */
724 int resized
; /* set when in a resize event */
725 int focussed
; /* Only set on one parent or child view at a time. */
727 struct tog_view
*parent
;
728 struct tog_view
*child
;
731 * This flag is initially set on parent views when a new child view
732 * is created. It gets toggled when the 'Tab' key switches focus
733 * between parent and child.
734 * The flag indicates whether focus should be passed on to our child
735 * view if this parent view gets picked for focus after another parent
736 * view was closed. This prevents child views from losing focus in such
741 enum tog_view_mode mode
;
742 /* type-specific state */
743 enum tog_view_type type
;
745 struct tog_diff_view_state diff
;
746 struct tog_log_view_state log
;
747 struct tog_blame_view_state blame
;
748 struct tog_tree_view_state tree
;
749 struct tog_ref_view_state ref
;
750 struct tog_help_view_state help
;
753 const struct got_error
*(*show
)(struct tog_view
*);
754 const struct got_error
*(*input
)(struct tog_view
**,
755 struct tog_view
*, int);
756 const struct got_error
*(*reset
)(struct tog_view
*);
757 const struct got_error
*(*resize
)(struct tog_view
*, int);
758 const struct got_error
*(*close
)(struct tog_view
*);
760 const struct got_error
*(*search_start
)(struct tog_view
*);
761 const struct got_error
*(*search_next
)(struct tog_view
*);
762 void (*search_setup
)(struct tog_view
*, FILE **, off_t
**, size_t *,
763 int **, int **, int **, int **);
766 #define TOG_SEARCH_FORWARD 1
767 #define TOG_SEARCH_BACKWARD 2
768 int search_next_done
;
769 #define TOG_SEARCH_HAVE_MORE 1
770 #define TOG_SEARCH_NO_MORE 2
771 #define TOG_SEARCH_HAVE_NONE 3
777 static const struct got_error
*open_diff_view(struct tog_view
*,
778 struct got_object_id
*, struct got_object_id
*, const char *, const char *,
779 int, int, int, int, int, const char *, struct tog_view
*,
780 struct got_repository
*, struct got_pathlist_head
*);
781 static const struct got_error
*show_diff_view(struct tog_view
*);
782 static const struct got_error
*input_diff_view(struct tog_view
**,
783 struct tog_view
*, int);
784 static const struct got_error
*reset_diff_view(struct tog_view
*);
785 static const struct got_error
* close_diff_view(struct tog_view
*);
786 static const struct got_error
*search_start_diff_view(struct tog_view
*);
787 static void search_setup_diff_view(struct tog_view
*, FILE **, off_t
**,
788 size_t *, int **, int **, int **, int **);
789 static const struct got_error
*search_next_view_match(struct tog_view
*);
791 static const struct got_error
*open_log_view(struct tog_view
*,
792 struct got_object_id
*, struct got_repository
*,
793 const char *, const char *, int, struct got_worktree
*);
794 static const struct got_error
* show_log_view(struct tog_view
*);
795 static const struct got_error
*input_log_view(struct tog_view
**,
796 struct tog_view
*, int);
797 static const struct got_error
*resize_log_view(struct tog_view
*, int);
798 static const struct got_error
*close_log_view(struct tog_view
*);
799 static const struct got_error
*search_start_log_view(struct tog_view
*);
800 static const struct got_error
*search_next_log_view(struct tog_view
*);
802 static const struct got_error
*open_blame_view(struct tog_view
*, char *,
803 struct got_object_id
*, struct got_repository
*);
804 static const struct got_error
*show_blame_view(struct tog_view
*);
805 static const struct got_error
*input_blame_view(struct tog_view
**,
806 struct tog_view
*, int);
807 static const struct got_error
*reset_blame_view(struct tog_view
*);
808 static const struct got_error
*close_blame_view(struct tog_view
*);
809 static const struct got_error
*search_start_blame_view(struct tog_view
*);
810 static void search_setup_blame_view(struct tog_view
*, FILE **, off_t
**,
811 size_t *, int **, int **, int **, int **);
813 static const struct got_error
*open_tree_view(struct tog_view
*,
814 struct got_object_id
*, const char *, struct got_repository
*);
815 static const struct got_error
*show_tree_view(struct tog_view
*);
816 static const struct got_error
*input_tree_view(struct tog_view
**,
817 struct tog_view
*, int);
818 static const struct got_error
*close_tree_view(struct tog_view
*);
819 static const struct got_error
*search_start_tree_view(struct tog_view
*);
820 static const struct got_error
*search_next_tree_view(struct tog_view
*);
822 static const struct got_error
*open_ref_view(struct tog_view
*,
823 struct got_repository
*);
824 static const struct got_error
*show_ref_view(struct tog_view
*);
825 static const struct got_error
*input_ref_view(struct tog_view
**,
826 struct tog_view
*, int);
827 static const struct got_error
*close_ref_view(struct tog_view
*);
828 static const struct got_error
*search_start_ref_view(struct tog_view
*);
829 static const struct got_error
*search_next_ref_view(struct tog_view
*);
831 static const struct got_error
*open_help_view(struct tog_view
*,
833 static const struct got_error
*show_help_view(struct tog_view
*);
834 static const struct got_error
*input_help_view(struct tog_view
**,
835 struct tog_view
*, int);
836 static const struct got_error
*reset_help_view(struct tog_view
*);
837 static const struct got_error
* close_help_view(struct tog_view
*);
838 static const struct got_error
*search_start_help_view(struct tog_view
*);
839 static void search_setup_help_view(struct tog_view
*, FILE **, off_t
**,
840 size_t *, int **, int **, int **, int **);
842 static volatile sig_atomic_t tog_sigwinch_received
;
843 static volatile sig_atomic_t tog_sigpipe_received
;
844 static volatile sig_atomic_t tog_sigcont_received
;
845 static volatile sig_atomic_t tog_sigint_received
;
846 static volatile sig_atomic_t tog_sigterm_received
;
849 tog_sigwinch(int signo
)
851 tog_sigwinch_received
= 1;
855 tog_sigpipe(int signo
)
857 tog_sigpipe_received
= 1;
861 tog_sigcont(int signo
)
863 tog_sigcont_received
= 1;
867 tog_sigint(int signo
)
869 tog_sigint_received
= 1;
873 tog_sigterm(int signo
)
875 tog_sigterm_received
= 1;
879 tog_fatal_signal_received(void)
881 return (tog_sigpipe_received
||
882 tog_sigint_received
|| tog_sigterm_received
);
885 static const struct got_error
*
886 view_close(struct tog_view
*view
)
888 const struct got_error
*err
= NULL
, *child_err
= NULL
;
891 child_err
= view_close(view
->child
);
895 err
= view
->close(view
);
897 del_panel(view
->panel
);
901 delwin(view
->window
);
905 return err
? err
: child_err
;
908 static struct tog_view
*
909 view_open(int nlines
, int ncols
, int begin_y
, int begin_x
,
910 enum tog_view_type type
)
912 struct tog_view
*view
= calloc(1, sizeof(*view
));
920 view
->nlines
= nlines
? nlines
: LINES
- begin_y
;
921 view
->ncols
= ncols
? ncols
: COLS
- begin_x
;
922 view
->begin_y
= begin_y
;
923 view
->begin_x
= begin_x
;
924 view
->window
= newwin(nlines
, ncols
, begin_y
, begin_x
);
925 if (view
->window
== NULL
) {
929 view
->panel
= new_panel(view
->window
);
930 if (view
->panel
== NULL
||
931 set_panel_userptr(view
->panel
, view
) != OK
) {
936 keypad(view
->window
, TRUE
);
941 view_split_begin_x(int begin_x
)
943 if (begin_x
> 0 || COLS
< 120)
945 return (COLS
- MAX(COLS
/ 2, 80));
948 /* XXX Stub till we decide what to do. */
950 view_split_begin_y(int lines
)
952 return lines
* HSPLIT_SCALE
;
955 static const struct got_error
*view_resize(struct tog_view
*);
957 static const struct got_error
*
958 view_splitscreen(struct tog_view
*view
)
960 const struct got_error
*err
= NULL
;
962 if (!view
->resized
&& view
->mode
== TOG_VIEW_SPLIT_HRZN
) {
963 if (view
->resized_y
&& view
->resized_y
< view
->lines
)
964 view
->begin_y
= view
->resized_y
;
966 view
->begin_y
= view_split_begin_y(view
->nlines
);
968 } else if (!view
->resized
) {
969 if (view
->resized_x
&& view
->resized_x
< view
->cols
- 1 &&
971 view
->begin_x
= view
->resized_x
;
973 view
->begin_x
= view_split_begin_x(0);
976 view
->nlines
= LINES
- view
->begin_y
;
977 view
->ncols
= COLS
- view
->begin_x
;
980 err
= view_resize(view
);
984 if (view
->parent
&& view
->mode
== TOG_VIEW_SPLIT_HRZN
)
985 view
->parent
->nlines
= view
->begin_y
;
987 if (mvwin(view
->window
, view
->begin_y
, view
->begin_x
) == ERR
)
988 return got_error_from_errno("mvwin");
993 static const struct got_error
*
994 view_fullscreen(struct tog_view
*view
)
996 const struct got_error
*err
= NULL
;
999 view
->begin_y
= view
->resized
? view
->begin_y
: 0;
1000 view
->nlines
= view
->resized
? view
->nlines
: LINES
;
1002 view
->lines
= LINES
;
1004 err
= view_resize(view
);
1008 if (mvwin(view
->window
, view
->begin_y
, view
->begin_x
) == ERR
)
1009 return got_error_from_errno("mvwin");
1015 view_is_parent_view(struct tog_view
*view
)
1017 return view
->parent
== NULL
;
1021 view_is_splitscreen(struct tog_view
*view
)
1023 return view
->begin_x
> 0 || view
->begin_y
> 0;
1027 view_is_fullscreen(struct tog_view
*view
)
1029 return view
->nlines
== LINES
&& view
->ncols
== COLS
;
1033 view_is_hsplit_top(struct tog_view
*view
)
1035 return view
->mode
== TOG_VIEW_SPLIT_HRZN
&& view
->child
&&
1036 view_is_splitscreen(view
->child
);
1040 view_border(struct tog_view
*view
)
1043 const struct tog_view
*view_above
;
1046 return view_border(view
->parent
);
1048 panel
= panel_above(view
->panel
);
1052 view_above
= panel_userptr(panel
);
1053 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
)
1054 mvwhline(view
->window
, view_above
->begin_y
- 1,
1055 view
->begin_x
, ACS_HLINE
, view
->ncols
);
1057 mvwvline(view
->window
, view
->begin_y
, view_above
->begin_x
- 1,
1058 ACS_VLINE
, view
->nlines
);
1061 static const struct got_error
*view_init_hsplit(struct tog_view
*, int);
1062 static const struct got_error
*request_log_commits(struct tog_view
*);
1063 static const struct got_error
*offset_selection_down(struct tog_view
*);
1064 static void offset_selection_up(struct tog_view
*);
1065 static void view_get_split(struct tog_view
*, int *, int *);
1067 static const struct got_error
*
1068 view_resize(struct tog_view
*view
)
1070 const struct got_error
*err
= NULL
;
1071 int dif
, nlines
, ncols
;
1073 dif
= LINES
- view
->lines
; /* line difference */
1075 if (view
->lines
> LINES
)
1076 nlines
= view
->nlines
- (view
->lines
- LINES
);
1078 nlines
= view
->nlines
+ (LINES
- view
->lines
);
1079 if (view
->cols
> COLS
)
1080 ncols
= view
->ncols
- (view
->cols
- COLS
);
1082 ncols
= view
->ncols
+ (COLS
- view
->cols
);
1085 int hs
= view
->child
->begin_y
;
1087 if (!view_is_fullscreen(view
))
1088 view
->child
->begin_x
= view_split_begin_x(view
->begin_x
);
1089 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
||
1090 view
->child
->begin_x
== 0) {
1093 view_fullscreen(view
->child
);
1094 if (view
->child
->focussed
)
1095 show_panel(view
->child
->panel
);
1097 show_panel(view
->panel
);
1099 ncols
= view
->child
->begin_x
;
1101 view_splitscreen(view
->child
);
1102 show_panel(view
->child
->panel
);
1105 * XXX This is ugly and needs to be moved into the above
1106 * logic but "works" for now and my attempts at moving it
1107 * break either 'tab' or 'F' key maps in horizontal splits.
1110 err
= view_splitscreen(view
->child
);
1113 if (dif
< 0) { /* top split decreased */
1114 err
= offset_selection_down(view
);
1121 show_panel(view
->child
->panel
);
1122 nlines
= view
->nlines
;
1124 } else if (view
->parent
== NULL
)
1127 if (view
->resize
&& dif
> 0) {
1128 err
= view
->resize(view
, dif
);
1133 if (wresize(view
->window
, nlines
, ncols
) == ERR
)
1134 return got_error_from_errno("wresize");
1135 if (replace_panel(view
->panel
, view
->window
) == ERR
)
1136 return got_error_from_errno("replace_panel");
1137 wclear(view
->window
);
1139 view
->nlines
= nlines
;
1140 view
->ncols
= ncols
;
1141 view
->lines
= LINES
;
1147 static const struct got_error
*
1148 resize_log_view(struct tog_view
*view
, int increase
)
1150 struct tog_log_view_state
*s
= &view
->state
.log
;
1151 const struct got_error
*err
= NULL
;
1154 if (s
->selected_entry
)
1155 n
= s
->selected_entry
->idx
+ view
->lines
- s
->selected
;
1158 * Request commits to account for the increased
1159 * height so we have enough to populate the view.
1161 if (s
->commits
->ncommits
< n
) {
1162 view
->nscrolled
= n
- s
->commits
->ncommits
+ increase
+ 1;
1163 err
= request_log_commits(view
);
1170 view_adjust_offset(struct tog_view
*view
, int n
)
1175 if (view
->parent
&& view
->parent
->offset
) {
1176 if (view
->parent
->offset
+ n
>= 0)
1177 view
->parent
->offset
+= n
;
1179 view
->parent
->offset
= 0;
1180 } else if (view
->offset
) {
1181 if (view
->offset
- n
>= 0)
1188 static const struct got_error
*
1189 view_resize_split(struct tog_view
*view
, int resize
)
1191 const struct got_error
*err
= NULL
;
1192 struct tog_view
*v
= NULL
;
1199 if (!v
->child
|| !view_is_splitscreen(v
->child
))
1202 v
->resized
= v
->child
->resized
= resize
; /* lock for resize event */
1204 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
) {
1205 if (v
->child
->resized_y
)
1206 v
->child
->begin_y
= v
->child
->resized_y
;
1208 v
->child
->begin_y
-= resize
;
1210 v
->child
->begin_y
+= resize
;
1211 if (v
->child
->begin_y
< 3) {
1213 v
->child
->begin_y
= 3;
1214 } else if (v
->child
->begin_y
> LINES
- 1) {
1216 v
->child
->begin_y
= LINES
- 1;
1219 v
->child
->ncols
= COLS
;
1220 view_adjust_offset(view
, resize
);
1221 err
= view_init_hsplit(v
, v
->child
->begin_y
);
1224 v
->child
->resized_y
= v
->child
->begin_y
;
1226 if (v
->child
->resized_x
)
1227 v
->child
->begin_x
= v
->child
->resized_x
;
1229 v
->child
->begin_x
-= resize
;
1231 v
->child
->begin_x
+= resize
;
1232 if (v
->child
->begin_x
< 11) {
1234 v
->child
->begin_x
= 11;
1235 } else if (v
->child
->begin_x
> COLS
- 1) {
1237 v
->child
->begin_x
= COLS
- 1;
1239 v
->child
->resized_x
= v
->child
->begin_x
;
1242 v
->child
->mode
= v
->mode
;
1243 v
->child
->nlines
= v
->lines
- v
->child
->begin_y
;
1244 v
->child
->ncols
= v
->cols
- v
->child
->begin_x
;
1247 err
= view_fullscreen(v
);
1250 err
= view_splitscreen(v
->child
);
1254 if (v
->mode
== TOG_VIEW_SPLIT_HRZN
) {
1255 err
= offset_selection_down(v
->child
);
1261 err
= v
->resize(v
, 0);
1262 else if (v
->child
->resize
)
1263 err
= v
->child
->resize(v
->child
, 0);
1265 v
->resized
= v
->child
->resized
= 0;
1271 view_transfer_size(struct tog_view
*dst
, struct tog_view
*src
)
1273 struct tog_view
*v
= src
->child
? src
->child
: src
;
1275 dst
->resized_x
= v
->resized_x
;
1276 dst
->resized_y
= v
->resized_y
;
1279 static const struct got_error
*
1280 view_close_child(struct tog_view
*view
)
1282 const struct got_error
*err
= NULL
;
1284 if (view
->child
== NULL
)
1287 err
= view_close(view
->child
);
1292 static const struct got_error
*
1293 view_set_child(struct tog_view
*view
, struct tog_view
*child
)
1295 const struct got_error
*err
= NULL
;
1297 view
->child
= child
;
1298 child
->parent
= view
;
1300 err
= view_resize(view
);
1304 if (view
->child
->resized_x
|| view
->child
->resized_y
)
1305 err
= view_resize_split(view
, 0);
1310 static const struct got_error
*view_dispatch_request(struct tog_view
**,
1311 struct tog_view
*, enum tog_view_type
, int, int);
1313 static const struct got_error
*
1314 view_request_new(struct tog_view
**requested
, struct tog_view
*view
,
1315 enum tog_view_type request
)
1317 struct tog_view
*new_view
= NULL
;
1318 const struct got_error
*err
;
1323 if (view_is_parent_view(view
) && request
!= TOG_VIEW_HELP
)
1324 view_get_split(view
, &y
, &x
);
1326 err
= view_dispatch_request(&new_view
, view
, request
, y
, x
);
1329 * The ref view expects its selected entry to resolve to
1330 * a commit object id to open either a log or tree view.
1332 if (err
->code
!= GOT_ERR_OBJ_TYPE
)
1334 view
->action
= "commit reference required";
1338 if (view_is_parent_view(view
) && view
->mode
== TOG_VIEW_SPLIT_HRZN
&&
1339 request
!= TOG_VIEW_HELP
) {
1340 err
= view_init_hsplit(view
, y
);
1346 new_view
->focussed
= 1;
1347 new_view
->mode
= view
->mode
;
1348 new_view
->nlines
= request
== TOG_VIEW_HELP
?
1349 view
->lines
: view
->lines
- y
;
1351 if (view_is_parent_view(view
) && request
!= TOG_VIEW_HELP
) {
1352 view_transfer_size(new_view
, view
);
1353 err
= view_close_child(view
);
1356 err
= view_set_child(view
, new_view
);
1359 view
->focus_child
= 1;
1361 *requested
= new_view
;
1367 tog_resizeterm(void)
1370 struct winsize size
;
1372 if (ioctl(STDOUT_FILENO
, TIOCGWINSZ
, &size
) < 0) {
1373 cols
= 80; /* Default */
1377 lines
= size
.ws_row
;
1379 resize_term(lines
, cols
);
1382 static const struct got_error
*
1383 view_search_start(struct tog_view
*view
, int fast_refresh
)
1385 const struct got_error
*err
= NULL
;
1386 struct tog_view
*v
= view
;
1390 if (view
->search_started
) {
1391 regfree(&view
->regex
);
1392 view
->searching
= 0;
1393 memset(&view
->regmatch
, 0, sizeof(view
->regmatch
));
1395 view
->search_started
= 0;
1397 if (view
->nlines
< 1)
1400 if (view_is_hsplit_top(view
))
1402 else if (view
->mode
== TOG_VIEW_SPLIT_VERT
&& view
->parent
)
1405 if (tog_io
.input_str
!= NULL
) {
1406 if (strlcpy(pattern
, tog_io
.input_str
, sizeof(pattern
)) >=
1408 return got_error(GOT_ERR_NO_SPACE
);
1410 mvwaddstr(v
->window
, v
->nlines
- 1, 0, "/");
1411 wclrtoeol(v
->window
);
1412 nodelay(v
->window
, FALSE
); /* block for search term input */
1415 ret
= wgetnstr(v
->window
, pattern
, sizeof(pattern
));
1416 wrefresh(v
->window
);
1419 nodelay(v
->window
, TRUE
);
1420 if (!fast_refresh
&& !using_mock_io
)
1426 if (regcomp(&view
->regex
, pattern
, REG_EXTENDED
| REG_NEWLINE
) == 0) {
1427 err
= view
->search_start(view
);
1429 regfree(&view
->regex
);
1432 view
->search_started
= 1;
1433 view
->searching
= TOG_SEARCH_FORWARD
;
1434 view
->search_next_done
= 0;
1435 view
->search_next(view
);
1441 /* Switch split mode. If view is a parent or child, draw the new splitscreen. */
1442 static const struct got_error
*
1443 switch_split(struct tog_view
*view
)
1445 const struct got_error
*err
= NULL
;
1446 struct tog_view
*v
= NULL
;
1453 if (v
->mode
== TOG_VIEW_SPLIT_HRZN
)
1454 v
->mode
= TOG_VIEW_SPLIT_VERT
;
1456 v
->mode
= TOG_VIEW_SPLIT_HRZN
;
1460 else if (v
->mode
== TOG_VIEW_SPLIT_VERT
&& v
->cols
< 120)
1461 v
->mode
= TOG_VIEW_SPLIT_NONE
;
1463 view_get_split(v
, &v
->child
->begin_y
, &v
->child
->begin_x
);
1464 if (v
->mode
== TOG_VIEW_SPLIT_HRZN
&& v
->child
->resized_y
)
1465 v
->child
->begin_y
= v
->child
->resized_y
;
1466 else if (v
->mode
== TOG_VIEW_SPLIT_VERT
&& v
->child
->resized_x
)
1467 v
->child
->begin_x
= v
->child
->resized_x
;
1470 if (v
->mode
== TOG_VIEW_SPLIT_HRZN
) {
1472 v
->child
->ncols
= COLS
;
1473 v
->child
->nscrolled
= LINES
- v
->child
->nlines
;
1475 err
= view_init_hsplit(v
, v
->child
->begin_y
);
1479 v
->child
->mode
= v
->mode
;
1480 v
->child
->nlines
= v
->lines
- v
->child
->begin_y
;
1483 err
= view_fullscreen(v
);
1486 err
= view_splitscreen(v
->child
);
1490 if (v
->mode
== TOG_VIEW_SPLIT_NONE
)
1491 v
->mode
= TOG_VIEW_SPLIT_VERT
;
1492 if (v
->mode
== TOG_VIEW_SPLIT_HRZN
) {
1493 err
= offset_selection_down(v
);
1496 err
= offset_selection_down(v
->child
);
1500 offset_selection_up(v
);
1501 offset_selection_up(v
->child
);
1504 err
= v
->resize(v
, 0);
1505 else if (v
->child
->resize
)
1506 err
= v
->child
->resize(v
->child
, 0);
1512 * Strip trailing whitespace from str starting at byte *n;
1513 * if *n < 0, use strlen(str). Return new str length in *n.
1516 strip_trailing_ws(char *str
, int *n
)
1520 if (str
== NULL
|| *str
== '\0')
1526 while (x
-- > 0 && isspace((unsigned char)str
[x
]))
1533 * Extract visible substring of line y from the curses screen
1534 * and strip trailing whitespace. If vline is set, overwrite
1535 * line[vline] with '|' because the ACS_VLINE character is
1536 * written out as 'x'. Write the line to file f.
1538 static const struct got_error
*
1539 view_write_line(FILE *f
, int y
, int vline
)
1541 char line
[COLS
* MB_LEN_MAX
]; /* allow for multibyte chars */
1544 r
= mvwinnstr(curscr
, y
, 0, line
, sizeof(line
));
1546 return got_error_fmt(GOT_ERR_RANGE
,
1547 "failed to extract line %d", y
);
1550 * In some views, lines are padded with blanks to COLS width.
1551 * Strip them so we can diff without the -b flag when testing.
1553 strip_trailing_ws(line
, &r
);
1558 w
= fprintf(f
, "%s\n", line
);
1559 if (w
!= r
+ 1) /* \n */
1560 return got_ferror(f
, GOT_ERR_IO
);
1566 * Capture the visible curses screen by writing each line to the
1567 * file at the path set via the TOG_SCR_DUMP environment variable.
1569 static const struct got_error
*
1570 screendump(struct tog_view
*view
)
1572 const struct got_error
*err
;
1575 err
= got_opentemp_truncate(tog_io
.sdump
);
1579 if ((view
->child
&& view
->child
->begin_x
) ||
1580 (view
->parent
&& view
->begin_x
)) {
1581 int ncols
= view
->child
? view
->ncols
: view
->parent
->ncols
;
1583 /* vertical splitscreen */
1584 for (i
= 0; i
< view
->nlines
; ++i
) {
1585 err
= view_write_line(tog_io
.sdump
, i
, ncols
- 1);
1592 /* fullscreen or horizontal splitscreen */
1593 if ((view
->child
&& view
->child
->begin_y
) ||
1594 (view
->parent
&& view
->begin_y
)) /* hsplit */
1595 hline
= view
->child
?
1596 view
->child
->begin_y
: view
->begin_y
;
1598 for (i
= 0; i
< view
->lines
; i
++) {
1599 if (hline
&& i
== hline
- 1) {
1602 /* ACS_HLINE writes out as 'q', overwrite it */
1603 for (c
= 0; c
< view
->cols
; ++c
)
1604 fputc('-', tog_io
.sdump
);
1605 fputc('\n', tog_io
.sdump
);
1609 err
= view_write_line(tog_io
.sdump
, i
, 0);
1620 * Compute view->count from numeric input. Assign total to view->count and
1621 * return first non-numeric key entered.
1624 get_compound_key(struct tog_view
*view
, int c
)
1626 struct tog_view
*v
= view
;
1629 if (view_is_hsplit_top(view
))
1631 else if (view
->mode
== TOG_VIEW_SPLIT_VERT
&& view
->parent
)
1635 cbreak(); /* block for input */
1636 nodelay(view
->window
, FALSE
);
1637 wmove(v
->window
, v
->nlines
- 1, 0);
1638 wclrtoeol(v
->window
);
1639 waddch(v
->window
, ':');
1642 x
= getcurx(v
->window
);
1643 if (x
!= ERR
&& x
< view
->ncols
) {
1644 waddch(v
->window
, c
);
1645 wrefresh(v
->window
);
1649 * Don't overflow. Max valid request should be the greatest
1650 * between the longest and total lines; cap at 10 million.
1655 n
= n
* 10 + (c
- '0');
1656 } while (((c
= wgetch(view
->window
))) >= '0' && c
<= '9' && c
!= ERR
);
1658 if (c
== 'G' || c
== 'g') { /* nG key map */
1659 view
->gline
= view
->hiline
= n
;
1664 /* Massage excessive or inapplicable values at the input handler. */
1671 action_report(struct tog_view
*view
)
1673 struct tog_view
*v
= view
;
1675 if (view_is_hsplit_top(view
))
1677 else if (view
->mode
== TOG_VIEW_SPLIT_VERT
&& view
->parent
)
1680 wmove(v
->window
, v
->nlines
- 1, 0);
1681 wclrtoeol(v
->window
);
1682 wprintw(v
->window
, ":%s", view
->action
);
1683 wrefresh(v
->window
);
1686 * Clear action status report. Only clear in blame view
1687 * once annotating is complete, otherwise it's too fast.
1688 * In diff view, let its state control view->action lifetime.
1690 if (view
->type
== TOG_VIEW_BLAME
) {
1691 if (view
->state
.blame
.blame_complete
)
1692 view
->action
= NULL
;
1693 } else if (view
->type
== TOG_VIEW_DIFF
) {
1694 view
->action
= view
->state
.diff
.action
;
1696 view
->action
= NULL
;
1700 * Read the next line from the test script and assign
1701 * key instruction to *ch. If at EOF, set the *done flag.
1703 static const struct got_error
*
1704 tog_read_script_key(FILE *script
, struct tog_view
*view
, int *ch
, int *done
)
1706 const struct got_error
*err
= NULL
;
1712 if (view
->count
&& --view
->count
) {
1718 if ((n
= getline(&line
, &linesz
, script
)) == -1) {
1723 err
= got_ferror(script
, GOT_ERR_IO
);
1728 if (strncasecmp(line
, "WAIT_FOR_UI", 11) == 0)
1729 tog_io
.wait_for_ui
= 1;
1730 else if (strncasecmp(line
, "KEY_ENTER", 9) == 0)
1732 else if (strncasecmp(line
, "KEY_RIGHT", 9) == 0)
1734 else if (strncasecmp(line
, "KEY_LEFT", 8) == 0)
1736 else if (strncasecmp(line
, "KEY_DOWN", 8) == 0)
1738 else if (strncasecmp(line
, "KEY_UP", 6) == 0)
1740 else if (strncasecmp(line
, "TAB", 3) == 0)
1742 else if (strncasecmp(line
, "SCREENDUMP", 10) == 0)
1743 *ch
= TOG_KEY_SCRDUMP
;
1744 else if (isdigit((unsigned char)*line
)) {
1747 while (isdigit((unsigned char)*t
))
1749 view
->ch
= *ch
= *t
;
1751 /* ignore error, view->count is 0 if instruction is invalid */
1752 view
->count
= strtonum(line
, 0, INT_MAX
, NULL
);
1755 if (n
> 2 && (*ch
== '/' || *ch
== '&')) {
1756 /* skip leading keymap and trim trailing newline */
1757 tog_io
.input_str
= strndup(line
+ 1, n
- 2);
1758 if (tog_io
.input_str
== NULL
) {
1759 err
= got_error_from_errno("strndup");
1771 log_mark_clear(struct tog_log_view_state
*s
)
1773 s
->marked_entry
= NULL
;
1776 static const struct got_error
*
1777 view_input(struct tog_view
**new, int *done
, struct tog_view
*view
,
1778 struct tog_view_list_head
*views
, int fast_refresh
)
1780 const struct got_error
*err
= NULL
;
1787 action_report(view
);
1789 /* Clear "no matches" indicator. */
1790 if (view
->search_next_done
== TOG_SEARCH_NO_MORE
||
1791 view
->search_next_done
== TOG_SEARCH_HAVE_NONE
) {
1792 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
1796 if (view
->searching
&& !view
->search_next_done
) {
1797 errcode
= pthread_mutex_unlock(&tog_mutex
);
1799 return got_error_set_errno(errcode
,
1800 "pthread_mutex_unlock");
1802 errcode
= pthread_mutex_lock(&tog_mutex
);
1804 return got_error_set_errno(errcode
,
1805 "pthread_mutex_lock");
1806 view
->search_next(view
);
1810 /* Allow threads to make progress while we are waiting for input. */
1811 errcode
= pthread_mutex_unlock(&tog_mutex
);
1813 return got_error_set_errno(errcode
, "pthread_mutex_unlock");
1815 if (using_mock_io
) {
1816 err
= tog_read_script_key(tog_io
.f
, view
, &ch
, done
);
1818 errcode
= pthread_mutex_lock(&tog_mutex
);
1821 } else if (view
->count
&& --view
->count
) {
1823 nodelay(view
->window
, TRUE
);
1824 ch
= wgetch(view
->window
);
1825 /* let C-g or backspace abort unfinished count */
1826 if (ch
== CTRL('g') || ch
== KEY_BACKSPACE
)
1831 ch
= wgetch(view
->window
);
1832 if (ch
>= '1' && ch
<= '9')
1833 view
->ch
= ch
= get_compound_key(view
, ch
);
1835 if (view
->hiline
&& ch
!= ERR
&& ch
!= 0)
1836 view
->hiline
= 0; /* key pressed, clear line highlight */
1837 wtimeout(view
->window
, fast_refresh
? 100 : 1000); /* milliseconds */
1838 errcode
= pthread_mutex_lock(&tog_mutex
);
1840 return got_error_set_errno(errcode
, "pthread_mutex_lock");
1842 if (tog_sigwinch_received
|| tog_sigcont_received
) {
1844 tog_sigwinch_received
= 0;
1845 tog_sigcont_received
= 0;
1846 TAILQ_FOREACH(v
, views
, entry
) {
1847 err
= view_resize(v
);
1850 err
= v
->input(new, v
, KEY_RESIZE
);
1854 err
= view_resize(v
->child
);
1857 err
= v
->child
->input(new, v
->child
,
1861 if (v
->child
->resized_x
|| v
->child
->resized_y
) {
1862 err
= view_resize_split(v
, 0);
1874 if (view
->type
== TOG_VIEW_HELP
)
1875 err
= view
->reset(view
);
1877 err
= view_request_new(new, view
, TOG_VIEW_HELP
);
1883 view
->child
->focussed
= 1;
1884 view
->focus_child
= 1;
1885 } else if (view
->parent
) {
1887 view
->parent
->focussed
= 1;
1888 view
->parent
->focus_child
= 0;
1889 if (!view_is_splitscreen(view
)) {
1890 if (view
->parent
->resize
) {
1891 err
= view
->parent
->resize(view
->parent
,
1896 offset_selection_up(view
->parent
);
1897 err
= view_fullscreen(view
->parent
);
1904 if (view
->parent
!= NULL
) {
1905 if (view
->parent
->type
== TOG_VIEW_LOG
)
1906 log_mark_clear(&view
->parent
->state
.log
);
1908 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
) {
1909 if (view
->parent
->resize
) {
1911 * Might need more commits
1912 * to fill fullscreen.
1914 err
= view
->parent
->resize(
1919 offset_selection_up(view
->parent
);
1922 err
= view
->input(new, view
, ch
);
1930 if (view_is_parent_view(view
)) {
1931 if (view
->child
== NULL
)
1933 if (view_is_splitscreen(view
->child
)) {
1935 view
->child
->focussed
= 1;
1936 err
= view_fullscreen(view
->child
);
1938 err
= view_splitscreen(view
->child
);
1940 err
= view_resize_split(view
, 0);
1944 err
= view
->child
->input(new, view
->child
,
1947 if (view_is_splitscreen(view
)) {
1948 view
->parent
->focussed
= 0;
1950 err
= view_fullscreen(view
);
1952 err
= view_splitscreen(view
);
1953 if (!err
&& view
->mode
!= TOG_VIEW_SPLIT_HRZN
)
1954 err
= view_resize(view
->parent
);
1956 err
= view_resize_split(view
, 0);
1960 err
= view
->input(new, view
, KEY_RESIZE
);
1965 err
= view
->resize(view
, 0);
1970 if (view
->parent
->resize
) {
1971 err
= view
->parent
->resize(view
->parent
, 0);
1975 err
= offset_selection_down(view
->parent
);
1979 err
= offset_selection_down(view
);
1983 err
= switch_split(view
);
1986 err
= view_resize_split(view
, -1);
1989 err
= view_resize_split(view
, 1);
1995 if (view
->search_start
)
1996 view_search_start(view
, fast_refresh
);
1998 err
= view
->input(new, view
, ch
);
2002 if (view
->search_started
&& view
->search_next
) {
2003 view
->searching
= (ch
== 'n' ?
2004 TOG_SEARCH_FORWARD
: TOG_SEARCH_BACKWARD
);
2005 view
->search_next_done
= 0;
2006 view
->search_next(view
);
2008 err
= view
->input(new, view
, ch
);
2011 if (tog_diff_algo
== GOT_DIFF_ALGORITHM_MYERS
) {
2012 tog_diff_algo
= GOT_DIFF_ALGORITHM_PATIENCE
;
2013 view
->action
= "Patience diff algorithm";
2015 tog_diff_algo
= GOT_DIFF_ALGORITHM_MYERS
;
2016 view
->action
= "Myers diff algorithm";
2018 TAILQ_FOREACH(v
, views
, entry
) {
2024 if (v
->child
&& v
->child
->reset
) {
2025 err
= v
->child
->reset(v
->child
);
2031 case TOG_KEY_SCRDUMP
:
2032 err
= screendump(view
);
2035 err
= view
->input(new, view
, ch
);
2043 view_needs_focus_indication(struct tog_view
*view
)
2045 if (view_is_parent_view(view
)) {
2046 if (view
->child
== NULL
|| view
->child
->focussed
)
2048 if (!view_is_splitscreen(view
->child
))
2050 } else if (!view_is_splitscreen(view
))
2053 return view
->focussed
;
2056 static const struct got_error
*
2059 const struct got_error
*err
= NULL
;
2061 if (tog_io
.cin
&& fclose(tog_io
.cin
) == EOF
)
2062 err
= got_ferror(tog_io
.cin
, GOT_ERR_IO
);
2063 if (tog_io
.cout
&& fclose(tog_io
.cout
) == EOF
&& err
== NULL
)
2064 err
= got_ferror(tog_io
.cout
, GOT_ERR_IO
);
2065 if (tog_io
.f
&& fclose(tog_io
.f
) == EOF
&& err
== NULL
)
2066 err
= got_ferror(tog_io
.f
, GOT_ERR_IO
);
2067 if (tog_io
.sdump
&& fclose(tog_io
.sdump
) == EOF
&& err
== NULL
)
2068 err
= got_ferror(tog_io
.sdump
, GOT_ERR_IO
);
2069 if (tog_io
.input_str
!= NULL
)
2070 free(tog_io
.input_str
);
2075 static const struct got_error
*
2076 view_loop(struct tog_view
*view
)
2078 const struct got_error
*err
= NULL
;
2079 struct tog_view_list_head views
;
2080 struct tog_view
*new_view
;
2082 int fast_refresh
= 10;
2083 int done
= 0, errcode
;
2085 mode
= getenv("TOG_VIEW_SPLIT_MODE");
2086 if (!mode
|| !(*mode
== 'h' || *mode
== 'H'))
2087 view
->mode
= TOG_VIEW_SPLIT_VERT
;
2089 view
->mode
= TOG_VIEW_SPLIT_HRZN
;
2091 errcode
= pthread_mutex_lock(&tog_mutex
);
2093 return got_error_set_errno(errcode
, "pthread_mutex_lock");
2096 TAILQ_INSERT_HEAD(&views
, view
, entry
);
2099 err
= view
->show(view
);
2104 while (!TAILQ_EMPTY(&views
) && !done
&& !tog_thread_error
&&
2105 !tog_fatal_signal_received()) {
2106 /* Refresh fast during initialization, then become slower. */
2107 if (fast_refresh
&& --fast_refresh
== 0 && !using_mock_io
)
2108 halfdelay(10); /* switch to once per second */
2110 err
= view_input(&new_view
, &done
, view
, &views
, fast_refresh
);
2114 if (view
->dying
&& view
== TAILQ_FIRST(&views
) &&
2115 TAILQ_NEXT(view
, entry
) == NULL
)
2121 * When we quit, scroll the screen up a single line
2122 * so we don't lose any information.
2124 TAILQ_FOREACH(v
, &views
, entry
) {
2125 wmove(v
->window
, 0, 0);
2126 wdeleteln(v
->window
);
2127 wnoutrefresh(v
->window
);
2128 if (v
->child
&& !view_is_fullscreen(v
)) {
2129 wmove(v
->child
->window
, 0, 0);
2130 wdeleteln(v
->child
->window
);
2131 wnoutrefresh(v
->child
->window
);
2138 struct tog_view
*v
, *prev
= NULL
;
2140 if (view_is_parent_view(view
))
2141 prev
= TAILQ_PREV(view
, tog_view_list_head
,
2143 else if (view
->parent
)
2144 prev
= view
->parent
;
2147 view
->parent
->child
= NULL
;
2148 view
->parent
->focus_child
= 0;
2149 /* Restore fullscreen line height. */
2150 view
->parent
->nlines
= view
->parent
->lines
;
2151 err
= view_resize(view
->parent
);
2154 /* Make resized splits persist. */
2155 view_transfer_size(view
->parent
, view
);
2157 TAILQ_REMOVE(&views
, view
, entry
);
2159 err
= view_close(view
);
2164 TAILQ_FOREACH(v
, &views
, entry
) {
2168 if (view
== NULL
&& new_view
== NULL
) {
2169 /* No view has focus. Try to pick one. */
2172 else if (!TAILQ_EMPTY(&views
)) {
2173 view
= TAILQ_LAST(&views
,
2174 tog_view_list_head
);
2177 if (view
->focus_child
) {
2178 view
->child
->focussed
= 1;
2186 struct tog_view
*v
, *t
;
2187 /* Only allow one parent view per type. */
2188 TAILQ_FOREACH_SAFE(v
, &views
, entry
, t
) {
2189 if (v
->type
!= new_view
->type
)
2191 TAILQ_REMOVE(&views
, v
, entry
);
2192 err
= view_close(v
);
2197 TAILQ_INSERT_TAIL(&views
, new_view
, entry
);
2200 if (view
&& !done
) {
2201 if (view_is_parent_view(view
)) {
2202 if (view
->child
&& view
->child
->focussed
)
2205 if (view
->parent
&& view
->parent
->focussed
)
2206 view
= view
->parent
;
2208 show_panel(view
->panel
);
2209 if (view
->child
&& view_is_splitscreen(view
->child
))
2210 show_panel(view
->child
->panel
);
2211 if (view
->parent
&& view_is_splitscreen(view
)) {
2212 err
= view
->parent
->show(view
->parent
);
2216 err
= view
->show(view
);
2220 err
= view
->child
->show(view
->child
);
2229 while (!TAILQ_EMPTY(&views
)) {
2230 const struct got_error
*close_err
;
2231 view
= TAILQ_FIRST(&views
);
2232 TAILQ_REMOVE(&views
, view
, entry
);
2233 close_err
= view_close(view
);
2234 if (close_err
&& err
== NULL
)
2238 errcode
= pthread_mutex_unlock(&tog_mutex
);
2239 if (errcode
&& err
== NULL
)
2240 err
= got_error_set_errno(errcode
, "pthread_mutex_unlock");
2250 "usage: %s log [-b] [-c commit] [-r repository-path] [path]\n",
2255 /* Create newly allocated wide-character string equivalent to a byte string. */
2256 static const struct got_error
*
2257 mbs2ws(wchar_t **ws
, size_t *wlen
, const char *s
)
2260 const struct got_error
*err
= NULL
;
2263 *wlen
= mbstowcs(NULL
, s
, 0);
2264 if (*wlen
== (size_t)-1) {
2266 if (errno
!= EILSEQ
)
2267 return got_error_from_errno("mbstowcs");
2269 /* byte string invalid in current encoding; try to "fix" it */
2270 err
= got_mbsavis(&vis
, &vislen
, s
);
2273 *wlen
= mbstowcs(NULL
, vis
, 0);
2274 if (*wlen
== (size_t)-1) {
2275 err
= got_error_from_errno("mbstowcs"); /* give up */
2280 *ws
= calloc(*wlen
+ 1, sizeof(**ws
));
2282 err
= got_error_from_errno("calloc");
2286 if (mbstowcs(*ws
, vis
? vis
: s
, *wlen
) != *wlen
)
2287 err
= got_error_from_errno("mbstowcs");
2298 static const struct got_error
*
2299 expand_tab(char **ptr
, const char *src
)
2302 size_t len
, n
, idx
= 0, sz
= 0;
2305 n
= len
= strlen(src
);
2306 dst
= malloc(n
+ 1);
2308 return got_error_from_errno("malloc");
2310 while (idx
< len
&& src
[idx
]) {
2311 const char c
= src
[idx
];
2314 size_t nb
= TABSIZE
- sz
% TABSIZE
;
2317 p
= realloc(dst
, n
+ nb
);
2320 return got_error_from_errno("realloc");
2325 memset(dst
+ sz
, ' ', nb
);
2328 dst
[sz
++] = src
[idx
];
2338 * Advance at most n columns from wline starting at offset off.
2339 * Return the index to the first character after the span operation.
2340 * Return the combined column width of all spanned wide characters in
2344 span_wline(int *rcol
, int off
, wchar_t *wline
, int n
, int col_tab_align
)
2346 int width
, i
, cols
= 0;
2353 for (i
= off
; wline
[i
] != L
'\0'; ++i
) {
2354 if (wline
[i
] == L
'\t')
2355 width
= TABSIZE
- ((cols
+ col_tab_align
) % TABSIZE
);
2357 width
= wcwidth(wline
[i
]);
2364 if (cols
+ width
> n
)
2374 * Format a line for display, ensuring that it won't overflow a width limit.
2375 * With scrolling, the width returned refers to the scrolled version of the
2376 * line, which starts at (*wlinep)[*scrollxp]. The caller must free *wlinep.
2378 static const struct got_error
*
2379 format_line(wchar_t **wlinep
, int *widthp
, int *scrollxp
,
2380 const char *line
, int nscroll
, int wlimit
, int col_tab_align
, int expand
)
2382 const struct got_error
*err
= NULL
;
2384 wchar_t *wline
= NULL
;
2393 err
= expand_tab(&exstr
, line
);
2398 err
= mbs2ws(&wline
, &wlen
, expand
? exstr
: line
);
2403 if (wlen
> 0 && wline
[wlen
- 1] == L
'\n') {
2404 wline
[wlen
- 1] = L
'\0';
2407 if (wlen
> 0 && wline
[wlen
- 1] == L
'\r') {
2408 wline
[wlen
- 1] = L
'\0';
2412 scrollx
= span_wline(&cols
, 0, wline
, nscroll
, col_tab_align
);
2414 i
= span_wline(&cols
, scrollx
, wline
, wlimit
, col_tab_align
);
2420 *scrollxp
= scrollx
;
2428 static const struct got_error
*
2429 build_refs_str(char **refs_str
, struct got_reflist_head
*refs
,
2430 struct got_object_id
*id
, struct got_repository
*repo
)
2432 static const struct got_error
*err
= NULL
;
2433 struct got_reflist_entry
*re
;
2442 TAILQ_FOREACH(re
, refs
, entry
) {
2443 struct got_tag_object
*tag
= NULL
;
2444 struct got_object_id
*ref_id
;
2447 name
= got_ref_get_name(re
->ref
);
2448 if (strcmp(name
, GOT_REF_HEAD
) == 0)
2450 if (strncmp(name
, "refs/", 5) == 0)
2452 if (strncmp(name
, "got/", 4) == 0)
2454 if (strncmp(name
, "heads/", 6) == 0)
2456 if (strncmp(name
, "remotes/", 8) == 0) {
2458 s
= strstr(name
, "/" GOT_REF_HEAD
);
2459 if (s
!= NULL
&& strcmp(s
, "/" GOT_REF_HEAD
) == 0)
2462 err
= got_ref_resolve(&ref_id
, repo
, re
->ref
);
2465 if (strncmp(name
, "tags/", 5) == 0) {
2466 err
= got_object_open_as_tag(&tag
, repo
, ref_id
);
2468 if (err
->code
!= GOT_ERR_OBJ_TYPE
) {
2472 /* Ref points at something other than a tag. */
2477 cmp
= got_object_id_cmp(tag
?
2478 got_object_tag_get_object_id(tag
) : ref_id
, id
);
2481 got_object_tag_close(tag
);
2485 if (asprintf(refs_str
, "%s%s%s", s
? s
: "",
2486 s
? ", " : "", name
) == -1) {
2487 err
= got_error_from_errno("asprintf");
2498 static const struct got_error
*
2499 format_author(wchar_t **wauthor
, int *author_width
, char *author
, int limit
,
2504 smallerthan
= strchr(author
, '<');
2505 if (smallerthan
&& smallerthan
[1] != '\0')
2506 author
= smallerthan
+ 1;
2507 author
[strcspn(author
, "@>")] = '\0';
2508 return format_line(wauthor
, author_width
, NULL
, author
, 0, limit
,
2512 static const struct got_error
*
2513 draw_commit_marker(struct tog_view
*view
, char c
)
2515 struct tog_color
*tc
;
2517 if (view
->type
!= TOG_VIEW_LOG
)
2518 return got_error_msg(GOT_ERR_NOT_IMPL
, "view not supported");
2520 tc
= get_color(&view
->state
.log
.colors
, TOG_COLOR_COMMIT
);
2522 wattr_on(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2523 if (waddch(view
->window
, c
) == ERR
)
2524 return got_error_msg(GOT_ERR_IO
, "waddch");
2526 wattr_off(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2532 tog_waddwstr(struct tog_view
*view
, wchar_t *wstr
, int width
,
2533 int *col
, int color
, int toeol
)
2535 struct tog_color
*tc
;
2538 x
= col
!= NULL
? *col
: getcurx(view
->window
);
2539 tc
= color
> 0 ? get_color(&view
->state
.log
.colors
, color
) : NULL
;
2542 wattr_on(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2543 waddwstr(view
->window
, wstr
);
2546 while (x
< view
->ncols
) {
2547 waddch(view
->window
, ' ');
2552 wattr_off(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2558 tog_waddnstr(struct tog_view
*view
, const char *str
, int limit
, int color
)
2560 struct tog_color
*tc
;
2563 limit
= view
->ncols
- getcurx(view
->window
);
2565 tc
= get_color(&view
->state
.log
.colors
, color
);
2567 wattr_on(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2568 waddnstr(view
->window
, str
, limit
);
2570 wattr_off(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2573 static const struct got_error
*
2574 draw_author(struct tog_view
*view
, char *author
, int author_display_cols
,
2575 int limit
, int *col
, int color
, int marker_column
,
2576 struct commit_queue_entry
*entry
)
2578 const struct got_error
*err
;
2579 struct tog_log_view_state
*s
= &view
->state
.log
;
2580 struct tog_color
*tc
;
2584 err
= format_author(&wauthor
, &author_width
, author
, limit
, *col
);
2587 if ((tc
= get_color(&s
->colors
, color
)) != NULL
)
2588 wattr_on(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2589 waddwstr(view
->window
, wauthor
);
2592 *col
+= author_width
;
2593 while (*col
< limit
&& author_width
< author_display_cols
+ 2) {
2594 if (entry
!= NULL
&& s
->marked_entry
== entry
&&
2595 author_width
== marker_column
) {
2596 err
= draw_commit_marker(view
, '>');
2599 } else if (entry
!= NULL
&&
2600 tog_base_commit
.marker
!= GOT_WORKTREE_STATE_UNKNOWN
&&
2601 author_width
== marker_column
&&
2602 entry
->idx
== tog_base_commit
.idx
&& !s
->limit_view
) {
2603 err
= draw_commit_marker(view
, tog_base_commit
.marker
);
2607 waddch(view
->window
, ' ');
2612 wattr_off(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2617 static const struct got_error
*
2618 draw_idstr(struct tog_view
*view
, const char *id_str
, int color
)
2622 if (strlen(id_str
) > 9 && asprintf(&str
, "%.8s ", id_str
) == -1)
2623 return got_error_from_errno("asprintf");
2625 tog_waddnstr(view
, str
!= NULL
? str
: id_str
, 0, color
);
2630 static const struct got_error
*
2631 draw_ymd(struct tog_view
*view
, time_t t
, int *limit
, int avail
,
2632 int date_display_cols
)
2635 char datebuf
[12]; /* YYYY-MM-DD + SPACE + NUL */
2637 if (gmtime_r(&t
, &tm
) == NULL
)
2638 return got_error_from_errno("gmtime_r");
2639 if (strftime(datebuf
, sizeof(datebuf
), "%F ", &tm
) == 0)
2640 return got_error(GOT_ERR_NO_SPACE
);
2642 if (avail
<= date_display_cols
)
2643 *limit
= MIN(sizeof(datebuf
) - 1, avail
);
2645 *limit
= MIN(date_display_cols
, sizeof(datebuf
) - 1);
2647 tog_waddnstr(view
, datebuf
, *limit
, TOG_COLOR_DATE
);
2651 static const struct got_error
*
2652 draw_worktree_entry(struct tog_view
*view
, int wt_entry
,
2653 const size_t date_display_cols
, int author_display_cols
)
2655 const struct got_error
*err
= NULL
;
2656 struct tog_log_view_state
*s
= &view
->state
.log
;
2657 wchar_t *wmsg
= NULL
;
2658 char *author
, *msg
= NULL
;
2659 char *base_commit_id
= NULL
;
2660 const char *p
= TOG_WORKTREE_CHANGES_LOCAL_MSG
;
2661 int col
, limit
, scrollx
, width
;
2662 const int avail
= view
->ncols
;
2664 err
= draw_ymd(view
, time(NULL
), &col
, avail
, date_display_cols
);
2670 err
= draw_idstr(view
, "........ ", TOG_COLOR_COMMIT
);
2678 author
= strdup(s
->thread_args
.wctx
.wt_author
);
2680 return got_error_from_errno("strdup");
2682 err
= draw_author(view
, author
, author_display_cols
, avail
- col
,
2683 &col
, TOG_COLOR_AUTHOR
, 0, NULL
);
2689 err
= got_object_id_str(&base_commit_id
, tog_base_commit
.id
);
2692 if (wt_entry
& TOG_WORKTREE_CHANGES_STAGED
)
2693 p
= TOG_WORKTREE_CHANGES_STAGED_MSG
;
2694 if (asprintf(&msg
, "%s based on [%.10s]", p
, base_commit_id
) == -1) {
2695 err
= got_error_from_errno("asprintf");
2699 limit
= avail
- col
;
2700 if (view
->child
!= NULL
&& !view_is_hsplit_top(view
) && limit
> 0)
2701 limit
--; /* for the border */
2703 err
= format_line(&wmsg
, &width
, &scrollx
, msg
, view
->x
, limit
, col
, 1);
2706 tog_waddwstr(view
, &wmsg
[scrollx
], width
, &col
, 0, 1);
2712 free(base_commit_id
);
2716 static const struct got_error
*
2717 draw_commit(struct tog_view
*view
, struct commit_queue_entry
*entry
,
2718 const size_t date_display_cols
, int author_display_cols
)
2720 struct tog_log_view_state
*s
= &view
->state
.log
;
2721 const struct got_error
*err
= NULL
;
2722 struct got_commit_object
*commit
= entry
->commit
;
2723 struct got_object_id
*id
= entry
->id
;
2724 char *author
, *newline
, *logmsg
, *logmsg0
= NULL
, *refs_str
= NULL
;
2725 wchar_t *wrefstr
= NULL
, *wlogmsg
= NULL
;
2726 int refstr_width
, logmsg_width
, col
, limit
, scrollx
, logmsg_x
;
2727 const int avail
= view
->ncols
, marker_column
= author_display_cols
+ 1;
2728 time_t committer_time
;
2729 struct got_reflist_head
*refs
;
2731 if (tog_base_commit
.id
!= NULL
&& tog_base_commit
.idx
== -1 &&
2732 got_object_id_cmp(id
, tog_base_commit
.id
) == 0)
2733 tog_base_commit
.idx
= entry
->idx
;
2734 if (tog_io
.wait_for_ui
&& s
->thread_args
.need_commit_marker
) {
2737 rc
= pthread_cond_wait(&s
->thread_args
.log_loaded
, &tog_mutex
);
2739 return got_error_set_errno(rc
, "pthread_cond_wait");
2742 committer_time
= got_object_commit_get_committer_time(commit
);
2743 err
= draw_ymd(view
, committer_time
, &col
, avail
, date_display_cols
);
2752 err
= got_object_id_str(&id_str
, id
);
2755 err
= draw_idstr(view
, id_str
, TOG_COLOR_COMMIT
);
2764 if (s
->use_committer
)
2765 author
= strdup(got_object_commit_get_committer(commit
));
2767 author
= strdup(got_object_commit_get_author(commit
));
2769 return got_error_from_errno("strdup");
2771 err
= draw_author(view
, author
, author_display_cols
,
2772 avail
- col
, &col
, TOG_COLOR_AUTHOR
, marker_column
, entry
);
2778 err
= got_object_commit_get_logmsg(&logmsg0
, commit
);
2782 while (*logmsg
== '\n')
2784 newline
= strchr(logmsg
, '\n');
2788 limit
= avail
- col
;
2789 if (view
->child
&& !view_is_hsplit_top(view
) && limit
> 0)
2790 limit
--; /* for the border */
2792 /* Prepend reference labels to log message if possible .*/
2793 refs
= got_reflist_object_id_map_lookup(tog_refs_idmap
, id
);
2794 err
= build_refs_str(&refs_str
, refs
, id
, s
->repo
);
2800 if (asprintf(&rs
, "[%s]", refs_str
) == -1) {
2801 err
= got_error_from_errno("asprintf");
2804 err
= format_line(&wrefstr
, &refstr_width
,
2805 &scrollx
, rs
, view
->x
, limit
, col
, 1);
2809 tog_waddwstr(view
, &wrefstr
[scrollx
], refstr_width
,
2810 &col
, TOG_COLOR_COMMIT
, 0);
2815 waddch(view
->window
, ' ');
2819 if (refstr_width
> 0)
2822 int unscrolled_refstr_width
;
2823 size_t len
= wcslen(wrefstr
);
2826 * No need to check for -1 return value here since
2827 * unprintables have been replaced by span_wline().
2829 unscrolled_refstr_width
= wcswidth(wrefstr
, len
);
2830 unscrolled_refstr_width
+= 1; /* trailing space */
2831 logmsg_x
= view
->x
- unscrolled_refstr_width
;
2834 limit
= avail
- col
;
2835 if (view
->child
&& !view_is_hsplit_top(view
) && limit
> 0)
2836 limit
--; /* for the border */
2840 err
= format_line(&wlogmsg
, &logmsg_width
, &scrollx
, logmsg
, logmsg_x
,
2844 tog_waddwstr(view
, &wlogmsg
[scrollx
], logmsg_width
, &col
, 0, 1);
2855 static struct commit_queue_entry
*
2856 alloc_commit_queue_entry(struct got_commit_object
*commit
,
2857 struct got_object_id
*id
)
2859 struct commit_queue_entry
*entry
;
2860 struct got_object_id
*dup
;
2862 entry
= calloc(1, sizeof(*entry
));
2866 dup
= got_object_id_dup(id
);
2873 entry
->commit
= commit
;
2878 pop_commit(struct commit_queue
*commits
)
2880 struct commit_queue_entry
*entry
;
2882 entry
= TAILQ_FIRST(&commits
->head
);
2883 TAILQ_REMOVE(&commits
->head
, entry
, entry
);
2884 if (entry
->worktree_entry
== 0)
2885 got_object_commit_close(entry
->commit
);
2886 commits
->ncommits
--;
2892 free_commits(struct commit_queue
*commits
)
2894 while (!TAILQ_EMPTY(&commits
->head
))
2895 pop_commit(commits
);
2898 static const struct got_error
*
2899 match_commit(int *have_match
, struct got_object_id
*id
,
2900 struct got_commit_object
*commit
, regex_t
*regex
)
2902 const struct got_error
*err
= NULL
;
2903 regmatch_t regmatch
;
2904 char *id_str
= NULL
, *logmsg
= NULL
;
2908 err
= got_object_id_str(&id_str
, id
);
2912 err
= got_object_commit_get_logmsg(&logmsg
, commit
);
2916 if (regexec(regex
, got_object_commit_get_author(commit
), 1,
2917 ®match
, 0) == 0 ||
2918 regexec(regex
, got_object_commit_get_committer(commit
), 1,
2919 ®match
, 0) == 0 ||
2920 regexec(regex
, id_str
, 1, ®match
, 0) == 0 ||
2921 regexec(regex
, logmsg
, 1, ®match
, 0) == 0)
2929 static const struct got_error
*
2930 queue_commits(struct tog_log_thread_args
*a
)
2932 const struct got_error
*err
= NULL
;
2935 * We keep all commits open throughout the lifetime of the log
2936 * view in order to avoid having to re-fetch commits from disk
2937 * while updating the display.
2940 struct got_object_id id
;
2941 struct got_commit_object
*commit
;
2942 struct commit_queue_entry
*entry
;
2943 int limit_match
= 0;
2946 err
= got_commit_graph_iter_next(&id
, a
->graph
, a
->repo
,
2951 err
= got_object_open_as_commit(&commit
, a
->repo
, &id
);
2954 entry
= alloc_commit_queue_entry(commit
, &id
);
2955 if (entry
== NULL
) {
2956 err
= got_error_from_errno("alloc_commit_queue_entry");
2960 errcode
= pthread_mutex_lock(&tog_mutex
);
2962 err
= got_error_set_errno(errcode
,
2963 "pthread_mutex_lock");
2967 entry
->idx
= a
->real_commits
->ncommits
;
2968 TAILQ_INSERT_TAIL(&a
->real_commits
->head
, entry
, entry
);
2969 a
->real_commits
->ncommits
++;
2972 err
= match_commit(&limit_match
, &id
, commit
,
2978 struct commit_queue_entry
*matched
;
2980 matched
= alloc_commit_queue_entry(
2981 entry
->commit
, entry
->id
);
2982 if (matched
== NULL
) {
2983 err
= got_error_from_errno(
2984 "alloc_commit_queue_entry");
2987 matched
->commit
= entry
->commit
;
2988 got_object_commit_retain(entry
->commit
);
2990 matched
->idx
= a
->limit_commits
->ncommits
;
2991 TAILQ_INSERT_TAIL(&a
->limit_commits
->head
,
2993 a
->limit_commits
->ncommits
++;
2997 * This is how we signal log_thread() that we
2998 * have found a match, and that it should be
2999 * counted as a new entry for the view.
3001 a
->limit_match
= limit_match
;
3004 if (*a
->searching
== TOG_SEARCH_FORWARD
&&
3005 !*a
->search_next_done
) {
3007 err
= match_commit(&have_match
, &id
, commit
, a
->regex
);
3012 if (limit_match
&& have_match
)
3013 *a
->search_next_done
=
3014 TOG_SEARCH_HAVE_MORE
;
3015 } else if (have_match
)
3016 *a
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
3019 errcode
= pthread_mutex_unlock(&tog_mutex
);
3020 if (errcode
&& err
== NULL
)
3021 err
= got_error_set_errno(errcode
,
3022 "pthread_mutex_unlock");
3025 } while (*a
->searching
== TOG_SEARCH_FORWARD
&& !*a
->search_next_done
);
3031 select_commit(struct tog_log_view_state
*s
)
3033 struct commit_queue_entry
*entry
;
3036 entry
= s
->first_displayed_entry
;
3038 if (ncommits
== s
->selected
) {
3039 s
->selected_entry
= entry
;
3042 entry
= TAILQ_NEXT(entry
, entry
);
3047 /* lifted from got.c:652 (TODO make lib routine) */
3048 static const struct got_error
*
3049 get_author(char **author
, struct got_repository
*repo
,
3050 struct got_worktree
*worktree
)
3052 const struct got_error
*err
= NULL
;
3053 const char *got_author
= NULL
, *name
, *email
;
3054 const struct got_gotconfig
*worktree_conf
= NULL
, *repo_conf
= NULL
;
3059 worktree_conf
= got_worktree_get_gotconfig(worktree
);
3060 repo_conf
= got_repo_get_gotconfig(repo
);
3063 * Priority of potential author information sources, from most
3064 * significant to least significant:
3065 * 1) work tree's .got/got.conf file
3066 * 2) repository's got.conf file
3067 * 3) repository's git config file
3068 * 4) environment variables
3069 * 5) global git config files (in user's home directory or /etc)
3073 got_author
= got_gotconfig_get_author(worktree_conf
);
3074 if (got_author
== NULL
)
3075 got_author
= got_gotconfig_get_author(repo_conf
);
3076 if (got_author
== NULL
) {
3077 name
= got_repo_get_gitconfig_author_name(repo
);
3078 email
= got_repo_get_gitconfig_author_email(repo
);
3079 if (name
&& email
) {
3080 if (asprintf(author
, "%s <%s>", name
, email
) == -1)
3081 return got_error_from_errno("asprintf");
3085 got_author
= getenv("GOT_AUTHOR");
3086 if (got_author
== NULL
) {
3087 name
= got_repo_get_global_gitconfig_author_name(repo
);
3088 email
= got_repo_get_global_gitconfig_author_email(
3090 if (name
&& email
) {
3091 if (asprintf(author
, "%s <%s>", name
, email
)
3093 return got_error_from_errno("asprintf");
3096 /* TODO: Look up user in password database? */
3097 return got_error(GOT_ERR_COMMIT_NO_AUTHOR
);
3101 *author
= strdup(got_author
);
3102 if (*author
== NULL
)
3103 err
= got_error_from_errno("strdup");
3107 static const struct got_error
*
3108 push_worktree_entry(struct tog_log_thread_args
*ta
, int wt_entry
,
3109 struct got_worktree
*worktree
)
3111 struct commit_queue_entry
*e
, *entry
;
3114 entry
= calloc(1, sizeof(*entry
));
3116 return got_error_from_errno("calloc");
3119 entry
->worktree_entry
= wt_entry
;
3121 rc
= pthread_mutex_lock(&tog_mutex
);
3124 return got_error_set_errno(rc
, "pthread_mutex_lock");
3127 TAILQ_FOREACH(e
, &ta
->real_commits
->head
, entry
)
3130 TAILQ_INSERT_HEAD(&ta
->real_commits
->head
, entry
, entry
);
3131 ta
->wctx
.wt_state
|= wt_entry
;
3132 ++ta
->real_commits
->ncommits
;
3133 ++tog_base_commit
.idx
;
3135 rc
= pthread_mutex_unlock(&tog_mutex
);
3137 return got_error_set_errno(rc
, "pthread_mutex_unlock");
3142 static const struct got_error
*
3143 check_cancelled(void *arg
)
3145 if (tog_sigint_received
|| tog_sigpipe_received
)
3146 return got_error(GOT_ERR_CANCELLED
);
3150 static const struct got_error
*
3151 check_local_changes(void *arg
, unsigned char status
,
3152 unsigned char staged_status
, const char *path
,
3153 struct got_object_id
*blob_id
, struct got_object_id
*staged_blob_id
,
3154 struct got_object_id
*commit_id
, int dirfd
, const char *de_name
)
3156 int *have_local_changes
= arg
;
3159 case GOT_STATUS_ADD
:
3160 case GOT_STATUS_DELETE
:
3161 case GOT_STATUS_MODIFY
:
3162 case GOT_STATUS_CONFLICT
:
3163 *have_local_changes
|= TOG_WORKTREE_CHANGES_LOCAL
;
3168 switch (staged_status
) {
3169 case GOT_STATUS_ADD
:
3170 case GOT_STATUS_DELETE
:
3171 case GOT_STATUS_MODIFY
:
3172 *have_local_changes
|= TOG_WORKTREE_CHANGES_STAGED
;
3180 static const struct got_error
*
3181 tog_worktree_status(struct tog_log_thread_args
*ta
)
3183 const struct got_error
*err
, *close_err
;
3184 struct tog_worktree_ctx
*wctx
= &ta
->wctx
;
3185 struct got_worktree
*wt
= ta
->worktree
;
3186 struct got_pathlist_head paths
;
3193 cwd
= getcwd(NULL
, 0);
3195 return got_error_from_errno("getcwd");
3197 err
= got_worktree_open(&wt
, cwd
, NULL
);
3199 if (err
->code
== GOT_ERR_NOT_WORKTREE
) {
3201 * Shouldn't happen; this routine should only
3202 * be called if tog is invoked in a worktree.
3206 } else if (err
->code
== GOT_ERR_WORKTREE_BUSY
)
3207 err
= NULL
; /* retry next redraw */
3212 err
= got_pathlist_insert(NULL
, &paths
, "", NULL
);
3216 err
= got_worktree_status(wt
, &paths
, ta
->repo
, 0,
3217 check_local_changes
, &wt_state
, check_cancelled
, NULL
);
3219 if (err
->code
!= GOT_ERR_CANCELLED
)
3224 if (wt_state
!= 0) {
3225 err
= get_author(&wctx
->wt_author
, ta
->repo
, wt
);
3227 if (err
->code
!= GOT_ERR_COMMIT_NO_AUTHOR
)
3229 if ((wctx
->wt_author
= strdup("")) == NULL
) {
3230 err
= got_error_from_errno("strdup");
3235 wctx
->wt_root
= strdup(got_worktree_get_root_path(wt
));
3236 if (wctx
->wt_root
== NULL
) {
3237 err
= got_error_from_errno("strdup");
3241 wctx
->wt_ref
= strdup(got_worktree_get_head_ref_name(wt
));
3242 if (wctx
->wt_ref
== NULL
) {
3243 err
= got_error_from_errno("strdup");
3249 * Push staged entry first so it's the second log entry
3250 * if there are both staged and unstaged work tree changes.
3252 if (wt_state
& TOG_WORKTREE_CHANGES_STAGED
&&
3253 (wctx
->wt_state
& TOG_WORKTREE_CHANGES_STAGED
) == 0) {
3254 err
= push_worktree_entry(ta
, TOG_WORKTREE_CHANGES_STAGED
, wt
);
3258 if (wt_state
& TOG_WORKTREE_CHANGES_LOCAL
&&
3259 (wctx
->wt_state
& TOG_WORKTREE_CHANGES_LOCAL
) == 0) {
3260 err
= push_worktree_entry(ta
, TOG_WORKTREE_CHANGES_LOCAL
, wt
);
3266 got_pathlist_free(&paths
, GOT_PATHLIST_FREE_NONE
);
3267 if (ta
->worktree
== NULL
&& wt
!= NULL
) {
3268 close_err
= got_worktree_close(wt
);
3269 if (close_err
!= NULL
&& err
== NULL
)
3276 static const struct got_error
*
3277 worktree_headref_str(char **ret
, const char *ref
)
3279 if (strncmp(ref
, "refs/heads/", 11) == 0)
3280 *ret
= strdup(ref
+ 11);
3284 return got_error_from_errno("strdup");
3289 static const struct got_error
*
3290 fmtindex(char **index
, int *ncommits
, int wt_state
,
3291 struct commit_queue_entry
*entry
, int limit_view
)
3296 if (*ncommits
> 0 && wt_state
& TOG_WORKTREE_CHANGES_LOCAL
)
3298 if (*ncommits
> 0 && wt_state
& TOG_WORKTREE_CHANGES_STAGED
)
3302 if (entry
!= NULL
&& entry
->worktree_entry
== 0) {
3304 * Display 1-based index of selected commit entries only.
3305 * If a work tree entry is selected, show an index of 0.
3308 if (wt_state
== 0 || limit_view
)
3310 else if (wt_state
> TOG_WORKTREE_CHANGES_STAGED
)
3313 if (asprintf(index
, " [%d/%d] ", idx
, *ncommits
) == -1) {
3315 return got_error_from_errno("asprintf");
3321 static const struct got_error
*
3322 fmtheader(char **header
, int *ncommits
, struct commit_queue_entry
*entry
,
3323 struct tog_view
*view
)
3325 const struct got_error
*err
;
3326 struct tog_log_view_state
*s
= &view
->state
.log
;
3327 struct tog_worktree_ctx
*wctx
= &s
->thread_args
.wctx
;
3328 struct got_reflist_head
*refs
;
3329 char *id_str
= NULL
, *index
= NULL
;
3330 char *wthdr
= NULL
, *ncommits_str
= NULL
;
3331 char *refs_str
= NULL
;
3335 wt_entry
= entry
!= NULL
? entry
->worktree_entry
: 0;
3337 if (entry
&& !(view
->searching
&& view
->search_next_done
== 0)) {
3338 if (entry
->worktree_entry
== 0) {
3339 err
= got_object_id_str(&id_str
, entry
->id
);
3342 refs
= got_reflist_object_id_map_lookup(tog_refs_idmap
,
3344 err
= build_refs_str(&refs_str
, refs
,
3345 entry
->id
, s
->repo
);
3349 err
= worktree_headref_str(&refs_str
, wctx
->wt_ref
);
3355 err
= fmtindex(&index
, ncommits
, wctx
->wt_state
, entry
, s
->limit_view
);
3359 if (s
->thread_args
.commits_needed
> 0 || s
->thread_args
.load_all
) {
3360 if (asprintf(&ncommits_str
, "%s%s", index
,
3361 (view
->searching
&& !view
->search_next_done
) ?
3362 "searching..." : "loading...") == -1) {
3363 err
= got_error_from_errno("asprintf");
3367 const char *search_str
= NULL
;
3368 const char *limit_str
= NULL
;
3370 if (view
->searching
) {
3371 if (view
->search_next_done
== TOG_SEARCH_NO_MORE
)
3372 search_str
= "no more matches";
3373 else if (view
->search_next_done
== TOG_SEARCH_HAVE_NONE
)
3374 search_str
= "no matches found";
3375 else if (!view
->search_next_done
)
3376 search_str
= "searching...";
3379 if (s
->limit_view
&& ncommits
== 0)
3380 limit_str
= "no matches found";
3382 if (asprintf(&ncommits_str
, "%s%s %s", index
,
3383 search_str
? search_str
: (refs_str
? refs_str
: ""),
3384 limit_str
? limit_str
: "") == -1) {
3385 err
= got_error_from_errno("asprintf");
3390 if (wt_entry
!= 0) {
3391 const char *t
= "", *p
= TOG_WORKTREE_CHANGES_LOCAL_MSG
;
3393 if (wt_entry
== TOG_WORKTREE_CHANGES_STAGED
) {
3394 p
= TOG_WORKTREE_CHANGES_STAGED_MSG
;
3397 if (asprintf(&wthdr
, "%s%s (%s)", t
, wctx
->wt_root
, p
) == -1) {
3398 err
= got_error_from_errno("asprintf");
3403 if (s
->in_repo_path
!= NULL
&& strcmp(s
->in_repo_path
, "/") != 0) {
3404 if (asprintf(header
, "%s%s %s%s",
3405 wt_entry
== 0 ? "commit " : "diff ",
3406 wt_entry
== 0 ? id_str
? id_str
:
3407 "........................................" :
3408 wthdr
!= NULL
? wthdr
: "", s
->in_repo_path
,
3409 ncommits_str
) == -1)
3410 err
= got_error_from_errno("asprintf");
3411 } else if (asprintf(header
, "%s%s%s",
3412 wt_entry
== 0 ? "commit " : "diff ",
3413 wt_entry
== 0 ? id_str
? id_str
:
3414 "........................................" :
3415 wthdr
!= NULL
? wthdr
: "", ncommits_str
) == -1)
3416 err
= got_error_from_errno("asprintf");
3429 static const struct got_error
*
3430 draw_commits(struct tog_view
*view
)
3432 const struct got_error
*err
;
3433 struct tog_log_view_state
*s
= &view
->state
.log
;
3434 struct commit_queue_entry
*entry
= s
->selected_entry
;
3435 int width
, limit
= view
->nlines
;
3436 int ncommits
= s
->commits
->ncommits
, author_cols
= 4, refstr_cols
;
3439 static const size_t date_display_cols
= 12;
3441 if (view_is_hsplit_top(view
))
3442 --limit
; /* account for border */
3444 if (s
->thread_args
.commits_needed
== 0 &&
3445 s
->thread_args
.need_wt_status
== 0 &&
3446 s
->thread_args
.need_commit_marker
== 0 && !using_mock_io
)
3447 halfdelay(10); /* disable fast refresh */
3449 err
= fmtheader(&header
, &ncommits
, entry
, view
);
3453 err
= format_line(&wline
, &width
, NULL
, header
, 0, view
->ncols
, 0, 0);
3458 werase(view
->window
);
3460 if (view_needs_focus_indication(view
))
3461 wstandout(view
->window
);
3462 tog_waddwstr(view
, wline
, width
, NULL
, TOG_COLOR_COMMIT
, 1);
3463 if (view_needs_focus_indication(view
))
3464 wstandend(view
->window
);
3469 /* Grow author column size if necessary, and set view->maxx. */
3470 entry
= s
->first_displayed_entry
;
3474 struct got_reflist_head
*refs
;
3475 struct got_commit_object
*c
= entry
->commit
;
3476 char *author
, *eol
, *msg
, *msg0
, *refs_str
;
3477 wchar_t *wauthor
, *wmsg
;
3480 if (ncommits
>= limit
- 1)
3482 if (entry
->worktree_entry
!= 0)
3483 author
= strdup(s
->thread_args
.wctx
.wt_author
);
3484 else if (s
->use_committer
)
3485 author
= strdup(got_object_commit_get_committer(c
));
3487 author
= strdup(got_object_commit_get_author(c
));
3489 return got_error_from_errno("strdup");
3491 err
= format_author(&wauthor
, &width
, author
, COLS
,
3493 if (author_cols
< width
)
3494 author_cols
= width
;
3499 if (entry
->worktree_entry
!= 0) {
3500 if (entry
->worktree_entry
== TOG_WORKTREE_CHANGES_LOCAL
)
3501 width
= sizeof(TOG_WORKTREE_CHANGES_LOCAL_MSG
);
3503 width
= sizeof(TOG_WORKTREE_CHANGES_STAGED_MSG
);
3504 view
->maxx
= MAX(view
->maxx
, width
- 1);
3505 entry
= TAILQ_NEXT(entry
, entry
);
3509 refs
= got_reflist_object_id_map_lookup(tog_refs_idmap
,
3511 err
= build_refs_str(&refs_str
, refs
, entry
->id
, s
->repo
);
3517 err
= format_line(&ws
, &width
, NULL
, refs_str
,
3518 0, INT_MAX
, date_display_cols
+ author_cols
, 0);
3524 refstr_cols
= width
+ 3; /* account for [ ] + space */
3527 err
= got_object_commit_get_logmsg(&msg0
, c
);
3531 while (*msg
== '\n')
3533 if ((eol
= strchr(msg
, '\n')))
3535 err
= format_line(&wmsg
, &width
, NULL
, msg
, 0, INT_MAX
,
3536 date_display_cols
+ author_cols
+ refstr_cols
, 0);
3541 view
->maxx
= MAX(view
->maxx
, width
+ refstr_cols
);
3543 entry
= TAILQ_NEXT(entry
, entry
);
3546 entry
= s
->first_displayed_entry
;
3547 s
->last_displayed_entry
= s
->first_displayed_entry
;
3550 if (ncommits
>= limit
- 1)
3552 if (ncommits
== s
->selected
)
3553 wstandout(view
->window
);
3554 if (entry
->worktree_entry
== 0)
3555 err
= draw_commit(view
, entry
,
3556 date_display_cols
, author_cols
);
3558 err
= draw_worktree_entry(view
, entry
->worktree_entry
,
3559 date_display_cols
, author_cols
);
3560 if (ncommits
== s
->selected
)
3561 wstandend(view
->window
);
3565 s
->last_displayed_entry
= entry
;
3566 entry
= TAILQ_NEXT(entry
, entry
);
3574 log_scroll_up(struct tog_log_view_state
*s
, int maxscroll
)
3576 struct commit_queue_entry
*entry
;
3579 entry
= TAILQ_FIRST(&s
->commits
->head
);
3580 if (s
->first_displayed_entry
== entry
)
3583 entry
= s
->first_displayed_entry
;
3584 while (entry
&& nscrolled
< maxscroll
) {
3585 entry
= TAILQ_PREV(entry
, commit_queue_head
, entry
);
3587 s
->first_displayed_entry
= entry
;
3593 static const struct got_error
*
3594 trigger_log_thread(struct tog_view
*view
, int wait
)
3596 const struct got_error
*err
;
3597 struct tog_log_thread_args
*ta
= &view
->state
.log
.thread_args
;
3601 halfdelay(1); /* fast refresh while loading commits */
3603 while (!ta
->log_complete
&& !tog_thread_error
&&
3604 (ta
->commits_needed
> 0 || ta
->load_all
)) {
3605 /* Wake the log thread. */
3606 errcode
= pthread_cond_signal(&ta
->need_commits
);
3608 return got_error_set_errno(errcode
,
3609 "pthread_cond_signal");
3612 * The mutex will be released while the view loop waits
3613 * in wgetch(), at which time the log thread will run.
3618 /* Display progress update in log view. */
3619 err
= show_log_view(view
);
3625 /* Wait right here while next commit is being loaded. */
3626 errcode
= pthread_cond_wait(&ta
->commit_loaded
, &tog_mutex
);
3628 return got_error_set_errno(errcode
,
3629 "pthread_cond_wait");
3631 /* Display progress update in log view. */
3632 err
= show_log_view(view
);
3642 static const struct got_error
*
3643 request_log_commits(struct tog_view
*view
)
3645 struct tog_log_view_state
*state
= &view
->state
.log
;
3646 const struct got_error
*err
= NULL
;
3648 if (state
->thread_args
.log_complete
)
3651 state
->thread_args
.commits_needed
+= view
->nscrolled
;
3652 err
= trigger_log_thread(view
, 1);
3653 view
->nscrolled
= 0;
3658 static const struct got_error
*
3659 log_scroll_down(struct tog_view
*view
, int maxscroll
)
3661 struct tog_log_view_state
*s
= &view
->state
.log
;
3662 const struct got_error
*err
= NULL
;
3663 struct commit_queue_entry
*pentry
;
3664 int nscrolled
= 0, ncommits_needed
;
3666 if (s
->last_displayed_entry
== NULL
)
3669 ncommits_needed
= s
->last_displayed_entry
->idx
+ 2 + maxscroll
;
3670 if (s
->commits
->ncommits
< ncommits_needed
&&
3671 !s
->thread_args
.log_complete
) {
3673 * Ask the log thread for required amount of commits.
3675 s
->thread_args
.commits_needed
+=
3676 ncommits_needed
- s
->commits
->ncommits
;
3677 err
= trigger_log_thread(view
, 1);
3683 pentry
= TAILQ_NEXT(s
->last_displayed_entry
, entry
);
3684 if (pentry
== NULL
&& view
->mode
!= TOG_VIEW_SPLIT_HRZN
)
3687 s
->last_displayed_entry
= pentry
?
3688 pentry
: s
->last_displayed_entry
;
3690 pentry
= TAILQ_NEXT(s
->first_displayed_entry
, entry
);
3693 s
->first_displayed_entry
= pentry
;
3694 } while (++nscrolled
< maxscroll
);
3696 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
&& !s
->thread_args
.log_complete
)
3697 view
->nscrolled
+= nscrolled
;
3699 view
->nscrolled
= 0;
3704 static const struct got_error
*
3705 open_diff_view_for_commit(struct tog_view
**new_view
, int begin_y
, int begin_x
,
3706 struct commit_queue_entry
*entry
, struct tog_view
*log_view
,
3707 struct got_repository
*repo
)
3709 const struct got_error
*err
;
3710 struct got_object_qid
*p
;
3711 struct got_object_id
*parent_id
;
3712 struct tog_view
*diff_view
;
3713 struct tog_log_view_state
*ls
= NULL
;
3714 const char *worktree_root
= NULL
;
3716 diff_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_DIFF
);
3717 if (diff_view
== NULL
)
3718 return got_error_from_errno("view_open");
3720 if (log_view
!= NULL
) {
3721 ls
= &log_view
->state
.log
;
3722 worktree_root
= ls
->thread_args
.wctx
.wt_root
;
3725 if (ls
!= NULL
&& ls
->marked_entry
!= NULL
&&
3726 ls
->marked_entry
!= ls
->selected_entry
)
3727 parent_id
= ls
->marked_entry
->id
;
3728 else if (entry
->worktree_entry
== 0 &&
3729 (p
= STAILQ_FIRST(got_object_commit_get_parent_ids(entry
->commit
))))
3734 err
= open_diff_view(diff_view
, parent_id
, entry
->id
, NULL
, NULL
, 3, 0,
3735 0, 0, entry
->worktree_entry
, worktree_root
, log_view
, repo
, NULL
);
3737 *new_view
= diff_view
;
3741 static const struct got_error
*
3742 tree_view_visit_subtree(struct tog_tree_view_state
*s
,
3743 struct got_tree_object
*subtree
)
3745 struct tog_parent_tree
*parent
;
3747 parent
= calloc(1, sizeof(*parent
));
3749 return got_error_from_errno("calloc");
3751 parent
->tree
= s
->tree
;
3752 parent
->first_displayed_entry
= s
->first_displayed_entry
;
3753 parent
->selected_entry
= s
->selected_entry
;
3754 parent
->selected
= s
->selected
;
3755 TAILQ_INSERT_HEAD(&s
->parents
, parent
, entry
);
3758 s
->first_displayed_entry
= NULL
;
3762 static const struct got_error
*
3763 tree_view_walk_path(struct tog_tree_view_state
*s
,
3764 struct got_commit_object
*commit
, const char *path
)
3766 const struct got_error
*err
= NULL
;
3767 struct got_tree_object
*tree
= NULL
;
3769 char *slash
, *subpath
= NULL
;
3771 /* Walk the path and open corresponding tree objects. */
3774 struct got_tree_entry
*te
;
3775 struct got_object_id
*tree_id
;
3781 /* Ensure the correct subtree entry is selected. */
3782 slash
= strchr(p
, '/');
3784 te_name
= strdup(p
);
3786 te_name
= strndup(p
, slash
- p
);
3787 if (te_name
== NULL
) {
3788 err
= got_error_from_errno("strndup");
3791 te
= got_object_tree_find_entry(s
->tree
, te_name
);
3793 err
= got_error_path(te_name
, GOT_ERR_NO_TREE_ENTRY
);
3798 s
->first_displayed_entry
= s
->selected_entry
= te
;
3800 if (!S_ISDIR(got_tree_entry_get_mode(s
->selected_entry
)))
3801 break; /* jump to this file's entry */
3803 slash
= strchr(p
, '/');
3805 subpath
= strndup(path
, slash
- path
);
3807 subpath
= strdup(path
);
3808 if (subpath
== NULL
) {
3809 err
= got_error_from_errno("strdup");
3813 err
= got_object_id_by_path(&tree_id
, s
->repo
, commit
,
3818 err
= got_object_open_as_tree(&tree
, s
->repo
, tree_id
);
3823 err
= tree_view_visit_subtree(s
, tree
);
3825 got_object_tree_close(tree
);
3839 static const struct got_error
*
3840 browse_commit_tree(struct tog_view
**new_view
, int begin_y
, int begin_x
,
3841 struct commit_queue_entry
*entry
, const char *path
,
3842 const char *head_ref_name
, struct got_repository
*repo
)
3844 const struct got_error
*err
= NULL
;
3845 struct tog_tree_view_state
*s
;
3846 struct tog_view
*tree_view
;
3847 struct got_commit_object
*commit
= NULL
;
3848 struct got_object_id
*commit_id
;
3852 if (entry
->id
!= NULL
)
3853 commit_id
= entry
->id
;
3854 else if (entry
->worktree_entry
)
3855 commit_id
= tog_base_commit
.id
;
3856 else /* cannot happen */
3857 return got_error(GOT_ERR_NOT_WORKTREE
);
3859 tree_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_TREE
);
3860 if (tree_view
== NULL
)
3861 return got_error_from_errno("view_open");
3863 err
= open_tree_view(tree_view
, commit_id
, head_ref_name
, repo
);
3866 s
= &tree_view
->state
.tree
;
3868 *new_view
= tree_view
;
3870 if (got_path_is_root_dir(path
))
3873 if (entry
->worktree_entry
) {
3874 err
= got_object_open_as_commit(&commit
, repo
, commit_id
);
3879 err
= tree_view_walk_path(s
, commit
? commit
: entry
->commit
, path
);
3883 got_object_commit_close(commit
);
3885 view_close(tree_view
);
3892 * If work tree entries have been pushed onto the commit queue and the
3893 * first commit entry is still displayed, scroll the view so the new
3894 * work tree entries are visible. If the selection cursor is still on
3895 * the first commit entry, keep the cursor in place such that the first
3896 * work tree entry is selected, otherwise move the selection cursor so
3897 * the currently selected commit stays selected if it remains on screen.
3900 worktree_entries_reveal(struct tog_log_thread_args
*a
)
3902 struct commit_queue_entry
**first
= a
->first_displayed_entry
;
3903 struct commit_queue_entry
**select
= a
->selected_entry
;
3904 int *cursor
= a
->selected
;
3905 int wts
= a
->wctx
.wt_state
;
3907 #define select_worktree_entry(_first, _selected) do { \
3908 *_first = TAILQ_FIRST(&a->real_commits->head); \
3909 *_selected = *_first; \
3913 select_worktree_entry(first
, select
);
3914 else if (*select
== *first
) {
3915 if (wts
== TOG_WORKTREE_CHANGES_LOCAL
&& (*first
)->idx
== 1)
3916 select_worktree_entry(first
, select
);
3917 else if (wts
== TOG_WORKTREE_CHANGES_STAGED
&&
3919 select_worktree_entry(first
, select
);
3920 else if (wts
& TOG_WORKTREE_CHANGES_ALL
&& (*first
)->idx
== 2)
3921 select_worktree_entry(first
, select
);
3922 } else if (wts
& TOG_WORKTREE_CHANGES_ALL
&& (*first
)->idx
== 2) {
3923 *first
= TAILQ_FIRST(&a
->real_commits
->head
);
3924 if (*cursor
+ 2 < *a
->view_nlines
- 1)
3926 else if (*cursor
+ 1 < *a
->view_nlines
- 1) {
3927 *select
= TAILQ_PREV(*select
, commit_queue_head
, entry
);
3930 *select
= TAILQ_PREV(*select
, commit_queue_head
, entry
);
3931 *select
= TAILQ_PREV(*select
, commit_queue_head
, entry
);
3933 } else if (wts
!= 0 && (*first
)->idx
== 1) {
3934 *first
= TAILQ_FIRST(&a
->real_commits
->head
);
3935 if (*cursor
+ 1 < *a
->view_nlines
- 1)
3938 *select
= TAILQ_PREV(*select
, commit_queue_head
, entry
);
3940 #undef select_worktree_entry
3943 static const struct got_error
*
3944 block_signals_used_by_main_thread(void)
3949 if (sigemptyset(&sigset
) == -1)
3950 return got_error_from_errno("sigemptyset");
3952 /* tog handles SIGWINCH, SIGCONT, SIGINT, SIGTERM */
3953 if (sigaddset(&sigset
, SIGWINCH
) == -1)
3954 return got_error_from_errno("sigaddset");
3955 if (sigaddset(&sigset
, SIGCONT
) == -1)
3956 return got_error_from_errno("sigaddset");
3957 if (sigaddset(&sigset
, SIGINT
) == -1)
3958 return got_error_from_errno("sigaddset");
3959 if (sigaddset(&sigset
, SIGTERM
) == -1)
3960 return got_error_from_errno("sigaddset");
3962 /* ncurses handles SIGTSTP */
3963 if (sigaddset(&sigset
, SIGTSTP
) == -1)
3964 return got_error_from_errno("sigaddset");
3966 errcode
= pthread_sigmask(SIG_BLOCK
, &sigset
, NULL
);
3968 return got_error_set_errno(errcode
, "pthread_sigmask");
3974 log_thread(void *arg
)
3976 const struct got_error
*err
= NULL
;
3978 struct tog_log_thread_args
*a
= arg
;
3982 * Sync startup with main thread such that we begin our
3983 * work once view_input() has released the mutex.
3985 errcode
= pthread_mutex_lock(&tog_mutex
);
3987 err
= got_error_set_errno(errcode
, "pthread_mutex_lock");
3991 err
= block_signals_used_by_main_thread();
3993 pthread_mutex_unlock(&tog_mutex
);
3997 while (!done
&& !err
&& !tog_fatal_signal_received()) {
3998 errcode
= pthread_mutex_unlock(&tog_mutex
);
4000 err
= got_error_set_errno(errcode
,
4001 "pthread_mutex_unlock");
4004 err
= queue_commits(a
);
4006 if (err
->code
!= GOT_ERR_ITER_COMPLETED
)
4010 a
->commits_needed
= 0;
4011 } else if (a
->commits_needed
> 0 && !a
->load_all
) {
4014 a
->commits_needed
--;
4016 a
->commits_needed
--;
4019 errcode
= pthread_mutex_lock(&tog_mutex
);
4021 err
= got_error_set_errno(errcode
,
4022 "pthread_mutex_lock");
4024 } else if (*a
->quit
)
4026 else if (*a
->limiting
&& *a
->first_displayed_entry
== NULL
) {
4027 *a
->first_displayed_entry
=
4028 TAILQ_FIRST(&a
->limit_commits
->head
);
4029 *a
->selected_entry
= *a
->first_displayed_entry
;
4030 } else if (*a
->first_displayed_entry
== NULL
) {
4031 *a
->first_displayed_entry
=
4032 TAILQ_FIRST(&a
->real_commits
->head
);
4033 *a
->selected_entry
= *a
->first_displayed_entry
;
4036 a
->log_complete
= 1;
4038 errcode
= pthread_cond_signal(&a
->commit_loaded
);
4040 err
= got_error_set_errno(errcode
,
4041 "pthread_cond_signal");
4042 pthread_mutex_unlock(&tog_mutex
);
4046 if (a
->commits_needed
== 0 && a
->need_wt_status
) {
4047 errcode
= pthread_mutex_unlock(&tog_mutex
);
4049 err
= got_error_set_errno(errcode
,
4050 "pthread_mutex_unlock");
4053 err
= tog_worktree_status(a
);
4056 errcode
= pthread_mutex_lock(&tog_mutex
);
4058 err
= got_error_set_errno(errcode
,
4059 "pthread_mutex_lock");
4062 if (a
->wctx
.wt_state
!= 0)
4063 worktree_entries_reveal(a
);
4064 a
->need_wt_status
= 0;
4067 if (a
->commits_needed
== 0 &&
4068 a
->need_commit_marker
&& a
->worktree
) {
4069 errcode
= pthread_mutex_unlock(&tog_mutex
);
4071 err
= got_error_set_errno(errcode
,
4072 "pthread_mutex_unlock");
4075 err
= got_worktree_get_state(&tog_base_commit
.marker
,
4076 a
->repo
, a
->worktree
, NULL
, NULL
);
4079 errcode
= pthread_mutex_lock(&tog_mutex
);
4081 err
= got_error_set_errno(errcode
,
4082 "pthread_mutex_lock");
4085 a
->need_commit_marker
= 0;
4087 * The main thread did not close this
4088 * work tree yet. Close it now.
4090 got_worktree_close(a
->worktree
);
4098 a
->commits_needed
= 0;
4100 if (a
->commits_needed
== 0 && !a
->load_all
) {
4101 if (tog_io
.wait_for_ui
) {
4102 errcode
= pthread_cond_signal(
4105 err
= got_error_set_errno(
4107 "pthread_cond_signal");
4108 pthread_mutex_unlock(
4114 errcode
= pthread_cond_wait(&a
->need_commits
,
4117 err
= got_error_set_errno(errcode
,
4118 "pthread_cond_wait");
4119 pthread_mutex_unlock(&tog_mutex
);
4127 a
->log_complete
= 1;
4128 if (tog_io
.wait_for_ui
) {
4129 errcode
= pthread_cond_signal(&a
->log_loaded
);
4130 if (errcode
&& err
== NULL
)
4131 err
= got_error_set_errno(errcode
,
4132 "pthread_cond_signal");
4135 errcode
= pthread_mutex_unlock(&tog_mutex
);
4137 err
= got_error_set_errno(errcode
, "pthread_mutex_unlock");
4140 tog_thread_error
= 1;
4141 pthread_cond_signal(&a
->commit_loaded
);
4143 got_worktree_close(a
->worktree
);
4150 static const struct got_error
*
4151 stop_log_thread(struct tog_log_view_state
*s
)
4153 const struct got_error
*err
= NULL
, *thread_err
= NULL
;
4158 errcode
= pthread_cond_signal(&s
->thread_args
.need_commits
);
4160 return got_error_set_errno(errcode
,
4161 "pthread_cond_signal");
4162 errcode
= pthread_mutex_unlock(&tog_mutex
);
4164 return got_error_set_errno(errcode
,
4165 "pthread_mutex_unlock");
4166 errcode
= pthread_join(s
->thread
, (void **)&thread_err
);
4168 return got_error_set_errno(errcode
, "pthread_join");
4169 errcode
= pthread_mutex_lock(&tog_mutex
);
4171 return got_error_set_errno(errcode
,
4172 "pthread_mutex_lock");
4173 s
->thread
= 0; //NULL;
4176 if (s
->thread_args
.repo
) {
4177 err
= got_repo_close(s
->thread_args
.repo
);
4178 s
->thread_args
.repo
= NULL
;
4181 if (s
->thread_args
.pack_fds
) {
4182 const struct got_error
*pack_err
=
4183 got_repo_pack_fds_close(s
->thread_args
.pack_fds
);
4186 s
->thread_args
.pack_fds
= NULL
;
4189 if (s
->thread_args
.graph
) {
4190 got_commit_graph_close(s
->thread_args
.graph
);
4191 s
->thread_args
.graph
= NULL
;
4194 return err
? err
: thread_err
;
4198 worktree_ctx_close(struct tog_log_thread_args
*ta
)
4200 struct tog_worktree_ctx
*wctx
= &ta
->wctx
;
4203 free(wctx
->wt_author
);
4204 wctx
->wt_author
= NULL
;
4205 free(wctx
->wt_root
);
4206 wctx
->wt_root
= NULL
;
4208 wctx
->wt_ref
= NULL
;
4210 ta
->need_wt_status
= 1;
4214 static const struct got_error
*
4215 close_log_view(struct tog_view
*view
)
4217 const struct got_error
*err
= NULL
;
4218 struct tog_log_view_state
*s
= &view
->state
.log
;
4223 err
= stop_log_thread(s
);
4225 errcode
= pthread_cond_destroy(&s
->thread_args
.need_commits
);
4226 if (errcode
&& err
== NULL
)
4227 err
= got_error_set_errno(errcode
, "pthread_cond_destroy");
4229 errcode
= pthread_cond_destroy(&s
->thread_args
.commit_loaded
);
4230 if (errcode
&& err
== NULL
)
4231 err
= got_error_set_errno(errcode
, "pthread_cond_destroy");
4233 if (using_mock_io
) {
4234 errcode
= pthread_cond_destroy(&s
->thread_args
.log_loaded
);
4235 if (errcode
&& err
== NULL
)
4236 err
= got_error_set_errno(errcode
,
4237 "pthread_cond_destroy");
4240 free_commits(&s
->limit_commits
);
4241 free_commits(&s
->real_commits
);
4242 free_colors(&s
->colors
);
4243 free(s
->in_repo_path
);
4244 s
->in_repo_path
= NULL
;
4247 free(s
->head_ref_name
);
4248 s
->head_ref_name
= NULL
;
4249 worktree_ctx_close(&s
->thread_args
);
4254 * We use two queues to implement the limit feature: first consists of
4255 * commits matching the current limit_regex; second is the real queue
4256 * of all known commits (real_commits). When the user starts limiting,
4257 * we swap queues such that all movement and displaying functionality
4258 * works with very slight change.
4260 static const struct got_error
*
4261 limit_log_view(struct tog_view
*view
)
4263 struct tog_log_view_state
*s
= &view
->state
.log
;
4264 struct commit_queue_entry
*entry
;
4265 struct tog_view
*v
= view
;
4266 const struct got_error
*err
= NULL
;
4270 if (view_is_hsplit_top(view
))
4272 else if (view
->mode
== TOG_VIEW_SPLIT_VERT
&& view
->parent
)
4275 if (tog_io
.input_str
!= NULL
) {
4276 if (strlcpy(pattern
, tog_io
.input_str
, sizeof(pattern
)) >=
4278 return got_error(GOT_ERR_NO_SPACE
);
4280 wmove(v
->window
, v
->nlines
- 1, 0);
4281 wclrtoeol(v
->window
);
4282 mvwaddstr(v
->window
, v
->nlines
- 1, 0, "&/");
4283 nodelay(v
->window
, FALSE
);
4286 ret
= wgetnstr(v
->window
, pattern
, sizeof(pattern
));
4289 nodelay(v
->window
, TRUE
);
4294 if (*pattern
== '\0') {
4296 * Safety measure for the situation where the user
4297 * resets limit without previously limiting anything.
4303 * User could have pressed Ctrl+L, which refreshed the
4304 * commit queues, it means we can't save previously
4305 * (before limit took place) displayed entries,
4306 * because they would point to already free'ed memory,
4307 * so we are forced to always select first entry of
4310 s
->commits
= &s
->real_commits
;
4311 s
->first_displayed_entry
= TAILQ_FIRST(&s
->real_commits
.head
);
4312 s
->selected_entry
= s
->first_displayed_entry
;
4319 if (regcomp(&s
->limit_regex
, pattern
, REG_EXTENDED
| REG_NEWLINE
))
4324 /* Clear the screen while loading limit view */
4325 s
->first_displayed_entry
= NULL
;
4326 s
->last_displayed_entry
= NULL
;
4327 s
->selected_entry
= NULL
;
4328 s
->commits
= &s
->limit_commits
;
4330 /* Prepare limit queue for new search */
4331 free_commits(&s
->limit_commits
);
4332 s
->limit_commits
.ncommits
= 0;
4334 /* First process commits, which are in queue already */
4335 TAILQ_FOREACH(entry
, &s
->real_commits
.head
, entry
) {
4338 if (entry
->worktree_entry
== 0) {
4339 err
= match_commit(&have_match
, entry
->id
,
4340 entry
->commit
, &s
->limit_regex
);
4346 struct commit_queue_entry
*matched
;
4348 matched
= alloc_commit_queue_entry(entry
->commit
,
4350 if (matched
== NULL
) {
4351 err
= got_error_from_errno(
4352 "alloc_commit_queue_entry");
4355 matched
->commit
= entry
->commit
;
4356 got_object_commit_retain(entry
->commit
);
4358 matched
->idx
= s
->limit_commits
.ncommits
;
4359 TAILQ_INSERT_TAIL(&s
->limit_commits
.head
,
4361 s
->limit_commits
.ncommits
++;
4365 /* Second process all the commits, until we fill the screen */
4366 if (s
->limit_commits
.ncommits
< view
->nlines
- 1 &&
4367 !s
->thread_args
.log_complete
) {
4368 s
->thread_args
.commits_needed
+=
4369 view
->nlines
- s
->limit_commits
.ncommits
- 1;
4370 err
= trigger_log_thread(view
, 1);
4375 s
->first_displayed_entry
= TAILQ_FIRST(&s
->commits
->head
);
4376 s
->selected_entry
= TAILQ_FIRST(&s
->commits
->head
);
4382 static const struct got_error
*
4383 search_start_log_view(struct tog_view
*view
)
4385 struct tog_log_view_state
*s
= &view
->state
.log
;
4387 s
->matched_entry
= NULL
;
4388 s
->search_entry
= NULL
;
4392 static const struct got_error
*
4393 search_next_log_view(struct tog_view
*view
)
4395 const struct got_error
*err
= NULL
;
4396 struct tog_log_view_state
*s
= &view
->state
.log
;
4397 struct commit_queue_entry
*entry
;
4399 /* Display progress update in log view. */
4400 err
= show_log_view(view
);
4406 if (s
->search_entry
) {
4407 if (!using_mock_io
) {
4410 errcode
= pthread_mutex_unlock(&tog_mutex
);
4412 return got_error_set_errno(errcode
,
4413 "pthread_mutex_unlock");
4414 ch
= wgetch(view
->window
);
4415 errcode
= pthread_mutex_lock(&tog_mutex
);
4417 return got_error_set_errno(errcode
,
4418 "pthread_mutex_lock");
4419 if (ch
== CTRL('g') || ch
== KEY_BACKSPACE
) {
4420 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
4424 if (view
->searching
== TOG_SEARCH_FORWARD
)
4425 entry
= TAILQ_NEXT(s
->search_entry
, entry
);
4427 entry
= TAILQ_PREV(s
->search_entry
,
4428 commit_queue_head
, entry
);
4429 } else if (s
->matched_entry
) {
4431 * If the user has moved the cursor after we hit a match,
4432 * the position from where we should continue searching
4433 * might have changed.
4435 if (view
->searching
== TOG_SEARCH_FORWARD
)
4436 entry
= TAILQ_NEXT(s
->selected_entry
, entry
);
4438 entry
= TAILQ_PREV(s
->selected_entry
, commit_queue_head
,
4441 entry
= s
->selected_entry
;
4447 if (entry
== NULL
) {
4448 if (s
->thread_args
.log_complete
||
4449 view
->searching
== TOG_SEARCH_BACKWARD
) {
4450 view
->search_next_done
=
4451 (s
->matched_entry
== NULL
?
4452 TOG_SEARCH_HAVE_NONE
: TOG_SEARCH_NO_MORE
);
4453 s
->search_entry
= NULL
;
4457 * Poke the log thread for more commits and return,
4458 * allowing the main loop to make progress. Search
4459 * will resume at s->search_entry once we come back.
4461 s
->search_entry
= s
->selected_entry
;
4462 s
->thread_args
.commits_needed
++;
4463 return trigger_log_thread(view
, 0);
4466 if (entry
->worktree_entry
== 0) {
4467 err
= match_commit(&have_match
, entry
->id
,
4468 entry
->commit
, &view
->regex
);
4472 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
4473 s
->matched_entry
= entry
;
4478 s
->search_entry
= entry
;
4479 if (view
->searching
== TOG_SEARCH_FORWARD
)
4480 entry
= TAILQ_NEXT(entry
, entry
);
4482 entry
= TAILQ_PREV(entry
, commit_queue_head
, entry
);
4485 if (s
->matched_entry
) {
4486 int cur
= s
->selected_entry
->idx
;
4487 while (cur
< s
->matched_entry
->idx
) {
4488 err
= input_log_view(NULL
, view
, KEY_DOWN
);
4493 while (cur
> s
->matched_entry
->idx
) {
4494 err
= input_log_view(NULL
, view
, KEY_UP
);
4501 s
->search_entry
= NULL
;
4506 static const struct got_error
*
4507 open_log_view(struct tog_view
*view
, struct got_object_id
*start_id
,
4508 struct got_repository
*repo
, const char *head_ref_name
,
4509 const char *in_repo_path
, int log_branches
,
4510 struct got_worktree
*worktree
)
4512 const struct got_error
*err
= NULL
;
4513 struct tog_log_view_state
*s
= &view
->state
.log
;
4514 struct got_repository
*thread_repo
= NULL
;
4515 struct got_commit_graph
*thread_graph
= NULL
;
4518 if (in_repo_path
!= s
->in_repo_path
) {
4519 free(s
->in_repo_path
);
4520 s
->in_repo_path
= strdup(in_repo_path
);
4521 if (s
->in_repo_path
== NULL
) {
4522 err
= got_error_from_errno("strdup");
4527 /* The commit queue only contains commits being displayed. */
4528 TAILQ_INIT(&s
->real_commits
.head
);
4529 s
->real_commits
.ncommits
= 0;
4530 s
->commits
= &s
->real_commits
;
4532 TAILQ_INIT(&s
->limit_commits
.head
);
4534 s
->limit_commits
.ncommits
= 0;
4537 if (head_ref_name
) {
4538 s
->head_ref_name
= strdup(head_ref_name
);
4539 if (s
->head_ref_name
== NULL
) {
4540 err
= got_error_from_errno("strdup");
4544 s
->start_id
= got_object_id_dup(start_id
);
4545 if (s
->start_id
== NULL
) {
4546 err
= got_error_from_errno("got_object_id_dup");
4549 s
->log_branches
= log_branches
;
4550 s
->use_committer
= 1;
4552 STAILQ_INIT(&s
->colors
);
4553 if (has_colors() && getenv("TOG_COLORS") != NULL
) {
4554 err
= add_color(&s
->colors
, "^$", TOG_COLOR_COMMIT
,
4555 get_color_value("TOG_COLOR_COMMIT"));
4558 err
= add_color(&s
->colors
, "^$", TOG_COLOR_AUTHOR
,
4559 get_color_value("TOG_COLOR_AUTHOR"));
4562 err
= add_color(&s
->colors
, "^$", TOG_COLOR_DATE
,
4563 get_color_value("TOG_COLOR_DATE"));
4568 view
->show
= show_log_view
;
4569 view
->input
= input_log_view
;
4570 view
->resize
= resize_log_view
;
4571 view
->close
= close_log_view
;
4572 view
->search_start
= search_start_log_view
;
4573 view
->search_next
= search_next_log_view
;
4575 if (s
->thread_args
.pack_fds
== NULL
) {
4576 err
= got_repo_pack_fds_open(&s
->thread_args
.pack_fds
);
4580 err
= got_repo_open(&thread_repo
, got_repo_get_path(repo
), NULL
,
4581 s
->thread_args
.pack_fds
);
4584 err
= got_commit_graph_open(&thread_graph
, s
->in_repo_path
,
4588 err
= got_commit_graph_bfsort(thread_graph
, s
->start_id
,
4589 s
->repo
, NULL
, NULL
);
4593 errcode
= pthread_cond_init(&s
->thread_args
.need_commits
, NULL
);
4595 err
= got_error_set_errno(errcode
, "pthread_cond_init");
4598 errcode
= pthread_cond_init(&s
->thread_args
.commit_loaded
, NULL
);
4600 err
= got_error_set_errno(errcode
, "pthread_cond_init");
4604 if (using_mock_io
) {
4607 rc
= pthread_cond_init(&s
->thread_args
.log_loaded
, NULL
);
4609 return got_error_set_errno(rc
, "pthread_cond_init");
4612 s
->thread_args
.view_nlines
= &view
->nlines
;
4613 s
->thread_args
.commits_needed
= view
->nlines
;
4614 s
->thread_args
.graph
= thread_graph
;
4615 s
->thread_args
.real_commits
= &s
->real_commits
;
4616 s
->thread_args
.limit_commits
= &s
->limit_commits
;
4617 s
->thread_args
.in_repo_path
= s
->in_repo_path
;
4618 s
->thread_args
.start_id
= s
->start_id
;
4619 s
->thread_args
.repo
= thread_repo
;
4620 s
->thread_args
.log_complete
= 0;
4621 s
->thread_args
.quit
= &s
->quit
;
4622 s
->thread_args
.first_displayed_entry
= &s
->first_displayed_entry
;
4623 s
->thread_args
.last_displayed_entry
= &s
->last_displayed_entry
;
4624 s
->thread_args
.selected_entry
= &s
->selected_entry
;
4625 s
->thread_args
.selected
= &s
->selected
;
4626 s
->thread_args
.searching
= &view
->searching
;
4627 s
->thread_args
.search_next_done
= &view
->search_next_done
;
4628 s
->thread_args
.regex
= &view
->regex
;
4629 s
->thread_args
.limiting
= &s
->limit_view
;
4630 s
->thread_args
.limit_regex
= &s
->limit_regex
;
4631 s
->thread_args
.limit_commits
= &s
->limit_commits
;
4632 s
->thread_args
.worktree
= worktree
;
4634 s
->thread_args
.wctx
.active
= 1;
4635 s
->thread_args
.need_wt_status
= 1;
4636 s
->thread_args
.need_commit_marker
= 1;
4641 if (view
->close
== NULL
)
4642 close_log_view(view
);
4648 static const struct got_error
*
4649 show_log_view(struct tog_view
*view
)
4651 const struct got_error
*err
;
4652 struct tog_log_view_state
*s
= &view
->state
.log
;
4654 if (s
->thread
== 0) { //NULL) {
4655 int errcode
= pthread_create(&s
->thread
, NULL
, log_thread
,
4658 return got_error_set_errno(errcode
, "pthread_create");
4659 if (s
->thread_args
.commits_needed
> 0) {
4660 err
= trigger_log_thread(view
, 1);
4666 return draw_commits(view
);
4670 log_move_cursor_up(struct tog_view
*view
, int page
, int home
)
4672 struct tog_log_view_state
*s
= &view
->state
.log
;
4674 if (s
->first_displayed_entry
== NULL
)
4676 if (s
->selected_entry
->idx
== 0)
4679 if ((page
&& TAILQ_FIRST(&s
->commits
->head
) == s
->first_displayed_entry
)
4681 s
->selected
= home
? 0 : MAX(0, s
->selected
- page
- 1);
4683 if (!page
&& !home
&& s
->selected
> 0)
4686 log_scroll_up(s
, home
? s
->commits
->ncommits
: MAX(page
, 1));
4692 static const struct got_error
*
4693 log_move_cursor_down(struct tog_view
*view
, int page
)
4695 struct tog_log_view_state
*s
= &view
->state
.log
;
4696 const struct got_error
*err
= NULL
;
4697 int eos
= view
->nlines
- 2;
4699 if (s
->first_displayed_entry
== NULL
)
4702 if (s
->thread_args
.log_complete
&&
4703 s
->selected_entry
->idx
>= s
->commits
->ncommits
- 1)
4706 if (view_is_hsplit_top(view
))
4707 --eos
; /* border consumes the last line */
4710 if (s
->selected
< MIN(eos
, s
->commits
->ncommits
- 1))
4713 err
= log_scroll_down(view
, 1);
4714 } else if (s
->thread_args
.load_all
&& s
->thread_args
.log_complete
) {
4715 struct commit_queue_entry
*entry
;
4719 entry
= TAILQ_LAST(&s
->commits
->head
, commit_queue_head
);
4720 s
->last_displayed_entry
= entry
;
4721 for (n
= 0; n
<= eos
; n
++) {
4724 s
->first_displayed_entry
= entry
;
4725 entry
= TAILQ_PREV(entry
, commit_queue_head
, entry
);
4728 s
->selected
= n
- 1;
4730 if (s
->last_displayed_entry
->idx
== s
->commits
->ncommits
- 1 &&
4731 s
->thread_args
.log_complete
)
4732 s
->selected
+= MIN(page
,
4733 s
->commits
->ncommits
- s
->selected_entry
->idx
- 1);
4735 err
= log_scroll_down(view
, page
);
4741 * We might necessarily overshoot in horizontal
4742 * splits; if so, select the last displayed commit.
4744 if (view_is_hsplit_top(view
) && s
->first_displayed_entry
&&
4745 s
->last_displayed_entry
) {
4746 s
->selected
= MIN(s
->selected
,
4747 s
->last_displayed_entry
->idx
-
4748 s
->first_displayed_entry
->idx
);
4753 if (s
->thread_args
.log_complete
&&
4754 s
->selected_entry
->idx
== s
->commits
->ncommits
- 1)
4761 view_get_split(struct tog_view
*view
, int *y
, int *x
)
4766 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
) {
4767 if (view
->child
&& view
->child
->resized_y
)
4768 *y
= view
->child
->resized_y
;
4769 else if (view
->resized_y
)
4770 *y
= view
->resized_y
;
4772 *y
= view_split_begin_y(view
->lines
);
4773 } else if (view
->mode
== TOG_VIEW_SPLIT_VERT
) {
4774 if (view
->child
&& view
->child
->resized_x
)
4775 *x
= view
->child
->resized_x
;
4776 else if (view
->resized_x
)
4777 *x
= view
->resized_x
;
4779 *x
= view_split_begin_x(view
->begin_x
);
4783 /* Split view horizontally at y and offset view->state->selected line. */
4784 static const struct got_error
*
4785 view_init_hsplit(struct tog_view
*view
, int y
)
4787 const struct got_error
*err
= NULL
;
4791 err
= view_resize(view
);
4795 err
= offset_selection_down(view
);
4800 static const struct got_error
*
4801 log_goto_line(struct tog_view
*view
, int nlines
)
4803 const struct got_error
*err
= NULL
;
4804 struct tog_log_view_state
*s
= &view
->state
.log
;
4805 int g
, idx
= s
->selected_entry
->idx
;
4807 if (s
->first_displayed_entry
== NULL
|| s
->last_displayed_entry
== NULL
)
4813 if (g
>= s
->first_displayed_entry
->idx
+ 1 &&
4814 g
<= s
->last_displayed_entry
->idx
+ 1 &&
4815 g
- s
->first_displayed_entry
->idx
- 1 < nlines
) {
4816 s
->selected
= g
- s
->first_displayed_entry
->idx
- 1;
4822 err
= log_move_cursor_down(view
, g
- idx
- 1);
4823 if (!err
&& g
> s
->selected_entry
->idx
+ 1)
4824 err
= log_move_cursor_down(view
,
4825 g
- s
->first_displayed_entry
->idx
- 1);
4828 } else if (idx
+ 1 > g
)
4829 log_move_cursor_up(view
, idx
- g
+ 1, 0);
4831 if (g
< nlines
&& s
->first_displayed_entry
->idx
== 0)
4832 s
->selected
= g
- 1;
4840 horizontal_scroll_input(struct tog_view
*view
, int ch
)
4846 view
->x
-= MIN(view
->x
, 2);
4852 if (view
->x
+ view
->ncols
/ 2 < view
->maxx
)
4861 view
->x
= MAX(view
->maxx
- view
->ncols
/ 2, 0);
4870 log_mark_commit(struct tog_log_view_state
*s
)
4872 if (s
->selected_entry
== s
->marked_entry
)
4873 s
->marked_entry
= NULL
;
4875 s
->marked_entry
= s
->selected_entry
;
4878 static const struct got_error
*
4879 input_log_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
4881 const struct got_error
*err
= NULL
;
4882 struct tog_log_view_state
*s
= &view
->state
.log
;
4885 if (s
->thread_args
.load_all
) {
4886 if (ch
== CTRL('g') || ch
== KEY_BACKSPACE
)
4887 s
->thread_args
.load_all
= 0;
4888 else if (s
->thread_args
.log_complete
) {
4889 err
= log_move_cursor_down(view
, s
->commits
->ncommits
);
4890 s
->thread_args
.load_all
= 0;
4896 eos
= nscroll
= view
->nlines
- 1;
4897 if (view_is_hsplit_top(view
))
4901 return log_goto_line(view
, eos
);
4905 err
= limit_log_view(view
);
4916 horizontal_scroll_input(view
, ch
);
4923 log_move_cursor_up(view
, 0, 0);
4928 log_move_cursor_up(view
, 0, 1);
4938 log_move_cursor_up(view
, nscroll
, 0);
4945 err
= log_move_cursor_down(view
, 0);
4948 s
->use_committer
= !s
->use_committer
;
4949 view
->action
= s
->use_committer
?
4950 "show committer" : "show commit author";
4955 /* We don't know yet how many commits, so we're forced to
4956 * traverse them all. */
4958 s
->thread_args
.load_all
= 1;
4959 if (!s
->thread_args
.log_complete
)
4960 return trigger_log_thread(view
, 0);
4961 err
= log_move_cursor_down(view
, s
->commits
->ncommits
);
4962 s
->thread_args
.load_all
= 0;
4973 err
= log_move_cursor_down(view
, nscroll
);
4976 if (s
->selected
> view
->nlines
- 2)
4977 s
->selected
= view
->nlines
- 2;
4978 if (s
->selected
> s
->commits
->ncommits
- 1)
4979 s
->selected
= s
->commits
->ncommits
- 1;
4981 if (s
->commits
->ncommits
< view
->nlines
- 1 &&
4982 !s
->thread_args
.log_complete
) {
4983 s
->thread_args
.commits_needed
+= (view
->nlines
- 1) -
4984 s
->commits
->ncommits
;
4985 err
= trigger_log_thread(view
, 1);
4991 if (s
->selected_entry
== NULL
)
4993 err
= view_request_new(new_view
, view
, TOG_VIEW_DIFF
);
4997 if (s
->selected_entry
== NULL
)
4999 err
= view_request_new(new_view
, view
, TOG_VIEW_TREE
);
5005 if (ch
== KEY_BACKSPACE
&&
5006 got_path_is_root_dir(s
->in_repo_path
))
5008 err
= stop_log_thread(s
);
5011 if (ch
== KEY_BACKSPACE
) {
5013 err
= got_path_dirname(&parent_path
, s
->in_repo_path
);
5016 free(s
->in_repo_path
);
5017 s
->in_repo_path
= parent_path
;
5018 s
->thread_args
.in_repo_path
= s
->in_repo_path
;
5019 } else if (ch
== CTRL('l')) {
5020 struct got_object_id
*start_id
;
5021 err
= got_repo_match_object_id(&start_id
, NULL
,
5022 s
->head_ref_name
? s
->head_ref_name
: GOT_REF_HEAD
,
5023 GOT_OBJ_TYPE_COMMIT
, &tog_refs
, s
->repo
);
5025 if (s
->head_ref_name
== NULL
||
5026 err
->code
!= GOT_ERR_NOT_REF
)
5028 /* Try to cope with deleted references. */
5029 free(s
->head_ref_name
);
5030 s
->head_ref_name
= NULL
;
5031 err
= got_repo_match_object_id(&start_id
,
5032 NULL
, GOT_REF_HEAD
, GOT_OBJ_TYPE_COMMIT
,
5033 &tog_refs
, s
->repo
);
5038 s
->start_id
= start_id
;
5039 s
->thread_args
.start_id
= s
->start_id
;
5041 s
->log_branches
= !s
->log_branches
;
5043 if (s
->thread_args
.pack_fds
== NULL
) {
5044 err
= got_repo_pack_fds_open(&s
->thread_args
.pack_fds
);
5048 err
= got_repo_open(&s
->thread_args
.repo
,
5049 got_repo_get_path(s
->repo
), NULL
,
5050 s
->thread_args
.pack_fds
);
5054 err
= tog_load_refs(s
->repo
, 0);
5057 err
= got_commit_graph_open(&s
->thread_args
.graph
,
5058 s
->in_repo_path
, !s
->log_branches
);
5061 err
= got_commit_graph_bfsort(s
->thread_args
.graph
,
5062 s
->start_id
, s
->repo
, NULL
, NULL
);
5065 free_commits(&s
->real_commits
);
5066 free_commits(&s
->limit_commits
);
5067 s
->first_displayed_entry
= NULL
;
5068 s
->last_displayed_entry
= NULL
;
5069 s
->selected_entry
= NULL
;
5071 s
->thread_args
.log_complete
= 0;
5073 s
->thread_args
.commits_needed
= view
->lines
;
5074 s
->matched_entry
= NULL
;
5075 s
->search_entry
= NULL
;
5076 tog_base_commit
.idx
= -1;
5077 worktree_ctx_close(&s
->thread_args
);
5081 if (s
->selected_entry
->worktree_entry
== 0)
5086 err
= view_request_new(new_view
, view
, TOG_VIEW_REF
);
5096 static const struct got_error
*
5097 apply_unveil(const char *repo_path
, const char *worktree_path
)
5099 const struct got_error
*error
;
5102 if (unveil("gmon.out", "rwc") != 0)
5103 return got_error_from_errno2("unveil", "gmon.out");
5105 if (repo_path
&& unveil(repo_path
, "r") != 0)
5106 return got_error_from_errno2("unveil", repo_path
);
5108 if (worktree_path
&& unveil(worktree_path
, "rwc") != 0)
5109 return got_error_from_errno2("unveil", worktree_path
);
5111 if (unveil(GOT_TMPDIR_STR
, "rwc") != 0)
5112 return got_error_from_errno2("unveil", GOT_TMPDIR_STR
);
5114 error
= got_privsep_unveil_exec_helpers();
5118 if (unveil(NULL
, NULL
) != 0)
5119 return got_error_from_errno("unveil");
5124 static const struct got_error
*
5125 init_mock_term(const char *test_script_path
)
5127 const struct got_error
*err
= NULL
;
5128 const char *screen_dump_path
;
5131 if (test_script_path
== NULL
|| *test_script_path
== '\0')
5132 return got_error_msg(GOT_ERR_IO
, "TOG_TEST_SCRIPT not defined");
5134 tog_io
.f
= fopen(test_script_path
, "re");
5135 if (tog_io
.f
== NULL
) {
5136 err
= got_error_from_errno_fmt("fopen: %s",
5141 /* test mode, we don't want any output */
5142 tog_io
.cout
= fopen("/dev/null", "w+");
5143 if (tog_io
.cout
== NULL
) {
5144 err
= got_error_from_errno2("fopen", "/dev/null");
5148 in
= dup(fileno(tog_io
.cout
));
5150 err
= got_error_from_errno("dup");
5153 tog_io
.cin
= fdopen(in
, "r");
5154 if (tog_io
.cin
== NULL
) {
5155 err
= got_error_from_errno("fdopen");
5160 screen_dump_path
= getenv("TOG_SCR_DUMP");
5161 if (screen_dump_path
== NULL
|| *screen_dump_path
== '\0')
5162 return got_error_msg(GOT_ERR_IO
, "TOG_SCR_DUMP not defined");
5163 tog_io
.sdump
= fopen(screen_dump_path
, "we");
5164 if (tog_io
.sdump
== NULL
) {
5165 err
= got_error_from_errno2("fopen", screen_dump_path
);
5169 if (fseeko(tog_io
.f
, 0L, SEEK_SET
) == -1) {
5170 err
= got_error_from_errno("fseeko");
5174 if (newterm(NULL
, tog_io
.cout
, tog_io
.cin
) == NULL
)
5175 err
= got_error_msg(GOT_ERR_IO
,
5176 "newterm: failed to initialise curses");
5189 if (using_mock_io
) /* In test mode we use a fake terminal */
5195 halfdelay(1); /* Fast refresh while initial view is loading. */
5198 intrflush(stdscr
, FALSE
);
5199 keypad(stdscr
, TRUE
);
5201 if (getenv("TOG_COLORS") != NULL
) {
5203 use_default_colors();
5209 static const struct got_error
*
5210 set_tog_base_commit(struct got_repository
*repo
, struct got_worktree
*worktree
)
5212 tog_base_commit
.id
= got_object_id_dup(
5213 got_worktree_get_base_commit_id(worktree
));
5214 if (tog_base_commit
.id
== NULL
)
5215 return got_error_from_errno( "got_object_id_dup");
5220 static const struct got_error
*
5221 get_in_repo_path_from_argv0(char **in_repo_path
, int argc
, char *argv
[],
5222 struct got_repository
*repo
, struct got_worktree
*worktree
)
5224 const struct got_error
*err
= NULL
;
5227 *in_repo_path
= strdup("/");
5228 if (*in_repo_path
== NULL
)
5229 return got_error_from_errno("strdup");
5234 const char *prefix
= got_worktree_get_path_prefix(worktree
);
5237 err
= got_worktree_resolve_path(&p
, worktree
, argv
[0]);
5240 if (asprintf(in_repo_path
, "%s%s%s", prefix
,
5241 (p
[0] != '\0' && !got_path_is_root_dir(prefix
)) ? "/" : "",
5243 err
= got_error_from_errno("asprintf");
5244 *in_repo_path
= NULL
;
5248 err
= got_repo_map_path(in_repo_path
, repo
, argv
[0]);
5253 static const struct got_error
*
5254 cmd_log(int argc
, char *argv
[])
5256 const struct got_error
*error
;
5257 struct got_repository
*repo
= NULL
;
5258 struct got_worktree
*worktree
= NULL
;
5259 struct got_object_id
*start_id
= NULL
;
5260 char *in_repo_path
= NULL
, *repo_path
= NULL
, *cwd
= NULL
;
5261 char *keyword_idstr
= NULL
, *start_commit
= NULL
, *label
= NULL
;
5262 struct got_reference
*ref
= NULL
;
5263 const char *head_ref_name
= NULL
;
5264 int ch
, log_branches
= 0;
5265 struct tog_view
*view
;
5266 int *pack_fds
= NULL
;
5268 while ((ch
= getopt(argc
, argv
, "bc:r:")) != -1) {
5274 start_commit
= optarg
;
5277 repo_path
= realpath(optarg
, NULL
);
5278 if (repo_path
== NULL
)
5279 return got_error_from_errno2("realpath",
5294 error
= got_repo_pack_fds_open(&pack_fds
);
5298 if (repo_path
== NULL
) {
5299 cwd
= getcwd(NULL
, 0);
5301 error
= got_error_from_errno("getcwd");
5304 error
= got_worktree_open(&worktree
, cwd
, NULL
);
5305 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
5309 strdup(got_worktree_get_repo_path(worktree
));
5311 repo_path
= strdup(cwd
);
5312 if (repo_path
== NULL
) {
5313 error
= got_error_from_errno("strdup");
5318 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
5322 error
= get_in_repo_path_from_argv0(&in_repo_path
, argc
, argv
,
5329 error
= apply_unveil(got_repo_get_path(repo
),
5330 worktree
? got_worktree_get_root_path(worktree
) : NULL
);
5334 /* already loaded by tog_log_with_path()? */
5335 if (TAILQ_EMPTY(&tog_refs
)) {
5336 error
= tog_load_refs(repo
, 0);
5341 if (start_commit
== NULL
) {
5342 error
= got_repo_match_object_id(&start_id
, &label
,
5343 worktree
? got_worktree_get_head_ref_name(worktree
) :
5344 GOT_REF_HEAD
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
5347 head_ref_name
= label
;
5349 error
= got_keyword_to_idstr(&keyword_idstr
, start_commit
,
5353 if (keyword_idstr
!= NULL
)
5354 start_commit
= keyword_idstr
;
5356 error
= got_ref_open(&ref
, repo
, start_commit
, 0);
5358 head_ref_name
= got_ref_get_name(ref
);
5359 else if (error
->code
!= GOT_ERR_NOT_REF
)
5361 error
= got_repo_match_object_id(&start_id
, NULL
,
5362 start_commit
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
5367 view
= view_open(0, 0, 0, 0, TOG_VIEW_LOG
);
5369 error
= got_error_from_errno("view_open");
5374 error
= set_tog_base_commit(repo
, worktree
);
5379 error
= open_log_view(view
, start_id
, repo
, head_ref_name
,
5380 in_repo_path
, log_branches
, worktree
);
5385 /* The work tree will be closed by the log thread. */
5389 error
= view_loop(view
);
5392 free(tog_base_commit
.id
);
5393 free(keyword_idstr
);
5402 const struct got_error
*close_err
= got_repo_close(repo
);
5407 got_worktree_close(worktree
);
5409 const struct got_error
*pack_err
=
5410 got_repo_pack_fds_close(pack_fds
);
5422 fprintf(stderr
, "usage: %s diff [-asw] [-C number] [-c commit] "
5423 "[-r repository-path] [object1 object2 | path ...]\n",
5429 match_line(const char *line
, regex_t
*regex
, size_t nmatch
,
5430 regmatch_t
*regmatch
)
5432 return regexec(regex
, line
, nmatch
, regmatch
, 0) == 0;
5435 static struct tog_color
*
5436 match_color(struct tog_colors
*colors
, const char *line
)
5438 struct tog_color
*tc
= NULL
;
5440 STAILQ_FOREACH(tc
, colors
, entry
) {
5441 if (match_line(line
, &tc
->regex
, 0, NULL
))
5448 static const struct got_error
*
5449 add_matched_line(int *wtotal
, const char *line
, int wlimit
, int col_tab_align
,
5450 WINDOW
*window
, int skipcol
, regmatch_t
*regmatch
)
5452 const struct got_error
*err
= NULL
;
5454 wchar_t *wline
= NULL
;
5455 int rme
, rms
, n
, width
, scrollx
;
5456 int width0
= 0, width1
= 0, width2
= 0;
5457 char *seg0
= NULL
, *seg1
= NULL
, *seg2
= NULL
;
5461 rms
= regmatch
->rm_so
;
5462 rme
= regmatch
->rm_eo
;
5464 err
= expand_tab(&exstr
, line
);
5468 /* Split the line into 3 segments, according to match offsets. */
5469 seg0
= strndup(exstr
, rms
);
5471 err
= got_error_from_errno("strndup");
5474 seg1
= strndup(exstr
+ rms
, rme
- rms
);
5476 err
= got_error_from_errno("strndup");
5479 seg2
= strdup(exstr
+ rme
);
5481 err
= got_error_from_errno("strndup");
5485 /* draw up to matched token if we haven't scrolled past it */
5486 err
= format_line(&wline
, &width0
, NULL
, seg0
, 0, wlimit
,
5490 n
= MAX(width0
- skipcol
, 0);
5493 err
= format_line(&wline
, &width
, &scrollx
, seg0
, skipcol
,
5494 wlimit
, col_tab_align
, 1);
5497 waddwstr(window
, &wline
[scrollx
]);
5507 err
= format_line(&wline
, &width1
, NULL
, seg1
, 0, wlimit
,
5511 wlen
= wcslen(wline
);
5513 width
= wcwidth(wline
[i
]);
5515 /* should not happen, tabs are expanded */
5516 err
= got_error(GOT_ERR_RANGE
);
5519 if (width0
+ w
+ width
> skipcol
)
5524 /* draw (visible part of) matched token (if scrolled into it) */
5525 if (width1
- w
> 0) {
5526 wattron(window
, A_STANDOUT
);
5527 waddwstr(window
, &wline
[i
]);
5528 wattroff(window
, A_STANDOUT
);
5529 wlimit
-= (width1
- w
);
5530 *wtotal
+= (width1
- w
);
5534 if (wlimit
> 0) { /* draw rest of line */
5536 if (skipcol
> width0
+ width1
) {
5537 err
= format_line(&wline
, &width2
, &scrollx
, seg2
,
5538 skipcol
- (width0
+ width1
), wlimit
,
5542 waddwstr(window
, &wline
[scrollx
]);
5544 err
= format_line(&wline
, &width2
, NULL
, seg2
, 0,
5545 wlimit
, col_tab_align
, 1);
5548 waddwstr(window
, wline
);
5562 gotoline(struct tog_view
*view
, int *lineno
, int *nprinted
)
5565 int *eof
, *first
, *selected
;
5567 if (view
->type
== TOG_VIEW_DIFF
) {
5568 struct tog_diff_view_state
*s
= &view
->state
.diff
;
5570 first
= &s
->first_displayed_line
;
5574 } else if (view
->type
== TOG_VIEW_HELP
) {
5575 struct tog_help_view_state
*s
= &view
->state
.help
;
5577 first
= &s
->first_displayed_line
;
5581 } else if (view
->type
== TOG_VIEW_BLAME
) {
5582 struct tog_blame_view_state
*s
= &view
->state
.blame
;
5584 first
= &s
->first_displayed_line
;
5585 selected
= &s
->selected_line
;
5591 /* Center gline in the middle of the page like vi(1). */
5592 if (*lineno
< view
->gline
- (view
->nlines
- 3) / 2)
5594 if (*first
!= 1 && (*lineno
> view
->gline
- (view
->nlines
- 3) / 2)) {
5603 *selected
= view
->gline
<= (view
->nlines
- 3) / 2 ?
5604 view
->gline
: (view
->nlines
- 3) / 2 + 1;
5610 static const struct got_error
*
5611 draw_file(struct tog_view
*view
, const char *header
)
5613 struct tog_diff_view_state
*s
= &view
->state
.diff
;
5614 regmatch_t
*regmatch
= &view
->regmatch
;
5615 const struct got_error
*err
;
5618 size_t linesize
= 0;
5622 int max_lines
= view
->nlines
;
5623 int nlines
= s
->nlines
;
5626 s
->lineno
= s
->first_displayed_line
- 1;
5627 line_offset
= s
->lines
[s
->first_displayed_line
- 1].offset
;
5628 if (fseeko(s
->f
, line_offset
, SEEK_SET
) == -1)
5629 return got_error_from_errno("fseek");
5631 werase(view
->window
);
5633 if (view
->gline
> s
->nlines
- 1)
5634 view
->gline
= s
->nlines
- 1;
5637 int ln
= view
->gline
? view
->gline
<= (view
->nlines
- 3) / 2 ?
5638 1 : view
->gline
- (view
->nlines
- 3) / 2 :
5639 s
->lineno
+ s
->selected_line
;
5641 if (asprintf(&line
, "[%d/%d] %s", ln
, nlines
, header
) == -1)
5642 return got_error_from_errno("asprintf");
5643 err
= format_line(&wline
, &width
, NULL
, line
, 0, view
->ncols
,
5649 if (view_needs_focus_indication(view
))
5650 wstandout(view
->window
);
5651 waddwstr(view
->window
, wline
);
5654 while (width
++ < view
->ncols
)
5655 waddch(view
->window
, ' ');
5656 if (view_needs_focus_indication(view
))
5657 wstandend(view
->window
);
5667 while (max_lines
> 0 && nprinted
< max_lines
) {
5668 enum got_diff_line_type linetype
;
5671 linelen
= getline(&line
, &linesize
, s
->f
);
5672 if (linelen
== -1) {
5678 return got_ferror(s
->f
, GOT_ERR_IO
);
5681 if (++s
->lineno
< s
->first_displayed_line
)
5683 if (view
->gline
&& !gotoline(view
, &s
->lineno
, &nprinted
))
5685 if (s
->lineno
== view
->hiline
)
5688 /* Set view->maxx based on full line length. */
5689 err
= format_line(&wline
, &width
, NULL
, line
, 0, INT_MAX
, 0,
5695 view
->maxx
= MAX(view
->maxx
, width
);
5699 linetype
= s
->lines
[s
->lineno
].type
;
5700 if (linetype
> GOT_DIFF_LINE_LOGMSG
&&
5701 linetype
< GOT_DIFF_LINE_CONTEXT
)
5702 attr
|= COLOR_PAIR(linetype
);
5704 wattron(view
->window
, attr
);
5705 if (s
->first_displayed_line
+ nprinted
== s
->matched_line
&&
5706 regmatch
->rm_so
>= 0 && regmatch
->rm_so
< regmatch
->rm_eo
) {
5707 err
= add_matched_line(&width
, line
, view
->ncols
, 0,
5708 view
->window
, view
->x
, regmatch
);
5715 err
= format_line(&wline
, &width
, &skip
, line
,
5716 view
->x
, view
->ncols
, 0, view
->x
? 1 : 0);
5721 waddwstr(view
->window
, &wline
[skip
]);
5725 if (s
->lineno
== view
->hiline
) {
5726 /* highlight full gline length */
5727 while (width
++ < view
->ncols
)
5728 waddch(view
->window
, ' ');
5730 if (width
<= view
->ncols
- 1)
5731 waddch(view
->window
, '\n');
5734 wattroff(view
->window
, attr
);
5735 if (++nprinted
== 1)
5736 s
->first_displayed_line
= s
->lineno
;
5740 s
->last_displayed_line
= s
->first_displayed_line
+
5743 s
->last_displayed_line
= s
->first_displayed_line
;
5748 while (nprinted
< view
->nlines
) {
5749 waddch(view
->window
, '\n');
5753 err
= format_line(&wline
, &width
, NULL
, TOG_EOF_STRING
, 0,
5759 wstandout(view
->window
);
5760 waddwstr(view
->window
, wline
);
5763 wstandend(view
->window
);
5770 get_datestr(time_t *time
, char *datebuf
)
5772 struct tm mytm
, *tm
;
5775 tm
= gmtime_r(time
, &mytm
);
5778 s
= asctime_r(tm
, datebuf
);
5781 p
= strchr(s
, '\n');
5787 static const struct got_error
*
5788 add_line_metadata(struct got_diff_line
**lines
, size_t *nlines
,
5789 off_t off
, uint8_t type
)
5791 struct got_diff_line
*p
;
5793 p
= reallocarray(*lines
, *nlines
+ 1, sizeof(**lines
));
5795 return got_error_from_errno("reallocarray");
5797 (*lines
)[*nlines
].offset
= off
;
5798 (*lines
)[*nlines
].type
= type
;
5804 static const struct got_error
*
5805 cat_diff(FILE *dst
, FILE *src
, struct got_diff_line
**d_lines
, size_t *d_nlines
,
5806 struct got_diff_line
*s_lines
, size_t s_nlines
)
5808 struct got_diff_line
*p
;
5812 if (fseeko(src
, 0L, SEEK_SET
) == -1)
5813 return got_error_from_errno("fseeko");
5816 r
= fread(buf
, 1, sizeof(buf
), src
);
5819 return got_error_from_errno("fread");
5823 if (fwrite(buf
, 1, r
, dst
) != r
)
5824 return got_ferror(dst
, GOT_ERR_IO
);
5827 if (s_nlines
== 0 && *d_nlines
== 0)
5831 * If commit info was in dst, increment line offsets
5832 * of the appended diff content, but skip s_lines[0]
5833 * because offset zero is already in *d_lines.
5835 if (*d_nlines
> 0) {
5836 for (i
= 1; i
< s_nlines
; ++i
)
5837 s_lines
[i
].offset
+= (*d_lines
)[*d_nlines
- 1].offset
;
5845 p
= reallocarray(*d_lines
, *d_nlines
+ s_nlines
, sizeof(*p
));
5847 /* d_lines is freed in close_diff_view() */
5848 return got_error_from_errno("reallocarray");
5853 memcpy(*d_lines
+ *d_nlines
, s_lines
, s_nlines
* sizeof(*s_lines
));
5854 *d_nlines
+= s_nlines
;
5859 static const struct got_error
*
5860 write_diffstat(FILE *outfile
, struct got_diff_line
**lines
, size_t *nlines
,
5861 struct got_diffstat_cb_arg
*dsa
)
5863 const struct got_error
*err
;
5864 struct got_pathlist_entry
*pe
;
5869 err
= add_line_metadata(lines
, nlines
, 0, GOT_DIFF_LINE_NONE
);
5874 offset
= (*lines
)[*nlines
- 1].offset
;
5876 RB_FOREACH(pe
, got_pathlist_head
, dsa
->paths
) {
5877 struct got_diff_changed_path
*cp
= pe
->data
;
5878 int pad
= dsa
->max_path_len
- pe
->path_len
+ 1;
5880 n
= fprintf(outfile
, "%c %s%*c | %*d+ %*d-\n", cp
->status
,
5881 pe
->path
, pad
, ' ', dsa
->add_cols
+ 1, cp
->add
,
5882 dsa
->rm_cols
+ 1, cp
->rm
);
5884 return got_error_from_errno("fprintf");
5887 err
= add_line_metadata(lines
, nlines
, offset
,
5888 GOT_DIFF_LINE_CHANGES
);
5893 if (fputc('\n', outfile
) == EOF
)
5894 return got_error_from_errno("fputc");
5897 err
= add_line_metadata(lines
, nlines
, offset
, GOT_DIFF_LINE_NONE
);
5901 n
= fprintf(outfile
,
5902 "%d file%s changed, %d insertion%s(+), %d deletion%s(-)\n",
5903 dsa
->nfiles
, dsa
->nfiles
> 1 ? "s" : "", dsa
->ins
,
5904 dsa
->ins
!= 1 ? "s" : "", dsa
->del
, dsa
->del
!= 1 ? "s" : "");
5906 return got_error_from_errno("fprintf");
5909 err
= add_line_metadata(lines
, nlines
, offset
, GOT_DIFF_LINE_NONE
);
5913 if (fputc('\n', outfile
) == EOF
)
5914 return got_error_from_errno("fputc");
5917 return add_line_metadata(lines
, nlines
, offset
, GOT_DIFF_LINE_NONE
);
5920 static const struct got_error
*
5921 write_commit_info(struct got_diff_line
**lines
, size_t *nlines
,
5922 struct got_object_id
*commit_id
, struct got_reflist_head
*refs
,
5923 struct got_repository
*repo
, int ignore_ws
, int force_text_diff
,
5924 struct got_diffstat_cb_arg
*dsa
, FILE *outfile
)
5926 const struct got_error
*err
= NULL
;
5927 char datebuf
[26], *datestr
;
5928 struct got_commit_object
*commit
;
5929 char *id_str
= NULL
, *logmsg
= NULL
, *s
= NULL
, *line
;
5930 time_t committer_time
;
5931 const char *author
, *committer
;
5932 char *refs_str
= NULL
;
5936 err
= build_refs_str(&refs_str
, refs
, commit_id
, repo
);
5940 err
= got_object_open_as_commit(&commit
, repo
, commit_id
);
5944 err
= got_object_id_str(&id_str
, commit_id
);
5946 err
= got_error_from_errno("got_object_id_str");
5950 err
= add_line_metadata(lines
, nlines
, 0, GOT_DIFF_LINE_NONE
);
5954 n
= fprintf(outfile
, "commit %s%s%s%s\n", id_str
, refs_str
? " (" : "",
5955 refs_str
? refs_str
: "", refs_str
? ")" : "");
5957 err
= got_error_from_errno("fprintf");
5961 err
= add_line_metadata(lines
, nlines
, outoff
, GOT_DIFF_LINE_META
);
5965 n
= fprintf(outfile
, "from: %s\n",
5966 got_object_commit_get_author(commit
));
5968 err
= got_error_from_errno("fprintf");
5972 err
= add_line_metadata(lines
, nlines
, outoff
, GOT_DIFF_LINE_AUTHOR
);
5976 author
= got_object_commit_get_author(commit
);
5977 committer
= got_object_commit_get_committer(commit
);
5978 if (strcmp(author
, committer
) != 0) {
5979 n
= fprintf(outfile
, "via: %s\n", committer
);
5981 err
= got_error_from_errno("fprintf");
5985 err
= add_line_metadata(lines
, nlines
, outoff
,
5986 GOT_DIFF_LINE_AUTHOR
);
5990 committer_time
= got_object_commit_get_committer_time(commit
);
5991 datestr
= get_datestr(&committer_time
, datebuf
);
5993 n
= fprintf(outfile
, "date: %s UTC\n", datestr
);
5995 err
= got_error_from_errno("fprintf");
5999 err
= add_line_metadata(lines
, nlines
, outoff
,
6000 GOT_DIFF_LINE_DATE
);
6004 if (got_object_commit_get_nparents(commit
) > 1) {
6005 const struct got_object_id_queue
*parent_ids
;
6006 struct got_object_qid
*qid
;
6008 parent_ids
= got_object_commit_get_parent_ids(commit
);
6009 STAILQ_FOREACH(qid
, parent_ids
, entry
) {
6010 err
= got_object_id_str(&id_str
, &qid
->id
);
6013 n
= fprintf(outfile
, "parent %d: %s\n", pn
++, id_str
);
6015 err
= got_error_from_errno("fprintf");
6019 err
= add_line_metadata(lines
, nlines
, outoff
,
6020 GOT_DIFF_LINE_META
);
6028 err
= got_object_commit_get_logmsg(&logmsg
, commit
);
6032 while ((line
= strsep(&s
, "\n")) != NULL
) {
6033 n
= fprintf(outfile
, "%s\n", line
);
6035 err
= got_error_from_errno("fprintf");
6039 err
= add_line_metadata(lines
, nlines
, outoff
,
6040 GOT_DIFF_LINE_LOGMSG
);
6049 got_object_commit_close(commit
);
6054 evict_worktree_entry(struct tog_log_thread_args
*ta
, int victim
)
6056 struct commit_queue_entry
*e
, *v
= *ta
->selected_entry
;
6059 return; /* paranoid check */
6061 if (v
->worktree_entry
!= victim
) {
6062 TAILQ_FOREACH(v
, &ta
->real_commits
->head
, entry
) {
6063 if (v
->worktree_entry
== victim
)
6070 ta
->wctx
.wt_state
&= ~victim
;
6072 if (*ta
->selected_entry
== v
)
6073 *ta
->selected_entry
= TAILQ_NEXT(v
, entry
);
6074 if (*ta
->first_displayed_entry
== v
)
6075 *ta
->first_displayed_entry
= TAILQ_NEXT(v
, entry
);
6076 if (*ta
->last_displayed_entry
== v
)
6077 *ta
->last_displayed_entry
= TAILQ_NEXT(v
, entry
);
6079 for (e
= TAILQ_NEXT(v
, entry
); e
!= NULL
; e
= TAILQ_NEXT(e
, entry
))
6082 --tog_base_commit
.idx
;
6083 --ta
->real_commits
->ncommits
;
6085 TAILQ_REMOVE(&ta
->real_commits
->head
, v
, entry
);
6090 * Create a file which contains the target path of a symlink so we can feed
6091 * it as content to the diff engine.
6093 static const struct got_error
*
6094 get_symlink_target_file(int *fd
, int dirfd
, const char *de_name
,
6095 const char *abspath
)
6097 const struct got_error
*err
= NULL
;
6098 char target_path
[PATH_MAX
];
6099 ssize_t target_len
, outlen
;
6104 target_len
= readlinkat(dirfd
, de_name
, target_path
, PATH_MAX
);
6105 if (target_len
== -1)
6106 return got_error_from_errno2("readlinkat", abspath
);
6108 target_len
= readlink(abspath
, target_path
, PATH_MAX
);
6109 if (target_len
== -1)
6110 return got_error_from_errno2("readlink", abspath
);
6113 *fd
= got_opentempfd();
6115 return got_error_from_errno("got_opentempfd");
6117 outlen
= write(*fd
, target_path
, target_len
);
6119 err
= got_error_from_errno("got_opentempfd");
6123 if (lseek(*fd
, 0, SEEK_SET
) == -1) {
6124 err
= got_error_from_errno2("lseek", abspath
);
6136 static const struct got_error
*
6137 emit_base_commit_header(FILE *f
, struct got_diff_line
**lines
, size_t *nlines
,
6138 struct got_object_id
*commit_id
, struct got_worktree
*worktree
)
6140 const struct got_error
*err
;
6141 struct got_object_id
*base_commit_id
;
6142 char *base_commit_idstr
;
6145 if (worktree
== NULL
) /* shouldn't happen */
6146 return got_error(GOT_ERR_NOT_WORKTREE
);
6148 base_commit_id
= got_worktree_get_base_commit_id(worktree
);
6150 if (commit_id
!= NULL
) {
6151 if (got_object_id_cmp(commit_id
, base_commit_id
) != 0)
6152 base_commit_id
= commit_id
;
6155 err
= got_object_id_str(&base_commit_idstr
, base_commit_id
);
6159 if ((n
= fprintf(f
, "commit - %s\n", base_commit_idstr
)) < 0)
6160 err
= got_error_from_errno("fprintf");
6161 free(base_commit_idstr
);
6165 return add_line_metadata(lines
, nlines
,
6166 (*lines
)[*nlines
- 1].offset
+ n
, GOT_DIFF_LINE_META
);
6169 static const struct got_error
*
6170 tog_worktree_diff(void *arg
, unsigned char status
, unsigned char staged_status
,
6171 const char *path
, struct got_object_id
*blob_id
,
6172 struct got_object_id
*staged_blob_id
, struct got_object_id
*commit_id
,
6173 int dirfd
, const char *de_name
)
6175 const struct got_error
*err
= NULL
;
6176 struct diff_worktree_arg
*a
= arg
;
6177 struct got_blob_object
*blob1
= NULL
;
6180 char *abspath
= NULL
, *label1
= NULL
;
6183 int fd
= -1, fd1
= -1, fd2
= -1;
6184 int n
, f2_exists
= 1;
6186 if (a
->diff_staged
) {
6187 if (staged_status
!= GOT_STATUS_MODIFY
&&
6188 staged_status
!= GOT_STATUS_ADD
&&
6189 staged_status
!= GOT_STATUS_DELETE
)
6192 if (staged_status
== GOT_STATUS_DELETE
)
6194 if (status
== GOT_STATUS_NONEXISTENT
)
6195 return got_error_set_errno(ENOENT
, path
);
6196 if (status
!= GOT_STATUS_MODIFY
&&
6197 status
!= GOT_STATUS_ADD
&&
6198 status
!= GOT_STATUS_DELETE
&&
6199 status
!= GOT_STATUS_CONFLICT
)
6203 err
= got_opentemp_truncate(a
->f1
);
6205 return got_error_from_errno("got_opentemp_truncate");
6206 err
= got_opentemp_truncate(a
->f2
);
6208 return got_error_from_errno("got_opentemp_truncate");
6210 if (!a
->header_shown
) {
6211 n
= fprintf(a
->outfile
, "path + %s%s\n",
6212 got_worktree_get_root_path(a
->worktree
),
6213 a
->diff_staged
? " (staged changes)" : "");
6215 return got_error_from_errno("fprintf");
6218 err
= add_line_metadata(a
->lines
, a
->nlines
, outoff
,
6219 GOT_DIFF_LINE_META
);
6223 a
->header_shown
= 1;
6226 err
= emit_base_commit_header(a
->outfile
,
6227 a
->lines
, a
->nlines
, commit_id
, a
->worktree
);
6231 if (a
->diff_staged
) {
6232 const char *label1
= NULL
, *label2
= NULL
;
6234 switch (staged_status
) {
6235 case GOT_STATUS_MODIFY
:
6239 case GOT_STATUS_ADD
:
6242 case GOT_STATUS_DELETE
:
6246 return got_error(GOT_ERR_FILE_STATUS
);
6249 fd1
= got_opentempfd();
6251 return got_error_from_errno("got_opentempfd");
6253 fd2
= got_opentempfd();
6255 err
= got_error_from_errno("got_opentempfd");
6259 err
= got_diff_objects_as_blobs(a
->lines
, a
->nlines
,
6260 a
->f1
, a
->f2
, fd1
, fd2
, blob_id
, staged_blob_id
,
6261 label1
, label2
, a
->diff_algo
, a
->diff_context
,
6262 a
->ignore_whitespace
, a
->force_text_diff
,
6263 a
->diffstat
, a
->repo
, a
->outfile
);
6267 fd1
= got_opentempfd();
6269 return got_error_from_errno("got_opentempfd");
6271 if (staged_status
== GOT_STATUS_ADD
||
6272 staged_status
== GOT_STATUS_MODIFY
) {
6275 err
= got_object_open_as_blob(&blob1
,
6276 a
->repo
, staged_blob_id
, 8192, fd1
);
6279 err
= got_object_id_str(&id_str
, staged_blob_id
);
6282 if (asprintf(&label1
, "%s (staged)", id_str
) == -1) {
6283 err
= got_error_from_errno("asprintf");
6288 } else if (status
!= GOT_STATUS_ADD
) {
6289 err
= got_object_open_as_blob(&blob1
,
6290 a
->repo
, blob_id
, 8192, fd1
);
6295 if (status
!= GOT_STATUS_DELETE
) {
6296 if (asprintf(&abspath
, "%s/%s",
6297 got_worktree_get_root_path(a
->worktree
), path
) == -1) {
6298 err
= got_error_from_errno("asprintf");
6303 fd
= openat(dirfd
, de_name
,
6304 O_RDONLY
| O_NOFOLLOW
| O_CLOEXEC
);
6306 if (!got_err_open_nofollow_on_symlink()) {
6307 err
= got_error_from_errno2("openat",
6311 err
= get_symlink_target_file(&fd
,
6312 dirfd
, de_name
, abspath
);
6317 fd
= open(abspath
, O_RDONLY
| O_NOFOLLOW
| O_CLOEXEC
);
6319 if (!got_err_open_nofollow_on_symlink()) {
6320 err
= got_error_from_errno2("open",
6324 err
= get_symlink_target_file(&fd
,
6325 dirfd
, de_name
, abspath
);
6330 if (fstat(fd
, &sb
) == -1) {
6331 err
= got_error_from_errno2("fstat", abspath
);
6334 f2
= fdopen(fd
, "r");
6336 err
= got_error_from_errno2("fdopen", abspath
);
6345 if (blob1
!= NULL
) {
6346 err
= got_object_blob_dump_to_file(&size1
,
6347 NULL
, NULL
, a
->f1
, blob1
);
6352 err
= got_diff_blob_file(a
->lines
, a
->nlines
, blob1
, a
->f1
, size1
,
6353 label1
, f2
!= NULL
? f2
: a
->f2
, f2_exists
, &sb
, path
,
6354 tog_diff_algo
, a
->diff_context
, a
->ignore_whitespace
,
6355 a
->force_text_diff
, a
->diffstat
, a
->outfile
);
6358 if (fd
!= -1 && close(fd
) == -1 && err
== NULL
)
6359 err
= got_error_from_errno("close");
6360 if (fd1
!= -1 && close(fd1
) == -1 && err
== NULL
)
6361 err
= got_error_from_errno("close");
6362 if (fd2
!= -1 && close(fd2
) == -1 && err
== NULL
)
6363 err
= got_error_from_errno("close");
6365 got_object_blob_close(blob1
);
6366 if (f2
!= NULL
&& fclose(f2
) == EOF
&& err
== NULL
)
6367 err
= got_error_from_errno("fclose");
6373 static const struct got_error
*
6374 tog_diff_worktree(struct tog_diff_view_state
*s
, FILE *f
,
6375 struct got_diff_line
**lines
, size_t *nlines
,
6376 struct got_diffstat_cb_arg
*dsa
)
6378 const struct got_error
*close_err
, *err
;
6379 struct got_worktree
*worktree
= NULL
;
6380 struct diff_worktree_arg arg
;
6381 struct got_pathlist_head pathlist
;
6382 char *cwd
, *id_str
= NULL
;
6386 cwd
= getcwd(NULL
, 0);
6388 return got_error_from_errno("getcwd");
6390 err
= add_line_metadata(lines
, nlines
, 0, GOT_DIFF_LINE_NONE
);
6394 err
= got_worktree_open(&worktree
, cwd
, NULL
);
6396 if (err
->code
== GOT_ERR_WORKTREE_BUSY
) {
6399 if ((n
= fprintf(f
, "%s\n", err
->msg
)) < 0) {
6400 err
= got_ferror(f
, GOT_ERR_IO
);
6403 err
= add_line_metadata(lines
, nlines
, n
,
6404 GOT_DIFF_LINE_META
);
6407 err
= got_error(GOT_ERR_DIFF_NOCHANGES
);
6412 err
= got_object_id_str(&id_str
,
6413 got_worktree_get_base_commit_id(worktree
));
6417 err
= got_repo_match_object_id(&s
->id1
, NULL
, id_str
,
6418 GOT_OBJ_TYPE_ANY
, &tog_refs
, s
->repo
);
6422 arg
.id_str
= id_str
;
6423 arg
.diff_algo
= tog_diff_algo
;
6425 arg
.worktree
= worktree
;
6427 arg
.diff_context
= s
->diff_context
;
6428 arg
.diff_staged
= s
->diff_staged
;
6429 arg
.ignore_whitespace
= s
->ignore_whitespace
;
6430 arg
.force_text_diff
= s
->force_text_diff
;
6431 arg
.header_shown
= 0;
6433 arg
.nlines
= nlines
;
6438 if (s
->paths
== NULL
) {
6439 err
= got_pathlist_insert(NULL
, &pathlist
, "", NULL
);
6444 err
= got_worktree_status(worktree
, s
->paths
? s
->paths
: &pathlist
,
6445 s
->repo
, 0, tog_worktree_diff
, &arg
, NULL
, NULL
);
6450 const char *msg
= TOG_WORKTREE_CHANGES_LOCAL_MSG
;
6451 int n
, victim
= TOG_WORKTREE_CHANGES_LOCAL
;
6453 if (s
->diff_staged
) {
6454 victim
= TOG_WORKTREE_CHANGES_STAGED
;
6455 msg
= TOG_WORKTREE_CHANGES_STAGED_MSG
;
6457 if ((n
= fprintf(f
, "no %s\n", msg
)) < 0) {
6458 err
= got_ferror(f
, GOT_ERR_IO
);
6461 err
= add_line_metadata(lines
, nlines
, n
, GOT_DIFF_LINE_META
);
6464 if (s
->parent_view
&& s
->parent_view
->type
== TOG_VIEW_LOG
)
6465 evict_worktree_entry(
6466 &s
->parent_view
->state
.log
.thread_args
, victim
);
6467 err
= got_error(GOT_ERR_DIFF_NOCHANGES
);
6473 got_pathlist_free(&pathlist
, GOT_PATHLIST_FREE_NONE
);
6474 if (worktree
!= NULL
) {
6475 if ((close_err
= got_worktree_close(worktree
)) != NULL
) {
6476 if (err
== NULL
|| err
->code
== GOT_ERR_DIFF_NOCHANGES
)
6483 static const struct got_error
*
6484 tog_diff_objects(struct tog_diff_view_state
*s
, FILE *f
,
6485 struct got_diff_line
**lines
, size_t *nlines
,
6486 struct got_diffstat_cb_arg
*dsa
)
6488 const struct got_error
*err
;
6492 err
= got_object_get_type(&obj_type
, s
->repo
, s
->id1
);
6494 err
= got_object_get_type(&obj_type
, s
->repo
, s
->id2
);
6499 case GOT_OBJ_TYPE_BLOB
:
6500 err
= got_diff_objects_as_blobs(lines
, nlines
, s
->f1
, s
->f2
,
6501 s
->fd1
, s
->fd2
, s
->id1
, s
->id2
, NULL
, NULL
, tog_diff_algo
,
6502 s
->diff_context
, s
->ignore_whitespace
, s
->force_text_diff
,
6507 case GOT_OBJ_TYPE_TREE
:
6508 err
= got_diff_objects_as_trees(lines
, nlines
,
6509 s
->f1
, s
->f2
, s
->fd1
, s
->fd2
, s
->id1
, s
->id2
,
6510 s
->paths
, "", "", tog_diff_algo
, s
->diff_context
,
6511 s
->ignore_whitespace
, s
->force_text_diff
, dsa
, s
->repo
, f
);
6515 case GOT_OBJ_TYPE_COMMIT
: {
6516 const struct got_object_id_queue
*parent_ids
;
6517 struct got_commit_object
*commit2
;
6518 struct got_object_qid
*pid
;
6519 struct got_reflist_head
*refs
;
6521 err
= got_diff_objects_as_commits(lines
, nlines
, s
->f1
, s
->f2
,
6522 s
->fd1
, s
->fd2
, s
->id1
, s
->id2
, s
->paths
, tog_diff_algo
,
6523 s
->diff_context
, s
->ignore_whitespace
, s
->force_text_diff
,
6528 refs
= got_reflist_object_id_map_lookup(tog_refs_idmap
, s
->id2
);
6529 /* Show commit info if we're diffing to a parent/root commit. */
6531 return write_commit_info(&s
->lines
, &s
->nlines
, s
->id2
,
6532 refs
, s
->repo
, s
->ignore_whitespace
,
6533 s
->force_text_diff
, dsa
, s
->f
);
6535 err
= got_object_open_as_commit(&commit2
, s
->repo
,
6540 parent_ids
= got_object_commit_get_parent_ids(commit2
);
6541 STAILQ_FOREACH(pid
, parent_ids
, entry
) {
6542 if (got_object_id_cmp(s
->id1
, &pid
->id
) == 0) {
6543 err
= write_commit_info(&s
->lines
, &s
->nlines
,
6544 s
->id2
, refs
, s
->repo
, s
->ignore_whitespace
,
6545 s
->force_text_diff
, dsa
, s
->f
);
6549 if (commit2
!= NULL
)
6550 got_object_commit_close(commit2
);
6556 return got_error(GOT_ERR_OBJ_TYPE
);
6562 static const struct got_error
*
6563 create_diff(struct tog_diff_view_state
*s
)
6565 const struct got_error
*err
= NULL
;
6566 FILE *tmp_diff_file
= NULL
;
6567 struct got_diff_line
*lines
= NULL
;
6568 struct got_pathlist_head changed_paths
;
6569 struct got_diffstat_cb_arg dsa
;
6572 RB_INIT(&changed_paths
);
6573 memset(&dsa
, 0, sizeof(dsa
));
6574 dsa
.paths
= &changed_paths
;
6575 dsa
.diff_algo
= tog_diff_algo
;
6576 dsa
.force_text
= s
->force_text_diff
;
6577 dsa
.ignore_ws
= s
->ignore_whitespace
;
6580 s
->lines
= malloc(sizeof(*s
->lines
));
6581 if (s
->lines
== NULL
)
6582 return got_error_from_errno("malloc");
6585 if (s
->f
&& fclose(s
->f
) == EOF
) {
6587 return got_error_from_errno("fclose");
6590 s
->f
= got_opentemp();
6592 return got_error_from_errno("got_opentemp");
6595 * The diffstat requires the diff to be built first, but we want the
6596 * diffstat to precede the diff when displayed. Build the diff first
6597 * in the temporary file and write the diffstat and/or commit info to
6598 * the persistent file (s->f) from which views are drawn, then append
6599 * the diff from the temp file to the diffstat/commit info in s->f.
6601 tmp_diff_file
= got_opentemp();
6602 if (tmp_diff_file
== NULL
)
6603 return got_error_from_errno("got_opentemp");
6605 lines
= malloc(sizeof(*lines
));
6606 if (lines
== NULL
) {
6607 err
= got_error_from_errno("malloc");
6611 if (s
->parent_view
!= NULL
&& s
->parent_view
->type
== TOG_VIEW_LOG
) {
6612 struct tog_log_view_state
*ls
= &s
->parent_view
->state
.log
;
6613 struct commit_queue_entry
*cqe
= ls
->selected_entry
;
6615 if (cqe
->worktree_entry
!= 0) {
6616 if (cqe
->worktree_entry
== TOG_WORKTREE_CHANGES_STAGED
)
6618 s
->diff_worktree
= 1;
6622 if (s
->diff_worktree
)
6623 err
= tog_diff_worktree(s
, tmp_diff_file
,
6624 &lines
, &nlines
, &dsa
);
6626 err
= tog_diff_objects(s
, tmp_diff_file
,
6627 &lines
, &nlines
, &dsa
);
6629 if (err
->code
!= GOT_ERR_DIFF_NOCHANGES
)
6632 err
= write_diffstat(s
->f
, &s
->lines
, &s
->nlines
, &dsa
);
6637 err
= cat_diff(s
->f
, tmp_diff_file
, &s
->lines
, &s
->nlines
,
6642 got_pathlist_free(&changed_paths
, GOT_PATHLIST_FREE_ALL
);
6643 if (s
->f
&& fflush(s
->f
) != 0 && err
== NULL
)
6644 err
= got_error_from_errno("fflush");
6645 if (tmp_diff_file
&& fclose(tmp_diff_file
) == EOF
&& err
== NULL
)
6646 err
= got_error_from_errno("fclose");
6651 diff_view_indicate_progress(struct tog_view
*view
)
6653 mvwaddstr(view
->window
, 0, 0, "diffing...");
6658 static const struct got_error
*
6659 search_start_diff_view(struct tog_view
*view
)
6661 struct tog_diff_view_state
*s
= &view
->state
.diff
;
6663 s
->matched_line
= 0;
6668 search_setup_diff_view(struct tog_view
*view
, FILE **f
, off_t
**line_offsets
,
6669 size_t *nlines
, int **first
, int **last
, int **match
, int **selected
)
6671 struct tog_diff_view_state
*s
= &view
->state
.diff
;
6674 *nlines
= s
->nlines
;
6675 *line_offsets
= NULL
;
6676 *match
= &s
->matched_line
;
6677 *first
= &s
->first_displayed_line
;
6678 *last
= &s
->last_displayed_line
;
6679 *selected
= &s
->selected_line
;
6682 static const struct got_error
*
6683 search_next_view_match(struct tog_view
*view
)
6685 const struct got_error
*err
= NULL
;
6689 size_t linesize
= 0;
6691 off_t
*line_offsets
;
6693 int *first
, *last
, *match
, *selected
;
6695 if (!view
->search_setup
)
6696 return got_error_msg(GOT_ERR_NOT_IMPL
,
6697 "view search not supported");
6698 view
->search_setup(view
, &f
, &line_offsets
, &nlines
, &first
, &last
,
6701 if (!view
->searching
) {
6702 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
6707 if (view
->searching
== TOG_SEARCH_FORWARD
)
6708 lineno
= *first
+ 1;
6710 lineno
= *first
- 1;
6712 lineno
= *first
- 1 + *selected
;
6717 if (lineno
<= 0 || lineno
> nlines
) {
6719 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
6723 if (view
->searching
== TOG_SEARCH_FORWARD
)
6729 offset
= view
->type
== TOG_VIEW_DIFF
?
6730 view
->state
.diff
.lines
[lineno
- 1].offset
:
6731 line_offsets
[lineno
- 1];
6732 if (fseeko(f
, offset
, SEEK_SET
) != 0) {
6734 return got_error_from_errno("fseeko");
6736 linelen
= getline(&line
, &linesize
, f
);
6737 if (linelen
!= -1) {
6739 err
= expand_tab(&exstr
, line
);
6742 if (match_line(exstr
, &view
->regex
, 1,
6744 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
6751 if (view
->searching
== TOG_SEARCH_FORWARD
)
6766 static const struct got_error
*
6767 close_diff_view(struct tog_view
*view
)
6769 const struct got_error
*err
= NULL
;
6770 struct tog_diff_view_state
*s
= &view
->state
.diff
;
6778 if (s
->f
&& fclose(s
->f
) == EOF
)
6779 err
= got_error_from_errno("fclose");
6781 if (s
->f1
&& fclose(s
->f1
) == EOF
&& err
== NULL
)
6782 err
= got_error_from_errno("fclose");
6784 if (s
->f2
&& fclose(s
->f2
) == EOF
&& err
== NULL
)
6785 err
= got_error_from_errno("fclose");
6787 if (s
->fd1
!= -1 && close(s
->fd1
) == -1 && err
== NULL
)
6788 err
= got_error_from_errno("close");
6790 if (s
->fd2
!= -1 && close(s
->fd2
) == -1 && err
== NULL
)
6791 err
= got_error_from_errno("close");
6799 static const struct got_error
*
6800 open_diff_view(struct tog_view
*view
, struct got_object_id
*id1
,
6801 struct got_object_id
*id2
, const char *label1
, const char *label2
,
6802 int diff_context
, int ignore_whitespace
, int force_text_diff
,
6803 int diff_staged
, int diff_worktree
, const char *worktree_root
,
6804 struct tog_view
*parent_view
, struct got_repository
*repo
,
6805 struct got_pathlist_head
*paths
)
6807 const struct got_error
*err
;
6808 struct tog_diff_view_state
*s
= &view
->state
.diff
;
6810 memset(s
, 0, sizeof(*s
));
6814 if (id1
!= NULL
&& id2
!= NULL
) {
6817 err
= got_object_get_type(&type1
, repo
, id1
);
6820 err
= got_object_get_type(&type2
, repo
, id2
);
6824 if (type1
!= type2
) {
6825 err
= got_error(GOT_ERR_OBJ_TYPE
);
6830 if (diff_worktree
== 0) {
6832 s
->id1
= got_object_id_dup(id1
);
6833 if (s
->id1
== NULL
) {
6834 err
= got_error_from_errno("got_object_id_dup");
6840 s
->id2
= got_object_id_dup(id2
);
6841 if (s
->id2
== NULL
) {
6842 err
= got_error_from_errno("got_object_id_dup");
6847 s
->f1
= got_opentemp();
6848 if (s
->f1
== NULL
) {
6849 err
= got_error_from_errno("got_opentemp");
6853 s
->f2
= got_opentemp();
6854 if (s
->f2
== NULL
) {
6855 err
= got_error_from_errno("got_opentemp");
6859 s
->fd1
= got_opentempfd();
6861 err
= got_error_from_errno("got_opentempfd");
6865 s
->fd2
= got_opentempfd();
6867 err
= got_error_from_errno("got_opentempfd");
6871 s
->first_displayed_line
= 1;
6872 s
->last_displayed_line
= view
->nlines
;
6873 s
->selected_line
= 1;
6876 s
->diff_context
= diff_context
;
6877 s
->ignore_whitespace
= ignore_whitespace
;
6878 s
->force_text_diff
= force_text_diff
;
6879 s
->diff_worktree
= diff_worktree
;
6880 s
->diff_staged
= diff_staged
;
6881 s
->parent_view
= parent_view
;
6884 s
->worktree_root
= worktree_root
;
6886 if (has_colors() && getenv("TOG_COLORS") != NULL
&& !using_mock_io
) {
6889 rc
= init_pair(GOT_DIFF_LINE_MINUS
,
6890 get_color_value("TOG_COLOR_DIFF_MINUS"), -1);
6892 rc
= init_pair(GOT_DIFF_LINE_PLUS
,
6893 get_color_value("TOG_COLOR_DIFF_PLUS"), -1);
6895 rc
= init_pair(GOT_DIFF_LINE_HUNK
,
6896 get_color_value("TOG_COLOR_DIFF_CHUNK_HEADER"), -1);
6898 rc
= init_pair(GOT_DIFF_LINE_META
,
6899 get_color_value("TOG_COLOR_DIFF_META"), -1);
6901 rc
= init_pair(GOT_DIFF_LINE_CHANGES
,
6902 get_color_value("TOG_COLOR_DIFF_META"), -1);
6904 rc
= init_pair(GOT_DIFF_LINE_BLOB_MIN
,
6905 get_color_value("TOG_COLOR_DIFF_META"), -1);
6907 rc
= init_pair(GOT_DIFF_LINE_BLOB_PLUS
,
6908 get_color_value("TOG_COLOR_DIFF_META"), -1);
6910 rc
= init_pair(GOT_DIFF_LINE_AUTHOR
,
6911 get_color_value("TOG_COLOR_AUTHOR"), -1);
6913 rc
= init_pair(GOT_DIFF_LINE_DATE
,
6914 get_color_value("TOG_COLOR_DATE"), -1);
6916 err
= got_error(GOT_ERR_RANGE
);
6921 if (parent_view
&& parent_view
->type
== TOG_VIEW_LOG
&&
6922 view_is_splitscreen(view
)) {
6923 err
= show_log_view(parent_view
); /* draw border */
6927 diff_view_indicate_progress(view
);
6929 err
= create_diff(s
);
6931 view
->show
= show_diff_view
;
6932 view
->input
= input_diff_view
;
6933 view
->reset
= reset_diff_view
;
6934 view
->close
= close_diff_view
;
6935 view
->search_start
= search_start_diff_view
;
6936 view
->search_setup
= search_setup_diff_view
;
6937 view
->search_next
= search_next_view_match
;
6940 if (view
->close
== NULL
)
6941 close_diff_view(view
);
6947 static const struct got_error
*
6948 show_diff_view(struct tog_view
*view
)
6950 const struct got_error
*err
;
6951 struct tog_diff_view_state
*s
= &view
->state
.diff
;
6954 if (s
->diff_worktree
) {
6955 if (asprintf(&header
, "diff %s%s",
6956 s
->diff_staged
? "-s " : "", s
->worktree_root
) == -1)
6957 return got_error_from_errno("asprintf");
6959 char *id_str2
, *id_str1
= NULL
;
6960 const char *label1
, *label2
;
6963 err
= got_object_id_str(&id_str1
, s
->id1
);
6966 label1
= s
->label1
? s
->label1
: id_str1
;
6968 label1
= "/dev/null";
6970 err
= got_object_id_str(&id_str2
, s
->id2
);
6973 label2
= s
->label2
? s
->label2
: id_str2
;
6975 if (asprintf(&header
, "diff %s %s", label1
, label2
) == -1) {
6976 err
= got_error_from_errno("asprintf");
6985 err
= draw_file(view
, header
);
6990 static const struct got_error
*
6991 diff_write_patch(struct tog_view
*view
)
6993 const struct got_error
*err
;
6994 struct tog_diff_view_state
*s
= &view
->state
.diff
;
6995 struct got_object_id
*id2
= s
->id2
;
6997 char buf
[BUFSIZ
], pathbase
[PATH_MAX
];
6998 char *idstr1
, *idstr2
= NULL
, *path
= NULL
;
7003 if (s
->action
!= NULL
) {
7010 return got_error_from_errno("ftello");
7011 if (fseeko(s
->f
, 0L, SEEK_SET
) == -1)
7012 return got_error_from_errno("fseeko");
7014 if (s
->id1
!= NULL
) {
7015 err
= got_object_id_str(&idstr1
, s
->id1
);
7020 if (s
->diff_worktree
== 0 || tog_base_commit
.id
== NULL
) {
7021 /* illegal state that should not be possible */
7022 err
= got_error(GOT_ERR_NOT_WORKTREE
);
7025 id2
= tog_base_commit
.id
;
7027 err
= got_object_id_str(&idstr2
, id2
);
7031 rc
= snprintf(pathbase
, sizeof(pathbase
), "%s/tog-%.8s-%.8s",
7032 GOT_TMPDIR_STR
, idstr1
!= NULL
? idstr1
: "empty", idstr2
);
7033 if (rc
< 0 || (size_t)rc
>= sizeof(pathbase
)) {
7034 err
= got_error(rc
< 0 ? GOT_ERR_IO
: GOT_ERR_NO_SPACE
);
7038 err
= got_opentemp_named(&path
, &f
, pathbase
, ".diff");
7042 while ((r
= fread(buf
, 1, sizeof(buf
), s
->f
)) > 0) {
7043 if (fwrite(buf
, 1, r
, f
) != r
) {
7044 err
= got_ferror(f
, GOT_ERR_IO
);
7050 err
= got_error_from_errno("fread");
7053 if (fseeko(s
->f
, pos
, SEEK_SET
) == -1) {
7054 err
= got_error_from_errno("fseeko");
7058 if (fflush(f
) == EOF
) {
7059 err
= got_error_from_errno2("fflush", path
);
7063 if (asprintf(&s
->action
, "patch file written to %s", path
) == -1) {
7064 err
= got_error_from_errno("asprintf");
7068 view
->action
= s
->action
;
7071 if (f
!= NULL
&& fclose(f
) == EOF
&& err
== NULL
)
7072 err
= got_error_from_errno2("fclose", path
);
7079 static const struct got_error
*
7080 set_selected_commit(struct tog_diff_view_state
*s
,
7081 struct commit_queue_entry
*entry
)
7083 const struct got_error
*err
;
7084 const struct got_object_id_queue
*parent_ids
;
7085 struct got_commit_object
*selected_commit
;
7086 struct got_object_qid
*pid
;
7093 if (entry
->worktree_entry
== 0) {
7094 s
->id2
= got_object_id_dup(entry
->id
);
7096 return got_error_from_errno("got_object_id_dup");
7098 err
= got_object_open_as_commit(&selected_commit
,
7099 s
->repo
, entry
->id
);
7102 parent_ids
= got_object_commit_get_parent_ids(selected_commit
);
7103 pid
= STAILQ_FIRST(parent_ids
);
7104 s
->id1
= pid
? got_object_id_dup(&pid
->id
) : NULL
;
7105 got_object_commit_close(selected_commit
);
7111 static const struct got_error
*
7112 reset_diff_view(struct tog_view
*view
)
7114 struct tog_diff_view_state
*s
= &view
->state
.diff
;
7117 wclear(view
->window
);
7118 s
->first_displayed_line
= 1;
7119 s
->last_displayed_line
= view
->nlines
;
7120 s
->matched_line
= 0;
7121 if (s
->action
!= NULL
) {
7125 diff_view_indicate_progress(view
);
7126 return create_diff(s
);
7130 diff_prev_index(struct tog_diff_view_state
*s
, enum got_diff_line_type type
)
7134 i
= start
= s
->first_displayed_line
- 1;
7136 while (s
->lines
[i
].type
!= type
) {
7140 return; /* do nothing, requested type not in file */
7143 s
->selected_line
= 1;
7144 s
->first_displayed_line
= i
;
7148 diff_next_index(struct tog_diff_view_state
*s
, enum got_diff_line_type type
)
7152 i
= start
= s
->first_displayed_line
+ 1;
7154 while (s
->lines
[i
].type
!= type
) {
7155 if (i
== s
->nlines
- 1)
7158 return; /* do nothing, requested type not in file */
7161 s
->selected_line
= 1;
7162 s
->first_displayed_line
= i
;
7165 static struct got_object_id
*get_selected_commit_id(struct tog_blame_line
*,
7167 static struct got_object_id
*get_annotation_for_line(struct tog_blame_line
*,
7170 static const struct got_error
*
7171 input_diff_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
7173 const struct got_error
*err
= NULL
;
7174 struct tog_diff_view_state
*s
= &view
->state
.diff
;
7175 struct tog_log_view_state
*ls
;
7176 struct commit_queue_entry
*old_selected_entry
;
7178 size_t linesize
= 0;
7180 int i
, nscroll
= view
->nlines
- 1, up
= 0;
7182 s
->lineno
= s
->first_displayed_line
- 1 + s
->selected_line
;
7184 if (s
->action
!= NULL
&& ch
!= ERR
) {
7187 view
->action
= NULL
;
7197 horizontal_scroll_input(view
, ch
);
7202 s
->force_text_diff
= !s
->force_text_diff
;
7203 view
->action
= s
->force_text_diff
?
7204 "force ASCII text enabled" :
7205 "force ASCII text disabled";
7207 else if (ch
== 'w') {
7208 s
->ignore_whitespace
= !s
->ignore_whitespace
;
7209 view
->action
= s
->ignore_whitespace
?
7210 "ignore whitespace enabled" :
7211 "ignore whitespace disabled";
7213 err
= reset_diff_view(view
);
7217 s
->first_displayed_line
= 1;
7226 s
->first_displayed_line
= (s
->nlines
- view
->nlines
) + 2;
7232 if (s
->first_displayed_line
> 1)
7233 s
->first_displayed_line
--;
7244 if (s
->first_displayed_line
== 1) {
7249 while (i
++ < nscroll
&& s
->first_displayed_line
> 1)
7250 s
->first_displayed_line
--;
7256 s
->first_displayed_line
++;
7273 while (!s
->eof
&& i
++ < nscroll
) {
7274 linelen
= getline(&line
, &linesize
, s
->f
);
7275 s
->first_displayed_line
++;
7276 if (linelen
== -1) {
7280 err
= got_ferror(s
->f
, GOT_ERR_IO
);
7287 diff_prev_index(s
, GOT_DIFF_LINE_BLOB_MIN
);
7290 diff_next_index(s
, GOT_DIFF_LINE_BLOB_MIN
);
7293 diff_prev_index(s
, GOT_DIFF_LINE_HUNK
);
7296 diff_next_index(s
, GOT_DIFF_LINE_HUNK
);
7299 if (s
->diff_context
> 0) {
7301 s
->matched_line
= 0;
7302 diff_view_indicate_progress(view
);
7303 err
= create_diff(s
);
7304 if (s
->first_displayed_line
+ view
->nlines
- 1 >
7306 s
->first_displayed_line
= 1;
7307 s
->last_displayed_line
= view
->nlines
;
7313 if (s
->diff_context
< GOT_DIFF_MAX_CONTEXT
) {
7315 s
->matched_line
= 0;
7316 diff_view_indicate_progress(view
);
7317 err
= create_diff(s
);
7329 if (s
->parent_view
== NULL
) {
7333 s
->parent_view
->count
= view
->count
;
7335 if (s
->parent_view
->type
== TOG_VIEW_LOG
) {
7336 ls
= &s
->parent_view
->state
.log
;
7337 old_selected_entry
= ls
->selected_entry
;
7339 err
= input_log_view(NULL
, s
->parent_view
,
7340 up
? KEY_UP
: KEY_DOWN
);
7343 view
->count
= s
->parent_view
->count
;
7345 if (old_selected_entry
== ls
->selected_entry
)
7350 err
= set_selected_commit(s
, ls
->selected_entry
);
7354 if (s
->worktree_root
== NULL
)
7355 s
->worktree_root
= ls
->thread_args
.wctx
.wt_root
;
7356 } else if (s
->parent_view
->type
== TOG_VIEW_BLAME
) {
7357 struct tog_blame_view_state
*bs
;
7358 struct got_object_id
*id
, *prev_id
;
7360 bs
= &s
->parent_view
->state
.blame
;
7361 prev_id
= get_annotation_for_line(bs
->blame
.lines
,
7362 bs
->blame
.nlines
, bs
->last_diffed_line
);
7364 err
= input_blame_view(&view
, s
->parent_view
,
7365 up
? KEY_UP
: KEY_DOWN
);
7368 view
->count
= s
->parent_view
->count
;
7370 if (prev_id
== NULL
)
7372 id
= get_selected_commit_id(bs
->blame
.lines
,
7373 bs
->blame
.nlines
, bs
->first_displayed_line
,
7378 if (!got_object_id_cmp(prev_id
, id
))
7381 err
= input_blame_view(&view
, s
->parent_view
, KEY_ENTER
);
7386 s
->diff_worktree
= 0;
7387 s
->first_displayed_line
= 1;
7388 s
->last_displayed_line
= view
->nlines
;
7389 s
->matched_line
= 0;
7392 diff_view_indicate_progress(view
);
7393 err
= create_diff(s
);
7396 err
= diff_write_patch(view
);
7406 static const struct got_error
*
7407 get_worktree_paths_from_argv(struct got_pathlist_head
*paths
, int argc
,
7408 char *argv
[], struct got_worktree
*worktree
)
7410 const struct got_error
*err
= NULL
;
7412 struct got_pathlist_entry
*new;
7418 return got_error_from_errno("strdup");
7419 return got_pathlist_insert(NULL
, paths
, path
, NULL
);
7422 for (i
= 0; i
< argc
; i
++) {
7423 err
= got_worktree_resolve_path(&path
, worktree
, argv
[i
]);
7426 err
= got_pathlist_insert(&new, paths
, path
, NULL
);
7427 if (err
!= NULL
|| new == NULL
) {
7437 static const struct got_error
*
7438 cmd_diff(int argc
, char *argv
[])
7440 const struct got_error
*error
;
7441 struct got_repository
*repo
= NULL
;
7442 struct got_worktree
*worktree
= NULL
;
7443 struct got_pathlist_head paths
;
7444 struct got_object_id
*ids
[2] = { NULL
, NULL
};
7445 const char *commit_args
[2] = { NULL
, NULL
};
7446 char *labels
[2] = { NULL
, NULL
};
7447 char *repo_path
= NULL
, *worktree_path
= NULL
, *cwd
= NULL
;
7448 int type1
= GOT_OBJ_TYPE_ANY
, type2
= GOT_OBJ_TYPE_ANY
;
7449 int i
, ncommit_args
= 0, diff_context
= 3, ignore_whitespace
= 0;
7450 int ch
, diff_staged
= 0, diff_worktree
= 0, force_text_diff
= 0;
7452 struct tog_view
*view
;
7453 int *pack_fds
= NULL
;
7457 while ((ch
= getopt(argc
, argv
, "aC:c:r:sw")) != -1) {
7460 force_text_diff
= 1;
7463 diff_context
= strtonum(optarg
, 0, GOT_DIFF_MAX_CONTEXT
,
7466 errx(1, "number of context lines is %s: %s",
7470 if (ncommit_args
>= 2)
7471 errx(1, "too many -c options used");
7472 commit_args
[ncommit_args
++] = optarg
;
7475 repo_path
= realpath(optarg
, NULL
);
7476 if (repo_path
== NULL
)
7477 return got_error_from_errno2("realpath",
7479 got_path_strip_trailing_slashes(repo_path
);
7485 ignore_whitespace
= 1;
7496 error
= got_repo_pack_fds_open(&pack_fds
);
7500 if (repo_path
== NULL
) {
7501 cwd
= getcwd(NULL
, 0);
7503 return got_error_from_errno("getcwd");
7504 error
= got_worktree_open(&worktree
, cwd
, NULL
);
7505 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
7509 strdup(got_worktree_get_repo_path(worktree
));
7511 repo_path
= strdup(cwd
);
7512 if (repo_path
== NULL
) {
7513 error
= got_error_from_errno("strdup");
7518 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
7522 if (diff_staged
&& (worktree
== NULL
|| ncommit_args
> 0)) {
7523 error
= got_error_msg(GOT_ERR_BAD_OPTION
,
7524 "-s can only be used when diffing a work tree");
7530 error
= apply_unveil(got_repo_get_path(repo
),
7531 worktree
!= NULL
? got_worktree_get_root_path(worktree
) : NULL
);
7535 if (argc
== 2 || ncommit_args
> 0) {
7536 int obj_type
= (ncommit_args
> 0 ?
7537 GOT_OBJ_TYPE_COMMIT
: GOT_OBJ_TYPE_ANY
);
7539 error
= tog_load_refs(repo
, 0);
7543 for (i
= 0; i
< (ncommit_args
> 0 ? ncommit_args
: argc
); ++i
) {
7545 char *keyword_idstr
= NULL
;
7547 if (ncommit_args
> 0)
7548 arg
= commit_args
[i
];
7552 error
= got_keyword_to_idstr(&keyword_idstr
, arg
,
7556 if (keyword_idstr
!= NULL
)
7557 arg
= keyword_idstr
;
7559 error
= got_repo_match_object_id(&ids
[i
], &labels
[i
],
7560 arg
, obj_type
, &tog_refs
, repo
);
7561 free(keyword_idstr
);
7562 if (error
!= NULL
) {
7563 if (error
->code
!= GOT_ERR_NOT_REF
&&
7564 error
->code
!= GOT_ERR_NO_OBJ
)
7566 if (ncommit_args
> 0)
7574 if (diff_staged
&& ids
[0] != NULL
) {
7575 error
= got_error_msg(GOT_ERR_BAD_OPTION
,
7576 "-s can only be used when diffing a work tree");
7580 if (ncommit_args
== 0 && (ids
[0] == NULL
|| ids
[1] == NULL
)) {
7581 if (worktree
== NULL
) {
7582 if (argc
== 2 && ids
[0] == NULL
) {
7583 error
= got_error_path(argv
[0], GOT_ERR_NO_OBJ
);
7585 } else if (argc
== 2 && ids
[1] == NULL
) {
7586 error
= got_error_path(argv
[1], GOT_ERR_NO_OBJ
);
7588 } else if (argc
> 0) {
7589 error
= got_error_fmt(GOT_ERR_NOT_WORKTREE
,
7590 "%s", "specified paths cannot be resolved");
7593 error
= got_error(GOT_ERR_NOT_WORKTREE
);
7598 error
= get_worktree_paths_from_argv(&paths
, argc
, argv
,
7603 worktree_path
= strdup(got_worktree_get_root_path(worktree
));
7604 if (worktree_path
== NULL
) {
7605 error
= got_error_from_errno("strdup");
7611 if (ncommit_args
== 1) { /* diff commit against its first parent */
7612 struct got_commit_object
*commit
;
7614 error
= got_object_open_as_commit(&commit
, repo
, ids
[0]);
7618 labels
[1] = labels
[0];
7620 if (got_object_commit_get_nparents(commit
) > 0) {
7621 const struct got_object_id_queue
*pids
;
7622 struct got_object_qid
*pid
;
7624 pids
= got_object_commit_get_parent_ids(commit
);
7625 pid
= STAILQ_FIRST(pids
);
7626 ids
[0] = got_object_id_dup(&pid
->id
);
7627 if (ids
[0] == NULL
) {
7628 error
= got_error_from_errno(
7629 "got_object_id_dup");
7630 got_object_commit_close(commit
);
7633 error
= got_object_id_str(&labels
[0], ids
[0]);
7634 if (error
!= NULL
) {
7635 got_object_commit_close(commit
);
7640 labels
[0] = strdup("/dev/null");
7641 if (labels
[0] == NULL
) {
7642 error
= got_error_from_errno("strdup");
7643 got_object_commit_close(commit
);
7648 got_object_commit_close(commit
);
7651 if (ncommit_args
== 0 && argc
> 2) {
7652 error
= got_error_msg(GOT_ERR_BAD_PATH
,
7653 "path arguments cannot be used when diffing two objects");
7658 error
= got_object_get_type(&type1
, repo
, ids
[0]);
7663 if (diff_worktree
== 0) {
7664 error
= got_object_get_type(&type2
, repo
, ids
[1]);
7667 if (type1
!= GOT_OBJ_TYPE_ANY
&& type1
!= type2
) {
7668 error
= got_error(GOT_ERR_OBJ_TYPE
);
7671 if (type1
== GOT_OBJ_TYPE_BLOB
&& argc
> 2) {
7672 error
= got_error_msg(GOT_ERR_OBJ_TYPE
,
7673 "path arguments cannot be used when diffing blobs");
7678 for (i
= 0; ncommit_args
> 0 && i
< argc
; i
++) {
7680 struct got_pathlist_entry
*new;
7686 error
= got_worktree_resolve_path(&p
, worktree
,
7690 prefix
= got_worktree_get_path_prefix(worktree
);
7691 while (prefix
[0] == '/')
7693 if (asprintf(&in_repo_path
, "%s%s%s", prefix
,
7694 (p
[0] != '\0' && prefix
[0] != '\0') ? "/" : "",
7696 error
= got_error_from_errno("asprintf");
7702 char *mapped_path
, *s
;
7704 error
= got_repo_map_path(&mapped_path
, repo
, argv
[i
]);
7710 in_repo_path
= strdup(s
);
7711 if (in_repo_path
== NULL
) {
7712 error
= got_error_from_errno("asprintf");
7719 error
= got_pathlist_insert(&new, &paths
, in_repo_path
, NULL
);
7720 if (error
!= NULL
|| new == NULL
)
7726 view
= view_open(0, 0, 0, 0, TOG_VIEW_DIFF
);
7728 error
= got_error_from_errno("view_open");
7733 error
= set_tog_base_commit(repo
, worktree
);
7737 /* Release work tree lock. */
7738 got_worktree_close(worktree
);
7742 error
= open_diff_view(view
, ids
[0], ids
[1], labels
[0], labels
[1],
7743 diff_context
, ignore_whitespace
, force_text_diff
, diff_staged
,
7744 diff_worktree
, worktree_path
, NULL
, repo
, &paths
);
7748 error
= view_loop(view
);
7751 got_pathlist_free(&paths
, GOT_PATHLIST_FREE_PATH
);
7752 free(tog_base_commit
.id
);
7753 free(worktree_path
);
7761 const struct got_error
*close_err
= got_repo_close(repo
);
7766 got_worktree_close(worktree
);
7768 const struct got_error
*pack_err
=
7769 got_repo_pack_fds_close(pack_fds
);
7782 "usage: %s blame [-c commit] [-r repository-path] path\n",
7787 struct tog_blame_line
{
7789 struct got_object_id
*id
;
7792 static const struct got_error
*
7793 draw_blame(struct tog_view
*view
)
7795 struct tog_blame_view_state
*s
= &view
->state
.blame
;
7796 struct tog_blame
*blame
= &s
->blame
;
7797 regmatch_t
*regmatch
= &view
->regmatch
;
7798 const struct got_error
*err
;
7799 int lineno
= 0, nprinted
= 0;
7801 size_t linesize
= 0;
7805 struct tog_blame_line
*blame_line
;
7806 struct got_object_id
*prev_id
= NULL
;
7808 struct tog_color
*tc
;
7810 err
= got_object_id_str(&id_str
, &s
->blamed_commit
->id
);
7815 werase(view
->window
);
7817 if (asprintf(&line
, "commit %s", id_str
) == -1) {
7818 err
= got_error_from_errno("asprintf");
7823 err
= format_line(&wline
, &width
, NULL
, line
, 0, view
->ncols
, 0, 0);
7828 if (view_needs_focus_indication(view
))
7829 wstandout(view
->window
);
7830 tc
= get_color(&s
->colors
, TOG_COLOR_COMMIT
);
7832 wattr_on(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
7833 waddwstr(view
->window
, wline
);
7834 while (width
++ < view
->ncols
)
7835 waddch(view
->window
, ' ');
7837 wattr_off(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
7838 if (view_needs_focus_indication(view
))
7839 wstandend(view
->window
);
7843 if (view
->gline
> blame
->nlines
)
7844 view
->gline
= blame
->nlines
;
7846 if (tog_io
.wait_for_ui
) {
7847 struct tog_blame_thread_args
*bta
= &s
->blame
.thread_args
;
7850 rc
= pthread_cond_wait(&bta
->blame_complete
, &tog_mutex
);
7852 return got_error_set_errno(rc
, "pthread_cond_wait");
7853 tog_io
.wait_for_ui
= 0;
7856 if (asprintf(&line
, "[%d/%d] %s%s", view
->gline
? view
->gline
:
7857 s
->first_displayed_line
- 1 + s
->selected_line
, blame
->nlines
,
7858 s
->blame_complete
? "" : "annotating... ", s
->path
) == -1) {
7860 return got_error_from_errno("asprintf");
7863 err
= format_line(&wline
, &width
, NULL
, line
, 0, view
->ncols
, 0, 0);
7868 waddwstr(view
->window
, wline
);
7871 if (width
< view
->ncols
- 1)
7872 waddch(view
->window
, '\n');
7876 while (nprinted
< view
->nlines
- 2) {
7877 linelen
= getline(&line
, &linesize
, blame
->f
);
7878 if (linelen
== -1) {
7879 if (feof(blame
->f
)) {
7884 return got_ferror(blame
->f
, GOT_ERR_IO
);
7886 if (++lineno
< s
->first_displayed_line
)
7888 if (view
->gline
&& !gotoline(view
, &lineno
, &nprinted
))
7891 /* Set view->maxx based on full line length. */
7892 err
= format_line(&wline
, &width
, NULL
, line
, 0, INT_MAX
, 9, 1);
7899 view
->maxx
= MAX(view
->maxx
, width
);
7901 if (nprinted
== s
->selected_line
- 1)
7902 wstandout(view
->window
);
7904 if (blame
->nlines
> 0) {
7905 blame_line
= &blame
->lines
[lineno
- 1];
7906 if (blame_line
->annotated
&& prev_id
&&
7907 got_object_id_cmp(prev_id
, blame_line
->id
) == 0 &&
7908 !(nprinted
== s
->selected_line
- 1)) {
7909 waddstr(view
->window
, " ");
7910 } else if (blame_line
->annotated
) {
7912 err
= got_object_id_str(&id_str
,
7918 tc
= get_color(&s
->colors
, TOG_COLOR_COMMIT
);
7920 wattr_on(view
->window
,
7921 COLOR_PAIR(tc
->colorpair
), NULL
);
7922 wprintw(view
->window
, "%.8s", id_str
);
7924 wattr_off(view
->window
,
7925 COLOR_PAIR(tc
->colorpair
), NULL
);
7927 prev_id
= blame_line
->id
;
7929 waddstr(view
->window
, "........");
7933 waddstr(view
->window
, "........");
7937 if (nprinted
== s
->selected_line
- 1)
7938 wstandend(view
->window
);
7939 waddstr(view
->window
, " ");
7941 if (view
->ncols
<= 9) {
7943 } else if (s
->first_displayed_line
+ nprinted
==
7945 regmatch
->rm_so
>= 0 && regmatch
->rm_so
< regmatch
->rm_eo
) {
7946 err
= add_matched_line(&width
, line
, view
->ncols
- 9, 9,
7947 view
->window
, view
->x
, regmatch
);
7955 err
= format_line(&wline
, &width
, &skip
, line
,
7956 view
->x
, view
->ncols
- 9, 9, 1);
7961 waddwstr(view
->window
, &wline
[skip
]);
7967 if (width
<= view
->ncols
- 1)
7968 waddch(view
->window
, '\n');
7969 if (++nprinted
== 1)
7970 s
->first_displayed_line
= lineno
;
7973 s
->last_displayed_line
= lineno
;
7980 static const struct got_error
*
7981 blame_cb(void *arg
, int nlines
, int lineno
,
7982 struct got_commit_object
*commit
, struct got_object_id
*id
)
7984 const struct got_error
*err
= NULL
;
7985 struct tog_blame_cb_args
*a
= arg
;
7986 struct tog_blame_line
*line
;
7989 if (nlines
!= a
->nlines
||
7990 (lineno
!= -1 && lineno
< 1) || lineno
> a
->nlines
)
7991 return got_error(GOT_ERR_RANGE
);
7993 errcode
= pthread_mutex_lock(&tog_mutex
);
7995 return got_error_set_errno(errcode
, "pthread_mutex_lock");
7997 if (*a
->quit
) { /* user has quit the blame view */
7998 err
= got_error(GOT_ERR_ITER_COMPLETED
);
8003 goto done
; /* no change in this commit */
8005 line
= &a
->lines
[lineno
- 1];
8006 if (line
->annotated
)
8009 line
->id
= got_object_id_dup(id
);
8010 if (line
->id
== NULL
) {
8011 err
= got_error_from_errno("got_object_id_dup");
8014 line
->annotated
= 1;
8016 errcode
= pthread_mutex_unlock(&tog_mutex
);
8018 err
= got_error_set_errno(errcode
, "pthread_mutex_unlock");
8023 blame_thread(void *arg
)
8025 const struct got_error
*err
, *close_err
;
8026 struct tog_blame_thread_args
*ta
= arg
;
8027 struct tog_blame_cb_args
*a
= ta
->cb_args
;
8028 int errcode
, fd1
= -1, fd2
= -1;
8029 FILE *f1
= NULL
, *f2
= NULL
;
8031 fd1
= got_opentempfd();
8033 return (void *)got_error_from_errno("got_opentempfd");
8035 fd2
= got_opentempfd();
8037 err
= got_error_from_errno("got_opentempfd");
8041 f1
= got_opentemp();
8043 err
= (void *)got_error_from_errno("got_opentemp");
8046 f2
= got_opentemp();
8048 err
= (void *)got_error_from_errno("got_opentemp");
8052 err
= block_signals_used_by_main_thread();
8056 err
= got_blame(ta
->path
, a
->commit_id
, ta
->repo
,
8057 tog_diff_algo
, blame_cb
, ta
->cb_args
,
8058 ta
->cancel_cb
, ta
->cancel_arg
, fd1
, fd2
, f1
, f2
);
8059 if (err
&& err
->code
== GOT_ERR_CANCELLED
)
8062 errcode
= pthread_mutex_lock(&tog_mutex
);
8064 err
= got_error_set_errno(errcode
, "pthread_mutex_lock");
8068 close_err
= got_repo_close(ta
->repo
);
8074 if (tog_io
.wait_for_ui
) {
8075 errcode
= pthread_cond_signal(&ta
->blame_complete
);
8076 if (errcode
&& err
== NULL
)
8077 err
= got_error_set_errno(errcode
,
8078 "pthread_cond_signal");
8081 errcode
= pthread_mutex_unlock(&tog_mutex
);
8082 if (errcode
&& err
== NULL
)
8083 err
= got_error_set_errno(errcode
, "pthread_mutex_unlock");
8086 if (fd1
!= -1 && close(fd1
) == -1 && err
== NULL
)
8087 err
= got_error_from_errno("close");
8088 if (fd2
!= -1 && close(fd2
) == -1 && err
== NULL
)
8089 err
= got_error_from_errno("close");
8090 if (f1
&& fclose(f1
) == EOF
&& err
== NULL
)
8091 err
= got_error_from_errno("fclose");
8092 if (f2
&& fclose(f2
) == EOF
&& err
== NULL
)
8093 err
= got_error_from_errno("fclose");
8098 static struct got_object_id
*
8099 get_selected_commit_id(struct tog_blame_line
*lines
, int nlines
,
8100 int first_displayed_line
, int selected_line
)
8102 struct tog_blame_line
*line
;
8107 line
= &lines
[first_displayed_line
- 1 + selected_line
- 1];
8108 if (!line
->annotated
)
8114 static struct got_object_id
*
8115 get_annotation_for_line(struct tog_blame_line
*lines
, int nlines
,
8118 struct tog_blame_line
*line
;
8120 if (nlines
<= 0 || lineno
>= nlines
)
8123 line
= &lines
[lineno
- 1];
8124 if (!line
->annotated
)
8130 static const struct got_error
*
8131 stop_blame(struct tog_blame
*blame
)
8133 const struct got_error
*err
= NULL
;
8136 if (blame
->thread
) {
8138 errcode
= pthread_mutex_unlock(&tog_mutex
);
8140 return got_error_set_errno(errcode
,
8141 "pthread_mutex_unlock");
8142 errcode
= pthread_join(blame
->thread
, (void **)&err
);
8144 return got_error_set_errno(errcode
, "pthread_join");
8145 errcode
= pthread_mutex_lock(&tog_mutex
);
8147 return got_error_set_errno(errcode
,
8148 "pthread_mutex_lock");
8149 if (err
&& err
->code
== GOT_ERR_ITER_COMPLETED
)
8151 blame
->thread
= 0; //NULL;
8153 if (blame
->thread_args
.repo
) {
8154 const struct got_error
*close_err
;
8155 close_err
= got_repo_close(blame
->thread_args
.repo
);
8158 blame
->thread_args
.repo
= NULL
;
8161 if (fclose(blame
->f
) == EOF
&& err
== NULL
)
8162 err
= got_error_from_errno("fclose");
8166 for (i
= 0; i
< blame
->nlines
; i
++)
8167 free(blame
->lines
[i
].id
);
8169 blame
->lines
= NULL
;
8171 free(blame
->cb_args
.commit_id
);
8172 blame
->cb_args
.commit_id
= NULL
;
8173 if (blame
->pack_fds
) {
8174 const struct got_error
*pack_err
=
8175 got_repo_pack_fds_close(blame
->pack_fds
);
8178 blame
->pack_fds
= NULL
;
8180 free(blame
->line_offsets
);
8181 blame
->line_offsets
= NULL
;
8185 static const struct got_error
*
8186 cancel_blame_view(void *arg
)
8188 const struct got_error
*err
= NULL
;
8192 errcode
= pthread_mutex_lock(&tog_mutex
);
8194 return got_error_set_errno(errcode
, "pthread_mutex_lock");
8197 err
= got_error(GOT_ERR_CANCELLED
);
8199 errcode
= pthread_mutex_unlock(&tog_mutex
);
8201 return got_error_set_errno(errcode
, "pthread_mutex_unlock");
8206 static const struct got_error
*
8207 run_blame(struct tog_view
*view
)
8209 struct tog_blame_view_state
*s
= &view
->state
.blame
;
8210 struct tog_blame
*blame
= &s
->blame
;
8211 const struct got_error
*err
= NULL
;
8212 struct got_commit_object
*commit
= NULL
;
8213 struct got_blob_object
*blob
= NULL
;
8214 struct got_repository
*thread_repo
= NULL
;
8215 struct got_object_id
*obj_id
= NULL
;
8216 int obj_type
, fd
= -1;
8217 int *pack_fds
= NULL
;
8219 err
= got_object_open_as_commit(&commit
, s
->repo
,
8220 &s
->blamed_commit
->id
);
8224 fd
= got_opentempfd();
8226 err
= got_error_from_errno("got_opentempfd");
8230 err
= got_object_id_by_path(&obj_id
, s
->repo
, commit
, s
->path
);
8234 err
= got_object_get_type(&obj_type
, s
->repo
, obj_id
);
8238 if (obj_type
!= GOT_OBJ_TYPE_BLOB
) {
8239 err
= got_error(GOT_ERR_OBJ_TYPE
);
8243 err
= got_object_open_as_blob(&blob
, s
->repo
, obj_id
, 8192, fd
);
8246 blame
->f
= got_opentemp();
8247 if (blame
->f
== NULL
) {
8248 err
= got_error_from_errno("got_opentemp");
8251 err
= got_object_blob_dump_to_file(&blame
->filesize
, &blame
->nlines
,
8252 &blame
->line_offsets
, blame
->f
, blob
);
8255 if (blame
->nlines
== 0) {
8256 s
->blame_complete
= 1;
8260 /* Don't include \n at EOF in the blame line count. */
8261 if (blame
->line_offsets
[blame
->nlines
- 1] == blame
->filesize
)
8264 blame
->lines
= calloc(blame
->nlines
, sizeof(*blame
->lines
));
8265 if (blame
->lines
== NULL
) {
8266 err
= got_error_from_errno("calloc");
8270 err
= got_repo_pack_fds_open(&pack_fds
);
8273 err
= got_repo_open(&thread_repo
, got_repo_get_path(s
->repo
), NULL
,
8278 blame
->pack_fds
= pack_fds
;
8279 blame
->cb_args
.view
= view
;
8280 blame
->cb_args
.lines
= blame
->lines
;
8281 blame
->cb_args
.nlines
= blame
->nlines
;
8282 blame
->cb_args
.commit_id
= got_object_id_dup(&s
->blamed_commit
->id
);
8283 if (blame
->cb_args
.commit_id
== NULL
) {
8284 err
= got_error_from_errno("got_object_id_dup");
8287 blame
->cb_args
.quit
= &s
->done
;
8289 blame
->thread_args
.path
= s
->path
;
8290 blame
->thread_args
.repo
= thread_repo
;
8291 blame
->thread_args
.cb_args
= &blame
->cb_args
;
8292 blame
->thread_args
.complete
= &s
->blame_complete
;
8293 blame
->thread_args
.cancel_cb
= cancel_blame_view
;
8294 blame
->thread_args
.cancel_arg
= &s
->done
;
8295 s
->blame_complete
= 0;
8297 if (s
->first_displayed_line
+ view
->nlines
- 1 > blame
->nlines
) {
8298 s
->first_displayed_line
= 1;
8299 s
->last_displayed_line
= view
->nlines
;
8300 s
->selected_line
= 1;
8302 s
->matched_line
= 0;
8306 got_object_commit_close(commit
);
8307 if (fd
!= -1 && close(fd
) == -1 && err
== NULL
)
8308 err
= got_error_from_errno("close");
8310 got_object_blob_close(blob
);
8317 static const struct got_error
*
8318 open_blame_view(struct tog_view
*view
, char *path
,
8319 struct got_object_id
*commit_id
, struct got_repository
*repo
)
8321 const struct got_error
*err
= NULL
;
8322 struct tog_blame_view_state
*s
= &view
->state
.blame
;
8324 STAILQ_INIT(&s
->blamed_commits
);
8326 s
->path
= strdup(path
);
8327 if (s
->path
== NULL
)
8328 return got_error_from_errno("strdup");
8330 err
= got_object_qid_alloc(&s
->blamed_commit
, commit_id
);
8336 STAILQ_INSERT_HEAD(&s
->blamed_commits
, s
->blamed_commit
, entry
);
8337 s
->first_displayed_line
= 1;
8338 s
->last_displayed_line
= view
->nlines
;
8339 s
->selected_line
= 1;
8340 s
->blame_complete
= 0;
8342 s
->commit_id
= commit_id
;
8343 memset(&s
->blame
, 0, sizeof(s
->blame
));
8345 STAILQ_INIT(&s
->colors
);
8346 if (has_colors() && getenv("TOG_COLORS") != NULL
) {
8347 err
= add_color(&s
->colors
, "^", TOG_COLOR_COMMIT
,
8348 get_color_value("TOG_COLOR_COMMIT"));
8353 view
->show
= show_blame_view
;
8354 view
->input
= input_blame_view
;
8355 view
->reset
= reset_blame_view
;
8356 view
->close
= close_blame_view
;
8357 view
->search_start
= search_start_blame_view
;
8358 view
->search_setup
= search_setup_blame_view
;
8359 view
->search_next
= search_next_view_match
;
8361 if (using_mock_io
) {
8362 struct tog_blame_thread_args
*bta
= &s
->blame
.thread_args
;
8365 rc
= pthread_cond_init(&bta
->blame_complete
, NULL
);
8367 return got_error_set_errno(rc
, "pthread_cond_init");
8370 return run_blame(view
);
8373 static const struct got_error
*
8374 close_blame_view(struct tog_view
*view
)
8376 const struct got_error
*err
= NULL
;
8377 struct tog_blame_view_state
*s
= &view
->state
.blame
;
8379 if (s
->blame
.thread
)
8380 err
= stop_blame(&s
->blame
);
8382 while (!STAILQ_EMPTY(&s
->blamed_commits
)) {
8383 struct got_object_qid
*blamed_commit
;
8384 blamed_commit
= STAILQ_FIRST(&s
->blamed_commits
);
8385 STAILQ_REMOVE_HEAD(&s
->blamed_commits
, entry
);
8386 got_object_qid_free(blamed_commit
);
8389 if (using_mock_io
) {
8390 struct tog_blame_thread_args
*bta
= &s
->blame
.thread_args
;
8393 rc
= pthread_cond_destroy(&bta
->blame_complete
);
8394 if (rc
&& err
== NULL
)
8395 err
= got_error_set_errno(rc
, "pthread_cond_destroy");
8399 free_colors(&s
->colors
);
8403 static const struct got_error
*
8404 search_start_blame_view(struct tog_view
*view
)
8406 struct tog_blame_view_state
*s
= &view
->state
.blame
;
8408 s
->matched_line
= 0;
8413 search_setup_blame_view(struct tog_view
*view
, FILE **f
, off_t
**line_offsets
,
8414 size_t *nlines
, int **first
, int **last
, int **match
, int **selected
)
8416 struct tog_blame_view_state
*s
= &view
->state
.blame
;
8419 *nlines
= s
->blame
.nlines
;
8420 *line_offsets
= s
->blame
.line_offsets
;
8421 *match
= &s
->matched_line
;
8422 *first
= &s
->first_displayed_line
;
8423 *last
= &s
->last_displayed_line
;
8424 *selected
= &s
->selected_line
;
8427 static const struct got_error
*
8428 show_blame_view(struct tog_view
*view
)
8430 const struct got_error
*err
= NULL
;
8431 struct tog_blame_view_state
*s
= &view
->state
.blame
;
8434 if (s
->blame
.thread
== 0 && !s
->blame_complete
) {
8435 errcode
= pthread_create(&s
->blame
.thread
, NULL
, blame_thread
,
8436 &s
->blame
.thread_args
);
8438 return got_error_set_errno(errcode
, "pthread_create");
8441 halfdelay(1); /* fast refresh while annotating */
8444 if (s
->blame_complete
&& !using_mock_io
)
8445 halfdelay(10); /* disable fast refresh */
8447 err
= draw_blame(view
);
8453 static const struct got_error
*
8454 log_annotated_line(struct tog_view
**new_view
, int begin_y
, int begin_x
,
8455 struct got_repository
*repo
, struct got_object_id
*id
)
8457 struct tog_view
*log_view
;
8458 const struct got_error
*err
= NULL
;
8462 log_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_LOG
);
8463 if (log_view
== NULL
)
8464 return got_error_from_errno("view_open");
8466 err
= open_log_view(log_view
, id
, repo
, GOT_REF_HEAD
, "", 0, NULL
);
8468 view_close(log_view
);
8470 *new_view
= log_view
;
8475 static const struct got_error
*
8476 input_blame_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
8478 const struct got_error
*err
= NULL
, *thread_err
= NULL
;
8479 struct tog_view
*diff_view
;
8480 struct tog_blame_view_state
*s
= &view
->state
.blame
;
8481 int eos
, nscroll
, begin_y
= 0, begin_x
= 0;
8483 eos
= nscroll
= view
->nlines
- 2;
8484 if (view_is_hsplit_top(view
))
8494 horizontal_scroll_input(view
, ch
);
8501 s
->selected_line
= 1;
8502 s
->first_displayed_line
= 1;
8507 if (s
->blame
.nlines
< eos
) {
8508 s
->selected_line
= s
->blame
.nlines
;
8509 s
->first_displayed_line
= 1;
8511 s
->selected_line
= eos
;
8512 s
->first_displayed_line
= s
->blame
.nlines
- (eos
- 1);
8519 if (s
->selected_line
> 1)
8521 else if (s
->selected_line
== 1 &&
8522 s
->first_displayed_line
> 1)
8523 s
->first_displayed_line
--;
8534 if (s
->first_displayed_line
== 1) {
8535 if (view
->count
> 1)
8537 s
->selected_line
= MAX(1, s
->selected_line
- nscroll
);
8541 if (s
->first_displayed_line
> nscroll
)
8542 s
->first_displayed_line
-= nscroll
;
8544 s
->first_displayed_line
= 1;
8549 if (s
->selected_line
< eos
&& s
->first_displayed_line
+
8550 s
->selected_line
<= s
->blame
.nlines
)
8552 else if (s
->first_displayed_line
< s
->blame
.nlines
- (eos
- 1))
8553 s
->first_displayed_line
++;
8559 struct got_object_id
*id
= NULL
;
8562 id
= get_selected_commit_id(s
->blame
.lines
, s
->blame
.nlines
,
8563 s
->first_displayed_line
, s
->selected_line
);
8567 struct got_commit_object
*commit
, *pcommit
;
8568 struct got_object_qid
*pid
;
8569 struct got_object_id
*blob_id
= NULL
;
8571 err
= got_object_open_as_commit(&commit
,
8576 got_object_commit_get_parent_ids(commit
));
8578 got_object_commit_close(commit
);
8581 /* Check if path history ends here. */
8582 err
= got_object_open_as_commit(&pcommit
,
8586 err
= got_object_id_by_path(&blob_id
, s
->repo
,
8588 got_object_commit_close(pcommit
);
8590 if (err
->code
== GOT_ERR_NO_TREE_ENTRY
)
8592 got_object_commit_close(commit
);
8595 err
= got_object_get_type(&obj_type
, s
->repo
,
8598 /* Can't blame non-blob type objects. */
8599 if (obj_type
!= GOT_OBJ_TYPE_BLOB
) {
8600 got_object_commit_close(commit
);
8603 err
= got_object_qid_alloc(&s
->blamed_commit
,
8605 got_object_commit_close(commit
);
8607 if (got_object_id_cmp(id
,
8608 &s
->blamed_commit
->id
) == 0)
8610 err
= got_object_qid_alloc(&s
->blamed_commit
,
8616 thread_err
= stop_blame(&s
->blame
);
8620 STAILQ_INSERT_HEAD(&s
->blamed_commits
,
8621 s
->blamed_commit
, entry
);
8622 err
= run_blame(view
);
8628 struct got_object_qid
*first
;
8631 first
= STAILQ_FIRST(&s
->blamed_commits
);
8632 if (!got_object_id_cmp(&first
->id
, s
->commit_id
))
8635 thread_err
= stop_blame(&s
->blame
);
8639 STAILQ_REMOVE_HEAD(&s
->blamed_commits
, entry
);
8640 got_object_qid_free(s
->blamed_commit
);
8642 STAILQ_FIRST(&s
->blamed_commits
);
8643 err
= run_blame(view
);
8650 s
->id_to_log
= get_selected_commit_id(s
->blame
.lines
,
8651 s
->blame
.nlines
, s
->first_displayed_line
, s
->selected_line
);
8653 err
= view_request_new(new_view
, view
, TOG_VIEW_LOG
);
8657 struct got_object_id
*id
= NULL
;
8658 struct got_object_qid
*pid
;
8659 struct got_commit_object
*commit
= NULL
;
8662 id
= get_selected_commit_id(s
->blame
.lines
, s
->blame
.nlines
,
8663 s
->first_displayed_line
, s
->selected_line
);
8666 err
= got_object_open_as_commit(&commit
, s
->repo
, id
);
8669 pid
= STAILQ_FIRST(got_object_commit_get_parent_ids(commit
));
8671 /* traversed from diff view, release diff resources */
8672 err
= close_diff_view(*new_view
);
8675 diff_view
= *new_view
;
8677 if (view_is_parent_view(view
))
8678 view_get_split(view
, &begin_y
, &begin_x
);
8680 diff_view
= view_open(0, 0, begin_y
, begin_x
,
8682 if (diff_view
== NULL
) {
8683 got_object_commit_close(commit
);
8684 err
= got_error_from_errno("view_open");
8688 err
= open_diff_view(diff_view
, pid
? &pid
->id
: NULL
,
8689 id
, NULL
, NULL
, 3, 0, 0, 0, 0, NULL
, view
, s
->repo
, NULL
);
8690 got_object_commit_close(commit
);
8693 s
->last_diffed_line
= s
->first_displayed_line
- 1 +
8696 break; /* still open from active diff view */
8697 if (view_is_parent_view(view
) &&
8698 view
->mode
== TOG_VIEW_SPLIT_HRZN
) {
8699 err
= view_init_hsplit(view
, begin_y
);
8705 diff_view
->focussed
= 1;
8706 diff_view
->mode
= view
->mode
;
8707 diff_view
->nlines
= view
->lines
- begin_y
;
8708 if (view_is_parent_view(view
)) {
8709 view_transfer_size(diff_view
, view
);
8710 err
= view_close_child(view
);
8713 err
= view_set_child(view
, diff_view
);
8716 view
->focus_child
= 1;
8718 *new_view
= diff_view
;
8731 if (s
->last_displayed_line
>= s
->blame
.nlines
&&
8732 s
->selected_line
>= MIN(s
->blame
.nlines
,
8733 view
->nlines
- 2)) {
8737 if (s
->last_displayed_line
>= s
->blame
.nlines
&&
8738 s
->selected_line
< view
->nlines
- 2) {
8740 MIN(nscroll
, s
->last_displayed_line
-
8741 s
->first_displayed_line
- s
->selected_line
+ 1);
8743 if (s
->last_displayed_line
+ nscroll
<= s
->blame
.nlines
)
8744 s
->first_displayed_line
+= nscroll
;
8746 s
->first_displayed_line
=
8747 s
->blame
.nlines
- (view
->nlines
- 3);
8750 if (s
->selected_line
> view
->nlines
- 2) {
8751 s
->selected_line
= MIN(s
->blame
.nlines
,
8759 return thread_err
? thread_err
: err
;
8762 static const struct got_error
*
8763 reset_blame_view(struct tog_view
*view
)
8765 const struct got_error
*err
;
8766 struct tog_blame_view_state
*s
= &view
->state
.blame
;
8770 err
= stop_blame(&s
->blame
);
8774 return run_blame(view
);
8777 static const struct got_error
*
8778 cmd_blame(int argc
, char *argv
[])
8780 const struct got_error
*error
;
8781 struct got_repository
*repo
= NULL
;
8782 struct got_worktree
*worktree
= NULL
;
8783 char *cwd
= NULL
, *repo_path
= NULL
, *in_repo_path
= NULL
;
8784 char *link_target
= NULL
;
8785 struct got_object_id
*commit_id
= NULL
;
8786 struct got_commit_object
*commit
= NULL
;
8787 char *keyword_idstr
= NULL
, *commit_id_str
= NULL
;
8789 struct tog_view
*view
= NULL
;
8790 int *pack_fds
= NULL
;
8792 while ((ch
= getopt(argc
, argv
, "c:r:")) != -1) {
8795 commit_id_str
= optarg
;
8798 repo_path
= realpath(optarg
, NULL
);
8799 if (repo_path
== NULL
)
8800 return got_error_from_errno2("realpath",
8815 error
= got_repo_pack_fds_open(&pack_fds
);
8819 if (repo_path
== NULL
) {
8820 cwd
= getcwd(NULL
, 0);
8822 return got_error_from_errno("getcwd");
8823 error
= got_worktree_open(&worktree
, cwd
, NULL
);
8824 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
8828 strdup(got_worktree_get_repo_path(worktree
));
8830 repo_path
= strdup(cwd
);
8831 if (repo_path
== NULL
) {
8832 error
= got_error_from_errno("strdup");
8837 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
8841 error
= get_in_repo_path_from_argv0(&in_repo_path
, argc
, argv
, repo
,
8848 error
= apply_unveil(got_repo_get_path(repo
), NULL
);
8852 error
= tog_load_refs(repo
, 0);
8856 if (commit_id_str
== NULL
) {
8857 struct got_reference
*head_ref
;
8858 error
= got_ref_open(&head_ref
, repo
, worktree
?
8859 got_worktree_get_head_ref_name(worktree
) : GOT_REF_HEAD
, 0);
8862 error
= got_ref_resolve(&commit_id
, repo
, head_ref
);
8863 got_ref_close(head_ref
);
8865 error
= got_keyword_to_idstr(&keyword_idstr
, commit_id_str
,
8869 if (keyword_idstr
!= NULL
)
8870 commit_id_str
= keyword_idstr
;
8872 error
= got_repo_match_object_id(&commit_id
, NULL
,
8873 commit_id_str
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
8878 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
8882 error
= got_object_resolve_symlinks(&link_target
, in_repo_path
,
8887 view
= view_open(0, 0, 0, 0, TOG_VIEW_BLAME
);
8889 error
= got_error_from_errno("view_open");
8892 error
= open_blame_view(view
, link_target
? link_target
: in_repo_path
,
8894 if (error
!= NULL
) {
8895 if (view
->close
== NULL
)
8896 close_blame_view(view
);
8902 error
= set_tog_base_commit(repo
, worktree
);
8906 /* Release work tree lock. */
8907 got_worktree_close(worktree
);
8911 error
= view_loop(view
);
8914 free(tog_base_commit
.id
);
8920 free(keyword_idstr
);
8922 got_object_commit_close(commit
);
8924 got_worktree_close(worktree
);
8926 const struct got_error
*close_err
= got_repo_close(repo
);
8931 const struct got_error
*pack_err
=
8932 got_repo_pack_fds_close(pack_fds
);
8940 static const struct got_error
*
8941 draw_tree_entries(struct tog_view
*view
, const char *parent_path
)
8943 struct tog_tree_view_state
*s
= &view
->state
.tree
;
8944 const struct got_error
*err
= NULL
;
8945 struct got_tree_entry
*te
;
8948 struct tog_color
*tc
;
8949 int width
, n
, nentries
, scrollx
, i
= 1;
8950 int limit
= view
->nlines
;
8953 if (view_is_hsplit_top(view
))
8954 --limit
; /* border */
8956 werase(view
->window
);
8961 err
= format_line(&wline
, &width
, NULL
, s
->tree_label
, 0, view
->ncols
,
8965 if (view_needs_focus_indication(view
))
8966 wstandout(view
->window
);
8967 tc
= get_color(&s
->colors
, TOG_COLOR_COMMIT
);
8969 wattr_on(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
8970 waddwstr(view
->window
, wline
);
8973 while (width
++ < view
->ncols
)
8974 waddch(view
->window
, ' ');
8976 wattr_off(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
8977 if (view_needs_focus_indication(view
))
8978 wstandend(view
->window
);
8983 if (s
->first_displayed_entry
) {
8984 i
+= got_tree_entry_get_index(s
->first_displayed_entry
);
8985 if (s
->tree
!= s
->root
)
8986 ++i
; /* account for ".." entry */
8988 nentries
= got_object_tree_get_nentries(s
->tree
);
8989 if (asprintf(&index
, "[%d/%d] %s",
8990 i
, nentries
+ (s
->tree
== s
->root
? 0 : 1), parent_path
) == -1)
8991 return got_error_from_errno("asprintf");
8992 err
= format_line(&wline
, &width
, NULL
, index
, 0, view
->ncols
, 0, 0);
8996 waddwstr(view
->window
, wline
);
8999 if (width
< view
->ncols
- 1)
9000 waddch(view
->window
, '\n');
9003 waddch(view
->window
, '\n');
9007 if (s
->first_displayed_entry
== NULL
) {
9008 te
= got_object_tree_get_first_entry(s
->tree
);
9009 if (s
->selected
== 0) {
9011 wstandout(view
->window
);
9012 s
->selected_entry
= NULL
;
9014 waddstr(view
->window
, " ..\n"); /* parent directory */
9015 if (s
->selected
== 0 && view
->focussed
)
9016 wstandend(view
->window
);
9023 te
= s
->first_displayed_entry
;
9027 for (i
= got_tree_entry_get_index(te
); i
< nentries
; i
++) {
9028 char *line
= NULL
, *id_str
= NULL
, *link_target
= NULL
;
9029 const char *modestr
= "";
9032 te
= got_object_tree_get_entry(s
->tree
, i
);
9033 mode
= got_tree_entry_get_mode(te
);
9036 err
= got_object_id_str(&id_str
,
9037 got_tree_entry_get_id(te
));
9039 return got_error_from_errno(
9040 "got_object_id_str");
9042 if (got_object_tree_entry_is_submodule(te
))
9044 else if (S_ISLNK(mode
)) {
9047 err
= got_tree_entry_get_symlink_target(&link_target
,
9053 for (i
= 0; link_target
[i
] != '\0'; i
++) {
9054 if (!isprint((unsigned char)link_target
[i
]))
9055 link_target
[i
] = '?';
9059 else if (S_ISDIR(mode
))
9061 else if (mode
& S_IXUSR
)
9063 if (asprintf(&line
, "%s %s%s%s%s", id_str
? id_str
: "",
9064 got_tree_entry_get_name(te
), modestr
,
9065 link_target
? " -> ": "",
9066 link_target
? link_target
: "") == -1) {
9069 return got_error_from_errno("asprintf");
9074 /* use full line width to determine view->maxx */
9075 err
= format_line(&wline
, &width
, NULL
, line
, 0, INT_MAX
, 0, 0);
9080 view
->maxx
= MAX(view
->maxx
, width
);
9084 err
= format_line(&wline
, &width
, &scrollx
, line
, view
->x
,
9090 if (n
== s
->selected
) {
9092 wstandout(view
->window
);
9093 s
->selected_entry
= te
;
9095 tc
= match_color(&s
->colors
, line
);
9097 wattr_on(view
->window
,
9098 COLOR_PAIR(tc
->colorpair
), NULL
);
9099 waddwstr(view
->window
, &wline
[scrollx
]);
9101 wattr_off(view
->window
,
9102 COLOR_PAIR(tc
->colorpair
), NULL
);
9103 if (width
< view
->ncols
)
9104 waddch(view
->window
, '\n');
9105 if (n
== s
->selected
&& view
->focussed
)
9106 wstandend(view
->window
);
9112 s
->last_displayed_entry
= te
;
9121 tree_scroll_up(struct tog_tree_view_state
*s
, int maxscroll
)
9123 struct got_tree_entry
*te
;
9124 int isroot
= s
->tree
== s
->root
;
9127 if (s
->first_displayed_entry
== NULL
)
9130 te
= got_tree_entry_get_prev(s
->tree
, s
->first_displayed_entry
);
9131 while (i
++ < maxscroll
) {
9134 s
->first_displayed_entry
= NULL
;
9137 s
->first_displayed_entry
= te
;
9138 te
= got_tree_entry_get_prev(s
->tree
, te
);
9142 static const struct got_error
*
9143 tree_scroll_down(struct tog_view
*view
, int maxscroll
)
9145 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9146 struct got_tree_entry
*next
, *last
;
9149 if (s
->first_displayed_entry
)
9150 next
= got_tree_entry_get_next(s
->tree
,
9151 s
->first_displayed_entry
);
9153 next
= got_object_tree_get_first_entry(s
->tree
);
9155 last
= s
->last_displayed_entry
;
9156 while (next
&& n
++ < maxscroll
) {
9158 s
->last_displayed_entry
= last
;
9159 last
= got_tree_entry_get_next(s
->tree
, last
);
9161 if (last
|| (view
->mode
== TOG_VIEW_SPLIT_HRZN
&& next
)) {
9162 s
->first_displayed_entry
= next
;
9163 next
= got_tree_entry_get_next(s
->tree
, next
);
9170 static const struct got_error
*
9171 tree_entry_path(char **path
, struct tog_parent_trees
*parents
,
9172 struct got_tree_entry
*te
)
9174 const struct got_error
*err
= NULL
;
9175 struct tog_parent_tree
*pt
;
9176 size_t len
= 2; /* for leading slash and NUL */
9178 TAILQ_FOREACH(pt
, parents
, entry
)
9179 len
+= strlen(got_tree_entry_get_name(pt
->selected_entry
))
9182 len
+= strlen(got_tree_entry_get_name(te
));
9184 *path
= calloc(1, len
);
9186 return got_error_from_errno("calloc");
9189 pt
= TAILQ_LAST(parents
, tog_parent_trees
);
9191 const char *name
= got_tree_entry_get_name(pt
->selected_entry
);
9192 if (strlcat(*path
, name
, len
) >= len
) {
9193 err
= got_error(GOT_ERR_NO_SPACE
);
9196 if (strlcat(*path
, "/", len
) >= len
) {
9197 err
= got_error(GOT_ERR_NO_SPACE
);
9200 pt
= TAILQ_PREV(pt
, tog_parent_trees
, entry
);
9203 if (strlcat(*path
, got_tree_entry_get_name(te
), len
) >= len
) {
9204 err
= got_error(GOT_ERR_NO_SPACE
);
9216 static const struct got_error
*
9217 blame_tree_entry(struct tog_view
**new_view
, int begin_y
, int begin_x
,
9218 struct got_tree_entry
*te
, struct tog_parent_trees
*parents
,
9219 struct got_object_id
*commit_id
, struct got_repository
*repo
)
9221 const struct got_error
*err
= NULL
;
9223 struct tog_view
*blame_view
;
9227 err
= tree_entry_path(&path
, parents
, te
);
9231 blame_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_BLAME
);
9232 if (blame_view
== NULL
) {
9233 err
= got_error_from_errno("view_open");
9237 err
= open_blame_view(blame_view
, path
, commit_id
, repo
);
9239 if (err
->code
== GOT_ERR_CANCELLED
)
9241 view_close(blame_view
);
9243 *new_view
= blame_view
;
9249 static const struct got_error
*
9250 log_selected_tree_entry(struct tog_view
**new_view
, int begin_y
, int begin_x
,
9251 struct tog_tree_view_state
*s
)
9253 struct tog_view
*log_view
;
9254 const struct got_error
*err
= NULL
;
9259 log_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_LOG
);
9260 if (log_view
== NULL
)
9261 return got_error_from_errno("view_open");
9263 err
= tree_entry_path(&path
, &s
->parents
, s
->selected_entry
);
9267 err
= open_log_view(log_view
, s
->commit_id
, s
->repo
, s
->head_ref_name
,
9270 view_close(log_view
);
9272 *new_view
= log_view
;
9277 static const struct got_error
*
9278 open_tree_view(struct tog_view
*view
, struct got_object_id
*commit_id
,
9279 const char *head_ref_name
, struct got_repository
*repo
)
9281 const struct got_error
*err
= NULL
;
9282 char *commit_id_str
= NULL
;
9283 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9284 struct got_commit_object
*commit
= NULL
;
9286 TAILQ_INIT(&s
->parents
);
9287 STAILQ_INIT(&s
->colors
);
9289 s
->commit_id
= got_object_id_dup(commit_id
);
9290 if (s
->commit_id
== NULL
) {
9291 err
= got_error_from_errno("got_object_id_dup");
9295 err
= got_object_open_as_commit(&commit
, repo
, commit_id
);
9300 * The root is opened here and will be closed when the view is closed.
9301 * Any visited subtrees and their path-wise parents are opened and
9304 err
= got_object_open_as_tree(&s
->root
, repo
,
9305 got_object_commit_get_tree_id(commit
));
9310 err
= got_object_id_str(&commit_id_str
, commit_id
);
9314 if (asprintf(&s
->tree_label
, "commit %s", commit_id_str
) == -1) {
9315 err
= got_error_from_errno("asprintf");
9319 s
->first_displayed_entry
= got_object_tree_get_entry(s
->tree
, 0);
9320 s
->selected_entry
= got_object_tree_get_entry(s
->tree
, 0);
9321 if (head_ref_name
) {
9322 s
->head_ref_name
= strdup(head_ref_name
);
9323 if (s
->head_ref_name
== NULL
) {
9324 err
= got_error_from_errno("strdup");
9330 if (has_colors() && getenv("TOG_COLORS") != NULL
) {
9331 err
= add_color(&s
->colors
, "\\$$",
9332 TOG_COLOR_TREE_SUBMODULE
,
9333 get_color_value("TOG_COLOR_TREE_SUBMODULE"));
9336 err
= add_color(&s
->colors
, "@$", TOG_COLOR_TREE_SYMLINK
,
9337 get_color_value("TOG_COLOR_TREE_SYMLINK"));
9340 err
= add_color(&s
->colors
, "/$",
9341 TOG_COLOR_TREE_DIRECTORY
,
9342 get_color_value("TOG_COLOR_TREE_DIRECTORY"));
9346 err
= add_color(&s
->colors
, "\\*$",
9347 TOG_COLOR_TREE_EXECUTABLE
,
9348 get_color_value("TOG_COLOR_TREE_EXECUTABLE"));
9352 err
= add_color(&s
->colors
, "^$", TOG_COLOR_COMMIT
,
9353 get_color_value("TOG_COLOR_COMMIT"));
9358 view
->show
= show_tree_view
;
9359 view
->input
= input_tree_view
;
9360 view
->close
= close_tree_view
;
9361 view
->search_start
= search_start_tree_view
;
9362 view
->search_next
= search_next_tree_view
;
9364 free(commit_id_str
);
9366 got_object_commit_close(commit
);
9368 if (view
->close
== NULL
)
9369 close_tree_view(view
);
9375 static const struct got_error
*
9376 close_tree_view(struct tog_view
*view
)
9378 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9380 free_colors(&s
->colors
);
9381 free(s
->tree_label
);
9382 s
->tree_label
= NULL
;
9384 s
->commit_id
= NULL
;
9385 free(s
->head_ref_name
);
9386 s
->head_ref_name
= NULL
;
9387 while (!TAILQ_EMPTY(&s
->parents
)) {
9388 struct tog_parent_tree
*parent
;
9389 parent
= TAILQ_FIRST(&s
->parents
);
9390 TAILQ_REMOVE(&s
->parents
, parent
, entry
);
9391 if (parent
->tree
!= s
->root
)
9392 got_object_tree_close(parent
->tree
);
9396 if (s
->tree
!= NULL
&& s
->tree
!= s
->root
)
9397 got_object_tree_close(s
->tree
);
9399 got_object_tree_close(s
->root
);
9403 static const struct got_error
*
9404 search_start_tree_view(struct tog_view
*view
)
9406 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9408 s
->matched_entry
= NULL
;
9413 match_tree_entry(struct got_tree_entry
*te
, regex_t
*regex
)
9415 regmatch_t regmatch
;
9417 return regexec(regex
, got_tree_entry_get_name(te
), 1, ®match
,
9421 static const struct got_error
*
9422 search_next_tree_view(struct tog_view
*view
)
9424 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9425 struct got_tree_entry
*te
= NULL
;
9427 if (!view
->searching
) {
9428 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
9432 if (s
->matched_entry
) {
9433 if (view
->searching
== TOG_SEARCH_FORWARD
) {
9434 if (s
->selected_entry
)
9435 te
= got_tree_entry_get_next(s
->tree
,
9438 te
= got_object_tree_get_first_entry(s
->tree
);
9440 if (s
->selected_entry
== NULL
)
9441 te
= got_object_tree_get_last_entry(s
->tree
);
9443 te
= got_tree_entry_get_prev(s
->tree
,
9447 if (s
->selected_entry
)
9448 te
= s
->selected_entry
;
9449 else if (view
->searching
== TOG_SEARCH_FORWARD
)
9450 te
= got_object_tree_get_first_entry(s
->tree
);
9452 te
= got_object_tree_get_last_entry(s
->tree
);
9457 if (s
->matched_entry
== NULL
) {
9458 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
9461 if (view
->searching
== TOG_SEARCH_FORWARD
)
9462 te
= got_object_tree_get_first_entry(s
->tree
);
9464 te
= got_object_tree_get_last_entry(s
->tree
);
9467 if (match_tree_entry(te
, &view
->regex
)) {
9468 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
9469 s
->matched_entry
= te
;
9473 if (view
->searching
== TOG_SEARCH_FORWARD
)
9474 te
= got_tree_entry_get_next(s
->tree
, te
);
9476 te
= got_tree_entry_get_prev(s
->tree
, te
);
9479 if (s
->matched_entry
) {
9480 s
->first_displayed_entry
= s
->matched_entry
;
9487 static const struct got_error
*
9488 show_tree_view(struct tog_view
*view
)
9490 const struct got_error
*err
= NULL
;
9491 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9494 err
= tree_entry_path(&parent_path
, &s
->parents
, NULL
);
9498 err
= draw_tree_entries(view
, parent_path
);
9505 static const struct got_error
*
9506 tree_goto_line(struct tog_view
*view
, int nlines
)
9508 const struct got_error
*err
= NULL
;
9509 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9510 struct got_tree_entry
**fte
, **lte
, **ste
;
9511 int g
, last
, first
= 1, i
= 1;
9512 int root
= s
->tree
== s
->root
;
9513 int off
= root
? 1 : 2;
9520 else if (g
> got_object_tree_get_nentries(s
->tree
))
9521 g
= got_object_tree_get_nentries(s
->tree
) + (root
? 0 : 1);
9523 fte
= &s
->first_displayed_entry
;
9524 lte
= &s
->last_displayed_entry
;
9525 ste
= &s
->selected_entry
;
9528 first
= got_tree_entry_get_index(*fte
);
9529 first
+= off
; /* account for ".." */
9531 last
= got_tree_entry_get_index(*lte
);
9534 if (g
>= first
&& g
<= last
&& g
- first
< nlines
) {
9535 s
->selected
= g
- first
;
9536 return NULL
; /* gline is on the current page */
9540 i
= got_tree_entry_get_index(*ste
);
9545 err
= tree_scroll_down(view
, g
- i
);
9548 if (got_tree_entry_get_index(*lte
) >=
9549 got_object_tree_get_nentries(s
->tree
) - 1 &&
9550 first
+ s
->selected
< g
&&
9551 s
->selected
< s
->ndisplayed
- 1) {
9552 first
= got_tree_entry_get_index(*fte
);
9554 s
->selected
= g
- first
;
9557 tree_scroll_up(s
, i
- g
);
9560 (*fte
== NULL
|| (root
&& !got_tree_entry_get_index(*fte
))))
9561 s
->selected
= g
- 1;
9566 static const struct got_error
*
9567 input_tree_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
9569 const struct got_error
*err
= NULL
;
9570 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9571 struct got_tree_entry
*te
;
9572 int n
, nscroll
= view
->nlines
- 3;
9575 return tree_goto_line(view
, nscroll
);
9584 horizontal_scroll_input(view
, ch
);
9587 s
->show_ids
= !s
->show_ids
;
9592 if (!s
->selected_entry
)
9594 err
= view_request_new(new_view
, view
, TOG_VIEW_LOG
);
9598 err
= view_request_new(new_view
, view
, TOG_VIEW_REF
);
9605 if (s
->tree
== s
->root
)
9606 s
->first_displayed_entry
=
9607 got_object_tree_get_first_entry(s
->tree
);
9609 s
->first_displayed_entry
= NULL
;
9614 int eos
= view
->nlines
- 3;
9616 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
)
9620 te
= got_object_tree_get_last_entry(s
->tree
);
9621 for (n
= 0; n
< eos
; n
++) {
9623 if (s
->tree
!= s
->root
) {
9624 s
->first_displayed_entry
= NULL
;
9629 s
->first_displayed_entry
= te
;
9630 te
= got_tree_entry_get_prev(s
->tree
, te
);
9633 s
->selected
= n
- 1;
9639 if (s
->selected
> 0) {
9643 tree_scroll_up(s
, 1);
9644 if (s
->selected_entry
== NULL
||
9645 (s
->tree
== s
->root
&& s
->selected_entry
==
9646 got_object_tree_get_first_entry(s
->tree
)))
9656 if (s
->tree
== s
->root
) {
9657 if (got_object_tree_get_first_entry(s
->tree
) ==
9658 s
->first_displayed_entry
)
9659 s
->selected
-= MIN(s
->selected
, nscroll
);
9661 if (s
->first_displayed_entry
== NULL
)
9662 s
->selected
-= MIN(s
->selected
, nscroll
);
9664 tree_scroll_up(s
, MAX(0, nscroll
));
9665 if (s
->selected_entry
== NULL
||
9666 (s
->tree
== s
->root
&& s
->selected_entry
==
9667 got_object_tree_get_first_entry(s
->tree
)))
9673 if (s
->selected
< s
->ndisplayed
- 1) {
9677 if (s
->last_displayed_entry
== NULL
||
9678 got_tree_entry_get_next(s
->tree
, s
->last_displayed_entry
)
9680 /* can't scroll any further */
9684 tree_scroll_down(view
, 1);
9694 if (s
->last_displayed_entry
== NULL
||
9695 got_tree_entry_get_next(s
->tree
, s
->last_displayed_entry
)
9697 /* can't scroll any further; move cursor down */
9698 if (s
->selected
< s
->ndisplayed
- 1)
9699 s
->selected
+= MIN(nscroll
,
9700 s
->ndisplayed
- s
->selected
- 1);
9705 tree_scroll_down(view
, nscroll
);
9710 if (s
->selected_entry
== NULL
|| ch
== KEY_BACKSPACE
) {
9711 struct tog_parent_tree
*parent
;
9712 /* user selected '..' */
9713 if (s
->tree
== s
->root
) {
9717 parent
= TAILQ_FIRST(&s
->parents
);
9718 TAILQ_REMOVE(&s
->parents
, parent
,
9720 got_object_tree_close(s
->tree
);
9721 s
->tree
= parent
->tree
;
9722 s
->first_displayed_entry
=
9723 parent
->first_displayed_entry
;
9725 parent
->selected_entry
;
9726 s
->selected
= parent
->selected
;
9727 if (s
->selected
> view
->nlines
- 3) {
9728 err
= offset_selection_down(view
);
9733 } else if (S_ISDIR(got_tree_entry_get_mode(
9734 s
->selected_entry
))) {
9735 struct got_tree_object
*subtree
;
9737 err
= got_object_open_as_tree(&subtree
, s
->repo
,
9738 got_tree_entry_get_id(s
->selected_entry
));
9741 err
= tree_view_visit_subtree(s
, subtree
);
9743 got_object_tree_close(subtree
);
9746 } else if (S_ISREG(got_tree_entry_get_mode(s
->selected_entry
)))
9747 err
= view_request_new(new_view
, view
, TOG_VIEW_BLAME
);
9750 if (view
->nlines
>= 4 && s
->selected
>= view
->nlines
- 3)
9751 s
->selected
= view
->nlines
- 4;
9767 "usage: %s tree [-c commit] [-r repository-path] [path]\n",
9772 static const struct got_error
*
9773 cmd_tree(int argc
, char *argv
[])
9775 const struct got_error
*error
;
9776 struct got_repository
*repo
= NULL
;
9777 struct got_worktree
*worktree
= NULL
;
9778 char *cwd
= NULL
, *repo_path
= NULL
, *in_repo_path
= NULL
;
9779 struct got_object_id
*commit_id
= NULL
;
9780 struct got_commit_object
*commit
= NULL
;
9781 const char *commit_id_arg
= NULL
;
9782 char *keyword_idstr
= NULL
, *label
= NULL
;
9783 struct got_reference
*ref
= NULL
;
9784 const char *head_ref_name
= NULL
;
9786 struct tog_view
*view
;
9787 int *pack_fds
= NULL
;
9789 while ((ch
= getopt(argc
, argv
, "c:r:")) != -1) {
9792 commit_id_arg
= optarg
;
9795 repo_path
= realpath(optarg
, NULL
);
9796 if (repo_path
== NULL
)
9797 return got_error_from_errno2("realpath",
9812 error
= got_repo_pack_fds_open(&pack_fds
);
9816 if (repo_path
== NULL
) {
9817 cwd
= getcwd(NULL
, 0);
9819 return got_error_from_errno("getcwd");
9820 error
= got_worktree_open(&worktree
, cwd
, NULL
);
9821 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
9825 strdup(got_worktree_get_repo_path(worktree
));
9827 repo_path
= strdup(cwd
);
9828 if (repo_path
== NULL
) {
9829 error
= got_error_from_errno("strdup");
9834 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
9838 error
= get_in_repo_path_from_argv0(&in_repo_path
, argc
, argv
,
9845 error
= apply_unveil(got_repo_get_path(repo
), NULL
);
9849 error
= tog_load_refs(repo
, 0);
9853 if (commit_id_arg
== NULL
) {
9854 error
= got_repo_match_object_id(&commit_id
, &label
,
9855 worktree
? got_worktree_get_head_ref_name(worktree
) :
9856 GOT_REF_HEAD
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
9859 head_ref_name
= label
;
9861 error
= got_keyword_to_idstr(&keyword_idstr
, commit_id_arg
,
9865 if (keyword_idstr
!= NULL
)
9866 commit_id_arg
= keyword_idstr
;
9868 error
= got_ref_open(&ref
, repo
, commit_id_arg
, 0);
9870 head_ref_name
= got_ref_get_name(ref
);
9871 else if (error
->code
!= GOT_ERR_NOT_REF
)
9873 error
= got_repo_match_object_id(&commit_id
, NULL
,
9874 commit_id_arg
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
9879 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
9883 view
= view_open(0, 0, 0, 0, TOG_VIEW_TREE
);
9885 error
= got_error_from_errno("view_open");
9888 error
= open_tree_view(view
, commit_id
, head_ref_name
, repo
);
9891 if (!got_path_is_root_dir(in_repo_path
)) {
9892 error
= tree_view_walk_path(&view
->state
.tree
, commit
,
9899 error
= set_tog_base_commit(repo
, worktree
);
9903 /* Release work tree lock. */
9904 got_worktree_close(worktree
);
9908 error
= view_loop(view
);
9911 free(tog_base_commit
.id
);
9912 free(keyword_idstr
);
9918 got_object_commit_close(commit
);
9921 if (worktree
!= NULL
)
9922 got_worktree_close(worktree
);
9924 const struct got_error
*close_err
= got_repo_close(repo
);
9929 const struct got_error
*pack_err
=
9930 got_repo_pack_fds_close(pack_fds
);
9938 static const struct got_error
*
9939 ref_view_load_refs(struct tog_ref_view_state
*s
)
9941 struct got_reflist_entry
*sre
;
9942 struct tog_reflist_entry
*re
;
9945 TAILQ_FOREACH(sre
, &tog_refs
, entry
) {
9946 if (strncmp(got_ref_get_name(sre
->ref
),
9947 "refs/got/", 9) == 0 &&
9948 strncmp(got_ref_get_name(sre
->ref
),
9949 "refs/got/backup/", 16) != 0)
9952 re
= malloc(sizeof(*re
));
9954 return got_error_from_errno("malloc");
9956 re
->ref
= got_ref_dup(sre
->ref
);
9957 if (re
->ref
== NULL
)
9958 return got_error_from_errno("got_ref_dup");
9959 re
->idx
= s
->nrefs
++;
9960 TAILQ_INSERT_TAIL(&s
->refs
, re
, entry
);
9963 s
->first_displayed_entry
= TAILQ_FIRST(&s
->refs
);
9968 ref_view_free_refs(struct tog_ref_view_state
*s
)
9970 struct tog_reflist_entry
*re
;
9972 while (!TAILQ_EMPTY(&s
->refs
)) {
9973 re
= TAILQ_FIRST(&s
->refs
);
9974 TAILQ_REMOVE(&s
->refs
, re
, entry
);
9975 got_ref_close(re
->ref
);
9980 static const struct got_error
*
9981 open_ref_view(struct tog_view
*view
, struct got_repository
*repo
)
9983 const struct got_error
*err
= NULL
;
9984 struct tog_ref_view_state
*s
= &view
->state
.ref
;
9986 s
->selected_entry
= 0;
9989 TAILQ_INIT(&s
->refs
);
9990 STAILQ_INIT(&s
->colors
);
9992 err
= ref_view_load_refs(s
);
9996 if (has_colors() && getenv("TOG_COLORS") != NULL
) {
9997 err
= add_color(&s
->colors
, "^refs/heads/",
9998 TOG_COLOR_REFS_HEADS
,
9999 get_color_value("TOG_COLOR_REFS_HEADS"));
10003 err
= add_color(&s
->colors
, "^refs/tags/",
10004 TOG_COLOR_REFS_TAGS
,
10005 get_color_value("TOG_COLOR_REFS_TAGS"));
10009 err
= add_color(&s
->colors
, "^refs/remotes/",
10010 TOG_COLOR_REFS_REMOTES
,
10011 get_color_value("TOG_COLOR_REFS_REMOTES"));
10015 err
= add_color(&s
->colors
, "^refs/got/backup/",
10016 TOG_COLOR_REFS_BACKUP
,
10017 get_color_value("TOG_COLOR_REFS_BACKUP"));
10022 view
->show
= show_ref_view
;
10023 view
->input
= input_ref_view
;
10024 view
->close
= close_ref_view
;
10025 view
->search_start
= search_start_ref_view
;
10026 view
->search_next
= search_next_ref_view
;
10029 if (view
->close
== NULL
)
10030 close_ref_view(view
);
10036 static const struct got_error
*
10037 close_ref_view(struct tog_view
*view
)
10039 struct tog_ref_view_state
*s
= &view
->state
.ref
;
10041 ref_view_free_refs(s
);
10042 free_colors(&s
->colors
);
10047 static const struct got_error
*
10048 resolve_reflist_entry(struct got_object_id
**commit_id
,
10049 struct tog_reflist_entry
*re
, struct got_repository
*repo
)
10051 const struct got_error
*err
= NULL
;
10052 struct got_object_id
*obj_id
;
10053 struct got_tag_object
*tag
= NULL
;
10058 err
= got_ref_resolve(&obj_id
, repo
, re
->ref
);
10062 err
= got_object_get_type(&obj_type
, repo
, obj_id
);
10066 switch (obj_type
) {
10067 case GOT_OBJ_TYPE_COMMIT
:
10069 case GOT_OBJ_TYPE_TAG
:
10071 * Git allows nested tags that point to tags; keep peeling
10072 * till we reach the bottom, which is always a non-tag ref.
10076 got_object_tag_close(tag
);
10077 err
= got_object_open_as_tag(&tag
, repo
, obj_id
);
10081 obj_id
= got_object_id_dup(
10082 got_object_tag_get_object_id(tag
));
10083 if (obj_id
== NULL
) {
10084 err
= got_error_from_errno("got_object_id_dup");
10087 err
= got_object_get_type(&obj_type
, repo
, obj_id
);
10090 } while (obj_type
== GOT_OBJ_TYPE_TAG
);
10091 if (obj_type
!= GOT_OBJ_TYPE_COMMIT
)
10092 err
= got_error(GOT_ERR_OBJ_TYPE
);
10095 err
= got_error(GOT_ERR_OBJ_TYPE
);
10101 got_object_tag_close(tag
);
10103 *commit_id
= obj_id
;
10109 static const struct got_error
*
10110 log_ref_entry(struct tog_view
**new_view
, int begin_y
, int begin_x
,
10111 struct tog_reflist_entry
*re
, struct got_repository
*repo
)
10113 struct tog_view
*log_view
;
10114 const struct got_error
*err
= NULL
;
10115 struct got_object_id
*commit_id
= NULL
;
10119 err
= resolve_reflist_entry(&commit_id
, re
, repo
);
10123 log_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_LOG
);
10124 if (log_view
== NULL
) {
10125 err
= got_error_from_errno("view_open");
10129 err
= open_log_view(log_view
, commit_id
, repo
,
10130 got_ref_get_name(re
->ref
), "", 0, NULL
);
10133 view_close(log_view
);
10135 *new_view
= log_view
;
10141 ref_scroll_up(struct tog_ref_view_state
*s
, int maxscroll
)
10143 struct tog_reflist_entry
*re
;
10146 if (s
->first_displayed_entry
== TAILQ_FIRST(&s
->refs
))
10149 re
= TAILQ_PREV(s
->first_displayed_entry
, tog_reflist_head
, entry
);
10150 while (i
++ < maxscroll
) {
10153 s
->first_displayed_entry
= re
;
10154 re
= TAILQ_PREV(re
, tog_reflist_head
, entry
);
10158 static const struct got_error
*
10159 ref_scroll_down(struct tog_view
*view
, int maxscroll
)
10161 struct tog_ref_view_state
*s
= &view
->state
.ref
;
10162 struct tog_reflist_entry
*next
, *last
;
10165 if (s
->first_displayed_entry
)
10166 next
= TAILQ_NEXT(s
->first_displayed_entry
, entry
);
10168 next
= TAILQ_FIRST(&s
->refs
);
10170 last
= s
->last_displayed_entry
;
10171 while (next
&& n
++ < maxscroll
) {
10173 s
->last_displayed_entry
= last
;
10174 last
= TAILQ_NEXT(last
, entry
);
10176 if (last
|| (view
->mode
== TOG_VIEW_SPLIT_HRZN
)) {
10177 s
->first_displayed_entry
= next
;
10178 next
= TAILQ_NEXT(next
, entry
);
10185 static const struct got_error
*
10186 search_start_ref_view(struct tog_view
*view
)
10188 struct tog_ref_view_state
*s
= &view
->state
.ref
;
10190 s
->matched_entry
= NULL
;
10195 match_reflist_entry(struct tog_reflist_entry
*re
, regex_t
*regex
)
10197 regmatch_t regmatch
;
10199 return regexec(regex
, got_ref_get_name(re
->ref
), 1, ®match
,
10203 static const struct got_error
*
10204 search_next_ref_view(struct tog_view
*view
)
10206 struct tog_ref_view_state
*s
= &view
->state
.ref
;
10207 struct tog_reflist_entry
*re
= NULL
;
10209 if (!view
->searching
) {
10210 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
10214 if (s
->matched_entry
) {
10215 if (view
->searching
== TOG_SEARCH_FORWARD
) {
10216 if (s
->selected_entry
)
10217 re
= TAILQ_NEXT(s
->selected_entry
, entry
);
10219 re
= TAILQ_PREV(s
->selected_entry
,
10220 tog_reflist_head
, entry
);
10222 if (s
->selected_entry
== NULL
)
10223 re
= TAILQ_LAST(&s
->refs
, tog_reflist_head
);
10225 re
= TAILQ_PREV(s
->selected_entry
,
10226 tog_reflist_head
, entry
);
10229 if (s
->selected_entry
)
10230 re
= s
->selected_entry
;
10231 else if (view
->searching
== TOG_SEARCH_FORWARD
)
10232 re
= TAILQ_FIRST(&s
->refs
);
10234 re
= TAILQ_LAST(&s
->refs
, tog_reflist_head
);
10239 if (s
->matched_entry
== NULL
) {
10240 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
10243 if (view
->searching
== TOG_SEARCH_FORWARD
)
10244 re
= TAILQ_FIRST(&s
->refs
);
10246 re
= TAILQ_LAST(&s
->refs
, tog_reflist_head
);
10249 if (match_reflist_entry(re
, &view
->regex
)) {
10250 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
10251 s
->matched_entry
= re
;
10255 if (view
->searching
== TOG_SEARCH_FORWARD
)
10256 re
= TAILQ_NEXT(re
, entry
);
10258 re
= TAILQ_PREV(re
, tog_reflist_head
, entry
);
10261 if (s
->matched_entry
) {
10262 s
->first_displayed_entry
= s
->matched_entry
;
10269 static const struct got_error
*
10270 show_ref_view(struct tog_view
*view
)
10272 const struct got_error
*err
= NULL
;
10273 struct tog_ref_view_state
*s
= &view
->state
.ref
;
10274 struct tog_reflist_entry
*re
;
10277 struct tog_color
*tc
;
10278 int width
, n
, scrollx
;
10279 int limit
= view
->nlines
;
10281 werase(view
->window
);
10284 if (view_is_hsplit_top(view
))
10285 --limit
; /* border */
10290 re
= s
->first_displayed_entry
;
10292 if (asprintf(&line
, "references [%d/%d]", re
->idx
+ s
->selected
+ 1,
10294 return got_error_from_errno("asprintf");
10296 err
= format_line(&wline
, &width
, NULL
, line
, 0, view
->ncols
, 0, 0);
10301 if (view_needs_focus_indication(view
))
10302 wstandout(view
->window
);
10303 waddwstr(view
->window
, wline
);
10304 while (width
++ < view
->ncols
)
10305 waddch(view
->window
, ' ');
10306 if (view_needs_focus_indication(view
))
10307 wstandend(view
->window
);
10317 while (re
&& limit
> 0) {
10319 char ymd
[13]; /* YYYY-MM-DD + " " + NUL */
10321 if (s
->show_date
) {
10322 struct got_commit_object
*ci
;
10323 struct got_tag_object
*tag
;
10324 struct got_object_id
*id
;
10328 err
= got_ref_resolve(&id
, s
->repo
, re
->ref
);
10331 err
= got_object_open_as_tag(&tag
, s
->repo
, id
);
10333 if (err
->code
!= GOT_ERR_OBJ_TYPE
) {
10337 err
= got_object_open_as_commit(&ci
, s
->repo
,
10343 t
= got_object_commit_get_committer_time(ci
);
10344 got_object_commit_close(ci
);
10346 t
= got_object_tag_get_tagger_time(tag
);
10347 got_object_tag_close(tag
);
10350 if (gmtime_r(&t
, &tm
) == NULL
)
10351 return got_error_from_errno("gmtime_r");
10352 if (strftime(ymd
, sizeof(ymd
), "%F ", &tm
) == 0)
10353 return got_error(GOT_ERR_NO_SPACE
);
10355 if (got_ref_is_symbolic(re
->ref
)) {
10356 if (asprintf(&line
, "%s%s -> %s", s
->show_date
?
10357 ymd
: "", got_ref_get_name(re
->ref
),
10358 got_ref_get_symref_target(re
->ref
)) == -1)
10359 return got_error_from_errno("asprintf");
10360 } else if (s
->show_ids
) {
10361 struct got_object_id
*id
;
10363 err
= got_ref_resolve(&id
, s
->repo
, re
->ref
);
10366 err
= got_object_id_str(&id_str
, id
);
10371 if (asprintf(&line
, "%s%s: %s", s
->show_date
? ymd
: "",
10372 got_ref_get_name(re
->ref
), id_str
) == -1) {
10373 err
= got_error_from_errno("asprintf");
10380 } else if (asprintf(&line
, "%s%s", s
->show_date
? ymd
: "",
10381 got_ref_get_name(re
->ref
)) == -1)
10382 return got_error_from_errno("asprintf");
10384 /* use full line width to determine view->maxx */
10385 err
= format_line(&wline
, &width
, NULL
, line
, 0, INT_MAX
, 0, 0);
10390 view
->maxx
= MAX(view
->maxx
, width
);
10394 err
= format_line(&wline
, &width
, &scrollx
, line
, view
->x
,
10395 view
->ncols
, 0, 0);
10400 if (n
== s
->selected
) {
10401 if (view
->focussed
)
10402 wstandout(view
->window
);
10403 s
->selected_entry
= re
;
10405 tc
= match_color(&s
->colors
, got_ref_get_name(re
->ref
));
10407 wattr_on(view
->window
,
10408 COLOR_PAIR(tc
->colorpair
), NULL
);
10409 waddwstr(view
->window
, &wline
[scrollx
]);
10411 wattr_off(view
->window
,
10412 COLOR_PAIR(tc
->colorpair
), NULL
);
10413 if (width
< view
->ncols
)
10414 waddch(view
->window
, '\n');
10415 if (n
== s
->selected
&& view
->focussed
)
10416 wstandend(view
->window
);
10422 s
->last_displayed_entry
= re
;
10425 re
= TAILQ_NEXT(re
, entry
);
10432 static const struct got_error
*
10433 browse_ref_tree(struct tog_view
**new_view
, int begin_y
, int begin_x
,
10434 struct tog_reflist_entry
*re
, struct got_repository
*repo
)
10436 const struct got_error
*err
= NULL
;
10437 struct got_object_id
*commit_id
= NULL
;
10438 struct tog_view
*tree_view
;
10442 err
= resolve_reflist_entry(&commit_id
, re
, repo
);
10446 tree_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_TREE
);
10447 if (tree_view
== NULL
) {
10448 err
= got_error_from_errno("view_open");
10452 err
= open_tree_view(tree_view
, commit_id
,
10453 got_ref_get_name(re
->ref
), repo
);
10457 *new_view
= tree_view
;
10463 static const struct got_error
*
10464 ref_goto_line(struct tog_view
*view
, int nlines
)
10466 const struct got_error
*err
= NULL
;
10467 struct tog_ref_view_state
*s
= &view
->state
.ref
;
10468 int g
, idx
= s
->selected_entry
->idx
;
10475 else if (g
> s
->nrefs
)
10478 if (g
>= s
->first_displayed_entry
->idx
+ 1 &&
10479 g
<= s
->last_displayed_entry
->idx
+ 1 &&
10480 g
- s
->first_displayed_entry
->idx
- 1 < nlines
) {
10481 s
->selected
= g
- s
->first_displayed_entry
->idx
- 1;
10486 err
= ref_scroll_down(view
, g
- idx
- 1);
10489 if (TAILQ_NEXT(s
->last_displayed_entry
, entry
) == NULL
&&
10490 s
->first_displayed_entry
->idx
+ s
->selected
< g
&&
10491 s
->selected
< s
->ndisplayed
- 1)
10492 s
->selected
= g
- s
->first_displayed_entry
->idx
- 1;
10493 } else if (idx
+ 1 > g
)
10494 ref_scroll_up(s
, idx
- g
+ 1);
10496 if (g
< nlines
&& s
->first_displayed_entry
->idx
== 0)
10497 s
->selected
= g
- 1;
10503 static const struct got_error
*
10504 input_ref_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
10506 const struct got_error
*err
= NULL
;
10507 struct tog_ref_view_state
*s
= &view
->state
.ref
;
10508 struct tog_reflist_entry
*re
;
10509 int n
, nscroll
= view
->nlines
- 1;
10512 return ref_goto_line(view
, nscroll
);
10521 horizontal_scroll_input(view
, ch
);
10524 s
->show_ids
= !s
->show_ids
;
10528 s
->show_date
= !s
->show_date
;
10532 s
->sort_by_date
= !s
->sort_by_date
;
10533 view
->action
= s
->sort_by_date
? "sort by date" : "sort by name";
10535 err
= got_reflist_sort(&tog_refs
, s
->sort_by_date
?
10536 got_ref_cmp_by_commit_timestamp_descending
:
10537 tog_ref_cmp_by_name
, s
->repo
);
10540 got_reflist_object_id_map_free(tog_refs_idmap
);
10541 err
= got_reflist_object_id_map_create(&tog_refs_idmap
,
10542 &tog_refs
, s
->repo
);
10545 ref_view_free_refs(s
);
10546 err
= ref_view_load_refs(s
);
10551 if (!s
->selected_entry
)
10553 err
= view_request_new(new_view
, view
, TOG_VIEW_LOG
);
10557 if (!s
->selected_entry
)
10559 err
= view_request_new(new_view
, view
, TOG_VIEW_TREE
);
10566 s
->first_displayed_entry
= TAILQ_FIRST(&s
->refs
);
10571 int eos
= view
->nlines
- 1;
10573 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
)
10574 --eos
; /* border */
10577 re
= TAILQ_LAST(&s
->refs
, tog_reflist_head
);
10578 for (n
= 0; n
< eos
; n
++) {
10581 s
->first_displayed_entry
= re
;
10582 re
= TAILQ_PREV(re
, tog_reflist_head
, entry
);
10585 s
->selected
= n
- 1;
10591 if (s
->selected
> 0) {
10595 ref_scroll_up(s
, 1);
10596 if (s
->selected_entry
== TAILQ_FIRST(&s
->refs
))
10606 if (s
->first_displayed_entry
== TAILQ_FIRST(&s
->refs
))
10607 s
->selected
-= MIN(nscroll
, s
->selected
);
10608 ref_scroll_up(s
, MAX(0, nscroll
));
10609 if (s
->selected_entry
== TAILQ_FIRST(&s
->refs
))
10615 if (s
->selected
< s
->ndisplayed
- 1) {
10619 if (TAILQ_NEXT(s
->last_displayed_entry
, entry
) == NULL
) {
10620 /* can't scroll any further */
10624 ref_scroll_down(view
, 1);
10634 if (TAILQ_NEXT(s
->last_displayed_entry
, entry
) == NULL
) {
10635 /* can't scroll any further; move cursor down */
10636 if (s
->selected
< s
->ndisplayed
- 1)
10637 s
->selected
+= MIN(nscroll
,
10638 s
->ndisplayed
- s
->selected
- 1);
10639 if (view
->count
> 1 && s
->selected
< s
->ndisplayed
- 1)
10640 s
->selected
+= s
->ndisplayed
- s
->selected
- 1;
10644 ref_scroll_down(view
, nscroll
);
10649 err
= tog_load_refs(s
->repo
, s
->sort_by_date
);
10652 ref_view_free_refs(s
);
10653 err
= ref_view_load_refs(s
);
10656 if (view
->nlines
>= 2 && s
->selected
>= view
->nlines
- 1)
10657 s
->selected
= view
->nlines
- 2;
10671 fprintf(stderr
, "usage: %s ref [-r repository-path]\n",
10676 static const struct got_error
*
10677 cmd_ref(int argc
, char *argv
[])
10679 const struct got_error
*error
;
10680 struct got_repository
*repo
= NULL
;
10681 struct got_worktree
*worktree
= NULL
;
10682 char *cwd
= NULL
, *repo_path
= NULL
;
10684 struct tog_view
*view
;
10685 int *pack_fds
= NULL
;
10687 while ((ch
= getopt(argc
, argv
, "r:")) != -1) {
10690 repo_path
= realpath(optarg
, NULL
);
10691 if (repo_path
== NULL
)
10692 return got_error_from_errno2("realpath",
10707 error
= got_repo_pack_fds_open(&pack_fds
);
10711 if (repo_path
== NULL
) {
10712 cwd
= getcwd(NULL
, 0);
10714 return got_error_from_errno("getcwd");
10715 error
= got_worktree_open(&worktree
, cwd
, NULL
);
10716 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
10720 strdup(got_worktree_get_repo_path(worktree
));
10722 repo_path
= strdup(cwd
);
10723 if (repo_path
== NULL
) {
10724 error
= got_error_from_errno("strdup");
10729 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
10735 error
= apply_unveil(got_repo_get_path(repo
), NULL
);
10739 error
= tog_load_refs(repo
, 0);
10743 view
= view_open(0, 0, 0, 0, TOG_VIEW_REF
);
10744 if (view
== NULL
) {
10745 error
= got_error_from_errno("view_open");
10749 error
= open_ref_view(view
, repo
);
10754 error
= set_tog_base_commit(repo
, worktree
);
10758 /* Release work tree lock. */
10759 got_worktree_close(worktree
);
10763 error
= view_loop(view
);
10766 free(tog_base_commit
.id
);
10769 if (worktree
!= NULL
)
10770 got_worktree_close(worktree
);
10772 const struct got_error
*close_err
;
10774 close_err
= got_repo_close(repo
);
10775 if (close_err
&& error
== NULL
)
10779 const struct got_error
*pack_err
;
10781 pack_err
= got_repo_pack_fds_close(pack_fds
);
10782 if (pack_err
&& error
== NULL
)
10789 static const struct got_error
*
10790 win_draw_center(WINDOW
*win
, size_t y
, size_t x
, size_t maxx
, int focus
,
10799 x
= x
? x
: maxx
> len
? (maxx
- len
) / 2 : 0;
10803 if (mvwprintw(win
, y
, x
, "%s", str
) == ERR
)
10804 return got_error_msg(GOT_ERR_RANGE
, "mvwprintw");
10811 static const struct got_error
*
10812 add_line_offset(off_t
**line_offsets
, size_t *nlines
, off_t off
)
10816 p
= reallocarray(*line_offsets
, *nlines
+ 1, sizeof(off_t
));
10818 free(*line_offsets
);
10819 *line_offsets
= NULL
;
10820 return got_error_from_errno("reallocarray");
10824 (*line_offsets
)[*nlines
] = off
;
10829 static const struct got_error
*
10830 max_key_str(int *ret
, const struct tog_key_map
*km
, size_t n
)
10834 for (;n
> 0; --n
, ++km
) {
10838 if (km
->keys
== NULL
)
10841 t
= t0
= strdup(km
->keys
);
10843 return got_error_from_errno("strdup");
10846 while ((k
= strsep(&t
, " ")) != NULL
)
10847 len
+= strlen(k
) > 1 ? 2 : 0;
10849 *ret
= MAX(*ret
, len
);
10856 * Write keymap section headers, keys, and key info in km to f.
10857 * Save line offset to *off. If terminal has UTF8 encoding enabled,
10858 * wrap control and symbolic keys in guillemets, else use <>.
10860 static const struct got_error
*
10861 format_help_line(off_t
*off
, FILE *f
, const struct tog_key_map
*km
, int width
)
10863 int n
, len
= width
;
10866 static const char *u8_glyph
[] = {
10867 "\xe2\x80\xb9", /* U+2039 (utf8 <) */
10868 "\xe2\x80\xba" /* U+203A (utf8 >) */
10871 int cs
, s
, first
= 1;
10873 cs
= got_locale_is_utf8();
10875 t
= t0
= strdup(km
->keys
);
10877 return got_error_from_errno("strdup");
10879 len
= strlen(km
->keys
);
10880 while ((k
= strsep(&t
, " ")) != NULL
) {
10881 s
= strlen(k
) > 1; /* control or symbolic key */
10882 n
= fprintf(f
, "%s%s%s%s%s", first
? " " : "",
10883 cs
&& s
? u8_glyph
[0] : s
? "<" : "", k
,
10884 cs
&& s
? u8_glyph
[1] : s
? ">" : "", t
? " " : "");
10887 return got_error_from_errno("fprintf");
10895 n
= fprintf(f
, "%*s%s\n", width
- len
, width
- len
? " " : "", km
->info
);
10897 return got_error_from_errno("fprintf");
10903 static const struct got_error
*
10904 format_help(struct tog_help_view_state
*s
)
10906 const struct got_error
*err
= NULL
;
10908 int i
, max
, n
, show
= s
->all
;
10909 static const struct tog_key_map km
[] = {
10910 #define KEYMAP_(info, type) { NULL, (info), type }
10911 #define KEY_(keys, info) { (keys), (info), TOG_KEYMAP_KEYS }
10917 err
= add_line_offset(&s
->line_offsets
, &s
->nlines
, 0);
10922 err
= max_key_str(&max
, km
, n
);
10926 for (i
= 0; i
< n
; ++i
) {
10927 if (km
[i
].keys
== NULL
) {
10929 if (km
[i
].type
== TOG_KEYMAP_GLOBAL
||
10930 km
[i
].type
== s
->type
|| s
->all
)
10934 err
= format_help_line(&off
, s
->f
, &km
[i
], max
);
10937 err
= add_line_offset(&s
->line_offsets
, &s
->nlines
, off
);
10944 err
= add_line_offset(&s
->line_offsets
, &s
->nlines
, off
);
10948 static const struct got_error
*
10949 create_help(struct tog_help_view_state
*s
)
10952 const struct got_error
*err
;
10954 free(s
->line_offsets
);
10955 s
->line_offsets
= NULL
;
10958 f
= got_opentemp();
10960 return got_error_from_errno("got_opentemp");
10963 err
= format_help(s
);
10967 if (s
->f
&& fflush(s
->f
) != 0)
10968 return got_error_from_errno("fflush");
10973 static const struct got_error
*
10974 search_start_help_view(struct tog_view
*view
)
10976 view
->state
.help
.matched_line
= 0;
10981 search_setup_help_view(struct tog_view
*view
, FILE **f
, off_t
**line_offsets
,
10982 size_t *nlines
, int **first
, int **last
, int **match
, int **selected
)
10984 struct tog_help_view_state
*s
= &view
->state
.help
;
10987 *nlines
= s
->nlines
;
10988 *line_offsets
= s
->line_offsets
;
10989 *match
= &s
->matched_line
;
10990 *first
= &s
->first_displayed_line
;
10991 *last
= &s
->last_displayed_line
;
10992 *selected
= &s
->selected_line
;
10995 static const struct got_error
*
10996 show_help_view(struct tog_view
*view
)
10998 struct tog_help_view_state
*s
= &view
->state
.help
;
10999 const struct got_error
*err
;
11000 regmatch_t
*regmatch
= &view
->regmatch
;
11005 int width
, nprinted
= 0, rc
= 0;
11006 int eos
= view
->nlines
;
11008 if (view_is_hsplit_top(view
))
11009 --eos
; /* account for border */
11013 werase(view
->window
);
11015 if (view
->gline
> s
->nlines
- 1)
11016 view
->gline
= s
->nlines
- 1;
11018 err
= win_draw_center(view
->window
, 0, 0, view
->ncols
,
11019 view_needs_focus_indication(view
),
11020 "tog help (press q to return to tog)");
11025 waddstr(view
->window
, "\n\n");
11031 while (eos
> 0 && nprinted
< eos
) {
11034 linelen
= getline(&line
, &linesz
, s
->f
);
11035 if (linelen
== -1) {
11038 return got_ferror(s
->f
, GOT_ERR_IO
);
11043 if (++s
->lineno
< s
->first_displayed_line
)
11045 if (view
->gline
&& !gotoline(view
, &s
->lineno
, &nprinted
))
11047 if (s
->lineno
== view
->hiline
)
11050 err
= format_line(&wline
, &width
, NULL
, line
, 0, INT_MAX
, 0,
11056 view
->maxx
= MAX(view
->maxx
, width
);
11061 wattron(view
->window
, attr
);
11062 if (s
->first_displayed_line
+ nprinted
== s
->matched_line
&&
11063 regmatch
->rm_so
>= 0 && regmatch
->rm_so
< regmatch
->rm_eo
) {
11064 err
= add_matched_line(&width
, line
, view
->ncols
- 1, 0,
11065 view
->window
, view
->x
, regmatch
);
11073 err
= format_line(&wline
, &width
, &skip
, line
,
11074 view
->x
, view
->ncols
, 0, view
->x
? 1 : 0);
11079 waddwstr(view
->window
, &wline
[skip
]);
11083 if (s
->lineno
== view
->hiline
) {
11084 while (width
++ < view
->ncols
)
11085 waddch(view
->window
, ' ');
11087 if (width
< view
->ncols
)
11088 waddch(view
->window
, '\n');
11091 wattroff(view
->window
, attr
);
11092 if (++nprinted
== 1)
11093 s
->first_displayed_line
= s
->lineno
;
11097 s
->last_displayed_line
= s
->first_displayed_line
+ nprinted
- 1;
11099 s
->last_displayed_line
= s
->first_displayed_line
;
11104 rc
= waddnstr(view
->window
,
11105 "See the tog(1) manual page for full documentation",
11108 return got_error_msg(GOT_ERR_RANGE
, "waddnstr");
11110 wmove(view
->window
, view
->nlines
- 1, 0);
11111 wclrtoeol(view
->window
);
11112 wstandout(view
->window
);
11113 rc
= waddnstr(view
->window
, "scroll down for more...",
11116 return got_error_msg(GOT_ERR_RANGE
, "waddnstr");
11117 if (getcurx(view
->window
) < view
->ncols
- 6) {
11118 rc
= wprintw(view
->window
, "[%.0f%%]",
11119 100.00 * s
->last_displayed_line
/ s
->nlines
);
11121 return got_error_msg(GOT_ERR_IO
, "wprintw");
11123 wstandend(view
->window
);
11129 static const struct got_error
*
11130 input_help_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
11132 struct tog_help_view_state
*s
= &view
->state
.help
;
11133 const struct got_error
*err
= NULL
;
11139 eos
= nscroll
= view
->nlines
;
11140 if (view_is_hsplit_top(view
))
11141 --eos
; /* border */
11143 s
->lineno
= s
->first_displayed_line
- 1 + s
->selected_line
;
11152 horizontal_scroll_input(view
, ch
);
11156 s
->first_displayed_line
= 1;
11164 s
->first_displayed_line
= (s
->nlines
- eos
) + 3;
11169 if (s
->first_displayed_line
> 1)
11170 --s
->first_displayed_line
;
11181 if (s
->first_displayed_line
== 1) {
11185 while (--nscroll
> 0 && s
->first_displayed_line
> 1)
11186 s
->first_displayed_line
--;
11192 ++s
->first_displayed_line
;
11208 while (!s
->eof
&& --nscroll
> 0) {
11209 linelen
= getline(&line
, &linesz
, s
->f
);
11210 s
->first_displayed_line
++;
11211 if (linelen
== -1) {
11215 err
= got_ferror(s
->f
, GOT_ERR_IO
);
11229 static const struct got_error
*
11230 close_help_view(struct tog_view
*view
)
11232 struct tog_help_view_state
*s
= &view
->state
.help
;
11234 free(s
->line_offsets
);
11235 s
->line_offsets
= NULL
;
11236 if (fclose(s
->f
) == EOF
)
11237 return got_error_from_errno("fclose");
11242 static const struct got_error
*
11243 reset_help_view(struct tog_view
*view
)
11245 struct tog_help_view_state
*s
= &view
->state
.help
;
11248 if (s
->f
&& fclose(s
->f
) == EOF
)
11249 return got_error_from_errno("fclose");
11251 wclear(view
->window
);
11255 s
->first_displayed_line
= 1;
11256 s
->last_displayed_line
= view
->nlines
;
11257 s
->matched_line
= 0;
11259 return create_help(s
);
11262 static const struct got_error
*
11263 open_help_view(struct tog_view
*view
, struct tog_view
*parent
)
11265 const struct got_error
*err
= NULL
;
11266 struct tog_help_view_state
*s
= &view
->state
.help
;
11268 s
->type
= (enum tog_keymap_type
)parent
->type
;
11269 s
->first_displayed_line
= 1;
11270 s
->last_displayed_line
= view
->nlines
;
11271 s
->selected_line
= 1;
11273 view
->show
= show_help_view
;
11274 view
->input
= input_help_view
;
11275 view
->reset
= reset_help_view
;
11276 view
->close
= close_help_view
;
11277 view
->search_start
= search_start_help_view
;
11278 view
->search_setup
= search_setup_help_view
;
11279 view
->search_next
= search_next_view_match
;
11281 err
= create_help(s
);
11285 static const struct got_error
*
11286 view_dispatch_request(struct tog_view
**new_view
, struct tog_view
*view
,
11287 enum tog_view_type request
, int y
, int x
)
11289 const struct got_error
*err
= NULL
;
11294 case TOG_VIEW_DIFF
:
11295 if (view
->type
== TOG_VIEW_LOG
) {
11296 struct tog_log_view_state
*s
= &view
->state
.log
;
11298 err
= open_diff_view_for_commit(new_view
, y
, x
,
11299 s
->selected_entry
, view
, s
->repo
);
11301 return got_error_msg(GOT_ERR_NOT_IMPL
,
11302 "parent/child view pair not supported");
11304 case TOG_VIEW_BLAME
:
11305 if (view
->type
== TOG_VIEW_TREE
) {
11306 struct tog_tree_view_state
*s
= &view
->state
.tree
;
11308 err
= blame_tree_entry(new_view
, y
, x
,
11309 s
->selected_entry
, &s
->parents
, s
->commit_id
,
11312 return got_error_msg(GOT_ERR_NOT_IMPL
,
11313 "parent/child view pair not supported");
11316 tog_base_commit
.idx
= -1;
11317 if (view
->type
== TOG_VIEW_BLAME
)
11318 err
= log_annotated_line(new_view
, y
, x
,
11319 view
->state
.blame
.repo
, view
->state
.blame
.id_to_log
);
11320 else if (view
->type
== TOG_VIEW_TREE
)
11321 err
= log_selected_tree_entry(new_view
, y
, x
,
11322 &view
->state
.tree
);
11323 else if (view
->type
== TOG_VIEW_REF
)
11324 err
= log_ref_entry(new_view
, y
, x
,
11325 view
->state
.ref
.selected_entry
,
11326 view
->state
.ref
.repo
);
11328 return got_error_msg(GOT_ERR_NOT_IMPL
,
11329 "parent/child view pair not supported");
11331 case TOG_VIEW_TREE
:
11332 if (view
->type
== TOG_VIEW_LOG
)
11333 err
= browse_commit_tree(new_view
, y
, x
,
11334 view
->state
.log
.selected_entry
,
11335 view
->state
.log
.in_repo_path
,
11336 view
->state
.log
.head_ref_name
,
11337 view
->state
.log
.repo
);
11338 else if (view
->type
== TOG_VIEW_REF
)
11339 err
= browse_ref_tree(new_view
, y
, x
,
11340 view
->state
.ref
.selected_entry
,
11341 view
->state
.ref
.repo
);
11343 return got_error_msg(GOT_ERR_NOT_IMPL
,
11344 "parent/child view pair not supported");
11347 *new_view
= view_open(0, 0, y
, x
, TOG_VIEW_REF
);
11348 if (*new_view
== NULL
)
11349 return got_error_from_errno("view_open");
11350 if (view
->type
== TOG_VIEW_LOG
)
11351 err
= open_ref_view(*new_view
, view
->state
.log
.repo
);
11352 else if (view
->type
== TOG_VIEW_TREE
)
11353 err
= open_ref_view(*new_view
, view
->state
.tree
.repo
);
11355 err
= got_error_msg(GOT_ERR_NOT_IMPL
,
11356 "parent/child view pair not supported");
11358 view_close(*new_view
);
11360 case TOG_VIEW_HELP
:
11361 *new_view
= view_open(0, 0, 0, 0, TOG_VIEW_HELP
);
11362 if (*new_view
== NULL
)
11363 return got_error_from_errno("view_open");
11364 err
= open_help_view(*new_view
, view
);
11366 view_close(*new_view
);
11369 return got_error_msg(GOT_ERR_NOT_IMPL
, "invalid view");
11376 * If view was scrolled down to move the selected line into view when opening a
11377 * horizontal split, scroll back up when closing the split/toggling fullscreen.
11380 offset_selection_up(struct tog_view
*view
)
11382 switch (view
->type
) {
11383 case TOG_VIEW_BLAME
: {
11384 struct tog_blame_view_state
*s
= &view
->state
.blame
;
11385 if (s
->first_displayed_line
== 1) {
11386 s
->selected_line
= MAX(s
->selected_line
- view
->offset
,
11390 if (s
->first_displayed_line
> view
->offset
)
11391 s
->first_displayed_line
-= view
->offset
;
11393 s
->first_displayed_line
= 1;
11394 s
->selected_line
+= view
->offset
;
11398 log_scroll_up(&view
->state
.log
, view
->offset
);
11399 view
->state
.log
.selected
+= view
->offset
;
11402 ref_scroll_up(&view
->state
.ref
, view
->offset
);
11403 view
->state
.ref
.selected
+= view
->offset
;
11405 case TOG_VIEW_TREE
:
11406 tree_scroll_up(&view
->state
.tree
, view
->offset
);
11407 view
->state
.tree
.selected
+= view
->offset
;
11417 * If the selected line is in the section of screen covered by the bottom split,
11418 * scroll down offset lines to move it into view and index its new position.
11420 static const struct got_error
*
11421 offset_selection_down(struct tog_view
*view
)
11423 const struct got_error
*err
= NULL
;
11424 const struct got_error
*(*scrolld
)(struct tog_view
*, int);
11425 int *selected
= NULL
;
11426 int header
, offset
;
11428 switch (view
->type
) {
11429 case TOG_VIEW_BLAME
: {
11430 struct tog_blame_view_state
*s
= &view
->state
.blame
;
11433 if (s
->selected_line
> view
->nlines
- header
) {
11434 offset
= abs(view
->nlines
- s
->selected_line
- header
);
11435 s
->first_displayed_line
+= offset
;
11436 s
->selected_line
-= offset
;
11437 view
->offset
= offset
;
11441 case TOG_VIEW_LOG
: {
11442 struct tog_log_view_state
*s
= &view
->state
.log
;
11443 scrolld
= &log_scroll_down
;
11444 header
= view_is_parent_view(view
) ? 3 : 2;
11445 selected
= &s
->selected
;
11448 case TOG_VIEW_REF
: {
11449 struct tog_ref_view_state
*s
= &view
->state
.ref
;
11450 scrolld
= &ref_scroll_down
;
11452 selected
= &s
->selected
;
11455 case TOG_VIEW_TREE
: {
11456 struct tog_tree_view_state
*s
= &view
->state
.tree
;
11457 scrolld
= &tree_scroll_down
;
11459 selected
= &s
->selected
;
11469 if (selected
&& *selected
> view
->nlines
- header
) {
11470 offset
= abs(view
->nlines
- *selected
- header
);
11471 view
->offset
= offset
;
11472 if (scrolld
&& offset
) {
11473 err
= scrolld(view
, offset
);
11474 *selected
-= MIN(*selected
, offset
);
11482 list_commands(FILE *fp
)
11486 fprintf(fp
, "commands:");
11487 for (i
= 0; i
< nitems(tog_commands
); i
++) {
11488 const struct tog_cmd
*cmd
= &tog_commands
[i
];
11489 fprintf(fp
, " %s", cmd
->name
);
11495 usage(int hflag
, int status
)
11497 FILE *fp
= (status
== 0) ? stdout
: stderr
;
11499 fprintf(fp
, "usage: %s [-hV] command [arg ...]\n",
11502 fprintf(fp
, "lazy usage: %s path\n", getprogname());
11509 make_argv(int argc
, ...)
11515 va_start(ap
, argc
);
11517 argv
= calloc(argc
, sizeof(char *));
11520 for (i
= 0; i
< argc
; i
++) {
11521 argv
[i
] = strdup(va_arg(ap
, char *));
11522 if (argv
[i
] == NULL
)
11531 * Try to convert 'tog path' into a 'tog log path' command.
11532 * The user could simply have mistyped the command rather than knowingly
11533 * provided a path. So check whether argv[0] can in fact be resolved
11534 * to a path in the HEAD commit and print a special error if not.
11535 * This hack is for mpi@ <3
11537 static const struct got_error
*
11538 tog_log_with_path(int argc
, char *argv
[])
11540 const struct got_error
*error
= NULL
, *close_err
;
11541 const struct tog_cmd
*cmd
= NULL
;
11542 struct got_repository
*repo
= NULL
;
11543 struct got_worktree
*worktree
= NULL
;
11544 struct got_object_id
*commit_id
= NULL
, *id
= NULL
;
11545 struct got_commit_object
*commit
= NULL
;
11546 char *cwd
= NULL
, *repo_path
= NULL
, *in_repo_path
= NULL
;
11547 char *commit_id_str
= NULL
, **cmd_argv
= NULL
;
11548 int *pack_fds
= NULL
;
11550 cwd
= getcwd(NULL
, 0);
11552 return got_error_from_errno("getcwd");
11554 error
= got_repo_pack_fds_open(&pack_fds
);
11558 error
= got_worktree_open(&worktree
, cwd
, NULL
);
11559 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
11563 repo_path
= strdup(got_worktree_get_repo_path(worktree
));
11565 repo_path
= strdup(cwd
);
11566 if (repo_path
== NULL
) {
11567 error
= got_error_from_errno("strdup");
11571 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
11575 error
= get_in_repo_path_from_argv0(&in_repo_path
, argc
, argv
,
11580 error
= tog_load_refs(repo
, 0);
11583 error
= got_repo_match_object_id(&commit_id
, NULL
, worktree
?
11584 got_worktree_get_head_ref_name(worktree
) : GOT_REF_HEAD
,
11585 GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
11590 got_worktree_close(worktree
);
11594 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
11598 error
= got_object_id_by_path(&id
, repo
, commit
, in_repo_path
);
11600 if (error
->code
!= GOT_ERR_NO_TREE_ENTRY
)
11602 fprintf(stderr
, "%s: '%s' is no known command or path\n",
11603 getprogname(), argv
[0]);
11608 error
= got_object_id_str(&commit_id_str
, commit_id
);
11612 cmd
= &tog_commands
[0]; /* log */
11614 cmd_argv
= make_argv(argc
, cmd
->name
, "-c", commit_id_str
, argv
[0]);
11615 error
= cmd
->cmd_main(argc
, cmd_argv
);
11618 close_err
= got_repo_close(repo
);
11623 got_object_commit_close(commit
);
11625 got_worktree_close(worktree
);
11627 const struct got_error
*pack_err
=
11628 got_repo_pack_fds_close(pack_fds
);
11633 free(commit_id_str
);
11637 free(in_repo_path
);
11640 for (i
= 0; i
< argc
; i
++)
11649 main(int argc
, char *argv
[])
11651 const struct got_error
*io_err
, *error
= NULL
;
11652 const struct tog_cmd
*cmd
= NULL
;
11653 int ch
, hflag
= 0, Vflag
= 0;
11654 char **cmd_argv
= NULL
;
11655 static const struct option longopts
[] = {
11656 { "version", no_argument
, NULL
, 'V' },
11657 { NULL
, 0, NULL
, 0}
11659 char *diff_algo_str
= NULL
;
11660 const char *test_script_path
;
11662 setlocale(LC_CTYPE
, "");
11665 * Override default signal handlers before starting ncurses.
11666 * This should prevent ncurses from installing its own
11667 * broken cleanup() signal handler.
11669 signal(SIGWINCH
, tog_sigwinch
);
11670 signal(SIGPIPE
, tog_sigpipe
);
11671 signal(SIGCONT
, tog_sigcont
);
11672 signal(SIGINT
, tog_sigint
);
11673 signal(SIGTERM
, tog_sigterm
);
11676 * Test mode init must happen before pledge() because "tty" will
11677 * not allow TTY-related ioctls to occur via regular files.
11679 test_script_path
= getenv("TOG_TEST_SCRIPT");
11680 if (test_script_path
!= NULL
) {
11681 error
= init_mock_term(test_script_path
);
11683 fprintf(stderr
, "%s: %s\n", getprogname(), error
->msg
);
11686 } else if (!isatty(STDIN_FILENO
))
11687 errx(1, "standard input is not a tty");
11689 #if !defined(PROFILE)
11690 if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd unveil",
11695 while ((ch
= getopt_long(argc
, argv
, "+hV", longopts
, NULL
)) != -1) {
11715 got_version_print_str();
11722 /* Build an argument vector which runs a default command. */
11723 cmd
= &tog_commands
[0];
11725 cmd_argv
= make_argv(argc
, cmd
->name
);
11729 /* Did the user specify a command? */
11730 for (i
= 0; i
< nitems(tog_commands
); i
++) {
11731 if (strncmp(tog_commands
[i
].name
, argv
[0],
11732 strlen(argv
[0])) == 0) {
11733 cmd
= &tog_commands
[i
];
11739 diff_algo_str
= getenv("TOG_DIFF_ALGORITHM");
11740 if (diff_algo_str
) {
11741 if (strcasecmp(diff_algo_str
, "patience") == 0)
11742 tog_diff_algo
= GOT_DIFF_ALGORITHM_PATIENCE
;
11743 if (strcasecmp(diff_algo_str
, "myers") == 0)
11744 tog_diff_algo
= GOT_DIFF_ALGORITHM_MYERS
;
11747 tog_base_commit
.idx
= -1;
11748 tog_base_commit
.marker
= GOT_WORKTREE_STATE_UNKNOWN
;
11753 /* No command specified; try log with a path */
11754 error
= tog_log_with_path(argc
, argv
);
11759 error
= cmd
->cmd_main(argc
, cmd_argv
? cmd_argv
: argv
);
11762 if (using_mock_io
) {
11763 io_err
= tog_io_close();
11770 for (i
= 0; i
< argc
; i
++)
11775 if (error
&& error
->code
!= GOT_ERR_CANCELLED
&&
11776 error
->code
!= GOT_ERR_EOF
&&
11777 error
->code
!= GOT_ERR_PRIVSEP_EXIT
&&
11778 error
->code
!= GOT_ERR_PRIVSEP_PIPE
&&
11779 !(error
->code
== GOT_ERR_ERRNO
&& errno
== EINTR
)) {
11780 fprintf(stderr
, "%s: %s\n", getprogname(), error
->msg
);