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 commit_queue_entry {
93 TAILQ_ENTRY(commit_queue_entry) entry;
94 struct got_object_id *id;
95 struct got_commit_object *commit;
97 TAILQ_HEAD(commit_queue_head, commit_queue_entry);
100 struct commit_queue_head head;
103 struct tog_diff_view_state {
105 int first_displayed_line;
106 int last_displayed_line;
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;
123 int nlines, ncols, begin_y, begin_x;
124 int lines, cols; /* copies of LINES and COLS */
125 struct tog_view *parent;
127 /* type-specific state */
128 enum tog_view_type type;
130 struct tog_diff_view_state diff;
131 struct tog_log_view_state log;
135 static const struct got_error *open_diff_view(struct tog_view *,
136 struct got_object *, struct got_object *, struct got_repository *);
137 static const struct got_error *show_diff_view(struct tog_view *);
138 static void close_diff_view(struct tog_view *);
139 static const struct got_error *open_log_view(struct tog_view *,
140 struct got_object_id *, struct got_repository *, const char *);
141 static const struct got_error * show_log_view(struct tog_view *);
142 static void close_log_view(struct tog_view *);
143 static const struct got_error *show_blame_view(struct tog_view *, const char *,
144 struct got_object_id *, struct got_repository *);
145 static const struct got_error *show_tree_view(struct tog_view *,
146 struct got_tree_object *, struct got_object_id *, struct got_repository *);
149 view_close(struct tog_view *view)
152 del_panel(view->panel);
154 delwin(view->window);
158 static struct tog_view *
159 view_open(int nlines, int ncols, int begin_y, int begin_x,
160 struct tog_view *parent, enum tog_view_type type)
162 struct tog_view *view = malloc(sizeof(*view));
167 view->parent = parent;
171 view->nlines = nlines ? nlines : LINES - begin_y;
172 view->ncols = ncols ? ncols : COLS - begin_x;
173 view->begin_y = begin_y;
174 view->begin_x = begin_x;
175 view->window = newwin(nlines, ncols, begin_y, begin_x);
176 if (view->window == NULL) {
180 view->panel = new_panel(view->window);
181 if (view->panel == NULL) {
186 keypad(view->window, TRUE);
191 view_show(struct tog_view *view)
193 show_panel(view->panel);
197 const struct got_error *
198 view_resize(struct tog_view *view)
203 if (view->lines > LINES)
204 nlines = view->nlines - (view->lines - LINES);
206 nlines = view->nlines + (LINES - view->lines);
208 if (view->cols > COLS)
209 ncols = view->ncols - (view->cols - COLS);
211 ncols = view->ncols + (COLS - view->cols);
213 if (wresize(view->window, nlines, ncols) == ERR)
214 return got_error_from_errno();
215 replace_panel(view->panel, view->window);
217 view->nlines = nlines;
233 "usage: %s log [-c commit] [-r repository-path] [path]\n",
238 /* Create newly allocated wide-character string equivalent to a byte string. */
239 static const struct got_error *
240 mbs2ws(wchar_t **ws, size_t *wlen, const char *s)
243 const struct got_error *err = NULL;
246 *wlen = mbstowcs(NULL, s, 0);
247 if (*wlen == (size_t)-1) {
250 return got_error_from_errno();
252 /* byte string invalid in current encoding; try to "fix" it */
253 err = got_mbsavis(&vis, &vislen, s);
256 *wlen = mbstowcs(NULL, vis, 0);
257 if (*wlen == (size_t)-1) {
258 err = got_error_from_errno(); /* give up */
263 *ws = calloc(*wlen + 1, sizeof(*ws));
265 err = got_error_from_errno();
269 if (mbstowcs(*ws, vis ? vis : s, *wlen) != *wlen)
270 err = got_error_from_errno();
281 /* Format a line for display, ensuring that it won't overflow a width limit. */
282 static const struct got_error *
283 format_line(wchar_t **wlinep, int *widthp, const char *line, int wlimit)
285 const struct got_error *err = NULL;
287 wchar_t *wline = NULL;
294 err = mbs2ws(&wline, &wlen, line);
299 while (i < wlen && cols < wlimit) {
300 int width = wcwidth(wline[i]);
307 if (cols + width <= wlimit) {
313 if (wline[i] == L'\t')
314 cols += TABSIZE - ((cols + 1) % TABSIZE);
318 err = got_error_from_errno();
333 static const struct got_error *
334 draw_commit(struct tog_view *view, struct got_commit_object *commit,
335 struct got_object_id *id)
337 const struct got_error *err = NULL;
338 char datebuf[10]; /* YY-MM-DD + SPACE + NUL */
339 char *logmsg0 = NULL, *logmsg = NULL;
340 char *author0 = NULL, *author = NULL;
341 wchar_t *wlogmsg = NULL, *wauthor = NULL;
342 int author_width, logmsg_width;
343 char *newline, *smallerthan;
346 static const size_t date_display_cols = 9;
347 static const size_t author_display_cols = 16;
348 const int avail = view->ncols;
350 if (strftime(datebuf, sizeof(datebuf), "%g/%m/%d ",
351 &commit->tm_committer) >= sizeof(datebuf))
352 return got_error(GOT_ERR_NO_SPACE);
354 if (avail < date_display_cols)
355 limit = MIN(sizeof(datebuf) - 1, avail);
357 limit = MIN(date_display_cols, sizeof(datebuf) - 1);
358 waddnstr(view->window, datebuf, limit);
363 author0 = strdup(commit->author);
364 if (author0 == NULL) {
365 err = got_error_from_errno();
369 smallerthan = strchr(author, '<');
373 char *at = strchr(author, '@');
378 err = format_line(&wauthor, &author_width, author, limit);
381 waddwstr(view->window, wauthor);
383 while (col <= avail && author_width < author_display_cols + 1) {
384 waddch(view->window, ' ');
391 logmsg0 = strdup(commit->logmsg);
392 if (logmsg0 == NULL) {
393 err = got_error_from_errno();
397 while (*logmsg == '\n')
399 newline = strchr(logmsg, '\n');
403 err = format_line(&wlogmsg, &logmsg_width, logmsg, limit);
406 waddwstr(view->window, wlogmsg);
408 while (col <= avail) {
409 waddch(view->window, ' ');
421 static struct commit_queue_entry *
422 alloc_commit_queue_entry(struct got_commit_object *commit,
423 struct got_object_id *id)
425 struct commit_queue_entry *entry;
427 entry = calloc(1, sizeof(*entry));
432 entry->commit = commit;
437 pop_commit(struct commit_queue *commits)
439 struct commit_queue_entry *entry;
441 entry = TAILQ_FIRST(&commits->head);
442 TAILQ_REMOVE(&commits->head, entry, entry);
443 got_object_commit_close(entry->commit);
445 /* Don't free entry->id! It is owned by the commit graph. */
450 free_commits(struct commit_queue *commits)
452 while (!TAILQ_EMPTY(&commits->head))
456 static const struct got_error *
457 queue_commits(struct got_commit_graph *graph, struct commit_queue *commits,
458 struct got_object_id *start_id, int minqueue, int init,
459 struct got_repository *repo, const char *path)
461 const struct got_error *err = NULL;
462 struct got_object_id *id;
463 struct commit_queue_entry *entry;
464 int nfetched, nqueued = 0, found_obj = 0;
465 int is_root_path = strcmp(path, "/") == 0;
467 err = got_commit_graph_iter_start(graph, start_id);
471 entry = TAILQ_LAST(&commits->head, commit_queue_head);
472 if (entry && got_object_id_cmp(entry->id, start_id) == 0) {
475 /* Start ID's commit is already on the queue; skip over it. */
476 err = got_commit_graph_iter_next(&id, graph);
477 if (err && err->code != GOT_ERR_ITER_NEED_MORE)
480 err = got_commit_graph_fetch_commits(&nfetched, graph, 1, repo);
486 struct got_commit_object *commit;
488 err = got_commit_graph_iter_next(&id, graph);
490 if (err->code != GOT_ERR_ITER_NEED_MORE)
492 if (nqueued >= minqueue) {
496 err = got_commit_graph_fetch_commits(&nfetched,
505 err = got_object_open_as_commit(&commit, repo, id);
510 struct got_object *obj;
511 struct got_object_qid *pid;
514 err = got_object_open_by_path(&obj, repo, id, path);
516 got_object_commit_close(commit);
517 if (err->code == GOT_ERR_NO_OBJ &&
518 (found_obj || !init)) {
519 /* History stops here. */
520 err = got_error(GOT_ERR_ITER_COMPLETED);
526 pid = SIMPLEQ_FIRST(&commit->parent_ids);
528 struct got_object *pobj;
529 err = got_object_open_by_path(&pobj, repo,
532 if (err->code != GOT_ERR_NO_OBJ) {
533 got_object_close(obj);
534 got_object_commit_close(commit);
540 struct got_object_id *id, *pid;
541 id = got_object_get_id(obj);
543 err = got_error_from_errno();
544 got_object_close(obj);
545 got_object_close(pobj);
548 pid = got_object_get_id(pobj);
550 err = got_error_from_errno();
552 got_object_close(obj);
553 got_object_close(pobj);
557 (got_object_id_cmp(id, pid) != 0);
558 got_object_close(pobj);
563 got_object_close(obj);
565 got_object_commit_close(commit);
570 entry = alloc_commit_queue_entry(commit, id);
572 err = got_error_from_errno();
575 TAILQ_INSERT_TAIL(&commits->head, entry, entry);
583 static const struct got_error *
584 fetch_next_commit(struct commit_queue_entry **pentry,
585 struct commit_queue_entry *entry, struct commit_queue *commits,
586 struct got_commit_graph *graph, struct got_repository *repo,
589 const struct got_error *err = NULL;
593 err = queue_commits(graph, commits, entry->id, 1, 0, repo, path);
597 /* Next entry to display should now be available. */
598 *pentry = TAILQ_NEXT(entry, entry);
600 return got_error(GOT_ERR_NO_OBJ);
605 static const struct got_error *
606 get_head_commit_id(struct got_object_id **head_id, struct got_repository *repo)
608 const struct got_error *err = NULL;
609 struct got_reference *head_ref;
613 err = got_ref_open(&head_ref, repo, GOT_REF_HEAD);
617 err = got_ref_resolve(head_id, repo, head_ref);
618 got_ref_close(head_ref);
627 static const struct got_error *
628 draw_commits(struct tog_view *view, struct commit_queue_entry **last,
629 struct commit_queue_entry **selected, struct commit_queue_entry *first,
630 struct commit_queue *commits, int selected_idx, int limit,
631 struct got_commit_graph *graph, struct got_repository *repo,
634 const struct got_error *err = NULL;
635 struct commit_queue_entry *entry;
637 char *id_str, *header;
643 if (ncommits == selected_idx) {
647 entry = TAILQ_NEXT(entry, entry);
651 err = got_object_id_str(&id_str, (*selected)->id);
655 if (path && strcmp(path, "/") != 0) {
656 if (asprintf(&header, "commit: %s [%s]", id_str, path) == -1) {
657 err = got_error_from_errno();
661 } else if (asprintf(&header, "commit: %s", id_str) == -1) {
662 err = got_error_from_errno();
667 err = format_line(&wline, &width, header, view->ncols);
674 werase(view->window);
676 waddwstr(view->window, wline);
677 if (width < view->ncols)
678 waddch(view->window, '\n');
687 if (ncommits >= limit - 1)
689 if (ncommits == selected_idx)
690 wstandout(view->window);
691 err = draw_commit(view, entry->commit, entry->id);
692 if (ncommits == selected_idx)
693 wstandend(view->window);
698 if (entry == TAILQ_LAST(&commits->head, commit_queue_head)) {
699 err = queue_commits(graph, commits, entry->id, 1,
702 if (err->code != GOT_ERR_ITER_COMPLETED)
707 entry = TAILQ_NEXT(entry, entry);
717 scroll_up(struct commit_queue_entry **first_displayed_entry, int maxscroll,
718 struct commit_queue *commits)
720 struct commit_queue_entry *entry;
723 entry = TAILQ_FIRST(&commits->head);
724 if (*first_displayed_entry == entry)
727 entry = *first_displayed_entry;
728 while (entry && nscrolled < maxscroll) {
729 entry = TAILQ_PREV(entry, commit_queue_head, entry);
731 *first_displayed_entry = entry;
737 static const struct got_error *
738 scroll_down(struct commit_queue_entry **first_displayed_entry, int maxscroll,
739 struct commit_queue_entry *last_displayed_entry,
740 struct commit_queue *commits, struct got_commit_graph *graph,
741 struct got_repository *repo, const char *path)
743 const struct got_error *err = NULL;
744 struct commit_queue_entry *pentry;
748 pentry = TAILQ_NEXT(last_displayed_entry, entry);
749 if (pentry == NULL) {
750 err = fetch_next_commit(&pentry, last_displayed_entry,
751 commits, graph, repo, path);
752 if (err || pentry == NULL)
755 last_displayed_entry = pentry;
757 pentry = TAILQ_NEXT(*first_displayed_entry, entry);
760 *first_displayed_entry = pentry;
761 } while (++nscrolled < maxscroll);
766 static const struct got_error *
767 show_commit(struct tog_view *parent_view, struct commit_queue_entry *entry,
768 struct got_repository *repo)
770 const struct got_error *err;
771 struct got_object *obj1 = NULL, *obj2 = NULL;
772 struct got_object_qid *parent_id;
773 struct tog_view *view;
775 err = got_object_open(&obj2, repo, entry->id);
779 parent_id = SIMPLEQ_FIRST(&entry->commit->parent_ids);
781 err = got_object_open(&obj1, repo, parent_id->id);
786 view = view_open(0, 0, 0, 0, parent_view, TOG_VIEW_DIFF);
788 err = got_error_from_errno();
792 err = open_diff_view(view, obj1, obj2, repo);
795 err = show_diff_view(view);
796 close_diff_view(view);
798 view_show(parent_view);
801 got_object_close(obj1);
803 got_object_close(obj2);
807 static const struct got_error *
808 browse_commit(struct tog_view *parent_view, struct commit_queue_entry *entry,
809 struct got_repository *repo)
811 const struct got_error *err = NULL;
812 struct got_tree_object *tree;
813 struct tog_view *view;
815 err = got_object_open_as_tree(&tree, repo, entry->commit->tree_id);
819 view = view_open(0, 0, 0, 0, parent_view, TOG_VIEW_TREE);
821 err = got_error_from_errno();
824 err = show_tree_view(view, tree, entry->id, repo);
826 view_show(parent_view);
828 got_object_tree_close(tree);
832 static const struct got_error *
833 open_log_view(struct tog_view *view, struct got_object_id *start_id,
834 struct got_repository *repo, const char *path)
836 const struct got_error *err = NULL;
837 struct got_object_id *head_id = NULL;
839 struct tog_log_view_state *state = &view->state.log;
841 err = got_repo_map_path(&state->in_repo_path, repo, path);
845 err = get_head_commit_id(&head_id, repo);
849 /* The graph contains all commits. */
850 err = got_commit_graph_open(&state->graph, head_id, 0, repo);
853 /* The commit queue contains a subset of commits filtered by path. */
854 TAILQ_INIT(&state->commits.head);
855 state->commits.ncommits = 0;
857 /* Populate commit graph with a sufficient number of commits. */
858 err = got_commit_graph_fetch_commits_up_to(&nfetched,
859 state->graph, start_id, repo);
864 * Open the initial batch of commits, sorted in commit graph order.
865 * We keep all commits open throughout the lifetime of the log view
866 * in order to avoid having to re-fetch commits from disk while
867 * updating the display.
869 err = queue_commits(state->graph, &state->commits, start_id,
870 view->nlines, 1, repo, state->in_repo_path);
872 if (err->code != GOT_ERR_ITER_COMPLETED)
883 static void close_log_view(struct tog_view *view)
885 struct tog_log_view_state *state = &view->state.log;
888 got_commit_graph_close(state->graph);
889 free_commits(&state->commits);
890 free(state->in_repo_path);
893 static const struct got_error *
894 show_log_view(struct tog_view *view)
896 const struct got_error *err = NULL;
898 struct tog_log_view_state *state = &view->state.log;
902 state->first_displayed_entry =
903 TAILQ_FIRST(&state->commits.head);
904 state->selected_entry = state->first_displayed_entry;
906 err = draw_commits(view, &state->last_displayed_entry,
907 &state->selected_entry, state->first_displayed_entry,
908 &state->commits, state->selected, view->nlines,
909 state->graph, state->repo, state->in_repo_path);
913 nodelay(stdscr, FALSE);
914 ch = wgetch(view->window);
915 nodelay(stdscr, TRUE);
924 if (state->selected > 0)
926 if (state->selected > 0)
928 scroll_up(&state->first_displayed_entry, 1,
932 if (TAILQ_FIRST(&state->commits.head) ==
933 state->first_displayed_entry) {
937 scroll_up(&state->first_displayed_entry,
938 view->nlines, &state->commits);
942 if (state->selected < MIN(view->nlines - 2,
943 state->commits.ncommits - 1)) {
947 err = scroll_down(&state->first_displayed_entry,
948 1, state->last_displayed_entry,
949 &state->commits, state->graph, state->repo,
950 state->in_repo_path);
952 if (err->code != GOT_ERR_ITER_COMPLETED)
958 struct commit_queue_entry *first;
959 first = state->first_displayed_entry;
960 err = scroll_down(&state->first_displayed_entry,
961 view->nlines, state->last_displayed_entry,
962 &state->commits, state->graph, state->repo,
963 state->in_repo_path);
966 if (err->code != GOT_ERR_ITER_COMPLETED)
968 if (first == state->first_displayed_entry &&
969 state->selected < MIN(view->nlines - 2,
970 state->commits.ncommits - 1)) {
971 /* can't scroll further down */
972 state->selected = MIN(view->nlines - 2,
973 state->commits.ncommits - 1);
979 err = view_resize(view);
982 if (state->selected > view->nlines - 2)
983 state->selected = view->nlines - 2;
984 if (state->selected >
985 state->commits.ncommits - 1)
987 state->commits.ncommits - 1;
991 err = show_commit(view, state->selected_entry,
998 err = browse_commit(view, state->selected_entry,
1012 static const struct got_error *
1013 cmd_log(int argc, char *argv[])
1015 const struct got_error *error;
1016 struct got_repository *repo = NULL;
1017 struct got_object_id *start_id = NULL;
1018 char *path = NULL, *repo_path = NULL, *cwd = NULL;
1019 char *start_commit = NULL;
1021 struct tog_view *view;
1024 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
1028 while ((ch = getopt(argc, argv, "c:r:")) != -1) {
1031 start_commit = optarg;
1034 repo_path = realpath(optarg, NULL);
1035 if (repo_path == NULL)
1036 err(1, "-r option");
1050 path = strdup(argv[0]);
1054 return got_error_from_errno();
1056 cwd = getcwd(NULL, 0);
1058 error = got_error_from_errno();
1061 if (repo_path == NULL) {
1062 repo_path = strdup(cwd);
1063 if (repo_path == NULL) {
1064 error = got_error_from_errno();
1069 error = got_repo_open(&repo, repo_path);
1073 if (start_commit == NULL) {
1074 error = get_head_commit_id(&start_id, repo);
1078 struct got_object *obj;
1079 error = got_object_open_by_id_str(&obj, repo, start_commit);
1080 if (error == NULL) {
1081 start_id = got_object_get_id(obj);
1082 if (start_id == NULL)
1083 error = got_error_from_errno();
1090 view = view_open(0, 0, 0, 0, NULL, TOG_VIEW_LOG);
1092 error = got_error_from_errno();
1095 error = open_log_view(view, start_id, repo, path);
1098 error = show_log_view(view);
1099 close_log_view(view);
1107 got_repo_close(repo);
1115 fprintf(stderr, "usage: %s diff [repository-path] object1 object2\n",
1121 parse_next_line(FILE *f, size_t *len)
1126 const char delim[3] = { '\0', '\0', '\0'};
1128 line = fparseln(f, &linelen, &lineno, delim, 0);
1134 static const struct got_error *
1135 draw_file(struct tog_view *view, FILE *f, int *first_displayed_line,
1136 int *last_displayed_line, int *eof, int max_lines)
1138 const struct got_error *err;
1139 int nlines = 0, nprinted = 0;
1146 werase(view->window);
1149 while (nprinted < max_lines) {
1150 line = parse_next_line(f, &len);
1155 if (++nlines < *first_displayed_line) {
1160 err = format_line(&wline, &width, line, view->ncols);
1166 waddwstr(view->window, wline);
1167 if (width < view->ncols)
1168 waddch(view->window, '\n');
1169 if (++nprinted == 1)
1170 *first_displayed_line = nlines;
1175 *last_displayed_line = nlines;
1183 static const struct got_error *
1184 open_diff_view(struct tog_view *view, struct got_object *obj1,
1185 struct got_object *obj2, struct got_repository *repo)
1187 const struct got_error *err;
1190 if (obj1 != NULL && obj2 != NULL &&
1191 got_object_get_type(obj1) != got_object_get_type(obj2))
1192 return got_error(GOT_ERR_OBJ_TYPE);
1196 return got_error_from_errno();
1198 switch (got_object_get_type(obj1 ? obj1 : obj2)) {
1199 case GOT_OBJ_TYPE_BLOB:
1200 err = got_diff_objects_as_blobs(obj1, obj2, repo, f);
1202 case GOT_OBJ_TYPE_TREE:
1203 err = got_diff_objects_as_trees(obj1, obj2, repo, f);
1205 case GOT_OBJ_TYPE_COMMIT:
1206 err = got_diff_objects_as_commits(obj1, obj2, repo, f);
1209 return got_error(GOT_ERR_OBJ_TYPE);
1214 view->state.diff.f = f;
1215 view->state.diff.first_displayed_line = 1;
1216 view->state.diff.last_displayed_line = view->nlines;
1222 close_diff_view(struct tog_view *view)
1224 fclose(view->state.diff.f);
1227 static const struct got_error *
1228 show_diff_view(struct tog_view *view)
1230 const struct got_error *err = NULL;
1233 struct tog_diff_view_state *state = &view->state.diff;
1238 err = draw_file(view, state->f, &state->first_displayed_line,
1239 &state->last_displayed_line, &eof, view->nlines);
1242 nodelay(stdscr, FALSE);
1243 ch = wgetch(view->window);
1244 nodelay(stdscr, TRUE);
1251 if (state->first_displayed_line > 1)
1252 state->first_displayed_line--;
1257 while (i++ < view->nlines - 1 &&
1258 state->first_displayed_line > 1)
1259 state->first_displayed_line--;
1264 state->first_displayed_line++;
1269 while (!eof && i++ < view->nlines - 1) {
1270 char *line = parse_next_line(
1272 state->first_displayed_line++;
1278 err = view_resize(view);
1290 static const struct got_error *
1291 cmd_diff(int argc, char *argv[])
1293 const struct got_error *error = NULL;
1294 struct got_repository *repo = NULL;
1295 struct got_object *obj1 = NULL, *obj2 = NULL;
1296 char *repo_path = NULL;
1297 char *obj_id_str1 = NULL, *obj_id_str2 = NULL;
1299 struct tog_view *view;
1302 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
1306 while ((ch = getopt(argc, argv, "")) != -1) {
1318 usage_diff(); /* TODO show local worktree changes */
1319 } else if (argc == 2) {
1320 repo_path = getcwd(NULL, 0);
1321 if (repo_path == NULL)
1322 return got_error_from_errno();
1323 obj_id_str1 = argv[0];
1324 obj_id_str2 = argv[1];
1325 } else if (argc == 3) {
1326 repo_path = realpath(argv[0], NULL);
1327 if (repo_path == NULL)
1328 return got_error_from_errno();
1329 obj_id_str1 = argv[1];
1330 obj_id_str2 = argv[2];
1334 error = got_repo_open(&repo, repo_path);
1339 error = got_object_open_by_id_str(&obj1, repo, obj_id_str1);
1343 error = got_object_open_by_id_str(&obj2, repo, obj_id_str2);
1347 view = view_open(0, 0, 0, 0, NULL, TOG_VIEW_DIFF);
1349 error = got_error_from_errno();
1352 error = open_diff_view(view, obj1, obj2, repo);
1355 error = show_diff_view(view);
1356 close_diff_view(view);
1359 got_repo_close(repo);
1361 got_object_close(obj1);
1363 got_object_close(obj2);
1371 fprintf(stderr, "usage: %s blame [-c commit] [-r repository-path] path\n",
1376 struct tog_blame_line {
1378 struct got_object_id *id;
1381 static const struct got_error *
1382 draw_blame(struct tog_view *view, struct got_object_id *id, FILE *f,
1383 const char *path, struct tog_blame_line *lines, int nlines,
1384 int blame_complete, int selected_line, int *first_displayed_line,
1385 int *last_displayed_line, int *eof, int max_lines)
1387 const struct got_error *err;
1388 int lineno = 0, nprinted = 0;
1393 struct tog_blame_line *blame_line;
1394 struct got_object_id *prev_id = NULL;
1397 err = got_object_id_str(&id_str, id);
1402 werase(view->window);
1404 if (asprintf(&line, "commit: %s", id_str) == -1) {
1405 err = got_error_from_errno();
1410 err = format_line(&wline, &width, line, view->ncols);
1413 waddwstr(view->window, wline);
1416 if (width < view->ncols)
1417 waddch(view->window, '\n');
1419 if (asprintf(&line, "[%d/%d] %s%s",
1420 *first_displayed_line - 1 + selected_line, nlines,
1421 blame_complete ? "" : "annotating ", path) == -1) {
1423 return got_error_from_errno();
1426 err = format_line(&wline, &width, line, view->ncols);
1431 waddwstr(view->window, wline);
1434 if (width < view->ncols)
1435 waddch(view->window, '\n');
1438 while (nprinted < max_lines - 2) {
1439 line = parse_next_line(f, &len);
1444 if (++lineno < *first_displayed_line) {
1449 wlimit = view->ncols < 9 ? 0 : view->ncols - 9;
1450 err = format_line(&wline, &width, line, wlimit);
1456 if (nprinted == selected_line - 1)
1457 wstandout(view->window);
1459 blame_line = &lines[lineno - 1];
1460 if (blame_line->annotated && prev_id &&
1461 got_object_id_cmp(prev_id, blame_line->id) == 0)
1462 waddstr(view->window, " ");
1463 else if (blame_line->annotated) {
1465 err = got_object_id_str(&id_str, blame_line->id);
1471 wprintw(view->window, "%.8s ", id_str);
1473 prev_id = blame_line->id;
1475 waddstr(view->window, "........ ");
1479 waddwstr(view->window, wline);
1480 while (width < wlimit) {
1481 waddch(view->window, ' ');
1484 if (nprinted == selected_line - 1)
1485 wstandend(view->window);
1486 if (++nprinted == 1)
1487 *first_displayed_line = lineno;
1492 *last_displayed_line = lineno;
1500 struct tog_blame_cb_args {
1501 pthread_mutex_t *mutex;
1502 struct tog_blame_line *lines; /* one per line */
1505 struct tog_view *view;
1506 struct got_object_id *commit_id;
1509 int *first_displayed_line;
1510 int *last_displayed_line;
1515 static const struct got_error *
1516 blame_cb(void *arg, int nlines, int lineno, struct got_object_id *id)
1518 const struct got_error *err = NULL;
1519 struct tog_blame_cb_args *a = arg;
1520 struct tog_blame_line *line;
1523 if (nlines != a->nlines ||
1524 (lineno != -1 && lineno < 1) || lineno > a->nlines)
1525 return got_error(GOT_ERR_RANGE);
1527 if (pthread_mutex_lock(a->mutex) != 0)
1528 return got_error_from_errno();
1530 if (*a->quit) { /* user has quit the blame view */
1531 err = got_error(GOT_ERR_ITER_COMPLETED);
1536 goto done; /* no change in this commit */
1538 line = &a->lines[lineno - 1];
1539 if (line->annotated)
1542 line->id = got_object_id_dup(id);
1543 if (line->id == NULL) {
1544 err = got_error_from_errno();
1547 line->annotated = 1;
1549 err = draw_blame(a->view, a->commit_id, a->f, a->path,
1550 a->lines, a->nlines, 0, *a->selected_line, a->first_displayed_line,
1551 a->last_displayed_line, &eof, a->view->nlines);
1553 if (pthread_mutex_unlock(a->mutex) != 0)
1554 return got_error_from_errno();
1558 struct tog_blame_thread_args {
1560 struct got_repository *repo;
1561 struct tog_blame_cb_args *cb_args;
1566 blame_thread(void *arg)
1568 const struct got_error *err;
1569 struct tog_blame_thread_args *ta = arg;
1570 struct tog_blame_cb_args *a = ta->cb_args;
1573 err = got_blame_incremental(ta->path, a->commit_id, ta->repo,
1574 blame_cb, ta->cb_args);
1576 if (pthread_mutex_lock(a->mutex) != 0)
1577 return (void *)got_error_from_errno();
1579 got_repo_close(ta->repo);
1583 err = draw_blame(a->view, a->commit_id, a->f, a->path,
1584 a->lines, a->nlines, 1, *a->selected_line,
1585 a->first_displayed_line, a->last_displayed_line, &eof,
1588 if (pthread_mutex_unlock(a->mutex) != 0 && err == NULL)
1589 err = got_error_from_errno();
1594 static struct got_object_id *
1595 get_selected_commit_id(struct tog_blame_line *lines,
1596 int first_displayed_line, int selected_line)
1598 struct tog_blame_line *line;
1600 line = &lines[first_displayed_line - 1 + selected_line - 1];
1601 if (!line->annotated)
1607 static const struct got_error *
1608 open_selected_commit(struct got_object **pobj, struct got_object **obj,
1609 struct tog_blame_line *lines, int first_displayed_line,
1610 int selected_line, struct got_repository *repo)
1612 const struct got_error *err = NULL;
1613 struct got_commit_object *commit = NULL;
1614 struct got_object_id *selected_id;
1615 struct got_object_qid *pid;
1620 selected_id = get_selected_commit_id(lines,
1621 first_displayed_line, selected_line);
1622 if (selected_id == NULL)
1625 err = got_object_open(obj, repo, selected_id);
1629 err = got_object_commit_open(&commit, repo, *obj);
1633 pid = SIMPLEQ_FIRST(&commit->parent_ids);
1635 err = got_object_open(pobj, repo, pid->id);
1641 got_object_commit_close(commit);
1648 struct tog_blame_line *lines;
1651 struct tog_blame_thread_args thread_args;
1652 struct tog_blame_cb_args cb_args;
1656 static const struct got_error *
1657 stop_blame(struct tog_blame *blame)
1659 const struct got_error *err = NULL;
1662 if (blame->thread) {
1663 if (pthread_join(blame->thread, (void **)&err) != 0)
1664 err = got_error_from_errno();
1665 if (err && err->code == GOT_ERR_ITER_COMPLETED)
1667 blame->thread = NULL;
1669 if (blame->thread_args.repo) {
1670 got_repo_close(blame->thread_args.repo);
1671 blame->thread_args.repo = NULL;
1677 for (i = 0; i < blame->nlines; i++)
1678 free(blame->lines[i].id);
1680 blame->lines = NULL;
1681 free(blame->cb_args.commit_id);
1682 blame->cb_args.commit_id = NULL;
1687 static const struct got_error *
1688 run_blame(struct tog_blame *blame, pthread_mutex_t *mutex,
1689 struct tog_view *view, int *blame_complete,
1690 int *first_displayed_line, int *last_displayed_line,
1691 int *selected_line, int *done, const char *path,
1692 struct got_object_id *commit_id,
1693 struct got_repository *repo)
1695 const struct got_error *err = NULL;
1696 struct got_blob_object *blob = NULL;
1697 struct got_repository *thread_repo = NULL;
1698 struct got_object *obj;
1700 err = got_object_open_by_path(&obj, repo, commit_id, path);
1703 if (got_object_get_type(obj) != GOT_OBJ_TYPE_BLOB) {
1704 err = got_error(GOT_ERR_OBJ_TYPE);
1708 err = got_object_blob_open(&blob, repo, obj, 8192);
1711 blame->f = got_opentemp();
1712 if (blame->f == NULL) {
1713 err = got_error_from_errno();
1716 err = got_object_blob_dump_to_file(&blame->filesize, &blame->nlines,
1721 blame->lines = calloc(blame->nlines, sizeof(*blame->lines));
1722 if (blame->lines == NULL) {
1723 err = got_error_from_errno();
1727 err = got_repo_open(&thread_repo, got_repo_get_path(repo));
1731 blame->cb_args.view = view;
1732 blame->cb_args.lines = blame->lines;
1733 blame->cb_args.nlines = blame->nlines;
1734 blame->cb_args.mutex = mutex;
1735 blame->cb_args.commit_id = got_object_id_dup(commit_id);
1736 if (blame->cb_args.commit_id == NULL) {
1737 err = got_error_from_errno();
1740 blame->cb_args.f = blame->f;
1741 blame->cb_args.path = path;
1742 blame->cb_args.first_displayed_line = first_displayed_line;
1743 blame->cb_args.selected_line = selected_line;
1744 blame->cb_args.last_displayed_line = last_displayed_line;
1745 blame->cb_args.quit = done;
1747 blame->thread_args.path = path;
1748 blame->thread_args.repo = thread_repo;
1749 blame->thread_args.cb_args = &blame->cb_args;
1750 blame->thread_args.complete = blame_complete;
1751 *blame_complete = 0;
1753 if (pthread_create(&blame->thread, NULL, blame_thread,
1754 &blame->thread_args) != 0) {
1755 err = got_error_from_errno();
1761 got_object_blob_close(blob);
1763 got_object_close(obj);
1769 static const struct got_error *
1770 show_blame_view(struct tog_view *view, const char *path,
1771 struct got_object_id *commit_id, struct got_repository *repo)
1773 const struct got_error *err = NULL, *thread_err = NULL;
1774 int ch, done = 0, first_displayed_line = 1, last_displayed_line;
1775 int selected_line = first_displayed_line;
1776 int eof, blame_complete = 0;
1777 struct got_object *obj = NULL, *pobj = NULL;
1778 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
1779 struct tog_blame blame;
1780 struct got_object_id_queue blamed_commits;
1781 struct got_object_qid *blamed_commit = NULL;
1782 struct tog_view *diff_view;
1784 SIMPLEQ_INIT(&blamed_commits);
1786 if (pthread_mutex_init(&mutex, NULL) != 0) {
1787 err = got_error_from_errno();
1791 err = got_object_qid_alloc(&blamed_commit, commit_id);
1794 SIMPLEQ_INSERT_HEAD(&blamed_commits, blamed_commit, entry);
1797 last_displayed_line = view->nlines;
1799 memset(&blame, 0, sizeof(blame));
1800 err = run_blame(&blame, &mutex, view, &blame_complete,
1801 &first_displayed_line, &last_displayed_line,
1802 &selected_line, &done, path, blamed_commit->id, repo);
1807 if (pthread_mutex_lock(&mutex) != 0) {
1808 err = got_error_from_errno();
1811 err = draw_blame(view, blamed_commit->id, blame.f, path,
1812 blame.lines, blame.nlines, blame_complete, selected_line,
1813 &first_displayed_line, &last_displayed_line, &eof,
1815 if (pthread_mutex_unlock(&mutex) != 0) {
1816 err = got_error_from_errno();
1821 nodelay(stdscr, FALSE);
1822 ch = wgetch(view->window);
1823 nodelay(stdscr, TRUE);
1824 if (pthread_mutex_lock(&mutex) != 0) {
1825 err = got_error_from_errno();
1834 if (selected_line > 1)
1836 else if (selected_line == 1 &&
1837 first_displayed_line > 1)
1838 first_displayed_line--;
1842 if (first_displayed_line == 1) {
1846 if (first_displayed_line > view->nlines - 2)
1847 first_displayed_line -=
1850 first_displayed_line = 1;
1854 if (selected_line < view->nlines - 2 &&
1855 first_displayed_line + selected_line <=
1858 else if (last_displayed_line < blame.nlines)
1859 first_displayed_line++;
1863 struct got_object_id *id;
1864 id = get_selected_commit_id(blame.lines,
1865 first_displayed_line, selected_line);
1866 if (id == NULL || got_object_id_cmp(id,
1867 blamed_commit->id) == 0)
1869 err = open_selected_commit(&pobj, &obj,
1870 blame.lines, first_displayed_line,
1871 selected_line, repo);
1874 if (pobj == NULL && obj == NULL)
1876 if (ch == 'p' && pobj == NULL)
1879 if (pthread_mutex_unlock(&mutex) != 0) {
1880 err = got_error_from_errno();
1883 thread_err = stop_blame(&blame);
1885 if (pthread_mutex_lock(&mutex) != 0) {
1886 err = got_error_from_errno();
1891 id = got_object_get_id(ch == 'b' ? obj : pobj);
1892 got_object_close(obj);
1895 got_object_close(pobj);
1899 err = got_error_from_errno();
1902 err = got_object_qid_alloc(&blamed_commit, id);
1906 SIMPLEQ_INSERT_HEAD(&blamed_commits,
1907 blamed_commit, entry);
1908 err = run_blame(&blame, &mutex, view,
1909 &blame_complete, &first_displayed_line,
1910 &last_displayed_line, &selected_line,
1911 &done, path, blamed_commit->id, repo);
1917 struct got_object_qid *first;
1918 first = SIMPLEQ_FIRST(&blamed_commits);
1919 if (!got_object_id_cmp(first->id, commit_id))
1922 if (pthread_mutex_unlock(&mutex) != 0) {
1923 err = got_error_from_errno();
1926 thread_err = stop_blame(&blame);
1928 if (pthread_mutex_lock(&mutex) != 0) {
1929 err = got_error_from_errno();
1934 SIMPLEQ_REMOVE_HEAD(&blamed_commits, entry);
1935 got_object_qid_free(blamed_commit);
1936 blamed_commit = SIMPLEQ_FIRST(&blamed_commits);
1937 err = run_blame(&blame, &mutex, view,
1938 &blame_complete, &first_displayed_line,
1939 &last_displayed_line, &selected_line,
1940 &done, path, blamed_commit->id, repo);
1947 err = open_selected_commit(&pobj, &obj,
1948 blame.lines, first_displayed_line,
1949 selected_line, repo);
1952 if (pobj == NULL && obj == NULL)
1954 diff_view = view_open(0, 0, 0, 0, view,
1956 if (diff_view == NULL) {
1957 err = got_error_from_errno();
1960 err = open_diff_view(diff_view, pobj, obj, repo);
1963 err = show_diff_view(diff_view);
1964 close_diff_view(diff_view);
1965 view_close(diff_view);
1970 got_object_close(pobj);
1973 got_object_close(obj);
1980 if (last_displayed_line >= blame.nlines &&
1981 selected_line < view->nlines - 2) {
1982 selected_line = MIN(blame.nlines,
1986 if (last_displayed_line + view->nlines - 2 <=
1988 first_displayed_line +=
1991 first_displayed_line =
1992 blame.nlines - (view->nlines - 3);
1995 err = view_resize(view);
1998 if (selected_line > view->nlines - 2) {
1999 selected_line = MIN(blame.nlines,
2006 if (pthread_mutex_unlock(&mutex) != 0)
2007 err = got_error_from_errno();
2008 if (err || thread_err)
2013 got_object_close(pobj);
2015 thread_err = stop_blame(&blame);
2016 while (!SIMPLEQ_EMPTY(&blamed_commits)) {
2017 blamed_commit = SIMPLEQ_FIRST(&blamed_commits);
2018 SIMPLEQ_REMOVE_HEAD(&blamed_commits, entry);
2019 got_object_qid_free(blamed_commit);
2021 return thread_err ? thread_err : err;
2024 static const struct got_error *
2025 cmd_blame(int argc, char *argv[])
2027 const struct got_error *error;
2028 struct got_repository *repo = NULL;
2029 char *path, *cwd = NULL, *repo_path = NULL, *in_repo_path = NULL;
2030 struct got_object_id *commit_id = NULL;
2031 char *commit_id_str = NULL;
2033 struct tog_view *view;
2036 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
2040 while ((ch = getopt(argc, argv, "c:r:")) != -1) {
2043 commit_id_str = optarg;
2046 repo_path = realpath(optarg, NULL);
2047 if (repo_path == NULL)
2048 err(1, "-r option");
2064 cwd = getcwd(NULL, 0);
2066 error = got_error_from_errno();
2069 if (repo_path == NULL) {
2070 repo_path = strdup(cwd);
2071 if (repo_path == NULL) {
2072 error = got_error_from_errno();
2078 error = got_repo_open(&repo, repo_path);
2082 error = got_repo_map_path(&in_repo_path, repo, path);
2086 if (commit_id_str == NULL) {
2087 struct got_reference *head_ref;
2088 error = got_ref_open(&head_ref, repo, GOT_REF_HEAD);
2091 error = got_ref_resolve(&commit_id, repo, head_ref);
2092 got_ref_close(head_ref);
2094 struct got_object *obj;
2095 error = got_object_open_by_id_str(&obj, repo, commit_id_str);
2098 commit_id = got_object_get_id(obj);
2099 if (commit_id == NULL)
2100 error = got_error_from_errno();
2101 got_object_close(obj);
2106 view = view_open(0, 0, 0, 0, NULL, TOG_VIEW_BLAME);
2108 error = got_error_from_errno();
2111 error = show_blame_view(view, in_repo_path, commit_id, repo);
2119 got_repo_close(repo);
2123 static const struct got_error *
2124 draw_tree_entries(struct tog_view *view,
2125 struct got_tree_entry **first_displayed_entry,
2126 struct got_tree_entry **last_displayed_entry,
2127 struct got_tree_entry **selected_entry, int *ndisplayed,
2128 const char *label, int show_ids, const char *parent_path,
2129 const struct got_tree_entries *entries, int selected, int limit, int isroot)
2131 const struct got_error *err = NULL;
2132 struct got_tree_entry *te;
2138 werase(view->window);
2143 err = format_line(&wline, &width, label, view->ncols);
2146 waddwstr(view->window, wline);
2149 if (width < view->ncols)
2150 waddch(view->window, '\n');
2153 err = format_line(&wline, &width, parent_path, view->ncols);
2156 waddwstr(view->window, wline);
2159 if (width < view->ncols)
2160 waddch(view->window, '\n');
2163 waddch(view->window, '\n');
2167 te = SIMPLEQ_FIRST(&entries->head);
2168 if (*first_displayed_entry == NULL) {
2169 if (selected == 0) {
2170 wstandout(view->window);
2171 *selected_entry = NULL;
2173 waddstr(view->window, " ..\n"); /* parent directory */
2175 wstandend(view->window);
2182 while (te != *first_displayed_entry)
2183 te = SIMPLEQ_NEXT(te, entry);
2187 char *line = NULL, *id_str = NULL;
2190 err = got_object_id_str(&id_str, te->id);
2192 return got_error_from_errno();
2194 if (asprintf(&line, "%s %s%s", id_str ? id_str : "",
2195 te->name, S_ISDIR(te->mode) ? "/" : "") == -1) {
2197 return got_error_from_errno();
2200 err = format_line(&wline, &width, line, view->ncols);
2205 if (n == selected) {
2206 wstandout(view->window);
2207 *selected_entry = te;
2209 waddwstr(view->window, wline);
2210 if (width < view->ncols)
2211 waddch(view->window, '\n');
2213 wstandend(view->window);
2219 *last_displayed_entry = te;
2222 te = SIMPLEQ_NEXT(te, entry);
2229 tree_scroll_up(struct got_tree_entry **first_displayed_entry, int maxscroll,
2230 const struct got_tree_entries *entries, int isroot)
2232 struct got_tree_entry *te, *prev;
2235 if (*first_displayed_entry == NULL)
2238 te = SIMPLEQ_FIRST(&entries->head);
2239 if (*first_displayed_entry == te) {
2241 *first_displayed_entry = NULL;
2245 /* XXX this is stupid... switch to TAILQ? */
2246 for (i = 0; i < maxscroll; i++) {
2247 while (te != *first_displayed_entry) {
2249 te = SIMPLEQ_NEXT(te, entry);
2251 *first_displayed_entry = prev;
2252 te = SIMPLEQ_FIRST(&entries->head);
2254 if (!isroot && te == SIMPLEQ_FIRST(&entries->head) && i < maxscroll)
2255 *first_displayed_entry = NULL;
2259 tree_scroll_down(struct got_tree_entry **first_displayed_entry, int maxscroll,
2260 struct got_tree_entry *last_displayed_entry,
2261 const struct got_tree_entries *entries)
2263 struct got_tree_entry *next;
2266 if (SIMPLEQ_NEXT(last_displayed_entry, entry) == NULL)
2269 if (*first_displayed_entry)
2270 next = SIMPLEQ_NEXT(*first_displayed_entry, entry);
2272 next = SIMPLEQ_FIRST(&entries->head);
2274 *first_displayed_entry = next;
2275 if (++n >= maxscroll)
2277 next = SIMPLEQ_NEXT(next, entry);
2281 struct tog_parent_tree {
2282 TAILQ_ENTRY(tog_parent_tree) entry;
2283 struct got_tree_object *tree;
2284 struct got_tree_entry *first_displayed_entry;
2285 struct got_tree_entry *selected_entry;
2289 TAILQ_HEAD(tog_parent_trees, tog_parent_tree);
2291 static const struct got_error *
2292 tree_entry_path(char **path, struct tog_parent_trees *parents,
2293 struct got_tree_entry *te)
2295 const struct got_error *err = NULL;
2296 struct tog_parent_tree *pt;
2297 size_t len = 2; /* for leading slash and NUL */
2299 TAILQ_FOREACH(pt, parents, entry)
2300 len += strlen(pt->selected_entry->name) + 1 /* slash */;
2302 len += strlen(te->name);
2304 *path = calloc(1, len);
2306 return got_error_from_errno();
2309 pt = TAILQ_LAST(parents, tog_parent_trees);
2311 if (strlcat(*path, pt->selected_entry->name, len) >= len) {
2312 err = got_error(GOT_ERR_NO_SPACE);
2315 if (strlcat(*path, "/", len) >= len) {
2316 err = got_error(GOT_ERR_NO_SPACE);
2319 pt = TAILQ_PREV(pt, tog_parent_trees, entry);
2322 if (strlcat(*path, te->name, len) >= len) {
2323 err = got_error(GOT_ERR_NO_SPACE);
2335 static const struct got_error *
2336 blame_tree_entry(struct tog_view *parent_view, struct got_tree_entry *te,
2337 struct tog_parent_trees *parents, struct got_object_id *commit_id,
2338 struct got_repository *repo)
2340 const struct got_error *err = NULL;
2342 struct tog_view *view;
2344 err = tree_entry_path(&path, parents, te);
2348 view = view_open(0, 0, 0, 0, parent_view, TOG_VIEW_BLAME);
2350 err = show_blame_view(view, path, commit_id, repo);
2353 err = got_error_from_errno();
2355 view_show(parent_view);
2360 static const struct got_error *
2361 log_tree_entry(struct tog_view *view, struct got_tree_entry *te,
2362 struct tog_parent_trees *parents, struct got_object_id *commit_id,
2363 struct got_repository *repo)
2365 const struct got_error *err = NULL;
2368 err = tree_entry_path(&path, parents, te);
2372 err = open_log_view(view, commit_id, repo, path);
2375 err = show_log_view(view);
2376 close_log_view(view);
2382 static const struct got_error *
2383 show_tree_view(struct tog_view *view, struct got_tree_object *root,
2384 struct got_object_id *commit_id, struct got_repository *repo)
2386 const struct got_error *err = NULL;
2387 int ch, done = 0, selected = 0, show_ids = 0;
2388 struct got_tree_object *tree = root;
2389 const struct got_tree_entries *entries;
2390 struct got_tree_entry *first_displayed_entry = NULL;
2391 struct got_tree_entry *last_displayed_entry = NULL;
2392 struct got_tree_entry *selected_entry = NULL;
2393 char *commit_id_str = NULL, *tree_label = NULL;
2394 int nentries, ndisplayed;
2395 struct tog_parent_trees parents;
2397 TAILQ_INIT(&parents);
2399 err = got_object_id_str(&commit_id_str, commit_id);
2403 if (asprintf(&tree_label, "commit: %s", commit_id_str) == -1) {
2404 err = got_error_from_errno();
2410 entries = got_object_tree_get_entries(root);
2411 first_displayed_entry = SIMPLEQ_FIRST(&entries->head);
2414 entries = got_object_tree_get_entries(tree);
2415 nentries = entries->nentries;
2417 nentries++; /* '..' directory */
2419 err = tree_entry_path(&parent_path, &parents, NULL);
2423 err = draw_tree_entries(view, &first_displayed_entry,
2424 &last_displayed_entry, &selected_entry, &ndisplayed,
2425 tree_label, show_ids, parent_path, entries, selected,
2426 view->nlines, tree == root);
2431 nodelay(stdscr, FALSE);
2432 ch = wgetch(view->window);
2433 nodelay(stdscr, TRUE);
2439 show_ids = !show_ids;
2442 if (selected_entry) {
2443 struct tog_view *log_view;
2444 log_view = view_open(0, 0, 0, 0, view,
2446 if (log_view == NULL) {
2447 err = got_error_from_errno();
2450 err = log_tree_entry(log_view,
2451 selected_entry, &parents,
2453 view_close(log_view);
2465 tree_scroll_up(&first_displayed_entry, 1,
2466 entries, tree == root);
2469 if (SIMPLEQ_FIRST(&entries->head) ==
2470 first_displayed_entry) {
2472 first_displayed_entry = NULL;
2476 tree_scroll_up(&first_displayed_entry,
2477 view->nlines, entries, tree == root);
2481 if (selected < ndisplayed - 1) {
2485 tree_scroll_down(&first_displayed_entry, 1,
2486 last_displayed_entry, entries);
2489 tree_scroll_down(&first_displayed_entry,
2490 view->nlines, last_displayed_entry,
2492 if (SIMPLEQ_NEXT(last_displayed_entry, entry))
2494 /* can't scroll any further; move cursor down */
2495 if (selected < ndisplayed - 1)
2496 selected = ndisplayed - 1;
2500 if (selected_entry == NULL) {
2501 struct tog_parent_tree *parent;
2503 /* user selected '..' */
2506 parent = TAILQ_FIRST(&parents);
2507 TAILQ_REMOVE(&parents, parent, entry);
2508 got_object_tree_close(tree);
2509 tree = parent->tree;
2510 first_displayed_entry =
2511 parent->first_displayed_entry;
2512 selected_entry = parent->selected_entry;
2513 selected = parent->selected;
2515 } else if (S_ISDIR(selected_entry->mode)) {
2516 struct tog_parent_tree *parent;
2517 struct got_tree_object *child;
2518 err = got_object_open_as_tree(
2519 &child, repo, selected_entry->id);
2522 parent = calloc(1, sizeof(*parent));
2523 if (parent == NULL) {
2524 err = got_error_from_errno();
2527 parent->tree = tree;
2528 parent->first_displayed_entry =
2529 first_displayed_entry;
2530 parent->selected_entry = selected_entry;
2531 parent->selected = selected;
2532 TAILQ_INSERT_HEAD(&parents, parent,
2536 first_displayed_entry = NULL;
2537 } else if (S_ISREG(selected_entry->mode)) {
2538 err = blame_tree_entry(view,
2539 selected_entry, &parents,
2546 err = view_resize(view);
2549 if (selected > view->nlines)
2550 selected = ndisplayed - 1;
2558 free(commit_id_str);
2559 while (!TAILQ_EMPTY(&parents)) {
2560 struct tog_parent_tree *parent;
2561 parent = TAILQ_FIRST(&parents);
2562 TAILQ_REMOVE(&parents, parent, entry);
2567 got_object_tree_close(tree);
2575 fprintf(stderr, "usage: %s tree [-c commit] [repository-path]\n",
2580 static const struct got_error *
2581 cmd_tree(int argc, char *argv[])
2583 const struct got_error *error;
2584 struct got_repository *repo = NULL;
2585 char *repo_path = NULL;
2586 struct got_object_id *commit_id = NULL;
2587 char *commit_id_arg = NULL;
2588 struct got_commit_object *commit = NULL;
2589 struct got_tree_object *tree = NULL;
2591 struct tog_view *view;
2594 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
2598 while ((ch = getopt(argc, argv, "c:")) != -1) {
2601 commit_id_arg = optarg;
2613 repo_path = getcwd(NULL, 0);
2614 if (repo_path == NULL)
2615 return got_error_from_errno();
2616 } else if (argc == 1) {
2617 repo_path = realpath(argv[0], NULL);
2618 if (repo_path == NULL)
2619 return got_error_from_errno();
2623 error = got_repo_open(&repo, repo_path);
2628 if (commit_id_arg == NULL) {
2629 error = get_head_commit_id(&commit_id, repo);
2633 struct got_object *obj;
2634 error = got_object_open_by_id_str(&obj, repo, commit_id_arg);
2635 if (error == NULL) {
2636 commit_id = got_object_get_id(obj);
2637 if (commit_id == NULL)
2638 error = got_error_from_errno();
2644 error = got_object_open_as_commit(&commit, repo, commit_id);
2648 error = got_object_open_as_tree(&tree, repo, commit->tree_id);
2652 view = view_open(0, 0, 0, 0, NULL, TOG_VIEW_TREE);
2654 error = got_error_from_errno();
2657 error = show_tree_view(view, tree, commit_id, repo);
2662 got_object_commit_close(commit);
2664 got_object_tree_close(tree);
2666 got_repo_close(repo);
2676 intrflush(stdscr, FALSE);
2677 keypad(stdscr, TRUE);
2686 fprintf(stderr, "usage: %s [-h] [command] [arg ...]\n\n"
2687 "Available commands:\n", getprogname());
2688 for (i = 0; i < nitems(tog_commands); i++) {
2689 struct tog_cmd *cmd = &tog_commands[i];
2690 fprintf(stderr, " %s: %s\n", cmd->name, cmd->descr);
2696 make_argv(const char *arg0, const char *arg1)
2699 int argc = (arg1 == NULL ? 1 : 2);
2701 argv = calloc(argc, sizeof(char *));
2704 argv[0] = strdup(arg0);
2705 if (argv[0] == NULL)
2708 argv[1] = strdup(arg1);
2709 if (argv[1] == NULL)
2717 main(int argc, char *argv[])
2719 const struct got_error *error = NULL;
2720 struct tog_cmd *cmd = NULL;
2722 char **cmd_argv = NULL;
2724 setlocale(LC_ALL, "");
2726 while ((ch = getopt(argc, argv, "h")) != -1) {
2745 /* Build an argument vector which runs a default command. */
2746 cmd = &tog_commands[0];
2747 cmd_argv = make_argv(cmd->name, NULL);
2752 /* Did the user specific a command? */
2753 for (i = 0; i < nitems(tog_commands); i++) {
2754 if (strncmp(tog_commands[i].name, argv[0],
2755 strlen(argv[0])) == 0) {
2756 cmd = &tog_commands[i];
2758 tog_commands[i].cmd_usage();
2763 /* Did the user specify a repository? */
2764 char *repo_path = realpath(argv[0], NULL);
2766 struct got_repository *repo;
2767 error = got_repo_open(&repo, repo_path);
2769 got_repo_close(repo);
2771 error = got_error_from_errno();
2774 fprintf(stderr, "%s: '%s' is not a "
2775 "known command\n", getprogname(),
2779 fprintf(stderr, "%s: '%s' is neither a known "
2780 "command nor a path to a repository\n",
2781 getprogname(), argv[0]);
2785 cmd = &tog_commands[0];
2786 cmd_argv = make_argv(cmd->name, repo_path);
2794 error = cmd->cmd_main(argc, cmd_argv ? cmd_argv : argv);
2801 fprintf(stderr, "%s: %s\n", getprogname(), error->msg);