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" },
85 static struct tog_view {
88 } tog_log_view, tog_diff_view, tog_blame_view, tog_tree_view;
90 static const struct got_error *
91 show_diff_view(struct got_object *, struct got_object *,
92 struct got_repository *);
93 static const struct got_error *
94 show_log_view(struct got_object_id *, struct got_repository *, const char *);
95 static const struct got_error *
96 show_blame_view(const char *, struct got_object_id *, struct got_repository *);
97 static const struct got_error *
98 show_tree_view(struct got_tree_object *, struct got_object_id *,
99 struct got_repository *);
105 fprintf(stderr, "usage: %s log [-c commit] [-r repository-path] [path]\n",
110 /* Create newly allocated wide-character string equivalent to a byte string. */
111 static const struct got_error *
112 mbs2ws(wchar_t **ws, size_t *wlen, const char *s)
115 const struct got_error *err = NULL;
118 *wlen = mbstowcs(NULL, s, 0);
119 if (*wlen == (size_t)-1) {
122 return got_error_from_errno();
124 /* byte string invalid in current encoding; try to "fix" it */
125 err = got_mbsavis(&vis, &vislen, s);
128 *wlen = mbstowcs(NULL, vis, 0);
129 if (*wlen == (size_t)-1) {
130 err = got_error_from_errno(); /* give up */
135 *ws = calloc(*wlen + 1, sizeof(*ws));
137 err = got_error_from_errno();
141 if (mbstowcs(*ws, vis ? vis : s, *wlen) != *wlen)
142 err = got_error_from_errno();
153 /* Format a line for display, ensuring that it won't overflow a width limit. */
154 static const struct got_error *
155 format_line(wchar_t **wlinep, int *widthp, const char *line, int wlimit)
157 const struct got_error *err = NULL;
159 wchar_t *wline = NULL;
166 err = mbs2ws(&wline, &wlen, line);
171 while (i < wlen && cols < wlimit) {
172 int width = wcwidth(wline[i]);
179 if (cols + width <= wlimit) {
185 if (wline[i] == L'\t')
186 cols += TABSIZE - ((cols + 1) % TABSIZE);
190 err = got_error_from_errno();
205 static const struct got_error *
206 draw_commit(struct got_commit_object *commit, struct got_object_id *id)
208 const struct got_error *err = NULL;
209 char datebuf[10]; /* YY-MM-DD + SPACE + NUL */
210 char *logmsg0 = NULL, *logmsg = NULL;
211 char *author0 = NULL, *author = NULL;
212 wchar_t *wlogmsg = NULL, *wauthor = NULL;
213 int author_width, logmsg_width;
214 char *newline, *smallerthan;
217 static const size_t date_display_cols = 9;
218 static const size_t author_display_cols = 16;
219 const int avail = COLS;
221 if (strftime(datebuf, sizeof(datebuf), "%g/%m/%d ", &commit->tm_committer)
223 return got_error(GOT_ERR_NO_SPACE);
225 if (avail < date_display_cols)
226 limit = MIN(sizeof(datebuf) - 1, avail);
228 limit = MIN(date_display_cols, sizeof(datebuf) - 1);
229 waddnstr(tog_log_view.window, datebuf, limit);
234 author0 = strdup(commit->author);
235 if (author0 == NULL) {
236 err = got_error_from_errno();
240 smallerthan = strchr(author, '<');
244 char *at = strchr(author, '@');
249 err = format_line(&wauthor, &author_width, author, limit);
252 waddwstr(tog_log_view.window, wauthor);
254 while (col <= avail && author_width < author_display_cols + 1) {
255 waddch(tog_log_view.window, ' ');
262 logmsg0 = strdup(commit->logmsg);
263 if (logmsg0 == NULL) {
264 err = got_error_from_errno();
268 while (*logmsg == '\n')
270 newline = strchr(logmsg, '\n');
274 err = format_line(&wlogmsg, &logmsg_width, logmsg, limit);
277 waddwstr(tog_log_view.window, wlogmsg);
279 while (col <= avail) {
280 waddch(tog_log_view.window, ' ');
292 struct commit_queue_entry {
293 TAILQ_ENTRY(commit_queue_entry) entry;
294 struct got_object_id *id;
295 struct got_commit_object *commit;
297 TAILQ_HEAD(commit_queue_head, commit_queue_entry);
298 struct commit_queue {
300 struct commit_queue_head head;
303 static struct commit_queue_entry *
304 alloc_commit_queue_entry(struct got_commit_object *commit,
305 struct got_object_id *id)
307 struct commit_queue_entry *entry;
309 entry = calloc(1, sizeof(*entry));
314 entry->commit = commit;
319 pop_commit(struct commit_queue *commits)
321 struct commit_queue_entry *entry;
323 entry = TAILQ_FIRST(&commits->head);
324 TAILQ_REMOVE(&commits->head, entry, entry);
325 got_object_commit_close(entry->commit);
327 /* Don't free entry->id! It is owned by the commit graph. */
332 free_commits(struct commit_queue *commits)
334 while (!TAILQ_EMPTY(&commits->head))
338 static const struct got_error *
339 queue_commits(struct got_commit_graph *graph, struct commit_queue *commits,
340 struct got_object_id *start_id, int minqueue, int init,
341 struct got_repository *repo, const char *path)
343 const struct got_error *err = NULL;
344 struct got_object_id *id;
345 struct commit_queue_entry *entry;
346 int nfetched, nqueued = 0, found_obj = 0;
348 err = got_commit_graph_iter_start(graph, start_id);
352 entry = TAILQ_LAST(&commits->head, commit_queue_head);
353 if (entry && got_object_id_cmp(entry->id, start_id) == 0) {
356 /* Start ID's commit is already on the queue; skip over it. */
357 err = got_commit_graph_iter_next(&id, graph);
358 if (err && err->code != GOT_ERR_ITER_NEED_MORE)
361 err = got_commit_graph_fetch_commits(&nfetched, graph, 1, repo);
367 struct got_commit_object *commit;
369 err = got_commit_graph_iter_next(&id, graph);
371 if (err->code != GOT_ERR_ITER_NEED_MORE)
373 if (nqueued >= minqueue) {
377 err = got_commit_graph_fetch_commits(&nfetched,
386 err = got_object_open_as_commit(&commit, repo, id);
391 struct got_object *obj;
392 struct got_object_qid *pid;
395 err = got_object_open_by_path(&obj, repo, id, path);
397 if (err->code == GOT_ERR_NO_OBJ &&
398 (found_obj || !init)) {
399 /* History stops here. */
400 err = got_error(GOT_ERR_ITER_COMPLETED);
406 pid = SIMPLEQ_FIRST(&commit->parent_ids);
408 struct got_object *pobj;
409 err = got_object_open_by_path(&pobj, repo,
412 if (err->code != GOT_ERR_NO_OBJ) {
413 got_object_close(obj);
419 changed = (got_object_id_cmp(
420 got_object_get_id(obj),
421 got_object_get_id(pobj)) != 0);
422 got_object_close(pobj);
426 got_object_commit_close(commit);
431 entry = alloc_commit_queue_entry(commit, id);
433 err = got_error_from_errno();
436 TAILQ_INSERT_TAIL(&commits->head, entry, entry);
444 static const struct got_error *
445 fetch_next_commit(struct commit_queue_entry **pentry,
446 struct commit_queue_entry *entry, struct commit_queue *commits,
447 struct got_commit_graph *graph, struct got_repository *repo,
450 const struct got_error *err = NULL;
454 err = queue_commits(graph, commits, entry->id, 1, 0, repo, path);
458 /* Next entry to display should now be available. */
459 *pentry = TAILQ_NEXT(entry, entry);
461 return got_error(GOT_ERR_NO_OBJ);
466 static const struct got_error *
467 get_head_commit_id(struct got_object_id **head_id, struct got_repository *repo)
469 const struct got_error *err = NULL;
470 struct got_reference *head_ref;
474 err = got_ref_open(&head_ref, repo, GOT_REF_HEAD);
478 err = got_ref_resolve(head_id, repo, head_ref);
479 got_ref_close(head_ref);
488 static const struct got_error *
489 draw_commits(struct commit_queue_entry **last,
490 struct commit_queue_entry **selected, struct commit_queue_entry *first,
491 int selected_idx, int limit, const char *path)
493 const struct got_error *err = NULL;
494 struct commit_queue_entry *entry;
496 char *id_str, *header;
503 if (++ncommits - 1 == selected_idx) {
507 entry = TAILQ_NEXT(entry, entry);
509 if (*selected == NULL)
510 return got_error(GOT_ERR_RANGE);
512 err = got_object_id_str(&id_str, (*selected)->id);
517 if (asprintf(&header, "commit: %s [%s]", id_str, path) == -1) {
518 err = got_error_from_errno();
522 } else if (asprintf(&header, "commit: %s", id_str) == -1) {
523 err = got_error_from_errno();
528 err = format_line(&wline, &width, header, COLS);
535 werase(tog_log_view.window);
537 waddwstr(tog_log_view.window, wline);
539 waddch(tog_log_view.window, '\n');
548 if (ncommits >= limit - 1)
550 if (ncommits == selected_idx) {
551 wstandout(tog_log_view.window);
554 err = draw_commit(entry->commit, entry->id);
555 if (ncommits == selected_idx)
556 wstandend(tog_log_view.window);
561 entry = TAILQ_NEXT(entry, entry);
571 scroll_up(struct commit_queue_entry **first_displayed_entry, int maxscroll,
572 struct commit_queue *commits)
574 struct commit_queue_entry *entry;
577 entry = TAILQ_FIRST(&commits->head);
578 if (*first_displayed_entry == entry)
581 entry = *first_displayed_entry;
582 while (entry && nscrolled < maxscroll) {
583 entry = TAILQ_PREV(entry, commit_queue_head, entry);
585 *first_displayed_entry = entry;
591 static const struct got_error *
592 scroll_down(struct commit_queue_entry **first_displayed_entry, int maxscroll,
593 struct commit_queue_entry *last_displayed_entry,
594 struct commit_queue *commits, struct got_commit_graph *graph,
595 struct got_repository *repo, const char *path)
597 const struct got_error *err = NULL;
598 struct commit_queue_entry *pentry;
602 pentry = TAILQ_NEXT(last_displayed_entry, entry);
603 if (pentry == NULL) {
604 err = fetch_next_commit(&pentry, last_displayed_entry,
605 commits, graph, repo, path);
606 if (err || pentry == NULL)
609 last_displayed_entry = pentry;
611 pentry = TAILQ_NEXT(*first_displayed_entry, entry);
614 *first_displayed_entry = pentry;
615 } while (++nscrolled < maxscroll);
620 static const struct got_error *
621 show_commit(struct commit_queue_entry *entry, struct got_repository *repo)
623 const struct got_error *err;
624 struct got_object *obj1 = NULL, *obj2 = NULL;
625 struct got_object_qid *parent_id;
627 err = got_object_open(&obj2, repo, entry->id);
631 parent_id = SIMPLEQ_FIRST(&entry->commit->parent_ids);
633 err = got_object_open(&obj1, repo, parent_id->id);
638 err = show_diff_view(obj1, obj2, repo);
641 got_object_close(obj1);
643 got_object_close(obj2);
647 static const struct got_error *
648 browse_commit(struct commit_queue_entry *entry, struct got_repository *repo)
650 const struct got_error *err = NULL;
651 struct got_tree_object *tree;
653 err = got_object_open_as_tree(&tree, repo, entry->commit->tree_id);
657 err = show_tree_view(tree, entry->id, repo);
658 got_object_tree_close(tree);
662 static const struct got_error *
663 show_log_view(struct got_object_id *start_id, struct got_repository *repo,
666 const struct got_error *err = NULL;
667 struct got_object_id *head_id = NULL;
668 int ch, done = 0, selected = 0, nfetched;
669 struct got_commit_graph *graph = NULL;
670 struct commit_queue commits;
671 struct commit_queue_entry *first_displayed_entry = NULL;
672 struct commit_queue_entry *last_displayed_entry = NULL;
673 struct commit_queue_entry *selected_entry = NULL;
674 struct commit_queue_entry *entry;
675 char *in_repo_path = NULL;
677 err = got_repo_map_path(&in_repo_path, repo, path);
681 if (tog_log_view.window == NULL) {
682 tog_log_view.window = newwin(0, 0, 0, 0);
683 if (tog_log_view.window == NULL)
684 return got_error_from_errno();
685 keypad(tog_log_view.window, TRUE);
687 if (tog_log_view.panel == NULL) {
688 tog_log_view.panel = new_panel(tog_log_view.window);
689 if (tog_log_view.panel == NULL)
690 return got_error_from_errno();
692 show_panel(tog_log_view.panel);
694 err = get_head_commit_id(&head_id, repo);
698 TAILQ_INIT(&commits.head);
699 commits.ncommits = 0;
701 err = got_commit_graph_open(&graph, head_id, 0, repo);
705 /* Populate commit graph with a sufficient number of commits. */
706 err = got_commit_graph_fetch_commits_up_to(&nfetched, graph, start_id,
712 * Open the initial batch of commits, sorted in commit graph order.
713 * We keep all commits open throughout the lifetime of the log view
714 * in order to avoid having to re-fetch commits from disk while
715 * updating the display.
717 err = queue_commits(graph, &commits, head_id, LINES, 1, repo,
719 if (err && err->code != GOT_ERR_ITER_COMPLETED)
723 * Find entry corresponding to the first commit to display.
724 * if both a path and start commit was specified, the first commit
725 * shown should be a commit <= start_commit which modified the path.
728 struct got_object_id *id;
730 err = got_commit_graph_iter_start(graph, start_id);
734 err = got_commit_graph_iter_next(&id, graph);
738 err = got_error(GOT_ERR_NO_OBJ);
742 * The graph contains all commits. The commit queue
743 * contains a subset of commits filtered by path.
745 TAILQ_FOREACH(entry, &commits.head, entry) {
746 if (got_object_id_cmp(entry->id, id) == 0) {
747 first_displayed_entry = entry;
751 } while (first_displayed_entry == NULL);
753 TAILQ_FOREACH(entry, &commits.head, entry) {
754 if (got_object_id_cmp(entry->id, start_id) == 0) {
755 first_displayed_entry = entry;
760 if (first_displayed_entry == NULL) {
761 err = got_error(GOT_ERR_NO_OBJ);
765 selected_entry = first_displayed_entry;
767 err = draw_commits(&last_displayed_entry, &selected_entry,
768 first_displayed_entry, selected, LINES, in_repo_path);
772 nodelay(stdscr, FALSE);
773 ch = wgetch(tog_log_view.window);
774 nodelay(stdscr, TRUE);
778 err = got_error_from_errno();
791 scroll_up(&first_displayed_entry, 1, &commits);
794 if (TAILQ_FIRST(&commits.head) ==
795 first_displayed_entry) {
799 scroll_up(&first_displayed_entry, LINES,
804 if (selected < MIN(LINES - 2,
805 commits.ncommits - 1)) {
809 err = scroll_down(&first_displayed_entry, 1,
810 last_displayed_entry, &commits, graph,
813 if (err->code != GOT_ERR_ITER_COMPLETED)
819 struct commit_queue_entry *first = first_displayed_entry;
820 err = scroll_down(&first_displayed_entry, LINES,
821 last_displayed_entry, &commits, graph,
824 if (err->code != GOT_ERR_ITER_COMPLETED)
826 /* can't scroll any further; move cursor down */
827 if (first == first_displayed_entry && selected <
828 MIN(LINES - 2, commits.ncommits - 1)) {
829 selected = MIN(LINES - 2,
830 commits.ncommits - 1);
837 if (selected > LINES - 1)
838 selected = LINES - 2;
842 err = show_commit(selected_entry, repo);
845 show_panel(tog_log_view.panel);
848 err = browse_commit(selected_entry, repo);
851 show_panel(tog_log_view.panel);
860 got_commit_graph_close(graph);
861 free_commits(&commits);
866 static const struct got_error *
867 cmd_log(int argc, char *argv[])
869 const struct got_error *error;
870 struct got_repository *repo = NULL;
871 struct got_object_id *start_id = NULL;
872 char *path = NULL, *repo_path = NULL, *cwd = NULL;
873 char *start_commit = NULL;
877 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
881 while ((ch = getopt(argc, argv, "c:r:")) != -1) {
884 start_commit = optarg;
887 repo_path = realpath(optarg, NULL);
888 if (repo_path == NULL)
903 path = strdup(argv[0]);
907 return got_error_from_errno();
909 cwd = getcwd(NULL, 0);
911 error = got_error_from_errno();
914 if (repo_path == NULL) {
915 repo_path = strdup(cwd);
916 if (repo_path == NULL) {
917 error = got_error_from_errno();
922 error = got_repo_open(&repo, repo_path);
926 if (start_commit == NULL) {
927 error = get_head_commit_id(&start_id, repo);
931 struct got_object *obj;
932 error = got_object_open_by_id_str(&obj, repo, start_commit);
934 start_id = got_object_get_id(obj);
935 if (start_id == NULL)
936 error = got_error_from_errno();
943 error = show_log_view(start_id, repo, path);
950 got_repo_close(repo);
958 fprintf(stderr, "usage: %s diff [repository-path] object1 object2\n",
964 parse_next_line(FILE *f, size_t *len)
969 const char delim[3] = { '\0', '\0', '\0'};
971 line = fparseln(f, &linelen, &lineno, delim, 0);
977 static const struct got_error *
978 draw_file(WINDOW *window, FILE *f, int *first_displayed_line,
979 int *last_displayed_line, int *eof, int max_lines)
981 const struct got_error *err;
982 int nlines = 0, nprinted = 0;
992 while (nprinted < max_lines) {
993 line = parse_next_line(f, &len);
998 if (++nlines < *first_displayed_line) {
1003 err = format_line(&wline, &width, line, COLS);
1009 waddwstr(window, wline);
1011 waddch(window, '\n');
1012 if (++nprinted == 1)
1013 *first_displayed_line = nlines;
1018 *last_displayed_line = nlines;
1026 static const struct got_error *
1027 show_diff_view(struct got_object *obj1, struct got_object *obj2,
1028 struct got_repository *repo)
1030 const struct got_error *err;
1032 int ch, done = 0, first_displayed_line = 1, last_displayed_line = LINES;
1035 if (obj1 != NULL && obj2 != NULL &&
1036 got_object_get_type(obj1) != got_object_get_type(obj2))
1037 return got_error(GOT_ERR_OBJ_TYPE);
1041 return got_error_from_errno();
1043 switch (got_object_get_type(obj1 ? obj1 : obj2)) {
1044 case GOT_OBJ_TYPE_BLOB:
1045 err = got_diff_objects_as_blobs(obj1, obj2, repo, f);
1047 case GOT_OBJ_TYPE_TREE:
1048 err = got_diff_objects_as_trees(obj1, obj2, repo, f);
1050 case GOT_OBJ_TYPE_COMMIT:
1051 err = got_diff_objects_as_commits(obj1, obj2, repo, f);
1054 return got_error(GOT_ERR_OBJ_TYPE);
1059 if (tog_diff_view.window == NULL) {
1060 tog_diff_view.window = newwin(0, 0, 0, 0);
1061 if (tog_diff_view.window == NULL)
1062 return got_error_from_errno();
1063 keypad(tog_diff_view.window, TRUE);
1065 if (tog_diff_view.panel == NULL) {
1066 tog_diff_view.panel = new_panel(tog_diff_view.window);
1067 if (tog_diff_view.panel == NULL)
1068 return got_error_from_errno();
1070 show_panel(tog_diff_view.panel);
1073 err = draw_file(tog_diff_view.window, f, &first_displayed_line,
1074 &last_displayed_line, &eof, LINES);
1077 nodelay(stdscr, FALSE);
1078 ch = wgetch(tog_diff_view.window);
1079 nodelay(stdscr, TRUE);
1086 if (first_displayed_line > 1)
1087 first_displayed_line--;
1092 while (i++ < LINES - 1 &&
1093 first_displayed_line > 1)
1094 first_displayed_line--;
1099 first_displayed_line++;
1104 while (!eof && i++ < LINES - 1) {
1105 char *line = parse_next_line(f, NULL);
1106 first_displayed_line++;
1119 static const struct got_error *
1120 cmd_diff(int argc, char *argv[])
1122 const struct got_error *error = NULL;
1123 struct got_repository *repo = NULL;
1124 struct got_object *obj1 = NULL, *obj2 = NULL;
1125 char *repo_path = NULL;
1126 char *obj_id_str1 = NULL, *obj_id_str2 = NULL;
1130 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
1134 while ((ch = getopt(argc, argv, "")) != -1) {
1146 usage_diff(); /* TODO show local worktree changes */
1147 } else if (argc == 2) {
1148 repo_path = getcwd(NULL, 0);
1149 if (repo_path == NULL)
1150 return got_error_from_errno();
1151 obj_id_str1 = argv[0];
1152 obj_id_str2 = argv[1];
1153 } else if (argc == 3) {
1154 repo_path = realpath(argv[0], NULL);
1155 if (repo_path == NULL)
1156 return got_error_from_errno();
1157 obj_id_str1 = argv[1];
1158 obj_id_str2 = argv[2];
1162 error = got_repo_open(&repo, repo_path);
1167 error = got_object_open_by_id_str(&obj1, repo, obj_id_str1);
1171 error = got_object_open_by_id_str(&obj2, repo, obj_id_str2);
1175 error = show_diff_view(obj1, obj2, repo);
1177 got_repo_close(repo);
1179 got_object_close(obj1);
1181 got_object_close(obj2);
1189 fprintf(stderr, "usage: %s blame [-c commit] [repository-path] path\n",
1194 struct tog_blame_line {
1196 struct got_object_id *id;
1199 static const struct got_error *
1200 draw_blame(WINDOW *window, struct got_object_id *id, FILE *f, const char *path,
1201 struct tog_blame_line *lines, int nlines, int blame_complete,
1202 int selected_line, int *first_displayed_line, int *last_displayed_line,
1203 int *eof, int max_lines)
1205 const struct got_error *err;
1206 int lineno = 0, nprinted = 0;
1211 struct tog_blame_line *blame_line;
1212 struct got_object_id *prev_id = NULL;
1215 err = got_object_id_str(&id_str, id);
1222 if (asprintf(&line, "commit: %s", id_str) == -1) {
1223 err = got_error_from_errno();
1228 err = format_line(&wline, &width, line, COLS);
1231 waddwstr(window, wline);
1235 waddch(window, '\n');
1237 if (asprintf(&line, "[%d/%d] %s%s",
1238 *first_displayed_line - 1 + selected_line, nlines,
1239 blame_complete ? "" : "annotating ", path) == -1) {
1241 return got_error_from_errno();
1244 err = format_line(&wline, &width, line, COLS);
1249 waddwstr(window, wline);
1253 waddch(window, '\n');
1256 while (nprinted < max_lines - 2) {
1257 line = parse_next_line(f, &len);
1262 if (++lineno < *first_displayed_line) {
1267 wlimit = COLS < 9 ? 0 : COLS - 9;
1268 err = format_line(&wline, &width, line, wlimit);
1274 if (nprinted == selected_line - 1)
1277 blame_line = &lines[lineno - 1];
1278 if (blame_line->annotated && prev_id &&
1279 got_object_id_cmp(prev_id, blame_line->id) == 0)
1280 waddstr(window, " ");
1281 else if (blame_line->annotated) {
1283 err = got_object_id_str(&id_str, blame_line->id);
1289 wprintw(window, "%.8s ", id_str);
1291 prev_id = blame_line->id;
1293 waddstr(window, "........ ");
1297 waddwstr(window, wline);
1298 while (width < wlimit) {
1299 waddch(window, ' '); /* width == wlimit - 1 ? '\n' : ' '); */
1302 if (nprinted == selected_line - 1)
1304 if (++nprinted == 1)
1305 *first_displayed_line = lineno;
1310 *last_displayed_line = lineno;
1318 struct tog_blame_cb_args {
1319 pthread_mutex_t *mutex;
1320 struct tog_blame_line *lines; /* one per line */
1323 struct got_object_id *commit_id;
1326 int *first_displayed_line;
1327 int *last_displayed_line;
1332 static const struct got_error *
1333 blame_cb(void *arg, int nlines, int lineno, struct got_object_id *id)
1335 const struct got_error *err = NULL;
1336 struct tog_blame_cb_args *a = arg;
1337 struct tog_blame_line *line;
1340 if (nlines != a->nlines ||
1341 (lineno != -1 && lineno < 1) || lineno > a->nlines)
1342 return got_error(GOT_ERR_RANGE);
1344 if (pthread_mutex_lock(a->mutex) != 0)
1345 return got_error_from_errno();
1347 if (*a->quit) { /* user has quit the blame view */
1348 err = got_error(GOT_ERR_ITER_COMPLETED);
1353 goto done; /* no change in this commit */
1355 line = &a->lines[lineno - 1];
1356 if (line->annotated)
1359 line->id = got_object_id_dup(id);
1360 if (line->id == NULL) {
1361 err = got_error_from_errno();
1364 line->annotated = 1;
1366 err = draw_blame(tog_blame_view.window, a->commit_id, a->f, a->path,
1367 a->lines, a->nlines, 0, *a->selected_line, a->first_displayed_line,
1368 a->last_displayed_line, &eof, LINES);
1370 if (pthread_mutex_unlock(a->mutex) != 0)
1371 return got_error_from_errno();
1375 struct tog_blame_thread_args {
1377 struct got_repository *repo;
1378 struct tog_blame_cb_args *cb_args;
1383 blame_thread(void *arg)
1385 const struct got_error *err;
1386 struct tog_blame_thread_args *ta = arg;
1387 struct tog_blame_cb_args *a = ta->cb_args;
1390 err = got_blame_incremental(ta->path, a->commit_id, ta->repo,
1391 blame_cb, ta->cb_args);
1392 got_repo_close(ta->repo);
1398 if (pthread_mutex_lock(a->mutex) != 0)
1399 return (void *)got_error_from_errno();
1401 err = draw_blame(tog_blame_view.window, a->commit_id, a->f, a->path,
1402 a->lines, a->nlines, 1, *a->selected_line, a->first_displayed_line,
1403 a->last_displayed_line, &eof, LINES);
1405 if (pthread_mutex_unlock(a->mutex) != 0 && err == NULL)
1406 err = got_error_from_errno();
1411 static struct got_object_id *
1412 get_selected_commit_id(struct tog_blame_line *lines,
1413 int first_displayed_line, int selected_line)
1415 struct tog_blame_line *line;
1417 line = &lines[first_displayed_line - 1 + selected_line - 1];
1418 if (!line->annotated)
1424 static const struct got_error *
1425 open_selected_commit(struct got_object **pobj, struct got_object **obj,
1426 struct tog_blame_line *lines, int first_displayed_line,
1427 int selected_line, struct got_repository *repo)
1429 const struct got_error *err = NULL;
1430 struct got_commit_object *commit = NULL;
1431 struct got_object_id *selected_id;
1432 struct got_object_qid *pid;
1437 selected_id = get_selected_commit_id(lines,
1438 first_displayed_line, selected_line);
1439 if (selected_id == NULL)
1442 err = got_object_open(obj, repo, selected_id);
1446 err = got_object_commit_open(&commit, repo, *obj);
1450 pid = SIMPLEQ_FIRST(&commit->parent_ids);
1452 err = got_object_open(pobj, repo, pid->id);
1458 got_object_commit_close(commit);
1465 struct tog_blame_line *lines;
1468 struct tog_blame_thread_args thread_args;
1469 struct tog_blame_cb_args cb_args;
1471 struct got_object_id *commit_id;
1474 static const struct got_error *
1475 stop_blame(struct tog_blame *blame)
1477 const struct got_error *err = NULL;
1480 if (blame->thread) {
1481 if (pthread_join(blame->thread, (void **)&err) != 0)
1482 err = got_error_from_errno();
1483 if (err && err->code == GOT_ERR_ITER_COMPLETED)
1485 blame->thread = NULL;
1487 if (blame->thread_args.repo) {
1488 got_repo_close(blame->thread_args.repo);
1489 blame->thread_args.repo = NULL;
1495 for (i = 0; i < blame->nlines; i++)
1496 free(blame->lines[i].id);
1498 blame->lines = NULL;
1499 free(blame->commit_id);
1500 blame->commit_id = NULL;
1505 static const struct got_error *
1506 run_blame(struct tog_blame *blame, pthread_mutex_t *mutex, int *blame_complete,
1507 int *first_displayed_line, int *last_displayed_line,
1508 int *selected_line, int *done, const char *path,
1509 struct got_object_id *commit_id,
1510 struct got_repository *repo)
1512 const struct got_error *err = NULL;
1513 struct got_blob_object *blob = NULL;
1514 struct got_repository *thread_repo = NULL;
1515 struct got_object *obj;
1517 err = got_object_open_by_path(&obj, repo, commit_id, path);
1520 if (got_object_get_type(obj) != GOT_OBJ_TYPE_BLOB) {
1521 err = got_error(GOT_ERR_OBJ_TYPE);
1525 err = got_object_blob_open(&blob, repo, obj, 8192);
1528 blame->f = got_opentemp();
1529 if (blame->f == NULL) {
1530 err = got_error_from_errno();
1533 err = got_object_blob_dump_to_file(&blame->filesize, &blame->nlines,
1538 blame->lines = calloc(blame->nlines, sizeof(*blame->lines));
1539 if (blame->lines == NULL) {
1540 err = got_error_from_errno();
1544 err = got_repo_open(&thread_repo, got_repo_get_path(repo));
1548 blame->cb_args.lines = blame->lines;
1549 blame->cb_args.nlines = blame->nlines;
1550 blame->cb_args.mutex = mutex;
1551 blame->cb_args.commit_id = got_object_id_dup(commit_id);
1552 if (blame->cb_args.commit_id == NULL) {
1553 err = got_error_from_errno();
1556 blame->cb_args.f = blame->f;
1557 blame->cb_args.path = path;
1558 blame->cb_args.first_displayed_line = first_displayed_line;
1559 blame->cb_args.selected_line = selected_line;
1560 blame->cb_args.last_displayed_line = last_displayed_line;
1561 blame->cb_args.quit = done;
1563 blame->thread_args.path = path;
1564 blame->thread_args.repo = thread_repo;
1565 blame->thread_args.cb_args = &blame->cb_args;
1566 blame->thread_args.complete = blame_complete;
1567 *blame_complete = 0;
1569 if (pthread_create(&blame->thread, NULL, blame_thread,
1570 &blame->thread_args) != 0) {
1571 err = got_error_from_errno();
1577 got_object_blob_close(blob);
1579 got_object_close(obj);
1585 static const struct got_error *
1586 show_blame_view(const char *path, struct got_object_id *commit_id,
1587 struct got_repository *repo)
1589 const struct got_error *err = NULL, *thread_err = NULL;
1590 int ch, done = 0, first_displayed_line = 1, last_displayed_line = LINES;
1591 int selected_line = first_displayed_line;
1592 int eof, blame_complete = 0;
1593 struct got_object *obj = NULL, *pobj = NULL;
1594 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
1595 struct tog_blame blame;
1596 int blame_running = 0;
1597 struct got_object_id_queue blamed_commits;
1598 struct got_object_qid *blamed_commit = NULL;
1600 SIMPLEQ_INIT(&blamed_commits);
1602 if (pthread_mutex_init(&mutex, NULL) != 0) {
1603 err = got_error_from_errno();
1607 err = got_object_qid_alloc(&blamed_commit, commit_id);
1610 SIMPLEQ_INSERT_HEAD(&blamed_commits, blamed_commit, entry);
1612 if (tog_blame_view.window == NULL) {
1613 tog_blame_view.window = newwin(0, 0, 0, 0);
1614 if (tog_blame_view.window == NULL)
1615 return got_error_from_errno();
1616 keypad(tog_blame_view.window, TRUE);
1618 if (tog_blame_view.panel == NULL) {
1619 tog_blame_view.panel = new_panel(tog_blame_view.window);
1620 if (tog_blame_view.panel == NULL)
1621 return got_error_from_errno();
1623 show_panel(tog_blame_view.panel);
1625 memset(&blame, 0, sizeof(blame));
1626 err = run_blame(&blame, &mutex, &blame_complete,
1627 &first_displayed_line, &last_displayed_line,
1628 &selected_line, &done, path, blamed_commit->id, repo);
1633 if (pthread_mutex_lock(&mutex) != 0) {
1634 err = got_error_from_errno();
1637 err = draw_blame(tog_blame_view.window, blamed_commit->id,
1638 blame.f, path, blame.lines, blame.nlines, blame_complete,
1639 selected_line, &first_displayed_line, &last_displayed_line,
1641 if (pthread_mutex_unlock(&mutex) != 0) {
1642 err = got_error_from_errno();
1647 nodelay(stdscr, FALSE);
1648 ch = wgetch(tog_blame_view.window);
1649 nodelay(stdscr, TRUE);
1650 if (pthread_mutex_lock(&mutex) != 0) {
1651 err = got_error_from_errno();
1660 if (selected_line > 1)
1662 else if (selected_line == 1 &&
1663 first_displayed_line > 1)
1664 first_displayed_line--;
1668 if (first_displayed_line == 1) {
1672 if (first_displayed_line > LINES - 2)
1673 first_displayed_line -= (LINES - 2);
1675 first_displayed_line = 1;
1679 if (selected_line < LINES - 2 &&
1680 first_displayed_line + selected_line <=
1683 else if (last_displayed_line < blame.nlines)
1684 first_displayed_line++;
1688 struct got_object_id *id;
1689 id = get_selected_commit_id(blame.lines,
1690 first_displayed_line, selected_line);
1691 if (id == NULL || got_object_id_cmp(id,
1692 blamed_commit->id) == 0)
1694 err = open_selected_commit(&pobj, &obj,
1695 blame.lines, first_displayed_line,
1696 selected_line, repo);
1699 if (pobj == NULL && obj == NULL)
1701 if (ch == 'p' && pobj == NULL)
1704 if (pthread_mutex_unlock(&mutex) != 0) {
1705 err = got_error_from_errno();
1708 thread_err = stop_blame(&blame);
1711 if (pthread_mutex_lock(&mutex) != 0) {
1712 err = got_error_from_errno();
1717 id = got_object_get_id(ch == 'b' ? obj : pobj);
1718 got_object_close(obj);
1721 got_object_close(pobj);
1725 err = got_error_from_errno();
1728 err = got_object_qid_alloc(&blamed_commit, id);
1732 SIMPLEQ_INSERT_HEAD(&blamed_commits,
1733 blamed_commit, entry);
1734 err = run_blame(&blame, &mutex,
1735 &blame_complete, &first_displayed_line,
1736 &last_displayed_line, &selected_line,
1737 &done, path, blamed_commit->id, repo);
1744 struct got_object_qid *first;
1745 first = SIMPLEQ_FIRST(&blamed_commits);
1746 if (!got_object_id_cmp(first->id, commit_id))
1749 if (pthread_mutex_unlock(&mutex) != 0) {
1750 err = got_error_from_errno();
1753 thread_err = stop_blame(&blame);
1756 if (pthread_mutex_lock(&mutex) != 0) {
1757 err = got_error_from_errno();
1762 SIMPLEQ_REMOVE_HEAD(&blamed_commits, entry);
1763 got_object_qid_free(blamed_commit);
1764 blamed_commit = SIMPLEQ_FIRST(&blamed_commits);
1765 err = run_blame(&blame, &mutex,
1766 &blame_complete, &first_displayed_line,
1767 &last_displayed_line, &selected_line,
1768 &done, path, blamed_commit->id, repo);
1776 err = open_selected_commit(&pobj, &obj,
1777 blame.lines, first_displayed_line,
1778 selected_line, repo);
1781 if (pobj == NULL && obj == NULL)
1783 err = show_diff_view(pobj, obj, repo);
1785 got_object_close(pobj);
1788 got_object_close(obj);
1790 show_panel(tog_blame_view.panel);
1796 if (last_displayed_line >= blame.nlines &&
1797 selected_line < LINES - 2) {
1798 selected_line = MIN(blame.nlines,
1802 if (last_displayed_line + LINES - 2 <=
1804 first_displayed_line += LINES - 2;
1806 first_displayed_line =
1807 blame.nlines - (LINES - 3);
1812 if (pthread_mutex_unlock(&mutex) != 0)
1813 err = got_error_from_errno();
1814 if (err || thread_err)
1819 got_object_close(pobj);
1821 thread_err = stop_blame(&blame);
1822 while (!SIMPLEQ_EMPTY(&blamed_commits)) {
1823 blamed_commit = SIMPLEQ_FIRST(&blamed_commits);
1824 SIMPLEQ_REMOVE_HEAD(&blamed_commits, entry);
1825 got_object_qid_free(blamed_commit);
1827 return thread_err ? thread_err : err;
1830 static const struct got_error *
1831 cmd_blame(int argc, char *argv[])
1833 const struct got_error *error;
1834 struct got_repository *repo = NULL;
1835 char *repo_path = NULL;
1837 struct got_object_id *commit_id = NULL;
1838 char *commit_id_str = NULL;
1842 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
1846 while ((ch = getopt(argc, argv, "c:")) != -1) {
1849 commit_id_str = optarg;
1862 } else if (argc == 1) {
1863 repo_path = getcwd(NULL, 0);
1864 if (repo_path == NULL)
1865 return got_error_from_errno();
1867 } else if (argc == 2) {
1868 repo_path = realpath(argv[0], NULL);
1869 if (repo_path == NULL)
1870 return got_error_from_errno();
1875 error = got_repo_open(&repo, repo_path);
1880 if (commit_id_str == NULL) {
1881 struct got_reference *head_ref;
1882 error = got_ref_open(&head_ref, repo, GOT_REF_HEAD);
1885 error = got_ref_resolve(&commit_id, repo, head_ref);
1886 got_ref_close(head_ref);
1888 struct got_object *obj;
1889 error = got_object_open_by_id_str(&obj, repo, commit_id_str);
1892 commit_id = got_object_get_id(obj);
1893 if (commit_id == NULL)
1894 error = got_error_from_errno();
1895 got_object_close(obj);
1900 error = show_blame_view(path, commit_id, repo);
1904 got_repo_close(repo);
1908 static const struct got_error *
1909 draw_tree_entries(struct got_tree_entry **first_displayed_entry,
1910 struct got_tree_entry **last_displayed_entry,
1911 struct got_tree_entry **selected_entry, int *ndisplayed,
1912 WINDOW *window, const char *label, int show_ids, const char *parent_path,
1913 const struct got_tree_entries *entries, int selected, int limit, int isroot)
1915 const struct got_error *err = NULL;
1916 struct got_tree_entry *te;
1927 err = format_line(&wline, &width, label, COLS);
1930 waddwstr(window, wline);
1934 waddch(window, '\n');
1937 err = format_line(&wline, &width, parent_path, COLS);
1940 waddwstr(window, wline);
1944 waddch(window, '\n');
1947 waddch(window, '\n');
1951 te = SIMPLEQ_FIRST(&entries->head);
1952 if (*first_displayed_entry == NULL) {
1953 if (selected == 0) {
1955 *selected_entry = NULL;
1957 waddstr(window, " ..\n"); /* parent directory */
1966 while (te != *first_displayed_entry)
1967 te = SIMPLEQ_NEXT(te, entry);
1971 char *line = NULL, *id_str = NULL;
1974 err = got_object_id_str(&id_str, te->id);
1976 return got_error_from_errno();
1978 if (asprintf(&line, "%s %s%s", id_str ? id_str : "",
1979 te->name, S_ISDIR(te->mode) ? "/" : "") == -1) {
1981 return got_error_from_errno();
1984 err = format_line(&wline, &width, line, COLS);
1989 if (n == selected) {
1991 *selected_entry = te;
1993 waddwstr(window, wline);
1995 waddch(window, '\n');
2003 *last_displayed_entry = te;
2006 te = SIMPLEQ_NEXT(te, entry);
2013 tree_scroll_up(struct got_tree_entry **first_displayed_entry, int maxscroll,
2014 const struct got_tree_entries *entries, int isroot)
2016 struct got_tree_entry *te, *prev;
2019 if (*first_displayed_entry == NULL)
2022 te = SIMPLEQ_FIRST(&entries->head);
2023 if (*first_displayed_entry == te) {
2025 *first_displayed_entry = NULL;
2029 /* XXX this is stupid... switch to TAILQ? */
2030 for (i = 0; i < maxscroll; i++) {
2031 while (te != *first_displayed_entry) {
2033 te = SIMPLEQ_NEXT(te, entry);
2035 *first_displayed_entry = prev;
2036 te = SIMPLEQ_FIRST(&entries->head);
2038 if (!isroot && te == SIMPLEQ_FIRST(&entries->head) && i < maxscroll)
2039 *first_displayed_entry = NULL;
2043 tree_scroll_down(struct got_tree_entry **first_displayed_entry, int maxscroll,
2044 struct got_tree_entry *last_displayed_entry,
2045 const struct got_tree_entries *entries)
2047 struct got_tree_entry *next;
2050 if (SIMPLEQ_NEXT(last_displayed_entry, entry) == NULL)
2053 if (*first_displayed_entry)
2054 next = SIMPLEQ_NEXT(*first_displayed_entry, entry);
2056 next = SIMPLEQ_FIRST(&entries->head);
2058 *first_displayed_entry = next;
2059 if (++n >= maxscroll)
2061 next = SIMPLEQ_NEXT(next, entry);
2065 struct tog_parent_tree {
2066 TAILQ_ENTRY(tog_parent_tree) entry;
2067 struct got_tree_object *tree;
2068 struct got_tree_entry *first_displayed_entry;
2069 struct got_tree_entry *selected_entry;
2073 TAILQ_HEAD(tog_parent_trees, tog_parent_tree);
2075 static const struct got_error *
2076 tree_entry_path(char **path, struct tog_parent_trees *parents,
2077 struct got_tree_entry *te)
2079 const struct got_error *err = NULL;
2080 struct tog_parent_tree *pt;
2081 size_t len = 2; /* for leading slash and NUL */
2083 TAILQ_FOREACH(pt, parents, entry)
2084 len += strlen(pt->selected_entry->name) + 1 /* slash */;
2086 len += strlen(te->name);
2088 *path = calloc(1, len);
2090 return got_error_from_errno();
2093 pt = TAILQ_LAST(parents, tog_parent_trees);
2095 if (strlcat(*path, pt->selected_entry->name, len) >= len) {
2096 err = got_error(GOT_ERR_NO_SPACE);
2099 if (strlcat(*path, "/", len) >= len) {
2100 err = got_error(GOT_ERR_NO_SPACE);
2103 pt = TAILQ_PREV(pt, tog_parent_trees, entry);
2106 if (strlcat(*path, te->name, len) >= len) {
2107 err = got_error(GOT_ERR_NO_SPACE);
2119 static const struct got_error *
2120 blame_tree_entry(struct got_tree_entry *te, struct tog_parent_trees *parents,
2121 struct got_object_id *commit_id, struct got_repository *repo)
2123 const struct got_error *err = NULL;
2126 err = tree_entry_path(&path, parents, te);
2130 err = show_blame_view(path, commit_id, repo);
2135 static const struct got_error *
2136 show_tree_view(struct got_tree_object *root, struct got_object_id *commit_id,
2137 struct got_repository *repo)
2139 const struct got_error *err = NULL;
2140 int ch, done = 0, selected = 0, show_ids = 0;
2141 struct got_tree_object *tree = root;
2142 const struct got_tree_entries *entries;
2143 struct got_tree_entry *first_displayed_entry = NULL;
2144 struct got_tree_entry *last_displayed_entry = NULL;
2145 struct got_tree_entry *selected_entry = NULL;
2146 char *commit_id_str = NULL, *tree_label = NULL;
2147 int nentries, ndisplayed;
2148 struct tog_parent_trees parents;
2150 TAILQ_INIT(&parents);
2152 err = got_object_id_str(&commit_id_str, commit_id);
2156 if (asprintf(&tree_label, "commit: %s", commit_id_str) == -1) {
2157 err = got_error_from_errno();
2161 if (tog_tree_view.window == NULL) {
2162 tog_tree_view.window = newwin(0, 0, 0, 0);
2163 if (tog_tree_view.window == NULL)
2164 return got_error_from_errno();
2165 keypad(tog_tree_view.window, TRUE);
2167 if (tog_tree_view.panel == NULL) {
2168 tog_tree_view.panel = new_panel(tog_tree_view.window);
2169 if (tog_tree_view.panel == NULL)
2170 return got_error_from_errno();
2172 show_panel(tog_tree_view.panel);
2174 entries = got_object_tree_get_entries(root);
2175 first_displayed_entry = SIMPLEQ_FIRST(&entries->head);
2178 entries = got_object_tree_get_entries(tree);
2179 nentries = entries->nentries;
2181 nentries++; /* '..' directory */
2183 err = tree_entry_path(&parent_path, &parents, NULL);
2187 err = draw_tree_entries(&first_displayed_entry,
2188 &last_displayed_entry, &selected_entry, &ndisplayed,
2189 tog_tree_view.window, tree_label, show_ids,
2190 parent_path, entries, selected, LINES, tree == root);
2195 nodelay(stdscr, FALSE);
2196 ch = wgetch(tog_tree_view.window);
2197 nodelay(stdscr, TRUE);
2203 show_ids = !show_ids;
2211 tree_scroll_up(&first_displayed_entry, 1,
2212 entries, tree == root);
2215 if (SIMPLEQ_FIRST(&entries->head) ==
2216 first_displayed_entry) {
2218 first_displayed_entry = NULL;
2222 tree_scroll_up(&first_displayed_entry, LINES,
2223 entries, tree == root);
2227 if (selected < ndisplayed - 1) {
2231 tree_scroll_down(&first_displayed_entry, 1,
2232 last_displayed_entry, entries);
2235 tree_scroll_down(&first_displayed_entry, LINES,
2236 last_displayed_entry, entries);
2237 if (SIMPLEQ_NEXT(last_displayed_entry, entry))
2239 /* can't scroll any further; move cursor down */
2240 if (selected < ndisplayed - 1)
2241 selected = ndisplayed - 1;
2245 if (selected_entry == NULL) {
2246 struct tog_parent_tree *parent;
2248 /* user selected '..' */
2251 parent = TAILQ_FIRST(&parents);
2252 TAILQ_REMOVE(&parents, parent, entry);
2253 got_object_tree_close(tree);
2254 tree = parent->tree;
2255 first_displayed_entry =
2256 parent->first_displayed_entry;
2257 selected_entry = parent->selected_entry;
2258 selected = parent->selected;
2260 } else if (S_ISDIR(selected_entry->mode)) {
2261 struct tog_parent_tree *parent;
2262 struct got_tree_object *child;
2263 err = got_object_open_as_tree(
2264 &child, repo, selected_entry->id);
2267 parent = calloc(1, sizeof(*parent));
2268 if (parent == NULL) {
2269 err = got_error_from_errno();
2272 parent->tree = tree;
2273 parent->first_displayed_entry =
2274 first_displayed_entry;
2275 parent->selected_entry = selected_entry;
2276 parent->selected = selected;
2277 TAILQ_INSERT_HEAD(&parents, parent,
2281 first_displayed_entry = NULL;
2282 } else if (S_ISREG(selected_entry->mode)) {
2283 err = blame_tree_entry(selected_entry,
2284 &parents, commit_id, repo);
2290 if (selected > LINES)
2291 selected = ndisplayed - 1;
2299 free(commit_id_str);
2300 while (!TAILQ_EMPTY(&parents)) {
2301 struct tog_parent_tree *parent;
2302 parent = TAILQ_FIRST(&parents);
2303 TAILQ_REMOVE(&parents, parent, entry);
2308 got_object_tree_close(tree);
2316 fprintf(stderr, "usage: %s tree [-c commit] [repository-path]\n",
2321 static const struct got_error *
2322 cmd_tree(int argc, char *argv[])
2324 const struct got_error *error;
2325 struct got_repository *repo = NULL;
2326 char *repo_path = NULL;
2327 struct got_object_id *commit_id = NULL;
2328 char *commit_id_arg = NULL;
2329 struct got_commit_object *commit = NULL;
2330 struct got_tree_object *tree = NULL;
2334 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
2338 while ((ch = getopt(argc, argv, "c:")) != -1) {
2341 commit_id_arg = optarg;
2353 repo_path = getcwd(NULL, 0);
2354 if (repo_path == NULL)
2355 return got_error_from_errno();
2356 } else if (argc == 1) {
2357 repo_path = realpath(argv[0], NULL);
2358 if (repo_path == NULL)
2359 return got_error_from_errno();
2363 error = got_repo_open(&repo, repo_path);
2368 if (commit_id_arg == NULL) {
2369 error = get_head_commit_id(&commit_id, repo);
2373 struct got_object *obj;
2374 error = got_object_open_by_id_str(&obj, repo, commit_id_arg);
2375 if (error == NULL) {
2376 commit_id = got_object_get_id(obj);
2377 if (commit_id == NULL)
2378 error = got_error_from_errno();
2384 error = got_object_open_as_commit(&commit, repo, commit_id);
2388 error = got_object_open_as_tree(&tree, repo, commit->tree_id);
2392 error = show_tree_view(tree, commit_id, repo);
2396 got_object_commit_close(commit);
2398 got_object_tree_close(tree);
2400 got_repo_close(repo);
2410 intrflush(stdscr, FALSE);
2411 keypad(stdscr, TRUE);
2420 fprintf(stderr, "usage: %s [-h] [command] [arg ...]\n\n"
2421 "Available commands:\n", getprogname());
2422 for (i = 0; i < nitems(tog_commands); i++) {
2423 struct tog_cmd *cmd = &tog_commands[i];
2424 fprintf(stderr, " %s: %s\n", cmd->name, cmd->descr);
2430 make_argv(const char *arg0, const char *arg1)
2433 int argc = (arg1 == NULL ? 1 : 2);
2435 argv = calloc(argc, sizeof(char *));
2438 argv[0] = strdup(arg0);
2439 if (argv[0] == NULL)
2442 argv[1] = strdup(arg1);
2443 if (argv[1] == NULL)
2451 main(int argc, char *argv[])
2453 const struct got_error *error = NULL;
2454 struct tog_cmd *cmd = NULL;
2456 char **cmd_argv = NULL;
2458 setlocale(LC_ALL, "");
2460 while ((ch = getopt(argc, argv, "h")) != -1) {
2479 /* Build an argument vector which runs a default command. */
2480 cmd = &tog_commands[0];
2481 cmd_argv = make_argv(cmd->name, NULL);
2486 /* Did the user specific a command? */
2487 for (i = 0; i < nitems(tog_commands); i++) {
2488 if (strncmp(tog_commands[i].name, argv[0],
2489 strlen(argv[0])) == 0) {
2490 cmd = &tog_commands[i];
2492 tog_commands[i].cmd_usage();
2497 /* Did the user specify a repository? */
2498 char *repo_path = realpath(argv[0], NULL);
2500 struct got_repository *repo;
2501 error = got_repo_open(&repo, repo_path);
2503 got_repo_close(repo);
2505 error = got_error_from_errno();
2508 fprintf(stderr, "%s: '%s' is not a "
2509 "known command\n", getprogname(),
2513 fprintf(stderr, "%s: '%s' is neither a known "
2514 "command nor a path to a repository\n",
2515 getprogname(), argv[0]);
2519 cmd = &tog_commands[0];
2520 cmd_argv = make_argv(cmd->name, repo_path);
2528 error = cmd->cmd_main(argc, cmd_argv ? cmd_argv : argv);
2535 fprintf(stderr, "%s: %s\n", getprogname(), error->msg);