Blob


1 /*
2 * Copyright (c) 2018 Stefan Sperling <stsp@openbsd.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
17 #include <sys/queue.h>
18 #include <sys/stat.h>
19 #include <sys/ioctl.h>
21 #include <errno.h>
22 #define _XOPEN_SOURCE_EXTENDED
23 #include <curses.h>
24 #undef _XOPEN_SOURCE_EXTENDED
25 #include <panel.h>
26 #include <locale.h>
27 #include <stdlib.h>
28 #include <stdio.h>
29 #include <getopt.h>
30 #include <string.h>
31 #include <err.h>
32 #include <unistd.h>
33 #include <util.h>
34 #include <limits.h>
35 #include <wchar.h>
36 #include <time.h>
37 #include <pthread.h>
38 #include <libgen.h>
40 #include "got_error.h"
41 #include "got_object.h"
42 #include "got_reference.h"
43 #include "got_repository.h"
44 #include "got_diff.h"
45 #include "got_opentemp.h"
46 #include "got_commit_graph.h"
47 #include "got_utf8.h"
48 #include "got_blame.h"
50 #ifndef MIN
51 #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b))
52 #endif
54 #ifndef MAX
55 #define MAX(_a,_b) ((_a) > (_b) ? (_a) : (_b))
56 #endif
59 #ifndef nitems
60 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
61 #endif
63 struct tog_cmd {
64 const char *name;
65 const struct got_error *(*cmd_main)(int, char *[]);
66 void (*cmd_usage)(void);
67 const char *descr;
68 };
70 __dead static void usage(void);
71 __dead static void usage_log(void);
72 __dead static void usage_diff(void);
73 __dead static void usage_blame(void);
74 __dead static void usage_tree(void);
76 static const struct got_error* cmd_log(int, char *[]);
77 static const struct got_error* cmd_diff(int, char *[]);
78 static const struct got_error* cmd_blame(int, char *[]);
79 static const struct got_error* cmd_tree(int, char *[]);
81 static struct tog_cmd tog_commands[] = {
82 { "log", cmd_log, usage_log,
83 "show repository history" },
84 { "diff", cmd_diff, usage_diff,
85 "compare files and directories" },
86 { "blame", cmd_blame, usage_blame,
87 "show line-by-line file history" },
88 { "tree", cmd_tree, usage_tree,
89 "browse trees in repository" },
90 };
92 enum tog_view_type {
93 TOG_VIEW_DIFF,
94 TOG_VIEW_LOG,
95 TOG_VIEW_BLAME,
96 TOG_VIEW_TREE
97 };
99 struct tog_diff_view_state {
100 struct got_object_id *id1, *id2;
101 FILE *f;
102 int first_displayed_line;
103 int last_displayed_line;
104 int eof;
105 int diff_context;
106 struct got_repository *repo;
107 };
109 struct commit_queue_entry {
110 TAILQ_ENTRY(commit_queue_entry) entry;
111 struct got_object_id *id;
112 struct got_commit_object *commit;
113 int idx;
114 };
115 TAILQ_HEAD(commit_queue_head, commit_queue_entry);
116 struct commit_queue {
117 int ncommits;
118 struct commit_queue_head head;
119 };
121 pthread_mutex_t tog_mutex = PTHREAD_MUTEX_INITIALIZER;
123 struct tog_log_thread_args {
124 pthread_cond_t need_commits;
125 int commits_needed;
126 struct got_commit_graph *graph;
127 struct commit_queue *commits;
128 const char *in_repo_path;
129 struct got_object_id *start_id;
130 struct got_repository *repo;
131 int log_complete;
132 sig_atomic_t *quit;
133 struct tog_view *view;
134 struct commit_queue_entry **first_displayed_entry;
135 struct commit_queue_entry **selected_entry;
136 };
138 struct tog_log_view_state {
139 struct commit_queue commits;
140 struct commit_queue_entry *first_displayed_entry;
141 struct commit_queue_entry *last_displayed_entry;
142 struct commit_queue_entry *selected_entry;
143 int selected;
144 char *in_repo_path;
145 struct got_repository *repo;
146 struct got_object_id *start_id;
147 sig_atomic_t quit;
148 pthread_t thread;
149 struct tog_log_thread_args thread_args;
150 };
152 struct tog_blame_cb_args {
153 struct tog_blame_line *lines; /* one per line */
154 int nlines;
156 struct tog_view *view;
157 struct got_object_id *commit_id;
158 int *quit;
159 };
161 struct tog_blame_thread_args {
162 const char *path;
163 struct got_repository *repo;
164 struct tog_blame_cb_args *cb_args;
165 int *complete;
166 };
168 struct tog_blame {
169 FILE *f;
170 size_t filesize;
171 struct tog_blame_line *lines;
172 size_t nlines;
173 pthread_t thread;
174 struct tog_blame_thread_args thread_args;
175 struct tog_blame_cb_args cb_args;
176 const char *path;
177 };
179 struct tog_blame_view_state {
180 int first_displayed_line;
181 int last_displayed_line;
182 int selected_line;
183 int blame_complete;
184 int eof;
185 int done;
186 struct got_object_id_queue blamed_commits;
187 struct got_object_qid *blamed_commit;
188 char *path;
189 struct got_repository *repo;
190 struct got_object_id *commit_id;
191 struct tog_blame blame;
192 };
194 struct tog_parent_tree {
195 TAILQ_ENTRY(tog_parent_tree) entry;
196 struct got_tree_object *tree;
197 struct got_tree_entry *first_displayed_entry;
198 struct got_tree_entry *selected_entry;
199 int selected;
200 };
202 TAILQ_HEAD(tog_parent_trees, tog_parent_tree);
204 struct tog_tree_view_state {
205 char *tree_label;
206 struct got_tree_object *root;
207 struct got_tree_object *tree;
208 const struct got_tree_entries *entries;
209 struct got_tree_entry *first_displayed_entry;
210 struct got_tree_entry *last_displayed_entry;
211 struct got_tree_entry *selected_entry;
212 int nentries, ndisplayed, selected, show_ids;
213 struct tog_parent_trees parents;
214 struct got_object_id *commit_id;
215 struct got_repository *repo;
216 };
218 /*
219 * We implement two types of views: parent views and child views.
221 * The 'Tab' key switches between a parent view and its child view.
222 * Child views are shown side-by-side to their parent view, provided
223 * there is enough screen estate.
225 * When a new view is opened from within a parent view, this new view
226 * becomes a child view of the parent view, replacing any existing child.
228 * When a new view is opened from within a child view, this new view
229 * becomes a parent view which will obscure the views below until the
230 * user quits the new parent view by typing 'q'.
232 * This list of views contains parent views only.
233 * Child views are only pointed to by their parent view.
234 */
235 TAILQ_HEAD(tog_view_list_head, tog_view);
237 struct tog_view {
238 TAILQ_ENTRY(tog_view) entry;
239 WINDOW *window;
240 PANEL *panel;
241 int nlines, ncols, begin_y, begin_x;
242 int lines, cols; /* copies of LINES and COLS */
243 int focussed;
244 struct tog_view *parent;
245 struct tog_view *child;
246 int child_focussed;
248 /* type-specific state */
249 enum tog_view_type type;
250 union {
251 struct tog_diff_view_state diff;
252 struct tog_log_view_state log;
253 struct tog_blame_view_state blame;
254 struct tog_tree_view_state tree;
255 } state;
257 const struct got_error *(*show)(struct tog_view *);
258 const struct got_error *(*input)(struct tog_view **,
259 struct tog_view **, struct tog_view**, struct tog_view *, int);
260 const struct got_error *(*close)(struct tog_view *);
261 };
263 static const struct got_error *open_diff_view(struct tog_view *,
264 struct got_object *, struct got_object *, struct got_repository *);
265 static const struct got_error *show_diff_view(struct tog_view *);
266 static const struct got_error *input_diff_view(struct tog_view **,
267 struct tog_view **, struct tog_view **, struct tog_view *, int);
268 static const struct got_error* close_diff_view(struct tog_view *);
270 static const struct got_error *open_log_view(struct tog_view *,
271 struct got_object_id *, struct got_repository *, const char *, int);
272 static const struct got_error * show_log_view(struct tog_view *);
273 static const struct got_error *input_log_view(struct tog_view **,
274 struct tog_view **, struct tog_view **, struct tog_view *, int);
275 static const struct got_error *close_log_view(struct tog_view *);
277 static const struct got_error *open_blame_view(struct tog_view *, char *,
278 struct got_object_id *, struct got_repository *);
279 static const struct got_error *show_blame_view(struct tog_view *);
280 static const struct got_error *input_blame_view(struct tog_view **,
281 struct tog_view **, struct tog_view **, struct tog_view *, int);
282 static const struct got_error *close_blame_view(struct tog_view *);
284 static const struct got_error *open_tree_view(struct tog_view *,
285 struct got_tree_object *, struct got_object_id *, struct got_repository *);
286 static const struct got_error *show_tree_view(struct tog_view *);
287 static const struct got_error *input_tree_view(struct tog_view **,
288 struct tog_view **, struct tog_view **, struct tog_view *, int);
289 static const struct got_error *close_tree_view(struct tog_view *);
291 static volatile sig_atomic_t tog_sigwinch_received;
293 static void
294 tog_sigwinch(int signo)
296 tog_sigwinch_received = 1;
299 static const struct got_error *
300 view_close(struct tog_view *view)
302 const struct got_error *err = NULL;
304 if (view->child) {
305 view_close(view->child);
306 view->child = NULL;
308 if (view->close)
309 err = view->close(view);
310 if (view->panel)
311 del_panel(view->panel);
312 if (view->window)
313 delwin(view->window);
314 free(view);
315 return err;
318 static struct tog_view *
319 view_open(int nlines, int ncols, int begin_y, int begin_x,
320 enum tog_view_type type)
322 struct tog_view *view = calloc(1, sizeof(*view));
324 if (view == NULL)
325 return NULL;
327 view->type = type;
328 view->lines = LINES;
329 view->cols = COLS;
330 view->nlines = nlines ? nlines : LINES - begin_y;
331 view->ncols = ncols ? ncols : COLS - begin_x;
332 view->begin_y = begin_y;
333 view->begin_x = begin_x;
334 view->window = newwin(nlines, ncols, begin_y, begin_x);
335 if (view->window == NULL) {
336 view_close(view);
337 return NULL;
339 view->panel = new_panel(view->window);
340 if (view->panel == NULL ||
341 set_panel_userptr(view->panel, view) != OK) {
342 view_close(view);
343 return NULL;
346 keypad(view->window, TRUE);
347 return view;
350 static int
351 view_split_begin_x(int begin_x)
353 if (begin_x > 0 || COLS < 120)
354 return 0;
355 return (COLS - MAX(COLS / 2, 80));
358 static const struct got_error *view_resize(struct tog_view *);
360 static const struct got_error *
361 view_splitscreen(struct tog_view *view)
363 const struct got_error *err = NULL;
365 view->begin_y = 0;
366 view->begin_x = view_split_begin_x(0);
367 view->nlines = LINES;
368 view->ncols = COLS - view->begin_x;
369 view->lines = LINES;
370 view->cols = COLS;
371 err = view_resize(view);
372 if (err)
373 return err;
375 if (mvwin(view->window, view->begin_y, view->begin_x) == ERR)
376 return got_error_from_errno();
378 return NULL;
381 static const struct got_error *
382 view_fullscreen(struct tog_view *view)
384 const struct got_error *err = NULL;
386 view->begin_x = 0;
387 view->begin_y = 0;
388 view->nlines = LINES;
389 view->ncols = COLS;
390 view->lines = LINES;
391 view->cols = COLS;
392 err = view_resize(view);
393 if (err)
394 return err;
396 if (mvwin(view->window, view->begin_y, view->begin_x) == ERR)
397 return got_error_from_errno();
399 return NULL;
402 static int
403 view_is_parent_view(struct tog_view *view)
405 return view->parent == NULL;
408 static const struct got_error *
409 view_resize(struct tog_view *view)
411 int nlines, ncols;
413 if (view->lines > LINES)
414 nlines = view->nlines - (view->lines - LINES);
415 else
416 nlines = view->nlines + (LINES - view->lines);
418 if (view->cols > COLS)
419 ncols = view->ncols - (view->cols - COLS);
420 else
421 ncols = view->ncols + (COLS - view->cols);
423 if (wresize(view->window, nlines, ncols) == ERR)
424 return got_error_from_errno();
425 if (replace_panel(view->panel, view->window) == ERR)
426 return got_error_from_errno();
427 wclear(view->window);
429 view->nlines = nlines;
430 view->ncols = ncols;
431 view->lines = LINES;
432 view->cols = COLS;
434 if (view->child) {
435 view->child->begin_x = view_split_begin_x(view->begin_x);
436 if (view->child->begin_x == 0) {
437 view_fullscreen(view->child);
438 if (view->child->focussed)
439 show_panel(view->child->panel);
440 else
441 show_panel(view->panel);
442 } else {
443 view_splitscreen(view->child);
444 show_panel(view->child->panel);
448 return NULL;
451 static const struct got_error *
452 view_close_child(struct tog_view *view)
454 const struct got_error *err = NULL;
456 if (view->child == NULL)
457 return NULL;
459 err = view_close(view->child);
460 view->child = NULL;
461 return err;
464 static const struct got_error *
465 view_set_child(struct tog_view *view, struct tog_view *child)
467 const struct got_error *err = NULL;
469 view->child = child;
470 child->parent = view;
471 return err;
474 static int
475 view_is_splitscreen(struct tog_view *view)
477 return !view_is_parent_view(view) && view->begin_x > 0;
480 static void
481 tog_resizeterm()
483 int cols, lines;
484 struct winsize size;
486 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) < 0) {
487 cols = 80; /* Default */
488 lines = 24;
489 } else {
490 cols = size.ws_col;
491 lines = size.ws_row;
493 resize_term(lines, cols);
496 static const struct got_error *
497 view_input(struct tog_view **new, struct tog_view **dead,
498 struct tog_view **focus, int *done, struct tog_view *view,
499 struct tog_view_list_head *views)
501 const struct got_error *err = NULL;
502 struct tog_view *v;
503 int ch, errcode;
505 *new = NULL;
506 *dead = NULL;
507 *focus = NULL;
509 nodelay(stdscr, FALSE);
510 /* Allow threads to make progress while we are waiting for input. */
511 errcode = pthread_mutex_unlock(&tog_mutex);
512 if (errcode)
513 return got_error_set_errno(errcode);
514 ch = wgetch(view->window);
515 errcode = pthread_mutex_lock(&tog_mutex);
516 if (errcode)
517 return got_error_set_errno(errcode);
518 nodelay(stdscr, TRUE);
520 if (tog_sigwinch_received) {
521 tog_resizeterm();
522 tog_sigwinch_received = 0;
523 TAILQ_FOREACH(v, views, entry) {
524 err = view_resize(v);
525 if (err)
526 return err;
527 err = v->input(new, dead, focus, v, KEY_RESIZE);
528 if (err)
529 return err;
533 switch (ch) {
534 case ERR:
535 break;
536 case '\t':
537 if (view->child) {
538 *focus = view->child;
539 view->child_focussed = 1;
540 } else if (view->parent) {
541 *focus = view->parent;
542 view->parent->child_focussed = 0;
544 break;
545 case 'q':
546 err = view->input(new, dead, focus, view, ch);
547 *dead = view;
548 break;
549 case 'Q':
550 *done = 1;
551 break;
552 case 'f':
553 if (view_is_parent_view(view)) {
554 if (view->child == NULL)
555 break;
556 if (view_is_splitscreen(view->child)) {
557 *focus = view->child;
558 view->child_focussed = 1;
559 err = view_fullscreen(view->child);
560 } else
561 err = view_splitscreen(view->child);
562 if (err)
563 break;
564 err = view->child->input(new, dead, focus,
565 view->child, KEY_RESIZE);
566 } else {
567 if (view_is_splitscreen(view)) {
568 *focus = view;
569 view->parent->child_focussed = 1;
570 err = view_fullscreen(view);
571 } else {
572 err = view_splitscreen(view);
574 if (err)
575 break;
576 err = view->input(new, dead, focus, view,
577 KEY_RESIZE);
579 break;
580 case KEY_RESIZE:
581 break;
582 default:
583 err = view->input(new, dead, focus, view, ch);
584 break;
587 return err;
590 void
591 view_vborder(struct tog_view *view)
593 PANEL *panel;
594 struct tog_view *view_above;
596 if (view->parent)
597 return view_vborder(view->parent);
599 panel = panel_above(view->panel);
600 if (panel == NULL)
601 return;
603 view_above = panel_userptr(panel);
604 mvwvline(view->window, view->begin_y, view_above->begin_x - 1,
605 got_locale_is_utf8() ? ACS_VLINE : '|', view->nlines);
608 int
609 view_needs_focus_indication(struct tog_view *view)
611 if (view_is_parent_view(view)) {
612 if (view->child == NULL || view->child_focussed)
613 return 0;
614 if (!view_is_splitscreen(view->child))
615 return 0;
616 } else if (!view_is_splitscreen(view))
617 return 0;
619 return view->focussed;
622 static const struct got_error *
623 view_loop(struct tog_view *view)
625 const struct got_error *err = NULL;
626 struct tog_view_list_head views;
627 struct tog_view *new_view, *dead_view, *focus_view, *main_view;
628 int fast_refresh = 10;
629 int done = 0, errcode;
631 errcode = pthread_mutex_lock(&tog_mutex);
632 if (errcode)
633 return got_error_set_errno(errcode);
635 TAILQ_INIT(&views);
636 TAILQ_INSERT_HEAD(&views, view, entry);
638 main_view = view;
639 view->focussed = 1;
640 err = view->show(view);
641 if (err)
642 return err;
643 update_panels();
644 doupdate();
645 while (!TAILQ_EMPTY(&views) && !done) {
646 /* Refresh fast during initialization, then become slower. */
647 if (fast_refresh && fast_refresh-- == 0)
648 halfdelay(10); /* switch to once per second */
650 err = view_input(&new_view, &dead_view, &focus_view, &done,
651 view, &views);
652 if (err)
653 break;
654 if (dead_view) {
655 struct tog_view *prev = NULL;
657 if (view_is_parent_view(dead_view))
658 prev = TAILQ_PREV(dead_view,
659 tog_view_list_head, entry);
660 else
661 prev = view->parent;
663 if (dead_view->parent)
664 dead_view->parent->child = NULL;
665 else
666 TAILQ_REMOVE(&views, dead_view, entry);
668 err = view_close(dead_view);
669 if (err || dead_view == main_view)
670 goto done;
672 if (view == dead_view) {
673 if (focus_view)
674 view = focus_view;
675 else if (prev)
676 view = prev;
677 else if (!TAILQ_EMPTY(&views))
678 view = TAILQ_LAST(&views,
679 tog_view_list_head);
680 else
681 view = NULL;
682 if (view) {
683 if (view->child && view->child_focussed)
684 focus_view = view->child;
685 else
686 focus_view = view;
690 if (new_view) {
691 struct tog_view *v, *t;
692 /* Only allow one parent view per type. */
693 TAILQ_FOREACH_SAFE(v, &views, entry, t) {
694 if (v->type != new_view->type)
695 continue;
696 TAILQ_REMOVE(&views, v, entry);
697 err = view_close(v);
698 if (err)
699 goto done;
700 if (v == view)
701 view = new_view;
702 break;
704 TAILQ_INSERT_TAIL(&views, new_view, entry);
705 if (focus_view == NULL)
706 focus_view = new_view;
708 if (focus_view) {
709 show_panel(focus_view->panel);
710 if (view)
711 view->focussed = 0;
712 focus_view->focussed = 1;
713 view = focus_view;
714 if (new_view)
715 show_panel(new_view->panel);
716 if (view->child && view_is_splitscreen(view->child))
717 show_panel(view->child->panel);
719 if (view) {
720 if (focus_view == NULL) {
721 view->focussed = 1;
722 show_panel(view->panel);
723 if (view->child && view_is_splitscreen(view->child))
724 show_panel(view->child->panel);
725 focus_view = view;
727 if (view->parent) {
728 err = view->parent->show(view->parent);
729 if (err)
730 goto done;
732 err = view->show(view);
733 if (err)
734 goto done;
735 if (view->child) {
736 err = view->child->show(view->child);
737 if (err)
738 goto done;
740 update_panels();
741 doupdate();
744 done:
745 while (!TAILQ_EMPTY(&views)) {
746 view = TAILQ_FIRST(&views);
747 TAILQ_REMOVE(&views, view, entry);
748 view_close(view);
751 errcode = pthread_mutex_unlock(&tog_mutex);
752 if (errcode)
753 return got_error_set_errno(errcode);
755 return err;
758 __dead static void
759 usage_log(void)
761 endwin();
762 fprintf(stderr,
763 "usage: %s log [-c commit] [-r repository-path] [path]\n",
764 getprogname());
765 exit(1);
768 /* Create newly allocated wide-character string equivalent to a byte string. */
769 static const struct got_error *
770 mbs2ws(wchar_t **ws, size_t *wlen, const char *s)
772 char *vis = NULL;
773 const struct got_error *err = NULL;
775 *ws = NULL;
776 *wlen = mbstowcs(NULL, s, 0);
777 if (*wlen == (size_t)-1) {
778 int vislen;
779 if (errno != EILSEQ)
780 return got_error_from_errno();
782 /* byte string invalid in current encoding; try to "fix" it */
783 err = got_mbsavis(&vis, &vislen, s);
784 if (err)
785 return err;
786 *wlen = mbstowcs(NULL, vis, 0);
787 if (*wlen == (size_t)-1) {
788 err = got_error_from_errno(); /* give up */
789 goto done;
793 *ws = calloc(*wlen + 1, sizeof(*ws));
794 if (*ws == NULL) {
795 err = got_error_from_errno();
796 goto done;
799 if (mbstowcs(*ws, vis ? vis : s, *wlen) != *wlen)
800 err = got_error_from_errno();
801 done:
802 free(vis);
803 if (err) {
804 free(*ws);
805 *ws = NULL;
806 *wlen = 0;
808 return err;
811 /* Format a line for display, ensuring that it won't overflow a width limit. */
812 static const struct got_error *
813 format_line(wchar_t **wlinep, int *widthp, const char *line, int wlimit)
815 const struct got_error *err = NULL;
816 int cols = 0;
817 wchar_t *wline = NULL;
818 size_t wlen;
819 int i;
821 *wlinep = NULL;
822 *widthp = 0;
824 err = mbs2ws(&wline, &wlen, line);
825 if (err)
826 return err;
828 i = 0;
829 while (i < wlen && cols < wlimit) {
830 int width = wcwidth(wline[i]);
831 switch (width) {
832 case 0:
833 i++;
834 break;
835 case 1:
836 case 2:
837 if (cols + width <= wlimit)
838 cols += width;
839 i++;
840 break;
841 case -1:
842 if (wline[i] == L'\t')
843 cols += TABSIZE - ((cols + 1) % TABSIZE);
844 i++;
845 break;
846 default:
847 err = got_error_from_errno();
848 goto done;
851 wline[i] = L'\0';
852 if (widthp)
853 *widthp = cols;
854 done:
855 if (err)
856 free(wline);
857 else
858 *wlinep = wline;
859 return err;
862 static const struct got_error *
863 draw_commit(struct tog_view *view, struct got_commit_object *commit,
864 struct got_object_id *id)
866 const struct got_error *err = NULL;
867 char datebuf[10]; /* YY-MM-DD + SPACE + NUL */
868 char *logmsg0 = NULL, *logmsg = NULL;
869 char *author0 = NULL, *author = NULL;
870 wchar_t *wlogmsg = NULL, *wauthor = NULL;
871 int author_width, logmsg_width;
872 char *newline, *smallerthan;
873 char *line = NULL;
874 int col, limit;
875 static const size_t date_display_cols = 9;
876 static const size_t author_display_cols = 16;
877 const int avail = view->ncols;
879 if (strftime(datebuf, sizeof(datebuf), "%g/%m/%d ",
880 &commit->tm_committer) >= sizeof(datebuf))
881 return got_error(GOT_ERR_NO_SPACE);
883 if (avail < date_display_cols)
884 limit = MIN(sizeof(datebuf) - 1, avail);
885 else
886 limit = MIN(date_display_cols, sizeof(datebuf) - 1);
887 waddnstr(view->window, datebuf, limit);
888 col = limit + 1;
889 if (col > avail)
890 goto done;
892 author0 = strdup(commit->author);
893 if (author0 == NULL) {
894 err = got_error_from_errno();
895 goto done;
897 author = author0;
898 smallerthan = strchr(author, '<');
899 if (smallerthan)
900 *smallerthan = '\0';
901 else {
902 char *at = strchr(author, '@');
903 if (at)
904 *at = '\0';
906 limit = avail - col;
907 err = format_line(&wauthor, &author_width, author, limit);
908 if (err)
909 goto done;
910 waddwstr(view->window, wauthor);
911 col += author_width;
912 while (col <= avail && author_width < author_display_cols + 1) {
913 waddch(view->window, ' ');
914 col++;
915 author_width++;
917 if (col > avail)
918 goto done;
920 logmsg0 = strdup(commit->logmsg);
921 if (logmsg0 == NULL) {
922 err = got_error_from_errno();
923 goto done;
925 logmsg = logmsg0;
926 while (*logmsg == '\n')
927 logmsg++;
928 newline = strchr(logmsg, '\n');
929 if (newline)
930 *newline = '\0';
931 limit = avail - col;
932 err = format_line(&wlogmsg, &logmsg_width, logmsg, limit);
933 if (err)
934 goto done;
935 waddwstr(view->window, wlogmsg);
936 col += logmsg_width;
937 while (col <= avail) {
938 waddch(view->window, ' ');
939 col++;
941 done:
942 free(logmsg0);
943 free(wlogmsg);
944 free(author0);
945 free(wauthor);
946 free(line);
947 return err;
950 static struct commit_queue_entry *
951 alloc_commit_queue_entry(struct got_commit_object *commit,
952 struct got_object_id *id)
954 struct commit_queue_entry *entry;
956 entry = calloc(1, sizeof(*entry));
957 if (entry == NULL)
958 return NULL;
960 entry->id = id;
961 entry->commit = commit;
962 return entry;
965 static void
966 pop_commit(struct commit_queue *commits)
968 struct commit_queue_entry *entry;
970 entry = TAILQ_FIRST(&commits->head);
971 TAILQ_REMOVE(&commits->head, entry, entry);
972 got_object_commit_close(entry->commit);
973 commits->ncommits--;
974 /* Don't free entry->id! It is owned by the commit graph. */
975 free(entry);
978 static void
979 free_commits(struct commit_queue *commits)
981 while (!TAILQ_EMPTY(&commits->head))
982 pop_commit(commits);
985 static const struct got_error *
986 queue_commits(struct got_commit_graph *graph, struct commit_queue *commits,
987 int minqueue, struct got_repository *repo, const char *path)
989 const struct got_error *err = NULL;
990 int nqueued = 0;
992 /*
993 * We keep all commits open throughout the lifetime of the log
994 * view in order to avoid having to re-fetch commits from disk
995 * while updating the display.
996 */
997 while (nqueued < minqueue) {
998 struct got_object_id *id;
999 struct got_commit_object *commit;
1000 struct commit_queue_entry *entry;
1001 int errcode;
1003 err = got_commit_graph_iter_next(&id, graph);
1004 if (err) {
1005 if (err->code != GOT_ERR_ITER_NEED_MORE)
1006 break;
1007 err = got_commit_graph_fetch_commits(graph,
1008 minqueue, repo);
1009 if (err)
1010 return err;
1011 continue;
1014 if (id == NULL)
1015 break;
1017 err = got_object_open_as_commit(&commit, repo, id);
1018 if (err)
1019 break;
1020 entry = alloc_commit_queue_entry(commit, id);
1021 if (entry == NULL) {
1022 err = got_error_from_errno();
1023 break;
1026 errcode = pthread_mutex_lock(&tog_mutex);
1027 if (errcode) {
1028 err = got_error_set_errno(errcode);
1029 break;
1032 entry->idx = commits->ncommits;
1033 TAILQ_INSERT_TAIL(&commits->head, entry, entry);
1034 nqueued++;
1035 commits->ncommits++;
1037 errcode = pthread_mutex_unlock(&tog_mutex);
1038 if (errcode && err == NULL)
1039 err = got_error_set_errno(errcode);
1042 return err;
1045 static const struct got_error *
1046 get_head_commit_id(struct got_object_id **head_id, struct got_repository *repo)
1048 const struct got_error *err = NULL;
1049 struct got_reference *head_ref;
1051 *head_id = NULL;
1053 err = got_ref_open(&head_ref, repo, GOT_REF_HEAD);
1054 if (err)
1055 return err;
1057 err = got_ref_resolve(head_id, repo, head_ref);
1058 got_ref_close(head_ref);
1059 if (err) {
1060 *head_id = NULL;
1061 return err;
1064 return NULL;
1067 static const struct got_error *
1068 draw_commits(struct tog_view *view, struct commit_queue_entry **last,
1069 struct commit_queue_entry **selected, struct commit_queue_entry *first,
1070 struct commit_queue *commits, int selected_idx, int limit,
1071 const char *path, int commits_needed)
1073 const struct got_error *err = NULL;
1074 struct commit_queue_entry *entry;
1075 int ncommits, width;
1076 char *id_str = NULL, *header = NULL, *ncommits_str = NULL;
1077 wchar_t *wline;
1079 entry = first;
1080 ncommits = 0;
1081 while (entry) {
1082 if (ncommits == selected_idx) {
1083 *selected = entry;
1084 break;
1086 entry = TAILQ_NEXT(entry, entry);
1087 ncommits++;
1090 if (*selected) {
1091 err = got_object_id_str(&id_str, (*selected)->id);
1092 if (err)
1093 return err;
1096 if (asprintf(&ncommits_str, " [%d/%d]%s ",
1097 entry ? entry->idx + 1 : 0, commits->ncommits,
1098 commits_needed == 0 ? "" : " loading...") == -1)
1099 return got_error_from_errno();
1101 if (path && strcmp(path, "/") != 0) {
1102 if (asprintf(&header, "commit: %s %s%s",
1103 id_str ? id_str : "........................................",
1104 path, ncommits_str) == -1) {
1105 err = got_error_from_errno();
1106 header = NULL;
1107 goto done;
1109 } else if (asprintf(&header, "commit: %s%s",
1110 id_str ? id_str : "........................................",
1111 ncommits_str) == -1) {
1112 err = got_error_from_errno();
1113 header = NULL;
1114 goto done;
1116 err = format_line(&wline, &width, header, view->ncols);
1117 if (err)
1118 goto done;
1120 werase(view->window);
1122 if (view_needs_focus_indication(view))
1123 wstandout(view->window);
1124 waddwstr(view->window, wline);
1125 while (width < view->ncols) {
1126 waddch(view->window, ' ');
1127 width++;
1129 if (view_needs_focus_indication(view))
1130 wstandend(view->window);
1131 free(wline);
1132 if (limit <= 1)
1133 goto done;
1135 entry = first;
1136 *last = first;
1137 ncommits = 0;
1138 while (entry) {
1139 if (ncommits >= limit - 1)
1140 break;
1141 if (view->focussed && ncommits == selected_idx)
1142 wstandout(view->window);
1143 err = draw_commit(view, entry->commit, entry->id);
1144 if (view->focussed && ncommits == selected_idx)
1145 wstandend(view->window);
1146 if (err)
1147 break;
1148 ncommits++;
1149 *last = entry;
1150 entry = TAILQ_NEXT(entry, entry);
1153 view_vborder(view);
1154 done:
1155 free(id_str);
1156 free(ncommits_str);
1157 free(header);
1158 return err;
1161 static void
1162 scroll_up(struct commit_queue_entry **first_displayed_entry, int maxscroll,
1163 struct commit_queue *commits)
1165 struct commit_queue_entry *entry;
1166 int nscrolled = 0;
1168 entry = TAILQ_FIRST(&commits->head);
1169 if (*first_displayed_entry == entry)
1170 return;
1172 entry = *first_displayed_entry;
1173 while (entry && nscrolled < maxscroll) {
1174 entry = TAILQ_PREV(entry, commit_queue_head, entry);
1175 if (entry) {
1176 *first_displayed_entry = entry;
1177 nscrolled++;
1182 static const struct got_error *
1183 scroll_down(struct commit_queue_entry **first_displayed_entry, int maxscroll,
1184 struct commit_queue_entry **last_displayed_entry,
1185 struct commit_queue *commits, int *log_complete, int *commits_needed,
1186 pthread_cond_t *need_commits)
1188 const struct got_error *err = NULL;
1189 struct commit_queue_entry *pentry;
1190 int nscrolled = 0;
1192 if (*last_displayed_entry == NULL)
1193 return NULL;
1195 do {
1196 pentry = TAILQ_NEXT(*last_displayed_entry, entry);
1197 if (pentry == NULL) {
1198 int errcode;
1199 if (*log_complete)
1200 return NULL;
1201 *commits_needed = maxscroll + 20;
1202 errcode = pthread_cond_signal(need_commits);
1203 if (errcode)
1204 return got_error_set_errno(errcode);
1205 return NULL;
1207 *last_displayed_entry = pentry;
1209 pentry = TAILQ_NEXT(*first_displayed_entry, entry);
1210 if (pentry == NULL)
1211 break;
1212 *first_displayed_entry = pentry;
1213 } while (++nscrolled < maxscroll);
1215 return err;
1218 static const struct got_error *
1219 open_diff_view_for_commit(struct tog_view **new_view, int begin_x,
1220 struct got_object_id *commit_id, struct got_commit_object *commit,
1221 struct got_repository *repo)
1223 const struct got_error *err;
1224 struct got_object *obj1 = NULL, *obj2 = NULL;
1225 struct got_object_qid *parent_id;
1226 struct tog_view *diff_view;
1228 err = got_object_open(&obj2, repo, commit_id);
1229 if (err)
1230 return err;
1232 parent_id = SIMPLEQ_FIRST(&commit->parent_ids);
1233 if (parent_id) {
1234 err = got_object_open(&obj1, repo, parent_id->id);
1235 if (err)
1236 goto done;
1239 diff_view = view_open(0, 0, 0, begin_x, TOG_VIEW_DIFF);
1240 if (diff_view == NULL) {
1241 err = got_error_from_errno();
1242 goto done;
1245 err = open_diff_view(diff_view, obj1, obj2, repo);
1246 if (err == NULL)
1247 *new_view = diff_view;
1248 done:
1249 if (obj1)
1250 got_object_close(obj1);
1251 if (obj2)
1252 got_object_close(obj2);
1253 return err;
1256 static const struct got_error *
1257 browse_commit(struct tog_view **new_view, int begin_x,
1258 struct commit_queue_entry *entry, struct got_repository *repo)
1260 const struct got_error *err = NULL;
1261 struct got_tree_object *tree;
1262 struct tog_view *tree_view;
1264 err = got_object_open_as_tree(&tree, repo, entry->commit->tree_id);
1265 if (err)
1266 return err;
1268 tree_view = view_open(0, 0, 0, begin_x, TOG_VIEW_TREE);
1269 if (tree_view == NULL)
1270 return got_error_from_errno();
1272 err = open_tree_view(tree_view, tree, entry->id, repo);
1273 if (err)
1274 got_object_tree_close(tree);
1275 else
1276 *new_view = tree_view;
1277 return err;
1280 static void *
1281 log_thread(void *arg)
1283 const struct got_error *err = NULL;
1284 int errcode = 0;
1285 struct tog_log_thread_args *a = arg;
1286 int done = 0;
1288 err = got_commit_graph_iter_start(a->graph, a->start_id, a->repo);
1289 if (err)
1290 return (void *)err;
1292 while (!done && !err) {
1293 err = queue_commits(a->graph, a->commits, 1, a->repo,
1294 a->in_repo_path);
1295 if (err) {
1296 if (err->code != GOT_ERR_ITER_COMPLETED)
1297 return (void *)err;
1298 err = NULL;
1299 done = 1;
1300 } else if (a->commits_needed > 0)
1301 a->commits_needed--;
1303 errcode = pthread_mutex_lock(&tog_mutex);
1304 if (errcode)
1305 return (void *)got_error_set_errno(errcode);
1307 if (done)
1308 a->log_complete = 1;
1309 else if (*a->quit) {
1310 done = 1;
1311 a->log_complete = 1;
1312 } else if (*a->first_displayed_entry == NULL) {
1313 *a->first_displayed_entry =
1314 TAILQ_FIRST(&a->commits->head);
1315 *a->selected_entry = *a->first_displayed_entry;
1318 if (done)
1319 a->commits_needed = 0;
1320 else if (a->commits_needed == 0) {
1321 errcode = pthread_cond_wait(&a->need_commits,
1322 &tog_mutex);
1323 if (errcode)
1324 err = got_error_set_errno(errcode);
1327 errcode = pthread_mutex_unlock(&tog_mutex);
1328 if (errcode && err == NULL)
1329 err = got_error_set_errno(errcode);
1331 return (void *)err;
1334 static const struct got_error *
1335 stop_log_thread(struct tog_log_view_state *s)
1337 const struct got_error *err = NULL;
1338 int errcode;
1340 if (s->thread) {
1341 s->quit = 1;
1342 errcode = pthread_cond_signal(&s->thread_args.need_commits);
1343 if (errcode)
1344 return got_error_set_errno(errcode);
1345 errcode = pthread_mutex_unlock(&tog_mutex);
1346 if (errcode)
1347 return got_error_set_errno(errcode);
1348 errcode = pthread_join(s->thread, (void **)&err);
1349 if (errcode)
1350 return got_error_set_errno(errcode);
1351 errcode = pthread_mutex_lock(&tog_mutex);
1352 if (errcode)
1353 return got_error_set_errno(errcode);
1354 s->thread = NULL;
1357 errcode = pthread_cond_destroy(&s->thread_args.need_commits);
1358 if (errcode && err == NULL)
1359 err = got_error_set_errno(errcode);
1361 if (s->thread_args.repo) {
1362 got_repo_close(s->thread_args.repo);
1363 s->thread_args.repo = NULL;
1366 if (s->thread_args.graph) {
1367 got_commit_graph_close(s->thread_args.graph);
1368 s->thread_args.graph = NULL;
1371 return err;
1374 static const struct got_error *
1375 close_log_view(struct tog_view *view)
1377 const struct got_error *err = NULL;
1378 struct tog_log_view_state *s = &view->state.log;
1380 err = stop_log_thread(s);
1381 free_commits(&s->commits);
1382 free(s->in_repo_path);
1383 s->in_repo_path = NULL;
1384 free(s->start_id);
1385 s->start_id = NULL;
1386 return err;
1389 static const struct got_error *
1390 open_log_view(struct tog_view *view, struct got_object_id *start_id,
1391 struct got_repository *repo, const char *path, int check_disk)
1393 const struct got_error *err = NULL;
1394 struct tog_log_view_state *s = &view->state.log;
1395 struct got_repository *thread_repo = NULL;
1396 struct got_commit_graph *thread_graph = NULL;
1397 int errcode;
1399 err = got_repo_map_path(&s->in_repo_path, repo, path, check_disk);
1400 if (err != NULL)
1401 goto done;
1403 /* The commit queue only contains commits being displayed. */
1404 TAILQ_INIT(&s->commits.head);
1405 s->commits.ncommits = 0;
1407 s->repo = repo;
1408 s->start_id = got_object_id_dup(start_id);
1409 if (s->start_id == NULL) {
1410 err = got_error_from_errno();
1411 goto done;
1414 view->show = show_log_view;
1415 view->input = input_log_view;
1416 view->close = close_log_view;
1418 err = got_repo_open(&thread_repo, got_repo_get_path(repo));
1419 if (err)
1420 goto done;
1421 err = got_commit_graph_open(&thread_graph, start_id, s->in_repo_path,
1422 0, thread_repo);
1423 if (err)
1424 goto done;
1426 errcode = pthread_cond_init(&s->thread_args.need_commits, NULL);
1427 if (errcode) {
1428 err = got_error_set_errno(errcode);
1429 goto done;
1432 s->thread_args.commits_needed = view->nlines;
1433 s->thread_args.graph = thread_graph;
1434 s->thread_args.commits = &s->commits;
1435 s->thread_args.in_repo_path = s->in_repo_path;
1436 s->thread_args.start_id = s->start_id;
1437 s->thread_args.repo = thread_repo;
1438 s->thread_args.log_complete = 0;
1439 s->thread_args.quit = &s->quit;
1440 s->thread_args.view = view;
1441 s->thread_args.first_displayed_entry = &s->first_displayed_entry;
1442 s->thread_args.selected_entry = &s->selected_entry;
1444 errcode = pthread_create(&s->thread, NULL, log_thread,
1445 &s->thread_args);
1446 if (errcode) {
1447 err = got_error_set_errno(errcode);
1448 goto done;
1451 done:
1452 if (err)
1453 close_log_view(view);
1454 return err;
1457 static const struct got_error *
1458 show_log_view(struct tog_view *view)
1460 struct tog_log_view_state *s = &view->state.log;
1462 return draw_commits(view, &s->last_displayed_entry,
1463 &s->selected_entry, s->first_displayed_entry,
1464 &s->commits, s->selected, view->nlines,
1465 s->in_repo_path, s->thread_args.commits_needed);
1468 static const struct got_error *
1469 input_log_view(struct tog_view **new_view, struct tog_view **dead_view,
1470 struct tog_view **focus_view, struct tog_view *view, int ch)
1472 const struct got_error *err = NULL;
1473 struct tog_log_view_state *s = &view->state.log;
1474 char *parent_path;
1475 struct tog_view *diff_view = NULL, *tree_view = NULL;
1476 int begin_x = 0;
1478 switch (ch) {
1479 case 'q':
1480 s->quit = 1;
1481 break;
1482 case 'k':
1483 case KEY_UP:
1484 if (s->selected > 0)
1485 s->selected--;
1486 if (s->selected > 0)
1487 break;
1488 scroll_up(&s->first_displayed_entry, 1,
1489 &s->commits);
1490 break;
1491 case KEY_PPAGE:
1492 if (TAILQ_FIRST(&s->commits.head) ==
1493 s->first_displayed_entry) {
1494 s->selected = 0;
1495 break;
1497 scroll_up(&s->first_displayed_entry,
1498 view->nlines, &s->commits);
1499 break;
1500 case 'j':
1501 case KEY_DOWN:
1502 if (s->selected < MIN(view->nlines - 2,
1503 s->commits.ncommits - 1)) {
1504 s->selected++;
1505 break;
1507 err = scroll_down(&s->first_displayed_entry, 1,
1508 &s->last_displayed_entry, &s->commits,
1509 &s->thread_args.log_complete,
1510 &s->thread_args.commits_needed,
1511 &s->thread_args.need_commits);
1512 break;
1513 case KEY_NPAGE: {
1514 struct commit_queue_entry *first;
1515 first = s->first_displayed_entry;
1516 err = scroll_down(&s->first_displayed_entry,
1517 view->nlines, &s->last_displayed_entry,
1518 &s->commits, &s->thread_args.log_complete,
1519 &s->thread_args.commits_needed,
1520 &s->thread_args.need_commits);
1521 if (first == s->first_displayed_entry &&
1522 s->selected < MIN(view->nlines - 2,
1523 s->commits.ncommits - 1)) {
1524 /* can't scroll further down */
1525 s->selected = MIN(view->nlines - 2,
1526 s->commits.ncommits - 1);
1528 err = NULL;
1529 break;
1531 case KEY_RESIZE:
1532 if (s->selected > view->nlines - 2)
1533 s->selected = view->nlines - 2;
1534 if (s->selected > s->commits.ncommits - 1)
1535 s->selected = s->commits.ncommits - 1;
1536 break;
1537 case KEY_ENTER:
1538 case '\r':
1539 if (view_is_parent_view(view))
1540 begin_x = view_split_begin_x(view->begin_x);
1541 err = open_diff_view_for_commit(&diff_view, begin_x,
1542 s->selected_entry->id, s->selected_entry->commit,
1543 s->repo);
1544 if (err)
1545 break;
1546 if (view_is_parent_view(view)) {
1547 err = view_close_child(view);
1548 if (err)
1549 return err;
1550 err = view_set_child(view, diff_view);
1551 if (err) {
1552 view_close(diff_view);
1553 break;
1555 if (!view_is_splitscreen(diff_view)) {
1556 *focus_view = diff_view;
1557 view->child_focussed = 1;
1559 } else
1560 *new_view = diff_view;
1561 break;
1562 case 't':
1563 if (view_is_parent_view(view))
1564 begin_x = view_split_begin_x(view->begin_x);
1565 err = browse_commit(&tree_view, begin_x,
1566 s->selected_entry, s->repo);
1567 if (view_is_parent_view(view)) {
1568 err = view_close_child(view);
1569 if (err)
1570 return err;
1571 err = view_set_child(view, tree_view);
1572 if (err) {
1573 view_close(tree_view);
1574 break;
1576 *focus_view = tree_view;
1577 view->child_focussed = 1;
1578 } else
1579 *new_view = tree_view;
1580 break;
1581 case KEY_BACKSPACE:
1582 if (strcmp(s->in_repo_path, "/") == 0)
1583 break;
1584 parent_path = dirname(s->in_repo_path);
1585 if (parent_path && strcmp(parent_path, ".") != 0) {
1586 struct tog_view *lv;
1587 err = stop_log_thread(s);
1588 if (err)
1589 return err;
1590 lv = view_open(view->nlines, view->ncols,
1591 view->begin_y, view->begin_x, TOG_VIEW_LOG);
1592 if (lv == NULL)
1593 return got_error_from_errno();
1594 err = open_log_view(lv, s->start_id, s->repo,
1595 parent_path, 0);
1596 if (err)
1597 return err;;
1598 if (view_is_parent_view(view))
1599 *new_view = lv;
1600 else {
1601 view_set_child(view->parent, lv);
1602 *focus_view = lv;
1604 return NULL;
1606 break;
1607 default:
1608 break;
1611 return err;
1614 static const struct got_error *
1615 cmd_log(int argc, char *argv[])
1617 const struct got_error *error;
1618 struct got_repository *repo = NULL;
1619 struct got_object_id *start_id = NULL;
1620 char *path = NULL, *repo_path = NULL, *cwd = NULL;
1621 char *start_commit = NULL;
1622 int ch;
1623 struct tog_view *view;
1625 #ifndef PROFILE
1626 if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd", NULL)
1627 == -1)
1628 err(1, "pledge");
1629 #endif
1631 while ((ch = getopt(argc, argv, "c:r:")) != -1) {
1632 switch (ch) {
1633 case 'c':
1634 start_commit = optarg;
1635 break;
1636 case 'r':
1637 repo_path = realpath(optarg, NULL);
1638 if (repo_path == NULL)
1639 err(1, "-r option");
1640 break;
1641 default:
1642 usage();
1643 /* NOTREACHED */
1647 argc -= optind;
1648 argv += optind;
1650 if (argc == 0)
1651 path = strdup("");
1652 else if (argc == 1)
1653 path = strdup(argv[0]);
1654 else
1655 usage_log();
1656 if (path == NULL)
1657 return got_error_from_errno();
1659 cwd = getcwd(NULL, 0);
1660 if (cwd == NULL) {
1661 error = got_error_from_errno();
1662 goto done;
1664 if (repo_path == NULL) {
1665 repo_path = strdup(cwd);
1666 if (repo_path == NULL) {
1667 error = got_error_from_errno();
1668 goto done;
1672 error = got_repo_open(&repo, repo_path);
1673 if (error != NULL)
1674 goto done;
1676 if (start_commit == NULL) {
1677 error = get_head_commit_id(&start_id, repo);
1678 if (error != NULL)
1679 goto done;
1680 } else {
1681 struct got_object *obj;
1682 error = got_object_open_by_id_str(&obj, repo, start_commit);
1683 if (error == NULL) {
1684 start_id = got_object_id_dup(got_object_get_id(obj));
1685 if (start_id == NULL)
1686 error = got_error_from_errno();
1687 goto done;
1690 if (error != NULL)
1691 goto done;
1693 view = view_open(0, 0, 0, 0, TOG_VIEW_LOG);
1694 if (view == NULL) {
1695 error = got_error_from_errno();
1696 goto done;
1698 error = open_log_view(view, start_id, repo, path, 1);
1699 if (error)
1700 goto done;
1701 error = view_loop(view);
1702 done:
1703 free(repo_path);
1704 free(cwd);
1705 free(path);
1706 free(start_id);
1707 if (repo)
1708 got_repo_close(repo);
1709 return error;
1712 __dead static void
1713 usage_diff(void)
1715 endwin();
1716 fprintf(stderr, "usage: %s diff [repository-path] object1 object2\n",
1717 getprogname());
1718 exit(1);
1721 static char *
1722 parse_next_line(FILE *f, size_t *len)
1724 char *line;
1725 size_t linelen;
1726 size_t lineno;
1727 const char delim[3] = { '\0', '\0', '\0'};
1729 line = fparseln(f, &linelen, &lineno, delim, 0);
1730 if (len)
1731 *len = linelen;
1732 return line;
1735 static const struct got_error *
1736 draw_file(struct tog_view *view, FILE *f, int *first_displayed_line,
1737 int *last_displayed_line, int *eof, int max_lines,
1738 char * header)
1740 const struct got_error *err;
1741 int nlines = 0, nprinted = 0;
1742 char *line;
1743 size_t len;
1744 wchar_t *wline;
1745 int width;
1747 rewind(f);
1748 werase(view->window);
1750 if (header) {
1751 err = format_line(&wline, &width, header, view->ncols);
1752 if (err) {
1753 return err;
1756 if (view_needs_focus_indication(view))
1757 wstandout(view->window);
1758 waddwstr(view->window, wline);
1759 if (view_needs_focus_indication(view))
1760 wstandend(view->window);
1761 if (width < view->ncols)
1762 waddch(view->window, '\n');
1764 if (max_lines <= 1)
1765 return NULL;
1766 max_lines--;
1769 *eof = 0;
1770 while (nprinted < max_lines) {
1771 line = parse_next_line(f, &len);
1772 if (line == NULL) {
1773 *eof = 1;
1774 break;
1776 if (++nlines < *first_displayed_line) {
1777 free(line);
1778 continue;
1781 err = format_line(&wline, &width, line, view->ncols);
1782 if (err) {
1783 free(line);
1784 return err;
1786 waddwstr(view->window, wline);
1787 if (width < view->ncols)
1788 waddch(view->window, '\n');
1789 if (++nprinted == 1)
1790 *first_displayed_line = nlines;
1791 free(line);
1792 free(wline);
1793 wline = NULL;
1795 *last_displayed_line = nlines;
1797 view_vborder(view);
1799 return NULL;
1802 static const struct got_error *
1803 create_diff(struct tog_diff_view_state *s)
1805 const struct got_error *err = NULL;
1806 struct got_object *obj1 = NULL, *obj2 = NULL;
1807 FILE *f = NULL;
1809 if (s->id1) {
1810 err = got_object_open(&obj1, s->repo, s->id1);
1811 if (err)
1812 return err;
1815 err = got_object_open(&obj2, s->repo, s->id2);
1816 if (err)
1817 goto done;
1819 f = got_opentemp();
1820 if (f == NULL) {
1821 err = got_error_from_errno();
1822 goto done;
1824 if (s->f)
1825 fclose(s->f);
1826 s->f = f;
1828 switch (got_object_get_type(obj1 ? obj1 : obj2)) {
1829 case GOT_OBJ_TYPE_BLOB:
1830 err = got_diff_objects_as_blobs(obj1, obj2, NULL, NULL,
1831 s->diff_context, s->repo, f);
1832 break;
1833 case GOT_OBJ_TYPE_TREE:
1834 err = got_diff_objects_as_trees(obj1, obj2, "", "",
1835 s->diff_context, s->repo, f);
1836 break;
1837 case GOT_OBJ_TYPE_COMMIT:
1838 err = got_diff_objects_as_commits(obj1, obj2, s->diff_context,
1839 s->repo, f);
1840 break;
1841 default:
1842 err = got_error(GOT_ERR_OBJ_TYPE);
1843 break;
1845 done:
1846 if (obj1)
1847 got_object_close(obj1);
1848 got_object_close(obj2);
1849 if (f)
1850 fflush(f);
1851 return err;
1854 static const struct got_error *
1855 open_diff_view(struct tog_view *view, struct got_object *obj1,
1856 struct got_object *obj2, struct got_repository *repo)
1858 const struct got_error *err;
1860 if (obj1 != NULL && obj2 != NULL &&
1861 got_object_get_type(obj1) != got_object_get_type(obj2))
1862 return got_error(GOT_ERR_OBJ_TYPE);
1864 if (obj1) {
1865 struct got_object_id *id1;
1866 id1 = got_object_id_dup(got_object_get_id(obj1));
1867 if (id1 == NULL)
1868 return got_error_from_errno();
1869 view->state.diff.id1 = id1;
1870 } else
1871 view->state.diff.id1 = NULL;
1873 view->state.diff.id2 = got_object_id_dup(got_object_get_id(obj2));
1874 if (view->state.diff.id2 == NULL) {
1875 free(view->state.diff.id1);
1876 view->state.diff.id1 = NULL;
1877 return got_error_from_errno();
1879 view->state.diff.f = NULL;
1880 view->state.diff.first_displayed_line = 1;
1881 view->state.diff.last_displayed_line = view->nlines;
1882 view->state.diff.diff_context = 3;
1883 view->state.diff.repo = repo;
1885 err = create_diff(&view->state.diff);
1886 if (err) {
1887 free(view->state.diff.id1);
1888 view->state.diff.id1 = NULL;
1889 free(view->state.diff.id2);
1890 view->state.diff.id2 = NULL;
1891 return err;
1894 view->show = show_diff_view;
1895 view->input = input_diff_view;
1896 view->close = close_diff_view;
1898 return NULL;
1901 static const struct got_error *
1902 close_diff_view(struct tog_view *view)
1904 const struct got_error *err = NULL;
1906 free(view->state.diff.id1);
1907 view->state.diff.id1 = NULL;
1908 free(view->state.diff.id2);
1909 view->state.diff.id2 = NULL;
1910 if (view->state.diff.f && fclose(view->state.diff.f) == EOF)
1911 err = got_error_from_errno();
1912 return err;
1915 static const struct got_error *
1916 show_diff_view(struct tog_view *view)
1918 const struct got_error *err;
1919 struct tog_diff_view_state *s = &view->state.diff;
1920 char *id_str1 = NULL, *id_str2, *header;
1922 if (s->id1) {
1923 err = got_object_id_str(&id_str1, s->id1);
1924 if (err)
1925 return err;
1927 err = got_object_id_str(&id_str2, s->id2);
1928 if (err)
1929 return err;
1931 if (asprintf(&header, "diff: %s %s",
1932 id_str1 ? id_str1 : "/dev/null", id_str2) == -1) {
1933 err = got_error_from_errno();
1934 free(id_str1);
1935 free(id_str2);
1936 return err;
1938 free(id_str1);
1939 free(id_str2);
1941 return draw_file(view, s->f, &s->first_displayed_line,
1942 &s->last_displayed_line, &s->eof, view->nlines,
1943 header);
1946 static const struct got_error *
1947 input_diff_view(struct tog_view **new_view, struct tog_view **dead_view,
1948 struct tog_view **focus_view, struct tog_view *view, int ch)
1950 const struct got_error *err = NULL;
1951 struct tog_diff_view_state *s = &view->state.diff;
1952 int i;
1954 switch (ch) {
1955 case 'k':
1956 case KEY_UP:
1957 if (s->first_displayed_line > 1)
1958 s->first_displayed_line--;
1959 break;
1960 case KEY_PPAGE:
1961 i = 0;
1962 while (i++ < view->nlines - 1 &&
1963 s->first_displayed_line > 1)
1964 s->first_displayed_line--;
1965 break;
1966 case 'j':
1967 case KEY_DOWN:
1968 if (!s->eof)
1969 s->first_displayed_line++;
1970 break;
1971 case KEY_NPAGE:
1972 case ' ':
1973 i = 0;
1974 while (!s->eof && i++ < view->nlines - 1) {
1975 char *line;
1976 line = parse_next_line(s->f, NULL);
1977 s->first_displayed_line++;
1978 if (line == NULL)
1979 break;
1981 break;
1982 case '[':
1983 if (s->diff_context > 0) {
1984 s->diff_context--;
1985 err = create_diff(s);
1987 break;
1988 case ']':
1989 if (s->diff_context < GOT_DIFF_MAX_CONTEXT) {
1990 s->diff_context++;
1991 err = create_diff(s);
1993 break;
1994 default:
1995 break;
1998 return err;
2001 static const struct got_error *
2002 cmd_diff(int argc, char *argv[])
2004 const struct got_error *error = NULL;
2005 struct got_repository *repo = NULL;
2006 struct got_object *obj1 = NULL, *obj2 = NULL;
2007 char *repo_path = NULL;
2008 char *obj_id_str1 = NULL, *obj_id_str2 = NULL;
2009 int ch;
2010 struct tog_view *view;
2012 #ifndef PROFILE
2013 if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd", NULL)
2014 == -1)
2015 err(1, "pledge");
2016 #endif
2018 while ((ch = getopt(argc, argv, "")) != -1) {
2019 switch (ch) {
2020 default:
2021 usage();
2022 /* NOTREACHED */
2026 argc -= optind;
2027 argv += optind;
2029 if (argc == 0) {
2030 usage_diff(); /* TODO show local worktree changes */
2031 } else if (argc == 2) {
2032 repo_path = getcwd(NULL, 0);
2033 if (repo_path == NULL)
2034 return got_error_from_errno();
2035 obj_id_str1 = argv[0];
2036 obj_id_str2 = argv[1];
2037 } else if (argc == 3) {
2038 repo_path = realpath(argv[0], NULL);
2039 if (repo_path == NULL)
2040 return got_error_from_errno();
2041 obj_id_str1 = argv[1];
2042 obj_id_str2 = argv[2];
2043 } else
2044 usage_diff();
2046 error = got_repo_open(&repo, repo_path);
2047 free(repo_path);
2048 if (error)
2049 goto done;
2051 error = got_object_open_by_id_str(&obj1, repo, obj_id_str1);
2052 if (error)
2053 goto done;
2055 error = got_object_open_by_id_str(&obj2, repo, obj_id_str2);
2056 if (error)
2057 goto done;
2059 view = view_open(0, 0, 0, 0, TOG_VIEW_DIFF);
2060 if (view == NULL) {
2061 error = got_error_from_errno();
2062 goto done;
2064 error = open_diff_view(view, obj1, obj2, repo);
2065 if (error)
2066 goto done;
2067 error = view_loop(view);
2068 done:
2069 got_repo_close(repo);
2070 if (obj1)
2071 got_object_close(obj1);
2072 if (obj2)
2073 got_object_close(obj2);
2074 return error;
2077 __dead static void
2078 usage_blame(void)
2080 endwin();
2081 fprintf(stderr, "usage: %s blame [-c commit] [-r repository-path] path\n",
2082 getprogname());
2083 exit(1);
2086 struct tog_blame_line {
2087 int annotated;
2088 struct got_object_id *id;
2091 static const struct got_error *
2092 draw_blame(struct tog_view *view, struct got_object_id *id, FILE *f,
2093 const char *path, struct tog_blame_line *lines, int nlines,
2094 int blame_complete, int selected_line, int *first_displayed_line,
2095 int *last_displayed_line, int *eof, int max_lines)
2097 const struct got_error *err;
2098 int lineno = 0, nprinted = 0;
2099 char *line;
2100 size_t len;
2101 wchar_t *wline;
2102 int width, wlimit;
2103 struct tog_blame_line *blame_line;
2104 struct got_object_id *prev_id = NULL;
2105 char *id_str;
2107 err = got_object_id_str(&id_str, id);
2108 if (err)
2109 return err;
2111 rewind(f);
2112 werase(view->window);
2114 if (asprintf(&line, "commit: %s", id_str) == -1) {
2115 err = got_error_from_errno();
2116 free(id_str);
2117 return err;
2120 err = format_line(&wline, &width, line, view->ncols);
2121 free(line);
2122 line = NULL;
2123 if (view_needs_focus_indication(view))
2124 wstandout(view->window);
2125 waddwstr(view->window, wline);
2126 if (view_needs_focus_indication(view))
2127 wstandend(view->window);
2128 free(wline);
2129 wline = NULL;
2130 if (width < view->ncols)
2131 waddch(view->window, '\n');
2133 if (asprintf(&line, "[%d/%d] %s%s",
2134 *first_displayed_line - 1 + selected_line, nlines,
2135 blame_complete ? "" : "annotating ", path) == -1) {
2136 free(id_str);
2137 return got_error_from_errno();
2139 free(id_str);
2140 err = format_line(&wline, &width, line, view->ncols);
2141 free(line);
2142 line = NULL;
2143 if (err)
2144 return err;
2145 waddwstr(view->window, wline);
2146 free(wline);
2147 wline = NULL;
2148 if (width < view->ncols)
2149 waddch(view->window, '\n');
2151 *eof = 0;
2152 while (nprinted < max_lines - 2) {
2153 line = parse_next_line(f, &len);
2154 if (line == NULL) {
2155 *eof = 1;
2156 break;
2158 if (++lineno < *first_displayed_line) {
2159 free(line);
2160 continue;
2163 wlimit = view->ncols < 9 ? 0 : view->ncols - 9;
2164 err = format_line(&wline, &width, line, wlimit);
2165 if (err) {
2166 free(line);
2167 return err;
2170 if (view->focussed && nprinted == selected_line - 1)
2171 wstandout(view->window);
2173 blame_line = &lines[lineno - 1];
2174 if (blame_line->annotated && prev_id &&
2175 got_object_id_cmp(prev_id, blame_line->id) == 0)
2176 waddstr(view->window, " ");
2177 else if (blame_line->annotated) {
2178 char *id_str;
2179 err = got_object_id_str(&id_str, blame_line->id);
2180 if (err) {
2181 free(line);
2182 free(wline);
2183 return err;
2185 wprintw(view->window, "%.8s ", id_str);
2186 free(id_str);
2187 prev_id = blame_line->id;
2188 } else {
2189 waddstr(view->window, "........ ");
2190 prev_id = NULL;
2193 waddwstr(view->window, wline);
2194 while (width < wlimit) {
2195 waddch(view->window, ' ');
2196 width++;
2198 if (view->focussed && nprinted == selected_line - 1)
2199 wstandend(view->window);
2200 if (++nprinted == 1)
2201 *first_displayed_line = lineno;
2202 free(line);
2203 free(wline);
2204 wline = NULL;
2206 *last_displayed_line = lineno;
2208 view_vborder(view);
2210 return NULL;
2213 static const struct got_error *
2214 blame_cb(void *arg, int nlines, int lineno, struct got_object_id *id)
2216 const struct got_error *err = NULL;
2217 struct tog_blame_cb_args *a = arg;
2218 struct tog_blame_line *line;
2219 int errcode;
2221 if (nlines != a->nlines ||
2222 (lineno != -1 && lineno < 1) || lineno > a->nlines)
2223 return got_error(GOT_ERR_RANGE);
2225 errcode = pthread_mutex_lock(&tog_mutex);
2226 if (errcode)
2227 return got_error_set_errno(errcode);
2229 if (*a->quit) { /* user has quit the blame view */
2230 err = got_error(GOT_ERR_ITER_COMPLETED);
2231 goto done;
2234 if (lineno == -1)
2235 goto done; /* no change in this commit */
2237 line = &a->lines[lineno - 1];
2238 if (line->annotated)
2239 goto done;
2241 line->id = got_object_id_dup(id);
2242 if (line->id == NULL) {
2243 err = got_error_from_errno();
2244 goto done;
2246 line->annotated = 1;
2247 done:
2248 errcode = pthread_mutex_unlock(&tog_mutex);
2249 if (errcode)
2250 err = got_error_set_errno(errcode);
2251 return err;
2254 static void *
2255 blame_thread(void *arg)
2257 const struct got_error *err;
2258 struct tog_blame_thread_args *ta = arg;
2259 struct tog_blame_cb_args *a = ta->cb_args;
2260 int errcode;
2262 err = got_blame_incremental(ta->path, a->commit_id, ta->repo,
2263 blame_cb, ta->cb_args);
2265 errcode = pthread_mutex_lock(&tog_mutex);
2266 if (errcode)
2267 return (void *)got_error_set_errno(errcode);
2269 got_repo_close(ta->repo);
2270 ta->repo = NULL;
2271 *ta->complete = 1;
2273 errcode = pthread_mutex_unlock(&tog_mutex);
2274 if (errcode && err == NULL)
2275 err = got_error_set_errno(errcode);
2277 return (void *)err;
2280 static struct got_object_id *
2281 get_selected_commit_id(struct tog_blame_line *lines,
2282 int first_displayed_line, int selected_line)
2284 struct tog_blame_line *line;
2286 line = &lines[first_displayed_line - 1 + selected_line - 1];
2287 if (!line->annotated)
2288 return NULL;
2290 return line->id;
2293 static const struct got_error *
2294 open_selected_commit(struct got_object **pobj, struct got_object **obj,
2295 struct tog_blame_line *lines, int first_displayed_line,
2296 int selected_line, struct got_repository *repo)
2298 const struct got_error *err = NULL;
2299 struct got_commit_object *commit = NULL;
2300 struct got_object_id *selected_id;
2301 struct got_object_qid *pid;
2303 *pobj = NULL;
2304 *obj = NULL;
2306 selected_id = get_selected_commit_id(lines,
2307 first_displayed_line, selected_line);
2308 if (selected_id == NULL)
2309 return NULL;
2311 err = got_object_open(obj, repo, selected_id);
2312 if (err)
2313 goto done;
2315 err = got_object_commit_open(&commit, repo, *obj);
2316 if (err)
2317 goto done;
2319 pid = SIMPLEQ_FIRST(&commit->parent_ids);
2320 if (pid) {
2321 err = got_object_open(pobj, repo, pid->id);
2322 if (err)
2323 goto done;
2325 done:
2326 if (commit)
2327 got_object_commit_close(commit);
2328 return err;
2331 static const struct got_error *
2332 stop_blame(struct tog_blame *blame)
2334 const struct got_error *err = NULL;
2335 int i;
2337 if (blame->thread) {
2338 int errcode;
2339 errcode = pthread_mutex_unlock(&tog_mutex);
2340 if (errcode)
2341 return got_error_set_errno(errcode);
2342 errcode = pthread_join(blame->thread, (void **)&err);
2343 if (errcode)
2344 return got_error_set_errno(errcode);
2345 errcode = pthread_mutex_lock(&tog_mutex);
2346 if (errcode)
2347 return got_error_set_errno(errcode);
2348 if (err && err->code == GOT_ERR_ITER_COMPLETED)
2349 err = NULL;
2350 blame->thread = NULL;
2352 if (blame->thread_args.repo) {
2353 got_repo_close(blame->thread_args.repo);
2354 blame->thread_args.repo = NULL;
2356 if (blame->f) {
2357 fclose(blame->f);
2358 blame->f = NULL;
2360 for (i = 0; i < blame->nlines; i++)
2361 free(blame->lines[i].id);
2362 free(blame->lines);
2363 blame->lines = NULL;
2364 free(blame->cb_args.commit_id);
2365 blame->cb_args.commit_id = NULL;
2367 return err;
2370 static const struct got_error *
2371 run_blame(struct tog_blame *blame, struct tog_view *view, int *blame_complete,
2372 int *first_displayed_line, int *last_displayed_line, int *selected_line,
2373 int *done, int *eof, const char *path, struct got_object_id *commit_id,
2374 struct got_repository *repo)
2376 const struct got_error *err = NULL;
2377 struct got_blob_object *blob = NULL;
2378 struct got_repository *thread_repo = NULL;
2379 struct got_object_id *obj_id = NULL;
2380 struct got_object *obj = NULL;
2381 int errcode;
2383 err = got_object_id_by_path(&obj_id, repo, commit_id, path);
2384 if (err)
2385 goto done;
2387 err = got_object_open(&obj, repo, obj_id);
2388 if (err)
2389 goto done;
2391 if (got_object_get_type(obj) != GOT_OBJ_TYPE_BLOB) {
2392 err = got_error(GOT_ERR_OBJ_TYPE);
2393 goto done;
2396 err = got_object_blob_open(&blob, repo, obj, 8192);
2397 if (err)
2398 goto done;
2399 blame->f = got_opentemp();
2400 if (blame->f == NULL) {
2401 err = got_error_from_errno();
2402 goto done;
2404 err = got_object_blob_dump_to_file(&blame->filesize, &blame->nlines,
2405 blame->f, blob);
2406 if (err)
2407 goto done;
2409 blame->lines = calloc(blame->nlines, sizeof(*blame->lines));
2410 if (blame->lines == NULL) {
2411 err = got_error_from_errno();
2412 goto done;
2415 err = got_repo_open(&thread_repo, got_repo_get_path(repo));
2416 if (err)
2417 goto done;
2419 blame->cb_args.view = view;
2420 blame->cb_args.lines = blame->lines;
2421 blame->cb_args.nlines = blame->nlines;
2422 blame->cb_args.commit_id = got_object_id_dup(commit_id);
2423 if (blame->cb_args.commit_id == NULL) {
2424 err = got_error_from_errno();
2425 goto done;
2427 blame->cb_args.quit = done;
2429 blame->thread_args.path = path;
2430 blame->thread_args.repo = thread_repo;
2431 blame->thread_args.cb_args = &blame->cb_args;
2432 blame->thread_args.complete = blame_complete;
2433 *blame_complete = 0;
2435 errcode = pthread_create(&blame->thread, NULL, blame_thread,
2436 &blame->thread_args);
2437 if (errcode) {
2438 err = got_error_set_errno(errcode);
2439 goto done;
2442 done:
2443 if (blob)
2444 got_object_blob_close(blob);
2445 free(obj_id);
2446 if (obj)
2447 got_object_close(obj);
2448 if (err)
2449 stop_blame(blame);
2450 return err;
2453 static const struct got_error *
2454 open_blame_view(struct tog_view *view, char *path,
2455 struct got_object_id *commit_id, struct got_repository *repo)
2457 const struct got_error *err = NULL;
2458 struct tog_blame_view_state *s = &view->state.blame;
2460 SIMPLEQ_INIT(&s->blamed_commits);
2462 err = got_object_qid_alloc(&s->blamed_commit, commit_id);
2463 if (err)
2464 return err;
2466 SIMPLEQ_INSERT_HEAD(&s->blamed_commits, s->blamed_commit, entry);
2467 s->first_displayed_line = 1;
2468 s->last_displayed_line = view->nlines;
2469 s->selected_line = 1;
2470 s->blame_complete = 0;
2471 s->path = path;
2472 if (s->path == NULL)
2473 return got_error_from_errno();
2474 s->repo = repo;
2475 s->commit_id = commit_id;
2476 memset(&s->blame, 0, sizeof(s->blame));
2478 view->show = show_blame_view;
2479 view->input = input_blame_view;
2480 view->close = close_blame_view;
2482 return run_blame(&s->blame, view, &s->blame_complete,
2483 &s->first_displayed_line, &s->last_displayed_line,
2484 &s->selected_line, &s->done, &s->eof, s->path,
2485 s->blamed_commit->id, s->repo);
2488 static const struct got_error *
2489 close_blame_view(struct tog_view *view)
2491 const struct got_error *err = NULL;
2492 struct tog_blame_view_state *s = &view->state.blame;
2494 if (s->blame.thread)
2495 err = stop_blame(&s->blame);
2497 while (!SIMPLEQ_EMPTY(&s->blamed_commits)) {
2498 struct got_object_qid *blamed_commit;
2499 blamed_commit = SIMPLEQ_FIRST(&s->blamed_commits);
2500 SIMPLEQ_REMOVE_HEAD(&s->blamed_commits, entry);
2501 got_object_qid_free(blamed_commit);
2504 free(s->path);
2506 return err;
2509 static const struct got_error *
2510 show_blame_view(struct tog_view *view)
2512 const struct got_error *err = NULL;
2513 struct tog_blame_view_state *s = &view->state.blame;
2515 err = draw_blame(view, s->blamed_commit->id, s->blame.f,
2516 s->path, s->blame.lines, s->blame.nlines, s->blame_complete,
2517 s->selected_line, &s->first_displayed_line,
2518 &s->last_displayed_line, &s->eof, view->nlines);
2520 view_vborder(view);
2521 return err;
2524 static const struct got_error *
2525 input_blame_view(struct tog_view **new_view, struct tog_view **dead_view,
2526 struct tog_view **focus_view, struct tog_view *view, int ch)
2528 const struct got_error *err = NULL, *thread_err = NULL;
2529 struct got_object *obj = NULL, *pobj = NULL;
2530 struct tog_view *diff_view;
2531 struct tog_blame_view_state *s = &view->state.blame;
2532 int begin_x = 0;
2534 switch (ch) {
2535 case 'q':
2536 s->done = 1;
2537 break;
2538 case 'k':
2539 case KEY_UP:
2540 if (s->selected_line > 1)
2541 s->selected_line--;
2542 else if (s->selected_line == 1 &&
2543 s->first_displayed_line > 1)
2544 s->first_displayed_line--;
2545 break;
2546 case KEY_PPAGE:
2547 if (s->first_displayed_line == 1) {
2548 s->selected_line = 1;
2549 break;
2551 if (s->first_displayed_line > view->nlines - 2)
2552 s->first_displayed_line -=
2553 (view->nlines - 2);
2554 else
2555 s->first_displayed_line = 1;
2556 break;
2557 case 'j':
2558 case KEY_DOWN:
2559 if (s->selected_line < view->nlines - 2 &&
2560 s->first_displayed_line +
2561 s->selected_line <= s->blame.nlines)
2562 s->selected_line++;
2563 else if (s->last_displayed_line <
2564 s->blame.nlines)
2565 s->first_displayed_line++;
2566 break;
2567 case 'b':
2568 case 'p': {
2569 struct got_object_id *id;
2570 id = get_selected_commit_id(s->blame.lines,
2571 s->first_displayed_line, s->selected_line);
2572 if (id == NULL || got_object_id_cmp(id,
2573 s->blamed_commit->id) == 0)
2574 break;
2575 err = open_selected_commit(&pobj, &obj,
2576 s->blame.lines, s->first_displayed_line,
2577 s->selected_line, s->repo);
2578 if (err)
2579 break;
2580 if (pobj == NULL && obj == NULL)
2581 break;
2582 if (ch == 'p' && pobj == NULL)
2583 break;
2584 s->done = 1;
2585 thread_err = stop_blame(&s->blame);
2586 s->done = 0;
2587 if (thread_err)
2588 break;
2589 id = got_object_get_id(ch == 'b' ? obj : pobj);
2590 got_object_close(obj);
2591 obj = NULL;
2592 if (pobj) {
2593 got_object_close(pobj);
2594 pobj = NULL;
2596 err = got_object_qid_alloc(&s->blamed_commit, id);
2597 if (err)
2598 goto done;
2599 SIMPLEQ_INSERT_HEAD(&s->blamed_commits,
2600 s->blamed_commit, entry);
2601 err = run_blame(&s->blame, view, &s->blame_complete,
2602 &s->first_displayed_line, &s->last_displayed_line,
2603 &s->selected_line, &s->done, &s->eof,
2604 s->path, s->blamed_commit->id, s->repo);
2605 if (err)
2606 break;
2607 break;
2609 case 'B': {
2610 struct got_object_qid *first;
2611 first = SIMPLEQ_FIRST(&s->blamed_commits);
2612 if (!got_object_id_cmp(first->id, s->commit_id))
2613 break;
2614 s->done = 1;
2615 thread_err = stop_blame(&s->blame);
2616 s->done = 0;
2617 if (thread_err)
2618 break;
2619 SIMPLEQ_REMOVE_HEAD(&s->blamed_commits, entry);
2620 got_object_qid_free(s->blamed_commit);
2621 s->blamed_commit =
2622 SIMPLEQ_FIRST(&s->blamed_commits);
2623 err = run_blame(&s->blame, view, &s->blame_complete,
2624 &s->first_displayed_line, &s->last_displayed_line,
2625 &s->selected_line, &s->done, &s->eof, s->path,
2626 s->blamed_commit->id, s->repo);
2627 if (err)
2628 break;
2629 break;
2631 case KEY_ENTER:
2632 case '\r':
2633 err = open_selected_commit(&pobj, &obj,
2634 s->blame.lines, s->first_displayed_line,
2635 s->selected_line, s->repo);
2636 if (err)
2637 break;
2638 if (pobj == NULL && obj == NULL)
2639 break;
2641 if (view_is_parent_view(view))
2642 begin_x = view_split_begin_x(view->begin_x);
2643 diff_view = view_open(0, 0, 0, begin_x, TOG_VIEW_DIFF);
2644 if (diff_view == NULL) {
2645 err = got_error_from_errno();
2646 break;
2648 err = open_diff_view(diff_view, pobj, obj, s->repo);
2649 if (err) {
2650 view_close(diff_view);
2651 break;
2653 if (view_is_parent_view(view)) {
2654 err = view_close_child(view);
2655 if (err)
2656 return err;
2657 err = view_set_child(view, diff_view);
2658 if (err) {
2659 view_close(diff_view);
2660 break;
2662 if (!view_is_splitscreen(diff_view)) {
2663 *focus_view = diff_view;
2664 view->child_focussed = 1;
2666 } else
2667 *new_view = diff_view;
2668 if (pobj) {
2669 got_object_close(pobj);
2670 pobj = NULL;
2672 got_object_close(obj);
2673 obj = NULL;
2674 if (err)
2675 break;
2676 break;
2677 case KEY_NPAGE:
2678 case ' ':
2679 if (s->last_displayed_line >= s->blame.nlines &&
2680 s->selected_line < view->nlines - 2) {
2681 s->selected_line = MIN(s->blame.nlines,
2682 view->nlines - 2);
2683 break;
2685 if (s->last_displayed_line + view->nlines - 2
2686 <= s->blame.nlines)
2687 s->first_displayed_line +=
2688 view->nlines - 2;
2689 else
2690 s->first_displayed_line =
2691 s->blame.nlines -
2692 (view->nlines - 3);
2693 break;
2694 case KEY_RESIZE:
2695 if (s->selected_line > view->nlines - 2) {
2696 s->selected_line = MIN(s->blame.nlines,
2697 view->nlines - 2);
2699 break;
2700 default:
2701 break;
2703 done:
2704 if (pobj)
2705 got_object_close(pobj);
2706 return thread_err ? thread_err : err;
2709 static const struct got_error *
2710 cmd_blame(int argc, char *argv[])
2712 const struct got_error *error;
2713 struct got_repository *repo = NULL;
2714 char *path, *cwd = NULL, *repo_path = NULL, *in_repo_path = NULL;
2715 struct got_object_id *commit_id = NULL;
2716 char *commit_id_str = NULL;
2717 int ch;
2718 struct tog_view *view;
2720 #ifndef PROFILE
2721 if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd", NULL)
2722 == -1)
2723 err(1, "pledge");
2724 #endif
2726 while ((ch = getopt(argc, argv, "c:r:")) != -1) {
2727 switch (ch) {
2728 case 'c':
2729 commit_id_str = optarg;
2730 break;
2731 case 'r':
2732 repo_path = realpath(optarg, NULL);
2733 if (repo_path == NULL)
2734 err(1, "-r option");
2735 break;
2736 default:
2737 usage();
2738 /* NOTREACHED */
2742 argc -= optind;
2743 argv += optind;
2745 if (argc == 1)
2746 path = argv[0];
2747 else
2748 usage_blame();
2750 cwd = getcwd(NULL, 0);
2751 if (cwd == NULL) {
2752 error = got_error_from_errno();
2753 goto done;
2755 if (repo_path == NULL) {
2756 repo_path = strdup(cwd);
2757 if (repo_path == NULL) {
2758 error = got_error_from_errno();
2759 goto done;
2764 error = got_repo_open(&repo, repo_path);
2765 if (error != NULL)
2766 return error;
2768 error = got_repo_map_path(&in_repo_path, repo, path, 1);
2769 if (error != NULL)
2770 goto done;
2772 if (commit_id_str == NULL) {
2773 struct got_reference *head_ref;
2774 error = got_ref_open(&head_ref, repo, GOT_REF_HEAD);
2775 if (error != NULL)
2776 goto done;
2777 error = got_ref_resolve(&commit_id, repo, head_ref);
2778 got_ref_close(head_ref);
2779 } else {
2780 struct got_object *obj;
2781 error = got_object_open_by_id_str(&obj, repo, commit_id_str);
2782 if (error != NULL)
2783 goto done;
2784 commit_id = got_object_id_dup(got_object_get_id(obj));
2785 if (commit_id == NULL)
2786 error = got_error_from_errno();
2787 got_object_close(obj);
2789 if (error != NULL)
2790 goto done;
2792 view = view_open(0, 0, 0, 0, TOG_VIEW_BLAME);
2793 if (view == NULL) {
2794 error = got_error_from_errno();
2795 goto done;
2797 error = open_blame_view(view, in_repo_path, commit_id, repo);
2798 if (error)
2799 goto done;
2800 error = view_loop(view);
2801 done:
2802 free(repo_path);
2803 free(cwd);
2804 free(commit_id);
2805 if (repo)
2806 got_repo_close(repo);
2807 return error;
2810 static const struct got_error *
2811 draw_tree_entries(struct tog_view *view,
2812 struct got_tree_entry **first_displayed_entry,
2813 struct got_tree_entry **last_displayed_entry,
2814 struct got_tree_entry **selected_entry, int *ndisplayed,
2815 const char *label, int show_ids, const char *parent_path,
2816 const struct got_tree_entries *entries, int selected, int limit, int isroot)
2818 const struct got_error *err = NULL;
2819 struct got_tree_entry *te;
2820 wchar_t *wline;
2821 int width, n;
2823 *ndisplayed = 0;
2825 werase(view->window);
2827 if (limit == 0)
2828 return NULL;
2830 err = format_line(&wline, &width, label, view->ncols);
2831 if (err)
2832 return err;
2833 if (view_needs_focus_indication(view))
2834 wstandout(view->window);
2835 waddwstr(view->window, wline);
2836 if (view_needs_focus_indication(view))
2837 wstandend(view->window);
2838 free(wline);
2839 wline = NULL;
2840 if (width < view->ncols)
2841 waddch(view->window, '\n');
2842 if (--limit <= 0)
2843 return NULL;
2844 err = format_line(&wline, &width, parent_path, view->ncols);
2845 if (err)
2846 return err;
2847 waddwstr(view->window, wline);
2848 free(wline);
2849 wline = NULL;
2850 if (width < view->ncols)
2851 waddch(view->window, '\n');
2852 if (--limit <= 0)
2853 return NULL;
2854 waddch(view->window, '\n');
2855 if (--limit <= 0)
2856 return NULL;
2858 te = SIMPLEQ_FIRST(&entries->head);
2859 if (*first_displayed_entry == NULL) {
2860 if (selected == 0) {
2861 if (view->focussed)
2862 wstandout(view->window);
2863 *selected_entry = NULL;
2865 waddstr(view->window, " ..\n"); /* parent directory */
2866 if (selected == 0 && view->focussed)
2867 wstandend(view->window);
2868 (*ndisplayed)++;
2869 if (--limit <= 0)
2870 return NULL;
2871 n = 1;
2872 } else {
2873 n = 0;
2874 while (te != *first_displayed_entry)
2875 te = SIMPLEQ_NEXT(te, entry);
2878 while (te) {
2879 char *line = NULL, *id_str = NULL;
2881 if (show_ids) {
2882 err = got_object_id_str(&id_str, te->id);
2883 if (err)
2884 return got_error_from_errno();
2886 if (asprintf(&line, "%s %s%s", id_str ? id_str : "",
2887 te->name, S_ISDIR(te->mode) ? "/" : "") == -1) {
2888 free(id_str);
2889 return got_error_from_errno();
2891 free(id_str);
2892 err = format_line(&wline, &width, line, view->ncols);
2893 if (err) {
2894 free(line);
2895 break;
2897 if (n == selected) {
2898 if (view->focussed)
2899 wstandout(view->window);
2900 *selected_entry = te;
2902 waddwstr(view->window, wline);
2903 if (width < view->ncols)
2904 waddch(view->window, '\n');
2905 if (n == selected && view->focussed)
2906 wstandend(view->window);
2907 free(line);
2908 free(wline);
2909 wline = NULL;
2910 n++;
2911 (*ndisplayed)++;
2912 *last_displayed_entry = te;
2913 if (--limit <= 0)
2914 break;
2915 te = SIMPLEQ_NEXT(te, entry);
2918 return err;
2921 static void
2922 tree_scroll_up(struct got_tree_entry **first_displayed_entry, int maxscroll,
2923 const struct got_tree_entries *entries, int isroot)
2925 struct got_tree_entry *te, *prev;
2926 int i;
2928 if (*first_displayed_entry == NULL)
2929 return;
2931 te = SIMPLEQ_FIRST(&entries->head);
2932 if (*first_displayed_entry == te) {
2933 if (!isroot)
2934 *first_displayed_entry = NULL;
2935 return;
2938 /* XXX this is stupid... switch to TAILQ? */
2939 for (i = 0; i < maxscroll; i++) {
2940 while (te != *first_displayed_entry) {
2941 prev = te;
2942 te = SIMPLEQ_NEXT(te, entry);
2944 *first_displayed_entry = prev;
2945 te = SIMPLEQ_FIRST(&entries->head);
2947 if (!isroot && te == SIMPLEQ_FIRST(&entries->head) && i < maxscroll)
2948 *first_displayed_entry = NULL;
2951 static void
2952 tree_scroll_down(struct got_tree_entry **first_displayed_entry, int maxscroll,
2953 struct got_tree_entry *last_displayed_entry,
2954 const struct got_tree_entries *entries)
2956 struct got_tree_entry *next;
2957 int n = 0;
2959 if (SIMPLEQ_NEXT(last_displayed_entry, entry) == NULL)
2960 return;
2962 if (*first_displayed_entry)
2963 next = SIMPLEQ_NEXT(*first_displayed_entry, entry);
2964 else
2965 next = SIMPLEQ_FIRST(&entries->head);
2966 while (next) {
2967 *first_displayed_entry = next;
2968 if (++n >= maxscroll)
2969 break;
2970 next = SIMPLEQ_NEXT(next, entry);
2974 static const struct got_error *
2975 tree_entry_path(char **path, struct tog_parent_trees *parents,
2976 struct got_tree_entry *te)
2978 const struct got_error *err = NULL;
2979 struct tog_parent_tree *pt;
2980 size_t len = 2; /* for leading slash and NUL */
2982 TAILQ_FOREACH(pt, parents, entry)
2983 len += strlen(pt->selected_entry->name) + 1 /* slash */;
2984 if (te)
2985 len += strlen(te->name);
2987 *path = calloc(1, len);
2988 if (path == NULL)
2989 return got_error_from_errno();
2991 (*path)[0] = '/';
2992 pt = TAILQ_LAST(parents, tog_parent_trees);
2993 while (pt) {
2994 if (strlcat(*path, pt->selected_entry->name, len) >= len) {
2995 err = got_error(GOT_ERR_NO_SPACE);
2996 goto done;
2998 if (strlcat(*path, "/", len) >= len) {
2999 err = got_error(GOT_ERR_NO_SPACE);
3000 goto done;
3002 pt = TAILQ_PREV(pt, tog_parent_trees, entry);
3004 if (te) {
3005 if (strlcat(*path, te->name, len) >= len) {
3006 err = got_error(GOT_ERR_NO_SPACE);
3007 goto done;
3010 done:
3011 if (err) {
3012 free(*path);
3013 *path = NULL;
3015 return err;
3018 static const struct got_error *
3019 blame_tree_entry(struct tog_view **new_view, int begin_x,
3020 struct got_tree_entry *te, struct tog_parent_trees *parents,
3021 struct got_object_id *commit_id, struct got_repository *repo)
3023 const struct got_error *err = NULL;
3024 char *path;
3025 struct tog_view *blame_view;
3027 err = tree_entry_path(&path, parents, te);
3028 if (err)
3029 return err;
3031 blame_view = view_open(0, 0, 0, begin_x, TOG_VIEW_BLAME);
3032 if (blame_view == NULL)
3033 return got_error_from_errno();
3035 err = open_blame_view(blame_view, path, commit_id, repo);
3036 if (err) {
3037 view_close(blame_view);
3038 free(path);
3039 } else
3040 *new_view = blame_view;
3041 return err;
3044 static const struct got_error *
3045 log_tree_entry(struct tog_view **new_view, int begin_x,
3046 struct got_tree_entry *te, struct tog_parent_trees *parents,
3047 struct got_object_id *commit_id, struct got_repository *repo)
3049 struct tog_view *log_view;
3050 const struct got_error *err = NULL;
3051 char *path;
3053 log_view = view_open(0, 0, 0, begin_x, TOG_VIEW_LOG);
3054 if (log_view == NULL)
3055 return got_error_from_errno();
3057 err = tree_entry_path(&path, parents, te);
3058 if (err)
3059 return err;
3061 err = open_log_view(log_view, commit_id, repo, path, 0);
3062 if (err)
3063 view_close(log_view);
3064 else
3065 *new_view = log_view;
3066 free(path);
3067 return err;
3070 static const struct got_error *
3071 open_tree_view(struct tog_view *view, struct got_tree_object *root,
3072 struct got_object_id *commit_id, struct got_repository *repo)
3074 const struct got_error *err = NULL;
3075 char *commit_id_str = NULL;
3076 struct tog_tree_view_state *s = &view->state.tree;
3078 TAILQ_INIT(&s->parents);
3080 err = got_object_id_str(&commit_id_str, commit_id);
3081 if (err != NULL)
3082 goto done;
3084 if (asprintf(&s->tree_label, "commit: %s", commit_id_str) == -1) {
3085 err = got_error_from_errno();
3086 goto done;
3089 s->root = s->tree = root;
3090 s->entries = got_object_tree_get_entries(root);
3091 s->first_displayed_entry = SIMPLEQ_FIRST(&s->entries->head);
3092 s->commit_id = got_object_id_dup(commit_id);
3093 if (s->commit_id == NULL) {
3094 err = got_error_from_errno();
3095 goto done;
3097 s->repo = repo;
3099 view->show = show_tree_view;
3100 view->input = input_tree_view;
3101 view->close = close_tree_view;
3102 done:
3103 free(commit_id_str);
3104 if (err) {
3105 free(s->tree_label);
3106 s->tree_label = NULL;
3108 return err;
3111 static const struct got_error *
3112 close_tree_view(struct tog_view *view)
3114 struct tog_tree_view_state *s = &view->state.tree;
3116 free(s->tree_label);
3117 s->tree_label = NULL;
3118 free(s->commit_id);
3119 s->commit_id = NULL;
3120 while (!TAILQ_EMPTY(&s->parents)) {
3121 struct tog_parent_tree *parent;
3122 parent = TAILQ_FIRST(&s->parents);
3123 TAILQ_REMOVE(&s->parents, parent, entry);
3124 free(parent);
3127 if (s->tree != s->root)
3128 got_object_tree_close(s->tree);
3129 got_object_tree_close(s->root);
3131 return NULL;
3134 static const struct got_error *
3135 show_tree_view(struct tog_view *view)
3137 const struct got_error *err = NULL;
3138 struct tog_tree_view_state *s = &view->state.tree;
3139 char *parent_path;
3141 err = tree_entry_path(&parent_path, &s->parents, NULL);
3142 if (err)
3143 return err;
3145 err = draw_tree_entries(view, &s->first_displayed_entry,
3146 &s->last_displayed_entry, &s->selected_entry,
3147 &s->ndisplayed, s->tree_label, s->show_ids, parent_path,
3148 s->entries, s->selected, view->nlines, s->tree == s->root);
3149 free(parent_path);
3151 view_vborder(view);
3152 return err;
3155 static const struct got_error *
3156 input_tree_view(struct tog_view **new_view, struct tog_view **dead_view,
3157 struct tog_view **focus_view, struct tog_view *view, int ch)
3159 const struct got_error *err = NULL;
3160 struct tog_tree_view_state *s = &view->state.tree;
3161 struct tog_view *log_view;
3162 int begin_x = 0;
3164 switch (ch) {
3165 case 'i':
3166 s->show_ids = !s->show_ids;
3167 break;
3168 case 'l':
3169 if (!s->selected_entry)
3170 break;
3171 if (view_is_parent_view(view))
3172 begin_x = view_split_begin_x(view->begin_x);
3173 err = log_tree_entry(&log_view, begin_x,
3174 s->selected_entry, &s->parents,
3175 s->commit_id, s->repo);
3176 if (view_is_parent_view(view)) {
3177 err = view_close_child(view);
3178 if (err)
3179 return err;
3180 err = view_set_child(view, log_view);
3181 if (err) {
3182 view_close(log_view);
3183 break;
3185 *focus_view = log_view;
3186 view->child_focussed = 1;
3187 } else
3188 *new_view = log_view;
3189 break;
3190 case 'k':
3191 case KEY_UP:
3192 if (s->selected > 0)
3193 s->selected--;
3194 if (s->selected > 0)
3195 break;
3196 tree_scroll_up(&s->first_displayed_entry, 1,
3197 s->entries, s->tree == s->root);
3198 break;
3199 case KEY_PPAGE:
3200 if (SIMPLEQ_FIRST(&s->entries->head) ==
3201 s->first_displayed_entry) {
3202 if (s->tree != s->root)
3203 s->first_displayed_entry = NULL;
3204 s->selected = 0;
3205 break;
3207 tree_scroll_up(&s->first_displayed_entry,
3208 view->nlines, s->entries,
3209 s->tree == s->root);
3210 break;
3211 case 'j':
3212 case KEY_DOWN:
3213 if (s->selected < s->ndisplayed - 1) {
3214 s->selected++;
3215 break;
3217 tree_scroll_down(&s->first_displayed_entry, 1,
3218 s->last_displayed_entry, s->entries);
3219 break;
3220 case KEY_NPAGE:
3221 tree_scroll_down(&s->first_displayed_entry,
3222 view->nlines, s->last_displayed_entry,
3223 s->entries);
3224 if (SIMPLEQ_NEXT(s->last_displayed_entry,
3225 entry))
3226 break;
3227 /* can't scroll any further; move cursor down */
3228 if (s->selected < s->ndisplayed - 1)
3229 s->selected = s->ndisplayed - 1;
3230 break;
3231 case KEY_ENTER:
3232 case '\r':
3233 if (s->selected_entry == NULL) {
3234 struct tog_parent_tree *parent;
3235 case KEY_BACKSPACE:
3236 /* user selected '..' */
3237 if (s->tree == s->root)
3238 break;
3239 parent = TAILQ_FIRST(&s->parents);
3240 TAILQ_REMOVE(&s->parents, parent,
3241 entry);
3242 got_object_tree_close(s->tree);
3243 s->tree = parent->tree;
3244 s->entries =
3245 got_object_tree_get_entries(s->tree);
3246 s->first_displayed_entry =
3247 parent->first_displayed_entry;
3248 s->selected_entry =
3249 parent->selected_entry;
3250 s->selected = parent->selected;
3251 free(parent);
3252 } else if (S_ISDIR(s->selected_entry->mode)) {
3253 struct tog_parent_tree *parent;
3254 struct got_tree_object *child;
3255 err = got_object_open_as_tree(&child,
3256 s->repo, s->selected_entry->id);
3257 if (err)
3258 break;
3259 parent = calloc(1, sizeof(*parent));
3260 if (parent == NULL) {
3261 err = got_error_from_errno();
3262 break;
3264 parent->tree = s->tree;
3265 parent->first_displayed_entry =
3266 s->first_displayed_entry;
3267 parent->selected_entry = s->selected_entry;
3268 parent->selected = s->selected;
3269 TAILQ_INSERT_HEAD(&s->parents, parent, entry);
3270 s->tree = child;
3271 s->entries =
3272 got_object_tree_get_entries(s->tree);
3273 s->selected = 0;
3274 s->first_displayed_entry = NULL;
3275 } else if (S_ISREG(s->selected_entry->mode)) {
3276 struct tog_view *blame_view;
3277 int begin_x = view_is_parent_view(view) ?
3278 view_split_begin_x(view->begin_x) : 0;
3280 err = blame_tree_entry(&blame_view, begin_x,
3281 s->selected_entry, &s->parents, s->commit_id,
3282 s->repo);
3283 if (err)
3284 break;
3285 if (view_is_parent_view(view)) {
3286 err = view_close_child(view);
3287 if (err)
3288 return err;
3289 err = view_set_child(view, blame_view);
3290 if (err) {
3291 view_close(blame_view);
3292 break;
3294 *focus_view = blame_view;
3295 view->child_focussed = 1;
3296 } else
3297 *new_view = blame_view;
3299 break;
3300 case KEY_RESIZE:
3301 if (s->selected > view->nlines)
3302 s->selected = s->ndisplayed - 1;
3303 break;
3304 default:
3305 break;
3308 return err;
3311 __dead static void
3312 usage_tree(void)
3314 endwin();
3315 fprintf(stderr, "usage: %s tree [-c commit] [repository-path]\n",
3316 getprogname());
3317 exit(1);
3320 static const struct got_error *
3321 cmd_tree(int argc, char *argv[])
3323 const struct got_error *error;
3324 struct got_repository *repo = NULL;
3325 char *repo_path = NULL;
3326 struct got_object_id *commit_id = NULL;
3327 char *commit_id_arg = NULL;
3328 struct got_commit_object *commit = NULL;
3329 struct got_tree_object *tree = NULL;
3330 int ch;
3331 struct tog_view *view;
3333 #ifndef PROFILE
3334 if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd", NULL)
3335 == -1)
3336 err(1, "pledge");
3337 #endif
3339 while ((ch = getopt(argc, argv, "c:")) != -1) {
3340 switch (ch) {
3341 case 'c':
3342 commit_id_arg = optarg;
3343 break;
3344 default:
3345 usage();
3346 /* NOTREACHED */
3350 argc -= optind;
3351 argv += optind;
3353 if (argc == 0) {
3354 repo_path = getcwd(NULL, 0);
3355 if (repo_path == NULL)
3356 return got_error_from_errno();
3357 } else if (argc == 1) {
3358 repo_path = realpath(argv[0], NULL);
3359 if (repo_path == NULL)
3360 return got_error_from_errno();
3361 } else
3362 usage_log();
3364 error = got_repo_open(&repo, repo_path);
3365 free(repo_path);
3366 if (error != NULL)
3367 return error;
3369 if (commit_id_arg == NULL) {
3370 error = get_head_commit_id(&commit_id, repo);
3371 if (error != NULL)
3372 goto done;
3373 } else {
3374 struct got_object *obj;
3375 error = got_object_open_by_id_str(&obj, repo, commit_id_arg);
3376 if (error == NULL) {
3377 commit_id = got_object_id_dup(got_object_get_id(obj));
3378 if (commit_id == NULL)
3379 error = got_error_from_errno();
3382 if (error != NULL)
3383 goto done;
3385 error = got_object_open_as_commit(&commit, repo, commit_id);
3386 if (error != NULL)
3387 goto done;
3389 error = got_object_open_as_tree(&tree, repo, commit->tree_id);
3390 if (error != NULL)
3391 goto done;
3393 view = view_open(0, 0, 0, 0, TOG_VIEW_TREE);
3394 if (view == NULL) {
3395 error = got_error_from_errno();
3396 goto done;
3398 error = open_tree_view(view, tree, commit_id, repo);
3399 if (error)
3400 goto done;
3401 error = view_loop(view);
3402 done:
3403 free(commit_id);
3404 if (commit)
3405 got_object_commit_close(commit);
3406 if (tree)
3407 got_object_tree_close(tree);
3408 if (repo)
3409 got_repo_close(repo);
3410 return error;
3413 static void
3414 init_curses(void)
3416 initscr();
3417 cbreak();
3418 halfdelay(1); /* Do fast refresh while initial view is loading. */
3419 noecho();
3420 nonl();
3421 intrflush(stdscr, FALSE);
3422 keypad(stdscr, TRUE);
3423 curs_set(0);
3424 signal(SIGWINCH, tog_sigwinch);
3427 __dead static void
3428 usage(void)
3430 int i;
3432 fprintf(stderr, "usage: %s [-h] [command] [arg ...]\n\n"
3433 "Available commands:\n", getprogname());
3434 for (i = 0; i < nitems(tog_commands); i++) {
3435 struct tog_cmd *cmd = &tog_commands[i];
3436 fprintf(stderr, " %s: %s\n", cmd->name, cmd->descr);
3438 exit(1);
3441 static char **
3442 make_argv(const char *arg0, const char *arg1)
3444 char **argv;
3445 int argc = (arg1 == NULL ? 1 : 2);
3447 argv = calloc(argc, sizeof(char *));
3448 if (argv == NULL)
3449 err(1, "calloc");
3450 argv[0] = strdup(arg0);
3451 if (argv[0] == NULL)
3452 err(1, "calloc");
3453 if (arg1) {
3454 argv[1] = strdup(arg1);
3455 if (argv[1] == NULL)
3456 err(1, "calloc");
3459 return argv;
3462 int
3463 main(int argc, char *argv[])
3465 const struct got_error *error = NULL;
3466 struct tog_cmd *cmd = NULL;
3467 int ch, hflag = 0;
3468 char **cmd_argv = NULL;
3470 setlocale(LC_ALL, "");
3472 while ((ch = getopt(argc, argv, "h")) != -1) {
3473 switch (ch) {
3474 case 'h':
3475 hflag = 1;
3476 break;
3477 default:
3478 usage();
3479 /* NOTREACHED */
3483 argc -= optind;
3484 argv += optind;
3485 optind = 0;
3486 optreset = 1;
3488 if (argc == 0) {
3489 if (hflag)
3490 usage();
3491 /* Build an argument vector which runs a default command. */
3492 cmd = &tog_commands[0];
3493 cmd_argv = make_argv(cmd->name, NULL);
3494 argc = 1;
3495 } else {
3496 int i;
3498 /* Did the user specific a command? */
3499 for (i = 0; i < nitems(tog_commands); i++) {
3500 if (strncmp(tog_commands[i].name, argv[0],
3501 strlen(argv[0])) == 0) {
3502 cmd = &tog_commands[i];
3503 if (hflag)
3504 tog_commands[i].cmd_usage();
3505 break;
3508 if (cmd == NULL) {
3509 /* Did the user specify a repository? */
3510 char *repo_path = realpath(argv[0], NULL);
3511 if (repo_path) {
3512 struct got_repository *repo;
3513 error = got_repo_open(&repo, repo_path);
3514 if (error == NULL)
3515 got_repo_close(repo);
3516 } else
3517 error = got_error_from_errno();
3518 if (error) {
3519 if (hflag) {
3520 fprintf(stderr, "%s: '%s' is not a "
3521 "known command\n", getprogname(),
3522 argv[0]);
3523 usage();
3525 fprintf(stderr, "%s: '%s' is neither a known "
3526 "command nor a path to a repository\n",
3527 getprogname(), argv[0]);
3528 free(repo_path);
3529 return 1;
3531 cmd = &tog_commands[0];
3532 cmd_argv = make_argv(cmd->name, repo_path);
3533 argc = 2;
3534 free(repo_path);
3538 init_curses();
3540 error = cmd->cmd_main(argc, cmd_argv ? cmd_argv : argv);
3541 if (error)
3542 goto done;
3543 done:
3544 endwin();
3545 free(cmd_argv);
3546 if (error)
3547 fprintf(stderr, "%s: %s\n", getprogname(), error->msg);
3548 return 0;