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 <ctype.h>
22 #include <errno.h>
23 #define _XOPEN_SOURCE_EXTENDED
24 #include <curses.h>
25 #undef _XOPEN_SOURCE_EXTENDED
26 #include <panel.h>
27 #include <locale.h>
28 #include <signal.h>
29 #include <stdlib.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <getopt.h>
33 #include <string.h>
34 #include <err.h>
35 #include <unistd.h>
36 #include <util.h>
37 #include <limits.h>
38 #include <wchar.h>
39 #include <time.h>
40 #include <pthread.h>
41 #include <libgen.h>
42 #include <regex.h>
44 #include "got_version.h"
45 #include "got_error.h"
46 #include "got_object.h"
47 #include "got_reference.h"
48 #include "got_repository.h"
49 #include "got_diff.h"
50 #include "got_opentemp.h"
51 #include "got_utf8.h"
52 #include "got_cancel.h"
53 #include "got_commit_graph.h"
54 #include "got_blame.h"
55 #include "got_privsep.h"
56 #include "got_path.h"
57 #include "got_worktree.h"
59 #ifndef MIN
60 #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b))
61 #endif
63 #ifndef MAX
64 #define MAX(_a,_b) ((_a) > (_b) ? (_a) : (_b))
65 #endif
67 #define CTRL(x) ((x) & 0x1f)
69 #ifndef nitems
70 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
71 #endif
73 struct tog_cmd {
74 const char *name;
75 const struct got_error *(*cmd_main)(int, char *[]);
76 void (*cmd_usage)(void);
77 };
79 __dead static void usage(int, int);
80 __dead static void usage_log(void);
81 __dead static void usage_diff(void);
82 __dead static void usage_blame(void);
83 __dead static void usage_tree(void);
84 __dead static void usage_ref(void);
86 static const struct got_error* cmd_log(int, char *[]);
87 static const struct got_error* cmd_diff(int, char *[]);
88 static const struct got_error* cmd_blame(int, char *[]);
89 static const struct got_error* cmd_tree(int, char *[]);
90 static const struct got_error* cmd_ref(int, char *[]);
92 static struct tog_cmd tog_commands[] = {
93 { "log", cmd_log, usage_log },
94 { "diff", cmd_diff, usage_diff },
95 { "blame", cmd_blame, usage_blame },
96 { "tree", cmd_tree, usage_tree },
97 { "ref", cmd_ref, usage_ref },
98 };
100 enum tog_view_type {
101 TOG_VIEW_DIFF,
102 TOG_VIEW_LOG,
103 TOG_VIEW_BLAME,
104 TOG_VIEW_TREE,
105 TOG_VIEW_REF,
106 };
108 #define TOG_EOF_STRING "(END)"
110 struct commit_queue_entry {
111 TAILQ_ENTRY(commit_queue_entry) entry;
112 struct got_object_id *id;
113 struct got_commit_object *commit;
114 int idx;
115 };
116 TAILQ_HEAD(commit_queue_head, commit_queue_entry);
117 struct commit_queue {
118 int ncommits;
119 struct commit_queue_head head;
120 };
122 struct tog_color {
123 SIMPLEQ_ENTRY(tog_color) entry;
124 regex_t regex;
125 short colorpair;
126 };
127 SIMPLEQ_HEAD(tog_colors, tog_color);
129 static const struct got_error *
130 add_color(struct tog_colors *colors, const char *pattern,
131 int idx, short color)
133 const struct got_error *err = NULL;
134 struct tog_color *tc;
135 int regerr = 0;
137 if (idx < 1 || idx > COLOR_PAIRS - 1)
138 return NULL;
140 init_pair(idx, color, -1);
142 tc = calloc(1, sizeof(*tc));
143 if (tc == NULL)
144 return got_error_from_errno("calloc");
145 regerr = regcomp(&tc->regex, pattern,
146 REG_EXTENDED | REG_NOSUB | REG_NEWLINE);
147 if (regerr) {
148 static char regerr_msg[512];
149 static char err_msg[512];
150 regerror(regerr, &tc->regex, regerr_msg,
151 sizeof(regerr_msg));
152 snprintf(err_msg, sizeof(err_msg), "regcomp: %s",
153 regerr_msg);
154 err = got_error_msg(GOT_ERR_REGEX, err_msg);
155 free(tc);
156 return err;
158 tc->colorpair = idx;
159 SIMPLEQ_INSERT_HEAD(colors, tc, entry);
160 return NULL;
163 static void
164 free_colors(struct tog_colors *colors)
166 struct tog_color *tc;
168 while (!SIMPLEQ_EMPTY(colors)) {
169 tc = SIMPLEQ_FIRST(colors);
170 SIMPLEQ_REMOVE_HEAD(colors, entry);
171 regfree(&tc->regex);
172 free(tc);
176 struct tog_color *
177 get_color(struct tog_colors *colors, int colorpair)
179 struct tog_color *tc = NULL;
181 SIMPLEQ_FOREACH(tc, colors, entry) {
182 if (tc->colorpair == colorpair)
183 return tc;
186 return NULL;
189 static int
190 default_color_value(const char *envvar)
192 if (strcmp(envvar, "TOG_COLOR_DIFF_MINUS") == 0)
193 return COLOR_MAGENTA;
194 if (strcmp(envvar, "TOG_COLOR_DIFF_PLUS") == 0)
195 return COLOR_CYAN;
196 if (strcmp(envvar, "TOG_COLOR_DIFF_CHUNK_HEADER") == 0)
197 return COLOR_YELLOW;
198 if (strcmp(envvar, "TOG_COLOR_DIFF_META") == 0)
199 return COLOR_GREEN;
200 if (strcmp(envvar, "TOG_COLOR_TREE_SUBMODULE") == 0)
201 return COLOR_MAGENTA;
202 if (strcmp(envvar, "TOG_COLOR_TREE_SYMLINK") == 0)
203 return COLOR_MAGENTA;
204 if (strcmp(envvar, "TOG_COLOR_TREE_DIRECTORY") == 0)
205 return COLOR_CYAN;
206 if (strcmp(envvar, "TOG_COLOR_TREE_EXECUTABLE") == 0)
207 return COLOR_GREEN;
208 if (strcmp(envvar, "TOG_COLOR_COMMIT") == 0)
209 return COLOR_GREEN;
210 if (strcmp(envvar, "TOG_COLOR_AUTHOR") == 0)
211 return COLOR_CYAN;
212 if (strcmp(envvar, "TOG_COLOR_DATE") == 0)
213 return COLOR_YELLOW;
214 if (strcmp(envvar, "TOG_COLOR_REFS_HEADS") == 0)
215 return COLOR_GREEN;
216 if (strcmp(envvar, "TOG_COLOR_REFS_TAGS") == 0)
217 return COLOR_MAGENTA;
218 if (strcmp(envvar, "TOG_COLOR_REFS_REMOTES") == 0)
219 return COLOR_YELLOW;
221 return -1;
224 static int
225 get_color_value(const char *envvar)
227 const char *val = getenv(envvar);
229 if (val == NULL)
230 return default_color_value(envvar);
232 if (strcasecmp(val, "black") == 0)
233 return COLOR_BLACK;
234 if (strcasecmp(val, "red") == 0)
235 return COLOR_RED;
236 if (strcasecmp(val, "green") == 0)
237 return COLOR_GREEN;
238 if (strcasecmp(val, "yellow") == 0)
239 return COLOR_YELLOW;
240 if (strcasecmp(val, "blue") == 0)
241 return COLOR_BLUE;
242 if (strcasecmp(val, "magenta") == 0)
243 return COLOR_MAGENTA;
244 if (strcasecmp(val, "cyan") == 0)
245 return COLOR_CYAN;
246 if (strcasecmp(val, "white") == 0)
247 return COLOR_WHITE;
248 if (strcasecmp(val, "default") == 0)
249 return -1;
251 return default_color_value(envvar);
255 struct tog_diff_view_state {
256 struct got_object_id *id1, *id2;
257 const char *label1, *label2;
258 FILE *f;
259 int first_displayed_line;
260 int last_displayed_line;
261 int eof;
262 int diff_context;
263 int ignore_whitespace;
264 int force_text_diff;
265 struct got_repository *repo;
266 struct got_reflist_head refs;
267 struct tog_colors colors;
268 size_t nlines;
269 off_t *line_offsets;
270 int matched_line;
271 int selected_line;
273 /* passed from log view; may be NULL */
274 struct tog_view *log_view;
275 };
277 pthread_mutex_t tog_mutex = PTHREAD_MUTEX_INITIALIZER;
279 struct tog_log_thread_args {
280 pthread_cond_t need_commits;
281 pthread_cond_t commit_loaded;
282 int commits_needed;
283 struct got_commit_graph *graph;
284 struct commit_queue *commits;
285 const char *in_repo_path;
286 struct got_object_id *start_id;
287 struct got_repository *repo;
288 int log_complete;
289 sig_atomic_t *quit;
290 struct commit_queue_entry **first_displayed_entry;
291 struct commit_queue_entry **selected_entry;
292 int *searching;
293 int *search_next_done;
294 regex_t *regex;
295 };
297 struct tog_log_view_state {
298 struct commit_queue commits;
299 struct commit_queue_entry *first_displayed_entry;
300 struct commit_queue_entry *last_displayed_entry;
301 struct commit_queue_entry *selected_entry;
302 int selected;
303 char *in_repo_path;
304 const char *head_ref_name;
305 int log_branches;
306 struct got_repository *repo;
307 struct got_reflist_head refs;
308 struct got_object_id *start_id;
309 sig_atomic_t quit;
310 pthread_t thread;
311 struct tog_log_thread_args thread_args;
312 struct commit_queue_entry *matched_entry;
313 struct commit_queue_entry *search_entry;
314 struct tog_colors colors;
315 };
317 #define TOG_COLOR_DIFF_MINUS 1
318 #define TOG_COLOR_DIFF_PLUS 2
319 #define TOG_COLOR_DIFF_CHUNK_HEADER 3
320 #define TOG_COLOR_DIFF_META 4
321 #define TOG_COLOR_TREE_SUBMODULE 5
322 #define TOG_COLOR_TREE_SYMLINK 6
323 #define TOG_COLOR_TREE_DIRECTORY 7
324 #define TOG_COLOR_TREE_EXECUTABLE 8
325 #define TOG_COLOR_COMMIT 9
326 #define TOG_COLOR_AUTHOR 10
327 #define TOG_COLOR_DATE 11
328 #define TOG_COLOR_REFS_HEADS 12
329 #define TOG_COLOR_REFS_TAGS 13
330 #define TOG_COLOR_REFS_REMOTES 14
332 struct tog_blame_cb_args {
333 struct tog_blame_line *lines; /* one per line */
334 int nlines;
336 struct tog_view *view;
337 struct got_object_id *commit_id;
338 int *quit;
339 };
341 struct tog_blame_thread_args {
342 const char *path;
343 struct got_repository *repo;
344 struct tog_blame_cb_args *cb_args;
345 int *complete;
346 got_cancel_cb cancel_cb;
347 void *cancel_arg;
348 };
350 struct tog_blame {
351 FILE *f;
352 off_t filesize;
353 struct tog_blame_line *lines;
354 int nlines;
355 off_t *line_offsets;
356 pthread_t thread;
357 struct tog_blame_thread_args thread_args;
358 struct tog_blame_cb_args cb_args;
359 const char *path;
360 };
362 struct tog_blame_view_state {
363 int first_displayed_line;
364 int last_displayed_line;
365 int selected_line;
366 int blame_complete;
367 int eof;
368 int done;
369 struct got_object_id_queue blamed_commits;
370 struct got_object_qid *blamed_commit;
371 char *path;
372 struct got_repository *repo;
373 struct got_object_id *commit_id;
374 struct tog_blame blame;
375 int matched_line;
376 struct tog_colors colors;
377 };
379 struct tog_parent_tree {
380 TAILQ_ENTRY(tog_parent_tree) entry;
381 struct got_tree_object *tree;
382 struct got_tree_entry *first_displayed_entry;
383 struct got_tree_entry *selected_entry;
384 int selected;
385 };
387 TAILQ_HEAD(tog_parent_trees, tog_parent_tree);
389 struct tog_tree_view_state {
390 char *tree_label;
391 struct got_tree_object *root;
392 struct got_tree_object *tree;
393 struct got_tree_entry *first_displayed_entry;
394 struct got_tree_entry *last_displayed_entry;
395 struct got_tree_entry *selected_entry;
396 int ndisplayed, selected, show_ids;
397 struct tog_parent_trees parents;
398 struct got_object_id *commit_id;
399 struct got_repository *repo;
400 struct got_tree_entry *matched_entry;
401 struct tog_colors colors;
402 };
404 struct tog_reflist_entry {
405 TAILQ_ENTRY(tog_reflist_entry) entry;
406 struct got_reference *ref;
407 int idx;
408 };
410 TAILQ_HEAD(tog_reflist_head, tog_reflist_entry);
412 struct tog_ref_view_state {
413 struct got_reflist_head simplerefs; /* SIMPLEQ */
414 struct tog_reflist_head refs; /* TAILQ */
415 struct tog_reflist_entry *first_displayed_entry;
416 struct tog_reflist_entry *last_displayed_entry;
417 struct tog_reflist_entry *selected_entry;
418 int nrefs, ndisplayed, selected, show_ids;
419 struct got_repository *repo;
420 struct tog_reflist_entry *matched_entry;
421 struct tog_colors colors;
422 };
424 /*
425 * We implement two types of views: parent views and child views.
427 * The 'Tab' key switches focus between a parent view and its child view.
428 * Child views are shown side-by-side to their parent view, provided
429 * there is enough screen estate.
431 * When a new view is opened from within a parent view, this new view
432 * becomes a child view of the parent view, replacing any existing child.
434 * When a new view is opened from within a child view, this new view
435 * becomes a parent view which will obscure the views below until the
436 * user quits the new parent view by typing 'q'.
438 * This list of views contains parent views only.
439 * Child views are only pointed to by their parent view.
440 */
441 TAILQ_HEAD(tog_view_list_head, tog_view);
443 struct tog_view {
444 TAILQ_ENTRY(tog_view) entry;
445 WINDOW *window;
446 PANEL *panel;
447 int nlines, ncols, begin_y, begin_x;
448 int lines, cols; /* copies of LINES and COLS */
449 int focussed; /* Only set on one parent or child view at a time. */
450 int dying;
451 struct tog_view *parent;
452 struct tog_view *child;
454 /*
455 * This flag is initially set on parent views when a new child view
456 * is created. It gets toggled when the 'Tab' key switches focus
457 * between parent and child.
458 * The flag indicates whether focus should be passed on to our child
459 * view if this parent view gets picked for focus after another parent
460 * view was closed. This prevents child views from losing focus in such
461 * situations.
462 */
463 int focus_child;
465 /* type-specific state */
466 enum tog_view_type type;
467 union {
468 struct tog_diff_view_state diff;
469 struct tog_log_view_state log;
470 struct tog_blame_view_state blame;
471 struct tog_tree_view_state tree;
472 struct tog_ref_view_state ref;
473 } state;
475 const struct got_error *(*show)(struct tog_view *);
476 const struct got_error *(*input)(struct tog_view **,
477 struct tog_view *, int);
478 const struct got_error *(*close)(struct tog_view *);
480 const struct got_error *(*search_start)(struct tog_view *);
481 const struct got_error *(*search_next)(struct tog_view *);
482 int searching;
483 #define TOG_SEARCH_FORWARD 1
484 #define TOG_SEARCH_BACKWARD 2
485 int search_next_done;
486 #define TOG_SEARCH_HAVE_MORE 1
487 #define TOG_SEARCH_NO_MORE 2
488 #define TOG_SEARCH_HAVE_NONE 3
489 regex_t regex;
490 regmatch_t regmatch;
491 };
493 static const struct got_error *open_diff_view(struct tog_view *,
494 struct got_object_id *, struct got_object_id *,
495 const char *, const char *, int, int, int, struct tog_view *,
496 struct got_repository *);
497 static const struct got_error *show_diff_view(struct tog_view *);
498 static const struct got_error *input_diff_view(struct tog_view **,
499 struct tog_view *, int);
500 static const struct got_error* close_diff_view(struct tog_view *);
501 static const struct got_error *search_start_diff_view(struct tog_view *);
502 static const struct got_error *search_next_diff_view(struct tog_view *);
504 static const struct got_error *open_log_view(struct tog_view *,
505 struct got_object_id *, struct got_repository *,
506 const char *, const char *, int);
507 static const struct got_error * show_log_view(struct tog_view *);
508 static const struct got_error *input_log_view(struct tog_view **,
509 struct tog_view *, int);
510 static const struct got_error *close_log_view(struct tog_view *);
511 static const struct got_error *search_start_log_view(struct tog_view *);
512 static const struct got_error *search_next_log_view(struct tog_view *);
514 static const struct got_error *open_blame_view(struct tog_view *, char *,
515 struct got_object_id *, struct got_repository *);
516 static const struct got_error *show_blame_view(struct tog_view *);
517 static const struct got_error *input_blame_view(struct tog_view **,
518 struct tog_view *, int);
519 static const struct got_error *close_blame_view(struct tog_view *);
520 static const struct got_error *search_start_blame_view(struct tog_view *);
521 static const struct got_error *search_next_blame_view(struct tog_view *);
523 static const struct got_error *open_tree_view(struct tog_view *,
524 struct got_tree_object *, struct got_object_id *, struct got_repository *);
525 static const struct got_error *show_tree_view(struct tog_view *);
526 static const struct got_error *input_tree_view(struct tog_view **,
527 struct tog_view *, int);
528 static const struct got_error *close_tree_view(struct tog_view *);
529 static const struct got_error *search_start_tree_view(struct tog_view *);
530 static const struct got_error *search_next_tree_view(struct tog_view *);
532 static const struct got_error *open_ref_view(struct tog_view *,
533 struct got_repository *);
534 static const struct got_error *show_ref_view(struct tog_view *);
535 static const struct got_error *input_ref_view(struct tog_view **,
536 struct tog_view *, int);
537 static const struct got_error *close_ref_view(struct tog_view *);
538 static const struct got_error *search_start_ref_view(struct tog_view *);
539 static const struct got_error *search_next_ref_view(struct tog_view *);
541 static volatile sig_atomic_t tog_sigwinch_received;
542 static volatile sig_atomic_t tog_sigpipe_received;
543 static volatile sig_atomic_t tog_sigcont_received;
545 static void
546 tog_sigwinch(int signo)
548 tog_sigwinch_received = 1;
551 static void
552 tog_sigpipe(int signo)
554 tog_sigpipe_received = 1;
557 static void
558 tog_sigcont(int signo)
560 tog_sigcont_received = 1;
563 static const struct got_error *
564 view_close(struct tog_view *view)
566 const struct got_error *err = NULL;
568 if (view->child) {
569 view_close(view->child);
570 view->child = NULL;
572 if (view->close)
573 err = view->close(view);
574 if (view->panel)
575 del_panel(view->panel);
576 if (view->window)
577 delwin(view->window);
578 free(view);
579 return err;
582 static struct tog_view *
583 view_open(int nlines, int ncols, int begin_y, int begin_x,
584 enum tog_view_type type)
586 struct tog_view *view = calloc(1, sizeof(*view));
588 if (view == NULL)
589 return NULL;
591 view->type = type;
592 view->lines = LINES;
593 view->cols = COLS;
594 view->nlines = nlines ? nlines : LINES - begin_y;
595 view->ncols = ncols ? ncols : COLS - begin_x;
596 view->begin_y = begin_y;
597 view->begin_x = begin_x;
598 view->window = newwin(nlines, ncols, begin_y, begin_x);
599 if (view->window == NULL) {
600 view_close(view);
601 return NULL;
603 view->panel = new_panel(view->window);
604 if (view->panel == NULL ||
605 set_panel_userptr(view->panel, view) != OK) {
606 view_close(view);
607 return NULL;
610 keypad(view->window, TRUE);
611 return view;
614 static int
615 view_split_begin_x(int begin_x)
617 if (begin_x > 0 || COLS < 120)
618 return 0;
619 return (COLS - MAX(COLS / 2, 80));
622 static const struct got_error *view_resize(struct tog_view *);
624 static const struct got_error *
625 view_splitscreen(struct tog_view *view)
627 const struct got_error *err = NULL;
629 view->begin_y = 0;
630 view->begin_x = view_split_begin_x(0);
631 view->nlines = LINES;
632 view->ncols = COLS - view->begin_x;
633 view->lines = LINES;
634 view->cols = COLS;
635 err = view_resize(view);
636 if (err)
637 return err;
639 if (mvwin(view->window, view->begin_y, view->begin_x) == ERR)
640 return got_error_from_errno("mvwin");
642 return NULL;
645 static const struct got_error *
646 view_fullscreen(struct tog_view *view)
648 const struct got_error *err = NULL;
650 view->begin_x = 0;
651 view->begin_y = 0;
652 view->nlines = LINES;
653 view->ncols = COLS;
654 view->lines = LINES;
655 view->cols = COLS;
656 err = view_resize(view);
657 if (err)
658 return err;
660 if (mvwin(view->window, view->begin_y, view->begin_x) == ERR)
661 return got_error_from_errno("mvwin");
663 return NULL;
666 static int
667 view_is_parent_view(struct tog_view *view)
669 return view->parent == NULL;
672 static const struct got_error *
673 view_resize(struct tog_view *view)
675 int nlines, ncols;
677 if (view->lines > LINES)
678 nlines = view->nlines - (view->lines - LINES);
679 else
680 nlines = view->nlines + (LINES - view->lines);
682 if (view->cols > COLS)
683 ncols = view->ncols - (view->cols - COLS);
684 else
685 ncols = view->ncols + (COLS - view->cols);
687 if (wresize(view->window, nlines, ncols) == ERR)
688 return got_error_from_errno("wresize");
689 if (replace_panel(view->panel, view->window) == ERR)
690 return got_error_from_errno("replace_panel");
691 wclear(view->window);
693 view->nlines = nlines;
694 view->ncols = ncols;
695 view->lines = LINES;
696 view->cols = COLS;
698 if (view->child) {
699 view->child->begin_x = view_split_begin_x(view->begin_x);
700 if (view->child->begin_x == 0) {
701 view_fullscreen(view->child);
702 if (view->child->focussed)
703 show_panel(view->child->panel);
704 else
705 show_panel(view->panel);
706 } else {
707 view_splitscreen(view->child);
708 show_panel(view->child->panel);
712 return NULL;
715 static const struct got_error *
716 view_close_child(struct tog_view *view)
718 const struct got_error *err = NULL;
720 if (view->child == NULL)
721 return NULL;
723 err = view_close(view->child);
724 view->child = NULL;
725 return err;
728 static void
729 view_set_child(struct tog_view *view, struct tog_view *child)
731 view->child = child;
732 child->parent = view;
735 static int
736 view_is_splitscreen(struct tog_view *view)
738 return view->begin_x > 0;
741 static void
742 tog_resizeterm(void)
744 int cols, lines;
745 struct winsize size;
747 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) < 0) {
748 cols = 80; /* Default */
749 lines = 24;
750 } else {
751 cols = size.ws_col;
752 lines = size.ws_row;
754 resize_term(lines, cols);
757 static const struct got_error *
758 view_search_start(struct tog_view *view)
760 const struct got_error *err = NULL;
761 char pattern[1024];
762 int ret;
764 if (view->nlines < 1)
765 return NULL;
767 mvwaddstr(view->window, view->begin_y + view->nlines - 1, 0, "/");
768 wclrtoeol(view->window);
770 nocbreak();
771 echo();
772 ret = wgetnstr(view->window, pattern, sizeof(pattern));
773 cbreak();
774 noecho();
775 if (ret == ERR)
776 return NULL;
778 if (view->searching) {
779 regfree(&view->regex);
780 view->searching = 0;
783 if (regcomp(&view->regex, pattern, REG_EXTENDED | REG_NEWLINE) == 0) {
784 err = view->search_start(view);
785 if (err) {
786 regfree(&view->regex);
787 return err;
789 view->searching = TOG_SEARCH_FORWARD;
790 view->search_next_done = 0;
791 view->search_next(view);
794 return NULL;
797 static const struct got_error *
798 view_input(struct tog_view **new, int *done, struct tog_view *view,
799 struct tog_view_list_head *views)
801 const struct got_error *err = NULL;
802 struct tog_view *v;
803 int ch, errcode;
805 *new = NULL;
807 /* Clear "no matches" indicator. */
808 if (view->search_next_done == TOG_SEARCH_NO_MORE ||
809 view->search_next_done == TOG_SEARCH_HAVE_NONE)
810 view->search_next_done = TOG_SEARCH_HAVE_MORE;
812 if (view->searching && !view->search_next_done) {
813 errcode = pthread_mutex_unlock(&tog_mutex);
814 if (errcode)
815 return got_error_set_errno(errcode,
816 "pthread_mutex_unlock");
817 pthread_yield();
818 errcode = pthread_mutex_lock(&tog_mutex);
819 if (errcode)
820 return got_error_set_errno(errcode,
821 "pthread_mutex_lock");
822 view->search_next(view);
823 return NULL;
826 nodelay(stdscr, FALSE);
827 /* Allow threads to make progress while we are waiting for input. */
828 errcode = pthread_mutex_unlock(&tog_mutex);
829 if (errcode)
830 return got_error_set_errno(errcode, "pthread_mutex_unlock");
831 ch = wgetch(view->window);
832 errcode = pthread_mutex_lock(&tog_mutex);
833 if (errcode)
834 return got_error_set_errno(errcode, "pthread_mutex_lock");
835 nodelay(stdscr, TRUE);
837 if (tog_sigwinch_received || tog_sigcont_received) {
838 tog_resizeterm();
839 tog_sigwinch_received = 0;
840 tog_sigcont_received = 0;
841 TAILQ_FOREACH(v, views, entry) {
842 err = view_resize(v);
843 if (err)
844 return err;
845 err = v->input(new, v, KEY_RESIZE);
846 if (err)
847 return err;
851 switch (ch) {
852 case ERR:
853 break;
854 case '\t':
855 if (view->child) {
856 view->focussed = 0;
857 view->child->focussed = 1;
858 view->focus_child = 1;
859 } else if (view->parent) {
860 view->focussed = 0;
861 view->parent->focussed = 1;
862 view->parent->focus_child = 0;
864 break;
865 case 'q':
866 err = view->input(new, view, ch);
867 view->dying = 1;
868 break;
869 case 'Q':
870 *done = 1;
871 break;
872 case 'f':
873 if (view_is_parent_view(view)) {
874 if (view->child == NULL)
875 break;
876 if (view_is_splitscreen(view->child)) {
877 view->focussed = 0;
878 view->child->focussed = 1;
879 err = view_fullscreen(view->child);
880 } else
881 err = view_splitscreen(view->child);
882 if (err)
883 break;
884 err = view->child->input(new, view->child,
885 KEY_RESIZE);
886 } else {
887 if (view_is_splitscreen(view)) {
888 view->parent->focussed = 0;
889 view->focussed = 1;
890 err = view_fullscreen(view);
891 } else {
892 err = view_splitscreen(view);
894 if (err)
895 break;
896 err = view->input(new, view, KEY_RESIZE);
898 break;
899 case KEY_RESIZE:
900 break;
901 case '/':
902 if (view->search_start)
903 view_search_start(view);
904 else
905 err = view->input(new, view, ch);
906 break;
907 case 'N':
908 case 'n':
909 if (view->search_next) {
910 view->searching = (ch == 'n' ?
911 TOG_SEARCH_FORWARD : TOG_SEARCH_BACKWARD);
912 view->search_next_done = 0;
913 view->search_next(view);
914 } else
915 err = view->input(new, view, ch);
916 break;
917 default:
918 err = view->input(new, view, ch);
919 break;
922 return err;
925 void
926 view_vborder(struct tog_view *view)
928 PANEL *panel;
929 struct tog_view *view_above;
931 if (view->parent)
932 return view_vborder(view->parent);
934 panel = panel_above(view->panel);
935 if (panel == NULL)
936 return;
938 view_above = panel_userptr(panel);
939 mvwvline(view->window, view->begin_y, view_above->begin_x - 1,
940 got_locale_is_utf8() ? ACS_VLINE : '|', view->nlines);
943 int
944 view_needs_focus_indication(struct tog_view *view)
946 if (view_is_parent_view(view)) {
947 if (view->child == NULL || view->child->focussed)
948 return 0;
949 if (!view_is_splitscreen(view->child))
950 return 0;
951 } else if (!view_is_splitscreen(view))
952 return 0;
954 return view->focussed;
957 static const struct got_error *
958 view_loop(struct tog_view *view)
960 const struct got_error *err = NULL;
961 struct tog_view_list_head views;
962 struct tog_view *new_view, *main_view;
963 int fast_refresh = 10;
964 int done = 0, errcode;
966 errcode = pthread_mutex_lock(&tog_mutex);
967 if (errcode)
968 return got_error_set_errno(errcode, "pthread_mutex_lock");
970 TAILQ_INIT(&views);
971 TAILQ_INSERT_HEAD(&views, view, entry);
973 main_view = view;
974 view->focussed = 1;
975 err = view->show(view);
976 if (err)
977 return err;
978 update_panels();
979 doupdate();
980 while (!TAILQ_EMPTY(&views) && !done && !tog_sigpipe_received) {
981 /* Refresh fast during initialization, then become slower. */
982 if (fast_refresh && fast_refresh-- == 0)
983 halfdelay(10); /* switch to once per second */
985 err = view_input(&new_view, &done, view, &views);
986 if (err)
987 break;
988 if (view->dying) {
989 struct tog_view *v, *prev = NULL;
991 if (view_is_parent_view(view))
992 prev = TAILQ_PREV(view, tog_view_list_head,
993 entry);
994 else if (view->parent)
995 prev = view->parent;
997 if (view->parent) {
998 view->parent->child = NULL;
999 view->parent->focus_child = 0;
1000 } else
1001 TAILQ_REMOVE(&views, view, entry);
1003 err = view_close(view);
1004 if (err || (view == main_view && new_view == NULL))
1005 goto done;
1007 view = NULL;
1008 TAILQ_FOREACH(v, &views, entry) {
1009 if (v->focussed)
1010 break;
1012 if (view == NULL && new_view == NULL) {
1013 /* No view has focus. Try to pick one. */
1014 if (prev)
1015 view = prev;
1016 else if (!TAILQ_EMPTY(&views)) {
1017 view = TAILQ_LAST(&views,
1018 tog_view_list_head);
1020 if (view) {
1021 if (view->focus_child) {
1022 view->child->focussed = 1;
1023 view = view->child;
1024 } else
1025 view->focussed = 1;
1029 if (new_view) {
1030 struct tog_view *v, *t;
1031 /* Only allow one parent view per type. */
1032 TAILQ_FOREACH_SAFE(v, &views, entry, t) {
1033 if (v->type != new_view->type)
1034 continue;
1035 TAILQ_REMOVE(&views, v, entry);
1036 err = view_close(v);
1037 if (err)
1038 goto done;
1039 break;
1041 TAILQ_INSERT_TAIL(&views, new_view, entry);
1042 view = new_view;
1044 if (view) {
1045 if (view_is_parent_view(view)) {
1046 if (view->child && view->child->focussed)
1047 view = view->child;
1048 } else {
1049 if (view->parent && view->parent->focussed)
1050 view = view->parent;
1052 show_panel(view->panel);
1053 if (view->child && view_is_splitscreen(view->child))
1054 show_panel(view->child->panel);
1055 if (view->parent && view_is_splitscreen(view)) {
1056 err = view->parent->show(view->parent);
1057 if (err)
1058 goto done;
1060 err = view->show(view);
1061 if (err)
1062 goto done;
1063 if (view->child) {
1064 err = view->child->show(view->child);
1065 if (err)
1066 goto done;
1068 update_panels();
1069 doupdate();
1072 done:
1073 while (!TAILQ_EMPTY(&views)) {
1074 view = TAILQ_FIRST(&views);
1075 TAILQ_REMOVE(&views, view, entry);
1076 view_close(view);
1079 errcode = pthread_mutex_unlock(&tog_mutex);
1080 if (errcode && err == NULL)
1081 err = got_error_set_errno(errcode, "pthread_mutex_unlock");
1083 return err;
1086 __dead static void
1087 usage_log(void)
1089 endwin();
1090 fprintf(stderr,
1091 "usage: %s log [-b] [-c commit] [-r repository-path] [path]\n",
1092 getprogname());
1093 exit(1);
1096 /* Create newly allocated wide-character string equivalent to a byte string. */
1097 static const struct got_error *
1098 mbs2ws(wchar_t **ws, size_t *wlen, const char *s)
1100 char *vis = NULL;
1101 const struct got_error *err = NULL;
1103 *ws = NULL;
1104 *wlen = mbstowcs(NULL, s, 0);
1105 if (*wlen == (size_t)-1) {
1106 int vislen;
1107 if (errno != EILSEQ)
1108 return got_error_from_errno("mbstowcs");
1110 /* byte string invalid in current encoding; try to "fix" it */
1111 err = got_mbsavis(&vis, &vislen, s);
1112 if (err)
1113 return err;
1114 *wlen = mbstowcs(NULL, vis, 0);
1115 if (*wlen == (size_t)-1) {
1116 err = got_error_from_errno("mbstowcs"); /* give up */
1117 goto done;
1121 *ws = calloc(*wlen + 1, sizeof(**ws));
1122 if (*ws == NULL) {
1123 err = got_error_from_errno("calloc");
1124 goto done;
1127 if (mbstowcs(*ws, vis ? vis : s, *wlen) != *wlen)
1128 err = got_error_from_errno("mbstowcs");
1129 done:
1130 free(vis);
1131 if (err) {
1132 free(*ws);
1133 *ws = NULL;
1134 *wlen = 0;
1136 return err;
1139 /* Format a line for display, ensuring that it won't overflow a width limit. */
1140 static const struct got_error *
1141 format_line(wchar_t **wlinep, int *widthp, const char *line, int wlimit,
1142 int col_tab_align)
1144 const struct got_error *err = NULL;
1145 int cols = 0;
1146 wchar_t *wline = NULL;
1147 size_t wlen;
1148 int i;
1150 *wlinep = NULL;
1151 *widthp = 0;
1153 err = mbs2ws(&wline, &wlen, line);
1154 if (err)
1155 return err;
1157 i = 0;
1158 while (i < wlen) {
1159 int width = wcwidth(wline[i]);
1161 if (width == 0) {
1162 i++;
1163 continue;
1166 if (width == 1 || width == 2) {
1167 if (cols + width > wlimit)
1168 break;
1169 cols += width;
1170 i++;
1171 } else if (width == -1) {
1172 if (wline[i] == L'\t') {
1173 width = TABSIZE -
1174 ((cols + col_tab_align) % TABSIZE);
1175 if (cols + width > wlimit)
1176 break;
1177 cols += width;
1179 i++;
1180 } else {
1181 err = got_error_from_errno("wcwidth");
1182 goto done;
1185 wline[i] = L'\0';
1186 if (widthp)
1187 *widthp = cols;
1188 done:
1189 if (err)
1190 free(wline);
1191 else
1192 *wlinep = wline;
1193 return err;
1196 static const struct got_error*
1197 build_refs_str(char **refs_str, struct got_reflist_head *refs,
1198 struct got_object_id *id, struct got_repository *repo)
1200 static const struct got_error *err = NULL;
1201 struct got_reflist_entry *re;
1202 char *s;
1203 const char *name;
1205 *refs_str = NULL;
1207 SIMPLEQ_FOREACH(re, refs, entry) {
1208 struct got_tag_object *tag = NULL;
1209 struct got_object_id *ref_id;
1210 int cmp;
1212 name = got_ref_get_name(re->ref);
1213 if (strcmp(name, GOT_REF_HEAD) == 0)
1214 continue;
1215 if (strncmp(name, "refs/", 5) == 0)
1216 name += 5;
1217 if (strncmp(name, "got/", 4) == 0)
1218 continue;
1219 if (strncmp(name, "heads/", 6) == 0)
1220 name += 6;
1221 if (strncmp(name, "remotes/", 8) == 0) {
1222 name += 8;
1223 s = strstr(name, "/" GOT_REF_HEAD);
1224 if (s != NULL && s[strlen(s)] == '\0')
1225 continue;
1227 err = got_ref_resolve(&ref_id, repo, re->ref);
1228 if (err)
1229 break;
1230 if (strncmp(name, "tags/", 5) == 0) {
1231 err = got_object_open_as_tag(&tag, repo, ref_id);
1232 if (err) {
1233 if (err->code != GOT_ERR_OBJ_TYPE) {
1234 free(ref_id);
1235 break;
1237 /* Ref points at something other than a tag. */
1238 err = NULL;
1239 tag = NULL;
1242 cmp = got_object_id_cmp(tag ?
1243 got_object_tag_get_object_id(tag) : ref_id, id);
1244 free(ref_id);
1245 if (tag)
1246 got_object_tag_close(tag);
1247 if (cmp != 0)
1248 continue;
1249 s = *refs_str;
1250 if (asprintf(refs_str, "%s%s%s", s ? s : "",
1251 s ? ", " : "", name) == -1) {
1252 err = got_error_from_errno("asprintf");
1253 free(s);
1254 *refs_str = NULL;
1255 break;
1257 free(s);
1260 return err;
1263 static const struct got_error *
1264 format_author(wchar_t **wauthor, int *author_width, char *author, int limit,
1265 int col_tab_align)
1267 char *smallerthan, *at;
1269 smallerthan = strchr(author, '<');
1270 if (smallerthan && smallerthan[1] != '\0')
1271 author = smallerthan + 1;
1272 at = strchr(author, '@');
1273 if (at)
1274 *at = '\0';
1275 return format_line(wauthor, author_width, author, limit, col_tab_align);
1278 static const struct got_error *
1279 draw_commit(struct tog_view *view, struct got_commit_object *commit,
1280 struct got_object_id *id, const size_t date_display_cols,
1281 int author_display_cols)
1283 struct tog_log_view_state *s = &view->state.log;
1284 const struct got_error *err = NULL;
1285 char datebuf[12]; /* YYYY-MM-DD + SPACE + NUL */
1286 char *logmsg0 = NULL, *logmsg = NULL;
1287 char *author = NULL;
1288 wchar_t *wlogmsg = NULL, *wauthor = NULL;
1289 int author_width, logmsg_width;
1290 char *newline, *line = NULL;
1291 int col, limit;
1292 const int avail = view->ncols;
1293 struct tm tm;
1294 time_t committer_time;
1295 struct tog_color *tc;
1297 committer_time = got_object_commit_get_committer_time(commit);
1298 if (localtime_r(&committer_time, &tm) == NULL)
1299 return got_error_from_errno("localtime_r");
1300 if (strftime(datebuf, sizeof(datebuf), "%G-%m-%d ", &tm)
1301 >= sizeof(datebuf))
1302 return got_error(GOT_ERR_NO_SPACE);
1304 if (avail <= date_display_cols)
1305 limit = MIN(sizeof(datebuf) - 1, avail);
1306 else
1307 limit = MIN(date_display_cols, sizeof(datebuf) - 1);
1308 tc = get_color(&s->colors, TOG_COLOR_DATE);
1309 if (tc)
1310 wattr_on(view->window,
1311 COLOR_PAIR(tc->colorpair), NULL);
1312 waddnstr(view->window, datebuf, limit);
1313 if (tc)
1314 wattr_off(view->window,
1315 COLOR_PAIR(tc->colorpair), NULL);
1316 col = limit;
1317 if (col > avail)
1318 goto done;
1320 if (avail >= 120) {
1321 char *id_str;
1322 err = got_object_id_str(&id_str, id);
1323 if (err)
1324 goto done;
1325 tc = get_color(&s->colors, TOG_COLOR_COMMIT);
1326 if (tc)
1327 wattr_on(view->window,
1328 COLOR_PAIR(tc->colorpair), NULL);
1329 wprintw(view->window, "%.8s ", id_str);
1330 if (tc)
1331 wattr_off(view->window,
1332 COLOR_PAIR(tc->colorpair), NULL);
1333 free(id_str);
1334 col += 9;
1335 if (col > avail)
1336 goto done;
1339 author = strdup(got_object_commit_get_author(commit));
1340 if (author == NULL) {
1341 err = got_error_from_errno("strdup");
1342 goto done;
1344 err = format_author(&wauthor, &author_width, author, avail - col, col);
1345 if (err)
1346 goto done;
1347 tc = get_color(&s->colors, TOG_COLOR_AUTHOR);
1348 if (tc)
1349 wattr_on(view->window,
1350 COLOR_PAIR(tc->colorpair), NULL);
1351 waddwstr(view->window, wauthor);
1352 if (tc)
1353 wattr_off(view->window,
1354 COLOR_PAIR(tc->colorpair), NULL);
1355 col += author_width;
1356 while (col < avail && author_width < author_display_cols + 2) {
1357 waddch(view->window, ' ');
1358 col++;
1359 author_width++;
1361 if (col > avail)
1362 goto done;
1364 err = got_object_commit_get_logmsg(&logmsg0, commit);
1365 if (err)
1366 goto done;
1367 logmsg = logmsg0;
1368 while (*logmsg == '\n')
1369 logmsg++;
1370 newline = strchr(logmsg, '\n');
1371 if (newline)
1372 *newline = '\0';
1373 limit = avail - col;
1374 err = format_line(&wlogmsg, &logmsg_width, logmsg, limit, col);
1375 if (err)
1376 goto done;
1377 waddwstr(view->window, wlogmsg);
1378 col += logmsg_width;
1379 while (col < avail) {
1380 waddch(view->window, ' ');
1381 col++;
1383 done:
1384 free(logmsg0);
1385 free(wlogmsg);
1386 free(author);
1387 free(wauthor);
1388 free(line);
1389 return err;
1392 static struct commit_queue_entry *
1393 alloc_commit_queue_entry(struct got_commit_object *commit,
1394 struct got_object_id *id)
1396 struct commit_queue_entry *entry;
1398 entry = calloc(1, sizeof(*entry));
1399 if (entry == NULL)
1400 return NULL;
1402 entry->id = id;
1403 entry->commit = commit;
1404 return entry;
1407 static void
1408 pop_commit(struct commit_queue *commits)
1410 struct commit_queue_entry *entry;
1412 entry = TAILQ_FIRST(&commits->head);
1413 TAILQ_REMOVE(&commits->head, entry, entry);
1414 got_object_commit_close(entry->commit);
1415 commits->ncommits--;
1416 /* Don't free entry->id! It is owned by the commit graph. */
1417 free(entry);
1420 static void
1421 free_commits(struct commit_queue *commits)
1423 while (!TAILQ_EMPTY(&commits->head))
1424 pop_commit(commits);
1427 static const struct got_error *
1428 match_commit(int *have_match, struct got_object_id *id,
1429 struct got_commit_object *commit, regex_t *regex)
1431 const struct got_error *err = NULL;
1432 regmatch_t regmatch;
1433 char *id_str = NULL, *logmsg = NULL;
1435 *have_match = 0;
1437 err = got_object_id_str(&id_str, id);
1438 if (err)
1439 return err;
1441 err = got_object_commit_get_logmsg(&logmsg, commit);
1442 if (err)
1443 goto done;
1445 if (regexec(regex, got_object_commit_get_author(commit), 1,
1446 &regmatch, 0) == 0 ||
1447 regexec(regex, got_object_commit_get_committer(commit), 1,
1448 &regmatch, 0) == 0 ||
1449 regexec(regex, id_str, 1, &regmatch, 0) == 0 ||
1450 regexec(regex, logmsg, 1, &regmatch, 0) == 0)
1451 *have_match = 1;
1452 done:
1453 free(id_str);
1454 free(logmsg);
1455 return err;
1458 static const struct got_error *
1459 queue_commits(struct got_commit_graph *graph, struct commit_queue *commits,
1460 int minqueue, struct got_repository *repo, const char *path,
1461 int *searching, int *search_next_done, regex_t *regex)
1463 const struct got_error *err = NULL;
1464 int nqueued = 0;
1467 * We keep all commits open throughout the lifetime of the log
1468 * view in order to avoid having to re-fetch commits from disk
1469 * while updating the display.
1471 while (nqueued < minqueue ||
1472 (*searching == TOG_SEARCH_FORWARD && !*search_next_done)) {
1473 struct got_object_id *id;
1474 struct got_commit_object *commit;
1475 struct commit_queue_entry *entry;
1476 int errcode;
1478 err = got_commit_graph_iter_next(&id, graph, repo, NULL, NULL);
1479 if (err || id == NULL)
1480 break;
1482 err = got_object_open_as_commit(&commit, repo, id);
1483 if (err)
1484 break;
1485 entry = alloc_commit_queue_entry(commit, id);
1486 if (entry == NULL) {
1487 err = got_error_from_errno("alloc_commit_queue_entry");
1488 break;
1491 errcode = pthread_mutex_lock(&tog_mutex);
1492 if (errcode) {
1493 err = got_error_set_errno(errcode,
1494 "pthread_mutex_lock");
1495 break;
1498 entry->idx = commits->ncommits;
1499 TAILQ_INSERT_TAIL(&commits->head, entry, entry);
1500 nqueued++;
1501 commits->ncommits++;
1503 if (*searching == TOG_SEARCH_FORWARD && !*search_next_done) {
1504 int have_match;
1505 err = match_commit(&have_match, id, commit, regex);
1506 if (err)
1507 break;
1508 if (have_match)
1509 *search_next_done = TOG_SEARCH_HAVE_MORE;
1512 errcode = pthread_mutex_unlock(&tog_mutex);
1513 if (errcode && err == NULL)
1514 err = got_error_set_errno(errcode,
1515 "pthread_mutex_unlock");
1516 if (err)
1517 break;
1520 return err;
1523 static const struct got_error *
1524 draw_commits(struct tog_view *view)
1526 const struct got_error *err = NULL;
1527 struct tog_log_view_state *s = &view->state.log;
1528 struct commit_queue_entry *entry;
1529 const int limit = view->nlines;
1530 int width;
1531 int ncommits, author_cols = 4;
1532 char *id_str = NULL, *header = NULL, *ncommits_str = NULL;
1533 char *refs_str = NULL;
1534 wchar_t *wline;
1535 struct tog_color *tc;
1536 static const size_t date_display_cols = 12;
1538 entry = s->first_displayed_entry;
1539 ncommits = 0;
1540 while (entry) {
1541 if (ncommits == s->selected) {
1542 s->selected_entry = entry;
1543 break;
1545 entry = TAILQ_NEXT(entry, entry);
1546 ncommits++;
1549 if (s->selected_entry &&
1550 !(view->searching && view->search_next_done == 0)) {
1551 err = got_object_id_str(&id_str, s->selected_entry->id);
1552 if (err)
1553 return err;
1554 err = build_refs_str(&refs_str, &s->refs,
1555 s->selected_entry->id, s->repo);
1556 if (err)
1557 goto done;
1560 if (s->thread_args.commits_needed == 0)
1561 halfdelay(10); /* disable fast refresh */
1563 if (s->thread_args.commits_needed > 0) {
1564 if (asprintf(&ncommits_str, " [%d/%d] %s",
1565 entry ? entry->idx + 1 : 0, s->commits.ncommits,
1566 (view->searching && !view->search_next_done) ?
1567 "searching..." : "loading...") == -1) {
1568 err = got_error_from_errno("asprintf");
1569 goto done;
1571 } else {
1572 const char *search_str = NULL;
1574 if (view->searching) {
1575 if (view->search_next_done == TOG_SEARCH_NO_MORE)
1576 search_str = "no more matches";
1577 else if (view->search_next_done == TOG_SEARCH_HAVE_NONE)
1578 search_str = "no matches found";
1579 else if (!view->search_next_done)
1580 search_str = "searching...";
1583 if (asprintf(&ncommits_str, " [%d/%d] %s",
1584 entry ? entry->idx + 1 : 0, s->commits.ncommits,
1585 search_str ? search_str :
1586 (refs_str ? refs_str : "")) == -1) {
1587 err = got_error_from_errno("asprintf");
1588 goto done;
1592 if (s->in_repo_path && strcmp(s->in_repo_path, "/") != 0) {
1593 if (asprintf(&header, "commit %s %s%s",
1594 id_str ? id_str : "........................................",
1595 s->in_repo_path, ncommits_str) == -1) {
1596 err = got_error_from_errno("asprintf");
1597 header = NULL;
1598 goto done;
1600 } else if (asprintf(&header, "commit %s%s",
1601 id_str ? id_str : "........................................",
1602 ncommits_str) == -1) {
1603 err = got_error_from_errno("asprintf");
1604 header = NULL;
1605 goto done;
1607 err = format_line(&wline, &width, header, view->ncols, 0);
1608 if (err)
1609 goto done;
1611 werase(view->window);
1613 if (view_needs_focus_indication(view))
1614 wstandout(view->window);
1615 tc = get_color(&s->colors, TOG_COLOR_COMMIT);
1616 if (tc)
1617 wattr_on(view->window,
1618 COLOR_PAIR(tc->colorpair), NULL);
1619 waddwstr(view->window, wline);
1620 if (tc)
1621 wattr_off(view->window,
1622 COLOR_PAIR(tc->colorpair), NULL);
1623 while (width < view->ncols) {
1624 waddch(view->window, ' ');
1625 width++;
1627 if (view_needs_focus_indication(view))
1628 wstandend(view->window);
1629 free(wline);
1630 if (limit <= 1)
1631 goto done;
1633 /* Grow author column size if necessary. */
1634 entry = s->first_displayed_entry;
1635 ncommits = 0;
1636 while (entry) {
1637 char *author;
1638 wchar_t *wauthor;
1639 int width;
1640 if (ncommits >= limit - 1)
1641 break;
1642 author = strdup(got_object_commit_get_author(entry->commit));
1643 if (author == NULL) {
1644 err = got_error_from_errno("strdup");
1645 goto done;
1647 err = format_author(&wauthor, &width, author, COLS,
1648 date_display_cols);
1649 if (author_cols < width)
1650 author_cols = width;
1651 free(wauthor);
1652 free(author);
1653 ncommits++;
1654 entry = TAILQ_NEXT(entry, entry);
1657 entry = s->first_displayed_entry;
1658 s->last_displayed_entry = s->first_displayed_entry;
1659 ncommits = 0;
1660 while (entry) {
1661 if (ncommits >= limit - 1)
1662 break;
1663 if (ncommits == s->selected)
1664 wstandout(view->window);
1665 err = draw_commit(view, entry->commit, entry->id,
1666 date_display_cols, author_cols);
1667 if (ncommits == s->selected)
1668 wstandend(view->window);
1669 if (err)
1670 goto done;
1671 ncommits++;
1672 s->last_displayed_entry = entry;
1673 entry = TAILQ_NEXT(entry, entry);
1676 view_vborder(view);
1677 done:
1678 free(id_str);
1679 free(refs_str);
1680 free(ncommits_str);
1681 free(header);
1682 return err;
1685 static void
1686 log_scroll_up(struct tog_log_view_state *s, int maxscroll)
1688 struct commit_queue_entry *entry;
1689 int nscrolled = 0;
1691 entry = TAILQ_FIRST(&s->commits.head);
1692 if (s->first_displayed_entry == entry)
1693 return;
1695 entry = s->first_displayed_entry;
1696 while (entry && nscrolled < maxscroll) {
1697 entry = TAILQ_PREV(entry, commit_queue_head, entry);
1698 if (entry) {
1699 s->first_displayed_entry = entry;
1700 nscrolled++;
1705 static const struct got_error *
1706 trigger_log_thread(struct tog_view *view, int wait)
1708 struct tog_log_thread_args *ta = &view->state.log.thread_args;
1709 int errcode;
1711 halfdelay(1); /* fast refresh while loading commits */
1713 while (ta->commits_needed > 0) {
1714 if (ta->log_complete)
1715 break;
1717 /* Wake the log thread. */
1718 errcode = pthread_cond_signal(&ta->need_commits);
1719 if (errcode)
1720 return got_error_set_errno(errcode,
1721 "pthread_cond_signal");
1724 * The mutex will be released while the view loop waits
1725 * in wgetch(), at which time the log thread will run.
1727 if (!wait)
1728 break;
1730 /* Display progress update in log view. */
1731 show_log_view(view);
1732 update_panels();
1733 doupdate();
1735 /* Wait right here while next commit is being loaded. */
1736 errcode = pthread_cond_wait(&ta->commit_loaded, &tog_mutex);
1737 if (errcode)
1738 return got_error_set_errno(errcode,
1739 "pthread_cond_wait");
1741 /* Display progress update in log view. */
1742 show_log_view(view);
1743 update_panels();
1744 doupdate();
1747 return NULL;
1750 static const struct got_error *
1751 log_scroll_down(struct tog_view *view, int maxscroll)
1753 struct tog_log_view_state *s = &view->state.log;
1754 const struct got_error *err = NULL;
1755 struct commit_queue_entry *pentry;
1756 int nscrolled = 0, ncommits_needed;
1758 if (s->last_displayed_entry == NULL)
1759 return NULL;
1761 ncommits_needed = (s->last_displayed_entry)->idx + 1 + maxscroll;
1762 if (s->commits.ncommits < ncommits_needed &&
1763 !s->thread_args.log_complete) {
1765 * Ask the log thread for required amount of commits.
1767 s->thread_args.commits_needed += maxscroll;
1768 err = trigger_log_thread(view, 1);
1769 if (err)
1770 return err;
1773 do {
1774 pentry = TAILQ_NEXT(s->last_displayed_entry, entry);
1775 if (pentry == NULL)
1776 break;
1778 s->last_displayed_entry = pentry;
1780 pentry = TAILQ_NEXT(s->first_displayed_entry, entry);
1781 if (pentry == NULL)
1782 break;
1783 s->first_displayed_entry = pentry;
1784 } while (++nscrolled < maxscroll);
1786 return err;
1789 static const struct got_error *
1790 open_diff_view_for_commit(struct tog_view **new_view, int begin_x,
1791 struct got_commit_object *commit, struct got_object_id *commit_id,
1792 struct tog_view *log_view, struct got_repository *repo)
1794 const struct got_error *err;
1795 struct got_object_qid *parent_id;
1796 struct tog_view *diff_view;
1798 diff_view = view_open(0, 0, 0, begin_x, TOG_VIEW_DIFF);
1799 if (diff_view == NULL)
1800 return got_error_from_errno("view_open");
1802 parent_id = SIMPLEQ_FIRST(got_object_commit_get_parent_ids(commit));
1803 err = open_diff_view(diff_view, parent_id ? parent_id->id : NULL,
1804 commit_id, NULL, NULL, 3, 0, 0, log_view, repo);
1805 if (err == NULL)
1806 *new_view = diff_view;
1807 return err;
1810 static const struct got_error *
1811 tree_view_visit_subtree(struct tog_tree_view_state *s,
1812 struct got_tree_object *subtree)
1814 struct tog_parent_tree *parent;
1816 parent = calloc(1, sizeof(*parent));
1817 if (parent == NULL)
1818 return got_error_from_errno("calloc");
1820 parent->tree = s->tree;
1821 parent->first_displayed_entry = s->first_displayed_entry;
1822 parent->selected_entry = s->selected_entry;
1823 parent->selected = s->selected;
1824 TAILQ_INSERT_HEAD(&s->parents, parent, entry);
1825 s->tree = subtree;
1826 s->selected = 0;
1827 s->first_displayed_entry = NULL;
1828 return NULL;
1831 static const struct got_error *
1832 tree_view_walk_path(struct tog_tree_view_state *s,
1833 struct got_object_id *commit_id, const char *path)
1835 const struct got_error *err = NULL;
1836 struct got_tree_object *tree = NULL;
1837 const char *p;
1838 char *slash, *subpath = NULL;
1840 /* Walk the path and open corresponding tree objects. */
1841 p = path;
1842 while (*p) {
1843 struct got_tree_entry *te;
1844 struct got_object_id *tree_id;
1845 char *te_name;
1847 while (p[0] == '/')
1848 p++;
1850 /* Ensure the correct subtree entry is selected. */
1851 slash = strchr(p, '/');
1852 if (slash == NULL)
1853 te_name = strdup(p);
1854 else
1855 te_name = strndup(p, slash - p);
1856 if (te_name == NULL) {
1857 err = got_error_from_errno("strndup");
1858 break;
1860 te = got_object_tree_find_entry(s->tree, te_name);
1861 if (te == NULL) {
1862 err = got_error_path(te_name, GOT_ERR_NO_TREE_ENTRY);
1863 free(te_name);
1864 break;
1866 free(te_name);
1867 s->first_displayed_entry = s->selected_entry = te;
1869 if (!S_ISDIR(got_tree_entry_get_mode(s->selected_entry)))
1870 break; /* jump to this file's entry */
1872 slash = strchr(p, '/');
1873 if (slash)
1874 subpath = strndup(path, slash - path);
1875 else
1876 subpath = strdup(path);
1877 if (subpath == NULL) {
1878 err = got_error_from_errno("strdup");
1879 break;
1882 err = got_object_id_by_path(&tree_id, s->repo, commit_id,
1883 subpath);
1884 if (err)
1885 break;
1887 err = got_object_open_as_tree(&tree, s->repo, tree_id);
1888 free(tree_id);
1889 if (err)
1890 break;
1892 err = tree_view_visit_subtree(s, tree);
1893 if (err) {
1894 got_object_tree_close(tree);
1895 break;
1897 if (slash == NULL)
1898 break;
1899 free(subpath);
1900 subpath = NULL;
1901 p = slash;
1904 free(subpath);
1905 return err;
1908 static const struct got_error *
1909 browse_commit_tree(struct tog_view **new_view, int begin_x,
1910 struct commit_queue_entry *entry, const char *path,
1911 struct got_repository *repo)
1913 const struct got_error *err = NULL;
1914 struct got_tree_object *tree;
1915 struct tog_tree_view_state *s;
1916 struct tog_view *tree_view;
1918 err = got_object_open_as_tree(&tree, repo,
1919 got_object_commit_get_tree_id(entry->commit));
1920 if (err)
1921 return err;
1923 tree_view = view_open(0, 0, 0, begin_x, TOG_VIEW_TREE);
1924 if (tree_view == NULL)
1925 return got_error_from_errno("view_open");
1927 err = open_tree_view(tree_view, tree, entry->id, repo);
1928 if (err) {
1929 got_object_tree_close(tree);
1930 return err;
1932 s = &tree_view->state.tree;
1934 *new_view = tree_view;
1936 if (got_path_is_root_dir(path))
1937 return NULL;
1939 return tree_view_walk_path(s, entry->id, path);
1942 static const struct got_error *
1943 block_signals_used_by_main_thread(void)
1945 sigset_t sigset;
1946 int errcode;
1948 if (sigemptyset(&sigset) == -1)
1949 return got_error_from_errno("sigemptyset");
1951 /* tog handles SIGWINCH and SIGCONT */
1952 if (sigaddset(&sigset, SIGWINCH) == -1)
1953 return got_error_from_errno("sigaddset");
1954 if (sigaddset(&sigset, SIGCONT) == -1)
1955 return got_error_from_errno("sigaddset");
1957 /* ncurses handles SIGTSTP */
1958 if (sigaddset(&sigset, SIGTSTP) == -1)
1959 return got_error_from_errno("sigaddset");
1961 errcode = pthread_sigmask(SIG_BLOCK, &sigset, NULL);
1962 if (errcode)
1963 return got_error_set_errno(errcode, "pthread_sigmask");
1965 return NULL;
1968 static void *
1969 log_thread(void *arg)
1971 const struct got_error *err = NULL;
1972 int errcode = 0;
1973 struct tog_log_thread_args *a = arg;
1974 int done = 0;
1976 err = block_signals_used_by_main_thread();
1977 if (err)
1978 return (void *)err;
1980 while (!done && !err && !tog_sigpipe_received) {
1981 err = queue_commits(a->graph, a->commits, 1, a->repo,
1982 a->in_repo_path, a->searching, a->search_next_done,
1983 a->regex);
1984 if (err) {
1985 if (err->code != GOT_ERR_ITER_COMPLETED)
1986 return (void *)err;
1987 err = NULL;
1988 done = 1;
1989 } else if (a->commits_needed > 0)
1990 a->commits_needed--;
1992 errcode = pthread_mutex_lock(&tog_mutex);
1993 if (errcode) {
1994 err = got_error_set_errno(errcode,
1995 "pthread_mutex_lock");
1996 break;
1997 } else if (*a->quit)
1998 done = 1;
1999 else if (*a->first_displayed_entry == NULL) {
2000 *a->first_displayed_entry =
2001 TAILQ_FIRST(&a->commits->head);
2002 *a->selected_entry = *a->first_displayed_entry;
2005 errcode = pthread_cond_signal(&a->commit_loaded);
2006 if (errcode) {
2007 err = got_error_set_errno(errcode,
2008 "pthread_cond_signal");
2009 pthread_mutex_unlock(&tog_mutex);
2010 break;
2013 if (done)
2014 a->commits_needed = 0;
2015 else {
2016 if (a->commits_needed == 0) {
2017 errcode = pthread_cond_wait(&a->need_commits,
2018 &tog_mutex);
2019 if (errcode)
2020 err = got_error_set_errno(errcode,
2021 "pthread_cond_wait");
2025 errcode = pthread_mutex_unlock(&tog_mutex);
2026 if (errcode && err == NULL)
2027 err = got_error_set_errno(errcode,
2028 "pthread_mutex_unlock");
2030 a->log_complete = 1;
2031 return (void *)err;
2034 static const struct got_error *
2035 stop_log_thread(struct tog_log_view_state *s)
2037 const struct got_error *err = NULL;
2038 int errcode;
2040 if (s->thread) {
2041 s->quit = 1;
2042 errcode = pthread_cond_signal(&s->thread_args.need_commits);
2043 if (errcode)
2044 return got_error_set_errno(errcode,
2045 "pthread_cond_signal");
2046 errcode = pthread_mutex_unlock(&tog_mutex);
2047 if (errcode)
2048 return got_error_set_errno(errcode,
2049 "pthread_mutex_unlock");
2050 errcode = pthread_join(s->thread, (void **)&err);
2051 if (errcode)
2052 return got_error_set_errno(errcode, "pthread_join");
2053 errcode = pthread_mutex_lock(&tog_mutex);
2054 if (errcode)
2055 return got_error_set_errno(errcode,
2056 "pthread_mutex_lock");
2057 s->thread = NULL;
2060 if (s->thread_args.repo) {
2061 got_repo_close(s->thread_args.repo);
2062 s->thread_args.repo = NULL;
2065 if (s->thread_args.graph) {
2066 got_commit_graph_close(s->thread_args.graph);
2067 s->thread_args.graph = NULL;
2070 return err;
2073 static const struct got_error *
2074 close_log_view(struct tog_view *view)
2076 const struct got_error *err = NULL;
2077 struct tog_log_view_state *s = &view->state.log;
2078 int errcode;
2080 err = stop_log_thread(s);
2082 errcode = pthread_cond_destroy(&s->thread_args.need_commits);
2083 if (errcode && err == NULL)
2084 err = got_error_set_errno(errcode, "pthread_cond_destroy");
2086 errcode = pthread_cond_destroy(&s->thread_args.commit_loaded);
2087 if (errcode && err == NULL)
2088 err = got_error_set_errno(errcode, "pthread_cond_destroy");
2090 free_commits(&s->commits);
2091 free(s->in_repo_path);
2092 s->in_repo_path = NULL;
2093 free(s->start_id);
2094 s->start_id = NULL;
2095 got_ref_list_free(&s->refs);
2096 return err;
2099 static const struct got_error *
2100 search_start_log_view(struct tog_view *view)
2102 struct tog_log_view_state *s = &view->state.log;
2104 s->matched_entry = NULL;
2105 s->search_entry = NULL;
2106 return NULL;
2109 static const struct got_error *
2110 search_next_log_view(struct tog_view *view)
2112 const struct got_error *err = NULL;
2113 struct tog_log_view_state *s = &view->state.log;
2114 struct commit_queue_entry *entry;
2116 /* Display progress update in log view. */
2117 show_log_view(view);
2118 update_panels();
2119 doupdate();
2121 if (s->search_entry) {
2122 int errcode, ch;
2123 errcode = pthread_mutex_unlock(&tog_mutex);
2124 if (errcode)
2125 return got_error_set_errno(errcode,
2126 "pthread_mutex_unlock");
2127 ch = wgetch(view->window);
2128 errcode = pthread_mutex_lock(&tog_mutex);
2129 if (errcode)
2130 return got_error_set_errno(errcode,
2131 "pthread_mutex_lock");
2132 if (ch == KEY_BACKSPACE) {
2133 view->search_next_done = TOG_SEARCH_HAVE_MORE;
2134 return NULL;
2136 if (view->searching == TOG_SEARCH_FORWARD)
2137 entry = TAILQ_NEXT(s->search_entry, entry);
2138 else
2139 entry = TAILQ_PREV(s->search_entry,
2140 commit_queue_head, entry);
2141 } else if (s->matched_entry) {
2142 if (view->searching == TOG_SEARCH_FORWARD)
2143 entry = TAILQ_NEXT(s->matched_entry, entry);
2144 else
2145 entry = TAILQ_PREV(s->matched_entry,
2146 commit_queue_head, entry);
2147 } else {
2148 if (view->searching == TOG_SEARCH_FORWARD)
2149 entry = TAILQ_FIRST(&s->commits.head);
2150 else
2151 entry = TAILQ_LAST(&s->commits.head, commit_queue_head);
2154 while (1) {
2155 int have_match = 0;
2157 if (entry == NULL) {
2158 if (s->thread_args.log_complete ||
2159 view->searching == TOG_SEARCH_BACKWARD) {
2160 view->search_next_done =
2161 (s->matched_entry == NULL ?
2162 TOG_SEARCH_HAVE_NONE : TOG_SEARCH_NO_MORE);
2163 s->search_entry = NULL;
2164 return NULL;
2167 * Poke the log thread for more commits and return,
2168 * allowing the main loop to make progress. Search
2169 * will resume at s->search_entry once we come back.
2171 s->thread_args.commits_needed++;
2172 return trigger_log_thread(view, 0);
2175 err = match_commit(&have_match, entry->id, entry->commit,
2176 &view->regex);
2177 if (err)
2178 break;
2179 if (have_match) {
2180 view->search_next_done = TOG_SEARCH_HAVE_MORE;
2181 s->matched_entry = entry;
2182 break;
2185 s->search_entry = entry;
2186 if (view->searching == TOG_SEARCH_FORWARD)
2187 entry = TAILQ_NEXT(entry, entry);
2188 else
2189 entry = TAILQ_PREV(entry, commit_queue_head, entry);
2192 if (s->matched_entry) {
2193 int cur = s->selected_entry->idx;
2194 while (cur < s->matched_entry->idx) {
2195 err = input_log_view(NULL, view, KEY_DOWN);
2196 if (err)
2197 return err;
2198 cur++;
2200 while (cur > s->matched_entry->idx) {
2201 err = input_log_view(NULL, view, KEY_UP);
2202 if (err)
2203 return err;
2204 cur--;
2208 s->search_entry = NULL;
2210 return NULL;
2213 static const struct got_error *
2214 open_log_view(struct tog_view *view, struct got_object_id *start_id,
2215 struct got_repository *repo, const char *head_ref_name,
2216 const char *in_repo_path, int log_branches)
2218 const struct got_error *err = NULL;
2219 struct tog_log_view_state *s = &view->state.log;
2220 struct got_repository *thread_repo = NULL;
2221 struct got_commit_graph *thread_graph = NULL;
2222 int errcode;
2224 SIMPLEQ_INIT(&s->refs);
2226 if (in_repo_path != s->in_repo_path) {
2227 free(s->in_repo_path);
2228 s->in_repo_path = strdup(in_repo_path);
2229 if (s->in_repo_path == NULL)
2230 return got_error_from_errno("strdup");
2233 /* The commit queue only contains commits being displayed. */
2234 TAILQ_INIT(&s->commits.head);
2235 s->commits.ncommits = 0;
2237 err = got_ref_list(&s->refs, repo, NULL, got_ref_cmp_by_name, NULL);
2238 if (err)
2239 goto done;
2241 s->repo = repo;
2242 s->head_ref_name = head_ref_name;
2243 s->start_id = got_object_id_dup(start_id);
2244 if (s->start_id == NULL) {
2245 err = got_error_from_errno("got_object_id_dup");
2246 goto done;
2248 s->log_branches = log_branches;
2250 SIMPLEQ_INIT(&s->colors);
2251 if (has_colors() && getenv("TOG_COLORS") != NULL) {
2252 err = add_color(&s->colors, "^$", TOG_COLOR_COMMIT,
2253 get_color_value("TOG_COLOR_COMMIT"));
2254 if (err)
2255 goto done;
2256 err = add_color(&s->colors, "^$", TOG_COLOR_AUTHOR,
2257 get_color_value("TOG_COLOR_AUTHOR"));
2258 if (err) {
2259 free_colors(&s->colors);
2260 goto done;
2262 err = add_color(&s->colors, "^$", TOG_COLOR_DATE,
2263 get_color_value("TOG_COLOR_DATE"));
2264 if (err) {
2265 free_colors(&s->colors);
2266 goto done;
2270 view->show = show_log_view;
2271 view->input = input_log_view;
2272 view->close = close_log_view;
2273 view->search_start = search_start_log_view;
2274 view->search_next = search_next_log_view;
2276 err = got_repo_open(&thread_repo, got_repo_get_path(repo), NULL);
2277 if (err)
2278 goto done;
2279 err = got_commit_graph_open(&thread_graph, s->in_repo_path,
2280 !s->log_branches);
2281 if (err)
2282 goto done;
2283 err = got_commit_graph_iter_start(thread_graph, s->start_id,
2284 s->repo, NULL, NULL);
2285 if (err)
2286 goto done;
2288 errcode = pthread_cond_init(&s->thread_args.need_commits, NULL);
2289 if (errcode) {
2290 err = got_error_set_errno(errcode, "pthread_cond_init");
2291 goto done;
2293 errcode = pthread_cond_init(&s->thread_args.commit_loaded, NULL);
2294 if (errcode) {
2295 err = got_error_set_errno(errcode, "pthread_cond_init");
2296 goto done;
2299 s->thread_args.commits_needed = view->nlines;
2300 s->thread_args.graph = thread_graph;
2301 s->thread_args.commits = &s->commits;
2302 s->thread_args.in_repo_path = s->in_repo_path;
2303 s->thread_args.start_id = s->start_id;
2304 s->thread_args.repo = thread_repo;
2305 s->thread_args.log_complete = 0;
2306 s->thread_args.quit = &s->quit;
2307 s->thread_args.first_displayed_entry = &s->first_displayed_entry;
2308 s->thread_args.selected_entry = &s->selected_entry;
2309 s->thread_args.searching = &view->searching;
2310 s->thread_args.search_next_done = &view->search_next_done;
2311 s->thread_args.regex = &view->regex;
2312 done:
2313 if (err)
2314 close_log_view(view);
2315 return err;
2318 static const struct got_error *
2319 show_log_view(struct tog_view *view)
2321 const struct got_error *err;
2322 struct tog_log_view_state *s = &view->state.log;
2324 if (s->thread == NULL) {
2325 int errcode = pthread_create(&s->thread, NULL, log_thread,
2326 &s->thread_args);
2327 if (errcode)
2328 return got_error_set_errno(errcode, "pthread_create");
2329 if (s->thread_args.commits_needed > 0) {
2330 err = trigger_log_thread(view, 1);
2331 if (err)
2332 return err;
2336 return draw_commits(view);
2339 static const struct got_error *
2340 input_log_view(struct tog_view **new_view, struct tog_view *view, int ch)
2342 const struct got_error *err = NULL;
2343 struct tog_log_view_state *s = &view->state.log;
2344 char *parent_path, *in_repo_path = NULL;
2345 struct tog_view *diff_view = NULL, *tree_view = NULL, *lv = NULL;
2346 struct tog_view *ref_view = NULL;
2347 int begin_x = 0;
2348 struct got_object_id *start_id;
2350 switch (ch) {
2351 case 'q':
2352 s->quit = 1;
2353 break;
2354 case 'k':
2355 case KEY_UP:
2356 case '<':
2357 case ',':
2358 if (s->first_displayed_entry == NULL)
2359 break;
2360 if (s->selected > 0)
2361 s->selected--;
2362 else
2363 log_scroll_up(s, 1);
2364 break;
2365 case KEY_PPAGE:
2366 case CTRL('b'):
2367 if (s->first_displayed_entry == NULL)
2368 break;
2369 if (TAILQ_FIRST(&s->commits.head) ==
2370 s->first_displayed_entry) {
2371 s->selected = 0;
2372 break;
2374 log_scroll_up(s, view->nlines - 1);
2375 break;
2376 case 'j':
2377 case KEY_DOWN:
2378 case '>':
2379 case '.':
2380 if (s->first_displayed_entry == NULL)
2381 break;
2382 if (s->selected < MIN(view->nlines - 2,
2383 s->commits.ncommits - 1)) {
2384 s->selected++;
2385 break;
2387 err = log_scroll_down(view, 1);
2388 break;
2389 case KEY_NPAGE:
2390 case CTRL('f'): {
2391 struct commit_queue_entry *first;
2392 first = s->first_displayed_entry;
2393 if (first == NULL)
2394 break;
2395 err = log_scroll_down(view, view->nlines - 1);
2396 if (err)
2397 break;
2398 if (first == s->first_displayed_entry &&
2399 s->selected < MIN(view->nlines - 2,
2400 s->commits.ncommits - 1)) {
2401 /* can't scroll further down */
2402 s->selected = MIN(view->nlines - 2,
2403 s->commits.ncommits - 1);
2405 err = NULL;
2406 break;
2408 case KEY_RESIZE:
2409 if (s->selected > view->nlines - 2)
2410 s->selected = view->nlines - 2;
2411 if (s->selected > s->commits.ncommits - 1)
2412 s->selected = s->commits.ncommits - 1;
2413 if (s->commits.ncommits < view->nlines - 1 &&
2414 !s->thread_args.log_complete) {
2415 s->thread_args.commits_needed += (view->nlines - 1) -
2416 s->commits.ncommits;
2417 err = trigger_log_thread(view, 1);
2419 break;
2420 case KEY_ENTER:
2421 case ' ':
2422 case '\r':
2423 if (s->selected_entry == NULL)
2424 break;
2425 if (view_is_parent_view(view))
2426 begin_x = view_split_begin_x(view->begin_x);
2427 err = open_diff_view_for_commit(&diff_view, begin_x,
2428 s->selected_entry->commit, s->selected_entry->id,
2429 view, s->repo);
2430 if (err)
2431 break;
2432 view->focussed = 0;
2433 diff_view->focussed = 1;
2434 if (view_is_parent_view(view)) {
2435 err = view_close_child(view);
2436 if (err)
2437 return err;
2438 view_set_child(view, diff_view);
2439 view->focus_child = 1;
2440 } else
2441 *new_view = diff_view;
2442 break;
2443 case 't':
2444 if (s->selected_entry == NULL)
2445 break;
2446 if (view_is_parent_view(view))
2447 begin_x = view_split_begin_x(view->begin_x);
2448 err = browse_commit_tree(&tree_view, begin_x,
2449 s->selected_entry, s->in_repo_path, s->repo);
2450 if (err)
2451 break;
2452 view->focussed = 0;
2453 tree_view->focussed = 1;
2454 if (view_is_parent_view(view)) {
2455 err = view_close_child(view);
2456 if (err)
2457 return err;
2458 view_set_child(view, tree_view);
2459 view->focus_child = 1;
2460 } else
2461 *new_view = tree_view;
2462 break;
2463 case KEY_BACKSPACE:
2464 if (got_path_cmp(s->in_repo_path, "/",
2465 strlen(s->in_repo_path), 1) == 0)
2466 break;
2467 err = got_path_dirname(&parent_path, s->in_repo_path);
2468 if (err)
2469 return err;
2470 err = stop_log_thread(s);
2471 if (err) {
2472 free(parent_path);
2473 return err;
2475 lv = view_open(view->nlines, view->ncols,
2476 view->begin_y, view->begin_x, TOG_VIEW_LOG);
2477 if (lv == NULL) {
2478 free(parent_path);
2479 return got_error_from_errno("view_open");
2481 err = open_log_view(lv, s->start_id, s->repo, s->head_ref_name,
2482 parent_path, s->log_branches);
2483 free(parent_path);
2484 if (err)
2485 return err;;
2486 view->focussed = 0;
2487 lv->focussed = 1;
2488 if (view_is_parent_view(view))
2489 *new_view = lv;
2490 else
2491 view_set_child(view->parent, lv);
2492 break;
2493 case CTRL('l'):
2494 err = stop_log_thread(s);
2495 if (err)
2496 return err;
2497 lv = view_open(view->nlines, view->ncols,
2498 view->begin_y, view->begin_x, TOG_VIEW_LOG);
2499 if (lv == NULL)
2500 return got_error_from_errno("view_open");
2501 err = got_repo_match_object_id(&start_id, NULL,
2502 s->head_ref_name ? s->head_ref_name : GOT_REF_HEAD,
2503 GOT_OBJ_TYPE_COMMIT, 1, s->repo);
2504 if (err) {
2505 view_close(lv);
2506 return err;
2508 in_repo_path = strdup(s->in_repo_path);
2509 if (in_repo_path == NULL) {
2510 free(start_id);
2511 view_close(lv);
2512 return got_error_from_errno("strdup");
2514 err = open_log_view(lv, start_id, s->repo, s->head_ref_name,
2515 in_repo_path, s->log_branches);
2516 if (err) {
2517 free(start_id);
2518 view_close(lv);
2519 return err;;
2521 view->dying = 1;
2522 *new_view = lv;
2523 break;
2524 case 'B':
2525 s->log_branches = !s->log_branches;
2526 err = stop_log_thread(s);
2527 if (err)
2528 return err;
2529 lv = view_open(view->nlines, view->ncols,
2530 view->begin_y, view->begin_x, TOG_VIEW_LOG);
2531 if (lv == NULL)
2532 return got_error_from_errno("view_open");
2533 err = open_log_view(lv, s->start_id, s->repo,
2534 s->head_ref_name, s->in_repo_path, s->log_branches);
2535 if (err) {
2536 view_close(lv);
2537 return err;;
2539 view->dying = 1;
2540 *new_view = lv;
2541 break;
2542 case 'r':
2543 if (view_is_parent_view(view))
2544 begin_x = view_split_begin_x(view->begin_x);
2545 ref_view = view_open(view->nlines, view->ncols,
2546 view->begin_y, begin_x, TOG_VIEW_REF);
2547 if (ref_view == NULL)
2548 return got_error_from_errno("view_open");
2549 err = open_ref_view(ref_view, s->repo);
2550 if (err) {
2551 view_close(ref_view);
2552 return err;
2554 view->focussed = 0;
2555 ref_view->focussed = 1;
2556 if (view_is_parent_view(view)) {
2557 err = view_close_child(view);
2558 if (err)
2559 return err;
2560 view_set_child(view, ref_view);
2561 view->focus_child = 1;
2562 } else
2563 *new_view = ref_view;
2564 break;
2565 default:
2566 break;
2569 return err;
2572 static const struct got_error *
2573 apply_unveil(const char *repo_path, const char *worktree_path)
2575 const struct got_error *error;
2577 #ifdef PROFILE
2578 if (unveil("gmon.out", "rwc") != 0)
2579 return got_error_from_errno2("unveil", "gmon.out");
2580 #endif
2581 if (repo_path && unveil(repo_path, "r") != 0)
2582 return got_error_from_errno2("unveil", repo_path);
2584 if (worktree_path && unveil(worktree_path, "rwc") != 0)
2585 return got_error_from_errno2("unveil", worktree_path);
2587 if (unveil(GOT_TMPDIR_STR, "rwc") != 0)
2588 return got_error_from_errno2("unveil", GOT_TMPDIR_STR);
2590 error = got_privsep_unveil_exec_helpers();
2591 if (error != NULL)
2592 return error;
2594 if (unveil(NULL, NULL) != 0)
2595 return got_error_from_errno("unveil");
2597 return NULL;
2600 static void
2601 init_curses(void)
2603 initscr();
2604 cbreak();
2605 halfdelay(1); /* Do fast refresh while initial view is loading. */
2606 noecho();
2607 nonl();
2608 intrflush(stdscr, FALSE);
2609 keypad(stdscr, TRUE);
2610 curs_set(0);
2611 if (getenv("TOG_COLORS") != NULL) {
2612 start_color();
2613 use_default_colors();
2615 signal(SIGWINCH, tog_sigwinch);
2616 signal(SIGPIPE, tog_sigpipe);
2617 signal(SIGCONT, tog_sigcont);
2620 static const struct got_error *
2621 get_in_repo_path_from_argv0(char **in_repo_path, int argc, char *argv[],
2622 struct got_repository *repo, struct got_worktree *worktree)
2624 const struct got_error *err = NULL;
2626 if (argc == 0) {
2627 *in_repo_path = strdup("/");
2628 if (*in_repo_path == NULL)
2629 return got_error_from_errno("strdup");
2630 return NULL;
2633 if (worktree) {
2634 const char *prefix = got_worktree_get_path_prefix(worktree);
2635 char *p;
2637 err = got_worktree_resolve_path(&p, worktree, argv[0]);
2638 if (err)
2639 return err;
2640 if (asprintf(in_repo_path, "%s%s%s", prefix,
2641 (p[0] != '\0' && !got_path_is_root_dir(prefix)) ? "/" : "",
2642 p) == -1) {
2643 err = got_error_from_errno("asprintf");
2644 *in_repo_path = NULL;
2646 free(p);
2647 } else
2648 err = got_repo_map_path(in_repo_path, repo, argv[0]);
2650 return err;
2653 static const struct got_error *
2654 cmd_log(int argc, char *argv[])
2656 const struct got_error *error;
2657 struct got_repository *repo = NULL;
2658 struct got_worktree *worktree = NULL;
2659 struct got_object_id *start_id = NULL;
2660 char *in_repo_path = NULL, *repo_path = NULL, *cwd = NULL;
2661 char *start_commit = NULL, *head_ref_name = NULL;
2662 int ch, log_branches = 0;
2663 struct tog_view *view;
2665 while ((ch = getopt(argc, argv, "bc:r:")) != -1) {
2666 switch (ch) {
2667 case 'b':
2668 log_branches = 1;
2669 break;
2670 case 'c':
2671 start_commit = optarg;
2672 break;
2673 case 'r':
2674 repo_path = realpath(optarg, NULL);
2675 if (repo_path == NULL)
2676 return got_error_from_errno2("realpath",
2677 optarg);
2678 break;
2679 default:
2680 usage_log();
2681 /* NOTREACHED */
2685 argc -= optind;
2686 argv += optind;
2688 if (argc > 1)
2689 usage_log();
2691 cwd = getcwd(NULL, 0);
2692 if (cwd == NULL)
2693 return got_error_from_errno("getcwd");
2695 error = got_worktree_open(&worktree, cwd);
2696 if (error && error->code != GOT_ERR_NOT_WORKTREE)
2697 goto done;
2699 if (repo_path == NULL) {
2700 if (worktree)
2701 repo_path =
2702 strdup(got_worktree_get_repo_path(worktree));
2703 else
2704 repo_path = strdup(cwd);
2706 if (repo_path == NULL) {
2707 error = got_error_from_errno("strdup");
2708 goto done;
2711 error = got_repo_open(&repo, repo_path, NULL);
2712 if (error != NULL)
2713 goto done;
2715 error = get_in_repo_path_from_argv0(&in_repo_path, argc, argv,
2716 repo, worktree);
2717 if (error)
2718 goto done;
2720 init_curses();
2722 error = apply_unveil(got_repo_get_path(repo),
2723 worktree ? got_worktree_get_root_path(worktree) : NULL);
2724 if (error)
2725 goto done;
2727 if (start_commit == NULL)
2728 error = got_repo_match_object_id(&start_id, NULL, worktree ?
2729 got_worktree_get_head_ref_name(worktree) : GOT_REF_HEAD,
2730 GOT_OBJ_TYPE_COMMIT, 1, repo);
2731 else
2732 error = got_repo_match_object_id(&start_id, NULL, start_commit,
2733 GOT_OBJ_TYPE_COMMIT, 1, repo);
2734 if (error != NULL)
2735 goto done;
2737 view = view_open(0, 0, 0, 0, TOG_VIEW_LOG);
2738 if (view == NULL) {
2739 error = got_error_from_errno("view_open");
2740 goto done;
2742 if (worktree) {
2743 head_ref_name = strdup(
2744 got_worktree_get_head_ref_name(worktree));
2745 if (head_ref_name == NULL) {
2746 error = got_error_from_errno("strdup");
2747 goto done;
2750 error = open_log_view(view, start_id, repo, head_ref_name,
2751 in_repo_path, log_branches);
2752 if (error)
2753 goto done;
2754 if (worktree) {
2755 /* Release work tree lock. */
2756 got_worktree_close(worktree);
2757 worktree = NULL;
2759 error = view_loop(view);
2760 done:
2761 free(in_repo_path);
2762 free(repo_path);
2763 free(cwd);
2764 free(start_id);
2765 free(head_ref_name);
2766 if (repo)
2767 got_repo_close(repo);
2768 if (worktree)
2769 got_worktree_close(worktree);
2770 return error;
2773 __dead static void
2774 usage_diff(void)
2776 endwin();
2777 fprintf(stderr, "usage: %s diff [-a] [-C number] [-r repository-path] "
2778 "[-w] object1 object2\n", getprogname());
2779 exit(1);
2782 static char *
2783 parse_next_line(FILE *f, size_t *len)
2785 char *line;
2786 size_t linelen;
2787 size_t lineno;
2788 const char delim[3] = { '\0', '\0', '\0'};
2790 line = fparseln(f, &linelen, &lineno, delim, 0);
2791 if (len)
2792 *len = linelen;
2793 return line;
2796 static int
2797 match_line(const char *line, regex_t *regex, size_t nmatch,
2798 regmatch_t *regmatch)
2800 return regexec(regex, line, nmatch, regmatch, 0) == 0;
2803 struct tog_color *
2804 match_color(struct tog_colors *colors, const char *line)
2806 struct tog_color *tc = NULL;
2808 SIMPLEQ_FOREACH(tc, colors, entry) {
2809 if (match_line(line, &tc->regex, 0, NULL))
2810 return tc;
2813 return NULL;
2816 static const struct got_error *
2817 add_matched_line(int *wtotal, const char *line, int wlimit, int col_tab_align,
2818 WINDOW *window, regmatch_t *regmatch)
2820 const struct got_error *err = NULL;
2821 wchar_t *wline;
2822 int width;
2823 char *s;
2825 *wtotal = 0;
2827 s = strndup(line, regmatch->rm_so);
2828 if (s == NULL)
2829 return got_error_from_errno("strndup");
2831 err = format_line(&wline, &width, s, wlimit, col_tab_align);
2832 if (err) {
2833 free(s);
2834 return err;
2836 waddwstr(window, wline);
2837 free(wline);
2838 free(s);
2839 wlimit -= width;
2840 *wtotal += width;
2842 if (wlimit > 0) {
2843 s = strndup(line + regmatch->rm_so,
2844 regmatch->rm_eo - regmatch->rm_so);
2845 if (s == NULL) {
2846 err = got_error_from_errno("strndup");
2847 free(s);
2848 return err;
2850 err = format_line(&wline, &width, s, wlimit, col_tab_align);
2851 if (err) {
2852 free(s);
2853 return err;
2855 wattr_on(window, A_STANDOUT, NULL);
2856 waddwstr(window, wline);
2857 wattr_off(window, A_STANDOUT, NULL);
2858 free(wline);
2859 free(s);
2860 wlimit -= width;
2861 *wtotal += width;
2864 if (wlimit > 0 && strlen(line) > regmatch->rm_eo) {
2865 err = format_line(&wline, &width,
2866 line + regmatch->rm_eo, wlimit, col_tab_align);
2867 if (err)
2868 return err;
2869 waddwstr(window, wline);
2870 free(wline);
2871 *wtotal += width;
2874 return NULL;
2877 static const struct got_error *
2878 draw_file(struct tog_view *view, const char *header)
2880 struct tog_diff_view_state *s = &view->state.diff;
2881 regmatch_t *regmatch = &view->regmatch;
2882 const struct got_error *err;
2883 int nprinted = 0;
2884 char *line;
2885 struct tog_color *tc;
2886 size_t len;
2887 wchar_t *wline;
2888 int width;
2889 int max_lines = view->nlines;
2890 int nlines = s->nlines;
2891 off_t line_offset;
2893 line_offset = s->line_offsets[s->first_displayed_line - 1];
2894 if (fseeko(s->f, line_offset, SEEK_SET) == -1)
2895 return got_error_from_errno("fseek");
2897 werase(view->window);
2899 if (header) {
2900 if (asprintf(&line, "[%d/%d] %s",
2901 s->first_displayed_line - 1 + s->selected_line, nlines,
2902 header) == -1)
2903 return got_error_from_errno("asprintf");
2904 err = format_line(&wline, &width, line, view->ncols, 0);
2905 free(line);
2906 if (err)
2907 return err;
2909 if (view_needs_focus_indication(view))
2910 wstandout(view->window);
2911 waddwstr(view->window, wline);
2912 free(wline);
2913 wline = NULL;
2914 if (view_needs_focus_indication(view))
2915 wstandend(view->window);
2916 if (width <= view->ncols - 1)
2917 waddch(view->window, '\n');
2919 if (max_lines <= 1)
2920 return NULL;
2921 max_lines--;
2924 s->eof = 0;
2925 while (max_lines > 0 && nprinted < max_lines) {
2926 line = parse_next_line(s->f, &len);
2927 if (line == NULL) {
2928 s->eof = 1;
2929 break;
2932 tc = match_color(&s->colors, line);
2933 if (tc)
2934 wattr_on(view->window,
2935 COLOR_PAIR(tc->colorpair), NULL);
2936 if (s->first_displayed_line + nprinted == s->matched_line &&
2937 regmatch->rm_so >= 0 && regmatch->rm_so < regmatch->rm_eo) {
2938 err = add_matched_line(&width, line, view->ncols, 0,
2939 view->window, regmatch);
2940 if (err) {
2941 free(line);
2942 return err;
2944 } else {
2945 err = format_line(&wline, &width, line, view->ncols, 0);
2946 if (err) {
2947 free(line);
2948 return err;
2950 waddwstr(view->window, wline);
2951 free(wline);
2952 wline = NULL;
2954 if (tc)
2955 wattr_off(view->window,
2956 COLOR_PAIR(tc->colorpair), NULL);
2957 if (width <= view->ncols - 1)
2958 waddch(view->window, '\n');
2959 nprinted++;
2960 free(line);
2962 if (nprinted >= 1)
2963 s->last_displayed_line = s->first_displayed_line +
2964 (nprinted - 1);
2965 else
2966 s->last_displayed_line = s->first_displayed_line;
2968 view_vborder(view);
2970 if (s->eof) {
2971 while (nprinted < view->nlines) {
2972 waddch(view->window, '\n');
2973 nprinted++;
2976 err = format_line(&wline, &width, TOG_EOF_STRING, view->ncols, 0);
2977 if (err) {
2978 return err;
2981 wstandout(view->window);
2982 waddwstr(view->window, wline);
2983 free(wline);
2984 wline = NULL;
2985 wstandend(view->window);
2988 return NULL;
2991 static char *
2992 get_datestr(time_t *time, char *datebuf)
2994 struct tm mytm, *tm;
2995 char *p, *s;
2997 tm = gmtime_r(time, &mytm);
2998 if (tm == NULL)
2999 return NULL;
3000 s = asctime_r(tm, datebuf);
3001 if (s == NULL)
3002 return NULL;
3003 p = strchr(s, '\n');
3004 if (p)
3005 *p = '\0';
3006 return s;
3009 static const struct got_error *
3010 get_changed_paths(struct got_pathlist_head *paths,
3011 struct got_commit_object *commit, struct got_repository *repo)
3013 const struct got_error *err = NULL;
3014 struct got_object_id *tree_id1 = NULL, *tree_id2 = NULL;
3015 struct got_tree_object *tree1 = NULL, *tree2 = NULL;
3016 struct got_object_qid *qid;
3018 qid = SIMPLEQ_FIRST(got_object_commit_get_parent_ids(commit));
3019 if (qid != NULL) {
3020 struct got_commit_object *pcommit;
3021 err = got_object_open_as_commit(&pcommit, repo,
3022 qid->id);
3023 if (err)
3024 return err;
3026 tree_id1 = got_object_commit_get_tree_id(pcommit);
3027 got_object_commit_close(pcommit);
3031 if (tree_id1) {
3032 err = got_object_open_as_tree(&tree1, repo, tree_id1);
3033 if (err)
3034 goto done;
3037 tree_id2 = got_object_commit_get_tree_id(commit);
3038 err = got_object_open_as_tree(&tree2, repo, tree_id2);
3039 if (err)
3040 goto done;
3042 err = got_diff_tree(tree1, tree2, "", "", repo,
3043 got_diff_tree_collect_changed_paths, paths, 0);
3044 done:
3045 if (tree1)
3046 got_object_tree_close(tree1);
3047 if (tree2)
3048 got_object_tree_close(tree2);
3049 return err;
3052 static const struct got_error *
3053 add_line_offset(off_t **line_offsets, size_t *nlines, off_t off)
3055 off_t *p;
3057 p = reallocarray(*line_offsets, *nlines + 1, sizeof(off_t));
3058 if (p == NULL)
3059 return got_error_from_errno("reallocarray");
3060 *line_offsets = p;
3061 (*line_offsets)[*nlines] = off;
3062 (*nlines)++;
3063 return NULL;
3066 static const struct got_error *
3067 write_commit_info(off_t **line_offsets, size_t *nlines,
3068 struct got_object_id *commit_id, struct got_reflist_head *refs,
3069 struct got_repository *repo, FILE *outfile)
3071 const struct got_error *err = NULL;
3072 char datebuf[26], *datestr;
3073 struct got_commit_object *commit;
3074 char *id_str = NULL, *logmsg = NULL, *s = NULL, *line;
3075 time_t committer_time;
3076 const char *author, *committer;
3077 char *refs_str = NULL;
3078 struct got_pathlist_head changed_paths;
3079 struct got_pathlist_entry *pe;
3080 off_t outoff = 0;
3081 int n;
3083 TAILQ_INIT(&changed_paths);
3085 if (refs) {
3086 err = build_refs_str(&refs_str, refs, commit_id, repo);
3087 if (err)
3088 return err;
3091 err = got_object_open_as_commit(&commit, repo, commit_id);
3092 if (err)
3093 return err;
3095 err = got_object_id_str(&id_str, commit_id);
3096 if (err) {
3097 err = got_error_from_errno("got_object_id_str");
3098 goto done;
3101 err = add_line_offset(line_offsets, nlines, 0);
3102 if (err)
3103 goto done;
3105 n = fprintf(outfile, "commit %s%s%s%s\n", id_str, refs_str ? " (" : "",
3106 refs_str ? refs_str : "", refs_str ? ")" : "");
3107 if (n < 0) {
3108 err = got_error_from_errno("fprintf");
3109 goto done;
3111 outoff += n;
3112 err = add_line_offset(line_offsets, nlines, outoff);
3113 if (err)
3114 goto done;
3116 n = fprintf(outfile, "from: %s\n",
3117 got_object_commit_get_author(commit));
3118 if (n < 0) {
3119 err = got_error_from_errno("fprintf");
3120 goto done;
3122 outoff += n;
3123 err = add_line_offset(line_offsets, nlines, outoff);
3124 if (err)
3125 goto done;
3127 committer_time = got_object_commit_get_committer_time(commit);
3128 datestr = get_datestr(&committer_time, datebuf);
3129 if (datestr) {
3130 n = fprintf(outfile, "date: %s UTC\n", datestr);
3131 if (n < 0) {
3132 err = got_error_from_errno("fprintf");
3133 goto done;
3135 outoff += n;
3136 err = add_line_offset(line_offsets, nlines, outoff);
3137 if (err)
3138 goto done;
3140 author = got_object_commit_get_author(commit);
3141 committer = got_object_commit_get_committer(commit);
3142 if (strcmp(author, committer) != 0) {
3143 n = fprintf(outfile, "via: %s\n", committer);
3144 if (n < 0) {
3145 err = got_error_from_errno("fprintf");
3146 goto done;
3148 outoff += n;
3149 err = add_line_offset(line_offsets, nlines, outoff);
3150 if (err)
3151 goto done;
3153 err = got_object_commit_get_logmsg(&logmsg, commit);
3154 if (err)
3155 goto done;
3156 s = logmsg;
3157 while ((line = strsep(&s, "\n")) != NULL) {
3158 n = fprintf(outfile, "%s\n", line);
3159 if (n < 0) {
3160 err = got_error_from_errno("fprintf");
3161 goto done;
3163 outoff += n;
3164 err = add_line_offset(line_offsets, nlines, outoff);
3165 if (err)
3166 goto done;
3169 err = get_changed_paths(&changed_paths, commit, repo);
3170 if (err)
3171 goto done;
3172 TAILQ_FOREACH(pe, &changed_paths, entry) {
3173 struct got_diff_changed_path *cp = pe->data;
3174 n = fprintf(outfile, "%c %s\n", cp->status, pe->path);
3175 if (n < 0) {
3176 err = got_error_from_errno("fprintf");
3177 goto done;
3179 outoff += n;
3180 err = add_line_offset(line_offsets, nlines, outoff);
3181 if (err)
3182 goto done;
3183 free((char *)pe->path);
3184 free(pe->data);
3187 fputc('\n', outfile);
3188 outoff++;
3189 err = add_line_offset(line_offsets, nlines, outoff);
3190 done:
3191 got_pathlist_free(&changed_paths);
3192 free(id_str);
3193 free(logmsg);
3194 free(refs_str);
3195 got_object_commit_close(commit);
3196 if (err) {
3197 free(*line_offsets);
3198 *line_offsets = NULL;
3199 *nlines = 0;
3201 return err;
3204 static const struct got_error *
3205 create_diff(struct tog_diff_view_state *s)
3207 const struct got_error *err = NULL;
3208 FILE *f = NULL;
3209 int obj_type;
3211 free(s->line_offsets);
3212 s->line_offsets = malloc(sizeof(off_t));
3213 if (s->line_offsets == NULL)
3214 return got_error_from_errno("malloc");
3215 s->nlines = 0;
3217 f = got_opentemp();
3218 if (f == NULL) {
3219 err = got_error_from_errno("got_opentemp");
3220 goto done;
3222 if (s->f && fclose(s->f) != 0) {
3223 err = got_error_from_errno("fclose");
3224 goto done;
3226 s->f = f;
3228 if (s->id1)
3229 err = got_object_get_type(&obj_type, s->repo, s->id1);
3230 else
3231 err = got_object_get_type(&obj_type, s->repo, s->id2);
3232 if (err)
3233 goto done;
3235 switch (obj_type) {
3236 case GOT_OBJ_TYPE_BLOB:
3237 err = got_diff_objects_as_blobs(&s->line_offsets, &s->nlines,
3238 s->id1, s->id2, s->label1, s->label2, s->diff_context,
3239 s->ignore_whitespace, s->force_text_diff, s->repo, s->f);
3240 break;
3241 case GOT_OBJ_TYPE_TREE:
3242 err = got_diff_objects_as_trees(&s->line_offsets, &s->nlines,
3243 s->id1, s->id2, "", "", s->diff_context,
3244 s->ignore_whitespace, s->force_text_diff, s->repo, s->f);
3245 break;
3246 case GOT_OBJ_TYPE_COMMIT: {
3247 const struct got_object_id_queue *parent_ids;
3248 struct got_object_qid *pid;
3249 struct got_commit_object *commit2;
3251 err = got_object_open_as_commit(&commit2, s->repo, s->id2);
3252 if (err)
3253 goto done;
3254 /* Show commit info if we're diffing to a parent/root commit. */
3255 if (s->id1 == NULL) {
3256 err = write_commit_info(&s->line_offsets, &s->nlines,
3257 s->id2, &s->refs, s->repo, s->f);
3258 if (err)
3259 goto done;
3260 } else {
3261 parent_ids = got_object_commit_get_parent_ids(commit2);
3262 SIMPLEQ_FOREACH(pid, parent_ids, entry) {
3263 if (got_object_id_cmp(s->id1, pid->id) == 0) {
3264 err = write_commit_info(
3265 &s->line_offsets, &s->nlines,
3266 s->id2, &s->refs, s->repo, s->f);
3267 if (err)
3268 goto done;
3269 break;
3273 got_object_commit_close(commit2);
3275 err = got_diff_objects_as_commits(&s->line_offsets, &s->nlines,
3276 s->id1, s->id2, s->diff_context, s->ignore_whitespace,
3277 s->force_text_diff, s->repo, s->f);
3278 break;
3280 default:
3281 err = got_error(GOT_ERR_OBJ_TYPE);
3282 break;
3284 if (err)
3285 goto done;
3286 done:
3287 if (s->f && fflush(s->f) != 0 && err == NULL)
3288 err = got_error_from_errno("fflush");
3289 return err;
3292 static void
3293 diff_view_indicate_progress(struct tog_view *view)
3295 mvwaddstr(view->window, 0, 0, "diffing...");
3296 update_panels();
3297 doupdate();
3300 static const struct got_error *
3301 search_start_diff_view(struct tog_view *view)
3303 struct tog_diff_view_state *s = &view->state.diff;
3305 s->matched_line = 0;
3306 return NULL;
3309 static const struct got_error *
3310 search_next_diff_view(struct tog_view *view)
3312 struct tog_diff_view_state *s = &view->state.diff;
3313 int lineno;
3315 if (!view->searching) {
3316 view->search_next_done = TOG_SEARCH_HAVE_MORE;
3317 return NULL;
3320 if (s->matched_line) {
3321 if (view->searching == TOG_SEARCH_FORWARD)
3322 lineno = s->matched_line + 1;
3323 else
3324 lineno = s->matched_line - 1;
3325 } else {
3326 if (view->searching == TOG_SEARCH_FORWARD)
3327 lineno = 1;
3328 else
3329 lineno = s->nlines;
3332 while (1) {
3333 char *line = NULL;
3334 off_t offset;
3335 size_t len;
3337 if (lineno <= 0 || lineno > s->nlines) {
3338 if (s->matched_line == 0) {
3339 view->search_next_done = TOG_SEARCH_HAVE_MORE;
3340 free(line);
3341 break;
3344 if (view->searching == TOG_SEARCH_FORWARD)
3345 lineno = 1;
3346 else
3347 lineno = s->nlines;
3350 offset = s->line_offsets[lineno - 1];
3351 if (fseeko(s->f, offset, SEEK_SET) != 0) {
3352 free(line);
3353 return got_error_from_errno("fseeko");
3355 free(line);
3356 line = parse_next_line(s->f, &len);
3357 if (line &&
3358 match_line(line, &view->regex, 1, &view->regmatch)) {
3359 view->search_next_done = TOG_SEARCH_HAVE_MORE;
3360 s->matched_line = lineno;
3361 free(line);
3362 break;
3364 free(line);
3365 if (view->searching == TOG_SEARCH_FORWARD)
3366 lineno++;
3367 else
3368 lineno--;
3371 if (s->matched_line) {
3372 s->first_displayed_line = s->matched_line;
3373 s->selected_line = 1;
3376 return NULL;
3379 static const struct got_error *
3380 open_diff_view(struct tog_view *view, struct got_object_id *id1,
3381 struct got_object_id *id2, const char *label1, const char *label2,
3382 int diff_context, int ignore_whitespace, int force_text_diff,
3383 struct tog_view *log_view, struct got_repository *repo)
3385 const struct got_error *err;
3386 struct tog_diff_view_state *s = &view->state.diff;
3388 SIMPLEQ_INIT(&s->refs);
3390 if (id1 != NULL && id2 != NULL) {
3391 int type1, type2;
3392 err = got_object_get_type(&type1, repo, id1);
3393 if (err)
3394 return err;
3395 err = got_object_get_type(&type2, repo, id2);
3396 if (err)
3397 return err;
3399 if (type1 != type2)
3400 return got_error(GOT_ERR_OBJ_TYPE);
3402 s->first_displayed_line = 1;
3403 s->last_displayed_line = view->nlines;
3404 s->selected_line = 1;
3405 s->repo = repo;
3406 s->id1 = id1;
3407 s->id2 = id2;
3408 s->label1 = label1;
3409 s->label2 = label2;
3411 if (id1) {
3412 s->id1 = got_object_id_dup(id1);
3413 if (s->id1 == NULL)
3414 return got_error_from_errno("got_object_id_dup");
3415 } else
3416 s->id1 = NULL;
3418 s->id2 = got_object_id_dup(id2);
3419 if (s->id2 == NULL) {
3420 free(s->id1);
3421 s->id1 = NULL;
3422 return got_error_from_errno("got_object_id_dup");
3424 s->f = NULL;
3425 s->first_displayed_line = 1;
3426 s->last_displayed_line = view->nlines;
3427 s->diff_context = diff_context;
3428 s->ignore_whitespace = ignore_whitespace;
3429 s->force_text_diff = force_text_diff;
3430 s->log_view = log_view;
3431 s->repo = repo;
3433 SIMPLEQ_INIT(&s->colors);
3434 if (has_colors() && getenv("TOG_COLORS") != NULL) {
3435 err = add_color(&s->colors,
3436 "^-", TOG_COLOR_DIFF_MINUS,
3437 get_color_value("TOG_COLOR_DIFF_MINUS"));
3438 if (err)
3439 return err;
3440 err = add_color(&s->colors, "^\\+",
3441 TOG_COLOR_DIFF_PLUS,
3442 get_color_value("TOG_COLOR_DIFF_PLUS"));
3443 if (err) {
3444 free_colors(&s->colors);
3445 return err;
3447 err = add_color(&s->colors,
3448 "^@@", TOG_COLOR_DIFF_CHUNK_HEADER,
3449 get_color_value("TOG_COLOR_DIFF_CHUNK_HEADER"));
3450 if (err) {
3451 free_colors(&s->colors);
3452 return err;
3455 err = add_color(&s->colors,
3456 "^(commit [0-9a-f]|(blob|file) [-+] |[MDmA] [^ ])",
3457 TOG_COLOR_DIFF_META,
3458 get_color_value("TOG_COLOR_DIFF_META"));
3459 if (err) {
3460 free_colors(&s->colors);
3461 return err;
3464 err = add_color(&s->colors,
3465 "^(from|via): ", TOG_COLOR_AUTHOR,
3466 get_color_value("TOG_COLOR_AUTHOR"));
3467 if (err) {
3468 free_colors(&s->colors);
3469 return err;
3472 err = add_color(&s->colors,
3473 "^date: ", TOG_COLOR_DATE,
3474 get_color_value("TOG_COLOR_DATE"));
3475 if (err) {
3476 free_colors(&s->colors);
3477 return err;
3481 err = got_ref_list(&s->refs, repo, NULL, got_ref_cmp_by_name, NULL);
3482 if (err) {
3483 free(s->id1);
3484 s->id1 = NULL;
3485 free(s->id2);
3486 s->id2 = NULL;
3487 free_colors(&s->colors);
3488 return err;
3491 if (log_view && view_is_splitscreen(view))
3492 show_log_view(log_view); /* draw vborder */
3493 diff_view_indicate_progress(view);
3495 s->line_offsets = NULL;
3496 s->nlines = 0;
3497 err = create_diff(s);
3498 if (err) {
3499 free(s->id1);
3500 s->id1 = NULL;
3501 free(s->id2);
3502 s->id2 = NULL;
3503 free_colors(&s->colors);
3504 got_ref_list_free(&s->refs);
3505 return err;
3508 view->show = show_diff_view;
3509 view->input = input_diff_view;
3510 view->close = close_diff_view;
3511 view->search_start = search_start_diff_view;
3512 view->search_next = search_next_diff_view;
3514 return NULL;
3517 static const struct got_error *
3518 close_diff_view(struct tog_view *view)
3520 const struct got_error *err = NULL;
3521 struct tog_diff_view_state *s = &view->state.diff;
3523 free(s->id1);
3524 s->id1 = NULL;
3525 free(s->id2);
3526 s->id2 = NULL;
3527 if (s->f && fclose(s->f) == EOF)
3528 err = got_error_from_errno("fclose");
3529 free_colors(&s->colors);
3530 free(s->line_offsets);
3531 s->line_offsets = NULL;
3532 s->nlines = 0;
3533 got_ref_list_free(&s->refs);
3534 return err;
3537 static const struct got_error *
3538 show_diff_view(struct tog_view *view)
3540 const struct got_error *err;
3541 struct tog_diff_view_state *s = &view->state.diff;
3542 char *id_str1 = NULL, *id_str2, *header;
3543 const char *label1, *label2;
3545 if (s->id1) {
3546 err = got_object_id_str(&id_str1, s->id1);
3547 if (err)
3548 return err;
3549 label1 = s->label1 ? : id_str1;
3550 } else
3551 label1 = "/dev/null";
3553 err = got_object_id_str(&id_str2, s->id2);
3554 if (err)
3555 return err;
3556 label2 = s->label2 ? : id_str2;
3558 if (asprintf(&header, "diff %s %s", label1, label2) == -1) {
3559 err = got_error_from_errno("asprintf");
3560 free(id_str1);
3561 free(id_str2);
3562 return err;
3564 free(id_str1);
3565 free(id_str2);
3567 return draw_file(view, header);
3570 static const struct got_error *
3571 set_selected_commit(struct tog_diff_view_state *s,
3572 struct commit_queue_entry *entry)
3574 const struct got_error *err;
3575 const struct got_object_id_queue *parent_ids;
3576 struct got_commit_object *selected_commit;
3577 struct got_object_qid *pid;
3579 free(s->id2);
3580 s->id2 = got_object_id_dup(entry->id);
3581 if (s->id2 == NULL)
3582 return got_error_from_errno("got_object_id_dup");
3584 err = got_object_open_as_commit(&selected_commit, s->repo, entry->id);
3585 if (err)
3586 return err;
3587 parent_ids = got_object_commit_get_parent_ids(selected_commit);
3588 free(s->id1);
3589 pid = SIMPLEQ_FIRST(parent_ids);
3590 s->id1 = pid ? got_object_id_dup(pid->id) : NULL;
3591 got_object_commit_close(selected_commit);
3592 return NULL;
3595 static const struct got_error *
3596 input_diff_view(struct tog_view **new_view, struct tog_view *view, int ch)
3598 const struct got_error *err = NULL;
3599 struct tog_diff_view_state *s = &view->state.diff;
3600 struct tog_log_view_state *ls;
3601 struct commit_queue_entry *entry;
3602 int i;
3604 switch (ch) {
3605 case 'a':
3606 case 'w':
3607 if (ch == 'a')
3608 s->force_text_diff = !s->force_text_diff;
3609 if (ch == 'w')
3610 s->ignore_whitespace = !s->ignore_whitespace;
3611 wclear(view->window);
3612 s->first_displayed_line = 1;
3613 s->last_displayed_line = view->nlines;
3614 diff_view_indicate_progress(view);
3615 err = create_diff(s);
3616 break;
3617 case 'k':
3618 case KEY_UP:
3619 if (s->first_displayed_line > 1)
3620 s->first_displayed_line--;
3621 break;
3622 case KEY_PPAGE:
3623 case CTRL('b'):
3624 if (s->first_displayed_line == 1)
3625 break;
3626 i = 0;
3627 while (i++ < view->nlines - 1 &&
3628 s->first_displayed_line > 1)
3629 s->first_displayed_line--;
3630 break;
3631 case 'j':
3632 case KEY_DOWN:
3633 if (!s->eof)
3634 s->first_displayed_line++;
3635 break;
3636 case KEY_NPAGE:
3637 case CTRL('f'):
3638 case ' ':
3639 if (s->eof)
3640 break;
3641 i = 0;
3642 while (!s->eof && i++ < view->nlines - 1) {
3643 char *line;
3644 line = parse_next_line(s->f, NULL);
3645 s->first_displayed_line++;
3646 if (line == NULL)
3647 break;
3649 break;
3650 case '[':
3651 if (s->diff_context > 0) {
3652 s->diff_context--;
3653 diff_view_indicate_progress(view);
3654 err = create_diff(s);
3655 if (s->first_displayed_line + view->nlines - 1 >
3656 s->nlines) {
3657 s->first_displayed_line = 1;
3658 s->last_displayed_line = view->nlines;
3661 break;
3662 case ']':
3663 if (s->diff_context < GOT_DIFF_MAX_CONTEXT) {
3664 s->diff_context++;
3665 diff_view_indicate_progress(view);
3666 err = create_diff(s);
3668 break;
3669 case '<':
3670 case ',':
3671 if (s->log_view == NULL)
3672 break;
3673 ls = &s->log_view->state.log;
3674 entry = TAILQ_PREV(ls->selected_entry,
3675 commit_queue_head, entry);
3676 if (entry == NULL)
3677 break;
3679 err = input_log_view(NULL, s->log_view, KEY_UP);
3680 if (err)
3681 break;
3683 err = set_selected_commit(s, entry);
3684 if (err)
3685 break;
3687 s->first_displayed_line = 1;
3688 s->last_displayed_line = view->nlines;
3690 diff_view_indicate_progress(view);
3691 err = create_diff(s);
3692 break;
3693 case '>':
3694 case '.':
3695 if (s->log_view == NULL)
3696 break;
3697 ls = &s->log_view->state.log;
3699 if (TAILQ_NEXT(ls->selected_entry, entry) == NULL) {
3700 ls->thread_args.commits_needed++;
3701 err = trigger_log_thread(s->log_view, 1);
3702 if (err)
3703 break;
3705 err = input_log_view(NULL, s->log_view, KEY_DOWN);
3706 if (err)
3707 break;
3709 entry = TAILQ_NEXT(ls->selected_entry, entry);
3710 if (entry == NULL)
3711 break;
3713 err = set_selected_commit(s, entry);
3714 if (err)
3715 break;
3717 s->first_displayed_line = 1;
3718 s->last_displayed_line = view->nlines;
3720 diff_view_indicate_progress(view);
3721 err = create_diff(s);
3722 break;
3723 default:
3724 break;
3727 return err;
3730 static const struct got_error *
3731 cmd_diff(int argc, char *argv[])
3733 const struct got_error *error = NULL;
3734 struct got_repository *repo = NULL;
3735 struct got_worktree *worktree = NULL;
3736 struct got_object_id *id1 = NULL, *id2 = NULL;
3737 char *repo_path = NULL, *cwd = NULL;
3738 char *id_str1 = NULL, *id_str2 = NULL;
3739 char *label1 = NULL, *label2 = NULL;
3740 int diff_context = 3, ignore_whitespace = 0;
3741 int ch, force_text_diff = 0;
3742 const char *errstr;
3743 struct tog_view *view;
3745 while ((ch = getopt(argc, argv, "aC:r:w")) != -1) {
3746 switch (ch) {
3747 case 'a':
3748 force_text_diff = 1;
3749 break;
3750 case 'C':
3751 diff_context = strtonum(optarg, 0, GOT_DIFF_MAX_CONTEXT,
3752 &errstr);
3753 if (errstr != NULL)
3754 err(1, "-C option %s", errstr);
3755 break;
3756 case 'r':
3757 repo_path = realpath(optarg, NULL);
3758 if (repo_path == NULL)
3759 return got_error_from_errno2("realpath",
3760 optarg);
3761 got_path_strip_trailing_slashes(repo_path);
3762 break;
3763 case 'w':
3764 ignore_whitespace = 1;
3765 break;
3766 default:
3767 usage_diff();
3768 /* NOTREACHED */
3772 argc -= optind;
3773 argv += optind;
3775 if (argc == 0) {
3776 usage_diff(); /* TODO show local worktree changes */
3777 } else if (argc == 2) {
3778 id_str1 = argv[0];
3779 id_str2 = argv[1];
3780 } else
3781 usage_diff();
3783 cwd = getcwd(NULL, 0);
3784 if (cwd == NULL)
3785 return got_error_from_errno("getcwd");
3787 error = got_worktree_open(&worktree, cwd);
3788 if (error && error->code != GOT_ERR_NOT_WORKTREE)
3789 goto done;
3791 if (repo_path == NULL) {
3792 if (worktree)
3793 repo_path =
3794 strdup(got_worktree_get_repo_path(worktree));
3795 else
3796 repo_path = strdup(cwd);
3798 if (repo_path == NULL) {
3799 error = got_error_from_errno("strdup");
3800 goto done;
3803 error = got_repo_open(&repo, repo_path, NULL);
3804 if (error)
3805 goto done;
3807 init_curses();
3809 error = apply_unveil(got_repo_get_path(repo), NULL);
3810 if (error)
3811 goto done;
3813 error = got_repo_match_object_id(&id1, &label1, id_str1,
3814 GOT_OBJ_TYPE_ANY, 1, repo);
3815 if (error)
3816 goto done;
3818 error = got_repo_match_object_id(&id2, &label2, id_str2,
3819 GOT_OBJ_TYPE_ANY, 1, repo);
3820 if (error)
3821 goto done;
3823 view = view_open(0, 0, 0, 0, TOG_VIEW_DIFF);
3824 if (view == NULL) {
3825 error = got_error_from_errno("view_open");
3826 goto done;
3828 error = open_diff_view(view, id1, id2, label1, label2, diff_context,
3829 ignore_whitespace, force_text_diff, NULL, repo);
3830 if (error)
3831 goto done;
3832 error = view_loop(view);
3833 done:
3834 free(label1);
3835 free(label2);
3836 free(repo_path);
3837 free(cwd);
3838 if (repo)
3839 got_repo_close(repo);
3840 if (worktree)
3841 got_worktree_close(worktree);
3842 return error;
3845 __dead static void
3846 usage_blame(void)
3848 endwin();
3849 fprintf(stderr, "usage: %s blame [-c commit] [-r repository-path] path\n",
3850 getprogname());
3851 exit(1);
3854 struct tog_blame_line {
3855 int annotated;
3856 struct got_object_id *id;
3859 static const struct got_error *
3860 draw_blame(struct tog_view *view)
3862 struct tog_blame_view_state *s = &view->state.blame;
3863 struct tog_blame *blame = &s->blame;
3864 regmatch_t *regmatch = &view->regmatch;
3865 const struct got_error *err;
3866 int lineno = 0, nprinted = 0;
3867 char *line;
3868 size_t len;
3869 wchar_t *wline;
3870 int width;
3871 struct tog_blame_line *blame_line;
3872 struct got_object_id *prev_id = NULL;
3873 char *id_str;
3874 struct tog_color *tc;
3876 err = got_object_id_str(&id_str, s->blamed_commit->id);
3877 if (err)
3878 return err;
3880 rewind(blame->f);
3881 werase(view->window);
3883 if (asprintf(&line, "commit %s", id_str) == -1) {
3884 err = got_error_from_errno("asprintf");
3885 free(id_str);
3886 return err;
3889 err = format_line(&wline, &width, line, view->ncols, 0);
3890 free(line);
3891 line = NULL;
3892 if (err)
3893 return err;
3894 if (view_needs_focus_indication(view))
3895 wstandout(view->window);
3896 tc = get_color(&s->colors, TOG_COLOR_COMMIT);
3897 if (tc)
3898 wattr_on(view->window,
3899 COLOR_PAIR(tc->colorpair), NULL);
3900 waddwstr(view->window, wline);
3901 if (tc)
3902 wattr_off(view->window,
3903 COLOR_PAIR(tc->colorpair), NULL);
3904 if (view_needs_focus_indication(view))
3905 wstandend(view->window);
3906 free(wline);
3907 wline = NULL;
3908 if (width < view->ncols - 1)
3909 waddch(view->window, '\n');
3911 if (asprintf(&line, "[%d/%d] %s%s",
3912 s->first_displayed_line - 1 + s->selected_line, blame->nlines,
3913 s->blame_complete ? "" : "annotating... ", s->path) == -1) {
3914 free(id_str);
3915 return got_error_from_errno("asprintf");
3917 free(id_str);
3918 err = format_line(&wline, &width, line, view->ncols, 0);
3919 free(line);
3920 line = NULL;
3921 if (err)
3922 return err;
3923 waddwstr(view->window, wline);
3924 free(wline);
3925 wline = NULL;
3926 if (width < view->ncols - 1)
3927 waddch(view->window, '\n');
3929 s->eof = 0;
3930 while (nprinted < view->nlines - 2) {
3931 line = parse_next_line(blame->f, &len);
3932 if (line == NULL) {
3933 s->eof = 1;
3934 break;
3936 if (++lineno < s->first_displayed_line) {
3937 free(line);
3938 continue;
3941 if (view->focussed && nprinted == s->selected_line - 1)
3942 wstandout(view->window);
3944 if (blame->nlines > 0) {
3945 blame_line = &blame->lines[lineno - 1];
3946 if (blame_line->annotated && prev_id &&
3947 got_object_id_cmp(prev_id, blame_line->id) == 0 &&
3948 !(view->focussed &&
3949 nprinted == s->selected_line - 1)) {
3950 waddstr(view->window, " ");
3951 } else if (blame_line->annotated) {
3952 char *id_str;
3953 err = got_object_id_str(&id_str, blame_line->id);
3954 if (err) {
3955 free(line);
3956 return err;
3958 tc = get_color(&s->colors, TOG_COLOR_COMMIT);
3959 if (tc)
3960 wattr_on(view->window,
3961 COLOR_PAIR(tc->colorpair), NULL);
3962 wprintw(view->window, "%.8s", id_str);
3963 if (tc)
3964 wattr_off(view->window,
3965 COLOR_PAIR(tc->colorpair), NULL);
3966 free(id_str);
3967 prev_id = blame_line->id;
3968 } else {
3969 waddstr(view->window, "........");
3970 prev_id = NULL;
3972 } else {
3973 waddstr(view->window, "........");
3974 prev_id = NULL;
3977 if (view->focussed && nprinted == s->selected_line - 1)
3978 wstandend(view->window);
3979 waddstr(view->window, " ");
3981 if (view->ncols <= 9) {
3982 width = 9;
3983 wline = wcsdup(L"");
3984 if (wline == NULL) {
3985 err = got_error_from_errno("wcsdup");
3986 free(line);
3987 return err;
3989 } else if (s->first_displayed_line + nprinted ==
3990 s->matched_line &&
3991 regmatch->rm_so >= 0 && regmatch->rm_so < regmatch->rm_eo) {
3992 err = add_matched_line(&width, line, view->ncols - 9, 9,
3993 view->window, regmatch);
3994 if (err) {
3995 free(line);
3996 return err;
3998 width += 9;
3999 } else {
4000 err = format_line(&wline, &width, line,
4001 view->ncols - 9, 9);
4002 waddwstr(view->window, wline);
4003 free(wline);
4004 wline = NULL;
4005 width += 9;
4008 if (width <= view->ncols - 1)
4009 waddch(view->window, '\n');
4010 if (++nprinted == 1)
4011 s->first_displayed_line = lineno;
4012 free(line);
4014 s->last_displayed_line = lineno;
4016 view_vborder(view);
4018 return NULL;
4021 static const struct got_error *
4022 blame_cb(void *arg, int nlines, int lineno, struct got_object_id *id)
4024 const struct got_error *err = NULL;
4025 struct tog_blame_cb_args *a = arg;
4026 struct tog_blame_line *line;
4027 int errcode;
4029 if (nlines != a->nlines ||
4030 (lineno != -1 && lineno < 1) || lineno > a->nlines)
4031 return got_error(GOT_ERR_RANGE);
4033 errcode = pthread_mutex_lock(&tog_mutex);
4034 if (errcode)
4035 return got_error_set_errno(errcode, "pthread_mutex_lock");
4037 if (*a->quit) { /* user has quit the blame view */
4038 err = got_error(GOT_ERR_ITER_COMPLETED);
4039 goto done;
4042 if (lineno == -1)
4043 goto done; /* no change in this commit */
4045 line = &a->lines[lineno - 1];
4046 if (line->annotated)
4047 goto done;
4049 line->id = got_object_id_dup(id);
4050 if (line->id == NULL) {
4051 err = got_error_from_errno("got_object_id_dup");
4052 goto done;
4054 line->annotated = 1;
4055 done:
4056 errcode = pthread_mutex_unlock(&tog_mutex);
4057 if (errcode)
4058 err = got_error_set_errno(errcode, "pthread_mutex_unlock");
4059 return err;
4062 static void *
4063 blame_thread(void *arg)
4065 const struct got_error *err;
4066 struct tog_blame_thread_args *ta = arg;
4067 struct tog_blame_cb_args *a = ta->cb_args;
4068 int errcode;
4070 err = block_signals_used_by_main_thread();
4071 if (err)
4072 return (void *)err;
4074 err = got_blame(ta->path, a->commit_id, ta->repo,
4075 blame_cb, ta->cb_args, ta->cancel_cb, ta->cancel_arg);
4076 if (err && err->code == GOT_ERR_CANCELLED)
4077 err = NULL;
4079 errcode = pthread_mutex_lock(&tog_mutex);
4080 if (errcode)
4081 return (void *)got_error_set_errno(errcode,
4082 "pthread_mutex_lock");
4084 got_repo_close(ta->repo);
4085 ta->repo = NULL;
4086 *ta->complete = 1;
4088 errcode = pthread_mutex_unlock(&tog_mutex);
4089 if (errcode && err == NULL)
4090 err = got_error_set_errno(errcode, "pthread_mutex_unlock");
4092 return (void *)err;
4095 static struct got_object_id *
4096 get_selected_commit_id(struct tog_blame_line *lines, int nlines,
4097 int first_displayed_line, int selected_line)
4099 struct tog_blame_line *line;
4101 if (nlines <= 0)
4102 return NULL;
4104 line = &lines[first_displayed_line - 1 + selected_line - 1];
4105 if (!line->annotated)
4106 return NULL;
4108 return line->id;
4111 static const struct got_error *
4112 stop_blame(struct tog_blame *blame)
4114 const struct got_error *err = NULL;
4115 int i;
4117 if (blame->thread) {
4118 int errcode;
4119 errcode = pthread_mutex_unlock(&tog_mutex);
4120 if (errcode)
4121 return got_error_set_errno(errcode,
4122 "pthread_mutex_unlock");
4123 errcode = pthread_join(blame->thread, (void **)&err);
4124 if (errcode)
4125 return got_error_set_errno(errcode, "pthread_join");
4126 errcode = pthread_mutex_lock(&tog_mutex);
4127 if (errcode)
4128 return got_error_set_errno(errcode,
4129 "pthread_mutex_lock");
4130 if (err && err->code == GOT_ERR_ITER_COMPLETED)
4131 err = NULL;
4132 blame->thread = NULL;
4134 if (blame->thread_args.repo) {
4135 got_repo_close(blame->thread_args.repo);
4136 blame->thread_args.repo = NULL;
4138 if (blame->f) {
4139 if (fclose(blame->f) != 0 && err == NULL)
4140 err = got_error_from_errno("fclose");
4141 blame->f = NULL;
4143 if (blame->lines) {
4144 for (i = 0; i < blame->nlines; i++)
4145 free(blame->lines[i].id);
4146 free(blame->lines);
4147 blame->lines = NULL;
4149 free(blame->cb_args.commit_id);
4150 blame->cb_args.commit_id = NULL;
4152 return err;
4155 static const struct got_error *
4156 cancel_blame_view(void *arg)
4158 const struct got_error *err = NULL;
4159 int *done = arg;
4160 int errcode;
4162 errcode = pthread_mutex_lock(&tog_mutex);
4163 if (errcode)
4164 return got_error_set_errno(errcode,
4165 "pthread_mutex_unlock");
4167 if (*done)
4168 err = got_error(GOT_ERR_CANCELLED);
4170 errcode = pthread_mutex_unlock(&tog_mutex);
4171 if (errcode)
4172 return got_error_set_errno(errcode,
4173 "pthread_mutex_lock");
4175 return err;
4178 static const struct got_error *
4179 run_blame(struct tog_view *view)
4181 struct tog_blame_view_state *s = &view->state.blame;
4182 struct tog_blame *blame = &s->blame;
4183 const struct got_error *err = NULL;
4184 struct got_blob_object *blob = NULL;
4185 struct got_repository *thread_repo = NULL;
4186 struct got_object_id *obj_id = NULL;
4187 int obj_type;
4189 err = got_object_id_by_path(&obj_id, s->repo, s->blamed_commit->id,
4190 s->path);
4191 if (err)
4192 return err;
4194 err = got_object_get_type(&obj_type, s->repo, obj_id);
4195 if (err)
4196 goto done;
4198 if (obj_type != GOT_OBJ_TYPE_BLOB) {
4199 err = got_error(GOT_ERR_OBJ_TYPE);
4200 goto done;
4203 err = got_object_open_as_blob(&blob, s->repo, obj_id, 8192);
4204 if (err)
4205 goto done;
4206 blame->f = got_opentemp();
4207 if (blame->f == NULL) {
4208 err = got_error_from_errno("got_opentemp");
4209 goto done;
4211 err = got_object_blob_dump_to_file(&blame->filesize, &blame->nlines,
4212 &blame->line_offsets, blame->f, blob);
4213 if (err || blame->nlines == 0)
4214 goto done;
4216 /* Don't include \n at EOF in the blame line count. */
4217 if (blame->line_offsets[blame->nlines - 1] == blame->filesize)
4218 blame->nlines--;
4220 blame->lines = calloc(blame->nlines, sizeof(*blame->lines));
4221 if (blame->lines == NULL) {
4222 err = got_error_from_errno("calloc");
4223 goto done;
4226 err = got_repo_open(&thread_repo, got_repo_get_path(s->repo), NULL);
4227 if (err)
4228 goto done;
4230 blame->cb_args.view = view;
4231 blame->cb_args.lines = blame->lines;
4232 blame->cb_args.nlines = blame->nlines;
4233 blame->cb_args.commit_id = got_object_id_dup(s->blamed_commit->id);
4234 if (blame->cb_args.commit_id == NULL) {
4235 err = got_error_from_errno("got_object_id_dup");
4236 goto done;
4238 blame->cb_args.quit = &s->done;
4240 blame->thread_args.path = s->path;
4241 blame->thread_args.repo = thread_repo;
4242 blame->thread_args.cb_args = &blame->cb_args;
4243 blame->thread_args.complete = &s->blame_complete;
4244 blame->thread_args.cancel_cb = cancel_blame_view;
4245 blame->thread_args.cancel_arg = &s->done;
4246 s->blame_complete = 0;
4248 done:
4249 if (blob)
4250 got_object_blob_close(blob);
4251 free(obj_id);
4252 if (err)
4253 stop_blame(blame);
4254 return err;
4257 static const struct got_error *
4258 open_blame_view(struct tog_view *view, char *path,
4259 struct got_object_id *commit_id, struct got_repository *repo)
4261 const struct got_error *err = NULL;
4262 struct tog_blame_view_state *s = &view->state.blame;
4264 SIMPLEQ_INIT(&s->blamed_commits);
4266 s->path = strdup(path);
4267 if (s->path == NULL)
4268 return got_error_from_errno("strdup");
4270 err = got_object_qid_alloc(&s->blamed_commit, commit_id);
4271 if (err) {
4272 free(s->path);
4273 return err;
4276 SIMPLEQ_INSERT_HEAD(&s->blamed_commits, s->blamed_commit, entry);
4277 s->first_displayed_line = 1;
4278 s->last_displayed_line = view->nlines;
4279 s->selected_line = 1;
4280 s->blame_complete = 0;
4281 s->repo = repo;
4282 s->commit_id = commit_id;
4283 memset(&s->blame, 0, sizeof(s->blame));
4285 SIMPLEQ_INIT(&s->colors);
4286 if (has_colors() && getenv("TOG_COLORS") != NULL) {
4287 err = add_color(&s->colors, "^", TOG_COLOR_COMMIT,
4288 get_color_value("TOG_COLOR_COMMIT"));
4289 if (err)
4290 return err;
4293 view->show = show_blame_view;
4294 view->input = input_blame_view;
4295 view->close = close_blame_view;
4296 view->search_start = search_start_blame_view;
4297 view->search_next = search_next_blame_view;
4299 return run_blame(view);
4302 static const struct got_error *
4303 close_blame_view(struct tog_view *view)
4305 const struct got_error *err = NULL;
4306 struct tog_blame_view_state *s = &view->state.blame;
4308 if (s->blame.thread)
4309 err = stop_blame(&s->blame);
4311 while (!SIMPLEQ_EMPTY(&s->blamed_commits)) {
4312 struct got_object_qid *blamed_commit;
4313 blamed_commit = SIMPLEQ_FIRST(&s->blamed_commits);
4314 SIMPLEQ_REMOVE_HEAD(&s->blamed_commits, entry);
4315 got_object_qid_free(blamed_commit);
4318 free(s->path);
4319 free_colors(&s->colors);
4321 return err;
4324 static const struct got_error *
4325 search_start_blame_view(struct tog_view *view)
4327 struct tog_blame_view_state *s = &view->state.blame;
4329 s->matched_line = 0;
4330 return NULL;
4333 static const struct got_error *
4334 search_next_blame_view(struct tog_view *view)
4336 struct tog_blame_view_state *s = &view->state.blame;
4337 int lineno;
4339 if (!view->searching) {
4340 view->search_next_done = TOG_SEARCH_HAVE_MORE;
4341 return NULL;
4344 if (s->matched_line) {
4345 if (view->searching == TOG_SEARCH_FORWARD)
4346 lineno = s->matched_line + 1;
4347 else
4348 lineno = s->matched_line - 1;
4349 } else {
4350 if (view->searching == TOG_SEARCH_FORWARD)
4351 lineno = 1;
4352 else
4353 lineno = s->blame.nlines;
4356 while (1) {
4357 char *line = NULL;
4358 off_t offset;
4359 size_t len;
4361 if (lineno <= 0 || lineno > s->blame.nlines) {
4362 if (s->matched_line == 0) {
4363 view->search_next_done = TOG_SEARCH_HAVE_MORE;
4364 free(line);
4365 break;
4368 if (view->searching == TOG_SEARCH_FORWARD)
4369 lineno = 1;
4370 else
4371 lineno = s->blame.nlines;
4374 offset = s->blame.line_offsets[lineno - 1];
4375 if (fseeko(s->blame.f, offset, SEEK_SET) != 0) {
4376 free(line);
4377 return got_error_from_errno("fseeko");
4379 free(line);
4380 line = parse_next_line(s->blame.f, &len);
4381 if (line &&
4382 match_line(line, &view->regex, 1, &view->regmatch)) {
4383 view->search_next_done = TOG_SEARCH_HAVE_MORE;
4384 s->matched_line = lineno;
4385 free(line);
4386 break;
4388 free(line);
4389 if (view->searching == TOG_SEARCH_FORWARD)
4390 lineno++;
4391 else
4392 lineno--;
4395 if (s->matched_line) {
4396 s->first_displayed_line = s->matched_line;
4397 s->selected_line = 1;
4400 return NULL;
4403 static const struct got_error *
4404 show_blame_view(struct tog_view *view)
4406 const struct got_error *err = NULL;
4407 struct tog_blame_view_state *s = &view->state.blame;
4408 int errcode;
4410 if (s->blame.thread == NULL) {
4411 errcode = pthread_create(&s->blame.thread, NULL, blame_thread,
4412 &s->blame.thread_args);
4413 if (errcode)
4414 return got_error_set_errno(errcode, "pthread_create");
4416 halfdelay(1); /* fast refresh while annotating */
4419 if (s->blame_complete)
4420 halfdelay(10); /* disable fast refresh */
4422 err = draw_blame(view);
4424 view_vborder(view);
4425 return err;
4428 static const struct got_error *
4429 input_blame_view(struct tog_view **new_view, struct tog_view *view, int ch)
4431 const struct got_error *err = NULL, *thread_err = NULL;
4432 struct tog_view *diff_view;
4433 struct tog_blame_view_state *s = &view->state.blame;
4434 int begin_x = 0;
4436 switch (ch) {
4437 case 'q':
4438 s->done = 1;
4439 break;
4440 case 'k':
4441 case KEY_UP:
4442 if (s->selected_line > 1)
4443 s->selected_line--;
4444 else if (s->selected_line == 1 &&
4445 s->first_displayed_line > 1)
4446 s->first_displayed_line--;
4447 break;
4448 case KEY_PPAGE:
4449 case CTRL('b'):
4450 if (s->first_displayed_line == 1) {
4451 s->selected_line = 1;
4452 break;
4454 if (s->first_displayed_line > view->nlines - 2)
4455 s->first_displayed_line -=
4456 (view->nlines - 2);
4457 else
4458 s->first_displayed_line = 1;
4459 break;
4460 case 'j':
4461 case KEY_DOWN:
4462 if (s->selected_line < view->nlines - 2 &&
4463 s->first_displayed_line +
4464 s->selected_line <= s->blame.nlines)
4465 s->selected_line++;
4466 else if (s->last_displayed_line <
4467 s->blame.nlines)
4468 s->first_displayed_line++;
4469 break;
4470 case 'b':
4471 case 'p': {
4472 struct got_object_id *id = NULL;
4473 id = get_selected_commit_id(s->blame.lines, s->blame.nlines,
4474 s->first_displayed_line, s->selected_line);
4475 if (id == NULL)
4476 break;
4477 if (ch == 'p') {
4478 struct got_commit_object *commit;
4479 struct got_object_qid *pid;
4480 struct got_object_id *blob_id = NULL;
4481 int obj_type;
4482 err = got_object_open_as_commit(&commit,
4483 s->repo, id);
4484 if (err)
4485 break;
4486 pid = SIMPLEQ_FIRST(
4487 got_object_commit_get_parent_ids(commit));
4488 if (pid == NULL) {
4489 got_object_commit_close(commit);
4490 break;
4492 /* Check if path history ends here. */
4493 err = got_object_id_by_path(&blob_id, s->repo,
4494 pid->id, s->path);
4495 if (err) {
4496 if (err->code == GOT_ERR_NO_TREE_ENTRY)
4497 err = NULL;
4498 got_object_commit_close(commit);
4499 break;
4501 err = got_object_get_type(&obj_type, s->repo,
4502 blob_id);
4503 free(blob_id);
4504 /* Can't blame non-blob type objects. */
4505 if (obj_type != GOT_OBJ_TYPE_BLOB) {
4506 got_object_commit_close(commit);
4507 break;
4509 err = got_object_qid_alloc(&s->blamed_commit,
4510 pid->id);
4511 got_object_commit_close(commit);
4512 } else {
4513 if (got_object_id_cmp(id,
4514 s->blamed_commit->id) == 0)
4515 break;
4516 err = got_object_qid_alloc(&s->blamed_commit,
4517 id);
4519 if (err)
4520 break;
4521 s->done = 1;
4522 thread_err = stop_blame(&s->blame);
4523 s->done = 0;
4524 if (thread_err)
4525 break;
4526 SIMPLEQ_INSERT_HEAD(&s->blamed_commits,
4527 s->blamed_commit, entry);
4528 err = run_blame(view);
4529 if (err)
4530 break;
4531 break;
4533 case 'B': {
4534 struct got_object_qid *first;
4535 first = SIMPLEQ_FIRST(&s->blamed_commits);
4536 if (!got_object_id_cmp(first->id, s->commit_id))
4537 break;
4538 s->done = 1;
4539 thread_err = stop_blame(&s->blame);
4540 s->done = 0;
4541 if (thread_err)
4542 break;
4543 SIMPLEQ_REMOVE_HEAD(&s->blamed_commits, entry);
4544 got_object_qid_free(s->blamed_commit);
4545 s->blamed_commit =
4546 SIMPLEQ_FIRST(&s->blamed_commits);
4547 err = run_blame(view);
4548 if (err)
4549 break;
4550 break;
4552 case KEY_ENTER:
4553 case '\r': {
4554 struct got_object_id *id = NULL;
4555 struct got_object_qid *pid;
4556 struct got_commit_object *commit = NULL;
4557 id = get_selected_commit_id(s->blame.lines, s->blame.nlines,
4558 s->first_displayed_line, s->selected_line);
4559 if (id == NULL)
4560 break;
4561 err = got_object_open_as_commit(&commit, s->repo, id);
4562 if (err)
4563 break;
4564 pid = SIMPLEQ_FIRST(
4565 got_object_commit_get_parent_ids(commit));
4566 if (view_is_parent_view(view))
4567 begin_x = view_split_begin_x(view->begin_x);
4568 diff_view = view_open(0, 0, 0, begin_x, TOG_VIEW_DIFF);
4569 if (diff_view == NULL) {
4570 got_object_commit_close(commit);
4571 err = got_error_from_errno("view_open");
4572 break;
4574 err = open_diff_view(diff_view, pid ? pid->id : NULL,
4575 id, NULL, NULL, 3, 0, 0, NULL, s->repo);
4576 got_object_commit_close(commit);
4577 if (err) {
4578 view_close(diff_view);
4579 break;
4581 view->focussed = 0;
4582 diff_view->focussed = 1;
4583 if (view_is_parent_view(view)) {
4584 err = view_close_child(view);
4585 if (err)
4586 break;
4587 view_set_child(view, diff_view);
4588 view->focus_child = 1;
4589 } else
4590 *new_view = diff_view;
4591 if (err)
4592 break;
4593 break;
4595 case KEY_NPAGE:
4596 case CTRL('f'):
4597 case ' ':
4598 if (s->last_displayed_line >= s->blame.nlines &&
4599 s->selected_line >= MIN(s->blame.nlines,
4600 view->nlines - 2)) {
4601 break;
4603 if (s->last_displayed_line >= s->blame.nlines &&
4604 s->selected_line < view->nlines - 2) {
4605 s->selected_line = MIN(s->blame.nlines,
4606 view->nlines - 2);
4607 break;
4609 if (s->last_displayed_line + view->nlines - 2
4610 <= s->blame.nlines)
4611 s->first_displayed_line +=
4612 view->nlines - 2;
4613 else
4614 s->first_displayed_line =
4615 s->blame.nlines -
4616 (view->nlines - 3);
4617 break;
4618 case KEY_RESIZE:
4619 if (s->selected_line > view->nlines - 2) {
4620 s->selected_line = MIN(s->blame.nlines,
4621 view->nlines - 2);
4623 break;
4624 default:
4625 break;
4627 return thread_err ? thread_err : err;
4630 static const struct got_error *
4631 cmd_blame(int argc, char *argv[])
4633 const struct got_error *error;
4634 struct got_repository *repo = NULL;
4635 struct got_worktree *worktree = NULL;
4636 char *cwd = NULL, *repo_path = NULL, *in_repo_path = NULL;
4637 char *link_target = NULL;
4638 struct got_object_id *commit_id = NULL;
4639 char *commit_id_str = NULL;
4640 int ch;
4641 struct tog_view *view;
4643 while ((ch = getopt(argc, argv, "c:r:")) != -1) {
4644 switch (ch) {
4645 case 'c':
4646 commit_id_str = optarg;
4647 break;
4648 case 'r':
4649 repo_path = realpath(optarg, NULL);
4650 if (repo_path == NULL)
4651 return got_error_from_errno2("realpath",
4652 optarg);
4653 break;
4654 default:
4655 usage_blame();
4656 /* NOTREACHED */
4660 argc -= optind;
4661 argv += optind;
4663 if (argc != 1)
4664 usage_blame();
4666 cwd = getcwd(NULL, 0);
4667 if (cwd == NULL)
4668 return got_error_from_errno("getcwd");
4670 error = got_worktree_open(&worktree, cwd);
4671 if (error && error->code != GOT_ERR_NOT_WORKTREE)
4672 goto done;
4674 if (repo_path == NULL) {
4675 if (worktree)
4676 repo_path =
4677 strdup(got_worktree_get_repo_path(worktree));
4678 else
4679 repo_path = strdup(cwd);
4681 if (repo_path == NULL) {
4682 error = got_error_from_errno("strdup");
4683 goto done;
4686 error = got_repo_open(&repo, repo_path, NULL);
4687 if (error != NULL)
4688 goto done;
4690 error = get_in_repo_path_from_argv0(&in_repo_path, argc, argv, repo,
4691 worktree);
4692 if (error)
4693 goto done;
4695 init_curses();
4697 error = apply_unveil(got_repo_get_path(repo), NULL);
4698 if (error)
4699 goto done;
4701 if (commit_id_str == NULL) {
4702 struct got_reference *head_ref;
4703 error = got_ref_open(&head_ref, repo, worktree ?
4704 got_worktree_get_head_ref_name(worktree) : GOT_REF_HEAD, 0);
4705 if (error != NULL)
4706 goto done;
4707 error = got_ref_resolve(&commit_id, repo, head_ref);
4708 got_ref_close(head_ref);
4709 } else {
4710 error = got_repo_match_object_id(&commit_id, NULL,
4711 commit_id_str, GOT_OBJ_TYPE_COMMIT, 1, repo);
4713 if (error != NULL)
4714 goto done;
4716 view = view_open(0, 0, 0, 0, TOG_VIEW_BLAME);
4717 if (view == NULL) {
4718 error = got_error_from_errno("view_open");
4719 goto done;
4722 error = got_object_resolve_symlinks(&link_target, in_repo_path,
4723 commit_id, repo);
4724 if (error)
4725 goto done;
4727 error = open_blame_view(view, link_target ? link_target : in_repo_path,
4728 commit_id, repo);
4729 if (error)
4730 goto done;
4731 if (worktree) {
4732 /* Release work tree lock. */
4733 got_worktree_close(worktree);
4734 worktree = NULL;
4736 error = view_loop(view);
4737 done:
4738 free(repo_path);
4739 free(in_repo_path);
4740 free(link_target);
4741 free(cwd);
4742 free(commit_id);
4743 if (worktree)
4744 got_worktree_close(worktree);
4745 if (repo)
4746 got_repo_close(repo);
4747 return error;
4750 static const struct got_error *
4751 draw_tree_entries(struct tog_view *view, const char *parent_path)
4753 struct tog_tree_view_state *s = &view->state.tree;
4754 const struct got_error *err = NULL;
4755 struct got_tree_entry *te;
4756 wchar_t *wline;
4757 struct tog_color *tc;
4758 int width, n, i, nentries;
4759 int limit = view->nlines;
4761 s->ndisplayed = 0;
4763 werase(view->window);
4765 if (limit == 0)
4766 return NULL;
4768 err = format_line(&wline, &width, s->tree_label, view->ncols, 0);
4769 if (err)
4770 return err;
4771 if (view_needs_focus_indication(view))
4772 wstandout(view->window);
4773 tc = get_color(&s->colors, TOG_COLOR_COMMIT);
4774 if (tc)
4775 wattr_on(view->window,
4776 COLOR_PAIR(tc->colorpair), NULL);
4777 waddwstr(view->window, wline);
4778 if (tc)
4779 wattr_off(view->window,
4780 COLOR_PAIR(tc->colorpair), NULL);
4781 if (view_needs_focus_indication(view))
4782 wstandend(view->window);
4783 free(wline);
4784 wline = NULL;
4785 if (width < view->ncols - 1)
4786 waddch(view->window, '\n');
4787 if (--limit <= 0)
4788 return NULL;
4789 err = format_line(&wline, &width, parent_path, view->ncols, 0);
4790 if (err)
4791 return err;
4792 waddwstr(view->window, wline);
4793 free(wline);
4794 wline = NULL;
4795 if (width < view->ncols - 1)
4796 waddch(view->window, '\n');
4797 if (--limit <= 0)
4798 return NULL;
4799 waddch(view->window, '\n');
4800 if (--limit <= 0)
4801 return NULL;
4803 if (s->first_displayed_entry == NULL) {
4804 te = got_object_tree_get_first_entry(s->tree);
4805 if (s->selected == 0) {
4806 if (view->focussed)
4807 wstandout(view->window);
4808 s->selected_entry = NULL;
4810 waddstr(view->window, " ..\n"); /* parent directory */
4811 if (s->selected == 0 && view->focussed)
4812 wstandend(view->window);
4813 s->ndisplayed++;
4814 if (--limit <= 0)
4815 return NULL;
4816 n = 1;
4817 } else {
4818 n = 0;
4819 te = s->first_displayed_entry;
4822 nentries = got_object_tree_get_nentries(s->tree);
4823 for (i = got_tree_entry_get_index(te); i < nentries; i++) {
4824 char *line = NULL, *id_str = NULL, *link_target = NULL;
4825 const char *modestr = "";
4826 mode_t mode;
4828 te = got_object_tree_get_entry(s->tree, i);
4829 mode = got_tree_entry_get_mode(te);
4831 if (s->show_ids) {
4832 err = got_object_id_str(&id_str,
4833 got_tree_entry_get_id(te));
4834 if (err)
4835 return got_error_from_errno(
4836 "got_object_id_str");
4838 if (got_object_tree_entry_is_submodule(te))
4839 modestr = "$";
4840 else if (S_ISLNK(mode)) {
4841 int i;
4843 err = got_tree_entry_get_symlink_target(&link_target,
4844 te, s->repo);
4845 if (err) {
4846 free(id_str);
4847 return err;
4849 for (i = 0; i < strlen(link_target); i++) {
4850 if (!isprint((unsigned char)link_target[i]))
4851 link_target[i] = '?';
4853 modestr = "@";
4855 else if (S_ISDIR(mode))
4856 modestr = "/";
4857 else if (mode & S_IXUSR)
4858 modestr = "*";
4859 if (asprintf(&line, "%s %s%s%s%s", id_str ? id_str : "",
4860 got_tree_entry_get_name(te), modestr,
4861 link_target ? " -> ": "",
4862 link_target ? link_target : "") == -1) {
4863 free(id_str);
4864 free(link_target);
4865 return got_error_from_errno("asprintf");
4867 free(id_str);
4868 free(link_target);
4869 err = format_line(&wline, &width, line, view->ncols, 0);
4870 if (err) {
4871 free(line);
4872 break;
4874 if (n == s->selected) {
4875 if (view->focussed)
4876 wstandout(view->window);
4877 s->selected_entry = te;
4879 tc = match_color(&s->colors, line);
4880 if (tc)
4881 wattr_on(view->window,
4882 COLOR_PAIR(tc->colorpair), NULL);
4883 waddwstr(view->window, wline);
4884 if (tc)
4885 wattr_off(view->window,
4886 COLOR_PAIR(tc->colorpair), NULL);
4887 if (width < view->ncols - 1)
4888 waddch(view->window, '\n');
4889 if (n == s->selected && view->focussed)
4890 wstandend(view->window);
4891 free(line);
4892 free(wline);
4893 wline = NULL;
4894 n++;
4895 s->ndisplayed++;
4896 s->last_displayed_entry = te;
4897 if (--limit <= 0)
4898 break;
4901 return err;
4904 static void
4905 tree_scroll_up(struct tog_tree_view_state *s, int maxscroll)
4907 struct got_tree_entry *te;
4908 int isroot = s->tree == s->root;
4909 int i = 0;
4911 if (s->first_displayed_entry == NULL)
4912 return;
4914 te = got_tree_entry_get_prev(s->tree, s->first_displayed_entry);
4915 while (i++ < maxscroll) {
4916 if (te == NULL) {
4917 if (!isroot)
4918 s->first_displayed_entry = NULL;
4919 break;
4921 s->first_displayed_entry = te;
4922 te = got_tree_entry_get_prev(s->tree, te);
4926 static void
4927 tree_scroll_down(struct tog_tree_view_state *s, int maxscroll)
4929 struct got_tree_entry *next, *last;
4930 int n = 0;
4932 if (s->first_displayed_entry)
4933 next = got_tree_entry_get_next(s->tree,
4934 s->first_displayed_entry);
4935 else
4936 next = got_object_tree_get_first_entry(s->tree);
4938 last = s->last_displayed_entry;
4939 while (next && last && n++ < maxscroll) {
4940 last = got_tree_entry_get_next(s->tree, last);
4941 if (last) {
4942 s->first_displayed_entry = next;
4943 next = got_tree_entry_get_next(s->tree, next);
4948 static const struct got_error *
4949 tree_entry_path(char **path, struct tog_parent_trees *parents,
4950 struct got_tree_entry *te)
4952 const struct got_error *err = NULL;
4953 struct tog_parent_tree *pt;
4954 size_t len = 2; /* for leading slash and NUL */
4956 TAILQ_FOREACH(pt, parents, entry)
4957 len += strlen(got_tree_entry_get_name(pt->selected_entry))
4958 + 1 /* slash */;
4959 if (te)
4960 len += strlen(got_tree_entry_get_name(te));
4962 *path = calloc(1, len);
4963 if (path == NULL)
4964 return got_error_from_errno("calloc");
4966 (*path)[0] = '/';
4967 pt = TAILQ_LAST(parents, tog_parent_trees);
4968 while (pt) {
4969 const char *name = got_tree_entry_get_name(pt->selected_entry);
4970 if (strlcat(*path, name, len) >= len) {
4971 err = got_error(GOT_ERR_NO_SPACE);
4972 goto done;
4974 if (strlcat(*path, "/", len) >= len) {
4975 err = got_error(GOT_ERR_NO_SPACE);
4976 goto done;
4978 pt = TAILQ_PREV(pt, tog_parent_trees, entry);
4980 if (te) {
4981 if (strlcat(*path, got_tree_entry_get_name(te), len) >= len) {
4982 err = got_error(GOT_ERR_NO_SPACE);
4983 goto done;
4986 done:
4987 if (err) {
4988 free(*path);
4989 *path = NULL;
4991 return err;
4994 static const struct got_error *
4995 blame_tree_entry(struct tog_view **new_view, int begin_x,
4996 struct got_tree_entry *te, struct tog_parent_trees *parents,
4997 struct got_object_id *commit_id, struct got_repository *repo)
4999 const struct got_error *err = NULL;
5000 char *path;
5001 struct tog_view *blame_view;
5003 *new_view = NULL;
5005 err = tree_entry_path(&path, parents, te);
5006 if (err)
5007 return err;
5009 blame_view = view_open(0, 0, 0, begin_x, TOG_VIEW_BLAME);
5010 if (blame_view == NULL) {
5011 err = got_error_from_errno("view_open");
5012 goto done;
5015 err = open_blame_view(blame_view, path, commit_id, repo);
5016 if (err) {
5017 if (err->code == GOT_ERR_CANCELLED)
5018 err = NULL;
5019 view_close(blame_view);
5020 } else
5021 *new_view = blame_view;
5022 done:
5023 free(path);
5024 return err;
5027 static const struct got_error *
5028 log_tree_entry(struct tog_view **new_view, int begin_x,
5029 struct got_tree_entry *te, struct tog_parent_trees *parents,
5030 struct got_object_id *commit_id, struct got_repository *repo)
5032 struct tog_view *log_view;
5033 const struct got_error *err = NULL;
5034 char *path;
5036 *new_view = NULL;
5038 log_view = view_open(0, 0, 0, begin_x, TOG_VIEW_LOG);
5039 if (log_view == NULL)
5040 return got_error_from_errno("view_open");
5042 err = tree_entry_path(&path, parents, te);
5043 if (err)
5044 return err;
5046 err = open_log_view(log_view, commit_id, repo, NULL, path, 0);
5047 if (err)
5048 view_close(log_view);
5049 else
5050 *new_view = log_view;
5051 free(path);
5052 return err;
5055 static const struct got_error *
5056 open_tree_view(struct tog_view *view, struct got_tree_object *root,
5057 struct got_object_id *commit_id, struct got_repository *repo)
5059 const struct got_error *err = NULL;
5060 char *commit_id_str = NULL;
5061 struct tog_tree_view_state *s = &view->state.tree;
5063 TAILQ_INIT(&s->parents);
5065 err = got_object_id_str(&commit_id_str, commit_id);
5066 if (err != NULL)
5067 goto done;
5069 if (asprintf(&s->tree_label, "commit %s", commit_id_str) == -1) {
5070 err = got_error_from_errno("asprintf");
5071 goto done;
5074 s->root = s->tree = root;
5075 s->first_displayed_entry = got_object_tree_get_entry(s->tree, 0);
5076 s->selected_entry = got_object_tree_get_entry(s->tree, 0);
5077 s->commit_id = got_object_id_dup(commit_id);
5078 if (s->commit_id == NULL) {
5079 err = got_error_from_errno("got_object_id_dup");
5080 goto done;
5082 s->repo = repo;
5084 SIMPLEQ_INIT(&s->colors);
5086 if (has_colors() && getenv("TOG_COLORS") != NULL) {
5087 err = add_color(&s->colors, "\\$$",
5088 TOG_COLOR_TREE_SUBMODULE,
5089 get_color_value("TOG_COLOR_TREE_SUBMODULE"));
5090 if (err)
5091 goto done;
5092 err = add_color(&s->colors, "@$", TOG_COLOR_TREE_SYMLINK,
5093 get_color_value("TOG_COLOR_TREE_SYMLINK"));
5094 if (err) {
5095 free_colors(&s->colors);
5096 goto done;
5098 err = add_color(&s->colors, "/$",
5099 TOG_COLOR_TREE_DIRECTORY,
5100 get_color_value("TOG_COLOR_TREE_DIRECTORY"));
5101 if (err) {
5102 free_colors(&s->colors);
5103 goto done;
5106 err = add_color(&s->colors, "\\*$",
5107 TOG_COLOR_TREE_EXECUTABLE,
5108 get_color_value("TOG_COLOR_TREE_EXECUTABLE"));
5109 if (err) {
5110 free_colors(&s->colors);
5111 goto done;
5114 err = add_color(&s->colors, "^$", TOG_COLOR_COMMIT,
5115 get_color_value("TOG_COLOR_COMMIT"));
5116 if (err) {
5117 free_colors(&s->colors);
5118 goto done;
5122 view->show = show_tree_view;
5123 view->input = input_tree_view;
5124 view->close = close_tree_view;
5125 view->search_start = search_start_tree_view;
5126 view->search_next = search_next_tree_view;
5127 done:
5128 free(commit_id_str);
5129 if (err) {
5130 free(s->tree_label);
5131 s->tree_label = NULL;
5133 return err;
5136 static const struct got_error *
5137 close_tree_view(struct tog_view *view)
5139 struct tog_tree_view_state *s = &view->state.tree;
5141 free_colors(&s->colors);
5142 free(s->tree_label);
5143 s->tree_label = NULL;
5144 free(s->commit_id);
5145 s->commit_id = NULL;
5146 while (!TAILQ_EMPTY(&s->parents)) {
5147 struct tog_parent_tree *parent;
5148 parent = TAILQ_FIRST(&s->parents);
5149 TAILQ_REMOVE(&s->parents, parent, entry);
5150 free(parent);
5153 if (s->tree != s->root)
5154 got_object_tree_close(s->tree);
5155 got_object_tree_close(s->root);
5156 return NULL;
5159 static const struct got_error *
5160 search_start_tree_view(struct tog_view *view)
5162 struct tog_tree_view_state *s = &view->state.tree;
5164 s->matched_entry = NULL;
5165 return NULL;
5168 static int
5169 match_tree_entry(struct got_tree_entry *te, regex_t *regex)
5171 regmatch_t regmatch;
5173 return regexec(regex, got_tree_entry_get_name(te), 1, &regmatch,
5174 0) == 0;
5177 static const struct got_error *
5178 search_next_tree_view(struct tog_view *view)
5180 struct tog_tree_view_state *s = &view->state.tree;
5181 struct got_tree_entry *te = NULL;
5183 if (!view->searching) {
5184 view->search_next_done = TOG_SEARCH_HAVE_MORE;
5185 return NULL;
5188 if (s->matched_entry) {
5189 if (view->searching == TOG_SEARCH_FORWARD) {
5190 if (s->selected_entry)
5191 te = got_tree_entry_get_next(s->tree,
5192 s->selected_entry);
5193 else
5194 te = got_object_tree_get_first_entry(s->tree);
5195 } else {
5196 if (s->selected_entry == NULL)
5197 te = got_object_tree_get_last_entry(s->tree);
5198 else
5199 te = got_tree_entry_get_prev(s->tree,
5200 s->selected_entry);
5202 } else {
5203 if (view->searching == TOG_SEARCH_FORWARD)
5204 te = got_object_tree_get_first_entry(s->tree);
5205 else
5206 te = got_object_tree_get_last_entry(s->tree);
5209 while (1) {
5210 if (te == NULL) {
5211 if (s->matched_entry == NULL) {
5212 view->search_next_done = TOG_SEARCH_HAVE_MORE;
5213 return NULL;
5215 if (view->searching == TOG_SEARCH_FORWARD)
5216 te = got_object_tree_get_first_entry(s->tree);
5217 else
5218 te = got_object_tree_get_last_entry(s->tree);
5221 if (match_tree_entry(te, &view->regex)) {
5222 view->search_next_done = TOG_SEARCH_HAVE_MORE;
5223 s->matched_entry = te;
5224 break;
5227 if (view->searching == TOG_SEARCH_FORWARD)
5228 te = got_tree_entry_get_next(s->tree, te);
5229 else
5230 te = got_tree_entry_get_prev(s->tree, te);
5233 if (s->matched_entry) {
5234 s->first_displayed_entry = s->matched_entry;
5235 s->selected = 0;
5238 return NULL;
5241 static const struct got_error *
5242 show_tree_view(struct tog_view *view)
5244 const struct got_error *err = NULL;
5245 struct tog_tree_view_state *s = &view->state.tree;
5246 char *parent_path;
5248 err = tree_entry_path(&parent_path, &s->parents, NULL);
5249 if (err)
5250 return err;
5252 err = draw_tree_entries(view, parent_path);
5253 free(parent_path);
5255 view_vborder(view);
5256 return err;
5259 static const struct got_error *
5260 input_tree_view(struct tog_view **new_view, struct tog_view *view, int ch)
5262 const struct got_error *err = NULL;
5263 struct tog_tree_view_state *s = &view->state.tree;
5264 struct tog_view *log_view, *ref_view;
5265 int begin_x = 0;
5267 switch (ch) {
5268 case 'i':
5269 s->show_ids = !s->show_ids;
5270 break;
5271 case 'l':
5272 if (!s->selected_entry)
5273 break;
5274 if (view_is_parent_view(view))
5275 begin_x = view_split_begin_x(view->begin_x);
5276 err = log_tree_entry(&log_view, begin_x, s->selected_entry,
5277 &s->parents, s->commit_id, s->repo);
5278 view->focussed = 0;
5279 log_view->focussed = 1;
5280 if (view_is_parent_view(view)) {
5281 err = view_close_child(view);
5282 if (err)
5283 return err;
5284 view_set_child(view, log_view);
5285 view->focus_child = 1;
5286 } else
5287 *new_view = log_view;
5288 break;
5289 case 'r':
5290 if (view_is_parent_view(view))
5291 begin_x = view_split_begin_x(view->begin_x);
5292 ref_view = view_open(view->nlines, view->ncols,
5293 view->begin_y, begin_x, TOG_VIEW_REF);
5294 if (ref_view == NULL)
5295 return got_error_from_errno("view_open");
5296 err = open_ref_view(ref_view, s->repo);
5297 if (err) {
5298 view_close(ref_view);
5299 return err;
5301 view->focussed = 0;
5302 ref_view->focussed = 1;
5303 if (view_is_parent_view(view)) {
5304 err = view_close_child(view);
5305 if (err)
5306 return err;
5307 view_set_child(view, ref_view);
5308 view->focus_child = 1;
5309 } else
5310 *new_view = ref_view;
5311 break;
5312 case 'k':
5313 case KEY_UP:
5314 if (s->selected > 0) {
5315 s->selected--;
5316 break;
5318 tree_scroll_up(s, 1);
5319 break;
5320 case KEY_PPAGE:
5321 case CTRL('b'):
5322 if (s->tree == s->root) {
5323 if (got_object_tree_get_first_entry(s->tree) ==
5324 s->first_displayed_entry)
5325 s->selected = 0;
5326 } else {
5327 if (s->first_displayed_entry == NULL)
5328 s->selected = 0;
5330 tree_scroll_up(s, MAX(0, view->nlines - 3));
5331 break;
5332 case 'j':
5333 case KEY_DOWN:
5334 if (s->selected < s->ndisplayed - 1) {
5335 s->selected++;
5336 break;
5338 if (got_tree_entry_get_next(s->tree, s->last_displayed_entry)
5339 == NULL)
5340 /* can't scroll any further */
5341 break;
5342 tree_scroll_down(s, 1);
5343 break;
5344 case KEY_NPAGE:
5345 case CTRL('f'):
5346 if (got_tree_entry_get_next(s->tree, s->last_displayed_entry)
5347 == NULL) {
5348 /* can't scroll any further; move cursor down */
5349 if (s->selected < s->ndisplayed - 1)
5350 s->selected = s->ndisplayed - 1;
5351 break;
5353 tree_scroll_down(s, view->nlines - 3);
5354 break;
5355 case KEY_ENTER:
5356 case '\r':
5357 case KEY_BACKSPACE:
5358 if (s->selected_entry == NULL || ch == KEY_BACKSPACE) {
5359 struct tog_parent_tree *parent;
5360 /* user selected '..' */
5361 if (s->tree == s->root)
5362 break;
5363 parent = TAILQ_FIRST(&s->parents);
5364 TAILQ_REMOVE(&s->parents, parent,
5365 entry);
5366 got_object_tree_close(s->tree);
5367 s->tree = parent->tree;
5368 s->first_displayed_entry =
5369 parent->first_displayed_entry;
5370 s->selected_entry =
5371 parent->selected_entry;
5372 s->selected = parent->selected;
5373 free(parent);
5374 } else if (S_ISDIR(got_tree_entry_get_mode(
5375 s->selected_entry))) {
5376 struct got_tree_object *subtree;
5377 err = got_object_open_as_tree(&subtree, s->repo,
5378 got_tree_entry_get_id(s->selected_entry));
5379 if (err)
5380 break;
5381 err = tree_view_visit_subtree(s, subtree);
5382 if (err) {
5383 got_object_tree_close(subtree);
5384 break;
5386 } else if (S_ISREG(got_tree_entry_get_mode(
5387 s->selected_entry))) {
5388 struct tog_view *blame_view;
5389 int begin_x = view_is_parent_view(view) ?
5390 view_split_begin_x(view->begin_x) : 0;
5392 err = blame_tree_entry(&blame_view, begin_x,
5393 s->selected_entry, &s->parents,
5394 s->commit_id, s->repo);
5395 if (err)
5396 break;
5397 view->focussed = 0;
5398 blame_view->focussed = 1;
5399 if (view_is_parent_view(view)) {
5400 err = view_close_child(view);
5401 if (err)
5402 return err;
5403 view_set_child(view, blame_view);
5404 view->focus_child = 1;
5405 } else
5406 *new_view = blame_view;
5408 break;
5409 case KEY_RESIZE:
5410 if (s->selected > view->nlines)
5411 s->selected = s->ndisplayed - 1;
5412 break;
5413 default:
5414 break;
5417 return err;
5420 __dead static void
5421 usage_tree(void)
5423 endwin();
5424 fprintf(stderr, "usage: %s tree [-c commit] [-r repository-path] [path]\n",
5425 getprogname());
5426 exit(1);
5429 static const struct got_error *
5430 cmd_tree(int argc, char *argv[])
5432 const struct got_error *error;
5433 struct got_repository *repo = NULL;
5434 struct got_worktree *worktree = NULL;
5435 char *cwd = NULL, *repo_path = NULL, *in_repo_path = NULL;
5436 struct got_object_id *commit_id = NULL;
5437 char *commit_id_arg = NULL;
5438 struct got_commit_object *commit = NULL;
5439 struct got_tree_object *tree = NULL;
5440 int ch;
5441 struct tog_view *view;
5443 while ((ch = getopt(argc, argv, "c:r:")) != -1) {
5444 switch (ch) {
5445 case 'c':
5446 commit_id_arg = optarg;
5447 break;
5448 case 'r':
5449 repo_path = realpath(optarg, NULL);
5450 if (repo_path == NULL)
5451 return got_error_from_errno2("realpath",
5452 optarg);
5453 break;
5454 default:
5455 usage_tree();
5456 /* NOTREACHED */
5460 argc -= optind;
5461 argv += optind;
5463 if (argc > 1)
5464 usage_tree();
5466 cwd = getcwd(NULL, 0);
5467 if (cwd == NULL)
5468 return got_error_from_errno("getcwd");
5470 error = got_worktree_open(&worktree, cwd);
5471 if (error && error->code != GOT_ERR_NOT_WORKTREE)
5472 goto done;
5474 if (repo_path == NULL) {
5475 if (worktree)
5476 repo_path =
5477 strdup(got_worktree_get_repo_path(worktree));
5478 else
5479 repo_path = strdup(cwd);
5481 if (repo_path == NULL) {
5482 error = got_error_from_errno("strdup");
5483 goto done;
5486 error = got_repo_open(&repo, repo_path, NULL);
5487 if (error != NULL)
5488 goto done;
5490 error = get_in_repo_path_from_argv0(&in_repo_path, argc, argv,
5491 repo, worktree);
5492 if (error)
5493 goto done;
5495 init_curses();
5497 error = apply_unveil(got_repo_get_path(repo), NULL);
5498 if (error)
5499 goto done;
5501 error = got_repo_match_object_id(&commit_id, NULL,
5502 commit_id_arg ? commit_id_arg : GOT_REF_HEAD,
5503 GOT_OBJ_TYPE_COMMIT, 1, repo);
5504 if (error)
5505 goto done;
5507 error = got_object_open_as_commit(&commit, repo, commit_id);
5508 if (error)
5509 goto done;
5511 error = got_object_open_as_tree(&tree, repo,
5512 got_object_commit_get_tree_id(commit));
5513 if (error)
5514 goto done;
5516 view = view_open(0, 0, 0, 0, TOG_VIEW_TREE);
5517 if (view == NULL) {
5518 error = got_error_from_errno("view_open");
5519 goto done;
5521 error = open_tree_view(view, tree, commit_id, repo);
5522 if (error)
5523 goto done;
5524 if (!got_path_is_root_dir(in_repo_path)) {
5525 error = tree_view_walk_path(&view->state.tree, commit_id,
5526 in_repo_path);
5527 if (error)
5528 goto done;
5531 if (worktree) {
5532 /* Release work tree lock. */
5533 got_worktree_close(worktree);
5534 worktree = NULL;
5536 error = view_loop(view);
5537 done:
5538 free(repo_path);
5539 free(cwd);
5540 free(commit_id);
5541 if (commit)
5542 got_object_commit_close(commit);
5543 if (tree)
5544 got_object_tree_close(tree);
5545 if (repo)
5546 got_repo_close(repo);
5547 return error;
5550 static const struct got_error *
5551 ref_view_load_refs(struct tog_ref_view_state *s)
5553 const struct got_error *err;
5554 struct got_reflist_entry *sre;
5555 struct tog_reflist_entry *re;
5557 err = got_ref_list(&s->simplerefs, s->repo, NULL,
5558 got_ref_cmp_by_name, NULL);
5559 if (err)
5560 return err;
5562 s->nrefs = 0;
5563 SIMPLEQ_FOREACH(sre, &s->simplerefs, entry) {
5564 if (strncmp(got_ref_get_name(sre->ref), "refs/got/", 9) == 0)
5565 continue;
5567 re = malloc(sizeof(*re));
5568 if (re == NULL)
5569 return got_error_from_errno("malloc");
5571 re->ref = sre->ref;
5572 re->idx = s->nrefs++;
5573 TAILQ_INSERT_TAIL(&s->refs, re, entry);
5576 return NULL;
5579 void
5580 ref_view_free_refs(struct tog_ref_view_state *s)
5582 struct tog_reflist_entry *re;
5584 while (!TAILQ_EMPTY(&s->refs)) {
5585 re = TAILQ_FIRST(&s->refs);
5586 TAILQ_REMOVE(&s->refs, re, entry);
5587 free(re);
5589 got_ref_list_free(&s->simplerefs);
5592 static const struct got_error *
5593 open_ref_view(struct tog_view *view, struct got_repository *repo)
5595 const struct got_error *err = NULL;
5596 struct tog_ref_view_state *s = &view->state.ref;
5598 s->selected_entry = 0;
5599 s->repo = repo;
5601 SIMPLEQ_INIT(&s->simplerefs);
5602 TAILQ_INIT(&s->refs);
5603 SIMPLEQ_INIT(&s->colors);
5605 err = ref_view_load_refs(s);
5606 if (err)
5607 return err;
5609 s->first_displayed_entry = TAILQ_FIRST(&s->refs);
5611 if (has_colors() && getenv("TOG_COLORS") != NULL) {
5612 err = add_color(&s->colors, "^refs/heads/",
5613 TOG_COLOR_REFS_HEADS,
5614 get_color_value("TOG_COLOR_REFS_HEADS"));
5615 if (err)
5616 goto done;
5618 err = add_color(&s->colors, "^refs/tags/",
5619 TOG_COLOR_REFS_TAGS,
5620 get_color_value("TOG_COLOR_REFS_TAGS"));
5621 if (err)
5622 goto done;
5624 err = add_color(&s->colors, "^refs/remotes/",
5625 TOG_COLOR_REFS_REMOTES,
5626 get_color_value("TOG_COLOR_REFS_REMOTES"));
5627 if (err)
5628 goto done;
5631 view->show = show_ref_view;
5632 view->input = input_ref_view;
5633 view->close = close_ref_view;
5634 view->search_start = search_start_ref_view;
5635 view->search_next = search_next_ref_view;
5636 done:
5637 if (err)
5638 free_colors(&s->colors);
5639 return err;
5642 static const struct got_error *
5643 close_ref_view(struct tog_view *view)
5645 struct tog_ref_view_state *s = &view->state.ref;
5647 ref_view_free_refs(s);
5648 free_colors(&s->colors);
5650 return NULL;
5653 static const struct got_error *
5654 resolve_reflist_entry(struct got_object_id **commit_id,
5655 struct tog_reflist_entry *re, struct got_repository *repo)
5657 const struct got_error *err = NULL;
5658 struct got_object_id *obj_id;
5659 struct got_tag_object *tag = NULL;
5660 int obj_type;
5662 *commit_id = NULL;
5664 err = got_ref_resolve(&obj_id, repo, re->ref);
5665 if (err)
5666 return err;
5668 err = got_object_get_type(&obj_type, repo, obj_id);
5669 if (err)
5670 goto done;
5672 switch (obj_type) {
5673 case GOT_OBJ_TYPE_COMMIT:
5674 *commit_id = obj_id;
5675 break;
5676 case GOT_OBJ_TYPE_TAG:
5677 err = got_object_open_as_tag(&tag, repo, obj_id);
5678 if (err)
5679 goto done;
5680 free(obj_id);
5681 err = got_object_get_type(&obj_type, repo,
5682 got_object_tag_get_object_id(tag));
5683 if (err)
5684 goto done;
5685 if (obj_type != GOT_OBJ_TYPE_COMMIT) {
5686 err = got_error(GOT_ERR_OBJ_TYPE);
5687 goto done;
5689 *commit_id = got_object_id_dup(
5690 got_object_tag_get_object_id(tag));
5691 if (*commit_id == NULL) {
5692 err = got_error_from_errno("got_object_id_dup");
5693 goto done;
5695 break;
5696 default:
5697 err = got_error(GOT_ERR_OBJ_TYPE);
5698 break;
5701 done:
5702 if (tag)
5703 got_object_tag_close(tag);
5704 if (err) {
5705 free(*commit_id);
5706 *commit_id = NULL;
5708 return err;
5711 static const struct got_error *
5712 log_ref_entry(struct tog_view **new_view, int begin_x,
5713 struct tog_reflist_entry *re, struct got_repository *repo)
5715 struct tog_view *log_view;
5716 const struct got_error *err = NULL;
5717 struct got_object_id *commit_id = NULL;
5719 *new_view = NULL;
5721 err = resolve_reflist_entry(&commit_id, re, repo);
5722 if (err) {
5723 if (err->code != GOT_ERR_OBJ_TYPE)
5724 return err;
5725 else
5726 return NULL;
5729 log_view = view_open(0, 0, 0, begin_x, TOG_VIEW_LOG);
5730 if (log_view == NULL) {
5731 err = got_error_from_errno("view_open");
5732 goto done;
5735 err = open_log_view(log_view, commit_id, repo, NULL, "", 0);
5736 done:
5737 if (err)
5738 view_close(log_view);
5739 else
5740 *new_view = log_view;
5741 free(commit_id);
5742 return err;
5745 static void
5746 ref_scroll_up(struct tog_ref_view_state *s, int maxscroll)
5748 struct tog_reflist_entry *re;
5749 int i = 0;
5751 if (s->first_displayed_entry == TAILQ_FIRST(&s->refs))
5752 return;
5754 re = TAILQ_PREV(s->first_displayed_entry, tog_reflist_head, entry);
5755 while (i++ < maxscroll) {
5756 if (re == NULL)
5757 break;
5758 s->first_displayed_entry = re;
5759 re = TAILQ_PREV(re, tog_reflist_head, entry);
5763 static void
5764 ref_scroll_down(struct tog_ref_view_state *s, int maxscroll)
5766 struct tog_reflist_entry *next, *last;
5767 int n = 0;
5769 if (s->first_displayed_entry)
5770 next = TAILQ_NEXT(s->first_displayed_entry, entry);
5771 else
5772 next = TAILQ_FIRST(&s->refs);
5774 last = s->last_displayed_entry;
5775 while (next && last && n++ < maxscroll) {
5776 last = TAILQ_NEXT(last, entry);
5777 if (last) {
5778 s->first_displayed_entry = next;
5779 next = TAILQ_NEXT(next, entry);
5784 static const struct got_error *
5785 search_start_ref_view(struct tog_view *view)
5787 struct tog_ref_view_state *s = &view->state.ref;
5789 s->matched_entry = NULL;
5790 return NULL;
5793 static int
5794 match_reflist_entry(struct tog_reflist_entry *re, regex_t *regex)
5796 regmatch_t regmatch;
5798 return regexec(regex, got_ref_get_name(re->ref), 1, &regmatch,
5799 0) == 0;
5802 static const struct got_error *
5803 search_next_ref_view(struct tog_view *view)
5805 struct tog_ref_view_state *s = &view->state.ref;
5806 struct tog_reflist_entry *re = NULL;
5808 if (!view->searching) {
5809 view->search_next_done = TOG_SEARCH_HAVE_MORE;
5810 return NULL;
5813 if (s->matched_entry) {
5814 if (view->searching == TOG_SEARCH_FORWARD) {
5815 if (s->selected_entry)
5816 re = TAILQ_NEXT(s->selected_entry, entry);
5817 else
5818 re = TAILQ_PREV(s->selected_entry,
5819 tog_reflist_head, entry);
5820 } else {
5821 if (s->selected_entry == NULL)
5822 re = TAILQ_LAST(&s->refs, tog_reflist_head);
5823 else
5824 re = TAILQ_PREV(s->selected_entry,
5825 tog_reflist_head, entry);
5827 } else {
5828 if (view->searching == TOG_SEARCH_FORWARD)
5829 re = TAILQ_FIRST(&s->refs);
5830 else
5831 re = TAILQ_LAST(&s->refs, tog_reflist_head);
5834 while (1) {
5835 if (re == NULL) {
5836 if (s->matched_entry == NULL) {
5837 view->search_next_done = TOG_SEARCH_HAVE_MORE;
5838 return NULL;
5840 if (view->searching == TOG_SEARCH_FORWARD)
5841 re = TAILQ_FIRST(&s->refs);
5842 else
5843 re = TAILQ_LAST(&s->refs, tog_reflist_head);
5846 if (match_reflist_entry(re, &view->regex)) {
5847 view->search_next_done = TOG_SEARCH_HAVE_MORE;
5848 s->matched_entry = re;
5849 break;
5852 if (view->searching == TOG_SEARCH_FORWARD)
5853 re = TAILQ_NEXT(re, entry);
5854 else
5855 re = TAILQ_PREV(re, tog_reflist_head, entry);
5858 if (s->matched_entry) {
5859 s->first_displayed_entry = s->matched_entry;
5860 s->selected = 0;
5863 return NULL;
5866 static const struct got_error *
5867 show_ref_view(struct tog_view *view)
5869 const struct got_error *err = NULL;
5870 struct tog_ref_view_state *s = &view->state.ref;
5871 struct tog_reflist_entry *re;
5872 char *line = NULL;
5873 wchar_t *wline;
5874 struct tog_color *tc;
5875 int width, n;
5876 int limit = view->nlines;
5878 werase(view->window);
5880 s->ndisplayed = 0;
5882 if (limit == 0)
5883 return NULL;
5885 re = s->first_displayed_entry;
5887 if (asprintf(&line, "references [%d/%d]", re->idx + s->selected + 1,
5888 s->nrefs) == -1)
5889 return got_error_from_errno("asprintf");
5891 err = format_line(&wline, &width, line, view->ncols, 0);
5892 if (err) {
5893 free(line);
5894 return err;
5896 if (view_needs_focus_indication(view))
5897 wstandout(view->window);
5898 waddwstr(view->window, wline);
5899 if (view_needs_focus_indication(view))
5900 wstandend(view->window);
5901 free(wline);
5902 wline = NULL;
5903 free(line);
5904 line = NULL;
5905 if (width < view->ncols - 1)
5906 waddch(view->window, '\n');
5907 if (--limit <= 0)
5908 return NULL;
5910 n = 0;
5911 while (re && limit > 0) {
5912 char *line = NULL;
5914 if (got_ref_is_symbolic(re->ref)) {
5915 if (asprintf(&line, "%s -> %s",
5916 got_ref_get_name(re->ref),
5917 got_ref_get_symref_target(re->ref)) == -1)
5918 return got_error_from_errno("asprintf");
5919 } else if (s->show_ids) {
5920 struct got_object_id *id;
5921 char *id_str;
5922 err = got_ref_resolve(&id, s->repo, re->ref);
5923 if (err)
5924 return err;
5925 err = got_object_id_str(&id_str, id);
5926 if (err) {
5927 free(id);
5928 return err;
5930 if (asprintf(&line, "%s: %s",
5931 got_ref_get_name(re->ref), id_str) == -1) {
5932 err = got_error_from_errno("asprintf");
5933 free(id);
5934 free(id_str);
5935 return err;
5937 free(id);
5938 free(id_str);
5939 } else {
5940 line = strdup(got_ref_get_name(re->ref));
5941 if (line == NULL)
5942 return got_error_from_errno("strdup");
5945 err = format_line(&wline, &width, line, view->ncols, 0);
5946 if (err) {
5947 free(line);
5948 return err;
5950 if (n == s->selected) {
5951 if (view->focussed)
5952 wstandout(view->window);
5953 s->selected_entry = re;
5955 tc = match_color(&s->colors, got_ref_get_name(re->ref));
5956 if (tc)
5957 wattr_on(view->window,
5958 COLOR_PAIR(tc->colorpair), NULL);
5959 waddwstr(view->window, wline);
5960 if (tc)
5961 wattr_off(view->window,
5962 COLOR_PAIR(tc->colorpair), NULL);
5963 if (width < view->ncols - 1)
5964 waddch(view->window, '\n');
5965 if (n == s->selected && view->focussed)
5966 wstandend(view->window);
5967 free(line);
5968 free(wline);
5969 wline = NULL;
5970 n++;
5971 s->ndisplayed++;
5972 s->last_displayed_entry = re;
5974 limit--;
5975 re = TAILQ_NEXT(re, entry);
5978 view_vborder(view);
5979 return err;
5982 static const struct got_error *
5983 browse_ref_tree(struct tog_view **new_view, int begin_x,
5984 struct tog_reflist_entry *re, struct got_repository *repo)
5986 const struct got_error *err = NULL;
5987 struct got_object_id *commit_id = NULL, *tree_id = NULL;
5988 struct got_tree_object *tree = NULL;
5989 struct tog_view *tree_view;
5991 *new_view = NULL;
5993 err = resolve_reflist_entry(&commit_id, re, repo);
5994 if (err) {
5995 if (err->code != GOT_ERR_OBJ_TYPE)
5996 return err;
5997 else
5998 return NULL;
6001 err = got_object_id_by_path(&tree_id, repo, commit_id, "/");
6002 if (err)
6003 goto done;
6005 err = got_object_open_as_tree(&tree, repo, tree_id);
6006 if (err)
6007 goto done;
6009 tree_view = view_open(0, 0, 0, begin_x, TOG_VIEW_TREE);
6010 if (tree_view == NULL) {
6011 err = got_error_from_errno("view_open");
6012 goto done;
6015 err = open_tree_view(tree_view, tree, commit_id, repo);
6016 if (err)
6017 goto done;
6019 *new_view = tree_view;
6020 done:
6021 free(commit_id);
6022 free(tree_id);
6023 if (err) {
6024 if (tree)
6025 got_object_tree_close(tree);
6027 return err;
6029 static const struct got_error *
6030 input_ref_view(struct tog_view **new_view, struct tog_view *view, int ch)
6032 const struct got_error *err = NULL;
6033 struct tog_ref_view_state *s = &view->state.ref;
6034 struct tog_view *log_view, *tree_view;
6035 int begin_x = 0;
6037 switch (ch) {
6038 case 'i':
6039 s->show_ids = !s->show_ids;
6040 break;
6041 case KEY_ENTER:
6042 case '\r':
6043 if (!s->selected_entry)
6044 break;
6045 if (view_is_parent_view(view))
6046 begin_x = view_split_begin_x(view->begin_x);
6047 err = log_ref_entry(&log_view, begin_x, s->selected_entry,
6048 s->repo);
6049 view->focussed = 0;
6050 log_view->focussed = 1;
6051 if (view_is_parent_view(view)) {
6052 err = view_close_child(view);
6053 if (err)
6054 return err;
6055 view_set_child(view, log_view);
6056 view->focus_child = 1;
6057 } else
6058 *new_view = log_view;
6059 break;
6060 case 't':
6061 if (!s->selected_entry)
6062 break;
6063 if (view_is_parent_view(view))
6064 begin_x = view_split_begin_x(view->begin_x);
6065 err = browse_ref_tree(&tree_view, begin_x, s->selected_entry,
6066 s->repo);
6067 if (err || tree_view == NULL)
6068 break;
6069 view->focussed = 0;
6070 tree_view->focussed = 1;
6071 if (view_is_parent_view(view)) {
6072 err = view_close_child(view);
6073 if (err)
6074 return err;
6075 view_set_child(view, tree_view);
6076 view->focus_child = 1;
6077 } else
6078 *new_view = tree_view;
6079 break;
6080 case 'k':
6081 case KEY_UP:
6082 if (s->selected > 0) {
6083 s->selected--;
6084 break;
6086 ref_scroll_up(s, 1);
6087 break;
6088 case KEY_PPAGE:
6089 case CTRL('b'):
6090 if (s->first_displayed_entry == TAILQ_FIRST(&s->refs))
6091 s->selected = 0;
6092 ref_scroll_up(s, MAX(0, view->nlines - 1));
6093 break;
6094 case 'j':
6095 case KEY_DOWN:
6096 if (s->selected < s->ndisplayed - 1) {
6097 s->selected++;
6098 break;
6100 if (TAILQ_NEXT(s->last_displayed_entry, entry) == NULL)
6101 /* can't scroll any further */
6102 break;
6103 ref_scroll_down(s, 1);
6104 break;
6105 case KEY_NPAGE:
6106 case CTRL('f'):
6107 if (TAILQ_NEXT(s->last_displayed_entry, entry) == NULL) {
6108 /* can't scroll any further; move cursor down */
6109 if (s->selected < s->ndisplayed - 1)
6110 s->selected = s->ndisplayed - 1;
6111 break;
6113 ref_scroll_down(s, view->nlines - 1);
6114 break;
6115 case CTRL('l'):
6116 ref_view_free_refs(s);
6117 err = ref_view_load_refs(s);
6118 break;
6119 case KEY_RESIZE:
6120 if (s->selected > view->nlines)
6121 s->selected = s->ndisplayed - 1;
6122 break;
6123 default:
6124 break;
6127 return err;
6130 __dead static void
6131 usage_ref(void)
6133 endwin();
6134 fprintf(stderr, "usage: %s ref [-r repository-path]\n",
6135 getprogname());
6136 exit(1);
6139 static const struct got_error *
6140 cmd_ref(int argc, char *argv[])
6142 const struct got_error *error;
6143 struct got_repository *repo = NULL;
6144 struct got_worktree *worktree = NULL;
6145 char *cwd = NULL, *repo_path = NULL;
6146 int ch;
6147 struct tog_view *view;
6149 while ((ch = getopt(argc, argv, "r:")) != -1) {
6150 switch (ch) {
6151 case 'r':
6152 repo_path = realpath(optarg, NULL);
6153 if (repo_path == NULL)
6154 return got_error_from_errno2("realpath",
6155 optarg);
6156 break;
6157 default:
6158 usage_ref();
6159 /* NOTREACHED */
6163 argc -= optind;
6164 argv += optind;
6166 if (argc > 1)
6167 usage_ref();
6169 cwd = getcwd(NULL, 0);
6170 if (cwd == NULL)
6171 return got_error_from_errno("getcwd");
6173 error = got_worktree_open(&worktree, cwd);
6174 if (error && error->code != GOT_ERR_NOT_WORKTREE)
6175 goto done;
6177 if (repo_path == NULL) {
6178 if (worktree)
6179 repo_path =
6180 strdup(got_worktree_get_repo_path(worktree));
6181 else
6182 repo_path = strdup(cwd);
6184 if (repo_path == NULL) {
6185 error = got_error_from_errno("strdup");
6186 goto done;
6189 error = got_repo_open(&repo, repo_path, NULL);
6190 if (error != NULL)
6191 goto done;
6193 init_curses();
6195 error = apply_unveil(got_repo_get_path(repo), NULL);
6196 if (error)
6197 goto done;
6199 view = view_open(0, 0, 0, 0, TOG_VIEW_REF);
6200 if (view == NULL) {
6201 error = got_error_from_errno("view_open");
6202 goto done;
6205 error = open_ref_view(view, repo);
6206 if (error)
6207 goto done;
6209 if (worktree) {
6210 /* Release work tree lock. */
6211 got_worktree_close(worktree);
6212 worktree = NULL;
6214 error = view_loop(view);
6215 done:
6216 free(repo_path);
6217 free(cwd);
6218 if (repo)
6219 got_repo_close(repo);
6220 return error;
6223 static void
6224 list_commands(FILE *fp)
6226 int i;
6228 fprintf(fp, "commands:");
6229 for (i = 0; i < nitems(tog_commands); i++) {
6230 struct tog_cmd *cmd = &tog_commands[i];
6231 fprintf(fp, " %s", cmd->name);
6233 fputc('\n', fp);
6236 __dead static void
6237 usage(int hflag, int status)
6239 FILE *fp = (status == 0) ? stdout : stderr;
6241 fprintf(fp, "usage: %s [-h] [-V | --version] [command] [arg ...]\n",
6242 getprogname());
6243 if (hflag) {
6244 fprintf(fp, "lazy usage: %s path\n", getprogname());
6245 list_commands(fp);
6247 exit(status);
6250 static char **
6251 make_argv(int argc, ...)
6253 va_list ap;
6254 char **argv;
6255 int i;
6257 va_start(ap, argc);
6259 argv = calloc(argc, sizeof(char *));
6260 if (argv == NULL)
6261 err(1, "calloc");
6262 for (i = 0; i < argc; i++) {
6263 argv[i] = strdup(va_arg(ap, char *));
6264 if (argv[i] == NULL)
6265 err(1, "strdup");
6268 va_end(ap);
6269 return argv;
6273 * Try to convert 'tog path' into a 'tog log path' command.
6274 * The user could simply have mistyped the command rather than knowingly
6275 * provided a path. So check whether argv[0] can in fact be resolved
6276 * to a path in the HEAD commit and print a special error if not.
6277 * This hack is for mpi@ <3
6279 static const struct got_error *
6280 tog_log_with_path(int argc, char *argv[])
6282 const struct got_error *error = NULL;
6283 struct tog_cmd *cmd = NULL;
6284 struct got_repository *repo = NULL;
6285 struct got_worktree *worktree = NULL;
6286 struct got_object_id *commit_id = NULL, *id = NULL;
6287 char *cwd = NULL, *repo_path = NULL, *in_repo_path = NULL;
6288 char *commit_id_str = NULL, **cmd_argv = NULL;
6290 cwd = getcwd(NULL, 0);
6291 if (cwd == NULL)
6292 return got_error_from_errno("getcwd");
6294 error = got_worktree_open(&worktree, cwd);
6295 if (error && error->code != GOT_ERR_NOT_WORKTREE)
6296 goto done;
6298 if (worktree)
6299 repo_path = strdup(got_worktree_get_repo_path(worktree));
6300 else
6301 repo_path = strdup(cwd);
6302 if (repo_path == NULL) {
6303 error = got_error_from_errno("strdup");
6304 goto done;
6307 error = got_repo_open(&repo, repo_path, NULL);
6308 if (error != NULL)
6309 goto done;
6311 error = get_in_repo_path_from_argv0(&in_repo_path, argc, argv,
6312 repo, worktree);
6313 if (error)
6314 goto done;
6316 error = got_repo_match_object_id(&commit_id, NULL, worktree ?
6317 got_worktree_get_head_ref_name(worktree) : GOT_REF_HEAD,
6318 GOT_OBJ_TYPE_COMMIT, 1, repo);
6319 if (error)
6320 goto done;
6322 if (worktree) {
6323 got_worktree_close(worktree);
6324 worktree = NULL;
6327 error = got_object_id_by_path(&id, repo, commit_id, in_repo_path);
6328 if (error) {
6329 if (error->code != GOT_ERR_NO_TREE_ENTRY)
6330 goto done;
6331 fprintf(stderr, "%s: '%s' is no known command or path\n",
6332 getprogname(), argv[0]);
6333 usage(1, 1);
6334 /* not reached */
6337 got_repo_close(repo);
6338 repo = NULL;
6340 error = got_object_id_str(&commit_id_str, commit_id);
6341 if (error)
6342 goto done;
6344 cmd = &tog_commands[0]; /* log */
6345 argc = 4;
6346 cmd_argv = make_argv(argc, cmd->name, "-c", commit_id_str, argv[0]);
6347 error = cmd->cmd_main(argc, cmd_argv);
6348 done:
6349 if (repo)
6350 got_repo_close(repo);
6351 if (worktree)
6352 got_worktree_close(worktree);
6353 free(id);
6354 free(commit_id_str);
6355 free(commit_id);
6356 free(cwd);
6357 free(repo_path);
6358 free(in_repo_path);
6359 if (cmd_argv) {
6360 int i;
6361 for (i = 0; i < argc; i++)
6362 free(cmd_argv[i]);
6363 free(cmd_argv);
6365 return error;
6368 int
6369 main(int argc, char *argv[])
6371 const struct got_error *error = NULL;
6372 struct tog_cmd *cmd = NULL;
6373 int ch, hflag = 0, Vflag = 0;
6374 char **cmd_argv = NULL;
6375 static struct option longopts[] = {
6376 { "version", no_argument, NULL, 'V' },
6377 { NULL, 0, NULL, 0}
6380 setlocale(LC_CTYPE, "");
6382 while ((ch = getopt_long(argc, argv, "+hV", longopts, NULL)) != -1) {
6383 switch (ch) {
6384 case 'h':
6385 hflag = 1;
6386 break;
6387 case 'V':
6388 Vflag = 1;
6389 break;
6390 default:
6391 usage(hflag, 1);
6392 /* NOTREACHED */
6396 argc -= optind;
6397 argv += optind;
6398 optind = 1;
6399 optreset = 1;
6401 if (Vflag) {
6402 got_version_print_str();
6403 return 0;
6406 #ifndef PROFILE
6407 if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd unveil",
6408 NULL) == -1)
6409 err(1, "pledge");
6410 #endif
6412 if (argc == 0) {
6413 if (hflag)
6414 usage(hflag, 0);
6415 /* Build an argument vector which runs a default command. */
6416 cmd = &tog_commands[0];
6417 argc = 1;
6418 cmd_argv = make_argv(argc, cmd->name);
6419 } else {
6420 int i;
6422 /* Did the user specify a command? */
6423 for (i = 0; i < nitems(tog_commands); i++) {
6424 if (strncmp(tog_commands[i].name, argv[0],
6425 strlen(argv[0])) == 0) {
6426 cmd = &tog_commands[i];
6427 break;
6432 if (cmd == NULL) {
6433 if (argc != 1)
6434 usage(0, 1);
6435 /* No command specified; try log with a path */
6436 error = tog_log_with_path(argc, argv);
6437 } else {
6438 if (hflag)
6439 cmd->cmd_usage();
6440 else
6441 error = cmd->cmd_main(argc, cmd_argv ? cmd_argv : argv);
6444 endwin();
6445 putchar('\n');
6446 if (cmd_argv) {
6447 int i;
6448 for (i = 0; i < argc; i++)
6449 free(cmd_argv[i]);
6450 free(cmd_argv);
6453 if (error && error->code != GOT_ERR_CANCELLED)
6454 fprintf(stderr, "%s: %s\n", getprogname(), error->msg);
6455 return 0;