Blob


1 /*
2 * Copyright (c) 2018 Stefan Sperling <stsp@openbsd.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
17 #include <sys/queue.h>
18 #include <sys/stat.h>
20 #include <errno.h>
21 #define _XOPEN_SOURCE_EXTENDED
22 #include <curses.h>
23 #undef _XOPEN_SOURCE_EXTENDED
24 #include <panel.h>
25 #include <locale.h>
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <getopt.h>
29 #include <string.h>
30 #include <err.h>
31 #include <unistd.h>
32 #include <util.h>
33 #include <limits.h>
34 #include <wchar.h>
35 #include <time.h>
36 #include <pthread.h>
38 #include "got_error.h"
39 #include "got_object.h"
40 #include "got_reference.h"
41 #include "got_repository.h"
42 #include "got_diff.h"
43 #include "got_opentemp.h"
44 #include "got_commit_graph.h"
45 #include "got_utf8.h"
46 #include "got_blame.h"
48 #ifndef MIN
49 #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b))
50 #endif
52 #ifndef nitems
53 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
54 #endif
56 struct tog_cmd {
57 const char *name;
58 const struct got_error *(*cmd_main)(int, char *[]);
59 void (*cmd_usage)(void);
60 const char *descr;
61 };
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" },
83 };
85 enum tog_view_type {
86 TOG_VIEW_DIFF,
87 TOG_VIEW_LOG,
88 TOG_VIEW_BLAME,
89 TOG_VIEW_TREE
90 };
92 struct tog_diff_view_state {
93 FILE *f;
94 int first_displayed_line;
95 int last_displayed_line;
96 };
98 struct commit_queue_entry {
99 TAILQ_ENTRY(commit_queue_entry) entry;
100 struct got_object_id *id;
101 struct got_commit_object *commit;
102 };
103 TAILQ_HEAD(commit_queue_head, commit_queue_entry);
104 struct commit_queue {
105 int ncommits;
106 struct commit_queue_head head;
107 };
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;
115 int selected;
116 char *in_repo_path;
117 struct got_repository *repo;
118 };
120 struct tog_blame_view_state {
121 int first_displayed_line;
122 int last_displayed_line;
123 int selected_line;
124 int blame_complete;
125 pthread_mutex_t mutex;
126 struct got_object_id_queue blamed_commits;
127 struct got_object_qid *blamed_commit;
128 const char *path;
129 struct got_repository *repo;
130 struct got_object_id *commit_id;
131 };
133 struct tog_parent_tree {
134 TAILQ_ENTRY(tog_parent_tree) entry;
135 struct got_tree_object *tree;
136 struct got_tree_entry *first_displayed_entry;
137 struct got_tree_entry *selected_entry;
138 int selected;
139 };
141 TAILQ_HEAD(tog_parent_trees, tog_parent_tree);
143 struct tog_tree_view_state {
144 char *tree_label;
145 struct got_tree_object *root;
146 struct got_tree_object *tree;
147 const struct got_tree_entries *entries;
148 struct got_tree_entry *first_displayed_entry;
149 struct got_tree_entry *last_displayed_entry;
150 struct got_tree_entry *selected_entry;
151 int nentries, ndisplayed, selected, show_ids;
152 struct tog_parent_trees parents;
153 struct got_object_id *commit_id;
154 struct got_repository *repo;
155 };
157 struct tog_view {
158 WINDOW *window;
159 PANEL *panel;
160 int nlines, ncols, begin_y, begin_x;
161 int lines, cols; /* copies of LINES and COLS */
162 struct tog_view *parent;
164 /* type-specific state */
165 enum tog_view_type type;
166 union {
167 struct tog_diff_view_state diff;
168 struct tog_log_view_state log;
169 struct tog_blame_view_state blame;
170 struct tog_tree_view_state tree;
171 } state;
172 };
174 static const struct got_error *open_diff_view(struct tog_view *,
175 struct got_object *, struct got_object *, struct got_repository *);
176 static const struct got_error *show_diff_view(struct tog_view *);
177 static void close_diff_view(struct tog_view *);
178 static const struct got_error *open_log_view(struct tog_view *,
179 struct got_object_id *, struct got_repository *, const char *);
180 static const struct got_error * show_log_view(struct tog_view *);
181 static void close_log_view(struct tog_view *);
182 static const struct got_error *open_blame_view(struct tog_view *, const char *,
183 struct got_object_id *, struct got_repository *);
184 static const struct got_error *show_blame_view(struct tog_view *);
185 static void close_blame_view(struct tog_view *);
186 static const struct got_error *open_tree_view(struct tog_view *,
187 struct got_tree_object *, struct got_object_id *, struct got_repository *);
188 static const struct got_error *show_tree_view(struct tog_view *);
189 static void close_tree_view(struct tog_view *);
191 static void
192 view_close(struct tog_view *view)
194 if (view->panel)
195 del_panel(view->panel);
196 if (view->window)
197 delwin(view->window);
198 free(view);
201 static struct tog_view *
202 view_open(int nlines, int ncols, int begin_y, int begin_x,
203 struct tog_view *parent, enum tog_view_type type)
205 struct tog_view *view = calloc(1, sizeof(*view));
207 if (view == NULL)
208 return NULL;
210 view->parent = parent;
211 view->type = type;
212 view->lines = LINES;
213 view->cols = COLS;
214 view->nlines = nlines ? nlines : LINES - begin_y;
215 view->ncols = ncols ? ncols : COLS - begin_x;
216 view->begin_y = begin_y;
217 view->begin_x = begin_x;
218 view->window = newwin(nlines, ncols, begin_y, begin_x);
219 if (view->window == NULL) {
220 view_close(view);
221 return NULL;
223 view->panel = new_panel(view->window);
224 if (view->panel == NULL) {
225 view_close(view);
226 return NULL;
229 keypad(view->window, TRUE);
230 return view;
233 void
234 view_show(struct tog_view *view)
236 show_panel(view->panel);
237 update_panels();
240 const struct got_error *
241 view_resize(struct tog_view *view)
243 int nlines, ncols;
245 while (view) {
246 if (view->lines > LINES)
247 nlines = view->nlines - (view->lines - LINES);
248 else
249 nlines = view->nlines + (LINES - view->lines);
251 if (view->cols > COLS)
252 ncols = view->ncols - (view->cols - COLS);
253 else
254 ncols = view->ncols + (COLS - view->cols);
256 if (wresize(view->window, nlines, ncols) == ERR)
257 return got_error_from_errno();
258 replace_panel(view->panel, view->window);
260 view->nlines = nlines;
261 view->ncols = ncols;
262 view->lines = LINES;
263 view->cols = COLS;
265 view = view->parent;
268 return NULL;
271 __dead static void
272 usage_log(void)
274 endwin();
275 fprintf(stderr,
276 "usage: %s log [-c commit] [-r repository-path] [path]\n",
277 getprogname());
278 exit(1);
281 /* Create newly allocated wide-character string equivalent to a byte string. */
282 static const struct got_error *
283 mbs2ws(wchar_t **ws, size_t *wlen, const char *s)
285 char *vis = NULL;
286 const struct got_error *err = NULL;
288 *ws = NULL;
289 *wlen = mbstowcs(NULL, s, 0);
290 if (*wlen == (size_t)-1) {
291 int vislen;
292 if (errno != EILSEQ)
293 return got_error_from_errno();
295 /* byte string invalid in current encoding; try to "fix" it */
296 err = got_mbsavis(&vis, &vislen, s);
297 if (err)
298 return err;
299 *wlen = mbstowcs(NULL, vis, 0);
300 if (*wlen == (size_t)-1) {
301 err = got_error_from_errno(); /* give up */
302 goto done;
306 *ws = calloc(*wlen + 1, sizeof(*ws));
307 if (*ws == NULL) {
308 err = got_error_from_errno();
309 goto done;
312 if (mbstowcs(*ws, vis ? vis : s, *wlen) != *wlen)
313 err = got_error_from_errno();
314 done:
315 free(vis);
316 if (err) {
317 free(*ws);
318 *ws = NULL;
319 *wlen = 0;
321 return err;
324 /* Format a line for display, ensuring that it won't overflow a width limit. */
325 static const struct got_error *
326 format_line(wchar_t **wlinep, int *widthp, const char *line, int wlimit)
328 const struct got_error *err = NULL;
329 int cols = 0;
330 wchar_t *wline = NULL;
331 size_t wlen;
332 int i;
334 *wlinep = NULL;
335 *widthp = 0;
337 err = mbs2ws(&wline, &wlen, line);
338 if (err)
339 return err;
341 i = 0;
342 while (i < wlen && cols < wlimit) {
343 int width = wcwidth(wline[i]);
344 switch (width) {
345 case 0:
346 i++;
347 break;
348 case 1:
349 case 2:
350 if (cols + width <= wlimit) {
351 cols += width;
352 i++;
354 break;
355 case -1:
356 if (wline[i] == L'\t')
357 cols += TABSIZE - ((cols + 1) % TABSIZE);
358 i++;
359 break;
360 default:
361 err = got_error_from_errno();
362 goto done;
365 wline[i] = L'\0';
366 if (widthp)
367 *widthp = cols;
368 done:
369 if (err)
370 free(wline);
371 else
372 *wlinep = wline;
373 return err;
376 static const struct got_error *
377 draw_commit(struct tog_view *view, struct got_commit_object *commit,
378 struct got_object_id *id)
380 const struct got_error *err = NULL;
381 char datebuf[10]; /* YY-MM-DD + SPACE + NUL */
382 char *logmsg0 = NULL, *logmsg = NULL;
383 char *author0 = NULL, *author = NULL;
384 wchar_t *wlogmsg = NULL, *wauthor = NULL;
385 int author_width, logmsg_width;
386 char *newline, *smallerthan;
387 char *line = NULL;
388 int col, limit;
389 static const size_t date_display_cols = 9;
390 static const size_t author_display_cols = 16;
391 const int avail = view->ncols;
393 if (strftime(datebuf, sizeof(datebuf), "%g/%m/%d ",
394 &commit->tm_committer) >= sizeof(datebuf))
395 return got_error(GOT_ERR_NO_SPACE);
397 if (avail < date_display_cols)
398 limit = MIN(sizeof(datebuf) - 1, avail);
399 else
400 limit = MIN(date_display_cols, sizeof(datebuf) - 1);
401 waddnstr(view->window, datebuf, limit);
402 col = limit + 1;
403 if (col > avail)
404 goto done;
406 author0 = strdup(commit->author);
407 if (author0 == NULL) {
408 err = got_error_from_errno();
409 goto done;
411 author = author0;
412 smallerthan = strchr(author, '<');
413 if (smallerthan)
414 *smallerthan = '\0';
415 else {
416 char *at = strchr(author, '@');
417 if (at)
418 *at = '\0';
420 limit = avail - col;
421 err = format_line(&wauthor, &author_width, author, limit);
422 if (err)
423 goto done;
424 waddwstr(view->window, wauthor);
425 col += author_width;
426 while (col <= avail && author_width < author_display_cols + 1) {
427 waddch(view->window, ' ');
428 col++;
429 author_width++;
431 if (col > avail)
432 goto done;
434 logmsg0 = strdup(commit->logmsg);
435 if (logmsg0 == NULL) {
436 err = got_error_from_errno();
437 goto done;
439 logmsg = logmsg0;
440 while (*logmsg == '\n')
441 logmsg++;
442 newline = strchr(logmsg, '\n');
443 if (newline)
444 *newline = '\0';
445 limit = avail - col;
446 err = format_line(&wlogmsg, &logmsg_width, logmsg, limit);
447 if (err)
448 goto done;
449 waddwstr(view->window, wlogmsg);
450 col += logmsg_width;
451 while (col <= avail) {
452 waddch(view->window, ' ');
453 col++;
455 done:
456 free(logmsg0);
457 free(wlogmsg);
458 free(author0);
459 free(wauthor);
460 free(line);
461 return err;
464 static struct commit_queue_entry *
465 alloc_commit_queue_entry(struct got_commit_object *commit,
466 struct got_object_id *id)
468 struct commit_queue_entry *entry;
470 entry = calloc(1, sizeof(*entry));
471 if (entry == NULL)
472 return NULL;
474 entry->id = id;
475 entry->commit = commit;
476 return entry;
479 static void
480 pop_commit(struct commit_queue *commits)
482 struct commit_queue_entry *entry;
484 entry = TAILQ_FIRST(&commits->head);
485 TAILQ_REMOVE(&commits->head, entry, entry);
486 got_object_commit_close(entry->commit);
487 commits->ncommits--;
488 /* Don't free entry->id! It is owned by the commit graph. */
489 free(entry);
492 static void
493 free_commits(struct commit_queue *commits)
495 while (!TAILQ_EMPTY(&commits->head))
496 pop_commit(commits);
499 static const struct got_error *
500 queue_commits(struct got_commit_graph *graph, struct commit_queue *commits,
501 struct got_object_id *start_id, int minqueue, int init,
502 struct got_repository *repo, const char *path)
504 const struct got_error *err = NULL;
505 struct got_object_id *id;
506 struct commit_queue_entry *entry;
507 int nfetched, nqueued = 0, found_obj = 0;
508 int is_root_path = strcmp(path, "/") == 0;
510 err = got_commit_graph_iter_start(graph, start_id);
511 if (err)
512 return err;
514 entry = TAILQ_LAST(&commits->head, commit_queue_head);
515 if (entry && got_object_id_cmp(entry->id, start_id) == 0) {
516 int nfetched;
518 /* Start ID's commit is already on the queue; skip over it. */
519 err = got_commit_graph_iter_next(&id, graph);
520 if (err && err->code != GOT_ERR_ITER_NEED_MORE)
521 return err;
523 err = got_commit_graph_fetch_commits(&nfetched, graph, 1, repo);
524 if (err)
525 return err;
528 while (1) {
529 struct got_commit_object *commit;
531 err = got_commit_graph_iter_next(&id, graph);
532 if (err) {
533 if (err->code != GOT_ERR_ITER_NEED_MORE)
534 break;
535 if (nqueued >= minqueue) {
536 err = NULL;
537 break;
539 err = got_commit_graph_fetch_commits(&nfetched,
540 graph, 1, repo);
541 if (err)
542 return err;
543 continue;
545 if (id == NULL)
546 break;
548 err = got_object_open_as_commit(&commit, repo, id);
549 if (err)
550 break;
552 if (!is_root_path) {
553 struct got_object *obj;
554 struct got_object_qid *pid;
555 int changed = 0;
557 err = got_object_open_by_path(&obj, repo, id, path);
558 if (err) {
559 got_object_commit_close(commit);
560 if (err->code == GOT_ERR_NO_OBJ &&
561 (found_obj || !init)) {
562 /* History stops here. */
563 err = got_error(GOT_ERR_ITER_COMPLETED);
565 break;
567 found_obj = 1;
569 pid = SIMPLEQ_FIRST(&commit->parent_ids);
570 if (pid != NULL) {
571 struct got_object *pobj;
572 err = got_object_open_by_path(&pobj, repo,
573 pid->id, path);
574 if (err) {
575 if (err->code != GOT_ERR_NO_OBJ) {
576 got_object_close(obj);
577 got_object_commit_close(commit);
578 break;
580 err = NULL;
581 changed = 1;
582 } else {
583 struct got_object_id *id, *pid;
584 id = got_object_get_id(obj);
585 if (id == NULL) {
586 err = got_error_from_errno();
587 got_object_close(obj);
588 got_object_close(pobj);
589 break;
591 pid = got_object_get_id(pobj);
592 if (pid == NULL) {
593 err = got_error_from_errno();
594 free(id);
595 got_object_close(obj);
596 got_object_close(pobj);
597 break;
599 changed =
600 (got_object_id_cmp(id, pid) != 0);
601 got_object_close(pobj);
602 free(id);
603 free(pid);
606 got_object_close(obj);
607 if (!changed) {
608 got_object_commit_close(commit);
609 continue;
613 entry = alloc_commit_queue_entry(commit, id);
614 if (entry == NULL) {
615 err = got_error_from_errno();
616 break;
618 TAILQ_INSERT_TAIL(&commits->head, entry, entry);
619 nqueued++;
620 commits->ncommits++;
623 return err;
626 static const struct got_error *
627 fetch_next_commit(struct commit_queue_entry **pentry,
628 struct commit_queue_entry *entry, struct commit_queue *commits,
629 struct got_commit_graph *graph, struct got_repository *repo,
630 const char *path)
632 const struct got_error *err = NULL;
634 *pentry = NULL;
636 err = queue_commits(graph, commits, entry->id, 1, 0, repo, path);
637 if (err)
638 return err;
640 /* Next entry to display should now be available. */
641 *pentry = TAILQ_NEXT(entry, entry);
642 if (*pentry == NULL)
643 return got_error(GOT_ERR_NO_OBJ);
645 return NULL;
648 static const struct got_error *
649 get_head_commit_id(struct got_object_id **head_id, struct got_repository *repo)
651 const struct got_error *err = NULL;
652 struct got_reference *head_ref;
654 *head_id = NULL;
656 err = got_ref_open(&head_ref, repo, GOT_REF_HEAD);
657 if (err)
658 return err;
660 err = got_ref_resolve(head_id, repo, head_ref);
661 got_ref_close(head_ref);
662 if (err) {
663 *head_id = NULL;
664 return err;
667 return NULL;
670 static const struct got_error *
671 draw_commits(struct tog_view *view, struct commit_queue_entry **last,
672 struct commit_queue_entry **selected, struct commit_queue_entry *first,
673 struct commit_queue *commits, int selected_idx, int limit,
674 struct got_commit_graph *graph, struct got_repository *repo,
675 const char *path)
677 const struct got_error *err = NULL;
678 struct commit_queue_entry *entry;
679 int ncommits, width;
680 char *id_str, *header;
681 wchar_t *wline;
683 entry = first;
684 ncommits = 0;
685 while (entry) {
686 if (ncommits == selected_idx) {
687 *selected = entry;
688 break;
690 entry = TAILQ_NEXT(entry, entry);
691 ncommits++;
694 err = got_object_id_str(&id_str, (*selected)->id);
695 if (err)
696 return err;
698 if (path && strcmp(path, "/") != 0) {
699 if (asprintf(&header, "commit: %s [%s]", id_str, path) == -1) {
700 err = got_error_from_errno();
701 free(id_str);
702 return err;
704 } else if (asprintf(&header, "commit: %s", id_str) == -1) {
705 err = got_error_from_errno();
706 free(id_str);
707 return err;
709 free(id_str);
710 err = format_line(&wline, &width, header, view->ncols);
711 if (err) {
712 free(header);
713 return err;
715 free(header);
717 werase(view->window);
719 waddwstr(view->window, wline);
720 if (width < view->ncols)
721 waddch(view->window, '\n');
722 free(wline);
723 if (limit <= 1)
724 return NULL;
726 entry = first;
727 *last = first;
728 ncommits = 0;
729 while (entry) {
730 if (ncommits >= limit - 1)
731 break;
732 if (ncommits == selected_idx)
733 wstandout(view->window);
734 err = draw_commit(view, entry->commit, entry->id);
735 if (ncommits == selected_idx)
736 wstandend(view->window);
737 if (err)
738 break;
739 ncommits++;
740 *last = entry;
741 if (entry == TAILQ_LAST(&commits->head, commit_queue_head)) {
742 err = queue_commits(graph, commits, entry->id, 1,
743 0, repo, path);
744 if (err) {
745 if (err->code != GOT_ERR_ITER_COMPLETED)
746 return err;
747 err = NULL;
750 entry = TAILQ_NEXT(entry, entry);
753 update_panels();
754 doupdate();
756 return err;
759 static void
760 scroll_up(struct commit_queue_entry **first_displayed_entry, int maxscroll,
761 struct commit_queue *commits)
763 struct commit_queue_entry *entry;
764 int nscrolled = 0;
766 entry = TAILQ_FIRST(&commits->head);
767 if (*first_displayed_entry == entry)
768 return;
770 entry = *first_displayed_entry;
771 while (entry && nscrolled < maxscroll) {
772 entry = TAILQ_PREV(entry, commit_queue_head, entry);
773 if (entry) {
774 *first_displayed_entry = entry;
775 nscrolled++;
780 static const struct got_error *
781 scroll_down(struct commit_queue_entry **first_displayed_entry, int maxscroll,
782 struct commit_queue_entry *last_displayed_entry,
783 struct commit_queue *commits, struct got_commit_graph *graph,
784 struct got_repository *repo, const char *path)
786 const struct got_error *err = NULL;
787 struct commit_queue_entry *pentry;
788 int nscrolled = 0;
790 do {
791 pentry = TAILQ_NEXT(last_displayed_entry, entry);
792 if (pentry == NULL) {
793 err = fetch_next_commit(&pentry, last_displayed_entry,
794 commits, graph, repo, path);
795 if (err || pentry == NULL)
796 break;
798 last_displayed_entry = pentry;
800 pentry = TAILQ_NEXT(*first_displayed_entry, entry);
801 if (pentry == NULL)
802 break;
803 *first_displayed_entry = pentry;
804 } while (++nscrolled < maxscroll);
806 return err;
809 static const struct got_error *
810 show_commit(struct tog_view *parent_view, struct commit_queue_entry *entry,
811 struct got_repository *repo)
813 const struct got_error *err;
814 struct got_object *obj1 = NULL, *obj2 = NULL;
815 struct got_object_qid *parent_id;
816 struct tog_view *view;
818 err = got_object_open(&obj2, repo, entry->id);
819 if (err)
820 return err;
822 parent_id = SIMPLEQ_FIRST(&entry->commit->parent_ids);
823 if (parent_id) {
824 err = got_object_open(&obj1, repo, parent_id->id);
825 if (err)
826 goto done;
829 view = view_open(0, 0, 0, 0, parent_view, TOG_VIEW_DIFF);
830 if (view == NULL) {
831 err = got_error_from_errno();
832 goto done;
835 err = open_diff_view(view, obj1, obj2, repo);
836 if (err)
837 goto done;
838 err = show_diff_view(view);
839 close_diff_view(view);
840 view_close(view);
841 view_show(parent_view);
842 done:
843 if (obj1)
844 got_object_close(obj1);
845 if (obj2)
846 got_object_close(obj2);
847 return err;
850 static const struct got_error *
851 browse_commit(struct tog_view *parent_view, struct commit_queue_entry *entry,
852 struct got_repository *repo)
854 const struct got_error *err = NULL;
855 struct got_tree_object *tree;
856 struct tog_view *view;
858 err = got_object_open_as_tree(&tree, repo, entry->commit->tree_id);
859 if (err)
860 return err;
862 view = view_open(0, 0, 0, 0, parent_view, TOG_VIEW_TREE);
863 if (view == NULL) {
864 err = got_error_from_errno();
865 goto done;
867 err = open_tree_view(view, tree, entry->id, repo);
868 if (err)
869 goto done;
870 err = show_tree_view(view);
871 close_tree_view(view);
872 view_close(view);
873 view_show(parent_view);
874 done:
875 got_object_tree_close(tree);
876 return err;
879 static const struct got_error *
880 open_log_view(struct tog_view *view, struct got_object_id *start_id,
881 struct got_repository *repo, const char *path)
883 const struct got_error *err = NULL;
884 struct got_object_id *head_id = NULL;
885 int nfetched;
886 struct tog_log_view_state *state = &view->state.log;
888 err = got_repo_map_path(&state->in_repo_path, repo, path);
889 if (err != NULL)
890 goto done;
892 err = get_head_commit_id(&head_id, repo);
893 if (err)
894 return err;
896 /* The graph contains all commits. */
897 err = got_commit_graph_open(&state->graph, head_id, 0, repo);
898 if (err)
899 goto done;
900 /* The commit queue contains a subset of commits filtered by path. */
901 TAILQ_INIT(&state->commits.head);
902 state->commits.ncommits = 0;
904 /* Populate commit graph with a sufficient number of commits. */
905 err = got_commit_graph_fetch_commits_up_to(&nfetched,
906 state->graph, start_id, repo);
907 if (err)
908 goto done;
910 /*
911 * Open the initial batch of commits, sorted in commit graph order.
912 * We keep all commits open throughout the lifetime of the log view
913 * in order to avoid having to re-fetch commits from disk while
914 * updating the display.
915 */
916 err = queue_commits(state->graph, &state->commits, start_id,
917 view->nlines, 1, repo, state->in_repo_path);
918 if (err) {
919 if (err->code != GOT_ERR_ITER_COMPLETED)
920 goto done;
921 err = NULL;
924 state->repo = repo;
925 done:
926 free(head_id);
927 return err;
930 static void close_log_view(struct tog_view *view)
932 struct tog_log_view_state *state = &view->state.log;
934 if (state->graph)
935 got_commit_graph_close(state->graph);
936 free_commits(&state->commits);
937 free(state->in_repo_path);
940 static const struct got_error *
941 show_log_view(struct tog_view *view)
943 const struct got_error *err = NULL;
944 int ch, done = 0;
945 struct tog_log_view_state *state = &view->state.log;
947 view_show(view);
949 state->first_displayed_entry =
950 TAILQ_FIRST(&state->commits.head);
951 state->selected_entry = state->first_displayed_entry;
952 while (!done) {
953 err = draw_commits(view, &state->last_displayed_entry,
954 &state->selected_entry, state->first_displayed_entry,
955 &state->commits, state->selected, view->nlines,
956 state->graph, state->repo, state->in_repo_path);
957 if (err)
958 goto done;
960 nodelay(stdscr, FALSE);
961 ch = wgetch(view->window);
962 nodelay(stdscr, TRUE);
963 switch (ch) {
964 case ERR:
965 break;
966 case 'q':
967 done = 1;
968 break;
969 case 'k':
970 case KEY_UP:
971 if (state->selected > 0)
972 state->selected--;
973 if (state->selected > 0)
974 break;
975 scroll_up(&state->first_displayed_entry, 1,
976 &state->commits);
977 break;
978 case KEY_PPAGE:
979 if (TAILQ_FIRST(&state->commits.head) ==
980 state->first_displayed_entry) {
981 state->selected = 0;
982 break;
984 scroll_up(&state->first_displayed_entry,
985 view->nlines, &state->commits);
986 break;
987 case 'j':
988 case KEY_DOWN:
989 if (state->selected < MIN(view->nlines - 2,
990 state->commits.ncommits - 1)) {
991 state->selected++;
992 break;
994 err = scroll_down(&state->first_displayed_entry,
995 1, state->last_displayed_entry,
996 &state->commits, state->graph, state->repo,
997 state->in_repo_path);
998 if (err) {
999 if (err->code != GOT_ERR_ITER_COMPLETED)
1000 goto done;
1001 err = NULL;
1003 break;
1004 case KEY_NPAGE: {
1005 struct commit_queue_entry *first;
1006 first = state->first_displayed_entry;
1007 err = scroll_down(&state->first_displayed_entry,
1008 view->nlines, state->last_displayed_entry,
1009 &state->commits, state->graph, state->repo,
1010 state->in_repo_path);
1011 if (err == NULL)
1012 break;
1013 if (err->code != GOT_ERR_ITER_COMPLETED)
1014 goto done;
1015 if (first == state->first_displayed_entry &&
1016 state->selected < MIN(view->nlines - 2,
1017 state->commits.ncommits - 1)) {
1018 /* can't scroll further down */
1019 state->selected = MIN(view->nlines - 2,
1020 state->commits.ncommits - 1);
1022 err = NULL;
1023 break;
1025 case KEY_RESIZE:
1026 err = view_resize(view);
1027 if (err)
1028 goto done;
1029 if (state->selected > view->nlines - 2)
1030 state->selected = view->nlines - 2;
1031 if (state->selected >
1032 state->commits.ncommits - 1)
1033 state->selected =
1034 state->commits.ncommits - 1;
1035 break;
1036 case KEY_ENTER:
1037 case '\r':
1038 err = show_commit(view, state->selected_entry,
1039 state->repo);
1040 if (err)
1041 goto done;
1042 view_show(view);
1043 break;
1044 case 't':
1045 err = browse_commit(view, state->selected_entry,
1046 state->repo);
1047 if (err)
1048 goto done;
1049 view_show(view);
1050 break;
1051 default:
1052 break;
1055 done:
1056 return err;
1059 static const struct got_error *
1060 cmd_log(int argc, char *argv[])
1062 const struct got_error *error;
1063 struct got_repository *repo = NULL;
1064 struct got_object_id *start_id = NULL;
1065 char *path = NULL, *repo_path = NULL, *cwd = NULL;
1066 char *start_commit = NULL;
1067 int ch;
1068 struct tog_view *view;
1070 #ifndef PROFILE
1071 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
1072 err(1, "pledge");
1073 #endif
1075 while ((ch = getopt(argc, argv, "c:r:")) != -1) {
1076 switch (ch) {
1077 case 'c':
1078 start_commit = optarg;
1079 break;
1080 case 'r':
1081 repo_path = realpath(optarg, NULL);
1082 if (repo_path == NULL)
1083 err(1, "-r option");
1084 break;
1085 default:
1086 usage();
1087 /* NOTREACHED */
1091 argc -= optind;
1092 argv += optind;
1094 if (argc == 0)
1095 path = strdup("");
1096 else if (argc == 1)
1097 path = strdup(argv[0]);
1098 else
1099 usage_log();
1100 if (path == NULL)
1101 return got_error_from_errno();
1103 cwd = getcwd(NULL, 0);
1104 if (cwd == NULL) {
1105 error = got_error_from_errno();
1106 goto done;
1108 if (repo_path == NULL) {
1109 repo_path = strdup(cwd);
1110 if (repo_path == NULL) {
1111 error = got_error_from_errno();
1112 goto done;
1116 error = got_repo_open(&repo, repo_path);
1117 if (error != NULL)
1118 goto done;
1120 if (start_commit == NULL) {
1121 error = get_head_commit_id(&start_id, repo);
1122 if (error != NULL)
1123 goto done;
1124 } else {
1125 struct got_object *obj;
1126 error = got_object_open_by_id_str(&obj, repo, start_commit);
1127 if (error == NULL) {
1128 start_id = got_object_get_id(obj);
1129 if (start_id == NULL)
1130 error = got_error_from_errno();
1131 goto done;
1134 if (error != NULL)
1135 goto done;
1137 view = view_open(0, 0, 0, 0, NULL, TOG_VIEW_LOG);
1138 if (view == NULL) {
1139 error = got_error_from_errno();
1140 goto done;
1142 error = open_log_view(view, start_id, repo, path);
1143 if (error)
1144 goto done;
1145 error = show_log_view(view);
1146 close_log_view(view);
1147 view_close(view);
1148 done:
1149 free(repo_path);
1150 free(cwd);
1151 free(path);
1152 free(start_id);
1153 if (repo)
1154 got_repo_close(repo);
1155 return error;
1158 __dead static void
1159 usage_diff(void)
1161 endwin();
1162 fprintf(stderr, "usage: %s diff [repository-path] object1 object2\n",
1163 getprogname());
1164 exit(1);
1167 static char *
1168 parse_next_line(FILE *f, size_t *len)
1170 char *line;
1171 size_t linelen;
1172 size_t lineno;
1173 const char delim[3] = { '\0', '\0', '\0'};
1175 line = fparseln(f, &linelen, &lineno, delim, 0);
1176 if (len)
1177 *len = linelen;
1178 return line;
1181 static const struct got_error *
1182 draw_file(struct tog_view *view, FILE *f, int *first_displayed_line,
1183 int *last_displayed_line, int *eof, int max_lines)
1185 const struct got_error *err;
1186 int nlines = 0, nprinted = 0;
1187 char *line;
1188 size_t len;
1189 wchar_t *wline;
1190 int width;
1192 rewind(f);
1193 werase(view->window);
1195 *eof = 0;
1196 while (nprinted < max_lines) {
1197 line = parse_next_line(f, &len);
1198 if (line == NULL) {
1199 *eof = 1;
1200 break;
1202 if (++nlines < *first_displayed_line) {
1203 free(line);
1204 continue;
1207 err = format_line(&wline, &width, line, view->ncols);
1208 if (err) {
1209 free(line);
1210 free(wline);
1211 return err;
1213 waddwstr(view->window, wline);
1214 if (width < view->ncols)
1215 waddch(view->window, '\n');
1216 if (++nprinted == 1)
1217 *first_displayed_line = nlines;
1218 free(line);
1219 free(wline);
1220 wline = NULL;
1222 *last_displayed_line = nlines;
1224 update_panels();
1225 doupdate();
1227 return NULL;
1230 static const struct got_error *
1231 open_diff_view(struct tog_view *view, struct got_object *obj1,
1232 struct got_object *obj2, struct got_repository *repo)
1234 const struct got_error *err;
1235 FILE *f;
1237 if (obj1 != NULL && obj2 != NULL &&
1238 got_object_get_type(obj1) != got_object_get_type(obj2))
1239 return got_error(GOT_ERR_OBJ_TYPE);
1241 f = got_opentemp();
1242 if (f == NULL)
1243 return got_error_from_errno();
1245 switch (got_object_get_type(obj1 ? obj1 : obj2)) {
1246 case GOT_OBJ_TYPE_BLOB:
1247 err = got_diff_objects_as_blobs(obj1, obj2, repo, f);
1248 break;
1249 case GOT_OBJ_TYPE_TREE:
1250 err = got_diff_objects_as_trees(obj1, obj2, repo, f);
1251 break;
1252 case GOT_OBJ_TYPE_COMMIT:
1253 err = got_diff_objects_as_commits(obj1, obj2, repo, f);
1254 break;
1255 default:
1256 return got_error(GOT_ERR_OBJ_TYPE);
1259 fflush(f);
1261 view->state.diff.f = f;
1262 view->state.diff.first_displayed_line = 1;
1263 view->state.diff.last_displayed_line = view->nlines;
1265 return NULL;
1268 static void
1269 close_diff_view(struct tog_view *view)
1271 fclose(view->state.diff.f);
1274 static const struct got_error *
1275 show_diff_view(struct tog_view *view)
1277 const struct got_error *err = NULL;
1278 int ch, done = 0;
1279 int eof, i;
1280 struct tog_diff_view_state *state = &view->state.diff;
1282 view_show(view);
1284 while (!done) {
1285 err = draw_file(view, state->f, &state->first_displayed_line,
1286 &state->last_displayed_line, &eof, view->nlines);
1287 if (err)
1288 break;
1289 nodelay(stdscr, FALSE);
1290 ch = wgetch(view->window);
1291 nodelay(stdscr, TRUE);
1292 switch (ch) {
1293 case 'q':
1294 done = 1;
1295 break;
1296 case 'k':
1297 case KEY_UP:
1298 if (state->first_displayed_line > 1)
1299 state->first_displayed_line--;
1300 break;
1301 case KEY_PPAGE:
1302 case KEY_BACKSPACE:
1303 i = 0;
1304 while (i++ < view->nlines - 1 &&
1305 state->first_displayed_line > 1)
1306 state->first_displayed_line--;
1307 break;
1308 case 'j':
1309 case KEY_DOWN:
1310 if (!eof)
1311 state->first_displayed_line++;
1312 break;
1313 case KEY_NPAGE:
1314 case ' ':
1315 i = 0;
1316 while (!eof && i++ < view->nlines - 1) {
1317 char *line = parse_next_line(
1318 state->f, NULL);
1319 state->first_displayed_line++;
1320 if (line == NULL)
1321 break;
1323 break;
1324 case KEY_RESIZE:
1325 err = view_resize(view);
1326 if (err)
1327 goto done;
1328 break;
1329 default:
1330 break;
1333 done:
1334 return err;
1337 static const struct got_error *
1338 cmd_diff(int argc, char *argv[])
1340 const struct got_error *error = NULL;
1341 struct got_repository *repo = NULL;
1342 struct got_object *obj1 = NULL, *obj2 = NULL;
1343 char *repo_path = NULL;
1344 char *obj_id_str1 = NULL, *obj_id_str2 = NULL;
1345 int ch;
1346 struct tog_view *view;
1348 #ifndef PROFILE
1349 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
1350 err(1, "pledge");
1351 #endif
1353 while ((ch = getopt(argc, argv, "")) != -1) {
1354 switch (ch) {
1355 default:
1356 usage();
1357 /* NOTREACHED */
1361 argc -= optind;
1362 argv += optind;
1364 if (argc == 0) {
1365 usage_diff(); /* TODO show local worktree changes */
1366 } else if (argc == 2) {
1367 repo_path = getcwd(NULL, 0);
1368 if (repo_path == NULL)
1369 return got_error_from_errno();
1370 obj_id_str1 = argv[0];
1371 obj_id_str2 = argv[1];
1372 } else if (argc == 3) {
1373 repo_path = realpath(argv[0], NULL);
1374 if (repo_path == NULL)
1375 return got_error_from_errno();
1376 obj_id_str1 = argv[1];
1377 obj_id_str2 = argv[2];
1378 } else
1379 usage_diff();
1381 error = got_repo_open(&repo, repo_path);
1382 free(repo_path);
1383 if (error)
1384 goto done;
1386 error = got_object_open_by_id_str(&obj1, repo, obj_id_str1);
1387 if (error)
1388 goto done;
1390 error = got_object_open_by_id_str(&obj2, repo, obj_id_str2);
1391 if (error)
1392 goto done;
1394 view = view_open(0, 0, 0, 0, NULL, TOG_VIEW_DIFF);
1395 if (view == NULL) {
1396 error = got_error_from_errno();
1397 goto done;
1399 error = open_diff_view(view, obj1, obj2, repo);
1400 if (error)
1401 goto done;
1402 error = show_diff_view(view);
1403 close_diff_view(view);
1404 view_close(view);
1405 done:
1406 got_repo_close(repo);
1407 if (obj1)
1408 got_object_close(obj1);
1409 if (obj2)
1410 got_object_close(obj2);
1411 return error;
1414 __dead static void
1415 usage_blame(void)
1417 endwin();
1418 fprintf(stderr, "usage: %s blame [-c commit] [-r repository-path] path\n",
1419 getprogname());
1420 exit(1);
1423 struct tog_blame_line {
1424 int annotated;
1425 struct got_object_id *id;
1428 static const struct got_error *
1429 draw_blame(struct tog_view *view, struct got_object_id *id, FILE *f,
1430 const char *path, struct tog_blame_line *lines, int nlines,
1431 int blame_complete, int selected_line, int *first_displayed_line,
1432 int *last_displayed_line, int *eof, int max_lines)
1434 const struct got_error *err;
1435 int lineno = 0, nprinted = 0;
1436 char *line;
1437 size_t len;
1438 wchar_t *wline;
1439 int width, wlimit;
1440 struct tog_blame_line *blame_line;
1441 struct got_object_id *prev_id = NULL;
1442 char *id_str;
1444 err = got_object_id_str(&id_str, id);
1445 if (err)
1446 return err;
1448 rewind(f);
1449 werase(view->window);
1451 if (asprintf(&line, "commit: %s", id_str) == -1) {
1452 err = got_error_from_errno();
1453 free(id_str);
1454 return err;
1457 err = format_line(&wline, &width, line, view->ncols);
1458 free(line);
1459 line = NULL;
1460 waddwstr(view->window, wline);
1461 free(wline);
1462 wline = NULL;
1463 if (width < view->ncols)
1464 waddch(view->window, '\n');
1466 if (asprintf(&line, "[%d/%d] %s%s",
1467 *first_displayed_line - 1 + selected_line, nlines,
1468 blame_complete ? "" : "annotating ", path) == -1) {
1469 free(id_str);
1470 return got_error_from_errno();
1472 free(id_str);
1473 err = format_line(&wline, &width, line, view->ncols);
1474 free(line);
1475 line = NULL;
1476 if (err)
1477 return err;
1478 waddwstr(view->window, wline);
1479 free(wline);
1480 wline = NULL;
1481 if (width < view->ncols)
1482 waddch(view->window, '\n');
1484 *eof = 0;
1485 while (nprinted < max_lines - 2) {
1486 line = parse_next_line(f, &len);
1487 if (line == NULL) {
1488 *eof = 1;
1489 break;
1491 if (++lineno < *first_displayed_line) {
1492 free(line);
1493 continue;
1496 wlimit = view->ncols < 9 ? 0 : view->ncols - 9;
1497 err = format_line(&wline, &width, line, wlimit);
1498 if (err) {
1499 free(line);
1500 return err;
1503 if (nprinted == selected_line - 1)
1504 wstandout(view->window);
1506 blame_line = &lines[lineno - 1];
1507 if (blame_line->annotated && prev_id &&
1508 got_object_id_cmp(prev_id, blame_line->id) == 0)
1509 waddstr(view->window, " ");
1510 else if (blame_line->annotated) {
1511 char *id_str;
1512 err = got_object_id_str(&id_str, blame_line->id);
1513 if (err) {
1514 free(line);
1515 free(wline);
1516 return err;
1518 wprintw(view->window, "%.8s ", id_str);
1519 free(id_str);
1520 prev_id = blame_line->id;
1521 } else {
1522 waddstr(view->window, "........ ");
1523 prev_id = NULL;
1526 waddwstr(view->window, wline);
1527 while (width < wlimit) {
1528 waddch(view->window, ' ');
1529 width++;
1531 if (nprinted == selected_line - 1)
1532 wstandend(view->window);
1533 if (++nprinted == 1)
1534 *first_displayed_line = lineno;
1535 free(line);
1536 free(wline);
1537 wline = NULL;
1539 *last_displayed_line = lineno;
1541 update_panels();
1542 doupdate();
1544 return NULL;
1547 struct tog_blame_cb_args {
1548 pthread_mutex_t *mutex;
1549 struct tog_blame_line *lines; /* one per line */
1550 int nlines;
1552 struct tog_view *view;
1553 struct got_object_id *commit_id;
1554 FILE *f;
1555 const char *path;
1556 int *first_displayed_line;
1557 int *last_displayed_line;
1558 int *selected_line;
1559 int *quit;
1562 static const struct got_error *
1563 blame_cb(void *arg, int nlines, int lineno, struct got_object_id *id)
1565 const struct got_error *err = NULL;
1566 struct tog_blame_cb_args *a = arg;
1567 struct tog_blame_line *line;
1568 int eof;
1570 if (nlines != a->nlines ||
1571 (lineno != -1 && lineno < 1) || lineno > a->nlines)
1572 return got_error(GOT_ERR_RANGE);
1574 if (pthread_mutex_lock(a->mutex) != 0)
1575 return got_error_from_errno();
1577 if (*a->quit) { /* user has quit the blame view */
1578 err = got_error(GOT_ERR_ITER_COMPLETED);
1579 goto done;
1582 if (lineno == -1)
1583 goto done; /* no change in this commit */
1585 line = &a->lines[lineno - 1];
1586 if (line->annotated)
1587 goto done;
1589 line->id = got_object_id_dup(id);
1590 if (line->id == NULL) {
1591 err = got_error_from_errno();
1592 goto done;
1594 line->annotated = 1;
1596 err = draw_blame(a->view, a->commit_id, a->f, a->path,
1597 a->lines, a->nlines, 0, *a->selected_line, a->first_displayed_line,
1598 a->last_displayed_line, &eof, a->view->nlines);
1599 done:
1600 if (pthread_mutex_unlock(a->mutex) != 0)
1601 return got_error_from_errno();
1602 return err;
1605 struct tog_blame_thread_args {
1606 const char *path;
1607 struct got_repository *repo;
1608 struct tog_blame_cb_args *cb_args;
1609 int *complete;
1612 static void *
1613 blame_thread(void *arg)
1615 const struct got_error *err;
1616 struct tog_blame_thread_args *ta = arg;
1617 struct tog_blame_cb_args *a = ta->cb_args;
1618 int eof;
1620 err = got_blame_incremental(ta->path, a->commit_id, ta->repo,
1621 blame_cb, ta->cb_args);
1623 if (pthread_mutex_lock(a->mutex) != 0)
1624 return (void *)got_error_from_errno();
1626 got_repo_close(ta->repo);
1627 ta->repo = NULL;
1628 *ta->complete = 1;
1629 if (!err)
1630 err = draw_blame(a->view, a->commit_id, a->f, a->path,
1631 a->lines, a->nlines, 1, *a->selected_line,
1632 a->first_displayed_line, a->last_displayed_line, &eof,
1633 a->view->nlines);
1635 if (pthread_mutex_unlock(a->mutex) != 0 && err == NULL)
1636 err = got_error_from_errno();
1638 return (void *)err;
1641 static struct got_object_id *
1642 get_selected_commit_id(struct tog_blame_line *lines,
1643 int first_displayed_line, int selected_line)
1645 struct tog_blame_line *line;
1647 line = &lines[first_displayed_line - 1 + selected_line - 1];
1648 if (!line->annotated)
1649 return NULL;
1651 return line->id;
1654 static const struct got_error *
1655 open_selected_commit(struct got_object **pobj, struct got_object **obj,
1656 struct tog_blame_line *lines, int first_displayed_line,
1657 int selected_line, struct got_repository *repo)
1659 const struct got_error *err = NULL;
1660 struct got_commit_object *commit = NULL;
1661 struct got_object_id *selected_id;
1662 struct got_object_qid *pid;
1664 *pobj = NULL;
1665 *obj = NULL;
1667 selected_id = get_selected_commit_id(lines,
1668 first_displayed_line, selected_line);
1669 if (selected_id == NULL)
1670 return NULL;
1672 err = got_object_open(obj, repo, selected_id);
1673 if (err)
1674 goto done;
1676 err = got_object_commit_open(&commit, repo, *obj);
1677 if (err)
1678 goto done;
1680 pid = SIMPLEQ_FIRST(&commit->parent_ids);
1681 if (pid) {
1682 err = got_object_open(pobj, repo, pid->id);
1683 if (err)
1684 goto done;
1686 done:
1687 if (commit)
1688 got_object_commit_close(commit);
1689 return err;
1692 struct tog_blame {
1693 FILE *f;
1694 size_t filesize;
1695 struct tog_blame_line *lines;
1696 size_t nlines;
1697 pthread_t thread;
1698 struct tog_blame_thread_args thread_args;
1699 struct tog_blame_cb_args cb_args;
1700 const char *path;
1703 static const struct got_error *
1704 stop_blame(struct tog_blame *blame)
1706 const struct got_error *err = NULL;
1707 int i;
1709 if (blame->thread) {
1710 if (pthread_join(blame->thread, (void **)&err) != 0)
1711 err = got_error_from_errno();
1712 if (err && err->code == GOT_ERR_ITER_COMPLETED)
1713 err = NULL;
1714 blame->thread = NULL;
1716 if (blame->thread_args.repo) {
1717 got_repo_close(blame->thread_args.repo);
1718 blame->thread_args.repo = NULL;
1720 if (blame->f) {
1721 fclose(blame->f);
1722 blame->f = NULL;
1724 for (i = 0; i < blame->nlines; i++)
1725 free(blame->lines[i].id);
1726 free(blame->lines);
1727 blame->lines = NULL;
1728 free(blame->cb_args.commit_id);
1729 blame->cb_args.commit_id = NULL;
1731 return err;
1734 static const struct got_error *
1735 run_blame(struct tog_blame *blame, pthread_mutex_t *mutex,
1736 struct tog_view *view, int *blame_complete,
1737 int *first_displayed_line, int *last_displayed_line,
1738 int *selected_line, int *done, const char *path,
1739 struct got_object_id *commit_id,
1740 struct got_repository *repo)
1742 const struct got_error *err = NULL;
1743 struct got_blob_object *blob = NULL;
1744 struct got_repository *thread_repo = NULL;
1745 struct got_object *obj;
1747 err = got_object_open_by_path(&obj, repo, commit_id, path);
1748 if (err)
1749 goto done;
1750 if (got_object_get_type(obj) != GOT_OBJ_TYPE_BLOB) {
1751 err = got_error(GOT_ERR_OBJ_TYPE);
1752 goto done;
1755 err = got_object_blob_open(&blob, repo, obj, 8192);
1756 if (err)
1757 goto done;
1758 blame->f = got_opentemp();
1759 if (blame->f == NULL) {
1760 err = got_error_from_errno();
1761 goto done;
1763 err = got_object_blob_dump_to_file(&blame->filesize, &blame->nlines,
1764 blame->f, blob);
1765 if (err)
1766 goto done;
1768 blame->lines = calloc(blame->nlines, sizeof(*blame->lines));
1769 if (blame->lines == NULL) {
1770 err = got_error_from_errno();
1771 goto done;
1774 err = got_repo_open(&thread_repo, got_repo_get_path(repo));
1775 if (err)
1776 goto done;
1778 blame->cb_args.view = view;
1779 blame->cb_args.lines = blame->lines;
1780 blame->cb_args.nlines = blame->nlines;
1781 blame->cb_args.mutex = mutex;
1782 blame->cb_args.commit_id = got_object_id_dup(commit_id);
1783 if (blame->cb_args.commit_id == NULL) {
1784 err = got_error_from_errno();
1785 goto done;
1787 blame->cb_args.f = blame->f;
1788 blame->cb_args.path = path;
1789 blame->cb_args.first_displayed_line = first_displayed_line;
1790 blame->cb_args.selected_line = selected_line;
1791 blame->cb_args.last_displayed_line = last_displayed_line;
1792 blame->cb_args.quit = done;
1794 blame->thread_args.path = path;
1795 blame->thread_args.repo = thread_repo;
1796 blame->thread_args.cb_args = &blame->cb_args;
1797 blame->thread_args.complete = blame_complete;
1798 *blame_complete = 0;
1800 if (pthread_create(&blame->thread, NULL, blame_thread,
1801 &blame->thread_args) != 0) {
1802 err = got_error_from_errno();
1803 goto done;
1806 done:
1807 if (blob)
1808 got_object_blob_close(blob);
1809 if (obj)
1810 got_object_close(obj);
1811 if (err)
1812 stop_blame(blame);
1813 return err;
1816 static const struct got_error *
1817 open_blame_view(struct tog_view *view, const char *path,
1818 struct got_object_id *commit_id, struct got_repository *repo)
1820 const struct got_error *err = NULL;
1821 struct tog_blame_view_state *state = &view->state.blame;
1823 SIMPLEQ_INIT(&state->blamed_commits);
1825 if (pthread_mutex_init(&state->mutex, NULL) != 0)
1826 return got_error_from_errno();
1828 err = got_object_qid_alloc(&state->blamed_commit, commit_id);
1829 if (err)
1830 return err;
1832 SIMPLEQ_INSERT_HEAD(&state->blamed_commits, state->blamed_commit,
1833 entry);
1834 state->first_displayed_line = 1;
1835 state->last_displayed_line = view->nlines;
1836 state->selected_line = 1;
1837 state->blame_complete = 0;
1838 state->path = path;
1839 state->repo = repo;
1840 state->commit_id = commit_id;
1842 return NULL;
1845 static void
1846 close_blame_view(struct tog_view *view)
1848 struct tog_blame_view_state *state = &view->state.blame;
1850 while (!SIMPLEQ_EMPTY(&state->blamed_commits)) {
1851 struct got_object_qid *blamed_commit;
1852 blamed_commit = SIMPLEQ_FIRST(&state->blamed_commits);
1853 SIMPLEQ_REMOVE_HEAD(&state->blamed_commits, entry);
1854 got_object_qid_free(blamed_commit);
1858 static const struct got_error *
1859 show_blame_view(struct tog_view *view)
1861 const struct got_error *err = NULL, *thread_err = NULL;
1862 int ch, done = 0, eof;
1863 struct got_object *obj = NULL, *pobj = NULL;
1864 struct tog_blame blame;
1865 struct tog_view *diff_view;
1866 struct tog_blame_view_state *state = &view->state.blame;
1868 view_show(view);
1870 memset(&blame, 0, sizeof(blame));
1871 err = run_blame(&blame, &state->mutex, view, &state->blame_complete,
1872 &state->first_displayed_line, &state->last_displayed_line,
1873 &state->selected_line, &done, state->path,
1874 state->blamed_commit->id, state->repo);
1875 if (err)
1876 return err;
1878 while (!done) {
1879 if (pthread_mutex_lock(&state->mutex) != 0) {
1880 err = got_error_from_errno();
1881 goto done;
1883 err = draw_blame(view, state->blamed_commit->id, blame.f,
1884 state->path, blame.lines, blame.nlines,
1885 state->blame_complete, state->selected_line,
1886 &state->first_displayed_line, &state->last_displayed_line,
1887 &eof, view->nlines);
1888 if (pthread_mutex_unlock(&state->mutex) != 0) {
1889 err = got_error_from_errno();
1890 goto done;
1892 if (err)
1893 break;
1894 nodelay(stdscr, FALSE);
1895 ch = wgetch(view->window);
1896 nodelay(stdscr, TRUE);
1897 if (pthread_mutex_lock(&state->mutex) != 0) {
1898 err = got_error_from_errno();
1899 goto done;
1901 switch (ch) {
1902 case 'q':
1903 done = 1;
1904 break;
1905 case 'k':
1906 case KEY_UP:
1907 if (state->selected_line > 1)
1908 state->selected_line--;
1909 else if (state->selected_line == 1 &&
1910 state->first_displayed_line > 1)
1911 state->first_displayed_line--;
1912 break;
1913 case KEY_PPAGE:
1914 case KEY_BACKSPACE:
1915 if (state->first_displayed_line == 1) {
1916 state->selected_line = 1;
1917 break;
1919 if (state->first_displayed_line >
1920 view->nlines - 2)
1921 state->first_displayed_line -=
1922 (view->nlines - 2);
1923 else
1924 state->first_displayed_line = 1;
1925 break;
1926 case 'j':
1927 case KEY_DOWN:
1928 if (state->selected_line < view->nlines - 2 &&
1929 state->first_displayed_line +
1930 state->selected_line <= blame.nlines)
1931 state->selected_line++;
1932 else if (state->last_displayed_line <
1933 blame.nlines)
1934 state->first_displayed_line++;
1935 break;
1936 case 'b':
1937 case 'p': {
1938 struct got_object_id *id;
1939 id = get_selected_commit_id(blame.lines,
1940 state->first_displayed_line,
1941 state->selected_line);
1942 if (id == NULL || got_object_id_cmp(id,
1943 state->blamed_commit->id) == 0)
1944 break;
1945 err = open_selected_commit(&pobj, &obj,
1946 blame.lines, state->first_displayed_line,
1947 state->selected_line, state->repo);
1948 if (err)
1949 break;
1950 if (pobj == NULL && obj == NULL)
1951 break;
1952 if (ch == 'p' && pobj == NULL)
1953 break;
1954 done = 1;
1955 if (pthread_mutex_unlock(&state->mutex) != 0) {
1956 err = got_error_from_errno();
1957 goto done;
1959 thread_err = stop_blame(&blame);
1960 done = 0;
1961 if (pthread_mutex_lock(&state->mutex) != 0) {
1962 err = got_error_from_errno();
1963 goto done;
1965 if (thread_err)
1966 break;
1967 id = got_object_get_id(ch == 'b' ? obj : pobj);
1968 got_object_close(obj);
1969 obj = NULL;
1970 if (pobj) {
1971 got_object_close(pobj);
1972 pobj = NULL;
1974 if (id == NULL) {
1975 err = got_error_from_errno();
1976 break;
1978 err = got_object_qid_alloc(
1979 &state->blamed_commit, id);
1980 free(id);
1981 if (err)
1982 goto done;
1983 SIMPLEQ_INSERT_HEAD(&state->blamed_commits,
1984 state->blamed_commit, entry);
1985 err = run_blame(&blame, &state->mutex, view,
1986 &state->blame_complete,
1987 &state->first_displayed_line,
1988 &state->last_displayed_line,
1989 &state->selected_line, &done, state->path,
1990 state->blamed_commit->id, state->repo);
1991 if (err)
1992 break;
1993 break;
1995 case 'B': {
1996 struct got_object_qid *first;
1997 first = SIMPLEQ_FIRST(&state->blamed_commits);
1998 if (!got_object_id_cmp(first->id,
1999 state->commit_id))
2000 break;
2001 done = 1;
2002 if (pthread_mutex_unlock(&state->mutex) != 0) {
2003 err = got_error_from_errno();
2004 goto done;
2006 thread_err = stop_blame(&blame);
2007 done = 0;
2008 if (pthread_mutex_lock(&state->mutex) != 0) {
2009 err = got_error_from_errno();
2010 goto done;
2012 if (thread_err)
2013 break;
2014 SIMPLEQ_REMOVE_HEAD(&state->blamed_commits,
2015 entry);
2016 got_object_qid_free(state->blamed_commit);
2017 state->blamed_commit =
2018 SIMPLEQ_FIRST(&state->blamed_commits);
2019 err = run_blame(&blame, &state->mutex, view,
2020 &state->blame_complete,
2021 &state->first_displayed_line,
2022 &state->last_displayed_line,
2023 &state->selected_line, &done, state->path,
2024 state->blamed_commit->id, state->repo);
2025 if (err)
2026 break;
2027 break;
2029 case KEY_ENTER:
2030 case '\r':
2031 err = open_selected_commit(&pobj, &obj,
2032 blame.lines, state->first_displayed_line,
2033 state->selected_line, state->repo);
2034 if (err)
2035 break;
2036 if (pobj == NULL && obj == NULL)
2037 break;
2038 diff_view = view_open(0, 0, 0, 0, view,
2039 TOG_VIEW_DIFF);
2040 if (diff_view == NULL) {
2041 err = got_error_from_errno();
2042 break;
2044 err = open_diff_view(diff_view, pobj, obj,
2045 state->repo);
2046 if (err)
2047 break;
2048 err = show_diff_view(diff_view);
2049 close_diff_view(diff_view);
2050 view_close(diff_view);
2051 if (err)
2052 break;
2053 view_show(view);
2054 if (pobj) {
2055 got_object_close(pobj);
2056 pobj = NULL;
2058 got_object_close(obj);
2059 obj = NULL;
2060 if (err)
2061 break;
2062 break;
2063 case KEY_NPAGE:
2064 case ' ':
2065 if (state->last_displayed_line
2066 >= blame.nlines &&
2067 state->selected_line < view->nlines - 2) {
2068 state->selected_line = MIN(blame.nlines,
2069 view->nlines - 2);
2070 break;
2072 if (state->last_displayed_line +
2073 view->nlines - 2 <= blame.nlines)
2074 state->first_displayed_line +=
2075 view->nlines - 2;
2076 else
2077 state->first_displayed_line =
2078 blame.nlines - (view->nlines - 3);
2079 break;
2080 case KEY_RESIZE:
2081 err = view_resize(view);
2082 if (err)
2083 break;
2084 if (state->selected_line > view->nlines - 2) {
2085 state->selected_line = MIN(blame.nlines,
2086 view->nlines - 2);
2088 break;
2089 default:
2090 break;
2092 if (pthread_mutex_unlock(&state->mutex) != 0)
2093 err = got_error_from_errno();
2094 if (err || thread_err)
2095 break;
2097 done:
2098 if (pobj)
2099 got_object_close(pobj);
2100 if (blame.thread)
2101 thread_err = stop_blame(&blame);
2102 return thread_err ? thread_err : err;
2105 static const struct got_error *
2106 cmd_blame(int argc, char *argv[])
2108 const struct got_error *error;
2109 struct got_repository *repo = NULL;
2110 char *path, *cwd = NULL, *repo_path = NULL, *in_repo_path = NULL;
2111 struct got_object_id *commit_id = NULL;
2112 char *commit_id_str = NULL;
2113 int ch;
2114 struct tog_view *view;
2116 #ifndef PROFILE
2117 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
2118 err(1, "pledge");
2119 #endif
2121 while ((ch = getopt(argc, argv, "c:r:")) != -1) {
2122 switch (ch) {
2123 case 'c':
2124 commit_id_str = optarg;
2125 break;
2126 case 'r':
2127 repo_path = realpath(optarg, NULL);
2128 if (repo_path == NULL)
2129 err(1, "-r option");
2130 break;
2131 default:
2132 usage();
2133 /* NOTREACHED */
2137 argc -= optind;
2138 argv += optind;
2140 if (argc == 1)
2141 path = argv[0];
2142 else
2143 usage_blame();
2145 cwd = getcwd(NULL, 0);
2146 if (cwd == NULL) {
2147 error = got_error_from_errno();
2148 goto done;
2150 if (repo_path == NULL) {
2151 repo_path = strdup(cwd);
2152 if (repo_path == NULL) {
2153 error = got_error_from_errno();
2154 goto done;
2159 error = got_repo_open(&repo, repo_path);
2160 if (error != NULL)
2161 return error;
2163 error = got_repo_map_path(&in_repo_path, repo, path);
2164 if (error != NULL)
2165 goto done;
2167 if (commit_id_str == NULL) {
2168 struct got_reference *head_ref;
2169 error = got_ref_open(&head_ref, repo, GOT_REF_HEAD);
2170 if (error != NULL)
2171 goto done;
2172 error = got_ref_resolve(&commit_id, repo, head_ref);
2173 got_ref_close(head_ref);
2174 } else {
2175 struct got_object *obj;
2176 error = got_object_open_by_id_str(&obj, repo, commit_id_str);
2177 if (error != NULL)
2178 goto done;
2179 commit_id = got_object_get_id(obj);
2180 if (commit_id == NULL)
2181 error = got_error_from_errno();
2182 got_object_close(obj);
2184 if (error != NULL)
2185 goto done;
2187 view = view_open(0, 0, 0, 0, NULL, TOG_VIEW_BLAME);
2188 if (view == NULL) {
2189 error = got_error_from_errno();
2190 goto done;
2192 error = open_blame_view(view, in_repo_path, commit_id, repo);
2193 if (error)
2194 goto done;
2195 error = show_blame_view(view);
2196 close_blame_view(view);
2197 view_close(view);
2198 done:
2199 free(in_repo_path);
2200 free(repo_path);
2201 free(cwd);
2202 free(commit_id);
2203 if (repo)
2204 got_repo_close(repo);
2205 return error;
2208 static const struct got_error *
2209 draw_tree_entries(struct tog_view *view,
2210 struct got_tree_entry **first_displayed_entry,
2211 struct got_tree_entry **last_displayed_entry,
2212 struct got_tree_entry **selected_entry, int *ndisplayed,
2213 const char *label, int show_ids, const char *parent_path,
2214 const struct got_tree_entries *entries, int selected, int limit, int isroot)
2216 const struct got_error *err = NULL;
2217 struct got_tree_entry *te;
2218 wchar_t *wline;
2219 int width, n;
2221 *ndisplayed = 0;
2223 werase(view->window);
2225 if (limit == 0)
2226 return NULL;
2228 err = format_line(&wline, &width, label, view->ncols);
2229 if (err)
2230 return err;
2231 waddwstr(view->window, wline);
2232 free(wline);
2233 wline = NULL;
2234 if (width < view->ncols)
2235 waddch(view->window, '\n');
2236 if (--limit <= 0)
2237 return NULL;
2238 err = format_line(&wline, &width, parent_path, view->ncols);
2239 if (err)
2240 return err;
2241 waddwstr(view->window, wline);
2242 free(wline);
2243 wline = NULL;
2244 if (width < view->ncols)
2245 waddch(view->window, '\n');
2246 if (--limit <= 0)
2247 return NULL;
2248 waddch(view->window, '\n');
2249 if (--limit <= 0)
2250 return NULL;
2252 te = SIMPLEQ_FIRST(&entries->head);
2253 if (*first_displayed_entry == NULL) {
2254 if (selected == 0) {
2255 wstandout(view->window);
2256 *selected_entry = NULL;
2258 waddstr(view->window, " ..\n"); /* parent directory */
2259 if (selected == 0)
2260 wstandend(view->window);
2261 (*ndisplayed)++;
2262 if (--limit <= 0)
2263 return NULL;
2264 n = 1;
2265 } else {
2266 n = 0;
2267 while (te != *first_displayed_entry)
2268 te = SIMPLEQ_NEXT(te, entry);
2271 while (te) {
2272 char *line = NULL, *id_str = NULL;
2274 if (show_ids) {
2275 err = got_object_id_str(&id_str, te->id);
2276 if (err)
2277 return got_error_from_errno();
2279 if (asprintf(&line, "%s %s%s", id_str ? id_str : "",
2280 te->name, S_ISDIR(te->mode) ? "/" : "") == -1) {
2281 free(id_str);
2282 return got_error_from_errno();
2284 free(id_str);
2285 err = format_line(&wline, &width, line, view->ncols);
2286 if (err) {
2287 free(line);
2288 break;
2290 if (n == selected) {
2291 wstandout(view->window);
2292 *selected_entry = te;
2294 waddwstr(view->window, wline);
2295 if (width < view->ncols)
2296 waddch(view->window, '\n');
2297 if (n == selected)
2298 wstandend(view->window);
2299 free(line);
2300 free(wline);
2301 wline = NULL;
2302 n++;
2303 (*ndisplayed)++;
2304 *last_displayed_entry = te;
2305 if (--limit <= 0)
2306 break;
2307 te = SIMPLEQ_NEXT(te, entry);
2310 return err;
2313 static void
2314 tree_scroll_up(struct got_tree_entry **first_displayed_entry, int maxscroll,
2315 const struct got_tree_entries *entries, int isroot)
2317 struct got_tree_entry *te, *prev;
2318 int i;
2320 if (*first_displayed_entry == NULL)
2321 return;
2323 te = SIMPLEQ_FIRST(&entries->head);
2324 if (*first_displayed_entry == te) {
2325 if (!isroot)
2326 *first_displayed_entry = NULL;
2327 return;
2330 /* XXX this is stupid... switch to TAILQ? */
2331 for (i = 0; i < maxscroll; i++) {
2332 while (te != *first_displayed_entry) {
2333 prev = te;
2334 te = SIMPLEQ_NEXT(te, entry);
2336 *first_displayed_entry = prev;
2337 te = SIMPLEQ_FIRST(&entries->head);
2339 if (!isroot && te == SIMPLEQ_FIRST(&entries->head) && i < maxscroll)
2340 *first_displayed_entry = NULL;
2343 static void
2344 tree_scroll_down(struct got_tree_entry **first_displayed_entry, int maxscroll,
2345 struct got_tree_entry *last_displayed_entry,
2346 const struct got_tree_entries *entries)
2348 struct got_tree_entry *next;
2349 int n = 0;
2351 if (SIMPLEQ_NEXT(last_displayed_entry, entry) == NULL)
2352 return;
2354 if (*first_displayed_entry)
2355 next = SIMPLEQ_NEXT(*first_displayed_entry, entry);
2356 else
2357 next = SIMPLEQ_FIRST(&entries->head);
2358 while (next) {
2359 *first_displayed_entry = next;
2360 if (++n >= maxscroll)
2361 break;
2362 next = SIMPLEQ_NEXT(next, entry);
2366 static const struct got_error *
2367 tree_entry_path(char **path, struct tog_parent_trees *parents,
2368 struct got_tree_entry *te)
2370 const struct got_error *err = NULL;
2371 struct tog_parent_tree *pt;
2372 size_t len = 2; /* for leading slash and NUL */
2374 TAILQ_FOREACH(pt, parents, entry)
2375 len += strlen(pt->selected_entry->name) + 1 /* slash */;
2376 if (te)
2377 len += strlen(te->name);
2379 *path = calloc(1, len);
2380 if (path == NULL)
2381 return got_error_from_errno();
2383 (*path)[0] = '/';
2384 pt = TAILQ_LAST(parents, tog_parent_trees);
2385 while (pt) {
2386 if (strlcat(*path, pt->selected_entry->name, len) >= len) {
2387 err = got_error(GOT_ERR_NO_SPACE);
2388 goto done;
2390 if (strlcat(*path, "/", len) >= len) {
2391 err = got_error(GOT_ERR_NO_SPACE);
2392 goto done;
2394 pt = TAILQ_PREV(pt, tog_parent_trees, entry);
2396 if (te) {
2397 if (strlcat(*path, te->name, len) >= len) {
2398 err = got_error(GOT_ERR_NO_SPACE);
2399 goto done;
2402 done:
2403 if (err) {
2404 free(*path);
2405 *path = NULL;
2407 return err;
2410 static const struct got_error *
2411 blame_tree_entry(struct tog_view *parent_view, struct got_tree_entry *te,
2412 struct tog_parent_trees *parents, struct got_object_id *commit_id,
2413 struct got_repository *repo)
2415 const struct got_error *err = NULL;
2416 char *path;
2417 struct tog_view *view;
2419 err = tree_entry_path(&path, parents, te);
2420 if (err)
2421 return err;
2423 view = view_open(0, 0, 0, 0, parent_view, TOG_VIEW_BLAME);
2424 if (view) {
2425 err = open_blame_view(view, path, commit_id, repo);
2426 if (err)
2427 goto done;
2428 err = show_blame_view(view);
2429 close_blame_view(view);
2430 view_close(view);
2431 } else
2432 err = got_error_from_errno();
2434 view_show(parent_view);
2435 done:
2436 free(path);
2437 return err;
2440 static const struct got_error *
2441 log_tree_entry(struct tog_view *view, struct got_tree_entry *te,
2442 struct tog_parent_trees *parents, struct got_object_id *commit_id,
2443 struct got_repository *repo)
2445 const struct got_error *err = NULL;
2446 char *path;
2448 err = tree_entry_path(&path, parents, te);
2449 if (err)
2450 return err;
2452 err = open_log_view(view, commit_id, repo, path);
2453 if (err)
2454 goto done;
2455 err = show_log_view(view);
2456 close_log_view(view);
2457 done:
2458 free(path);
2459 return err;
2462 static const struct got_error *
2463 open_tree_view(struct tog_view *view, struct got_tree_object *root,
2464 struct got_object_id *commit_id, struct got_repository *repo)
2466 const struct got_error *err = NULL;
2467 char *commit_id_str = NULL;
2468 struct tog_tree_view_state *state = &view->state.tree;
2470 TAILQ_INIT(&state->parents);
2472 err = got_object_id_str(&commit_id_str, commit_id);
2473 if (err != NULL)
2474 goto done;
2476 if (asprintf(&state->tree_label, "commit: %s", commit_id_str) == -1) {
2477 err = got_error_from_errno();
2478 goto done;
2481 state->root = state->tree = root;
2482 state->entries = got_object_tree_get_entries(root);
2483 state->first_displayed_entry = SIMPLEQ_FIRST(&state->entries->head);
2484 state->commit_id = commit_id;
2485 state->repo = repo;
2486 done:
2487 free(commit_id_str);
2488 if (err)
2489 free(state->tree_label);
2490 return err;
2493 static void
2494 close_tree_view(struct tog_view *view)
2496 struct tog_tree_view_state *state = &view->state.tree;
2498 free(state->tree_label);
2499 while (!TAILQ_EMPTY(&state->parents)) {
2500 struct tog_parent_tree *parent;
2501 parent = TAILQ_FIRST(&state->parents);
2502 TAILQ_REMOVE(&state->parents, parent, entry);
2503 free(parent);
2506 if (state->tree != state->root)
2507 got_object_tree_close(state->tree);
2510 static const struct got_error *
2511 show_tree_view(struct tog_view *view)
2513 const struct got_error *err = NULL;
2514 struct tog_tree_view_state *state = &view->state.tree;
2515 int ch, done = 0;
2516 int nentries;
2518 view_show(view);
2520 while (!done) {
2521 char *parent_path;
2522 state->entries = got_object_tree_get_entries(state->tree);
2523 nentries = state->entries->nentries;
2524 if (state->tree != state->root)
2525 nentries++; /* '..' directory */
2527 err = tree_entry_path(&parent_path, &state->parents, NULL);
2528 if (err)
2529 goto done;
2531 err = draw_tree_entries(view, &state->first_displayed_entry,
2532 &state->last_displayed_entry, &state->selected_entry,
2533 &state->ndisplayed, state->tree_label, state->show_ids,
2534 parent_path, state->entries, state->selected,
2535 view->nlines, state->tree == state->root);
2536 free(parent_path);
2537 if (err)
2538 break;
2540 nodelay(stdscr, FALSE);
2541 ch = wgetch(view->window);
2542 nodelay(stdscr, TRUE);
2543 switch (ch) {
2544 case 'q':
2545 done = 1;
2546 break;
2547 case 'i':
2548 state->show_ids = !state->show_ids;
2549 break;
2550 case 'l':
2551 if (state->selected_entry) {
2552 struct tog_view *log_view;
2553 log_view = view_open(0, 0, 0, 0, view,
2554 TOG_VIEW_LOG);
2555 if (log_view == NULL) {
2556 err = got_error_from_errno();
2557 goto done;
2559 err = log_tree_entry(log_view,
2560 state->selected_entry,
2561 &state->parents,
2562 state->commit_id, state->repo);
2563 view_close(log_view);
2564 view_show(view);
2565 if (err)
2566 goto done;
2568 break;
2569 case 'k':
2570 case KEY_UP:
2571 if (state->selected > 0)
2572 state->selected--;
2573 if (state->selected > 0)
2574 break;
2575 tree_scroll_up(&state->first_displayed_entry, 1,
2576 state->entries, state->tree == state->root);
2577 break;
2578 case KEY_PPAGE:
2579 if (SIMPLEQ_FIRST(&state->entries->head) ==
2580 state->first_displayed_entry) {
2581 if (state->tree != state->root)
2582 state->first_displayed_entry = NULL;
2583 state->selected = 0;
2584 break;
2586 tree_scroll_up(&state->first_displayed_entry,
2587 view->nlines, state->entries,
2588 state->tree == state->root);
2589 break;
2590 case 'j':
2591 case KEY_DOWN:
2592 if (state->selected < state->ndisplayed - 1) {
2593 state->selected++;
2594 break;
2596 tree_scroll_down(&state->first_displayed_entry, 1,
2597 state->last_displayed_entry, state->entries);
2598 break;
2599 case KEY_NPAGE:
2600 tree_scroll_down(&state->first_displayed_entry,
2601 view->nlines, state->last_displayed_entry,
2602 state->entries);
2603 if (SIMPLEQ_NEXT(state->last_displayed_entry, entry))
2604 break;
2605 /* can't scroll any further; move cursor down */
2606 if (state->selected < state->ndisplayed - 1)
2607 state->selected = state->ndisplayed - 1;
2608 break;
2609 case KEY_ENTER:
2610 case '\r':
2611 if (state->selected_entry == NULL) {
2612 struct tog_parent_tree *parent;
2613 case KEY_BACKSPACE:
2614 /* user selected '..' */
2615 if (state->tree == state->root)
2616 break;
2617 parent = TAILQ_FIRST(&state->parents);
2618 TAILQ_REMOVE(&state->parents, parent, entry);
2619 got_object_tree_close(state->tree);
2620 state->tree = parent->tree;
2621 state->first_displayed_entry =
2622 parent->first_displayed_entry;
2623 state->selected_entry = parent->selected_entry;
2624 state->selected = parent->selected;
2625 free(parent);
2626 } else if (S_ISDIR(state->selected_entry->mode)) {
2627 struct tog_parent_tree *parent;
2628 struct got_tree_object *child;
2629 err = got_object_open_as_tree(
2630 &child, state->repo, state->selected_entry->id);
2631 if (err)
2632 goto done;
2633 parent = calloc(1, sizeof(*parent));
2634 if (parent == NULL) {
2635 err = got_error_from_errno();
2636 goto done;
2638 parent->tree = state->tree;
2639 parent->first_displayed_entry =
2640 state->first_displayed_entry;
2641 parent->selected_entry = state->selected_entry;
2642 parent->selected = state->selected;
2643 TAILQ_INSERT_HEAD(&state->parents, parent,
2644 entry);
2645 state->tree = child;
2646 state->selected = 0;
2647 state->first_displayed_entry = NULL;
2648 } else if (S_ISREG(state->selected_entry->mode)) {
2649 err = blame_tree_entry(view,
2650 state->selected_entry, &state->parents,
2651 state->commit_id, state->repo);
2652 if (err)
2653 goto done;
2655 break;
2656 case KEY_RESIZE:
2657 err = view_resize(view);
2658 if (err)
2659 goto done;
2660 if (state->selected > view->nlines)
2661 state->selected = state->ndisplayed - 1;
2662 break;
2663 default:
2664 break;
2667 done:
2668 return err;
2671 __dead static void
2672 usage_tree(void)
2674 endwin();
2675 fprintf(stderr, "usage: %s tree [-c commit] [repository-path]\n",
2676 getprogname());
2677 exit(1);
2680 static const struct got_error *
2681 cmd_tree(int argc, char *argv[])
2683 const struct got_error *error;
2684 struct got_repository *repo = NULL;
2685 char *repo_path = NULL;
2686 struct got_object_id *commit_id = NULL;
2687 char *commit_id_arg = NULL;
2688 struct got_commit_object *commit = NULL;
2689 struct got_tree_object *tree = NULL;
2690 int ch;
2691 struct tog_view *view;
2693 #ifndef PROFILE
2694 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
2695 err(1, "pledge");
2696 #endif
2698 while ((ch = getopt(argc, argv, "c:")) != -1) {
2699 switch (ch) {
2700 case 'c':
2701 commit_id_arg = optarg;
2702 break;
2703 default:
2704 usage();
2705 /* NOTREACHED */
2709 argc -= optind;
2710 argv += optind;
2712 if (argc == 0) {
2713 repo_path = getcwd(NULL, 0);
2714 if (repo_path == NULL)
2715 return got_error_from_errno();
2716 } else if (argc == 1) {
2717 repo_path = realpath(argv[0], NULL);
2718 if (repo_path == NULL)
2719 return got_error_from_errno();
2720 } else
2721 usage_log();
2723 error = got_repo_open(&repo, repo_path);
2724 free(repo_path);
2725 if (error != NULL)
2726 return error;
2728 if (commit_id_arg == NULL) {
2729 error = get_head_commit_id(&commit_id, repo);
2730 if (error != NULL)
2731 goto done;
2732 } else {
2733 struct got_object *obj;
2734 error = got_object_open_by_id_str(&obj, repo, commit_id_arg);
2735 if (error == NULL) {
2736 commit_id = got_object_get_id(obj);
2737 if (commit_id == NULL)
2738 error = got_error_from_errno();
2741 if (error != NULL)
2742 goto done;
2744 error = got_object_open_as_commit(&commit, repo, commit_id);
2745 if (error != NULL)
2746 goto done;
2748 error = got_object_open_as_tree(&tree, repo, commit->tree_id);
2749 if (error != NULL)
2750 goto done;
2752 view = view_open(0, 0, 0, 0, NULL, TOG_VIEW_TREE);
2753 if (view == NULL) {
2754 error = got_error_from_errno();
2755 goto done;
2757 error = open_tree_view(view, tree, commit_id, repo);
2758 if (error)
2759 goto done;
2760 error = show_tree_view(view);
2761 close_tree_view(view);
2762 view_close(view);
2763 done:
2764 free(commit_id);
2765 if (commit)
2766 got_object_commit_close(commit);
2767 if (tree)
2768 got_object_tree_close(tree);
2769 if (repo)
2770 got_repo_close(repo);
2771 return error;
2773 static void
2774 init_curses(void)
2776 initscr();
2777 cbreak();
2778 noecho();
2779 nonl();
2780 intrflush(stdscr, FALSE);
2781 keypad(stdscr, TRUE);
2782 curs_set(0);
2785 __dead static void
2786 usage(void)
2788 int i;
2790 fprintf(stderr, "usage: %s [-h] [command] [arg ...]\n\n"
2791 "Available commands:\n", getprogname());
2792 for (i = 0; i < nitems(tog_commands); i++) {
2793 struct tog_cmd *cmd = &tog_commands[i];
2794 fprintf(stderr, " %s: %s\n", cmd->name, cmd->descr);
2796 exit(1);
2799 static char **
2800 make_argv(const char *arg0, const char *arg1)
2802 char **argv;
2803 int argc = (arg1 == NULL ? 1 : 2);
2805 argv = calloc(argc, sizeof(char *));
2806 if (argv == NULL)
2807 err(1, "calloc");
2808 argv[0] = strdup(arg0);
2809 if (argv[0] == NULL)
2810 err(1, "calloc");
2811 if (arg1) {
2812 argv[1] = strdup(arg1);
2813 if (argv[1] == NULL)
2814 err(1, "calloc");
2817 return argv;
2820 int
2821 main(int argc, char *argv[])
2823 const struct got_error *error = NULL;
2824 struct tog_cmd *cmd = NULL;
2825 int ch, hflag = 0;
2826 char **cmd_argv = NULL;
2828 setlocale(LC_ALL, "");
2830 while ((ch = getopt(argc, argv, "h")) != -1) {
2831 switch (ch) {
2832 case 'h':
2833 hflag = 1;
2834 break;
2835 default:
2836 usage();
2837 /* NOTREACHED */
2841 argc -= optind;
2842 argv += optind;
2843 optind = 0;
2844 optreset = 1;
2846 if (argc == 0) {
2847 if (hflag)
2848 usage();
2849 /* Build an argument vector which runs a default command. */
2850 cmd = &tog_commands[0];
2851 cmd_argv = make_argv(cmd->name, NULL);
2852 argc = 1;
2853 } else {
2854 int i;
2856 /* Did the user specific a command? */
2857 for (i = 0; i < nitems(tog_commands); i++) {
2858 if (strncmp(tog_commands[i].name, argv[0],
2859 strlen(argv[0])) == 0) {
2860 cmd = &tog_commands[i];
2861 if (hflag)
2862 tog_commands[i].cmd_usage();
2863 break;
2866 if (cmd == NULL) {
2867 /* Did the user specify a repository? */
2868 char *repo_path = realpath(argv[0], NULL);
2869 if (repo_path) {
2870 struct got_repository *repo;
2871 error = got_repo_open(&repo, repo_path);
2872 if (error == NULL)
2873 got_repo_close(repo);
2874 } else
2875 error = got_error_from_errno();
2876 if (error) {
2877 if (hflag) {
2878 fprintf(stderr, "%s: '%s' is not a "
2879 "known command\n", getprogname(),
2880 argv[0]);
2881 usage();
2883 fprintf(stderr, "%s: '%s' is neither a known "
2884 "command nor a path to a repository\n",
2885 getprogname(), argv[0]);
2886 free(repo_path);
2887 return 1;
2889 cmd = &tog_commands[0];
2890 cmd_argv = make_argv(cmd->name, repo_path);
2891 argc = 2;
2892 free(repo_path);
2896 init_curses();
2898 error = cmd->cmd_main(argc, cmd_argv ? cmd_argv : argv);
2899 if (error)
2900 goto done;
2901 done:
2902 endwin();
2903 free(cmd_argv);
2904 if (error)
2905 fprintf(stderr, "%s: %s\n", getprogname(), error->msg);
2906 return 0;