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_TREE,
89 TOG_VIEW_BLAME
90 };
92 struct commit_queue_entry {
93 TAILQ_ENTRY(commit_queue_entry) entry;
94 struct got_object_id *id;
95 struct got_commit_object *commit;
96 };
97 TAILQ_HEAD(commit_queue_head, commit_queue_entry);
98 struct commit_queue {
99 int ncommits;
100 struct commit_queue_head head;
101 };
103 struct tog_diff_view_state {
104 FILE *f;
105 int first_displayed_line;
106 int last_displayed_line;
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_view {
121 WINDOW *window;
122 PANEL *panel;
123 int nlines, ncols, begin_y, begin_x;
124 int lines, cols; /* copies of LINES and COLS */
125 struct tog_view *parent;
127 /* type-specific state */
128 enum tog_view_type type;
129 union {
130 struct tog_diff_view_state diff;
131 struct tog_log_view_state log;
132 } state;
133 };
135 static const struct got_error *open_diff_view(struct tog_view *,
136 struct got_object *, struct got_object *, struct got_repository *);
137 static const struct got_error *show_diff_view(struct tog_view *);
138 static void close_diff_view(struct tog_view *);
139 static const struct got_error *open_log_view(struct tog_view *,
140 struct got_object_id *, struct got_repository *, const char *);
141 static const struct got_error * show_log_view(struct tog_view *);
142 static void close_log_view(struct tog_view *);
143 static const struct got_error *show_blame_view(struct tog_view *, const char *,
144 struct got_object_id *, struct got_repository *);
145 static const struct got_error *show_tree_view(struct tog_view *,
146 struct got_tree_object *, struct got_object_id *, struct got_repository *);
148 static void
149 view_close(struct tog_view *view)
151 if (view->panel)
152 del_panel(view->panel);
153 if (view->window)
154 delwin(view->window);
155 free(view);
158 static struct tog_view *
159 view_open(int nlines, int ncols, int begin_y, int begin_x,
160 struct tog_view *parent, enum tog_view_type type)
162 struct tog_view *view = malloc(sizeof(*view));
164 if (view == NULL)
165 return NULL;
167 view->parent = parent;
168 view->type = type;
169 view->lines = LINES;
170 view->cols = COLS;
171 view->nlines = nlines ? nlines : LINES - begin_y;
172 view->ncols = ncols ? ncols : COLS - begin_x;
173 view->begin_y = begin_y;
174 view->begin_x = begin_x;
175 view->window = newwin(nlines, ncols, begin_y, begin_x);
176 if (view->window == NULL) {
177 view_close(view);
178 return NULL;
180 view->panel = new_panel(view->window);
181 if (view->panel == NULL) {
182 view_close(view);
183 return NULL;
186 keypad(view->window, TRUE);
187 return view;
190 void
191 view_show(struct tog_view *view)
193 show_panel(view->panel);
194 update_panels();
197 const struct got_error *
198 view_resize(struct tog_view *view)
200 int nlines, ncols;
202 while (view) {
203 if (view->lines > LINES)
204 nlines = view->nlines - (view->lines - LINES);
205 else
206 nlines = view->nlines + (LINES - view->lines);
208 if (view->cols > COLS)
209 ncols = view->ncols - (view->cols - COLS);
210 else
211 ncols = view->ncols + (COLS - view->cols);
213 if (wresize(view->window, nlines, ncols) == ERR)
214 return got_error_from_errno();
215 replace_panel(view->panel, view->window);
217 view->nlines = nlines;
218 view->ncols = ncols;
219 view->lines = LINES;
220 view->cols = COLS;
222 view = view->parent;
225 return NULL;
228 __dead static void
229 usage_log(void)
231 endwin();
232 fprintf(stderr,
233 "usage: %s log [-c commit] [-r repository-path] [path]\n",
234 getprogname());
235 exit(1);
238 /* Create newly allocated wide-character string equivalent to a byte string. */
239 static const struct got_error *
240 mbs2ws(wchar_t **ws, size_t *wlen, const char *s)
242 char *vis = NULL;
243 const struct got_error *err = NULL;
245 *ws = NULL;
246 *wlen = mbstowcs(NULL, s, 0);
247 if (*wlen == (size_t)-1) {
248 int vislen;
249 if (errno != EILSEQ)
250 return got_error_from_errno();
252 /* byte string invalid in current encoding; try to "fix" it */
253 err = got_mbsavis(&vis, &vislen, s);
254 if (err)
255 return err;
256 *wlen = mbstowcs(NULL, vis, 0);
257 if (*wlen == (size_t)-1) {
258 err = got_error_from_errno(); /* give up */
259 goto done;
263 *ws = calloc(*wlen + 1, sizeof(*ws));
264 if (*ws == NULL) {
265 err = got_error_from_errno();
266 goto done;
269 if (mbstowcs(*ws, vis ? vis : s, *wlen) != *wlen)
270 err = got_error_from_errno();
271 done:
272 free(vis);
273 if (err) {
274 free(*ws);
275 *ws = NULL;
276 *wlen = 0;
278 return err;
281 /* Format a line for display, ensuring that it won't overflow a width limit. */
282 static const struct got_error *
283 format_line(wchar_t **wlinep, int *widthp, const char *line, int wlimit)
285 const struct got_error *err = NULL;
286 int cols = 0;
287 wchar_t *wline = NULL;
288 size_t wlen;
289 int i;
291 *wlinep = NULL;
292 *widthp = 0;
294 err = mbs2ws(&wline, &wlen, line);
295 if (err)
296 return err;
298 i = 0;
299 while (i < wlen && cols < wlimit) {
300 int width = wcwidth(wline[i]);
301 switch (width) {
302 case 0:
303 i++;
304 break;
305 case 1:
306 case 2:
307 if (cols + width <= wlimit) {
308 cols += width;
309 i++;
311 break;
312 case -1:
313 if (wline[i] == L'\t')
314 cols += TABSIZE - ((cols + 1) % TABSIZE);
315 i++;
316 break;
317 default:
318 err = got_error_from_errno();
319 goto done;
322 wline[i] = L'\0';
323 if (widthp)
324 *widthp = cols;
325 done:
326 if (err)
327 free(wline);
328 else
329 *wlinep = wline;
330 return err;
333 static const struct got_error *
334 draw_commit(struct tog_view *view, struct got_commit_object *commit,
335 struct got_object_id *id)
337 const struct got_error *err = NULL;
338 char datebuf[10]; /* YY-MM-DD + SPACE + NUL */
339 char *logmsg0 = NULL, *logmsg = NULL;
340 char *author0 = NULL, *author = NULL;
341 wchar_t *wlogmsg = NULL, *wauthor = NULL;
342 int author_width, logmsg_width;
343 char *newline, *smallerthan;
344 char *line = NULL;
345 int col, limit;
346 static const size_t date_display_cols = 9;
347 static const size_t author_display_cols = 16;
348 const int avail = view->ncols;
350 if (strftime(datebuf, sizeof(datebuf), "%g/%m/%d ",
351 &commit->tm_committer) >= sizeof(datebuf))
352 return got_error(GOT_ERR_NO_SPACE);
354 if (avail < date_display_cols)
355 limit = MIN(sizeof(datebuf) - 1, avail);
356 else
357 limit = MIN(date_display_cols, sizeof(datebuf) - 1);
358 waddnstr(view->window, datebuf, limit);
359 col = limit + 1;
360 if (col > avail)
361 goto done;
363 author0 = strdup(commit->author);
364 if (author0 == NULL) {
365 err = got_error_from_errno();
366 goto done;
368 author = author0;
369 smallerthan = strchr(author, '<');
370 if (smallerthan)
371 *smallerthan = '\0';
372 else {
373 char *at = strchr(author, '@');
374 if (at)
375 *at = '\0';
377 limit = avail - col;
378 err = format_line(&wauthor, &author_width, author, limit);
379 if (err)
380 goto done;
381 waddwstr(view->window, wauthor);
382 col += author_width;
383 while (col <= avail && author_width < author_display_cols + 1) {
384 waddch(view->window, ' ');
385 col++;
386 author_width++;
388 if (col > avail)
389 goto done;
391 logmsg0 = strdup(commit->logmsg);
392 if (logmsg0 == NULL) {
393 err = got_error_from_errno();
394 goto done;
396 logmsg = logmsg0;
397 while (*logmsg == '\n')
398 logmsg++;
399 newline = strchr(logmsg, '\n');
400 if (newline)
401 *newline = '\0';
402 limit = avail - col;
403 err = format_line(&wlogmsg, &logmsg_width, logmsg, limit);
404 if (err)
405 goto done;
406 waddwstr(view->window, wlogmsg);
407 col += logmsg_width;
408 while (col <= avail) {
409 waddch(view->window, ' ');
410 col++;
412 done:
413 free(logmsg0);
414 free(wlogmsg);
415 free(author0);
416 free(wauthor);
417 free(line);
418 return err;
421 static struct commit_queue_entry *
422 alloc_commit_queue_entry(struct got_commit_object *commit,
423 struct got_object_id *id)
425 struct commit_queue_entry *entry;
427 entry = calloc(1, sizeof(*entry));
428 if (entry == NULL)
429 return NULL;
431 entry->id = id;
432 entry->commit = commit;
433 return entry;
436 static void
437 pop_commit(struct commit_queue *commits)
439 struct commit_queue_entry *entry;
441 entry = TAILQ_FIRST(&commits->head);
442 TAILQ_REMOVE(&commits->head, entry, entry);
443 got_object_commit_close(entry->commit);
444 commits->ncommits--;
445 /* Don't free entry->id! It is owned by the commit graph. */
446 free(entry);
449 static void
450 free_commits(struct commit_queue *commits)
452 while (!TAILQ_EMPTY(&commits->head))
453 pop_commit(commits);
456 static const struct got_error *
457 queue_commits(struct got_commit_graph *graph, struct commit_queue *commits,
458 struct got_object_id *start_id, int minqueue, int init,
459 struct got_repository *repo, const char *path)
461 const struct got_error *err = NULL;
462 struct got_object_id *id;
463 struct commit_queue_entry *entry;
464 int nfetched, nqueued = 0, found_obj = 0;
465 int is_root_path = strcmp(path, "/") == 0;
467 err = got_commit_graph_iter_start(graph, start_id);
468 if (err)
469 return err;
471 entry = TAILQ_LAST(&commits->head, commit_queue_head);
472 if (entry && got_object_id_cmp(entry->id, start_id) == 0) {
473 int nfetched;
475 /* Start ID's commit is already on the queue; skip over it. */
476 err = got_commit_graph_iter_next(&id, graph);
477 if (err && err->code != GOT_ERR_ITER_NEED_MORE)
478 return err;
480 err = got_commit_graph_fetch_commits(&nfetched, graph, 1, repo);
481 if (err)
482 return err;
485 while (1) {
486 struct got_commit_object *commit;
488 err = got_commit_graph_iter_next(&id, graph);
489 if (err) {
490 if (err->code != GOT_ERR_ITER_NEED_MORE)
491 break;
492 if (nqueued >= minqueue) {
493 err = NULL;
494 break;
496 err = got_commit_graph_fetch_commits(&nfetched,
497 graph, 1, repo);
498 if (err)
499 return err;
500 continue;
502 if (id == NULL)
503 break;
505 err = got_object_open_as_commit(&commit, repo, id);
506 if (err)
507 break;
509 if (!is_root_path) {
510 struct got_object *obj;
511 struct got_object_qid *pid;
512 int changed = 0;
514 err = got_object_open_by_path(&obj, repo, id, path);
515 if (err) {
516 got_object_commit_close(commit);
517 if (err->code == GOT_ERR_NO_OBJ &&
518 (found_obj || !init)) {
519 /* History stops here. */
520 err = got_error(GOT_ERR_ITER_COMPLETED);
522 break;
524 found_obj = 1;
526 pid = SIMPLEQ_FIRST(&commit->parent_ids);
527 if (pid != NULL) {
528 struct got_object *pobj;
529 err = got_object_open_by_path(&pobj, repo,
530 pid->id, path);
531 if (err) {
532 if (err->code != GOT_ERR_NO_OBJ) {
533 got_object_close(obj);
534 got_object_commit_close(commit);
535 break;
537 err = NULL;
538 changed = 1;
539 } else {
540 struct got_object_id *id, *pid;
541 id = got_object_get_id(obj);
542 if (id == NULL) {
543 err = got_error_from_errno();
544 got_object_close(obj);
545 got_object_close(pobj);
546 break;
548 pid = got_object_get_id(pobj);
549 if (pid == NULL) {
550 err = got_error_from_errno();
551 free(id);
552 got_object_close(obj);
553 got_object_close(pobj);
554 break;
556 changed =
557 (got_object_id_cmp(id, pid) != 0);
558 got_object_close(pobj);
559 free(id);
560 free(pid);
563 got_object_close(obj);
564 if (!changed) {
565 got_object_commit_close(commit);
566 continue;
570 entry = alloc_commit_queue_entry(commit, id);
571 if (entry == NULL) {
572 err = got_error_from_errno();
573 break;
575 TAILQ_INSERT_TAIL(&commits->head, entry, entry);
576 nqueued++;
577 commits->ncommits++;
580 return err;
583 static const struct got_error *
584 fetch_next_commit(struct commit_queue_entry **pentry,
585 struct commit_queue_entry *entry, struct commit_queue *commits,
586 struct got_commit_graph *graph, struct got_repository *repo,
587 const char *path)
589 const struct got_error *err = NULL;
591 *pentry = NULL;
593 err = queue_commits(graph, commits, entry->id, 1, 0, repo, path);
594 if (err)
595 return err;
597 /* Next entry to display should now be available. */
598 *pentry = TAILQ_NEXT(entry, entry);
599 if (*pentry == NULL)
600 return got_error(GOT_ERR_NO_OBJ);
602 return NULL;
605 static const struct got_error *
606 get_head_commit_id(struct got_object_id **head_id, struct got_repository *repo)
608 const struct got_error *err = NULL;
609 struct got_reference *head_ref;
611 *head_id = NULL;
613 err = got_ref_open(&head_ref, repo, GOT_REF_HEAD);
614 if (err)
615 return err;
617 err = got_ref_resolve(head_id, repo, head_ref);
618 got_ref_close(head_ref);
619 if (err) {
620 *head_id = NULL;
621 return err;
624 return NULL;
627 static const struct got_error *
628 draw_commits(struct tog_view *view, struct commit_queue_entry **last,
629 struct commit_queue_entry **selected, struct commit_queue_entry *first,
630 struct commit_queue *commits, int selected_idx, int limit,
631 struct got_commit_graph *graph, struct got_repository *repo,
632 const char *path)
634 const struct got_error *err = NULL;
635 struct commit_queue_entry *entry;
636 int ncommits, width;
637 char *id_str, *header;
638 wchar_t *wline;
640 entry = first;
641 ncommits = 0;
642 while (entry) {
643 if (ncommits == selected_idx) {
644 *selected = entry;
645 break;
647 entry = TAILQ_NEXT(entry, entry);
648 ncommits++;
651 err = got_object_id_str(&id_str, (*selected)->id);
652 if (err)
653 return err;
655 if (path && strcmp(path, "/") != 0) {
656 if (asprintf(&header, "commit: %s [%s]", id_str, path) == -1) {
657 err = got_error_from_errno();
658 free(id_str);
659 return err;
661 } else if (asprintf(&header, "commit: %s", id_str) == -1) {
662 err = got_error_from_errno();
663 free(id_str);
664 return err;
666 free(id_str);
667 err = format_line(&wline, &width, header, view->ncols);
668 if (err) {
669 free(header);
670 return err;
672 free(header);
674 werase(view->window);
676 waddwstr(view->window, wline);
677 if (width < view->ncols)
678 waddch(view->window, '\n');
679 free(wline);
680 if (limit <= 1)
681 return NULL;
683 entry = first;
684 *last = first;
685 ncommits = 0;
686 while (entry) {
687 if (ncommits >= limit - 1)
688 break;
689 if (ncommits == selected_idx)
690 wstandout(view->window);
691 err = draw_commit(view, entry->commit, entry->id);
692 if (ncommits == selected_idx)
693 wstandend(view->window);
694 if (err)
695 break;
696 ncommits++;
697 *last = entry;
698 if (entry == TAILQ_LAST(&commits->head, commit_queue_head)) {
699 err = queue_commits(graph, commits, entry->id, 1,
700 0, repo, path);
701 if (err) {
702 if (err->code != GOT_ERR_ITER_COMPLETED)
703 return err;
704 err = NULL;
707 entry = TAILQ_NEXT(entry, entry);
710 update_panels();
711 doupdate();
713 return err;
716 static void
717 scroll_up(struct commit_queue_entry **first_displayed_entry, int maxscroll,
718 struct commit_queue *commits)
720 struct commit_queue_entry *entry;
721 int nscrolled = 0;
723 entry = TAILQ_FIRST(&commits->head);
724 if (*first_displayed_entry == entry)
725 return;
727 entry = *first_displayed_entry;
728 while (entry && nscrolled < maxscroll) {
729 entry = TAILQ_PREV(entry, commit_queue_head, entry);
730 if (entry) {
731 *first_displayed_entry = entry;
732 nscrolled++;
737 static const struct got_error *
738 scroll_down(struct commit_queue_entry **first_displayed_entry, int maxscroll,
739 struct commit_queue_entry *last_displayed_entry,
740 struct commit_queue *commits, struct got_commit_graph *graph,
741 struct got_repository *repo, const char *path)
743 const struct got_error *err = NULL;
744 struct commit_queue_entry *pentry;
745 int nscrolled = 0;
747 do {
748 pentry = TAILQ_NEXT(last_displayed_entry, entry);
749 if (pentry == NULL) {
750 err = fetch_next_commit(&pentry, last_displayed_entry,
751 commits, graph, repo, path);
752 if (err || pentry == NULL)
753 break;
755 last_displayed_entry = pentry;
757 pentry = TAILQ_NEXT(*first_displayed_entry, entry);
758 if (pentry == NULL)
759 break;
760 *first_displayed_entry = pentry;
761 } while (++nscrolled < maxscroll);
763 return err;
766 static const struct got_error *
767 show_commit(struct tog_view *parent_view, struct commit_queue_entry *entry,
768 struct got_repository *repo)
770 const struct got_error *err;
771 struct got_object *obj1 = NULL, *obj2 = NULL;
772 struct got_object_qid *parent_id;
773 struct tog_view *view;
775 err = got_object_open(&obj2, repo, entry->id);
776 if (err)
777 return err;
779 parent_id = SIMPLEQ_FIRST(&entry->commit->parent_ids);
780 if (parent_id) {
781 err = got_object_open(&obj1, repo, parent_id->id);
782 if (err)
783 goto done;
786 view = view_open(0, 0, 0, 0, parent_view, TOG_VIEW_DIFF);
787 if (view == NULL) {
788 err = got_error_from_errno();
789 goto done;
792 err = open_diff_view(view, obj1, obj2, repo);
793 if (err)
794 goto done;
795 err = show_diff_view(view);
796 close_diff_view(view);
797 view_close(view);
798 view_show(parent_view);
799 done:
800 if (obj1)
801 got_object_close(obj1);
802 if (obj2)
803 got_object_close(obj2);
804 return err;
807 static const struct got_error *
808 browse_commit(struct tog_view *parent_view, struct commit_queue_entry *entry,
809 struct got_repository *repo)
811 const struct got_error *err = NULL;
812 struct got_tree_object *tree;
813 struct tog_view *view;
815 err = got_object_open_as_tree(&tree, repo, entry->commit->tree_id);
816 if (err)
817 return err;
819 view = view_open(0, 0, 0, 0, parent_view, TOG_VIEW_TREE);
820 if (view == NULL) {
821 err = got_error_from_errno();
822 goto done;
824 err = show_tree_view(view, tree, entry->id, repo);
825 view_close(view);
826 view_show(parent_view);
827 done:
828 got_object_tree_close(tree);
829 return err;
832 static const struct got_error *
833 open_log_view(struct tog_view *view, struct got_object_id *start_id,
834 struct got_repository *repo, const char *path)
836 const struct got_error *err = NULL;
837 struct got_object_id *head_id = NULL;
838 int nfetched;
839 struct tog_log_view_state *state = &view->state.log;
841 err = got_repo_map_path(&state->in_repo_path, repo, path);
842 if (err != NULL)
843 goto done;
845 err = get_head_commit_id(&head_id, repo);
846 if (err)
847 return err;
849 /* The graph contains all commits. */
850 err = got_commit_graph_open(&state->graph, head_id, 0, repo);
851 if (err)
852 goto done;
853 /* The commit queue contains a subset of commits filtered by path. */
854 TAILQ_INIT(&state->commits.head);
855 state->commits.ncommits = 0;
857 /* Populate commit graph with a sufficient number of commits. */
858 err = got_commit_graph_fetch_commits_up_to(&nfetched,
859 state->graph, start_id, repo);
860 if (err)
861 goto done;
863 /*
864 * Open the initial batch of commits, sorted in commit graph order.
865 * We keep all commits open throughout the lifetime of the log view
866 * in order to avoid having to re-fetch commits from disk while
867 * updating the display.
868 */
869 err = queue_commits(state->graph, &state->commits, start_id,
870 view->nlines, 1, repo, state->in_repo_path);
871 if (err) {
872 if (err->code != GOT_ERR_ITER_COMPLETED)
873 goto done;
874 err = NULL;
877 state->repo = repo;
878 done:
879 free(head_id);
880 return err;
883 static void close_log_view(struct tog_view *view)
885 struct tog_log_view_state *state = &view->state.log;
887 if (state->graph)
888 got_commit_graph_close(state->graph);
889 free_commits(&state->commits);
890 free(state->in_repo_path);
893 static const struct got_error *
894 show_log_view(struct tog_view *view)
896 const struct got_error *err = NULL;
897 int ch, done = 0;
898 struct tog_log_view_state *state = &view->state.log;
900 view_show(view);
902 state->first_displayed_entry =
903 TAILQ_FIRST(&state->commits.head);
904 state->selected_entry = state->first_displayed_entry;
905 while (!done) {
906 err = draw_commits(view, &state->last_displayed_entry,
907 &state->selected_entry, state->first_displayed_entry,
908 &state->commits, state->selected, view->nlines,
909 state->graph, state->repo, state->in_repo_path);
910 if (err)
911 goto done;
913 nodelay(stdscr, FALSE);
914 ch = wgetch(view->window);
915 nodelay(stdscr, TRUE);
916 switch (ch) {
917 case ERR:
918 break;
919 case 'q':
920 done = 1;
921 break;
922 case 'k':
923 case KEY_UP:
924 if (state->selected > 0)
925 state->selected--;
926 if (state->selected > 0)
927 break;
928 scroll_up(&state->first_displayed_entry, 1,
929 &state->commits);
930 break;
931 case KEY_PPAGE:
932 if (TAILQ_FIRST(&state->commits.head) ==
933 state->first_displayed_entry) {
934 state->selected = 0;
935 break;
937 scroll_up(&state->first_displayed_entry,
938 view->nlines, &state->commits);
939 break;
940 case 'j':
941 case KEY_DOWN:
942 if (state->selected < MIN(view->nlines - 2,
943 state->commits.ncommits - 1)) {
944 state->selected++;
945 break;
947 err = scroll_down(&state->first_displayed_entry,
948 1, state->last_displayed_entry,
949 &state->commits, state->graph, state->repo,
950 state->in_repo_path);
951 if (err) {
952 if (err->code != GOT_ERR_ITER_COMPLETED)
953 goto done;
954 err = NULL;
956 break;
957 case KEY_NPAGE: {
958 struct commit_queue_entry *first;
959 first = state->first_displayed_entry;
960 err = scroll_down(&state->first_displayed_entry,
961 view->nlines, state->last_displayed_entry,
962 &state->commits, state->graph, state->repo,
963 state->in_repo_path);
964 if (err == NULL)
965 break;
966 if (err->code != GOT_ERR_ITER_COMPLETED)
967 goto done;
968 if (first == state->first_displayed_entry &&
969 state->selected < MIN(view->nlines - 2,
970 state->commits.ncommits - 1)) {
971 /* can't scroll further down */
972 state->selected = MIN(view->nlines - 2,
973 state->commits.ncommits - 1);
975 err = NULL;
976 break;
978 case KEY_RESIZE:
979 err = view_resize(view);
980 if (err)
981 goto done;
982 if (state->selected > view->nlines - 2)
983 state->selected = view->nlines - 2;
984 if (state->selected >
985 state->commits.ncommits - 1)
986 state->selected =
987 state->commits.ncommits - 1;
988 break;
989 case KEY_ENTER:
990 case '\r':
991 err = show_commit(view, state->selected_entry,
992 state->repo);
993 if (err)
994 goto done;
995 view_show(view);
996 break;
997 case 't':
998 err = browse_commit(view, state->selected_entry,
999 state->repo);
1000 if (err)
1001 goto done;
1002 view_show(view);
1003 break;
1004 default:
1005 break;
1008 done:
1009 return err;
1012 static const struct got_error *
1013 cmd_log(int argc, char *argv[])
1015 const struct got_error *error;
1016 struct got_repository *repo = NULL;
1017 struct got_object_id *start_id = NULL;
1018 char *path = NULL, *repo_path = NULL, *cwd = NULL;
1019 char *start_commit = NULL;
1020 int ch;
1021 struct tog_view *view;
1023 #ifndef PROFILE
1024 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
1025 err(1, "pledge");
1026 #endif
1028 while ((ch = getopt(argc, argv, "c:r:")) != -1) {
1029 switch (ch) {
1030 case 'c':
1031 start_commit = optarg;
1032 break;
1033 case 'r':
1034 repo_path = realpath(optarg, NULL);
1035 if (repo_path == NULL)
1036 err(1, "-r option");
1037 break;
1038 default:
1039 usage();
1040 /* NOTREACHED */
1044 argc -= optind;
1045 argv += optind;
1047 if (argc == 0)
1048 path = strdup("");
1049 else if (argc == 1)
1050 path = strdup(argv[0]);
1051 else
1052 usage_log();
1053 if (path == NULL)
1054 return got_error_from_errno();
1056 cwd = getcwd(NULL, 0);
1057 if (cwd == NULL) {
1058 error = got_error_from_errno();
1059 goto done;
1061 if (repo_path == NULL) {
1062 repo_path = strdup(cwd);
1063 if (repo_path == NULL) {
1064 error = got_error_from_errno();
1065 goto done;
1069 error = got_repo_open(&repo, repo_path);
1070 if (error != NULL)
1071 goto done;
1073 if (start_commit == NULL) {
1074 error = get_head_commit_id(&start_id, repo);
1075 if (error != NULL)
1076 goto done;
1077 } else {
1078 struct got_object *obj;
1079 error = got_object_open_by_id_str(&obj, repo, start_commit);
1080 if (error == NULL) {
1081 start_id = got_object_get_id(obj);
1082 if (start_id == NULL)
1083 error = got_error_from_errno();
1084 goto done;
1087 if (error != NULL)
1088 goto done;
1090 view = view_open(0, 0, 0, 0, NULL, TOG_VIEW_LOG);
1091 if (view == NULL) {
1092 error = got_error_from_errno();
1093 goto done;
1095 error = open_log_view(view, start_id, repo, path);
1096 if (error)
1097 goto done;
1098 error = show_log_view(view);
1099 close_log_view(view);
1100 view_close(view);
1101 done:
1102 free(repo_path);
1103 free(cwd);
1104 free(path);
1105 free(start_id);
1106 if (repo)
1107 got_repo_close(repo);
1108 return error;
1111 __dead static void
1112 usage_diff(void)
1114 endwin();
1115 fprintf(stderr, "usage: %s diff [repository-path] object1 object2\n",
1116 getprogname());
1117 exit(1);
1120 static char *
1121 parse_next_line(FILE *f, size_t *len)
1123 char *line;
1124 size_t linelen;
1125 size_t lineno;
1126 const char delim[3] = { '\0', '\0', '\0'};
1128 line = fparseln(f, &linelen, &lineno, delim, 0);
1129 if (len)
1130 *len = linelen;
1131 return line;
1134 static const struct got_error *
1135 draw_file(struct tog_view *view, FILE *f, int *first_displayed_line,
1136 int *last_displayed_line, int *eof, int max_lines)
1138 const struct got_error *err;
1139 int nlines = 0, nprinted = 0;
1140 char *line;
1141 size_t len;
1142 wchar_t *wline;
1143 int width;
1145 rewind(f);
1146 werase(view->window);
1148 *eof = 0;
1149 while (nprinted < max_lines) {
1150 line = parse_next_line(f, &len);
1151 if (line == NULL) {
1152 *eof = 1;
1153 break;
1155 if (++nlines < *first_displayed_line) {
1156 free(line);
1157 continue;
1160 err = format_line(&wline, &width, line, view->ncols);
1161 if (err) {
1162 free(line);
1163 free(wline);
1164 return err;
1166 waddwstr(view->window, wline);
1167 if (width < view->ncols)
1168 waddch(view->window, '\n');
1169 if (++nprinted == 1)
1170 *first_displayed_line = nlines;
1171 free(line);
1172 free(wline);
1173 wline = NULL;
1175 *last_displayed_line = nlines;
1177 update_panels();
1178 doupdate();
1180 return NULL;
1183 static const struct got_error *
1184 open_diff_view(struct tog_view *view, struct got_object *obj1,
1185 struct got_object *obj2, struct got_repository *repo)
1187 const struct got_error *err;
1188 FILE *f;
1190 if (obj1 != NULL && obj2 != NULL &&
1191 got_object_get_type(obj1) != got_object_get_type(obj2))
1192 return got_error(GOT_ERR_OBJ_TYPE);
1194 f = got_opentemp();
1195 if (f == NULL)
1196 return got_error_from_errno();
1198 switch (got_object_get_type(obj1 ? obj1 : obj2)) {
1199 case GOT_OBJ_TYPE_BLOB:
1200 err = got_diff_objects_as_blobs(obj1, obj2, repo, f);
1201 break;
1202 case GOT_OBJ_TYPE_TREE:
1203 err = got_diff_objects_as_trees(obj1, obj2, repo, f);
1204 break;
1205 case GOT_OBJ_TYPE_COMMIT:
1206 err = got_diff_objects_as_commits(obj1, obj2, repo, f);
1207 break;
1208 default:
1209 return got_error(GOT_ERR_OBJ_TYPE);
1212 fflush(f);
1214 view->state.diff.f = f;
1215 view->state.diff.first_displayed_line = 1;
1216 view->state.diff.last_displayed_line = view->nlines;
1218 return NULL;
1221 static void
1222 close_diff_view(struct tog_view *view)
1224 fclose(view->state.diff.f);
1227 static const struct got_error *
1228 show_diff_view(struct tog_view *view)
1230 const struct got_error *err = NULL;
1231 int ch, done = 0;
1232 int eof, i;
1233 struct tog_diff_view_state *state = &view->state.diff;
1235 view_show(view);
1237 while (!done) {
1238 err = draw_file(view, state->f, &state->first_displayed_line,
1239 &state->last_displayed_line, &eof, view->nlines);
1240 if (err)
1241 break;
1242 nodelay(stdscr, FALSE);
1243 ch = wgetch(view->window);
1244 nodelay(stdscr, TRUE);
1245 switch (ch) {
1246 case 'q':
1247 done = 1;
1248 break;
1249 case 'k':
1250 case KEY_UP:
1251 if (state->first_displayed_line > 1)
1252 state->first_displayed_line--;
1253 break;
1254 case KEY_PPAGE:
1255 case KEY_BACKSPACE:
1256 i = 0;
1257 while (i++ < view->nlines - 1 &&
1258 state->first_displayed_line > 1)
1259 state->first_displayed_line--;
1260 break;
1261 case 'j':
1262 case KEY_DOWN:
1263 if (!eof)
1264 state->first_displayed_line++;
1265 break;
1266 case KEY_NPAGE:
1267 case ' ':
1268 i = 0;
1269 while (!eof && i++ < view->nlines - 1) {
1270 char *line = parse_next_line(
1271 state->f, NULL);
1272 state->first_displayed_line++;
1273 if (line == NULL)
1274 break;
1276 break;
1277 case KEY_RESIZE:
1278 err = view_resize(view);
1279 if (err)
1280 goto done;
1281 break;
1282 default:
1283 break;
1286 done:
1287 return err;
1290 static const struct got_error *
1291 cmd_diff(int argc, char *argv[])
1293 const struct got_error *error = NULL;
1294 struct got_repository *repo = NULL;
1295 struct got_object *obj1 = NULL, *obj2 = NULL;
1296 char *repo_path = NULL;
1297 char *obj_id_str1 = NULL, *obj_id_str2 = NULL;
1298 int ch;
1299 struct tog_view *view;
1301 #ifndef PROFILE
1302 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
1303 err(1, "pledge");
1304 #endif
1306 while ((ch = getopt(argc, argv, "")) != -1) {
1307 switch (ch) {
1308 default:
1309 usage();
1310 /* NOTREACHED */
1314 argc -= optind;
1315 argv += optind;
1317 if (argc == 0) {
1318 usage_diff(); /* TODO show local worktree changes */
1319 } else if (argc == 2) {
1320 repo_path = getcwd(NULL, 0);
1321 if (repo_path == NULL)
1322 return got_error_from_errno();
1323 obj_id_str1 = argv[0];
1324 obj_id_str2 = argv[1];
1325 } else if (argc == 3) {
1326 repo_path = realpath(argv[0], NULL);
1327 if (repo_path == NULL)
1328 return got_error_from_errno();
1329 obj_id_str1 = argv[1];
1330 obj_id_str2 = argv[2];
1331 } else
1332 usage_diff();
1334 error = got_repo_open(&repo, repo_path);
1335 free(repo_path);
1336 if (error)
1337 goto done;
1339 error = got_object_open_by_id_str(&obj1, repo, obj_id_str1);
1340 if (error)
1341 goto done;
1343 error = got_object_open_by_id_str(&obj2, repo, obj_id_str2);
1344 if (error)
1345 goto done;
1347 view = view_open(0, 0, 0, 0, NULL, TOG_VIEW_DIFF);
1348 if (view == NULL) {
1349 error = got_error_from_errno();
1350 goto done;
1352 error = open_diff_view(view, obj1, obj2, repo);
1353 if (error)
1354 goto done;
1355 error = show_diff_view(view);
1356 close_diff_view(view);
1357 view_close(view);
1358 done:
1359 got_repo_close(repo);
1360 if (obj1)
1361 got_object_close(obj1);
1362 if (obj2)
1363 got_object_close(obj2);
1364 return error;
1367 __dead static void
1368 usage_blame(void)
1370 endwin();
1371 fprintf(stderr, "usage: %s blame [-c commit] [-r repository-path] path\n",
1372 getprogname());
1373 exit(1);
1376 struct tog_blame_line {
1377 int annotated;
1378 struct got_object_id *id;
1381 static const struct got_error *
1382 draw_blame(struct tog_view *view, struct got_object_id *id, FILE *f,
1383 const char *path, struct tog_blame_line *lines, int nlines,
1384 int blame_complete, int selected_line, int *first_displayed_line,
1385 int *last_displayed_line, int *eof, int max_lines)
1387 const struct got_error *err;
1388 int lineno = 0, nprinted = 0;
1389 char *line;
1390 size_t len;
1391 wchar_t *wline;
1392 int width, wlimit;
1393 struct tog_blame_line *blame_line;
1394 struct got_object_id *prev_id = NULL;
1395 char *id_str;
1397 err = got_object_id_str(&id_str, id);
1398 if (err)
1399 return err;
1401 rewind(f);
1402 werase(view->window);
1404 if (asprintf(&line, "commit: %s", id_str) == -1) {
1405 err = got_error_from_errno();
1406 free(id_str);
1407 return err;
1410 err = format_line(&wline, &width, line, view->ncols);
1411 free(line);
1412 line = NULL;
1413 waddwstr(view->window, wline);
1414 free(wline);
1415 wline = NULL;
1416 if (width < view->ncols)
1417 waddch(view->window, '\n');
1419 if (asprintf(&line, "[%d/%d] %s%s",
1420 *first_displayed_line - 1 + selected_line, nlines,
1421 blame_complete ? "" : "annotating ", path) == -1) {
1422 free(id_str);
1423 return got_error_from_errno();
1425 free(id_str);
1426 err = format_line(&wline, &width, line, view->ncols);
1427 free(line);
1428 line = NULL;
1429 if (err)
1430 return err;
1431 waddwstr(view->window, wline);
1432 free(wline);
1433 wline = NULL;
1434 if (width < view->ncols)
1435 waddch(view->window, '\n');
1437 *eof = 0;
1438 while (nprinted < max_lines - 2) {
1439 line = parse_next_line(f, &len);
1440 if (line == NULL) {
1441 *eof = 1;
1442 break;
1444 if (++lineno < *first_displayed_line) {
1445 free(line);
1446 continue;
1449 wlimit = view->ncols < 9 ? 0 : view->ncols - 9;
1450 err = format_line(&wline, &width, line, wlimit);
1451 if (err) {
1452 free(line);
1453 return err;
1456 if (nprinted == selected_line - 1)
1457 wstandout(view->window);
1459 blame_line = &lines[lineno - 1];
1460 if (blame_line->annotated && prev_id &&
1461 got_object_id_cmp(prev_id, blame_line->id) == 0)
1462 waddstr(view->window, " ");
1463 else if (blame_line->annotated) {
1464 char *id_str;
1465 err = got_object_id_str(&id_str, blame_line->id);
1466 if (err) {
1467 free(line);
1468 free(wline);
1469 return err;
1471 wprintw(view->window, "%.8s ", id_str);
1472 free(id_str);
1473 prev_id = blame_line->id;
1474 } else {
1475 waddstr(view->window, "........ ");
1476 prev_id = NULL;
1479 waddwstr(view->window, wline);
1480 while (width < wlimit) {
1481 waddch(view->window, ' ');
1482 width++;
1484 if (nprinted == selected_line - 1)
1485 wstandend(view->window);
1486 if (++nprinted == 1)
1487 *first_displayed_line = lineno;
1488 free(line);
1489 free(wline);
1490 wline = NULL;
1492 *last_displayed_line = lineno;
1494 update_panels();
1495 doupdate();
1497 return NULL;
1500 struct tog_blame_cb_args {
1501 pthread_mutex_t *mutex;
1502 struct tog_blame_line *lines; /* one per line */
1503 int nlines;
1505 struct tog_view *view;
1506 struct got_object_id *commit_id;
1507 FILE *f;
1508 const char *path;
1509 int *first_displayed_line;
1510 int *last_displayed_line;
1511 int *selected_line;
1512 int *quit;
1515 static const struct got_error *
1516 blame_cb(void *arg, int nlines, int lineno, struct got_object_id *id)
1518 const struct got_error *err = NULL;
1519 struct tog_blame_cb_args *a = arg;
1520 struct tog_blame_line *line;
1521 int eof;
1523 if (nlines != a->nlines ||
1524 (lineno != -1 && lineno < 1) || lineno > a->nlines)
1525 return got_error(GOT_ERR_RANGE);
1527 if (pthread_mutex_lock(a->mutex) != 0)
1528 return got_error_from_errno();
1530 if (*a->quit) { /* user has quit the blame view */
1531 err = got_error(GOT_ERR_ITER_COMPLETED);
1532 goto done;
1535 if (lineno == -1)
1536 goto done; /* no change in this commit */
1538 line = &a->lines[lineno - 1];
1539 if (line->annotated)
1540 goto done;
1542 line->id = got_object_id_dup(id);
1543 if (line->id == NULL) {
1544 err = got_error_from_errno();
1545 goto done;
1547 line->annotated = 1;
1549 err = draw_blame(a->view, a->commit_id, a->f, a->path,
1550 a->lines, a->nlines, 0, *a->selected_line, a->first_displayed_line,
1551 a->last_displayed_line, &eof, a->view->nlines);
1552 done:
1553 if (pthread_mutex_unlock(a->mutex) != 0)
1554 return got_error_from_errno();
1555 return err;
1558 struct tog_blame_thread_args {
1559 const char *path;
1560 struct got_repository *repo;
1561 struct tog_blame_cb_args *cb_args;
1562 int *complete;
1565 static void *
1566 blame_thread(void *arg)
1568 const struct got_error *err;
1569 struct tog_blame_thread_args *ta = arg;
1570 struct tog_blame_cb_args *a = ta->cb_args;
1571 int eof;
1573 err = got_blame_incremental(ta->path, a->commit_id, ta->repo,
1574 blame_cb, ta->cb_args);
1576 if (pthread_mutex_lock(a->mutex) != 0)
1577 return (void *)got_error_from_errno();
1579 got_repo_close(ta->repo);
1580 ta->repo = NULL;
1581 *ta->complete = 1;
1582 if (!err)
1583 err = draw_blame(a->view, a->commit_id, a->f, a->path,
1584 a->lines, a->nlines, 1, *a->selected_line,
1585 a->first_displayed_line, a->last_displayed_line, &eof,
1586 a->view->nlines);
1588 if (pthread_mutex_unlock(a->mutex) != 0 && err == NULL)
1589 err = got_error_from_errno();
1591 return (void *)err;
1594 static struct got_object_id *
1595 get_selected_commit_id(struct tog_blame_line *lines,
1596 int first_displayed_line, int selected_line)
1598 struct tog_blame_line *line;
1600 line = &lines[first_displayed_line - 1 + selected_line - 1];
1601 if (!line->annotated)
1602 return NULL;
1604 return line->id;
1607 static const struct got_error *
1608 open_selected_commit(struct got_object **pobj, struct got_object **obj,
1609 struct tog_blame_line *lines, int first_displayed_line,
1610 int selected_line, struct got_repository *repo)
1612 const struct got_error *err = NULL;
1613 struct got_commit_object *commit = NULL;
1614 struct got_object_id *selected_id;
1615 struct got_object_qid *pid;
1617 *pobj = NULL;
1618 *obj = NULL;
1620 selected_id = get_selected_commit_id(lines,
1621 first_displayed_line, selected_line);
1622 if (selected_id == NULL)
1623 return NULL;
1625 err = got_object_open(obj, repo, selected_id);
1626 if (err)
1627 goto done;
1629 err = got_object_commit_open(&commit, repo, *obj);
1630 if (err)
1631 goto done;
1633 pid = SIMPLEQ_FIRST(&commit->parent_ids);
1634 if (pid) {
1635 err = got_object_open(pobj, repo, pid->id);
1636 if (err)
1637 goto done;
1639 done:
1640 if (commit)
1641 got_object_commit_close(commit);
1642 return err;
1645 struct tog_blame {
1646 FILE *f;
1647 size_t filesize;
1648 struct tog_blame_line *lines;
1649 size_t nlines;
1650 pthread_t thread;
1651 struct tog_blame_thread_args thread_args;
1652 struct tog_blame_cb_args cb_args;
1653 const char *path;
1656 static const struct got_error *
1657 stop_blame(struct tog_blame *blame)
1659 const struct got_error *err = NULL;
1660 int i;
1662 if (blame->thread) {
1663 if (pthread_join(blame->thread, (void **)&err) != 0)
1664 err = got_error_from_errno();
1665 if (err && err->code == GOT_ERR_ITER_COMPLETED)
1666 err = NULL;
1667 blame->thread = NULL;
1669 if (blame->thread_args.repo) {
1670 got_repo_close(blame->thread_args.repo);
1671 blame->thread_args.repo = NULL;
1673 if (blame->f) {
1674 fclose(blame->f);
1675 blame->f = NULL;
1677 for (i = 0; i < blame->nlines; i++)
1678 free(blame->lines[i].id);
1679 free(blame->lines);
1680 blame->lines = NULL;
1681 free(blame->cb_args.commit_id);
1682 blame->cb_args.commit_id = NULL;
1684 return err;
1687 static const struct got_error *
1688 run_blame(struct tog_blame *blame, pthread_mutex_t *mutex,
1689 struct tog_view *view, int *blame_complete,
1690 int *first_displayed_line, int *last_displayed_line,
1691 int *selected_line, int *done, const char *path,
1692 struct got_object_id *commit_id,
1693 struct got_repository *repo)
1695 const struct got_error *err = NULL;
1696 struct got_blob_object *blob = NULL;
1697 struct got_repository *thread_repo = NULL;
1698 struct got_object *obj;
1700 err = got_object_open_by_path(&obj, repo, commit_id, path);
1701 if (err)
1702 goto done;
1703 if (got_object_get_type(obj) != GOT_OBJ_TYPE_BLOB) {
1704 err = got_error(GOT_ERR_OBJ_TYPE);
1705 goto done;
1708 err = got_object_blob_open(&blob, repo, obj, 8192);
1709 if (err)
1710 goto done;
1711 blame->f = got_opentemp();
1712 if (blame->f == NULL) {
1713 err = got_error_from_errno();
1714 goto done;
1716 err = got_object_blob_dump_to_file(&blame->filesize, &blame->nlines,
1717 blame->f, blob);
1718 if (err)
1719 goto done;
1721 blame->lines = calloc(blame->nlines, sizeof(*blame->lines));
1722 if (blame->lines == NULL) {
1723 err = got_error_from_errno();
1724 goto done;
1727 err = got_repo_open(&thread_repo, got_repo_get_path(repo));
1728 if (err)
1729 goto done;
1731 blame->cb_args.view = view;
1732 blame->cb_args.lines = blame->lines;
1733 blame->cb_args.nlines = blame->nlines;
1734 blame->cb_args.mutex = mutex;
1735 blame->cb_args.commit_id = got_object_id_dup(commit_id);
1736 if (blame->cb_args.commit_id == NULL) {
1737 err = got_error_from_errno();
1738 goto done;
1740 blame->cb_args.f = blame->f;
1741 blame->cb_args.path = path;
1742 blame->cb_args.first_displayed_line = first_displayed_line;
1743 blame->cb_args.selected_line = selected_line;
1744 blame->cb_args.last_displayed_line = last_displayed_line;
1745 blame->cb_args.quit = done;
1747 blame->thread_args.path = path;
1748 blame->thread_args.repo = thread_repo;
1749 blame->thread_args.cb_args = &blame->cb_args;
1750 blame->thread_args.complete = blame_complete;
1751 *blame_complete = 0;
1753 if (pthread_create(&blame->thread, NULL, blame_thread,
1754 &blame->thread_args) != 0) {
1755 err = got_error_from_errno();
1756 goto done;
1759 done:
1760 if (blob)
1761 got_object_blob_close(blob);
1762 if (obj)
1763 got_object_close(obj);
1764 if (err)
1765 stop_blame(blame);
1766 return err;
1769 static const struct got_error *
1770 show_blame_view(struct tog_view *view, const char *path,
1771 struct got_object_id *commit_id, struct got_repository *repo)
1773 const struct got_error *err = NULL, *thread_err = NULL;
1774 int ch, done = 0, first_displayed_line = 1, last_displayed_line;
1775 int selected_line = first_displayed_line;
1776 int eof, blame_complete = 0;
1777 struct got_object *obj = NULL, *pobj = NULL;
1778 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
1779 struct tog_blame blame;
1780 struct got_object_id_queue blamed_commits;
1781 struct got_object_qid *blamed_commit = NULL;
1782 struct tog_view *diff_view;
1784 SIMPLEQ_INIT(&blamed_commits);
1786 if (pthread_mutex_init(&mutex, NULL) != 0) {
1787 err = got_error_from_errno();
1788 goto done;
1791 err = got_object_qid_alloc(&blamed_commit, commit_id);
1792 if (err)
1793 goto done;
1794 SIMPLEQ_INSERT_HEAD(&blamed_commits, blamed_commit, entry);
1796 view_show(view);
1797 last_displayed_line = view->nlines;
1799 memset(&blame, 0, sizeof(blame));
1800 err = run_blame(&blame, &mutex, view, &blame_complete,
1801 &first_displayed_line, &last_displayed_line,
1802 &selected_line, &done, path, blamed_commit->id, repo);
1803 if (err)
1804 return err;
1806 while (!done) {
1807 if (pthread_mutex_lock(&mutex) != 0) {
1808 err = got_error_from_errno();
1809 goto done;
1811 err = draw_blame(view, blamed_commit->id, blame.f, path,
1812 blame.lines, blame.nlines, blame_complete, selected_line,
1813 &first_displayed_line, &last_displayed_line, &eof,
1814 view->nlines);
1815 if (pthread_mutex_unlock(&mutex) != 0) {
1816 err = got_error_from_errno();
1817 goto done;
1819 if (err)
1820 break;
1821 nodelay(stdscr, FALSE);
1822 ch = wgetch(view->window);
1823 nodelay(stdscr, TRUE);
1824 if (pthread_mutex_lock(&mutex) != 0) {
1825 err = got_error_from_errno();
1826 goto done;
1828 switch (ch) {
1829 case 'q':
1830 done = 1;
1831 break;
1832 case 'k':
1833 case KEY_UP:
1834 if (selected_line > 1)
1835 selected_line--;
1836 else if (selected_line == 1 &&
1837 first_displayed_line > 1)
1838 first_displayed_line--;
1839 break;
1840 case KEY_PPAGE:
1841 case KEY_BACKSPACE:
1842 if (first_displayed_line == 1) {
1843 selected_line = 1;
1844 break;
1846 if (first_displayed_line > view->nlines - 2)
1847 first_displayed_line -=
1848 (view->nlines - 2);
1849 else
1850 first_displayed_line = 1;
1851 break;
1852 case 'j':
1853 case KEY_DOWN:
1854 if (selected_line < view->nlines - 2 &&
1855 first_displayed_line + selected_line <=
1856 blame.nlines)
1857 selected_line++;
1858 else if (last_displayed_line < blame.nlines)
1859 first_displayed_line++;
1860 break;
1861 case 'b':
1862 case 'p': {
1863 struct got_object_id *id;
1864 id = get_selected_commit_id(blame.lines,
1865 first_displayed_line, selected_line);
1866 if (id == NULL || got_object_id_cmp(id,
1867 blamed_commit->id) == 0)
1868 break;
1869 err = open_selected_commit(&pobj, &obj,
1870 blame.lines, first_displayed_line,
1871 selected_line, repo);
1872 if (err)
1873 break;
1874 if (pobj == NULL && obj == NULL)
1875 break;
1876 if (ch == 'p' && pobj == NULL)
1877 break;
1878 done = 1;
1879 if (pthread_mutex_unlock(&mutex) != 0) {
1880 err = got_error_from_errno();
1881 goto done;
1883 thread_err = stop_blame(&blame);
1884 done = 0;
1885 if (pthread_mutex_lock(&mutex) != 0) {
1886 err = got_error_from_errno();
1887 goto done;
1889 if (thread_err)
1890 break;
1891 id = got_object_get_id(ch == 'b' ? obj : pobj);
1892 got_object_close(obj);
1893 obj = NULL;
1894 if (pobj) {
1895 got_object_close(pobj);
1896 pobj = NULL;
1898 if (id == NULL) {
1899 err = got_error_from_errno();
1900 break;
1902 err = got_object_qid_alloc(&blamed_commit, id);
1903 free(id);
1904 if (err)
1905 goto done;
1906 SIMPLEQ_INSERT_HEAD(&blamed_commits,
1907 blamed_commit, entry);
1908 err = run_blame(&blame, &mutex, view,
1909 &blame_complete, &first_displayed_line,
1910 &last_displayed_line, &selected_line,
1911 &done, path, blamed_commit->id, repo);
1912 if (err)
1913 break;
1914 break;
1916 case 'B': {
1917 struct got_object_qid *first;
1918 first = SIMPLEQ_FIRST(&blamed_commits);
1919 if (!got_object_id_cmp(first->id, commit_id))
1920 break;
1921 done = 1;
1922 if (pthread_mutex_unlock(&mutex) != 0) {
1923 err = got_error_from_errno();
1924 goto done;
1926 thread_err = stop_blame(&blame);
1927 done = 0;
1928 if (pthread_mutex_lock(&mutex) != 0) {
1929 err = got_error_from_errno();
1930 goto done;
1932 if (thread_err)
1933 break;
1934 SIMPLEQ_REMOVE_HEAD(&blamed_commits, entry);
1935 got_object_qid_free(blamed_commit);
1936 blamed_commit = SIMPLEQ_FIRST(&blamed_commits);
1937 err = run_blame(&blame, &mutex, view,
1938 &blame_complete, &first_displayed_line,
1939 &last_displayed_line, &selected_line,
1940 &done, path, blamed_commit->id, repo);
1941 if (err)
1942 break;
1943 break;
1945 case KEY_ENTER:
1946 case '\r':
1947 err = open_selected_commit(&pobj, &obj,
1948 blame.lines, first_displayed_line,
1949 selected_line, repo);
1950 if (err)
1951 break;
1952 if (pobj == NULL && obj == NULL)
1953 break;
1954 diff_view = view_open(0, 0, 0, 0, view,
1955 TOG_VIEW_DIFF);
1956 if (diff_view == NULL) {
1957 err = got_error_from_errno();
1958 break;
1960 err = open_diff_view(diff_view, pobj, obj, repo);
1961 if (err)
1962 break;
1963 err = show_diff_view(diff_view);
1964 close_diff_view(diff_view);
1965 view_close(diff_view);
1966 if (err)
1967 break;
1968 view_show(view);
1969 if (pobj) {
1970 got_object_close(pobj);
1971 pobj = NULL;
1973 got_object_close(obj);
1974 obj = NULL;
1975 if (err)
1976 break;
1977 break;
1978 case KEY_NPAGE:
1979 case ' ':
1980 if (last_displayed_line >= blame.nlines &&
1981 selected_line < view->nlines - 2) {
1982 selected_line = MIN(blame.nlines,
1983 view->nlines - 2);
1984 break;
1986 if (last_displayed_line + view->nlines - 2 <=
1987 blame.nlines)
1988 first_displayed_line +=
1989 view->nlines - 2;
1990 else
1991 first_displayed_line =
1992 blame.nlines - (view->nlines - 3);
1993 break;
1994 case KEY_RESIZE:
1995 err = view_resize(view);
1996 if (err)
1997 break;
1998 if (selected_line > view->nlines - 2) {
1999 selected_line = MIN(blame.nlines,
2000 view->nlines - 2);
2002 break;
2003 default:
2004 break;
2006 if (pthread_mutex_unlock(&mutex) != 0)
2007 err = got_error_from_errno();
2008 if (err || thread_err)
2009 break;
2011 done:
2012 if (pobj)
2013 got_object_close(pobj);
2014 if (blame.thread)
2015 thread_err = stop_blame(&blame);
2016 while (!SIMPLEQ_EMPTY(&blamed_commits)) {
2017 blamed_commit = SIMPLEQ_FIRST(&blamed_commits);
2018 SIMPLEQ_REMOVE_HEAD(&blamed_commits, entry);
2019 got_object_qid_free(blamed_commit);
2021 return thread_err ? thread_err : err;
2024 static const struct got_error *
2025 cmd_blame(int argc, char *argv[])
2027 const struct got_error *error;
2028 struct got_repository *repo = NULL;
2029 char *path, *cwd = NULL, *repo_path = NULL, *in_repo_path = NULL;
2030 struct got_object_id *commit_id = NULL;
2031 char *commit_id_str = NULL;
2032 int ch;
2033 struct tog_view *view;
2035 #ifndef PROFILE
2036 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
2037 err(1, "pledge");
2038 #endif
2040 while ((ch = getopt(argc, argv, "c:r:")) != -1) {
2041 switch (ch) {
2042 case 'c':
2043 commit_id_str = optarg;
2044 break;
2045 case 'r':
2046 repo_path = realpath(optarg, NULL);
2047 if (repo_path == NULL)
2048 err(1, "-r option");
2049 break;
2050 default:
2051 usage();
2052 /* NOTREACHED */
2056 argc -= optind;
2057 argv += optind;
2059 if (argc == 1)
2060 path = argv[0];
2061 else
2062 usage_blame();
2064 cwd = getcwd(NULL, 0);
2065 if (cwd == NULL) {
2066 error = got_error_from_errno();
2067 goto done;
2069 if (repo_path == NULL) {
2070 repo_path = strdup(cwd);
2071 if (repo_path == NULL) {
2072 error = got_error_from_errno();
2073 goto done;
2078 error = got_repo_open(&repo, repo_path);
2079 if (error != NULL)
2080 return error;
2082 error = got_repo_map_path(&in_repo_path, repo, path);
2083 if (error != NULL)
2084 goto done;
2086 if (commit_id_str == NULL) {
2087 struct got_reference *head_ref;
2088 error = got_ref_open(&head_ref, repo, GOT_REF_HEAD);
2089 if (error != NULL)
2090 goto done;
2091 error = got_ref_resolve(&commit_id, repo, head_ref);
2092 got_ref_close(head_ref);
2093 } else {
2094 struct got_object *obj;
2095 error = got_object_open_by_id_str(&obj, repo, commit_id_str);
2096 if (error != NULL)
2097 goto done;
2098 commit_id = got_object_get_id(obj);
2099 if (commit_id == NULL)
2100 error = got_error_from_errno();
2101 got_object_close(obj);
2103 if (error != NULL)
2104 goto done;
2106 view = view_open(0, 0, 0, 0, NULL, TOG_VIEW_BLAME);
2107 if (view == NULL) {
2108 error = got_error_from_errno();
2109 goto done;
2111 error = show_blame_view(view, in_repo_path, commit_id, repo);
2112 view_close(view);
2113 done:
2114 free(in_repo_path);
2115 free(repo_path);
2116 free(cwd);
2117 free(commit_id);
2118 if (repo)
2119 got_repo_close(repo);
2120 return error;
2123 static const struct got_error *
2124 draw_tree_entries(struct tog_view *view,
2125 struct got_tree_entry **first_displayed_entry,
2126 struct got_tree_entry **last_displayed_entry,
2127 struct got_tree_entry **selected_entry, int *ndisplayed,
2128 const char *label, int show_ids, const char *parent_path,
2129 const struct got_tree_entries *entries, int selected, int limit, int isroot)
2131 const struct got_error *err = NULL;
2132 struct got_tree_entry *te;
2133 wchar_t *wline;
2134 int width, n;
2136 *ndisplayed = 0;
2138 werase(view->window);
2140 if (limit == 0)
2141 return NULL;
2143 err = format_line(&wline, &width, label, view->ncols);
2144 if (err)
2145 return err;
2146 waddwstr(view->window, wline);
2147 free(wline);
2148 wline = NULL;
2149 if (width < view->ncols)
2150 waddch(view->window, '\n');
2151 if (--limit <= 0)
2152 return NULL;
2153 err = format_line(&wline, &width, parent_path, view->ncols);
2154 if (err)
2155 return err;
2156 waddwstr(view->window, wline);
2157 free(wline);
2158 wline = NULL;
2159 if (width < view->ncols)
2160 waddch(view->window, '\n');
2161 if (--limit <= 0)
2162 return NULL;
2163 waddch(view->window, '\n');
2164 if (--limit <= 0)
2165 return NULL;
2167 te = SIMPLEQ_FIRST(&entries->head);
2168 if (*first_displayed_entry == NULL) {
2169 if (selected == 0) {
2170 wstandout(view->window);
2171 *selected_entry = NULL;
2173 waddstr(view->window, " ..\n"); /* parent directory */
2174 if (selected == 0)
2175 wstandend(view->window);
2176 (*ndisplayed)++;
2177 if (--limit <= 0)
2178 return NULL;
2179 n = 1;
2180 } else {
2181 n = 0;
2182 while (te != *first_displayed_entry)
2183 te = SIMPLEQ_NEXT(te, entry);
2186 while (te) {
2187 char *line = NULL, *id_str = NULL;
2189 if (show_ids) {
2190 err = got_object_id_str(&id_str, te->id);
2191 if (err)
2192 return got_error_from_errno();
2194 if (asprintf(&line, "%s %s%s", id_str ? id_str : "",
2195 te->name, S_ISDIR(te->mode) ? "/" : "") == -1) {
2196 free(id_str);
2197 return got_error_from_errno();
2199 free(id_str);
2200 err = format_line(&wline, &width, line, view->ncols);
2201 if (err) {
2202 free(line);
2203 break;
2205 if (n == selected) {
2206 wstandout(view->window);
2207 *selected_entry = te;
2209 waddwstr(view->window, wline);
2210 if (width < view->ncols)
2211 waddch(view->window, '\n');
2212 if (n == selected)
2213 wstandend(view->window);
2214 free(line);
2215 free(wline);
2216 wline = NULL;
2217 n++;
2218 (*ndisplayed)++;
2219 *last_displayed_entry = te;
2220 if (--limit <= 0)
2221 break;
2222 te = SIMPLEQ_NEXT(te, entry);
2225 return err;
2228 static void
2229 tree_scroll_up(struct got_tree_entry **first_displayed_entry, int maxscroll,
2230 const struct got_tree_entries *entries, int isroot)
2232 struct got_tree_entry *te, *prev;
2233 int i;
2235 if (*first_displayed_entry == NULL)
2236 return;
2238 te = SIMPLEQ_FIRST(&entries->head);
2239 if (*first_displayed_entry == te) {
2240 if (!isroot)
2241 *first_displayed_entry = NULL;
2242 return;
2245 /* XXX this is stupid... switch to TAILQ? */
2246 for (i = 0; i < maxscroll; i++) {
2247 while (te != *first_displayed_entry) {
2248 prev = te;
2249 te = SIMPLEQ_NEXT(te, entry);
2251 *first_displayed_entry = prev;
2252 te = SIMPLEQ_FIRST(&entries->head);
2254 if (!isroot && te == SIMPLEQ_FIRST(&entries->head) && i < maxscroll)
2255 *first_displayed_entry = NULL;
2258 static void
2259 tree_scroll_down(struct got_tree_entry **first_displayed_entry, int maxscroll,
2260 struct got_tree_entry *last_displayed_entry,
2261 const struct got_tree_entries *entries)
2263 struct got_tree_entry *next;
2264 int n = 0;
2266 if (SIMPLEQ_NEXT(last_displayed_entry, entry) == NULL)
2267 return;
2269 if (*first_displayed_entry)
2270 next = SIMPLEQ_NEXT(*first_displayed_entry, entry);
2271 else
2272 next = SIMPLEQ_FIRST(&entries->head);
2273 while (next) {
2274 *first_displayed_entry = next;
2275 if (++n >= maxscroll)
2276 break;
2277 next = SIMPLEQ_NEXT(next, entry);
2281 struct tog_parent_tree {
2282 TAILQ_ENTRY(tog_parent_tree) entry;
2283 struct got_tree_object *tree;
2284 struct got_tree_entry *first_displayed_entry;
2285 struct got_tree_entry *selected_entry;
2286 int selected;
2289 TAILQ_HEAD(tog_parent_trees, tog_parent_tree);
2291 static const struct got_error *
2292 tree_entry_path(char **path, struct tog_parent_trees *parents,
2293 struct got_tree_entry *te)
2295 const struct got_error *err = NULL;
2296 struct tog_parent_tree *pt;
2297 size_t len = 2; /* for leading slash and NUL */
2299 TAILQ_FOREACH(pt, parents, entry)
2300 len += strlen(pt->selected_entry->name) + 1 /* slash */;
2301 if (te)
2302 len += strlen(te->name);
2304 *path = calloc(1, len);
2305 if (path == NULL)
2306 return got_error_from_errno();
2308 (*path)[0] = '/';
2309 pt = TAILQ_LAST(parents, tog_parent_trees);
2310 while (pt) {
2311 if (strlcat(*path, pt->selected_entry->name, len) >= len) {
2312 err = got_error(GOT_ERR_NO_SPACE);
2313 goto done;
2315 if (strlcat(*path, "/", len) >= len) {
2316 err = got_error(GOT_ERR_NO_SPACE);
2317 goto done;
2319 pt = TAILQ_PREV(pt, tog_parent_trees, entry);
2321 if (te) {
2322 if (strlcat(*path, te->name, len) >= len) {
2323 err = got_error(GOT_ERR_NO_SPACE);
2324 goto done;
2327 done:
2328 if (err) {
2329 free(*path);
2330 *path = NULL;
2332 return err;
2335 static const struct got_error *
2336 blame_tree_entry(struct tog_view *parent_view, struct got_tree_entry *te,
2337 struct tog_parent_trees *parents, struct got_object_id *commit_id,
2338 struct got_repository *repo)
2340 const struct got_error *err = NULL;
2341 char *path;
2342 struct tog_view *view;
2344 err = tree_entry_path(&path, parents, te);
2345 if (err)
2346 return err;
2348 view = view_open(0, 0, 0, 0, parent_view, TOG_VIEW_BLAME);
2349 if (view) {
2350 err = show_blame_view(view, path, commit_id, repo);
2351 view_close(view);
2352 } else
2353 err = got_error_from_errno();
2355 view_show(parent_view);
2356 free(path);
2357 return err;
2360 static const struct got_error *
2361 log_tree_entry(struct tog_view *view, struct got_tree_entry *te,
2362 struct tog_parent_trees *parents, struct got_object_id *commit_id,
2363 struct got_repository *repo)
2365 const struct got_error *err = NULL;
2366 char *path;
2368 err = tree_entry_path(&path, parents, te);
2369 if (err)
2370 return err;
2372 err = open_log_view(view, commit_id, repo, path);
2373 if (err)
2374 goto done;
2375 err = show_log_view(view);
2376 close_log_view(view);
2377 done:
2378 free(path);
2379 return err;
2382 static const struct got_error *
2383 show_tree_view(struct tog_view *view, struct got_tree_object *root,
2384 struct got_object_id *commit_id, struct got_repository *repo)
2386 const struct got_error *err = NULL;
2387 int ch, done = 0, selected = 0, show_ids = 0;
2388 struct got_tree_object *tree = root;
2389 const struct got_tree_entries *entries;
2390 struct got_tree_entry *first_displayed_entry = NULL;
2391 struct got_tree_entry *last_displayed_entry = NULL;
2392 struct got_tree_entry *selected_entry = NULL;
2393 char *commit_id_str = NULL, *tree_label = NULL;
2394 int nentries, ndisplayed;
2395 struct tog_parent_trees parents;
2397 TAILQ_INIT(&parents);
2399 err = got_object_id_str(&commit_id_str, commit_id);
2400 if (err != NULL)
2401 goto done;
2403 if (asprintf(&tree_label, "commit: %s", commit_id_str) == -1) {
2404 err = got_error_from_errno();
2405 goto done;
2408 view_show(view);
2410 entries = got_object_tree_get_entries(root);
2411 first_displayed_entry = SIMPLEQ_FIRST(&entries->head);
2412 while (!done) {
2413 char *parent_path;
2414 entries = got_object_tree_get_entries(tree);
2415 nentries = entries->nentries;
2416 if (tree != root)
2417 nentries++; /* '..' directory */
2419 err = tree_entry_path(&parent_path, &parents, NULL);
2420 if (err)
2421 goto done;
2423 err = draw_tree_entries(view, &first_displayed_entry,
2424 &last_displayed_entry, &selected_entry, &ndisplayed,
2425 tree_label, show_ids, parent_path, entries, selected,
2426 view->nlines, tree == root);
2427 free(parent_path);
2428 if (err)
2429 break;
2431 nodelay(stdscr, FALSE);
2432 ch = wgetch(view->window);
2433 nodelay(stdscr, TRUE);
2434 switch (ch) {
2435 case 'q':
2436 done = 1;
2437 break;
2438 case 'i':
2439 show_ids = !show_ids;
2440 break;
2441 case 'l':
2442 if (selected_entry) {
2443 struct tog_view *log_view;
2444 log_view = view_open(0, 0, 0, 0, view,
2445 TOG_VIEW_LOG);
2446 if (log_view == NULL) {
2447 err = got_error_from_errno();
2448 goto done;
2450 err = log_tree_entry(log_view,
2451 selected_entry, &parents,
2452 commit_id, repo);
2453 view_close(log_view);
2454 view_show(view);
2455 if (err)
2456 goto done;
2458 break;
2459 case 'k':
2460 case KEY_UP:
2461 if (selected > 0)
2462 selected--;
2463 if (selected > 0)
2464 break;
2465 tree_scroll_up(&first_displayed_entry, 1,
2466 entries, tree == root);
2467 break;
2468 case KEY_PPAGE:
2469 if (SIMPLEQ_FIRST(&entries->head) ==
2470 first_displayed_entry) {
2471 if (tree != root)
2472 first_displayed_entry = NULL;
2473 selected = 0;
2474 break;
2476 tree_scroll_up(&first_displayed_entry,
2477 view->nlines, entries, tree == root);
2478 break;
2479 case 'j':
2480 case KEY_DOWN:
2481 if (selected < ndisplayed - 1) {
2482 selected++;
2483 break;
2485 tree_scroll_down(&first_displayed_entry, 1,
2486 last_displayed_entry, entries);
2487 break;
2488 case KEY_NPAGE:
2489 tree_scroll_down(&first_displayed_entry,
2490 view->nlines, last_displayed_entry,
2491 entries);
2492 if (SIMPLEQ_NEXT(last_displayed_entry, entry))
2493 break;
2494 /* can't scroll any further; move cursor down */
2495 if (selected < ndisplayed - 1)
2496 selected = ndisplayed - 1;
2497 break;
2498 case KEY_ENTER:
2499 case '\r':
2500 if (selected_entry == NULL) {
2501 struct tog_parent_tree *parent;
2502 case KEY_BACKSPACE:
2503 /* user selected '..' */
2504 if (tree == root)
2505 break;
2506 parent = TAILQ_FIRST(&parents);
2507 TAILQ_REMOVE(&parents, parent, entry);
2508 got_object_tree_close(tree);
2509 tree = parent->tree;
2510 first_displayed_entry =
2511 parent->first_displayed_entry;
2512 selected_entry = parent->selected_entry;
2513 selected = parent->selected;
2514 free(parent);
2515 } else if (S_ISDIR(selected_entry->mode)) {
2516 struct tog_parent_tree *parent;
2517 struct got_tree_object *child;
2518 err = got_object_open_as_tree(
2519 &child, repo, selected_entry->id);
2520 if (err)
2521 goto done;
2522 parent = calloc(1, sizeof(*parent));
2523 if (parent == NULL) {
2524 err = got_error_from_errno();
2525 goto done;
2527 parent->tree = tree;
2528 parent->first_displayed_entry =
2529 first_displayed_entry;
2530 parent->selected_entry = selected_entry;
2531 parent->selected = selected;
2532 TAILQ_INSERT_HEAD(&parents, parent,
2533 entry);
2534 tree = child;
2535 selected = 0;
2536 first_displayed_entry = NULL;
2537 } else if (S_ISREG(selected_entry->mode)) {
2538 err = blame_tree_entry(view,
2539 selected_entry, &parents,
2540 commit_id, repo);
2541 if (err)
2542 goto done;
2544 break;
2545 case KEY_RESIZE:
2546 err = view_resize(view);
2547 if (err)
2548 goto done;
2549 if (selected > view->nlines)
2550 selected = ndisplayed - 1;
2551 break;
2552 default:
2553 break;
2556 done:
2557 free(tree_label);
2558 free(commit_id_str);
2559 while (!TAILQ_EMPTY(&parents)) {
2560 struct tog_parent_tree *parent;
2561 parent = TAILQ_FIRST(&parents);
2562 TAILQ_REMOVE(&parents, parent, entry);
2563 free(parent);
2566 if (tree != root)
2567 got_object_tree_close(tree);
2568 return err;
2571 __dead static void
2572 usage_tree(void)
2574 endwin();
2575 fprintf(stderr, "usage: %s tree [-c commit] [repository-path]\n",
2576 getprogname());
2577 exit(1);
2580 static const struct got_error *
2581 cmd_tree(int argc, char *argv[])
2583 const struct got_error *error;
2584 struct got_repository *repo = NULL;
2585 char *repo_path = NULL;
2586 struct got_object_id *commit_id = NULL;
2587 char *commit_id_arg = NULL;
2588 struct got_commit_object *commit = NULL;
2589 struct got_tree_object *tree = NULL;
2590 int ch;
2591 struct tog_view *view;
2593 #ifndef PROFILE
2594 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
2595 err(1, "pledge");
2596 #endif
2598 while ((ch = getopt(argc, argv, "c:")) != -1) {
2599 switch (ch) {
2600 case 'c':
2601 commit_id_arg = optarg;
2602 break;
2603 default:
2604 usage();
2605 /* NOTREACHED */
2609 argc -= optind;
2610 argv += optind;
2612 if (argc == 0) {
2613 repo_path = getcwd(NULL, 0);
2614 if (repo_path == NULL)
2615 return got_error_from_errno();
2616 } else if (argc == 1) {
2617 repo_path = realpath(argv[0], NULL);
2618 if (repo_path == NULL)
2619 return got_error_from_errno();
2620 } else
2621 usage_log();
2623 error = got_repo_open(&repo, repo_path);
2624 free(repo_path);
2625 if (error != NULL)
2626 return error;
2628 if (commit_id_arg == NULL) {
2629 error = get_head_commit_id(&commit_id, repo);
2630 if (error != NULL)
2631 goto done;
2632 } else {
2633 struct got_object *obj;
2634 error = got_object_open_by_id_str(&obj, repo, commit_id_arg);
2635 if (error == NULL) {
2636 commit_id = got_object_get_id(obj);
2637 if (commit_id == NULL)
2638 error = got_error_from_errno();
2641 if (error != NULL)
2642 goto done;
2644 error = got_object_open_as_commit(&commit, repo, commit_id);
2645 if (error != NULL)
2646 goto done;
2648 error = got_object_open_as_tree(&tree, repo, commit->tree_id);
2649 if (error != NULL)
2650 goto done;
2652 view = view_open(0, 0, 0, 0, NULL, TOG_VIEW_TREE);
2653 if (view == NULL) {
2654 error = got_error_from_errno();
2655 goto done;
2657 error = show_tree_view(view, tree, commit_id, repo);
2658 view_close(view);
2659 done:
2660 free(commit_id);
2661 if (commit)
2662 got_object_commit_close(commit);
2663 if (tree)
2664 got_object_tree_close(tree);
2665 if (repo)
2666 got_repo_close(repo);
2667 return error;
2669 static void
2670 init_curses(void)
2672 initscr();
2673 cbreak();
2674 noecho();
2675 nonl();
2676 intrflush(stdscr, FALSE);
2677 keypad(stdscr, TRUE);
2678 curs_set(0);
2681 __dead static void
2682 usage(void)
2684 int i;
2686 fprintf(stderr, "usage: %s [-h] [command] [arg ...]\n\n"
2687 "Available commands:\n", getprogname());
2688 for (i = 0; i < nitems(tog_commands); i++) {
2689 struct tog_cmd *cmd = &tog_commands[i];
2690 fprintf(stderr, " %s: %s\n", cmd->name, cmd->descr);
2692 exit(1);
2695 static char **
2696 make_argv(const char *arg0, const char *arg1)
2698 char **argv;
2699 int argc = (arg1 == NULL ? 1 : 2);
2701 argv = calloc(argc, sizeof(char *));
2702 if (argv == NULL)
2703 err(1, "calloc");
2704 argv[0] = strdup(arg0);
2705 if (argv[0] == NULL)
2706 err(1, "calloc");
2707 if (arg1) {
2708 argv[1] = strdup(arg1);
2709 if (argv[1] == NULL)
2710 err(1, "calloc");
2713 return argv;
2716 int
2717 main(int argc, char *argv[])
2719 const struct got_error *error = NULL;
2720 struct tog_cmd *cmd = NULL;
2721 int ch, hflag = 0;
2722 char **cmd_argv = NULL;
2724 setlocale(LC_ALL, "");
2726 while ((ch = getopt(argc, argv, "h")) != -1) {
2727 switch (ch) {
2728 case 'h':
2729 hflag = 1;
2730 break;
2731 default:
2732 usage();
2733 /* NOTREACHED */
2737 argc -= optind;
2738 argv += optind;
2739 optind = 0;
2740 optreset = 1;
2742 if (argc == 0) {
2743 if (hflag)
2744 usage();
2745 /* Build an argument vector which runs a default command. */
2746 cmd = &tog_commands[0];
2747 cmd_argv = make_argv(cmd->name, NULL);
2748 argc = 1;
2749 } else {
2750 int i;
2752 /* Did the user specific a command? */
2753 for (i = 0; i < nitems(tog_commands); i++) {
2754 if (strncmp(tog_commands[i].name, argv[0],
2755 strlen(argv[0])) == 0) {
2756 cmd = &tog_commands[i];
2757 if (hflag)
2758 tog_commands[i].cmd_usage();
2759 break;
2762 if (cmd == NULL) {
2763 /* Did the user specify a repository? */
2764 char *repo_path = realpath(argv[0], NULL);
2765 if (repo_path) {
2766 struct got_repository *repo;
2767 error = got_repo_open(&repo, repo_path);
2768 if (error == NULL)
2769 got_repo_close(repo);
2770 } else
2771 error = got_error_from_errno();
2772 if (error) {
2773 if (hflag) {
2774 fprintf(stderr, "%s: '%s' is not a "
2775 "known command\n", getprogname(),
2776 argv[0]);
2777 usage();
2779 fprintf(stderr, "%s: '%s' is neither a known "
2780 "command nor a path to a repository\n",
2781 getprogname(), argv[0]);
2782 free(repo_path);
2783 return 1;
2785 cmd = &tog_commands[0];
2786 cmd_argv = make_argv(cmd->name, repo_path);
2787 argc = 2;
2788 free(repo_path);
2792 init_curses();
2794 error = cmd->cmd_main(argc, cmd_argv ? cmd_argv : argv);
2795 if (error)
2796 goto done;
2797 done:
2798 endwin();
2799 free(cmd_argv);
2800 if (error)
2801 fprintf(stderr, "%s: %s\n", getprogname(), error->msg);
2802 return 0;