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>
19 #include <errno.h>
20 #define _XOPEN_SOURCE_EXTENDED
21 #include <curses.h>
22 #undef _XOPEN_SOURCE_EXTENDED
23 #include <panel.h>
24 #include <locale.h>
25 #include <stdlib.h>
26 #include <stdio.h>
27 #include <getopt.h>
28 #include <string.h>
29 #include <err.h>
30 #include <unistd.h>
31 #include <util.h>
32 #include <limits.h>
33 #include <wchar.h>
35 #include "got_error.h"
36 #include "got_object.h"
37 #include "got_reference.h"
38 #include "got_repository.h"
39 #include "got_diff.h"
40 #include "got_opentemp.h"
42 #ifndef MIN
43 #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b))
44 #endif
46 #ifndef nitems
47 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
48 #endif
50 struct tog_cmd {
51 const char *name;
52 const struct got_error *(*cmd_main)(int, char *[]);
53 void (*cmd_usage)(void);
54 const char *descr;
55 };
57 __dead static void usage(void);
58 __dead static void usage_log(void);
59 __dead static void usage_diff(void);
60 __dead static void usage_blame(void);
62 static const struct got_error* cmd_log(int, char *[]);
63 static const struct got_error* cmd_diff(int, char *[]);
64 static const struct got_error* cmd_blame(int, char *[]);
66 static struct tog_cmd tog_commands[] = {
67 { "log", cmd_log, usage_log,
68 "show repository history" },
69 { "diff", cmd_diff, usage_diff,
70 "compare files and directories" },
71 { "blame", cmd_blame, usage_blame,
72 "show line-by-line file history" },
73 };
75 static struct tog_view {
76 WINDOW *window;
77 PANEL *panel;
78 } tog_log_view, tog_diff_view;
80 static const struct got_error *
81 show_diff_view(struct got_object *, struct got_object *,
82 struct got_repository *);
83 static const struct got_error *
84 show_log_view(struct got_object_id *, struct got_repository *);
86 __dead static void
87 usage_log(void)
88 {
89 endwin();
90 fprintf(stderr, "usage: %s log [-c commit] [repository-path]\n",
91 getprogname());
92 exit(1);
93 }
95 /* Create newly allocated wide-character string equivalent to a byte string. */
96 static const struct got_error *
97 mbs2ws(wchar_t **ws, size_t *wlen, const char *s)
98 {
99 const struct got_error *err = NULL;
101 *ws = NULL;
102 *wlen = mbstowcs(NULL, s, 0);
103 if (*wlen == (size_t)-1)
104 return got_error_from_errno();
106 *ws = calloc(*wlen + 1, sizeof(*ws));
107 if (*ws == NULL)
108 return got_error_from_errno();
110 if (mbstowcs(*ws, s, *wlen) != *wlen)
111 err = got_error_from_errno();
113 if (err) {
114 free(*ws);
115 *ws = NULL;
116 *wlen = 0;
118 return err;
121 /* Format a line for display, ensuring that it won't overflow a width limit. */
122 static const struct got_error *
123 format_line(wchar_t **wlinep, int *widthp, char *line, int wlimit)
125 const struct got_error *err = NULL;
126 int cols = 0;
127 wchar_t *wline = NULL;
128 size_t wlen;
129 int i;
131 *wlinep = NULL;
133 err = mbs2ws(&wline, &wlen, line);
134 if (err)
135 return err;
137 i = 0;
138 while (i < wlen && cols <= wlimit) {
139 int width = wcwidth(wline[i]);
140 switch (width) {
141 case 0:
142 break;
143 case 1:
144 case 2:
145 cols += width;
146 break;
147 case -1:
148 if (wline[i] == L'\t')
149 cols += TABSIZE;
150 break;
151 default:
152 err = got_error_from_errno();
153 goto done;
155 if (cols <= COLS) {
156 i++;
157 if (widthp)
158 *widthp = cols;
161 wline[i] = L'\0';
162 done:
163 if (err)
164 free(wline);
165 else
166 *wlinep = wline;
167 return err;
170 static const struct got_error *
171 draw_commit(struct got_commit_object *commit, struct got_object_id *id)
173 const struct got_error *err = NULL;
174 char *logmsg0 = NULL, *logmsg = NULL;
175 char *author0 = NULL, *author = NULL;
176 wchar_t *wlogmsg = NULL, *wauthor = NULL;
177 int author_width, logmsg_width;
178 char *newline, *smallerthan;
179 char *line = NULL;
180 char *id_str = NULL;
181 size_t id_len;
182 int col, limit;
183 static const size_t id_display_cols = 8;
184 static const size_t author_display_cols = 16;
185 const int avail = COLS;
187 err = got_object_id_str(&id_str, id);
188 if (err)
189 return err;
190 id_len = strlen(id_str);
191 if (avail < id_display_cols) {
192 limit = MIN(id_len, avail);
193 waddnstr(tog_log_view.window, id_str, limit);
194 } else {
195 limit = MIN(id_display_cols, id_len);
196 waddnstr(tog_log_view.window, id_str, limit);
198 col = limit + 1;
199 while (col <= avail && col < id_display_cols + 2) {
200 waddch(tog_log_view.window, ' ');
201 col++;
203 if (col > avail)
204 goto done;
206 author0 = strdup(commit->author);
207 if (author0 == NULL) {
208 err = got_error_from_errno();
209 goto done;
211 author = author0;
212 smallerthan = strchr(author, '<');
213 if (smallerthan)
214 *smallerthan = '\0';
215 else {
216 char *at = strchr(author, '@');
217 if (at)
218 *at = '\0';
220 limit = MIN(avail, author_display_cols);
221 err = format_line(&wauthor, &author_width, author, limit);
222 if (err)
223 goto done;
224 waddwstr(tog_log_view.window, wauthor);
225 col += author_width;
226 while (col <= avail && author_width < author_display_cols + 1) {
227 waddch(tog_log_view.window, ' ');
228 col++;
229 author_width++;
231 if (col > avail)
232 goto done;
234 logmsg0 = strdup(commit->logmsg);
235 if (logmsg0 == NULL) {
236 err = got_error_from_errno();
237 goto done;
239 logmsg = logmsg0;
240 while (*logmsg == '\n')
241 logmsg++;
242 newline = strchr(logmsg, '\n');
243 if (newline)
244 *newline = '\0';
245 limit = avail - col;
246 err = format_line(&wlogmsg, &logmsg_width, logmsg, limit);
247 if (err)
248 goto done;
249 waddwstr(tog_log_view.window, wlogmsg);
250 col += logmsg_width;
251 while (col <= avail) {
252 waddch(tog_log_view.window, ' ');
253 col++;
255 done:
256 free(logmsg0);
257 free(wlogmsg);
258 free(author0);
259 free(wauthor);
260 free(line);
261 free(id_str);
262 return err;
265 struct commit_queue_entry {
266 TAILQ_ENTRY(commit_queue_entry) entry;
267 struct got_object_id *id;
268 struct got_commit_object *commit;
269 };
270 TAILQ_HEAD(commit_queue, commit_queue_entry);
272 static struct commit_queue_entry *
273 alloc_commit_queue_entry(struct got_commit_object *commit,
274 struct got_object_id *id)
276 struct commit_queue_entry *entry;
278 entry = calloc(1, sizeof(*entry));
279 if (entry == NULL)
280 return NULL;
282 entry->id = id;
283 entry->commit = commit;
284 return entry;
287 static void
288 pop_commit(struct commit_queue *commits)
290 struct commit_queue_entry *entry;
292 entry = TAILQ_FIRST(commits);
293 TAILQ_REMOVE(commits, entry, entry);
294 got_object_commit_close(entry->commit);
295 free(entry->id);
296 free(entry);
299 static void
300 free_commits(struct commit_queue *commits)
302 while (!TAILQ_EMPTY(commits))
303 pop_commit(commits);
306 static const struct got_error *
307 fetch_parent_commit(struct commit_queue_entry **pentry,
308 struct commit_queue_entry *entry, struct got_repository *repo)
310 const struct got_error *err = NULL;
311 struct got_object *obj = NULL;
312 struct got_commit_object *commit;
313 struct got_object_id *id;
314 struct got_parent_id *pid;
316 *pentry = NULL;
318 /* Follow the first parent (TODO: handle merge commits). */
319 pid = SIMPLEQ_FIRST(&entry->commit->parent_ids);
320 if (pid == NULL)
321 return NULL;
322 err = got_object_open(&obj, repo, pid->id);
323 if (err)
324 return err;
325 if (got_object_get_type(obj) != GOT_OBJ_TYPE_COMMIT) {
326 err = got_error(GOT_ERR_OBJ_TYPE);
327 got_object_close(obj);
328 return err;
331 err = got_object_commit_open(&commit, repo, obj);
332 got_object_close(obj);
333 if (err)
334 return err;
336 id = got_object_id_dup(pid->id);
337 if (id == NULL) {
338 err = got_error_from_errno();
339 got_object_commit_close(commit);
340 return err;;
343 *pentry = alloc_commit_queue_entry(commit, id);
344 if (*pentry == NULL) {
345 err = got_error_from_errno();
346 got_object_commit_close(commit);
349 return err;;
352 static const struct got_error *
353 get_head_commit_id(struct got_object_id **head_id, struct got_repository *repo)
355 const struct got_error *err = NULL;
356 struct got_reference *head_ref;
358 *head_id = NULL;
360 err = got_ref_open(&head_ref, repo, GOT_REF_HEAD);
361 if (err)
362 return err;
364 err = got_ref_resolve(head_id, repo, head_ref);
365 got_ref_close(head_ref);
366 if (err) {
367 *head_id = NULL;
368 return err;
371 return NULL;
374 static const struct got_error *
375 prepend_commits(int *ncommits, struct commit_queue *commits,
376 struct got_object_id *first_id, struct got_object_id *last_id,
377 int limit, struct got_repository *repo)
379 const struct got_error *err = NULL;
380 struct got_object *first_obj = NULL, *last_obj = NULL;
381 struct got_commit_object *commit = NULL;
382 struct got_object_id *id = NULL;
383 struct commit_queue_entry *entry, *old_head_entry;
385 *ncommits = 0;
387 err = got_object_open(&first_obj, repo, first_id);
388 if (err)
389 goto done;
390 if (got_object_get_type(first_obj) != GOT_OBJ_TYPE_COMMIT) {
391 err = got_error(GOT_ERR_OBJ_TYPE);
392 goto done;
394 err = got_object_open(&last_obj, repo, last_id);
395 if (err)
396 goto done;
397 if (got_object_get_type(last_obj) != GOT_OBJ_TYPE_COMMIT) {
398 err = got_error(GOT_ERR_OBJ_TYPE);
399 goto done;
402 err = got_object_commit_open(&commit, repo, first_obj);
403 if (err)
404 goto done;
406 id = got_object_id_dup(first_id);
407 if (id == NULL) {
408 err = got_error_from_errno();
409 goto done;
412 entry = alloc_commit_queue_entry(commit, id);
413 if (entry == NULL)
414 return got_error_from_errno();
416 old_head_entry = TAILQ_FIRST(commits);
417 if (old_head_entry)
418 TAILQ_INSERT_BEFORE(old_head_entry, entry, entry);
419 else
420 TAILQ_INSERT_HEAD(commits, entry, entry);
422 *ncommits = 1;
424 /*
425 * Fetch parent commits.
426 * XXX If first and last commit aren't ancestrally related this loop
427 * we will keep iterating until a root commit is encountered.
428 */
429 while (1) {
430 struct commit_queue_entry *pentry;
432 err = fetch_parent_commit(&pentry, entry, repo);
433 if (err)
434 goto done;
435 if (pentry == NULL)
436 break;
438 /*
439 * Fill up to old HEAD commit if commit queue was not empty.
440 * We must not leave a gap in history.
441 */
442 if (old_head_entry &&
443 got_object_id_cmp(pentry->id, old_head_entry->id) == 0)
444 break;
446 TAILQ_INSERT_AFTER(commits, entry, pentry, entry);
447 (*ncommits)++;
448 if (*ncommits >= limit)
449 break;
451 /* Fill up to last requested commit if queue was empty. */
452 if (old_head_entry == NULL &&
453 got_object_id_cmp(pentry->id, last_id) == 0)
454 break;
456 entry = pentry;
459 done:
460 if (first_obj)
461 got_object_close(first_obj);
462 if (last_obj)
463 got_object_close(last_obj);
464 return err;
467 static const struct got_error *
468 fetch_commits(struct commit_queue_entry **start_entry,
469 struct got_object_id *start_id, struct commit_queue *commits,
470 int limit, struct got_repository *repo)
472 const struct got_error *err;
473 struct commit_queue_entry *entry;
474 int ncommits = 0;
475 struct got_object_id *head_id = NULL;
477 *start_entry = NULL;
479 err = get_head_commit_id(&head_id, repo);
480 if (err)
481 return err;
483 /* Prepend HEAD commit and all ancestors up to start commit. */
484 err = prepend_commits(&ncommits, commits, head_id, start_id, limit,
485 repo);
486 if (err)
487 return err;
489 if (got_object_id_cmp(head_id, start_id) == 0)
490 *start_entry = TAILQ_FIRST(commits);
491 else
492 *start_entry = TAILQ_LAST(commits, commit_queue);
494 if (ncommits >= limit)
495 return NULL;
497 /* Append more commits from start commit up to the requested limit. */
498 entry = TAILQ_LAST(commits, commit_queue);
499 while (entry && ncommits < limit) {
500 struct commit_queue_entry *pentry;
502 err = fetch_parent_commit(&pentry, entry, repo);
503 if (err)
504 break;
505 if (pentry)
506 TAILQ_INSERT_TAIL(commits, pentry, entry);
507 entry = pentry;
508 ncommits++;
511 if (err)
512 *start_entry = NULL;
513 return err;
516 static const struct got_error *
517 draw_commits(struct commit_queue_entry **last, struct commit_queue_entry **selected,
518 struct commit_queue_entry *first, int selected_idx, int limit)
520 const struct got_error *err = NULL;
521 struct commit_queue_entry *entry;
522 int ncommits = 0;
524 werase(tog_log_view.window);
526 entry = first;
527 *last = first;
528 while (entry) {
529 if (ncommits == limit)
530 break;
531 if (ncommits == selected_idx) {
532 wstandout(tog_log_view.window);
533 *selected = entry;
535 err = draw_commit(entry->commit, entry->id);
536 if (ncommits == selected_idx)
537 wstandend(tog_log_view.window);
538 if (err)
539 break;
540 ncommits++;
541 *last = entry;
542 entry = TAILQ_NEXT(entry, entry);
545 update_panels();
546 doupdate();
548 return err;
551 static void
552 scroll_up(struct commit_queue_entry **first_displayed_entry, int maxscroll,
553 struct commit_queue *commits)
555 struct commit_queue_entry *entry;
556 int nscrolled = 0;
558 entry = TAILQ_FIRST(commits);
559 if (*first_displayed_entry == entry)
560 return;
562 entry = *first_displayed_entry;
563 while (entry && nscrolled < maxscroll) {
564 entry = TAILQ_PREV(entry, commit_queue, entry);
565 if (entry) {
566 *first_displayed_entry = entry;
567 nscrolled++;
572 static const struct got_error *
573 scroll_down(struct commit_queue_entry **first_displayed_entry, int maxscroll,
574 struct commit_queue_entry *last_displayed_entry,
575 struct commit_queue *commits, struct got_repository *repo)
577 const struct got_error *err = NULL;
578 struct commit_queue_entry *pentry;
579 int nscrolled = 0;
581 do {
582 pentry = TAILQ_NEXT(last_displayed_entry, entry);
583 if (pentry == NULL) {
584 err = fetch_parent_commit(&pentry,
585 last_displayed_entry, repo);
586 if (err || pentry == NULL)
587 break;
588 TAILQ_INSERT_TAIL(commits, pentry, entry);
590 last_displayed_entry = pentry;
592 pentry = TAILQ_NEXT(*first_displayed_entry, entry);
593 if (pentry == NULL)
594 break;
595 *first_displayed_entry = pentry;
596 } while (++nscrolled < maxscroll);
598 return err;
601 static int
602 num_parents(struct commit_queue_entry *entry)
604 int nparents = 0;
606 while (entry) {
607 entry = TAILQ_NEXT(entry, entry);
608 nparents++;
611 return nparents;
614 static const struct got_error *
615 show_commit(struct commit_queue_entry *entry, struct got_repository *repo)
617 const struct got_error *err;
618 struct commit_queue_entry *pentry;
619 struct got_object *obj1 = NULL, *obj2 = NULL;
621 err = got_object_open(&obj2, repo, entry->id);
622 if (err)
623 return err;
625 pentry = TAILQ_NEXT(entry, entry);
626 if (pentry == NULL) {
627 err = fetch_parent_commit(&pentry, entry, repo);
628 if (err)
629 return err;
631 if (pentry) {
632 err = got_object_open(&obj1, repo, pentry->id);
633 if (err)
634 goto done;
637 err = show_diff_view(obj1, obj2, repo);
638 done:
639 if (obj1)
640 got_object_close(obj1);
641 if (obj2)
642 got_object_close(obj2);
643 return err;
646 static const struct got_error *
647 show_log_view(struct got_object_id *start_id, struct got_repository *repo)
649 const struct got_error *err = NULL;
650 struct got_object_id *id;
651 int ch, done = 0, selected = 0, nparents;
652 struct commit_queue commits;
653 struct commit_queue_entry *first_displayed_entry = NULL;
654 struct commit_queue_entry *last_displayed_entry = NULL;
655 struct commit_queue_entry *selected_entry = NULL;
657 id = got_object_id_dup(start_id);
658 if (id == NULL)
659 return got_error_from_errno();
661 if (tog_log_view.window == NULL) {
662 tog_log_view.window = newwin(0, 0, 0, 0);
663 if (tog_log_view.window == NULL)
664 return got_error_from_errno();
665 keypad(tog_log_view.window, TRUE);
667 if (tog_log_view.panel == NULL) {
668 tog_log_view.panel = new_panel(tog_log_view.window);
669 if (tog_log_view.panel == NULL)
670 return got_error_from_errno();
671 } else
672 show_panel(tog_log_view.panel);
674 TAILQ_INIT(&commits);
675 err = fetch_commits(&first_displayed_entry, id, &commits, LINES, repo);
676 if (err)
677 goto done;
678 while (!done) {
679 err = draw_commits(&last_displayed_entry, &selected_entry,
680 first_displayed_entry, selected, LINES);
681 if (err)
682 goto done;
684 nodelay(stdscr, FALSE);
685 ch = wgetch(tog_log_view.window);
686 nodelay(stdscr, TRUE);
687 switch (ch) {
688 case ERR:
689 if (errno) {
690 err = got_error_from_errno();
691 goto done;
693 break;
694 case 'q':
695 done = 1;
696 break;
697 case 'k':
698 case KEY_UP:
699 if (selected > 0)
700 selected--;
701 if (selected > 0)
702 break;
703 scroll_up(&first_displayed_entry, 1, &commits);
704 break;
705 case KEY_PPAGE:
706 if (TAILQ_FIRST(&commits) ==
707 first_displayed_entry) {
708 selected = 0;
709 break;
711 scroll_up(&first_displayed_entry, LINES,
712 &commits);
713 break;
714 case 'j':
715 case KEY_DOWN:
716 nparents = num_parents(first_displayed_entry);
717 if (selected < LINES - 1 &&
718 selected < nparents - 1) {
719 selected++;
720 break;
722 err = scroll_down(&first_displayed_entry, 1,
723 last_displayed_entry, &commits, repo);
724 if (err)
725 goto done;
726 break;
727 case KEY_NPAGE:
728 err = scroll_down(&first_displayed_entry, LINES,
729 last_displayed_entry, &commits, repo);
730 if (err)
731 goto done;
732 if (last_displayed_entry->commit->nparents > 0)
733 break;
734 /* can't scroll any further; move cursor down */
735 nparents = num_parents(first_displayed_entry);
736 if (selected < LINES - 1 ||
737 selected < nparents - 1)
738 selected = MIN(LINES - 1, nparents - 1);
739 break;
740 case KEY_RESIZE:
741 if (selected > LINES)
742 selected = LINES - 1;
743 break;
744 case KEY_ENTER:
745 case '\r':
746 err = show_commit(selected_entry, repo);
747 if (err)
748 break;
749 show_panel(tog_log_view.panel);
750 break;
751 default:
752 break;
755 done:
756 free_commits(&commits);
757 return err;
760 static const struct got_error *
761 cmd_log(int argc, char *argv[])
763 const struct got_error *error;
764 struct got_repository *repo;
765 struct got_object_id *start_id = NULL;
766 char *repo_path = NULL;
767 char *start_commit = NULL;
768 int ch;
770 #ifndef PROFILE
771 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
772 err(1, "pledge");
773 #endif
775 while ((ch = getopt(argc, argv, "c:")) != -1) {
776 switch (ch) {
777 case 'c':
778 start_commit = optarg;
779 break;
780 default:
781 usage();
782 /* NOTREACHED */
786 argc -= optind;
787 argv += optind;
789 if (argc == 0) {
790 repo_path = getcwd(NULL, 0);
791 if (repo_path == NULL)
792 return got_error_from_errno();
793 } else if (argc == 1) {
794 repo_path = realpath(argv[0], NULL);
795 if (repo_path == NULL)
796 return got_error_from_errno();
797 } else
798 usage_log();
800 error = got_repo_open(&repo, repo_path);
801 free(repo_path);
802 if (error != NULL)
803 return error;
805 if (start_commit == NULL) {
806 error = get_head_commit_id(&start_id, repo);
807 if (error != NULL)
808 return error;
809 } else {
810 struct got_object *obj;
811 error = got_object_open_by_id_str(&obj, repo, start_commit);
812 if (error == NULL) {
813 start_id = got_object_get_id(obj);
814 if (start_id == NULL)
815 error = got_error_from_errno();
818 if (error != NULL)
819 return error;
820 error = show_log_view(start_id, repo);
821 free(start_id);
822 got_repo_close(repo);
823 return error;
826 __dead static void
827 usage_diff(void)
829 endwin();
830 fprintf(stderr, "usage: %s diff [repository-path] object1 object2\n",
831 getprogname());
832 exit(1);
835 static char *
836 parse_next_line(FILE *f, size_t *len)
838 char *line;
839 size_t linelen;
840 size_t lineno;
841 const char delim[3] = { '\0', '\0', '\0'};
843 line = fparseln(f, &linelen, &lineno, delim, 0);
844 if (len)
845 *len = linelen;
846 return line;
849 static const struct got_error *
850 draw_diff(FILE *f, int *first_displayed_line, int *last_displayed_line,
851 int *eof, int max_lines)
853 const struct got_error *err;
854 int nlines = 0, nprinted = 0;
855 char *line;
856 size_t len;
857 wchar_t *wline;
858 int width;
860 rewind(f);
861 werase(tog_diff_view.window);
863 *eof = 0;
864 while (nprinted < max_lines) {
865 line = parse_next_line(f, &len);
866 if (line == NULL) {
867 *eof = 1;
868 break;
870 if (++nlines < *first_displayed_line) {
871 free(line);
872 continue;
875 err = format_line(&wline, &width, line, COLS);
876 if (err) {
877 free(line);
878 return err;
880 waddwstr(tog_diff_view.window, wline);
881 if (width < COLS)
882 waddch(tog_diff_view.window, '\n');
883 if (++nprinted == 1)
884 *first_displayed_line = nlines;
885 free(line);
887 *last_displayed_line = nlines;
889 update_panels();
890 doupdate();
892 return NULL;
895 static const struct got_error *
896 show_diff_view(struct got_object *obj1, struct got_object *obj2,
897 struct got_repository *repo)
899 const struct got_error *err;
900 FILE *f;
901 int ch, done = 0, first_displayed_line = 1, last_displayed_line = LINES;
902 int eof, i;
904 if (obj1 != NULL && obj2 != NULL &&
905 got_object_get_type(obj1) != got_object_get_type(obj2))
906 return got_error(GOT_ERR_OBJ_TYPE);
908 f = got_opentemp();
909 if (f == NULL)
910 return got_error_from_errno();
912 switch (got_object_get_type(obj1 ? obj1 : obj2)) {
913 case GOT_OBJ_TYPE_BLOB:
914 err = got_diff_objects_as_blobs(obj1, obj2, repo, f);
915 break;
916 case GOT_OBJ_TYPE_TREE:
917 err = got_diff_objects_as_trees(obj1, obj2, repo, f);
918 break;
919 case GOT_OBJ_TYPE_COMMIT:
920 err = got_diff_objects_as_commits(obj1, obj2, repo, f);
921 break;
922 default:
923 return got_error(GOT_ERR_OBJ_TYPE);
926 fflush(f);
928 if (tog_diff_view.window == NULL) {
929 tog_diff_view.window = newwin(0, 0, 0, 0);
930 if (tog_diff_view.window == NULL)
931 return got_error_from_errno();
932 keypad(tog_diff_view.window, TRUE);
934 if (tog_diff_view.panel == NULL) {
935 tog_diff_view.panel = new_panel(tog_diff_view.window);
936 if (tog_diff_view.panel == NULL)
937 return got_error_from_errno();
938 } else
939 show_panel(tog_diff_view.panel);
941 while (!done) {
942 err = draw_diff(f, &first_displayed_line, &last_displayed_line,
943 &eof, LINES);
944 if (err)
945 break;
946 nodelay(stdscr, FALSE);
947 ch = wgetch(tog_diff_view.window);
948 nodelay(stdscr, TRUE);
949 switch (ch) {
950 case 'q':
951 done = 1;
952 break;
953 case 'k':
954 case KEY_UP:
955 case KEY_BACKSPACE:
956 if (first_displayed_line > 1)
957 first_displayed_line--;
958 break;
959 case KEY_PPAGE:
960 i = 0;
961 while (i++ < LINES - 1 &&
962 first_displayed_line > 1)
963 first_displayed_line--;
964 break;
965 case 'j':
966 case KEY_DOWN:
967 case KEY_ENTER:
968 case '\r':
969 if (!eof)
970 first_displayed_line++;
971 break;
972 case KEY_NPAGE:
973 case ' ':
974 i = 0;
975 while (!eof && i++ < LINES - 1) {
976 char *line = parse_next_line(f, NULL);
977 first_displayed_line++;
978 if (line == NULL)
979 break;
981 break;
982 default:
983 break;
986 fclose(f);
987 return err;
990 static const struct got_error *
991 cmd_diff(int argc, char *argv[])
993 const struct got_error *error = NULL;
994 struct got_repository *repo = NULL;
995 struct got_object *obj1 = NULL, *obj2 = NULL;
996 char *repo_path = NULL;
997 char *obj_id_str1 = NULL, *obj_id_str2 = NULL;
998 int ch;
1000 #ifndef PROFILE
1001 if (pledge("stdio rpath wpath cpath flock proc tty", NULL) == -1)
1002 err(1, "pledge");
1003 #endif
1005 while ((ch = getopt(argc, argv, "")) != -1) {
1006 switch (ch) {
1007 default:
1008 usage();
1009 /* NOTREACHED */
1013 argc -= optind;
1014 argv += optind;
1016 if (argc == 0) {
1017 usage_diff(); /* TODO show local worktree changes */
1018 } else if (argc == 2) {
1019 repo_path = getcwd(NULL, 0);
1020 if (repo_path == NULL)
1021 return got_error_from_errno();
1022 obj_id_str1 = argv[0];
1023 obj_id_str2 = argv[1];
1024 } else if (argc == 3) {
1025 repo_path = realpath(argv[0], NULL);
1026 if (repo_path == NULL)
1027 return got_error_from_errno();
1028 obj_id_str1 = argv[1];
1029 obj_id_str2 = argv[2];
1030 } else
1031 usage_diff();
1033 error = got_repo_open(&repo, repo_path);
1034 free(repo_path);
1035 if (error)
1036 goto done;
1038 error = got_object_open_by_id_str(&obj1, repo, obj_id_str1);
1039 if (error)
1040 goto done;
1042 error = got_object_open_by_id_str(&obj2, repo, obj_id_str2);
1043 if (error)
1044 goto done;
1046 error = show_diff_view(obj1, obj2, repo);
1047 done:
1048 got_repo_close(repo);
1049 if (obj1)
1050 got_object_close(obj1);
1051 if (obj2)
1052 got_object_close(obj2);
1053 return error;
1056 __dead static void
1057 usage_blame(void)
1059 endwin();
1060 fprintf(stderr, "usage: %s blame [repository-path] blob-object\n",
1061 getprogname());
1062 exit(1);
1065 static const struct got_error *
1066 cmd_blame(int argc, char *argv[])
1068 return got_error(GOT_ERR_NOT_IMPL);
1071 static void
1072 init_curses(void)
1074 initscr();
1075 cbreak();
1076 noecho();
1077 nonl();
1078 intrflush(stdscr, FALSE);
1079 keypad(stdscr, TRUE);
1080 curs_set(0);
1083 __dead static void
1084 usage(void)
1086 int i;
1088 fprintf(stderr, "usage: %s [-h] [command] [arg ...]\n\n"
1089 "Available commands:\n", getprogname());
1090 for (i = 0; i < nitems(tog_commands); i++) {
1091 struct tog_cmd *cmd = &tog_commands[i];
1092 fprintf(stderr, " %s: %s\n", cmd->name, cmd->descr);
1094 exit(1);
1097 static char **
1098 make_argv(const char *arg0, const char *arg1)
1100 char **argv;
1101 int argc = (arg1 == NULL ? 1 : 2);
1103 argv = calloc(argc, sizeof(char *));
1104 if (argv == NULL)
1105 err(1, "calloc");
1106 argv[0] = strdup(arg0);
1107 if (argv[0] == NULL)
1108 err(1, "calloc");
1109 if (arg1) {
1110 argv[1] = strdup(arg1);
1111 if (argv[1] == NULL)
1112 err(1, "calloc");
1115 return argv;
1118 int
1119 main(int argc, char *argv[])
1121 const struct got_error *error = NULL;
1122 struct tog_cmd *cmd = NULL;
1123 int ch, hflag = 0;
1124 char **cmd_argv = NULL;
1126 setlocale(LC_ALL, "");
1128 while ((ch = getopt(argc, argv, "h")) != -1) {
1129 switch (ch) {
1130 case 'h':
1131 hflag = 1;
1132 break;
1133 default:
1134 usage();
1135 /* NOTREACHED */
1139 argc -= optind;
1140 argv += optind;
1141 optind = 0;
1142 optreset = 1;
1144 if (argc == 0) {
1145 /* Build an argument vector which runs a default command. */
1146 cmd = &tog_commands[0];
1147 cmd_argv = make_argv(cmd->name, NULL);
1148 argc = 1;
1149 } else {
1150 int i;
1152 /* Did the user specific a command? */
1153 for (i = 0; i < nitems(tog_commands); i++) {
1154 if (strncmp(tog_commands[i].name, argv[0],
1155 strlen(argv[0])) == 0) {
1156 cmd = &tog_commands[i];
1157 if (hflag)
1158 tog_commands[i].cmd_usage();
1159 break;
1162 if (cmd == NULL) {
1163 /* Did the user specify a repository? */
1164 char *repo_path = realpath(argv[0], NULL);
1165 if (repo_path) {
1166 struct got_repository *repo;
1167 error = got_repo_open(&repo, repo_path);
1168 if (error == NULL)
1169 got_repo_close(repo);
1170 } else
1171 error = got_error_from_errno();
1172 if (error) {
1173 fprintf(stderr, "%s: '%s' is neither a known "
1174 "command nor a path to a repository\n",
1175 getprogname(), argv[0]);
1176 free(repo_path);
1177 return 1;
1179 cmd = &tog_commands[0];
1180 cmd_argv = make_argv(cmd->name, repo_path);
1181 argc = 2;
1182 free(repo_path);
1186 init_curses();
1188 error = cmd->cmd_main(argc, cmd_argv ? cmd_argv : argv);
1189 if (error)
1190 goto done;
1191 done:
1192 endwin();
1193 free(cmd_argv);
1194 if (error)
1195 fprintf(stderr, "%s: %s\n", getprogname(), error->msg);
1196 return 0;