2 * Copyright (c) 2018 Stefan Sperling <stsp@openbsd.org>
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 #include <sys/queue.h>
21 #define _XOPEN_SOURCE_EXTENDED
23 #undef _XOPEN_SOURCE_EXTENDED
39 #include "got_error.h"
40 #include "got_object.h"
41 #include "got_reference.h"
42 #include "got_repository.h"
44 #include "got_opentemp.h"
45 #include "got_commit_graph.h"
47 #include "got_blame.h"
50 #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b))
54 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
59 const struct got_error *(*cmd_main)(int, char *[]);
60 void (*cmd_usage)(void);
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" },
93 struct tog_diff_view_state {
94 struct got_object_id *id1, *id2;
96 int first_displayed_line;
97 int last_displayed_line;
101 struct commit_queue_entry {
102 TAILQ_ENTRY(commit_queue_entry) entry;
103 struct got_object_id *id;
104 struct got_commit_object *commit;
106 TAILQ_HEAD(commit_queue_head, commit_queue_entry);
107 struct commit_queue {
109 struct commit_queue_head head;
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;
120 struct got_repository *repo;
121 struct got_object_id *start_id;
124 struct tog_blame_cb_args {
125 pthread_mutex_t *mutex;
126 struct tog_blame_line *lines; /* one per line */
129 struct tog_view *view;
130 struct got_object_id *commit_id;
133 int *first_displayed_line;
134 int *last_displayed_line;
140 struct tog_blame_thread_args {
142 struct got_repository *repo;
143 struct tog_blame_cb_args *cb_args;
150 struct tog_blame_line *lines;
153 struct tog_blame_thread_args thread_args;
154 struct tog_blame_cb_args cb_args;
158 struct tog_blame_view_state {
159 int first_displayed_line;
160 int last_displayed_line;
165 pthread_mutex_t mutex;
166 struct got_object_id_queue blamed_commits;
167 struct got_object_qid *blamed_commit;
169 struct got_repository *repo;
170 struct got_object_id *commit_id;
171 struct tog_blame blame;
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;
182 TAILQ_HEAD(tog_parent_trees, tog_parent_tree);
184 struct tog_tree_view_state {
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;
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.
215 TAILQ_HEAD(tog_view_list_head, tog_view);
218 TAILQ_ENTRY(tog_view) entry;
221 int nlines, ncols, begin_y, begin_x;
222 int lines, cols; /* copies of LINES and COLS */
224 struct tog_view *parent;
225 struct tog_view *child;
228 /* type-specific state */
229 enum tog_view_type type;
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;
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 *);
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;
277 view_close(view->child);
281 err = view->close(view);
283 del_panel(view->panel);
285 delwin(view->window);
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));
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) {
311 view->panel = new_panel(view->window);
312 if (view->panel == NULL ||
313 set_panel_userptr(view->panel, view) != OK) {
318 keypad(view->window, TRUE);
323 view_split_begin_x(int begin_x)
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;
338 view->begin_x = view_split_begin_x(0);
339 view->nlines = LINES;
340 view->ncols = COLS - view->begin_x;
343 err = view_resize(view);
347 if (mvwin(view->window, view->begin_y, view->begin_x) == ERR)
348 return got_error_from_errno();
353 static const struct got_error *
354 view_fullscreen(struct tog_view *view)
356 const struct got_error *err = NULL;
360 view->nlines = LINES;
364 err = view_resize(view);
368 if (mvwin(view->window, view->begin_y, view->begin_x) == ERR)
369 return got_error_from_errno();
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)
385 if (view->lines > LINES)
386 nlines = view->nlines - (view->lines - LINES);
388 nlines = view->nlines + (LINES - view->lines);
390 if (view->cols > COLS)
391 ncols = view->ncols - (view->cols - COLS);
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;
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);
411 show_panel(view->panel);
413 view_splitscreen(view->child);
414 show_panel(view->child->panel);
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)
429 err = view_close(view->child);
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;
440 child->parent = view;
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;
463 nodelay(stdscr, FALSE);
464 ch = wgetch(view->window);
465 nodelay(stdscr, TRUE);
471 *focus = view->child;
472 view->child_focussed = 1;
473 } else if (view->parent) {
474 *focus = view->parent;
475 view->parent->child_focussed = 0;
479 err = view->input(new, dead, focus, view, ch);
486 if (view_is_parent_view(view)) {
487 if (view->child == NULL)
489 if (view_is_splitscreen(view->child)) {
490 *focus = view->child;
491 view->child_focussed = 1;
492 err = view_fullscreen(view->child);
494 err = view_splitscreen(view->child);
497 err = view->child->input(new, dead, focus,
498 view->child, KEY_RESIZE);
500 if (view_is_splitscreen(view)) {
502 view->parent->child_focussed = 1;
503 err = view_fullscreen(view);
505 err = view_splitscreen(view);
509 err = view->input(new, dead, focus, view,
514 TAILQ_FOREACH(v, views, entry) {
515 err = view_resize(v);
518 err = v->input(new, dead, focus, v, ch);
522 err = view->input(new, dead, focus, view, ch);
530 view_vborder(struct tog_view *view)
533 struct tog_view *view_above;
536 return view_vborder(view->parent);
538 panel = panel_above(view->panel);
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);
548 view_needs_focus_indication(struct tog_view *view)
550 if (view_is_parent_view(view)) {
551 if (view->child == NULL || view->child_focussed)
553 if (!view_is_splitscreen(view->child))
555 } else if (!view_is_splitscreen(view))
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;
570 TAILQ_INSERT_HEAD(&views, view, entry);
574 err = view->show(view);
579 while (!TAILQ_EMPTY(&views) && !done) {
580 err = view_input(&new_view, &dead_view, &focus_view, &done,
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);
593 if (dead_view->parent)
594 dead_view->parent->child = NULL;
596 TAILQ_REMOVE(&views, dead_view, entry);
598 err = view_close(dead_view);
599 if (err || dead_view == main_view)
602 if (view == dead_view) {
607 else if (!TAILQ_EMPTY(&views))
608 view = TAILQ_LAST(&views,
613 if (view->child && view->child_focussed)
614 focus_view = view->child;
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)
626 TAILQ_REMOVE(&views, v, entry);
634 TAILQ_INSERT_TAIL(&views, new_view, entry);
635 if (focus_view == NULL)
636 focus_view = new_view;
639 show_panel(focus_view->panel);
642 focus_view->focussed = 1;
645 show_panel(new_view->panel);
646 if (view->child && view_is_splitscreen(view->child))
647 show_panel(view->child->panel);
651 err = view->parent->show(view->parent);
655 err = view->show(view);
659 err = view->child->show(view->child);
668 while (!TAILQ_EMPTY(&views)) {
669 view = TAILQ_FIRST(&views);
670 TAILQ_REMOVE(&views, view, entry);
681 "usage: %s log [-c commit] [-r repository-path] [path]\n",
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)
691 const struct got_error *err = NULL;
694 *wlen = mbstowcs(NULL, s, 0);
695 if (*wlen == (size_t)-1) {
698 return got_error_from_errno();
700 /* byte string invalid in current encoding; try to "fix" it */
701 err = got_mbsavis(&vis, &vislen, s);
704 *wlen = mbstowcs(NULL, vis, 0);
705 if (*wlen == (size_t)-1) {
706 err = got_error_from_errno(); /* give up */
711 *ws = calloc(*wlen + 1, sizeof(*ws));
713 err = got_error_from_errno();
717 if (mbstowcs(*ws, vis ? vis : s, *wlen) != *wlen)
718 err = got_error_from_errno();
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;
735 wchar_t *wline = NULL;
742 err = mbs2ws(&wline, &wlen, line);
747 while (i < wlen && cols < wlimit) {
748 int width = wcwidth(wline[i]);
755 if (cols + width <= wlimit)
760 if (wline[i] == L'\t')
761 cols += TABSIZE - ((cols + 1) % TABSIZE);
765 err = got_error_from_errno();
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;
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);
804 limit = MIN(date_display_cols, sizeof(datebuf) - 1);
805 waddnstr(view->window, datebuf, limit);
810 author0 = strdup(commit->author);
811 if (author0 == NULL) {
812 err = got_error_from_errno();
816 smallerthan = strchr(author, '<');
820 char *at = strchr(author, '@');
825 err = format_line(&wauthor, &author_width, author, limit);
828 waddwstr(view->window, wauthor);
830 while (col <= avail && author_width < author_display_cols + 1) {
831 waddch(view->window, ' ');
838 logmsg0 = strdup(commit->logmsg);
839 if (logmsg0 == NULL) {
840 err = got_error_from_errno();
844 while (*logmsg == '\n')
846 newline = strchr(logmsg, '\n');
850 err = format_line(&wlogmsg, &logmsg_width, logmsg, limit);
853 waddwstr(view->window, wlogmsg);
855 while (col <= avail) {
856 waddch(view->window, ' ');
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));
879 entry->commit = commit;
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);
892 /* Don't free entry->id! It is owned by the commit graph. */
897 free_commits(struct commit_queue *commits)
899 while (!TAILQ_EMPTY(&commits->head))
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;
912 err = got_commit_graph_iter_start(graph, start_id, repo);
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);
924 if (err->code == GOT_ERR_ITER_COMPLETED) {
928 if (err->code != GOT_ERR_ITER_NEED_MORE)
930 err = got_commit_graph_fetch_commits(graph,
940 err = got_object_open_as_commit(&commit, repo, id);
943 entry = alloc_commit_queue_entry(commit, id);
945 err = got_error_from_errno();
949 TAILQ_INSERT_TAIL(&commits->head, entry, entry);
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,
963 const struct got_error *err = NULL;
967 err = queue_commits(graph, commits, NULL, 1, repo, path);
971 /* Next entry to display should now be available. */
972 *pentry = TAILQ_NEXT(entry, entry);
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;
984 err = got_ref_open(&head_ref, repo, GOT_REF_HEAD);
988 err = got_ref_resolve(head_id, repo, head_ref);
989 got_ref_close(head_ref);
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,
1005 const struct got_error *err = NULL;
1006 struct commit_queue_entry *entry;
1007 int ncommits, width;
1008 char *id_str, *header;
1014 if (ncommits == selected_idx) {
1018 entry = TAILQ_NEXT(entry, entry);
1022 err = got_object_id_str(&id_str, (*selected)->id);
1026 if (path && strcmp(path, "/") != 0) {
1027 if (asprintf(&header, "commit: %s [%s]", id_str, path) == -1) {
1028 err = got_error_from_errno();
1032 } else if (asprintf(&header, "commit: %s", id_str) == -1) {
1033 err = got_error_from_errno();
1038 err = format_line(&wline, &width, header, view->ncols);
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');
1062 if (ncommits >= limit - 1)
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);
1073 if (entry == TAILQ_LAST(&commits->head, commit_queue_head)) {
1074 err = queue_commits(graph, commits, NULL, 1,
1077 if (err->code != GOT_ERR_ITER_COMPLETED)
1082 entry = TAILQ_NEXT(entry, entry);
1091 scroll_up(struct commit_queue_entry **first_displayed_entry, int maxscroll,
1092 struct commit_queue *commits)
1094 struct commit_queue_entry *entry;
1097 entry = TAILQ_FIRST(&commits->head);
1098 if (*first_displayed_entry == entry)
1101 entry = *first_displayed_entry;
1102 while (entry && nscrolled < maxscroll) {
1103 entry = TAILQ_PREV(entry, commit_queue_head, entry);
1105 *first_displayed_entry = entry;
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;
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)
1129 *last_displayed_entry = pentry;
1131 pentry = TAILQ_NEXT(*first_displayed_entry, entry);
1134 *first_displayed_entry = pentry;
1135 } while (++nscrolled < maxscroll);
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);
1154 parent_id = SIMPLEQ_FIRST(&commit->parent_ids);
1156 err = got_object_open(&obj1, repo, parent_id->id);
1161 diff_view = view_open(0, 0, 0, begin_x, TOG_VIEW_DIFF);
1162 if (diff_view == NULL) {
1163 err = got_error_from_errno();
1167 err = open_diff_view(diff_view, obj1, obj2, repo);
1169 *new_view = diff_view;
1172 got_object_close(obj1);
1174 got_object_close(obj2);
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);
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);
1196 got_object_tree_close(tree);
1198 *new_view = tree_view;
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);
1213 err = got_commit_graph_open(&s->graph, start_id, s->in_repo_path,
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);
1230 if (err->code != GOT_ERR_ITER_COMPLETED)
1235 s->first_displayed_entry = TAILQ_FIRST(&s->commits.head);
1236 s->selected_entry = s->first_displayed_entry;
1238 s->start_id = got_object_id_dup(start_id);
1239 if (s->start_id == NULL) {
1240 err = got_error_from_errno();
1244 view->show = show_log_view;
1245 view->input = input_log_view;
1246 view->close = close_log_view;
1251 static const struct got_error *
1252 close_log_view(struct tog_view *view)
1254 struct tog_log_view_state *s = &view->state.log;
1257 got_commit_graph_close(s->graph);
1258 free_commits(&s->commits);
1259 free(s->in_repo_path);
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);
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;
1285 struct tog_view *diff_view = NULL, *tree_view = NULL;
1292 if (s->selected > 0)
1294 if (s->selected > 0)
1296 scroll_up(&s->first_displayed_entry, 1,
1300 if (TAILQ_FIRST(&s->commits.head) ==
1301 s->first_displayed_entry) {
1305 scroll_up(&s->first_displayed_entry,
1306 view->nlines, &s->commits);
1311 if (s->selected < MIN(view->nlines - 2,
1312 s->commits.ncommits - 1)) {
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);
1320 if (err->code != GOT_ERR_ITER_COMPLETED)
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,
1332 if (err && err->code != GOT_ERR_ITER_COMPLETED)
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);
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;
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,
1359 if (view_is_parent_view(view)) {
1360 err = view_close_child(view);
1363 err = view_set_child(view, diff_view);
1365 view_close(diff_view);
1368 if (!view_is_splitscreen(diff_view)) {
1369 *focus_view = diff_view;
1370 view->child_focussed = 1;
1373 *new_view = diff_view;
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);
1384 err = view_set_child(view, tree_view);
1386 view_close(tree_view);
1389 *focus_view = tree_view;
1390 view->child_focussed = 1;
1392 *new_view = tree_view;
1395 if (strcmp(s->in_repo_path, "/") == 0)
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);
1403 return got_error_from_errno();
1404 err = open_log_view(lv, s->start_id, s->repo,
1408 if (view_is_parent_view(view))
1411 view_set_child(view->parent, lv);
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;
1432 struct tog_view *view;
1435 if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd", NULL)
1440 while ((ch = getopt(argc, argv, "c:r:")) != -1) {
1443 start_commit = optarg;
1446 repo_path = realpath(optarg, NULL);
1447 if (repo_path == NULL)
1448 err(1, "-r option");
1462 path = strdup(argv[0]);
1466 return got_error_from_errno();
1468 cwd = getcwd(NULL, 0);
1470 error = got_error_from_errno();
1473 if (repo_path == NULL) {
1474 repo_path = strdup(cwd);
1475 if (repo_path == NULL) {
1476 error = got_error_from_errno();
1481 error = got_repo_open(&repo, repo_path);
1485 if (start_commit == NULL) {
1486 error = get_head_commit_id(&start_id, repo);
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();
1502 view = view_open(0, 0, 0, 0, TOG_VIEW_LOG);
1504 error = got_error_from_errno();
1507 error = open_log_view(view, start_id, repo, path);
1510 error = view_loop(view);
1517 got_repo_close(repo);
1525 fprintf(stderr, "usage: %s diff [repository-path] object1 object2\n",
1531 parse_next_line(FILE *f, size_t *len)
1536 const char delim[3] = { '\0', '\0', '\0'};
1538 line = fparseln(f, &linelen, &lineno, delim, 0);
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,
1549 const struct got_error *err;
1550 int nlines = 0, nprinted = 0;
1557 werase(view->window);
1560 err = format_line(&wline, &width, header, view->ncols);
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');
1579 while (nprinted < max_lines) {
1580 line = parse_next_line(f, &len);
1585 if (++nlines < *first_displayed_line) {
1590 err = format_line(&wline, &width, line, view->ncols);
1595 waddwstr(view->window, wline);
1596 if (width < view->ncols)
1597 waddch(view->window, '\n');
1598 if (++nprinted == 1)
1599 *first_displayed_line = nlines;
1604 *last_displayed_line = nlines;
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;
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);
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,
1631 case GOT_OBJ_TYPE_TREE:
1632 err = got_diff_objects_as_trees(obj1, obj2, "", "", 3, repo, f);
1634 case GOT_OBJ_TYPE_COMMIT:
1635 err = got_diff_objects_as_commits(obj1, obj2, 3, repo, f);
1638 return got_error(GOT_ERR_OBJ_TYPE);
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;
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();
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;
1674 err = got_object_id_str(&id_str1, s->id1);
1678 err = got_object_id_str(&id_str2, s->id2);
1682 if (asprintf(&header, "diff: %s %s",
1683 id_str1 ? id_str1 : "/dev/null", id_str2) == -1) {
1684 err = got_error_from_errno();
1692 return draw_file(view, s->f, &s->first_displayed_line,
1693 &s->last_displayed_line, &s->eof, view->nlines,
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;
1708 if (s->first_displayed_line > 1)
1709 s->first_displayed_line--;
1713 while (i++ < view->nlines - 1 &&
1714 s->first_displayed_line > 1)
1715 s->first_displayed_line--;
1720 s->first_displayed_line++;
1725 while (!s->eof && i++ < view->nlines - 1) {
1727 line = parse_next_line(s->f, NULL);
1728 s->first_displayed_line++;
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;
1749 struct tog_view *view;
1752 if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd", NULL)
1757 while ((ch = getopt(argc, argv, "")) != -1) {
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];
1785 error = got_repo_open(&repo, repo_path);
1790 error = got_object_open_by_id_str(&obj1, repo, obj_id_str1);
1794 error = got_object_open_by_id_str(&obj2, repo, obj_id_str2);
1798 view = view_open(0, 0, 0, 0, TOG_VIEW_DIFF);
1800 error = got_error_from_errno();
1803 error = open_diff_view(view, obj1, obj2, repo);
1806 error = view_loop(view);
1808 got_repo_close(repo);
1810 got_object_close(obj1);
1812 got_object_close(obj2);
1820 fprintf(stderr, "usage: %s blame [-c commit] [-r repository-path] path\n",
1825 struct tog_blame_line {
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;
1842 struct tog_blame_line *blame_line;
1843 struct got_object_id *prev_id = NULL;
1846 err = got_object_id_str(&id_str, id);
1851 werase(view->window);
1853 if (asprintf(&line, "commit: %s", id_str) == -1) {
1854 err = got_error_from_errno();
1859 err = format_line(&wline, &width, line, view->ncols);
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);
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) {
1876 return got_error_from_errno();
1879 err = format_line(&wline, &width, line, view->ncols);
1884 waddwstr(view->window, wline);
1887 if (width < view->ncols)
1888 waddch(view->window, '\n');
1891 while (nprinted < max_lines - 2) {
1892 line = parse_next_line(f, &len);
1897 if (++lineno < *first_displayed_line) {
1902 wlimit = view->ncols < 9 ? 0 : view->ncols - 9;
1903 err = format_line(&wline, &width, line, wlimit);
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) {
1918 err = got_object_id_str(&id_str, blame_line->id);
1924 wprintw(view->window, "%.8s ", id_str);
1926 prev_id = blame_line->id;
1928 waddstr(view->window, "........ ");
1932 waddwstr(view->window, wline);
1933 while (width < wlimit) {
1934 waddch(view->window, ' ');
1937 if (view->focussed && nprinted == selected_line - 1)
1938 wstandend(view->window);
1939 if (++nprinted == 1)
1940 *first_displayed_line = lineno;
1945 *last_displayed_line = lineno;
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);
1972 goto done; /* no change in this commit */
1974 line = &a->lines[lineno - 1];
1975 if (line->annotated)
1978 line->id = got_object_id_dup(id);
1979 if (line->id == NULL) {
1980 err = got_error_from_errno();
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);
1989 if (pthread_mutex_unlock(a->mutex) != 0)
1990 return got_error_from_errno();
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);
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,
2016 if (pthread_mutex_unlock(a->mutex) != 0 && err == NULL)
2017 err = got_error_from_errno();
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)
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;
2048 selected_id = get_selected_commit_id(lines,
2049 first_displayed_line, selected_line);
2050 if (selected_id == NULL)
2053 err = got_object_open(obj, repo, selected_id);
2057 err = got_object_commit_open(&commit, repo, *obj);
2061 pid = SIMPLEQ_FIRST(&commit->parent_ids);
2063 err = got_object_open(pobj, repo, pid->id);
2069 got_object_commit_close(commit);
2073 static const struct got_error *
2074 stop_blame(struct tog_blame *blame)
2076 const struct got_error *err = NULL;
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)
2084 blame->thread = NULL;
2086 if (blame->thread_args.repo) {
2087 got_repo_close(blame->thread_args.repo);
2088 blame->thread_args.repo = NULL;
2094 for (i = 0; i < blame->nlines; i++)
2095 free(blame->lines[i].id);
2097 blame->lines = NULL;
2098 free(blame->cb_args.commit_id);
2099 blame->cb_args.commit_id = NULL;
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);
2122 err = got_object_open(&obj, repo, obj_id);
2126 if (got_object_get_type(obj) != GOT_OBJ_TYPE_BLOB) {
2127 err = got_error(GOT_ERR_OBJ_TYPE);
2131 err = got_object_blob_open(&blob, repo, obj, 8192);
2134 blame->f = got_opentemp();
2135 if (blame->f == NULL) {
2136 err = got_error_from_errno();
2139 err = got_object_blob_dump_to_file(&blame->filesize, &blame->nlines,
2144 blame->lines = calloc(blame->nlines, sizeof(*blame->lines));
2145 if (blame->lines == NULL) {
2146 err = got_error_from_errno();
2150 err = got_repo_open(&thread_repo, got_repo_get_path(repo));
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();
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();
2185 got_object_blob_close(blob);
2188 got_object_close(obj);
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);
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;
2216 if (s->path == NULL)
2217 return got_error_from_errno();
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);
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();
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;
2284 if (pthread_mutex_lock(&s->mutex) != 0) {
2285 err = got_error_from_errno();
2292 if (pthread_mutex_unlock(&s->mutex) != 0) {
2293 err = got_error_from_errno();
2296 return stop_blame(&s->blame);
2299 if (s->selected_line > 1)
2301 else if (s->selected_line == 1 &&
2302 s->first_displayed_line > 1)
2303 s->first_displayed_line--;
2306 if (s->first_displayed_line == 1) {
2307 s->selected_line = 1;
2310 if (s->first_displayed_line > view->nlines - 2)
2311 s->first_displayed_line -=
2314 s->first_displayed_line = 1;
2318 if (s->selected_line < view->nlines - 2 &&
2319 s->first_displayed_line +
2320 s->selected_line <= s->blame.nlines)
2322 else if (s->last_displayed_line <
2324 s->first_displayed_line++;
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)
2334 err = open_selected_commit(&pobj, &obj,
2335 s->blame.lines, s->first_displayed_line,
2336 s->selected_line, s->repo);
2339 if (pobj == NULL && obj == NULL)
2341 if (ch == 'p' && pobj == NULL)
2344 if (pthread_mutex_unlock(&s->mutex) != 0) {
2345 err = got_error_from_errno();
2348 thread_err = stop_blame(&s->blame);
2350 if (pthread_mutex_lock(&s->mutex) != 0) {
2351 err = got_error_from_errno();
2356 id = got_object_get_id(ch == 'b' ? obj : pobj);
2357 got_object_close(obj);
2360 got_object_close(pobj);
2363 err = got_object_qid_alloc(&s->blamed_commit, id);
2366 SIMPLEQ_INSERT_HEAD(&s->blamed_commits,
2367 s->blamed_commit, entry);
2368 err = run_blame(&s->blame, &s->mutex, view,
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);
2379 struct got_object_qid *first;
2380 first = SIMPLEQ_FIRST(&s->blamed_commits);
2381 if (!got_object_id_cmp(first->id, s->commit_id))
2384 if (pthread_mutex_unlock(&s->mutex) != 0) {
2385 err = got_error_from_errno();
2388 thread_err = stop_blame(&s->blame);
2390 if (pthread_mutex_lock(&s->mutex) != 0) {
2391 err = got_error_from_errno();
2396 SIMPLEQ_REMOVE_HEAD(&s->blamed_commits, entry);
2397 got_object_qid_free(s->blamed_commit);
2399 SIMPLEQ_FIRST(&s->blamed_commits);
2400 err = run_blame(&s->blame, &s->mutex, view,
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);
2412 err = open_selected_commit(&pobj, &obj,
2413 s->blame.lines, s->first_displayed_line,
2414 s->selected_line, s->repo);
2417 if (pobj == NULL && obj == NULL)
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();
2427 err = open_diff_view(diff_view, pobj, obj, s->repo);
2429 view_close(diff_view);
2432 if (view_is_parent_view(view)) {
2433 err = view_close_child(view);
2436 err = view_set_child(view, diff_view);
2438 view_close(diff_view);
2441 if (!view_is_splitscreen(diff_view)) {
2442 *focus_view = diff_view;
2443 view->child_focussed = 1;
2446 *new_view = diff_view;
2448 got_object_close(pobj);
2451 got_object_close(obj);
2458 if (s->last_displayed_line >= s->blame.nlines &&
2459 s->selected_line < view->nlines - 2) {
2460 s->selected_line = MIN(s->blame.nlines,
2464 if (s->last_displayed_line + view->nlines - 2
2466 s->first_displayed_line +=
2469 s->first_displayed_line =
2474 if (s->selected_line > view->nlines - 2) {
2475 s->selected_line = MIN(s->blame.nlines,
2483 if (pthread_mutex_unlock(&s->mutex) != 0)
2484 err = got_error_from_errno();
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;
2500 struct tog_view *view;
2503 if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd", NULL)
2508 while ((ch = getopt(argc, argv, "c:r:")) != -1) {
2511 commit_id_str = optarg;
2514 repo_path = realpath(optarg, NULL);
2515 if (repo_path == NULL)
2516 err(1, "-r option");
2532 cwd = getcwd(NULL, 0);
2534 error = got_error_from_errno();
2537 if (repo_path == NULL) {
2538 repo_path = strdup(cwd);
2539 if (repo_path == NULL) {
2540 error = got_error_from_errno();
2546 error = got_repo_open(&repo, repo_path);
2550 error = got_repo_map_path(&in_repo_path, repo, path);
2554 if (commit_id_str == NULL) {
2555 struct got_reference *head_ref;
2556 error = got_ref_open(&head_ref, repo, GOT_REF_HEAD);
2559 error = got_ref_resolve(&commit_id, repo, head_ref);
2560 got_ref_close(head_ref);
2562 struct got_object *obj;
2563 error = got_object_open_by_id_str(&obj, repo, commit_id_str);
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);
2574 view = view_open(0, 0, 0, 0, TOG_VIEW_BLAME);
2576 error = got_error_from_errno();
2579 error = open_blame_view(view, in_repo_path, commit_id, repo);
2582 error = view_loop(view);
2588 got_repo_close(repo);
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;
2607 werase(view->window);
2612 err = format_line(&wline, &width, label, view->ncols);
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);
2622 if (width < view->ncols)
2623 waddch(view->window, '\n');
2626 err = format_line(&wline, &width, parent_path, view->ncols);
2629 waddwstr(view->window, wline);
2632 if (width < view->ncols)
2633 waddch(view->window, '\n');
2636 waddch(view->window, '\n');
2640 te = SIMPLEQ_FIRST(&entries->head);
2641 if (*first_displayed_entry == NULL) {
2642 if (selected == 0) {
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);
2656 while (te != *first_displayed_entry)
2657 te = SIMPLEQ_NEXT(te, entry);
2661 char *line = NULL, *id_str = NULL;
2664 err = got_object_id_str(&id_str, te->id);
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) {
2671 return got_error_from_errno();
2674 err = format_line(&wline, &width, line, view->ncols);
2679 if (n == selected) {
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);
2694 *last_displayed_entry = te;
2697 te = SIMPLEQ_NEXT(te, entry);
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;
2710 if (*first_displayed_entry == NULL)
2713 te = SIMPLEQ_FIRST(&entries->head);
2714 if (*first_displayed_entry == te) {
2716 *first_displayed_entry = NULL;
2720 /* XXX this is stupid... switch to TAILQ? */
2721 for (i = 0; i < maxscroll; i++) {
2722 while (te != *first_displayed_entry) {
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;
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;
2741 if (SIMPLEQ_NEXT(last_displayed_entry, entry) == NULL)
2744 if (*first_displayed_entry)
2745 next = SIMPLEQ_NEXT(*first_displayed_entry, entry);
2747 next = SIMPLEQ_FIRST(&entries->head);
2749 *first_displayed_entry = next;
2750 if (++n >= maxscroll)
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 */;
2767 len += strlen(te->name);
2769 *path = calloc(1, len);
2771 return got_error_from_errno();
2774 pt = TAILQ_LAST(parents, tog_parent_trees);
2776 if (strlcat(*path, pt->selected_entry->name, len) >= len) {
2777 err = got_error(GOT_ERR_NO_SPACE);
2780 if (strlcat(*path, "/", len) >= len) {
2781 err = got_error(GOT_ERR_NO_SPACE);
2784 pt = TAILQ_PREV(pt, tog_parent_trees, entry);
2787 if (strlcat(*path, te->name, len) >= len) {
2788 err = got_error(GOT_ERR_NO_SPACE);
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;
2807 struct tog_view *blame_view;
2809 err = tree_entry_path(&path, parents, te);
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);
2819 view_close(blame_view);
2822 *new_view = blame_view;
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;
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);
2843 err = open_log_view(log_view, commit_id, repo, path);
2845 view_close(log_view);
2847 *new_view = log_view;
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);
2866 if (asprintf(&s->tree_label, "commit: %s", commit_id_str) == -1) {
2867 err = got_error_from_errno();
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();
2881 view->show = show_tree_view;
2882 view->input = input_tree_view;
2883 view->close = close_tree_view;
2885 free(commit_id_str);
2887 free(s->tree_label);
2888 s->tree_label = NULL;
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;
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);
2909 if (s->tree != s->root)
2910 got_object_tree_close(s->tree);
2911 got_object_tree_close(s->root);
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;
2923 err = tree_entry_path(&parent_path, &s->parents, NULL);
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);
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;
2948 s->show_ids = !s->show_ids;
2951 if (!s->selected_entry)
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);
2962 err = view_set_child(view, log_view);
2964 view_close(log_view);
2967 *focus_view = log_view;
2968 view->child_focussed = 1;
2970 *new_view = log_view;
2974 if (s->selected > 0)
2976 if (s->selected > 0)
2978 tree_scroll_up(&s->first_displayed_entry, 1,
2979 s->entries, s->tree == s->root);
2982 if (SIMPLEQ_FIRST(&s->entries->head) ==
2983 s->first_displayed_entry) {
2984 if (s->tree != s->root)
2985 s->first_displayed_entry = NULL;
2989 tree_scroll_up(&s->first_displayed_entry,
2990 view->nlines, s->entries,
2991 s->tree == s->root);
2995 if (s->selected < s->ndisplayed - 1) {
2999 tree_scroll_down(&s->first_displayed_entry, 1,
3000 s->last_displayed_entry, s->entries);
3003 tree_scroll_down(&s->first_displayed_entry,
3004 view->nlines, s->last_displayed_entry,
3006 if (SIMPLEQ_NEXT(s->last_displayed_entry,
3009 /* can't scroll any further; move cursor down */
3010 if (s->selected < s->ndisplayed - 1)
3011 s->selected = s->ndisplayed - 1;
3015 if (s->selected_entry == NULL) {
3016 struct tog_parent_tree *parent;
3018 /* user selected '..' */
3019 if (s->tree == s->root)
3021 parent = TAILQ_FIRST(&s->parents);
3022 TAILQ_REMOVE(&s->parents, parent,
3024 got_object_tree_close(s->tree);
3025 s->tree = parent->tree;
3027 got_object_tree_get_entries(s->tree);
3028 s->first_displayed_entry =
3029 parent->first_displayed_entry;
3031 parent->selected_entry;
3032 s->selected = parent->selected;
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);
3041 parent = calloc(1, sizeof(*parent));
3042 if (parent == NULL) {
3043 err = got_error_from_errno();
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);
3054 got_object_tree_get_entries(s->tree);
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,
3067 if (view_is_parent_view(view)) {
3068 err = view_close_child(view);
3071 err = view_set_child(view, blame_view);
3073 view_close(blame_view);
3076 *focus_view = blame_view;
3077 view->child_focussed = 1;
3079 *new_view = blame_view;
3083 if (s->selected > view->nlines)
3084 s->selected = s->ndisplayed - 1;
3097 fprintf(stderr, "usage: %s tree [-c commit] [repository-path]\n",
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;
3113 struct tog_view *view;
3116 if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd", NULL)
3121 while ((ch = getopt(argc, argv, "c:")) != -1) {
3124 commit_id_arg = optarg;
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();
3146 error = got_repo_open(&repo, repo_path);
3151 if (commit_id_arg == NULL) {
3152 error = get_head_commit_id(&commit_id, repo);
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();
3167 error = got_object_open_as_commit(&commit, repo, commit_id);
3171 error = got_object_open_as_tree(&tree, repo, commit->tree_id);
3175 view = view_open(0, 0, 0, 0, TOG_VIEW_TREE);
3177 error = got_error_from_errno();
3180 error = open_tree_view(view, tree, commit_id, repo);
3183 error = view_loop(view);
3187 got_object_commit_close(commit);
3189 got_object_tree_close(tree);
3191 got_repo_close(repo);
3201 intrflush(stdscr, FALSE);
3202 keypad(stdscr, TRUE);
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);
3221 make_argv(const char *arg0, const char *arg1)
3224 int argc = (arg1 == NULL ? 1 : 2);
3226 argv = calloc(argc, sizeof(char *));
3229 argv[0] = strdup(arg0);
3230 if (argv[0] == NULL)
3233 argv[1] = strdup(arg1);
3234 if (argv[1] == NULL)
3242 main(int argc, char *argv[])
3244 const struct got_error *error = NULL;
3245 struct tog_cmd *cmd = NULL;
3247 char **cmd_argv = NULL;
3249 setlocale(LC_ALL, "");
3251 while ((ch = getopt(argc, argv, "h")) != -1) {
3270 /* Build an argument vector which runs a default command. */
3271 cmd = &tog_commands[0];
3272 cmd_argv = make_argv(cmd->name, NULL);
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];
3283 tog_commands[i].cmd_usage();
3288 /* Did the user specify a repository? */
3289 char *repo_path = realpath(argv[0], NULL);
3291 struct got_repository *repo;
3292 error = got_repo_open(&repo, repo_path);
3294 got_repo_close(repo);
3296 error = got_error_from_errno();
3299 fprintf(stderr, "%s: '%s' is not a "
3300 "known command\n", getprogname(),
3304 fprintf(stderr, "%s: '%s' is neither a known "
3305 "command nor a path to a repository\n",
3306 getprogname(), argv[0]);
3310 cmd = &tog_commands[0];
3311 cmd_argv = make_argv(cmd->name, repo_path);
3319 error = cmd->cmd_main(argc, cmd_argv ? cmd_argv : argv);
3326 fprintf(stderr, "%s: %s\n", getprogname(), error->msg);