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>
20 #include <errno.h>
21 #define _XOPEN_SOURCE_EXTENDED
22 #include <curses.h>
23 #undef _XOPEN_SOURCE_EXTENDED
24 #include <panel.h>
25 #include <locale.h>
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <getopt.h>
29 #include <string.h>
30 #include <err.h>
31 #include <unistd.h>
32 #include <util.h>
33 #include <limits.h>
34 #include <wchar.h>
35 #include <time.h>
36 #include <pthread.h>
37 #include <libgen.h>
39 #include "got_error.h"
40 #include "got_object.h"
41 #include "got_reference.h"
42 #include "got_repository.h"
43 #include "got_diff.h"
44 #include "got_opentemp.h"
45 #include "got_commit_graph.h"
46 #include "got_utf8.h"
47 #include "got_blame.h"
49 #ifndef MIN
50 #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b))
51 #endif
53 #ifndef nitems
54 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
55 #endif
57 struct tog_cmd {
58 const char *name;
59 const struct got_error *(*cmd_main)(int, char *[]);
60 void (*cmd_usage)(void);
61 const char *descr;
62 };
64 __dead static void usage(void);
65 __dead static void usage_log(void);
66 __dead static void usage_diff(void);
67 __dead static void usage_blame(void);
68 __dead static void usage_tree(void);
70 static const struct got_error* cmd_log(int, char *[]);
71 static const struct got_error* cmd_diff(int, char *[]);
72 static const struct got_error* cmd_blame(int, char *[]);
73 static const struct got_error* cmd_tree(int, char *[]);
75 static struct tog_cmd tog_commands[] = {
76 { "log", cmd_log, usage_log,
77 "show repository history" },
78 { "diff", cmd_diff, usage_diff,
79 "compare files and directories" },
80 { "blame", cmd_blame, usage_blame,
81 "show line-by-line file history" },
82 { "tree", cmd_tree, usage_tree,
83 "browse trees in repository" },
84 };
86 enum tog_view_type {
87 TOG_VIEW_DIFF,
88 TOG_VIEW_LOG,
89 TOG_VIEW_BLAME,
90 TOG_VIEW_TREE
91 };
93 struct tog_diff_view_state {
94 struct got_object_id *id1, *id2;
95 FILE *f;
96 int first_displayed_line;
97 int last_displayed_line;
98 int eof;
99 };
101 struct commit_queue_entry {
102 TAILQ_ENTRY(commit_queue_entry) entry;
103 struct got_object_id *id;
104 struct got_commit_object *commit;
105 };
106 TAILQ_HEAD(commit_queue_head, commit_queue_entry);
107 struct commit_queue {
108 int ncommits;
109 struct commit_queue_head head;
110 };
112 struct tog_log_view_state {
113 struct got_commit_graph *graph;
114 struct commit_queue commits;
115 struct commit_queue_entry *first_displayed_entry;
116 struct commit_queue_entry *last_displayed_entry;
117 struct commit_queue_entry *selected_entry;
118 int selected;
119 char *in_repo_path;
120 struct got_repository *repo;
121 struct got_object_id *start_id;
122 };
124 struct tog_blame_cb_args {
125 pthread_mutex_t *mutex;
126 struct tog_blame_line *lines; /* one per line */
127 int nlines;
129 struct tog_view *view;
130 struct got_object_id *commit_id;
131 FILE *f;
132 const char *path;
133 int *first_displayed_line;
134 int *last_displayed_line;
135 int *selected_line;
136 int *quit;
137 int *eof;
138 };
140 struct tog_blame_thread_args {
141 const char *path;
142 struct got_repository *repo;
143 struct tog_blame_cb_args *cb_args;
144 int *complete;
145 };
147 struct tog_blame {
148 FILE *f;
149 size_t filesize;
150 struct tog_blame_line *lines;
151 size_t nlines;
152 pthread_t thread;
153 struct tog_blame_thread_args thread_args;
154 struct tog_blame_cb_args cb_args;
155 const char *path;
156 };
158 struct tog_blame_view_state {
159 int first_displayed_line;
160 int last_displayed_line;
161 int selected_line;
162 int blame_complete;
163 int eof;
164 int done;
165 pthread_mutex_t mutex;
166 struct got_object_id_queue blamed_commits;
167 struct got_object_qid *blamed_commit;
168 char *path;
169 struct got_repository *repo;
170 struct got_object_id *commit_id;
171 struct tog_blame blame;
172 };
174 struct tog_parent_tree {
175 TAILQ_ENTRY(tog_parent_tree) entry;
176 struct got_tree_object *tree;
177 struct got_tree_entry *first_displayed_entry;
178 struct got_tree_entry *selected_entry;
179 int selected;
180 };
182 TAILQ_HEAD(tog_parent_trees, tog_parent_tree);
184 struct tog_tree_view_state {
185 char *tree_label;
186 struct got_tree_object *root;
187 struct got_tree_object *tree;
188 const struct got_tree_entries *entries;
189 struct got_tree_entry *first_displayed_entry;
190 struct got_tree_entry *last_displayed_entry;
191 struct got_tree_entry *selected_entry;
192 int nentries, ndisplayed, selected, show_ids;
193 struct tog_parent_trees parents;
194 struct got_object_id *commit_id;
195 struct got_repository *repo;
196 };
198 /*
199 * We implement two types of views: parent views and child views.
201 * The 'Tab' key switches between a parent view and its child view.
202 * Child views are shown side-by-side to their parent view, provided
203 * there is enough screen estate.
205 * When a new view is opened from within a parent view, this new view
206 * becomes a child view of the parent view, replacing any existing child.
208 * When a new view is opened from within a child view, this new view
209 * becomes a parent view which will obscure the views below until the
210 * user quits the new parent view by typing 'q'.
212 * This list of views contains parent views only.
213 * Child views are only pointed to by their parent view.
214 */
215 TAILQ_HEAD(tog_view_list_head, tog_view);
217 struct tog_view {
218 TAILQ_ENTRY(tog_view) entry;
219 WINDOW *window;
220 PANEL *panel;
221 int nlines, ncols, begin_y, begin_x;
222 int lines, cols; /* copies of LINES and COLS */
223 int focussed;
224 struct tog_view *parent;
225 struct tog_view *child;
226 int child_focussed;
228 /* type-specific state */
229 enum tog_view_type type;
230 union {
231 struct tog_diff_view_state diff;
232 struct tog_log_view_state log;
233 struct tog_blame_view_state blame;
234 struct tog_tree_view_state tree;
235 } state;
237 const struct got_error *(*show)(struct tog_view *);
238 const struct got_error *(*input)(struct tog_view **,
239 struct tog_view **, struct tog_view**, struct tog_view *, int);
240 const struct got_error *(*close)(struct tog_view *);
241 };
243 static const struct got_error *open_diff_view(struct tog_view *,
244 struct got_object *, struct got_object *, struct got_repository *);
245 static const struct got_error *show_diff_view(struct tog_view *);
246 static const struct got_error *input_diff_view(struct tog_view **,
247 struct tog_view **, struct tog_view **, struct tog_view *, int);
248 static const struct got_error* close_diff_view(struct tog_view *);
250 static const struct got_error *open_log_view(struct tog_view *,
251 struct got_object_id *, struct got_repository *, const char *);
252 static const struct got_error * show_log_view(struct tog_view *);
253 static const struct got_error *input_log_view(struct tog_view **,
254 struct tog_view **, struct tog_view **, struct tog_view *, int);
255 static const struct got_error *close_log_view(struct tog_view *);
257 static const struct got_error *open_blame_view(struct tog_view *, char *,
258 struct got_object_id *, struct got_repository *);
259 static const struct got_error *show_blame_view(struct tog_view *);
260 static const struct got_error *input_blame_view(struct tog_view **,
261 struct tog_view **, struct tog_view **, struct tog_view *, int);
262 static const struct got_error *close_blame_view(struct tog_view *);
264 static const struct got_error *open_tree_view(struct tog_view *,
265 struct got_tree_object *, struct got_object_id *, struct got_repository *);
266 static const struct got_error *show_tree_view(struct tog_view *);
267 static const struct got_error *input_tree_view(struct tog_view **,
268 struct tog_view **, struct tog_view **, struct tog_view *, int);
269 static const struct got_error *close_tree_view(struct tog_view *);
271 static const struct got_error *
272 view_close(struct tog_view *view)
274 const struct got_error *err = NULL;
276 if (view->child) {
277 view_close(view->child);
278 view->child = NULL;
280 if (view->close)
281 err = view->close(view);
282 if (view->panel)
283 del_panel(view->panel);
284 if (view->window)
285 delwin(view->window);
286 free(view);
287 return err;
290 static struct tog_view *
291 view_open(int nlines, int ncols, int begin_y, int begin_x,
292 enum tog_view_type type)
294 struct tog_view *view = calloc(1, sizeof(*view));
296 if (view == NULL)
297 return NULL;
299 view->type = type;
300 view->lines = LINES;
301 view->cols = COLS;
302 view->nlines = nlines ? nlines : LINES - begin_y;
303 view->ncols = ncols ? ncols : COLS - begin_x;
304 view->begin_y = begin_y;
305 view->begin_x = begin_x;
306 view->window = newwin(nlines, ncols, begin_y, begin_x);
307 if (view->window == NULL) {
308 view_close(view);
309 return NULL;
311 view->panel = new_panel(view->window);
312 if (view->panel == NULL ||
313 set_panel_userptr(view->panel, view) != OK) {
314 view_close(view);
315 return NULL;
318 keypad(view->window, TRUE);
319 return view;
322 static int
323 view_split_begin_x(int begin_x)
325 if (begin_x > 0)
326 return 0;
327 return (COLS >= 120 ? COLS/2 : 0);
330 static const struct got_error *view_resize(struct tog_view *);
332 static const struct got_error *
333 view_splitscreen(struct tog_view *view)
335 const struct got_error *err = NULL;
337 view->begin_y = 0;
338 view->begin_x = view_split_begin_x(0);
339 view->nlines = LINES;
340 view->ncols = COLS - view->begin_x;
341 view->lines = LINES;
342 view->cols = COLS;
343 err = view_resize(view);
344 if (err)
345 return err;
347 if (mvwin(view->window, view->begin_y, view->begin_x) == ERR)
348 return got_error_from_errno();
350 return NULL;
353 static const struct got_error *
354 view_fullscreen(struct tog_view *view)
356 const struct got_error *err = NULL;
358 view->begin_x = 0;
359 view->begin_y = 0;
360 view->nlines = LINES;
361 view->ncols = COLS;
362 view->lines = LINES;
363 view->cols = COLS;
364 err = view_resize(view);
365 if (err)
366 return err;
368 if (mvwin(view->window, view->begin_y, view->begin_x) == ERR)
369 return got_error_from_errno();
371 return NULL;
374 static int
375 view_is_parent_view(struct tog_view *view)
377 return view->parent == NULL;
380 static const struct got_error *
381 view_resize(struct tog_view *view)
383 int nlines, ncols;
385 if (view->lines > LINES)
386 nlines = view->nlines - (view->lines - LINES);
387 else
388 nlines = view->nlines + (LINES - view->lines);
390 if (view->cols > COLS)
391 ncols = view->ncols - (view->cols - COLS);
392 else
393 ncols = view->ncols + (COLS - view->cols);
395 if (wresize(view->window, nlines, ncols) == ERR)
396 return got_error_from_errno();
397 replace_panel(view->panel, view->window);
399 view->nlines = nlines;
400 view->ncols = ncols;
401 view->lines = LINES;
402 view->cols = COLS;
404 if (view_is_parent_view(view)) {
405 view->child->begin_x = view_split_begin_x(view->begin_x);
406 if (view->child->begin_x == 0) {
407 view_fullscreen(view->child);
408 if (view->child->focussed)
409 show_panel(view->child->panel);
410 else
411 show_panel(view->panel);
412 } else {
413 view_splitscreen(view->child);
414 show_panel(view->child->panel);
418 return NULL;
421 static const struct got_error *
422 view_close_child(struct tog_view *view)
424 const struct got_error *err;
426 if (view->child == NULL)
427 return NULL;
429 err = view_close(view->child);
430 view->child = NULL;
431 return err;
434 static const struct got_error *
435 view_set_child(struct tog_view *view, struct tog_view *child)
437 const struct got_error *err = NULL;
439 view->child = child;
440 child->parent = view;
441 return err;
444 static int
445 view_is_splitscreen(struct tog_view *view)
447 return !view_is_parent_view(view) && view->begin_x > 0;
450 static const struct got_error *
451 view_input(struct tog_view **new, struct tog_view **dead,
452 struct tog_view **focus, int *done, struct tog_view *view,
453 struct tog_view_list_head *views)
455 const struct got_error *err = NULL;
456 struct tog_view *v;
457 int ch;
459 *new = NULL;
460 *dead = NULL;
461 *focus = NULL;
463 nodelay(stdscr, FALSE);
464 ch = wgetch(view->window);
465 nodelay(stdscr, TRUE);
466 switch (ch) {
467 case ERR:
468 break;
469 case '\t':
470 if (view->child) {
471 *focus = view->child;
472 view->child_focussed = 1;
473 } else if (view->parent) {
474 *focus = view->parent;
475 view->parent->child_focussed = 0;
477 break;
478 case 'q':
479 err = view->input(new, dead, focus, view, ch);
480 *dead = view;
481 break;
482 case 'Q':
483 *done = 1;
484 break;
485 case 'f':
486 if (view_is_parent_view(view)) {
487 if (view->child == NULL)
488 break;
489 if (view_is_splitscreen(view->child)) {
490 *focus = view->child;
491 view->child_focussed = 1;
492 err = view_fullscreen(view->child);
493 } else
494 err = view_splitscreen(view->child);
495 if (err)
496 break;
497 err = view->child->input(new, dead, focus,
498 view->child, KEY_RESIZE);
499 } else {
500 if (view_is_splitscreen(view)) {
501 *focus = view;
502 view->parent->child_focussed = 1;
503 err = view_fullscreen(view);
504 } else {
505 err = view_splitscreen(view);
507 if (err)
508 break;
509 err = view->input(new, dead, focus, view,
510 KEY_RESIZE);
512 break;
513 case KEY_RESIZE:
514 TAILQ_FOREACH(v, views, entry) {
515 err = view_resize(v);
516 if (err)
517 return err;
518 err = v->input(new, dead, focus, v, ch);
520 break;
521 default:
522 err = view->input(new, dead, focus, view, ch);
523 break;
526 return err;
529 void
530 view_vborder(struct tog_view *view)
532 PANEL *panel;
533 struct tog_view *view_above;
535 if (view->parent)
536 return view_vborder(view->parent);
538 panel = panel_above(view->panel);
539 if (panel == NULL)
540 return;
542 view_above = panel_userptr(panel);
543 mvwvline(view->window, view->begin_y, view_above->begin_x - 1,
544 got_locale_is_utf8() ? ACS_VLINE : '|', view->nlines);
547 int
548 view_needs_focus_indication(struct tog_view *view)
550 if (view_is_parent_view(view)) {
551 if (view->child == NULL || view->child_focussed)
552 return 0;
553 if (!view_is_splitscreen(view->child))
554 return 0;
555 } else if (!view_is_splitscreen(view))
556 return 0;
558 return view->focussed;
561 static const struct got_error *
562 view_loop(struct tog_view *view)
564 const struct got_error *err = NULL;
565 struct tog_view_list_head views;
566 struct tog_view *new_view, *dead_view, *focus_view, *main_view;
567 int done = 0;
569 TAILQ_INIT(&views);
570 TAILQ_INSERT_HEAD(&views, view, entry);
572 main_view = view;
573 view->focussed = 1;
574 err = view->show(view);
575 if (err)
576 return err;
577 update_panels();
578 doupdate();
579 while (!TAILQ_EMPTY(&views) && !done) {
580 err = view_input(&new_view, &dead_view, &focus_view, &done,
581 view, &views);
582 if (err)
583 break;
584 if (dead_view) {
585 struct tog_view *prev = NULL;
587 if (view_is_parent_view(dead_view))
588 prev = TAILQ_PREV(dead_view,
589 tog_view_list_head, entry);
590 else
591 prev = view->parent;
593 if (dead_view->parent)
594 dead_view->parent->child = NULL;
595 else
596 TAILQ_REMOVE(&views, dead_view, entry);
598 err = view_close(dead_view);
599 if (err || dead_view == main_view)
600 goto done;
602 if (view == dead_view) {
603 if (focus_view)
604 view = focus_view;
605 else if (prev)
606 view = prev;
607 else if (!TAILQ_EMPTY(&views))
608 view = TAILQ_LAST(&views,
609 tog_view_list_head);
610 else
611 view = NULL;
612 if (view) {
613 if (view->child && view->child_focussed)
614 focus_view = view->child;
615 else
616 focus_view = view;
620 if (new_view) {
621 struct tog_view *v, *t;
622 /* Only allow one parent view per type. */
623 TAILQ_FOREACH_SAFE(v, &views, entry, t) {
624 if (v->type != new_view->type)
625 continue;
626 TAILQ_REMOVE(&views, v, entry);
627 err = view_close(v);
628 if (err)
629 goto done;
630 if (v == view)
631 view = new_view;
632 break;
634 TAILQ_INSERT_TAIL(&views, new_view, entry);
635 if (focus_view == NULL)
636 focus_view = new_view;
638 if (focus_view) {
639 show_panel(focus_view->panel);
640 if (view)
641 view->focussed = 0;
642 focus_view->focussed = 1;
643 view = focus_view;
644 if (new_view)
645 show_panel(new_view->panel);
646 if (view->child && view_is_splitscreen(view->child))
647 show_panel(view->child->panel);
649 if (view) {
650 if (view->parent) {
651 err = view->parent->show(view->parent);
652 if (err)
653 return err;
655 err = view->show(view);
656 if (err)
657 return err;
658 if (view->child) {
659 err = view->child->show(view->child);
660 if (err)
661 return err;
664 update_panels();
665 doupdate();
667 done:
668 while (!TAILQ_EMPTY(&views)) {
669 view = TAILQ_FIRST(&views);
670 TAILQ_REMOVE(&views, view, entry);
671 view_close(view);
673 return err;
676 __dead static void
677 usage_log(void)
679 endwin();
680 fprintf(stderr,
681 "usage: %s log [-c commit] [-r repository-path] [path]\n",
682 getprogname());
683 exit(1);
686 /* Create newly allocated wide-character string equivalent to a byte string. */
687 static const struct got_error *
688 mbs2ws(wchar_t **ws, size_t *wlen, const char *s)
690 char *vis = NULL;
691 const struct got_error *err = NULL;
693 *ws = NULL;
694 *wlen = mbstowcs(NULL, s, 0);
695 if (*wlen == (size_t)-1) {
696 int vislen;
697 if (errno != EILSEQ)
698 return got_error_from_errno();
700 /* byte string invalid in current encoding; try to "fix" it */
701 err = got_mbsavis(&vis, &vislen, s);
702 if (err)
703 return err;
704 *wlen = mbstowcs(NULL, vis, 0);
705 if (*wlen == (size_t)-1) {
706 err = got_error_from_errno(); /* give up */
707 goto done;
711 *ws = calloc(*wlen + 1, sizeof(*ws));
712 if (*ws == NULL) {
713 err = got_error_from_errno();
714 goto done;
717 if (mbstowcs(*ws, vis ? vis : s, *wlen) != *wlen)
718 err = got_error_from_errno();
719 done:
720 free(vis);
721 if (err) {
722 free(*ws);
723 *ws = NULL;
724 *wlen = 0;
726 return err;
729 /* Format a line for display, ensuring that it won't overflow a width limit. */
730 static const struct got_error *
731 format_line(wchar_t **wlinep, int *widthp, const char *line, int wlimit)
733 const struct got_error *err = NULL;
734 int cols = 0;
735 wchar_t *wline = NULL;
736 size_t wlen;
737 int i;
739 *wlinep = NULL;
740 *widthp = 0;
742 err = mbs2ws(&wline, &wlen, line);
743 if (err)
744 return err;
746 i = 0;
747 while (i < wlen && cols < wlimit) {
748 int width = wcwidth(wline[i]);
749 switch (width) {
750 case 0:
751 i++;
752 break;
753 case 1:
754 case 2:
755 if (cols + width <= wlimit)
756 cols += width;
757 i++;
758 break;
759 case -1:
760 if (wline[i] == L'\t')
761 cols += TABSIZE - ((cols + 1) % TABSIZE);
762 i++;
763 break;
764 default:
765 err = got_error_from_errno();
766 goto done;
769 wline[i] = L'\0';
770 if (widthp)
771 *widthp = cols;
772 done:
773 if (err)
774 free(wline);
775 else
776 *wlinep = wline;
777 return err;
780 static const struct got_error *
781 draw_commit(struct tog_view *view, struct got_commit_object *commit,
782 struct got_object_id *id)
784 const struct got_error *err = NULL;
785 char datebuf[10]; /* YY-MM-DD + SPACE + NUL */
786 char *logmsg0 = NULL, *logmsg = NULL;
787 char *author0 = NULL, *author = NULL;
788 wchar_t *wlogmsg = NULL, *wauthor = NULL;
789 int author_width, logmsg_width;
790 char *newline, *smallerthan;
791 char *line = NULL;
792 int col, limit;
793 static const size_t date_display_cols = 9;
794 static const size_t author_display_cols = 16;
795 const int avail = view->ncols;
797 if (strftime(datebuf, sizeof(datebuf), "%g/%m/%d ",
798 &commit->tm_committer) >= sizeof(datebuf))
799 return got_error(GOT_ERR_NO_SPACE);
801 if (avail < date_display_cols)
802 limit = MIN(sizeof(datebuf) - 1, avail);
803 else
804 limit = MIN(date_display_cols, sizeof(datebuf) - 1);
805 waddnstr(view->window, datebuf, limit);
806 col = limit + 1;
807 if (col > avail)
808 goto done;
810 author0 = strdup(commit->author);
811 if (author0 == NULL) {
812 err = got_error_from_errno();
813 goto done;
815 author = author0;
816 smallerthan = strchr(author, '<');
817 if (smallerthan)
818 *smallerthan = '\0';
819 else {
820 char *at = strchr(author, '@');
821 if (at)
822 *at = '\0';
824 limit = avail - col;
825 err = format_line(&wauthor, &author_width, author, limit);
826 if (err)
827 goto done;
828 waddwstr(view->window, wauthor);
829 col += author_width;
830 while (col <= avail && author_width < author_display_cols + 1) {
831 waddch(view->window, ' ');
832 col++;
833 author_width++;
835 if (col > avail)
836 goto done;
838 logmsg0 = strdup(commit->logmsg);
839 if (logmsg0 == NULL) {
840 err = got_error_from_errno();
841 goto done;
843 logmsg = logmsg0;
844 while (*logmsg == '\n')
845 logmsg++;
846 newline = strchr(logmsg, '\n');
847 if (newline)
848 *newline = '\0';
849 limit = avail - col;
850 err = format_line(&wlogmsg, &logmsg_width, logmsg, limit);
851 if (err)
852 goto done;
853 waddwstr(view->window, wlogmsg);
854 col += logmsg_width;
855 while (col <= avail) {
856 waddch(view->window, ' ');
857 col++;
859 done:
860 free(logmsg0);
861 free(wlogmsg);
862 free(author0);
863 free(wauthor);
864 free(line);
865 return err;
868 static struct commit_queue_entry *
869 alloc_commit_queue_entry(struct got_commit_object *commit,
870 struct got_object_id *id)
872 struct commit_queue_entry *entry;
874 entry = calloc(1, sizeof(*entry));
875 if (entry == NULL)
876 return NULL;
878 entry->id = id;
879 entry->commit = commit;
880 return entry;
883 static void
884 pop_commit(struct commit_queue *commits)
886 struct commit_queue_entry *entry;
888 entry = TAILQ_FIRST(&commits->head);
889 TAILQ_REMOVE(&commits->head, entry, entry);
890 got_object_commit_close(entry->commit);
891 commits->ncommits--;
892 /* Don't free entry->id! It is owned by the commit graph. */
893 free(entry);
896 static void
897 free_commits(struct commit_queue *commits)
899 while (!TAILQ_EMPTY(&commits->head))
900 pop_commit(commits);
903 static const struct got_error *
904 queue_commits(struct got_commit_graph *graph, struct commit_queue *commits,
905 struct got_object_id *start_id, int minqueue,
906 struct got_repository *repo, const char *path)
908 const struct got_error *err = NULL;
909 int nqueued = 0;
911 if (start_id) {
912 err = got_commit_graph_iter_start(graph, start_id, repo);
913 if (err)
914 return err;
917 while (nqueued < minqueue) {
918 struct got_object_id *id;
919 struct got_commit_object *commit;
920 struct commit_queue_entry *entry;
922 err = got_commit_graph_iter_next(&id, graph);
923 if (err) {
924 if (err->code == GOT_ERR_ITER_COMPLETED) {
925 err = NULL;
926 break;
928 if (err->code != GOT_ERR_ITER_NEED_MORE)
929 break;
930 err = got_commit_graph_fetch_commits(graph,
931 minqueue, repo);
932 if (err)
933 return err;
934 continue;
937 if (id == NULL)
938 break;
940 err = got_object_open_as_commit(&commit, repo, id);
941 if (err)
942 break;
943 entry = alloc_commit_queue_entry(commit, id);
944 if (entry == NULL) {
945 err = got_error_from_errno();
946 break;
949 TAILQ_INSERT_TAIL(&commits->head, entry, entry);
950 nqueued++;
951 commits->ncommits++;
954 return err;
957 static const struct got_error *
958 fetch_next_commit(struct commit_queue_entry **pentry,
959 struct commit_queue_entry *entry, struct commit_queue *commits,
960 struct got_commit_graph *graph, struct got_repository *repo,
961 const char *path)
963 const struct got_error *err = NULL;
965 *pentry = NULL;
967 err = queue_commits(graph, commits, NULL, 1, repo, path);
968 if (err)
969 return err;
971 /* Next entry to display should now be available. */
972 *pentry = TAILQ_NEXT(entry, entry);
973 return NULL;
976 static const struct got_error *
977 get_head_commit_id(struct got_object_id **head_id, struct got_repository *repo)
979 const struct got_error *err = NULL;
980 struct got_reference *head_ref;
982 *head_id = NULL;
984 err = got_ref_open(&head_ref, repo, GOT_REF_HEAD);
985 if (err)
986 return err;
988 err = got_ref_resolve(head_id, repo, head_ref);
989 got_ref_close(head_ref);
990 if (err) {
991 *head_id = NULL;
992 return err;
995 return NULL;
998 static const struct got_error *
999 draw_commits(struct tog_view *view, struct commit_queue_entry **last,
1000 struct commit_queue_entry **selected, struct commit_queue_entry *first,
1001 struct commit_queue *commits, int selected_idx, int limit,
1002 struct got_commit_graph *graph, struct got_repository *repo,
1003 const char *path)
1005 const struct got_error *err = NULL;
1006 struct commit_queue_entry *entry;
1007 int ncommits, width;
1008 char *id_str, *header;
1009 wchar_t *wline;
1011 entry = first;
1012 ncommits = 0;
1013 while (entry) {
1014 if (ncommits == selected_idx) {
1015 *selected = entry;
1016 break;
1018 entry = TAILQ_NEXT(entry, entry);
1019 ncommits++;
1022 err = got_object_id_str(&id_str, (*selected)->id);
1023 if (err)
1024 return err;
1026 if (path && strcmp(path, "/") != 0) {
1027 if (asprintf(&header, "commit: %s [%s]", id_str, path) == -1) {
1028 err = got_error_from_errno();
1029 free(id_str);
1030 return err;
1032 } else if (asprintf(&header, "commit: %s", id_str) == -1) {
1033 err = got_error_from_errno();
1034 free(id_str);
1035 return err;
1037 free(id_str);
1038 err = format_line(&wline, &width, header, view->ncols);
1039 if (err) {
1040 free(header);
1041 return err;
1043 free(header);
1045 werase(view->window);
1047 if (view_needs_focus_indication(view))
1048 wstandout(view->window);
1049 waddwstr(view->window, wline);
1050 if (view_needs_focus_indication(view))
1051 wstandend(view->window);
1052 if (width < view->ncols)
1053 waddch(view->window, '\n');
1054 free(wline);
1055 if (limit <= 1)
1056 return NULL;
1058 entry = first;
1059 *last = first;
1060 ncommits = 0;
1061 while (entry) {
1062 if (ncommits >= limit - 1)
1063 break;
1064 if (view->focussed && ncommits == selected_idx)
1065 wstandout(view->window);
1066 err = draw_commit(view, entry->commit, entry->id);
1067 if (view->focussed && ncommits == selected_idx)
1068 wstandend(view->window);
1069 if (err)
1070 break;
1071 ncommits++;
1072 *last = entry;
1073 if (entry == TAILQ_LAST(&commits->head, commit_queue_head)) {
1074 err = queue_commits(graph, commits, NULL, 1,
1075 repo, path);
1076 if (err) {
1077 if (err->code != GOT_ERR_ITER_COMPLETED)
1078 return err;
1079 err = NULL;
1082 entry = TAILQ_NEXT(entry, entry);
1085 view_vborder(view);
1087 return err;
1090 static void
1091 scroll_up(struct commit_queue_entry **first_displayed_entry, int maxscroll,
1092 struct commit_queue *commits)
1094 struct commit_queue_entry *entry;
1095 int nscrolled = 0;
1097 entry = TAILQ_FIRST(&commits->head);
1098 if (*first_displayed_entry == entry)
1099 return;
1101 entry = *first_displayed_entry;
1102 while (entry && nscrolled < maxscroll) {
1103 entry = TAILQ_PREV(entry, commit_queue_head, entry);
1104 if (entry) {
1105 *first_displayed_entry = entry;
1106 nscrolled++;
1111 static const struct got_error *
1112 scroll_down(struct commit_queue_entry **first_displayed_entry, int maxscroll,
1113 struct commit_queue_entry **last_displayed_entry,
1114 struct commit_queue *commits, struct got_commit_graph *graph,
1115 struct got_repository *repo, const char *path)
1117 const struct got_error *err = NULL;
1118 struct commit_queue_entry *pentry;
1119 int nscrolled = 0;
1121 do {
1122 pentry = TAILQ_NEXT(*last_displayed_entry, entry);
1123 if (pentry == NULL) {
1124 err = fetch_next_commit(&pentry, *last_displayed_entry,
1125 commits, graph, repo, path);
1126 if (err || pentry == NULL)
1127 break;
1129 *last_displayed_entry = pentry;
1131 pentry = TAILQ_NEXT(*first_displayed_entry, entry);
1132 if (pentry == NULL)
1133 break;
1134 *first_displayed_entry = pentry;
1135 } while (++nscrolled < maxscroll);
1137 return err;
1140 static const struct got_error *
1141 open_diff_view_for_commit(struct tog_view **new_view, int begin_x,
1142 struct got_object_id *commit_id, struct got_commit_object *commit,
1143 struct got_repository *repo)
1145 const struct got_error *err;
1146 struct got_object *obj1 = NULL, *obj2 = NULL;
1147 struct got_object_qid *parent_id;
1148 struct tog_view *diff_view;
1150 err = got_object_open(&obj2, repo, commit_id);
1151 if (err)
1152 return err;
1154 parent_id = SIMPLEQ_FIRST(&commit->parent_ids);
1155 if (parent_id) {
1156 err = got_object_open(&obj1, repo, parent_id->id);
1157 if (err)
1158 goto done;
1161 diff_view = view_open(0, 0, 0, begin_x, TOG_VIEW_DIFF);
1162 if (diff_view == NULL) {
1163 err = got_error_from_errno();
1164 goto done;
1167 err = open_diff_view(diff_view, obj1, obj2, repo);
1168 if (err == NULL)
1169 *new_view = diff_view;
1170 done:
1171 if (obj1)
1172 got_object_close(obj1);
1173 if (obj2)
1174 got_object_close(obj2);
1175 return err;
1178 static const struct got_error *
1179 browse_commit(struct tog_view **new_view, int begin_x,
1180 struct commit_queue_entry *entry, struct got_repository *repo)
1182 const struct got_error *err = NULL;
1183 struct got_tree_object *tree;
1184 struct tog_view *tree_view;
1186 err = got_object_open_as_tree(&tree, repo, entry->commit->tree_id);
1187 if (err)
1188 return err;
1190 tree_view = view_open(0, 0, 0, begin_x, TOG_VIEW_TREE);
1191 if (tree_view == NULL)
1192 return got_error_from_errno();
1194 err = open_tree_view(tree_view, tree, entry->id, repo);
1195 if (err)
1196 got_object_tree_close(tree);
1197 else
1198 *new_view = tree_view;
1199 return err;
1202 static const struct got_error *
1203 open_log_view(struct tog_view *view, struct got_object_id *start_id,
1204 struct got_repository *repo, const char *path)
1206 const struct got_error *err = NULL;
1207 struct tog_log_view_state *s = &view->state.log;
1209 err = got_repo_map_path(&s->in_repo_path, repo, path);
1210 if (err != NULL)
1211 goto done;
1213 err = got_commit_graph_open(&s->graph, start_id, s->in_repo_path,
1214 0, repo);
1215 if (err)
1216 goto done;
1217 /* The commit queue only contains commits being displayed. */
1218 TAILQ_INIT(&s->commits.head);
1219 s->commits.ncommits = 0;
1222 * Open the initial batch of commits, sorted in commit graph order.
1223 * We keep all commits open throughout the lifetime of the log view
1224 * in order to avoid having to re-fetch commits from disk while
1225 * updating the display.
1227 err = queue_commits(s->graph, &s->commits, start_id, view->nlines,
1228 repo, s->in_repo_path);
1229 if (err) {
1230 if (err->code != GOT_ERR_ITER_COMPLETED)
1231 goto done;
1232 err = NULL;
1235 s->first_displayed_entry = TAILQ_FIRST(&s->commits.head);
1236 s->selected_entry = s->first_displayed_entry;
1237 s->repo = repo;
1238 s->start_id = got_object_id_dup(start_id);
1239 if (s->start_id == NULL) {
1240 err = got_error_from_errno();
1241 goto done;
1244 view->show = show_log_view;
1245 view->input = input_log_view;
1246 view->close = close_log_view;
1247 done:
1248 return err;
1251 static const struct got_error *
1252 close_log_view(struct tog_view *view)
1254 struct tog_log_view_state *s = &view->state.log;
1256 if (s->graph)
1257 got_commit_graph_close(s->graph);
1258 free_commits(&s->commits);
1259 free(s->in_repo_path);
1260 free(s->start_id);
1261 return NULL;
1264 static const struct got_error *
1265 show_log_view(struct tog_view *view)
1267 const struct got_error *err = NULL;
1268 struct tog_log_view_state *s = &view->state.log;
1270 return draw_commits(view, &s->last_displayed_entry,
1271 &s->selected_entry, s->first_displayed_entry,
1272 &s->commits, s->selected, view->nlines, s->graph,
1273 s->repo, s->in_repo_path);
1274 if (err)
1275 return err;
1278 static const struct got_error *
1279 input_log_view(struct tog_view **new_view, struct tog_view **dead_view,
1280 struct tog_view **focus_view, struct tog_view *view, int ch)
1282 const struct got_error *err = NULL;
1283 struct tog_log_view_state *s = &view->state.log;
1284 char *parent_path;
1285 struct tog_view *diff_view = NULL, *tree_view = NULL;
1286 int begin_x = 0;
1288 switch (ch) {
1289 case 'k':
1290 case KEY_UP:
1291 case '[':
1292 if (s->selected > 0)
1293 s->selected--;
1294 if (s->selected > 0)
1295 break;
1296 scroll_up(&s->first_displayed_entry, 1,
1297 &s->commits);
1298 break;
1299 case KEY_PPAGE:
1300 if (TAILQ_FIRST(&s->commits.head) ==
1301 s->first_displayed_entry) {
1302 s->selected = 0;
1303 break;
1305 scroll_up(&s->first_displayed_entry,
1306 view->nlines, &s->commits);
1307 break;
1308 case 'j':
1309 case KEY_DOWN:
1310 case ']':
1311 if (s->selected < MIN(view->nlines - 2,
1312 s->commits.ncommits - 1)) {
1313 s->selected++;
1314 break;
1316 err = scroll_down(&s->first_displayed_entry, 1,
1317 &s->last_displayed_entry, &s->commits,
1318 s->graph, s->repo, s->in_repo_path);
1319 if (err) {
1320 if (err->code != GOT_ERR_ITER_COMPLETED)
1321 break;
1322 err = NULL;
1324 break;
1325 case KEY_NPAGE: {
1326 struct commit_queue_entry *first;
1327 first = s->first_displayed_entry;
1328 err = scroll_down(&s->first_displayed_entry,
1329 view->nlines, &s->last_displayed_entry,
1330 &s->commits, s->graph, s->repo,
1331 s->in_repo_path);
1332 if (err && err->code != GOT_ERR_ITER_COMPLETED)
1333 break;
1334 if (first == s->first_displayed_entry &&
1335 s->selected < MIN(view->nlines - 2,
1336 s->commits.ncommits - 1)) {
1337 /* can't scroll further down */
1338 s->selected = MIN(view->nlines - 2,
1339 s->commits.ncommits - 1);
1341 err = NULL;
1342 break;
1344 case KEY_RESIZE:
1345 if (s->selected > view->nlines - 2)
1346 s->selected = view->nlines - 2;
1347 if (s->selected > s->commits.ncommits - 1)
1348 s->selected = s->commits.ncommits - 1;
1349 break;
1350 case KEY_ENTER:
1351 case '\r':
1352 if (view_is_parent_view(view))
1353 begin_x = view_split_begin_x(view->begin_x);
1354 err = open_diff_view_for_commit(&diff_view, begin_x,
1355 s->selected_entry->id, s->selected_entry->commit,
1356 s->repo);
1357 if (err)
1358 break;
1359 if (view_is_parent_view(view)) {
1360 err = view_close_child(view);
1361 if (err)
1362 return err;
1363 err = view_set_child(view, diff_view);
1364 if (err) {
1365 view_close(diff_view);
1366 break;
1368 if (!view_is_splitscreen(diff_view)) {
1369 *focus_view = diff_view;
1370 view->child_focussed = 1;
1372 } else
1373 *new_view = diff_view;
1374 break;
1375 case 't':
1376 if (view_is_parent_view(view))
1377 begin_x = view_split_begin_x(view->begin_x);
1378 err = browse_commit(&tree_view, begin_x,
1379 s->selected_entry, s->repo);
1380 if (view_is_parent_view(view)) {
1381 err = view_close_child(view);
1382 if (err)
1383 return err;
1384 err = view_set_child(view, tree_view);
1385 if (err) {
1386 view_close(tree_view);
1387 break;
1389 *focus_view = tree_view;
1390 view->child_focussed = 1;
1391 } else
1392 *new_view = tree_view;
1393 break;
1394 case KEY_BACKSPACE:
1395 if (strcmp(s->in_repo_path, "/") == 0)
1396 break;
1397 parent_path = dirname(s->in_repo_path);
1398 if (parent_path && strcmp(parent_path, ".") != 0) {
1399 struct tog_view *lv;
1400 lv = view_open(view->nlines, view->ncols,
1401 view->begin_y, view->begin_x, TOG_VIEW_LOG);
1402 if (lv == NULL)
1403 return got_error_from_errno();
1404 err = open_log_view(lv, s->start_id, s->repo,
1405 parent_path);
1406 if (err)
1407 break;
1408 if (view_is_parent_view(view))
1409 *new_view = lv;
1410 else {
1411 view_set_child(view->parent, lv);
1412 *dead_view = view;
1415 break;
1416 default:
1417 break;
1420 return err;
1423 static const struct got_error *
1424 cmd_log(int argc, char *argv[])
1426 const struct got_error *error;
1427 struct got_repository *repo = NULL;
1428 struct got_object_id *start_id = NULL;
1429 char *path = NULL, *repo_path = NULL, *cwd = NULL;
1430 char *start_commit = NULL;
1431 int ch;
1432 struct tog_view *view;
1434 #ifndef PROFILE
1435 if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd", NULL)
1436 == -1)
1437 err(1, "pledge");
1438 #endif
1440 while ((ch = getopt(argc, argv, "c:r:")) != -1) {
1441 switch (ch) {
1442 case 'c':
1443 start_commit = optarg;
1444 break;
1445 case 'r':
1446 repo_path = realpath(optarg, NULL);
1447 if (repo_path == NULL)
1448 err(1, "-r option");
1449 break;
1450 default:
1451 usage();
1452 /* NOTREACHED */
1456 argc -= optind;
1457 argv += optind;
1459 if (argc == 0)
1460 path = strdup("");
1461 else if (argc == 1)
1462 path = strdup(argv[0]);
1463 else
1464 usage_log();
1465 if (path == NULL)
1466 return got_error_from_errno();
1468 cwd = getcwd(NULL, 0);
1469 if (cwd == NULL) {
1470 error = got_error_from_errno();
1471 goto done;
1473 if (repo_path == NULL) {
1474 repo_path = strdup(cwd);
1475 if (repo_path == NULL) {
1476 error = got_error_from_errno();
1477 goto done;
1481 error = got_repo_open(&repo, repo_path);
1482 if (error != NULL)
1483 goto done;
1485 if (start_commit == NULL) {
1486 error = get_head_commit_id(&start_id, repo);
1487 if (error != NULL)
1488 goto done;
1489 } else {
1490 struct got_object *obj;
1491 error = got_object_open_by_id_str(&obj, repo, start_commit);
1492 if (error == NULL) {
1493 start_id = got_object_id_dup(got_object_get_id(obj));
1494 if (start_id == NULL)
1495 error = got_error_from_errno();
1496 goto done;
1499 if (error != NULL)
1500 goto done;
1502 view = view_open(0, 0, 0, 0, TOG_VIEW_LOG);
1503 if (view == NULL) {
1504 error = got_error_from_errno();
1505 goto done;
1507 error = open_log_view(view, start_id, repo, path);
1508 if (error)
1509 goto done;
1510 error = view_loop(view);
1511 done:
1512 free(repo_path);
1513 free(cwd);
1514 free(path);
1515 free(start_id);
1516 if (repo)
1517 got_repo_close(repo);
1518 return error;
1521 __dead static void
1522 usage_diff(void)
1524 endwin();
1525 fprintf(stderr, "usage: %s diff [repository-path] object1 object2\n",
1526 getprogname());
1527 exit(1);
1530 static char *
1531 parse_next_line(FILE *f, size_t *len)
1533 char *line;
1534 size_t linelen;
1535 size_t lineno;
1536 const char delim[3] = { '\0', '\0', '\0'};
1538 line = fparseln(f, &linelen, &lineno, delim, 0);
1539 if (len)
1540 *len = linelen;
1541 return line;
1544 static const struct got_error *
1545 draw_file(struct tog_view *view, FILE *f, int *first_displayed_line,
1546 int *last_displayed_line, int *eof, int max_lines,
1547 char * header)
1549 const struct got_error *err;
1550 int nlines = 0, nprinted = 0;
1551 char *line;
1552 size_t len;
1553 wchar_t *wline;
1554 int width;
1556 rewind(f);
1557 werase(view->window);
1559 if (header) {
1560 err = format_line(&wline, &width, header, view->ncols);
1561 if (err) {
1562 return err;
1565 if (view_needs_focus_indication(view))
1566 wstandout(view->window);
1567 waddwstr(view->window, wline);
1568 if (view_needs_focus_indication(view))
1569 wstandend(view->window);
1570 if (width < view->ncols)
1571 waddch(view->window, '\n');
1573 if (max_lines <= 1)
1574 return NULL;
1575 max_lines--;
1578 *eof = 0;
1579 while (nprinted < max_lines) {
1580 line = parse_next_line(f, &len);
1581 if (line == NULL) {
1582 *eof = 1;
1583 break;
1585 if (++nlines < *first_displayed_line) {
1586 free(line);
1587 continue;
1590 err = format_line(&wline, &width, line, view->ncols);
1591 if (err) {
1592 free(line);
1593 return err;
1595 waddwstr(view->window, wline);
1596 if (width < view->ncols)
1597 waddch(view->window, '\n');
1598 if (++nprinted == 1)
1599 *first_displayed_line = nlines;
1600 free(line);
1601 free(wline);
1602 wline = NULL;
1604 *last_displayed_line = nlines;
1606 view_vborder(view);
1608 return NULL;
1611 static const struct got_error *
1612 open_diff_view(struct tog_view *view, struct got_object *obj1,
1613 struct got_object *obj2, struct got_repository *repo)
1615 const struct got_error *err;
1616 FILE *f;
1618 if (obj1 != NULL && obj2 != NULL &&
1619 got_object_get_type(obj1) != got_object_get_type(obj2))
1620 return got_error(GOT_ERR_OBJ_TYPE);
1622 f = got_opentemp();
1623 if (f == NULL)
1624 return got_error_from_errno();
1626 switch (got_object_get_type(obj1 ? obj1 : obj2)) {
1627 case GOT_OBJ_TYPE_BLOB:
1628 err = got_diff_objects_as_blobs(obj1, obj2, NULL, NULL, 3,
1629 repo, f);
1630 break;
1631 case GOT_OBJ_TYPE_TREE:
1632 err = got_diff_objects_as_trees(obj1, obj2, "", "", 3, repo, f);
1633 break;
1634 case GOT_OBJ_TYPE_COMMIT:
1635 err = got_diff_objects_as_commits(obj1, obj2, 3, repo, f);
1636 break;
1637 default:
1638 return got_error(GOT_ERR_OBJ_TYPE);
1641 fflush(f);
1643 view->state.diff.id1 = obj1 ? got_object_get_id(obj1) : NULL;
1644 view->state.diff.id2 = got_object_get_id(obj2);
1645 view->state.diff.f = f;
1646 view->state.diff.first_displayed_line = 1;
1647 view->state.diff.last_displayed_line = view->nlines;
1649 view->show = show_diff_view;
1650 view->input = input_diff_view;
1651 view->close = close_diff_view;
1653 return NULL;
1656 static const struct got_error *
1657 close_diff_view(struct tog_view *view)
1659 const struct got_error *err = NULL;
1661 if (view->state.diff.f && fclose(view->state.diff.f) == EOF)
1662 err = got_error_from_errno();
1663 return err;
1666 static const struct got_error *
1667 show_diff_view(struct tog_view *view)
1669 const struct got_error *err;
1670 struct tog_diff_view_state *s = &view->state.diff;
1671 char *id_str1 = NULL, *id_str2, *header;
1673 if (s->id1) {
1674 err = got_object_id_str(&id_str1, s->id1);
1675 if (err)
1676 return err;
1678 err = got_object_id_str(&id_str2, s->id2);
1679 if (err)
1680 return err;
1682 if (asprintf(&header, "diff: %s %s",
1683 id_str1 ? id_str1 : "/dev/null", id_str2) == -1) {
1684 err = got_error_from_errno();
1685 free(id_str1);
1686 free(id_str2);
1687 return err;
1689 free(id_str1);
1690 free(id_str2);
1692 return draw_file(view, s->f, &s->first_displayed_line,
1693 &s->last_displayed_line, &s->eof, view->nlines,
1694 header);
1697 static const struct got_error *
1698 input_diff_view(struct tog_view **new_view, struct tog_view **dead_view,
1699 struct tog_view **focus_view, struct tog_view *view, int ch)
1701 const struct got_error *err = NULL;
1702 struct tog_diff_view_state *s = &view->state.diff;
1703 int i;
1705 switch (ch) {
1706 case 'k':
1707 case KEY_UP:
1708 if (s->first_displayed_line > 1)
1709 s->first_displayed_line--;
1710 break;
1711 case KEY_PPAGE:
1712 i = 0;
1713 while (i++ < view->nlines - 1 &&
1714 s->first_displayed_line > 1)
1715 s->first_displayed_line--;
1716 break;
1717 case 'j':
1718 case KEY_DOWN:
1719 if (!s->eof)
1720 s->first_displayed_line++;
1721 break;
1722 case KEY_NPAGE:
1723 case ' ':
1724 i = 0;
1725 while (!s->eof && i++ < view->nlines - 1) {
1726 char *line;
1727 line = parse_next_line(s->f, NULL);
1728 s->first_displayed_line++;
1729 if (line == NULL)
1730 break;
1732 break;
1733 default:
1734 break;
1737 return err;
1740 static const struct got_error *
1741 cmd_diff(int argc, char *argv[])
1743 const struct got_error *error = NULL;
1744 struct got_repository *repo = NULL;
1745 struct got_object *obj1 = NULL, *obj2 = NULL;
1746 char *repo_path = NULL;
1747 char *obj_id_str1 = NULL, *obj_id_str2 = NULL;
1748 int ch;
1749 struct tog_view *view;
1751 #ifndef PROFILE
1752 if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd", NULL)
1753 == -1)
1754 err(1, "pledge");
1755 #endif
1757 while ((ch = getopt(argc, argv, "")) != -1) {
1758 switch (ch) {
1759 default:
1760 usage();
1761 /* NOTREACHED */
1765 argc -= optind;
1766 argv += optind;
1768 if (argc == 0) {
1769 usage_diff(); /* TODO show local worktree changes */
1770 } else if (argc == 2) {
1771 repo_path = getcwd(NULL, 0);
1772 if (repo_path == NULL)
1773 return got_error_from_errno();
1774 obj_id_str1 = argv[0];
1775 obj_id_str2 = argv[1];
1776 } else if (argc == 3) {
1777 repo_path = realpath(argv[0], NULL);
1778 if (repo_path == NULL)
1779 return got_error_from_errno();
1780 obj_id_str1 = argv[1];
1781 obj_id_str2 = argv[2];
1782 } else
1783 usage_diff();
1785 error = got_repo_open(&repo, repo_path);
1786 free(repo_path);
1787 if (error)
1788 goto done;
1790 error = got_object_open_by_id_str(&obj1, repo, obj_id_str1);
1791 if (error)
1792 goto done;
1794 error = got_object_open_by_id_str(&obj2, repo, obj_id_str2);
1795 if (error)
1796 goto done;
1798 view = view_open(0, 0, 0, 0, TOG_VIEW_DIFF);
1799 if (view == NULL) {
1800 error = got_error_from_errno();
1801 goto done;
1803 error = open_diff_view(view, obj1, obj2, repo);
1804 if (error)
1805 goto done;
1806 error = view_loop(view);
1807 done:
1808 got_repo_close(repo);
1809 if (obj1)
1810 got_object_close(obj1);
1811 if (obj2)
1812 got_object_close(obj2);
1813 return error;
1816 __dead static void
1817 usage_blame(void)
1819 endwin();
1820 fprintf(stderr, "usage: %s blame [-c commit] [-r repository-path] path\n",
1821 getprogname());
1822 exit(1);
1825 struct tog_blame_line {
1826 int annotated;
1827 struct got_object_id *id;
1830 static const struct got_error *
1831 draw_blame(struct tog_view *view, struct got_object_id *id, FILE *f,
1832 const char *path, struct tog_blame_line *lines, int nlines,
1833 int blame_complete, int selected_line, int *first_displayed_line,
1834 int *last_displayed_line, int *eof, int max_lines)
1836 const struct got_error *err;
1837 int lineno = 0, nprinted = 0;
1838 char *line;
1839 size_t len;
1840 wchar_t *wline;
1841 int width, wlimit;
1842 struct tog_blame_line *blame_line;
1843 struct got_object_id *prev_id = NULL;
1844 char *id_str;
1846 err = got_object_id_str(&id_str, id);
1847 if (err)
1848 return err;
1850 rewind(f);
1851 werase(view->window);
1853 if (asprintf(&line, "commit: %s", id_str) == -1) {
1854 err = got_error_from_errno();
1855 free(id_str);
1856 return err;
1859 err = format_line(&wline, &width, line, view->ncols);
1860 free(line);
1861 line = NULL;
1862 if (view_needs_focus_indication(view))
1863 wstandout(view->window);
1864 waddwstr(view->window, wline);
1865 if (view_needs_focus_indication(view))
1866 wstandend(view->window);
1867 free(wline);
1868 wline = NULL;
1869 if (width < view->ncols)
1870 waddch(view->window, '\n');
1872 if (asprintf(&line, "[%d/%d] %s%s",
1873 *first_displayed_line - 1 + selected_line, nlines,
1874 blame_complete ? "" : "annotating ", path) == -1) {
1875 free(id_str);
1876 return got_error_from_errno();
1878 free(id_str);
1879 err = format_line(&wline, &width, line, view->ncols);
1880 free(line);
1881 line = NULL;
1882 if (err)
1883 return err;
1884 waddwstr(view->window, wline);
1885 free(wline);
1886 wline = NULL;
1887 if (width < view->ncols)
1888 waddch(view->window, '\n');
1890 *eof = 0;
1891 while (nprinted < max_lines - 2) {
1892 line = parse_next_line(f, &len);
1893 if (line == NULL) {
1894 *eof = 1;
1895 break;
1897 if (++lineno < *first_displayed_line) {
1898 free(line);
1899 continue;
1902 wlimit = view->ncols < 9 ? 0 : view->ncols - 9;
1903 err = format_line(&wline, &width, line, wlimit);
1904 if (err) {
1905 free(line);
1906 return err;
1909 if (view->focussed && nprinted == selected_line - 1)
1910 wstandout(view->window);
1912 blame_line = &lines[lineno - 1];
1913 if (blame_line->annotated && prev_id &&
1914 got_object_id_cmp(prev_id, blame_line->id) == 0)
1915 waddstr(view->window, " ");
1916 else if (blame_line->annotated) {
1917 char *id_str;
1918 err = got_object_id_str(&id_str, blame_line->id);
1919 if (err) {
1920 free(line);
1921 free(wline);
1922 return err;
1924 wprintw(view->window, "%.8s ", id_str);
1925 free(id_str);
1926 prev_id = blame_line->id;
1927 } else {
1928 waddstr(view->window, "........ ");
1929 prev_id = NULL;
1932 waddwstr(view->window, wline);
1933 while (width < wlimit) {
1934 waddch(view->window, ' ');
1935 width++;
1937 if (view->focussed && nprinted == selected_line - 1)
1938 wstandend(view->window);
1939 if (++nprinted == 1)
1940 *first_displayed_line = lineno;
1941 free(line);
1942 free(wline);
1943 wline = NULL;
1945 *last_displayed_line = lineno;
1947 view_vborder(view);
1949 return NULL;
1952 static const struct got_error *
1953 blame_cb(void *arg, int nlines, int lineno, struct got_object_id *id)
1955 const struct got_error *err = NULL;
1956 struct tog_blame_cb_args *a = arg;
1957 struct tog_blame_line *line;
1959 if (nlines != a->nlines ||
1960 (lineno != -1 && lineno < 1) || lineno > a->nlines)
1961 return got_error(GOT_ERR_RANGE);
1963 if (pthread_mutex_lock(a->mutex) != 0)
1964 return got_error_from_errno();
1966 if (*a->quit) { /* user has quit the blame view */
1967 err = got_error(GOT_ERR_ITER_COMPLETED);
1968 goto done;
1971 if (lineno == -1)
1972 goto done; /* no change in this commit */
1974 line = &a->lines[lineno - 1];
1975 if (line->annotated)
1976 goto done;
1978 line->id = got_object_id_dup(id);
1979 if (line->id == NULL) {
1980 err = got_error_from_errno();
1981 goto done;
1983 line->annotated = 1;
1985 err = draw_blame(a->view, a->commit_id, a->f, a->path,
1986 a->lines, a->nlines, 0, *a->selected_line, a->first_displayed_line,
1987 a->last_displayed_line, a->eof, a->view->nlines);
1988 done:
1989 if (pthread_mutex_unlock(a->mutex) != 0)
1990 return got_error_from_errno();
1991 return err;
1994 static void *
1995 blame_thread(void *arg)
1997 const struct got_error *err;
1998 struct tog_blame_thread_args *ta = arg;
1999 struct tog_blame_cb_args *a = ta->cb_args;
2001 err = got_blame_incremental(ta->path, a->commit_id, ta->repo,
2002 blame_cb, ta->cb_args);
2004 if (pthread_mutex_lock(a->mutex) != 0)
2005 return (void *)got_error_from_errno();
2007 got_repo_close(ta->repo);
2008 ta->repo = NULL;
2009 *ta->complete = 1;
2010 if (!err)
2011 err = draw_blame(a->view, a->commit_id, a->f, a->path,
2012 a->lines, a->nlines, 1, *a->selected_line,
2013 a->first_displayed_line, a->last_displayed_line, a->eof,
2014 a->view->nlines);
2016 if (pthread_mutex_unlock(a->mutex) != 0 && err == NULL)
2017 err = got_error_from_errno();
2019 return (void *)err;
2022 static struct got_object_id *
2023 get_selected_commit_id(struct tog_blame_line *lines,
2024 int first_displayed_line, int selected_line)
2026 struct tog_blame_line *line;
2028 line = &lines[first_displayed_line - 1 + selected_line - 1];
2029 if (!line->annotated)
2030 return NULL;
2032 return line->id;
2035 static const struct got_error *
2036 open_selected_commit(struct got_object **pobj, struct got_object **obj,
2037 struct tog_blame_line *lines, int first_displayed_line,
2038 int selected_line, struct got_repository *repo)
2040 const struct got_error *err = NULL;
2041 struct got_commit_object *commit = NULL;
2042 struct got_object_id *selected_id;
2043 struct got_object_qid *pid;
2045 *pobj = NULL;
2046 *obj = NULL;
2048 selected_id = get_selected_commit_id(lines,
2049 first_displayed_line, selected_line);
2050 if (selected_id == NULL)
2051 return NULL;
2053 err = got_object_open(obj, repo, selected_id);
2054 if (err)
2055 goto done;
2057 err = got_object_commit_open(&commit, repo, *obj);
2058 if (err)
2059 goto done;
2061 pid = SIMPLEQ_FIRST(&commit->parent_ids);
2062 if (pid) {
2063 err = got_object_open(pobj, repo, pid->id);
2064 if (err)
2065 goto done;
2067 done:
2068 if (commit)
2069 got_object_commit_close(commit);
2070 return err;
2073 static const struct got_error *
2074 stop_blame(struct tog_blame *blame)
2076 const struct got_error *err = NULL;
2077 int i;
2079 if (blame->thread) {
2080 if (pthread_join(blame->thread, (void **)&err) != 0)
2081 err = got_error_from_errno();
2082 if (err && err->code == GOT_ERR_ITER_COMPLETED)
2083 err = NULL;
2084 blame->thread = NULL;
2086 if (blame->thread_args.repo) {
2087 got_repo_close(blame->thread_args.repo);
2088 blame->thread_args.repo = NULL;
2090 if (blame->f) {
2091 fclose(blame->f);
2092 blame->f = NULL;
2094 for (i = 0; i < blame->nlines; i++)
2095 free(blame->lines[i].id);
2096 free(blame->lines);
2097 blame->lines = NULL;
2098 free(blame->cb_args.commit_id);
2099 blame->cb_args.commit_id = NULL;
2101 return err;
2104 static const struct got_error *
2105 run_blame(struct tog_blame *blame, pthread_mutex_t *mutex,
2106 struct tog_view *view, int *blame_complete,
2107 int *first_displayed_line, int *last_displayed_line,
2108 int *selected_line, int *done, int *eof, const char *path,
2109 struct got_object_id *commit_id,
2110 struct got_repository *repo)
2112 const struct got_error *err = NULL;
2113 struct got_blob_object *blob = NULL;
2114 struct got_repository *thread_repo = NULL;
2115 struct got_object_id *obj_id = NULL;
2116 struct got_object *obj = NULL;
2118 err = got_object_id_by_path(&obj_id, repo, commit_id, path);
2119 if (err)
2120 goto done;
2122 err = got_object_open(&obj, repo, obj_id);
2123 if (err)
2124 goto done;
2126 if (got_object_get_type(obj) != GOT_OBJ_TYPE_BLOB) {
2127 err = got_error(GOT_ERR_OBJ_TYPE);
2128 goto done;
2131 err = got_object_blob_open(&blob, repo, obj, 8192);
2132 if (err)
2133 goto done;
2134 blame->f = got_opentemp();
2135 if (blame->f == NULL) {
2136 err = got_error_from_errno();
2137 goto done;
2139 err = got_object_blob_dump_to_file(&blame->filesize, &blame->nlines,
2140 blame->f, blob);
2141 if (err)
2142 goto done;
2144 blame->lines = calloc(blame->nlines, sizeof(*blame->lines));
2145 if (blame->lines == NULL) {
2146 err = got_error_from_errno();
2147 goto done;
2150 err = got_repo_open(&thread_repo, got_repo_get_path(repo));
2151 if (err)
2152 goto done;
2154 blame->cb_args.view = view;
2155 blame->cb_args.lines = blame->lines;
2156 blame->cb_args.nlines = blame->nlines;
2157 blame->cb_args.mutex = mutex;
2158 blame->cb_args.commit_id = got_object_id_dup(commit_id);
2159 if (blame->cb_args.commit_id == NULL) {
2160 err = got_error_from_errno();
2161 goto done;
2163 blame->cb_args.f = blame->f;
2164 blame->cb_args.path = path;
2165 blame->cb_args.first_displayed_line = first_displayed_line;
2166 blame->cb_args.selected_line = selected_line;
2167 blame->cb_args.last_displayed_line = last_displayed_line;
2168 blame->cb_args.quit = done;
2169 blame->cb_args.eof = eof;
2171 blame->thread_args.path = path;
2172 blame->thread_args.repo = thread_repo;
2173 blame->thread_args.cb_args = &blame->cb_args;
2174 blame->thread_args.complete = blame_complete;
2175 *blame_complete = 0;
2177 if (pthread_create(&blame->thread, NULL, blame_thread,
2178 &blame->thread_args) != 0) {
2179 err = got_error_from_errno();
2180 goto done;
2183 done:
2184 if (blob)
2185 got_object_blob_close(blob);
2186 free(obj_id);
2187 if (obj)
2188 got_object_close(obj);
2189 if (err)
2190 stop_blame(blame);
2191 return err;
2194 static const struct got_error *
2195 open_blame_view(struct tog_view *view, char *path,
2196 struct got_object_id *commit_id, struct got_repository *repo)
2198 const struct got_error *err = NULL;
2199 struct tog_blame_view_state *s = &view->state.blame;
2201 SIMPLEQ_INIT(&s->blamed_commits);
2203 if (pthread_mutex_init(&s->mutex, NULL) != 0)
2204 return got_error_from_errno();
2206 err = got_object_qid_alloc(&s->blamed_commit, commit_id);
2207 if (err)
2208 return err;
2210 SIMPLEQ_INSERT_HEAD(&s->blamed_commits, s->blamed_commit, entry);
2211 s->first_displayed_line = 1;
2212 s->last_displayed_line = view->nlines;
2213 s->selected_line = 1;
2214 s->blame_complete = 0;
2215 s->path = path;
2216 if (s->path == NULL)
2217 return got_error_from_errno();
2218 s->repo = repo;
2219 s->commit_id = commit_id;
2220 memset(&s->blame, 0, sizeof(s->blame));
2222 view->show = show_blame_view;
2223 view->input = input_blame_view;
2224 view->close = close_blame_view;
2226 return run_blame(&s->blame, &s->mutex, view, &s->blame_complete,
2227 &s->first_displayed_line, &s->last_displayed_line,
2228 &s->selected_line, &s->done, &s->eof, s->path,
2229 s->blamed_commit->id, s->repo);
2232 static const struct got_error *
2233 close_blame_view(struct tog_view *view)
2235 const struct got_error *err = NULL;
2236 struct tog_blame_view_state *s = &view->state.blame;
2238 if (s->blame.thread)
2239 err = stop_blame(&s->blame);
2241 while (!SIMPLEQ_EMPTY(&s->blamed_commits)) {
2242 struct got_object_qid *blamed_commit;
2243 blamed_commit = SIMPLEQ_FIRST(&s->blamed_commits);
2244 SIMPLEQ_REMOVE_HEAD(&s->blamed_commits, entry);
2245 got_object_qid_free(blamed_commit);
2248 free(s->path);
2250 return err;
2253 static const struct got_error *
2254 show_blame_view(struct tog_view *view)
2256 const struct got_error *err = NULL;
2257 struct tog_blame_view_state *s = &view->state.blame;
2259 if (pthread_mutex_lock(&s->mutex) != 0)
2260 return got_error_from_errno();
2262 err = draw_blame(view, s->blamed_commit->id, s->blame.f,
2263 s->path, s->blame.lines, s->blame.nlines, s->blame_complete,
2264 s->selected_line, &s->first_displayed_line,
2265 &s->last_displayed_line, &s->eof, view->nlines);
2267 if (pthread_mutex_unlock(&s->mutex) != 0 && err == NULL)
2268 err = got_error_from_errno();
2270 view_vborder(view);
2271 return err;
2274 static const struct got_error *
2275 input_blame_view(struct tog_view **new_view, struct tog_view **dead_view,
2276 struct tog_view **focus_view, struct tog_view *view, int ch)
2278 const struct got_error *err = NULL, *thread_err = NULL;
2279 struct got_object *obj = NULL, *pobj = NULL;
2280 struct tog_view *diff_view;
2281 struct tog_blame_view_state *s = &view->state.blame;
2282 int begin_x = 0;
2284 if (pthread_mutex_lock(&s->mutex) != 0) {
2285 err = got_error_from_errno();
2286 goto done;
2289 switch (ch) {
2290 case 'q':
2291 s->done = 1;
2292 if (pthread_mutex_unlock(&s->mutex) != 0) {
2293 err = got_error_from_errno();
2294 goto done;
2296 return stop_blame(&s->blame);
2297 case 'k':
2298 case KEY_UP:
2299 if (s->selected_line > 1)
2300 s->selected_line--;
2301 else if (s->selected_line == 1 &&
2302 s->first_displayed_line > 1)
2303 s->first_displayed_line--;
2304 break;
2305 case KEY_PPAGE:
2306 if (s->first_displayed_line == 1) {
2307 s->selected_line = 1;
2308 break;
2310 if (s->first_displayed_line > view->nlines - 2)
2311 s->first_displayed_line -=
2312 (view->nlines - 2);
2313 else
2314 s->first_displayed_line = 1;
2315 break;
2316 case 'j':
2317 case KEY_DOWN:
2318 if (s->selected_line < view->nlines - 2 &&
2319 s->first_displayed_line +
2320 s->selected_line <= s->blame.nlines)
2321 s->selected_line++;
2322 else if (s->last_displayed_line <
2323 s->blame.nlines)
2324 s->first_displayed_line++;
2325 break;
2326 case 'b':
2327 case 'p': {
2328 struct got_object_id *id;
2329 id = get_selected_commit_id(s->blame.lines,
2330 s->first_displayed_line, s->selected_line);
2331 if (id == NULL || got_object_id_cmp(id,
2332 s->blamed_commit->id) == 0)
2333 break;
2334 err = open_selected_commit(&pobj, &obj,
2335 s->blame.lines, s->first_displayed_line,
2336 s->selected_line, s->repo);
2337 if (err)
2338 break;
2339 if (pobj == NULL && obj == NULL)
2340 break;
2341 if (ch == 'p' && pobj == NULL)
2342 break;
2343 s->done = 1;
2344 if (pthread_mutex_unlock(&s->mutex) != 0) {
2345 err = got_error_from_errno();
2346 goto done;
2348 thread_err = stop_blame(&s->blame);
2349 s->done = 0;
2350 if (pthread_mutex_lock(&s->mutex) != 0) {
2351 err = got_error_from_errno();
2352 goto done;
2354 if (thread_err)
2355 break;
2356 id = got_object_get_id(ch == 'b' ? obj : pobj);
2357 got_object_close(obj);
2358 obj = NULL;
2359 if (pobj) {
2360 got_object_close(pobj);
2361 pobj = NULL;
2363 err = got_object_qid_alloc(&s->blamed_commit, id);
2364 if (err)
2365 goto done;
2366 SIMPLEQ_INSERT_HEAD(&s->blamed_commits,
2367 s->blamed_commit, entry);
2368 err = run_blame(&s->blame, &s->mutex, view,
2369 &s->blame_complete,
2370 &s->first_displayed_line,
2371 &s->last_displayed_line,
2372 &s->selected_line, &s->done, &s->eof,
2373 s->path, s->blamed_commit->id, s->repo);
2374 if (err)
2375 break;
2376 break;
2378 case 'B': {
2379 struct got_object_qid *first;
2380 first = SIMPLEQ_FIRST(&s->blamed_commits);
2381 if (!got_object_id_cmp(first->id, s->commit_id))
2382 break;
2383 s->done = 1;
2384 if (pthread_mutex_unlock(&s->mutex) != 0) {
2385 err = got_error_from_errno();
2386 goto done;
2388 thread_err = stop_blame(&s->blame);
2389 s->done = 0;
2390 if (pthread_mutex_lock(&s->mutex) != 0) {
2391 err = got_error_from_errno();
2392 goto done;
2394 if (thread_err)
2395 break;
2396 SIMPLEQ_REMOVE_HEAD(&s->blamed_commits, entry);
2397 got_object_qid_free(s->blamed_commit);
2398 s->blamed_commit =
2399 SIMPLEQ_FIRST(&s->blamed_commits);
2400 err = run_blame(&s->blame, &s->mutex, view,
2401 &s->blame_complete,
2402 &s->first_displayed_line,
2403 &s->last_displayed_line,
2404 &s->selected_line, &s->done, &s->eof, s->path,
2405 s->blamed_commit->id, s->repo);
2406 if (err)
2407 break;
2408 break;
2410 case KEY_ENTER:
2411 case '\r':
2412 err = open_selected_commit(&pobj, &obj,
2413 s->blame.lines, s->first_displayed_line,
2414 s->selected_line, s->repo);
2415 if (err)
2416 break;
2417 if (pobj == NULL && obj == NULL)
2418 break;
2420 if (view_is_parent_view(view))
2421 begin_x = view_split_begin_x(view->begin_x);
2422 diff_view = view_open(0, 0, 0, begin_x, TOG_VIEW_DIFF);
2423 if (diff_view == NULL) {
2424 err = got_error_from_errno();
2425 break;
2427 err = open_diff_view(diff_view, pobj, obj, s->repo);
2428 if (err) {
2429 view_close(diff_view);
2430 break;
2432 if (view_is_parent_view(view)) {
2433 err = view_close_child(view);
2434 if (err)
2435 return err;
2436 err = view_set_child(view, diff_view);
2437 if (err) {
2438 view_close(diff_view);
2439 break;
2441 if (!view_is_splitscreen(diff_view)) {
2442 *focus_view = diff_view;
2443 view->child_focussed = 1;
2445 } else
2446 *new_view = diff_view;
2447 if (pobj) {
2448 got_object_close(pobj);
2449 pobj = NULL;
2451 got_object_close(obj);
2452 obj = NULL;
2453 if (err)
2454 break;
2455 break;
2456 case KEY_NPAGE:
2457 case ' ':
2458 if (s->last_displayed_line >= s->blame.nlines &&
2459 s->selected_line < view->nlines - 2) {
2460 s->selected_line = MIN(s->blame.nlines,
2461 view->nlines - 2);
2462 break;
2464 if (s->last_displayed_line + view->nlines - 2
2465 <= s->blame.nlines)
2466 s->first_displayed_line +=
2467 view->nlines - 2;
2468 else
2469 s->first_displayed_line =
2470 s->blame.nlines -
2471 (view->nlines - 3);
2472 break;
2473 case KEY_RESIZE:
2474 if (s->selected_line > view->nlines - 2) {
2475 s->selected_line = MIN(s->blame.nlines,
2476 view->nlines - 2);
2478 break;
2479 default:
2480 break;
2483 if (pthread_mutex_unlock(&s->mutex) != 0)
2484 err = got_error_from_errno();
2485 done:
2486 if (pobj)
2487 got_object_close(pobj);
2488 return thread_err ? thread_err : err;
2491 static const struct got_error *
2492 cmd_blame(int argc, char *argv[])
2494 const struct got_error *error;
2495 struct got_repository *repo = NULL;
2496 char *path, *cwd = NULL, *repo_path = NULL, *in_repo_path = NULL;
2497 struct got_object_id *commit_id = NULL;
2498 char *commit_id_str = NULL;
2499 int ch;
2500 struct tog_view *view;
2502 #ifndef PROFILE
2503 if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd", NULL)
2504 == -1)
2505 err(1, "pledge");
2506 #endif
2508 while ((ch = getopt(argc, argv, "c:r:")) != -1) {
2509 switch (ch) {
2510 case 'c':
2511 commit_id_str = optarg;
2512 break;
2513 case 'r':
2514 repo_path = realpath(optarg, NULL);
2515 if (repo_path == NULL)
2516 err(1, "-r option");
2517 break;
2518 default:
2519 usage();
2520 /* NOTREACHED */
2524 argc -= optind;
2525 argv += optind;
2527 if (argc == 1)
2528 path = argv[0];
2529 else
2530 usage_blame();
2532 cwd = getcwd(NULL, 0);
2533 if (cwd == NULL) {
2534 error = got_error_from_errno();
2535 goto done;
2537 if (repo_path == NULL) {
2538 repo_path = strdup(cwd);
2539 if (repo_path == NULL) {
2540 error = got_error_from_errno();
2541 goto done;
2546 error = got_repo_open(&repo, repo_path);
2547 if (error != NULL)
2548 return error;
2550 error = got_repo_map_path(&in_repo_path, repo, path);
2551 if (error != NULL)
2552 goto done;
2554 if (commit_id_str == NULL) {
2555 struct got_reference *head_ref;
2556 error = got_ref_open(&head_ref, repo, GOT_REF_HEAD);
2557 if (error != NULL)
2558 goto done;
2559 error = got_ref_resolve(&commit_id, repo, head_ref);
2560 got_ref_close(head_ref);
2561 } else {
2562 struct got_object *obj;
2563 error = got_object_open_by_id_str(&obj, repo, commit_id_str);
2564 if (error != NULL)
2565 goto done;
2566 commit_id = got_object_id_dup(got_object_get_id(obj));
2567 if (commit_id == NULL)
2568 error = got_error_from_errno();
2569 got_object_close(obj);
2571 if (error != NULL)
2572 goto done;
2574 view = view_open(0, 0, 0, 0, TOG_VIEW_BLAME);
2575 if (view == NULL) {
2576 error = got_error_from_errno();
2577 goto done;
2579 error = open_blame_view(view, in_repo_path, commit_id, repo);
2580 if (error)
2581 goto done;
2582 error = view_loop(view);
2583 done:
2584 free(repo_path);
2585 free(cwd);
2586 free(commit_id);
2587 if (repo)
2588 got_repo_close(repo);
2589 return error;
2592 static const struct got_error *
2593 draw_tree_entries(struct tog_view *view,
2594 struct got_tree_entry **first_displayed_entry,
2595 struct got_tree_entry **last_displayed_entry,
2596 struct got_tree_entry **selected_entry, int *ndisplayed,
2597 const char *label, int show_ids, const char *parent_path,
2598 const struct got_tree_entries *entries, int selected, int limit, int isroot)
2600 const struct got_error *err = NULL;
2601 struct got_tree_entry *te;
2602 wchar_t *wline;
2603 int width, n;
2605 *ndisplayed = 0;
2607 werase(view->window);
2609 if (limit == 0)
2610 return NULL;
2612 err = format_line(&wline, &width, label, view->ncols);
2613 if (err)
2614 return err;
2615 if (view_needs_focus_indication(view))
2616 wstandout(view->window);
2617 waddwstr(view->window, wline);
2618 if (view_needs_focus_indication(view))
2619 wstandend(view->window);
2620 free(wline);
2621 wline = NULL;
2622 if (width < view->ncols)
2623 waddch(view->window, '\n');
2624 if (--limit <= 0)
2625 return NULL;
2626 err = format_line(&wline, &width, parent_path, view->ncols);
2627 if (err)
2628 return err;
2629 waddwstr(view->window, wline);
2630 free(wline);
2631 wline = NULL;
2632 if (width < view->ncols)
2633 waddch(view->window, '\n');
2634 if (--limit <= 0)
2635 return NULL;
2636 waddch(view->window, '\n');
2637 if (--limit <= 0)
2638 return NULL;
2640 te = SIMPLEQ_FIRST(&entries->head);
2641 if (*first_displayed_entry == NULL) {
2642 if (selected == 0) {
2643 if (view->focussed)
2644 wstandout(view->window);
2645 *selected_entry = NULL;
2647 waddstr(view->window, " ..\n"); /* parent directory */
2648 if (selected == 0 && view->focussed)
2649 wstandend(view->window);
2650 (*ndisplayed)++;
2651 if (--limit <= 0)
2652 return NULL;
2653 n = 1;
2654 } else {
2655 n = 0;
2656 while (te != *first_displayed_entry)
2657 te = SIMPLEQ_NEXT(te, entry);
2660 while (te) {
2661 char *line = NULL, *id_str = NULL;
2663 if (show_ids) {
2664 err = got_object_id_str(&id_str, te->id);
2665 if (err)
2666 return got_error_from_errno();
2668 if (asprintf(&line, "%s %s%s", id_str ? id_str : "",
2669 te->name, S_ISDIR(te->mode) ? "/" : "") == -1) {
2670 free(id_str);
2671 return got_error_from_errno();
2673 free(id_str);
2674 err = format_line(&wline, &width, line, view->ncols);
2675 if (err) {
2676 free(line);
2677 break;
2679 if (n == selected) {
2680 if (view->focussed)
2681 wstandout(view->window);
2682 *selected_entry = te;
2684 waddwstr(view->window, wline);
2685 if (width < view->ncols)
2686 waddch(view->window, '\n');
2687 if (n == selected && view->focussed)
2688 wstandend(view->window);
2689 free(line);
2690 free(wline);
2691 wline = NULL;
2692 n++;
2693 (*ndisplayed)++;
2694 *last_displayed_entry = te;
2695 if (--limit <= 0)
2696 break;
2697 te = SIMPLEQ_NEXT(te, entry);
2700 return err;
2703 static void
2704 tree_scroll_up(struct got_tree_entry **first_displayed_entry, int maxscroll,
2705 const struct got_tree_entries *entries, int isroot)
2707 struct got_tree_entry *te, *prev;
2708 int i;
2710 if (*first_displayed_entry == NULL)
2711 return;
2713 te = SIMPLEQ_FIRST(&entries->head);
2714 if (*first_displayed_entry == te) {
2715 if (!isroot)
2716 *first_displayed_entry = NULL;
2717 return;
2720 /* XXX this is stupid... switch to TAILQ? */
2721 for (i = 0; i < maxscroll; i++) {
2722 while (te != *first_displayed_entry) {
2723 prev = te;
2724 te = SIMPLEQ_NEXT(te, entry);
2726 *first_displayed_entry = prev;
2727 te = SIMPLEQ_FIRST(&entries->head);
2729 if (!isroot && te == SIMPLEQ_FIRST(&entries->head) && i < maxscroll)
2730 *first_displayed_entry = NULL;
2733 static void
2734 tree_scroll_down(struct got_tree_entry **first_displayed_entry, int maxscroll,
2735 struct got_tree_entry *last_displayed_entry,
2736 const struct got_tree_entries *entries)
2738 struct got_tree_entry *next;
2739 int n = 0;
2741 if (SIMPLEQ_NEXT(last_displayed_entry, entry) == NULL)
2742 return;
2744 if (*first_displayed_entry)
2745 next = SIMPLEQ_NEXT(*first_displayed_entry, entry);
2746 else
2747 next = SIMPLEQ_FIRST(&entries->head);
2748 while (next) {
2749 *first_displayed_entry = next;
2750 if (++n >= maxscroll)
2751 break;
2752 next = SIMPLEQ_NEXT(next, entry);
2756 static const struct got_error *
2757 tree_entry_path(char **path, struct tog_parent_trees *parents,
2758 struct got_tree_entry *te)
2760 const struct got_error *err = NULL;
2761 struct tog_parent_tree *pt;
2762 size_t len = 2; /* for leading slash and NUL */
2764 TAILQ_FOREACH(pt, parents, entry)
2765 len += strlen(pt->selected_entry->name) + 1 /* slash */;
2766 if (te)
2767 len += strlen(te->name);
2769 *path = calloc(1, len);
2770 if (path == NULL)
2771 return got_error_from_errno();
2773 (*path)[0] = '/';
2774 pt = TAILQ_LAST(parents, tog_parent_trees);
2775 while (pt) {
2776 if (strlcat(*path, pt->selected_entry->name, len) >= len) {
2777 err = got_error(GOT_ERR_NO_SPACE);
2778 goto done;
2780 if (strlcat(*path, "/", len) >= len) {
2781 err = got_error(GOT_ERR_NO_SPACE);
2782 goto done;
2784 pt = TAILQ_PREV(pt, tog_parent_trees, entry);
2786 if (te) {
2787 if (strlcat(*path, te->name, len) >= len) {
2788 err = got_error(GOT_ERR_NO_SPACE);
2789 goto done;
2792 done:
2793 if (err) {
2794 free(*path);
2795 *path = NULL;
2797 return err;
2800 static const struct got_error *
2801 blame_tree_entry(struct tog_view **new_view, int begin_x,
2802 struct got_tree_entry *te, struct tog_parent_trees *parents,
2803 struct got_object_id *commit_id, struct got_repository *repo)
2805 const struct got_error *err = NULL;
2806 char *path;
2807 struct tog_view *blame_view;
2809 err = tree_entry_path(&path, parents, te);
2810 if (err)
2811 return err;
2813 blame_view = view_open(0, 0, 0, begin_x, TOG_VIEW_BLAME);
2814 if (blame_view == NULL)
2815 return got_error_from_errno();
2817 err = open_blame_view(blame_view, path, commit_id, repo);
2818 if (err) {
2819 view_close(blame_view);
2820 free(path);
2821 } else
2822 *new_view = blame_view;
2823 return err;
2826 static const struct got_error *
2827 log_tree_entry(struct tog_view **new_view, int begin_x,
2828 struct got_tree_entry *te, struct tog_parent_trees *parents,
2829 struct got_object_id *commit_id, struct got_repository *repo)
2831 struct tog_view *log_view;
2832 const struct got_error *err = NULL;
2833 char *path;
2835 log_view = view_open(0, 0, 0, begin_x, TOG_VIEW_LOG);
2836 if (log_view == NULL)
2837 return got_error_from_errno();
2839 err = tree_entry_path(&path, parents, te);
2840 if (err)
2841 return err;
2843 err = open_log_view(log_view, commit_id, repo, path);
2844 if (err)
2845 view_close(log_view);
2846 else
2847 *new_view = log_view;
2848 free(path);
2849 return err;
2852 static const struct got_error *
2853 open_tree_view(struct tog_view *view, struct got_tree_object *root,
2854 struct got_object_id *commit_id, struct got_repository *repo)
2856 const struct got_error *err = NULL;
2857 char *commit_id_str = NULL;
2858 struct tog_tree_view_state *s = &view->state.tree;
2860 TAILQ_INIT(&s->parents);
2862 err = got_object_id_str(&commit_id_str, commit_id);
2863 if (err != NULL)
2864 goto done;
2866 if (asprintf(&s->tree_label, "commit: %s", commit_id_str) == -1) {
2867 err = got_error_from_errno();
2868 goto done;
2871 s->root = s->tree = root;
2872 s->entries = got_object_tree_get_entries(root);
2873 s->first_displayed_entry = SIMPLEQ_FIRST(&s->entries->head);
2874 s->commit_id = got_object_id_dup(commit_id);
2875 if (s->commit_id == NULL) {
2876 err = got_error_from_errno();
2877 goto done;
2879 s->repo = repo;
2881 view->show = show_tree_view;
2882 view->input = input_tree_view;
2883 view->close = close_tree_view;
2884 done:
2885 free(commit_id_str);
2886 if (err) {
2887 free(s->tree_label);
2888 s->tree_label = NULL;
2890 return err;
2893 static const struct got_error *
2894 close_tree_view(struct tog_view *view)
2896 struct tog_tree_view_state *s = &view->state.tree;
2898 free(s->tree_label);
2899 s->tree_label = NULL;
2900 free(s->commit_id);
2901 s->commit_id = NULL;
2902 while (!TAILQ_EMPTY(&s->parents)) {
2903 struct tog_parent_tree *parent;
2904 parent = TAILQ_FIRST(&s->parents);
2905 TAILQ_REMOVE(&s->parents, parent, entry);
2906 free(parent);
2909 if (s->tree != s->root)
2910 got_object_tree_close(s->tree);
2911 got_object_tree_close(s->root);
2913 return NULL;
2916 static const struct got_error *
2917 show_tree_view(struct tog_view *view)
2919 const struct got_error *err = NULL;
2920 struct tog_tree_view_state *s = &view->state.tree;
2921 char *parent_path;
2923 err = tree_entry_path(&parent_path, &s->parents, NULL);
2924 if (err)
2925 return err;
2927 err = draw_tree_entries(view, &s->first_displayed_entry,
2928 &s->last_displayed_entry, &s->selected_entry,
2929 &s->ndisplayed, s->tree_label, s->show_ids, parent_path,
2930 s->entries, s->selected, view->nlines, s->tree == s->root);
2931 free(parent_path);
2933 view_vborder(view);
2934 return err;
2937 static const struct got_error *
2938 input_tree_view(struct tog_view **new_view, struct tog_view **dead_view,
2939 struct tog_view **focus_view, struct tog_view *view, int ch)
2941 const struct got_error *err = NULL;
2942 struct tog_tree_view_state *s = &view->state.tree;
2943 struct tog_view *log_view;
2944 int begin_x = 0;
2946 switch (ch) {
2947 case 'i':
2948 s->show_ids = !s->show_ids;
2949 break;
2950 case 'l':
2951 if (!s->selected_entry)
2952 break;
2953 if (view_is_parent_view(view))
2954 begin_x = view_split_begin_x(view->begin_x);
2955 err = log_tree_entry(&log_view, begin_x,
2956 s->selected_entry, &s->parents,
2957 s->commit_id, s->repo);
2958 if (view_is_parent_view(view)) {
2959 err = view_close_child(view);
2960 if (err)
2961 return err;
2962 err = view_set_child(view, log_view);
2963 if (err) {
2964 view_close(log_view);
2965 break;
2967 *focus_view = log_view;
2968 view->child_focussed = 1;
2969 } else
2970 *new_view = log_view;
2971 break;
2972 case 'k':
2973 case KEY_UP:
2974 if (s->selected > 0)
2975 s->selected--;
2976 if (s->selected > 0)
2977 break;
2978 tree_scroll_up(&s->first_displayed_entry, 1,
2979 s->entries, s->tree == s->root);
2980 break;
2981 case KEY_PPAGE:
2982 if (SIMPLEQ_FIRST(&s->entries->head) ==
2983 s->first_displayed_entry) {
2984 if (s->tree != s->root)
2985 s->first_displayed_entry = NULL;
2986 s->selected = 0;
2987 break;
2989 tree_scroll_up(&s->first_displayed_entry,
2990 view->nlines, s->entries,
2991 s->tree == s->root);
2992 break;
2993 case 'j':
2994 case KEY_DOWN:
2995 if (s->selected < s->ndisplayed - 1) {
2996 s->selected++;
2997 break;
2999 tree_scroll_down(&s->first_displayed_entry, 1,
3000 s->last_displayed_entry, s->entries);
3001 break;
3002 case KEY_NPAGE:
3003 tree_scroll_down(&s->first_displayed_entry,
3004 view->nlines, s->last_displayed_entry,
3005 s->entries);
3006 if (SIMPLEQ_NEXT(s->last_displayed_entry,
3007 entry))
3008 break;
3009 /* can't scroll any further; move cursor down */
3010 if (s->selected < s->ndisplayed - 1)
3011 s->selected = s->ndisplayed - 1;
3012 break;
3013 case KEY_ENTER:
3014 case '\r':
3015 if (s->selected_entry == NULL) {
3016 struct tog_parent_tree *parent;
3017 case KEY_BACKSPACE:
3018 /* user selected '..' */
3019 if (s->tree == s->root)
3020 break;
3021 parent = TAILQ_FIRST(&s->parents);
3022 TAILQ_REMOVE(&s->parents, parent,
3023 entry);
3024 got_object_tree_close(s->tree);
3025 s->tree = parent->tree;
3026 s->entries =
3027 got_object_tree_get_entries(s->tree);
3028 s->first_displayed_entry =
3029 parent->first_displayed_entry;
3030 s->selected_entry =
3031 parent->selected_entry;
3032 s->selected = parent->selected;
3033 free(parent);
3034 } else if (S_ISDIR(s->selected_entry->mode)) {
3035 struct tog_parent_tree *parent;
3036 struct got_tree_object *child;
3037 err = got_object_open_as_tree(&child,
3038 s->repo, s->selected_entry->id);
3039 if (err)
3040 break;
3041 parent = calloc(1, sizeof(*parent));
3042 if (parent == NULL) {
3043 err = got_error_from_errno();
3044 break;
3046 parent->tree = s->tree;
3047 parent->first_displayed_entry =
3048 s->first_displayed_entry;
3049 parent->selected_entry = s->selected_entry;
3050 parent->selected = s->selected;
3051 TAILQ_INSERT_HEAD(&s->parents, parent, entry);
3052 s->tree = child;
3053 s->entries =
3054 got_object_tree_get_entries(s->tree);
3055 s->selected = 0;
3056 s->first_displayed_entry = NULL;
3057 } else if (S_ISREG(s->selected_entry->mode)) {
3058 struct tog_view *blame_view;
3059 int begin_x = view_is_parent_view(view) ?
3060 view_split_begin_x(view->begin_x) : 0;
3062 err = blame_tree_entry(&blame_view, begin_x,
3063 s->selected_entry, &s->parents, s->commit_id,
3064 s->repo);
3065 if (err)
3066 break;
3067 if (view_is_parent_view(view)) {
3068 err = view_close_child(view);
3069 if (err)
3070 return err;
3071 err = view_set_child(view, blame_view);
3072 if (err) {
3073 view_close(blame_view);
3074 break;
3076 *focus_view = blame_view;
3077 view->child_focussed = 1;
3078 } else
3079 *new_view = blame_view;
3081 break;
3082 case KEY_RESIZE:
3083 if (s->selected > view->nlines)
3084 s->selected = s->ndisplayed - 1;
3085 break;
3086 default:
3087 break;
3090 return err;
3093 __dead static void
3094 usage_tree(void)
3096 endwin();
3097 fprintf(stderr, "usage: %s tree [-c commit] [repository-path]\n",
3098 getprogname());
3099 exit(1);
3102 static const struct got_error *
3103 cmd_tree(int argc, char *argv[])
3105 const struct got_error *error;
3106 struct got_repository *repo = NULL;
3107 char *repo_path = NULL;
3108 struct got_object_id *commit_id = NULL;
3109 char *commit_id_arg = NULL;
3110 struct got_commit_object *commit = NULL;
3111 struct got_tree_object *tree = NULL;
3112 int ch;
3113 struct tog_view *view;
3115 #ifndef PROFILE
3116 if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd", NULL)
3117 == -1)
3118 err(1, "pledge");
3119 #endif
3121 while ((ch = getopt(argc, argv, "c:")) != -1) {
3122 switch (ch) {
3123 case 'c':
3124 commit_id_arg = optarg;
3125 break;
3126 default:
3127 usage();
3128 /* NOTREACHED */
3132 argc -= optind;
3133 argv += optind;
3135 if (argc == 0) {
3136 repo_path = getcwd(NULL, 0);
3137 if (repo_path == NULL)
3138 return got_error_from_errno();
3139 } else if (argc == 1) {
3140 repo_path = realpath(argv[0], NULL);
3141 if (repo_path == NULL)
3142 return got_error_from_errno();
3143 } else
3144 usage_log();
3146 error = got_repo_open(&repo, repo_path);
3147 free(repo_path);
3148 if (error != NULL)
3149 return error;
3151 if (commit_id_arg == NULL) {
3152 error = get_head_commit_id(&commit_id, repo);
3153 if (error != NULL)
3154 goto done;
3155 } else {
3156 struct got_object *obj;
3157 error = got_object_open_by_id_str(&obj, repo, commit_id_arg);
3158 if (error == NULL) {
3159 commit_id = got_object_id_dup(got_object_get_id(obj));
3160 if (commit_id == NULL)
3161 error = got_error_from_errno();
3164 if (error != NULL)
3165 goto done;
3167 error = got_object_open_as_commit(&commit, repo, commit_id);
3168 if (error != NULL)
3169 goto done;
3171 error = got_object_open_as_tree(&tree, repo, commit->tree_id);
3172 if (error != NULL)
3173 goto done;
3175 view = view_open(0, 0, 0, 0, TOG_VIEW_TREE);
3176 if (view == NULL) {
3177 error = got_error_from_errno();
3178 goto done;
3180 error = open_tree_view(view, tree, commit_id, repo);
3181 if (error)
3182 goto done;
3183 error = view_loop(view);
3184 done:
3185 free(commit_id);
3186 if (commit)
3187 got_object_commit_close(commit);
3188 if (tree)
3189 got_object_tree_close(tree);
3190 if (repo)
3191 got_repo_close(repo);
3192 return error;
3194 static void
3195 init_curses(void)
3197 initscr();
3198 cbreak();
3199 noecho();
3200 nonl();
3201 intrflush(stdscr, FALSE);
3202 keypad(stdscr, TRUE);
3203 curs_set(0);
3206 __dead static void
3207 usage(void)
3209 int i;
3211 fprintf(stderr, "usage: %s [-h] [command] [arg ...]\n\n"
3212 "Available commands:\n", getprogname());
3213 for (i = 0; i < nitems(tog_commands); i++) {
3214 struct tog_cmd *cmd = &tog_commands[i];
3215 fprintf(stderr, " %s: %s\n", cmd->name, cmd->descr);
3217 exit(1);
3220 static char **
3221 make_argv(const char *arg0, const char *arg1)
3223 char **argv;
3224 int argc = (arg1 == NULL ? 1 : 2);
3226 argv = calloc(argc, sizeof(char *));
3227 if (argv == NULL)
3228 err(1, "calloc");
3229 argv[0] = strdup(arg0);
3230 if (argv[0] == NULL)
3231 err(1, "calloc");
3232 if (arg1) {
3233 argv[1] = strdup(arg1);
3234 if (argv[1] == NULL)
3235 err(1, "calloc");
3238 return argv;
3241 int
3242 main(int argc, char *argv[])
3244 const struct got_error *error = NULL;
3245 struct tog_cmd *cmd = NULL;
3246 int ch, hflag = 0;
3247 char **cmd_argv = NULL;
3249 setlocale(LC_ALL, "");
3251 while ((ch = getopt(argc, argv, "h")) != -1) {
3252 switch (ch) {
3253 case 'h':
3254 hflag = 1;
3255 break;
3256 default:
3257 usage();
3258 /* NOTREACHED */
3262 argc -= optind;
3263 argv += optind;
3264 optind = 0;
3265 optreset = 1;
3267 if (argc == 0) {
3268 if (hflag)
3269 usage();
3270 /* Build an argument vector which runs a default command. */
3271 cmd = &tog_commands[0];
3272 cmd_argv = make_argv(cmd->name, NULL);
3273 argc = 1;
3274 } else {
3275 int i;
3277 /* Did the user specific a command? */
3278 for (i = 0; i < nitems(tog_commands); i++) {
3279 if (strncmp(tog_commands[i].name, argv[0],
3280 strlen(argv[0])) == 0) {
3281 cmd = &tog_commands[i];
3282 if (hflag)
3283 tog_commands[i].cmd_usage();
3284 break;
3287 if (cmd == NULL) {
3288 /* Did the user specify a repository? */
3289 char *repo_path = realpath(argv[0], NULL);
3290 if (repo_path) {
3291 struct got_repository *repo;
3292 error = got_repo_open(&repo, repo_path);
3293 if (error == NULL)
3294 got_repo_close(repo);
3295 } else
3296 error = got_error_from_errno();
3297 if (error) {
3298 if (hflag) {
3299 fprintf(stderr, "%s: '%s' is not a "
3300 "known command\n", getprogname(),
3301 argv[0]);
3302 usage();
3304 fprintf(stderr, "%s: '%s' is neither a known "
3305 "command nor a path to a repository\n",
3306 getprogname(), argv[0]);
3307 free(repo_path);
3308 return 1;
3310 cmd = &tog_commands[0];
3311 cmd_argv = make_argv(cmd->name, repo_path);
3312 argc = 2;
3313 free(repo_path);
3317 init_curses();
3319 error = cmd->cmd_main(argc, cmd_argv ? cmd_argv : argv);
3320 if (error)
3321 goto done;
3322 done:
3323 endwin();
3324 free(cmd_argv);
3325 if (error)
3326 fprintf(stderr, "%s: %s\n", getprogname(), error->msg);
3327 return 0;