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 struct tog_view {
86 WINDOW *window;
87 PANEL *panel;
88 int nlines, ncols, begin_y, begin_x;
89 };
91 static const struct got_error *
92 show_diff_view(struct tog_view *, struct got_object *, struct got_object *,
93 struct got_repository *);
94 static const struct got_error *
95 show_log_view(struct got_object_id *, struct got_repository *, const char *);
96 static const struct got_error *
97 show_blame_view(const char *, struct got_object_id *, struct got_repository *);
98 static const struct got_error *
99 show_tree_view(struct got_tree_object *, struct got_object_id *,
100 struct got_repository *);
102 static void
103 close_view(struct tog_view *view)
105 if (view->panel)
106 del_panel(view->panel);
107 if (view->window)
108 delwin(view->window);
109 free(view);
112 static struct tog_view *
113 open_view(int nlines, int ncols, int begin_y, int begin_x)
115 struct tog_view *view = malloc(sizeof(*view));
117 if (view == NULL)
118 return NULL;
120 view->nlines = nlines;
121 view->ncols = ncols;
122 view->begin_y = begin_y;
123 view->begin_x = begin_x;
124 view->window = newwin(nlines, ncols, begin_y, begin_x);
125 if (view->window == NULL) {
126 close_view(view);
127 return NULL;
129 view->panel = new_panel(view->window);
130 if (view->panel == NULL) {
131 close_view(view);
132 return NULL;
135 keypad(view->window, TRUE);
136 return view;
139 __dead static void
140 usage_log(void)
142 endwin();
143 fprintf(stderr, "usage: %s log [-c commit] [-r repository-path] [path]\n",
144 getprogname());
145 exit(1);
148 /* Create newly allocated wide-character string equivalent to a byte string. */
149 static const struct got_error *
150 mbs2ws(wchar_t **ws, size_t *wlen, const char *s)
152 char *vis = NULL;
153 const struct got_error *err = NULL;
155 *ws = NULL;
156 *wlen = mbstowcs(NULL, s, 0);
157 if (*wlen == (size_t)-1) {
158 int vislen;
159 if (errno != EILSEQ)
160 return got_error_from_errno();
162 /* byte string invalid in current encoding; try to "fix" it */
163 err = got_mbsavis(&vis, &vislen, s);
164 if (err)
165 return err;
166 *wlen = mbstowcs(NULL, vis, 0);
167 if (*wlen == (size_t)-1) {
168 err = got_error_from_errno(); /* give up */
169 goto done;
173 *ws = calloc(*wlen + 1, sizeof(*ws));
174 if (*ws == NULL) {
175 err = got_error_from_errno();
176 goto done;
179 if (mbstowcs(*ws, vis ? vis : s, *wlen) != *wlen)
180 err = got_error_from_errno();
181 done:
182 free(vis);
183 if (err) {
184 free(*ws);
185 *ws = NULL;
186 *wlen = 0;
188 return err;
191 /* Format a line for display, ensuring that it won't overflow a width limit. */
192 static const struct got_error *
193 format_line(wchar_t **wlinep, int *widthp, const char *line, int wlimit)
195 const struct got_error *err = NULL;
196 int cols = 0;
197 wchar_t *wline = NULL;
198 size_t wlen;
199 int i;
201 *wlinep = NULL;
202 *widthp = 0;
204 err = mbs2ws(&wline, &wlen, line);
205 if (err)
206 return err;
208 i = 0;
209 while (i < wlen && cols < wlimit) {
210 int width = wcwidth(wline[i]);
211 switch (width) {
212 case 0:
213 i++;
214 break;
215 case 1:
216 case 2:
217 if (cols + width <= wlimit) {
218 cols += width;
219 i++;
221 break;
222 case -1:
223 if (wline[i] == L'\t')
224 cols += TABSIZE - ((cols + 1) % TABSIZE);
225 i++;
226 break;
227 default:
228 err = got_error_from_errno();
229 goto done;
232 wline[i] = L'\0';
233 if (widthp)
234 *widthp = cols;
235 done:
236 if (err)
237 free(wline);
238 else
239 *wlinep = wline;
240 return err;
243 static const struct got_error *
244 draw_commit(struct tog_view *view, struct got_commit_object *commit,
245 struct got_object_id *id)
247 const struct got_error *err = NULL;
248 char datebuf[10]; /* YY-MM-DD + SPACE + NUL */
249 char *logmsg0 = NULL, *logmsg = NULL;
250 char *author0 = NULL, *author = NULL;
251 wchar_t *wlogmsg = NULL, *wauthor = NULL;
252 int author_width, logmsg_width;
253 char *newline, *smallerthan;
254 char *line = NULL;
255 int col, limit;
256 static const size_t date_display_cols = 9;
257 static const size_t author_display_cols = 16;
258 const int avail = COLS;
260 if (strftime(datebuf, sizeof(datebuf), "%g/%m/%d ", &commit->tm_committer)
261 >= sizeof(datebuf))
262 return got_error(GOT_ERR_NO_SPACE);
264 if (avail < date_display_cols)
265 limit = MIN(sizeof(datebuf) - 1, avail);
266 else
267 limit = MIN(date_display_cols, sizeof(datebuf) - 1);
268 waddnstr(view->window, datebuf, limit);
269 col = limit + 1;
270 if (col > avail)
271 goto done;
273 author0 = strdup(commit->author);
274 if (author0 == NULL) {
275 err = got_error_from_errno();
276 goto done;
278 author = author0;
279 smallerthan = strchr(author, '<');
280 if (smallerthan)
281 *smallerthan = '\0';
282 else {
283 char *at = strchr(author, '@');
284 if (at)
285 *at = '\0';
287 limit = avail - col;
288 err = format_line(&wauthor, &author_width, author, limit);
289 if (err)
290 goto done;
291 waddwstr(view->window, wauthor);
292 col += author_width;
293 while (col <= avail && author_width < author_display_cols + 1) {
294 waddch(view->window, ' ');
295 col++;
296 author_width++;
298 if (col > avail)
299 goto done;
301 logmsg0 = strdup(commit->logmsg);
302 if (logmsg0 == NULL) {
303 err = got_error_from_errno();
304 goto done;
306 logmsg = logmsg0;
307 while (*logmsg == '\n')
308 logmsg++;
309 newline = strchr(logmsg, '\n');
310 if (newline)
311 *newline = '\0';
312 limit = avail - col;
313 err = format_line(&wlogmsg, &logmsg_width, logmsg, limit);
314 if (err)
315 goto done;
316 waddwstr(view->window, wlogmsg);
317 col += logmsg_width;
318 while (col <= avail) {
319 waddch(view->window, ' ');
320 col++;
322 done:
323 free(logmsg0);
324 free(wlogmsg);
325 free(author0);
326 free(wauthor);
327 free(line);
328 return err;
331 struct commit_queue_entry {
332 TAILQ_ENTRY(commit_queue_entry) entry;
333 struct got_object_id *id;
334 struct got_commit_object *commit;
335 };
336 TAILQ_HEAD(commit_queue_head, commit_queue_entry);
337 struct commit_queue {
338 int ncommits;
339 struct commit_queue_head head;
340 };
342 static struct commit_queue_entry *
343 alloc_commit_queue_entry(struct got_commit_object *commit,
344 struct got_object_id *id)
346 struct commit_queue_entry *entry;
348 entry = calloc(1, sizeof(*entry));
349 if (entry == NULL)
350 return NULL;
352 entry->id = id;
353 entry->commit = commit;
354 return entry;
357 static void
358 pop_commit(struct commit_queue *commits)
360 struct commit_queue_entry *entry;
362 entry = TAILQ_FIRST(&commits->head);
363 TAILQ_REMOVE(&commits->head, entry, entry);
364 got_object_commit_close(entry->commit);
365 commits->ncommits--;
366 /* Don't free entry->id! It is owned by the commit graph. */
367 free(entry);
370 static void
371 free_commits(struct commit_queue *commits)
373 while (!TAILQ_EMPTY(&commits->head))
374 pop_commit(commits);
377 static const struct got_error *
378 queue_commits(struct got_commit_graph *graph, struct commit_queue *commits,
379 struct got_object_id *start_id, int minqueue, int init,
380 struct got_repository *repo, const char *path)
382 const struct got_error *err = NULL;
383 struct got_object_id *id;
384 struct commit_queue_entry *entry;
385 int nfetched, nqueued = 0, found_obj = 0;
386 int is_root_path = strcmp(path, "/") == 0;
388 err = got_commit_graph_iter_start(graph, start_id);
389 if (err)
390 return err;
392 entry = TAILQ_LAST(&commits->head, commit_queue_head);
393 if (entry && got_object_id_cmp(entry->id, start_id) == 0) {
394 int nfetched;
396 /* Start ID's commit is already on the queue; skip over it. */
397 err = got_commit_graph_iter_next(&id, graph);
398 if (err && err->code != GOT_ERR_ITER_NEED_MORE)
399 return err;
401 err = got_commit_graph_fetch_commits(&nfetched, graph, 1, repo);
402 if (err)
403 return err;
406 while (1) {
407 struct got_commit_object *commit;
409 err = got_commit_graph_iter_next(&id, graph);
410 if (err) {
411 if (err->code != GOT_ERR_ITER_NEED_MORE)
412 break;
413 if (nqueued >= minqueue) {
414 err = NULL;
415 break;
417 err = got_commit_graph_fetch_commits(&nfetched,
418 graph, 1, repo);
419 if (err)
420 return err;
421 continue;
423 if (id == NULL)
424 break;
426 err = got_object_open_as_commit(&commit, repo, id);
427 if (err)
428 break;
430 if (!is_root_path) {
431 struct got_object *obj;
432 struct got_object_qid *pid;
433 int changed = 0;
435 err = got_object_open_by_path(&obj, repo, id, path);
436 if (err) {
437 got_object_commit_close(commit);
438 if (err->code == GOT_ERR_NO_OBJ &&
439 (found_obj || !init)) {
440 /* History stops here. */
441 err = got_error(GOT_ERR_ITER_COMPLETED);
443 break;
445 found_obj = 1;
447 pid = SIMPLEQ_FIRST(&commit->parent_ids);
448 if (pid != NULL) {
449 struct got_object *pobj;
450 err = got_object_open_by_path(&pobj, repo,
451 pid->id, path);
452 if (err) {
453 if (err->code != GOT_ERR_NO_OBJ) {
454 got_object_close(obj);
455 got_object_commit_close(commit);
456 break;
458 err = NULL;
459 changed = 1;
460 } else {
461 struct got_object_id *id, *pid;
462 id = got_object_get_id(obj);
463 if (id == NULL) {
464 err = got_error_from_errno();
465 got_object_close(obj);
466 got_object_close(pobj);
467 break;
469 pid = got_object_get_id(pobj);
470 if (pid == NULL) {
471 err = got_error_from_errno();
472 free(id);
473 got_object_close(obj);
474 got_object_close(pobj);
475 break;
477 changed =
478 (got_object_id_cmp(id, pid) != 0);
479 got_object_close(pobj);
480 free(id);
481 free(pid);
484 got_object_close(obj);
485 if (!changed) {
486 got_object_commit_close(commit);
487 continue;
491 entry = alloc_commit_queue_entry(commit, id);
492 if (entry == NULL) {
493 err = got_error_from_errno();
494 break;
496 TAILQ_INSERT_TAIL(&commits->head, entry, entry);
497 nqueued++;
498 commits->ncommits++;
501 return err;
504 static const struct got_error *
505 fetch_next_commit(struct commit_queue_entry **pentry,
506 struct commit_queue_entry *entry, struct commit_queue *commits,
507 struct got_commit_graph *graph, struct got_repository *repo,
508 const char *path)
510 const struct got_error *err = NULL;
512 *pentry = NULL;
514 err = queue_commits(graph, commits, entry->id, 1, 0, repo, path);
515 if (err)
516 return err;
518 /* Next entry to display should now be available. */
519 *pentry = TAILQ_NEXT(entry, entry);
520 if (*pentry == NULL)
521 return got_error(GOT_ERR_NO_OBJ);
523 return NULL;
526 static const struct got_error *
527 get_head_commit_id(struct got_object_id **head_id, struct got_repository *repo)
529 const struct got_error *err = NULL;
530 struct got_reference *head_ref;
532 *head_id = NULL;
534 err = got_ref_open(&head_ref, repo, GOT_REF_HEAD);
535 if (err)
536 return err;
538 err = got_ref_resolve(head_id, repo, head_ref);
539 got_ref_close(head_ref);
540 if (err) {
541 *head_id = NULL;
542 return err;
545 return NULL;
548 static const struct got_error *
549 draw_commits(struct tog_view *view, struct commit_queue_entry **last,
550 struct commit_queue_entry **selected, struct commit_queue_entry *first,
551 struct commit_queue *commits, int selected_idx, int limit,
552 struct got_commit_graph *graph, struct got_repository *repo,
553 const char *path)
555 const struct got_error *err = NULL;
556 struct commit_queue_entry *entry;
557 int ncommits, width;
558 char *id_str, *header;
559 wchar_t *wline;
561 entry = first;
562 ncommits = 0;
563 while (entry) {
564 if (ncommits == selected_idx) {
565 *selected = entry;
566 break;
568 entry = TAILQ_NEXT(entry, entry);
569 ncommits++;
572 err = got_object_id_str(&id_str, (*selected)->id);
573 if (err)
574 return err;
576 if (path) {
577 if (asprintf(&header, "commit: %s [%s]", id_str, path) == -1) {
578 err = got_error_from_errno();
579 free(id_str);
580 return err;
582 } else if (asprintf(&header, "commit: %s", id_str) == -1) {
583 err = got_error_from_errno();
584 free(id_str);
585 return err;
587 free(id_str);
588 err = format_line(&wline, &width, header, COLS);
589 if (err) {
590 free(header);
591 return err;
593 free(header);
595 werase(view->window);
597 waddwstr(view->window, wline);
598 if (width < COLS)
599 waddch(view->window, '\n');
600 free(wline);
601 if (limit <= 1)
602 return NULL;
604 entry = first;
605 *last = first;
606 ncommits = 0;
607 while (entry) {
608 if (ncommits >= limit - 1)
609 break;
610 if (ncommits == selected_idx)
611 wstandout(view->window);
612 err = draw_commit(view, entry->commit, entry->id);
613 if (ncommits == selected_idx)
614 wstandend(view->window);
615 if (err)
616 break;
617 ncommits++;
618 *last = entry;
619 if (entry == TAILQ_LAST(&commits->head, commit_queue_head)) {
620 err = queue_commits(graph, commits, entry->id, 1,
621 0, repo, path);
622 if (err) {
623 if (err->code != GOT_ERR_ITER_COMPLETED)
624 return err;
625 err = NULL;
628 entry = TAILQ_NEXT(entry, entry);
631 update_panels();
632 doupdate();
634 return err;
637 static void
638 scroll_up(struct commit_queue_entry **first_displayed_entry, int maxscroll,
639 struct commit_queue *commits)
641 struct commit_queue_entry *entry;
642 int nscrolled = 0;
644 entry = TAILQ_FIRST(&commits->head);
645 if (*first_displayed_entry == entry)
646 return;
648 entry = *first_displayed_entry;
649 while (entry && nscrolled < maxscroll) {
650 entry = TAILQ_PREV(entry, commit_queue_head, entry);
651 if (entry) {
652 *first_displayed_entry = entry;
653 nscrolled++;
658 static const struct got_error *
659 scroll_down(struct commit_queue_entry **first_displayed_entry, int maxscroll,
660 struct commit_queue_entry *last_displayed_entry,
661 struct commit_queue *commits, struct got_commit_graph *graph,
662 struct got_repository *repo, const char *path)
664 const struct got_error *err = NULL;
665 struct commit_queue_entry *pentry;
666 int nscrolled = 0;
668 do {
669 pentry = TAILQ_NEXT(last_displayed_entry, entry);
670 if (pentry == NULL) {
671 err = fetch_next_commit(&pentry, last_displayed_entry,
672 commits, graph, repo, path);
673 if (err || pentry == NULL)
674 break;
676 last_displayed_entry = pentry;
678 pentry = TAILQ_NEXT(*first_displayed_entry, entry);
679 if (pentry == NULL)
680 break;
681 *first_displayed_entry = pentry;
682 } while (++nscrolled < maxscroll);
684 return err;
687 static const struct got_error *
688 show_commit(struct commit_queue_entry *entry, struct got_repository *repo)
690 const struct got_error *err;
691 struct got_object *obj1 = NULL, *obj2 = NULL;
692 struct got_object_qid *parent_id;
693 struct tog_view *view;
695 err = got_object_open(&obj2, repo, entry->id);
696 if (err)
697 return err;
699 parent_id = SIMPLEQ_FIRST(&entry->commit->parent_ids);
700 if (parent_id) {
701 err = got_object_open(&obj1, repo, parent_id->id);
702 if (err)
703 goto done;
706 view = open_view(0, 0, 0, 0);
707 if (view == NULL) {
708 err = got_error_from_errno();
709 goto done;
712 err = show_diff_view(view, obj1, obj2, repo);
713 close_view(view);
714 done:
715 if (obj1)
716 got_object_close(obj1);
717 if (obj2)
718 got_object_close(obj2);
719 return err;
722 static const struct got_error *
723 browse_commit(struct commit_queue_entry *entry, struct got_repository *repo)
725 const struct got_error *err = NULL;
726 struct got_tree_object *tree;
728 err = got_object_open_as_tree(&tree, repo, entry->commit->tree_id);
729 if (err)
730 return err;
732 err = show_tree_view(tree, entry->id, repo);
733 got_object_tree_close(tree);
734 return err;
737 static const struct got_error *
738 show_log_view(struct got_object_id *start_id, struct got_repository *repo,
739 const char *path)
741 const struct got_error *err = NULL;
742 struct got_object_id *head_id = NULL;
743 int ch, done = 0, selected = 0, nfetched;
744 struct got_commit_graph *graph = NULL;
745 struct commit_queue commits;
746 struct commit_queue_entry *first_displayed_entry = NULL;
747 struct commit_queue_entry *last_displayed_entry = NULL;
748 struct commit_queue_entry *selected_entry = NULL;
749 char *in_repo_path = NULL;
750 struct tog_view *view = NULL;
752 err = got_repo_map_path(&in_repo_path, repo, path);
753 if (err != NULL)
754 goto done;
756 err = get_head_commit_id(&head_id, repo);
757 if (err)
758 return err;
760 /* The graph contains all commits. */
761 err = got_commit_graph_open(&graph, head_id, 0, repo);
762 if (err)
763 goto done;
764 /* The commit queue contains a subset of commits filtered by path. */
765 TAILQ_INIT(&commits.head);
766 commits.ncommits = 0;
768 /* Populate commit graph with a sufficient number of commits. */
769 err = got_commit_graph_fetch_commits_up_to(&nfetched, graph, start_id,
770 repo);
771 if (err)
772 goto done;
774 /*
775 * Open the initial batch of commits, sorted in commit graph order.
776 * We keep all commits open throughout the lifetime of the log view
777 * in order to avoid having to re-fetch commits from disk while
778 * updating the display.
779 */
780 err = queue_commits(graph, &commits, start_id, LINES, 1, repo,
781 in_repo_path);
782 if (err) {
783 if (err->code != GOT_ERR_ITER_COMPLETED)
784 goto done;
785 err = NULL;
788 view = open_view(0, 0, 0, 0);
789 if (view == NULL) {
790 err = got_error_from_errno();
791 goto done;
794 show_panel(view->panel);
796 first_displayed_entry = TAILQ_FIRST(&commits.head);
797 selected_entry = first_displayed_entry;
798 while (!done) {
799 err = draw_commits(view, &last_displayed_entry, &selected_entry,
800 first_displayed_entry, &commits, selected, LINES,
801 graph, repo, in_repo_path);
802 if (err)
803 goto done;
805 nodelay(stdscr, FALSE);
806 ch = wgetch(view->window);
807 nodelay(stdscr, TRUE);
808 switch (ch) {
809 case ERR:
810 break;
811 case 'q':
812 done = 1;
813 break;
814 case 'k':
815 case KEY_UP:
816 if (selected > 0)
817 selected--;
818 if (selected > 0)
819 break;
820 scroll_up(&first_displayed_entry, 1, &commits);
821 break;
822 case KEY_PPAGE:
823 if (TAILQ_FIRST(&commits.head) ==
824 first_displayed_entry) {
825 selected = 0;
826 break;
828 scroll_up(&first_displayed_entry, LINES,
829 &commits);
830 break;
831 case 'j':
832 case KEY_DOWN:
833 if (selected < MIN(LINES - 2,
834 commits.ncommits - 1)) {
835 selected++;
836 break;
838 err = scroll_down(&first_displayed_entry, 1,
839 last_displayed_entry, &commits, graph,
840 repo, in_repo_path);
841 if (err) {
842 if (err->code != GOT_ERR_ITER_COMPLETED)
843 goto done;
844 err = NULL;
846 break;
847 case KEY_NPAGE: {
848 struct commit_queue_entry *first = first_displayed_entry;
849 err = scroll_down(&first_displayed_entry, LINES,
850 last_displayed_entry, &commits, graph,
851 repo, in_repo_path);
852 if (err) {
853 if (err->code != GOT_ERR_ITER_COMPLETED)
854 goto done;
855 /* can't scroll any further; move cursor down */
856 if (first == first_displayed_entry && selected <
857 MIN(LINES - 2, commits.ncommits - 1)) {
858 selected = MIN(LINES - 2,
859 commits.ncommits - 1);
861 err = NULL;
863 break;
865 case KEY_RESIZE:
866 if (selected > LINES - 2)
867 selected = LINES - 2;
868 if (selected > commits.ncommits - 1)
869 selected = commits.ncommits - 1;
870 break;
871 case KEY_ENTER:
872 case '\r':
873 err = show_commit(selected_entry, repo);
874 if (err)
875 goto done;
876 show_panel(view->panel);
877 break;
878 case 't':
879 err = browse_commit(selected_entry, repo);
880 if (err)
881 goto done;
882 show_panel(view->panel);
883 break;
884 default:
885 break;
888 done:
889 if (view)
890 close_view(view);
891 free(head_id);
892 if (graph)
893 got_commit_graph_close(graph);
894 free_commits(&commits);
895 free(in_repo_path);
896 return err;
899 static const struct got_error *
900 cmd_log(int argc, char *argv[])
902 const struct got_error *error;
903 struct got_repository *repo = NULL;
904 struct got_object_id *start_id = NULL;
905 char *path = NULL, *repo_path = NULL, *cwd = NULL;
906 char *start_commit = NULL;
907 int ch;
909 #ifndef PROFILE
910 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
911 err(1, "pledge");
912 #endif
914 while ((ch = getopt(argc, argv, "c:r:")) != -1) {
915 switch (ch) {
916 case 'c':
917 start_commit = optarg;
918 break;
919 case 'r':
920 repo_path = realpath(optarg, NULL);
921 if (repo_path == NULL)
922 err(1, "-r option");
923 break;
924 default:
925 usage();
926 /* NOTREACHED */
930 argc -= optind;
931 argv += optind;
933 if (argc == 0)
934 path = strdup("");
935 else if (argc == 1)
936 path = strdup(argv[0]);
937 else
938 usage_log();
939 if (path == NULL)
940 return got_error_from_errno();
942 cwd = getcwd(NULL, 0);
943 if (cwd == NULL) {
944 error = got_error_from_errno();
945 goto done;
947 if (repo_path == NULL) {
948 repo_path = strdup(cwd);
949 if (repo_path == NULL) {
950 error = got_error_from_errno();
951 goto done;
955 error = got_repo_open(&repo, repo_path);
956 if (error != NULL)
957 goto done;
959 if (start_commit == NULL) {
960 error = get_head_commit_id(&start_id, repo);
961 if (error != NULL)
962 goto done;
963 } else {
964 struct got_object *obj;
965 error = got_object_open_by_id_str(&obj, repo, start_commit);
966 if (error == NULL) {
967 start_id = got_object_get_id(obj);
968 if (start_id == NULL)
969 error = got_error_from_errno();
970 goto done;
973 if (error != NULL)
974 goto done;
976 error = show_log_view(start_id, repo, path);
977 done:
978 free(repo_path);
979 free(cwd);
980 free(path);
981 free(start_id);
982 if (repo)
983 got_repo_close(repo);
984 return error;
987 __dead static void
988 usage_diff(void)
990 endwin();
991 fprintf(stderr, "usage: %s diff [repository-path] object1 object2\n",
992 getprogname());
993 exit(1);
996 static char *
997 parse_next_line(FILE *f, size_t *len)
999 char *line;
1000 size_t linelen;
1001 size_t lineno;
1002 const char delim[3] = { '\0', '\0', '\0'};
1004 line = fparseln(f, &linelen, &lineno, delim, 0);
1005 if (len)
1006 *len = linelen;
1007 return line;
1010 static const struct got_error *
1011 draw_file(WINDOW *window, FILE *f, int *first_displayed_line,
1012 int *last_displayed_line, int *eof, int max_lines)
1014 const struct got_error *err;
1015 int nlines = 0, nprinted = 0;
1016 char *line;
1017 size_t len;
1018 wchar_t *wline;
1019 int width;
1021 rewind(f);
1022 werase(window);
1024 *eof = 0;
1025 while (nprinted < max_lines) {
1026 line = parse_next_line(f, &len);
1027 if (line == NULL) {
1028 *eof = 1;
1029 break;
1031 if (++nlines < *first_displayed_line) {
1032 free(line);
1033 continue;
1036 err = format_line(&wline, &width, line, COLS);
1037 if (err) {
1038 free(line);
1039 free(wline);
1040 return err;
1042 waddwstr(window, wline);
1043 if (width < COLS)
1044 waddch(window, '\n');
1045 if (++nprinted == 1)
1046 *first_displayed_line = nlines;
1047 free(line);
1048 free(wline);
1049 wline = NULL;
1051 *last_displayed_line = nlines;
1053 update_panels();
1054 doupdate();
1056 return NULL;
1059 static const struct got_error *
1060 show_diff_view(struct tog_view *view, struct got_object *obj1,
1061 struct got_object *obj2, struct got_repository *repo)
1063 const struct got_error *err;
1064 FILE *f;
1065 int ch, done = 0, first_displayed_line = 1, last_displayed_line = LINES;
1066 int eof, i;
1068 if (obj1 != NULL && obj2 != NULL &&
1069 got_object_get_type(obj1) != got_object_get_type(obj2))
1070 return got_error(GOT_ERR_OBJ_TYPE);
1072 f = got_opentemp();
1073 if (f == NULL)
1074 return got_error_from_errno();
1076 switch (got_object_get_type(obj1 ? obj1 : obj2)) {
1077 case GOT_OBJ_TYPE_BLOB:
1078 err = got_diff_objects_as_blobs(obj1, obj2, repo, f);
1079 break;
1080 case GOT_OBJ_TYPE_TREE:
1081 err = got_diff_objects_as_trees(obj1, obj2, repo, f);
1082 break;
1083 case GOT_OBJ_TYPE_COMMIT:
1084 err = got_diff_objects_as_commits(obj1, obj2, repo, f);
1085 break;
1086 default:
1087 return got_error(GOT_ERR_OBJ_TYPE);
1090 fflush(f);
1092 show_panel(view->panel);
1094 while (!done) {
1095 err = draw_file(view->window, f, &first_displayed_line,
1096 &last_displayed_line, &eof, LINES);
1097 if (err)
1098 break;
1099 nodelay(stdscr, FALSE);
1100 ch = wgetch(view->window);
1101 nodelay(stdscr, TRUE);
1102 switch (ch) {
1103 case 'q':
1104 done = 1;
1105 break;
1106 case 'k':
1107 case KEY_UP:
1108 if (first_displayed_line > 1)
1109 first_displayed_line--;
1110 break;
1111 case KEY_PPAGE:
1112 case KEY_BACKSPACE:
1113 i = 0;
1114 while (i++ < LINES - 1 &&
1115 first_displayed_line > 1)
1116 first_displayed_line--;
1117 break;
1118 case 'j':
1119 case KEY_DOWN:
1120 if (!eof)
1121 first_displayed_line++;
1122 break;
1123 case KEY_NPAGE:
1124 case ' ':
1125 i = 0;
1126 while (!eof && i++ < LINES - 1) {
1127 char *line = parse_next_line(f, NULL);
1128 first_displayed_line++;
1129 if (line == NULL)
1130 break;
1132 break;
1133 default:
1134 break;
1137 fclose(f);
1138 return err;
1141 static const struct got_error *
1142 cmd_diff(int argc, char *argv[])
1144 const struct got_error *error = NULL;
1145 struct got_repository *repo = NULL;
1146 struct got_object *obj1 = NULL, *obj2 = NULL;
1147 char *repo_path = NULL;
1148 char *obj_id_str1 = NULL, *obj_id_str2 = NULL;
1149 int ch;
1150 struct tog_view *view;
1152 #ifndef PROFILE
1153 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
1154 err(1, "pledge");
1155 #endif
1157 while ((ch = getopt(argc, argv, "")) != -1) {
1158 switch (ch) {
1159 default:
1160 usage();
1161 /* NOTREACHED */
1165 argc -= optind;
1166 argv += optind;
1168 if (argc == 0) {
1169 usage_diff(); /* TODO show local worktree changes */
1170 } else if (argc == 2) {
1171 repo_path = getcwd(NULL, 0);
1172 if (repo_path == NULL)
1173 return got_error_from_errno();
1174 obj_id_str1 = argv[0];
1175 obj_id_str2 = argv[1];
1176 } else if (argc == 3) {
1177 repo_path = realpath(argv[0], NULL);
1178 if (repo_path == NULL)
1179 return got_error_from_errno();
1180 obj_id_str1 = argv[1];
1181 obj_id_str2 = argv[2];
1182 } else
1183 usage_diff();
1185 error = got_repo_open(&repo, repo_path);
1186 free(repo_path);
1187 if (error)
1188 goto done;
1190 error = got_object_open_by_id_str(&obj1, repo, obj_id_str1);
1191 if (error)
1192 goto done;
1194 error = got_object_open_by_id_str(&obj2, repo, obj_id_str2);
1195 if (error)
1196 goto done;
1198 view = open_view(0, 0, 0, 0);
1199 if (view == NULL) {
1200 error = got_error_from_errno();
1201 goto done;
1203 error = show_diff_view(view, obj1, obj2, repo);
1204 close_view(view);
1205 done:
1206 got_repo_close(repo);
1207 if (obj1)
1208 got_object_close(obj1);
1209 if (obj2)
1210 got_object_close(obj2);
1211 return error;
1214 __dead static void
1215 usage_blame(void)
1217 endwin();
1218 fprintf(stderr, "usage: %s blame [-c commit] [repository-path] path\n",
1219 getprogname());
1220 exit(1);
1223 struct tog_blame_line {
1224 int annotated;
1225 struct got_object_id *id;
1228 static const struct got_error *
1229 draw_blame(WINDOW *window, struct got_object_id *id, FILE *f, const char *path,
1230 struct tog_blame_line *lines, int nlines, int blame_complete,
1231 int selected_line, int *first_displayed_line, int *last_displayed_line,
1232 int *eof, int max_lines)
1234 const struct got_error *err;
1235 int lineno = 0, nprinted = 0;
1236 char *line;
1237 size_t len;
1238 wchar_t *wline;
1239 int width, wlimit;
1240 struct tog_blame_line *blame_line;
1241 struct got_object_id *prev_id = NULL;
1242 char *id_str;
1244 err = got_object_id_str(&id_str, id);
1245 if (err)
1246 return err;
1248 rewind(f);
1249 werase(window);
1251 if (asprintf(&line, "commit: %s", id_str) == -1) {
1252 err = got_error_from_errno();
1253 free(id_str);
1254 return err;
1257 err = format_line(&wline, &width, line, COLS);
1258 free(line);
1259 line = NULL;
1260 waddwstr(window, wline);
1261 free(wline);
1262 wline = NULL;
1263 if (width < COLS)
1264 waddch(window, '\n');
1266 if (asprintf(&line, "[%d/%d] %s%s",
1267 *first_displayed_line - 1 + selected_line, nlines,
1268 blame_complete ? "" : "annotating ", path) == -1) {
1269 free(id_str);
1270 return got_error_from_errno();
1272 free(id_str);
1273 err = format_line(&wline, &width, line, COLS);
1274 free(line);
1275 line = NULL;
1276 if (err)
1277 return err;
1278 waddwstr(window, wline);
1279 free(wline);
1280 wline = NULL;
1281 if (width < COLS)
1282 waddch(window, '\n');
1284 *eof = 0;
1285 while (nprinted < max_lines - 2) {
1286 line = parse_next_line(f, &len);
1287 if (line == NULL) {
1288 *eof = 1;
1289 break;
1291 if (++lineno < *first_displayed_line) {
1292 free(line);
1293 continue;
1296 wlimit = COLS < 9 ? 0 : COLS - 9;
1297 err = format_line(&wline, &width, line, wlimit);
1298 if (err) {
1299 free(line);
1300 return err;
1303 if (nprinted == selected_line - 1)
1304 wstandout(window);
1306 blame_line = &lines[lineno - 1];
1307 if (blame_line->annotated && prev_id &&
1308 got_object_id_cmp(prev_id, blame_line->id) == 0)
1309 waddstr(window, " ");
1310 else if (blame_line->annotated) {
1311 char *id_str;
1312 err = got_object_id_str(&id_str, blame_line->id);
1313 if (err) {
1314 free(line);
1315 free(wline);
1316 return err;
1318 wprintw(window, "%.8s ", id_str);
1319 free(id_str);
1320 prev_id = blame_line->id;
1321 } else {
1322 waddstr(window, "........ ");
1323 prev_id = NULL;
1326 waddwstr(window, wline);
1327 while (width < wlimit) {
1328 waddch(window, ' '); /* width == wlimit - 1 ? '\n' : ' '); */
1329 width++;
1331 if (nprinted == selected_line - 1)
1332 wstandend(window);
1333 if (++nprinted == 1)
1334 *first_displayed_line = lineno;
1335 free(line);
1336 free(wline);
1337 wline = NULL;
1339 *last_displayed_line = lineno;
1341 update_panels();
1342 doupdate();
1344 return NULL;
1347 struct tog_blame_cb_args {
1348 pthread_mutex_t *mutex;
1349 struct tog_blame_line *lines; /* one per line */
1350 int nlines;
1352 struct tog_view *view;
1353 struct got_object_id *commit_id;
1354 FILE *f;
1355 const char *path;
1356 int *first_displayed_line;
1357 int *last_displayed_line;
1358 int *selected_line;
1359 int *quit;
1362 static const struct got_error *
1363 blame_cb(void *arg, int nlines, int lineno, struct got_object_id *id)
1365 const struct got_error *err = NULL;
1366 struct tog_blame_cb_args *a = arg;
1367 struct tog_blame_line *line;
1368 int eof;
1370 if (nlines != a->nlines ||
1371 (lineno != -1 && lineno < 1) || lineno > a->nlines)
1372 return got_error(GOT_ERR_RANGE);
1374 if (pthread_mutex_lock(a->mutex) != 0)
1375 return got_error_from_errno();
1377 if (*a->quit) { /* user has quit the blame view */
1378 err = got_error(GOT_ERR_ITER_COMPLETED);
1379 goto done;
1382 if (lineno == -1)
1383 goto done; /* no change in this commit */
1385 line = &a->lines[lineno - 1];
1386 if (line->annotated)
1387 goto done;
1389 line->id = got_object_id_dup(id);
1390 if (line->id == NULL) {
1391 err = got_error_from_errno();
1392 goto done;
1394 line->annotated = 1;
1396 err = draw_blame(a->view->window, a->commit_id, a->f, a->path,
1397 a->lines, a->nlines, 0, *a->selected_line, a->first_displayed_line,
1398 a->last_displayed_line, &eof, LINES);
1399 done:
1400 if (pthread_mutex_unlock(a->mutex) != 0)
1401 return got_error_from_errno();
1402 return err;
1405 struct tog_blame_thread_args {
1406 const char *path;
1407 struct got_repository *repo;
1408 struct tog_blame_cb_args *cb_args;
1409 int *complete;
1412 static void *
1413 blame_thread(void *arg)
1415 const struct got_error *err;
1416 struct tog_blame_thread_args *ta = arg;
1417 struct tog_blame_cb_args *a = ta->cb_args;
1418 int eof;
1420 err = got_blame_incremental(ta->path, a->commit_id, ta->repo,
1421 blame_cb, ta->cb_args);
1423 if (pthread_mutex_lock(a->mutex) != 0)
1424 return (void *)got_error_from_errno();
1426 got_repo_close(ta->repo);
1427 ta->repo = NULL;
1428 *ta->complete = 1;
1429 if (!err)
1430 err = draw_blame(a->view->window, a->commit_id, a->f,
1431 a->path, a->lines, a->nlines, 1, *a->selected_line,
1432 a->first_displayed_line, a->last_displayed_line, &eof,
1433 LINES);
1435 if (pthread_mutex_unlock(a->mutex) != 0 && err == NULL)
1436 err = got_error_from_errno();
1438 return (void *)err;
1441 static struct got_object_id *
1442 get_selected_commit_id(struct tog_blame_line *lines,
1443 int first_displayed_line, int selected_line)
1445 struct tog_blame_line *line;
1447 line = &lines[first_displayed_line - 1 + selected_line - 1];
1448 if (!line->annotated)
1449 return NULL;
1451 return line->id;
1454 static const struct got_error *
1455 open_selected_commit(struct got_object **pobj, struct got_object **obj,
1456 struct tog_blame_line *lines, int first_displayed_line,
1457 int selected_line, struct got_repository *repo)
1459 const struct got_error *err = NULL;
1460 struct got_commit_object *commit = NULL;
1461 struct got_object_id *selected_id;
1462 struct got_object_qid *pid;
1464 *pobj = NULL;
1465 *obj = NULL;
1467 selected_id = get_selected_commit_id(lines,
1468 first_displayed_line, selected_line);
1469 if (selected_id == NULL)
1470 return NULL;
1472 err = got_object_open(obj, repo, selected_id);
1473 if (err)
1474 goto done;
1476 err = got_object_commit_open(&commit, repo, *obj);
1477 if (err)
1478 goto done;
1480 pid = SIMPLEQ_FIRST(&commit->parent_ids);
1481 if (pid) {
1482 err = got_object_open(pobj, repo, pid->id);
1483 if (err)
1484 goto done;
1486 done:
1487 if (commit)
1488 got_object_commit_close(commit);
1489 return err;
1492 struct tog_blame {
1493 FILE *f;
1494 size_t filesize;
1495 struct tog_blame_line *lines;
1496 size_t nlines;
1497 pthread_t thread;
1498 struct tog_blame_thread_args thread_args;
1499 struct tog_blame_cb_args cb_args;
1500 const char *path;
1503 static const struct got_error *
1504 stop_blame(struct tog_blame *blame)
1506 const struct got_error *err = NULL;
1507 int i;
1509 if (blame->thread) {
1510 if (pthread_join(blame->thread, (void **)&err) != 0)
1511 err = got_error_from_errno();
1512 if (err && err->code == GOT_ERR_ITER_COMPLETED)
1513 err = NULL;
1514 blame->thread = NULL;
1516 if (blame->thread_args.repo) {
1517 got_repo_close(blame->thread_args.repo);
1518 blame->thread_args.repo = NULL;
1520 if (blame->f) {
1521 fclose(blame->f);
1522 blame->f = NULL;
1524 for (i = 0; i < blame->nlines; i++)
1525 free(blame->lines[i].id);
1526 free(blame->lines);
1527 blame->lines = NULL;
1528 free(blame->cb_args.commit_id);
1529 blame->cb_args.commit_id = NULL;
1531 return err;
1534 static const struct got_error *
1535 run_blame(struct tog_blame *blame, pthread_mutex_t *mutex,
1536 struct tog_view *view, int *blame_complete,
1537 int *first_displayed_line, int *last_displayed_line,
1538 int *selected_line, int *done, const char *path,
1539 struct got_object_id *commit_id,
1540 struct got_repository *repo)
1542 const struct got_error *err = NULL;
1543 struct got_blob_object *blob = NULL;
1544 struct got_repository *thread_repo = NULL;
1545 struct got_object *obj;
1547 err = got_object_open_by_path(&obj, repo, commit_id, path);
1548 if (err)
1549 goto done;
1550 if (got_object_get_type(obj) != GOT_OBJ_TYPE_BLOB) {
1551 err = got_error(GOT_ERR_OBJ_TYPE);
1552 goto done;
1555 err = got_object_blob_open(&blob, repo, obj, 8192);
1556 if (err)
1557 goto done;
1558 blame->f = got_opentemp();
1559 if (blame->f == NULL) {
1560 err = got_error_from_errno();
1561 goto done;
1563 err = got_object_blob_dump_to_file(&blame->filesize, &blame->nlines,
1564 blame->f, blob);
1565 if (err)
1566 goto done;
1568 blame->lines = calloc(blame->nlines, sizeof(*blame->lines));
1569 if (blame->lines == NULL) {
1570 err = got_error_from_errno();
1571 goto done;
1574 err = got_repo_open(&thread_repo, got_repo_get_path(repo));
1575 if (err)
1576 goto done;
1578 blame->cb_args.view = view;
1579 blame->cb_args.lines = blame->lines;
1580 blame->cb_args.nlines = blame->nlines;
1581 blame->cb_args.mutex = mutex;
1582 blame->cb_args.commit_id = got_object_id_dup(commit_id);
1583 if (blame->cb_args.commit_id == NULL) {
1584 err = got_error_from_errno();
1585 goto done;
1587 blame->cb_args.f = blame->f;
1588 blame->cb_args.path = path;
1589 blame->cb_args.first_displayed_line = first_displayed_line;
1590 blame->cb_args.selected_line = selected_line;
1591 blame->cb_args.last_displayed_line = last_displayed_line;
1592 blame->cb_args.quit = done;
1594 blame->thread_args.path = path;
1595 blame->thread_args.repo = thread_repo;
1596 blame->thread_args.cb_args = &blame->cb_args;
1597 blame->thread_args.complete = blame_complete;
1598 *blame_complete = 0;
1600 if (pthread_create(&blame->thread, NULL, blame_thread,
1601 &blame->thread_args) != 0) {
1602 err = got_error_from_errno();
1603 goto done;
1606 done:
1607 if (blob)
1608 got_object_blob_close(blob);
1609 if (obj)
1610 got_object_close(obj);
1611 if (err)
1612 stop_blame(blame);
1613 return err;
1616 static const struct got_error *
1617 show_blame_view(const char *path, struct got_object_id *commit_id,
1618 struct got_repository *repo)
1620 const struct got_error *err = NULL, *thread_err = NULL;
1621 int ch, done = 0, first_displayed_line = 1, last_displayed_line = LINES;
1622 int selected_line = first_displayed_line;
1623 int eof, blame_complete = 0;
1624 struct got_object *obj = NULL, *pobj = NULL;
1625 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
1626 struct tog_blame blame;
1627 struct got_object_id_queue blamed_commits;
1628 struct got_object_qid *blamed_commit = NULL;
1629 struct tog_view *view = NULL, *diff_view;
1631 SIMPLEQ_INIT(&blamed_commits);
1633 if (pthread_mutex_init(&mutex, NULL) != 0) {
1634 err = got_error_from_errno();
1635 goto done;
1638 err = got_object_qid_alloc(&blamed_commit, commit_id);
1639 if (err)
1640 goto done;
1641 SIMPLEQ_INSERT_HEAD(&blamed_commits, blamed_commit, entry);
1643 view = open_view(0, 0, 0, 0);
1644 if (view == NULL) {
1645 err = got_error_from_errno();
1646 goto done;
1648 show_panel(view->panel);
1650 memset(&blame, 0, sizeof(blame));
1651 err = run_blame(&blame, &mutex, view, &blame_complete,
1652 &first_displayed_line, &last_displayed_line,
1653 &selected_line, &done, path, blamed_commit->id, repo);
1654 if (err)
1655 return err;
1657 while (!done) {
1658 if (pthread_mutex_lock(&mutex) != 0) {
1659 err = got_error_from_errno();
1660 goto done;
1662 err = draw_blame(view->window, blamed_commit->id,
1663 blame.f, path, blame.lines, blame.nlines, blame_complete,
1664 selected_line, &first_displayed_line, &last_displayed_line,
1665 &eof, LINES);
1666 if (pthread_mutex_unlock(&mutex) != 0) {
1667 err = got_error_from_errno();
1668 goto done;
1670 if (err)
1671 break;
1672 nodelay(stdscr, FALSE);
1673 ch = wgetch(view->window);
1674 nodelay(stdscr, TRUE);
1675 if (pthread_mutex_lock(&mutex) != 0) {
1676 err = got_error_from_errno();
1677 goto done;
1679 switch (ch) {
1680 case 'q':
1681 done = 1;
1682 break;
1683 case 'k':
1684 case KEY_UP:
1685 if (selected_line > 1)
1686 selected_line--;
1687 else if (selected_line == 1 &&
1688 first_displayed_line > 1)
1689 first_displayed_line--;
1690 break;
1691 case KEY_PPAGE:
1692 case KEY_BACKSPACE:
1693 if (first_displayed_line == 1) {
1694 selected_line = 1;
1695 break;
1697 if (first_displayed_line > LINES - 2)
1698 first_displayed_line -= (LINES - 2);
1699 else
1700 first_displayed_line = 1;
1701 break;
1702 case 'j':
1703 case KEY_DOWN:
1704 if (selected_line < LINES - 2 &&
1705 first_displayed_line + selected_line <=
1706 blame.nlines)
1707 selected_line++;
1708 else if (last_displayed_line < blame.nlines)
1709 first_displayed_line++;
1710 break;
1711 case 'b':
1712 case 'p': {
1713 struct got_object_id *id;
1714 id = get_selected_commit_id(blame.lines,
1715 first_displayed_line, selected_line);
1716 if (id == NULL || got_object_id_cmp(id,
1717 blamed_commit->id) == 0)
1718 break;
1719 err = open_selected_commit(&pobj, &obj,
1720 blame.lines, first_displayed_line,
1721 selected_line, repo);
1722 if (err)
1723 break;
1724 if (pobj == NULL && obj == NULL)
1725 break;
1726 if (ch == 'p' && pobj == NULL)
1727 break;
1728 done = 1;
1729 if (pthread_mutex_unlock(&mutex) != 0) {
1730 err = got_error_from_errno();
1731 goto done;
1733 thread_err = stop_blame(&blame);
1734 done = 0;
1735 if (pthread_mutex_lock(&mutex) != 0) {
1736 err = got_error_from_errno();
1737 goto done;
1739 if (thread_err)
1740 break;
1741 id = got_object_get_id(ch == 'b' ? obj : pobj);
1742 got_object_close(obj);
1743 obj = NULL;
1744 if (pobj) {
1745 got_object_close(pobj);
1746 pobj = NULL;
1748 if (id == NULL) {
1749 err = got_error_from_errno();
1750 break;
1752 err = got_object_qid_alloc(&blamed_commit, id);
1753 free(id);
1754 if (err)
1755 goto done;
1756 SIMPLEQ_INSERT_HEAD(&blamed_commits,
1757 blamed_commit, entry);
1758 err = run_blame(&blame, &mutex, view,
1759 &blame_complete, &first_displayed_line,
1760 &last_displayed_line, &selected_line,
1761 &done, path, blamed_commit->id, repo);
1762 if (err)
1763 break;
1764 break;
1766 case 'B': {
1767 struct got_object_qid *first;
1768 first = SIMPLEQ_FIRST(&blamed_commits);
1769 if (!got_object_id_cmp(first->id, commit_id))
1770 break;
1771 done = 1;
1772 if (pthread_mutex_unlock(&mutex) != 0) {
1773 err = got_error_from_errno();
1774 goto done;
1776 thread_err = stop_blame(&blame);
1777 done = 0;
1778 if (pthread_mutex_lock(&mutex) != 0) {
1779 err = got_error_from_errno();
1780 goto done;
1782 if (thread_err)
1783 break;
1784 SIMPLEQ_REMOVE_HEAD(&blamed_commits, entry);
1785 got_object_qid_free(blamed_commit);
1786 blamed_commit = SIMPLEQ_FIRST(&blamed_commits);
1787 err = run_blame(&blame, &mutex, view,
1788 &blame_complete, &first_displayed_line,
1789 &last_displayed_line, &selected_line,
1790 &done, path, blamed_commit->id, repo);
1791 if (err)
1792 break;
1793 break;
1795 case KEY_ENTER:
1796 case '\r':
1797 err = open_selected_commit(&pobj, &obj,
1798 blame.lines, first_displayed_line,
1799 selected_line, repo);
1800 if (err)
1801 break;
1802 if (pobj == NULL && obj == NULL)
1803 break;
1804 diff_view = open_view(0, 0, 0, 0);
1805 if (diff_view == NULL) {
1806 err = got_error_from_errno();
1807 break;
1809 err = show_diff_view(diff_view, pobj, obj, repo);
1810 close_view(diff_view);
1811 if (pobj) {
1812 got_object_close(pobj);
1813 pobj = NULL;
1815 got_object_close(obj);
1816 obj = NULL;
1817 show_panel(view->panel);
1818 if (err)
1819 break;
1820 break;
1821 case KEY_NPAGE:
1822 case ' ':
1823 if (last_displayed_line >= blame.nlines &&
1824 selected_line < LINES - 2) {
1825 selected_line = MIN(blame.nlines,
1826 LINES - 2);
1827 break;
1829 if (last_displayed_line + LINES - 2 <=
1830 blame.nlines)
1831 first_displayed_line += LINES - 2;
1832 else
1833 first_displayed_line =
1834 blame.nlines - (LINES - 3);
1835 break;
1836 default:
1837 break;
1839 if (pthread_mutex_unlock(&mutex) != 0)
1840 err = got_error_from_errno();
1841 if (err || thread_err)
1842 break;
1844 done:
1845 if (pobj)
1846 got_object_close(pobj);
1847 if (blame.thread)
1848 thread_err = stop_blame(&blame);
1849 if (view)
1850 close_view(view);
1851 while (!SIMPLEQ_EMPTY(&blamed_commits)) {
1852 blamed_commit = SIMPLEQ_FIRST(&blamed_commits);
1853 SIMPLEQ_REMOVE_HEAD(&blamed_commits, entry);
1854 got_object_qid_free(blamed_commit);
1856 return thread_err ? thread_err : err;
1859 static const struct got_error *
1860 cmd_blame(int argc, char *argv[])
1862 const struct got_error *error;
1863 struct got_repository *repo = NULL;
1864 char *repo_path = NULL;
1865 char *path = NULL;
1866 struct got_object_id *commit_id = NULL;
1867 char *commit_id_str = NULL;
1868 int ch;
1870 #ifndef PROFILE
1871 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
1872 err(1, "pledge");
1873 #endif
1875 while ((ch = getopt(argc, argv, "c:")) != -1) {
1876 switch (ch) {
1877 case 'c':
1878 commit_id_str = optarg;
1879 break;
1880 default:
1881 usage();
1882 /* NOTREACHED */
1886 argc -= optind;
1887 argv += optind;
1889 if (argc == 0) {
1890 usage_blame();
1891 } else if (argc == 1) {
1892 repo_path = getcwd(NULL, 0);
1893 if (repo_path == NULL)
1894 return got_error_from_errno();
1895 path = argv[0];
1896 } else if (argc == 2) {
1897 repo_path = realpath(argv[0], NULL);
1898 if (repo_path == NULL)
1899 return got_error_from_errno();
1900 path = argv[1];
1901 } else
1902 usage_blame();
1904 error = got_repo_open(&repo, repo_path);
1905 free(repo_path);
1906 if (error != NULL)
1907 return error;
1909 if (commit_id_str == NULL) {
1910 struct got_reference *head_ref;
1911 error = got_ref_open(&head_ref, repo, GOT_REF_HEAD);
1912 if (error != NULL)
1913 goto done;
1914 error = got_ref_resolve(&commit_id, repo, head_ref);
1915 got_ref_close(head_ref);
1916 } else {
1917 struct got_object *obj;
1918 error = got_object_open_by_id_str(&obj, repo, commit_id_str);
1919 if (error != NULL)
1920 goto done;
1921 commit_id = got_object_get_id(obj);
1922 if (commit_id == NULL)
1923 error = got_error_from_errno();
1924 got_object_close(obj);
1926 if (error != NULL)
1927 goto done;
1929 error = show_blame_view(path, commit_id, repo);
1930 done:
1931 free(commit_id);
1932 if (repo)
1933 got_repo_close(repo);
1934 return error;
1937 static const struct got_error *
1938 draw_tree_entries(struct got_tree_entry **first_displayed_entry,
1939 struct got_tree_entry **last_displayed_entry,
1940 struct got_tree_entry **selected_entry, int *ndisplayed,
1941 WINDOW *window, const char *label, int show_ids, const char *parent_path,
1942 const struct got_tree_entries *entries, int selected, int limit, int isroot)
1944 const struct got_error *err = NULL;
1945 struct got_tree_entry *te;
1946 wchar_t *wline;
1947 int width, n;
1949 *ndisplayed = 0;
1951 werase(window);
1953 if (limit == 0)
1954 return NULL;
1956 err = format_line(&wline, &width, label, COLS);
1957 if (err)
1958 return err;
1959 waddwstr(window, wline);
1960 free(wline);
1961 wline = NULL;
1962 if (width < COLS)
1963 waddch(window, '\n');
1964 if (--limit <= 0)
1965 return NULL;
1966 err = format_line(&wline, &width, parent_path, COLS);
1967 if (err)
1968 return err;
1969 waddwstr(window, wline);
1970 free(wline);
1971 wline = NULL;
1972 if (width < COLS)
1973 waddch(window, '\n');
1974 if (--limit <= 0)
1975 return NULL;
1976 waddch(window, '\n');
1977 if (--limit <= 0)
1978 return NULL;
1980 te = SIMPLEQ_FIRST(&entries->head);
1981 if (*first_displayed_entry == NULL) {
1982 if (selected == 0) {
1983 wstandout(window);
1984 *selected_entry = NULL;
1986 waddstr(window, " ..\n"); /* parent directory */
1987 if (selected == 0)
1988 wstandend(window);
1989 (*ndisplayed)++;
1990 if (--limit <= 0)
1991 return NULL;
1992 n = 1;
1993 } else {
1994 n = 0;
1995 while (te != *first_displayed_entry)
1996 te = SIMPLEQ_NEXT(te, entry);
1999 while (te) {
2000 char *line = NULL, *id_str = NULL;
2002 if (show_ids) {
2003 err = got_object_id_str(&id_str, te->id);
2004 if (err)
2005 return got_error_from_errno();
2007 if (asprintf(&line, "%s %s%s", id_str ? id_str : "",
2008 te->name, S_ISDIR(te->mode) ? "/" : "") == -1) {
2009 free(id_str);
2010 return got_error_from_errno();
2012 free(id_str);
2013 err = format_line(&wline, &width, line, COLS);
2014 if (err) {
2015 free(line);
2016 break;
2018 if (n == selected) {
2019 wstandout(window);
2020 *selected_entry = te;
2022 waddwstr(window, wline);
2023 if (width < COLS)
2024 waddch(window, '\n');
2025 if (n == selected)
2026 wstandend(window);
2027 free(line);
2028 free(wline);
2029 wline = NULL;
2030 n++;
2031 (*ndisplayed)++;
2032 *last_displayed_entry = te;
2033 if (--limit <= 0)
2034 break;
2035 te = SIMPLEQ_NEXT(te, entry);
2038 return err;
2041 static void
2042 tree_scroll_up(struct got_tree_entry **first_displayed_entry, int maxscroll,
2043 const struct got_tree_entries *entries, int isroot)
2045 struct got_tree_entry *te, *prev;
2046 int i;
2048 if (*first_displayed_entry == NULL)
2049 return;
2051 te = SIMPLEQ_FIRST(&entries->head);
2052 if (*first_displayed_entry == te) {
2053 if (!isroot)
2054 *first_displayed_entry = NULL;
2055 return;
2058 /* XXX this is stupid... switch to TAILQ? */
2059 for (i = 0; i < maxscroll; i++) {
2060 while (te != *first_displayed_entry) {
2061 prev = te;
2062 te = SIMPLEQ_NEXT(te, entry);
2064 *first_displayed_entry = prev;
2065 te = SIMPLEQ_FIRST(&entries->head);
2067 if (!isroot && te == SIMPLEQ_FIRST(&entries->head) && i < maxscroll)
2068 *first_displayed_entry = NULL;
2071 static void
2072 tree_scroll_down(struct got_tree_entry **first_displayed_entry, int maxscroll,
2073 struct got_tree_entry *last_displayed_entry,
2074 const struct got_tree_entries *entries)
2076 struct got_tree_entry *next;
2077 int n = 0;
2079 if (SIMPLEQ_NEXT(last_displayed_entry, entry) == NULL)
2080 return;
2082 if (*first_displayed_entry)
2083 next = SIMPLEQ_NEXT(*first_displayed_entry, entry);
2084 else
2085 next = SIMPLEQ_FIRST(&entries->head);
2086 while (next) {
2087 *first_displayed_entry = next;
2088 if (++n >= maxscroll)
2089 break;
2090 next = SIMPLEQ_NEXT(next, entry);
2094 struct tog_parent_tree {
2095 TAILQ_ENTRY(tog_parent_tree) entry;
2096 struct got_tree_object *tree;
2097 struct got_tree_entry *first_displayed_entry;
2098 struct got_tree_entry *selected_entry;
2099 int selected;
2102 TAILQ_HEAD(tog_parent_trees, tog_parent_tree);
2104 static const struct got_error *
2105 tree_entry_path(char **path, struct tog_parent_trees *parents,
2106 struct got_tree_entry *te)
2108 const struct got_error *err = NULL;
2109 struct tog_parent_tree *pt;
2110 size_t len = 2; /* for leading slash and NUL */
2112 TAILQ_FOREACH(pt, parents, entry)
2113 len += strlen(pt->selected_entry->name) + 1 /* slash */;
2114 if (te)
2115 len += strlen(te->name);
2117 *path = calloc(1, len);
2118 if (path == NULL)
2119 return got_error_from_errno();
2121 (*path)[0] = '/';
2122 pt = TAILQ_LAST(parents, tog_parent_trees);
2123 while (pt) {
2124 if (strlcat(*path, pt->selected_entry->name, len) >= len) {
2125 err = got_error(GOT_ERR_NO_SPACE);
2126 goto done;
2128 if (strlcat(*path, "/", len) >= len) {
2129 err = got_error(GOT_ERR_NO_SPACE);
2130 goto done;
2132 pt = TAILQ_PREV(pt, tog_parent_trees, entry);
2134 if (te) {
2135 if (strlcat(*path, te->name, len) >= len) {
2136 err = got_error(GOT_ERR_NO_SPACE);
2137 goto done;
2140 done:
2141 if (err) {
2142 free(*path);
2143 *path = NULL;
2145 return err;
2148 static const struct got_error *
2149 blame_tree_entry(struct got_tree_entry *te, struct tog_parent_trees *parents,
2150 struct got_object_id *commit_id, struct got_repository *repo)
2152 const struct got_error *err = NULL;
2153 char *path;
2155 err = tree_entry_path(&path, parents, te);
2156 if (err)
2157 return err;
2159 err = show_blame_view(path, commit_id, repo);
2160 free(path);
2161 return err;
2164 static const struct got_error *
2165 log_tree_entry(struct got_tree_entry *te, struct tog_parent_trees *parents,
2166 struct got_object_id *commit_id, struct got_repository *repo)
2168 const struct got_error *err = NULL;
2169 char *path;
2171 err = tree_entry_path(&path, parents, te);
2172 if (err)
2173 return err;
2175 err = show_log_view(commit_id, repo, path);
2176 free(path);
2177 return err;
2180 static const struct got_error *
2181 show_tree_view(struct got_tree_object *root, struct got_object_id *commit_id,
2182 struct got_repository *repo)
2184 const struct got_error *err = NULL;
2185 int ch, done = 0, selected = 0, show_ids = 0;
2186 struct got_tree_object *tree = root;
2187 const struct got_tree_entries *entries;
2188 struct got_tree_entry *first_displayed_entry = NULL;
2189 struct got_tree_entry *last_displayed_entry = NULL;
2190 struct got_tree_entry *selected_entry = NULL;
2191 char *commit_id_str = NULL, *tree_label = NULL;
2192 int nentries, ndisplayed;
2193 struct tog_parent_trees parents;
2194 struct tog_view *view = NULL;
2196 TAILQ_INIT(&parents);
2198 err = got_object_id_str(&commit_id_str, commit_id);
2199 if (err != NULL)
2200 goto done;
2202 if (asprintf(&tree_label, "commit: %s", commit_id_str) == -1) {
2203 err = got_error_from_errno();
2204 goto done;
2207 view = open_view(0, 0, 0, 0);
2208 if (view == NULL) {
2209 err = got_error_from_errno();
2210 goto done;
2212 show_panel(view->panel);
2214 entries = got_object_tree_get_entries(root);
2215 first_displayed_entry = SIMPLEQ_FIRST(&entries->head);
2216 while (!done) {
2217 char *parent_path;
2218 entries = got_object_tree_get_entries(tree);
2219 nentries = entries->nentries;
2220 if (tree != root)
2221 nentries++; /* '..' directory */
2223 err = tree_entry_path(&parent_path, &parents, NULL);
2224 if (err)
2225 goto done;
2227 err = draw_tree_entries(&first_displayed_entry,
2228 &last_displayed_entry, &selected_entry, &ndisplayed,
2229 view->window, tree_label, show_ids,
2230 parent_path, entries, selected, LINES, tree == root);
2231 free(parent_path);
2232 if (err)
2233 break;
2235 nodelay(stdscr, FALSE);
2236 ch = wgetch(view->window);
2237 nodelay(stdscr, TRUE);
2238 switch (ch) {
2239 case 'q':
2240 done = 1;
2241 break;
2242 case 'i':
2243 show_ids = !show_ids;
2244 break;
2245 case 'l':
2246 if (selected_entry) {
2247 err = log_tree_entry(selected_entry,
2248 &parents, commit_id, repo);
2249 if (err)
2250 goto done;
2252 break;
2253 case 'k':
2254 case KEY_UP:
2255 if (selected > 0)
2256 selected--;
2257 if (selected > 0)
2258 break;
2259 tree_scroll_up(&first_displayed_entry, 1,
2260 entries, tree == root);
2261 break;
2262 case KEY_PPAGE:
2263 if (SIMPLEQ_FIRST(&entries->head) ==
2264 first_displayed_entry) {
2265 if (tree != root)
2266 first_displayed_entry = NULL;
2267 selected = 0;
2268 break;
2270 tree_scroll_up(&first_displayed_entry, LINES,
2271 entries, tree == root);
2272 break;
2273 case 'j':
2274 case KEY_DOWN:
2275 if (selected < ndisplayed - 1) {
2276 selected++;
2277 break;
2279 tree_scroll_down(&first_displayed_entry, 1,
2280 last_displayed_entry, entries);
2281 break;
2282 case KEY_NPAGE:
2283 tree_scroll_down(&first_displayed_entry, LINES,
2284 last_displayed_entry, entries);
2285 if (SIMPLEQ_NEXT(last_displayed_entry, entry))
2286 break;
2287 /* can't scroll any further; move cursor down */
2288 if (selected < ndisplayed - 1)
2289 selected = ndisplayed - 1;
2290 break;
2291 case KEY_ENTER:
2292 case '\r':
2293 if (selected_entry == NULL) {
2294 struct tog_parent_tree *parent;
2295 case KEY_BACKSPACE:
2296 /* user selected '..' */
2297 if (tree == root)
2298 break;
2299 parent = TAILQ_FIRST(&parents);
2300 TAILQ_REMOVE(&parents, parent, entry);
2301 got_object_tree_close(tree);
2302 tree = parent->tree;
2303 first_displayed_entry =
2304 parent->first_displayed_entry;
2305 selected_entry = parent->selected_entry;
2306 selected = parent->selected;
2307 free(parent);
2308 } else if (S_ISDIR(selected_entry->mode)) {
2309 struct tog_parent_tree *parent;
2310 struct got_tree_object *child;
2311 err = got_object_open_as_tree(
2312 &child, repo, selected_entry->id);
2313 if (err)
2314 goto done;
2315 parent = calloc(1, sizeof(*parent));
2316 if (parent == NULL) {
2317 err = got_error_from_errno();
2318 goto done;
2320 parent->tree = tree;
2321 parent->first_displayed_entry =
2322 first_displayed_entry;
2323 parent->selected_entry = selected_entry;
2324 parent->selected = selected;
2325 TAILQ_INSERT_HEAD(&parents, parent,
2326 entry);
2327 tree = child;
2328 selected = 0;
2329 first_displayed_entry = NULL;
2330 } else if (S_ISREG(selected_entry->mode)) {
2331 err = blame_tree_entry(selected_entry,
2332 &parents, commit_id, repo);
2333 if (err)
2334 goto done;
2336 break;
2337 case KEY_RESIZE:
2338 if (selected > LINES)
2339 selected = ndisplayed - 1;
2340 break;
2341 default:
2342 break;
2345 done:
2346 if (view)
2347 close_view(view);
2348 free(tree_label);
2349 free(commit_id_str);
2350 while (!TAILQ_EMPTY(&parents)) {
2351 struct tog_parent_tree *parent;
2352 parent = TAILQ_FIRST(&parents);
2353 TAILQ_REMOVE(&parents, parent, entry);
2354 free(parent);
2357 if (tree != root)
2358 got_object_tree_close(tree);
2359 return err;
2362 __dead static void
2363 usage_tree(void)
2365 endwin();
2366 fprintf(stderr, "usage: %s tree [-c commit] [repository-path]\n",
2367 getprogname());
2368 exit(1);
2371 static const struct got_error *
2372 cmd_tree(int argc, char *argv[])
2374 const struct got_error *error;
2375 struct got_repository *repo = NULL;
2376 char *repo_path = NULL;
2377 struct got_object_id *commit_id = NULL;
2378 char *commit_id_arg = NULL;
2379 struct got_commit_object *commit = NULL;
2380 struct got_tree_object *tree = NULL;
2381 int ch;
2383 #ifndef PROFILE
2384 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
2385 err(1, "pledge");
2386 #endif
2388 while ((ch = getopt(argc, argv, "c:")) != -1) {
2389 switch (ch) {
2390 case 'c':
2391 commit_id_arg = optarg;
2392 break;
2393 default:
2394 usage();
2395 /* NOTREACHED */
2399 argc -= optind;
2400 argv += optind;
2402 if (argc == 0) {
2403 repo_path = getcwd(NULL, 0);
2404 if (repo_path == NULL)
2405 return got_error_from_errno();
2406 } else if (argc == 1) {
2407 repo_path = realpath(argv[0], NULL);
2408 if (repo_path == NULL)
2409 return got_error_from_errno();
2410 } else
2411 usage_log();
2413 error = got_repo_open(&repo, repo_path);
2414 free(repo_path);
2415 if (error != NULL)
2416 return error;
2418 if (commit_id_arg == NULL) {
2419 error = get_head_commit_id(&commit_id, repo);
2420 if (error != NULL)
2421 goto done;
2422 } else {
2423 struct got_object *obj;
2424 error = got_object_open_by_id_str(&obj, repo, commit_id_arg);
2425 if (error == NULL) {
2426 commit_id = got_object_get_id(obj);
2427 if (commit_id == NULL)
2428 error = got_error_from_errno();
2431 if (error != NULL)
2432 goto done;
2434 error = got_object_open_as_commit(&commit, repo, commit_id);
2435 if (error != NULL)
2436 goto done;
2438 error = got_object_open_as_tree(&tree, repo, commit->tree_id);
2439 if (error != NULL)
2440 goto done;
2442 error = show_tree_view(tree, commit_id, repo);
2443 done:
2444 free(commit_id);
2445 if (commit)
2446 got_object_commit_close(commit);
2447 if (tree)
2448 got_object_tree_close(tree);
2449 if (repo)
2450 got_repo_close(repo);
2451 return error;
2453 static void
2454 init_curses(void)
2456 initscr();
2457 cbreak();
2458 noecho();
2459 nonl();
2460 intrflush(stdscr, FALSE);
2461 keypad(stdscr, TRUE);
2462 curs_set(0);
2465 __dead static void
2466 usage(void)
2468 int i;
2470 fprintf(stderr, "usage: %s [-h] [command] [arg ...]\n\n"
2471 "Available commands:\n", getprogname());
2472 for (i = 0; i < nitems(tog_commands); i++) {
2473 struct tog_cmd *cmd = &tog_commands[i];
2474 fprintf(stderr, " %s: %s\n", cmd->name, cmd->descr);
2476 exit(1);
2479 static char **
2480 make_argv(const char *arg0, const char *arg1)
2482 char **argv;
2483 int argc = (arg1 == NULL ? 1 : 2);
2485 argv = calloc(argc, sizeof(char *));
2486 if (argv == NULL)
2487 err(1, "calloc");
2488 argv[0] = strdup(arg0);
2489 if (argv[0] == NULL)
2490 err(1, "calloc");
2491 if (arg1) {
2492 argv[1] = strdup(arg1);
2493 if (argv[1] == NULL)
2494 err(1, "calloc");
2497 return argv;
2500 int
2501 main(int argc, char *argv[])
2503 const struct got_error *error = NULL;
2504 struct tog_cmd *cmd = NULL;
2505 int ch, hflag = 0;
2506 char **cmd_argv = NULL;
2508 setlocale(LC_ALL, "");
2510 while ((ch = getopt(argc, argv, "h")) != -1) {
2511 switch (ch) {
2512 case 'h':
2513 hflag = 1;
2514 break;
2515 default:
2516 usage();
2517 /* NOTREACHED */
2521 argc -= optind;
2522 argv += optind;
2523 optind = 0;
2524 optreset = 1;
2526 if (argc == 0) {
2527 if (hflag)
2528 usage();
2529 /* Build an argument vector which runs a default command. */
2530 cmd = &tog_commands[0];
2531 cmd_argv = make_argv(cmd->name, NULL);
2532 argc = 1;
2533 } else {
2534 int i;
2536 /* Did the user specific a command? */
2537 for (i = 0; i < nitems(tog_commands); i++) {
2538 if (strncmp(tog_commands[i].name, argv[0],
2539 strlen(argv[0])) == 0) {
2540 cmd = &tog_commands[i];
2541 if (hflag)
2542 tog_commands[i].cmd_usage();
2543 break;
2546 if (cmd == NULL) {
2547 /* Did the user specify a repository? */
2548 char *repo_path = realpath(argv[0], NULL);
2549 if (repo_path) {
2550 struct got_repository *repo;
2551 error = got_repo_open(&repo, repo_path);
2552 if (error == NULL)
2553 got_repo_close(repo);
2554 } else
2555 error = got_error_from_errno();
2556 if (error) {
2557 if (hflag) {
2558 fprintf(stderr, "%s: '%s' is not a "
2559 "known command\n", getprogname(),
2560 argv[0]);
2561 usage();
2563 fprintf(stderr, "%s: '%s' is neither a known "
2564 "command nor a path to a repository\n",
2565 getprogname(), argv[0]);
2566 free(repo_path);
2567 return 1;
2569 cmd = &tog_commands[0];
2570 cmd_argv = make_argv(cmd->name, repo_path);
2571 argc = 2;
2572 free(repo_path);
2576 init_curses();
2578 error = cmd->cmd_main(argc, cmd_argv ? cmd_argv : argv);
2579 if (error)
2580 goto done;
2581 done:
2582 endwin();
2583 free(cmd_argv);
2584 if (error)
2585 fprintf(stderr, "%s: %s\n", getprogname(), error->msg);
2586 return 0;