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 static struct tog_view {
86 WINDOW *window;
87 PANEL *panel;
88 } tog_log_view, tog_diff_view, tog_blame_view, tog_tree_view;
90 static const struct got_error *
91 show_diff_view(struct got_object *, struct got_object *,
92 struct got_repository *);
93 static const struct got_error *
94 show_log_view(struct got_object_id *, struct got_repository *, const char *);
95 static const struct got_error *
96 show_blame_view(const char *, struct got_object_id *, struct got_repository *);
97 static const struct got_error *
98 show_tree_view(struct got_tree_object *, struct got_object_id *,
99 struct got_repository *);
101 __dead static void
102 usage_log(void)
104 endwin();
105 fprintf(stderr, "usage: %s log [-c commit] [-r repository-path] [path]\n",
106 getprogname());
107 exit(1);
110 /* Create newly allocated wide-character string equivalent to a byte string. */
111 static const struct got_error *
112 mbs2ws(wchar_t **ws, size_t *wlen, const char *s)
114 char *vis = NULL;
115 const struct got_error *err = NULL;
117 *ws = NULL;
118 *wlen = mbstowcs(NULL, s, 0);
119 if (*wlen == (size_t)-1) {
120 int vislen;
121 if (errno != EILSEQ)
122 return got_error_from_errno();
124 /* byte string invalid in current encoding; try to "fix" it */
125 err = got_mbsavis(&vis, &vislen, s);
126 if (err)
127 return err;
128 *wlen = mbstowcs(NULL, vis, 0);
129 if (*wlen == (size_t)-1) {
130 err = got_error_from_errno(); /* give up */
131 goto done;
135 *ws = calloc(*wlen + 1, sizeof(*ws));
136 if (*ws == NULL) {
137 err = got_error_from_errno();
138 goto done;
141 if (mbstowcs(*ws, vis ? vis : s, *wlen) != *wlen)
142 err = got_error_from_errno();
143 done:
144 free(vis);
145 if (err) {
146 free(*ws);
147 *ws = NULL;
148 *wlen = 0;
150 return err;
153 /* Format a line for display, ensuring that it won't overflow a width limit. */
154 static const struct got_error *
155 format_line(wchar_t **wlinep, int *widthp, const char *line, int wlimit)
157 const struct got_error *err = NULL;
158 int cols = 0;
159 wchar_t *wline = NULL;
160 size_t wlen;
161 int i;
163 *wlinep = NULL;
164 *widthp = 0;
166 err = mbs2ws(&wline, &wlen, line);
167 if (err)
168 return err;
170 i = 0;
171 while (i < wlen && cols < wlimit) {
172 int width = wcwidth(wline[i]);
173 switch (width) {
174 case 0:
175 i++;
176 break;
177 case 1:
178 case 2:
179 if (cols + width <= wlimit) {
180 cols += width;
181 i++;
183 break;
184 case -1:
185 if (wline[i] == L'\t')
186 cols += TABSIZE - ((cols + 1) % TABSIZE);
187 i++;
188 break;
189 default:
190 err = got_error_from_errno();
191 goto done;
194 wline[i] = L'\0';
195 if (widthp)
196 *widthp = cols;
197 done:
198 if (err)
199 free(wline);
200 else
201 *wlinep = wline;
202 return err;
205 static const struct got_error *
206 draw_commit(struct got_commit_object *commit, struct got_object_id *id)
208 const struct got_error *err = NULL;
209 char datebuf[10]; /* YY-MM-DD + SPACE + NUL */
210 char *logmsg0 = NULL, *logmsg = NULL;
211 char *author0 = NULL, *author = NULL;
212 wchar_t *wlogmsg = NULL, *wauthor = NULL;
213 int author_width, logmsg_width;
214 char *newline, *smallerthan;
215 char *line = NULL;
216 int col, limit;
217 static const size_t date_display_cols = 9;
218 static const size_t author_display_cols = 16;
219 const int avail = COLS;
221 if (strftime(datebuf, sizeof(datebuf), "%g/%m/%d ", &commit->tm_committer)
222 >= sizeof(datebuf))
223 return got_error(GOT_ERR_NO_SPACE);
225 if (avail < date_display_cols)
226 limit = MIN(sizeof(datebuf) - 1, avail);
227 else
228 limit = MIN(date_display_cols, sizeof(datebuf) - 1);
229 waddnstr(tog_log_view.window, datebuf, limit);
230 col = limit + 1;
231 if (col > avail)
232 goto done;
234 author0 = strdup(commit->author);
235 if (author0 == NULL) {
236 err = got_error_from_errno();
237 goto done;
239 author = author0;
240 smallerthan = strchr(author, '<');
241 if (smallerthan)
242 *smallerthan = '\0';
243 else {
244 char *at = strchr(author, '@');
245 if (at)
246 *at = '\0';
248 limit = avail - col;
249 err = format_line(&wauthor, &author_width, author, limit);
250 if (err)
251 goto done;
252 waddwstr(tog_log_view.window, wauthor);
253 col += author_width;
254 while (col <= avail && author_width < author_display_cols + 1) {
255 waddch(tog_log_view.window, ' ');
256 col++;
257 author_width++;
259 if (col > avail)
260 goto done;
262 logmsg0 = strdup(commit->logmsg);
263 if (logmsg0 == NULL) {
264 err = got_error_from_errno();
265 goto done;
267 logmsg = logmsg0;
268 while (*logmsg == '\n')
269 logmsg++;
270 newline = strchr(logmsg, '\n');
271 if (newline)
272 *newline = '\0';
273 limit = avail - col;
274 err = format_line(&wlogmsg, &logmsg_width, logmsg, limit);
275 if (err)
276 goto done;
277 waddwstr(tog_log_view.window, wlogmsg);
278 col += logmsg_width;
279 while (col <= avail) {
280 waddch(tog_log_view.window, ' ');
281 col++;
283 done:
284 free(logmsg0);
285 free(wlogmsg);
286 free(author0);
287 free(wauthor);
288 free(line);
289 return err;
292 struct commit_queue_entry {
293 TAILQ_ENTRY(commit_queue_entry) entry;
294 struct got_object_id *id;
295 struct got_commit_object *commit;
296 };
297 TAILQ_HEAD(commit_queue_head, commit_queue_entry);
298 struct commit_queue {
299 int ncommits;
300 struct commit_queue_head head;
301 };
303 static struct commit_queue_entry *
304 alloc_commit_queue_entry(struct got_commit_object *commit,
305 struct got_object_id *id)
307 struct commit_queue_entry *entry;
309 entry = calloc(1, sizeof(*entry));
310 if (entry == NULL)
311 return NULL;
313 entry->id = id;
314 entry->commit = commit;
315 return entry;
318 static void
319 pop_commit(struct commit_queue *commits)
321 struct commit_queue_entry *entry;
323 entry = TAILQ_FIRST(&commits->head);
324 TAILQ_REMOVE(&commits->head, entry, entry);
325 got_object_commit_close(entry->commit);
326 commits->ncommits--;
327 /* Don't free entry->id! It is owned by the commit graph. */
328 free(entry);
331 static void
332 free_commits(struct commit_queue *commits)
334 while (!TAILQ_EMPTY(&commits->head))
335 pop_commit(commits);
338 static const struct got_error *
339 queue_commits(struct got_commit_graph *graph, struct commit_queue *commits,
340 struct got_object_id *start_id, int minqueue, int init,
341 struct got_repository *repo, const char *path)
343 const struct got_error *err = NULL;
344 struct got_object_id *id;
345 struct commit_queue_entry *entry;
346 int nfetched, nqueued = 0, found_obj = 0;
348 err = got_commit_graph_iter_start(graph, start_id);
349 if (err)
350 return err;
352 entry = TAILQ_LAST(&commits->head, commit_queue_head);
353 if (entry && got_object_id_cmp(entry->id, start_id) == 0) {
354 int nfetched;
356 /* Start ID's commit is already on the queue; skip over it. */
357 err = got_commit_graph_iter_next(&id, graph);
358 if (err && err->code != GOT_ERR_ITER_NEED_MORE)
359 return err;
361 err = got_commit_graph_fetch_commits(&nfetched, graph, 1, repo);
362 if (err)
363 return err;
366 while (1) {
367 struct got_commit_object *commit;
369 err = got_commit_graph_iter_next(&id, graph);
370 if (err) {
371 if (err->code != GOT_ERR_ITER_NEED_MORE)
372 break;
373 if (nqueued >= minqueue) {
374 err = NULL;
375 break;
377 err = got_commit_graph_fetch_commits(&nfetched,
378 graph, 1, repo);
379 if (err)
380 return err;
381 continue;
383 if (id == NULL)
384 break;
386 err = got_object_open_as_commit(&commit, repo, id);
387 if (err)
388 break;
390 if (path) {
391 struct got_object *obj;
392 struct got_object_qid *pid;
393 int changed = 0;
395 err = got_object_open_by_path(&obj, repo, id, path);
396 if (err) {
397 if (err->code == GOT_ERR_NO_OBJ &&
398 (found_obj || !init)) {
399 /* History stops here. */
400 err = got_error(GOT_ERR_ITER_COMPLETED);
402 break;
404 found_obj = 1;
406 pid = SIMPLEQ_FIRST(&commit->parent_ids);
407 if (pid != NULL) {
408 struct got_object *pobj;
409 err = got_object_open_by_path(&pobj, repo,
410 pid->id, path);
411 if (err) {
412 if (err->code != GOT_ERR_NO_OBJ) {
413 got_object_close(obj);
414 break;
416 err = NULL;
417 changed = 1;
418 } else {
419 changed = (got_object_id_cmp(
420 got_object_get_id(obj),
421 got_object_get_id(pobj)) != 0);
422 got_object_close(pobj);
425 if (!changed) {
426 got_object_commit_close(commit);
427 continue;
431 entry = alloc_commit_queue_entry(commit, id);
432 if (entry == NULL) {
433 err = got_error_from_errno();
434 break;
436 TAILQ_INSERT_TAIL(&commits->head, entry, entry);
437 nqueued++;
438 commits->ncommits++;
441 return err;
444 static const struct got_error *
445 fetch_next_commit(struct commit_queue_entry **pentry,
446 struct commit_queue_entry *entry, struct commit_queue *commits,
447 struct got_commit_graph *graph, struct got_repository *repo,
448 const char *path)
450 const struct got_error *err = NULL;
452 *pentry = NULL;
454 err = queue_commits(graph, commits, entry->id, 1, 0, repo, path);
455 if (err)
456 return err;
458 /* Next entry to display should now be available. */
459 *pentry = TAILQ_NEXT(entry, entry);
460 if (*pentry == NULL)
461 return got_error(GOT_ERR_NO_OBJ);
463 return NULL;
466 static const struct got_error *
467 get_head_commit_id(struct got_object_id **head_id, struct got_repository *repo)
469 const struct got_error *err = NULL;
470 struct got_reference *head_ref;
472 *head_id = NULL;
474 err = got_ref_open(&head_ref, repo, GOT_REF_HEAD);
475 if (err)
476 return err;
478 err = got_ref_resolve(head_id, repo, head_ref);
479 got_ref_close(head_ref);
480 if (err) {
481 *head_id = NULL;
482 return err;
485 return NULL;
488 static const struct got_error *
489 draw_commits(struct commit_queue_entry **last,
490 struct commit_queue_entry **selected, struct commit_queue_entry *first,
491 int selected_idx, int limit, const char *path)
493 const struct got_error *err = NULL;
494 struct commit_queue_entry *entry;
495 int ncommits, width;
496 char *id_str, *header;
497 wchar_t *wline;
499 entry = first;
500 *selected = NULL;
501 ncommits = 0;
502 while (entry) {
503 if (++ncommits - 1 == selected_idx) {
504 *selected = entry;
505 break;
507 entry = TAILQ_NEXT(entry, entry);
509 if (*selected == NULL)
510 return got_error(GOT_ERR_RANGE);
512 err = got_object_id_str(&id_str, (*selected)->id);
513 if (err)
514 return err;
516 if (path) {
517 if (asprintf(&header, "commit: %s [%s]", id_str, path) == -1) {
518 err = got_error_from_errno();
519 free(id_str);
520 return err;
522 } else if (asprintf(&header, "commit: %s", id_str) == -1) {
523 err = got_error_from_errno();
524 free(id_str);
525 return err;
527 free(id_str);
528 err = format_line(&wline, &width, header, COLS);
529 if (err) {
530 free(header);
531 return err;
533 free(header);
535 werase(tog_log_view.window);
537 waddwstr(tog_log_view.window, wline);
538 if (width < COLS)
539 waddch(tog_log_view.window, '\n');
540 free(wline);
541 if (limit <= 1)
542 return NULL;
544 entry = first;
545 *last = first;
546 ncommits = 0;
547 while (entry) {
548 if (ncommits >= limit - 1)
549 break;
550 if (ncommits == selected_idx) {
551 wstandout(tog_log_view.window);
552 *selected = entry;
554 err = draw_commit(entry->commit, entry->id);
555 if (ncommits == selected_idx)
556 wstandend(tog_log_view.window);
557 if (err)
558 break;
559 ncommits++;
560 *last = entry;
561 entry = TAILQ_NEXT(entry, entry);
564 update_panels();
565 doupdate();
567 return err;
570 static void
571 scroll_up(struct commit_queue_entry **first_displayed_entry, int maxscroll,
572 struct commit_queue *commits)
574 struct commit_queue_entry *entry;
575 int nscrolled = 0;
577 entry = TAILQ_FIRST(&commits->head);
578 if (*first_displayed_entry == entry)
579 return;
581 entry = *first_displayed_entry;
582 while (entry && nscrolled < maxscroll) {
583 entry = TAILQ_PREV(entry, commit_queue_head, entry);
584 if (entry) {
585 *first_displayed_entry = entry;
586 nscrolled++;
591 static const struct got_error *
592 scroll_down(struct commit_queue_entry **first_displayed_entry, int maxscroll,
593 struct commit_queue_entry *last_displayed_entry,
594 struct commit_queue *commits, struct got_commit_graph *graph,
595 struct got_repository *repo, const char *path)
597 const struct got_error *err = NULL;
598 struct commit_queue_entry *pentry;
599 int nscrolled = 0;
601 do {
602 pentry = TAILQ_NEXT(last_displayed_entry, entry);
603 if (pentry == NULL) {
604 err = fetch_next_commit(&pentry, last_displayed_entry,
605 commits, graph, repo, path);
606 if (err || pentry == NULL)
607 break;
609 last_displayed_entry = pentry;
611 pentry = TAILQ_NEXT(*first_displayed_entry, entry);
612 if (pentry == NULL)
613 break;
614 *first_displayed_entry = pentry;
615 } while (++nscrolled < maxscroll);
617 return err;
620 static const struct got_error *
621 show_commit(struct commit_queue_entry *entry, struct got_repository *repo)
623 const struct got_error *err;
624 struct got_object *obj1 = NULL, *obj2 = NULL;
625 struct got_object_qid *parent_id;
627 err = got_object_open(&obj2, repo, entry->id);
628 if (err)
629 return err;
631 parent_id = SIMPLEQ_FIRST(&entry->commit->parent_ids);
632 if (parent_id) {
633 err = got_object_open(&obj1, repo, parent_id->id);
634 if (err)
635 goto done;
638 err = show_diff_view(obj1, obj2, repo);
639 done:
640 if (obj1)
641 got_object_close(obj1);
642 if (obj2)
643 got_object_close(obj2);
644 return err;
647 static const struct got_error *
648 browse_commit(struct commit_queue_entry *entry, struct got_repository *repo)
650 const struct got_error *err = NULL;
651 struct got_tree_object *tree;
653 err = got_object_open_as_tree(&tree, repo, entry->commit->tree_id);
654 if (err)
655 return err;
657 err = show_tree_view(tree, entry->id, repo);
658 got_object_tree_close(tree);
659 return err;
662 static const struct got_error *
663 show_log_view(struct got_object_id *start_id, struct got_repository *repo,
664 const char *path)
666 const struct got_error *err = NULL;
667 struct got_object_id *head_id = NULL;
668 int ch, done = 0, selected = 0, nfetched;
669 struct got_commit_graph *graph = NULL;
670 struct commit_queue commits;
671 struct commit_queue_entry *first_displayed_entry = NULL;
672 struct commit_queue_entry *last_displayed_entry = NULL;
673 struct commit_queue_entry *selected_entry = NULL;
674 struct commit_queue_entry *entry;
675 char *in_repo_path = NULL;
677 err = got_repo_map_path(&in_repo_path, repo, path);
678 if (err != NULL)
679 goto done;
681 if (tog_log_view.window == NULL) {
682 tog_log_view.window = newwin(0, 0, 0, 0);
683 if (tog_log_view.window == NULL)
684 return got_error_from_errno();
685 keypad(tog_log_view.window, TRUE);
687 if (tog_log_view.panel == NULL) {
688 tog_log_view.panel = new_panel(tog_log_view.window);
689 if (tog_log_view.panel == NULL)
690 return got_error_from_errno();
691 } else
692 show_panel(tog_log_view.panel);
694 err = get_head_commit_id(&head_id, repo);
695 if (err)
696 return err;
698 TAILQ_INIT(&commits.head);
699 commits.ncommits = 0;
701 err = got_commit_graph_open(&graph, head_id, 0, repo);
702 if (err)
703 goto done;
705 /* Populate commit graph with a sufficient number of commits. */
706 err = got_commit_graph_fetch_commits_up_to(&nfetched, graph, start_id,
707 repo);
708 if (err)
709 goto done;
711 /*
712 * Open the initial batch of commits, sorted in commit graph order.
713 * We keep all commits open throughout the lifetime of the log view
714 * in order to avoid having to re-fetch commits from disk while
715 * updating the display.
716 */
717 err = queue_commits(graph, &commits, head_id, LINES, 1, repo,
718 in_repo_path);
719 if (err && err->code != GOT_ERR_ITER_COMPLETED)
720 goto done;
722 /*
723 * Find entry corresponding to the first commit to display.
724 * if both a path and start commit was specified, the first commit
725 * shown should be a commit <= start_commit which modified the path.
726 */
727 if (in_repo_path) {
728 struct got_object_id *id;
730 err = got_commit_graph_iter_start(graph, start_id);
731 if (err)
732 return err;
733 do {
734 err = got_commit_graph_iter_next(&id, graph);
735 if (err)
736 goto done;
737 if (id == NULL) {
738 err = got_error(GOT_ERR_NO_OBJ);
739 goto done;
741 /*
742 * The graph contains all commits. The commit queue
743 * contains a subset of commits filtered by path.
744 */
745 TAILQ_FOREACH(entry, &commits.head, entry) {
746 if (got_object_id_cmp(entry->id, id) == 0) {
747 first_displayed_entry = entry;
748 break;
751 } while (first_displayed_entry == NULL);
752 } else {
753 TAILQ_FOREACH(entry, &commits.head, entry) {
754 if (got_object_id_cmp(entry->id, start_id) == 0) {
755 first_displayed_entry = entry;
756 break;
760 if (first_displayed_entry == NULL) {
761 err = got_error(GOT_ERR_NO_OBJ);
762 goto done;
765 selected_entry = first_displayed_entry;
766 while (!done) {
767 err = draw_commits(&last_displayed_entry, &selected_entry,
768 first_displayed_entry, selected, LINES, in_repo_path);
769 if (err)
770 goto done;
772 nodelay(stdscr, FALSE);
773 ch = wgetch(tog_log_view.window);
774 nodelay(stdscr, TRUE);
775 switch (ch) {
776 case ERR:
777 if (errno) {
778 err = got_error_from_errno();
779 goto done;
781 break;
782 case 'q':
783 done = 1;
784 break;
785 case 'k':
786 case KEY_UP:
787 if (selected > 0)
788 selected--;
789 if (selected > 0)
790 break;
791 scroll_up(&first_displayed_entry, 1, &commits);
792 break;
793 case KEY_PPAGE:
794 if (TAILQ_FIRST(&commits.head) ==
795 first_displayed_entry) {
796 selected = 0;
797 break;
799 scroll_up(&first_displayed_entry, LINES,
800 &commits);
801 break;
802 case 'j':
803 case KEY_DOWN:
804 if (selected < MIN(LINES - 2,
805 commits.ncommits - 1)) {
806 selected++;
807 break;
809 err = scroll_down(&first_displayed_entry, 1,
810 last_displayed_entry, &commits, graph,
811 repo, in_repo_path);
812 if (err) {
813 if (err->code != GOT_ERR_ITER_COMPLETED)
814 goto done;
815 err = NULL;
817 break;
818 case KEY_NPAGE: {
819 struct commit_queue_entry *first = first_displayed_entry;
820 err = scroll_down(&first_displayed_entry, LINES,
821 last_displayed_entry, &commits, graph,
822 repo, in_repo_path);
823 if (err) {
824 if (err->code != GOT_ERR_ITER_COMPLETED)
825 goto done;
826 /* can't scroll any further; move cursor down */
827 if (first == first_displayed_entry && selected <
828 MIN(LINES - 2, commits.ncommits - 1)) {
829 selected = MIN(LINES - 2,
830 commits.ncommits - 1);
832 err = NULL;
834 break;
836 case KEY_RESIZE:
837 if (selected > LINES - 1)
838 selected = LINES - 2;
839 break;
840 case KEY_ENTER:
841 case '\r':
842 err = show_commit(selected_entry, repo);
843 if (err)
844 goto done;
845 show_panel(tog_log_view.panel);
846 break;
847 case 't':
848 err = browse_commit(selected_entry, repo);
849 if (err)
850 goto done;
851 show_panel(tog_log_view.panel);
852 break;
853 default:
854 break;
857 done:
858 free(head_id);
859 if (graph)
860 got_commit_graph_close(graph);
861 free_commits(&commits);
862 free(in_repo_path);
863 return err;
866 static const struct got_error *
867 cmd_log(int argc, char *argv[])
869 const struct got_error *error;
870 struct got_repository *repo = NULL;
871 struct got_object_id *start_id = NULL;
872 char *path = NULL, *repo_path = NULL, *cwd = NULL;
873 char *start_commit = NULL;
874 int ch;
876 #ifndef PROFILE
877 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
878 err(1, "pledge");
879 #endif
881 while ((ch = getopt(argc, argv, "c:r:")) != -1) {
882 switch (ch) {
883 case 'c':
884 start_commit = optarg;
885 break;
886 case 'r':
887 repo_path = realpath(optarg, NULL);
888 if (repo_path == NULL)
889 err(1, "-r option");
890 break;
891 default:
892 usage();
893 /* NOTREACHED */
897 argc -= optind;
898 argv += optind;
900 if (argc == 0)
901 path = strdup("");
902 else if (argc == 1)
903 path = strdup(argv[0]);
904 else
905 usage_log();
906 if (path == NULL)
907 return got_error_from_errno();
909 cwd = getcwd(NULL, 0);
910 if (cwd == NULL) {
911 error = got_error_from_errno();
912 goto done;
914 if (repo_path == NULL) {
915 repo_path = strdup(cwd);
916 if (repo_path == NULL) {
917 error = got_error_from_errno();
918 goto done;
922 error = got_repo_open(&repo, repo_path);
923 if (error != NULL)
924 goto done;
926 if (start_commit == NULL) {
927 error = get_head_commit_id(&start_id, repo);
928 if (error != NULL)
929 goto done;
930 } else {
931 struct got_object *obj;
932 error = got_object_open_by_id_str(&obj, repo, start_commit);
933 if (error == NULL) {
934 start_id = got_object_get_id(obj);
935 if (start_id == NULL)
936 error = got_error_from_errno();
937 goto done;
940 if (error != NULL)
941 goto done;
943 error = show_log_view(start_id, repo, path);
944 done:
945 free(repo_path);
946 free(cwd);
947 free(path);
948 free(start_id);
949 if (repo)
950 got_repo_close(repo);
951 return error;
954 __dead static void
955 usage_diff(void)
957 endwin();
958 fprintf(stderr, "usage: %s diff [repository-path] object1 object2\n",
959 getprogname());
960 exit(1);
963 static char *
964 parse_next_line(FILE *f, size_t *len)
966 char *line;
967 size_t linelen;
968 size_t lineno;
969 const char delim[3] = { '\0', '\0', '\0'};
971 line = fparseln(f, &linelen, &lineno, delim, 0);
972 if (len)
973 *len = linelen;
974 return line;
977 static const struct got_error *
978 draw_file(WINDOW *window, FILE *f, int *first_displayed_line,
979 int *last_displayed_line, int *eof, int max_lines)
981 const struct got_error *err;
982 int nlines = 0, nprinted = 0;
983 char *line;
984 size_t len;
985 wchar_t *wline;
986 int width;
988 rewind(f);
989 werase(window);
991 *eof = 0;
992 while (nprinted < max_lines) {
993 line = parse_next_line(f, &len);
994 if (line == NULL) {
995 *eof = 1;
996 break;
998 if (++nlines < *first_displayed_line) {
999 free(line);
1000 continue;
1003 err = format_line(&wline, &width, line, COLS);
1004 if (err) {
1005 free(line);
1006 free(wline);
1007 return err;
1009 waddwstr(window, wline);
1010 if (width < COLS)
1011 waddch(window, '\n');
1012 if (++nprinted == 1)
1013 *first_displayed_line = nlines;
1014 free(line);
1015 free(wline);
1016 wline = NULL;
1018 *last_displayed_line = nlines;
1020 update_panels();
1021 doupdate();
1023 return NULL;
1026 static const struct got_error *
1027 show_diff_view(struct got_object *obj1, struct got_object *obj2,
1028 struct got_repository *repo)
1030 const struct got_error *err;
1031 FILE *f;
1032 int ch, done = 0, first_displayed_line = 1, last_displayed_line = LINES;
1033 int eof, i;
1035 if (obj1 != NULL && obj2 != NULL &&
1036 got_object_get_type(obj1) != got_object_get_type(obj2))
1037 return got_error(GOT_ERR_OBJ_TYPE);
1039 f = got_opentemp();
1040 if (f == NULL)
1041 return got_error_from_errno();
1043 switch (got_object_get_type(obj1 ? obj1 : obj2)) {
1044 case GOT_OBJ_TYPE_BLOB:
1045 err = got_diff_objects_as_blobs(obj1, obj2, repo, f);
1046 break;
1047 case GOT_OBJ_TYPE_TREE:
1048 err = got_diff_objects_as_trees(obj1, obj2, repo, f);
1049 break;
1050 case GOT_OBJ_TYPE_COMMIT:
1051 err = got_diff_objects_as_commits(obj1, obj2, repo, f);
1052 break;
1053 default:
1054 return got_error(GOT_ERR_OBJ_TYPE);
1057 fflush(f);
1059 if (tog_diff_view.window == NULL) {
1060 tog_diff_view.window = newwin(0, 0, 0, 0);
1061 if (tog_diff_view.window == NULL)
1062 return got_error_from_errno();
1063 keypad(tog_diff_view.window, TRUE);
1065 if (tog_diff_view.panel == NULL) {
1066 tog_diff_view.panel = new_panel(tog_diff_view.window);
1067 if (tog_diff_view.panel == NULL)
1068 return got_error_from_errno();
1069 } else
1070 show_panel(tog_diff_view.panel);
1072 while (!done) {
1073 err = draw_file(tog_diff_view.window, f, &first_displayed_line,
1074 &last_displayed_line, &eof, LINES);
1075 if (err)
1076 break;
1077 nodelay(stdscr, FALSE);
1078 ch = wgetch(tog_diff_view.window);
1079 nodelay(stdscr, TRUE);
1080 switch (ch) {
1081 case 'q':
1082 done = 1;
1083 break;
1084 case 'k':
1085 case KEY_UP:
1086 if (first_displayed_line > 1)
1087 first_displayed_line--;
1088 break;
1089 case KEY_PPAGE:
1090 case KEY_BACKSPACE:
1091 i = 0;
1092 while (i++ < LINES - 1 &&
1093 first_displayed_line > 1)
1094 first_displayed_line--;
1095 break;
1096 case 'j':
1097 case KEY_DOWN:
1098 if (!eof)
1099 first_displayed_line++;
1100 break;
1101 case KEY_NPAGE:
1102 case ' ':
1103 i = 0;
1104 while (!eof && i++ < LINES - 1) {
1105 char *line = parse_next_line(f, NULL);
1106 first_displayed_line++;
1107 if (line == NULL)
1108 break;
1110 break;
1111 default:
1112 break;
1115 fclose(f);
1116 return err;
1119 static const struct got_error *
1120 cmd_diff(int argc, char *argv[])
1122 const struct got_error *error = NULL;
1123 struct got_repository *repo = NULL;
1124 struct got_object *obj1 = NULL, *obj2 = NULL;
1125 char *repo_path = NULL;
1126 char *obj_id_str1 = NULL, *obj_id_str2 = NULL;
1127 int ch;
1129 #ifndef PROFILE
1130 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
1131 err(1, "pledge");
1132 #endif
1134 while ((ch = getopt(argc, argv, "")) != -1) {
1135 switch (ch) {
1136 default:
1137 usage();
1138 /* NOTREACHED */
1142 argc -= optind;
1143 argv += optind;
1145 if (argc == 0) {
1146 usage_diff(); /* TODO show local worktree changes */
1147 } else if (argc == 2) {
1148 repo_path = getcwd(NULL, 0);
1149 if (repo_path == NULL)
1150 return got_error_from_errno();
1151 obj_id_str1 = argv[0];
1152 obj_id_str2 = argv[1];
1153 } else if (argc == 3) {
1154 repo_path = realpath(argv[0], NULL);
1155 if (repo_path == NULL)
1156 return got_error_from_errno();
1157 obj_id_str1 = argv[1];
1158 obj_id_str2 = argv[2];
1159 } else
1160 usage_diff();
1162 error = got_repo_open(&repo, repo_path);
1163 free(repo_path);
1164 if (error)
1165 goto done;
1167 error = got_object_open_by_id_str(&obj1, repo, obj_id_str1);
1168 if (error)
1169 goto done;
1171 error = got_object_open_by_id_str(&obj2, repo, obj_id_str2);
1172 if (error)
1173 goto done;
1175 error = show_diff_view(obj1, obj2, repo);
1176 done:
1177 got_repo_close(repo);
1178 if (obj1)
1179 got_object_close(obj1);
1180 if (obj2)
1181 got_object_close(obj2);
1182 return error;
1185 __dead static void
1186 usage_blame(void)
1188 endwin();
1189 fprintf(stderr, "usage: %s blame [-c commit] [repository-path] path\n",
1190 getprogname());
1191 exit(1);
1194 struct tog_blame_line {
1195 int annotated;
1196 struct got_object_id *id;
1199 static const struct got_error *
1200 draw_blame(WINDOW *window, struct got_object_id *id, FILE *f, const char *path,
1201 struct tog_blame_line *lines, int nlines, int blame_complete,
1202 int selected_line, int *first_displayed_line, int *last_displayed_line,
1203 int *eof, int max_lines)
1205 const struct got_error *err;
1206 int lineno = 0, nprinted = 0;
1207 char *line;
1208 size_t len;
1209 wchar_t *wline;
1210 int width, wlimit;
1211 struct tog_blame_line *blame_line;
1212 struct got_object_id *prev_id = NULL;
1213 char *id_str;
1215 err = got_object_id_str(&id_str, id);
1216 if (err)
1217 return err;
1219 rewind(f);
1220 werase(window);
1222 if (asprintf(&line, "commit: %s", id_str) == -1) {
1223 err = got_error_from_errno();
1224 free(id_str);
1225 return err;
1228 err = format_line(&wline, &width, line, COLS);
1229 free(line);
1230 line = NULL;
1231 waddwstr(window, wline);
1232 free(wline);
1233 wline = NULL;
1234 if (width < COLS)
1235 waddch(window, '\n');
1237 if (asprintf(&line, "[%d/%d] %s%s",
1238 *first_displayed_line - 1 + selected_line, nlines,
1239 blame_complete ? "" : "annotating ", path) == -1) {
1240 free(id_str);
1241 return got_error_from_errno();
1243 free(id_str);
1244 err = format_line(&wline, &width, line, COLS);
1245 free(line);
1246 line = NULL;
1247 if (err)
1248 return err;
1249 waddwstr(window, wline);
1250 free(wline);
1251 wline = NULL;
1252 if (width < COLS)
1253 waddch(window, '\n');
1255 *eof = 0;
1256 while (nprinted < max_lines - 2) {
1257 line = parse_next_line(f, &len);
1258 if (line == NULL) {
1259 *eof = 1;
1260 break;
1262 if (++lineno < *first_displayed_line) {
1263 free(line);
1264 continue;
1267 wlimit = COLS < 9 ? 0 : COLS - 9;
1268 err = format_line(&wline, &width, line, wlimit);
1269 if (err) {
1270 free(line);
1271 return err;
1274 if (nprinted == selected_line - 1)
1275 wstandout(window);
1277 blame_line = &lines[lineno - 1];
1278 if (blame_line->annotated && prev_id &&
1279 got_object_id_cmp(prev_id, blame_line->id) == 0)
1280 waddstr(window, " ");
1281 else if (blame_line->annotated) {
1282 char *id_str;
1283 err = got_object_id_str(&id_str, blame_line->id);
1284 if (err) {
1285 free(line);
1286 free(wline);
1287 return err;
1289 wprintw(window, "%.8s ", id_str);
1290 free(id_str);
1291 prev_id = blame_line->id;
1292 } else {
1293 waddstr(window, "........ ");
1294 prev_id = NULL;
1297 waddwstr(window, wline);
1298 while (width < wlimit) {
1299 waddch(window, ' '); /* width == wlimit - 1 ? '\n' : ' '); */
1300 width++;
1302 if (nprinted == selected_line - 1)
1303 wstandend(window);
1304 if (++nprinted == 1)
1305 *first_displayed_line = lineno;
1306 free(line);
1307 free(wline);
1308 wline = NULL;
1310 *last_displayed_line = lineno;
1312 update_panels();
1313 doupdate();
1315 return NULL;
1318 struct tog_blame_cb_args {
1319 pthread_mutex_t *mutex;
1320 struct tog_blame_line *lines; /* one per line */
1321 int nlines;
1323 struct got_object_id *commit_id;
1324 FILE *f;
1325 const char *path;
1326 int *first_displayed_line;
1327 int *last_displayed_line;
1328 int *selected_line;
1329 int *quit;
1332 static const struct got_error *
1333 blame_cb(void *arg, int nlines, int lineno, struct got_object_id *id)
1335 const struct got_error *err = NULL;
1336 struct tog_blame_cb_args *a = arg;
1337 struct tog_blame_line *line;
1338 int eof;
1340 if (nlines != a->nlines ||
1341 (lineno != -1 && lineno < 1) || lineno > a->nlines)
1342 return got_error(GOT_ERR_RANGE);
1344 if (pthread_mutex_lock(a->mutex) != 0)
1345 return got_error_from_errno();
1347 if (*a->quit) { /* user has quit the blame view */
1348 err = got_error(GOT_ERR_ITER_COMPLETED);
1349 goto done;
1352 if (lineno == -1)
1353 goto done; /* no change in this commit */
1355 line = &a->lines[lineno - 1];
1356 if (line->annotated)
1357 goto done;
1359 line->id = got_object_id_dup(id);
1360 if (line->id == NULL) {
1361 err = got_error_from_errno();
1362 goto done;
1364 line->annotated = 1;
1366 err = draw_blame(tog_blame_view.window, a->commit_id, a->f, a->path,
1367 a->lines, a->nlines, 0, *a->selected_line, a->first_displayed_line,
1368 a->last_displayed_line, &eof, LINES);
1369 done:
1370 if (pthread_mutex_unlock(a->mutex) != 0)
1371 return got_error_from_errno();
1372 return err;
1375 struct tog_blame_thread_args {
1376 const char *path;
1377 struct got_repository *repo;
1378 struct tog_blame_cb_args *cb_args;
1379 int *complete;
1382 static void *
1383 blame_thread(void *arg)
1385 const struct got_error *err;
1386 struct tog_blame_thread_args *ta = arg;
1387 struct tog_blame_cb_args *a = ta->cb_args;
1388 int eof;
1390 err = got_blame_incremental(ta->path, a->commit_id, ta->repo,
1391 blame_cb, ta->cb_args);
1392 got_repo_close(ta->repo);
1393 ta->repo = NULL;
1394 *ta->complete = 1;
1395 if (err)
1396 return (void *)err;
1398 if (pthread_mutex_lock(a->mutex) != 0)
1399 return (void *)got_error_from_errno();
1401 err = draw_blame(tog_blame_view.window, a->commit_id, a->f, a->path,
1402 a->lines, a->nlines, 1, *a->selected_line, a->first_displayed_line,
1403 a->last_displayed_line, &eof, LINES);
1405 if (pthread_mutex_unlock(a->mutex) != 0 && err == NULL)
1406 err = got_error_from_errno();
1408 return (void *)err;
1411 static struct got_object_id *
1412 get_selected_commit_id(struct tog_blame_line *lines,
1413 int first_displayed_line, int selected_line)
1415 struct tog_blame_line *line;
1417 line = &lines[first_displayed_line - 1 + selected_line - 1];
1418 if (!line->annotated)
1419 return NULL;
1421 return line->id;
1424 static const struct got_error *
1425 open_selected_commit(struct got_object **pobj, struct got_object **obj,
1426 struct tog_blame_line *lines, int first_displayed_line,
1427 int selected_line, struct got_repository *repo)
1429 const struct got_error *err = NULL;
1430 struct got_commit_object *commit = NULL;
1431 struct got_object_id *selected_id;
1432 struct got_object_qid *pid;
1434 *pobj = NULL;
1435 *obj = NULL;
1437 selected_id = get_selected_commit_id(lines,
1438 first_displayed_line, selected_line);
1439 if (selected_id == NULL)
1440 return NULL;
1442 err = got_object_open(obj, repo, selected_id);
1443 if (err)
1444 goto done;
1446 err = got_object_commit_open(&commit, repo, *obj);
1447 if (err)
1448 goto done;
1450 pid = SIMPLEQ_FIRST(&commit->parent_ids);
1451 if (pid) {
1452 err = got_object_open(pobj, repo, pid->id);
1453 if (err)
1454 goto done;
1456 done:
1457 if (commit)
1458 got_object_commit_close(commit);
1459 return err;
1462 struct tog_blame {
1463 FILE *f;
1464 size_t filesize;
1465 struct tog_blame_line *lines;
1466 size_t nlines;
1467 pthread_t thread;
1468 struct tog_blame_thread_args thread_args;
1469 struct tog_blame_cb_args cb_args;
1470 const char *path;
1471 struct got_object_id *commit_id;
1474 static const struct got_error *
1475 stop_blame(struct tog_blame *blame)
1477 const struct got_error *err = NULL;
1478 int i;
1480 if (blame->thread) {
1481 if (pthread_join(blame->thread, (void **)&err) != 0)
1482 err = got_error_from_errno();
1483 if (err && err->code == GOT_ERR_ITER_COMPLETED)
1484 err = NULL;
1485 blame->thread = NULL;
1487 if (blame->thread_args.repo) {
1488 got_repo_close(blame->thread_args.repo);
1489 blame->thread_args.repo = NULL;
1491 if (blame->f) {
1492 fclose(blame->f);
1493 blame->f = NULL;
1495 for (i = 0; i < blame->nlines; i++)
1496 free(blame->lines[i].id);
1497 free(blame->lines);
1498 blame->lines = NULL;
1499 free(blame->commit_id);
1500 blame->commit_id = NULL;
1502 return err;
1505 static const struct got_error *
1506 run_blame(struct tog_blame *blame, pthread_mutex_t *mutex, int *blame_complete,
1507 int *first_displayed_line, int *last_displayed_line,
1508 int *selected_line, int *done, const char *path,
1509 struct got_object_id *commit_id,
1510 struct got_repository *repo)
1512 const struct got_error *err = NULL;
1513 struct got_blob_object *blob = NULL;
1514 struct got_repository *thread_repo = NULL;
1515 struct got_object *obj;
1517 err = got_object_open_by_path(&obj, repo, commit_id, path);
1518 if (err)
1519 goto done;
1520 if (got_object_get_type(obj) != GOT_OBJ_TYPE_BLOB) {
1521 err = got_error(GOT_ERR_OBJ_TYPE);
1522 goto done;
1525 err = got_object_blob_open(&blob, repo, obj, 8192);
1526 if (err)
1527 goto done;
1528 blame->f = got_opentemp();
1529 if (blame->f == NULL) {
1530 err = got_error_from_errno();
1531 goto done;
1533 err = got_object_blob_dump_to_file(&blame->filesize, &blame->nlines,
1534 blame->f, blob);
1535 if (err)
1536 goto done;
1538 blame->lines = calloc(blame->nlines, sizeof(*blame->lines));
1539 if (blame->lines == NULL) {
1540 err = got_error_from_errno();
1541 goto done;
1544 err = got_repo_open(&thread_repo, got_repo_get_path(repo));
1545 if (err)
1546 goto done;
1548 blame->cb_args.lines = blame->lines;
1549 blame->cb_args.nlines = blame->nlines;
1550 blame->cb_args.mutex = mutex;
1551 blame->cb_args.commit_id = got_object_id_dup(commit_id);
1552 if (blame->cb_args.commit_id == NULL) {
1553 err = got_error_from_errno();
1554 goto done;
1556 blame->cb_args.f = blame->f;
1557 blame->cb_args.path = path;
1558 blame->cb_args.first_displayed_line = first_displayed_line;
1559 blame->cb_args.selected_line = selected_line;
1560 blame->cb_args.last_displayed_line = last_displayed_line;
1561 blame->cb_args.quit = done;
1563 blame->thread_args.path = path;
1564 blame->thread_args.repo = thread_repo;
1565 blame->thread_args.cb_args = &blame->cb_args;
1566 blame->thread_args.complete = blame_complete;
1567 *blame_complete = 0;
1569 if (pthread_create(&blame->thread, NULL, blame_thread,
1570 &blame->thread_args) != 0) {
1571 err = got_error_from_errno();
1572 goto done;
1575 done:
1576 if (blob)
1577 got_object_blob_close(blob);
1578 if (obj)
1579 got_object_close(obj);
1580 if (err)
1581 stop_blame(blame);
1582 return err;
1585 static const struct got_error *
1586 show_blame_view(const char *path, struct got_object_id *commit_id,
1587 struct got_repository *repo)
1589 const struct got_error *err = NULL, *thread_err = NULL;
1590 int ch, done = 0, first_displayed_line = 1, last_displayed_line = LINES;
1591 int selected_line = first_displayed_line;
1592 int eof, blame_complete = 0;
1593 struct got_object *obj = NULL, *pobj = NULL;
1594 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
1595 struct tog_blame blame;
1596 int blame_running = 0;
1597 struct got_object_id_queue blamed_commits;
1598 struct got_object_qid *blamed_commit = NULL;
1600 SIMPLEQ_INIT(&blamed_commits);
1602 if (pthread_mutex_init(&mutex, NULL) != 0) {
1603 err = got_error_from_errno();
1604 goto done;
1607 err = got_object_qid_alloc(&blamed_commit, commit_id);
1608 if (err)
1609 goto done;
1610 SIMPLEQ_INSERT_HEAD(&blamed_commits, blamed_commit, entry);
1612 if (tog_blame_view.window == NULL) {
1613 tog_blame_view.window = newwin(0, 0, 0, 0);
1614 if (tog_blame_view.window == NULL)
1615 return got_error_from_errno();
1616 keypad(tog_blame_view.window, TRUE);
1618 if (tog_blame_view.panel == NULL) {
1619 tog_blame_view.panel = new_panel(tog_blame_view.window);
1620 if (tog_blame_view.panel == NULL)
1621 return got_error_from_errno();
1622 } else
1623 show_panel(tog_blame_view.panel);
1625 memset(&blame, 0, sizeof(blame));
1626 err = run_blame(&blame, &mutex, &blame_complete,
1627 &first_displayed_line, &last_displayed_line,
1628 &selected_line, &done, path, blamed_commit->id, repo);
1629 if (err)
1630 return err;
1632 while (!done) {
1633 if (pthread_mutex_lock(&mutex) != 0) {
1634 err = got_error_from_errno();
1635 goto done;
1637 err = draw_blame(tog_blame_view.window, blamed_commit->id,
1638 blame.f, path, blame.lines, blame.nlines, blame_complete,
1639 selected_line, &first_displayed_line, &last_displayed_line,
1640 &eof, LINES);
1641 if (pthread_mutex_unlock(&mutex) != 0) {
1642 err = got_error_from_errno();
1643 goto done;
1645 if (err)
1646 break;
1647 nodelay(stdscr, FALSE);
1648 ch = wgetch(tog_blame_view.window);
1649 nodelay(stdscr, TRUE);
1650 if (pthread_mutex_lock(&mutex) != 0) {
1651 err = got_error_from_errno();
1652 goto done;
1654 switch (ch) {
1655 case 'q':
1656 done = 1;
1657 break;
1658 case 'k':
1659 case KEY_UP:
1660 if (selected_line > 1)
1661 selected_line--;
1662 else if (selected_line == 1 &&
1663 first_displayed_line > 1)
1664 first_displayed_line--;
1665 break;
1666 case KEY_PPAGE:
1667 case KEY_BACKSPACE:
1668 if (first_displayed_line == 1) {
1669 selected_line = 1;
1670 break;
1672 if (first_displayed_line > LINES - 2)
1673 first_displayed_line -= (LINES - 2);
1674 else
1675 first_displayed_line = 1;
1676 break;
1677 case 'j':
1678 case KEY_DOWN:
1679 if (selected_line < LINES - 2 &&
1680 first_displayed_line + selected_line <=
1681 blame.nlines)
1682 selected_line++;
1683 else if (last_displayed_line < blame.nlines)
1684 first_displayed_line++;
1685 break;
1686 case 'b':
1687 case 'p': {
1688 struct got_object_id *id;
1689 id = get_selected_commit_id(blame.lines,
1690 first_displayed_line, selected_line);
1691 if (id == NULL || got_object_id_cmp(id,
1692 blamed_commit->id) == 0)
1693 break;
1694 err = open_selected_commit(&pobj, &obj,
1695 blame.lines, first_displayed_line,
1696 selected_line, repo);
1697 if (err)
1698 break;
1699 if (pobj == NULL && obj == NULL)
1700 break;
1701 if (ch == 'p' && pobj == NULL)
1702 break;
1703 done = 1;
1704 if (pthread_mutex_unlock(&mutex) != 0) {
1705 err = got_error_from_errno();
1706 goto done;
1708 thread_err = stop_blame(&blame);
1709 blame_running = 0;
1710 done = 0;
1711 if (pthread_mutex_lock(&mutex) != 0) {
1712 err = got_error_from_errno();
1713 goto done;
1715 if (thread_err)
1716 break;
1717 id = got_object_get_id(ch == 'b' ? obj : pobj);
1718 got_object_close(obj);
1719 obj = NULL;
1720 if (pobj) {
1721 got_object_close(pobj);
1722 pobj = NULL;
1724 if (id == NULL) {
1725 err = got_error_from_errno();
1726 break;
1728 err = got_object_qid_alloc(&blamed_commit, id);
1729 free(id);
1730 if (err)
1731 goto done;
1732 SIMPLEQ_INSERT_HEAD(&blamed_commits,
1733 blamed_commit, entry);
1734 err = run_blame(&blame, &mutex,
1735 &blame_complete, &first_displayed_line,
1736 &last_displayed_line, &selected_line,
1737 &done, path, blamed_commit->id, repo);
1738 if (err)
1739 break;
1740 blame_running = 1;
1741 break;
1743 case 'B': {
1744 struct got_object_qid *first;
1745 first = SIMPLEQ_FIRST(&blamed_commits);
1746 if (!got_object_id_cmp(first->id, commit_id))
1747 break;
1748 done = 1;
1749 if (pthread_mutex_unlock(&mutex) != 0) {
1750 err = got_error_from_errno();
1751 goto done;
1753 thread_err = stop_blame(&blame);
1754 blame_running = 0;
1755 done = 0;
1756 if (pthread_mutex_lock(&mutex) != 0) {
1757 err = got_error_from_errno();
1758 goto done;
1760 if (thread_err)
1761 break;
1762 SIMPLEQ_REMOVE_HEAD(&blamed_commits, entry);
1763 got_object_qid_free(blamed_commit);
1764 blamed_commit = SIMPLEQ_FIRST(&blamed_commits);
1765 err = run_blame(&blame, &mutex,
1766 &blame_complete, &first_displayed_line,
1767 &last_displayed_line, &selected_line,
1768 &done, path, blamed_commit->id, repo);
1769 if (err)
1770 break;
1771 blame_running = 1;
1772 break;
1774 case KEY_ENTER:
1775 case '\r':
1776 err = open_selected_commit(&pobj, &obj,
1777 blame.lines, first_displayed_line,
1778 selected_line, repo);
1779 if (err)
1780 break;
1781 if (pobj == NULL && obj == NULL)
1782 break;
1783 err = show_diff_view(pobj, obj, repo);
1784 if (pobj) {
1785 got_object_close(pobj);
1786 pobj = NULL;
1788 got_object_close(obj);
1789 obj = NULL;
1790 show_panel(tog_blame_view.panel);
1791 if (err)
1792 break;
1793 break;
1794 case KEY_NPAGE:
1795 case ' ':
1796 if (last_displayed_line >= blame.nlines &&
1797 selected_line < LINES - 2) {
1798 selected_line = MIN(blame.nlines,
1799 LINES - 2);
1800 break;
1802 if (last_displayed_line + LINES - 2 <=
1803 blame.nlines)
1804 first_displayed_line += LINES - 2;
1805 else
1806 first_displayed_line =
1807 blame.nlines - (LINES - 3);
1808 break;
1809 default:
1810 break;
1812 if (pthread_mutex_unlock(&mutex) != 0)
1813 err = got_error_from_errno();
1814 if (err || thread_err)
1815 break;
1817 done:
1818 if (pobj)
1819 got_object_close(pobj);
1820 if (blame_running)
1821 thread_err = stop_blame(&blame);
1822 while (!SIMPLEQ_EMPTY(&blamed_commits)) {
1823 blamed_commit = SIMPLEQ_FIRST(&blamed_commits);
1824 SIMPLEQ_REMOVE_HEAD(&blamed_commits, entry);
1825 got_object_qid_free(blamed_commit);
1827 return thread_err ? thread_err : err;
1830 static const struct got_error *
1831 cmd_blame(int argc, char *argv[])
1833 const struct got_error *error;
1834 struct got_repository *repo = NULL;
1835 char *repo_path = NULL;
1836 char *path = NULL;
1837 struct got_object_id *commit_id = NULL;
1838 char *commit_id_str = NULL;
1839 int ch;
1841 #ifndef PROFILE
1842 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
1843 err(1, "pledge");
1844 #endif
1846 while ((ch = getopt(argc, argv, "c:")) != -1) {
1847 switch (ch) {
1848 case 'c':
1849 commit_id_str = optarg;
1850 break;
1851 default:
1852 usage();
1853 /* NOTREACHED */
1857 argc -= optind;
1858 argv += optind;
1860 if (argc == 0) {
1861 usage_blame();
1862 } else if (argc == 1) {
1863 repo_path = getcwd(NULL, 0);
1864 if (repo_path == NULL)
1865 return got_error_from_errno();
1866 path = argv[0];
1867 } else if (argc == 2) {
1868 repo_path = realpath(argv[0], NULL);
1869 if (repo_path == NULL)
1870 return got_error_from_errno();
1871 path = argv[1];
1872 } else
1873 usage_blame();
1875 error = got_repo_open(&repo, repo_path);
1876 free(repo_path);
1877 if (error != NULL)
1878 return error;
1880 if (commit_id_str == NULL) {
1881 struct got_reference *head_ref;
1882 error = got_ref_open(&head_ref, repo, GOT_REF_HEAD);
1883 if (error != NULL)
1884 goto done;
1885 error = got_ref_resolve(&commit_id, repo, head_ref);
1886 got_ref_close(head_ref);
1887 } else {
1888 struct got_object *obj;
1889 error = got_object_open_by_id_str(&obj, repo, commit_id_str);
1890 if (error != NULL)
1891 goto done;
1892 commit_id = got_object_get_id(obj);
1893 if (commit_id == NULL)
1894 error = got_error_from_errno();
1895 got_object_close(obj);
1897 if (error != NULL)
1898 goto done;
1900 error = show_blame_view(path, commit_id, repo);
1901 done:
1902 free(commit_id);
1903 if (repo)
1904 got_repo_close(repo);
1905 return error;
1908 static const struct got_error *
1909 draw_tree_entries(struct got_tree_entry **first_displayed_entry,
1910 struct got_tree_entry **last_displayed_entry,
1911 struct got_tree_entry **selected_entry, int *ndisplayed,
1912 WINDOW *window, const char *label, int show_ids, const char *parent_path,
1913 const struct got_tree_entries *entries, int selected, int limit, int isroot)
1915 const struct got_error *err = NULL;
1916 struct got_tree_entry *te;
1917 wchar_t *wline;
1918 int width, n;
1920 *ndisplayed = 0;
1922 werase(window);
1924 if (limit == 0)
1925 return NULL;
1927 err = format_line(&wline, &width, label, COLS);
1928 if (err)
1929 return err;
1930 waddwstr(window, wline);
1931 free(wline);
1932 wline = NULL;
1933 if (width < COLS)
1934 waddch(window, '\n');
1935 if (--limit <= 0)
1936 return NULL;
1937 err = format_line(&wline, &width, parent_path, COLS);
1938 if (err)
1939 return err;
1940 waddwstr(window, wline);
1941 free(wline);
1942 wline = NULL;
1943 if (width < COLS)
1944 waddch(window, '\n');
1945 if (--limit <= 0)
1946 return NULL;
1947 waddch(window, '\n');
1948 if (--limit <= 0)
1949 return NULL;
1951 te = SIMPLEQ_FIRST(&entries->head);
1952 if (*first_displayed_entry == NULL) {
1953 if (selected == 0) {
1954 wstandout(window);
1955 *selected_entry = NULL;
1957 waddstr(window, " ..\n"); /* parent directory */
1958 if (selected == 0)
1959 wstandend(window);
1960 (*ndisplayed)++;
1961 if (--limit <= 0)
1962 return NULL;
1963 n = 1;
1964 } else {
1965 n = 0;
1966 while (te != *first_displayed_entry)
1967 te = SIMPLEQ_NEXT(te, entry);
1970 while (te) {
1971 char *line = NULL, *id_str = NULL;
1973 if (show_ids) {
1974 err = got_object_id_str(&id_str, te->id);
1975 if (err)
1976 return got_error_from_errno();
1978 if (asprintf(&line, "%s %s%s", id_str ? id_str : "",
1979 te->name, S_ISDIR(te->mode) ? "/" : "") == -1) {
1980 free(id_str);
1981 return got_error_from_errno();
1983 free(id_str);
1984 err = format_line(&wline, &width, line, COLS);
1985 if (err) {
1986 free(line);
1987 break;
1989 if (n == selected) {
1990 wstandout(window);
1991 *selected_entry = te;
1993 waddwstr(window, wline);
1994 if (width < COLS)
1995 waddch(window, '\n');
1996 if (n == selected)
1997 wstandend(window);
1998 free(line);
1999 free(wline);
2000 wline = NULL;
2001 n++;
2002 (*ndisplayed)++;
2003 *last_displayed_entry = te;
2004 if (--limit <= 0)
2005 break;
2006 te = SIMPLEQ_NEXT(te, entry);
2009 return err;
2012 static void
2013 tree_scroll_up(struct got_tree_entry **first_displayed_entry, int maxscroll,
2014 const struct got_tree_entries *entries, int isroot)
2016 struct got_tree_entry *te, *prev;
2017 int i;
2019 if (*first_displayed_entry == NULL)
2020 return;
2022 te = SIMPLEQ_FIRST(&entries->head);
2023 if (*first_displayed_entry == te) {
2024 if (!isroot)
2025 *first_displayed_entry = NULL;
2026 return;
2029 /* XXX this is stupid... switch to TAILQ? */
2030 for (i = 0; i < maxscroll; i++) {
2031 while (te != *first_displayed_entry) {
2032 prev = te;
2033 te = SIMPLEQ_NEXT(te, entry);
2035 *first_displayed_entry = prev;
2036 te = SIMPLEQ_FIRST(&entries->head);
2038 if (!isroot && te == SIMPLEQ_FIRST(&entries->head) && i < maxscroll)
2039 *first_displayed_entry = NULL;
2042 static void
2043 tree_scroll_down(struct got_tree_entry **first_displayed_entry, int maxscroll,
2044 struct got_tree_entry *last_displayed_entry,
2045 const struct got_tree_entries *entries)
2047 struct got_tree_entry *next;
2048 int n = 0;
2050 if (SIMPLEQ_NEXT(last_displayed_entry, entry) == NULL)
2051 return;
2053 if (*first_displayed_entry)
2054 next = SIMPLEQ_NEXT(*first_displayed_entry, entry);
2055 else
2056 next = SIMPLEQ_FIRST(&entries->head);
2057 while (next) {
2058 *first_displayed_entry = next;
2059 if (++n >= maxscroll)
2060 break;
2061 next = SIMPLEQ_NEXT(next, entry);
2065 struct tog_parent_tree {
2066 TAILQ_ENTRY(tog_parent_tree) entry;
2067 struct got_tree_object *tree;
2068 struct got_tree_entry *first_displayed_entry;
2069 struct got_tree_entry *selected_entry;
2070 int selected;
2073 TAILQ_HEAD(tog_parent_trees, tog_parent_tree);
2075 static const struct got_error *
2076 tree_entry_path(char **path, struct tog_parent_trees *parents,
2077 struct got_tree_entry *te)
2079 const struct got_error *err = NULL;
2080 struct tog_parent_tree *pt;
2081 size_t len = 2; /* for leading slash and NUL */
2083 TAILQ_FOREACH(pt, parents, entry)
2084 len += strlen(pt->selected_entry->name) + 1 /* slash */;
2085 if (te)
2086 len += strlen(te->name);
2088 *path = calloc(1, len);
2089 if (path == NULL)
2090 return got_error_from_errno();
2092 (*path)[0] = '/';
2093 pt = TAILQ_LAST(parents, tog_parent_trees);
2094 while (pt) {
2095 if (strlcat(*path, pt->selected_entry->name, len) >= len) {
2096 err = got_error(GOT_ERR_NO_SPACE);
2097 goto done;
2099 if (strlcat(*path, "/", len) >= len) {
2100 err = got_error(GOT_ERR_NO_SPACE);
2101 goto done;
2103 pt = TAILQ_PREV(pt, tog_parent_trees, entry);
2105 if (te) {
2106 if (strlcat(*path, te->name, len) >= len) {
2107 err = got_error(GOT_ERR_NO_SPACE);
2108 goto done;
2111 done:
2112 if (err) {
2113 free(*path);
2114 *path = NULL;
2116 return err;
2119 static const struct got_error *
2120 blame_tree_entry(struct got_tree_entry *te, struct tog_parent_trees *parents,
2121 struct got_object_id *commit_id, struct got_repository *repo)
2123 const struct got_error *err = NULL;
2124 char *path;
2126 err = tree_entry_path(&path, parents, te);
2127 if (err)
2128 return err;
2130 err = show_blame_view(path, commit_id, repo);
2131 free(path);
2132 return err;
2135 static const struct got_error *
2136 show_tree_view(struct got_tree_object *root, struct got_object_id *commit_id,
2137 struct got_repository *repo)
2139 const struct got_error *err = NULL;
2140 int ch, done = 0, selected = 0, show_ids = 0;
2141 struct got_tree_object *tree = root;
2142 const struct got_tree_entries *entries;
2143 struct got_tree_entry *first_displayed_entry = NULL;
2144 struct got_tree_entry *last_displayed_entry = NULL;
2145 struct got_tree_entry *selected_entry = NULL;
2146 char *commit_id_str = NULL, *tree_label = NULL;
2147 int nentries, ndisplayed;
2148 struct tog_parent_trees parents;
2150 TAILQ_INIT(&parents);
2152 err = got_object_id_str(&commit_id_str, commit_id);
2153 if (err != NULL)
2154 goto done;
2156 if (asprintf(&tree_label, "commit: %s", commit_id_str) == -1) {
2157 err = got_error_from_errno();
2158 goto done;
2161 if (tog_tree_view.window == NULL) {
2162 tog_tree_view.window = newwin(0, 0, 0, 0);
2163 if (tog_tree_view.window == NULL)
2164 return got_error_from_errno();
2165 keypad(tog_tree_view.window, TRUE);
2167 if (tog_tree_view.panel == NULL) {
2168 tog_tree_view.panel = new_panel(tog_tree_view.window);
2169 if (tog_tree_view.panel == NULL)
2170 return got_error_from_errno();
2171 } else
2172 show_panel(tog_tree_view.panel);
2174 entries = got_object_tree_get_entries(root);
2175 first_displayed_entry = SIMPLEQ_FIRST(&entries->head);
2176 while (!done) {
2177 char *parent_path;
2178 entries = got_object_tree_get_entries(tree);
2179 nentries = entries->nentries;
2180 if (tree != root)
2181 nentries++; /* '..' directory */
2183 err = tree_entry_path(&parent_path, &parents, NULL);
2184 if (err)
2185 goto done;
2187 err = draw_tree_entries(&first_displayed_entry,
2188 &last_displayed_entry, &selected_entry, &ndisplayed,
2189 tog_tree_view.window, tree_label, show_ids,
2190 parent_path, entries, selected, LINES, tree == root);
2191 free(parent_path);
2192 if (err)
2193 break;
2195 nodelay(stdscr, FALSE);
2196 ch = wgetch(tog_tree_view.window);
2197 nodelay(stdscr, TRUE);
2198 switch (ch) {
2199 case 'q':
2200 done = 1;
2201 break;
2202 case 'i':
2203 show_ids = !show_ids;
2204 break;
2205 case 'k':
2206 case KEY_UP:
2207 if (selected > 0)
2208 selected--;
2209 if (selected > 0)
2210 break;
2211 tree_scroll_up(&first_displayed_entry, 1,
2212 entries, tree == root);
2213 break;
2214 case KEY_PPAGE:
2215 if (SIMPLEQ_FIRST(&entries->head) ==
2216 first_displayed_entry) {
2217 if (tree != root)
2218 first_displayed_entry = NULL;
2219 selected = 0;
2220 break;
2222 tree_scroll_up(&first_displayed_entry, LINES,
2223 entries, tree == root);
2224 break;
2225 case 'j':
2226 case KEY_DOWN:
2227 if (selected < ndisplayed - 1) {
2228 selected++;
2229 break;
2231 tree_scroll_down(&first_displayed_entry, 1,
2232 last_displayed_entry, entries);
2233 break;
2234 case KEY_NPAGE:
2235 tree_scroll_down(&first_displayed_entry, LINES,
2236 last_displayed_entry, entries);
2237 if (SIMPLEQ_NEXT(last_displayed_entry, entry))
2238 break;
2239 /* can't scroll any further; move cursor down */
2240 if (selected < ndisplayed - 1)
2241 selected = ndisplayed - 1;
2242 break;
2243 case KEY_ENTER:
2244 case '\r':
2245 if (selected_entry == NULL) {
2246 struct tog_parent_tree *parent;
2247 case KEY_BACKSPACE:
2248 /* user selected '..' */
2249 if (tree == root)
2250 break;
2251 parent = TAILQ_FIRST(&parents);
2252 TAILQ_REMOVE(&parents, parent, entry);
2253 got_object_tree_close(tree);
2254 tree = parent->tree;
2255 first_displayed_entry =
2256 parent->first_displayed_entry;
2257 selected_entry = parent->selected_entry;
2258 selected = parent->selected;
2259 free(parent);
2260 } else if (S_ISDIR(selected_entry->mode)) {
2261 struct tog_parent_tree *parent;
2262 struct got_tree_object *child;
2263 err = got_object_open_as_tree(
2264 &child, repo, selected_entry->id);
2265 if (err)
2266 goto done;
2267 parent = calloc(1, sizeof(*parent));
2268 if (parent == NULL) {
2269 err = got_error_from_errno();
2270 goto done;
2272 parent->tree = tree;
2273 parent->first_displayed_entry =
2274 first_displayed_entry;
2275 parent->selected_entry = selected_entry;
2276 parent->selected = selected;
2277 TAILQ_INSERT_HEAD(&parents, parent,
2278 entry);
2279 tree = child;
2280 selected = 0;
2281 first_displayed_entry = NULL;
2282 } else if (S_ISREG(selected_entry->mode)) {
2283 err = blame_tree_entry(selected_entry,
2284 &parents, commit_id, repo);
2285 if (err)
2286 goto done;
2288 break;
2289 case KEY_RESIZE:
2290 if (selected > LINES)
2291 selected = ndisplayed - 1;
2292 break;
2293 default:
2294 break;
2297 done:
2298 free(tree_label);
2299 free(commit_id_str);
2300 while (!TAILQ_EMPTY(&parents)) {
2301 struct tog_parent_tree *parent;
2302 parent = TAILQ_FIRST(&parents);
2303 TAILQ_REMOVE(&parents, parent, entry);
2304 free(parent);
2307 if (tree != root)
2308 got_object_tree_close(tree);
2309 return err;
2312 __dead static void
2313 usage_tree(void)
2315 endwin();
2316 fprintf(stderr, "usage: %s tree [-c commit] [repository-path]\n",
2317 getprogname());
2318 exit(1);
2321 static const struct got_error *
2322 cmd_tree(int argc, char *argv[])
2324 const struct got_error *error;
2325 struct got_repository *repo = NULL;
2326 char *repo_path = NULL;
2327 struct got_object_id *commit_id = NULL;
2328 char *commit_id_arg = NULL;
2329 struct got_commit_object *commit = NULL;
2330 struct got_tree_object *tree = NULL;
2331 int ch;
2333 #ifndef PROFILE
2334 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
2335 err(1, "pledge");
2336 #endif
2338 while ((ch = getopt(argc, argv, "c:")) != -1) {
2339 switch (ch) {
2340 case 'c':
2341 commit_id_arg = optarg;
2342 break;
2343 default:
2344 usage();
2345 /* NOTREACHED */
2349 argc -= optind;
2350 argv += optind;
2352 if (argc == 0) {
2353 repo_path = getcwd(NULL, 0);
2354 if (repo_path == NULL)
2355 return got_error_from_errno();
2356 } else if (argc == 1) {
2357 repo_path = realpath(argv[0], NULL);
2358 if (repo_path == NULL)
2359 return got_error_from_errno();
2360 } else
2361 usage_log();
2363 error = got_repo_open(&repo, repo_path);
2364 free(repo_path);
2365 if (error != NULL)
2366 return error;
2368 if (commit_id_arg == NULL) {
2369 error = get_head_commit_id(&commit_id, repo);
2370 if (error != NULL)
2371 goto done;
2372 } else {
2373 struct got_object *obj;
2374 error = got_object_open_by_id_str(&obj, repo, commit_id_arg);
2375 if (error == NULL) {
2376 commit_id = got_object_get_id(obj);
2377 if (commit_id == NULL)
2378 error = got_error_from_errno();
2381 if (error != NULL)
2382 goto done;
2384 error = got_object_open_as_commit(&commit, repo, commit_id);
2385 if (error != NULL)
2386 goto done;
2388 error = got_object_open_as_tree(&tree, repo, commit->tree_id);
2389 if (error != NULL)
2390 goto done;
2392 error = show_tree_view(tree, commit_id, repo);
2393 done:
2394 free(commit_id);
2395 if (commit)
2396 got_object_commit_close(commit);
2397 if (tree)
2398 got_object_tree_close(tree);
2399 if (repo)
2400 got_repo_close(repo);
2401 return error;
2403 static void
2404 init_curses(void)
2406 initscr();
2407 cbreak();
2408 noecho();
2409 nonl();
2410 intrflush(stdscr, FALSE);
2411 keypad(stdscr, TRUE);
2412 curs_set(0);
2415 __dead static void
2416 usage(void)
2418 int i;
2420 fprintf(stderr, "usage: %s [-h] [command] [arg ...]\n\n"
2421 "Available commands:\n", getprogname());
2422 for (i = 0; i < nitems(tog_commands); i++) {
2423 struct tog_cmd *cmd = &tog_commands[i];
2424 fprintf(stderr, " %s: %s\n", cmd->name, cmd->descr);
2426 exit(1);
2429 static char **
2430 make_argv(const char *arg0, const char *arg1)
2432 char **argv;
2433 int argc = (arg1 == NULL ? 1 : 2);
2435 argv = calloc(argc, sizeof(char *));
2436 if (argv == NULL)
2437 err(1, "calloc");
2438 argv[0] = strdup(arg0);
2439 if (argv[0] == NULL)
2440 err(1, "calloc");
2441 if (arg1) {
2442 argv[1] = strdup(arg1);
2443 if (argv[1] == NULL)
2444 err(1, "calloc");
2447 return argv;
2450 int
2451 main(int argc, char *argv[])
2453 const struct got_error *error = NULL;
2454 struct tog_cmd *cmd = NULL;
2455 int ch, hflag = 0;
2456 char **cmd_argv = NULL;
2458 setlocale(LC_ALL, "");
2460 while ((ch = getopt(argc, argv, "h")) != -1) {
2461 switch (ch) {
2462 case 'h':
2463 hflag = 1;
2464 break;
2465 default:
2466 usage();
2467 /* NOTREACHED */
2471 argc -= optind;
2472 argv += optind;
2473 optind = 0;
2474 optreset = 1;
2476 if (argc == 0) {
2477 if (hflag)
2478 usage();
2479 /* Build an argument vector which runs a default command. */
2480 cmd = &tog_commands[0];
2481 cmd_argv = make_argv(cmd->name, NULL);
2482 argc = 1;
2483 } else {
2484 int i;
2486 /* Did the user specific a command? */
2487 for (i = 0; i < nitems(tog_commands); i++) {
2488 if (strncmp(tog_commands[i].name, argv[0],
2489 strlen(argv[0])) == 0) {
2490 cmd = &tog_commands[i];
2491 if (hflag)
2492 tog_commands[i].cmd_usage();
2493 break;
2496 if (cmd == NULL) {
2497 /* Did the user specify a repository? */
2498 char *repo_path = realpath(argv[0], NULL);
2499 if (repo_path) {
2500 struct got_repository *repo;
2501 error = got_repo_open(&repo, repo_path);
2502 if (error == NULL)
2503 got_repo_close(repo);
2504 } else
2505 error = got_error_from_errno();
2506 if (error) {
2507 if (hflag) {
2508 fprintf(stderr, "%s: '%s' is not a "
2509 "known command\n", getprogname(),
2510 argv[0]);
2511 usage();
2513 fprintf(stderr, "%s: '%s' is neither a known "
2514 "command nor a path to a repository\n",
2515 getprogname(), argv[0]);
2516 free(repo_path);
2517 return 1;
2519 cmd = &tog_commands[0];
2520 cmd_argv = make_argv(cmd->name, repo_path);
2521 argc = 2;
2522 free(repo_path);
2526 init_curses();
2528 error = cmd->cmd_main(argc, cmd_argv ? cmd_argv : argv);
2529 if (error)
2530 goto done;
2531 done:
2532 endwin();
2533 free(cmd_argv);
2534 if (error)
2535 fprintf(stderr, "%s: %s\n", getprogname(), error->msg);
2536 return 0;