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
38 #include "got_error.h"
39 #include "got_object.h"
40 #include "got_reference.h"
41 #include "got_repository.h"
43 #include "got_opentemp.h"
44 #include "got_commit_graph.h"
46 #include "got_blame.h"
49 #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b))
53 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
58 const struct got_error *(*cmd_main)(int, char *[]);
59 void (*cmd_usage)(void);
63 __dead static void usage(void);
64 __dead static void usage_log(void);
65 __dead static void usage_diff(void);
66 __dead static void usage_blame(void);
67 __dead static void usage_tree(void);
69 static const struct got_error* cmd_log(int, char *[]);
70 static const struct got_error* cmd_diff(int, char *[]);
71 static const struct got_error* cmd_blame(int, char *[]);
72 static const struct got_error* cmd_tree(int, char *[]);
74 static struct tog_cmd tog_commands[] = {
75 { "log", cmd_log, usage_log,
76 "show repository history" },
77 { "diff", cmd_diff, usage_diff,
78 "compare files and directories" },
79 { "blame", cmd_blame, usage_blame,
80 "show line-by-line file history" },
81 { "tree", cmd_tree, usage_tree,
82 "browse trees in repository" },
92 struct tog_diff_view_state {
94 int first_displayed_line;
95 int last_displayed_line;
98 struct commit_queue_entry {
99 TAILQ_ENTRY(commit_queue_entry) entry;
100 struct got_object_id *id;
101 struct got_commit_object *commit;
103 TAILQ_HEAD(commit_queue_head, commit_queue_entry);
104 struct commit_queue {
106 struct commit_queue_head head;
109 struct tog_log_view_state {
110 struct got_commit_graph *graph;
111 struct commit_queue commits;
112 struct commit_queue_entry *first_displayed_entry;
113 struct commit_queue_entry *last_displayed_entry;
114 struct commit_queue_entry *selected_entry;
117 struct got_repository *repo;
120 struct tog_blame_view_state {
121 int first_displayed_line;
122 int last_displayed_line;
125 pthread_mutex_t mutex;
126 struct got_object_id_queue blamed_commits;
127 struct got_object_qid *blamed_commit;
129 struct got_repository *repo;
130 struct got_object_id *commit_id;
133 struct tog_parent_tree {
134 TAILQ_ENTRY(tog_parent_tree) entry;
135 struct got_tree_object *tree;
136 struct got_tree_entry *first_displayed_entry;
137 struct got_tree_entry *selected_entry;
141 TAILQ_HEAD(tog_parent_trees, tog_parent_tree);
143 struct tog_tree_view_state {
145 struct got_tree_object *root;
146 struct got_tree_object *tree;
147 const struct got_tree_entries *entries;
148 struct got_tree_entry *first_displayed_entry;
149 struct got_tree_entry *last_displayed_entry;
150 struct got_tree_entry *selected_entry;
151 int nentries, ndisplayed, selected, show_ids;
152 struct tog_parent_trees parents;
153 struct got_object_id *commit_id;
154 struct got_repository *repo;
160 int nlines, ncols, begin_y, begin_x;
161 int lines, cols; /* copies of LINES and COLS */
162 struct tog_view *parent;
164 /* type-specific state */
165 enum tog_view_type type;
167 struct tog_diff_view_state diff;
168 struct tog_log_view_state log;
169 struct tog_blame_view_state blame;
170 struct tog_tree_view_state tree;
174 static const struct got_error *open_diff_view(struct tog_view *,
175 struct got_object *, struct got_object *, struct got_repository *);
176 static const struct got_error *show_diff_view(struct tog_view *);
177 static void close_diff_view(struct tog_view *);
178 static const struct got_error *open_log_view(struct tog_view *,
179 struct got_object_id *, struct got_repository *, const char *);
180 static const struct got_error * show_log_view(struct tog_view *);
181 static void close_log_view(struct tog_view *);
182 static const struct got_error *open_blame_view(struct tog_view *, const char *,
183 struct got_object_id *, struct got_repository *);
184 static const struct got_error *show_blame_view(struct tog_view *);
185 static void close_blame_view(struct tog_view *);
186 static const struct got_error *open_tree_view(struct tog_view *,
187 struct got_tree_object *, struct got_object_id *, struct got_repository *);
188 static const struct got_error *show_tree_view(struct tog_view *);
189 static void close_tree_view(struct tog_view *);
192 view_close(struct tog_view *view)
195 del_panel(view->panel);
197 delwin(view->window);
201 static struct tog_view *
202 view_open(int nlines, int ncols, int begin_y, int begin_x,
203 struct tog_view *parent, enum tog_view_type type)
205 struct tog_view *view = calloc(1, sizeof(*view));
210 view->parent = parent;
214 view->nlines = nlines ? nlines : LINES - begin_y;
215 view->ncols = ncols ? ncols : COLS - begin_x;
216 view->begin_y = begin_y;
217 view->begin_x = begin_x;
218 view->window = newwin(nlines, ncols, begin_y, begin_x);
219 if (view->window == NULL) {
223 view->panel = new_panel(view->window);
224 if (view->panel == NULL) {
229 keypad(view->window, TRUE);
234 view_show(struct tog_view *view)
236 show_panel(view->panel);
240 const struct got_error *
241 view_resize(struct tog_view *view)
246 if (view->lines > LINES)
247 nlines = view->nlines - (view->lines - LINES);
249 nlines = view->nlines + (LINES - view->lines);
251 if (view->cols > COLS)
252 ncols = view->ncols - (view->cols - COLS);
254 ncols = view->ncols + (COLS - view->cols);
256 if (wresize(view->window, nlines, ncols) == ERR)
257 return got_error_from_errno();
258 replace_panel(view->panel, view->window);
260 view->nlines = nlines;
276 "usage: %s log [-c commit] [-r repository-path] [path]\n",
281 /* Create newly allocated wide-character string equivalent to a byte string. */
282 static const struct got_error *
283 mbs2ws(wchar_t **ws, size_t *wlen, const char *s)
286 const struct got_error *err = NULL;
289 *wlen = mbstowcs(NULL, s, 0);
290 if (*wlen == (size_t)-1) {
293 return got_error_from_errno();
295 /* byte string invalid in current encoding; try to "fix" it */
296 err = got_mbsavis(&vis, &vislen, s);
299 *wlen = mbstowcs(NULL, vis, 0);
300 if (*wlen == (size_t)-1) {
301 err = got_error_from_errno(); /* give up */
306 *ws = calloc(*wlen + 1, sizeof(*ws));
308 err = got_error_from_errno();
312 if (mbstowcs(*ws, vis ? vis : s, *wlen) != *wlen)
313 err = got_error_from_errno();
324 /* Format a line for display, ensuring that it won't overflow a width limit. */
325 static const struct got_error *
326 format_line(wchar_t **wlinep, int *widthp, const char *line, int wlimit)
328 const struct got_error *err = NULL;
330 wchar_t *wline = NULL;
337 err = mbs2ws(&wline, &wlen, line);
342 while (i < wlen && cols < wlimit) {
343 int width = wcwidth(wline[i]);
350 if (cols + width <= wlimit) {
356 if (wline[i] == L'\t')
357 cols += TABSIZE - ((cols + 1) % TABSIZE);
361 err = got_error_from_errno();
376 static const struct got_error *
377 draw_commit(struct tog_view *view, struct got_commit_object *commit,
378 struct got_object_id *id)
380 const struct got_error *err = NULL;
381 char datebuf[10]; /* YY-MM-DD + SPACE + NUL */
382 char *logmsg0 = NULL, *logmsg = NULL;
383 char *author0 = NULL, *author = NULL;
384 wchar_t *wlogmsg = NULL, *wauthor = NULL;
385 int author_width, logmsg_width;
386 char *newline, *smallerthan;
389 static const size_t date_display_cols = 9;
390 static const size_t author_display_cols = 16;
391 const int avail = view->ncols;
393 if (strftime(datebuf, sizeof(datebuf), "%g/%m/%d ",
394 &commit->tm_committer) >= sizeof(datebuf))
395 return got_error(GOT_ERR_NO_SPACE);
397 if (avail < date_display_cols)
398 limit = MIN(sizeof(datebuf) - 1, avail);
400 limit = MIN(date_display_cols, sizeof(datebuf) - 1);
401 waddnstr(view->window, datebuf, limit);
406 author0 = strdup(commit->author);
407 if (author0 == NULL) {
408 err = got_error_from_errno();
412 smallerthan = strchr(author, '<');
416 char *at = strchr(author, '@');
421 err = format_line(&wauthor, &author_width, author, limit);
424 waddwstr(view->window, wauthor);
426 while (col <= avail && author_width < author_display_cols + 1) {
427 waddch(view->window, ' ');
434 logmsg0 = strdup(commit->logmsg);
435 if (logmsg0 == NULL) {
436 err = got_error_from_errno();
440 while (*logmsg == '\n')
442 newline = strchr(logmsg, '\n');
446 err = format_line(&wlogmsg, &logmsg_width, logmsg, limit);
449 waddwstr(view->window, wlogmsg);
451 while (col <= avail) {
452 waddch(view->window, ' ');
464 static struct commit_queue_entry *
465 alloc_commit_queue_entry(struct got_commit_object *commit,
466 struct got_object_id *id)
468 struct commit_queue_entry *entry;
470 entry = calloc(1, sizeof(*entry));
475 entry->commit = commit;
480 pop_commit(struct commit_queue *commits)
482 struct commit_queue_entry *entry;
484 entry = TAILQ_FIRST(&commits->head);
485 TAILQ_REMOVE(&commits->head, entry, entry);
486 got_object_commit_close(entry->commit);
488 /* Don't free entry->id! It is owned by the commit graph. */
493 free_commits(struct commit_queue *commits)
495 while (!TAILQ_EMPTY(&commits->head))
499 static const struct got_error *
500 queue_commits(struct got_commit_graph *graph, struct commit_queue *commits,
501 struct got_object_id *start_id, int minqueue, int init,
502 struct got_repository *repo, const char *path)
504 const struct got_error *err = NULL;
505 struct got_object_id *id;
506 struct commit_queue_entry *entry;
507 int nfetched, nqueued = 0, found_obj = 0;
508 int is_root_path = strcmp(path, "/") == 0;
510 err = got_commit_graph_iter_start(graph, start_id);
514 entry = TAILQ_LAST(&commits->head, commit_queue_head);
515 if (entry && got_object_id_cmp(entry->id, start_id) == 0) {
518 /* Start ID's commit is already on the queue; skip over it. */
519 err = got_commit_graph_iter_next(&id, graph);
520 if (err && err->code != GOT_ERR_ITER_NEED_MORE)
523 err = got_commit_graph_fetch_commits(&nfetched, graph, 1, repo);
529 struct got_commit_object *commit;
531 err = got_commit_graph_iter_next(&id, graph);
533 if (err->code != GOT_ERR_ITER_NEED_MORE)
535 if (nqueued >= minqueue) {
539 err = got_commit_graph_fetch_commits(&nfetched,
548 err = got_object_open_as_commit(&commit, repo, id);
553 struct got_object *obj;
554 struct got_object_qid *pid;
557 err = got_object_open_by_path(&obj, repo, id, path);
559 got_object_commit_close(commit);
560 if (err->code == GOT_ERR_NO_OBJ &&
561 (found_obj || !init)) {
562 /* History stops here. */
563 err = got_error(GOT_ERR_ITER_COMPLETED);
569 pid = SIMPLEQ_FIRST(&commit->parent_ids);
571 struct got_object *pobj;
572 err = got_object_open_by_path(&pobj, repo,
575 if (err->code != GOT_ERR_NO_OBJ) {
576 got_object_close(obj);
577 got_object_commit_close(commit);
583 struct got_object_id *id, *pid;
584 id = got_object_get_id(obj);
586 err = got_error_from_errno();
587 got_object_close(obj);
588 got_object_close(pobj);
591 pid = got_object_get_id(pobj);
593 err = got_error_from_errno();
595 got_object_close(obj);
596 got_object_close(pobj);
600 (got_object_id_cmp(id, pid) != 0);
601 got_object_close(pobj);
606 got_object_close(obj);
608 got_object_commit_close(commit);
613 entry = alloc_commit_queue_entry(commit, id);
615 err = got_error_from_errno();
618 TAILQ_INSERT_TAIL(&commits->head, entry, entry);
626 static const struct got_error *
627 fetch_next_commit(struct commit_queue_entry **pentry,
628 struct commit_queue_entry *entry, struct commit_queue *commits,
629 struct got_commit_graph *graph, struct got_repository *repo,
632 const struct got_error *err = NULL;
636 err = queue_commits(graph, commits, entry->id, 1, 0, repo, path);
640 /* Next entry to display should now be available. */
641 *pentry = TAILQ_NEXT(entry, entry);
643 return got_error(GOT_ERR_NO_OBJ);
648 static const struct got_error *
649 get_head_commit_id(struct got_object_id **head_id, struct got_repository *repo)
651 const struct got_error *err = NULL;
652 struct got_reference *head_ref;
656 err = got_ref_open(&head_ref, repo, GOT_REF_HEAD);
660 err = got_ref_resolve(head_id, repo, head_ref);
661 got_ref_close(head_ref);
670 static const struct got_error *
671 draw_commits(struct tog_view *view, struct commit_queue_entry **last,
672 struct commit_queue_entry **selected, struct commit_queue_entry *first,
673 struct commit_queue *commits, int selected_idx, int limit,
674 struct got_commit_graph *graph, struct got_repository *repo,
677 const struct got_error *err = NULL;
678 struct commit_queue_entry *entry;
680 char *id_str, *header;
686 if (ncommits == selected_idx) {
690 entry = TAILQ_NEXT(entry, entry);
694 err = got_object_id_str(&id_str, (*selected)->id);
698 if (path && strcmp(path, "/") != 0) {
699 if (asprintf(&header, "commit: %s [%s]", id_str, path) == -1) {
700 err = got_error_from_errno();
704 } else if (asprintf(&header, "commit: %s", id_str) == -1) {
705 err = got_error_from_errno();
710 err = format_line(&wline, &width, header, view->ncols);
717 werase(view->window);
719 waddwstr(view->window, wline);
720 if (width < view->ncols)
721 waddch(view->window, '\n');
730 if (ncommits >= limit - 1)
732 if (ncommits == selected_idx)
733 wstandout(view->window);
734 err = draw_commit(view, entry->commit, entry->id);
735 if (ncommits == selected_idx)
736 wstandend(view->window);
741 if (entry == TAILQ_LAST(&commits->head, commit_queue_head)) {
742 err = queue_commits(graph, commits, entry->id, 1,
745 if (err->code != GOT_ERR_ITER_COMPLETED)
750 entry = TAILQ_NEXT(entry, entry);
760 scroll_up(struct commit_queue_entry **first_displayed_entry, int maxscroll,
761 struct commit_queue *commits)
763 struct commit_queue_entry *entry;
766 entry = TAILQ_FIRST(&commits->head);
767 if (*first_displayed_entry == entry)
770 entry = *first_displayed_entry;
771 while (entry && nscrolled < maxscroll) {
772 entry = TAILQ_PREV(entry, commit_queue_head, entry);
774 *first_displayed_entry = entry;
780 static const struct got_error *
781 scroll_down(struct commit_queue_entry **first_displayed_entry, int maxscroll,
782 struct commit_queue_entry *last_displayed_entry,
783 struct commit_queue *commits, struct got_commit_graph *graph,
784 struct got_repository *repo, const char *path)
786 const struct got_error *err = NULL;
787 struct commit_queue_entry *pentry;
791 pentry = TAILQ_NEXT(last_displayed_entry, entry);
792 if (pentry == NULL) {
793 err = fetch_next_commit(&pentry, last_displayed_entry,
794 commits, graph, repo, path);
795 if (err || pentry == NULL)
798 last_displayed_entry = pentry;
800 pentry = TAILQ_NEXT(*first_displayed_entry, entry);
803 *first_displayed_entry = pentry;
804 } while (++nscrolled < maxscroll);
809 static const struct got_error *
810 show_commit(struct tog_view *parent_view, struct commit_queue_entry *entry,
811 struct got_repository *repo)
813 const struct got_error *err;
814 struct got_object *obj1 = NULL, *obj2 = NULL;
815 struct got_object_qid *parent_id;
816 struct tog_view *view;
818 err = got_object_open(&obj2, repo, entry->id);
822 parent_id = SIMPLEQ_FIRST(&entry->commit->parent_ids);
824 err = got_object_open(&obj1, repo, parent_id->id);
829 view = view_open(0, 0, 0, 0, parent_view, TOG_VIEW_DIFF);
831 err = got_error_from_errno();
835 err = open_diff_view(view, obj1, obj2, repo);
838 err = show_diff_view(view);
839 close_diff_view(view);
841 view_show(parent_view);
844 got_object_close(obj1);
846 got_object_close(obj2);
850 static const struct got_error *
851 browse_commit(struct tog_view *parent_view, struct commit_queue_entry *entry,
852 struct got_repository *repo)
854 const struct got_error *err = NULL;
855 struct got_tree_object *tree;
856 struct tog_view *view;
858 err = got_object_open_as_tree(&tree, repo, entry->commit->tree_id);
862 view = view_open(0, 0, 0, 0, parent_view, TOG_VIEW_TREE);
864 err = got_error_from_errno();
867 err = open_tree_view(view, tree, entry->id, repo);
870 err = show_tree_view(view);
871 close_tree_view(view);
873 view_show(parent_view);
875 got_object_tree_close(tree);
879 static const struct got_error *
880 open_log_view(struct tog_view *view, struct got_object_id *start_id,
881 struct got_repository *repo, const char *path)
883 const struct got_error *err = NULL;
884 struct got_object_id *head_id = NULL;
886 struct tog_log_view_state *state = &view->state.log;
888 err = got_repo_map_path(&state->in_repo_path, repo, path);
892 err = get_head_commit_id(&head_id, repo);
896 /* The graph contains all commits. */
897 err = got_commit_graph_open(&state->graph, head_id, 0, repo);
900 /* The commit queue contains a subset of commits filtered by path. */
901 TAILQ_INIT(&state->commits.head);
902 state->commits.ncommits = 0;
904 /* Populate commit graph with a sufficient number of commits. */
905 err = got_commit_graph_fetch_commits_up_to(&nfetched,
906 state->graph, start_id, repo);
911 * Open the initial batch of commits, sorted in commit graph order.
912 * We keep all commits open throughout the lifetime of the log view
913 * in order to avoid having to re-fetch commits from disk while
914 * updating the display.
916 err = queue_commits(state->graph, &state->commits, start_id,
917 view->nlines, 1, repo, state->in_repo_path);
919 if (err->code != GOT_ERR_ITER_COMPLETED)
930 static void close_log_view(struct tog_view *view)
932 struct tog_log_view_state *state = &view->state.log;
935 got_commit_graph_close(state->graph);
936 free_commits(&state->commits);
937 free(state->in_repo_path);
940 static const struct got_error *
941 show_log_view(struct tog_view *view)
943 const struct got_error *err = NULL;
945 struct tog_log_view_state *state = &view->state.log;
949 state->first_displayed_entry =
950 TAILQ_FIRST(&state->commits.head);
951 state->selected_entry = state->first_displayed_entry;
953 err = draw_commits(view, &state->last_displayed_entry,
954 &state->selected_entry, state->first_displayed_entry,
955 &state->commits, state->selected, view->nlines,
956 state->graph, state->repo, state->in_repo_path);
960 nodelay(stdscr, FALSE);
961 ch = wgetch(view->window);
962 nodelay(stdscr, TRUE);
971 if (state->selected > 0)
973 if (state->selected > 0)
975 scroll_up(&state->first_displayed_entry, 1,
979 if (TAILQ_FIRST(&state->commits.head) ==
980 state->first_displayed_entry) {
984 scroll_up(&state->first_displayed_entry,
985 view->nlines, &state->commits);
989 if (state->selected < MIN(view->nlines - 2,
990 state->commits.ncommits - 1)) {
994 err = scroll_down(&state->first_displayed_entry,
995 1, state->last_displayed_entry,
996 &state->commits, state->graph, state->repo,
997 state->in_repo_path);
999 if (err->code != GOT_ERR_ITER_COMPLETED)
1005 struct commit_queue_entry *first;
1006 first = state->first_displayed_entry;
1007 err = scroll_down(&state->first_displayed_entry,
1008 view->nlines, state->last_displayed_entry,
1009 &state->commits, state->graph, state->repo,
1010 state->in_repo_path);
1013 if (err->code != GOT_ERR_ITER_COMPLETED)
1015 if (first == state->first_displayed_entry &&
1016 state->selected < MIN(view->nlines - 2,
1017 state->commits.ncommits - 1)) {
1018 /* can't scroll further down */
1019 state->selected = MIN(view->nlines - 2,
1020 state->commits.ncommits - 1);
1026 err = view_resize(view);
1029 if (state->selected > view->nlines - 2)
1030 state->selected = view->nlines - 2;
1031 if (state->selected >
1032 state->commits.ncommits - 1)
1034 state->commits.ncommits - 1;
1038 err = show_commit(view, state->selected_entry,
1045 err = browse_commit(view, state->selected_entry,
1059 static const struct got_error *
1060 cmd_log(int argc, char *argv[])
1062 const struct got_error *error;
1063 struct got_repository *repo = NULL;
1064 struct got_object_id *start_id = NULL;
1065 char *path = NULL, *repo_path = NULL, *cwd = NULL;
1066 char *start_commit = NULL;
1068 struct tog_view *view;
1071 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
1075 while ((ch = getopt(argc, argv, "c:r:")) != -1) {
1078 start_commit = optarg;
1081 repo_path = realpath(optarg, NULL);
1082 if (repo_path == NULL)
1083 err(1, "-r option");
1097 path = strdup(argv[0]);
1101 return got_error_from_errno();
1103 cwd = getcwd(NULL, 0);
1105 error = got_error_from_errno();
1108 if (repo_path == NULL) {
1109 repo_path = strdup(cwd);
1110 if (repo_path == NULL) {
1111 error = got_error_from_errno();
1116 error = got_repo_open(&repo, repo_path);
1120 if (start_commit == NULL) {
1121 error = get_head_commit_id(&start_id, repo);
1125 struct got_object *obj;
1126 error = got_object_open_by_id_str(&obj, repo, start_commit);
1127 if (error == NULL) {
1128 start_id = got_object_get_id(obj);
1129 if (start_id == NULL)
1130 error = got_error_from_errno();
1137 view = view_open(0, 0, 0, 0, NULL, TOG_VIEW_LOG);
1139 error = got_error_from_errno();
1142 error = open_log_view(view, start_id, repo, path);
1145 error = show_log_view(view);
1146 close_log_view(view);
1154 got_repo_close(repo);
1162 fprintf(stderr, "usage: %s diff [repository-path] object1 object2\n",
1168 parse_next_line(FILE *f, size_t *len)
1173 const char delim[3] = { '\0', '\0', '\0'};
1175 line = fparseln(f, &linelen, &lineno, delim, 0);
1181 static const struct got_error *
1182 draw_file(struct tog_view *view, FILE *f, int *first_displayed_line,
1183 int *last_displayed_line, int *eof, int max_lines)
1185 const struct got_error *err;
1186 int nlines = 0, nprinted = 0;
1193 werase(view->window);
1196 while (nprinted < max_lines) {
1197 line = parse_next_line(f, &len);
1202 if (++nlines < *first_displayed_line) {
1207 err = format_line(&wline, &width, line, view->ncols);
1213 waddwstr(view->window, wline);
1214 if (width < view->ncols)
1215 waddch(view->window, '\n');
1216 if (++nprinted == 1)
1217 *first_displayed_line = nlines;
1222 *last_displayed_line = nlines;
1230 static const struct got_error *
1231 open_diff_view(struct tog_view *view, struct got_object *obj1,
1232 struct got_object *obj2, struct got_repository *repo)
1234 const struct got_error *err;
1237 if (obj1 != NULL && obj2 != NULL &&
1238 got_object_get_type(obj1) != got_object_get_type(obj2))
1239 return got_error(GOT_ERR_OBJ_TYPE);
1243 return got_error_from_errno();
1245 switch (got_object_get_type(obj1 ? obj1 : obj2)) {
1246 case GOT_OBJ_TYPE_BLOB:
1247 err = got_diff_objects_as_blobs(obj1, obj2, repo, f);
1249 case GOT_OBJ_TYPE_TREE:
1250 err = got_diff_objects_as_trees(obj1, obj2, repo, f);
1252 case GOT_OBJ_TYPE_COMMIT:
1253 err = got_diff_objects_as_commits(obj1, obj2, repo, f);
1256 return got_error(GOT_ERR_OBJ_TYPE);
1261 view->state.diff.f = f;
1262 view->state.diff.first_displayed_line = 1;
1263 view->state.diff.last_displayed_line = view->nlines;
1269 close_diff_view(struct tog_view *view)
1271 fclose(view->state.diff.f);
1274 static const struct got_error *
1275 show_diff_view(struct tog_view *view)
1277 const struct got_error *err = NULL;
1280 struct tog_diff_view_state *state = &view->state.diff;
1285 err = draw_file(view, state->f, &state->first_displayed_line,
1286 &state->last_displayed_line, &eof, view->nlines);
1289 nodelay(stdscr, FALSE);
1290 ch = wgetch(view->window);
1291 nodelay(stdscr, TRUE);
1298 if (state->first_displayed_line > 1)
1299 state->first_displayed_line--;
1304 while (i++ < view->nlines - 1 &&
1305 state->first_displayed_line > 1)
1306 state->first_displayed_line--;
1311 state->first_displayed_line++;
1316 while (!eof && i++ < view->nlines - 1) {
1317 char *line = parse_next_line(
1319 state->first_displayed_line++;
1325 err = view_resize(view);
1337 static const struct got_error *
1338 cmd_diff(int argc, char *argv[])
1340 const struct got_error *error = NULL;
1341 struct got_repository *repo = NULL;
1342 struct got_object *obj1 = NULL, *obj2 = NULL;
1343 char *repo_path = NULL;
1344 char *obj_id_str1 = NULL, *obj_id_str2 = NULL;
1346 struct tog_view *view;
1349 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
1353 while ((ch = getopt(argc, argv, "")) != -1) {
1365 usage_diff(); /* TODO show local worktree changes */
1366 } else if (argc == 2) {
1367 repo_path = getcwd(NULL, 0);
1368 if (repo_path == NULL)
1369 return got_error_from_errno();
1370 obj_id_str1 = argv[0];
1371 obj_id_str2 = argv[1];
1372 } else if (argc == 3) {
1373 repo_path = realpath(argv[0], NULL);
1374 if (repo_path == NULL)
1375 return got_error_from_errno();
1376 obj_id_str1 = argv[1];
1377 obj_id_str2 = argv[2];
1381 error = got_repo_open(&repo, repo_path);
1386 error = got_object_open_by_id_str(&obj1, repo, obj_id_str1);
1390 error = got_object_open_by_id_str(&obj2, repo, obj_id_str2);
1394 view = view_open(0, 0, 0, 0, NULL, TOG_VIEW_DIFF);
1396 error = got_error_from_errno();
1399 error = open_diff_view(view, obj1, obj2, repo);
1402 error = show_diff_view(view);
1403 close_diff_view(view);
1406 got_repo_close(repo);
1408 got_object_close(obj1);
1410 got_object_close(obj2);
1418 fprintf(stderr, "usage: %s blame [-c commit] [-r repository-path] path\n",
1423 struct tog_blame_line {
1425 struct got_object_id *id;
1428 static const struct got_error *
1429 draw_blame(struct tog_view *view, struct got_object_id *id, FILE *f,
1430 const char *path, struct tog_blame_line *lines, int nlines,
1431 int blame_complete, int selected_line, int *first_displayed_line,
1432 int *last_displayed_line, int *eof, int max_lines)
1434 const struct got_error *err;
1435 int lineno = 0, nprinted = 0;
1440 struct tog_blame_line *blame_line;
1441 struct got_object_id *prev_id = NULL;
1444 err = got_object_id_str(&id_str, id);
1449 werase(view->window);
1451 if (asprintf(&line, "commit: %s", id_str) == -1) {
1452 err = got_error_from_errno();
1457 err = format_line(&wline, &width, line, view->ncols);
1460 waddwstr(view->window, wline);
1463 if (width < view->ncols)
1464 waddch(view->window, '\n');
1466 if (asprintf(&line, "[%d/%d] %s%s",
1467 *first_displayed_line - 1 + selected_line, nlines,
1468 blame_complete ? "" : "annotating ", path) == -1) {
1470 return got_error_from_errno();
1473 err = format_line(&wline, &width, line, view->ncols);
1478 waddwstr(view->window, wline);
1481 if (width < view->ncols)
1482 waddch(view->window, '\n');
1485 while (nprinted < max_lines - 2) {
1486 line = parse_next_line(f, &len);
1491 if (++lineno < *first_displayed_line) {
1496 wlimit = view->ncols < 9 ? 0 : view->ncols - 9;
1497 err = format_line(&wline, &width, line, wlimit);
1503 if (nprinted == selected_line - 1)
1504 wstandout(view->window);
1506 blame_line = &lines[lineno - 1];
1507 if (blame_line->annotated && prev_id &&
1508 got_object_id_cmp(prev_id, blame_line->id) == 0)
1509 waddstr(view->window, " ");
1510 else if (blame_line->annotated) {
1512 err = got_object_id_str(&id_str, blame_line->id);
1518 wprintw(view->window, "%.8s ", id_str);
1520 prev_id = blame_line->id;
1522 waddstr(view->window, "........ ");
1526 waddwstr(view->window, wline);
1527 while (width < wlimit) {
1528 waddch(view->window, ' ');
1531 if (nprinted == selected_line - 1)
1532 wstandend(view->window);
1533 if (++nprinted == 1)
1534 *first_displayed_line = lineno;
1539 *last_displayed_line = lineno;
1547 struct tog_blame_cb_args {
1548 pthread_mutex_t *mutex;
1549 struct tog_blame_line *lines; /* one per line */
1552 struct tog_view *view;
1553 struct got_object_id *commit_id;
1556 int *first_displayed_line;
1557 int *last_displayed_line;
1562 static const struct got_error *
1563 blame_cb(void *arg, int nlines, int lineno, struct got_object_id *id)
1565 const struct got_error *err = NULL;
1566 struct tog_blame_cb_args *a = arg;
1567 struct tog_blame_line *line;
1570 if (nlines != a->nlines ||
1571 (lineno != -1 && lineno < 1) || lineno > a->nlines)
1572 return got_error(GOT_ERR_RANGE);
1574 if (pthread_mutex_lock(a->mutex) != 0)
1575 return got_error_from_errno();
1577 if (*a->quit) { /* user has quit the blame view */
1578 err = got_error(GOT_ERR_ITER_COMPLETED);
1583 goto done; /* no change in this commit */
1585 line = &a->lines[lineno - 1];
1586 if (line->annotated)
1589 line->id = got_object_id_dup(id);
1590 if (line->id == NULL) {
1591 err = got_error_from_errno();
1594 line->annotated = 1;
1596 err = draw_blame(a->view, a->commit_id, a->f, a->path,
1597 a->lines, a->nlines, 0, *a->selected_line, a->first_displayed_line,
1598 a->last_displayed_line, &eof, a->view->nlines);
1600 if (pthread_mutex_unlock(a->mutex) != 0)
1601 return got_error_from_errno();
1605 struct tog_blame_thread_args {
1607 struct got_repository *repo;
1608 struct tog_blame_cb_args *cb_args;
1613 blame_thread(void *arg)
1615 const struct got_error *err;
1616 struct tog_blame_thread_args *ta = arg;
1617 struct tog_blame_cb_args *a = ta->cb_args;
1620 err = got_blame_incremental(ta->path, a->commit_id, ta->repo,
1621 blame_cb, ta->cb_args);
1623 if (pthread_mutex_lock(a->mutex) != 0)
1624 return (void *)got_error_from_errno();
1626 got_repo_close(ta->repo);
1630 err = draw_blame(a->view, a->commit_id, a->f, a->path,
1631 a->lines, a->nlines, 1, *a->selected_line,
1632 a->first_displayed_line, a->last_displayed_line, &eof,
1635 if (pthread_mutex_unlock(a->mutex) != 0 && err == NULL)
1636 err = got_error_from_errno();
1641 static struct got_object_id *
1642 get_selected_commit_id(struct tog_blame_line *lines,
1643 int first_displayed_line, int selected_line)
1645 struct tog_blame_line *line;
1647 line = &lines[first_displayed_line - 1 + selected_line - 1];
1648 if (!line->annotated)
1654 static const struct got_error *
1655 open_selected_commit(struct got_object **pobj, struct got_object **obj,
1656 struct tog_blame_line *lines, int first_displayed_line,
1657 int selected_line, struct got_repository *repo)
1659 const struct got_error *err = NULL;
1660 struct got_commit_object *commit = NULL;
1661 struct got_object_id *selected_id;
1662 struct got_object_qid *pid;
1667 selected_id = get_selected_commit_id(lines,
1668 first_displayed_line, selected_line);
1669 if (selected_id == NULL)
1672 err = got_object_open(obj, repo, selected_id);
1676 err = got_object_commit_open(&commit, repo, *obj);
1680 pid = SIMPLEQ_FIRST(&commit->parent_ids);
1682 err = got_object_open(pobj, repo, pid->id);
1688 got_object_commit_close(commit);
1695 struct tog_blame_line *lines;
1698 struct tog_blame_thread_args thread_args;
1699 struct tog_blame_cb_args cb_args;
1703 static const struct got_error *
1704 stop_blame(struct tog_blame *blame)
1706 const struct got_error *err = NULL;
1709 if (blame->thread) {
1710 if (pthread_join(blame->thread, (void **)&err) != 0)
1711 err = got_error_from_errno();
1712 if (err && err->code == GOT_ERR_ITER_COMPLETED)
1714 blame->thread = NULL;
1716 if (blame->thread_args.repo) {
1717 got_repo_close(blame->thread_args.repo);
1718 blame->thread_args.repo = NULL;
1724 for (i = 0; i < blame->nlines; i++)
1725 free(blame->lines[i].id);
1727 blame->lines = NULL;
1728 free(blame->cb_args.commit_id);
1729 blame->cb_args.commit_id = NULL;
1734 static const struct got_error *
1735 run_blame(struct tog_blame *blame, pthread_mutex_t *mutex,
1736 struct tog_view *view, int *blame_complete,
1737 int *first_displayed_line, int *last_displayed_line,
1738 int *selected_line, int *done, const char *path,
1739 struct got_object_id *commit_id,
1740 struct got_repository *repo)
1742 const struct got_error *err = NULL;
1743 struct got_blob_object *blob = NULL;
1744 struct got_repository *thread_repo = NULL;
1745 struct got_object *obj;
1747 err = got_object_open_by_path(&obj, repo, commit_id, path);
1750 if (got_object_get_type(obj) != GOT_OBJ_TYPE_BLOB) {
1751 err = got_error(GOT_ERR_OBJ_TYPE);
1755 err = got_object_blob_open(&blob, repo, obj, 8192);
1758 blame->f = got_opentemp();
1759 if (blame->f == NULL) {
1760 err = got_error_from_errno();
1763 err = got_object_blob_dump_to_file(&blame->filesize, &blame->nlines,
1768 blame->lines = calloc(blame->nlines, sizeof(*blame->lines));
1769 if (blame->lines == NULL) {
1770 err = got_error_from_errno();
1774 err = got_repo_open(&thread_repo, got_repo_get_path(repo));
1778 blame->cb_args.view = view;
1779 blame->cb_args.lines = blame->lines;
1780 blame->cb_args.nlines = blame->nlines;
1781 blame->cb_args.mutex = mutex;
1782 blame->cb_args.commit_id = got_object_id_dup(commit_id);
1783 if (blame->cb_args.commit_id == NULL) {
1784 err = got_error_from_errno();
1787 blame->cb_args.f = blame->f;
1788 blame->cb_args.path = path;
1789 blame->cb_args.first_displayed_line = first_displayed_line;
1790 blame->cb_args.selected_line = selected_line;
1791 blame->cb_args.last_displayed_line = last_displayed_line;
1792 blame->cb_args.quit = done;
1794 blame->thread_args.path = path;
1795 blame->thread_args.repo = thread_repo;
1796 blame->thread_args.cb_args = &blame->cb_args;
1797 blame->thread_args.complete = blame_complete;
1798 *blame_complete = 0;
1800 if (pthread_create(&blame->thread, NULL, blame_thread,
1801 &blame->thread_args) != 0) {
1802 err = got_error_from_errno();
1808 got_object_blob_close(blob);
1810 got_object_close(obj);
1816 static const struct got_error *
1817 open_blame_view(struct tog_view *view, const char *path,
1818 struct got_object_id *commit_id, struct got_repository *repo)
1820 const struct got_error *err = NULL;
1821 struct tog_blame_view_state *state = &view->state.blame;
1823 SIMPLEQ_INIT(&state->blamed_commits);
1825 if (pthread_mutex_init(&state->mutex, NULL) != 0)
1826 return got_error_from_errno();
1828 err = got_object_qid_alloc(&state->blamed_commit, commit_id);
1832 SIMPLEQ_INSERT_HEAD(&state->blamed_commits, state->blamed_commit,
1834 state->first_displayed_line = 1;
1835 state->last_displayed_line = view->nlines;
1836 state->selected_line = 1;
1837 state->blame_complete = 0;
1840 state->commit_id = commit_id;
1846 close_blame_view(struct tog_view *view)
1848 struct tog_blame_view_state *state = &view->state.blame;
1850 while (!SIMPLEQ_EMPTY(&state->blamed_commits)) {
1851 struct got_object_qid *blamed_commit;
1852 blamed_commit = SIMPLEQ_FIRST(&state->blamed_commits);
1853 SIMPLEQ_REMOVE_HEAD(&state->blamed_commits, entry);
1854 got_object_qid_free(blamed_commit);
1858 static const struct got_error *
1859 show_blame_view(struct tog_view *view)
1861 const struct got_error *err = NULL, *thread_err = NULL;
1862 int ch, done = 0, eof;
1863 struct got_object *obj = NULL, *pobj = NULL;
1864 struct tog_blame blame;
1865 struct tog_view *diff_view;
1866 struct tog_blame_view_state *state = &view->state.blame;
1870 memset(&blame, 0, sizeof(blame));
1871 err = run_blame(&blame, &state->mutex, view, &state->blame_complete,
1872 &state->first_displayed_line, &state->last_displayed_line,
1873 &state->selected_line, &done, state->path,
1874 state->blamed_commit->id, state->repo);
1879 if (pthread_mutex_lock(&state->mutex) != 0) {
1880 err = got_error_from_errno();
1883 err = draw_blame(view, state->blamed_commit->id, blame.f,
1884 state->path, blame.lines, blame.nlines,
1885 state->blame_complete, state->selected_line,
1886 &state->first_displayed_line, &state->last_displayed_line,
1887 &eof, view->nlines);
1888 if (pthread_mutex_unlock(&state->mutex) != 0) {
1889 err = got_error_from_errno();
1894 nodelay(stdscr, FALSE);
1895 ch = wgetch(view->window);
1896 nodelay(stdscr, TRUE);
1897 if (pthread_mutex_lock(&state->mutex) != 0) {
1898 err = got_error_from_errno();
1907 if (state->selected_line > 1)
1908 state->selected_line--;
1909 else if (state->selected_line == 1 &&
1910 state->first_displayed_line > 1)
1911 state->first_displayed_line--;
1915 if (state->first_displayed_line == 1) {
1916 state->selected_line = 1;
1919 if (state->first_displayed_line >
1921 state->first_displayed_line -=
1924 state->first_displayed_line = 1;
1928 if (state->selected_line < view->nlines - 2 &&
1929 state->first_displayed_line +
1930 state->selected_line <= blame.nlines)
1931 state->selected_line++;
1932 else if (state->last_displayed_line <
1934 state->first_displayed_line++;
1938 struct got_object_id *id;
1939 id = get_selected_commit_id(blame.lines,
1940 state->first_displayed_line,
1941 state->selected_line);
1942 if (id == NULL || got_object_id_cmp(id,
1943 state->blamed_commit->id) == 0)
1945 err = open_selected_commit(&pobj, &obj,
1946 blame.lines, state->first_displayed_line,
1947 state->selected_line, state->repo);
1950 if (pobj == NULL && obj == NULL)
1952 if (ch == 'p' && pobj == NULL)
1955 if (pthread_mutex_unlock(&state->mutex) != 0) {
1956 err = got_error_from_errno();
1959 thread_err = stop_blame(&blame);
1961 if (pthread_mutex_lock(&state->mutex) != 0) {
1962 err = got_error_from_errno();
1967 id = got_object_get_id(ch == 'b' ? obj : pobj);
1968 got_object_close(obj);
1971 got_object_close(pobj);
1975 err = got_error_from_errno();
1978 err = got_object_qid_alloc(
1979 &state->blamed_commit, id);
1983 SIMPLEQ_INSERT_HEAD(&state->blamed_commits,
1984 state->blamed_commit, entry);
1985 err = run_blame(&blame, &state->mutex, view,
1986 &state->blame_complete,
1987 &state->first_displayed_line,
1988 &state->last_displayed_line,
1989 &state->selected_line, &done, state->path,
1990 state->blamed_commit->id, state->repo);
1996 struct got_object_qid *first;
1997 first = SIMPLEQ_FIRST(&state->blamed_commits);
1998 if (!got_object_id_cmp(first->id,
2002 if (pthread_mutex_unlock(&state->mutex) != 0) {
2003 err = got_error_from_errno();
2006 thread_err = stop_blame(&blame);
2008 if (pthread_mutex_lock(&state->mutex) != 0) {
2009 err = got_error_from_errno();
2014 SIMPLEQ_REMOVE_HEAD(&state->blamed_commits,
2016 got_object_qid_free(state->blamed_commit);
2017 state->blamed_commit =
2018 SIMPLEQ_FIRST(&state->blamed_commits);
2019 err = run_blame(&blame, &state->mutex, view,
2020 &state->blame_complete,
2021 &state->first_displayed_line,
2022 &state->last_displayed_line,
2023 &state->selected_line, &done, state->path,
2024 state->blamed_commit->id, state->repo);
2031 err = open_selected_commit(&pobj, &obj,
2032 blame.lines, state->first_displayed_line,
2033 state->selected_line, state->repo);
2036 if (pobj == NULL && obj == NULL)
2038 diff_view = view_open(0, 0, 0, 0, view,
2040 if (diff_view == NULL) {
2041 err = got_error_from_errno();
2044 err = open_diff_view(diff_view, pobj, obj,
2048 err = show_diff_view(diff_view);
2049 close_diff_view(diff_view);
2050 view_close(diff_view);
2055 got_object_close(pobj);
2058 got_object_close(obj);
2065 if (state->last_displayed_line
2067 state->selected_line < view->nlines - 2) {
2068 state->selected_line = MIN(blame.nlines,
2072 if (state->last_displayed_line +
2073 view->nlines - 2 <= blame.nlines)
2074 state->first_displayed_line +=
2077 state->first_displayed_line =
2078 blame.nlines - (view->nlines - 3);
2081 err = view_resize(view);
2084 if (state->selected_line > view->nlines - 2) {
2085 state->selected_line = MIN(blame.nlines,
2092 if (pthread_mutex_unlock(&state->mutex) != 0)
2093 err = got_error_from_errno();
2094 if (err || thread_err)
2099 got_object_close(pobj);
2101 thread_err = stop_blame(&blame);
2102 return thread_err ? thread_err : err;
2105 static const struct got_error *
2106 cmd_blame(int argc, char *argv[])
2108 const struct got_error *error;
2109 struct got_repository *repo = NULL;
2110 char *path, *cwd = NULL, *repo_path = NULL, *in_repo_path = NULL;
2111 struct got_object_id *commit_id = NULL;
2112 char *commit_id_str = NULL;
2114 struct tog_view *view;
2117 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
2121 while ((ch = getopt(argc, argv, "c:r:")) != -1) {
2124 commit_id_str = optarg;
2127 repo_path = realpath(optarg, NULL);
2128 if (repo_path == NULL)
2129 err(1, "-r option");
2145 cwd = getcwd(NULL, 0);
2147 error = got_error_from_errno();
2150 if (repo_path == NULL) {
2151 repo_path = strdup(cwd);
2152 if (repo_path == NULL) {
2153 error = got_error_from_errno();
2159 error = got_repo_open(&repo, repo_path);
2163 error = got_repo_map_path(&in_repo_path, repo, path);
2167 if (commit_id_str == NULL) {
2168 struct got_reference *head_ref;
2169 error = got_ref_open(&head_ref, repo, GOT_REF_HEAD);
2172 error = got_ref_resolve(&commit_id, repo, head_ref);
2173 got_ref_close(head_ref);
2175 struct got_object *obj;
2176 error = got_object_open_by_id_str(&obj, repo, commit_id_str);
2179 commit_id = got_object_get_id(obj);
2180 if (commit_id == NULL)
2181 error = got_error_from_errno();
2182 got_object_close(obj);
2187 view = view_open(0, 0, 0, 0, NULL, TOG_VIEW_BLAME);
2189 error = got_error_from_errno();
2192 error = open_blame_view(view, in_repo_path, commit_id, repo);
2195 error = show_blame_view(view);
2196 close_blame_view(view);
2204 got_repo_close(repo);
2208 static const struct got_error *
2209 draw_tree_entries(struct tog_view *view,
2210 struct got_tree_entry **first_displayed_entry,
2211 struct got_tree_entry **last_displayed_entry,
2212 struct got_tree_entry **selected_entry, int *ndisplayed,
2213 const char *label, int show_ids, const char *parent_path,
2214 const struct got_tree_entries *entries, int selected, int limit, int isroot)
2216 const struct got_error *err = NULL;
2217 struct got_tree_entry *te;
2223 werase(view->window);
2228 err = format_line(&wline, &width, label, view->ncols);
2231 waddwstr(view->window, wline);
2234 if (width < view->ncols)
2235 waddch(view->window, '\n');
2238 err = format_line(&wline, &width, parent_path, view->ncols);
2241 waddwstr(view->window, wline);
2244 if (width < view->ncols)
2245 waddch(view->window, '\n');
2248 waddch(view->window, '\n');
2252 te = SIMPLEQ_FIRST(&entries->head);
2253 if (*first_displayed_entry == NULL) {
2254 if (selected == 0) {
2255 wstandout(view->window);
2256 *selected_entry = NULL;
2258 waddstr(view->window, " ..\n"); /* parent directory */
2260 wstandend(view->window);
2267 while (te != *first_displayed_entry)
2268 te = SIMPLEQ_NEXT(te, entry);
2272 char *line = NULL, *id_str = NULL;
2275 err = got_object_id_str(&id_str, te->id);
2277 return got_error_from_errno();
2279 if (asprintf(&line, "%s %s%s", id_str ? id_str : "",
2280 te->name, S_ISDIR(te->mode) ? "/" : "") == -1) {
2282 return got_error_from_errno();
2285 err = format_line(&wline, &width, line, view->ncols);
2290 if (n == selected) {
2291 wstandout(view->window);
2292 *selected_entry = te;
2294 waddwstr(view->window, wline);
2295 if (width < view->ncols)
2296 waddch(view->window, '\n');
2298 wstandend(view->window);
2304 *last_displayed_entry = te;
2307 te = SIMPLEQ_NEXT(te, entry);
2314 tree_scroll_up(struct got_tree_entry **first_displayed_entry, int maxscroll,
2315 const struct got_tree_entries *entries, int isroot)
2317 struct got_tree_entry *te, *prev;
2320 if (*first_displayed_entry == NULL)
2323 te = SIMPLEQ_FIRST(&entries->head);
2324 if (*first_displayed_entry == te) {
2326 *first_displayed_entry = NULL;
2330 /* XXX this is stupid... switch to TAILQ? */
2331 for (i = 0; i < maxscroll; i++) {
2332 while (te != *first_displayed_entry) {
2334 te = SIMPLEQ_NEXT(te, entry);
2336 *first_displayed_entry = prev;
2337 te = SIMPLEQ_FIRST(&entries->head);
2339 if (!isroot && te == SIMPLEQ_FIRST(&entries->head) && i < maxscroll)
2340 *first_displayed_entry = NULL;
2344 tree_scroll_down(struct got_tree_entry **first_displayed_entry, int maxscroll,
2345 struct got_tree_entry *last_displayed_entry,
2346 const struct got_tree_entries *entries)
2348 struct got_tree_entry *next;
2351 if (SIMPLEQ_NEXT(last_displayed_entry, entry) == NULL)
2354 if (*first_displayed_entry)
2355 next = SIMPLEQ_NEXT(*first_displayed_entry, entry);
2357 next = SIMPLEQ_FIRST(&entries->head);
2359 *first_displayed_entry = next;
2360 if (++n >= maxscroll)
2362 next = SIMPLEQ_NEXT(next, entry);
2366 static const struct got_error *
2367 tree_entry_path(char **path, struct tog_parent_trees *parents,
2368 struct got_tree_entry *te)
2370 const struct got_error *err = NULL;
2371 struct tog_parent_tree *pt;
2372 size_t len = 2; /* for leading slash and NUL */
2374 TAILQ_FOREACH(pt, parents, entry)
2375 len += strlen(pt->selected_entry->name) + 1 /* slash */;
2377 len += strlen(te->name);
2379 *path = calloc(1, len);
2381 return got_error_from_errno();
2384 pt = TAILQ_LAST(parents, tog_parent_trees);
2386 if (strlcat(*path, pt->selected_entry->name, len) >= len) {
2387 err = got_error(GOT_ERR_NO_SPACE);
2390 if (strlcat(*path, "/", len) >= len) {
2391 err = got_error(GOT_ERR_NO_SPACE);
2394 pt = TAILQ_PREV(pt, tog_parent_trees, entry);
2397 if (strlcat(*path, te->name, len) >= len) {
2398 err = got_error(GOT_ERR_NO_SPACE);
2410 static const struct got_error *
2411 blame_tree_entry(struct tog_view *parent_view, struct got_tree_entry *te,
2412 struct tog_parent_trees *parents, struct got_object_id *commit_id,
2413 struct got_repository *repo)
2415 const struct got_error *err = NULL;
2417 struct tog_view *view;
2419 err = tree_entry_path(&path, parents, te);
2423 view = view_open(0, 0, 0, 0, parent_view, TOG_VIEW_BLAME);
2425 err = open_blame_view(view, path, commit_id, repo);
2428 err = show_blame_view(view);
2429 close_blame_view(view);
2432 err = got_error_from_errno();
2434 view_show(parent_view);
2440 static const struct got_error *
2441 log_tree_entry(struct tog_view *view, struct got_tree_entry *te,
2442 struct tog_parent_trees *parents, struct got_object_id *commit_id,
2443 struct got_repository *repo)
2445 const struct got_error *err = NULL;
2448 err = tree_entry_path(&path, parents, te);
2452 err = open_log_view(view, commit_id, repo, path);
2455 err = show_log_view(view);
2456 close_log_view(view);
2462 static const struct got_error *
2463 open_tree_view(struct tog_view *view, struct got_tree_object *root,
2464 struct got_object_id *commit_id, struct got_repository *repo)
2466 const struct got_error *err = NULL;
2467 char *commit_id_str = NULL;
2468 struct tog_tree_view_state *state = &view->state.tree;
2470 TAILQ_INIT(&state->parents);
2472 err = got_object_id_str(&commit_id_str, commit_id);
2476 if (asprintf(&state->tree_label, "commit: %s", commit_id_str) == -1) {
2477 err = got_error_from_errno();
2481 state->root = state->tree = root;
2482 state->entries = got_object_tree_get_entries(root);
2483 state->first_displayed_entry = SIMPLEQ_FIRST(&state->entries->head);
2484 state->commit_id = commit_id;
2487 free(commit_id_str);
2489 free(state->tree_label);
2494 close_tree_view(struct tog_view *view)
2496 struct tog_tree_view_state *state = &view->state.tree;
2498 free(state->tree_label);
2499 while (!TAILQ_EMPTY(&state->parents)) {
2500 struct tog_parent_tree *parent;
2501 parent = TAILQ_FIRST(&state->parents);
2502 TAILQ_REMOVE(&state->parents, parent, entry);
2506 if (state->tree != state->root)
2507 got_object_tree_close(state->tree);
2510 static const struct got_error *
2511 show_tree_view(struct tog_view *view)
2513 const struct got_error *err = NULL;
2514 struct tog_tree_view_state *state = &view->state.tree;
2522 state->entries = got_object_tree_get_entries(state->tree);
2523 nentries = state->entries->nentries;
2524 if (state->tree != state->root)
2525 nentries++; /* '..' directory */
2527 err = tree_entry_path(&parent_path, &state->parents, NULL);
2531 err = draw_tree_entries(view, &state->first_displayed_entry,
2532 &state->last_displayed_entry, &state->selected_entry,
2533 &state->ndisplayed, state->tree_label, state->show_ids,
2534 parent_path, state->entries, state->selected,
2535 view->nlines, state->tree == state->root);
2540 nodelay(stdscr, FALSE);
2541 ch = wgetch(view->window);
2542 nodelay(stdscr, TRUE);
2548 state->show_ids = !state->show_ids;
2551 if (state->selected_entry) {
2552 struct tog_view *log_view;
2553 log_view = view_open(0, 0, 0, 0, view,
2555 if (log_view == NULL) {
2556 err = got_error_from_errno();
2559 err = log_tree_entry(log_view,
2560 state->selected_entry,
2562 state->commit_id, state->repo);
2563 view_close(log_view);
2571 if (state->selected > 0)
2573 if (state->selected > 0)
2575 tree_scroll_up(&state->first_displayed_entry, 1,
2576 state->entries, state->tree == state->root);
2579 if (SIMPLEQ_FIRST(&state->entries->head) ==
2580 state->first_displayed_entry) {
2581 if (state->tree != state->root)
2582 state->first_displayed_entry = NULL;
2583 state->selected = 0;
2586 tree_scroll_up(&state->first_displayed_entry,
2587 view->nlines, state->entries,
2588 state->tree == state->root);
2592 if (state->selected < state->ndisplayed - 1) {
2596 tree_scroll_down(&state->first_displayed_entry, 1,
2597 state->last_displayed_entry, state->entries);
2600 tree_scroll_down(&state->first_displayed_entry,
2601 view->nlines, state->last_displayed_entry,
2603 if (SIMPLEQ_NEXT(state->last_displayed_entry, entry))
2605 /* can't scroll any further; move cursor down */
2606 if (state->selected < state->ndisplayed - 1)
2607 state->selected = state->ndisplayed - 1;
2611 if (state->selected_entry == NULL) {
2612 struct tog_parent_tree *parent;
2614 /* user selected '..' */
2615 if (state->tree == state->root)
2617 parent = TAILQ_FIRST(&state->parents);
2618 TAILQ_REMOVE(&state->parents, parent, entry);
2619 got_object_tree_close(state->tree);
2620 state->tree = parent->tree;
2621 state->first_displayed_entry =
2622 parent->first_displayed_entry;
2623 state->selected_entry = parent->selected_entry;
2624 state->selected = parent->selected;
2626 } else if (S_ISDIR(state->selected_entry->mode)) {
2627 struct tog_parent_tree *parent;
2628 struct got_tree_object *child;
2629 err = got_object_open_as_tree(
2630 &child, state->repo, state->selected_entry->id);
2633 parent = calloc(1, sizeof(*parent));
2634 if (parent == NULL) {
2635 err = got_error_from_errno();
2638 parent->tree = state->tree;
2639 parent->first_displayed_entry =
2640 state->first_displayed_entry;
2641 parent->selected_entry = state->selected_entry;
2642 parent->selected = state->selected;
2643 TAILQ_INSERT_HEAD(&state->parents, parent,
2645 state->tree = child;
2646 state->selected = 0;
2647 state->first_displayed_entry = NULL;
2648 } else if (S_ISREG(state->selected_entry->mode)) {
2649 err = blame_tree_entry(view,
2650 state->selected_entry, &state->parents,
2651 state->commit_id, state->repo);
2657 err = view_resize(view);
2660 if (state->selected > view->nlines)
2661 state->selected = state->ndisplayed - 1;
2675 fprintf(stderr, "usage: %s tree [-c commit] [repository-path]\n",
2680 static const struct got_error *
2681 cmd_tree(int argc, char *argv[])
2683 const struct got_error *error;
2684 struct got_repository *repo = NULL;
2685 char *repo_path = NULL;
2686 struct got_object_id *commit_id = NULL;
2687 char *commit_id_arg = NULL;
2688 struct got_commit_object *commit = NULL;
2689 struct got_tree_object *tree = NULL;
2691 struct tog_view *view;
2694 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
2698 while ((ch = getopt(argc, argv, "c:")) != -1) {
2701 commit_id_arg = optarg;
2713 repo_path = getcwd(NULL, 0);
2714 if (repo_path == NULL)
2715 return got_error_from_errno();
2716 } else if (argc == 1) {
2717 repo_path = realpath(argv[0], NULL);
2718 if (repo_path == NULL)
2719 return got_error_from_errno();
2723 error = got_repo_open(&repo, repo_path);
2728 if (commit_id_arg == NULL) {
2729 error = get_head_commit_id(&commit_id, repo);
2733 struct got_object *obj;
2734 error = got_object_open_by_id_str(&obj, repo, commit_id_arg);
2735 if (error == NULL) {
2736 commit_id = got_object_get_id(obj);
2737 if (commit_id == NULL)
2738 error = got_error_from_errno();
2744 error = got_object_open_as_commit(&commit, repo, commit_id);
2748 error = got_object_open_as_tree(&tree, repo, commit->tree_id);
2752 view = view_open(0, 0, 0, 0, NULL, TOG_VIEW_TREE);
2754 error = got_error_from_errno();
2757 error = open_tree_view(view, tree, commit_id, repo);
2760 error = show_tree_view(view);
2761 close_tree_view(view);
2766 got_object_commit_close(commit);
2768 got_object_tree_close(tree);
2770 got_repo_close(repo);
2780 intrflush(stdscr, FALSE);
2781 keypad(stdscr, TRUE);
2790 fprintf(stderr, "usage: %s [-h] [command] [arg ...]\n\n"
2791 "Available commands:\n", getprogname());
2792 for (i = 0; i < nitems(tog_commands); i++) {
2793 struct tog_cmd *cmd = &tog_commands[i];
2794 fprintf(stderr, " %s: %s\n", cmd->name, cmd->descr);
2800 make_argv(const char *arg0, const char *arg1)
2803 int argc = (arg1 == NULL ? 1 : 2);
2805 argv = calloc(argc, sizeof(char *));
2808 argv[0] = strdup(arg0);
2809 if (argv[0] == NULL)
2812 argv[1] = strdup(arg1);
2813 if (argv[1] == NULL)
2821 main(int argc, char *argv[])
2823 const struct got_error *error = NULL;
2824 struct tog_cmd *cmd = NULL;
2826 char **cmd_argv = NULL;
2828 setlocale(LC_ALL, "");
2830 while ((ch = getopt(argc, argv, "h")) != -1) {
2849 /* Build an argument vector which runs a default command. */
2850 cmd = &tog_commands[0];
2851 cmd_argv = make_argv(cmd->name, NULL);
2856 /* Did the user specific a command? */
2857 for (i = 0; i < nitems(tog_commands); i++) {
2858 if (strncmp(tog_commands[i].name, argv[0],
2859 strlen(argv[0])) == 0) {
2860 cmd = &tog_commands[i];
2862 tog_commands[i].cmd_usage();
2867 /* Did the user specify a repository? */
2868 char *repo_path = realpath(argv[0], NULL);
2870 struct got_repository *repo;
2871 error = got_repo_open(&repo, repo_path);
2873 got_repo_close(repo);
2875 error = got_error_from_errno();
2878 fprintf(stderr, "%s: '%s' is not a "
2879 "known command\n", getprogname(),
2883 fprintf(stderr, "%s: '%s' is neither a known "
2884 "command nor a path to a repository\n",
2885 getprogname(), argv[0]);
2889 cmd = &tog_commands[0];
2890 cmd_argv = make_argv(cmd->name, repo_path);
2898 error = cmd->cmd_main(argc, cmd_argv ? cmd_argv : argv);
2905 fprintf(stderr, "%s: %s\n", getprogname(), error->msg);