Blob


1 /*
2 * Copyright (c) 2018, 2019, 2020 Stefan Sperling <stsp@openbsd.org>
3 *
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.
7 *
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.
15 */
17 #include <sys/queue.h>
18 #include <sys/stat.h>
19 #include <sys/ioctl.h>
21 #include <errno.h>
22 #define _XOPEN_SOURCE_EXTENDED
23 #include <curses.h>
24 #undef _XOPEN_SOURCE_EXTENDED
25 #include <panel.h>
26 #include <locale.h>
27 #include <signal.h>
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <getopt.h>
31 #include <string.h>
32 #include <err.h>
33 #include <unistd.h>
34 #include <util.h>
35 #include <limits.h>
36 #include <wchar.h>
37 #include <time.h>
38 #include <pthread.h>
39 #include <libgen.h>
40 #include <regex.h>
42 #include "got_version.h"
43 #include "got_error.h"
44 #include "got_object.h"
45 #include "got_reference.h"
46 #include "got_repository.h"
47 #include "got_diff.h"
48 #include "got_opentemp.h"
49 #include "got_utf8.h"
50 #include "got_cancel.h"
51 #include "got_commit_graph.h"
52 #include "got_blame.h"
53 #include "got_privsep.h"
54 #include "got_path.h"
55 #include "got_worktree.h"
57 #ifndef MIN
58 #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b))
59 #endif
61 #ifndef MAX
62 #define MAX(_a,_b) ((_a) > (_b) ? (_a) : (_b))
63 #endif
65 #define CTRL(x) ((x) & 0x1f)
67 #ifndef nitems
68 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
69 #endif
71 struct tog_cmd {
72 const char *name;
73 const struct got_error *(*cmd_main)(int, char *[]);
74 void (*cmd_usage)(void);
75 };
77 __dead static void usage(int);
78 __dead static void usage_log(void);
79 __dead static void usage_diff(void);
80 __dead static void usage_blame(void);
81 __dead static void usage_tree(void);
83 static const struct got_error* cmd_log(int, char *[]);
84 static const struct got_error* cmd_diff(int, char *[]);
85 static const struct got_error* cmd_blame(int, char *[]);
86 static const struct got_error* cmd_tree(int, char *[]);
88 static struct tog_cmd tog_commands[] = {
89 { "log", cmd_log, usage_log },
90 { "diff", cmd_diff, usage_diff },
91 { "blame", cmd_blame, usage_blame },
92 { "tree", cmd_tree, usage_tree },
93 };
95 enum tog_view_type {
96 TOG_VIEW_DIFF,
97 TOG_VIEW_LOG,
98 TOG_VIEW_BLAME,
99 TOG_VIEW_TREE
100 };
102 #define TOG_EOF_STRING "(END)"
104 struct commit_queue_entry {
105 TAILQ_ENTRY(commit_queue_entry) entry;
106 struct got_object_id *id;
107 struct got_commit_object *commit;
108 int idx;
109 };
110 TAILQ_HEAD(commit_queue_head, commit_queue_entry);
111 struct commit_queue {
112 int ncommits;
113 struct commit_queue_head head;
114 };
116 struct tog_color {
117 SIMPLEQ_ENTRY(tog_color) entry;
118 regex_t regex;
119 short colorpair;
120 };
121 SIMPLEQ_HEAD(tog_colors, tog_color);
123 static const struct got_error *
124 add_color(struct tog_colors *colors, const char *pattern,
125 int idx, short color)
127 const struct got_error *err = NULL;
128 struct tog_color *tc;
129 int regerr = 0;
131 if (idx < 1 || idx > COLOR_PAIRS - 1)
132 return NULL;
134 init_pair(idx, color, -1);
136 tc = calloc(1, sizeof(*tc));
137 if (tc == NULL)
138 return got_error_from_errno("calloc");
139 regerr = regcomp(&tc->regex, pattern,
140 REG_EXTENDED | REG_NOSUB | REG_NEWLINE);
141 if (regerr) {
142 static char regerr_msg[512];
143 static char err_msg[512];
144 regerror(regerr, &tc->regex, regerr_msg,
145 sizeof(regerr_msg));
146 snprintf(err_msg, sizeof(err_msg), "regcomp: %s",
147 regerr_msg);
148 err = got_error_msg(GOT_ERR_REGEX, err_msg);
149 free(tc);
150 return err;
152 tc->colorpair = idx;
153 SIMPLEQ_INSERT_HEAD(colors, tc, entry);
154 return NULL;
157 static void
158 free_colors(struct tog_colors *colors)
160 struct tog_color *tc;
162 while (!SIMPLEQ_EMPTY(colors)) {
163 tc = SIMPLEQ_FIRST(colors);
164 SIMPLEQ_REMOVE_HEAD(colors, entry);
165 regfree(&tc->regex);
166 free(tc);
170 struct tog_color *
171 get_color(struct tog_colors *colors, int colorpair)
173 struct tog_color *tc = NULL;
175 SIMPLEQ_FOREACH(tc, colors, entry) {
176 if (tc->colorpair == colorpair)
177 return tc;
180 return NULL;
183 static int
184 default_color_value(const char *envvar)
186 if (strcmp(envvar, "TOG_COLOR_DIFF_MINUS") == 0)
187 return COLOR_MAGENTA;
188 if (strcmp(envvar, "TOG_COLOR_DIFF_PLUS") == 0)
189 return COLOR_CYAN;
190 if (strcmp(envvar, "TOG_COLOR_DIFF_CHUNK_HEADER") == 0)
191 return COLOR_YELLOW;
192 if (strcmp(envvar, "TOG_COLOR_DIFF_META") == 0)
193 return COLOR_GREEN;
194 if (strcmp(envvar, "TOG_COLOR_TREE_SUBMODULE") == 0)
195 return COLOR_MAGENTA;
196 if (strcmp(envvar, "TOG_COLOR_TREE_SYMLINK") == 0)
197 return COLOR_MAGENTA;
198 if (strcmp(envvar, "TOG_COLOR_TREE_DIRECTORY") == 0)
199 return COLOR_CYAN;
200 if (strcmp(envvar, "TOG_COLOR_TREE_EXECUTABLE") == 0)
201 return COLOR_GREEN;
202 if (strcmp(envvar, "TOG_COLOR_COMMIT") == 0)
203 return COLOR_GREEN;
204 if (strcmp(envvar, "TOG_COLOR_AUTHOR") == 0)
205 return COLOR_CYAN;
206 if (strcmp(envvar, "TOG_COLOR_DATE") == 0)
207 return COLOR_YELLOW;
209 return -1;
212 static int
213 get_color_value(const char *envvar)
215 const char *val = getenv(envvar);
217 if (val == NULL)
218 return default_color_value(envvar);
220 if (strcasecmp(val, "black") == 0)
221 return COLOR_BLACK;
222 if (strcasecmp(val, "red") == 0)
223 return COLOR_RED;
224 if (strcasecmp(val, "green") == 0)
225 return COLOR_GREEN;
226 if (strcasecmp(val, "yellow") == 0)
227 return COLOR_YELLOW;
228 if (strcasecmp(val, "blue") == 0)
229 return COLOR_BLUE;
230 if (strcasecmp(val, "magenta") == 0)
231 return COLOR_MAGENTA;
232 if (strcasecmp(val, "cyan") == 0)
233 return COLOR_CYAN;
234 if (strcasecmp(val, "white") == 0)
235 return COLOR_WHITE;
236 if (strcasecmp(val, "default") == 0)
237 return -1;
239 return default_color_value(envvar);
243 struct tog_diff_view_state {
244 struct got_object_id *id1, *id2;
245 FILE *f;
246 int first_displayed_line;
247 int last_displayed_line;
248 int eof;
249 int diff_context;
250 struct got_repository *repo;
251 struct got_reflist_head *refs;
252 struct tog_colors colors;
254 /* passed from log view; may be NULL */
255 struct tog_view *log_view;
256 };
258 pthread_mutex_t tog_mutex = PTHREAD_MUTEX_INITIALIZER;
260 struct tog_log_thread_args {
261 pthread_cond_t need_commits;
262 int commits_needed;
263 struct got_commit_graph *graph;
264 struct commit_queue *commits;
265 const char *in_repo_path;
266 struct got_object_id *start_id;
267 struct got_repository *repo;
268 int log_complete;
269 sig_atomic_t *quit;
270 struct commit_queue_entry **first_displayed_entry;
271 struct commit_queue_entry **selected_entry;
272 int *searching;
273 int *search_next_done;
274 regex_t *regex;
275 };
277 struct tog_log_view_state {
278 struct commit_queue commits;
279 struct commit_queue_entry *first_displayed_entry;
280 struct commit_queue_entry *last_displayed_entry;
281 struct commit_queue_entry *selected_entry;
282 int selected;
283 char *in_repo_path;
284 const char *head_ref_name;
285 int log_branches;
286 struct got_repository *repo;
287 struct got_reflist_head *refs;
288 struct got_object_id *start_id;
289 sig_atomic_t quit;
290 pthread_t thread;
291 struct tog_log_thread_args thread_args;
292 struct commit_queue_entry *matched_entry;
293 struct commit_queue_entry *search_entry;
294 struct tog_colors colors;
295 };
297 #define TOG_COLOR_DIFF_MINUS 1
298 #define TOG_COLOR_DIFF_PLUS 2
299 #define TOG_COLOR_DIFF_CHUNK_HEADER 3
300 #define TOG_COLOR_DIFF_META 4
301 #define TOG_COLOR_TREE_SUBMODULE 5
302 #define TOG_COLOR_TREE_SYMLINK 6
303 #define TOG_COLOR_TREE_DIRECTORY 7
304 #define TOG_COLOR_TREE_EXECUTABLE 8
305 #define TOG_COLOR_COMMIT 9
306 #define TOG_COLOR_AUTHOR 10
307 #define TOG_COLOR_DATE 11
309 struct tog_blame_cb_args {
310 struct tog_blame_line *lines; /* one per line */
311 int nlines;
313 struct tog_view *view;
314 struct got_object_id *commit_id;
315 int *quit;
316 };
318 struct tog_blame_thread_args {
319 const char *path;
320 struct got_repository *repo;
321 struct tog_blame_cb_args *cb_args;
322 int *complete;
323 got_cancel_cb cancel_cb;
324 void *cancel_arg;
325 };
327 struct tog_blame {
328 FILE *f;
329 size_t filesize;
330 struct tog_blame_line *lines;
331 int nlines;
332 off_t *line_offsets;
333 pthread_t thread;
334 struct tog_blame_thread_args thread_args;
335 struct tog_blame_cb_args cb_args;
336 const char *path;
337 };
339 struct tog_blame_view_state {
340 int first_displayed_line;
341 int last_displayed_line;
342 int selected_line;
343 int blame_complete;
344 int eof;
345 int done;
346 struct got_object_id_queue blamed_commits;
347 struct got_object_qid *blamed_commit;
348 char *path;
349 struct got_repository *repo;
350 struct got_reflist_head *refs;
351 struct got_object_id *commit_id;
352 struct tog_blame blame;
353 int matched_line;
354 struct tog_colors colors;
355 };
357 struct tog_parent_tree {
358 TAILQ_ENTRY(tog_parent_tree) entry;
359 struct got_tree_object *tree;
360 struct got_tree_entry *first_displayed_entry;
361 struct got_tree_entry *selected_entry;
362 int selected;
363 };
365 TAILQ_HEAD(tog_parent_trees, tog_parent_tree);
367 struct tog_tree_view_state {
368 char *tree_label;
369 struct got_tree_object *root;
370 struct got_tree_object *tree;
371 struct got_tree_entry *first_displayed_entry;
372 struct got_tree_entry *last_displayed_entry;
373 struct got_tree_entry *selected_entry;
374 int ndisplayed, selected, show_ids;
375 struct tog_parent_trees parents;
376 struct got_object_id *commit_id;
377 struct got_repository *repo;
378 struct got_reflist_head *refs;
379 struct got_tree_entry *matched_entry;
380 struct tog_colors colors;
381 };
383 /*
384 * We implement two types of views: parent views and child views.
386 * The 'Tab' key switches between a parent view and its child view.
387 * Child views are shown side-by-side to their parent view, provided
388 * there is enough screen estate.
390 * When a new view is opened from within a parent view, this new view
391 * becomes a child view of the parent view, replacing any existing child.
393 * When a new view is opened from within a child view, this new view
394 * becomes a parent view which will obscure the views below until the
395 * user quits the new parent view by typing 'q'.
397 * This list of views contains parent views only.
398 * Child views are only pointed to by their parent view.
399 */
400 TAILQ_HEAD(tog_view_list_head, tog_view);
402 struct tog_view {
403 TAILQ_ENTRY(tog_view) entry;
404 WINDOW *window;
405 PANEL *panel;
406 int nlines, ncols, begin_y, begin_x;
407 int lines, cols; /* copies of LINES and COLS */
408 int focussed;
409 struct tog_view *parent;
410 struct tog_view *child;
411 int child_focussed;
413 /* type-specific state */
414 enum tog_view_type type;
415 union {
416 struct tog_diff_view_state diff;
417 struct tog_log_view_state log;
418 struct tog_blame_view_state blame;
419 struct tog_tree_view_state tree;
420 } state;
422 const struct got_error *(*show)(struct tog_view *);
423 const struct got_error *(*input)(struct tog_view **,
424 struct tog_view **, struct tog_view**, struct tog_view *, int);
425 const struct got_error *(*close)(struct tog_view *);
427 const struct got_error *(*search_start)(struct tog_view *);
428 const struct got_error *(*search_next)(struct tog_view *);
429 int searching;
430 #define TOG_SEARCH_FORWARD 1
431 #define TOG_SEARCH_BACKWARD 2
432 int search_next_done;
433 regex_t regex;
434 };
436 static const struct got_error *open_diff_view(struct tog_view *,
437 struct got_object_id *, struct got_object_id *, struct tog_view *,
438 struct got_reflist_head *, struct got_repository *);
439 static const struct got_error *show_diff_view(struct tog_view *);
440 static const struct got_error *input_diff_view(struct tog_view **,
441 struct tog_view **, struct tog_view **, struct tog_view *, int);
442 static const struct got_error* close_diff_view(struct tog_view *);
444 static const struct got_error *open_log_view(struct tog_view *,
445 struct got_object_id *, struct got_reflist_head *,
446 struct got_repository *, const char *, const char *, int, int);
447 static const struct got_error * show_log_view(struct tog_view *);
448 static const struct got_error *input_log_view(struct tog_view **,
449 struct tog_view **, struct tog_view **, struct tog_view *, int);
450 static const struct got_error *close_log_view(struct tog_view *);
451 static const struct got_error *search_start_log_view(struct tog_view *);
452 static const struct got_error *search_next_log_view(struct tog_view *);
454 static const struct got_error *open_blame_view(struct tog_view *, char *,
455 struct got_object_id *, struct got_reflist_head *, struct got_repository *);
456 static const struct got_error *show_blame_view(struct tog_view *);
457 static const struct got_error *input_blame_view(struct tog_view **,
458 struct tog_view **, struct tog_view **, struct tog_view *, int);
459 static const struct got_error *close_blame_view(struct tog_view *);
460 static const struct got_error *search_start_blame_view(struct tog_view *);
461 static const struct got_error *search_next_blame_view(struct tog_view *);
463 static const struct got_error *open_tree_view(struct tog_view *,
464 struct got_tree_object *, struct got_object_id *,
465 struct got_reflist_head *, struct got_repository *);
466 static const struct got_error *show_tree_view(struct tog_view *);
467 static const struct got_error *input_tree_view(struct tog_view **,
468 struct tog_view **, struct tog_view **, struct tog_view *, int);
469 static const struct got_error *close_tree_view(struct tog_view *);
470 static const struct got_error *search_start_tree_view(struct tog_view *);
471 static const struct got_error *search_next_tree_view(struct tog_view *);
473 static volatile sig_atomic_t tog_sigwinch_received;
474 static volatile sig_atomic_t tog_sigpipe_received;
475 static volatile sig_atomic_t tog_sigcont_received;
477 static void
478 tog_sigwinch(int signo)
480 tog_sigwinch_received = 1;
483 static void
484 tog_sigpipe(int signo)
486 tog_sigpipe_received = 1;
489 static void
490 tog_sigcont(int signo)
492 tog_sigcont_received = 1;
495 static const struct got_error *
496 view_close(struct tog_view *view)
498 const struct got_error *err = NULL;
500 if (view->child) {
501 view_close(view->child);
502 view->child = NULL;
504 if (view->close)
505 err = view->close(view);
506 if (view->panel)
507 del_panel(view->panel);
508 if (view->window)
509 delwin(view->window);
510 free(view);
511 return err;
514 static struct tog_view *
515 view_open(int nlines, int ncols, int begin_y, int begin_x,
516 enum tog_view_type type)
518 struct tog_view *view = calloc(1, sizeof(*view));
520 if (view == NULL)
521 return NULL;
523 view->type = type;
524 view->lines = LINES;
525 view->cols = COLS;
526 view->nlines = nlines ? nlines : LINES - begin_y;
527 view->ncols = ncols ? ncols : COLS - begin_x;
528 view->begin_y = begin_y;
529 view->begin_x = begin_x;
530 view->window = newwin(nlines, ncols, begin_y, begin_x);
531 if (view->window == NULL) {
532 view_close(view);
533 return NULL;
535 view->panel = new_panel(view->window);
536 if (view->panel == NULL ||
537 set_panel_userptr(view->panel, view) != OK) {
538 view_close(view);
539 return NULL;
542 keypad(view->window, TRUE);
543 return view;
546 static int
547 view_split_begin_x(int begin_x)
549 if (begin_x > 0 || COLS < 120)
550 return 0;
551 return (COLS - MAX(COLS / 2, 80));
554 static const struct got_error *view_resize(struct tog_view *);
556 static const struct got_error *
557 view_splitscreen(struct tog_view *view)
559 const struct got_error *err = NULL;
561 view->begin_y = 0;
562 view->begin_x = view_split_begin_x(0);
563 view->nlines = LINES;
564 view->ncols = COLS - view->begin_x;
565 view->lines = LINES;
566 view->cols = COLS;
567 err = view_resize(view);
568 if (err)
569 return err;
571 if (mvwin(view->window, view->begin_y, view->begin_x) == ERR)
572 return got_error_from_errno("mvwin");
574 return NULL;
577 static const struct got_error *
578 view_fullscreen(struct tog_view *view)
580 const struct got_error *err = NULL;
582 view->begin_x = 0;
583 view->begin_y = 0;
584 view->nlines = LINES;
585 view->ncols = COLS;
586 view->lines = LINES;
587 view->cols = COLS;
588 err = view_resize(view);
589 if (err)
590 return err;
592 if (mvwin(view->window, view->begin_y, view->begin_x) == ERR)
593 return got_error_from_errno("mvwin");
595 return NULL;
598 static int
599 view_is_parent_view(struct tog_view *view)
601 return view->parent == NULL;
604 static const struct got_error *
605 view_resize(struct tog_view *view)
607 int nlines, ncols;
609 if (view->lines > LINES)
610 nlines = view->nlines - (view->lines - LINES);
611 else
612 nlines = view->nlines + (LINES - view->lines);
614 if (view->cols > COLS)
615 ncols = view->ncols - (view->cols - COLS);
616 else
617 ncols = view->ncols + (COLS - view->cols);
619 if (wresize(view->window, nlines, ncols) == ERR)
620 return got_error_from_errno("wresize");
621 if (replace_panel(view->panel, view->window) == ERR)
622 return got_error_from_errno("replace_panel");
623 wclear(view->window);
625 view->nlines = nlines;
626 view->ncols = ncols;
627 view->lines = LINES;
628 view->cols = COLS;
630 if (view->child) {
631 view->child->begin_x = view_split_begin_x(view->begin_x);
632 if (view->child->begin_x == 0) {
633 view_fullscreen(view->child);
634 if (view->child->focussed)
635 show_panel(view->child->panel);
636 else
637 show_panel(view->panel);
638 } else {
639 view_splitscreen(view->child);
640 show_panel(view->child->panel);
644 return NULL;
647 static const struct got_error *
648 view_close_child(struct tog_view *view)
650 const struct got_error *err = NULL;
652 if (view->child == NULL)
653 return NULL;
655 err = view_close(view->child);
656 view->child = NULL;
657 return err;
660 static const struct got_error *
661 view_set_child(struct tog_view *view, struct tog_view *child)
663 const struct got_error *err = NULL;
665 view->child = child;
666 child->parent = view;
667 return err;
670 static int
671 view_is_splitscreen(struct tog_view *view)
673 return view->begin_x > 0;
676 static void
677 tog_resizeterm(void)
679 int cols, lines;
680 struct winsize size;
682 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) < 0) {
683 cols = 80; /* Default */
684 lines = 24;
685 } else {
686 cols = size.ws_col;
687 lines = size.ws_row;
689 resize_term(lines, cols);
692 static const struct got_error *
693 view_search_start(struct tog_view *view)
695 const struct got_error *err = NULL;
696 char pattern[1024];
697 int ret;
698 int begin_x = 0;
700 if (view->nlines < 1)
701 return NULL;
703 if (!view_is_parent_view(view))
704 begin_x = view_split_begin_x(view->begin_x);
705 mvwaddstr(view->window, view->begin_y + view->nlines - 1,
706 begin_x, "/");
707 wclrtoeol(view->window);
709 nocbreak();
710 echo();
711 ret = wgetnstr(view->window, pattern, sizeof(pattern));
712 cbreak();
713 noecho();
714 if (ret == ERR)
715 return NULL;
717 if (view->searching) {
718 regfree(&view->regex);
719 view->searching = 0;
722 if (regcomp(&view->regex, pattern,
723 REG_EXTENDED | REG_NOSUB | REG_NEWLINE) == 0) {
724 err = view->search_start(view);
725 if (err) {
726 regfree(&view->regex);
727 return err;
729 view->searching = TOG_SEARCH_FORWARD;
730 view->search_next_done = 0;
731 view->search_next(view);
734 return NULL;
737 static const struct got_error *
738 view_input(struct tog_view **new, struct tog_view **dead,
739 struct tog_view **focus, int *done, struct tog_view *view,
740 struct tog_view_list_head *views)
742 const struct got_error *err = NULL;
743 struct tog_view *v;
744 int ch, errcode;
746 *new = NULL;
747 *dead = NULL;
748 *focus = NULL;
750 if (view->searching && !view->search_next_done) {
751 view->search_next(view);
752 return NULL;
755 nodelay(stdscr, FALSE);
756 /* Allow threads to make progress while we are waiting for input. */
757 errcode = pthread_mutex_unlock(&tog_mutex);
758 if (errcode)
759 return got_error_set_errno(errcode, "pthread_mutex_unlock");
760 ch = wgetch(view->window);
761 errcode = pthread_mutex_lock(&tog_mutex);
762 if (errcode)
763 return got_error_set_errno(errcode, "pthread_mutex_lock");
764 nodelay(stdscr, TRUE);
766 if (tog_sigwinch_received || tog_sigcont_received) {
767 tog_resizeterm();
768 tog_sigwinch_received = 0;
769 tog_sigcont_received = 0;
770 TAILQ_FOREACH(v, views, entry) {
771 err = view_resize(v);
772 if (err)
773 return err;
774 err = v->input(new, dead, focus, v, KEY_RESIZE);
775 if (err)
776 return err;
780 switch (ch) {
781 case ERR:
782 break;
783 case '\t':
784 if (view->child) {
785 *focus = view->child;
786 view->child_focussed = 1;
787 } else if (view->parent) {
788 *focus = view->parent;
789 view->parent->child_focussed = 0;
791 break;
792 case 'q':
793 err = view->input(new, dead, focus, view, ch);
794 *dead = view;
795 break;
796 case 'Q':
797 *done = 1;
798 break;
799 case 'f':
800 if (view_is_parent_view(view)) {
801 if (view->child == NULL)
802 break;
803 if (view_is_splitscreen(view->child)) {
804 *focus = view->child;
805 view->child_focussed = 1;
806 err = view_fullscreen(view->child);
807 } else
808 err = view_splitscreen(view->child);
809 if (err)
810 break;
811 err = view->child->input(new, dead, focus,
812 view->child, KEY_RESIZE);
813 } else {
814 if (view_is_splitscreen(view)) {
815 *focus = view;
816 view->parent->child_focussed = 1;
817 err = view_fullscreen(view);
818 } else {
819 err = view_splitscreen(view);
821 if (err)
822 break;
823 err = view->input(new, dead, focus, view,
824 KEY_RESIZE);
826 break;
827 case KEY_RESIZE:
828 break;
829 case '/':
830 if (view->search_start)
831 view_search_start(view);
832 else
833 err = view->input(new, dead, focus, view, ch);
834 break;
835 case 'N':
836 case 'n':
837 if (view->search_next && view->searching) {
838 view->searching = (ch == 'n' ?
839 TOG_SEARCH_FORWARD : TOG_SEARCH_BACKWARD);
840 view->search_next_done = 0;
841 view->search_next(view);
842 } else
843 err = view->input(new, dead, focus, view, ch);
844 break;
845 default:
846 err = view->input(new, dead, focus, view, ch);
847 break;
850 return err;
853 void
854 view_vborder(struct tog_view *view)
856 PANEL *panel;
857 struct tog_view *view_above;
859 if (view->parent)
860 return view_vborder(view->parent);
862 panel = panel_above(view->panel);
863 if (panel == NULL)
864 return;
866 view_above = panel_userptr(panel);
867 mvwvline(view->window, view->begin_y, view_above->begin_x - 1,
868 got_locale_is_utf8() ? ACS_VLINE : '|', view->nlines);
871 int
872 view_needs_focus_indication(struct tog_view *view)
874 if (view_is_parent_view(view)) {
875 if (view->child == NULL || view->child_focussed)
876 return 0;
877 if (!view_is_splitscreen(view->child))
878 return 0;
879 } else if (!view_is_splitscreen(view))
880 return 0;
882 return view->focussed;
885 static const struct got_error *
886 view_loop(struct tog_view *view)
888 const struct got_error *err = NULL;
889 struct tog_view_list_head views;
890 struct tog_view *new_view, *dead_view, *focus_view, *main_view;
891 int fast_refresh = 10;
892 int done = 0, errcode;
894 errcode = pthread_mutex_lock(&tog_mutex);
895 if (errcode)
896 return got_error_set_errno(errcode, "pthread_mutex_lock");
898 TAILQ_INIT(&views);
899 TAILQ_INSERT_HEAD(&views, view, entry);
901 main_view = view;
902 view->focussed = 1;
903 err = view->show(view);
904 if (err)
905 return err;
906 update_panels();
907 doupdate();
908 while (!TAILQ_EMPTY(&views) && !done && !tog_sigpipe_received) {
909 /* Refresh fast during initialization, then become slower. */
910 if (fast_refresh && fast_refresh-- == 0)
911 halfdelay(10); /* switch to once per second */
913 err = view_input(&new_view, &dead_view, &focus_view, &done,
914 view, &views);
915 if (err)
916 break;
917 if (dead_view) {
918 struct tog_view *prev = NULL;
920 if (view_is_parent_view(dead_view))
921 prev = TAILQ_PREV(dead_view,
922 tog_view_list_head, entry);
923 else if (view->parent != dead_view)
924 prev = view->parent;
926 if (dead_view->parent)
927 dead_view->parent->child = NULL;
928 else
929 TAILQ_REMOVE(&views, dead_view, entry);
931 err = view_close(dead_view);
932 if (err || (dead_view == main_view && new_view == NULL))
933 goto done;
935 if (view == dead_view) {
936 if (focus_view)
937 view = focus_view;
938 else if (prev)
939 view = prev;
940 else if (!TAILQ_EMPTY(&views))
941 view = TAILQ_LAST(&views,
942 tog_view_list_head);
943 else
944 view = NULL;
945 if (view) {
946 if (view->child && view->child_focussed)
947 focus_view = view->child;
948 else
949 focus_view = view;
953 if (new_view) {
954 struct tog_view *v, *t;
955 /* Only allow one parent view per type. */
956 TAILQ_FOREACH_SAFE(v, &views, entry, t) {
957 if (v->type != new_view->type)
958 continue;
959 TAILQ_REMOVE(&views, v, entry);
960 err = view_close(v);
961 if (err)
962 goto done;
963 break;
965 TAILQ_INSERT_TAIL(&views, new_view, entry);
966 view = new_view;
967 if (focus_view == NULL)
968 focus_view = new_view;
970 if (focus_view) {
971 show_panel(focus_view->panel);
972 if (view)
973 view->focussed = 0;
974 focus_view->focussed = 1;
975 view = focus_view;
976 if (new_view)
977 show_panel(new_view->panel);
978 if (view->child && view_is_splitscreen(view->child))
979 show_panel(view->child->panel);
981 if (view) {
982 if (focus_view == NULL) {
983 view->focussed = 1;
984 show_panel(view->panel);
985 if (view->child && view_is_splitscreen(view->child))
986 show_panel(view->child->panel);
987 focus_view = view;
989 if (view->parent) {
990 err = view->parent->show(view->parent);
991 if (err)
992 goto done;
994 err = view->show(view);
995 if (err)
996 goto done;
997 if (view->child) {
998 err = view->child->show(view->child);
999 if (err)
1000 goto done;
1002 update_panels();
1003 doupdate();
1006 done:
1007 while (!TAILQ_EMPTY(&views)) {
1008 view = TAILQ_FIRST(&views);
1009 TAILQ_REMOVE(&views, view, entry);
1010 view_close(view);
1013 errcode = pthread_mutex_unlock(&tog_mutex);
1014 if (errcode && err == NULL)
1015 err = got_error_set_errno(errcode, "pthread_mutex_unlock");
1017 return err;
1020 __dead static void
1021 usage_log(void)
1023 endwin();
1024 fprintf(stderr,
1025 "usage: %s log [-b] [-c commit] [-r repository-path] [path]\n",
1026 getprogname());
1027 exit(1);
1030 /* Create newly allocated wide-character string equivalent to a byte string. */
1031 static const struct got_error *
1032 mbs2ws(wchar_t **ws, size_t *wlen, const char *s)
1034 char *vis = NULL;
1035 const struct got_error *err = NULL;
1037 *ws = NULL;
1038 *wlen = mbstowcs(NULL, s, 0);
1039 if (*wlen == (size_t)-1) {
1040 int vislen;
1041 if (errno != EILSEQ)
1042 return got_error_from_errno("mbstowcs");
1044 /* byte string invalid in current encoding; try to "fix" it */
1045 err = got_mbsavis(&vis, &vislen, s);
1046 if (err)
1047 return err;
1048 *wlen = mbstowcs(NULL, vis, 0);
1049 if (*wlen == (size_t)-1) {
1050 err = got_error_from_errno("mbstowcs"); /* give up */
1051 goto done;
1055 *ws = calloc(*wlen + 1, sizeof(**ws));
1056 if (*ws == NULL) {
1057 err = got_error_from_errno("calloc");
1058 goto done;
1061 if (mbstowcs(*ws, vis ? vis : s, *wlen) != *wlen)
1062 err = got_error_from_errno("mbstowcs");
1063 done:
1064 free(vis);
1065 if (err) {
1066 free(*ws);
1067 *ws = NULL;
1068 *wlen = 0;
1070 return err;
1073 /* Format a line for display, ensuring that it won't overflow a width limit. */
1074 static const struct got_error *
1075 format_line(wchar_t **wlinep, int *widthp, const char *line, int wlimit,
1076 int col_tab_align)
1078 const struct got_error *err = NULL;
1079 int cols = 0;
1080 wchar_t *wline = NULL;
1081 size_t wlen;
1082 int i;
1084 *wlinep = NULL;
1085 *widthp = 0;
1087 err = mbs2ws(&wline, &wlen, line);
1088 if (err)
1089 return err;
1091 i = 0;
1092 while (i < wlen) {
1093 int width = wcwidth(wline[i]);
1095 if (width == 0) {
1096 i++;
1097 continue;
1100 if (width == 1 || width == 2) {
1101 if (cols + width > wlimit)
1102 break;
1103 cols += width;
1104 i++;
1105 } else if (width == -1) {
1106 if (wline[i] == L'\t') {
1107 width = TABSIZE -
1108 ((cols + col_tab_align) % TABSIZE);
1109 if (cols + width > wlimit)
1110 break;
1111 cols += width;
1113 i++;
1114 } else {
1115 err = got_error_from_errno("wcwidth");
1116 goto done;
1119 wline[i] = L'\0';
1120 if (widthp)
1121 *widthp = cols;
1122 done:
1123 if (err)
1124 free(wline);
1125 else
1126 *wlinep = wline;
1127 return err;
1130 static const struct got_error*
1131 build_refs_str(char **refs_str, struct got_reflist_head *refs,
1132 struct got_object_id *id, struct got_repository *repo)
1134 static const struct got_error *err = NULL;
1135 struct got_reflist_entry *re;
1136 char *s;
1137 const char *name;
1139 *refs_str = NULL;
1141 SIMPLEQ_FOREACH(re, refs, entry) {
1142 struct got_tag_object *tag = NULL;
1143 int cmp;
1145 name = got_ref_get_name(re->ref);
1146 if (strcmp(name, GOT_REF_HEAD) == 0)
1147 continue;
1148 if (strncmp(name, "refs/", 5) == 0)
1149 name += 5;
1150 if (strncmp(name, "got/", 4) == 0)
1151 continue;
1152 if (strncmp(name, "heads/", 6) == 0)
1153 name += 6;
1154 if (strncmp(name, "remotes/", 8) == 0)
1155 name += 8;
1156 if (strncmp(name, "tags/", 5) == 0) {
1157 err = got_object_open_as_tag(&tag, repo, re->id);
1158 if (err) {
1159 if (err->code != GOT_ERR_OBJ_TYPE)
1160 break;
1161 /* Ref points at something other than a tag. */
1162 err = NULL;
1163 tag = NULL;
1166 cmp = got_object_id_cmp(tag ?
1167 got_object_tag_get_object_id(tag) : re->id, id);
1168 if (tag)
1169 got_object_tag_close(tag);
1170 if (cmp != 0)
1171 continue;
1172 s = *refs_str;
1173 if (asprintf(refs_str, "%s%s%s", s ? s : "",
1174 s ? ", " : "", name) == -1) {
1175 err = got_error_from_errno("asprintf");
1176 free(s);
1177 *refs_str = NULL;
1178 break;
1180 free(s);
1183 return err;
1186 static const struct got_error *
1187 format_author(wchar_t **wauthor, int *author_width, char *author, int limit,
1188 int col_tab_align)
1190 char *smallerthan, *at;
1192 smallerthan = strchr(author, '<');
1193 if (smallerthan && smallerthan[1] != '\0')
1194 author = smallerthan + 1;
1195 at = strchr(author, '@');
1196 if (at)
1197 *at = '\0';
1198 return format_line(wauthor, author_width, author, limit, col_tab_align);
1201 static const struct got_error *
1202 draw_commit(struct tog_view *view, struct got_commit_object *commit,
1203 struct got_object_id *id, struct got_reflist_head *refs,
1204 const size_t date_display_cols, int author_display_cols,
1205 struct tog_colors *colors)
1207 const struct got_error *err = NULL;
1208 char datebuf[12]; /* YYYY-MM-DD + SPACE + NUL */
1209 char *logmsg0 = NULL, *logmsg = NULL;
1210 char *author = NULL;
1211 wchar_t *wlogmsg = NULL, *wauthor = NULL;
1212 int author_width, logmsg_width;
1213 char *newline, *line = NULL;
1214 int col, limit;
1215 const int avail = view->ncols;
1216 struct tm tm;
1217 time_t committer_time;
1218 struct tog_color *tc;
1220 committer_time = got_object_commit_get_committer_time(commit);
1221 if (localtime_r(&committer_time, &tm) == NULL)
1222 return got_error_from_errno("localtime_r");
1223 if (strftime(datebuf, sizeof(datebuf), "%G-%m-%d ", &tm)
1224 >= sizeof(datebuf))
1225 return got_error(GOT_ERR_NO_SPACE);
1227 if (avail <= date_display_cols)
1228 limit = MIN(sizeof(datebuf) - 1, avail);
1229 else
1230 limit = MIN(date_display_cols, sizeof(datebuf) - 1);
1231 tc = get_color(colors, TOG_COLOR_DATE);
1232 if (tc)
1233 wattr_on(view->window,
1234 COLOR_PAIR(tc->colorpair), NULL);
1235 waddnstr(view->window, datebuf, limit);
1236 if (tc)
1237 wattr_off(view->window,
1238 COLOR_PAIR(tc->colorpair), NULL);
1239 col = limit;
1240 if (col > avail)
1241 goto done;
1243 if (avail >= 120) {
1244 char *id_str;
1245 err = got_object_id_str(&id_str, id);
1246 if (err)
1247 goto done;
1248 tc = get_color(colors, TOG_COLOR_COMMIT);
1249 if (tc)
1250 wattr_on(view->window,
1251 COLOR_PAIR(tc->colorpair), NULL);
1252 wprintw(view->window, "%.8s ", id_str);
1253 if (tc)
1254 wattr_off(view->window,
1255 COLOR_PAIR(tc->colorpair), NULL);
1256 free(id_str);
1257 col += 9;
1258 if (col > avail)
1259 goto done;
1262 author = strdup(got_object_commit_get_author(commit));
1263 if (author == NULL) {
1264 err = got_error_from_errno("strdup");
1265 goto done;
1267 err = format_author(&wauthor, &author_width, author, avail - col, col);
1268 if (err)
1269 goto done;
1270 tc = get_color(colors, TOG_COLOR_AUTHOR);
1271 if (tc)
1272 wattr_on(view->window,
1273 COLOR_PAIR(tc->colorpair), NULL);
1274 waddwstr(view->window, wauthor);
1275 if (tc)
1276 wattr_off(view->window,
1277 COLOR_PAIR(tc->colorpair), NULL);
1278 col += author_width;
1279 while (col < avail && author_width < author_display_cols + 2) {
1280 waddch(view->window, ' ');
1281 col++;
1282 author_width++;
1284 if (col > avail)
1285 goto done;
1287 err = got_object_commit_get_logmsg(&logmsg0, commit);
1288 if (err)
1289 goto done;
1290 logmsg = logmsg0;
1291 while (*logmsg == '\n')
1292 logmsg++;
1293 newline = strchr(logmsg, '\n');
1294 if (newline)
1295 *newline = '\0';
1296 limit = avail - col;
1297 err = format_line(&wlogmsg, &logmsg_width, logmsg, limit, col);
1298 if (err)
1299 goto done;
1300 waddwstr(view->window, wlogmsg);
1301 col += logmsg_width;
1302 while (col < avail) {
1303 waddch(view->window, ' ');
1304 col++;
1306 done:
1307 free(logmsg0);
1308 free(wlogmsg);
1309 free(author);
1310 free(wauthor);
1311 free(line);
1312 return err;
1315 static struct commit_queue_entry *
1316 alloc_commit_queue_entry(struct got_commit_object *commit,
1317 struct got_object_id *id)
1319 struct commit_queue_entry *entry;
1321 entry = calloc(1, sizeof(*entry));
1322 if (entry == NULL)
1323 return NULL;
1325 entry->id = id;
1326 entry->commit = commit;
1327 return entry;
1330 static void
1331 pop_commit(struct commit_queue *commits)
1333 struct commit_queue_entry *entry;
1335 entry = TAILQ_FIRST(&commits->head);
1336 TAILQ_REMOVE(&commits->head, entry, entry);
1337 got_object_commit_close(entry->commit);
1338 commits->ncommits--;
1339 /* Don't free entry->id! It is owned by the commit graph. */
1340 free(entry);
1343 static void
1344 free_commits(struct commit_queue *commits)
1346 while (!TAILQ_EMPTY(&commits->head))
1347 pop_commit(commits);
1350 static const struct got_error *
1351 match_commit(int *have_match, struct got_object_id *id,
1352 struct got_commit_object *commit, regex_t *regex)
1354 const struct got_error *err = NULL;
1355 regmatch_t regmatch;
1356 char *id_str = NULL, *logmsg = NULL;
1358 *have_match = 0;
1360 err = got_object_id_str(&id_str, id);
1361 if (err)
1362 return err;
1364 err = got_object_commit_get_logmsg(&logmsg, commit);
1365 if (err)
1366 goto done;
1368 if (regexec(regex, got_object_commit_get_author(commit), 1,
1369 &regmatch, 0) == 0 ||
1370 regexec(regex, got_object_commit_get_committer(commit), 1,
1371 &regmatch, 0) == 0 ||
1372 regexec(regex, id_str, 1, &regmatch, 0) == 0 ||
1373 regexec(regex, logmsg, 1, &regmatch, 0) == 0)
1374 *have_match = 1;
1375 done:
1376 free(id_str);
1377 free(logmsg);
1378 return err;
1381 static const struct got_error *
1382 queue_commits(struct got_commit_graph *graph, struct commit_queue *commits,
1383 int minqueue, struct got_repository *repo, const char *path,
1384 int *searching, int *search_next_done, regex_t *regex)
1386 const struct got_error *err = NULL;
1387 int nqueued = 0, have_match = 0;
1390 * We keep all commits open throughout the lifetime of the log
1391 * view in order to avoid having to re-fetch commits from disk
1392 * while updating the display.
1394 while (nqueued < minqueue ||
1395 (*searching == TOG_SEARCH_FORWARD && !*search_next_done)) {
1396 struct got_object_id *id;
1397 struct got_commit_object *commit;
1398 struct commit_queue_entry *entry;
1399 int errcode;
1401 err = got_commit_graph_iter_next(&id, graph, repo, NULL, NULL);
1402 if (err || id == NULL)
1403 break;
1405 err = got_object_open_as_commit(&commit, repo, id);
1406 if (err)
1407 break;
1408 entry = alloc_commit_queue_entry(commit, id);
1409 if (entry == NULL) {
1410 err = got_error_from_errno("alloc_commit_queue_entry");
1411 break;
1414 errcode = pthread_mutex_lock(&tog_mutex);
1415 if (errcode) {
1416 err = got_error_set_errno(errcode,
1417 "pthread_mutex_lock");
1418 break;
1421 entry->idx = commits->ncommits;
1422 TAILQ_INSERT_TAIL(&commits->head, entry, entry);
1423 nqueued++;
1424 commits->ncommits++;
1426 if (*searching == TOG_SEARCH_FORWARD && !*search_next_done) {
1427 err = match_commit(&have_match, id, commit, regex);
1430 errcode = pthread_mutex_unlock(&tog_mutex);
1431 if (errcode && err == NULL)
1432 err = got_error_set_errno(errcode,
1433 "pthread_mutex_unlock");
1435 if (err || have_match)
1436 break;
1439 return err;
1442 static const struct got_error *
1443 draw_commits(struct tog_view *view, struct commit_queue_entry **last,
1444 struct commit_queue_entry **selected, struct commit_queue_entry *first,
1445 struct commit_queue *commits, int selected_idx, int limit,
1446 struct got_reflist_head *refs, const char *path, int commits_needed,
1447 struct tog_colors *colors)
1449 const struct got_error *err = NULL;
1450 struct tog_log_view_state *s = &view->state.log;
1451 struct commit_queue_entry *entry;
1452 int width;
1453 int ncommits, author_cols = 4;
1454 char *id_str = NULL, *header = NULL, *ncommits_str = NULL;
1455 char *refs_str = NULL;
1456 wchar_t *wline;
1457 struct tog_color *tc;
1458 static const size_t date_display_cols = 12;
1460 entry = first;
1461 ncommits = 0;
1462 while (entry) {
1463 if (ncommits == selected_idx) {
1464 *selected = entry;
1465 break;
1467 entry = TAILQ_NEXT(entry, entry);
1468 ncommits++;
1471 if (*selected && !(view->searching && view->search_next_done == 0)) {
1472 err = got_object_id_str(&id_str, (*selected)->id);
1473 if (err)
1474 return err;
1475 if (refs) {
1476 err = build_refs_str(&refs_str, refs, (*selected)->id,
1477 s->repo);
1478 if (err)
1479 goto done;
1483 if (commits_needed == 0)
1484 halfdelay(10); /* disable fast refresh */
1486 if (asprintf(&ncommits_str, " [%d/%d] %s",
1487 entry ? entry->idx + 1 : 0, commits->ncommits,
1488 commits_needed > 0 ?
1489 (view->searching && view->search_next_done == 0
1490 ? "searching..." : "loading... ") :
1491 (refs_str ? refs_str : "")) == -1) {
1492 err = got_error_from_errno("asprintf");
1493 goto done;
1496 if (path && strcmp(path, "/") != 0) {
1497 if (asprintf(&header, "commit %s %s%s",
1498 id_str ? id_str : "........................................",
1499 path, ncommits_str) == -1) {
1500 err = got_error_from_errno("asprintf");
1501 header = NULL;
1502 goto done;
1504 } else if (asprintf(&header, "commit %s%s",
1505 id_str ? id_str : "........................................",
1506 ncommits_str) == -1) {
1507 err = got_error_from_errno("asprintf");
1508 header = NULL;
1509 goto done;
1511 err = format_line(&wline, &width, header, view->ncols, 0);
1512 if (err)
1513 goto done;
1515 werase(view->window);
1517 if (view_needs_focus_indication(view))
1518 wstandout(view->window);
1519 tc = get_color(colors, TOG_COLOR_COMMIT);
1520 if (tc)
1521 wattr_on(view->window,
1522 COLOR_PAIR(tc->colorpair), NULL);
1523 waddwstr(view->window, wline);
1524 if (tc)
1525 wattr_off(view->window,
1526 COLOR_PAIR(tc->colorpair), NULL);
1527 while (width < view->ncols) {
1528 waddch(view->window, ' ');
1529 width++;
1531 if (view_needs_focus_indication(view))
1532 wstandend(view->window);
1533 free(wline);
1534 if (limit <= 1)
1535 goto done;
1537 /* Grow author column size if necessary. */
1538 entry = first;
1539 ncommits = 0;
1540 while (entry) {
1541 char *author;
1542 wchar_t *wauthor;
1543 int width;
1544 if (ncommits >= limit - 1)
1545 break;
1546 author = strdup(got_object_commit_get_author(entry->commit));
1547 if (author == NULL) {
1548 err = got_error_from_errno("strdup");
1549 goto done;
1551 err = format_author(&wauthor, &width, author, COLS,
1552 date_display_cols);
1553 if (author_cols < width)
1554 author_cols = width;
1555 free(wauthor);
1556 free(author);
1557 ncommits++;
1558 entry = TAILQ_NEXT(entry, entry);
1561 entry = first;
1562 *last = first;
1563 ncommits = 0;
1564 while (entry) {
1565 if (ncommits >= limit - 1)
1566 break;
1567 if (ncommits == selected_idx)
1568 wstandout(view->window);
1569 err = draw_commit(view, entry->commit, entry->id, refs,
1570 date_display_cols, author_cols, colors);
1571 if (ncommits == selected_idx)
1572 wstandend(view->window);
1573 if (err)
1574 goto done;
1575 ncommits++;
1576 *last = entry;
1577 entry = TAILQ_NEXT(entry, entry);
1580 view_vborder(view);
1581 done:
1582 free(id_str);
1583 free(refs_str);
1584 free(ncommits_str);
1585 free(header);
1586 return err;
1589 static void
1590 scroll_up(struct tog_view *view,
1591 struct commit_queue_entry **first_displayed_entry, int maxscroll,
1592 struct commit_queue *commits)
1594 struct commit_queue_entry *entry;
1595 int nscrolled = 0;
1597 entry = TAILQ_FIRST(&commits->head);
1598 if (*first_displayed_entry == entry)
1599 return;
1601 entry = *first_displayed_entry;
1602 while (entry && nscrolled < maxscroll) {
1603 entry = TAILQ_PREV(entry, commit_queue_head, entry);
1604 if (entry) {
1605 *first_displayed_entry = entry;
1606 nscrolled++;
1611 static const struct got_error *
1612 trigger_log_thread(int load_all, int *commits_needed, int *log_complete,
1613 pthread_cond_t *need_commits)
1615 int errcode;
1616 int max_wait = 20;
1618 halfdelay(1); /* fast refresh while loading commits */
1620 while (*commits_needed > 0) {
1621 if (*log_complete)
1622 break;
1624 /* Wake the log thread. */
1625 errcode = pthread_cond_signal(need_commits);
1626 if (errcode)
1627 return got_error_set_errno(errcode,
1628 "pthread_cond_signal");
1629 if (*commits_needed > 0 && (!load_all || --max_wait <= 0)) {
1631 * Thread is not done yet; lose a key press
1632 * and let the user retry... this way the GUI
1633 * remains interactive while logging deep paths
1634 * with few commits in history.
1636 return NULL;
1640 return NULL;
1643 static const struct got_error *
1644 scroll_down(struct tog_view *view,
1645 struct commit_queue_entry **first_displayed_entry, int maxscroll,
1646 struct commit_queue_entry **last_displayed_entry,
1647 struct commit_queue *commits, int *log_complete, int *commits_needed,
1648 pthread_cond_t *need_commits)
1650 const struct got_error *err = NULL;
1651 struct commit_queue_entry *pentry;
1652 int nscrolled = 0;
1654 if (*last_displayed_entry == NULL)
1655 return NULL;
1657 pentry = TAILQ_NEXT(*last_displayed_entry, entry);
1658 if (pentry == NULL && !*log_complete) {
1660 * Ask the log thread for required amount of commits
1661 * plus some amount of pre-fetching.
1663 (*commits_needed) += maxscroll + 20;
1664 err = trigger_log_thread(0, commits_needed, log_complete,
1665 need_commits);
1666 if (err)
1667 return err;
1670 do {
1671 pentry = TAILQ_NEXT(*last_displayed_entry, entry);
1672 if (pentry == NULL)
1673 break;
1675 *last_displayed_entry = pentry;
1677 pentry = TAILQ_NEXT(*first_displayed_entry, entry);
1678 if (pentry == NULL)
1679 break;
1680 *first_displayed_entry = pentry;
1681 } while (++nscrolled < maxscroll);
1683 return err;
1686 static const struct got_error *
1687 open_diff_view_for_commit(struct tog_view **new_view, int begin_x,
1688 struct got_commit_object *commit, struct got_object_id *commit_id,
1689 struct tog_view *log_view, struct got_reflist_head *refs,
1690 struct got_repository *repo)
1692 const struct got_error *err;
1693 struct got_object_qid *parent_id;
1694 struct tog_view *diff_view;
1696 diff_view = view_open(0, 0, 0, begin_x, TOG_VIEW_DIFF);
1697 if (diff_view == NULL)
1698 return got_error_from_errno("view_open");
1700 parent_id = SIMPLEQ_FIRST(got_object_commit_get_parent_ids(commit));
1701 err = open_diff_view(diff_view, parent_id ? parent_id->id : NULL,
1702 commit_id, log_view, refs, repo);
1703 if (err == NULL)
1704 *new_view = diff_view;
1705 return err;
1708 static const struct got_error *
1709 tree_view_visit_subtree(struct got_tree_object *subtree,
1710 struct tog_tree_view_state *s)
1712 struct tog_parent_tree *parent;
1714 parent = calloc(1, sizeof(*parent));
1715 if (parent == NULL)
1716 return got_error_from_errno("calloc");
1718 parent->tree = s->tree;
1719 parent->first_displayed_entry = s->first_displayed_entry;
1720 parent->selected_entry = s->selected_entry;
1721 parent->selected = s->selected;
1722 TAILQ_INSERT_HEAD(&s->parents, parent, entry);
1723 s->tree = subtree;
1724 s->selected = 0;
1725 s->first_displayed_entry = NULL;
1726 return NULL;
1730 static const struct got_error *
1731 browse_commit_tree(struct tog_view **new_view, int begin_x,
1732 struct commit_queue_entry *entry, const char *path,
1733 struct got_reflist_head *refs, struct got_repository *repo)
1735 const struct got_error *err = NULL;
1736 struct got_tree_object *tree;
1737 struct tog_tree_view_state *s;
1738 struct tog_view *tree_view;
1739 char *slash, *subpath = NULL;
1740 const char *p;
1742 err = got_object_open_as_tree(&tree, repo,
1743 got_object_commit_get_tree_id(entry->commit));
1744 if (err)
1745 return err;
1747 tree_view = view_open(0, 0, 0, begin_x, TOG_VIEW_TREE);
1748 if (tree_view == NULL)
1749 return got_error_from_errno("view_open");
1751 err = open_tree_view(tree_view, tree, entry->id, refs, repo);
1752 if (err) {
1753 got_object_tree_close(tree);
1754 return err;
1756 s = &tree_view->state.tree;
1758 *new_view = tree_view;
1760 if (got_path_is_root_dir(path))
1761 return NULL;
1763 /* Walk the path and open corresponding tree objects. */
1764 p = path;
1765 while (*p) {
1766 struct got_tree_entry *te;
1767 struct got_object_id *tree_id;
1768 char *te_name;
1770 while (p[0] == '/')
1771 p++;
1773 /* Ensure the correct subtree entry is selected. */
1774 slash = strchr(p, '/');
1775 if (slash == NULL)
1776 te_name = strdup(p);
1777 else
1778 te_name = strndup(p, slash - p);
1779 if (te_name == NULL) {
1780 err = got_error_from_errno("strndup");
1781 break;
1783 te = got_object_tree_find_entry(s->tree, te_name);
1784 if (te == NULL) {
1785 err = got_error_path(te_name, GOT_ERR_NO_TREE_ENTRY);
1786 free(te_name);
1787 break;
1789 free(te_name);
1790 s->selected_entry = te;
1791 s->selected = got_tree_entry_get_index(te);
1792 if (s->tree != s->root)
1793 s->selected++; /* skip '..' */
1795 if (!S_ISDIR(got_tree_entry_get_mode(s->selected_entry))) {
1796 /* Jump to this file's entry. */
1797 s->first_displayed_entry = s->selected_entry;
1798 s->selected = 0;
1799 break;
1802 slash = strchr(p, '/');
1803 if (slash)
1804 subpath = strndup(path, slash - path);
1805 else
1806 subpath = strdup(path);
1807 if (subpath == NULL) {
1808 err = got_error_from_errno("strdup");
1809 break;
1812 err = got_object_id_by_path(&tree_id, repo, entry->id,
1813 subpath);
1814 if (err)
1815 break;
1817 err = got_object_open_as_tree(&tree, repo, tree_id);
1818 free(tree_id);
1819 if (err)
1820 break;
1822 err = tree_view_visit_subtree(tree, s);
1823 if (err) {
1824 got_object_tree_close(tree);
1825 break;
1827 if (slash == NULL)
1828 break;
1829 free(subpath);
1830 subpath = NULL;
1831 p = slash;
1834 free(subpath);
1835 return err;
1838 static const struct got_error *
1839 block_signals_used_by_main_thread(void)
1841 sigset_t sigset;
1842 int errcode;
1844 if (sigemptyset(&sigset) == -1)
1845 return got_error_from_errno("sigemptyset");
1847 /* tog handles SIGWINCH and SIGCONT */
1848 if (sigaddset(&sigset, SIGWINCH) == -1)
1849 return got_error_from_errno("sigaddset");
1850 if (sigaddset(&sigset, SIGCONT) == -1)
1851 return got_error_from_errno("sigaddset");
1853 /* ncurses handles SIGTSTP */
1854 if (sigaddset(&sigset, SIGTSTP) == -1)
1855 return got_error_from_errno("sigaddset");
1857 errcode = pthread_sigmask(SIG_BLOCK, &sigset, NULL);
1858 if (errcode)
1859 return got_error_set_errno(errcode, "pthread_sigmask");
1861 return NULL;
1864 static void *
1865 log_thread(void *arg)
1867 const struct got_error *err = NULL;
1868 int errcode = 0;
1869 struct tog_log_thread_args *a = arg;
1870 int done = 0;
1872 err = block_signals_used_by_main_thread();
1873 if (err)
1874 return (void *)err;
1876 while (!done && !err && !tog_sigpipe_received) {
1877 err = queue_commits(a->graph, a->commits, 1, a->repo,
1878 a->in_repo_path, a->searching, a->search_next_done,
1879 a->regex);
1880 if (err) {
1881 if (err->code != GOT_ERR_ITER_COMPLETED)
1882 return (void *)err;
1883 err = NULL;
1884 done = 1;
1885 } else if (a->commits_needed > 0)
1886 a->commits_needed--;
1888 errcode = pthread_mutex_lock(&tog_mutex);
1889 if (errcode) {
1890 err = got_error_set_errno(errcode,
1891 "pthread_mutex_lock");
1892 break;
1893 } else if (*a->quit)
1894 done = 1;
1895 else if (*a->first_displayed_entry == NULL) {
1896 *a->first_displayed_entry =
1897 TAILQ_FIRST(&a->commits->head);
1898 *a->selected_entry = *a->first_displayed_entry;
1901 if (done)
1902 a->commits_needed = 0;
1903 else if (a->commits_needed == 0) {
1904 errcode = pthread_cond_wait(&a->need_commits,
1905 &tog_mutex);
1906 if (errcode)
1907 err = got_error_set_errno(errcode,
1908 "pthread_cond_wait");
1911 errcode = pthread_mutex_unlock(&tog_mutex);
1912 if (errcode && err == NULL)
1913 err = got_error_set_errno(errcode,
1914 "pthread_mutex_unlock");
1916 a->log_complete = 1;
1917 return (void *)err;
1920 static const struct got_error *
1921 stop_log_thread(struct tog_log_view_state *s)
1923 const struct got_error *err = NULL;
1924 int errcode;
1926 if (s->thread) {
1927 s->quit = 1;
1928 errcode = pthread_cond_signal(&s->thread_args.need_commits);
1929 if (errcode)
1930 return got_error_set_errno(errcode,
1931 "pthread_cond_signal");
1932 errcode = pthread_mutex_unlock(&tog_mutex);
1933 if (errcode)
1934 return got_error_set_errno(errcode,
1935 "pthread_mutex_unlock");
1936 errcode = pthread_join(s->thread, (void **)&err);
1937 if (errcode)
1938 return got_error_set_errno(errcode, "pthread_join");
1939 errcode = pthread_mutex_lock(&tog_mutex);
1940 if (errcode)
1941 return got_error_set_errno(errcode,
1942 "pthread_mutex_lock");
1943 s->thread = NULL;
1946 errcode = pthread_cond_destroy(&s->thread_args.need_commits);
1947 if (errcode && err == NULL)
1948 err = got_error_set_errno(errcode, "pthread_cond_destroy");
1950 if (s->thread_args.repo) {
1951 got_repo_close(s->thread_args.repo);
1952 s->thread_args.repo = NULL;
1955 if (s->thread_args.graph) {
1956 got_commit_graph_close(s->thread_args.graph);
1957 s->thread_args.graph = NULL;
1960 return err;
1963 static const struct got_error *
1964 close_log_view(struct tog_view *view)
1966 const struct got_error *err = NULL;
1967 struct tog_log_view_state *s = &view->state.log;
1969 err = stop_log_thread(s);
1970 free_commits(&s->commits);
1971 free(s->in_repo_path);
1972 s->in_repo_path = NULL;
1973 free(s->start_id);
1974 s->start_id = NULL;
1975 return err;
1978 static const struct got_error *
1979 search_start_log_view(struct tog_view *view)
1981 struct tog_log_view_state *s = &view->state.log;
1983 s->matched_entry = NULL;
1984 s->search_entry = NULL;
1985 return NULL;
1988 static const struct got_error *
1989 search_next_log_view(struct tog_view *view)
1991 const struct got_error *err = NULL;
1992 struct tog_log_view_state *s = &view->state.log;
1993 struct commit_queue_entry *entry;
1995 if (!view->searching) {
1996 view->search_next_done = 1;
1997 return NULL;
2000 if (s->search_entry) {
2001 int errcode, ch;
2002 errcode = pthread_mutex_unlock(&tog_mutex);
2003 if (errcode)
2004 return got_error_set_errno(errcode,
2005 "pthread_mutex_unlock");
2006 ch = wgetch(view->window);
2007 errcode = pthread_mutex_lock(&tog_mutex);
2008 if (errcode)
2009 return got_error_set_errno(errcode,
2010 "pthread_mutex_lock");
2011 if (ch == KEY_BACKSPACE) {
2012 view->search_next_done = 1;
2013 return NULL;
2015 if (view->searching == TOG_SEARCH_FORWARD)
2016 entry = TAILQ_NEXT(s->search_entry, entry);
2017 else
2018 entry = TAILQ_PREV(s->search_entry,
2019 commit_queue_head, entry);
2020 } else if (s->matched_entry) {
2021 if (view->searching == TOG_SEARCH_FORWARD)
2022 entry = TAILQ_NEXT(s->selected_entry, entry);
2023 else
2024 entry = TAILQ_PREV(s->selected_entry,
2025 commit_queue_head, entry);
2026 } else {
2027 if (view->searching == TOG_SEARCH_FORWARD)
2028 entry = TAILQ_FIRST(&s->commits.head);
2029 else
2030 entry = TAILQ_LAST(&s->commits.head, commit_queue_head);
2033 while (1) {
2034 int have_match = 0;
2036 if (entry == NULL) {
2037 if (s->thread_args.log_complete ||
2038 view->searching == TOG_SEARCH_BACKWARD) {
2039 view->search_next_done = 1;
2040 return NULL;
2043 * Poke the log thread for more commits and return,
2044 * allowing the main loop to make progress. Search
2045 * will resume at s->search_entry once we come back.
2047 s->thread_args.commits_needed++;
2048 return trigger_log_thread(1,
2049 &s->thread_args.commits_needed,
2050 &s->thread_args.log_complete,
2051 &s->thread_args.need_commits);
2054 err = match_commit(&have_match, entry->id, entry->commit,
2055 &view->regex);
2056 if (err)
2057 break;
2058 if (have_match) {
2059 view->search_next_done = 1;
2060 s->matched_entry = entry;
2061 break;
2064 s->search_entry = entry;
2065 if (view->searching == TOG_SEARCH_FORWARD)
2066 entry = TAILQ_NEXT(entry, entry);
2067 else
2068 entry = TAILQ_PREV(entry, commit_queue_head, entry);
2071 if (s->matched_entry) {
2072 int cur = s->selected_entry->idx;
2073 while (cur < s->matched_entry->idx) {
2074 err = input_log_view(NULL, NULL, NULL, view, KEY_DOWN);
2075 if (err)
2076 return err;
2077 cur++;
2079 while (cur > s->matched_entry->idx) {
2080 err = input_log_view(NULL, NULL, NULL, view, KEY_UP);
2081 if (err)
2082 return err;
2083 cur--;
2087 s->search_entry = NULL;
2089 return NULL;
2092 static const struct got_error *
2093 open_log_view(struct tog_view *view, struct got_object_id *start_id,
2094 struct got_reflist_head *refs, struct got_repository *repo,
2095 const char *head_ref_name, const char *path, int check_disk,
2096 int log_branches)
2098 const struct got_error *err = NULL;
2099 struct tog_log_view_state *s = &view->state.log;
2100 struct got_repository *thread_repo = NULL;
2101 struct got_commit_graph *thread_graph = NULL;
2102 int errcode;
2104 err = got_repo_map_path(&s->in_repo_path, repo, path, check_disk);
2105 if (err != NULL)
2106 goto done;
2108 /* The commit queue only contains commits being displayed. */
2109 TAILQ_INIT(&s->commits.head);
2110 s->commits.ncommits = 0;
2112 s->refs = refs;
2113 s->repo = repo;
2114 s->head_ref_name = head_ref_name;
2115 s->start_id = got_object_id_dup(start_id);
2116 if (s->start_id == NULL) {
2117 err = got_error_from_errno("got_object_id_dup");
2118 goto done;
2120 s->log_branches = log_branches;
2122 SIMPLEQ_INIT(&s->colors);
2123 if (has_colors() && getenv("TOG_COLORS") != NULL) {
2124 err = add_color(&s->colors, "^$", TOG_COLOR_COMMIT,
2125 get_color_value("TOG_COLOR_COMMIT"));
2126 if (err)
2127 goto done;
2128 err = add_color(&s->colors, "^$", TOG_COLOR_AUTHOR,
2129 get_color_value("TOG_COLOR_AUTHOR"));
2130 if (err) {
2131 free_colors(&s->colors);
2132 goto done;
2134 err = add_color(&s->colors, "^$", TOG_COLOR_DATE,
2135 get_color_value("TOG_COLOR_DATE"));
2136 if (err) {
2137 free_colors(&s->colors);
2138 goto done;
2142 view->show = show_log_view;
2143 view->input = input_log_view;
2144 view->close = close_log_view;
2145 view->search_start = search_start_log_view;
2146 view->search_next = search_next_log_view;
2148 err = got_repo_open(&thread_repo, got_repo_get_path(repo), NULL);
2149 if (err)
2150 goto done;
2151 err = got_commit_graph_open(&thread_graph, s->in_repo_path,
2152 !s->log_branches);
2153 if (err)
2154 goto done;
2155 err = got_commit_graph_iter_start(thread_graph,
2156 s->start_id, s->repo, NULL, NULL);
2157 if (err)
2158 goto done;
2160 errcode = pthread_cond_init(&s->thread_args.need_commits, NULL);
2161 if (errcode) {
2162 err = got_error_set_errno(errcode, "pthread_cond_init");
2163 goto done;
2166 s->thread_args.commits_needed = view->nlines;
2167 s->thread_args.graph = thread_graph;
2168 s->thread_args.commits = &s->commits;
2169 s->thread_args.in_repo_path = s->in_repo_path;
2170 s->thread_args.start_id = s->start_id;
2171 s->thread_args.repo = thread_repo;
2172 s->thread_args.log_complete = 0;
2173 s->thread_args.quit = &s->quit;
2174 s->thread_args.first_displayed_entry = &s->first_displayed_entry;
2175 s->thread_args.selected_entry = &s->selected_entry;
2176 s->thread_args.searching = &view->searching;
2177 s->thread_args.search_next_done = &view->search_next_done;
2178 s->thread_args.regex = &view->regex;
2179 done:
2180 if (err)
2181 close_log_view(view);
2182 return err;
2185 static const struct got_error *
2186 show_log_view(struct tog_view *view)
2188 struct tog_log_view_state *s = &view->state.log;
2190 if (s->thread == NULL) {
2191 int errcode = pthread_create(&s->thread, NULL, log_thread,
2192 &s->thread_args);
2193 if (errcode)
2194 return got_error_set_errno(errcode, "pthread_create");
2197 return draw_commits(view, &s->last_displayed_entry,
2198 &s->selected_entry, s->first_displayed_entry,
2199 &s->commits, s->selected, view->nlines, s->refs,
2200 s->in_repo_path, s->thread_args.commits_needed, &s->colors);
2203 static const struct got_error *
2204 input_log_view(struct tog_view **new_view, struct tog_view **dead_view,
2205 struct tog_view **focus_view, struct tog_view *view, int ch)
2207 const struct got_error *err = NULL;
2208 struct tog_log_view_state *s = &view->state.log;
2209 char *parent_path, *in_repo_path = NULL;
2210 struct tog_view *diff_view = NULL, *tree_view = NULL, *lv = NULL;
2211 int begin_x = 0;
2212 struct got_object_id *start_id;
2214 switch (ch) {
2215 case 'q':
2216 s->quit = 1;
2217 break;
2218 case 'k':
2219 case KEY_UP:
2220 case '<':
2221 case ',':
2222 if (s->first_displayed_entry == NULL)
2223 break;
2224 if (s->selected > 0)
2225 s->selected--;
2226 else
2227 scroll_up(view, &s->first_displayed_entry, 1,
2228 &s->commits);
2229 break;
2230 case KEY_PPAGE:
2231 case CTRL('b'):
2232 if (s->first_displayed_entry == NULL)
2233 break;
2234 if (TAILQ_FIRST(&s->commits.head) ==
2235 s->first_displayed_entry) {
2236 s->selected = 0;
2237 break;
2239 scroll_up(view, &s->first_displayed_entry,
2240 view->nlines, &s->commits);
2241 break;
2242 case 'j':
2243 case KEY_DOWN:
2244 case '>':
2245 case '.':
2246 if (s->first_displayed_entry == NULL)
2247 break;
2248 if (s->selected < MIN(view->nlines - 2,
2249 s->commits.ncommits - 1)) {
2250 s->selected++;
2251 break;
2253 err = scroll_down(view, &s->first_displayed_entry, 1,
2254 &s->last_displayed_entry, &s->commits,
2255 &s->thread_args.log_complete,
2256 &s->thread_args.commits_needed,
2257 &s->thread_args.need_commits);
2258 break;
2259 case KEY_NPAGE:
2260 case CTRL('f'): {
2261 struct commit_queue_entry *first;
2262 first = s->first_displayed_entry;
2263 if (first == NULL)
2264 break;
2265 err = scroll_down(view, &s->first_displayed_entry,
2266 view->nlines, &s->last_displayed_entry,
2267 &s->commits, &s->thread_args.log_complete,
2268 &s->thread_args.commits_needed,
2269 &s->thread_args.need_commits);
2270 if (err)
2271 break;
2272 if (first == s->first_displayed_entry &&
2273 s->selected < MIN(view->nlines - 2,
2274 s->commits.ncommits - 1)) {
2275 /* can't scroll further down */
2276 s->selected = MIN(view->nlines - 2,
2277 s->commits.ncommits - 1);
2279 err = NULL;
2280 break;
2282 case KEY_RESIZE:
2283 if (s->selected > view->nlines - 2)
2284 s->selected = view->nlines - 2;
2285 if (s->selected > s->commits.ncommits - 1)
2286 s->selected = s->commits.ncommits - 1;
2287 break;
2288 case KEY_ENTER:
2289 case ' ':
2290 case '\r':
2291 if (s->selected_entry == NULL)
2292 break;
2293 if (view_is_parent_view(view))
2294 begin_x = view_split_begin_x(view->begin_x);
2295 err = open_diff_view_for_commit(&diff_view, begin_x,
2296 s->selected_entry->commit, s->selected_entry->id,
2297 view, s->refs, s->repo);
2298 if (err)
2299 break;
2300 if (view_is_parent_view(view)) {
2301 err = view_close_child(view);
2302 if (err)
2303 return err;
2304 err = view_set_child(view, diff_view);
2305 if (err) {
2306 view_close(diff_view);
2307 break;
2309 *focus_view = diff_view;
2310 view->child_focussed = 1;
2311 } else
2312 *new_view = diff_view;
2313 break;
2314 case 't':
2315 if (s->selected_entry == NULL)
2316 break;
2317 if (view_is_parent_view(view))
2318 begin_x = view_split_begin_x(view->begin_x);
2319 err = browse_commit_tree(&tree_view, begin_x,
2320 s->selected_entry, s->in_repo_path, s->refs, s->repo);
2321 if (err)
2322 break;
2323 if (view_is_parent_view(view)) {
2324 err = view_close_child(view);
2325 if (err)
2326 return err;
2327 err = view_set_child(view, tree_view);
2328 if (err) {
2329 view_close(tree_view);
2330 break;
2332 *focus_view = tree_view;
2333 view->child_focussed = 1;
2334 } else
2335 *new_view = tree_view;
2336 break;
2337 case KEY_BACKSPACE:
2338 if (strcmp(s->in_repo_path, "/") == 0)
2339 break;
2340 parent_path = dirname(s->in_repo_path);
2341 if (parent_path && strcmp(parent_path, ".") != 0) {
2342 err = stop_log_thread(s);
2343 if (err)
2344 return err;
2345 lv = view_open(view->nlines, view->ncols,
2346 view->begin_y, view->begin_x, TOG_VIEW_LOG);
2347 if (lv == NULL)
2348 return got_error_from_errno(
2349 "view_open");
2350 err = open_log_view(lv, s->start_id, s->refs,
2351 s->repo, s->head_ref_name, parent_path, 0,
2352 s->log_branches);
2353 if (err)
2354 return err;;
2355 if (view_is_parent_view(view))
2356 *new_view = lv;
2357 else {
2358 view_set_child(view->parent, lv);
2359 *focus_view = lv;
2361 return NULL;
2363 break;
2364 case CTRL('l'):
2365 err = stop_log_thread(s);
2366 if (err)
2367 return err;
2368 lv = view_open(view->nlines, view->ncols,
2369 view->begin_y, view->begin_x, TOG_VIEW_LOG);
2370 if (lv == NULL)
2371 return got_error_from_errno("view_open");
2372 err = got_repo_match_object_id(&start_id, NULL,
2373 s->head_ref_name ? s->head_ref_name : GOT_REF_HEAD,
2374 GOT_OBJ_TYPE_COMMIT, 1, s->repo);
2375 if (err) {
2376 view_close(lv);
2377 return err;
2379 in_repo_path = strdup(s->in_repo_path);
2380 if (in_repo_path == NULL) {
2381 free(start_id);
2382 view_close(lv);
2383 return got_error_from_errno("strdup");
2385 got_ref_list_free(s->refs);
2386 err = got_ref_list(s->refs, s->repo, NULL,
2387 got_ref_cmp_by_name, NULL);
2388 if (err) {
2389 free(start_id);
2390 view_close(lv);
2391 return err;
2393 err = open_log_view(lv, start_id, s->refs, s->repo,
2394 s->head_ref_name, in_repo_path, 0, s->log_branches);
2395 if (err) {
2396 free(start_id);
2397 view_close(lv);
2398 return err;;
2400 *dead_view = view;
2401 *new_view = lv;
2402 break;
2403 case 'B':
2404 s->log_branches = !s->log_branches;
2405 err = stop_log_thread(s);
2406 if (err)
2407 return err;
2408 lv = view_open(view->nlines, view->ncols,
2409 view->begin_y, view->begin_x, TOG_VIEW_LOG);
2410 if (lv == NULL)
2411 return got_error_from_errno("view_open");
2412 err = open_log_view(lv, s->start_id, s->refs, s->repo,
2413 s->head_ref_name, s->in_repo_path, 0, s->log_branches);
2414 if (err) {
2415 view_close(lv);
2416 return err;;
2418 *dead_view = view;
2419 *new_view = lv;
2420 break;
2421 default:
2422 break;
2425 return err;
2428 static const struct got_error *
2429 apply_unveil(const char *repo_path, const char *worktree_path)
2431 const struct got_error *error;
2433 #ifdef PROFILE
2434 if (unveil("gmon.out", "rwc") != 0)
2435 return got_error_from_errno2("unveil", "gmon.out");
2436 #endif
2437 if (repo_path && unveil(repo_path, "r") != 0)
2438 return got_error_from_errno2("unveil", repo_path);
2440 if (worktree_path && unveil(worktree_path, "rwc") != 0)
2441 return got_error_from_errno2("unveil", worktree_path);
2443 if (unveil("/tmp", "rwc") != 0)
2444 return got_error_from_errno2("unveil", "/tmp");
2446 error = got_privsep_unveil_exec_helpers();
2447 if (error != NULL)
2448 return error;
2450 if (unveil(NULL, NULL) != 0)
2451 return got_error_from_errno("unveil");
2453 return NULL;
2456 static void
2457 init_curses(void)
2459 initscr();
2460 cbreak();
2461 halfdelay(1); /* Do fast refresh while initial view is loading. */
2462 noecho();
2463 nonl();
2464 intrflush(stdscr, FALSE);
2465 keypad(stdscr, TRUE);
2466 curs_set(0);
2467 if (getenv("TOG_COLORS") != NULL) {
2468 start_color();
2469 use_default_colors();
2471 signal(SIGWINCH, tog_sigwinch);
2472 signal(SIGPIPE, tog_sigpipe);
2473 signal(SIGCONT, tog_sigcont);
2476 static const struct got_error *
2477 cmd_log(int argc, char *argv[])
2479 const struct got_error *error;
2480 struct got_repository *repo = NULL;
2481 struct got_worktree *worktree = NULL;
2482 struct got_reflist_head refs;
2483 struct got_object_id *start_id = NULL;
2484 char *path = NULL, *repo_path = NULL, *cwd = NULL;
2485 char *start_commit = NULL, *head_ref_name = NULL;
2486 int ch, log_branches = 0;
2487 struct tog_view *view;
2489 SIMPLEQ_INIT(&refs);
2491 #ifndef PROFILE
2492 if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd unveil",
2493 NULL) == -1)
2494 err(1, "pledge");
2495 #endif
2497 while ((ch = getopt(argc, argv, "bc:r:")) != -1) {
2498 switch (ch) {
2499 case 'b':
2500 log_branches = 1;
2501 break;
2502 case 'c':
2503 start_commit = optarg;
2504 break;
2505 case 'r':
2506 repo_path = realpath(optarg, NULL);
2507 if (repo_path == NULL)
2508 return got_error_from_errno2("realpath",
2509 optarg);
2510 break;
2511 default:
2512 usage_log();
2513 /* NOTREACHED */
2517 argc -= optind;
2518 argv += optind;
2520 cwd = getcwd(NULL, 0);
2521 if (cwd == NULL) {
2522 error = got_error_from_errno("getcwd");
2523 goto done;
2525 error = got_worktree_open(&worktree, cwd);
2526 if (error && error->code != GOT_ERR_NOT_WORKTREE)
2527 goto done;
2528 error = NULL;
2530 if (argc == 0) {
2531 path = strdup("");
2532 if (path == NULL) {
2533 error = got_error_from_errno("strdup");
2534 goto done;
2536 } else if (argc == 1) {
2537 if (worktree) {
2538 error = got_worktree_resolve_path(&path, worktree,
2539 argv[0]);
2540 if (error)
2541 goto done;
2542 } else {
2543 path = strdup(argv[0]);
2544 if (path == NULL) {
2545 error = got_error_from_errno("strdup");
2546 goto done;
2549 } else
2550 usage_log();
2552 if (repo_path == NULL) {
2553 if (worktree)
2554 repo_path = strdup(
2555 got_worktree_get_repo_path(worktree));
2556 else
2557 repo_path = strdup(cwd);
2559 if (repo_path == NULL) {
2560 error = got_error_from_errno("strdup");
2561 goto done;
2564 init_curses();
2566 error = got_repo_open(&repo, repo_path, NULL);
2567 if (error != NULL)
2568 goto done;
2570 error = apply_unveil(got_repo_get_path(repo),
2571 worktree ? got_worktree_get_root_path(worktree) : NULL);
2572 if (error)
2573 goto done;
2575 if (start_commit == NULL)
2576 error = got_repo_match_object_id(&start_id, NULL, worktree ?
2577 got_worktree_get_head_ref_name(worktree) : GOT_REF_HEAD,
2578 GOT_OBJ_TYPE_COMMIT, 1, repo);
2579 else
2580 error = got_repo_match_object_id(&start_id, NULL, start_commit,
2581 GOT_OBJ_TYPE_COMMIT, 1, repo);
2582 if (error != NULL)
2583 goto done;
2585 error = got_ref_list(&refs, repo, NULL, got_ref_cmp_by_name, NULL);
2586 if (error)
2587 goto done;
2589 view = view_open(0, 0, 0, 0, TOG_VIEW_LOG);
2590 if (view == NULL) {
2591 error = got_error_from_errno("view_open");
2592 goto done;
2594 if (worktree) {
2595 head_ref_name = strdup(
2596 got_worktree_get_head_ref_name(worktree));
2597 if (head_ref_name == NULL) {
2598 error = got_error_from_errno("strdup");
2599 goto done;
2602 error = open_log_view(view, start_id, &refs, repo, head_ref_name,
2603 path, 1, log_branches);
2604 if (error)
2605 goto done;
2606 if (worktree) {
2607 /* Release work tree lock. */
2608 got_worktree_close(worktree);
2609 worktree = NULL;
2611 error = view_loop(view);
2612 done:
2613 free(repo_path);
2614 free(cwd);
2615 free(path);
2616 free(start_id);
2617 free(head_ref_name);
2618 if (repo)
2619 got_repo_close(repo);
2620 if (worktree)
2621 got_worktree_close(worktree);
2622 got_ref_list_free(&refs);
2623 return error;
2626 __dead static void
2627 usage_diff(void)
2629 endwin();
2630 fprintf(stderr, "usage: %s diff [repository-path] object1 object2\n",
2631 getprogname());
2632 exit(1);
2635 static char *
2636 parse_next_line(FILE *f, size_t *len)
2638 char *line;
2639 size_t linelen;
2640 size_t lineno;
2641 const char delim[3] = { '\0', '\0', '\0'};
2643 line = fparseln(f, &linelen, &lineno, delim, 0);
2644 if (len)
2645 *len = linelen;
2646 return line;
2649 static int
2650 match_line(const char *line, regex_t *regex)
2652 regmatch_t regmatch;
2654 return regexec(regex, line, 1, &regmatch, 0) == 0;
2657 struct tog_color *
2658 match_color(struct tog_colors *colors, const char *line)
2660 struct tog_color *tc = NULL;
2662 SIMPLEQ_FOREACH(tc, colors, entry) {
2663 if (match_line(line, &tc->regex))
2664 return tc;
2667 return NULL;
2670 static const struct got_error *
2671 draw_file(struct tog_view *view, FILE *f, int *first_displayed_line,
2672 int *last_displayed_line, int *eof, int max_lines, char *header,
2673 struct tog_colors *colors)
2675 const struct got_error *err;
2676 int nlines = 0, nprinted = 0;
2677 char *line;
2678 struct tog_color *tc;
2679 size_t len;
2680 wchar_t *wline;
2681 int width;
2683 rewind(f);
2684 werase(view->window);
2686 if (header) {
2687 err = format_line(&wline, &width, header, view->ncols, 0);
2688 if (err) {
2689 return err;
2692 if (view_needs_focus_indication(view))
2693 wstandout(view->window);
2694 waddwstr(view->window, wline);
2695 if (view_needs_focus_indication(view))
2696 wstandend(view->window);
2697 if (width <= view->ncols - 1)
2698 waddch(view->window, '\n');
2700 if (max_lines <= 1)
2701 return NULL;
2702 max_lines--;
2705 *eof = 0;
2706 while (nprinted < max_lines) {
2707 line = parse_next_line(f, &len);
2708 if (line == NULL) {
2709 *eof = 1;
2710 break;
2712 if (++nlines < *first_displayed_line) {
2713 free(line);
2714 continue;
2717 err = format_line(&wline, &width, line, view->ncols, 0);
2718 if (err) {
2719 free(line);
2720 return err;
2723 tc = match_color(colors, line);
2724 if (tc)
2725 wattr_on(view->window,
2726 COLOR_PAIR(tc->colorpair), NULL);
2727 waddwstr(view->window, wline);
2728 if (tc)
2729 wattr_off(view->window,
2730 COLOR_PAIR(tc->colorpair), NULL);
2731 if (width <= view->ncols - 1)
2732 waddch(view->window, '\n');
2733 if (++nprinted == 1)
2734 *first_displayed_line = nlines;
2735 free(line);
2736 free(wline);
2737 wline = NULL;
2739 *last_displayed_line = nlines;
2741 view_vborder(view);
2743 if (*eof) {
2744 while (nprinted < view->nlines) {
2745 waddch(view->window, '\n');
2746 nprinted++;
2749 err = format_line(&wline, &width, TOG_EOF_STRING, view->ncols, 0);
2750 if (err) {
2751 return err;
2754 wstandout(view->window);
2755 waddwstr(view->window, wline);
2756 wstandend(view->window);
2759 return NULL;
2762 static char *
2763 get_datestr(time_t *time, char *datebuf)
2765 struct tm mytm, *tm;
2766 char *p, *s;
2768 tm = gmtime_r(time, &mytm);
2769 if (tm == NULL)
2770 return NULL;
2771 s = asctime_r(tm, datebuf);
2772 if (s == NULL)
2773 return NULL;
2774 p = strchr(s, '\n');
2775 if (p)
2776 *p = '\0';
2777 return s;
2780 static const struct got_error *
2781 write_commit_info(struct got_object_id *commit_id,
2782 struct got_reflist_head *refs, struct got_repository *repo, FILE *outfile)
2784 const struct got_error *err = NULL;
2785 char datebuf[26], *datestr;
2786 struct got_commit_object *commit;
2787 char *id_str = NULL, *logmsg = NULL;
2788 time_t committer_time;
2789 const char *author, *committer;
2790 char *refs_str = NULL;
2792 if (refs) {
2793 err = build_refs_str(&refs_str, refs, commit_id, repo);
2794 if (err)
2795 return err;
2798 err = got_object_open_as_commit(&commit, repo, commit_id);
2799 if (err)
2800 return err;
2802 err = got_object_id_str(&id_str, commit_id);
2803 if (err) {
2804 err = got_error_from_errno("got_object_id_str");
2805 goto done;
2808 if (fprintf(outfile, "commit %s%s%s%s\n", id_str, refs_str ? " (" : "",
2809 refs_str ? refs_str : "", refs_str ? ")" : "") < 0) {
2810 err = got_error_from_errno("fprintf");
2811 goto done;
2813 if (fprintf(outfile, "from: %s\n",
2814 got_object_commit_get_author(commit)) < 0) {
2815 err = got_error_from_errno("fprintf");
2816 goto done;
2818 committer_time = got_object_commit_get_committer_time(commit);
2819 datestr = get_datestr(&committer_time, datebuf);
2820 if (datestr && fprintf(outfile, "date: %s UTC\n", datestr) < 0) {
2821 err = got_error_from_errno("fprintf");
2822 goto done;
2824 author = got_object_commit_get_author(commit);
2825 committer = got_object_commit_get_committer(commit);
2826 if (strcmp(author, committer) != 0 &&
2827 fprintf(outfile, "via: %s\n", committer) < 0) {
2828 err = got_error_from_errno("fprintf");
2829 goto done;
2831 err = got_object_commit_get_logmsg(&logmsg, commit);
2832 if (err)
2833 goto done;
2834 if (fprintf(outfile, "%s\n", logmsg) < 0) {
2835 err = got_error_from_errno("fprintf");
2836 goto done;
2838 done:
2839 free(id_str);
2840 free(logmsg);
2841 free(refs_str);
2842 got_object_commit_close(commit);
2843 return err;
2846 static const struct got_error *
2847 create_diff(struct tog_diff_view_state *s)
2849 const struct got_error *err = NULL;
2850 FILE *f = NULL;
2851 int obj_type;
2853 f = got_opentemp();
2854 if (f == NULL) {
2855 err = got_error_from_errno("got_opentemp");
2856 goto done;
2858 if (s->f && fclose(s->f) != 0) {
2859 err = got_error_from_errno("fclose");
2860 goto done;
2862 s->f = f;
2864 if (s->id1)
2865 err = got_object_get_type(&obj_type, s->repo, s->id1);
2866 else
2867 err = got_object_get_type(&obj_type, s->repo, s->id2);
2868 if (err)
2869 goto done;
2871 switch (obj_type) {
2872 case GOT_OBJ_TYPE_BLOB:
2873 err = got_diff_objects_as_blobs(s->id1, s->id2, NULL, NULL,
2874 s->diff_context, 0, s->repo, f);
2875 break;
2876 case GOT_OBJ_TYPE_TREE:
2877 err = got_diff_objects_as_trees(s->id1, s->id2, "", "",
2878 s->diff_context, 0, s->repo, f);
2879 break;
2880 case GOT_OBJ_TYPE_COMMIT: {
2881 const struct got_object_id_queue *parent_ids;
2882 struct got_object_qid *pid;
2883 struct got_commit_object *commit2;
2885 err = got_object_open_as_commit(&commit2, s->repo, s->id2);
2886 if (err)
2887 break;
2888 /* Show commit info if we're diffing to a parent/root commit. */
2889 if (s->id1 == NULL)
2890 write_commit_info(s->id2, s->refs, s->repo, f);
2891 else {
2892 parent_ids = got_object_commit_get_parent_ids(commit2);
2893 SIMPLEQ_FOREACH(pid, parent_ids, entry) {
2894 if (got_object_id_cmp(s->id1, pid->id) == 0) {
2895 write_commit_info(s->id2, s->refs,
2896 s->repo, f);
2897 break;
2901 got_object_commit_close(commit2);
2903 err = got_diff_objects_as_commits(s->id1, s->id2,
2904 s->diff_context, 0, s->repo, f);
2905 break;
2907 default:
2908 err = got_error(GOT_ERR_OBJ_TYPE);
2909 break;
2911 done:
2912 if (f && fflush(f) != 0 && err == NULL)
2913 err = got_error_from_errno("fflush");
2914 return err;
2917 static void
2918 diff_view_indicate_progress(struct tog_view *view)
2920 mvwaddstr(view->window, 0, 0, "diffing...");
2921 update_panels();
2922 doupdate();
2925 static const struct got_error *
2926 open_diff_view(struct tog_view *view, struct got_object_id *id1,
2927 struct got_object_id *id2, struct tog_view *log_view,
2928 struct got_reflist_head *refs, struct got_repository *repo)
2930 const struct got_error *err;
2932 if (id1 != NULL && id2 != NULL) {
2933 int type1, type2;
2934 err = got_object_get_type(&type1, repo, id1);
2935 if (err)
2936 return err;
2937 err = got_object_get_type(&type2, repo, id2);
2938 if (err)
2939 return err;
2941 if (type1 != type2)
2942 return got_error(GOT_ERR_OBJ_TYPE);
2945 if (id1) {
2946 view->state.diff.id1 = got_object_id_dup(id1);
2947 if (view->state.diff.id1 == NULL)
2948 return got_error_from_errno("got_object_id_dup");
2949 } else
2950 view->state.diff.id1 = NULL;
2952 view->state.diff.id2 = got_object_id_dup(id2);
2953 if (view->state.diff.id2 == NULL) {
2954 free(view->state.diff.id1);
2955 view->state.diff.id1 = NULL;
2956 return got_error_from_errno("got_object_id_dup");
2958 view->state.diff.f = NULL;
2959 view->state.diff.first_displayed_line = 1;
2960 view->state.diff.last_displayed_line = view->nlines;
2961 view->state.diff.diff_context = 3;
2962 view->state.diff.log_view = log_view;
2963 view->state.diff.repo = repo;
2964 view->state.diff.refs = refs;
2965 SIMPLEQ_INIT(&view->state.diff.colors);
2967 if (has_colors() && getenv("TOG_COLORS") != NULL) {
2968 err = add_color(&view->state.diff.colors,
2969 "^-", TOG_COLOR_DIFF_MINUS,
2970 get_color_value("TOG_COLOR_DIFF_MINUS"));
2971 if (err)
2972 return err;
2973 err = add_color(&view->state.diff.colors, "^\\+",
2974 TOG_COLOR_DIFF_PLUS,
2975 get_color_value("TOG_COLOR_DIFF_PLUS"));
2976 if (err) {
2977 free_colors(&view->state.diff.colors);
2978 return err;
2980 err = add_color(&view->state.diff.colors,
2981 "^@@", TOG_COLOR_DIFF_CHUNK_HEADER,
2982 get_color_value("TOG_COLOR_DIFF_CHUNK_HEADER"));
2983 if (err) {
2984 free_colors(&view->state.diff.colors);
2985 return err;
2988 err = add_color(&view->state.diff.colors,
2989 "^(commit|(blob|file) [-+] )", TOG_COLOR_DIFF_META,
2990 get_color_value("TOG_COLOR_DIFF_META"));
2991 if (err) {
2992 free_colors(&view->state.diff.colors);
2993 return err;
2996 err = add_color(&view->state.diff.colors,
2997 "^(from|via): ", TOG_COLOR_AUTHOR,
2998 get_color_value("TOG_COLOR_AUTHOR"));
2999 if (err) {
3000 free_colors(&view->state.diff.colors);
3001 return err;
3004 err = add_color(&view->state.diff.colors,
3005 "^date: ", TOG_COLOR_DATE,
3006 get_color_value("TOG_COLOR_DATE"));
3007 if (err) {
3008 free_colors(&view->state.diff.colors);
3009 return err;
3013 if (log_view && view_is_splitscreen(view))
3014 show_log_view(log_view); /* draw vborder */
3015 diff_view_indicate_progress(view);
3017 err = create_diff(&view->state.diff);
3018 if (err) {
3019 free(view->state.diff.id1);
3020 view->state.diff.id1 = NULL;
3021 free(view->state.diff.id2);
3022 view->state.diff.id2 = NULL;
3023 return err;
3026 view->show = show_diff_view;
3027 view->input = input_diff_view;
3028 view->close = close_diff_view;
3030 return NULL;
3033 static const struct got_error *
3034 close_diff_view(struct tog_view *view)
3036 const struct got_error *err = NULL;
3038 free(view->state.diff.id1);
3039 view->state.diff.id1 = NULL;
3040 free(view->state.diff.id2);
3041 view->state.diff.id2 = NULL;
3042 if (view->state.diff.f && fclose(view->state.diff.f) == EOF)
3043 err = got_error_from_errno("fclose");
3044 free_colors(&view->state.diff.colors);
3045 return err;
3048 static const struct got_error *
3049 show_diff_view(struct tog_view *view)
3051 const struct got_error *err;
3052 struct tog_diff_view_state *s = &view->state.diff;
3053 char *id_str1 = NULL, *id_str2, *header;
3055 if (s->id1) {
3056 err = got_object_id_str(&id_str1, s->id1);
3057 if (err)
3058 return err;
3060 err = got_object_id_str(&id_str2, s->id2);
3061 if (err)
3062 return err;
3064 if (asprintf(&header, "diff %s %s",
3065 id_str1 ? id_str1 : "/dev/null", id_str2) == -1) {
3066 err = got_error_from_errno("asprintf");
3067 free(id_str1);
3068 free(id_str2);
3069 return err;
3071 free(id_str1);
3072 free(id_str2);
3074 return draw_file(view, s->f, &s->first_displayed_line,
3075 &s->last_displayed_line, &s->eof, view->nlines,
3076 header, &s->colors);
3079 static const struct got_error *
3080 set_selected_commit(struct tog_diff_view_state *s,
3081 struct commit_queue_entry *entry)
3083 const struct got_error *err;
3084 const struct got_object_id_queue *parent_ids;
3085 struct got_commit_object *selected_commit;
3086 struct got_object_qid *pid;
3088 free(s->id2);
3089 s->id2 = got_object_id_dup(entry->id);
3090 if (s->id2 == NULL)
3091 return got_error_from_errno("got_object_id_dup");
3093 err = got_object_open_as_commit(&selected_commit, s->repo, entry->id);
3094 if (err)
3095 return err;
3096 parent_ids = got_object_commit_get_parent_ids(selected_commit);
3097 free(s->id1);
3098 pid = SIMPLEQ_FIRST(parent_ids);
3099 s->id1 = pid ? got_object_id_dup(pid->id) : NULL;
3100 got_object_commit_close(selected_commit);
3101 return NULL;
3104 static const struct got_error *
3105 input_diff_view(struct tog_view **new_view, struct tog_view **dead_view,
3106 struct tog_view **focus_view, struct tog_view *view, int ch)
3108 const struct got_error *err = NULL;
3109 struct tog_diff_view_state *s = &view->state.diff;
3110 struct tog_log_view_state *ls;
3111 struct commit_queue_entry *entry;
3112 int i;
3114 switch (ch) {
3115 case 'k':
3116 case KEY_UP:
3117 if (s->first_displayed_line > 1)
3118 s->first_displayed_line--;
3119 break;
3120 case KEY_PPAGE:
3121 case CTRL('b'):
3122 if (s->first_displayed_line == 1)
3123 break;
3124 i = 0;
3125 while (i++ < view->nlines - 1 &&
3126 s->first_displayed_line > 1)
3127 s->first_displayed_line--;
3128 break;
3129 case 'j':
3130 case KEY_DOWN:
3131 if (!s->eof)
3132 s->first_displayed_line++;
3133 break;
3134 case KEY_NPAGE:
3135 case CTRL('f'):
3136 case ' ':
3137 if (s->eof)
3138 break;
3139 i = 0;
3140 while (!s->eof && i++ < view->nlines - 1) {
3141 char *line;
3142 line = parse_next_line(s->f, NULL);
3143 s->first_displayed_line++;
3144 if (line == NULL)
3145 break;
3147 break;
3148 case '[':
3149 if (s->diff_context > 0) {
3150 s->diff_context--;
3151 diff_view_indicate_progress(view);
3152 err = create_diff(s);
3154 break;
3155 case ']':
3156 if (s->diff_context < GOT_DIFF_MAX_CONTEXT) {
3157 s->diff_context++;
3158 diff_view_indicate_progress(view);
3159 err = create_diff(s);
3161 break;
3162 case '<':
3163 case ',':
3164 if (s->log_view == NULL)
3165 break;
3166 ls = &s->log_view->state.log;
3167 entry = TAILQ_PREV(ls->selected_entry,
3168 commit_queue_head, entry);
3169 if (entry == NULL)
3170 break;
3172 err = input_log_view(NULL, NULL, NULL, s->log_view,
3173 KEY_UP);
3174 if (err)
3175 break;
3177 err = set_selected_commit(s, entry);
3178 if (err)
3179 break;
3181 s->first_displayed_line = 1;
3182 s->last_displayed_line = view->nlines;
3184 diff_view_indicate_progress(view);
3185 err = create_diff(s);
3186 break;
3187 case '>':
3188 case '.':
3189 if (s->log_view == NULL)
3190 break;
3191 ls = &s->log_view->state.log;
3193 if (TAILQ_NEXT(ls->selected_entry, entry) == NULL) {
3194 ls->thread_args.commits_needed++;
3196 /* Display "loading..." in log view. */
3197 show_log_view(s->log_view);
3198 update_panels();
3199 doupdate();
3201 err = trigger_log_thread(1 /* load_all */,
3202 &ls->thread_args.commits_needed,
3203 &ls->thread_args.log_complete,
3204 &ls->thread_args.need_commits);
3205 if (err)
3206 break;
3208 err = input_log_view(NULL, NULL, NULL, s->log_view,
3209 KEY_DOWN);
3210 if (err)
3211 break;
3213 entry = TAILQ_NEXT(ls->selected_entry, entry);
3214 if (entry == NULL)
3215 break;
3217 err = set_selected_commit(s, entry);
3218 if (err)
3219 break;
3221 s->first_displayed_line = 1;
3222 s->last_displayed_line = view->nlines;
3224 diff_view_indicate_progress(view);
3225 err = create_diff(s);
3226 break;
3227 default:
3228 break;
3231 return err;
3234 static const struct got_error *
3235 cmd_diff(int argc, char *argv[])
3237 const struct got_error *error = NULL;
3238 struct got_repository *repo = NULL;
3239 struct got_reflist_head refs;
3240 struct got_object_id *id1 = NULL, *id2 = NULL;
3241 char *repo_path = NULL;
3242 char *id_str1 = NULL, *id_str2 = NULL;
3243 int ch;
3244 struct tog_view *view;
3246 SIMPLEQ_INIT(&refs);
3248 #ifndef PROFILE
3249 if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd unveil",
3250 NULL) == -1)
3251 err(1, "pledge");
3252 #endif
3254 while ((ch = getopt(argc, argv, "")) != -1) {
3255 switch (ch) {
3256 default:
3257 usage_diff();
3258 /* NOTREACHED */
3262 argc -= optind;
3263 argv += optind;
3265 if (argc == 0) {
3266 usage_diff(); /* TODO show local worktree changes */
3267 } else if (argc == 2) {
3268 repo_path = getcwd(NULL, 0);
3269 if (repo_path == NULL)
3270 return got_error_from_errno("getcwd");
3271 id_str1 = argv[0];
3272 id_str2 = argv[1];
3273 } else if (argc == 3) {
3274 repo_path = realpath(argv[0], NULL);
3275 if (repo_path == NULL)
3276 return got_error_from_errno2("realpath", argv[0]);
3277 id_str1 = argv[1];
3278 id_str2 = argv[2];
3279 } else
3280 usage_diff();
3282 init_curses();
3284 error = got_repo_open(&repo, repo_path, NULL);
3285 if (error)
3286 goto done;
3288 error = apply_unveil(got_repo_get_path(repo), NULL);
3289 if (error)
3290 goto done;
3292 error = got_repo_match_object_id_prefix(&id1, id_str1,
3293 GOT_OBJ_TYPE_ANY, repo);
3294 if (error)
3295 goto done;
3297 error = got_repo_match_object_id_prefix(&id2, id_str2,
3298 GOT_OBJ_TYPE_ANY, repo);
3299 if (error)
3300 goto done;
3302 error = got_ref_list(&refs, repo, NULL, got_ref_cmp_by_name, NULL);
3303 if (error)
3304 goto done;
3306 view = view_open(0, 0, 0, 0, TOG_VIEW_DIFF);
3307 if (view == NULL) {
3308 error = got_error_from_errno("view_open");
3309 goto done;
3311 error = open_diff_view(view, id1, id2, NULL, &refs, repo);
3312 if (error)
3313 goto done;
3314 error = view_loop(view);
3315 done:
3316 free(repo_path);
3317 if (repo)
3318 got_repo_close(repo);
3319 got_ref_list_free(&refs);
3320 return error;
3323 __dead static void
3324 usage_blame(void)
3326 endwin();
3327 fprintf(stderr, "usage: %s blame [-c commit] [-r repository-path] path\n",
3328 getprogname());
3329 exit(1);
3332 struct tog_blame_line {
3333 int annotated;
3334 struct got_object_id *id;
3337 static const struct got_error *
3338 draw_blame(struct tog_view *view, struct got_object_id *id, FILE *f,
3339 const char *path, struct tog_blame_line *lines, int nlines,
3340 int blame_complete, int selected_line, int *first_displayed_line,
3341 int *last_displayed_line, int *eof, int max_lines,
3342 struct tog_colors *colors)
3344 const struct got_error *err;
3345 int lineno = 0, nprinted = 0;
3346 char *line;
3347 size_t len;
3348 wchar_t *wline;
3349 int width;
3350 struct tog_blame_line *blame_line;
3351 struct got_object_id *prev_id = NULL;
3352 char *id_str;
3353 struct tog_color *tc;
3355 err = got_object_id_str(&id_str, id);
3356 if (err)
3357 return err;
3359 rewind(f);
3360 werase(view->window);
3362 if (asprintf(&line, "commit %s", id_str) == -1) {
3363 err = got_error_from_errno("asprintf");
3364 free(id_str);
3365 return err;
3368 err = format_line(&wline, &width, line, view->ncols, 0);
3369 free(line);
3370 line = NULL;
3371 if (err)
3372 return err;
3373 if (view_needs_focus_indication(view))
3374 wstandout(view->window);
3375 tc = get_color(colors, TOG_COLOR_COMMIT);
3376 if (tc)
3377 wattr_on(view->window,
3378 COLOR_PAIR(tc->colorpair), NULL);
3379 waddwstr(view->window, wline);
3380 if (tc)
3381 wattr_off(view->window,
3382 COLOR_PAIR(tc->colorpair), NULL);
3383 if (view_needs_focus_indication(view))
3384 wstandend(view->window);
3385 free(wline);
3386 wline = NULL;
3387 if (width < view->ncols - 1)
3388 waddch(view->window, '\n');
3390 if (asprintf(&line, "[%d/%d] %s%s",
3391 *first_displayed_line - 1 + selected_line, nlines,
3392 blame_complete ? "" : "annotating... ", path) == -1) {
3393 free(id_str);
3394 return got_error_from_errno("asprintf");
3396 free(id_str);
3397 err = format_line(&wline, &width, line, view->ncols, 0);
3398 free(line);
3399 line = NULL;
3400 if (err)
3401 return err;
3402 waddwstr(view->window, wline);
3403 free(wline);
3404 wline = NULL;
3405 if (width < view->ncols - 1)
3406 waddch(view->window, '\n');
3408 *eof = 0;
3409 while (nprinted < max_lines - 2) {
3410 line = parse_next_line(f, &len);
3411 if (line == NULL) {
3412 *eof = 1;
3413 break;
3415 if (++lineno < *first_displayed_line) {
3416 free(line);
3417 continue;
3420 if (view->ncols <= 9) {
3421 width = 9;
3422 wline = wcsdup(L"");
3423 if (wline == NULL)
3424 err = got_error_from_errno("wcsdup");
3425 } else {
3426 err = format_line(&wline, &width, line,
3427 view->ncols - 9, 9);
3428 width += 9;
3430 if (err) {
3431 free(line);
3432 return err;
3435 if (view->focussed && nprinted == selected_line - 1)
3436 wstandout(view->window);
3438 if (nlines > 0) {
3439 blame_line = &lines[lineno - 1];
3440 if (blame_line->annotated && prev_id &&
3441 got_object_id_cmp(prev_id, blame_line->id) == 0 &&
3442 !(view->focussed &&
3443 nprinted == selected_line - 1)) {
3444 waddstr(view->window, " ");
3445 } else if (blame_line->annotated) {
3446 char *id_str;
3447 err = got_object_id_str(&id_str, blame_line->id);
3448 if (err) {
3449 free(line);
3450 free(wline);
3451 return err;
3453 tc = get_color(colors, TOG_COLOR_COMMIT);
3454 if (tc)
3455 wattr_on(view->window,
3456 COLOR_PAIR(tc->colorpair), NULL);
3457 wprintw(view->window, "%.8s", id_str);
3458 if (tc)
3459 wattr_off(view->window,
3460 COLOR_PAIR(tc->colorpair), NULL);
3461 free(id_str);
3462 prev_id = blame_line->id;
3463 } else {
3464 waddstr(view->window, "........");
3465 prev_id = NULL;
3467 } else {
3468 waddstr(view->window, "........");
3469 prev_id = NULL;
3472 if (view->focussed && nprinted == selected_line - 1)
3473 wstandend(view->window);
3474 waddstr(view->window, " ");
3476 waddwstr(view->window, wline);
3477 if (width <= view->ncols - 1)
3478 waddch(view->window, '\n');
3479 if (++nprinted == 1)
3480 *first_displayed_line = lineno;
3481 free(line);
3482 free(wline);
3483 wline = NULL;
3485 *last_displayed_line = lineno;
3487 view_vborder(view);
3489 return NULL;
3492 static const struct got_error *
3493 blame_cb(void *arg, int nlines, int lineno, struct got_object_id *id)
3495 const struct got_error *err = NULL;
3496 struct tog_blame_cb_args *a = arg;
3497 struct tog_blame_line *line;
3498 int errcode;
3500 if (nlines != a->nlines ||
3501 (lineno != -1 && lineno < 1) || lineno > a->nlines)
3502 return got_error(GOT_ERR_RANGE);
3504 errcode = pthread_mutex_lock(&tog_mutex);
3505 if (errcode)
3506 return got_error_set_errno(errcode, "pthread_mutex_lock");
3508 if (*a->quit) { /* user has quit the blame view */
3509 err = got_error(GOT_ERR_ITER_COMPLETED);
3510 goto done;
3513 if (lineno == -1)
3514 goto done; /* no change in this commit */
3516 line = &a->lines[lineno - 1];
3517 if (line->annotated)
3518 goto done;
3520 line->id = got_object_id_dup(id);
3521 if (line->id == NULL) {
3522 err = got_error_from_errno("got_object_id_dup");
3523 goto done;
3525 line->annotated = 1;
3526 done:
3527 errcode = pthread_mutex_unlock(&tog_mutex);
3528 if (errcode)
3529 err = got_error_set_errno(errcode, "pthread_mutex_unlock");
3530 return err;
3533 static void *
3534 blame_thread(void *arg)
3536 const struct got_error *err;
3537 struct tog_blame_thread_args *ta = arg;
3538 struct tog_blame_cb_args *a = ta->cb_args;
3539 int errcode;
3541 err = block_signals_used_by_main_thread();
3542 if (err)
3543 return (void *)err;
3545 err = got_blame(ta->path, a->commit_id, ta->repo,
3546 blame_cb, ta->cb_args, ta->cancel_cb, ta->cancel_arg);
3547 if (err && err->code == GOT_ERR_CANCELLED)
3548 err = NULL;
3550 errcode = pthread_mutex_lock(&tog_mutex);
3551 if (errcode)
3552 return (void *)got_error_set_errno(errcode,
3553 "pthread_mutex_lock");
3555 got_repo_close(ta->repo);
3556 ta->repo = NULL;
3557 *ta->complete = 1;
3559 errcode = pthread_mutex_unlock(&tog_mutex);
3560 if (errcode && err == NULL)
3561 err = got_error_set_errno(errcode, "pthread_mutex_unlock");
3563 return (void *)err;
3566 static struct got_object_id *
3567 get_selected_commit_id(struct tog_blame_line *lines, int nlines,
3568 int first_displayed_line, int selected_line)
3570 struct tog_blame_line *line;
3572 if (nlines <= 0)
3573 return NULL;
3575 line = &lines[first_displayed_line - 1 + selected_line - 1];
3576 if (!line->annotated)
3577 return NULL;
3579 return line->id;
3582 static const struct got_error *
3583 stop_blame(struct tog_blame *blame)
3585 const struct got_error *err = NULL;
3586 int i;
3588 if (blame->thread) {
3589 int errcode;
3590 errcode = pthread_mutex_unlock(&tog_mutex);
3591 if (errcode)
3592 return got_error_set_errno(errcode,
3593 "pthread_mutex_unlock");
3594 errcode = pthread_join(blame->thread, (void **)&err);
3595 if (errcode)
3596 return got_error_set_errno(errcode, "pthread_join");
3597 errcode = pthread_mutex_lock(&tog_mutex);
3598 if (errcode)
3599 return got_error_set_errno(errcode,
3600 "pthread_mutex_lock");
3601 if (err && err->code == GOT_ERR_ITER_COMPLETED)
3602 err = NULL;
3603 blame->thread = NULL;
3605 if (blame->thread_args.repo) {
3606 got_repo_close(blame->thread_args.repo);
3607 blame->thread_args.repo = NULL;
3609 if (blame->f) {
3610 if (fclose(blame->f) != 0 && err == NULL)
3611 err = got_error_from_errno("fclose");
3612 blame->f = NULL;
3614 if (blame->lines) {
3615 for (i = 0; i < blame->nlines; i++)
3616 free(blame->lines[i].id);
3617 free(blame->lines);
3618 blame->lines = NULL;
3620 free(blame->cb_args.commit_id);
3621 blame->cb_args.commit_id = NULL;
3623 return err;
3626 static const struct got_error *
3627 cancel_blame_view(void *arg)
3629 const struct got_error *err = NULL;
3630 int *done = arg;
3631 int errcode;
3633 errcode = pthread_mutex_lock(&tog_mutex);
3634 if (errcode)
3635 return got_error_set_errno(errcode,
3636 "pthread_mutex_unlock");
3638 if (*done)
3639 err = got_error(GOT_ERR_CANCELLED);
3641 errcode = pthread_mutex_unlock(&tog_mutex);
3642 if (errcode)
3643 return got_error_set_errno(errcode,
3644 "pthread_mutex_lock");
3646 return err;
3649 static const struct got_error *
3650 run_blame(struct tog_blame *blame, struct tog_view *view, int *blame_complete,
3651 int *first_displayed_line, int *last_displayed_line, int *selected_line,
3652 int *done, int *eof, const char *path, struct got_object_id *commit_id,
3653 struct got_repository *repo)
3655 const struct got_error *err = NULL;
3656 struct got_blob_object *blob = NULL;
3657 struct got_repository *thread_repo = NULL;
3658 struct got_object_id *obj_id = NULL;
3659 int obj_type;
3661 err = got_object_id_by_path(&obj_id, repo, commit_id, path);
3662 if (err)
3663 return err;
3665 err = got_object_get_type(&obj_type, repo, obj_id);
3666 if (err)
3667 goto done;
3669 if (obj_type != GOT_OBJ_TYPE_BLOB) {
3670 err = got_error(GOT_ERR_OBJ_TYPE);
3671 goto done;
3674 err = got_object_open_as_blob(&blob, repo, obj_id, 8192);
3675 if (err)
3676 goto done;
3677 blame->f = got_opentemp();
3678 if (blame->f == NULL) {
3679 err = got_error_from_errno("got_opentemp");
3680 goto done;
3682 err = got_object_blob_dump_to_file(&blame->filesize, &blame->nlines,
3683 &blame->line_offsets, blame->f, blob);
3684 if (err || blame->nlines == 0)
3685 goto done;
3687 /* Don't include \n at EOF in the blame line count. */
3688 if (blame->line_offsets[blame->nlines - 1] == blame->filesize)
3689 blame->nlines--;
3691 blame->lines = calloc(blame->nlines, sizeof(*blame->lines));
3692 if (blame->lines == NULL) {
3693 err = got_error_from_errno("calloc");
3694 goto done;
3697 err = got_repo_open(&thread_repo, got_repo_get_path(repo), NULL);
3698 if (err)
3699 goto done;
3701 blame->cb_args.view = view;
3702 blame->cb_args.lines = blame->lines;
3703 blame->cb_args.nlines = blame->nlines;
3704 blame->cb_args.commit_id = got_object_id_dup(commit_id);
3705 if (blame->cb_args.commit_id == NULL) {
3706 err = got_error_from_errno("got_object_id_dup");
3707 goto done;
3709 blame->cb_args.quit = done;
3711 blame->thread_args.path = path;
3712 blame->thread_args.repo = thread_repo;
3713 blame->thread_args.cb_args = &blame->cb_args;
3714 blame->thread_args.complete = blame_complete;
3715 blame->thread_args.cancel_cb = cancel_blame_view;
3716 blame->thread_args.cancel_arg = done;
3717 *blame_complete = 0;
3719 done:
3720 if (blob)
3721 got_object_blob_close(blob);
3722 free(obj_id);
3723 if (err)
3724 stop_blame(blame);
3725 return err;
3728 static const struct got_error *
3729 open_blame_view(struct tog_view *view, char *path,
3730 struct got_object_id *commit_id, struct got_reflist_head *refs,
3731 struct got_repository *repo)
3733 const struct got_error *err = NULL;
3734 struct tog_blame_view_state *s = &view->state.blame;
3736 SIMPLEQ_INIT(&s->blamed_commits);
3738 s->path = strdup(path);
3739 if (s->path == NULL)
3740 return got_error_from_errno("strdup");
3742 err = got_object_qid_alloc(&s->blamed_commit, commit_id);
3743 if (err) {
3744 free(s->path);
3745 return err;
3748 SIMPLEQ_INSERT_HEAD(&s->blamed_commits, s->blamed_commit, entry);
3749 s->first_displayed_line = 1;
3750 s->last_displayed_line = view->nlines;
3751 s->selected_line = 1;
3752 s->blame_complete = 0;
3753 s->repo = repo;
3754 s->refs = refs;
3755 s->commit_id = commit_id;
3756 memset(&s->blame, 0, sizeof(s->blame));
3758 SIMPLEQ_INIT(&s->colors);
3759 if (has_colors() && getenv("TOG_COLORS") != NULL) {
3760 err = add_color(&s->colors, "^", TOG_COLOR_COMMIT,
3761 get_color_value("TOG_COLOR_COMMIT"));
3762 if (err)
3763 return err;
3766 view->show = show_blame_view;
3767 view->input = input_blame_view;
3768 view->close = close_blame_view;
3769 view->search_start = search_start_blame_view;
3770 view->search_next = search_next_blame_view;
3772 return run_blame(&s->blame, view, &s->blame_complete,
3773 &s->first_displayed_line, &s->last_displayed_line,
3774 &s->selected_line, &s->done, &s->eof, s->path,
3775 s->blamed_commit->id, s->repo);
3778 static const struct got_error *
3779 close_blame_view(struct tog_view *view)
3781 const struct got_error *err = NULL;
3782 struct tog_blame_view_state *s = &view->state.blame;
3784 if (s->blame.thread)
3785 err = stop_blame(&s->blame);
3787 while (!SIMPLEQ_EMPTY(&s->blamed_commits)) {
3788 struct got_object_qid *blamed_commit;
3789 blamed_commit = SIMPLEQ_FIRST(&s->blamed_commits);
3790 SIMPLEQ_REMOVE_HEAD(&s->blamed_commits, entry);
3791 got_object_qid_free(blamed_commit);
3794 free(s->path);
3795 free_colors(&s->colors);
3797 return err;
3800 static const struct got_error *
3801 search_start_blame_view(struct tog_view *view)
3803 struct tog_blame_view_state *s = &view->state.blame;
3805 s->matched_line = 0;
3806 return NULL;
3809 static const struct got_error *
3810 search_next_blame_view(struct tog_view *view)
3812 struct tog_blame_view_state *s = &view->state.blame;
3813 int lineno;
3815 if (!view->searching) {
3816 view->search_next_done = 1;
3817 return NULL;
3820 if (s->matched_line) {
3821 if (view->searching == TOG_SEARCH_FORWARD)
3822 lineno = s->matched_line + 1;
3823 else
3824 lineno = s->matched_line - 1;
3825 } else {
3826 if (view->searching == TOG_SEARCH_FORWARD)
3827 lineno = 1;
3828 else
3829 lineno = s->blame.nlines;
3832 while (1) {
3833 char *line = NULL;
3834 off_t offset;
3835 size_t len;
3837 if (lineno <= 0 || lineno > s->blame.nlines) {
3838 if (s->matched_line == 0) {
3839 view->search_next_done = 1;
3840 free(line);
3841 break;
3844 if (view->searching == TOG_SEARCH_FORWARD)
3845 lineno = 1;
3846 else
3847 lineno = s->blame.nlines;
3850 offset = s->blame.line_offsets[lineno - 1];
3851 if (fseeko(s->blame.f, offset, SEEK_SET) != 0) {
3852 free(line);
3853 return got_error_from_errno("fseeko");
3855 free(line);
3856 line = parse_next_line(s->blame.f, &len);
3857 if (line && match_line(line, &view->regex)) {
3858 view->search_next_done = 1;
3859 s->matched_line = lineno;
3860 free(line);
3861 break;
3863 free(line);
3864 if (view->searching == TOG_SEARCH_FORWARD)
3865 lineno++;
3866 else
3867 lineno--;
3870 if (s->matched_line) {
3871 s->first_displayed_line = s->matched_line;
3872 s->selected_line = 1;
3875 return NULL;
3878 static const struct got_error *
3879 show_blame_view(struct tog_view *view)
3881 const struct got_error *err = NULL;
3882 struct tog_blame_view_state *s = &view->state.blame;
3883 int errcode;
3885 if (s->blame.thread == NULL) {
3886 errcode = pthread_create(&s->blame.thread, NULL, blame_thread,
3887 &s->blame.thread_args);
3888 if (errcode)
3889 return got_error_set_errno(errcode, "pthread_create");
3891 halfdelay(1); /* fast refresh while annotating */
3894 if (s->blame_complete)
3895 halfdelay(10); /* disable fast refresh */
3897 err = draw_blame(view, s->blamed_commit->id, s->blame.f,
3898 s->path, s->blame.lines, s->blame.nlines, s->blame_complete,
3899 s->selected_line, &s->first_displayed_line,
3900 &s->last_displayed_line, &s->eof, view->nlines, &s->colors);
3902 view_vborder(view);
3903 return err;
3906 static const struct got_error *
3907 input_blame_view(struct tog_view **new_view, struct tog_view **dead_view,
3908 struct tog_view **focus_view, struct tog_view *view, int ch)
3910 const struct got_error *err = NULL, *thread_err = NULL;
3911 struct tog_view *diff_view;
3912 struct tog_blame_view_state *s = &view->state.blame;
3913 int begin_x = 0;
3915 switch (ch) {
3916 case 'q':
3917 s->done = 1;
3918 break;
3919 case 'k':
3920 case KEY_UP:
3921 if (s->selected_line > 1)
3922 s->selected_line--;
3923 else if (s->selected_line == 1 &&
3924 s->first_displayed_line > 1)
3925 s->first_displayed_line--;
3926 break;
3927 case KEY_PPAGE:
3928 if (s->first_displayed_line == 1) {
3929 s->selected_line = 1;
3930 break;
3932 if (s->first_displayed_line > view->nlines - 2)
3933 s->first_displayed_line -=
3934 (view->nlines - 2);
3935 else
3936 s->first_displayed_line = 1;
3937 break;
3938 case 'j':
3939 case KEY_DOWN:
3940 if (s->selected_line < view->nlines - 2 &&
3941 s->first_displayed_line +
3942 s->selected_line <= s->blame.nlines)
3943 s->selected_line++;
3944 else if (s->last_displayed_line <
3945 s->blame.nlines)
3946 s->first_displayed_line++;
3947 break;
3948 case 'b':
3949 case 'p': {
3950 struct got_object_id *id = NULL;
3951 id = get_selected_commit_id(s->blame.lines, s->blame.nlines,
3952 s->first_displayed_line, s->selected_line);
3953 if (id == NULL)
3954 break;
3955 if (ch == 'p') {
3956 struct got_commit_object *commit;
3957 struct got_object_qid *pid;
3958 struct got_object_id *blob_id = NULL;
3959 int obj_type;
3960 err = got_object_open_as_commit(&commit,
3961 s->repo, id);
3962 if (err)
3963 break;
3964 pid = SIMPLEQ_FIRST(
3965 got_object_commit_get_parent_ids(commit));
3966 if (pid == NULL) {
3967 got_object_commit_close(commit);
3968 break;
3970 /* Check if path history ends here. */
3971 err = got_object_id_by_path(&blob_id, s->repo,
3972 pid->id, s->path);
3973 if (err) {
3974 if (err->code == GOT_ERR_NO_TREE_ENTRY)
3975 err = NULL;
3976 got_object_commit_close(commit);
3977 break;
3979 err = got_object_get_type(&obj_type, s->repo,
3980 blob_id);
3981 free(blob_id);
3982 /* Can't blame non-blob type objects. */
3983 if (obj_type != GOT_OBJ_TYPE_BLOB) {
3984 got_object_commit_close(commit);
3985 break;
3987 err = got_object_qid_alloc(&s->blamed_commit,
3988 pid->id);
3989 got_object_commit_close(commit);
3990 } else {
3991 if (got_object_id_cmp(id,
3992 s->blamed_commit->id) == 0)
3993 break;
3994 err = got_object_qid_alloc(&s->blamed_commit,
3995 id);
3997 if (err)
3998 break;
3999 s->done = 1;
4000 thread_err = stop_blame(&s->blame);
4001 s->done = 0;
4002 if (thread_err)
4003 break;
4004 SIMPLEQ_INSERT_HEAD(&s->blamed_commits,
4005 s->blamed_commit, entry);
4006 err = run_blame(&s->blame, view, &s->blame_complete,
4007 &s->first_displayed_line, &s->last_displayed_line,
4008 &s->selected_line, &s->done, &s->eof,
4009 s->path, s->blamed_commit->id, s->repo);
4010 if (err)
4011 break;
4012 break;
4014 case 'B': {
4015 struct got_object_qid *first;
4016 first = SIMPLEQ_FIRST(&s->blamed_commits);
4017 if (!got_object_id_cmp(first->id, s->commit_id))
4018 break;
4019 s->done = 1;
4020 thread_err = stop_blame(&s->blame);
4021 s->done = 0;
4022 if (thread_err)
4023 break;
4024 SIMPLEQ_REMOVE_HEAD(&s->blamed_commits, entry);
4025 got_object_qid_free(s->blamed_commit);
4026 s->blamed_commit =
4027 SIMPLEQ_FIRST(&s->blamed_commits);
4028 err = run_blame(&s->blame, view, &s->blame_complete,
4029 &s->first_displayed_line, &s->last_displayed_line,
4030 &s->selected_line, &s->done, &s->eof, s->path,
4031 s->blamed_commit->id, s->repo);
4032 if (err)
4033 break;
4034 break;
4036 case KEY_ENTER:
4037 case '\r': {
4038 struct got_object_id *id = NULL;
4039 struct got_object_qid *pid;
4040 struct got_commit_object *commit = NULL;
4041 id = get_selected_commit_id(s->blame.lines, s->blame.nlines,
4042 s->first_displayed_line, s->selected_line);
4043 if (id == NULL)
4044 break;
4045 err = got_object_open_as_commit(&commit, s->repo, id);
4046 if (err)
4047 break;
4048 pid = SIMPLEQ_FIRST(
4049 got_object_commit_get_parent_ids(commit));
4050 if (view_is_parent_view(view))
4051 begin_x = view_split_begin_x(view->begin_x);
4052 diff_view = view_open(0, 0, 0, begin_x, TOG_VIEW_DIFF);
4053 if (diff_view == NULL) {
4054 got_object_commit_close(commit);
4055 err = got_error_from_errno("view_open");
4056 break;
4058 err = open_diff_view(diff_view, pid ? pid->id : NULL,
4059 id, NULL, s->refs, s->repo);
4060 got_object_commit_close(commit);
4061 if (err) {
4062 view_close(diff_view);
4063 break;
4065 if (view_is_parent_view(view)) {
4066 err = view_close_child(view);
4067 if (err)
4068 break;
4069 err = view_set_child(view, diff_view);
4070 if (err) {
4071 view_close(diff_view);
4072 break;
4074 *focus_view = diff_view;
4075 view->child_focussed = 1;
4076 } else
4077 *new_view = diff_view;
4078 if (err)
4079 break;
4080 break;
4082 case KEY_NPAGE:
4083 case ' ':
4084 if (s->last_displayed_line >= s->blame.nlines &&
4085 s->selected_line >= MIN(s->blame.nlines,
4086 view->nlines - 2)) {
4087 break;
4089 if (s->last_displayed_line >= s->blame.nlines &&
4090 s->selected_line < view->nlines - 2) {
4091 s->selected_line = MIN(s->blame.nlines,
4092 view->nlines - 2);
4093 break;
4095 if (s->last_displayed_line + view->nlines - 2
4096 <= s->blame.nlines)
4097 s->first_displayed_line +=
4098 view->nlines - 2;
4099 else
4100 s->first_displayed_line =
4101 s->blame.nlines -
4102 (view->nlines - 3);
4103 break;
4104 case KEY_RESIZE:
4105 if (s->selected_line > view->nlines - 2) {
4106 s->selected_line = MIN(s->blame.nlines,
4107 view->nlines - 2);
4109 break;
4110 default:
4111 break;
4113 return thread_err ? thread_err : err;
4116 static const struct got_error *
4117 cmd_blame(int argc, char *argv[])
4119 const struct got_error *error;
4120 struct got_repository *repo = NULL;
4121 struct got_reflist_head refs;
4122 struct got_worktree *worktree = NULL;
4123 char *path, *cwd = NULL, *repo_path = NULL, *in_repo_path = NULL;
4124 struct got_object_id *commit_id = NULL;
4125 char *commit_id_str = NULL;
4126 int ch;
4127 struct tog_view *view;
4129 SIMPLEQ_INIT(&refs);
4131 #ifndef PROFILE
4132 if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd unveil",
4133 NULL) == -1)
4134 err(1, "pledge");
4135 #endif
4137 while ((ch = getopt(argc, argv, "c:r:")) != -1) {
4138 switch (ch) {
4139 case 'c':
4140 commit_id_str = optarg;
4141 break;
4142 case 'r':
4143 repo_path = realpath(optarg, NULL);
4144 if (repo_path == NULL)
4145 return got_error_from_errno2("realpath",
4146 optarg);
4147 break;
4148 default:
4149 usage_blame();
4150 /* NOTREACHED */
4154 argc -= optind;
4155 argv += optind;
4157 if (argc == 1)
4158 path = argv[0];
4159 else
4160 usage_blame();
4162 cwd = getcwd(NULL, 0);
4163 if (cwd == NULL) {
4164 error = got_error_from_errno("getcwd");
4165 goto done;
4167 if (repo_path == NULL) {
4168 error = got_worktree_open(&worktree, cwd);
4169 if (error && error->code != GOT_ERR_NOT_WORKTREE)
4170 goto done;
4171 else
4172 error = NULL;
4173 if (worktree) {
4174 repo_path =
4175 strdup(got_worktree_get_repo_path(worktree));
4176 if (repo_path == NULL)
4177 error = got_error_from_errno("strdup");
4178 if (error)
4179 goto done;
4180 } else {
4181 repo_path = strdup(cwd);
4182 if (repo_path == NULL) {
4183 error = got_error_from_errno("strdup");
4184 goto done;
4189 init_curses();
4191 error = got_repo_open(&repo, repo_path, NULL);
4192 if (error != NULL)
4193 goto done;
4195 error = apply_unveil(got_repo_get_path(repo), NULL);
4196 if (error)
4197 goto done;
4199 if (worktree) {
4200 const char *prefix = got_worktree_get_path_prefix(worktree);
4201 char *p, *worktree_subdir = cwd +
4202 strlen(got_worktree_get_root_path(worktree));
4203 if (asprintf(&p, "%s%s%s%s%s",
4204 prefix, (strcmp(prefix, "/") != 0) ? "/" : "",
4205 worktree_subdir, worktree_subdir[0] ? "/" : "",
4206 path) == -1) {
4207 error = got_error_from_errno("asprintf");
4208 goto done;
4210 error = got_repo_map_path(&in_repo_path, repo, p, 0);
4211 free(p);
4212 } else {
4213 error = got_repo_map_path(&in_repo_path, repo, path, 1);
4215 if (error)
4216 goto done;
4218 if (commit_id_str == NULL) {
4219 struct got_reference *head_ref;
4220 error = got_ref_open(&head_ref, repo, GOT_REF_HEAD, 0);
4221 if (error != NULL)
4222 goto done;
4223 error = got_ref_resolve(&commit_id, repo, head_ref);
4224 got_ref_close(head_ref);
4225 } else {
4226 error = got_repo_match_object_id(&commit_id, NULL,
4227 commit_id_str, GOT_OBJ_TYPE_COMMIT, 1, repo);
4229 if (error != NULL)
4230 goto done;
4232 error = got_ref_list(&refs, repo, NULL, got_ref_cmp_by_name, NULL);
4233 if (error)
4234 goto done;
4236 view = view_open(0, 0, 0, 0, TOG_VIEW_BLAME);
4237 if (view == NULL) {
4238 error = got_error_from_errno("view_open");
4239 goto done;
4241 error = open_blame_view(view, in_repo_path, commit_id, &refs, repo);
4242 if (error)
4243 goto done;
4244 if (worktree) {
4245 /* Release work tree lock. */
4246 got_worktree_close(worktree);
4247 worktree = NULL;
4249 error = view_loop(view);
4250 done:
4251 free(repo_path);
4252 free(cwd);
4253 free(commit_id);
4254 if (worktree)
4255 got_worktree_close(worktree);
4256 if (repo)
4257 got_repo_close(repo);
4258 got_ref_list_free(&refs);
4259 return error;
4262 static const struct got_error *
4263 draw_tree_entries(struct tog_view *view,
4264 struct got_tree_entry **first_displayed_entry,
4265 struct got_tree_entry **last_displayed_entry,
4266 struct got_tree_entry **selected_entry, int *ndisplayed,
4267 const char *label, int show_ids, const char *parent_path,
4268 struct got_tree_object *tree, int selected, int limit,
4269 int isroot, struct tog_colors *colors)
4271 const struct got_error *err = NULL;
4272 struct got_tree_entry *te;
4273 wchar_t *wline;
4274 struct tog_color *tc;
4275 int width, n, i, nentries;
4277 *ndisplayed = 0;
4279 werase(view->window);
4281 if (limit == 0)
4282 return NULL;
4284 err = format_line(&wline, &width, label, view->ncols, 0);
4285 if (err)
4286 return err;
4287 if (view_needs_focus_indication(view))
4288 wstandout(view->window);
4289 tc = get_color(colors, TOG_COLOR_COMMIT);
4290 if (tc)
4291 wattr_on(view->window,
4292 COLOR_PAIR(tc->colorpair), NULL);
4293 waddwstr(view->window, wline);
4294 if (tc)
4295 wattr_off(view->window,
4296 COLOR_PAIR(tc->colorpair), NULL);
4297 if (view_needs_focus_indication(view))
4298 wstandend(view->window);
4299 free(wline);
4300 wline = NULL;
4301 if (width < view->ncols - 1)
4302 waddch(view->window, '\n');
4303 if (--limit <= 0)
4304 return NULL;
4305 err = format_line(&wline, &width, parent_path, view->ncols, 0);
4306 if (err)
4307 return err;
4308 waddwstr(view->window, wline);
4309 free(wline);
4310 wline = NULL;
4311 if (width < view->ncols - 1)
4312 waddch(view->window, '\n');
4313 if (--limit <= 0)
4314 return NULL;
4315 waddch(view->window, '\n');
4316 if (--limit <= 0)
4317 return NULL;
4319 if (*first_displayed_entry == NULL) {
4320 te = got_object_tree_get_first_entry(tree);
4321 if (selected == 0) {
4322 if (view->focussed)
4323 wstandout(view->window);
4324 *selected_entry = NULL;
4326 waddstr(view->window, " ..\n"); /* parent directory */
4327 if (selected == 0 && view->focussed)
4328 wstandend(view->window);
4329 (*ndisplayed)++;
4330 if (--limit <= 0)
4331 return NULL;
4332 n = 1;
4333 } else {
4334 n = 0;
4335 te = *first_displayed_entry;
4338 nentries = got_object_tree_get_nentries(tree);
4339 for (i = got_tree_entry_get_index(te); i < nentries; i++) {
4340 char *line = NULL, *id_str = NULL;
4341 const char *modestr = "";
4342 mode_t mode;
4344 te = got_object_tree_get_entry(tree, i);
4345 mode = got_tree_entry_get_mode(te);
4347 if (show_ids) {
4348 err = got_object_id_str(&id_str,
4349 got_tree_entry_get_id(te));
4350 if (err)
4351 return got_error_from_errno(
4352 "got_object_id_str");
4354 if (got_object_tree_entry_is_submodule(te))
4355 modestr = "$";
4356 else if (S_ISLNK(mode))
4357 modestr = "@";
4358 else if (S_ISDIR(mode))
4359 modestr = "/";
4360 else if (mode & S_IXUSR)
4361 modestr = "*";
4362 if (asprintf(&line, "%s %s%s", id_str ? id_str : "",
4363 got_tree_entry_get_name(te), modestr) == -1) {
4364 free(id_str);
4365 return got_error_from_errno("asprintf");
4367 free(id_str);
4368 err = format_line(&wline, &width, line, view->ncols, 0);
4369 if (err) {
4370 free(line);
4371 break;
4373 if (n == selected) {
4374 if (view->focussed)
4375 wstandout(view->window);
4376 *selected_entry = te;
4378 tc = match_color(colors, line);
4379 if (tc)
4380 wattr_on(view->window,
4381 COLOR_PAIR(tc->colorpair), NULL);
4382 waddwstr(view->window, wline);
4383 if (tc)
4384 wattr_off(view->window,
4385 COLOR_PAIR(tc->colorpair), NULL);
4386 if (width < view->ncols - 1)
4387 waddch(view->window, '\n');
4388 if (n == selected && view->focussed)
4389 wstandend(view->window);
4390 free(line);
4391 free(wline);
4392 wline = NULL;
4393 n++;
4394 (*ndisplayed)++;
4395 *last_displayed_entry = te;
4396 if (--limit <= 0)
4397 break;
4400 return err;
4403 static void
4404 tree_scroll_up(struct tog_view *view,
4405 struct got_tree_entry **first_displayed_entry, int maxscroll,
4406 struct got_tree_object *tree, int isroot)
4408 struct got_tree_entry *te;
4409 int i;
4411 if (*first_displayed_entry == NULL)
4412 return;
4414 te = got_object_tree_get_entry(tree, 0);
4415 if (*first_displayed_entry == te) {
4416 if (!isroot)
4417 *first_displayed_entry = NULL;
4418 return;
4421 i = 0;
4422 while (*first_displayed_entry && i < maxscroll) {
4423 *first_displayed_entry = got_tree_entry_get_prev(tree,
4424 *first_displayed_entry);
4425 i++;
4427 if (!isroot && te == got_object_tree_get_first_entry(tree) && i < maxscroll)
4428 *first_displayed_entry = NULL;
4431 static int
4432 tree_scroll_down(struct got_tree_entry **first_displayed_entry, int maxscroll,
4433 struct got_tree_entry *last_displayed_entry,
4434 struct got_tree_object *tree)
4436 struct got_tree_entry *next, *last;
4437 int n = 0;
4439 if (*first_displayed_entry)
4440 next = got_tree_entry_get_next(tree, *first_displayed_entry);
4441 else
4442 next = got_object_tree_get_first_entry(tree);
4444 last = last_displayed_entry;
4445 while (next && last && n++ < maxscroll) {
4446 last = got_tree_entry_get_next(tree, last);
4447 if (last) {
4448 *first_displayed_entry = next;
4449 next = got_tree_entry_get_next(tree, next);
4452 return n;
4455 static const struct got_error *
4456 tree_entry_path(char **path, struct tog_parent_trees *parents,
4457 struct got_tree_entry *te)
4459 const struct got_error *err = NULL;
4460 struct tog_parent_tree *pt;
4461 size_t len = 2; /* for leading slash and NUL */
4463 TAILQ_FOREACH(pt, parents, entry)
4464 len += strlen(got_tree_entry_get_name(pt->selected_entry))
4465 + 1 /* slash */;
4466 if (te)
4467 len += strlen(got_tree_entry_get_name(te));
4469 *path = calloc(1, len);
4470 if (path == NULL)
4471 return got_error_from_errno("calloc");
4473 (*path)[0] = '/';
4474 pt = TAILQ_LAST(parents, tog_parent_trees);
4475 while (pt) {
4476 const char *name = got_tree_entry_get_name(pt->selected_entry);
4477 if (strlcat(*path, name, len) >= len) {
4478 err = got_error(GOT_ERR_NO_SPACE);
4479 goto done;
4481 if (strlcat(*path, "/", len) >= len) {
4482 err = got_error(GOT_ERR_NO_SPACE);
4483 goto done;
4485 pt = TAILQ_PREV(pt, tog_parent_trees, entry);
4487 if (te) {
4488 if (strlcat(*path, got_tree_entry_get_name(te), len) >= len) {
4489 err = got_error(GOT_ERR_NO_SPACE);
4490 goto done;
4493 done:
4494 if (err) {
4495 free(*path);
4496 *path = NULL;
4498 return err;
4501 static const struct got_error *
4502 blame_tree_entry(struct tog_view **new_view, int begin_x,
4503 struct got_tree_entry *te, struct tog_parent_trees *parents,
4504 struct got_object_id *commit_id, struct got_reflist_head *refs,
4505 struct got_repository *repo)
4507 const struct got_error *err = NULL;
4508 char *path;
4509 struct tog_view *blame_view;
4511 *new_view = NULL;
4513 err = tree_entry_path(&path, parents, te);
4514 if (err)
4515 return err;
4517 blame_view = view_open(0, 0, 0, begin_x, TOG_VIEW_BLAME);
4518 if (blame_view == NULL) {
4519 err = got_error_from_errno("view_open");
4520 goto done;
4523 err = open_blame_view(blame_view, path, commit_id, refs, repo);
4524 if (err) {
4525 if (err->code == GOT_ERR_CANCELLED)
4526 err = NULL;
4527 view_close(blame_view);
4528 } else
4529 *new_view = blame_view;
4530 done:
4531 free(path);
4532 return err;
4535 static const struct got_error *
4536 log_tree_entry(struct tog_view **new_view, int begin_x,
4537 struct got_tree_entry *te, struct tog_parent_trees *parents,
4538 struct got_object_id *commit_id, struct got_reflist_head *refs,
4539 struct got_repository *repo)
4541 struct tog_view *log_view;
4542 const struct got_error *err = NULL;
4543 char *path;
4545 *new_view = NULL;
4547 log_view = view_open(0, 0, 0, begin_x, TOG_VIEW_LOG);
4548 if (log_view == NULL)
4549 return got_error_from_errno("view_open");
4551 err = tree_entry_path(&path, parents, te);
4552 if (err)
4553 return err;
4555 err = open_log_view(log_view, commit_id, refs, repo, NULL, path, 0, 0);
4556 if (err)
4557 view_close(log_view);
4558 else
4559 *new_view = log_view;
4560 free(path);
4561 return err;
4564 static const struct got_error *
4565 open_tree_view(struct tog_view *view, struct got_tree_object *root,
4566 struct got_object_id *commit_id, struct got_reflist_head *refs,
4567 struct got_repository *repo)
4569 const struct got_error *err = NULL;
4570 char *commit_id_str = NULL;
4571 struct tog_tree_view_state *s = &view->state.tree;
4573 TAILQ_INIT(&s->parents);
4575 err = got_object_id_str(&commit_id_str, commit_id);
4576 if (err != NULL)
4577 goto done;
4579 if (asprintf(&s->tree_label, "commit %s", commit_id_str) == -1) {
4580 err = got_error_from_errno("asprintf");
4581 goto done;
4584 s->root = s->tree = root;
4585 s->first_displayed_entry = got_object_tree_get_entry(s->tree, 0);
4586 s->selected_entry = got_object_tree_get_entry(s->tree, 0);
4587 s->commit_id = got_object_id_dup(commit_id);
4588 if (s->commit_id == NULL) {
4589 err = got_error_from_errno("got_object_id_dup");
4590 goto done;
4592 s->refs = refs;
4593 s->repo = repo;
4595 SIMPLEQ_INIT(&s->colors);
4597 if (has_colors() && getenv("TOG_COLORS") != NULL) {
4598 err = add_color(&s->colors, "\\$$",
4599 TOG_COLOR_TREE_SUBMODULE,
4600 get_color_value("TOG_COLOR_TREE_SUBMODULE"));
4601 if (err)
4602 goto done;
4603 err = add_color(&s->colors, "@$", TOG_COLOR_TREE_SYMLINK,
4604 get_color_value("TOG_COLOR_TREE_SYMLINK"));
4605 if (err) {
4606 free_colors(&s->colors);
4607 goto done;
4609 err = add_color(&s->colors, "/$",
4610 TOG_COLOR_TREE_DIRECTORY,
4611 get_color_value("TOG_COLOR_TREE_DIRECTORY"));
4612 if (err) {
4613 free_colors(&s->colors);
4614 goto done;
4617 err = add_color(&s->colors, "\\*$",
4618 TOG_COLOR_TREE_EXECUTABLE,
4619 get_color_value("TOG_COLOR_TREE_EXECUTABLE"));
4620 if (err) {
4621 free_colors(&s->colors);
4622 goto done;
4625 err = add_color(&s->colors, "^$", TOG_COLOR_COMMIT,
4626 get_color_value("TOG_COLOR_COMMIT"));
4627 if (err) {
4628 free_colors(&s->colors);
4629 goto done;
4633 view->show = show_tree_view;
4634 view->input = input_tree_view;
4635 view->close = close_tree_view;
4636 view->search_start = search_start_tree_view;
4637 view->search_next = search_next_tree_view;
4638 done:
4639 free(commit_id_str);
4640 if (err) {
4641 free(s->tree_label);
4642 s->tree_label = NULL;
4644 return err;
4647 static const struct got_error *
4648 close_tree_view(struct tog_view *view)
4650 struct tog_tree_view_state *s = &view->state.tree;
4652 free_colors(&s->colors);
4653 free(s->tree_label);
4654 s->tree_label = NULL;
4655 free(s->commit_id);
4656 s->commit_id = NULL;
4657 while (!TAILQ_EMPTY(&s->parents)) {
4658 struct tog_parent_tree *parent;
4659 parent = TAILQ_FIRST(&s->parents);
4660 TAILQ_REMOVE(&s->parents, parent, entry);
4661 free(parent);
4664 if (s->tree != s->root)
4665 got_object_tree_close(s->tree);
4666 got_object_tree_close(s->root);
4668 return NULL;
4671 static const struct got_error *
4672 search_start_tree_view(struct tog_view *view)
4674 struct tog_tree_view_state *s = &view->state.tree;
4676 s->matched_entry = NULL;
4677 return NULL;
4680 static int
4681 match_tree_entry(struct got_tree_entry *te, regex_t *regex)
4683 regmatch_t regmatch;
4685 return regexec(regex, got_tree_entry_get_name(te), 1, &regmatch,
4686 0) == 0;
4689 static const struct got_error *
4690 search_next_tree_view(struct tog_view *view)
4692 struct tog_tree_view_state *s = &view->state.tree;
4693 struct got_tree_entry *te = NULL;
4695 if (!view->searching) {
4696 view->search_next_done = 1;
4697 return NULL;
4700 if (s->matched_entry) {
4701 if (view->searching == TOG_SEARCH_FORWARD) {
4702 if (s->selected_entry)
4703 te = got_tree_entry_get_next(s->tree,
4704 s->selected_entry);
4705 else
4706 te = got_object_tree_get_first_entry(s->tree);
4707 } else {
4708 if (s->selected_entry == NULL)
4709 te = got_object_tree_get_last_entry(s->tree);
4710 else
4711 te = got_tree_entry_get_prev(s->tree,
4712 s->selected_entry);
4714 } else {
4715 if (view->searching == TOG_SEARCH_FORWARD)
4716 te = got_object_tree_get_first_entry(s->tree);
4717 else
4718 te = got_object_tree_get_last_entry(s->tree);
4721 while (1) {
4722 if (te == NULL) {
4723 if (s->matched_entry == NULL) {
4724 view->search_next_done = 1;
4725 return NULL;
4727 if (view->searching == TOG_SEARCH_FORWARD)
4728 te = got_object_tree_get_first_entry(s->tree);
4729 else
4730 te = got_object_tree_get_last_entry(s->tree);
4733 if (match_tree_entry(te, &view->regex)) {
4734 view->search_next_done = 1;
4735 s->matched_entry = te;
4736 break;
4739 if (view->searching == TOG_SEARCH_FORWARD)
4740 te = got_tree_entry_get_next(s->tree, te);
4741 else
4742 te = got_tree_entry_get_prev(s->tree, te);
4745 if (s->matched_entry) {
4746 s->first_displayed_entry = s->matched_entry;
4747 s->selected = 0;
4750 return NULL;
4753 static const struct got_error *
4754 show_tree_view(struct tog_view *view)
4756 const struct got_error *err = NULL;
4757 struct tog_tree_view_state *s = &view->state.tree;
4758 char *parent_path;
4760 err = tree_entry_path(&parent_path, &s->parents, NULL);
4761 if (err)
4762 return err;
4764 err = draw_tree_entries(view, &s->first_displayed_entry,
4765 &s->last_displayed_entry, &s->selected_entry,
4766 &s->ndisplayed, s->tree_label, s->show_ids, parent_path,
4767 s->tree, s->selected, view->nlines, s->tree == s->root,
4768 &s->colors);
4769 free(parent_path);
4771 view_vborder(view);
4772 return err;
4775 static const struct got_error *
4776 input_tree_view(struct tog_view **new_view, struct tog_view **dead_view,
4777 struct tog_view **focus_view, struct tog_view *view, int ch)
4779 const struct got_error *err = NULL;
4780 struct tog_tree_view_state *s = &view->state.tree;
4781 struct tog_view *log_view;
4782 int begin_x = 0, nscrolled;
4784 switch (ch) {
4785 case 'i':
4786 s->show_ids = !s->show_ids;
4787 break;
4788 case 'l':
4789 if (!s->selected_entry)
4790 break;
4791 if (view_is_parent_view(view))
4792 begin_x = view_split_begin_x(view->begin_x);
4793 err = log_tree_entry(&log_view, begin_x,
4794 s->selected_entry, &s->parents,
4795 s->commit_id, s->refs, s->repo);
4796 if (view_is_parent_view(view)) {
4797 err = view_close_child(view);
4798 if (err)
4799 return err;
4800 err = view_set_child(view, log_view);
4801 if (err) {
4802 view_close(log_view);
4803 break;
4805 *focus_view = log_view;
4806 view->child_focussed = 1;
4807 } else
4808 *new_view = log_view;
4809 break;
4810 case 'k':
4811 case KEY_UP:
4812 if (s->selected > 0) {
4813 s->selected--;
4814 if (s->selected == 0)
4815 break;
4817 if (s->selected > 0)
4818 break;
4819 tree_scroll_up(view, &s->first_displayed_entry, 1,
4820 s->tree, s->tree == s->root);
4821 break;
4822 case KEY_PPAGE:
4823 tree_scroll_up(view, &s->first_displayed_entry,
4824 MAX(0, view->nlines - 4 - s->selected), s->tree,
4825 s->tree == s->root);
4826 s->selected = 0;
4827 if (got_object_tree_get_first_entry(s->tree) ==
4828 s->first_displayed_entry && s->tree != s->root)
4829 s->first_displayed_entry = NULL;
4830 break;
4831 case 'j':
4832 case KEY_DOWN:
4833 if (s->selected < s->ndisplayed - 1) {
4834 s->selected++;
4835 break;
4837 if (got_tree_entry_get_next(s->tree, s->last_displayed_entry)
4838 == NULL)
4839 /* can't scroll any further */
4840 break;
4841 tree_scroll_down(&s->first_displayed_entry, 1,
4842 s->last_displayed_entry, s->tree);
4843 break;
4844 case KEY_NPAGE:
4845 if (got_tree_entry_get_next(s->tree, s->last_displayed_entry)
4846 == NULL) {
4847 /* can't scroll any further; move cursor down */
4848 if (s->selected < s->ndisplayed - 1)
4849 s->selected = s->ndisplayed - 1;
4850 break;
4852 nscrolled = tree_scroll_down(&s->first_displayed_entry,
4853 view->nlines, s->last_displayed_entry, s->tree);
4854 if (nscrolled < view->nlines) {
4855 int ndisplayed = 0;
4856 struct got_tree_entry *te;
4857 te = s->first_displayed_entry;
4858 do {
4859 ndisplayed++;
4860 te = got_tree_entry_get_next(s->tree, te);
4861 } while (te);
4862 s->selected = ndisplayed - 1;
4864 break;
4865 case KEY_ENTER:
4866 case '\r':
4867 case KEY_BACKSPACE:
4868 if (s->selected_entry == NULL || ch == KEY_BACKSPACE) {
4869 struct tog_parent_tree *parent;
4870 /* user selected '..' */
4871 if (s->tree == s->root)
4872 break;
4873 parent = TAILQ_FIRST(&s->parents);
4874 TAILQ_REMOVE(&s->parents, parent,
4875 entry);
4876 got_object_tree_close(s->tree);
4877 s->tree = parent->tree;
4878 s->first_displayed_entry =
4879 parent->first_displayed_entry;
4880 s->selected_entry =
4881 parent->selected_entry;
4882 s->selected = parent->selected;
4883 free(parent);
4884 } else if (S_ISDIR(got_tree_entry_get_mode(
4885 s->selected_entry))) {
4886 struct got_tree_object *subtree;
4887 err = got_object_open_as_tree(&subtree, s->repo,
4888 got_tree_entry_get_id(s->selected_entry));
4889 if (err)
4890 break;
4891 err = tree_view_visit_subtree(subtree, s);
4892 if (err) {
4893 got_object_tree_close(subtree);
4894 break;
4896 } else if (S_ISREG(got_tree_entry_get_mode(
4897 s->selected_entry))) {
4898 struct tog_view *blame_view;
4899 int begin_x = view_is_parent_view(view) ?
4900 view_split_begin_x(view->begin_x) : 0;
4902 err = blame_tree_entry(&blame_view, begin_x,
4903 s->selected_entry, &s->parents,
4904 s->commit_id, s->refs, s->repo);
4905 if (err)
4906 break;
4907 if (view_is_parent_view(view)) {
4908 err = view_close_child(view);
4909 if (err)
4910 return err;
4911 err = view_set_child(view, blame_view);
4912 if (err) {
4913 view_close(blame_view);
4914 break;
4916 *focus_view = blame_view;
4917 view->child_focussed = 1;
4918 } else
4919 *new_view = blame_view;
4921 break;
4922 case KEY_RESIZE:
4923 if (s->selected > view->nlines)
4924 s->selected = s->ndisplayed - 1;
4925 break;
4926 default:
4927 break;
4930 return err;
4933 __dead static void
4934 usage_tree(void)
4936 endwin();
4937 fprintf(stderr, "usage: %s tree [-c commit] [repository-path]\n",
4938 getprogname());
4939 exit(1);
4942 static const struct got_error *
4943 cmd_tree(int argc, char *argv[])
4945 const struct got_error *error;
4946 struct got_repository *repo = NULL;
4947 struct got_reflist_head refs;
4948 char *repo_path = NULL;
4949 struct got_object_id *commit_id = NULL;
4950 char *commit_id_arg = NULL;
4951 struct got_commit_object *commit = NULL;
4952 struct got_tree_object *tree = NULL;
4953 int ch;
4954 struct tog_view *view;
4956 SIMPLEQ_INIT(&refs);
4958 #ifndef PROFILE
4959 if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd unveil",
4960 NULL) == -1)
4961 err(1, "pledge");
4962 #endif
4964 while ((ch = getopt(argc, argv, "c:")) != -1) {
4965 switch (ch) {
4966 case 'c':
4967 commit_id_arg = optarg;
4968 break;
4969 default:
4970 usage_tree();
4971 /* NOTREACHED */
4975 argc -= optind;
4976 argv += optind;
4978 if (argc == 0) {
4979 struct got_worktree *worktree;
4980 char *cwd = getcwd(NULL, 0);
4981 if (cwd == NULL)
4982 return got_error_from_errno("getcwd");
4983 error = got_worktree_open(&worktree, cwd);
4984 if (error && error->code != GOT_ERR_NOT_WORKTREE)
4985 goto done;
4986 if (worktree) {
4987 free(cwd);
4988 repo_path =
4989 strdup(got_worktree_get_repo_path(worktree));
4990 got_worktree_close(worktree);
4991 } else
4992 repo_path = cwd;
4993 if (repo_path == NULL) {
4994 error = got_error_from_errno("strdup");
4995 goto done;
4997 } else if (argc == 1) {
4998 repo_path = realpath(argv[0], NULL);
4999 if (repo_path == NULL)
5000 return got_error_from_errno2("realpath", argv[0]);
5001 } else
5002 usage_tree();
5004 init_curses();
5006 error = got_repo_open(&repo, repo_path, NULL);
5007 if (error != NULL)
5008 goto done;
5010 error = apply_unveil(got_repo_get_path(repo), NULL);
5011 if (error)
5012 goto done;
5014 error = got_repo_match_object_id(&commit_id, NULL,
5015 commit_id_arg ? commit_id_arg : GOT_REF_HEAD,
5016 GOT_OBJ_TYPE_COMMIT, 1, repo);
5017 if (error != NULL)
5018 goto done;
5020 error = got_object_open_as_commit(&commit, repo, commit_id);
5021 if (error != NULL)
5022 goto done;
5024 error = got_object_open_as_tree(&tree, repo,
5025 got_object_commit_get_tree_id(commit));
5026 if (error != NULL)
5027 goto done;
5029 error = got_ref_list(&refs, repo, NULL, got_ref_cmp_by_name, NULL);
5030 if (error)
5031 goto done;
5033 view = view_open(0, 0, 0, 0, TOG_VIEW_TREE);
5034 if (view == NULL) {
5035 error = got_error_from_errno("view_open");
5036 goto done;
5038 error = open_tree_view(view, tree, commit_id, &refs, repo);
5039 if (error)
5040 goto done;
5041 error = view_loop(view);
5042 done:
5043 free(repo_path);
5044 free(commit_id);
5045 if (commit)
5046 got_object_commit_close(commit);
5047 if (tree)
5048 got_object_tree_close(tree);
5049 if (repo)
5050 got_repo_close(repo);
5051 got_ref_list_free(&refs);
5052 return error;
5055 static void
5056 list_commands(void)
5058 int i;
5060 fprintf(stderr, "commands:");
5061 for (i = 0; i < nitems(tog_commands); i++) {
5062 struct tog_cmd *cmd = &tog_commands[i];
5063 fprintf(stderr, " %s", cmd->name);
5065 fputc('\n', stderr);
5068 __dead static void
5069 usage(int hflag)
5071 fprintf(stderr, "usage: %s [-h] [-V | --version] [command] [arg ...]\n",
5072 getprogname());
5073 if (hflag)
5074 list_commands();
5075 exit(1);
5078 static char **
5079 make_argv(const char *arg0, const char *arg1)
5081 char **argv;
5082 int argc = (arg1 == NULL ? 1 : 2);
5084 argv = calloc(argc, sizeof(char *));
5085 if (argv == NULL)
5086 err(1, "calloc");
5087 argv[0] = strdup(arg0);
5088 if (argv[0] == NULL)
5089 err(1, "strdup");
5090 if (arg1) {
5091 argv[1] = strdup(arg1);
5092 if (argv[1] == NULL)
5093 err(1, "strdup");
5096 return argv;
5099 int
5100 main(int argc, char *argv[])
5102 const struct got_error *error = NULL;
5103 struct tog_cmd *cmd = NULL;
5104 int ch, hflag = 0, Vflag = 0;
5105 char **cmd_argv = NULL;
5106 static struct option longopts[] = {
5107 { "version", no_argument, NULL, 'V' },
5108 { NULL, 0, NULL, 0}
5111 setlocale(LC_CTYPE, "");
5113 while ((ch = getopt_long(argc, argv, "+hV", longopts, NULL)) != -1) {
5114 switch (ch) {
5115 case 'h':
5116 hflag = 1;
5117 break;
5118 case 'V':
5119 Vflag = 1;
5120 break;
5121 default:
5122 usage(hflag);
5123 /* NOTREACHED */
5127 argc -= optind;
5128 argv += optind;
5129 optind = 0;
5130 optreset = 1;
5132 if (Vflag) {
5133 got_version_print_str();
5134 return 1;
5137 if (argc == 0) {
5138 if (hflag)
5139 usage(hflag);
5140 /* Build an argument vector which runs a default command. */
5141 cmd = &tog_commands[0];
5142 cmd_argv = make_argv(cmd->name, NULL);
5143 argc = 1;
5144 } else {
5145 int i;
5147 /* Did the user specific a command? */
5148 for (i = 0; i < nitems(tog_commands); i++) {
5149 if (strncmp(tog_commands[i].name, argv[0],
5150 strlen(argv[0])) == 0) {
5151 cmd = &tog_commands[i];
5152 break;
5156 if (cmd == NULL) {
5157 fprintf(stderr, "%s: unknown command '%s'\n",
5158 getprogname(), argv[0]);
5159 list_commands();
5160 return 1;
5164 if (hflag)
5165 cmd->cmd_usage();
5166 else
5167 error = cmd->cmd_main(argc, cmd_argv ? cmd_argv : argv);
5169 endwin();
5170 free(cmd_argv);
5171 if (error && error->code != GOT_ERR_CANCELLED)
5172 fprintf(stderr, "%s: %s\n", getprogname(), error->msg);
5173 return 0;