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);
1875 if (view
->type
== TOG_VIEW_HELP
)
1876 err
= view
->reset(view
);
1878 err
= view_request_new(new, view
, TOG_VIEW_HELP
);
1884 view
->child
->focussed
= 1;
1885 view
->focus_child
= 1;
1886 } else if (view
->parent
) {
1888 view
->parent
->focussed
= 1;
1889 view
->parent
->focus_child
= 0;
1890 if (!view_is_splitscreen(view
)) {
1891 if (view
->parent
->resize
) {
1892 err
= view
->parent
->resize(view
->parent
,
1897 offset_selection_up(view
->parent
);
1898 err
= view_fullscreen(view
->parent
);
1905 if (view
->parent
!= NULL
) {
1906 if (view
->parent
->type
== TOG_VIEW_LOG
)
1907 log_mark_clear(&view
->parent
->state
.log
);
1909 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
) {
1910 if (view
->parent
->resize
) {
1912 * Might need more commits
1913 * to fill fullscreen.
1915 err
= view
->parent
->resize(
1920 offset_selection_up(view
->parent
);
1923 err
= view
->input(new, view
, ch
);
1931 if (view_is_parent_view(view
)) {
1932 if (view
->child
== NULL
)
1934 if (view_is_splitscreen(view
->child
)) {
1936 view
->child
->focussed
= 1;
1937 err
= view_fullscreen(view
->child
);
1939 err
= view_splitscreen(view
->child
);
1941 err
= view_resize_split(view
, 0);
1945 err
= view
->child
->input(new, view
->child
,
1948 if (view_is_splitscreen(view
)) {
1949 view
->parent
->focussed
= 0;
1951 err
= view_fullscreen(view
);
1953 err
= view_splitscreen(view
);
1954 if (!err
&& view
->mode
!= TOG_VIEW_SPLIT_HRZN
)
1955 err
= view_resize(view
->parent
);
1957 err
= view_resize_split(view
, 0);
1961 err
= view
->input(new, view
, KEY_RESIZE
);
1966 err
= view
->resize(view
, 0);
1971 if (view
->parent
->resize
) {
1972 err
= view
->parent
->resize(view
->parent
, 0);
1976 err
= offset_selection_down(view
->parent
);
1980 err
= offset_selection_down(view
);
1984 err
= switch_split(view
);
1987 err
= view_resize_split(view
, -1);
1990 err
= view_resize_split(view
, 1);
1996 if (view
->search_start
)
1997 view_search_start(view
, fast_refresh
);
1999 err
= view
->input(new, view
, ch
);
2003 if (view
->search_started
&& view
->search_next
) {
2004 view
->searching
= (ch
== 'n' ?
2005 TOG_SEARCH_FORWARD
: TOG_SEARCH_BACKWARD
);
2006 view
->search_next_done
= 0;
2007 view
->search_next(view
);
2009 err
= view
->input(new, view
, ch
);
2012 if (tog_diff_algo
== GOT_DIFF_ALGORITHM_MYERS
) {
2013 tog_diff_algo
= GOT_DIFF_ALGORITHM_PATIENCE
;
2014 view
->action
= "Patience diff algorithm";
2016 tog_diff_algo
= GOT_DIFF_ALGORITHM_MYERS
;
2017 view
->action
= "Myers diff algorithm";
2019 TAILQ_FOREACH(v
, views
, entry
) {
2025 if (v
->child
&& v
->child
->reset
) {
2026 err
= v
->child
->reset(v
->child
);
2032 case TOG_KEY_SCRDUMP
:
2033 err
= screendump(view
);
2036 err
= view
->input(new, view
, ch
);
2044 view_needs_focus_indication(struct tog_view
*view
)
2046 if (view_is_parent_view(view
)) {
2047 if (view
->child
== NULL
|| view
->child
->focussed
)
2049 if (!view_is_splitscreen(view
->child
))
2051 } else if (!view_is_splitscreen(view
))
2054 return view
->focussed
;
2057 static const struct got_error
*
2060 const struct got_error
*err
= NULL
;
2062 if (tog_io
.cin
&& fclose(tog_io
.cin
) == EOF
)
2063 err
= got_ferror(tog_io
.cin
, GOT_ERR_IO
);
2064 if (tog_io
.cout
&& fclose(tog_io
.cout
) == EOF
&& err
== NULL
)
2065 err
= got_ferror(tog_io
.cout
, GOT_ERR_IO
);
2066 if (tog_io
.f
&& fclose(tog_io
.f
) == EOF
&& err
== NULL
)
2067 err
= got_ferror(tog_io
.f
, GOT_ERR_IO
);
2068 if (tog_io
.sdump
&& fclose(tog_io
.sdump
) == EOF
&& err
== NULL
)
2069 err
= got_ferror(tog_io
.sdump
, GOT_ERR_IO
);
2070 if (tog_io
.input_str
!= NULL
)
2071 free(tog_io
.input_str
);
2076 static const struct got_error
*
2077 view_loop(struct tog_view
*view
)
2079 const struct got_error
*err
= NULL
;
2080 struct tog_view_list_head views
;
2081 struct tog_view
*new_view
;
2083 int fast_refresh
= 10;
2084 int done
= 0, errcode
;
2086 mode
= getenv("TOG_VIEW_SPLIT_MODE");
2087 if (!mode
|| !(*mode
== 'h' || *mode
== 'H'))
2088 view
->mode
= TOG_VIEW_SPLIT_VERT
;
2090 view
->mode
= TOG_VIEW_SPLIT_HRZN
;
2092 errcode
= pthread_mutex_lock(&tog_mutex
);
2094 return got_error_set_errno(errcode
, "pthread_mutex_lock");
2097 TAILQ_INSERT_HEAD(&views
, view
, entry
);
2100 err
= view
->show(view
);
2105 while (!TAILQ_EMPTY(&views
) && !done
&& !tog_thread_error
&&
2106 !tog_fatal_signal_received()) {
2107 /* Refresh fast during initialization, then become slower. */
2108 if (fast_refresh
&& --fast_refresh
== 0 && !using_mock_io
)
2109 halfdelay(10); /* switch to once per second */
2111 err
= view_input(&new_view
, &done
, view
, &views
, fast_refresh
);
2115 if (view
->dying
&& view
== TAILQ_FIRST(&views
) &&
2116 TAILQ_NEXT(view
, entry
) == NULL
)
2122 * When we quit, scroll the screen up a single line
2123 * so we don't lose any information.
2125 TAILQ_FOREACH(v
, &views
, entry
) {
2126 wmove(v
->window
, 0, 0);
2127 wdeleteln(v
->window
);
2128 wnoutrefresh(v
->window
);
2129 if (v
->child
&& !view_is_fullscreen(v
)) {
2130 wmove(v
->child
->window
, 0, 0);
2131 wdeleteln(v
->child
->window
);
2132 wnoutrefresh(v
->child
->window
);
2139 struct tog_view
*v
, *prev
= NULL
;
2141 if (view_is_parent_view(view
))
2142 prev
= TAILQ_PREV(view
, tog_view_list_head
,
2144 else if (view
->parent
)
2145 prev
= view
->parent
;
2148 view
->parent
->child
= NULL
;
2149 view
->parent
->focus_child
= 0;
2150 /* Restore fullscreen line height. */
2151 view
->parent
->nlines
= view
->parent
->lines
;
2152 err
= view_resize(view
->parent
);
2155 /* Make resized splits persist. */
2156 view_transfer_size(view
->parent
, view
);
2158 TAILQ_REMOVE(&views
, view
, entry
);
2160 err
= view_close(view
);
2165 TAILQ_FOREACH(v
, &views
, entry
) {
2169 if (view
== NULL
&& new_view
== NULL
) {
2170 /* No view has focus. Try to pick one. */
2173 else if (!TAILQ_EMPTY(&views
)) {
2174 view
= TAILQ_LAST(&views
,
2175 tog_view_list_head
);
2178 if (view
->focus_child
) {
2179 view
->child
->focussed
= 1;
2187 struct tog_view
*v
, *t
;
2188 /* Only allow one parent view per type. */
2189 TAILQ_FOREACH_SAFE(v
, &views
, entry
, t
) {
2190 if (v
->type
!= new_view
->type
)
2192 TAILQ_REMOVE(&views
, v
, entry
);
2193 err
= view_close(v
);
2198 TAILQ_INSERT_TAIL(&views
, new_view
, entry
);
2201 if (view
&& !done
) {
2202 if (view_is_parent_view(view
)) {
2203 if (view
->child
&& view
->child
->focussed
)
2206 if (view
->parent
&& view
->parent
->focussed
)
2207 view
= view
->parent
;
2209 show_panel(view
->panel
);
2210 if (view
->child
&& view_is_splitscreen(view
->child
))
2211 show_panel(view
->child
->panel
);
2212 if (view
->parent
&& view_is_splitscreen(view
)) {
2213 err
= view
->parent
->show(view
->parent
);
2217 err
= view
->show(view
);
2221 err
= view
->child
->show(view
->child
);
2230 while (!TAILQ_EMPTY(&views
)) {
2231 const struct got_error
*close_err
;
2232 view
= TAILQ_FIRST(&views
);
2233 TAILQ_REMOVE(&views
, view
, entry
);
2234 close_err
= view_close(view
);
2235 if (close_err
&& err
== NULL
)
2239 errcode
= pthread_mutex_unlock(&tog_mutex
);
2240 if (errcode
&& err
== NULL
)
2241 err
= got_error_set_errno(errcode
, "pthread_mutex_unlock");
2251 "usage: %s log [-b] [-c commit] [-r repository-path] [path]\n",
2256 /* Create newly allocated wide-character string equivalent to a byte string. */
2257 static const struct got_error
*
2258 mbs2ws(wchar_t **ws
, size_t *wlen
, const char *s
)
2261 const struct got_error
*err
= NULL
;
2264 *wlen
= mbstowcs(NULL
, s
, 0);
2265 if (*wlen
== (size_t)-1) {
2267 if (errno
!= EILSEQ
)
2268 return got_error_from_errno("mbstowcs");
2270 /* byte string invalid in current encoding; try to "fix" it */
2271 err
= got_mbsavis(&vis
, &vislen
, s
);
2274 *wlen
= mbstowcs(NULL
, vis
, 0);
2275 if (*wlen
== (size_t)-1) {
2276 err
= got_error_from_errno("mbstowcs"); /* give up */
2281 *ws
= calloc(*wlen
+ 1, sizeof(**ws
));
2283 err
= got_error_from_errno("calloc");
2287 if (mbstowcs(*ws
, vis
? vis
: s
, *wlen
) != *wlen
)
2288 err
= got_error_from_errno("mbstowcs");
2299 static const struct got_error
*
2300 expand_tab(char **ptr
, const char *src
)
2303 size_t len
, n
, idx
= 0, sz
= 0;
2306 n
= len
= strlen(src
);
2307 dst
= malloc(n
+ 1);
2309 return got_error_from_errno("malloc");
2311 while (idx
< len
&& src
[idx
]) {
2312 const char c
= src
[idx
];
2315 size_t nb
= TABSIZE
- sz
% TABSIZE
;
2318 p
= realloc(dst
, n
+ nb
);
2321 return got_error_from_errno("realloc");
2326 memset(dst
+ sz
, ' ', nb
);
2329 dst
[sz
++] = src
[idx
];
2339 * Advance at most n columns from wline starting at offset off.
2340 * Return the index to the first character after the span operation.
2341 * Return the combined column width of all spanned wide characters in
2345 span_wline(int *rcol
, int off
, wchar_t *wline
, int n
, int col_tab_align
)
2347 int width
, i
, cols
= 0;
2354 for (i
= off
; wline
[i
] != L
'\0'; ++i
) {
2355 if (wline
[i
] == L
'\t')
2356 width
= TABSIZE
- ((cols
+ col_tab_align
) % TABSIZE
);
2358 width
= wcwidth(wline
[i
]);
2365 if (cols
+ width
> n
)
2375 * Format a line for display, ensuring that it won't overflow a width limit.
2376 * With scrolling, the width returned refers to the scrolled version of the
2377 * line, which starts at (*wlinep)[*scrollxp]. The caller must free *wlinep.
2379 static const struct got_error
*
2380 format_line(wchar_t **wlinep
, int *widthp
, int *scrollxp
,
2381 const char *line
, int nscroll
, int wlimit
, int col_tab_align
, int expand
)
2383 const struct got_error
*err
= NULL
;
2385 wchar_t *wline
= NULL
;
2394 err
= expand_tab(&exstr
, line
);
2399 err
= mbs2ws(&wline
, &wlen
, expand
? exstr
: line
);
2404 if (wlen
> 0 && wline
[wlen
- 1] == L
'\n') {
2405 wline
[wlen
- 1] = L
'\0';
2408 if (wlen
> 0 && wline
[wlen
- 1] == L
'\r') {
2409 wline
[wlen
- 1] = L
'\0';
2413 scrollx
= span_wline(&cols
, 0, wline
, nscroll
, col_tab_align
);
2415 i
= span_wline(&cols
, scrollx
, wline
, wlimit
, col_tab_align
);
2421 *scrollxp
= scrollx
;
2429 static const struct got_error
*
2430 build_refs_str(char **refs_str
, struct got_reflist_head
*refs
,
2431 struct got_object_id
*id
, struct got_repository
*repo
)
2433 static const struct got_error
*err
= NULL
;
2434 struct got_reflist_entry
*re
;
2443 TAILQ_FOREACH(re
, refs
, entry
) {
2444 struct got_tag_object
*tag
= NULL
;
2445 struct got_object_id
*ref_id
;
2448 name
= got_ref_get_name(re
->ref
);
2449 if (strcmp(name
, GOT_REF_HEAD
) == 0)
2451 if (strncmp(name
, "refs/", 5) == 0)
2453 if (strncmp(name
, "got/", 4) == 0)
2455 if (strncmp(name
, "heads/", 6) == 0)
2457 if (strncmp(name
, "remotes/", 8) == 0) {
2459 s
= strstr(name
, "/" GOT_REF_HEAD
);
2460 if (s
!= NULL
&& strcmp(s
, "/" GOT_REF_HEAD
) == 0)
2463 err
= got_ref_resolve(&ref_id
, repo
, re
->ref
);
2466 if (strncmp(name
, "tags/", 5) == 0) {
2467 err
= got_object_open_as_tag(&tag
, repo
, ref_id
);
2469 if (err
->code
!= GOT_ERR_OBJ_TYPE
) {
2473 /* Ref points at something other than a tag. */
2478 cmp
= got_object_id_cmp(tag
?
2479 got_object_tag_get_object_id(tag
) : ref_id
, id
);
2482 got_object_tag_close(tag
);
2486 if (asprintf(refs_str
, "%s%s%s", s
? s
: "",
2487 s
? ", " : "", name
) == -1) {
2488 err
= got_error_from_errno("asprintf");
2499 static const struct got_error
*
2500 format_author(wchar_t **wauthor
, int *author_width
, char *author
, int limit
,
2505 smallerthan
= strchr(author
, '<');
2506 if (smallerthan
&& smallerthan
[1] != '\0')
2507 author
= smallerthan
+ 1;
2508 author
[strcspn(author
, "@>")] = '\0';
2509 return format_line(wauthor
, author_width
, NULL
, author
, 0, limit
,
2513 static const struct got_error
*
2514 draw_commit_marker(struct tog_view
*view
, char c
)
2516 struct tog_color
*tc
;
2518 if (view
->type
!= TOG_VIEW_LOG
)
2519 return got_error_msg(GOT_ERR_NOT_IMPL
, "view not supported");
2521 tc
= get_color(&view
->state
.log
.colors
, TOG_COLOR_COMMIT
);
2523 wattr_on(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2524 if (waddch(view
->window
, c
) == ERR
)
2525 return got_error_msg(GOT_ERR_IO
, "waddch");
2527 wattr_off(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2533 tog_waddwstr(struct tog_view
*view
, wchar_t *wstr
, int width
,
2534 int *col
, int color
, int toeol
)
2536 struct tog_color
*tc
;
2539 x
= col
!= NULL
? *col
: getcurx(view
->window
);
2540 tc
= color
> 0 ? get_color(&view
->state
.log
.colors
, color
) : NULL
;
2543 wattr_on(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2544 waddwstr(view
->window
, wstr
);
2547 while (x
< view
->ncols
) {
2548 waddch(view
->window
, ' ');
2553 wattr_off(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2559 tog_waddnstr(struct tog_view
*view
, const char *str
, int limit
, int color
)
2561 struct tog_color
*tc
;
2564 limit
= view
->ncols
- getcurx(view
->window
);
2566 tc
= get_color(&view
->state
.log
.colors
, color
);
2568 wattr_on(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2569 waddnstr(view
->window
, str
, limit
);
2571 wattr_off(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2574 static const struct got_error
*
2575 draw_author(struct tog_view
*view
, char *author
, int author_display_cols
,
2576 int limit
, int *col
, int color
, int marker_column
,
2577 struct commit_queue_entry
*entry
)
2579 const struct got_error
*err
;
2580 struct tog_log_view_state
*s
= &view
->state
.log
;
2581 struct tog_color
*tc
;
2585 err
= format_author(&wauthor
, &author_width
, author
, limit
, *col
);
2588 if ((tc
= get_color(&s
->colors
, color
)) != NULL
)
2589 wattr_on(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2590 waddwstr(view
->window
, wauthor
);
2593 *col
+= author_width
;
2594 while (*col
< limit
&& author_width
< author_display_cols
+ 2) {
2595 if (entry
!= NULL
&& s
->marked_entry
== entry
&&
2596 author_width
== marker_column
) {
2597 err
= draw_commit_marker(view
, '>');
2600 } else if (entry
!= NULL
&&
2601 tog_base_commit
.marker
!= GOT_WORKTREE_STATE_UNKNOWN
&&
2602 author_width
== marker_column
&&
2603 entry
->idx
== tog_base_commit
.idx
&& !s
->limit_view
) {
2604 err
= draw_commit_marker(view
, tog_base_commit
.marker
);
2608 waddch(view
->window
, ' ');
2613 wattr_off(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
2618 static const struct got_error
*
2619 draw_idstr(struct tog_view
*view
, const char *id_str
, int color
)
2623 if (strlen(id_str
) > 9 && asprintf(&str
, "%.8s ", id_str
) == -1)
2624 return got_error_from_errno("asprintf");
2626 tog_waddnstr(view
, str
!= NULL
? str
: id_str
, 0, color
);
2631 static const struct got_error
*
2632 draw_ymd(struct tog_view
*view
, time_t t
, int *limit
, int avail
,
2633 int date_display_cols
)
2636 char datebuf
[12]; /* YYYY-MM-DD + SPACE + NUL */
2638 if (gmtime_r(&t
, &tm
) == NULL
)
2639 return got_error_from_errno("gmtime_r");
2640 if (strftime(datebuf
, sizeof(datebuf
), "%F ", &tm
) == 0)
2641 return got_error(GOT_ERR_NO_SPACE
);
2643 if (avail
<= date_display_cols
)
2644 *limit
= MIN(sizeof(datebuf
) - 1, avail
);
2646 *limit
= MIN(date_display_cols
, sizeof(datebuf
) - 1);
2648 tog_waddnstr(view
, datebuf
, *limit
, TOG_COLOR_DATE
);
2652 static const struct got_error
*
2653 draw_worktree_entry(struct tog_view
*view
, int wt_entry
,
2654 const size_t date_display_cols
, int author_display_cols
)
2656 const struct got_error
*err
= NULL
;
2657 struct tog_log_view_state
*s
= &view
->state
.log
;
2658 wchar_t *wmsg
= NULL
;
2659 char *author
, *msg
= NULL
;
2660 char *base_commit_id
= NULL
;
2661 const char *p
= TOG_WORKTREE_CHANGES_LOCAL_MSG
;
2662 int col
, limit
, scrollx
, width
;
2663 const int avail
= view
->ncols
;
2665 err
= draw_ymd(view
, time(NULL
), &col
, avail
, date_display_cols
);
2671 err
= draw_idstr(view
, "........ ", TOG_COLOR_COMMIT
);
2679 author
= strdup(s
->thread_args
.wctx
.wt_author
);
2681 return got_error_from_errno("strdup");
2683 err
= draw_author(view
, author
, author_display_cols
, avail
- col
,
2684 &col
, TOG_COLOR_AUTHOR
, 0, NULL
);
2690 err
= got_object_id_str(&base_commit_id
, tog_base_commit
.id
);
2693 if (wt_entry
& TOG_WORKTREE_CHANGES_STAGED
)
2694 p
= TOG_WORKTREE_CHANGES_STAGED_MSG
;
2695 if (asprintf(&msg
, "%s based on [%.10s]", p
, base_commit_id
) == -1) {
2696 err
= got_error_from_errno("asprintf");
2700 limit
= avail
- col
;
2701 if (view
->child
!= NULL
&& !view_is_hsplit_top(view
) && limit
> 0)
2702 limit
--; /* for the border */
2704 err
= format_line(&wmsg
, &width
, &scrollx
, msg
, view
->x
, limit
, col
, 1);
2707 tog_waddwstr(view
, &wmsg
[scrollx
], width
, &col
, 0, 1);
2713 free(base_commit_id
);
2717 static const struct got_error
*
2718 draw_commit(struct tog_view
*view
, struct commit_queue_entry
*entry
,
2719 const size_t date_display_cols
, int author_display_cols
)
2721 struct tog_log_view_state
*s
= &view
->state
.log
;
2722 const struct got_error
*err
= NULL
;
2723 struct got_commit_object
*commit
= entry
->commit
;
2724 struct got_object_id
*id
= entry
->id
;
2725 char *author
, *newline
, *logmsg
, *logmsg0
= NULL
, *refs_str
= NULL
;
2726 wchar_t *wrefstr
= NULL
, *wlogmsg
= NULL
;
2727 int refstr_width
, logmsg_width
, col
, limit
, scrollx
, logmsg_x
;
2728 const int avail
= view
->ncols
, marker_column
= author_display_cols
+ 1;
2729 time_t committer_time
;
2730 struct got_reflist_head
*refs
;
2732 if (tog_base_commit
.id
!= NULL
&& tog_base_commit
.idx
== -1 &&
2733 got_object_id_cmp(id
, tog_base_commit
.id
) == 0)
2734 tog_base_commit
.idx
= entry
->idx
;
2735 if (tog_io
.wait_for_ui
&& s
->thread_args
.need_commit_marker
) {
2738 rc
= pthread_cond_wait(&s
->thread_args
.log_loaded
, &tog_mutex
);
2740 return got_error_set_errno(rc
, "pthread_cond_wait");
2743 committer_time
= got_object_commit_get_committer_time(commit
);
2744 err
= draw_ymd(view
, committer_time
, &col
, avail
, date_display_cols
);
2753 err
= got_object_id_str(&id_str
, id
);
2756 err
= draw_idstr(view
, id_str
, TOG_COLOR_COMMIT
);
2765 if (s
->use_committer
)
2766 author
= strdup(got_object_commit_get_committer(commit
));
2768 author
= strdup(got_object_commit_get_author(commit
));
2770 return got_error_from_errno("strdup");
2772 err
= draw_author(view
, author
, author_display_cols
,
2773 avail
- col
, &col
, TOG_COLOR_AUTHOR
, marker_column
, entry
);
2779 err
= got_object_commit_get_logmsg(&logmsg0
, commit
);
2783 while (*logmsg
== '\n')
2785 newline
= strchr(logmsg
, '\n');
2789 limit
= avail
- col
;
2790 if (view
->child
&& !view_is_hsplit_top(view
) && limit
> 0)
2791 limit
--; /* for the border */
2793 /* Prepend reference labels to log message if possible .*/
2794 refs
= got_reflist_object_id_map_lookup(tog_refs_idmap
, id
);
2795 err
= build_refs_str(&refs_str
, refs
, id
, s
->repo
);
2801 if (asprintf(&rs
, "[%s]", refs_str
) == -1) {
2802 err
= got_error_from_errno("asprintf");
2805 err
= format_line(&wrefstr
, &refstr_width
,
2806 &scrollx
, rs
, view
->x
, limit
, col
, 1);
2810 tog_waddwstr(view
, &wrefstr
[scrollx
], refstr_width
,
2811 &col
, TOG_COLOR_COMMIT
, 0);
2816 waddch(view
->window
, ' ');
2820 if (refstr_width
> 0)
2823 int unscrolled_refstr_width
;
2824 size_t len
= wcslen(wrefstr
);
2827 * No need to check for -1 return value here since
2828 * unprintables have been replaced by span_wline().
2830 unscrolled_refstr_width
= wcswidth(wrefstr
, len
);
2831 unscrolled_refstr_width
+= 1; /* trailing space */
2832 logmsg_x
= view
->x
- unscrolled_refstr_width
;
2835 limit
= avail
- col
;
2836 if (view
->child
&& !view_is_hsplit_top(view
) && limit
> 0)
2837 limit
--; /* for the border */
2841 err
= format_line(&wlogmsg
, &logmsg_width
, &scrollx
, logmsg
, logmsg_x
,
2845 tog_waddwstr(view
, &wlogmsg
[scrollx
], logmsg_width
, &col
, 0, 1);
2856 static struct commit_queue_entry
*
2857 alloc_commit_queue_entry(struct got_commit_object
*commit
,
2858 struct got_object_id
*id
)
2860 struct commit_queue_entry
*entry
;
2861 struct got_object_id
*dup
;
2863 entry
= calloc(1, sizeof(*entry
));
2867 dup
= got_object_id_dup(id
);
2874 entry
->commit
= commit
;
2879 pop_commit(struct commit_queue
*commits
)
2881 struct commit_queue_entry
*entry
;
2883 entry
= TAILQ_FIRST(&commits
->head
);
2884 TAILQ_REMOVE(&commits
->head
, entry
, entry
);
2885 if (entry
->worktree_entry
== 0)
2886 got_object_commit_close(entry
->commit
);
2887 commits
->ncommits
--;
2893 free_commits(struct commit_queue
*commits
)
2895 while (!TAILQ_EMPTY(&commits
->head
))
2896 pop_commit(commits
);
2899 static const struct got_error
*
2900 match_commit(int *have_match
, struct got_object_id
*id
,
2901 struct got_commit_object
*commit
, regex_t
*regex
)
2903 const struct got_error
*err
= NULL
;
2904 regmatch_t regmatch
;
2905 char *id_str
= NULL
, *logmsg
= NULL
;
2909 err
= got_object_id_str(&id_str
, id
);
2913 err
= got_object_commit_get_logmsg(&logmsg
, commit
);
2917 if (regexec(regex
, got_object_commit_get_author(commit
), 1,
2918 ®match
, 0) == 0 ||
2919 regexec(regex
, got_object_commit_get_committer(commit
), 1,
2920 ®match
, 0) == 0 ||
2921 regexec(regex
, id_str
, 1, ®match
, 0) == 0 ||
2922 regexec(regex
, logmsg
, 1, ®match
, 0) == 0)
2930 static const struct got_error
*
2931 queue_commits(struct tog_log_thread_args
*a
)
2933 const struct got_error
*err
= NULL
;
2936 * We keep all commits open throughout the lifetime of the log
2937 * view in order to avoid having to re-fetch commits from disk
2938 * while updating the display.
2941 struct got_object_id id
;
2942 struct got_commit_object
*commit
;
2943 struct commit_queue_entry
*entry
;
2944 int limit_match
= 0;
2947 err
= got_commit_graph_iter_next(&id
, a
->graph
, a
->repo
,
2952 err
= got_object_open_as_commit(&commit
, a
->repo
, &id
);
2955 entry
= alloc_commit_queue_entry(commit
, &id
);
2956 if (entry
== NULL
) {
2957 err
= got_error_from_errno("alloc_commit_queue_entry");
2961 errcode
= pthread_mutex_lock(&tog_mutex
);
2963 err
= got_error_set_errno(errcode
,
2964 "pthread_mutex_lock");
2968 entry
->idx
= a
->real_commits
->ncommits
;
2969 TAILQ_INSERT_TAIL(&a
->real_commits
->head
, entry
, entry
);
2970 a
->real_commits
->ncommits
++;
2973 err
= match_commit(&limit_match
, &id
, commit
,
2979 struct commit_queue_entry
*matched
;
2981 matched
= alloc_commit_queue_entry(
2982 entry
->commit
, entry
->id
);
2983 if (matched
== NULL
) {
2984 err
= got_error_from_errno(
2985 "alloc_commit_queue_entry");
2988 matched
->commit
= entry
->commit
;
2989 got_object_commit_retain(entry
->commit
);
2991 matched
->idx
= a
->limit_commits
->ncommits
;
2992 TAILQ_INSERT_TAIL(&a
->limit_commits
->head
,
2994 a
->limit_commits
->ncommits
++;
2998 * This is how we signal log_thread() that we
2999 * have found a match, and that it should be
3000 * counted as a new entry for the view.
3002 a
->limit_match
= limit_match
;
3005 if (*a
->searching
== TOG_SEARCH_FORWARD
&&
3006 !*a
->search_next_done
) {
3008 err
= match_commit(&have_match
, &id
, commit
, a
->regex
);
3013 if (limit_match
&& have_match
)
3014 *a
->search_next_done
=
3015 TOG_SEARCH_HAVE_MORE
;
3016 } else if (have_match
)
3017 *a
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
3020 errcode
= pthread_mutex_unlock(&tog_mutex
);
3021 if (errcode
&& err
== NULL
)
3022 err
= got_error_set_errno(errcode
,
3023 "pthread_mutex_unlock");
3026 } while (*a
->searching
== TOG_SEARCH_FORWARD
&& !*a
->search_next_done
);
3032 select_commit(struct tog_log_view_state
*s
)
3034 struct commit_queue_entry
*entry
;
3037 entry
= s
->first_displayed_entry
;
3039 if (ncommits
== s
->selected
) {
3040 s
->selected_entry
= entry
;
3043 entry
= TAILQ_NEXT(entry
, entry
);
3048 /* lifted from got.c:652 (TODO make lib routine) */
3049 static const struct got_error
*
3050 get_author(char **author
, struct got_repository
*repo
,
3051 struct got_worktree
*worktree
)
3053 const struct got_error
*err
= NULL
;
3054 const char *got_author
= NULL
, *name
, *email
;
3055 const struct got_gotconfig
*worktree_conf
= NULL
, *repo_conf
= NULL
;
3060 worktree_conf
= got_worktree_get_gotconfig(worktree
);
3061 repo_conf
= got_repo_get_gotconfig(repo
);
3064 * Priority of potential author information sources, from most
3065 * significant to least significant:
3066 * 1) work tree's .got/got.conf file
3067 * 2) repository's got.conf file
3068 * 3) repository's git config file
3069 * 4) environment variables
3070 * 5) global git config files (in user's home directory or /etc)
3074 got_author
= got_gotconfig_get_author(worktree_conf
);
3075 if (got_author
== NULL
)
3076 got_author
= got_gotconfig_get_author(repo_conf
);
3077 if (got_author
== NULL
) {
3078 name
= got_repo_get_gitconfig_author_name(repo
);
3079 email
= got_repo_get_gitconfig_author_email(repo
);
3080 if (name
&& email
) {
3081 if (asprintf(author
, "%s <%s>", name
, email
) == -1)
3082 return got_error_from_errno("asprintf");
3086 got_author
= getenv("GOT_AUTHOR");
3087 if (got_author
== NULL
) {
3088 name
= got_repo_get_global_gitconfig_author_name(repo
);
3089 email
= got_repo_get_global_gitconfig_author_email(
3091 if (name
&& email
) {
3092 if (asprintf(author
, "%s <%s>", name
, email
)
3094 return got_error_from_errno("asprintf");
3097 /* TODO: Look up user in password database? */
3098 return got_error(GOT_ERR_COMMIT_NO_AUTHOR
);
3102 *author
= strdup(got_author
);
3103 if (*author
== NULL
)
3104 err
= got_error_from_errno("strdup");
3108 static const struct got_error
*
3109 push_worktree_entry(struct tog_log_thread_args
*ta
, int wt_entry
,
3110 struct got_worktree
*worktree
)
3112 struct commit_queue_entry
*e
, *entry
;
3115 entry
= calloc(1, sizeof(*entry
));
3117 return got_error_from_errno("calloc");
3120 entry
->worktree_entry
= wt_entry
;
3122 rc
= pthread_mutex_lock(&tog_mutex
);
3125 return got_error_set_errno(rc
, "pthread_mutex_lock");
3128 TAILQ_FOREACH(e
, &ta
->real_commits
->head
, entry
)
3131 TAILQ_INSERT_HEAD(&ta
->real_commits
->head
, entry
, entry
);
3132 ta
->wctx
.wt_state
|= wt_entry
;
3133 ++ta
->real_commits
->ncommits
;
3134 ++tog_base_commit
.idx
;
3136 rc
= pthread_mutex_unlock(&tog_mutex
);
3138 return got_error_set_errno(rc
, "pthread_mutex_unlock");
3143 static const struct got_error
*
3144 check_cancelled(void *arg
)
3146 if (tog_sigint_received
|| tog_sigpipe_received
)
3147 return got_error(GOT_ERR_CANCELLED
);
3151 static const struct got_error
*
3152 check_local_changes(void *arg
, unsigned char status
,
3153 unsigned char staged_status
, const char *path
,
3154 struct got_object_id
*blob_id
, struct got_object_id
*staged_blob_id
,
3155 struct got_object_id
*commit_id
, int dirfd
, const char *de_name
)
3157 int *have_local_changes
= arg
;
3160 case GOT_STATUS_ADD
:
3161 case GOT_STATUS_DELETE
:
3162 case GOT_STATUS_MODIFY
:
3163 case GOT_STATUS_CONFLICT
:
3164 *have_local_changes
|= TOG_WORKTREE_CHANGES_LOCAL
;
3169 switch (staged_status
) {
3170 case GOT_STATUS_ADD
:
3171 case GOT_STATUS_DELETE
:
3172 case GOT_STATUS_MODIFY
:
3173 *have_local_changes
|= TOG_WORKTREE_CHANGES_STAGED
;
3181 static const struct got_error
*
3182 tog_worktree_status(struct tog_log_thread_args
*ta
)
3184 const struct got_error
*err
, *close_err
;
3185 struct tog_worktree_ctx
*wctx
= &ta
->wctx
;
3186 struct got_worktree
*wt
= ta
->worktree
;
3187 struct got_pathlist_head paths
;
3194 cwd
= getcwd(NULL
, 0);
3196 return got_error_from_errno("getcwd");
3198 err
= got_worktree_open(&wt
, cwd
, NULL
);
3200 if (err
->code
== GOT_ERR_NOT_WORKTREE
) {
3202 * Shouldn't happen; this routine should only
3203 * be called if tog is invoked in a worktree.
3207 } else if (err
->code
== GOT_ERR_WORKTREE_BUSY
)
3208 err
= NULL
; /* retry next redraw */
3213 err
= got_pathlist_insert(NULL
, &paths
, "", NULL
);
3217 err
= got_worktree_status(wt
, &paths
, ta
->repo
, 0,
3218 check_local_changes
, &wt_state
, check_cancelled
, NULL
);
3220 if (err
->code
!= GOT_ERR_CANCELLED
)
3225 if (wt_state
!= 0) {
3226 err
= get_author(&wctx
->wt_author
, ta
->repo
, wt
);
3228 if (err
->code
!= GOT_ERR_COMMIT_NO_AUTHOR
)
3230 if ((wctx
->wt_author
= strdup("")) == NULL
) {
3231 err
= got_error_from_errno("strdup");
3236 wctx
->wt_root
= strdup(got_worktree_get_root_path(wt
));
3237 if (wctx
->wt_root
== NULL
) {
3238 err
= got_error_from_errno("strdup");
3242 wctx
->wt_ref
= strdup(got_worktree_get_head_ref_name(wt
));
3243 if (wctx
->wt_ref
== NULL
) {
3244 err
= got_error_from_errno("strdup");
3250 * Push staged entry first so it's the second log entry
3251 * if there are both staged and unstaged work tree changes.
3253 if (wt_state
& TOG_WORKTREE_CHANGES_STAGED
&&
3254 (wctx
->wt_state
& TOG_WORKTREE_CHANGES_STAGED
) == 0) {
3255 err
= push_worktree_entry(ta
, TOG_WORKTREE_CHANGES_STAGED
, wt
);
3259 if (wt_state
& TOG_WORKTREE_CHANGES_LOCAL
&&
3260 (wctx
->wt_state
& TOG_WORKTREE_CHANGES_LOCAL
) == 0) {
3261 err
= push_worktree_entry(ta
, TOG_WORKTREE_CHANGES_LOCAL
, wt
);
3267 got_pathlist_free(&paths
, GOT_PATHLIST_FREE_NONE
);
3268 if (ta
->worktree
== NULL
&& wt
!= NULL
) {
3269 close_err
= got_worktree_close(wt
);
3270 if (close_err
!= NULL
&& err
== NULL
)
3277 static const struct got_error
*
3278 worktree_headref_str(char **ret
, const char *ref
)
3280 if (strncmp(ref
, "refs/heads/", 11) == 0)
3281 *ret
= strdup(ref
+ 11);
3285 return got_error_from_errno("strdup");
3290 static const struct got_error
*
3291 fmtindex(char **index
, int *ncommits
, int wt_state
,
3292 struct commit_queue_entry
*entry
, int limit_view
)
3297 if (*ncommits
> 0 && wt_state
& TOG_WORKTREE_CHANGES_LOCAL
)
3299 if (*ncommits
> 0 && wt_state
& TOG_WORKTREE_CHANGES_STAGED
)
3303 if (entry
!= NULL
&& entry
->worktree_entry
== 0) {
3305 * Display 1-based index of selected commit entries only.
3306 * If a work tree entry is selected, show an index of 0.
3309 if (wt_state
== 0 || limit_view
)
3311 else if (wt_state
> TOG_WORKTREE_CHANGES_STAGED
)
3314 if (asprintf(index
, " [%d/%d] ", idx
, *ncommits
) == -1) {
3316 return got_error_from_errno("asprintf");
3322 static const struct got_error
*
3323 fmtheader(char **header
, int *ncommits
, struct commit_queue_entry
*entry
,
3324 struct tog_view
*view
)
3326 const struct got_error
*err
;
3327 struct tog_log_view_state
*s
= &view
->state
.log
;
3328 struct tog_worktree_ctx
*wctx
= &s
->thread_args
.wctx
;
3329 struct got_reflist_head
*refs
;
3330 char *id_str
= NULL
, *index
= NULL
;
3331 char *wthdr
= NULL
, *ncommits_str
= NULL
;
3332 char *refs_str
= NULL
;
3336 wt_entry
= entry
!= NULL
? entry
->worktree_entry
: 0;
3338 if (entry
&& !(view
->searching
&& view
->search_next_done
== 0)) {
3339 if (entry
->worktree_entry
== 0) {
3340 err
= got_object_id_str(&id_str
, entry
->id
);
3343 refs
= got_reflist_object_id_map_lookup(tog_refs_idmap
,
3345 err
= build_refs_str(&refs_str
, refs
,
3346 entry
->id
, s
->repo
);
3350 err
= worktree_headref_str(&refs_str
, wctx
->wt_ref
);
3356 err
= fmtindex(&index
, ncommits
, wctx
->wt_state
, entry
, s
->limit_view
);
3360 if (s
->thread_args
.commits_needed
> 0 || s
->thread_args
.load_all
) {
3361 if (asprintf(&ncommits_str
, "%s%s", index
,
3362 (view
->searching
&& !view
->search_next_done
) ?
3363 "searching..." : "loading...") == -1) {
3364 err
= got_error_from_errno("asprintf");
3368 const char *search_str
= NULL
;
3369 const char *limit_str
= NULL
;
3371 if (view
->searching
) {
3372 if (view
->search_next_done
== TOG_SEARCH_NO_MORE
)
3373 search_str
= "no more matches";
3374 else if (view
->search_next_done
== TOG_SEARCH_HAVE_NONE
)
3375 search_str
= "no matches found";
3376 else if (!view
->search_next_done
)
3377 search_str
= "searching...";
3380 if (s
->limit_view
&& ncommits
== 0)
3381 limit_str
= "no matches found";
3383 if (asprintf(&ncommits_str
, "%s%s %s", index
,
3384 search_str
? search_str
: (refs_str
? refs_str
: ""),
3385 limit_str
? limit_str
: "") == -1) {
3386 err
= got_error_from_errno("asprintf");
3391 if (wt_entry
!= 0) {
3392 const char *t
= "", *p
= TOG_WORKTREE_CHANGES_LOCAL_MSG
;
3394 if (wt_entry
== TOG_WORKTREE_CHANGES_STAGED
) {
3395 p
= TOG_WORKTREE_CHANGES_STAGED_MSG
;
3398 if (asprintf(&wthdr
, "%s%s (%s)", t
, wctx
->wt_root
, p
) == -1) {
3399 err
= got_error_from_errno("asprintf");
3404 if (s
->in_repo_path
!= NULL
&& strcmp(s
->in_repo_path
, "/") != 0) {
3405 if (asprintf(header
, "%s%s %s%s",
3406 wt_entry
== 0 ? "commit " : "diff ",
3407 wt_entry
== 0 ? id_str
? id_str
:
3408 "........................................" :
3409 wthdr
!= NULL
? wthdr
: "", s
->in_repo_path
,
3410 ncommits_str
) == -1)
3411 err
= got_error_from_errno("asprintf");
3412 } else if (asprintf(header
, "%s%s%s",
3413 wt_entry
== 0 ? "commit " : "diff ",
3414 wt_entry
== 0 ? id_str
? id_str
:
3415 "........................................" :
3416 wthdr
!= NULL
? wthdr
: "", ncommits_str
) == -1)
3417 err
= got_error_from_errno("asprintf");
3430 static const struct got_error
*
3431 draw_commits(struct tog_view
*view
)
3433 const struct got_error
*err
;
3434 struct tog_log_view_state
*s
= &view
->state
.log
;
3435 struct commit_queue_entry
*entry
= s
->selected_entry
;
3436 int width
, limit
= view
->nlines
;
3437 int ncommits
= s
->commits
->ncommits
, author_cols
= 4, refstr_cols
;
3440 static const size_t date_display_cols
= 12;
3442 if (view_is_hsplit_top(view
))
3443 --limit
; /* account for border */
3445 if (s
->thread_args
.commits_needed
== 0 &&
3446 s
->thread_args
.need_wt_status
== 0 &&
3447 s
->thread_args
.need_commit_marker
== 0 && !using_mock_io
)
3448 halfdelay(10); /* disable fast refresh */
3450 err
= fmtheader(&header
, &ncommits
, entry
, view
);
3454 err
= format_line(&wline
, &width
, NULL
, header
, 0, view
->ncols
, 0, 0);
3459 werase(view
->window
);
3461 if (view_needs_focus_indication(view
))
3462 wstandout(view
->window
);
3463 tog_waddwstr(view
, wline
, width
, NULL
, TOG_COLOR_COMMIT
, 1);
3464 if (view_needs_focus_indication(view
))
3465 wstandend(view
->window
);
3470 /* Grow author column size if necessary, and set view->maxx. */
3471 entry
= s
->first_displayed_entry
;
3475 struct got_reflist_head
*refs
;
3476 struct got_commit_object
*c
= entry
->commit
;
3477 char *author
, *eol
, *msg
, *msg0
, *refs_str
;
3478 wchar_t *wauthor
, *wmsg
;
3481 if (ncommits
>= limit
- 1)
3483 if (entry
->worktree_entry
!= 0)
3484 author
= strdup(s
->thread_args
.wctx
.wt_author
);
3485 else if (s
->use_committer
)
3486 author
= strdup(got_object_commit_get_committer(c
));
3488 author
= strdup(got_object_commit_get_author(c
));
3490 return got_error_from_errno("strdup");
3492 err
= format_author(&wauthor
, &width
, author
, COLS
,
3494 if (author_cols
< width
)
3495 author_cols
= width
;
3500 if (entry
->worktree_entry
!= 0) {
3501 if (entry
->worktree_entry
== TOG_WORKTREE_CHANGES_LOCAL
)
3502 width
= sizeof(TOG_WORKTREE_CHANGES_LOCAL_MSG
);
3504 width
= sizeof(TOG_WORKTREE_CHANGES_STAGED_MSG
);
3505 view
->maxx
= MAX(view
->maxx
, width
- 1);
3506 entry
= TAILQ_NEXT(entry
, entry
);
3510 refs
= got_reflist_object_id_map_lookup(tog_refs_idmap
,
3512 err
= build_refs_str(&refs_str
, refs
, entry
->id
, s
->repo
);
3518 err
= format_line(&ws
, &width
, NULL
, refs_str
,
3519 0, INT_MAX
, date_display_cols
+ author_cols
, 0);
3525 refstr_cols
= width
+ 3; /* account for [ ] + space */
3528 err
= got_object_commit_get_logmsg(&msg0
, c
);
3532 while (*msg
== '\n')
3534 if ((eol
= strchr(msg
, '\n')))
3536 err
= format_line(&wmsg
, &width
, NULL
, msg
, 0, INT_MAX
,
3537 date_display_cols
+ author_cols
+ refstr_cols
, 0);
3542 view
->maxx
= MAX(view
->maxx
, width
+ refstr_cols
);
3544 entry
= TAILQ_NEXT(entry
, entry
);
3547 entry
= s
->first_displayed_entry
;
3548 s
->last_displayed_entry
= s
->first_displayed_entry
;
3551 if (ncommits
>= limit
- 1)
3553 if (ncommits
== s
->selected
)
3554 wstandout(view
->window
);
3555 if (entry
->worktree_entry
== 0)
3556 err
= draw_commit(view
, entry
,
3557 date_display_cols
, author_cols
);
3559 err
= draw_worktree_entry(view
, entry
->worktree_entry
,
3560 date_display_cols
, author_cols
);
3561 if (ncommits
== s
->selected
)
3562 wstandend(view
->window
);
3566 s
->last_displayed_entry
= entry
;
3567 entry
= TAILQ_NEXT(entry
, entry
);
3575 log_scroll_up(struct tog_log_view_state
*s
, int maxscroll
)
3577 struct commit_queue_entry
*entry
;
3580 entry
= TAILQ_FIRST(&s
->commits
->head
);
3581 if (s
->first_displayed_entry
== entry
)
3584 entry
= s
->first_displayed_entry
;
3585 while (entry
&& nscrolled
< maxscroll
) {
3586 entry
= TAILQ_PREV(entry
, commit_queue_head
, entry
);
3588 s
->first_displayed_entry
= entry
;
3594 static const struct got_error
*
3595 trigger_log_thread(struct tog_view
*view
, int wait
)
3597 const struct got_error
*err
;
3598 struct tog_log_thread_args
*ta
= &view
->state
.log
.thread_args
;
3602 halfdelay(1); /* fast refresh while loading commits */
3604 while (!ta
->log_complete
&& !tog_thread_error
&&
3605 (ta
->commits_needed
> 0 || ta
->load_all
)) {
3606 /* Wake the log thread. */
3607 errcode
= pthread_cond_signal(&ta
->need_commits
);
3609 return got_error_set_errno(errcode
,
3610 "pthread_cond_signal");
3613 * The mutex will be released while the view loop waits
3614 * in wgetch(), at which time the log thread will run.
3619 /* Display progress update in log view. */
3620 err
= show_log_view(view
);
3626 /* Wait right here while next commit is being loaded. */
3627 errcode
= pthread_cond_wait(&ta
->commit_loaded
, &tog_mutex
);
3629 return got_error_set_errno(errcode
,
3630 "pthread_cond_wait");
3632 /* Display progress update in log view. */
3633 err
= show_log_view(view
);
3643 static const struct got_error
*
3644 request_log_commits(struct tog_view
*view
)
3646 struct tog_log_view_state
*state
= &view
->state
.log
;
3647 const struct got_error
*err
= NULL
;
3649 if (state
->thread_args
.log_complete
)
3652 state
->thread_args
.commits_needed
+= view
->nscrolled
;
3653 err
= trigger_log_thread(view
, 1);
3654 view
->nscrolled
= 0;
3659 static const struct got_error
*
3660 log_scroll_down(struct tog_view
*view
, int maxscroll
)
3662 struct tog_log_view_state
*s
= &view
->state
.log
;
3663 const struct got_error
*err
= NULL
;
3664 struct commit_queue_entry
*pentry
;
3665 int nscrolled
= 0, ncommits_needed
;
3667 if (s
->last_displayed_entry
== NULL
)
3670 ncommits_needed
= s
->last_displayed_entry
->idx
+ 2 + maxscroll
;
3671 if (s
->commits
->ncommits
< ncommits_needed
&&
3672 !s
->thread_args
.log_complete
) {
3674 * Ask the log thread for required amount of commits.
3676 s
->thread_args
.commits_needed
+=
3677 ncommits_needed
- s
->commits
->ncommits
;
3678 err
= trigger_log_thread(view
, 1);
3684 pentry
= TAILQ_NEXT(s
->last_displayed_entry
, entry
);
3685 if (pentry
== NULL
&& view
->mode
!= TOG_VIEW_SPLIT_HRZN
)
3688 s
->last_displayed_entry
= pentry
?
3689 pentry
: s
->last_displayed_entry
;
3691 pentry
= TAILQ_NEXT(s
->first_displayed_entry
, entry
);
3694 s
->first_displayed_entry
= pentry
;
3695 } while (++nscrolled
< maxscroll
);
3697 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
&& !s
->thread_args
.log_complete
)
3698 view
->nscrolled
+= nscrolled
;
3700 view
->nscrolled
= 0;
3705 static const struct got_error
*
3706 open_diff_view_for_commit(struct tog_view
**new_view
, int begin_y
, int begin_x
,
3707 struct commit_queue_entry
*entry
, struct tog_view
*log_view
,
3708 struct got_repository
*repo
)
3710 const struct got_error
*err
;
3711 struct got_object_qid
*p
;
3712 struct got_object_id
*parent_id
;
3713 struct tog_view
*diff_view
;
3714 struct tog_log_view_state
*ls
= NULL
;
3715 const char *worktree_root
= NULL
;
3717 diff_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_DIFF
);
3718 if (diff_view
== NULL
)
3719 return got_error_from_errno("view_open");
3721 if (log_view
!= NULL
) {
3722 ls
= &log_view
->state
.log
;
3723 worktree_root
= ls
->thread_args
.wctx
.wt_root
;
3726 if (ls
!= NULL
&& ls
->marked_entry
!= NULL
&&
3727 ls
->marked_entry
!= ls
->selected_entry
)
3728 parent_id
= ls
->marked_entry
->id
;
3729 else if (entry
->worktree_entry
== 0 &&
3730 (p
= STAILQ_FIRST(got_object_commit_get_parent_ids(entry
->commit
))))
3735 err
= open_diff_view(diff_view
, parent_id
, entry
->id
, NULL
, NULL
, 3, 0,
3736 0, 0, entry
->worktree_entry
, worktree_root
, log_view
, repo
, NULL
);
3738 *new_view
= diff_view
;
3742 static const struct got_error
*
3743 tree_view_visit_subtree(struct tog_tree_view_state
*s
,
3744 struct got_tree_object
*subtree
)
3746 struct tog_parent_tree
*parent
;
3748 parent
= calloc(1, sizeof(*parent
));
3750 return got_error_from_errno("calloc");
3752 parent
->tree
= s
->tree
;
3753 parent
->first_displayed_entry
= s
->first_displayed_entry
;
3754 parent
->selected_entry
= s
->selected_entry
;
3755 parent
->selected
= s
->selected
;
3756 TAILQ_INSERT_HEAD(&s
->parents
, parent
, entry
);
3759 s
->first_displayed_entry
= NULL
;
3763 static const struct got_error
*
3764 tree_view_walk_path(struct tog_tree_view_state
*s
,
3765 struct got_commit_object
*commit
, const char *path
)
3767 const struct got_error
*err
= NULL
;
3768 struct got_tree_object
*tree
= NULL
;
3770 char *slash
, *subpath
= NULL
;
3772 /* Walk the path and open corresponding tree objects. */
3775 struct got_tree_entry
*te
;
3776 struct got_object_id
*tree_id
;
3782 /* Ensure the correct subtree entry is selected. */
3783 slash
= strchr(p
, '/');
3785 te_name
= strdup(p
);
3787 te_name
= strndup(p
, slash
- p
);
3788 if (te_name
== NULL
) {
3789 err
= got_error_from_errno("strndup");
3792 te
= got_object_tree_find_entry(s
->tree
, te_name
);
3794 err
= got_error_path(te_name
, GOT_ERR_NO_TREE_ENTRY
);
3799 s
->first_displayed_entry
= s
->selected_entry
= te
;
3801 if (!S_ISDIR(got_tree_entry_get_mode(s
->selected_entry
)))
3802 break; /* jump to this file's entry */
3804 slash
= strchr(p
, '/');
3806 subpath
= strndup(path
, slash
- path
);
3808 subpath
= strdup(path
);
3809 if (subpath
== NULL
) {
3810 err
= got_error_from_errno("strdup");
3814 err
= got_object_id_by_path(&tree_id
, s
->repo
, commit
,
3819 err
= got_object_open_as_tree(&tree
, s
->repo
, tree_id
);
3824 err
= tree_view_visit_subtree(s
, tree
);
3826 got_object_tree_close(tree
);
3840 static const struct got_error
*
3841 browse_commit_tree(struct tog_view
**new_view
, int begin_y
, int begin_x
,
3842 struct commit_queue_entry
*entry
, const char *path
,
3843 const char *head_ref_name
, struct got_repository
*repo
)
3845 const struct got_error
*err
= NULL
;
3846 struct tog_tree_view_state
*s
;
3847 struct tog_view
*tree_view
;
3848 struct got_commit_object
*commit
= NULL
;
3849 struct got_object_id
*commit_id
;
3853 if (entry
->id
!= NULL
)
3854 commit_id
= entry
->id
;
3855 else if (entry
->worktree_entry
)
3856 commit_id
= tog_base_commit
.id
;
3857 else /* cannot happen */
3858 return got_error(GOT_ERR_NOT_WORKTREE
);
3860 tree_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_TREE
);
3861 if (tree_view
== NULL
)
3862 return got_error_from_errno("view_open");
3864 err
= open_tree_view(tree_view
, commit_id
, head_ref_name
, repo
);
3867 s
= &tree_view
->state
.tree
;
3869 *new_view
= tree_view
;
3871 if (got_path_is_root_dir(path
))
3874 if (entry
->worktree_entry
) {
3875 err
= got_object_open_as_commit(&commit
, repo
, commit_id
);
3880 err
= tree_view_walk_path(s
, commit
? commit
: entry
->commit
, path
);
3884 got_object_commit_close(commit
);
3886 view_close(tree_view
);
3893 * If work tree entries have been pushed onto the commit queue and the
3894 * first commit entry is still displayed, scroll the view so the new
3895 * work tree entries are visible. If the selection cursor is still on
3896 * the first commit entry, keep the cursor in place such that the first
3897 * work tree entry is selected, otherwise move the selection cursor so
3898 * the currently selected commit stays selected if it remains on screen.
3901 worktree_entries_reveal(struct tog_log_thread_args
*a
)
3903 struct commit_queue_entry
**first
= a
->first_displayed_entry
;
3904 struct commit_queue_entry
**select
= a
->selected_entry
;
3905 int *cursor
= a
->selected
;
3906 int wts
= a
->wctx
.wt_state
;
3908 #define select_worktree_entry(_first, _selected) do { \
3909 *_first = TAILQ_FIRST(&a->real_commits->head); \
3910 *_selected = *_first; \
3914 select_worktree_entry(first
, select
);
3915 else if (*select
== *first
) {
3916 if (wts
== TOG_WORKTREE_CHANGES_LOCAL
&& (*first
)->idx
== 1)
3917 select_worktree_entry(first
, select
);
3918 else if (wts
== TOG_WORKTREE_CHANGES_STAGED
&&
3920 select_worktree_entry(first
, select
);
3921 else if (wts
& TOG_WORKTREE_CHANGES_ALL
&& (*first
)->idx
== 2)
3922 select_worktree_entry(first
, select
);
3923 } else if (wts
& TOG_WORKTREE_CHANGES_ALL
&& (*first
)->idx
== 2) {
3924 *first
= TAILQ_FIRST(&a
->real_commits
->head
);
3925 if (*cursor
+ 2 < *a
->view_nlines
- 1)
3927 else if (*cursor
+ 1 < *a
->view_nlines
- 1) {
3928 *select
= TAILQ_PREV(*select
, commit_queue_head
, entry
);
3931 *select
= TAILQ_PREV(*select
, commit_queue_head
, entry
);
3932 *select
= TAILQ_PREV(*select
, commit_queue_head
, entry
);
3934 } else if (wts
!= 0 && (*first
)->idx
== 1) {
3935 *first
= TAILQ_FIRST(&a
->real_commits
->head
);
3936 if (*cursor
+ 1 < *a
->view_nlines
- 1)
3939 *select
= TAILQ_PREV(*select
, commit_queue_head
, entry
);
3941 #undef select_worktree_entry
3944 static const struct got_error
*
3945 block_signals_used_by_main_thread(void)
3950 if (sigemptyset(&sigset
) == -1)
3951 return got_error_from_errno("sigemptyset");
3953 /* tog handles SIGWINCH, SIGCONT, SIGINT, SIGTERM */
3954 if (sigaddset(&sigset
, SIGWINCH
) == -1)
3955 return got_error_from_errno("sigaddset");
3956 if (sigaddset(&sigset
, SIGCONT
) == -1)
3957 return got_error_from_errno("sigaddset");
3958 if (sigaddset(&sigset
, SIGINT
) == -1)
3959 return got_error_from_errno("sigaddset");
3960 if (sigaddset(&sigset
, SIGTERM
) == -1)
3961 return got_error_from_errno("sigaddset");
3963 /* ncurses handles SIGTSTP */
3964 if (sigaddset(&sigset
, SIGTSTP
) == -1)
3965 return got_error_from_errno("sigaddset");
3967 errcode
= pthread_sigmask(SIG_BLOCK
, &sigset
, NULL
);
3969 return got_error_set_errno(errcode
, "pthread_sigmask");
3975 log_thread(void *arg
)
3977 const struct got_error
*err
= NULL
;
3979 struct tog_log_thread_args
*a
= arg
;
3983 * Sync startup with main thread such that we begin our
3984 * work once view_input() has released the mutex.
3986 errcode
= pthread_mutex_lock(&tog_mutex
);
3988 err
= got_error_set_errno(errcode
, "pthread_mutex_lock");
3992 err
= block_signals_used_by_main_thread();
3994 pthread_mutex_unlock(&tog_mutex
);
3998 while (!done
&& !err
&& !tog_fatal_signal_received()) {
3999 errcode
= pthread_mutex_unlock(&tog_mutex
);
4001 err
= got_error_set_errno(errcode
,
4002 "pthread_mutex_unlock");
4005 err
= queue_commits(a
);
4007 if (err
->code
!= GOT_ERR_ITER_COMPLETED
)
4011 a
->commits_needed
= 0;
4012 } else if (a
->commits_needed
> 0 && !a
->load_all
) {
4015 a
->commits_needed
--;
4017 a
->commits_needed
--;
4020 errcode
= pthread_mutex_lock(&tog_mutex
);
4022 err
= got_error_set_errno(errcode
,
4023 "pthread_mutex_lock");
4025 } else if (*a
->quit
)
4027 else if (*a
->limiting
&& *a
->first_displayed_entry
== NULL
) {
4028 *a
->first_displayed_entry
=
4029 TAILQ_FIRST(&a
->limit_commits
->head
);
4030 *a
->selected_entry
= *a
->first_displayed_entry
;
4031 } else if (*a
->first_displayed_entry
== NULL
) {
4032 *a
->first_displayed_entry
=
4033 TAILQ_FIRST(&a
->real_commits
->head
);
4034 *a
->selected_entry
= *a
->first_displayed_entry
;
4037 a
->log_complete
= 1;
4039 errcode
= pthread_cond_signal(&a
->commit_loaded
);
4041 err
= got_error_set_errno(errcode
,
4042 "pthread_cond_signal");
4043 pthread_mutex_unlock(&tog_mutex
);
4047 if (a
->commits_needed
== 0 && a
->need_wt_status
) {
4048 errcode
= pthread_mutex_unlock(&tog_mutex
);
4050 err
= got_error_set_errno(errcode
,
4051 "pthread_mutex_unlock");
4054 err
= tog_worktree_status(a
);
4057 errcode
= pthread_mutex_lock(&tog_mutex
);
4059 err
= got_error_set_errno(errcode
,
4060 "pthread_mutex_lock");
4063 if (a
->wctx
.wt_state
!= 0)
4064 worktree_entries_reveal(a
);
4065 a
->need_wt_status
= 0;
4068 if (a
->commits_needed
== 0 &&
4069 a
->need_commit_marker
&& a
->worktree
) {
4070 errcode
= pthread_mutex_unlock(&tog_mutex
);
4072 err
= got_error_set_errno(errcode
,
4073 "pthread_mutex_unlock");
4076 err
= got_worktree_get_state(&tog_base_commit
.marker
,
4077 a
->repo
, a
->worktree
, NULL
, NULL
);
4080 errcode
= pthread_mutex_lock(&tog_mutex
);
4082 err
= got_error_set_errno(errcode
,
4083 "pthread_mutex_lock");
4086 a
->need_commit_marker
= 0;
4088 * The main thread did not close this
4089 * work tree yet. Close it now.
4091 got_worktree_close(a
->worktree
);
4099 a
->commits_needed
= 0;
4101 if (a
->commits_needed
== 0 && !a
->load_all
) {
4102 if (tog_io
.wait_for_ui
) {
4103 errcode
= pthread_cond_signal(
4106 err
= got_error_set_errno(
4108 "pthread_cond_signal");
4109 pthread_mutex_unlock(
4115 errcode
= pthread_cond_wait(&a
->need_commits
,
4118 err
= got_error_set_errno(errcode
,
4119 "pthread_cond_wait");
4120 pthread_mutex_unlock(&tog_mutex
);
4128 a
->log_complete
= 1;
4129 if (tog_io
.wait_for_ui
) {
4130 errcode
= pthread_cond_signal(&a
->log_loaded
);
4131 if (errcode
&& err
== NULL
)
4132 err
= got_error_set_errno(errcode
,
4133 "pthread_cond_signal");
4136 errcode
= pthread_mutex_unlock(&tog_mutex
);
4138 err
= got_error_set_errno(errcode
, "pthread_mutex_unlock");
4141 tog_thread_error
= 1;
4142 pthread_cond_signal(&a
->commit_loaded
);
4144 got_worktree_close(a
->worktree
);
4151 static const struct got_error
*
4152 stop_log_thread(struct tog_log_view_state
*s
)
4154 const struct got_error
*err
= NULL
, *thread_err
= NULL
;
4159 errcode
= pthread_cond_signal(&s
->thread_args
.need_commits
);
4161 return got_error_set_errno(errcode
,
4162 "pthread_cond_signal");
4163 errcode
= pthread_mutex_unlock(&tog_mutex
);
4165 return got_error_set_errno(errcode
,
4166 "pthread_mutex_unlock");
4167 errcode
= pthread_join(s
->thread
, (void **)&thread_err
);
4169 return got_error_set_errno(errcode
, "pthread_join");
4170 errcode
= pthread_mutex_lock(&tog_mutex
);
4172 return got_error_set_errno(errcode
,
4173 "pthread_mutex_lock");
4174 s
->thread
= 0; //NULL;
4177 if (s
->thread_args
.repo
) {
4178 err
= got_repo_close(s
->thread_args
.repo
);
4179 s
->thread_args
.repo
= NULL
;
4182 if (s
->thread_args
.pack_fds
) {
4183 const struct got_error
*pack_err
=
4184 got_repo_pack_fds_close(s
->thread_args
.pack_fds
);
4187 s
->thread_args
.pack_fds
= NULL
;
4190 if (s
->thread_args
.graph
) {
4191 got_commit_graph_close(s
->thread_args
.graph
);
4192 s
->thread_args
.graph
= NULL
;
4195 return err
? err
: thread_err
;
4199 worktree_ctx_close(struct tog_log_thread_args
*ta
)
4201 struct tog_worktree_ctx
*wctx
= &ta
->wctx
;
4204 free(wctx
->wt_author
);
4205 wctx
->wt_author
= NULL
;
4206 free(wctx
->wt_root
);
4207 wctx
->wt_root
= NULL
;
4209 wctx
->wt_ref
= NULL
;
4211 ta
->need_wt_status
= 1;
4215 static const struct got_error
*
4216 close_log_view(struct tog_view
*view
)
4218 const struct got_error
*err
= NULL
;
4219 struct tog_log_view_state
*s
= &view
->state
.log
;
4224 err
= stop_log_thread(s
);
4226 errcode
= pthread_cond_destroy(&s
->thread_args
.need_commits
);
4227 if (errcode
&& err
== NULL
)
4228 err
= got_error_set_errno(errcode
, "pthread_cond_destroy");
4230 errcode
= pthread_cond_destroy(&s
->thread_args
.commit_loaded
);
4231 if (errcode
&& err
== NULL
)
4232 err
= got_error_set_errno(errcode
, "pthread_cond_destroy");
4234 if (using_mock_io
) {
4235 errcode
= pthread_cond_destroy(&s
->thread_args
.log_loaded
);
4236 if (errcode
&& err
== NULL
)
4237 err
= got_error_set_errno(errcode
,
4238 "pthread_cond_destroy");
4241 free_commits(&s
->limit_commits
);
4242 free_commits(&s
->real_commits
);
4243 free_colors(&s
->colors
);
4244 free(s
->in_repo_path
);
4245 s
->in_repo_path
= NULL
;
4248 free(s
->head_ref_name
);
4249 s
->head_ref_name
= NULL
;
4250 worktree_ctx_close(&s
->thread_args
);
4255 * We use two queues to implement the limit feature: first consists of
4256 * commits matching the current limit_regex; second is the real queue
4257 * of all known commits (real_commits). When the user starts limiting,
4258 * we swap queues such that all movement and displaying functionality
4259 * works with very slight change.
4261 static const struct got_error
*
4262 limit_log_view(struct tog_view
*view
)
4264 struct tog_log_view_state
*s
= &view
->state
.log
;
4265 struct commit_queue_entry
*entry
;
4266 struct tog_view
*v
= view
;
4267 const struct got_error
*err
= NULL
;
4271 if (view_is_hsplit_top(view
))
4273 else if (view
->mode
== TOG_VIEW_SPLIT_VERT
&& view
->parent
)
4276 if (tog_io
.input_str
!= NULL
) {
4277 if (strlcpy(pattern
, tog_io
.input_str
, sizeof(pattern
)) >=
4279 return got_error(GOT_ERR_NO_SPACE
);
4281 wmove(v
->window
, v
->nlines
- 1, 0);
4282 wclrtoeol(v
->window
);
4283 mvwaddstr(v
->window
, v
->nlines
- 1, 0, "&/");
4284 nodelay(v
->window
, FALSE
);
4287 ret
= wgetnstr(v
->window
, pattern
, sizeof(pattern
));
4290 nodelay(v
->window
, TRUE
);
4295 if (*pattern
== '\0') {
4297 * Safety measure for the situation where the user
4298 * resets limit without previously limiting anything.
4304 * User could have pressed Ctrl+L, which refreshed the
4305 * commit queues, it means we can't save previously
4306 * (before limit took place) displayed entries,
4307 * because they would point to already free'ed memory,
4308 * so we are forced to always select first entry of
4311 s
->commits
= &s
->real_commits
;
4312 s
->first_displayed_entry
= TAILQ_FIRST(&s
->real_commits
.head
);
4313 s
->selected_entry
= s
->first_displayed_entry
;
4320 if (regcomp(&s
->limit_regex
, pattern
, REG_EXTENDED
| REG_NEWLINE
))
4325 /* Clear the screen while loading limit view */
4326 s
->first_displayed_entry
= NULL
;
4327 s
->last_displayed_entry
= NULL
;
4328 s
->selected_entry
= NULL
;
4329 s
->commits
= &s
->limit_commits
;
4331 /* Prepare limit queue for new search */
4332 free_commits(&s
->limit_commits
);
4333 s
->limit_commits
.ncommits
= 0;
4335 /* First process commits, which are in queue already */
4336 TAILQ_FOREACH(entry
, &s
->real_commits
.head
, entry
) {
4339 if (entry
->worktree_entry
== 0) {
4340 err
= match_commit(&have_match
, entry
->id
,
4341 entry
->commit
, &s
->limit_regex
);
4347 struct commit_queue_entry
*matched
;
4349 matched
= alloc_commit_queue_entry(entry
->commit
,
4351 if (matched
== NULL
) {
4352 err
= got_error_from_errno(
4353 "alloc_commit_queue_entry");
4356 matched
->commit
= entry
->commit
;
4357 got_object_commit_retain(entry
->commit
);
4359 matched
->idx
= s
->limit_commits
.ncommits
;
4360 TAILQ_INSERT_TAIL(&s
->limit_commits
.head
,
4362 s
->limit_commits
.ncommits
++;
4366 /* Second process all the commits, until we fill the screen */
4367 if (s
->limit_commits
.ncommits
< view
->nlines
- 1 &&
4368 !s
->thread_args
.log_complete
) {
4369 s
->thread_args
.commits_needed
+=
4370 view
->nlines
- s
->limit_commits
.ncommits
- 1;
4371 err
= trigger_log_thread(view
, 1);
4376 s
->first_displayed_entry
= TAILQ_FIRST(&s
->commits
->head
);
4377 s
->selected_entry
= TAILQ_FIRST(&s
->commits
->head
);
4383 static const struct got_error
*
4384 search_start_log_view(struct tog_view
*view
)
4386 struct tog_log_view_state
*s
= &view
->state
.log
;
4388 s
->matched_entry
= NULL
;
4389 s
->search_entry
= NULL
;
4393 static const struct got_error
*
4394 search_next_log_view(struct tog_view
*view
)
4396 const struct got_error
*err
= NULL
;
4397 struct tog_log_view_state
*s
= &view
->state
.log
;
4398 struct commit_queue_entry
*entry
;
4400 /* Display progress update in log view. */
4401 err
= show_log_view(view
);
4407 if (s
->search_entry
) {
4408 if (!using_mock_io
) {
4411 errcode
= pthread_mutex_unlock(&tog_mutex
);
4413 return got_error_set_errno(errcode
,
4414 "pthread_mutex_unlock");
4415 ch
= wgetch(view
->window
);
4416 errcode
= pthread_mutex_lock(&tog_mutex
);
4418 return got_error_set_errno(errcode
,
4419 "pthread_mutex_lock");
4420 if (ch
== CTRL('g') || ch
== KEY_BACKSPACE
) {
4421 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
4425 if (view
->searching
== TOG_SEARCH_FORWARD
)
4426 entry
= TAILQ_NEXT(s
->search_entry
, entry
);
4428 entry
= TAILQ_PREV(s
->search_entry
,
4429 commit_queue_head
, entry
);
4430 } else if (s
->matched_entry
) {
4432 * If the user has moved the cursor after we hit a match,
4433 * the position from where we should continue searching
4434 * might have changed.
4436 if (view
->searching
== TOG_SEARCH_FORWARD
)
4437 entry
= TAILQ_NEXT(s
->selected_entry
, entry
);
4439 entry
= TAILQ_PREV(s
->selected_entry
, commit_queue_head
,
4442 entry
= s
->selected_entry
;
4448 if (entry
== NULL
) {
4449 if (s
->thread_args
.log_complete
||
4450 view
->searching
== TOG_SEARCH_BACKWARD
) {
4451 view
->search_next_done
=
4452 (s
->matched_entry
== NULL
?
4453 TOG_SEARCH_HAVE_NONE
: TOG_SEARCH_NO_MORE
);
4454 s
->search_entry
= NULL
;
4458 * Poke the log thread for more commits and return,
4459 * allowing the main loop to make progress. Search
4460 * will resume at s->search_entry once we come back.
4462 s
->search_entry
= s
->selected_entry
;
4463 s
->thread_args
.commits_needed
++;
4464 return trigger_log_thread(view
, 0);
4467 if (entry
->worktree_entry
== 0) {
4468 err
= match_commit(&have_match
, entry
->id
,
4469 entry
->commit
, &view
->regex
);
4473 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
4474 s
->matched_entry
= entry
;
4479 s
->search_entry
= entry
;
4480 if (view
->searching
== TOG_SEARCH_FORWARD
)
4481 entry
= TAILQ_NEXT(entry
, entry
);
4483 entry
= TAILQ_PREV(entry
, commit_queue_head
, entry
);
4486 if (s
->matched_entry
) {
4487 int cur
= s
->selected_entry
->idx
;
4488 while (cur
< s
->matched_entry
->idx
) {
4489 err
= input_log_view(NULL
, view
, KEY_DOWN
);
4494 while (cur
> s
->matched_entry
->idx
) {
4495 err
= input_log_view(NULL
, view
, KEY_UP
);
4502 s
->search_entry
= NULL
;
4507 static const struct got_error
*
4508 open_log_view(struct tog_view
*view
, struct got_object_id
*start_id
,
4509 struct got_repository
*repo
, const char *head_ref_name
,
4510 const char *in_repo_path
, int log_branches
,
4511 struct got_worktree
*worktree
)
4513 const struct got_error
*err
= NULL
;
4514 struct tog_log_view_state
*s
= &view
->state
.log
;
4515 struct got_repository
*thread_repo
= NULL
;
4516 struct got_commit_graph
*thread_graph
= NULL
;
4519 if (in_repo_path
!= s
->in_repo_path
) {
4520 free(s
->in_repo_path
);
4521 s
->in_repo_path
= strdup(in_repo_path
);
4522 if (s
->in_repo_path
== NULL
) {
4523 err
= got_error_from_errno("strdup");
4528 /* The commit queue only contains commits being displayed. */
4529 TAILQ_INIT(&s
->real_commits
.head
);
4530 s
->real_commits
.ncommits
= 0;
4531 s
->commits
= &s
->real_commits
;
4533 TAILQ_INIT(&s
->limit_commits
.head
);
4535 s
->limit_commits
.ncommits
= 0;
4538 if (head_ref_name
) {
4539 s
->head_ref_name
= strdup(head_ref_name
);
4540 if (s
->head_ref_name
== NULL
) {
4541 err
= got_error_from_errno("strdup");
4545 s
->start_id
= got_object_id_dup(start_id
);
4546 if (s
->start_id
== NULL
) {
4547 err
= got_error_from_errno("got_object_id_dup");
4550 s
->log_branches
= log_branches
;
4551 s
->use_committer
= 1;
4553 STAILQ_INIT(&s
->colors
);
4554 if (has_colors() && getenv("TOG_COLORS") != NULL
) {
4555 err
= add_color(&s
->colors
, "^$", TOG_COLOR_COMMIT
,
4556 get_color_value("TOG_COLOR_COMMIT"));
4559 err
= add_color(&s
->colors
, "^$", TOG_COLOR_AUTHOR
,
4560 get_color_value("TOG_COLOR_AUTHOR"));
4563 err
= add_color(&s
->colors
, "^$", TOG_COLOR_DATE
,
4564 get_color_value("TOG_COLOR_DATE"));
4569 view
->show
= show_log_view
;
4570 view
->input
= input_log_view
;
4571 view
->resize
= resize_log_view
;
4572 view
->close
= close_log_view
;
4573 view
->search_start
= search_start_log_view
;
4574 view
->search_next
= search_next_log_view
;
4576 if (s
->thread_args
.pack_fds
== NULL
) {
4577 err
= got_repo_pack_fds_open(&s
->thread_args
.pack_fds
);
4581 err
= got_repo_open(&thread_repo
, got_repo_get_path(repo
), NULL
,
4582 s
->thread_args
.pack_fds
);
4585 err
= got_commit_graph_open(&thread_graph
, s
->in_repo_path
,
4589 err
= got_commit_graph_bfsort(thread_graph
, s
->start_id
,
4590 s
->repo
, NULL
, NULL
);
4594 errcode
= pthread_cond_init(&s
->thread_args
.need_commits
, NULL
);
4596 err
= got_error_set_errno(errcode
, "pthread_cond_init");
4599 errcode
= pthread_cond_init(&s
->thread_args
.commit_loaded
, NULL
);
4601 err
= got_error_set_errno(errcode
, "pthread_cond_init");
4605 if (using_mock_io
) {
4608 rc
= pthread_cond_init(&s
->thread_args
.log_loaded
, NULL
);
4610 return got_error_set_errno(rc
, "pthread_cond_init");
4613 s
->thread_args
.view_nlines
= &view
->nlines
;
4614 s
->thread_args
.commits_needed
= view
->nlines
;
4615 s
->thread_args
.graph
= thread_graph
;
4616 s
->thread_args
.real_commits
= &s
->real_commits
;
4617 s
->thread_args
.limit_commits
= &s
->limit_commits
;
4618 s
->thread_args
.in_repo_path
= s
->in_repo_path
;
4619 s
->thread_args
.start_id
= s
->start_id
;
4620 s
->thread_args
.repo
= thread_repo
;
4621 s
->thread_args
.log_complete
= 0;
4622 s
->thread_args
.quit
= &s
->quit
;
4623 s
->thread_args
.first_displayed_entry
= &s
->first_displayed_entry
;
4624 s
->thread_args
.last_displayed_entry
= &s
->last_displayed_entry
;
4625 s
->thread_args
.selected_entry
= &s
->selected_entry
;
4626 s
->thread_args
.selected
= &s
->selected
;
4627 s
->thread_args
.searching
= &view
->searching
;
4628 s
->thread_args
.search_next_done
= &view
->search_next_done
;
4629 s
->thread_args
.regex
= &view
->regex
;
4630 s
->thread_args
.limiting
= &s
->limit_view
;
4631 s
->thread_args
.limit_regex
= &s
->limit_regex
;
4632 s
->thread_args
.limit_commits
= &s
->limit_commits
;
4633 s
->thread_args
.worktree
= worktree
;
4635 s
->thread_args
.wctx
.active
= 1;
4636 s
->thread_args
.need_wt_status
= 1;
4637 s
->thread_args
.need_commit_marker
= 1;
4642 if (view
->close
== NULL
)
4643 close_log_view(view
);
4649 static const struct got_error
*
4650 show_log_view(struct tog_view
*view
)
4652 const struct got_error
*err
;
4653 struct tog_log_view_state
*s
= &view
->state
.log
;
4655 if (s
->thread
== 0) { //NULL) {
4656 int errcode
= pthread_create(&s
->thread
, NULL
, log_thread
,
4659 return got_error_set_errno(errcode
, "pthread_create");
4660 if (s
->thread_args
.commits_needed
> 0) {
4661 err
= trigger_log_thread(view
, 1);
4667 return draw_commits(view
);
4671 log_move_cursor_up(struct tog_view
*view
, int page
, int home
)
4673 struct tog_log_view_state
*s
= &view
->state
.log
;
4675 if (s
->first_displayed_entry
== NULL
)
4677 if (s
->selected_entry
->idx
== 0)
4680 if ((page
&& TAILQ_FIRST(&s
->commits
->head
) == s
->first_displayed_entry
)
4682 s
->selected
= home
? 0 : MAX(0, s
->selected
- page
- 1);
4684 if (!page
&& !home
&& s
->selected
> 0)
4687 log_scroll_up(s
, home
? s
->commits
->ncommits
: MAX(page
, 1));
4693 static const struct got_error
*
4694 log_move_cursor_down(struct tog_view
*view
, int page
)
4696 struct tog_log_view_state
*s
= &view
->state
.log
;
4697 const struct got_error
*err
= NULL
;
4698 int eos
= view
->nlines
- 2;
4700 if (s
->first_displayed_entry
== NULL
)
4703 if (s
->thread_args
.log_complete
&&
4704 s
->selected_entry
->idx
>= s
->commits
->ncommits
- 1)
4707 if (view_is_hsplit_top(view
))
4708 --eos
; /* border consumes the last line */
4711 if (s
->selected
< MIN(eos
, s
->commits
->ncommits
- 1))
4714 err
= log_scroll_down(view
, 1);
4715 } else if (s
->thread_args
.load_all
&& s
->thread_args
.log_complete
) {
4716 struct commit_queue_entry
*entry
;
4720 entry
= TAILQ_LAST(&s
->commits
->head
, commit_queue_head
);
4721 s
->last_displayed_entry
= entry
;
4722 for (n
= 0; n
<= eos
; n
++) {
4725 s
->first_displayed_entry
= entry
;
4726 entry
= TAILQ_PREV(entry
, commit_queue_head
, entry
);
4729 s
->selected
= n
- 1;
4731 if (s
->last_displayed_entry
->idx
== s
->commits
->ncommits
- 1 &&
4732 s
->thread_args
.log_complete
)
4733 s
->selected
+= MIN(page
,
4734 s
->commits
->ncommits
- s
->selected_entry
->idx
- 1);
4736 err
= log_scroll_down(view
, page
);
4742 * We might necessarily overshoot in horizontal
4743 * splits; if so, select the last displayed commit.
4745 if (view_is_hsplit_top(view
) && s
->first_displayed_entry
&&
4746 s
->last_displayed_entry
) {
4747 s
->selected
= MIN(s
->selected
,
4748 s
->last_displayed_entry
->idx
-
4749 s
->first_displayed_entry
->idx
);
4754 if (s
->thread_args
.log_complete
&&
4755 s
->selected_entry
->idx
== s
->commits
->ncommits
- 1)
4762 view_get_split(struct tog_view
*view
, int *y
, int *x
)
4767 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
) {
4768 if (view
->child
&& view
->child
->resized_y
)
4769 *y
= view
->child
->resized_y
;
4770 else if (view
->resized_y
)
4771 *y
= view
->resized_y
;
4773 *y
= view_split_begin_y(view
->lines
);
4774 } else if (view
->mode
== TOG_VIEW_SPLIT_VERT
) {
4775 if (view
->child
&& view
->child
->resized_x
)
4776 *x
= view
->child
->resized_x
;
4777 else if (view
->resized_x
)
4778 *x
= view
->resized_x
;
4780 *x
= view_split_begin_x(view
->begin_x
);
4784 /* Split view horizontally at y and offset view->state->selected line. */
4785 static const struct got_error
*
4786 view_init_hsplit(struct tog_view
*view
, int y
)
4788 const struct got_error
*err
= NULL
;
4792 err
= view_resize(view
);
4796 err
= offset_selection_down(view
);
4801 static const struct got_error
*
4802 log_goto_line(struct tog_view
*view
, int nlines
)
4804 const struct got_error
*err
= NULL
;
4805 struct tog_log_view_state
*s
= &view
->state
.log
;
4806 int g
, idx
= s
->selected_entry
->idx
;
4808 if (s
->first_displayed_entry
== NULL
|| s
->last_displayed_entry
== NULL
)
4814 if (g
>= s
->first_displayed_entry
->idx
+ 1 &&
4815 g
<= s
->last_displayed_entry
->idx
+ 1 &&
4816 g
- s
->first_displayed_entry
->idx
- 1 < nlines
) {
4817 s
->selected
= g
- s
->first_displayed_entry
->idx
- 1;
4823 err
= log_move_cursor_down(view
, g
- idx
- 1);
4824 if (!err
&& g
> s
->selected_entry
->idx
+ 1)
4825 err
= log_move_cursor_down(view
,
4826 g
- s
->first_displayed_entry
->idx
- 1);
4829 } else if (idx
+ 1 > g
)
4830 log_move_cursor_up(view
, idx
- g
+ 1, 0);
4832 if (g
< nlines
&& s
->first_displayed_entry
->idx
== 0)
4833 s
->selected
= g
- 1;
4841 horizontal_scroll_input(struct tog_view
*view
, int ch
)
4847 view
->x
-= MIN(view
->x
, 2);
4853 if (view
->x
+ view
->ncols
/ 2 < view
->maxx
)
4862 view
->x
= MAX(view
->maxx
- view
->ncols
/ 2, 0);
4871 log_mark_commit(struct tog_log_view_state
*s
)
4873 if (s
->selected_entry
== s
->marked_entry
)
4874 s
->marked_entry
= NULL
;
4876 s
->marked_entry
= s
->selected_entry
;
4879 static const struct got_error
*
4880 input_log_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
4882 const struct got_error
*err
= NULL
;
4883 struct tog_log_view_state
*s
= &view
->state
.log
;
4886 if (s
->thread_args
.load_all
) {
4887 if (ch
== CTRL('g') || ch
== KEY_BACKSPACE
)
4888 s
->thread_args
.load_all
= 0;
4889 else if (s
->thread_args
.log_complete
) {
4890 err
= log_move_cursor_down(view
, s
->commits
->ncommits
);
4891 s
->thread_args
.load_all
= 0;
4897 eos
= nscroll
= view
->nlines
- 1;
4898 if (view_is_hsplit_top(view
))
4902 return log_goto_line(view
, eos
);
4907 err
= limit_log_view(view
);
4918 horizontal_scroll_input(view
, ch
);
4925 log_move_cursor_up(view
, 0, 0);
4930 log_move_cursor_up(view
, 0, 1);
4940 log_move_cursor_up(view
, nscroll
, 0);
4947 err
= log_move_cursor_down(view
, 0);
4950 s
->use_committer
= !s
->use_committer
;
4951 view
->action
= s
->use_committer
?
4952 "show committer" : "show commit author";
4957 /* We don't know yet how many commits, so we're forced to
4958 * traverse them all. */
4960 s
->thread_args
.load_all
= 1;
4961 if (!s
->thread_args
.log_complete
)
4962 return trigger_log_thread(view
, 0);
4963 err
= log_move_cursor_down(view
, s
->commits
->ncommits
);
4964 s
->thread_args
.load_all
= 0;
4975 err
= log_move_cursor_down(view
, nscroll
);
4978 if (s
->selected
> view
->nlines
- 2)
4979 s
->selected
= view
->nlines
- 2;
4980 if (s
->selected
> s
->commits
->ncommits
- 1)
4981 s
->selected
= s
->commits
->ncommits
- 1;
4983 if (s
->commits
->ncommits
< view
->nlines
- 1 &&
4984 !s
->thread_args
.log_complete
) {
4985 s
->thread_args
.commits_needed
+= (view
->nlines
- 1) -
4986 s
->commits
->ncommits
;
4987 err
= trigger_log_thread(view
, 1);
4993 if (s
->selected_entry
== NULL
)
4995 err
= view_request_new(new_view
, view
, TOG_VIEW_DIFF
);
4999 if (s
->selected_entry
== NULL
)
5001 err
= view_request_new(new_view
, view
, TOG_VIEW_TREE
);
5007 if (ch
== KEY_BACKSPACE
&&
5008 got_path_is_root_dir(s
->in_repo_path
))
5010 err
= stop_log_thread(s
);
5013 if (ch
== KEY_BACKSPACE
) {
5015 err
= got_path_dirname(&parent_path
, s
->in_repo_path
);
5018 free(s
->in_repo_path
);
5019 s
->in_repo_path
= parent_path
;
5020 s
->thread_args
.in_repo_path
= s
->in_repo_path
;
5021 } else if (ch
== CTRL('l')) {
5022 struct got_object_id
*start_id
;
5023 err
= got_repo_match_object_id(&start_id
, NULL
,
5024 s
->head_ref_name
? s
->head_ref_name
: GOT_REF_HEAD
,
5025 GOT_OBJ_TYPE_COMMIT
, &tog_refs
, s
->repo
);
5027 if (s
->head_ref_name
== NULL
||
5028 err
->code
!= GOT_ERR_NOT_REF
)
5030 /* Try to cope with deleted references. */
5031 free(s
->head_ref_name
);
5032 s
->head_ref_name
= NULL
;
5033 err
= got_repo_match_object_id(&start_id
,
5034 NULL
, GOT_REF_HEAD
, GOT_OBJ_TYPE_COMMIT
,
5035 &tog_refs
, s
->repo
);
5040 s
->start_id
= start_id
;
5041 s
->thread_args
.start_id
= s
->start_id
;
5043 s
->log_branches
= !s
->log_branches
;
5045 if (s
->thread_args
.pack_fds
== NULL
) {
5046 err
= got_repo_pack_fds_open(&s
->thread_args
.pack_fds
);
5050 err
= got_repo_open(&s
->thread_args
.repo
,
5051 got_repo_get_path(s
->repo
), NULL
,
5052 s
->thread_args
.pack_fds
);
5056 err
= tog_load_refs(s
->repo
, 0);
5059 err
= got_commit_graph_open(&s
->thread_args
.graph
,
5060 s
->in_repo_path
, !s
->log_branches
);
5063 err
= got_commit_graph_bfsort(s
->thread_args
.graph
,
5064 s
->start_id
, s
->repo
, NULL
, NULL
);
5067 free_commits(&s
->real_commits
);
5068 free_commits(&s
->limit_commits
);
5069 s
->first_displayed_entry
= NULL
;
5070 s
->last_displayed_entry
= NULL
;
5071 s
->selected_entry
= NULL
;
5073 s
->thread_args
.log_complete
= 0;
5075 s
->thread_args
.commits_needed
= view
->lines
;
5076 s
->matched_entry
= NULL
;
5077 s
->search_entry
= NULL
;
5078 tog_base_commit
.idx
= -1;
5079 worktree_ctx_close(&s
->thread_args
);
5083 if (s
->selected_entry
->worktree_entry
== 0)
5088 err
= view_request_new(new_view
, view
, TOG_VIEW_REF
);
5098 static const struct got_error
*
5099 apply_unveil(const char *repo_path
, const char *worktree_path
)
5101 const struct got_error
*error
;
5104 if (unveil("gmon.out", "rwc") != 0)
5105 return got_error_from_errno2("unveil", "gmon.out");
5107 if (repo_path
&& unveil(repo_path
, "r") != 0)
5108 return got_error_from_errno2("unveil", repo_path
);
5110 if (worktree_path
&& unveil(worktree_path
, "rwc") != 0)
5111 return got_error_from_errno2("unveil", worktree_path
);
5113 if (unveil(GOT_TMPDIR_STR
, "rwc") != 0)
5114 return got_error_from_errno2("unveil", GOT_TMPDIR_STR
);
5116 error
= got_privsep_unveil_exec_helpers();
5120 if (unveil(NULL
, NULL
) != 0)
5121 return got_error_from_errno("unveil");
5126 static const struct got_error
*
5127 init_mock_term(const char *test_script_path
)
5129 const struct got_error
*err
= NULL
;
5130 const char *screen_dump_path
;
5133 if (test_script_path
== NULL
|| *test_script_path
== '\0')
5134 return got_error_msg(GOT_ERR_IO
, "TOG_TEST_SCRIPT not defined");
5136 tog_io
.f
= fopen(test_script_path
, "re");
5137 if (tog_io
.f
== NULL
) {
5138 err
= got_error_from_errno_fmt("fopen: %s",
5143 /* test mode, we don't want any output */
5144 tog_io
.cout
= fopen("/dev/null", "w+");
5145 if (tog_io
.cout
== NULL
) {
5146 err
= got_error_from_errno2("fopen", "/dev/null");
5150 in
= dup(fileno(tog_io
.cout
));
5152 err
= got_error_from_errno("dup");
5155 tog_io
.cin
= fdopen(in
, "r");
5156 if (tog_io
.cin
== NULL
) {
5157 err
= got_error_from_errno("fdopen");
5162 screen_dump_path
= getenv("TOG_SCR_DUMP");
5163 if (screen_dump_path
== NULL
|| *screen_dump_path
== '\0')
5164 return got_error_msg(GOT_ERR_IO
, "TOG_SCR_DUMP not defined");
5165 tog_io
.sdump
= fopen(screen_dump_path
, "we");
5166 if (tog_io
.sdump
== NULL
) {
5167 err
= got_error_from_errno2("fopen", screen_dump_path
);
5171 if (fseeko(tog_io
.f
, 0L, SEEK_SET
) == -1) {
5172 err
= got_error_from_errno("fseeko");
5176 if (newterm(NULL
, tog_io
.cout
, tog_io
.cin
) == NULL
)
5177 err
= got_error_msg(GOT_ERR_IO
,
5178 "newterm: failed to initialise curses");
5191 if (using_mock_io
) /* In test mode we use a fake terminal */
5197 halfdelay(1); /* Fast refresh while initial view is loading. */
5200 intrflush(stdscr
, FALSE
);
5201 keypad(stdscr
, TRUE
);
5203 if (getenv("TOG_COLORS") != NULL
) {
5205 use_default_colors();
5211 static const struct got_error
*
5212 set_tog_base_commit(struct got_repository
*repo
, struct got_worktree
*worktree
)
5214 tog_base_commit
.id
= got_object_id_dup(
5215 got_worktree_get_base_commit_id(worktree
));
5216 if (tog_base_commit
.id
== NULL
)
5217 return got_error_from_errno( "got_object_id_dup");
5222 static const struct got_error
*
5223 get_in_repo_path_from_argv0(char **in_repo_path
, int argc
, char *argv
[],
5224 struct got_repository
*repo
, struct got_worktree
*worktree
)
5226 const struct got_error
*err
= NULL
;
5229 *in_repo_path
= strdup("/");
5230 if (*in_repo_path
== NULL
)
5231 return got_error_from_errno("strdup");
5236 const char *prefix
= got_worktree_get_path_prefix(worktree
);
5239 err
= got_worktree_resolve_path(&p
, worktree
, argv
[0]);
5242 if (asprintf(in_repo_path
, "%s%s%s", prefix
,
5243 (p
[0] != '\0' && !got_path_is_root_dir(prefix
)) ? "/" : "",
5245 err
= got_error_from_errno("asprintf");
5246 *in_repo_path
= NULL
;
5250 err
= got_repo_map_path(in_repo_path
, repo
, argv
[0]);
5255 static const struct got_error
*
5256 cmd_log(int argc
, char *argv
[])
5258 const struct got_error
*error
;
5259 struct got_repository
*repo
= NULL
;
5260 struct got_worktree
*worktree
= NULL
;
5261 struct got_object_id
*start_id
= NULL
;
5262 char *in_repo_path
= NULL
, *repo_path
= NULL
, *cwd
= NULL
;
5263 char *keyword_idstr
= NULL
, *start_commit
= NULL
, *label
= NULL
;
5264 struct got_reference
*ref
= NULL
;
5265 const char *head_ref_name
= NULL
;
5266 int ch
, log_branches
= 0;
5267 struct tog_view
*view
;
5268 int *pack_fds
= NULL
;
5270 while ((ch
= getopt(argc
, argv
, "bc:r:")) != -1) {
5276 start_commit
= optarg
;
5279 repo_path
= realpath(optarg
, NULL
);
5280 if (repo_path
== NULL
)
5281 return got_error_from_errno2("realpath",
5296 error
= got_repo_pack_fds_open(&pack_fds
);
5300 if (repo_path
== NULL
) {
5301 cwd
= getcwd(NULL
, 0);
5303 error
= got_error_from_errno("getcwd");
5306 error
= got_worktree_open(&worktree
, cwd
, NULL
);
5307 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
5311 strdup(got_worktree_get_repo_path(worktree
));
5313 repo_path
= strdup(cwd
);
5314 if (repo_path
== NULL
) {
5315 error
= got_error_from_errno("strdup");
5320 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
5324 error
= get_in_repo_path_from_argv0(&in_repo_path
, argc
, argv
,
5331 error
= apply_unveil(got_repo_get_path(repo
),
5332 worktree
? got_worktree_get_root_path(worktree
) : NULL
);
5336 /* already loaded by tog_log_with_path()? */
5337 if (TAILQ_EMPTY(&tog_refs
)) {
5338 error
= tog_load_refs(repo
, 0);
5343 if (start_commit
== NULL
) {
5344 error
= got_repo_match_object_id(&start_id
, &label
,
5345 worktree
? got_worktree_get_head_ref_name(worktree
) :
5346 GOT_REF_HEAD
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
5349 head_ref_name
= label
;
5351 error
= got_keyword_to_idstr(&keyword_idstr
, start_commit
,
5355 if (keyword_idstr
!= NULL
)
5356 start_commit
= keyword_idstr
;
5358 error
= got_ref_open(&ref
, repo
, start_commit
, 0);
5360 head_ref_name
= got_ref_get_name(ref
);
5361 else if (error
->code
!= GOT_ERR_NOT_REF
)
5363 error
= got_repo_match_object_id(&start_id
, NULL
,
5364 start_commit
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
5369 view
= view_open(0, 0, 0, 0, TOG_VIEW_LOG
);
5371 error
= got_error_from_errno("view_open");
5376 error
= set_tog_base_commit(repo
, worktree
);
5381 error
= open_log_view(view
, start_id
, repo
, head_ref_name
,
5382 in_repo_path
, log_branches
, worktree
);
5387 /* The work tree will be closed by the log thread. */
5391 error
= view_loop(view
);
5394 free(tog_base_commit
.id
);
5395 free(keyword_idstr
);
5404 const struct got_error
*close_err
= got_repo_close(repo
);
5409 got_worktree_close(worktree
);
5411 const struct got_error
*pack_err
=
5412 got_repo_pack_fds_close(pack_fds
);
5424 fprintf(stderr
, "usage: %s diff [-asw] [-C number] [-c commit] "
5425 "[-r repository-path] [object1 object2 | path ...]\n",
5431 match_line(const char *line
, regex_t
*regex
, size_t nmatch
,
5432 regmatch_t
*regmatch
)
5434 return regexec(regex
, line
, nmatch
, regmatch
, 0) == 0;
5437 static struct tog_color
*
5438 match_color(struct tog_colors
*colors
, const char *line
)
5440 struct tog_color
*tc
= NULL
;
5442 STAILQ_FOREACH(tc
, colors
, entry
) {
5443 if (match_line(line
, &tc
->regex
, 0, NULL
))
5450 static const struct got_error
*
5451 add_matched_line(int *wtotal
, const char *line
, int wlimit
, int col_tab_align
,
5452 WINDOW
*window
, int skipcol
, regmatch_t
*regmatch
)
5454 const struct got_error
*err
= NULL
;
5456 wchar_t *wline
= NULL
;
5457 int rme
, rms
, n
, width
, scrollx
;
5458 int width0
= 0, width1
= 0, width2
= 0;
5459 char *seg0
= NULL
, *seg1
= NULL
, *seg2
= NULL
;
5463 rms
= regmatch
->rm_so
;
5464 rme
= regmatch
->rm_eo
;
5466 err
= expand_tab(&exstr
, line
);
5470 /* Split the line into 3 segments, according to match offsets. */
5471 seg0
= strndup(exstr
, rms
);
5473 err
= got_error_from_errno("strndup");
5476 seg1
= strndup(exstr
+ rms
, rme
- rms
);
5478 err
= got_error_from_errno("strndup");
5481 seg2
= strdup(exstr
+ rme
);
5483 err
= got_error_from_errno("strndup");
5487 /* draw up to matched token if we haven't scrolled past it */
5488 err
= format_line(&wline
, &width0
, NULL
, seg0
, 0, wlimit
,
5492 n
= MAX(width0
- skipcol
, 0);
5495 err
= format_line(&wline
, &width
, &scrollx
, seg0
, skipcol
,
5496 wlimit
, col_tab_align
, 1);
5499 waddwstr(window
, &wline
[scrollx
]);
5509 err
= format_line(&wline
, &width1
, NULL
, seg1
, 0, wlimit
,
5513 wlen
= wcslen(wline
);
5515 width
= wcwidth(wline
[i
]);
5517 /* should not happen, tabs are expanded */
5518 err
= got_error(GOT_ERR_RANGE
);
5521 if (width0
+ w
+ width
> skipcol
)
5526 /* draw (visible part of) matched token (if scrolled into it) */
5527 if (width1
- w
> 0) {
5528 wattron(window
, A_STANDOUT
);
5529 waddwstr(window
, &wline
[i
]);
5530 wattroff(window
, A_STANDOUT
);
5531 wlimit
-= (width1
- w
);
5532 *wtotal
+= (width1
- w
);
5536 if (wlimit
> 0) { /* draw rest of line */
5538 if (skipcol
> width0
+ width1
) {
5539 err
= format_line(&wline
, &width2
, &scrollx
, seg2
,
5540 skipcol
- (width0
+ width1
), wlimit
,
5544 waddwstr(window
, &wline
[scrollx
]);
5546 err
= format_line(&wline
, &width2
, NULL
, seg2
, 0,
5547 wlimit
, col_tab_align
, 1);
5550 waddwstr(window
, wline
);
5564 gotoline(struct tog_view
*view
, int *lineno
, int *nprinted
)
5567 int *eof
, *first
, *selected
;
5569 if (view
->type
== TOG_VIEW_DIFF
) {
5570 struct tog_diff_view_state
*s
= &view
->state
.diff
;
5572 first
= &s
->first_displayed_line
;
5576 } else if (view
->type
== TOG_VIEW_HELP
) {
5577 struct tog_help_view_state
*s
= &view
->state
.help
;
5579 first
= &s
->first_displayed_line
;
5583 } else if (view
->type
== TOG_VIEW_BLAME
) {
5584 struct tog_blame_view_state
*s
= &view
->state
.blame
;
5586 first
= &s
->first_displayed_line
;
5587 selected
= &s
->selected_line
;
5593 /* Center gline in the middle of the page like vi(1). */
5594 if (*lineno
< view
->gline
- (view
->nlines
- 3) / 2)
5596 if (*first
!= 1 && (*lineno
> view
->gline
- (view
->nlines
- 3) / 2)) {
5605 *selected
= view
->gline
<= (view
->nlines
- 3) / 2 ?
5606 view
->gline
: (view
->nlines
- 3) / 2 + 1;
5612 static const struct got_error
*
5613 draw_file(struct tog_view
*view
, const char *header
)
5615 struct tog_diff_view_state
*s
= &view
->state
.diff
;
5616 regmatch_t
*regmatch
= &view
->regmatch
;
5617 const struct got_error
*err
;
5620 size_t linesize
= 0;
5624 int max_lines
= view
->nlines
;
5625 int nlines
= s
->nlines
;
5628 s
->lineno
= s
->first_displayed_line
- 1;
5629 line_offset
= s
->lines
[s
->first_displayed_line
- 1].offset
;
5630 if (fseeko(s
->f
, line_offset
, SEEK_SET
) == -1)
5631 return got_error_from_errno("fseek");
5633 werase(view
->window
);
5635 if (view
->gline
> s
->nlines
- 1)
5636 view
->gline
= s
->nlines
- 1;
5639 int ln
= view
->gline
? view
->gline
<= (view
->nlines
- 3) / 2 ?
5640 1 : view
->gline
- (view
->nlines
- 3) / 2 :
5641 s
->lineno
+ s
->selected_line
;
5643 if (asprintf(&line
, "[%d/%d] %s", ln
, nlines
, header
) == -1)
5644 return got_error_from_errno("asprintf");
5645 err
= format_line(&wline
, &width
, NULL
, line
, 0, view
->ncols
,
5651 if (view_needs_focus_indication(view
))
5652 wstandout(view
->window
);
5653 waddwstr(view
->window
, wline
);
5656 while (width
++ < view
->ncols
)
5657 waddch(view
->window
, ' ');
5658 if (view_needs_focus_indication(view
))
5659 wstandend(view
->window
);
5669 while (max_lines
> 0 && nprinted
< max_lines
) {
5670 enum got_diff_line_type linetype
;
5673 linelen
= getline(&line
, &linesize
, s
->f
);
5674 if (linelen
== -1) {
5680 return got_ferror(s
->f
, GOT_ERR_IO
);
5683 if (++s
->lineno
< s
->first_displayed_line
)
5685 if (view
->gline
&& !gotoline(view
, &s
->lineno
, &nprinted
))
5687 if (s
->lineno
== view
->hiline
)
5690 /* Set view->maxx based on full line length. */
5691 err
= format_line(&wline
, &width
, NULL
, line
, 0, INT_MAX
, 0,
5697 view
->maxx
= MAX(view
->maxx
, width
);
5701 linetype
= s
->lines
[s
->lineno
].type
;
5702 if (linetype
> GOT_DIFF_LINE_LOGMSG
&&
5703 linetype
< GOT_DIFF_LINE_CONTEXT
)
5704 attr
|= COLOR_PAIR(linetype
);
5706 wattron(view
->window
, attr
);
5707 if (s
->first_displayed_line
+ nprinted
== s
->matched_line
&&
5708 regmatch
->rm_so
>= 0 && regmatch
->rm_so
< regmatch
->rm_eo
) {
5709 err
= add_matched_line(&width
, line
, view
->ncols
, 0,
5710 view
->window
, view
->x
, regmatch
);
5717 err
= format_line(&wline
, &width
, &skip
, line
,
5718 view
->x
, view
->ncols
, 0, view
->x
? 1 : 0);
5723 waddwstr(view
->window
, &wline
[skip
]);
5727 if (s
->lineno
== view
->hiline
) {
5728 /* highlight full gline length */
5729 while (width
++ < view
->ncols
)
5730 waddch(view
->window
, ' ');
5732 if (width
<= view
->ncols
- 1)
5733 waddch(view
->window
, '\n');
5736 wattroff(view
->window
, attr
);
5737 if (++nprinted
== 1)
5738 s
->first_displayed_line
= s
->lineno
;
5742 s
->last_displayed_line
= s
->first_displayed_line
+
5745 s
->last_displayed_line
= s
->first_displayed_line
;
5750 while (nprinted
< view
->nlines
) {
5751 waddch(view
->window
, '\n');
5755 err
= format_line(&wline
, &width
, NULL
, TOG_EOF_STRING
, 0,
5761 wstandout(view
->window
);
5762 waddwstr(view
->window
, wline
);
5765 wstandend(view
->window
);
5772 get_datestr(time_t *time
, char *datebuf
)
5774 struct tm mytm
, *tm
;
5777 tm
= gmtime_r(time
, &mytm
);
5780 s
= asctime_r(tm
, datebuf
);
5783 p
= strchr(s
, '\n');
5789 static const struct got_error
*
5790 add_line_metadata(struct got_diff_line
**lines
, size_t *nlines
,
5791 off_t off
, uint8_t type
)
5793 struct got_diff_line
*p
;
5795 p
= reallocarray(*lines
, *nlines
+ 1, sizeof(**lines
));
5797 return got_error_from_errno("reallocarray");
5799 (*lines
)[*nlines
].offset
= off
;
5800 (*lines
)[*nlines
].type
= type
;
5806 static const struct got_error
*
5807 cat_diff(FILE *dst
, FILE *src
, struct got_diff_line
**d_lines
, size_t *d_nlines
,
5808 struct got_diff_line
*s_lines
, size_t s_nlines
)
5810 struct got_diff_line
*p
;
5814 if (fseeko(src
, 0L, SEEK_SET
) == -1)
5815 return got_error_from_errno("fseeko");
5818 r
= fread(buf
, 1, sizeof(buf
), src
);
5821 return got_error_from_errno("fread");
5825 if (fwrite(buf
, 1, r
, dst
) != r
)
5826 return got_ferror(dst
, GOT_ERR_IO
);
5829 if (s_nlines
== 0 && *d_nlines
== 0)
5833 * If commit info was in dst, increment line offsets
5834 * of the appended diff content, but skip s_lines[0]
5835 * because offset zero is already in *d_lines.
5837 if (*d_nlines
> 0) {
5838 for (i
= 1; i
< s_nlines
; ++i
)
5839 s_lines
[i
].offset
+= (*d_lines
)[*d_nlines
- 1].offset
;
5847 p
= reallocarray(*d_lines
, *d_nlines
+ s_nlines
, sizeof(*p
));
5849 /* d_lines is freed in close_diff_view() */
5850 return got_error_from_errno("reallocarray");
5855 memcpy(*d_lines
+ *d_nlines
, s_lines
, s_nlines
* sizeof(*s_lines
));
5856 *d_nlines
+= s_nlines
;
5861 static const struct got_error
*
5862 write_diffstat(FILE *outfile
, struct got_diff_line
**lines
, size_t *nlines
,
5863 struct got_diffstat_cb_arg
*dsa
)
5865 const struct got_error
*err
;
5866 struct got_pathlist_entry
*pe
;
5871 err
= add_line_metadata(lines
, nlines
, 0, GOT_DIFF_LINE_NONE
);
5876 offset
= (*lines
)[*nlines
- 1].offset
;
5878 RB_FOREACH(pe
, got_pathlist_head
, dsa
->paths
) {
5879 struct got_diff_changed_path
*cp
= pe
->data
;
5880 int pad
= dsa
->max_path_len
- pe
->path_len
+ 1;
5882 n
= fprintf(outfile
, "%c %s%*c | %*d+ %*d-\n", cp
->status
,
5883 pe
->path
, pad
, ' ', dsa
->add_cols
+ 1, cp
->add
,
5884 dsa
->rm_cols
+ 1, cp
->rm
);
5886 return got_error_from_errno("fprintf");
5889 err
= add_line_metadata(lines
, nlines
, offset
,
5890 GOT_DIFF_LINE_CHANGES
);
5895 if (fputc('\n', outfile
) == EOF
)
5896 return got_error_from_errno("fputc");
5899 err
= add_line_metadata(lines
, nlines
, offset
, GOT_DIFF_LINE_NONE
);
5903 n
= fprintf(outfile
,
5904 "%d file%s changed, %d insertion%s(+), %d deletion%s(-)\n",
5905 dsa
->nfiles
, dsa
->nfiles
> 1 ? "s" : "", dsa
->ins
,
5906 dsa
->ins
!= 1 ? "s" : "", dsa
->del
, dsa
->del
!= 1 ? "s" : "");
5908 return got_error_from_errno("fprintf");
5911 err
= add_line_metadata(lines
, nlines
, offset
, GOT_DIFF_LINE_NONE
);
5915 if (fputc('\n', outfile
) == EOF
)
5916 return got_error_from_errno("fputc");
5919 return add_line_metadata(lines
, nlines
, offset
, GOT_DIFF_LINE_NONE
);
5922 static const struct got_error
*
5923 write_commit_info(struct got_diff_line
**lines
, size_t *nlines
,
5924 struct got_object_id
*commit_id
, struct got_reflist_head
*refs
,
5925 struct got_repository
*repo
, int ignore_ws
, int force_text_diff
,
5926 struct got_diffstat_cb_arg
*dsa
, FILE *outfile
)
5928 const struct got_error
*err
= NULL
;
5929 char datebuf
[26], *datestr
;
5930 struct got_commit_object
*commit
;
5931 char *id_str
= NULL
, *logmsg
= NULL
, *s
= NULL
, *line
;
5932 time_t committer_time
;
5933 const char *author
, *committer
;
5934 char *refs_str
= NULL
;
5938 err
= build_refs_str(&refs_str
, refs
, commit_id
, repo
);
5942 err
= got_object_open_as_commit(&commit
, repo
, commit_id
);
5946 err
= got_object_id_str(&id_str
, commit_id
);
5948 err
= got_error_from_errno("got_object_id_str");
5952 err
= add_line_metadata(lines
, nlines
, 0, GOT_DIFF_LINE_NONE
);
5956 n
= fprintf(outfile
, "commit %s%s%s%s\n", id_str
, refs_str
? " (" : "",
5957 refs_str
? refs_str
: "", refs_str
? ")" : "");
5959 err
= got_error_from_errno("fprintf");
5963 err
= add_line_metadata(lines
, nlines
, outoff
, GOT_DIFF_LINE_META
);
5967 n
= fprintf(outfile
, "from: %s\n",
5968 got_object_commit_get_author(commit
));
5970 err
= got_error_from_errno("fprintf");
5974 err
= add_line_metadata(lines
, nlines
, outoff
, GOT_DIFF_LINE_AUTHOR
);
5978 author
= got_object_commit_get_author(commit
);
5979 committer
= got_object_commit_get_committer(commit
);
5980 if (strcmp(author
, committer
) != 0) {
5981 n
= fprintf(outfile
, "via: %s\n", committer
);
5983 err
= got_error_from_errno("fprintf");
5987 err
= add_line_metadata(lines
, nlines
, outoff
,
5988 GOT_DIFF_LINE_AUTHOR
);
5992 committer_time
= got_object_commit_get_committer_time(commit
);
5993 datestr
= get_datestr(&committer_time
, datebuf
);
5995 n
= fprintf(outfile
, "date: %s UTC\n", datestr
);
5997 err
= got_error_from_errno("fprintf");
6001 err
= add_line_metadata(lines
, nlines
, outoff
,
6002 GOT_DIFF_LINE_DATE
);
6006 if (got_object_commit_get_nparents(commit
) > 1) {
6007 const struct got_object_id_queue
*parent_ids
;
6008 struct got_object_qid
*qid
;
6010 parent_ids
= got_object_commit_get_parent_ids(commit
);
6011 STAILQ_FOREACH(qid
, parent_ids
, entry
) {
6012 err
= got_object_id_str(&id_str
, &qid
->id
);
6015 n
= fprintf(outfile
, "parent %d: %s\n", pn
++, id_str
);
6017 err
= got_error_from_errno("fprintf");
6021 err
= add_line_metadata(lines
, nlines
, outoff
,
6022 GOT_DIFF_LINE_META
);
6030 err
= got_object_commit_get_logmsg(&logmsg
, commit
);
6034 while ((line
= strsep(&s
, "\n")) != NULL
) {
6035 n
= fprintf(outfile
, "%s\n", line
);
6037 err
= got_error_from_errno("fprintf");
6041 err
= add_line_metadata(lines
, nlines
, outoff
,
6042 GOT_DIFF_LINE_LOGMSG
);
6051 got_object_commit_close(commit
);
6056 evict_worktree_entry(struct tog_log_thread_args
*ta
, int victim
)
6058 struct commit_queue_entry
*e
, *v
= *ta
->selected_entry
;
6061 return; /* paranoid check */
6063 if (v
->worktree_entry
!= victim
) {
6064 TAILQ_FOREACH(v
, &ta
->real_commits
->head
, entry
) {
6065 if (v
->worktree_entry
== victim
)
6072 ta
->wctx
.wt_state
&= ~victim
;
6074 if (*ta
->selected_entry
== v
)
6075 *ta
->selected_entry
= TAILQ_NEXT(v
, entry
);
6076 if (*ta
->first_displayed_entry
== v
)
6077 *ta
->first_displayed_entry
= TAILQ_NEXT(v
, entry
);
6078 if (*ta
->last_displayed_entry
== v
)
6079 *ta
->last_displayed_entry
= TAILQ_NEXT(v
, entry
);
6081 for (e
= TAILQ_NEXT(v
, entry
); e
!= NULL
; e
= TAILQ_NEXT(e
, entry
))
6084 --tog_base_commit
.idx
;
6085 --ta
->real_commits
->ncommits
;
6087 TAILQ_REMOVE(&ta
->real_commits
->head
, v
, entry
);
6092 * Create a file which contains the target path of a symlink so we can feed
6093 * it as content to the diff engine.
6095 static const struct got_error
*
6096 get_symlink_target_file(int *fd
, int dirfd
, const char *de_name
,
6097 const char *abspath
)
6099 const struct got_error
*err
= NULL
;
6100 char target_path
[PATH_MAX
];
6101 ssize_t target_len
, outlen
;
6106 target_len
= readlinkat(dirfd
, de_name
, target_path
, PATH_MAX
);
6107 if (target_len
== -1)
6108 return got_error_from_errno2("readlinkat", abspath
);
6110 target_len
= readlink(abspath
, target_path
, PATH_MAX
);
6111 if (target_len
== -1)
6112 return got_error_from_errno2("readlink", abspath
);
6115 *fd
= got_opentempfd();
6117 return got_error_from_errno("got_opentempfd");
6119 outlen
= write(*fd
, target_path
, target_len
);
6121 err
= got_error_from_errno("got_opentempfd");
6125 if (lseek(*fd
, 0, SEEK_SET
) == -1) {
6126 err
= got_error_from_errno2("lseek", abspath
);
6138 static const struct got_error
*
6139 emit_base_commit_header(FILE *f
, struct got_diff_line
**lines
, size_t *nlines
,
6140 struct got_object_id
*commit_id
, struct got_worktree
*worktree
)
6142 const struct got_error
*err
;
6143 struct got_object_id
*base_commit_id
;
6144 char *base_commit_idstr
;
6147 if (worktree
== NULL
) /* shouldn't happen */
6148 return got_error(GOT_ERR_NOT_WORKTREE
);
6150 base_commit_id
= got_worktree_get_base_commit_id(worktree
);
6152 if (commit_id
!= NULL
) {
6153 if (got_object_id_cmp(commit_id
, base_commit_id
) != 0)
6154 base_commit_id
= commit_id
;
6157 err
= got_object_id_str(&base_commit_idstr
, base_commit_id
);
6161 if ((n
= fprintf(f
, "commit - %s\n", base_commit_idstr
)) < 0)
6162 err
= got_error_from_errno("fprintf");
6163 free(base_commit_idstr
);
6167 return add_line_metadata(lines
, nlines
,
6168 (*lines
)[*nlines
- 1].offset
+ n
, GOT_DIFF_LINE_META
);
6171 static const struct got_error
*
6172 tog_worktree_diff(void *arg
, unsigned char status
, unsigned char staged_status
,
6173 const char *path
, struct got_object_id
*blob_id
,
6174 struct got_object_id
*staged_blob_id
, struct got_object_id
*commit_id
,
6175 int dirfd
, const char *de_name
)
6177 const struct got_error
*err
= NULL
;
6178 struct diff_worktree_arg
*a
= arg
;
6179 struct got_blob_object
*blob1
= NULL
;
6182 char *abspath
= NULL
, *label1
= NULL
;
6185 int fd
= -1, fd1
= -1, fd2
= -1;
6186 int n
, f2_exists
= 1;
6188 if (a
->diff_staged
) {
6189 if (staged_status
!= GOT_STATUS_MODIFY
&&
6190 staged_status
!= GOT_STATUS_ADD
&&
6191 staged_status
!= GOT_STATUS_DELETE
)
6194 if (staged_status
== GOT_STATUS_DELETE
)
6196 if (status
== GOT_STATUS_NONEXISTENT
)
6197 return got_error_set_errno(ENOENT
, path
);
6198 if (status
!= GOT_STATUS_MODIFY
&&
6199 status
!= GOT_STATUS_ADD
&&
6200 status
!= GOT_STATUS_DELETE
&&
6201 status
!= GOT_STATUS_CONFLICT
)
6205 err
= got_opentemp_truncate(a
->f1
);
6207 return got_error_from_errno("got_opentemp_truncate");
6208 err
= got_opentemp_truncate(a
->f2
);
6210 return got_error_from_errno("got_opentemp_truncate");
6212 if (!a
->header_shown
) {
6213 n
= fprintf(a
->outfile
, "path + %s%s\n",
6214 got_worktree_get_root_path(a
->worktree
),
6215 a
->diff_staged
? " (staged changes)" : "");
6217 return got_error_from_errno("fprintf");
6220 err
= add_line_metadata(a
->lines
, a
->nlines
, outoff
,
6221 GOT_DIFF_LINE_META
);
6225 a
->header_shown
= 1;
6228 err
= emit_base_commit_header(a
->outfile
,
6229 a
->lines
, a
->nlines
, commit_id
, a
->worktree
);
6233 if (a
->diff_staged
) {
6234 const char *label1
= NULL
, *label2
= NULL
;
6236 switch (staged_status
) {
6237 case GOT_STATUS_MODIFY
:
6241 case GOT_STATUS_ADD
:
6244 case GOT_STATUS_DELETE
:
6248 return got_error(GOT_ERR_FILE_STATUS
);
6251 fd1
= got_opentempfd();
6253 return got_error_from_errno("got_opentempfd");
6255 fd2
= got_opentempfd();
6257 err
= got_error_from_errno("got_opentempfd");
6261 err
= got_diff_objects_as_blobs(a
->lines
, a
->nlines
,
6262 a
->f1
, a
->f2
, fd1
, fd2
, blob_id
, staged_blob_id
,
6263 label1
, label2
, a
->diff_algo
, a
->diff_context
,
6264 a
->ignore_whitespace
, a
->force_text_diff
,
6265 a
->diffstat
, a
->repo
, a
->outfile
);
6269 fd1
= got_opentempfd();
6271 return got_error_from_errno("got_opentempfd");
6273 if (staged_status
== GOT_STATUS_ADD
||
6274 staged_status
== GOT_STATUS_MODIFY
) {
6277 err
= got_object_open_as_blob(&blob1
,
6278 a
->repo
, staged_blob_id
, 8192, fd1
);
6281 err
= got_object_id_str(&id_str
, staged_blob_id
);
6284 if (asprintf(&label1
, "%s (staged)", id_str
) == -1) {
6285 err
= got_error_from_errno("asprintf");
6290 } else if (status
!= GOT_STATUS_ADD
) {
6291 err
= got_object_open_as_blob(&blob1
,
6292 a
->repo
, blob_id
, 8192, fd1
);
6297 if (status
!= GOT_STATUS_DELETE
) {
6298 if (asprintf(&abspath
, "%s/%s",
6299 got_worktree_get_root_path(a
->worktree
), path
) == -1) {
6300 err
= got_error_from_errno("asprintf");
6305 fd
= openat(dirfd
, de_name
,
6306 O_RDONLY
| O_NOFOLLOW
| O_CLOEXEC
);
6308 if (!got_err_open_nofollow_on_symlink()) {
6309 err
= got_error_from_errno2("openat",
6313 err
= get_symlink_target_file(&fd
,
6314 dirfd
, de_name
, abspath
);
6319 fd
= open(abspath
, O_RDONLY
| O_NOFOLLOW
| O_CLOEXEC
);
6321 if (!got_err_open_nofollow_on_symlink()) {
6322 err
= got_error_from_errno2("open",
6326 err
= get_symlink_target_file(&fd
,
6327 dirfd
, de_name
, abspath
);
6332 if (fstat(fd
, &sb
) == -1) {
6333 err
= got_error_from_errno2("fstat", abspath
);
6336 f2
= fdopen(fd
, "r");
6338 err
= got_error_from_errno2("fdopen", abspath
);
6347 if (blob1
!= NULL
) {
6348 err
= got_object_blob_dump_to_file(&size1
,
6349 NULL
, NULL
, a
->f1
, blob1
);
6354 err
= got_diff_blob_file(a
->lines
, a
->nlines
, blob1
, a
->f1
, size1
,
6355 label1
, f2
!= NULL
? f2
: a
->f2
, f2_exists
, &sb
, path
,
6356 tog_diff_algo
, a
->diff_context
, a
->ignore_whitespace
,
6357 a
->force_text_diff
, a
->diffstat
, a
->outfile
);
6360 if (fd
!= -1 && close(fd
) == -1 && err
== NULL
)
6361 err
= got_error_from_errno("close");
6362 if (fd1
!= -1 && close(fd1
) == -1 && err
== NULL
)
6363 err
= got_error_from_errno("close");
6364 if (fd2
!= -1 && close(fd2
) == -1 && err
== NULL
)
6365 err
= got_error_from_errno("close");
6367 got_object_blob_close(blob1
);
6368 if (f2
!= NULL
&& fclose(f2
) == EOF
&& err
== NULL
)
6369 err
= got_error_from_errno("fclose");
6375 static const struct got_error
*
6376 tog_diff_worktree(struct tog_diff_view_state
*s
, FILE *f
,
6377 struct got_diff_line
**lines
, size_t *nlines
,
6378 struct got_diffstat_cb_arg
*dsa
)
6380 const struct got_error
*close_err
, *err
;
6381 struct got_worktree
*worktree
= NULL
;
6382 struct diff_worktree_arg arg
;
6383 struct got_pathlist_head pathlist
;
6384 char *cwd
, *id_str
= NULL
;
6388 cwd
= getcwd(NULL
, 0);
6390 return got_error_from_errno("getcwd");
6392 err
= add_line_metadata(lines
, nlines
, 0, GOT_DIFF_LINE_NONE
);
6396 err
= got_worktree_open(&worktree
, cwd
, NULL
);
6398 if (err
->code
== GOT_ERR_WORKTREE_BUSY
) {
6401 if ((n
= fprintf(f
, "%s\n", err
->msg
)) < 0) {
6402 err
= got_ferror(f
, GOT_ERR_IO
);
6405 err
= add_line_metadata(lines
, nlines
, n
,
6406 GOT_DIFF_LINE_META
);
6409 err
= got_error(GOT_ERR_DIFF_NOCHANGES
);
6414 err
= got_object_id_str(&id_str
,
6415 got_worktree_get_base_commit_id(worktree
));
6419 err
= got_repo_match_object_id(&s
->id1
, NULL
, id_str
,
6420 GOT_OBJ_TYPE_ANY
, &tog_refs
, s
->repo
);
6424 arg
.id_str
= id_str
;
6425 arg
.diff_algo
= tog_diff_algo
;
6427 arg
.worktree
= worktree
;
6429 arg
.diff_context
= s
->diff_context
;
6430 arg
.diff_staged
= s
->diff_staged
;
6431 arg
.ignore_whitespace
= s
->ignore_whitespace
;
6432 arg
.force_text_diff
= s
->force_text_diff
;
6433 arg
.header_shown
= 0;
6435 arg
.nlines
= nlines
;
6440 if (s
->paths
== NULL
) {
6441 err
= got_pathlist_insert(NULL
, &pathlist
, "", NULL
);
6446 err
= got_worktree_status(worktree
, s
->paths
? s
->paths
: &pathlist
,
6447 s
->repo
, 0, tog_worktree_diff
, &arg
, NULL
, NULL
);
6452 const char *msg
= TOG_WORKTREE_CHANGES_LOCAL_MSG
;
6453 int n
, victim
= TOG_WORKTREE_CHANGES_LOCAL
;
6455 if (s
->diff_staged
) {
6456 victim
= TOG_WORKTREE_CHANGES_STAGED
;
6457 msg
= TOG_WORKTREE_CHANGES_STAGED_MSG
;
6459 if ((n
= fprintf(f
, "no %s\n", msg
)) < 0) {
6460 err
= got_ferror(f
, GOT_ERR_IO
);
6463 err
= add_line_metadata(lines
, nlines
, n
, GOT_DIFF_LINE_META
);
6466 if (s
->parent_view
&& s
->parent_view
->type
== TOG_VIEW_LOG
)
6467 evict_worktree_entry(
6468 &s
->parent_view
->state
.log
.thread_args
, victim
);
6469 err
= got_error(GOT_ERR_DIFF_NOCHANGES
);
6475 got_pathlist_free(&pathlist
, GOT_PATHLIST_FREE_NONE
);
6476 if (worktree
!= NULL
) {
6477 if ((close_err
= got_worktree_close(worktree
)) != NULL
) {
6478 if (err
== NULL
|| err
->code
== GOT_ERR_DIFF_NOCHANGES
)
6485 static const struct got_error
*
6486 tog_diff_objects(struct tog_diff_view_state
*s
, FILE *f
,
6487 struct got_diff_line
**lines
, size_t *nlines
,
6488 struct got_diffstat_cb_arg
*dsa
)
6490 const struct got_error
*err
;
6494 err
= got_object_get_type(&obj_type
, s
->repo
, s
->id1
);
6496 err
= got_object_get_type(&obj_type
, s
->repo
, s
->id2
);
6501 case GOT_OBJ_TYPE_BLOB
:
6502 err
= got_diff_objects_as_blobs(lines
, nlines
, s
->f1
, s
->f2
,
6503 s
->fd1
, s
->fd2
, s
->id1
, s
->id2
, NULL
, NULL
, tog_diff_algo
,
6504 s
->diff_context
, s
->ignore_whitespace
, s
->force_text_diff
,
6509 case GOT_OBJ_TYPE_TREE
:
6510 err
= got_diff_objects_as_trees(lines
, nlines
,
6511 s
->f1
, s
->f2
, s
->fd1
, s
->fd2
, s
->id1
, s
->id2
,
6512 s
->paths
, "", "", tog_diff_algo
, s
->diff_context
,
6513 s
->ignore_whitespace
, s
->force_text_diff
, dsa
, s
->repo
, f
);
6517 case GOT_OBJ_TYPE_COMMIT
: {
6518 const struct got_object_id_queue
*parent_ids
;
6519 struct got_commit_object
*commit2
;
6520 struct got_object_qid
*pid
;
6521 struct got_reflist_head
*refs
;
6523 err
= got_diff_objects_as_commits(lines
, nlines
, s
->f1
, s
->f2
,
6524 s
->fd1
, s
->fd2
, s
->id1
, s
->id2
, s
->paths
, tog_diff_algo
,
6525 s
->diff_context
, s
->ignore_whitespace
, s
->force_text_diff
,
6530 refs
= got_reflist_object_id_map_lookup(tog_refs_idmap
, s
->id2
);
6531 /* Show commit info if we're diffing to a parent/root commit. */
6533 return write_commit_info(&s
->lines
, &s
->nlines
, s
->id2
,
6534 refs
, s
->repo
, s
->ignore_whitespace
,
6535 s
->force_text_diff
, dsa
, s
->f
);
6537 err
= got_object_open_as_commit(&commit2
, s
->repo
,
6542 parent_ids
= got_object_commit_get_parent_ids(commit2
);
6543 STAILQ_FOREACH(pid
, parent_ids
, entry
) {
6544 if (got_object_id_cmp(s
->id1
, &pid
->id
) == 0) {
6545 err
= write_commit_info(&s
->lines
, &s
->nlines
,
6546 s
->id2
, refs
, s
->repo
, s
->ignore_whitespace
,
6547 s
->force_text_diff
, dsa
, s
->f
);
6551 if (commit2
!= NULL
)
6552 got_object_commit_close(commit2
);
6558 return got_error(GOT_ERR_OBJ_TYPE
);
6564 static const struct got_error
*
6565 create_diff(struct tog_diff_view_state
*s
)
6567 const struct got_error
*err
= NULL
;
6568 FILE *tmp_diff_file
= NULL
;
6569 struct got_diff_line
*lines
= NULL
;
6570 struct got_pathlist_head changed_paths
;
6571 struct got_diffstat_cb_arg dsa
;
6574 RB_INIT(&changed_paths
);
6575 memset(&dsa
, 0, sizeof(dsa
));
6576 dsa
.paths
= &changed_paths
;
6577 dsa
.diff_algo
= tog_diff_algo
;
6578 dsa
.force_text
= s
->force_text_diff
;
6579 dsa
.ignore_ws
= s
->ignore_whitespace
;
6582 s
->lines
= malloc(sizeof(*s
->lines
));
6583 if (s
->lines
== NULL
)
6584 return got_error_from_errno("malloc");
6587 if (s
->f
&& fclose(s
->f
) == EOF
) {
6589 return got_error_from_errno("fclose");
6592 s
->f
= got_opentemp();
6594 return got_error_from_errno("got_opentemp");
6597 * The diffstat requires the diff to be built first, but we want the
6598 * diffstat to precede the diff when displayed. Build the diff first
6599 * in the temporary file and write the diffstat and/or commit info to
6600 * the persistent file (s->f) from which views are drawn, then append
6601 * the diff from the temp file to the diffstat/commit info in s->f.
6603 tmp_diff_file
= got_opentemp();
6604 if (tmp_diff_file
== NULL
)
6605 return got_error_from_errno("got_opentemp");
6607 lines
= malloc(sizeof(*lines
));
6608 if (lines
== NULL
) {
6609 err
= got_error_from_errno("malloc");
6613 if (s
->parent_view
!= NULL
&& s
->parent_view
->type
== TOG_VIEW_LOG
) {
6614 struct tog_log_view_state
*ls
= &s
->parent_view
->state
.log
;
6615 struct commit_queue_entry
*cqe
= ls
->selected_entry
;
6617 if (cqe
->worktree_entry
!= 0) {
6618 if (cqe
->worktree_entry
== TOG_WORKTREE_CHANGES_STAGED
)
6620 s
->diff_worktree
= 1;
6624 if (s
->diff_worktree
)
6625 err
= tog_diff_worktree(s
, tmp_diff_file
,
6626 &lines
, &nlines
, &dsa
);
6628 err
= tog_diff_objects(s
, tmp_diff_file
,
6629 &lines
, &nlines
, &dsa
);
6631 if (err
->code
!= GOT_ERR_DIFF_NOCHANGES
)
6634 err
= write_diffstat(s
->f
, &s
->lines
, &s
->nlines
, &dsa
);
6639 err
= cat_diff(s
->f
, tmp_diff_file
, &s
->lines
, &s
->nlines
,
6644 got_pathlist_free(&changed_paths
, GOT_PATHLIST_FREE_ALL
);
6645 if (s
->f
&& fflush(s
->f
) != 0 && err
== NULL
)
6646 err
= got_error_from_errno("fflush");
6647 if (tmp_diff_file
&& fclose(tmp_diff_file
) == EOF
&& err
== NULL
)
6648 err
= got_error_from_errno("fclose");
6653 diff_view_indicate_progress(struct tog_view
*view
)
6655 mvwaddstr(view
->window
, 0, 0, "diffing...");
6660 static const struct got_error
*
6661 search_start_diff_view(struct tog_view
*view
)
6663 struct tog_diff_view_state
*s
= &view
->state
.diff
;
6665 s
->matched_line
= 0;
6670 search_setup_diff_view(struct tog_view
*view
, FILE **f
, off_t
**line_offsets
,
6671 size_t *nlines
, int **first
, int **last
, int **match
, int **selected
)
6673 struct tog_diff_view_state
*s
= &view
->state
.diff
;
6676 *nlines
= s
->nlines
;
6677 *line_offsets
= NULL
;
6678 *match
= &s
->matched_line
;
6679 *first
= &s
->first_displayed_line
;
6680 *last
= &s
->last_displayed_line
;
6681 *selected
= &s
->selected_line
;
6684 static const struct got_error
*
6685 search_next_view_match(struct tog_view
*view
)
6687 const struct got_error
*err
= NULL
;
6691 size_t linesize
= 0;
6693 off_t
*line_offsets
;
6695 int *first
, *last
, *match
, *selected
;
6697 if (!view
->search_setup
)
6698 return got_error_msg(GOT_ERR_NOT_IMPL
,
6699 "view search not supported");
6700 view
->search_setup(view
, &f
, &line_offsets
, &nlines
, &first
, &last
,
6703 if (!view
->searching
) {
6704 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
6709 if (view
->searching
== TOG_SEARCH_FORWARD
)
6710 lineno
= *first
+ 1;
6712 lineno
= *first
- 1;
6714 lineno
= *first
- 1 + *selected
;
6719 if (lineno
<= 0 || lineno
> nlines
) {
6721 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
6725 if (view
->searching
== TOG_SEARCH_FORWARD
)
6731 offset
= view
->type
== TOG_VIEW_DIFF
?
6732 view
->state
.diff
.lines
[lineno
- 1].offset
:
6733 line_offsets
[lineno
- 1];
6734 if (fseeko(f
, offset
, SEEK_SET
) != 0) {
6736 return got_error_from_errno("fseeko");
6738 linelen
= getline(&line
, &linesize
, f
);
6739 if (linelen
!= -1) {
6741 err
= expand_tab(&exstr
, line
);
6744 if (match_line(exstr
, &view
->regex
, 1,
6746 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
6753 if (view
->searching
== TOG_SEARCH_FORWARD
)
6768 static const struct got_error
*
6769 close_diff_view(struct tog_view
*view
)
6771 const struct got_error
*err
= NULL
;
6772 struct tog_diff_view_state
*s
= &view
->state
.diff
;
6780 if (s
->f
&& fclose(s
->f
) == EOF
)
6781 err
= got_error_from_errno("fclose");
6783 if (s
->f1
&& fclose(s
->f1
) == EOF
&& err
== NULL
)
6784 err
= got_error_from_errno("fclose");
6786 if (s
->f2
&& fclose(s
->f2
) == EOF
&& err
== NULL
)
6787 err
= got_error_from_errno("fclose");
6789 if (s
->fd1
!= -1 && close(s
->fd1
) == -1 && err
== NULL
)
6790 err
= got_error_from_errno("close");
6792 if (s
->fd2
!= -1 && close(s
->fd2
) == -1 && err
== NULL
)
6793 err
= got_error_from_errno("close");
6801 static const struct got_error
*
6802 open_diff_view(struct tog_view
*view
, struct got_object_id
*id1
,
6803 struct got_object_id
*id2
, const char *label1
, const char *label2
,
6804 int diff_context
, int ignore_whitespace
, int force_text_diff
,
6805 int diff_staged
, int diff_worktree
, const char *worktree_root
,
6806 struct tog_view
*parent_view
, struct got_repository
*repo
,
6807 struct got_pathlist_head
*paths
)
6809 const struct got_error
*err
;
6810 struct tog_diff_view_state
*s
= &view
->state
.diff
;
6812 memset(s
, 0, sizeof(*s
));
6816 if (id1
!= NULL
&& id2
!= NULL
) {
6819 err
= got_object_get_type(&type1
, repo
, id1
);
6822 err
= got_object_get_type(&type2
, repo
, id2
);
6826 if (type1
!= type2
) {
6827 err
= got_error(GOT_ERR_OBJ_TYPE
);
6832 if (diff_worktree
== 0) {
6834 s
->id1
= got_object_id_dup(id1
);
6835 if (s
->id1
== NULL
) {
6836 err
= got_error_from_errno("got_object_id_dup");
6842 s
->id2
= got_object_id_dup(id2
);
6843 if (s
->id2
== NULL
) {
6844 err
= got_error_from_errno("got_object_id_dup");
6849 s
->f1
= got_opentemp();
6850 if (s
->f1
== NULL
) {
6851 err
= got_error_from_errno("got_opentemp");
6855 s
->f2
= got_opentemp();
6856 if (s
->f2
== NULL
) {
6857 err
= got_error_from_errno("got_opentemp");
6861 s
->fd1
= got_opentempfd();
6863 err
= got_error_from_errno("got_opentempfd");
6867 s
->fd2
= got_opentempfd();
6869 err
= got_error_from_errno("got_opentempfd");
6873 s
->first_displayed_line
= 1;
6874 s
->last_displayed_line
= view
->nlines
;
6875 s
->selected_line
= 1;
6878 s
->diff_context
= diff_context
;
6879 s
->ignore_whitespace
= ignore_whitespace
;
6880 s
->force_text_diff
= force_text_diff
;
6881 s
->diff_worktree
= diff_worktree
;
6882 s
->diff_staged
= diff_staged
;
6883 s
->parent_view
= parent_view
;
6886 s
->worktree_root
= worktree_root
;
6888 if (has_colors() && getenv("TOG_COLORS") != NULL
&& !using_mock_io
) {
6891 rc
= init_pair(GOT_DIFF_LINE_MINUS
,
6892 get_color_value("TOG_COLOR_DIFF_MINUS"), -1);
6894 rc
= init_pair(GOT_DIFF_LINE_PLUS
,
6895 get_color_value("TOG_COLOR_DIFF_PLUS"), -1);
6897 rc
= init_pair(GOT_DIFF_LINE_HUNK
,
6898 get_color_value("TOG_COLOR_DIFF_CHUNK_HEADER"), -1);
6900 rc
= init_pair(GOT_DIFF_LINE_META
,
6901 get_color_value("TOG_COLOR_DIFF_META"), -1);
6903 rc
= init_pair(GOT_DIFF_LINE_CHANGES
,
6904 get_color_value("TOG_COLOR_DIFF_META"), -1);
6906 rc
= init_pair(GOT_DIFF_LINE_BLOB_MIN
,
6907 get_color_value("TOG_COLOR_DIFF_META"), -1);
6909 rc
= init_pair(GOT_DIFF_LINE_BLOB_PLUS
,
6910 get_color_value("TOG_COLOR_DIFF_META"), -1);
6912 rc
= init_pair(GOT_DIFF_LINE_AUTHOR
,
6913 get_color_value("TOG_COLOR_AUTHOR"), -1);
6915 rc
= init_pair(GOT_DIFF_LINE_DATE
,
6916 get_color_value("TOG_COLOR_DATE"), -1);
6918 err
= got_error(GOT_ERR_RANGE
);
6923 if (parent_view
&& parent_view
->type
== TOG_VIEW_LOG
&&
6924 view_is_splitscreen(view
)) {
6925 err
= show_log_view(parent_view
); /* draw border */
6929 diff_view_indicate_progress(view
);
6931 err
= create_diff(s
);
6933 view
->show
= show_diff_view
;
6934 view
->input
= input_diff_view
;
6935 view
->reset
= reset_diff_view
;
6936 view
->close
= close_diff_view
;
6937 view
->search_start
= search_start_diff_view
;
6938 view
->search_setup
= search_setup_diff_view
;
6939 view
->search_next
= search_next_view_match
;
6942 if (view
->close
== NULL
)
6943 close_diff_view(view
);
6949 static const struct got_error
*
6950 show_diff_view(struct tog_view
*view
)
6952 const struct got_error
*err
;
6953 struct tog_diff_view_state
*s
= &view
->state
.diff
;
6956 if (s
->diff_worktree
) {
6957 if (asprintf(&header
, "diff %s%s",
6958 s
->diff_staged
? "-s " : "", s
->worktree_root
) == -1)
6959 return got_error_from_errno("asprintf");
6961 char *id_str2
, *id_str1
= NULL
;
6962 const char *label1
, *label2
;
6965 err
= got_object_id_str(&id_str1
, s
->id1
);
6968 label1
= s
->label1
? s
->label1
: id_str1
;
6970 label1
= "/dev/null";
6972 err
= got_object_id_str(&id_str2
, s
->id2
);
6975 label2
= s
->label2
? s
->label2
: id_str2
;
6977 if (asprintf(&header
, "diff %s %s", label1
, label2
) == -1) {
6978 err
= got_error_from_errno("asprintf");
6987 err
= draw_file(view
, header
);
6992 static const struct got_error
*
6993 diff_write_patch(struct tog_view
*view
)
6995 const struct got_error
*err
;
6996 struct tog_diff_view_state
*s
= &view
->state
.diff
;
6997 struct got_object_id
*id2
= s
->id2
;
6999 char buf
[BUFSIZ
], pathbase
[PATH_MAX
];
7000 char *idstr1
, *idstr2
= NULL
, *path
= NULL
;
7005 if (s
->action
!= NULL
) {
7012 return got_error_from_errno("ftello");
7013 if (fseeko(s
->f
, 0L, SEEK_SET
) == -1)
7014 return got_error_from_errno("fseeko");
7016 if (s
->id1
!= NULL
) {
7017 err
= got_object_id_str(&idstr1
, s
->id1
);
7022 if (s
->diff_worktree
== 0 || tog_base_commit
.id
== NULL
) {
7023 /* illegal state that should not be possible */
7024 err
= got_error(GOT_ERR_NOT_WORKTREE
);
7027 id2
= tog_base_commit
.id
;
7029 err
= got_object_id_str(&idstr2
, id2
);
7033 rc
= snprintf(pathbase
, sizeof(pathbase
), "%s/tog-%.8s-%.8s",
7034 GOT_TMPDIR_STR
, idstr1
!= NULL
? idstr1
: "empty", idstr2
);
7035 if (rc
< 0 || (size_t)rc
>= sizeof(pathbase
)) {
7036 err
= got_error(rc
< 0 ? GOT_ERR_IO
: GOT_ERR_NO_SPACE
);
7040 err
= got_opentemp_named(&path
, &f
, pathbase
, ".diff");
7044 while ((r
= fread(buf
, 1, sizeof(buf
), s
->f
)) > 0) {
7045 if (fwrite(buf
, 1, r
, f
) != r
) {
7046 err
= got_ferror(f
, GOT_ERR_IO
);
7052 err
= got_error_from_errno("fread");
7055 if (fseeko(s
->f
, pos
, SEEK_SET
) == -1) {
7056 err
= got_error_from_errno("fseeko");
7060 if (fflush(f
) == EOF
) {
7061 err
= got_error_from_errno2("fflush", path
);
7065 if (asprintf(&s
->action
, "patch file written to %s", path
) == -1) {
7066 err
= got_error_from_errno("asprintf");
7070 view
->action
= s
->action
;
7073 if (f
!= NULL
&& fclose(f
) == EOF
&& err
== NULL
)
7074 err
= got_error_from_errno2("fclose", path
);
7081 static const struct got_error
*
7082 set_selected_commit(struct tog_diff_view_state
*s
,
7083 struct commit_queue_entry
*entry
)
7085 const struct got_error
*err
;
7086 const struct got_object_id_queue
*parent_ids
;
7087 struct got_commit_object
*selected_commit
;
7088 struct got_object_qid
*pid
;
7095 if (entry
->worktree_entry
== 0) {
7096 s
->id2
= got_object_id_dup(entry
->id
);
7098 return got_error_from_errno("got_object_id_dup");
7100 err
= got_object_open_as_commit(&selected_commit
,
7101 s
->repo
, entry
->id
);
7104 parent_ids
= got_object_commit_get_parent_ids(selected_commit
);
7105 pid
= STAILQ_FIRST(parent_ids
);
7106 s
->id1
= pid
? got_object_id_dup(&pid
->id
) : NULL
;
7107 got_object_commit_close(selected_commit
);
7113 static const struct got_error
*
7114 reset_diff_view(struct tog_view
*view
)
7116 struct tog_diff_view_state
*s
= &view
->state
.diff
;
7119 wclear(view
->window
);
7120 s
->first_displayed_line
= 1;
7121 s
->last_displayed_line
= view
->nlines
;
7122 s
->matched_line
= 0;
7123 if (s
->action
!= NULL
) {
7127 diff_view_indicate_progress(view
);
7128 return create_diff(s
);
7132 diff_prev_index(struct tog_diff_view_state
*s
, enum got_diff_line_type type
)
7136 i
= start
= s
->first_displayed_line
- 1;
7138 while (s
->lines
[i
].type
!= type
) {
7142 return; /* do nothing, requested type not in file */
7145 s
->selected_line
= 1;
7146 s
->first_displayed_line
= i
;
7150 diff_next_index(struct tog_diff_view_state
*s
, enum got_diff_line_type type
)
7154 i
= start
= s
->first_displayed_line
+ 1;
7156 while (s
->lines
[i
].type
!= type
) {
7157 if (i
== s
->nlines
- 1)
7160 return; /* do nothing, requested type not in file */
7163 s
->selected_line
= 1;
7164 s
->first_displayed_line
= i
;
7167 static struct got_object_id
*get_selected_commit_id(struct tog_blame_line
*,
7169 static struct got_object_id
*get_annotation_for_line(struct tog_blame_line
*,
7172 static const struct got_error
*
7173 input_diff_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
7175 const struct got_error
*err
= NULL
;
7176 struct tog_diff_view_state
*s
= &view
->state
.diff
;
7177 struct tog_log_view_state
*ls
;
7178 struct commit_queue_entry
*old_selected_entry
;
7180 size_t linesize
= 0;
7182 int i
, nscroll
= view
->nlines
- 1, up
= 0;
7184 s
->lineno
= s
->first_displayed_line
- 1 + s
->selected_line
;
7186 if (s
->action
!= NULL
&& ch
!= ERR
) {
7189 view
->action
= NULL
;
7199 horizontal_scroll_input(view
, ch
);
7204 s
->force_text_diff
= !s
->force_text_diff
;
7205 view
->action
= s
->force_text_diff
?
7206 "force ASCII text enabled" :
7207 "force ASCII text disabled";
7209 else if (ch
== 'w') {
7210 s
->ignore_whitespace
= !s
->ignore_whitespace
;
7211 view
->action
= s
->ignore_whitespace
?
7212 "ignore whitespace enabled" :
7213 "ignore whitespace disabled";
7215 err
= reset_diff_view(view
);
7219 s
->first_displayed_line
= 1;
7228 s
->first_displayed_line
= (s
->nlines
- view
->nlines
) + 2;
7234 if (s
->first_displayed_line
> 1)
7235 s
->first_displayed_line
--;
7246 if (s
->first_displayed_line
== 1) {
7251 while (i
++ < nscroll
&& s
->first_displayed_line
> 1)
7252 s
->first_displayed_line
--;
7258 s
->first_displayed_line
++;
7275 while (!s
->eof
&& i
++ < nscroll
) {
7276 linelen
= getline(&line
, &linesize
, s
->f
);
7277 s
->first_displayed_line
++;
7278 if (linelen
== -1) {
7282 err
= got_ferror(s
->f
, GOT_ERR_IO
);
7289 diff_prev_index(s
, GOT_DIFF_LINE_BLOB_MIN
);
7292 diff_next_index(s
, GOT_DIFF_LINE_BLOB_MIN
);
7295 diff_prev_index(s
, GOT_DIFF_LINE_HUNK
);
7298 diff_next_index(s
, GOT_DIFF_LINE_HUNK
);
7301 if (s
->diff_context
> 0) {
7303 s
->matched_line
= 0;
7304 diff_view_indicate_progress(view
);
7305 err
= create_diff(s
);
7306 if (s
->first_displayed_line
+ view
->nlines
- 1 >
7308 s
->first_displayed_line
= 1;
7309 s
->last_displayed_line
= view
->nlines
;
7315 if (s
->diff_context
< GOT_DIFF_MAX_CONTEXT
) {
7317 s
->matched_line
= 0;
7318 diff_view_indicate_progress(view
);
7319 err
= create_diff(s
);
7331 if (s
->parent_view
== NULL
) {
7335 s
->parent_view
->count
= view
->count
;
7337 if (s
->parent_view
->type
== TOG_VIEW_LOG
) {
7338 ls
= &s
->parent_view
->state
.log
;
7339 old_selected_entry
= ls
->selected_entry
;
7341 err
= input_log_view(NULL
, s
->parent_view
,
7342 up
? KEY_UP
: KEY_DOWN
);
7345 view
->count
= s
->parent_view
->count
;
7347 if (old_selected_entry
== ls
->selected_entry
)
7352 err
= set_selected_commit(s
, ls
->selected_entry
);
7356 if (s
->worktree_root
== NULL
)
7357 s
->worktree_root
= ls
->thread_args
.wctx
.wt_root
;
7358 } else if (s
->parent_view
->type
== TOG_VIEW_BLAME
) {
7359 struct tog_blame_view_state
*bs
;
7360 struct got_object_id
*id
, *prev_id
;
7362 bs
= &s
->parent_view
->state
.blame
;
7363 prev_id
= get_annotation_for_line(bs
->blame
.lines
,
7364 bs
->blame
.nlines
, bs
->last_diffed_line
);
7366 err
= input_blame_view(&view
, s
->parent_view
,
7367 up
? KEY_UP
: KEY_DOWN
);
7370 view
->count
= s
->parent_view
->count
;
7372 if (prev_id
== NULL
)
7374 id
= get_selected_commit_id(bs
->blame
.lines
,
7375 bs
->blame
.nlines
, bs
->first_displayed_line
,
7380 if (!got_object_id_cmp(prev_id
, id
))
7383 err
= input_blame_view(&view
, s
->parent_view
, KEY_ENTER
);
7388 s
->diff_worktree
= 0;
7389 s
->first_displayed_line
= 1;
7390 s
->last_displayed_line
= view
->nlines
;
7391 s
->matched_line
= 0;
7394 diff_view_indicate_progress(view
);
7395 err
= create_diff(s
);
7399 err
= diff_write_patch(view
);
7409 static const struct got_error
*
7410 get_worktree_paths_from_argv(struct got_pathlist_head
*paths
, int argc
,
7411 char *argv
[], struct got_worktree
*worktree
)
7413 const struct got_error
*err
= NULL
;
7415 struct got_pathlist_entry
*new;
7421 return got_error_from_errno("strdup");
7422 return got_pathlist_insert(NULL
, paths
, path
, NULL
);
7425 for (i
= 0; i
< argc
; i
++) {
7426 err
= got_worktree_resolve_path(&path
, worktree
, argv
[i
]);
7429 err
= got_pathlist_insert(&new, paths
, path
, NULL
);
7430 if (err
!= NULL
|| new == NULL
) {
7440 static const struct got_error
*
7441 cmd_diff(int argc
, char *argv
[])
7443 const struct got_error
*error
;
7444 struct got_repository
*repo
= NULL
;
7445 struct got_worktree
*worktree
= NULL
;
7446 struct got_pathlist_head paths
;
7447 struct got_object_id
*ids
[2] = { NULL
, NULL
};
7448 const char *commit_args
[2] = { NULL
, NULL
};
7449 char *labels
[2] = { NULL
, NULL
};
7450 char *repo_path
= NULL
, *worktree_path
= NULL
, *cwd
= NULL
;
7451 int type1
= GOT_OBJ_TYPE_ANY
, type2
= GOT_OBJ_TYPE_ANY
;
7452 int i
, ncommit_args
= 0, diff_context
= 3, ignore_whitespace
= 0;
7453 int ch
, diff_staged
= 0, diff_worktree
= 0, force_text_diff
= 0;
7455 struct tog_view
*view
;
7456 int *pack_fds
= NULL
;
7460 while ((ch
= getopt(argc
, argv
, "aC:c:r:sw")) != -1) {
7463 force_text_diff
= 1;
7466 diff_context
= strtonum(optarg
, 0, GOT_DIFF_MAX_CONTEXT
,
7469 errx(1, "number of context lines is %s: %s",
7473 if (ncommit_args
>= 2)
7474 errx(1, "too many -c options used");
7475 commit_args
[ncommit_args
++] = optarg
;
7478 repo_path
= realpath(optarg
, NULL
);
7479 if (repo_path
== NULL
)
7480 return got_error_from_errno2("realpath",
7482 got_path_strip_trailing_slashes(repo_path
);
7488 ignore_whitespace
= 1;
7499 error
= got_repo_pack_fds_open(&pack_fds
);
7503 if (repo_path
== NULL
) {
7504 cwd
= getcwd(NULL
, 0);
7506 return got_error_from_errno("getcwd");
7507 error
= got_worktree_open(&worktree
, cwd
, NULL
);
7508 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
7512 strdup(got_worktree_get_repo_path(worktree
));
7514 repo_path
= strdup(cwd
);
7515 if (repo_path
== NULL
) {
7516 error
= got_error_from_errno("strdup");
7521 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
7525 if (diff_staged
&& (worktree
== NULL
|| ncommit_args
> 0)) {
7526 error
= got_error_msg(GOT_ERR_BAD_OPTION
,
7527 "-s can only be used when diffing a work tree");
7533 error
= apply_unveil(got_repo_get_path(repo
),
7534 worktree
!= NULL
? got_worktree_get_root_path(worktree
) : NULL
);
7538 if (argc
== 2 || ncommit_args
> 0) {
7539 int obj_type
= (ncommit_args
> 0 ?
7540 GOT_OBJ_TYPE_COMMIT
: GOT_OBJ_TYPE_ANY
);
7542 error
= tog_load_refs(repo
, 0);
7546 for (i
= 0; i
< (ncommit_args
> 0 ? ncommit_args
: argc
); ++i
) {
7548 char *keyword_idstr
= NULL
;
7550 if (ncommit_args
> 0)
7551 arg
= commit_args
[i
];
7555 error
= got_keyword_to_idstr(&keyword_idstr
, arg
,
7559 if (keyword_idstr
!= NULL
)
7560 arg
= keyword_idstr
;
7562 error
= got_repo_match_object_id(&ids
[i
], &labels
[i
],
7563 arg
, obj_type
, &tog_refs
, repo
);
7564 free(keyword_idstr
);
7565 if (error
!= NULL
) {
7566 if (error
->code
!= GOT_ERR_NOT_REF
&&
7567 error
->code
!= GOT_ERR_NO_OBJ
)
7569 if (ncommit_args
> 0)
7577 if (diff_staged
&& ids
[0] != NULL
) {
7578 error
= got_error_msg(GOT_ERR_BAD_OPTION
,
7579 "-s can only be used when diffing a work tree");
7583 if (ncommit_args
== 0 && (ids
[0] == NULL
|| ids
[1] == NULL
)) {
7584 if (worktree
== NULL
) {
7585 if (argc
== 2 && ids
[0] == NULL
) {
7586 error
= got_error_path(argv
[0], GOT_ERR_NO_OBJ
);
7588 } else if (argc
== 2 && ids
[1] == NULL
) {
7589 error
= got_error_path(argv
[1], GOT_ERR_NO_OBJ
);
7591 } else if (argc
> 0) {
7592 error
= got_error_fmt(GOT_ERR_NOT_WORKTREE
,
7593 "%s", "specified paths cannot be resolved");
7596 error
= got_error(GOT_ERR_NOT_WORKTREE
);
7601 error
= get_worktree_paths_from_argv(&paths
, argc
, argv
,
7606 worktree_path
= strdup(got_worktree_get_root_path(worktree
));
7607 if (worktree_path
== NULL
) {
7608 error
= got_error_from_errno("strdup");
7614 if (ncommit_args
== 1) { /* diff commit against its first parent */
7615 struct got_commit_object
*commit
;
7617 error
= got_object_open_as_commit(&commit
, repo
, ids
[0]);
7621 labels
[1] = labels
[0];
7623 if (got_object_commit_get_nparents(commit
) > 0) {
7624 const struct got_object_id_queue
*pids
;
7625 struct got_object_qid
*pid
;
7627 pids
= got_object_commit_get_parent_ids(commit
);
7628 pid
= STAILQ_FIRST(pids
);
7629 ids
[0] = got_object_id_dup(&pid
->id
);
7630 if (ids
[0] == NULL
) {
7631 error
= got_error_from_errno(
7632 "got_object_id_dup");
7633 got_object_commit_close(commit
);
7636 error
= got_object_id_str(&labels
[0], ids
[0]);
7637 if (error
!= NULL
) {
7638 got_object_commit_close(commit
);
7643 labels
[0] = strdup("/dev/null");
7644 if (labels
[0] == NULL
) {
7645 error
= got_error_from_errno("strdup");
7646 got_object_commit_close(commit
);
7651 got_object_commit_close(commit
);
7654 if (ncommit_args
== 0 && argc
> 2) {
7655 error
= got_error_msg(GOT_ERR_BAD_PATH
,
7656 "path arguments cannot be used when diffing two objects");
7661 error
= got_object_get_type(&type1
, repo
, ids
[0]);
7666 if (diff_worktree
== 0) {
7667 error
= got_object_get_type(&type2
, repo
, ids
[1]);
7670 if (type1
!= GOT_OBJ_TYPE_ANY
&& type1
!= type2
) {
7671 error
= got_error(GOT_ERR_OBJ_TYPE
);
7674 if (type1
== GOT_OBJ_TYPE_BLOB
&& argc
> 2) {
7675 error
= got_error_msg(GOT_ERR_OBJ_TYPE
,
7676 "path arguments cannot be used when diffing blobs");
7681 for (i
= 0; ncommit_args
> 0 && i
< argc
; i
++) {
7683 struct got_pathlist_entry
*new;
7689 error
= got_worktree_resolve_path(&p
, worktree
,
7693 prefix
= got_worktree_get_path_prefix(worktree
);
7694 while (prefix
[0] == '/')
7696 if (asprintf(&in_repo_path
, "%s%s%s", prefix
,
7697 (p
[0] != '\0' && prefix
[0] != '\0') ? "/" : "",
7699 error
= got_error_from_errno("asprintf");
7705 char *mapped_path
, *s
;
7707 error
= got_repo_map_path(&mapped_path
, repo
, argv
[i
]);
7713 in_repo_path
= strdup(s
);
7714 if (in_repo_path
== NULL
) {
7715 error
= got_error_from_errno("asprintf");
7722 error
= got_pathlist_insert(&new, &paths
, in_repo_path
, NULL
);
7723 if (error
!= NULL
|| new == NULL
)
7729 view
= view_open(0, 0, 0, 0, TOG_VIEW_DIFF
);
7731 error
= got_error_from_errno("view_open");
7736 error
= set_tog_base_commit(repo
, worktree
);
7740 /* Release work tree lock. */
7741 got_worktree_close(worktree
);
7745 error
= open_diff_view(view
, ids
[0], ids
[1], labels
[0], labels
[1],
7746 diff_context
, ignore_whitespace
, force_text_diff
, diff_staged
,
7747 diff_worktree
, worktree_path
, NULL
, repo
, &paths
);
7751 error
= view_loop(view
);
7754 got_pathlist_free(&paths
, GOT_PATHLIST_FREE_PATH
);
7755 free(tog_base_commit
.id
);
7756 free(worktree_path
);
7764 const struct got_error
*close_err
= got_repo_close(repo
);
7769 got_worktree_close(worktree
);
7771 const struct got_error
*pack_err
=
7772 got_repo_pack_fds_close(pack_fds
);
7785 "usage: %s blame [-c commit] [-r repository-path] path\n",
7790 struct tog_blame_line
{
7792 struct got_object_id
*id
;
7795 static const struct got_error
*
7796 draw_blame(struct tog_view
*view
)
7798 struct tog_blame_view_state
*s
= &view
->state
.blame
;
7799 struct tog_blame
*blame
= &s
->blame
;
7800 regmatch_t
*regmatch
= &view
->regmatch
;
7801 const struct got_error
*err
;
7802 int lineno
= 0, nprinted
= 0;
7804 size_t linesize
= 0;
7808 struct tog_blame_line
*blame_line
;
7809 struct got_object_id
*prev_id
= NULL
;
7811 struct tog_color
*tc
;
7813 err
= got_object_id_str(&id_str
, &s
->blamed_commit
->id
);
7818 werase(view
->window
);
7820 if (asprintf(&line
, "commit %s", id_str
) == -1) {
7821 err
= got_error_from_errno("asprintf");
7826 err
= format_line(&wline
, &width
, NULL
, line
, 0, view
->ncols
, 0, 0);
7831 if (view_needs_focus_indication(view
))
7832 wstandout(view
->window
);
7833 tc
= get_color(&s
->colors
, TOG_COLOR_COMMIT
);
7835 wattr_on(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
7836 waddwstr(view
->window
, wline
);
7837 while (width
++ < view
->ncols
)
7838 waddch(view
->window
, ' ');
7840 wattr_off(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
7841 if (view_needs_focus_indication(view
))
7842 wstandend(view
->window
);
7846 if (view
->gline
> blame
->nlines
)
7847 view
->gline
= blame
->nlines
;
7849 if (tog_io
.wait_for_ui
) {
7850 struct tog_blame_thread_args
*bta
= &s
->blame
.thread_args
;
7853 rc
= pthread_cond_wait(&bta
->blame_complete
, &tog_mutex
);
7855 return got_error_set_errno(rc
, "pthread_cond_wait");
7856 tog_io
.wait_for_ui
= 0;
7859 if (asprintf(&line
, "[%d/%d] %s%s", view
->gline
? view
->gline
:
7860 s
->first_displayed_line
- 1 + s
->selected_line
, blame
->nlines
,
7861 s
->blame_complete
? "" : "annotating... ", s
->path
) == -1) {
7863 return got_error_from_errno("asprintf");
7866 err
= format_line(&wline
, &width
, NULL
, line
, 0, view
->ncols
, 0, 0);
7871 waddwstr(view
->window
, wline
);
7874 if (width
< view
->ncols
- 1)
7875 waddch(view
->window
, '\n');
7879 while (nprinted
< view
->nlines
- 2) {
7880 linelen
= getline(&line
, &linesize
, blame
->f
);
7881 if (linelen
== -1) {
7882 if (feof(blame
->f
)) {
7887 return got_ferror(blame
->f
, GOT_ERR_IO
);
7889 if (++lineno
< s
->first_displayed_line
)
7891 if (view
->gline
&& !gotoline(view
, &lineno
, &nprinted
))
7894 /* Set view->maxx based on full line length. */
7895 err
= format_line(&wline
, &width
, NULL
, line
, 0, INT_MAX
, 9, 1);
7902 view
->maxx
= MAX(view
->maxx
, width
);
7904 if (nprinted
== s
->selected_line
- 1)
7905 wstandout(view
->window
);
7907 if (blame
->nlines
> 0) {
7908 blame_line
= &blame
->lines
[lineno
- 1];
7909 if (blame_line
->annotated
&& prev_id
&&
7910 got_object_id_cmp(prev_id
, blame_line
->id
) == 0 &&
7911 !(nprinted
== s
->selected_line
- 1)) {
7912 waddstr(view
->window
, " ");
7913 } else if (blame_line
->annotated
) {
7915 err
= got_object_id_str(&id_str
,
7921 tc
= get_color(&s
->colors
, TOG_COLOR_COMMIT
);
7923 wattr_on(view
->window
,
7924 COLOR_PAIR(tc
->colorpair
), NULL
);
7925 wprintw(view
->window
, "%.8s", id_str
);
7927 wattr_off(view
->window
,
7928 COLOR_PAIR(tc
->colorpair
), NULL
);
7930 prev_id
= blame_line
->id
;
7932 waddstr(view
->window
, "........");
7936 waddstr(view
->window
, "........");
7940 if (nprinted
== s
->selected_line
- 1)
7941 wstandend(view
->window
);
7942 waddstr(view
->window
, " ");
7944 if (view
->ncols
<= 9) {
7946 } else if (s
->first_displayed_line
+ nprinted
==
7948 regmatch
->rm_so
>= 0 && regmatch
->rm_so
< regmatch
->rm_eo
) {
7949 err
= add_matched_line(&width
, line
, view
->ncols
- 9, 9,
7950 view
->window
, view
->x
, regmatch
);
7958 err
= format_line(&wline
, &width
, &skip
, line
,
7959 view
->x
, view
->ncols
- 9, 9, 1);
7964 waddwstr(view
->window
, &wline
[skip
]);
7970 if (width
<= view
->ncols
- 1)
7971 waddch(view
->window
, '\n');
7972 if (++nprinted
== 1)
7973 s
->first_displayed_line
= lineno
;
7976 s
->last_displayed_line
= lineno
;
7983 static const struct got_error
*
7984 blame_cb(void *arg
, int nlines
, int lineno
,
7985 struct got_commit_object
*commit
, struct got_object_id
*id
)
7987 const struct got_error
*err
= NULL
;
7988 struct tog_blame_cb_args
*a
= arg
;
7989 struct tog_blame_line
*line
;
7992 if (nlines
!= a
->nlines
||
7993 (lineno
!= -1 && lineno
< 1) || lineno
> a
->nlines
)
7994 return got_error(GOT_ERR_RANGE
);
7996 errcode
= pthread_mutex_lock(&tog_mutex
);
7998 return got_error_set_errno(errcode
, "pthread_mutex_lock");
8000 if (*a
->quit
) { /* user has quit the blame view */
8001 err
= got_error(GOT_ERR_ITER_COMPLETED
);
8006 goto done
; /* no change in this commit */
8008 line
= &a
->lines
[lineno
- 1];
8009 if (line
->annotated
)
8012 line
->id
= got_object_id_dup(id
);
8013 if (line
->id
== NULL
) {
8014 err
= got_error_from_errno("got_object_id_dup");
8017 line
->annotated
= 1;
8019 errcode
= pthread_mutex_unlock(&tog_mutex
);
8021 err
= got_error_set_errno(errcode
, "pthread_mutex_unlock");
8026 blame_thread(void *arg
)
8028 const struct got_error
*err
, *close_err
;
8029 struct tog_blame_thread_args
*ta
= arg
;
8030 struct tog_blame_cb_args
*a
= ta
->cb_args
;
8031 int errcode
, fd1
= -1, fd2
= -1;
8032 FILE *f1
= NULL
, *f2
= NULL
;
8034 fd1
= got_opentempfd();
8036 return (void *)got_error_from_errno("got_opentempfd");
8038 fd2
= got_opentempfd();
8040 err
= got_error_from_errno("got_opentempfd");
8044 f1
= got_opentemp();
8046 err
= (void *)got_error_from_errno("got_opentemp");
8049 f2
= got_opentemp();
8051 err
= (void *)got_error_from_errno("got_opentemp");
8055 err
= block_signals_used_by_main_thread();
8059 err
= got_blame(ta
->path
, a
->commit_id
, ta
->repo
,
8060 tog_diff_algo
, blame_cb
, ta
->cb_args
,
8061 ta
->cancel_cb
, ta
->cancel_arg
, fd1
, fd2
, f1
, f2
);
8062 if (err
&& err
->code
== GOT_ERR_CANCELLED
)
8065 errcode
= pthread_mutex_lock(&tog_mutex
);
8067 err
= got_error_set_errno(errcode
, "pthread_mutex_lock");
8071 close_err
= got_repo_close(ta
->repo
);
8077 if (tog_io
.wait_for_ui
) {
8078 errcode
= pthread_cond_signal(&ta
->blame_complete
);
8079 if (errcode
&& err
== NULL
)
8080 err
= got_error_set_errno(errcode
,
8081 "pthread_cond_signal");
8084 errcode
= pthread_mutex_unlock(&tog_mutex
);
8085 if (errcode
&& err
== NULL
)
8086 err
= got_error_set_errno(errcode
, "pthread_mutex_unlock");
8089 if (fd1
!= -1 && close(fd1
) == -1 && err
== NULL
)
8090 err
= got_error_from_errno("close");
8091 if (fd2
!= -1 && close(fd2
) == -1 && err
== NULL
)
8092 err
= got_error_from_errno("close");
8093 if (f1
&& fclose(f1
) == EOF
&& err
== NULL
)
8094 err
= got_error_from_errno("fclose");
8095 if (f2
&& fclose(f2
) == EOF
&& err
== NULL
)
8096 err
= got_error_from_errno("fclose");
8101 static struct got_object_id
*
8102 get_selected_commit_id(struct tog_blame_line
*lines
, int nlines
,
8103 int first_displayed_line
, int selected_line
)
8105 struct tog_blame_line
*line
;
8110 line
= &lines
[first_displayed_line
- 1 + selected_line
- 1];
8111 if (!line
->annotated
)
8117 static struct got_object_id
*
8118 get_annotation_for_line(struct tog_blame_line
*lines
, int nlines
,
8121 struct tog_blame_line
*line
;
8123 if (nlines
<= 0 || lineno
>= nlines
)
8126 line
= &lines
[lineno
- 1];
8127 if (!line
->annotated
)
8133 static const struct got_error
*
8134 stop_blame(struct tog_blame
*blame
)
8136 const struct got_error
*err
= NULL
;
8139 if (blame
->thread
) {
8141 errcode
= pthread_mutex_unlock(&tog_mutex
);
8143 return got_error_set_errno(errcode
,
8144 "pthread_mutex_unlock");
8145 errcode
= pthread_join(blame
->thread
, (void **)&err
);
8147 return got_error_set_errno(errcode
, "pthread_join");
8148 errcode
= pthread_mutex_lock(&tog_mutex
);
8150 return got_error_set_errno(errcode
,
8151 "pthread_mutex_lock");
8152 if (err
&& err
->code
== GOT_ERR_ITER_COMPLETED
)
8154 blame
->thread
= 0; //NULL;
8156 if (blame
->thread_args
.repo
) {
8157 const struct got_error
*close_err
;
8158 close_err
= got_repo_close(blame
->thread_args
.repo
);
8161 blame
->thread_args
.repo
= NULL
;
8164 if (fclose(blame
->f
) == EOF
&& err
== NULL
)
8165 err
= got_error_from_errno("fclose");
8169 for (i
= 0; i
< blame
->nlines
; i
++)
8170 free(blame
->lines
[i
].id
);
8172 blame
->lines
= NULL
;
8174 free(blame
->cb_args
.commit_id
);
8175 blame
->cb_args
.commit_id
= NULL
;
8176 if (blame
->pack_fds
) {
8177 const struct got_error
*pack_err
=
8178 got_repo_pack_fds_close(blame
->pack_fds
);
8181 blame
->pack_fds
= NULL
;
8183 free(blame
->line_offsets
);
8184 blame
->line_offsets
= NULL
;
8188 static const struct got_error
*
8189 cancel_blame_view(void *arg
)
8191 const struct got_error
*err
= NULL
;
8195 errcode
= pthread_mutex_lock(&tog_mutex
);
8197 return got_error_set_errno(errcode
, "pthread_mutex_lock");
8200 err
= got_error(GOT_ERR_CANCELLED
);
8202 errcode
= pthread_mutex_unlock(&tog_mutex
);
8204 return got_error_set_errno(errcode
, "pthread_mutex_unlock");
8209 static const struct got_error
*
8210 run_blame(struct tog_view
*view
)
8212 struct tog_blame_view_state
*s
= &view
->state
.blame
;
8213 struct tog_blame
*blame
= &s
->blame
;
8214 const struct got_error
*err
= NULL
;
8215 struct got_commit_object
*commit
= NULL
;
8216 struct got_blob_object
*blob
= NULL
;
8217 struct got_repository
*thread_repo
= NULL
;
8218 struct got_object_id
*obj_id
= NULL
;
8219 int obj_type
, fd
= -1;
8220 int *pack_fds
= NULL
;
8222 err
= got_object_open_as_commit(&commit
, s
->repo
,
8223 &s
->blamed_commit
->id
);
8227 fd
= got_opentempfd();
8229 err
= got_error_from_errno("got_opentempfd");
8233 err
= got_object_id_by_path(&obj_id
, s
->repo
, commit
, s
->path
);
8237 err
= got_object_get_type(&obj_type
, s
->repo
, obj_id
);
8241 if (obj_type
!= GOT_OBJ_TYPE_BLOB
) {
8242 err
= got_error(GOT_ERR_OBJ_TYPE
);
8246 err
= got_object_open_as_blob(&blob
, s
->repo
, obj_id
, 8192, fd
);
8249 blame
->f
= got_opentemp();
8250 if (blame
->f
== NULL
) {
8251 err
= got_error_from_errno("got_opentemp");
8254 err
= got_object_blob_dump_to_file(&blame
->filesize
, &blame
->nlines
,
8255 &blame
->line_offsets
, blame
->f
, blob
);
8258 if (blame
->nlines
== 0) {
8259 s
->blame_complete
= 1;
8263 /* Don't include \n at EOF in the blame line count. */
8264 if (blame
->line_offsets
[blame
->nlines
- 1] == blame
->filesize
)
8267 blame
->lines
= calloc(blame
->nlines
, sizeof(*blame
->lines
));
8268 if (blame
->lines
== NULL
) {
8269 err
= got_error_from_errno("calloc");
8273 err
= got_repo_pack_fds_open(&pack_fds
);
8276 err
= got_repo_open(&thread_repo
, got_repo_get_path(s
->repo
), NULL
,
8281 blame
->pack_fds
= pack_fds
;
8282 blame
->cb_args
.view
= view
;
8283 blame
->cb_args
.lines
= blame
->lines
;
8284 blame
->cb_args
.nlines
= blame
->nlines
;
8285 blame
->cb_args
.commit_id
= got_object_id_dup(&s
->blamed_commit
->id
);
8286 if (blame
->cb_args
.commit_id
== NULL
) {
8287 err
= got_error_from_errno("got_object_id_dup");
8290 blame
->cb_args
.quit
= &s
->done
;
8292 blame
->thread_args
.path
= s
->path
;
8293 blame
->thread_args
.repo
= thread_repo
;
8294 blame
->thread_args
.cb_args
= &blame
->cb_args
;
8295 blame
->thread_args
.complete
= &s
->blame_complete
;
8296 blame
->thread_args
.cancel_cb
= cancel_blame_view
;
8297 blame
->thread_args
.cancel_arg
= &s
->done
;
8298 s
->blame_complete
= 0;
8300 if (s
->first_displayed_line
+ view
->nlines
- 1 > blame
->nlines
) {
8301 s
->first_displayed_line
= 1;
8302 s
->last_displayed_line
= view
->nlines
;
8303 s
->selected_line
= 1;
8305 s
->matched_line
= 0;
8309 got_object_commit_close(commit
);
8310 if (fd
!= -1 && close(fd
) == -1 && err
== NULL
)
8311 err
= got_error_from_errno("close");
8313 got_object_blob_close(blob
);
8320 static const struct got_error
*
8321 open_blame_view(struct tog_view
*view
, char *path
,
8322 struct got_object_id
*commit_id
, struct got_repository
*repo
)
8324 const struct got_error
*err
= NULL
;
8325 struct tog_blame_view_state
*s
= &view
->state
.blame
;
8327 STAILQ_INIT(&s
->blamed_commits
);
8329 s
->path
= strdup(path
);
8330 if (s
->path
== NULL
)
8331 return got_error_from_errno("strdup");
8333 err
= got_object_qid_alloc(&s
->blamed_commit
, commit_id
);
8339 STAILQ_INSERT_HEAD(&s
->blamed_commits
, s
->blamed_commit
, entry
);
8340 s
->first_displayed_line
= 1;
8341 s
->last_displayed_line
= view
->nlines
;
8342 s
->selected_line
= 1;
8343 s
->blame_complete
= 0;
8345 s
->commit_id
= commit_id
;
8346 memset(&s
->blame
, 0, sizeof(s
->blame
));
8348 STAILQ_INIT(&s
->colors
);
8349 if (has_colors() && getenv("TOG_COLORS") != NULL
) {
8350 err
= add_color(&s
->colors
, "^", TOG_COLOR_COMMIT
,
8351 get_color_value("TOG_COLOR_COMMIT"));
8356 view
->show
= show_blame_view
;
8357 view
->input
= input_blame_view
;
8358 view
->reset
= reset_blame_view
;
8359 view
->close
= close_blame_view
;
8360 view
->search_start
= search_start_blame_view
;
8361 view
->search_setup
= search_setup_blame_view
;
8362 view
->search_next
= search_next_view_match
;
8364 if (using_mock_io
) {
8365 struct tog_blame_thread_args
*bta
= &s
->blame
.thread_args
;
8368 rc
= pthread_cond_init(&bta
->blame_complete
, NULL
);
8370 return got_error_set_errno(rc
, "pthread_cond_init");
8373 return run_blame(view
);
8376 static const struct got_error
*
8377 close_blame_view(struct tog_view
*view
)
8379 const struct got_error
*err
= NULL
;
8380 struct tog_blame_view_state
*s
= &view
->state
.blame
;
8382 if (s
->blame
.thread
)
8383 err
= stop_blame(&s
->blame
);
8385 got_object_id_queue_free(&s
->blamed_commits
);
8387 if (using_mock_io
) {
8388 struct tog_blame_thread_args
*bta
= &s
->blame
.thread_args
;
8391 rc
= pthread_cond_destroy(&bta
->blame_complete
);
8392 if (rc
&& err
== NULL
)
8393 err
= got_error_set_errno(rc
, "pthread_cond_destroy");
8397 free_colors(&s
->colors
);
8401 static const struct got_error
*
8402 search_start_blame_view(struct tog_view
*view
)
8404 struct tog_blame_view_state
*s
= &view
->state
.blame
;
8406 s
->matched_line
= 0;
8411 search_setup_blame_view(struct tog_view
*view
, FILE **f
, off_t
**line_offsets
,
8412 size_t *nlines
, int **first
, int **last
, int **match
, int **selected
)
8414 struct tog_blame_view_state
*s
= &view
->state
.blame
;
8417 *nlines
= s
->blame
.nlines
;
8418 *line_offsets
= s
->blame
.line_offsets
;
8419 *match
= &s
->matched_line
;
8420 *first
= &s
->first_displayed_line
;
8421 *last
= &s
->last_displayed_line
;
8422 *selected
= &s
->selected_line
;
8425 static const struct got_error
*
8426 show_blame_view(struct tog_view
*view
)
8428 const struct got_error
*err
= NULL
;
8429 struct tog_blame_view_state
*s
= &view
->state
.blame
;
8432 if (s
->blame
.thread
== 0 && !s
->blame_complete
) {
8433 errcode
= pthread_create(&s
->blame
.thread
, NULL
, blame_thread
,
8434 &s
->blame
.thread_args
);
8436 return got_error_set_errno(errcode
, "pthread_create");
8439 halfdelay(1); /* fast refresh while annotating */
8442 if (s
->blame_complete
&& !using_mock_io
)
8443 halfdelay(10); /* disable fast refresh */
8445 err
= draw_blame(view
);
8451 static const struct got_error
*
8452 log_annotated_line(struct tog_view
**new_view
, int begin_y
, int begin_x
,
8453 struct got_repository
*repo
, struct got_object_id
*id
)
8455 struct tog_view
*log_view
;
8456 const struct got_error
*err
= NULL
;
8460 log_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_LOG
);
8461 if (log_view
== NULL
)
8462 return got_error_from_errno("view_open");
8464 err
= open_log_view(log_view
, id
, repo
, GOT_REF_HEAD
, "", 0, NULL
);
8466 view_close(log_view
);
8468 *new_view
= log_view
;
8473 static const struct got_error
*
8474 input_blame_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
8476 const struct got_error
*err
= NULL
, *thread_err
= NULL
;
8477 struct tog_view
*diff_view
;
8478 struct tog_blame_view_state
*s
= &view
->state
.blame
;
8479 int eos
, nscroll
, begin_y
= 0, begin_x
= 0;
8481 eos
= nscroll
= view
->nlines
- 2;
8482 if (view_is_hsplit_top(view
))
8492 horizontal_scroll_input(view
, ch
);
8499 s
->selected_line
= 1;
8500 s
->first_displayed_line
= 1;
8505 if (s
->blame
.nlines
< eos
) {
8506 s
->selected_line
= s
->blame
.nlines
;
8507 s
->first_displayed_line
= 1;
8509 s
->selected_line
= eos
;
8510 s
->first_displayed_line
= s
->blame
.nlines
- (eos
- 1);
8517 if (s
->selected_line
> 1)
8519 else if (s
->selected_line
== 1 &&
8520 s
->first_displayed_line
> 1)
8521 s
->first_displayed_line
--;
8532 if (s
->first_displayed_line
== 1) {
8533 if (view
->count
> 1)
8535 s
->selected_line
= MAX(1, s
->selected_line
- nscroll
);
8539 if (s
->first_displayed_line
> nscroll
)
8540 s
->first_displayed_line
-= nscroll
;
8542 s
->first_displayed_line
= 1;
8547 if (s
->selected_line
< eos
&& s
->first_displayed_line
+
8548 s
->selected_line
<= s
->blame
.nlines
)
8550 else if (s
->first_displayed_line
< s
->blame
.nlines
- (eos
- 1))
8551 s
->first_displayed_line
++;
8557 struct got_object_id
*id
= NULL
;
8560 id
= get_selected_commit_id(s
->blame
.lines
, s
->blame
.nlines
,
8561 s
->first_displayed_line
, s
->selected_line
);
8565 struct got_commit_object
*commit
, *pcommit
;
8566 struct got_object_qid
*pid
;
8567 struct got_object_id
*blob_id
= NULL
;
8569 err
= got_object_open_as_commit(&commit
,
8574 got_object_commit_get_parent_ids(commit
));
8576 got_object_commit_close(commit
);
8579 /* Check if path history ends here. */
8580 err
= got_object_open_as_commit(&pcommit
,
8584 err
= got_object_id_by_path(&blob_id
, s
->repo
,
8586 got_object_commit_close(pcommit
);
8588 if (err
->code
== GOT_ERR_NO_TREE_ENTRY
)
8590 got_object_commit_close(commit
);
8593 err
= got_object_get_type(&obj_type
, s
->repo
,
8596 /* Can't blame non-blob type objects. */
8597 if (obj_type
!= GOT_OBJ_TYPE_BLOB
) {
8598 got_object_commit_close(commit
);
8601 err
= got_object_qid_alloc(&s
->blamed_commit
,
8603 got_object_commit_close(commit
);
8605 if (got_object_id_cmp(id
,
8606 &s
->blamed_commit
->id
) == 0)
8608 err
= got_object_qid_alloc(&s
->blamed_commit
,
8614 thread_err
= stop_blame(&s
->blame
);
8618 STAILQ_INSERT_HEAD(&s
->blamed_commits
,
8619 s
->blamed_commit
, entry
);
8620 err
= run_blame(view
);
8626 struct got_object_qid
*first
;
8629 first
= STAILQ_FIRST(&s
->blamed_commits
);
8630 if (!got_object_id_cmp(&first
->id
, s
->commit_id
))
8633 thread_err
= stop_blame(&s
->blame
);
8637 STAILQ_REMOVE_HEAD(&s
->blamed_commits
, entry
);
8638 got_object_qid_free(s
->blamed_commit
);
8640 STAILQ_FIRST(&s
->blamed_commits
);
8641 err
= run_blame(view
);
8648 s
->id_to_log
= get_selected_commit_id(s
->blame
.lines
,
8649 s
->blame
.nlines
, s
->first_displayed_line
, s
->selected_line
);
8651 err
= view_request_new(new_view
, view
, TOG_VIEW_LOG
);
8655 struct got_object_id
*id
= NULL
;
8656 struct got_object_qid
*pid
;
8657 struct got_commit_object
*commit
= NULL
;
8660 id
= get_selected_commit_id(s
->blame
.lines
, s
->blame
.nlines
,
8661 s
->first_displayed_line
, s
->selected_line
);
8664 err
= got_object_open_as_commit(&commit
, s
->repo
, id
);
8667 pid
= STAILQ_FIRST(got_object_commit_get_parent_ids(commit
));
8669 /* traversed from diff view, release diff resources */
8670 err
= close_diff_view(*new_view
);
8673 diff_view
= *new_view
;
8675 if (view_is_parent_view(view
))
8676 view_get_split(view
, &begin_y
, &begin_x
);
8678 diff_view
= view_open(0, 0, begin_y
, begin_x
,
8680 if (diff_view
== NULL
) {
8681 got_object_commit_close(commit
);
8682 err
= got_error_from_errno("view_open");
8686 err
= open_diff_view(diff_view
, pid
? &pid
->id
: NULL
,
8687 id
, NULL
, NULL
, 3, 0, 0, 0, 0, NULL
, view
, s
->repo
, NULL
);
8688 got_object_commit_close(commit
);
8691 s
->last_diffed_line
= s
->first_displayed_line
- 1 +
8694 break; /* still open from active diff view */
8695 if (view_is_parent_view(view
) &&
8696 view
->mode
== TOG_VIEW_SPLIT_HRZN
) {
8697 err
= view_init_hsplit(view
, begin_y
);
8703 diff_view
->focussed
= 1;
8704 diff_view
->mode
= view
->mode
;
8705 diff_view
->nlines
= view
->lines
- begin_y
;
8706 if (view_is_parent_view(view
)) {
8707 view_transfer_size(diff_view
, view
);
8708 err
= view_close_child(view
);
8711 err
= view_set_child(view
, diff_view
);
8714 view
->focus_child
= 1;
8716 *new_view
= diff_view
;
8729 if (s
->last_displayed_line
>= s
->blame
.nlines
&&
8730 s
->selected_line
>= MIN(s
->blame
.nlines
,
8731 view
->nlines
- 2)) {
8735 if (s
->last_displayed_line
>= s
->blame
.nlines
&&
8736 s
->selected_line
< view
->nlines
- 2) {
8738 MIN(nscroll
, s
->last_displayed_line
-
8739 s
->first_displayed_line
- s
->selected_line
+ 1);
8741 if (s
->last_displayed_line
+ nscroll
<= s
->blame
.nlines
)
8742 s
->first_displayed_line
+= nscroll
;
8744 s
->first_displayed_line
=
8745 s
->blame
.nlines
- (view
->nlines
- 3);
8748 if (s
->selected_line
> view
->nlines
- 2) {
8749 s
->selected_line
= MIN(s
->blame
.nlines
,
8757 return thread_err
? thread_err
: err
;
8760 static const struct got_error
*
8761 reset_blame_view(struct tog_view
*view
)
8763 const struct got_error
*err
;
8764 struct tog_blame_view_state
*s
= &view
->state
.blame
;
8768 err
= stop_blame(&s
->blame
);
8772 return run_blame(view
);
8775 static const struct got_error
*
8776 cmd_blame(int argc
, char *argv
[])
8778 const struct got_error
*error
;
8779 struct got_repository
*repo
= NULL
;
8780 struct got_worktree
*worktree
= NULL
;
8781 char *cwd
= NULL
, *repo_path
= NULL
, *in_repo_path
= NULL
;
8782 char *link_target
= NULL
;
8783 struct got_object_id
*commit_id
= NULL
;
8784 struct got_commit_object
*commit
= NULL
;
8785 char *keyword_idstr
= NULL
, *commit_id_str
= NULL
;
8787 struct tog_view
*view
= NULL
;
8788 int *pack_fds
= NULL
;
8790 while ((ch
= getopt(argc
, argv
, "c:r:")) != -1) {
8793 commit_id_str
= optarg
;
8796 repo_path
= realpath(optarg
, NULL
);
8797 if (repo_path
== NULL
)
8798 return got_error_from_errno2("realpath",
8813 error
= got_repo_pack_fds_open(&pack_fds
);
8817 if (repo_path
== NULL
) {
8818 cwd
= getcwd(NULL
, 0);
8820 return got_error_from_errno("getcwd");
8821 error
= got_worktree_open(&worktree
, cwd
, NULL
);
8822 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
8826 strdup(got_worktree_get_repo_path(worktree
));
8828 repo_path
= strdup(cwd
);
8829 if (repo_path
== NULL
) {
8830 error
= got_error_from_errno("strdup");
8835 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
8839 error
= get_in_repo_path_from_argv0(&in_repo_path
, argc
, argv
, repo
,
8846 error
= apply_unveil(got_repo_get_path(repo
), NULL
);
8850 error
= tog_load_refs(repo
, 0);
8854 if (commit_id_str
== NULL
) {
8855 struct got_reference
*head_ref
;
8856 error
= got_ref_open(&head_ref
, repo
, worktree
?
8857 got_worktree_get_head_ref_name(worktree
) : GOT_REF_HEAD
, 0);
8860 error
= got_ref_resolve(&commit_id
, repo
, head_ref
);
8861 got_ref_close(head_ref
);
8863 error
= got_keyword_to_idstr(&keyword_idstr
, commit_id_str
,
8867 if (keyword_idstr
!= NULL
)
8868 commit_id_str
= keyword_idstr
;
8870 error
= got_repo_match_object_id(&commit_id
, NULL
,
8871 commit_id_str
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
8876 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
8880 error
= got_object_resolve_symlinks(&link_target
, in_repo_path
,
8885 view
= view_open(0, 0, 0, 0, TOG_VIEW_BLAME
);
8887 error
= got_error_from_errno("view_open");
8890 error
= open_blame_view(view
, link_target
? link_target
: in_repo_path
,
8892 if (error
!= NULL
) {
8893 if (view
->close
== NULL
)
8894 close_blame_view(view
);
8900 error
= set_tog_base_commit(repo
, worktree
);
8904 /* Release work tree lock. */
8905 got_worktree_close(worktree
);
8909 error
= view_loop(view
);
8912 free(tog_base_commit
.id
);
8918 free(keyword_idstr
);
8920 got_object_commit_close(commit
);
8922 got_worktree_close(worktree
);
8924 const struct got_error
*close_err
= got_repo_close(repo
);
8929 const struct got_error
*pack_err
=
8930 got_repo_pack_fds_close(pack_fds
);
8938 static const struct got_error
*
8939 draw_tree_entries(struct tog_view
*view
, const char *parent_path
)
8941 struct tog_tree_view_state
*s
= &view
->state
.tree
;
8942 const struct got_error
*err
= NULL
;
8943 struct got_tree_entry
*te
;
8946 struct tog_color
*tc
;
8947 int width
, n
, nentries
, scrollx
, i
= 1;
8948 int limit
= view
->nlines
;
8951 if (view_is_hsplit_top(view
))
8952 --limit
; /* border */
8954 werase(view
->window
);
8959 err
= format_line(&wline
, &width
, NULL
, s
->tree_label
, 0, view
->ncols
,
8963 if (view_needs_focus_indication(view
))
8964 wstandout(view
->window
);
8965 tc
= get_color(&s
->colors
, TOG_COLOR_COMMIT
);
8967 wattr_on(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
8968 waddwstr(view
->window
, wline
);
8971 while (width
++ < view
->ncols
)
8972 waddch(view
->window
, ' ');
8974 wattr_off(view
->window
, COLOR_PAIR(tc
->colorpair
), NULL
);
8975 if (view_needs_focus_indication(view
))
8976 wstandend(view
->window
);
8981 if (s
->first_displayed_entry
) {
8982 i
+= got_tree_entry_get_index(s
->first_displayed_entry
);
8983 if (s
->tree
!= s
->root
)
8984 ++i
; /* account for ".." entry */
8986 nentries
= got_object_tree_get_nentries(s
->tree
);
8987 if (asprintf(&index
, "[%d/%d] %s",
8988 i
, nentries
+ (s
->tree
== s
->root
? 0 : 1), parent_path
) == -1)
8989 return got_error_from_errno("asprintf");
8990 err
= format_line(&wline
, &width
, NULL
, index
, 0, view
->ncols
, 0, 0);
8994 waddwstr(view
->window
, wline
);
8997 if (width
< view
->ncols
- 1)
8998 waddch(view
->window
, '\n');
9001 waddch(view
->window
, '\n');
9005 if (s
->first_displayed_entry
== NULL
) {
9006 te
= got_object_tree_get_first_entry(s
->tree
);
9007 if (s
->selected
== 0) {
9009 wstandout(view
->window
);
9010 s
->selected_entry
= NULL
;
9012 waddstr(view
->window
, " ..\n"); /* parent directory */
9013 if (s
->selected
== 0 && view
->focussed
)
9014 wstandend(view
->window
);
9021 te
= s
->first_displayed_entry
;
9025 for (i
= got_tree_entry_get_index(te
); i
< nentries
; i
++) {
9026 char *line
= NULL
, *id_str
= NULL
, *link_target
= NULL
;
9027 const char *modestr
= "";
9030 te
= got_object_tree_get_entry(s
->tree
, i
);
9031 mode
= got_tree_entry_get_mode(te
);
9034 err
= got_object_id_str(&id_str
,
9035 got_tree_entry_get_id(te
));
9037 return got_error_from_errno(
9038 "got_object_id_str");
9040 if (got_object_tree_entry_is_submodule(te
))
9042 else if (S_ISLNK(mode
)) {
9045 err
= got_tree_entry_get_symlink_target(&link_target
,
9051 for (i
= 0; link_target
[i
] != '\0'; i
++) {
9052 if (!isprint((unsigned char)link_target
[i
]))
9053 link_target
[i
] = '?';
9057 else if (S_ISDIR(mode
))
9059 else if (mode
& S_IXUSR
)
9061 if (asprintf(&line
, "%s %s%s%s%s", id_str
? id_str
: "",
9062 got_tree_entry_get_name(te
), modestr
,
9063 link_target
? " -> ": "",
9064 link_target
? link_target
: "") == -1) {
9067 return got_error_from_errno("asprintf");
9072 /* use full line width to determine view->maxx */
9073 err
= format_line(&wline
, &width
, NULL
, line
, 0, INT_MAX
, 0, 0);
9078 view
->maxx
= MAX(view
->maxx
, width
);
9082 err
= format_line(&wline
, &width
, &scrollx
, line
, view
->x
,
9088 if (n
== s
->selected
) {
9090 wstandout(view
->window
);
9091 s
->selected_entry
= te
;
9093 tc
= match_color(&s
->colors
, line
);
9095 wattr_on(view
->window
,
9096 COLOR_PAIR(tc
->colorpair
), NULL
);
9097 waddwstr(view
->window
, &wline
[scrollx
]);
9099 wattr_off(view
->window
,
9100 COLOR_PAIR(tc
->colorpair
), NULL
);
9101 if (width
< view
->ncols
)
9102 waddch(view
->window
, '\n');
9103 if (n
== s
->selected
&& view
->focussed
)
9104 wstandend(view
->window
);
9110 s
->last_displayed_entry
= te
;
9119 tree_scroll_up(struct tog_tree_view_state
*s
, int maxscroll
)
9121 struct got_tree_entry
*te
;
9122 int isroot
= s
->tree
== s
->root
;
9125 if (s
->first_displayed_entry
== NULL
)
9128 te
= got_tree_entry_get_prev(s
->tree
, s
->first_displayed_entry
);
9129 while (i
++ < maxscroll
) {
9132 s
->first_displayed_entry
= NULL
;
9135 s
->first_displayed_entry
= te
;
9136 te
= got_tree_entry_get_prev(s
->tree
, te
);
9140 static const struct got_error
*
9141 tree_scroll_down(struct tog_view
*view
, int maxscroll
)
9143 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9144 struct got_tree_entry
*next
, *last
;
9147 if (s
->first_displayed_entry
)
9148 next
= got_tree_entry_get_next(s
->tree
,
9149 s
->first_displayed_entry
);
9151 next
= got_object_tree_get_first_entry(s
->tree
);
9153 last
= s
->last_displayed_entry
;
9154 while (next
&& n
++ < maxscroll
) {
9156 s
->last_displayed_entry
= last
;
9157 last
= got_tree_entry_get_next(s
->tree
, last
);
9159 if (last
|| (view
->mode
== TOG_VIEW_SPLIT_HRZN
&& next
)) {
9160 s
->first_displayed_entry
= next
;
9161 next
= got_tree_entry_get_next(s
->tree
, next
);
9168 static const struct got_error
*
9169 tree_entry_path(char **path
, struct tog_parent_trees
*parents
,
9170 struct got_tree_entry
*te
)
9172 const struct got_error
*err
= NULL
;
9173 struct tog_parent_tree
*pt
;
9174 size_t len
= 2; /* for leading slash and NUL */
9176 TAILQ_FOREACH(pt
, parents
, entry
)
9177 len
+= strlen(got_tree_entry_get_name(pt
->selected_entry
))
9180 len
+= strlen(got_tree_entry_get_name(te
));
9182 *path
= calloc(1, len
);
9184 return got_error_from_errno("calloc");
9187 pt
= TAILQ_LAST(parents
, tog_parent_trees
);
9189 const char *name
= got_tree_entry_get_name(pt
->selected_entry
);
9190 if (strlcat(*path
, name
, len
) >= len
) {
9191 err
= got_error(GOT_ERR_NO_SPACE
);
9194 if (strlcat(*path
, "/", len
) >= len
) {
9195 err
= got_error(GOT_ERR_NO_SPACE
);
9198 pt
= TAILQ_PREV(pt
, tog_parent_trees
, entry
);
9201 if (strlcat(*path
, got_tree_entry_get_name(te
), len
) >= len
) {
9202 err
= got_error(GOT_ERR_NO_SPACE
);
9214 static const struct got_error
*
9215 blame_tree_entry(struct tog_view
**new_view
, int begin_y
, int begin_x
,
9216 struct got_tree_entry
*te
, struct tog_parent_trees
*parents
,
9217 struct got_object_id
*commit_id
, struct got_repository
*repo
)
9219 const struct got_error
*err
= NULL
;
9221 struct tog_view
*blame_view
;
9225 err
= tree_entry_path(&path
, parents
, te
);
9229 blame_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_BLAME
);
9230 if (blame_view
== NULL
) {
9231 err
= got_error_from_errno("view_open");
9235 err
= open_blame_view(blame_view
, path
, commit_id
, repo
);
9237 if (err
->code
== GOT_ERR_CANCELLED
)
9239 view_close(blame_view
);
9241 *new_view
= blame_view
;
9247 static const struct got_error
*
9248 log_selected_tree_entry(struct tog_view
**new_view
, int begin_y
, int begin_x
,
9249 struct tog_tree_view_state
*s
)
9251 struct tog_view
*log_view
;
9252 const struct got_error
*err
= NULL
;
9257 log_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_LOG
);
9258 if (log_view
== NULL
)
9259 return got_error_from_errno("view_open");
9261 err
= tree_entry_path(&path
, &s
->parents
, s
->selected_entry
);
9265 err
= open_log_view(log_view
, s
->commit_id
, s
->repo
, s
->head_ref_name
,
9268 view_close(log_view
);
9270 *new_view
= log_view
;
9275 static const struct got_error
*
9276 open_tree_view(struct tog_view
*view
, struct got_object_id
*commit_id
,
9277 const char *head_ref_name
, struct got_repository
*repo
)
9279 const struct got_error
*err
= NULL
;
9280 char *commit_id_str
= NULL
;
9281 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9282 struct got_commit_object
*commit
= NULL
;
9284 TAILQ_INIT(&s
->parents
);
9285 STAILQ_INIT(&s
->colors
);
9287 s
->commit_id
= got_object_id_dup(commit_id
);
9288 if (s
->commit_id
== NULL
) {
9289 err
= got_error_from_errno("got_object_id_dup");
9293 err
= got_object_open_as_commit(&commit
, repo
, commit_id
);
9298 * The root is opened here and will be closed when the view is closed.
9299 * Any visited subtrees and their path-wise parents are opened and
9302 err
= got_object_open_as_tree(&s
->root
, repo
,
9303 got_object_commit_get_tree_id(commit
));
9308 err
= got_object_id_str(&commit_id_str
, commit_id
);
9312 if (asprintf(&s
->tree_label
, "commit %s", commit_id_str
) == -1) {
9313 err
= got_error_from_errno("asprintf");
9317 s
->first_displayed_entry
= got_object_tree_get_entry(s
->tree
, 0);
9318 s
->selected_entry
= got_object_tree_get_entry(s
->tree
, 0);
9319 if (head_ref_name
) {
9320 s
->head_ref_name
= strdup(head_ref_name
);
9321 if (s
->head_ref_name
== NULL
) {
9322 err
= got_error_from_errno("strdup");
9328 if (has_colors() && getenv("TOG_COLORS") != NULL
) {
9329 err
= add_color(&s
->colors
, "\\$$",
9330 TOG_COLOR_TREE_SUBMODULE
,
9331 get_color_value("TOG_COLOR_TREE_SUBMODULE"));
9334 err
= add_color(&s
->colors
, "@$", TOG_COLOR_TREE_SYMLINK
,
9335 get_color_value("TOG_COLOR_TREE_SYMLINK"));
9338 err
= add_color(&s
->colors
, "/$",
9339 TOG_COLOR_TREE_DIRECTORY
,
9340 get_color_value("TOG_COLOR_TREE_DIRECTORY"));
9344 err
= add_color(&s
->colors
, "\\*$",
9345 TOG_COLOR_TREE_EXECUTABLE
,
9346 get_color_value("TOG_COLOR_TREE_EXECUTABLE"));
9350 err
= add_color(&s
->colors
, "^$", TOG_COLOR_COMMIT
,
9351 get_color_value("TOG_COLOR_COMMIT"));
9356 view
->show
= show_tree_view
;
9357 view
->input
= input_tree_view
;
9358 view
->close
= close_tree_view
;
9359 view
->search_start
= search_start_tree_view
;
9360 view
->search_next
= search_next_tree_view
;
9362 free(commit_id_str
);
9364 got_object_commit_close(commit
);
9366 if (view
->close
== NULL
)
9367 close_tree_view(view
);
9373 static const struct got_error
*
9374 close_tree_view(struct tog_view
*view
)
9376 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9378 free_colors(&s
->colors
);
9379 free(s
->tree_label
);
9380 s
->tree_label
= NULL
;
9382 s
->commit_id
= NULL
;
9383 free(s
->head_ref_name
);
9384 s
->head_ref_name
= NULL
;
9385 while (!TAILQ_EMPTY(&s
->parents
)) {
9386 struct tog_parent_tree
*parent
;
9387 parent
= TAILQ_FIRST(&s
->parents
);
9388 TAILQ_REMOVE(&s
->parents
, parent
, entry
);
9389 if (parent
->tree
!= s
->root
)
9390 got_object_tree_close(parent
->tree
);
9394 if (s
->tree
!= NULL
&& s
->tree
!= s
->root
)
9395 got_object_tree_close(s
->tree
);
9397 got_object_tree_close(s
->root
);
9401 static const struct got_error
*
9402 search_start_tree_view(struct tog_view
*view
)
9404 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9406 s
->matched_entry
= NULL
;
9411 match_tree_entry(struct got_tree_entry
*te
, regex_t
*regex
)
9413 regmatch_t regmatch
;
9415 return regexec(regex
, got_tree_entry_get_name(te
), 1, ®match
,
9419 static const struct got_error
*
9420 search_next_tree_view(struct tog_view
*view
)
9422 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9423 struct got_tree_entry
*te
= NULL
;
9425 if (!view
->searching
) {
9426 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
9430 if (s
->matched_entry
) {
9431 if (view
->searching
== TOG_SEARCH_FORWARD
) {
9432 if (s
->selected_entry
)
9433 te
= got_tree_entry_get_next(s
->tree
,
9436 te
= got_object_tree_get_first_entry(s
->tree
);
9438 if (s
->selected_entry
== NULL
)
9439 te
= got_object_tree_get_last_entry(s
->tree
);
9441 te
= got_tree_entry_get_prev(s
->tree
,
9445 if (s
->selected_entry
)
9446 te
= s
->selected_entry
;
9447 else if (view
->searching
== TOG_SEARCH_FORWARD
)
9448 te
= got_object_tree_get_first_entry(s
->tree
);
9450 te
= got_object_tree_get_last_entry(s
->tree
);
9455 if (s
->matched_entry
== NULL
) {
9456 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
9459 if (view
->searching
== TOG_SEARCH_FORWARD
)
9460 te
= got_object_tree_get_first_entry(s
->tree
);
9462 te
= got_object_tree_get_last_entry(s
->tree
);
9465 if (match_tree_entry(te
, &view
->regex
)) {
9466 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
9467 s
->matched_entry
= te
;
9471 if (view
->searching
== TOG_SEARCH_FORWARD
)
9472 te
= got_tree_entry_get_next(s
->tree
, te
);
9474 te
= got_tree_entry_get_prev(s
->tree
, te
);
9477 if (s
->matched_entry
) {
9478 s
->first_displayed_entry
= s
->matched_entry
;
9485 static const struct got_error
*
9486 show_tree_view(struct tog_view
*view
)
9488 const struct got_error
*err
= NULL
;
9489 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9492 err
= tree_entry_path(&parent_path
, &s
->parents
, NULL
);
9496 err
= draw_tree_entries(view
, parent_path
);
9503 static const struct got_error
*
9504 tree_goto_line(struct tog_view
*view
, int nlines
)
9506 const struct got_error
*err
= NULL
;
9507 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9508 struct got_tree_entry
**fte
, **lte
, **ste
;
9509 int g
, last
, first
= 1, i
= 1;
9510 int root
= s
->tree
== s
->root
;
9511 int off
= root
? 1 : 2;
9518 else if (g
> got_object_tree_get_nentries(s
->tree
))
9519 g
= got_object_tree_get_nentries(s
->tree
) + (root
? 0 : 1);
9521 fte
= &s
->first_displayed_entry
;
9522 lte
= &s
->last_displayed_entry
;
9523 ste
= &s
->selected_entry
;
9526 first
= got_tree_entry_get_index(*fte
);
9527 first
+= off
; /* account for ".." */
9529 last
= got_tree_entry_get_index(*lte
);
9532 if (g
>= first
&& g
<= last
&& g
- first
< nlines
) {
9533 s
->selected
= g
- first
;
9534 return NULL
; /* gline is on the current page */
9538 i
= got_tree_entry_get_index(*ste
);
9543 err
= tree_scroll_down(view
, g
- i
);
9546 if (got_tree_entry_get_index(*lte
) >=
9547 got_object_tree_get_nentries(s
->tree
) - 1 &&
9548 first
+ s
->selected
< g
&&
9549 s
->selected
< s
->ndisplayed
- 1) {
9550 first
= got_tree_entry_get_index(*fte
);
9552 s
->selected
= g
- first
;
9555 tree_scroll_up(s
, i
- g
);
9558 (*fte
== NULL
|| (root
&& !got_tree_entry_get_index(*fte
))))
9559 s
->selected
= g
- 1;
9564 static const struct got_error
*
9565 input_tree_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
9567 const struct got_error
*err
= NULL
;
9568 struct tog_tree_view_state
*s
= &view
->state
.tree
;
9569 struct got_tree_entry
*te
;
9570 int n
, nscroll
= view
->nlines
- 3;
9573 return tree_goto_line(view
, nscroll
);
9582 horizontal_scroll_input(view
, ch
);
9585 s
->show_ids
= !s
->show_ids
;
9590 if (!s
->selected_entry
)
9592 err
= view_request_new(new_view
, view
, TOG_VIEW_LOG
);
9596 err
= view_request_new(new_view
, view
, TOG_VIEW_REF
);
9603 if (s
->tree
== s
->root
)
9604 s
->first_displayed_entry
=
9605 got_object_tree_get_first_entry(s
->tree
);
9607 s
->first_displayed_entry
= NULL
;
9612 int eos
= view
->nlines
- 3;
9614 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
)
9618 te
= got_object_tree_get_last_entry(s
->tree
);
9619 for (n
= 0; n
< eos
; n
++) {
9621 if (s
->tree
!= s
->root
) {
9622 s
->first_displayed_entry
= NULL
;
9627 s
->first_displayed_entry
= te
;
9628 te
= got_tree_entry_get_prev(s
->tree
, te
);
9631 s
->selected
= n
- 1;
9637 if (s
->selected
> 0) {
9641 tree_scroll_up(s
, 1);
9642 if (s
->selected_entry
== NULL
||
9643 (s
->tree
== s
->root
&& s
->selected_entry
==
9644 got_object_tree_get_first_entry(s
->tree
)))
9654 if (s
->tree
== s
->root
) {
9655 if (got_object_tree_get_first_entry(s
->tree
) ==
9656 s
->first_displayed_entry
)
9657 s
->selected
-= MIN(s
->selected
, nscroll
);
9659 if (s
->first_displayed_entry
== NULL
)
9660 s
->selected
-= MIN(s
->selected
, nscroll
);
9662 tree_scroll_up(s
, MAX(0, nscroll
));
9663 if (s
->selected_entry
== NULL
||
9664 (s
->tree
== s
->root
&& s
->selected_entry
==
9665 got_object_tree_get_first_entry(s
->tree
)))
9671 if (s
->selected
< s
->ndisplayed
- 1) {
9675 if (s
->last_displayed_entry
== NULL
||
9676 got_tree_entry_get_next(s
->tree
, s
->last_displayed_entry
)
9678 /* can't scroll any further */
9682 tree_scroll_down(view
, 1);
9692 if (s
->last_displayed_entry
== NULL
||
9693 got_tree_entry_get_next(s
->tree
, s
->last_displayed_entry
)
9695 /* can't scroll any further; move cursor down */
9696 if (s
->selected
< s
->ndisplayed
- 1)
9697 s
->selected
+= MIN(nscroll
,
9698 s
->ndisplayed
- s
->selected
- 1);
9703 tree_scroll_down(view
, nscroll
);
9708 if (s
->selected_entry
== NULL
|| ch
== KEY_BACKSPACE
) {
9709 struct tog_parent_tree
*parent
;
9710 /* user selected '..' */
9711 if (s
->tree
== s
->root
) {
9715 parent
= TAILQ_FIRST(&s
->parents
);
9716 TAILQ_REMOVE(&s
->parents
, parent
,
9718 got_object_tree_close(s
->tree
);
9719 s
->tree
= parent
->tree
;
9720 s
->first_displayed_entry
=
9721 parent
->first_displayed_entry
;
9723 parent
->selected_entry
;
9724 s
->selected
= parent
->selected
;
9725 if (s
->selected
> view
->nlines
- 3) {
9726 err
= offset_selection_down(view
);
9731 } else if (S_ISDIR(got_tree_entry_get_mode(
9732 s
->selected_entry
))) {
9733 struct got_tree_object
*subtree
;
9735 err
= got_object_open_as_tree(&subtree
, s
->repo
,
9736 got_tree_entry_get_id(s
->selected_entry
));
9739 err
= tree_view_visit_subtree(s
, subtree
);
9741 got_object_tree_close(subtree
);
9744 } else if (S_ISREG(got_tree_entry_get_mode(s
->selected_entry
)))
9745 err
= view_request_new(new_view
, view
, TOG_VIEW_BLAME
);
9748 if (view
->nlines
>= 4 && s
->selected
>= view
->nlines
- 3)
9749 s
->selected
= view
->nlines
- 4;
9765 "usage: %s tree [-c commit] [-r repository-path] [path]\n",
9770 static const struct got_error
*
9771 cmd_tree(int argc
, char *argv
[])
9773 const struct got_error
*error
;
9774 struct got_repository
*repo
= NULL
;
9775 struct got_worktree
*worktree
= NULL
;
9776 char *cwd
= NULL
, *repo_path
= NULL
, *in_repo_path
= NULL
;
9777 struct got_object_id
*commit_id
= NULL
;
9778 struct got_commit_object
*commit
= NULL
;
9779 const char *commit_id_arg
= NULL
;
9780 char *keyword_idstr
= NULL
, *label
= NULL
;
9781 struct got_reference
*ref
= NULL
;
9782 const char *head_ref_name
= NULL
;
9784 struct tog_view
*view
;
9785 int *pack_fds
= NULL
;
9787 while ((ch
= getopt(argc
, argv
, "c:r:")) != -1) {
9790 commit_id_arg
= optarg
;
9793 repo_path
= realpath(optarg
, NULL
);
9794 if (repo_path
== NULL
)
9795 return got_error_from_errno2("realpath",
9810 error
= got_repo_pack_fds_open(&pack_fds
);
9814 if (repo_path
== NULL
) {
9815 cwd
= getcwd(NULL
, 0);
9817 return got_error_from_errno("getcwd");
9818 error
= got_worktree_open(&worktree
, cwd
, NULL
);
9819 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
9823 strdup(got_worktree_get_repo_path(worktree
));
9825 repo_path
= strdup(cwd
);
9826 if (repo_path
== NULL
) {
9827 error
= got_error_from_errno("strdup");
9832 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
9836 error
= get_in_repo_path_from_argv0(&in_repo_path
, argc
, argv
,
9843 error
= apply_unveil(got_repo_get_path(repo
), NULL
);
9847 error
= tog_load_refs(repo
, 0);
9851 if (commit_id_arg
== NULL
) {
9852 error
= got_repo_match_object_id(&commit_id
, &label
,
9853 worktree
? got_worktree_get_head_ref_name(worktree
) :
9854 GOT_REF_HEAD
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
9857 head_ref_name
= label
;
9859 error
= got_keyword_to_idstr(&keyword_idstr
, commit_id_arg
,
9863 if (keyword_idstr
!= NULL
)
9864 commit_id_arg
= keyword_idstr
;
9866 error
= got_ref_open(&ref
, repo
, commit_id_arg
, 0);
9868 head_ref_name
= got_ref_get_name(ref
);
9869 else if (error
->code
!= GOT_ERR_NOT_REF
)
9871 error
= got_repo_match_object_id(&commit_id
, NULL
,
9872 commit_id_arg
, GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
9877 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
9881 view
= view_open(0, 0, 0, 0, TOG_VIEW_TREE
);
9883 error
= got_error_from_errno("view_open");
9886 error
= open_tree_view(view
, commit_id
, head_ref_name
, repo
);
9889 if (!got_path_is_root_dir(in_repo_path
)) {
9890 error
= tree_view_walk_path(&view
->state
.tree
, commit
,
9897 error
= set_tog_base_commit(repo
, worktree
);
9901 /* Release work tree lock. */
9902 got_worktree_close(worktree
);
9906 error
= view_loop(view
);
9909 free(tog_base_commit
.id
);
9910 free(keyword_idstr
);
9916 got_object_commit_close(commit
);
9919 if (worktree
!= NULL
)
9920 got_worktree_close(worktree
);
9922 const struct got_error
*close_err
= got_repo_close(repo
);
9927 const struct got_error
*pack_err
=
9928 got_repo_pack_fds_close(pack_fds
);
9936 static const struct got_error
*
9937 ref_view_load_refs(struct tog_ref_view_state
*s
)
9939 struct got_reflist_entry
*sre
;
9940 struct tog_reflist_entry
*re
;
9943 TAILQ_FOREACH(sre
, &tog_refs
, entry
) {
9944 if (strncmp(got_ref_get_name(sre
->ref
),
9945 "refs/got/", 9) == 0 &&
9946 strncmp(got_ref_get_name(sre
->ref
),
9947 "refs/got/backup/", 16) != 0)
9950 re
= malloc(sizeof(*re
));
9952 return got_error_from_errno("malloc");
9954 re
->ref
= got_ref_dup(sre
->ref
);
9955 if (re
->ref
== NULL
)
9956 return got_error_from_errno("got_ref_dup");
9957 re
->idx
= s
->nrefs
++;
9958 TAILQ_INSERT_TAIL(&s
->refs
, re
, entry
);
9961 s
->first_displayed_entry
= TAILQ_FIRST(&s
->refs
);
9966 ref_view_free_refs(struct tog_ref_view_state
*s
)
9968 struct tog_reflist_entry
*re
;
9970 while (!TAILQ_EMPTY(&s
->refs
)) {
9971 re
= TAILQ_FIRST(&s
->refs
);
9972 TAILQ_REMOVE(&s
->refs
, re
, entry
);
9973 got_ref_close(re
->ref
);
9978 static const struct got_error
*
9979 open_ref_view(struct tog_view
*view
, struct got_repository
*repo
)
9981 const struct got_error
*err
= NULL
;
9982 struct tog_ref_view_state
*s
= &view
->state
.ref
;
9984 s
->selected_entry
= 0;
9987 TAILQ_INIT(&s
->refs
);
9988 STAILQ_INIT(&s
->colors
);
9990 err
= ref_view_load_refs(s
);
9994 if (has_colors() && getenv("TOG_COLORS") != NULL
) {
9995 err
= add_color(&s
->colors
, "^refs/heads/",
9996 TOG_COLOR_REFS_HEADS
,
9997 get_color_value("TOG_COLOR_REFS_HEADS"));
10001 err
= add_color(&s
->colors
, "^refs/tags/",
10002 TOG_COLOR_REFS_TAGS
,
10003 get_color_value("TOG_COLOR_REFS_TAGS"));
10007 err
= add_color(&s
->colors
, "^refs/remotes/",
10008 TOG_COLOR_REFS_REMOTES
,
10009 get_color_value("TOG_COLOR_REFS_REMOTES"));
10013 err
= add_color(&s
->colors
, "^refs/got/backup/",
10014 TOG_COLOR_REFS_BACKUP
,
10015 get_color_value("TOG_COLOR_REFS_BACKUP"));
10020 view
->show
= show_ref_view
;
10021 view
->input
= input_ref_view
;
10022 view
->close
= close_ref_view
;
10023 view
->search_start
= search_start_ref_view
;
10024 view
->search_next
= search_next_ref_view
;
10027 if (view
->close
== NULL
)
10028 close_ref_view(view
);
10034 static const struct got_error
*
10035 close_ref_view(struct tog_view
*view
)
10037 struct tog_ref_view_state
*s
= &view
->state
.ref
;
10039 ref_view_free_refs(s
);
10040 free_colors(&s
->colors
);
10045 static const struct got_error
*
10046 resolve_reflist_entry(struct got_object_id
**commit_id
,
10047 struct tog_reflist_entry
*re
, struct got_repository
*repo
)
10049 const struct got_error
*err
= NULL
;
10050 struct got_object_id
*obj_id
;
10051 struct got_tag_object
*tag
= NULL
;
10056 err
= got_ref_resolve(&obj_id
, repo
, re
->ref
);
10060 err
= got_object_get_type(&obj_type
, repo
, obj_id
);
10064 switch (obj_type
) {
10065 case GOT_OBJ_TYPE_COMMIT
:
10067 case GOT_OBJ_TYPE_TAG
:
10069 * Git allows nested tags that point to tags; keep peeling
10070 * till we reach the bottom, which is always a non-tag ref.
10074 got_object_tag_close(tag
);
10075 err
= got_object_open_as_tag(&tag
, repo
, obj_id
);
10079 obj_id
= got_object_id_dup(
10080 got_object_tag_get_object_id(tag
));
10081 if (obj_id
== NULL
) {
10082 err
= got_error_from_errno("got_object_id_dup");
10085 err
= got_object_get_type(&obj_type
, repo
, obj_id
);
10088 } while (obj_type
== GOT_OBJ_TYPE_TAG
);
10089 if (obj_type
!= GOT_OBJ_TYPE_COMMIT
)
10090 err
= got_error(GOT_ERR_OBJ_TYPE
);
10093 err
= got_error(GOT_ERR_OBJ_TYPE
);
10099 got_object_tag_close(tag
);
10101 *commit_id
= obj_id
;
10107 static const struct got_error
*
10108 log_ref_entry(struct tog_view
**new_view
, int begin_y
, int begin_x
,
10109 struct tog_reflist_entry
*re
, struct got_repository
*repo
)
10111 struct tog_view
*log_view
;
10112 const struct got_error
*err
= NULL
;
10113 struct got_object_id
*commit_id
= NULL
;
10117 err
= resolve_reflist_entry(&commit_id
, re
, repo
);
10121 log_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_LOG
);
10122 if (log_view
== NULL
) {
10123 err
= got_error_from_errno("view_open");
10127 err
= open_log_view(log_view
, commit_id
, repo
,
10128 got_ref_get_name(re
->ref
), "", 0, NULL
);
10131 view_close(log_view
);
10133 *new_view
= log_view
;
10139 ref_scroll_up(struct tog_ref_view_state
*s
, int maxscroll
)
10141 struct tog_reflist_entry
*re
;
10144 if (s
->first_displayed_entry
== TAILQ_FIRST(&s
->refs
))
10147 re
= TAILQ_PREV(s
->first_displayed_entry
, tog_reflist_head
, entry
);
10148 while (i
++ < maxscroll
) {
10151 s
->first_displayed_entry
= re
;
10152 re
= TAILQ_PREV(re
, tog_reflist_head
, entry
);
10156 static const struct got_error
*
10157 ref_scroll_down(struct tog_view
*view
, int maxscroll
)
10159 struct tog_ref_view_state
*s
= &view
->state
.ref
;
10160 struct tog_reflist_entry
*next
, *last
;
10163 if (s
->first_displayed_entry
)
10164 next
= TAILQ_NEXT(s
->first_displayed_entry
, entry
);
10166 next
= TAILQ_FIRST(&s
->refs
);
10168 last
= s
->last_displayed_entry
;
10169 while (next
&& n
++ < maxscroll
) {
10171 s
->last_displayed_entry
= last
;
10172 last
= TAILQ_NEXT(last
, entry
);
10174 if (last
|| (view
->mode
== TOG_VIEW_SPLIT_HRZN
)) {
10175 s
->first_displayed_entry
= next
;
10176 next
= TAILQ_NEXT(next
, entry
);
10183 static const struct got_error
*
10184 search_start_ref_view(struct tog_view
*view
)
10186 struct tog_ref_view_state
*s
= &view
->state
.ref
;
10188 s
->matched_entry
= NULL
;
10193 match_reflist_entry(struct tog_reflist_entry
*re
, regex_t
*regex
)
10195 regmatch_t regmatch
;
10197 return regexec(regex
, got_ref_get_name(re
->ref
), 1, ®match
,
10201 static const struct got_error
*
10202 search_next_ref_view(struct tog_view
*view
)
10204 struct tog_ref_view_state
*s
= &view
->state
.ref
;
10205 struct tog_reflist_entry
*re
= NULL
;
10207 if (!view
->searching
) {
10208 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
10212 if (s
->matched_entry
) {
10213 if (view
->searching
== TOG_SEARCH_FORWARD
) {
10214 if (s
->selected_entry
)
10215 re
= TAILQ_NEXT(s
->selected_entry
, entry
);
10217 re
= TAILQ_PREV(s
->selected_entry
,
10218 tog_reflist_head
, entry
);
10220 if (s
->selected_entry
== NULL
)
10221 re
= TAILQ_LAST(&s
->refs
, tog_reflist_head
);
10223 re
= TAILQ_PREV(s
->selected_entry
,
10224 tog_reflist_head
, entry
);
10227 if (s
->selected_entry
)
10228 re
= s
->selected_entry
;
10229 else if (view
->searching
== TOG_SEARCH_FORWARD
)
10230 re
= TAILQ_FIRST(&s
->refs
);
10232 re
= TAILQ_LAST(&s
->refs
, tog_reflist_head
);
10237 if (s
->matched_entry
== NULL
) {
10238 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
10241 if (view
->searching
== TOG_SEARCH_FORWARD
)
10242 re
= TAILQ_FIRST(&s
->refs
);
10244 re
= TAILQ_LAST(&s
->refs
, tog_reflist_head
);
10247 if (match_reflist_entry(re
, &view
->regex
)) {
10248 view
->search_next_done
= TOG_SEARCH_HAVE_MORE
;
10249 s
->matched_entry
= re
;
10253 if (view
->searching
== TOG_SEARCH_FORWARD
)
10254 re
= TAILQ_NEXT(re
, entry
);
10256 re
= TAILQ_PREV(re
, tog_reflist_head
, entry
);
10259 if (s
->matched_entry
) {
10260 s
->first_displayed_entry
= s
->matched_entry
;
10267 static const struct got_error
*
10268 show_ref_view(struct tog_view
*view
)
10270 const struct got_error
*err
= NULL
;
10271 struct tog_ref_view_state
*s
= &view
->state
.ref
;
10272 struct tog_reflist_entry
*re
;
10275 struct tog_color
*tc
;
10276 int width
, n
, scrollx
;
10277 int limit
= view
->nlines
;
10279 werase(view
->window
);
10282 if (view_is_hsplit_top(view
))
10283 --limit
; /* border */
10288 re
= s
->first_displayed_entry
;
10290 if (asprintf(&line
, "references [%d/%d]", re
->idx
+ s
->selected
+ 1,
10292 return got_error_from_errno("asprintf");
10294 err
= format_line(&wline
, &width
, NULL
, line
, 0, view
->ncols
, 0, 0);
10299 if (view_needs_focus_indication(view
))
10300 wstandout(view
->window
);
10301 waddwstr(view
->window
, wline
);
10302 while (width
++ < view
->ncols
)
10303 waddch(view
->window
, ' ');
10304 if (view_needs_focus_indication(view
))
10305 wstandend(view
->window
);
10315 while (re
&& limit
> 0) {
10317 char ymd
[13]; /* YYYY-MM-DD + " " + NUL */
10319 if (s
->show_date
) {
10320 struct got_commit_object
*ci
;
10321 struct got_tag_object
*tag
;
10322 struct got_object_id
*id
;
10326 err
= got_ref_resolve(&id
, s
->repo
, re
->ref
);
10329 err
= got_object_open_as_tag(&tag
, s
->repo
, id
);
10331 if (err
->code
!= GOT_ERR_OBJ_TYPE
) {
10335 err
= got_object_open_as_commit(&ci
, s
->repo
,
10341 t
= got_object_commit_get_committer_time(ci
);
10342 got_object_commit_close(ci
);
10344 t
= got_object_tag_get_tagger_time(tag
);
10345 got_object_tag_close(tag
);
10348 if (gmtime_r(&t
, &tm
) == NULL
)
10349 return got_error_from_errno("gmtime_r");
10350 if (strftime(ymd
, sizeof(ymd
), "%F ", &tm
) == 0)
10351 return got_error(GOT_ERR_NO_SPACE
);
10353 if (got_ref_is_symbolic(re
->ref
)) {
10354 if (asprintf(&line
, "%s%s -> %s", s
->show_date
?
10355 ymd
: "", got_ref_get_name(re
->ref
),
10356 got_ref_get_symref_target(re
->ref
)) == -1)
10357 return got_error_from_errno("asprintf");
10358 } else if (s
->show_ids
) {
10359 struct got_object_id
*id
;
10361 err
= got_ref_resolve(&id
, s
->repo
, re
->ref
);
10364 err
= got_object_id_str(&id_str
, id
);
10369 if (asprintf(&line
, "%s%s: %s", s
->show_date
? ymd
: "",
10370 got_ref_get_name(re
->ref
), id_str
) == -1) {
10371 err
= got_error_from_errno("asprintf");
10378 } else if (asprintf(&line
, "%s%s", s
->show_date
? ymd
: "",
10379 got_ref_get_name(re
->ref
)) == -1)
10380 return got_error_from_errno("asprintf");
10382 /* use full line width to determine view->maxx */
10383 err
= format_line(&wline
, &width
, NULL
, line
, 0, INT_MAX
, 0, 0);
10388 view
->maxx
= MAX(view
->maxx
, width
);
10392 err
= format_line(&wline
, &width
, &scrollx
, line
, view
->x
,
10393 view
->ncols
, 0, 0);
10398 if (n
== s
->selected
) {
10399 if (view
->focussed
)
10400 wstandout(view
->window
);
10401 s
->selected_entry
= re
;
10403 tc
= match_color(&s
->colors
, got_ref_get_name(re
->ref
));
10405 wattr_on(view
->window
,
10406 COLOR_PAIR(tc
->colorpair
), NULL
);
10407 waddwstr(view
->window
, &wline
[scrollx
]);
10409 wattr_off(view
->window
,
10410 COLOR_PAIR(tc
->colorpair
), NULL
);
10411 if (width
< view
->ncols
)
10412 waddch(view
->window
, '\n');
10413 if (n
== s
->selected
&& view
->focussed
)
10414 wstandend(view
->window
);
10420 s
->last_displayed_entry
= re
;
10423 re
= TAILQ_NEXT(re
, entry
);
10430 static const struct got_error
*
10431 browse_ref_tree(struct tog_view
**new_view
, int begin_y
, int begin_x
,
10432 struct tog_reflist_entry
*re
, struct got_repository
*repo
)
10434 const struct got_error
*err
= NULL
;
10435 struct got_object_id
*commit_id
= NULL
;
10436 struct tog_view
*tree_view
;
10440 err
= resolve_reflist_entry(&commit_id
, re
, repo
);
10444 tree_view
= view_open(0, 0, begin_y
, begin_x
, TOG_VIEW_TREE
);
10445 if (tree_view
== NULL
) {
10446 err
= got_error_from_errno("view_open");
10450 err
= open_tree_view(tree_view
, commit_id
,
10451 got_ref_get_name(re
->ref
), repo
);
10455 *new_view
= tree_view
;
10461 static const struct got_error
*
10462 ref_goto_line(struct tog_view
*view
, int nlines
)
10464 const struct got_error
*err
= NULL
;
10465 struct tog_ref_view_state
*s
= &view
->state
.ref
;
10466 int g
, idx
= s
->selected_entry
->idx
;
10473 else if (g
> s
->nrefs
)
10476 if (g
>= s
->first_displayed_entry
->idx
+ 1 &&
10477 g
<= s
->last_displayed_entry
->idx
+ 1 &&
10478 g
- s
->first_displayed_entry
->idx
- 1 < nlines
) {
10479 s
->selected
= g
- s
->first_displayed_entry
->idx
- 1;
10484 err
= ref_scroll_down(view
, g
- idx
- 1);
10487 if (TAILQ_NEXT(s
->last_displayed_entry
, entry
) == NULL
&&
10488 s
->first_displayed_entry
->idx
+ s
->selected
< g
&&
10489 s
->selected
< s
->ndisplayed
- 1)
10490 s
->selected
= g
- s
->first_displayed_entry
->idx
- 1;
10491 } else if (idx
+ 1 > g
)
10492 ref_scroll_up(s
, idx
- g
+ 1);
10494 if (g
< nlines
&& s
->first_displayed_entry
->idx
== 0)
10495 s
->selected
= g
- 1;
10501 static const struct got_error
*
10502 input_ref_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
10504 const struct got_error
*err
= NULL
;
10505 struct tog_ref_view_state
*s
= &view
->state
.ref
;
10506 struct tog_reflist_entry
*re
;
10507 int n
, nscroll
= view
->nlines
- 1;
10510 return ref_goto_line(view
, nscroll
);
10519 horizontal_scroll_input(view
, ch
);
10522 s
->show_ids
= !s
->show_ids
;
10526 s
->show_date
= !s
->show_date
;
10530 s
->sort_by_date
= !s
->sort_by_date
;
10531 view
->action
= s
->sort_by_date
? "sort by date" : "sort by name";
10533 err
= got_reflist_sort(&tog_refs
, s
->sort_by_date
?
10534 got_ref_cmp_by_commit_timestamp_descending
:
10535 tog_ref_cmp_by_name
, s
->repo
);
10538 got_reflist_object_id_map_free(tog_refs_idmap
);
10539 err
= got_reflist_object_id_map_create(&tog_refs_idmap
,
10540 &tog_refs
, s
->repo
);
10543 ref_view_free_refs(s
);
10544 err
= ref_view_load_refs(s
);
10549 if (!s
->selected_entry
)
10551 err
= view_request_new(new_view
, view
, TOG_VIEW_LOG
);
10555 if (!s
->selected_entry
)
10557 err
= view_request_new(new_view
, view
, TOG_VIEW_TREE
);
10564 s
->first_displayed_entry
= TAILQ_FIRST(&s
->refs
);
10569 int eos
= view
->nlines
- 1;
10571 if (view
->mode
== TOG_VIEW_SPLIT_HRZN
)
10572 --eos
; /* border */
10575 re
= TAILQ_LAST(&s
->refs
, tog_reflist_head
);
10576 for (n
= 0; n
< eos
; n
++) {
10579 s
->first_displayed_entry
= re
;
10580 re
= TAILQ_PREV(re
, tog_reflist_head
, entry
);
10583 s
->selected
= n
- 1;
10589 if (s
->selected
> 0) {
10593 ref_scroll_up(s
, 1);
10594 if (s
->selected_entry
== TAILQ_FIRST(&s
->refs
))
10604 if (s
->first_displayed_entry
== TAILQ_FIRST(&s
->refs
))
10605 s
->selected
-= MIN(nscroll
, s
->selected
);
10606 ref_scroll_up(s
, MAX(0, nscroll
));
10607 if (s
->selected_entry
== TAILQ_FIRST(&s
->refs
))
10613 if (s
->selected
< s
->ndisplayed
- 1) {
10617 if (TAILQ_NEXT(s
->last_displayed_entry
, entry
) == NULL
) {
10618 /* can't scroll any further */
10622 ref_scroll_down(view
, 1);
10632 if (TAILQ_NEXT(s
->last_displayed_entry
, entry
) == NULL
) {
10633 /* can't scroll any further; move cursor down */
10634 if (s
->selected
< s
->ndisplayed
- 1)
10635 s
->selected
+= MIN(nscroll
,
10636 s
->ndisplayed
- s
->selected
- 1);
10637 if (view
->count
> 1 && s
->selected
< s
->ndisplayed
- 1)
10638 s
->selected
+= s
->ndisplayed
- s
->selected
- 1;
10642 ref_scroll_down(view
, nscroll
);
10647 err
= tog_load_refs(s
->repo
, s
->sort_by_date
);
10650 ref_view_free_refs(s
);
10651 err
= ref_view_load_refs(s
);
10654 if (view
->nlines
>= 2 && s
->selected
>= view
->nlines
- 1)
10655 s
->selected
= view
->nlines
- 2;
10669 fprintf(stderr
, "usage: %s ref [-r repository-path]\n",
10674 static const struct got_error
*
10675 cmd_ref(int argc
, char *argv
[])
10677 const struct got_error
*error
;
10678 struct got_repository
*repo
= NULL
;
10679 struct got_worktree
*worktree
= NULL
;
10680 char *cwd
= NULL
, *repo_path
= NULL
;
10682 struct tog_view
*view
;
10683 int *pack_fds
= NULL
;
10685 while ((ch
= getopt(argc
, argv
, "r:")) != -1) {
10688 repo_path
= realpath(optarg
, NULL
);
10689 if (repo_path
== NULL
)
10690 return got_error_from_errno2("realpath",
10705 error
= got_repo_pack_fds_open(&pack_fds
);
10709 if (repo_path
== NULL
) {
10710 cwd
= getcwd(NULL
, 0);
10712 return got_error_from_errno("getcwd");
10713 error
= got_worktree_open(&worktree
, cwd
, NULL
);
10714 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
10718 strdup(got_worktree_get_repo_path(worktree
));
10720 repo_path
= strdup(cwd
);
10721 if (repo_path
== NULL
) {
10722 error
= got_error_from_errno("strdup");
10727 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
10733 error
= apply_unveil(got_repo_get_path(repo
), NULL
);
10737 error
= tog_load_refs(repo
, 0);
10741 view
= view_open(0, 0, 0, 0, TOG_VIEW_REF
);
10742 if (view
== NULL
) {
10743 error
= got_error_from_errno("view_open");
10747 error
= open_ref_view(view
, repo
);
10752 error
= set_tog_base_commit(repo
, worktree
);
10756 /* Release work tree lock. */
10757 got_worktree_close(worktree
);
10761 error
= view_loop(view
);
10764 free(tog_base_commit
.id
);
10767 if (worktree
!= NULL
)
10768 got_worktree_close(worktree
);
10770 const struct got_error
*close_err
;
10772 close_err
= got_repo_close(repo
);
10773 if (close_err
&& error
== NULL
)
10777 const struct got_error
*pack_err
;
10779 pack_err
= got_repo_pack_fds_close(pack_fds
);
10780 if (pack_err
&& error
== NULL
)
10787 static const struct got_error
*
10788 win_draw_center(WINDOW
*win
, size_t y
, size_t x
, size_t maxx
, int focus
,
10797 x
= x
? x
: maxx
> len
? (maxx
- len
) / 2 : 0;
10801 if (mvwprintw(win
, y
, x
, "%s", str
) == ERR
)
10802 return got_error_msg(GOT_ERR_RANGE
, "mvwprintw");
10809 static const struct got_error
*
10810 add_line_offset(off_t
**line_offsets
, size_t *nlines
, off_t off
)
10814 p
= reallocarray(*line_offsets
, *nlines
+ 1, sizeof(off_t
));
10816 free(*line_offsets
);
10817 *line_offsets
= NULL
;
10818 return got_error_from_errno("reallocarray");
10822 (*line_offsets
)[*nlines
] = off
;
10827 static const struct got_error
*
10828 max_key_str(int *ret
, const struct tog_key_map
*km
, size_t n
)
10832 for (;n
> 0; --n
, ++km
) {
10836 if (km
->keys
== NULL
)
10839 t
= t0
= strdup(km
->keys
);
10841 return got_error_from_errno("strdup");
10844 while ((k
= strsep(&t
, " ")) != NULL
)
10845 len
+= strlen(k
) > 1 ? 2 : 0;
10847 *ret
= MAX(*ret
, len
);
10854 * Write keymap section headers, keys, and key info in km to f.
10855 * Save line offset to *off. If terminal has UTF8 encoding enabled,
10856 * wrap control and symbolic keys in guillemets, else use <>.
10858 static const struct got_error
*
10859 format_help_line(off_t
*off
, FILE *f
, const struct tog_key_map
*km
, int width
)
10861 int n
, len
= width
;
10864 static const char *u8_glyph
[] = {
10865 "\xe2\x80\xb9", /* U+2039 (utf8 <) */
10866 "\xe2\x80\xba" /* U+203A (utf8 >) */
10869 int cs
, s
, first
= 1;
10871 cs
= got_locale_is_utf8();
10873 t
= t0
= strdup(km
->keys
);
10875 return got_error_from_errno("strdup");
10877 len
= strlen(km
->keys
);
10878 while ((k
= strsep(&t
, " ")) != NULL
) {
10879 s
= strlen(k
) > 1; /* control or symbolic key */
10880 n
= fprintf(f
, "%s%s%s%s%s", first
? " " : "",
10881 cs
&& s
? u8_glyph
[0] : s
? "<" : "", k
,
10882 cs
&& s
? u8_glyph
[1] : s
? ">" : "", t
? " " : "");
10885 return got_error_from_errno("fprintf");
10893 n
= fprintf(f
, "%*s%s\n", width
- len
, width
- len
? " " : "", km
->info
);
10895 return got_error_from_errno("fprintf");
10901 static const struct got_error
*
10902 format_help(struct tog_help_view_state
*s
)
10904 const struct got_error
*err
= NULL
;
10906 int i
, max
, n
, show
= s
->all
;
10907 static const struct tog_key_map km
[] = {
10908 #define KEYMAP_(info, type) { NULL, (info), type }
10909 #define KEY_(keys, info) { (keys), (info), TOG_KEYMAP_KEYS }
10915 err
= add_line_offset(&s
->line_offsets
, &s
->nlines
, 0);
10920 err
= max_key_str(&max
, km
, n
);
10924 for (i
= 0; i
< n
; ++i
) {
10925 if (km
[i
].keys
== NULL
) {
10927 if (km
[i
].type
== TOG_KEYMAP_GLOBAL
||
10928 km
[i
].type
== s
->type
|| s
->all
)
10932 err
= format_help_line(&off
, s
->f
, &km
[i
], max
);
10935 err
= add_line_offset(&s
->line_offsets
, &s
->nlines
, off
);
10942 err
= add_line_offset(&s
->line_offsets
, &s
->nlines
, off
);
10946 static const struct got_error
*
10947 create_help(struct tog_help_view_state
*s
)
10950 const struct got_error
*err
;
10952 free(s
->line_offsets
);
10953 s
->line_offsets
= NULL
;
10956 f
= got_opentemp();
10958 return got_error_from_errno("got_opentemp");
10961 err
= format_help(s
);
10965 if (s
->f
&& fflush(s
->f
) != 0)
10966 return got_error_from_errno("fflush");
10971 static const struct got_error
*
10972 search_start_help_view(struct tog_view
*view
)
10974 view
->state
.help
.matched_line
= 0;
10979 search_setup_help_view(struct tog_view
*view
, FILE **f
, off_t
**line_offsets
,
10980 size_t *nlines
, int **first
, int **last
, int **match
, int **selected
)
10982 struct tog_help_view_state
*s
= &view
->state
.help
;
10985 *nlines
= s
->nlines
;
10986 *line_offsets
= s
->line_offsets
;
10987 *match
= &s
->matched_line
;
10988 *first
= &s
->first_displayed_line
;
10989 *last
= &s
->last_displayed_line
;
10990 *selected
= &s
->selected_line
;
10993 static const struct got_error
*
10994 show_help_view(struct tog_view
*view
)
10996 struct tog_help_view_state
*s
= &view
->state
.help
;
10997 const struct got_error
*err
;
10998 regmatch_t
*regmatch
= &view
->regmatch
;
11003 int width
, nprinted
= 0, rc
= 0;
11004 int eos
= view
->nlines
;
11006 if (view_is_hsplit_top(view
))
11007 --eos
; /* account for border */
11011 werase(view
->window
);
11013 if (view
->gline
> s
->nlines
- 1)
11014 view
->gline
= s
->nlines
- 1;
11016 err
= win_draw_center(view
->window
, 0, 0, view
->ncols
,
11017 view_needs_focus_indication(view
),
11018 "tog help (press q to return to tog)");
11023 waddstr(view
->window
, "\n\n");
11029 while (eos
> 0 && nprinted
< eos
) {
11032 linelen
= getline(&line
, &linesz
, s
->f
);
11033 if (linelen
== -1) {
11036 return got_ferror(s
->f
, GOT_ERR_IO
);
11041 if (++s
->lineno
< s
->first_displayed_line
)
11043 if (view
->gline
&& !gotoline(view
, &s
->lineno
, &nprinted
))
11045 if (s
->lineno
== view
->hiline
)
11048 err
= format_line(&wline
, &width
, NULL
, line
, 0, INT_MAX
, 0,
11054 view
->maxx
= MAX(view
->maxx
, width
);
11059 wattron(view
->window
, attr
);
11060 if (s
->first_displayed_line
+ nprinted
== s
->matched_line
&&
11061 regmatch
->rm_so
>= 0 && regmatch
->rm_so
< regmatch
->rm_eo
) {
11062 err
= add_matched_line(&width
, line
, view
->ncols
- 1, 0,
11063 view
->window
, view
->x
, regmatch
);
11071 err
= format_line(&wline
, &width
, &skip
, line
,
11072 view
->x
, view
->ncols
, 0, view
->x
? 1 : 0);
11077 waddwstr(view
->window
, &wline
[skip
]);
11081 if (s
->lineno
== view
->hiline
) {
11082 while (width
++ < view
->ncols
)
11083 waddch(view
->window
, ' ');
11085 if (width
< view
->ncols
)
11086 waddch(view
->window
, '\n');
11089 wattroff(view
->window
, attr
);
11090 if (++nprinted
== 1)
11091 s
->first_displayed_line
= s
->lineno
;
11095 s
->last_displayed_line
= s
->first_displayed_line
+ nprinted
- 1;
11097 s
->last_displayed_line
= s
->first_displayed_line
;
11102 rc
= waddnstr(view
->window
,
11103 "See the tog(1) manual page for full documentation",
11106 return got_error_msg(GOT_ERR_RANGE
, "waddnstr");
11108 wmove(view
->window
, view
->nlines
- 1, 0);
11109 wclrtoeol(view
->window
);
11110 wstandout(view
->window
);
11111 rc
= waddnstr(view
->window
, "scroll down for more...",
11114 return got_error_msg(GOT_ERR_RANGE
, "waddnstr");
11115 if (getcurx(view
->window
) < view
->ncols
- 6) {
11116 rc
= wprintw(view
->window
, "[%.0f%%]",
11117 100.00 * s
->last_displayed_line
/ s
->nlines
);
11119 return got_error_msg(GOT_ERR_IO
, "wprintw");
11121 wstandend(view
->window
);
11127 static const struct got_error
*
11128 input_help_view(struct tog_view
**new_view
, struct tog_view
*view
, int ch
)
11130 struct tog_help_view_state
*s
= &view
->state
.help
;
11131 const struct got_error
*err
= NULL
;
11137 eos
= nscroll
= view
->nlines
;
11138 if (view_is_hsplit_top(view
))
11139 --eos
; /* border */
11141 s
->lineno
= s
->first_displayed_line
- 1 + s
->selected_line
;
11150 horizontal_scroll_input(view
, ch
);
11154 s
->first_displayed_line
= 1;
11162 s
->first_displayed_line
= (s
->nlines
- eos
) + 3;
11167 if (s
->first_displayed_line
> 1)
11168 --s
->first_displayed_line
;
11179 if (s
->first_displayed_line
== 1) {
11183 while (--nscroll
> 0 && s
->first_displayed_line
> 1)
11184 s
->first_displayed_line
--;
11190 ++s
->first_displayed_line
;
11206 while (!s
->eof
&& --nscroll
> 0) {
11207 linelen
= getline(&line
, &linesz
, s
->f
);
11208 s
->first_displayed_line
++;
11209 if (linelen
== -1) {
11213 err
= got_ferror(s
->f
, GOT_ERR_IO
);
11227 static const struct got_error
*
11228 close_help_view(struct tog_view
*view
)
11230 struct tog_help_view_state
*s
= &view
->state
.help
;
11232 free(s
->line_offsets
);
11233 s
->line_offsets
= NULL
;
11234 if (fclose(s
->f
) == EOF
)
11235 return got_error_from_errno("fclose");
11240 static const struct got_error
*
11241 reset_help_view(struct tog_view
*view
)
11243 struct tog_help_view_state
*s
= &view
->state
.help
;
11246 if (s
->f
&& fclose(s
->f
) == EOF
)
11247 return got_error_from_errno("fclose");
11249 wclear(view
->window
);
11253 s
->first_displayed_line
= 1;
11254 s
->last_displayed_line
= view
->nlines
;
11255 s
->matched_line
= 0;
11257 return create_help(s
);
11260 static const struct got_error
*
11261 open_help_view(struct tog_view
*view
, struct tog_view
*parent
)
11263 const struct got_error
*err
= NULL
;
11264 struct tog_help_view_state
*s
= &view
->state
.help
;
11266 s
->type
= (enum tog_keymap_type
)parent
->type
;
11267 s
->first_displayed_line
= 1;
11268 s
->last_displayed_line
= view
->nlines
;
11269 s
->selected_line
= 1;
11271 view
->show
= show_help_view
;
11272 view
->input
= input_help_view
;
11273 view
->reset
= reset_help_view
;
11274 view
->close
= close_help_view
;
11275 view
->search_start
= search_start_help_view
;
11276 view
->search_setup
= search_setup_help_view
;
11277 view
->search_next
= search_next_view_match
;
11279 err
= create_help(s
);
11283 static const struct got_error
*
11284 view_dispatch_request(struct tog_view
**new_view
, struct tog_view
*view
,
11285 enum tog_view_type request
, int y
, int x
)
11287 const struct got_error
*err
= NULL
;
11292 case TOG_VIEW_DIFF
:
11293 if (view
->type
== TOG_VIEW_LOG
) {
11294 struct tog_log_view_state
*s
= &view
->state
.log
;
11296 err
= open_diff_view_for_commit(new_view
, y
, x
,
11297 s
->selected_entry
, view
, s
->repo
);
11299 return got_error_msg(GOT_ERR_NOT_IMPL
,
11300 "parent/child view pair not supported");
11302 case TOG_VIEW_BLAME
:
11303 if (view
->type
== TOG_VIEW_TREE
) {
11304 struct tog_tree_view_state
*s
= &view
->state
.tree
;
11306 err
= blame_tree_entry(new_view
, y
, x
,
11307 s
->selected_entry
, &s
->parents
, s
->commit_id
,
11310 return got_error_msg(GOT_ERR_NOT_IMPL
,
11311 "parent/child view pair not supported");
11314 tog_base_commit
.idx
= -1;
11315 if (view
->type
== TOG_VIEW_BLAME
)
11316 err
= log_annotated_line(new_view
, y
, x
,
11317 view
->state
.blame
.repo
, view
->state
.blame
.id_to_log
);
11318 else if (view
->type
== TOG_VIEW_TREE
)
11319 err
= log_selected_tree_entry(new_view
, y
, x
,
11320 &view
->state
.tree
);
11321 else if (view
->type
== TOG_VIEW_REF
)
11322 err
= log_ref_entry(new_view
, y
, x
,
11323 view
->state
.ref
.selected_entry
,
11324 view
->state
.ref
.repo
);
11326 return got_error_msg(GOT_ERR_NOT_IMPL
,
11327 "parent/child view pair not supported");
11329 case TOG_VIEW_TREE
:
11330 if (view
->type
== TOG_VIEW_LOG
)
11331 err
= browse_commit_tree(new_view
, y
, x
,
11332 view
->state
.log
.selected_entry
,
11333 view
->state
.log
.in_repo_path
,
11334 view
->state
.log
.head_ref_name
,
11335 view
->state
.log
.repo
);
11336 else if (view
->type
== TOG_VIEW_REF
)
11337 err
= browse_ref_tree(new_view
, y
, x
,
11338 view
->state
.ref
.selected_entry
,
11339 view
->state
.ref
.repo
);
11341 return got_error_msg(GOT_ERR_NOT_IMPL
,
11342 "parent/child view pair not supported");
11345 *new_view
= view_open(0, 0, y
, x
, TOG_VIEW_REF
);
11346 if (*new_view
== NULL
)
11347 return got_error_from_errno("view_open");
11348 if (view
->type
== TOG_VIEW_LOG
)
11349 err
= open_ref_view(*new_view
, view
->state
.log
.repo
);
11350 else if (view
->type
== TOG_VIEW_TREE
)
11351 err
= open_ref_view(*new_view
, view
->state
.tree
.repo
);
11353 err
= got_error_msg(GOT_ERR_NOT_IMPL
,
11354 "parent/child view pair not supported");
11356 view_close(*new_view
);
11358 case TOG_VIEW_HELP
:
11359 *new_view
= view_open(0, 0, 0, 0, TOG_VIEW_HELP
);
11360 if (*new_view
== NULL
)
11361 return got_error_from_errno("view_open");
11362 err
= open_help_view(*new_view
, view
);
11364 view_close(*new_view
);
11367 return got_error_msg(GOT_ERR_NOT_IMPL
, "invalid view");
11374 * If view was scrolled down to move the selected line into view when opening a
11375 * horizontal split, scroll back up when closing the split/toggling fullscreen.
11378 offset_selection_up(struct tog_view
*view
)
11380 switch (view
->type
) {
11381 case TOG_VIEW_BLAME
: {
11382 struct tog_blame_view_state
*s
= &view
->state
.blame
;
11383 if (s
->first_displayed_line
== 1) {
11384 s
->selected_line
= MAX(s
->selected_line
- view
->offset
,
11388 if (s
->first_displayed_line
> view
->offset
)
11389 s
->first_displayed_line
-= view
->offset
;
11391 s
->first_displayed_line
= 1;
11392 s
->selected_line
+= view
->offset
;
11396 log_scroll_up(&view
->state
.log
, view
->offset
);
11397 view
->state
.log
.selected
+= view
->offset
;
11400 ref_scroll_up(&view
->state
.ref
, view
->offset
);
11401 view
->state
.ref
.selected
+= view
->offset
;
11403 case TOG_VIEW_TREE
:
11404 tree_scroll_up(&view
->state
.tree
, view
->offset
);
11405 view
->state
.tree
.selected
+= view
->offset
;
11415 * If the selected line is in the section of screen covered by the bottom split,
11416 * scroll down offset lines to move it into view and index its new position.
11418 static const struct got_error
*
11419 offset_selection_down(struct tog_view
*view
)
11421 const struct got_error
*err
= NULL
;
11422 const struct got_error
*(*scrolld
)(struct tog_view
*, int);
11423 int *selected
= NULL
;
11424 int header
, offset
;
11426 switch (view
->type
) {
11427 case TOG_VIEW_BLAME
: {
11428 struct tog_blame_view_state
*s
= &view
->state
.blame
;
11431 if (s
->selected_line
> view
->nlines
- header
) {
11432 offset
= abs(view
->nlines
- s
->selected_line
- header
);
11433 s
->first_displayed_line
+= offset
;
11434 s
->selected_line
-= offset
;
11435 view
->offset
= offset
;
11439 case TOG_VIEW_LOG
: {
11440 struct tog_log_view_state
*s
= &view
->state
.log
;
11441 scrolld
= &log_scroll_down
;
11442 header
= view_is_parent_view(view
) ? 3 : 2;
11443 selected
= &s
->selected
;
11446 case TOG_VIEW_REF
: {
11447 struct tog_ref_view_state
*s
= &view
->state
.ref
;
11448 scrolld
= &ref_scroll_down
;
11450 selected
= &s
->selected
;
11453 case TOG_VIEW_TREE
: {
11454 struct tog_tree_view_state
*s
= &view
->state
.tree
;
11455 scrolld
= &tree_scroll_down
;
11457 selected
= &s
->selected
;
11467 if (selected
&& *selected
> view
->nlines
- header
) {
11468 offset
= abs(view
->nlines
- *selected
- header
);
11469 view
->offset
= offset
;
11470 if (scrolld
&& offset
) {
11471 err
= scrolld(view
, offset
);
11472 *selected
-= MIN(*selected
, offset
);
11480 list_commands(FILE *fp
)
11484 fprintf(fp
, "commands:");
11485 for (i
= 0; i
< nitems(tog_commands
); i
++) {
11486 const struct tog_cmd
*cmd
= &tog_commands
[i
];
11487 fprintf(fp
, " %s", cmd
->name
);
11493 usage(int hflag
, int status
)
11495 FILE *fp
= (status
== 0) ? stdout
: stderr
;
11497 fprintf(fp
, "usage: %s [-hV] command [arg ...]\n",
11500 fprintf(fp
, "lazy usage: %s path\n", getprogname());
11507 make_argv(int argc
, ...)
11513 va_start(ap
, argc
);
11515 argv
= calloc(argc
, sizeof(char *));
11518 for (i
= 0; i
< argc
; i
++) {
11519 argv
[i
] = strdup(va_arg(ap
, char *));
11520 if (argv
[i
] == NULL
)
11529 * Try to convert 'tog path' into a 'tog log path' command.
11530 * The user could simply have mistyped the command rather than knowingly
11531 * provided a path. So check whether argv[0] can in fact be resolved
11532 * to a path in the HEAD commit and print a special error if not.
11533 * This hack is for mpi@ <3
11535 static const struct got_error
*
11536 tog_log_with_path(int argc
, char *argv
[])
11538 const struct got_error
*error
= NULL
, *close_err
;
11539 const struct tog_cmd
*cmd
= NULL
;
11540 struct got_repository
*repo
= NULL
;
11541 struct got_worktree
*worktree
= NULL
;
11542 struct got_object_id
*commit_id
= NULL
, *id
= NULL
;
11543 struct got_commit_object
*commit
= NULL
;
11544 char *cwd
= NULL
, *repo_path
= NULL
, *in_repo_path
= NULL
;
11545 char *commit_id_str
= NULL
, **cmd_argv
= NULL
;
11546 int *pack_fds
= NULL
;
11548 cwd
= getcwd(NULL
, 0);
11550 return got_error_from_errno("getcwd");
11552 error
= got_repo_pack_fds_open(&pack_fds
);
11556 error
= got_worktree_open(&worktree
, cwd
, NULL
);
11557 if (error
&& error
->code
!= GOT_ERR_NOT_WORKTREE
)
11561 repo_path
= strdup(got_worktree_get_repo_path(worktree
));
11563 repo_path
= strdup(cwd
);
11564 if (repo_path
== NULL
) {
11565 error
= got_error_from_errno("strdup");
11569 error
= got_repo_open(&repo
, repo_path
, NULL
, pack_fds
);
11573 error
= get_in_repo_path_from_argv0(&in_repo_path
, argc
, argv
,
11578 error
= tog_load_refs(repo
, 0);
11581 error
= got_repo_match_object_id(&commit_id
, NULL
, worktree
?
11582 got_worktree_get_head_ref_name(worktree
) : GOT_REF_HEAD
,
11583 GOT_OBJ_TYPE_COMMIT
, &tog_refs
, repo
);
11588 got_worktree_close(worktree
);
11592 error
= got_object_open_as_commit(&commit
, repo
, commit_id
);
11596 error
= got_object_id_by_path(&id
, repo
, commit
, in_repo_path
);
11598 if (error
->code
!= GOT_ERR_NO_TREE_ENTRY
)
11600 fprintf(stderr
, "%s: '%s' is no known command or path\n",
11601 getprogname(), argv
[0]);
11606 error
= got_object_id_str(&commit_id_str
, commit_id
);
11610 cmd
= &tog_commands
[0]; /* log */
11612 cmd_argv
= make_argv(argc
, cmd
->name
, "-c", commit_id_str
, argv
[0]);
11613 error
= cmd
->cmd_main(argc
, cmd_argv
);
11616 close_err
= got_repo_close(repo
);
11621 got_object_commit_close(commit
);
11623 got_worktree_close(worktree
);
11625 const struct got_error
*pack_err
=
11626 got_repo_pack_fds_close(pack_fds
);
11631 free(commit_id_str
);
11635 free(in_repo_path
);
11638 for (i
= 0; i
< argc
; i
++)
11647 main(int argc
, char *argv
[])
11649 const struct got_error
*io_err
, *error
= NULL
;
11650 const struct tog_cmd
*cmd
= NULL
;
11651 int ch
, hflag
= 0, Vflag
= 0;
11652 char **cmd_argv
= NULL
;
11653 static const struct option longopts
[] = {
11654 { "version", no_argument
, NULL
, 'V' },
11655 { NULL
, 0, NULL
, 0}
11657 char *diff_algo_str
= NULL
;
11658 const char *test_script_path
;
11660 setlocale(LC_CTYPE
, "");
11663 * Override default signal handlers before starting ncurses.
11664 * This should prevent ncurses from installing its own
11665 * broken cleanup() signal handler.
11667 signal(SIGWINCH
, tog_sigwinch
);
11668 signal(SIGPIPE
, tog_sigpipe
);
11669 signal(SIGCONT
, tog_sigcont
);
11670 signal(SIGINT
, tog_sigint
);
11671 signal(SIGTERM
, tog_sigterm
);
11674 * Test mode init must happen before pledge() because "tty" will
11675 * not allow TTY-related ioctls to occur via regular files.
11677 test_script_path
= getenv("TOG_TEST_SCRIPT");
11678 if (test_script_path
!= NULL
) {
11679 error
= init_mock_term(test_script_path
);
11681 fprintf(stderr
, "%s: %s\n", getprogname(), error
->msg
);
11684 } else if (!isatty(STDIN_FILENO
))
11685 errx(1, "standard input is not a tty");
11687 #if !defined(PROFILE)
11688 if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd unveil",
11693 while ((ch
= getopt_long(argc
, argv
, "+hV", longopts
, NULL
)) != -1) {
11713 got_version_print_str();
11720 /* Build an argument vector which runs a default command. */
11721 cmd
= &tog_commands
[0];
11723 cmd_argv
= make_argv(argc
, cmd
->name
);
11727 /* Did the user specify a command? */
11728 for (i
= 0; i
< nitems(tog_commands
); i
++) {
11729 if (strncmp(tog_commands
[i
].name
, argv
[0],
11730 strlen(argv
[0])) == 0) {
11731 cmd
= &tog_commands
[i
];
11737 diff_algo_str
= getenv("TOG_DIFF_ALGORITHM");
11738 if (diff_algo_str
) {
11739 if (strcasecmp(diff_algo_str
, "patience") == 0)
11740 tog_diff_algo
= GOT_DIFF_ALGORITHM_PATIENCE
;
11741 if (strcasecmp(diff_algo_str
, "myers") == 0)
11742 tog_diff_algo
= GOT_DIFF_ALGORITHM_MYERS
;
11745 tog_base_commit
.idx
= -1;
11746 tog_base_commit
.marker
= GOT_WORKTREE_STATE_UNKNOWN
;
11751 /* No command specified; try log with a path */
11752 error
= tog_log_with_path(argc
, argv
);
11757 error
= cmd
->cmd_main(argc
, cmd_argv
? cmd_argv
: argv
);
11760 if (using_mock_io
) {
11761 io_err
= tog_io_close();
11768 for (i
= 0; i
< argc
; i
++)
11773 if (error
&& error
->code
!= GOT_ERR_CANCELLED
&&
11774 error
->code
!= GOT_ERR_EOF
&&
11775 error
->code
!= GOT_ERR_PRIVSEP_EXIT
&&
11776 error
->code
!= GOT_ERR_PRIVSEP_PIPE
&&
11777 !(error
->code
== GOT_ERR_ERRNO
&& errno
== EINTR
)) {
11778 fprintf(stderr
, "%s: %s\n", getprogname(), error
->msg
);