Blob


1 /*
2 * Copyright (c) 2018, 2019, 2020 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/stat.h>
18 #include <sys/ioctl.h>
20 #include <ctype.h>
21 #include <errno.h>
22 #if defined(__FreeBSD__) || defined(__APPLE__)
23 #define _XOPEN_SOURCE_EXTENDED /* for ncurses wide-character functions */
24 #endif
25 #include <curses.h>
26 #include <panel.h>
27 #include <locale.h>
28 #include <signal.h>
29 #include <stdlib.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <getopt.h>
33 #include <string.h>
34 #include <err.h>
35 #include <unistd.h>
36 #include <limits.h>
37 #include <wchar.h>
38 #include <time.h>
39 #include <pthread.h>
40 #include <libgen.h>
41 #include <regex.h>
42 #include <sched.h>
44 #include "got_compat.h"
46 #include "got_version.h"
47 #include "got_error.h"
48 #include "got_object.h"
49 #include "got_reference.h"
50 #include "got_repository.h"
51 #include "got_diff.h"
52 #include "got_opentemp.h"
53 #include "got_utf8.h"
54 #include "got_cancel.h"
55 #include "got_commit_graph.h"
56 #include "got_blame.h"
57 #include "got_privsep.h"
58 #include "got_path.h"
59 #include "got_worktree.h"
61 //#define update_panels() (0)
62 //#define doupdate() (0)
64 #ifndef MIN
65 #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b))
66 #endif
68 #ifndef MAX
69 #define MAX(_a,_b) ((_a) > (_b) ? (_a) : (_b))
70 #endif
72 #ifndef CTRL
73 #define CTRL(x) ((x) & 0x1f)
74 #endif
76 #ifndef nitems
77 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
78 #endif
80 struct tog_cmd {
81 const char *name;
82 const struct got_error *(*cmd_main)(int, char *[]);
83 void (*cmd_usage)(void);
84 };
86 __dead static void usage(int, int);
87 __dead static void usage_log(void);
88 __dead static void usage_diff(void);
89 __dead static void usage_blame(void);
90 __dead static void usage_tree(void);
91 __dead static void usage_ref(void);
93 static const struct got_error* cmd_log(int, char *[]);
94 static const struct got_error* cmd_diff(int, char *[]);
95 static const struct got_error* cmd_blame(int, char *[]);
96 static const struct got_error* cmd_tree(int, char *[]);
97 static const struct got_error* cmd_ref(int, char *[]);
99 static const struct tog_cmd tog_commands[] = {
100 { "log", cmd_log, usage_log },
101 { "diff", cmd_diff, usage_diff },
102 { "blame", cmd_blame, usage_blame },
103 { "tree", cmd_tree, usage_tree },
104 { "ref", cmd_ref, usage_ref },
105 };
107 enum tog_view_type {
108 TOG_VIEW_DIFF,
109 TOG_VIEW_LOG,
110 TOG_VIEW_BLAME,
111 TOG_VIEW_TREE,
112 TOG_VIEW_REF,
113 };
115 #define TOG_EOF_STRING "(END)"
117 struct commit_queue_entry {
118 TAILQ_ENTRY(commit_queue_entry) entry;
119 struct got_object_id *id;
120 struct got_commit_object *commit;
121 int idx;
122 };
123 TAILQ_HEAD(commit_queue_head, commit_queue_entry);
124 struct commit_queue {
125 int ncommits;
126 struct commit_queue_head head;
127 };
129 struct tog_color {
130 STAILQ_ENTRY(tog_color) entry;
131 regex_t regex;
132 short colorpair;
133 };
134 STAILQ_HEAD(tog_colors, tog_color);
136 static struct got_reflist_head tog_refs = TAILQ_HEAD_INITIALIZER(tog_refs);
137 static struct got_reflist_object_id_map *tog_refs_idmap;
139 static const struct got_error *
140 tog_ref_cmp_by_name(void *arg, int *cmp, struct got_reference *re1,
141 struct got_reference* re2)
143 const char *name1 = got_ref_get_name(re1);
144 const char *name2 = got_ref_get_name(re2);
145 int isbackup1, isbackup2;
147 /* Sort backup refs towards the bottom of the list. */
148 isbackup1 = strncmp(name1, "refs/got/backup/", 16) == 0;
149 isbackup2 = strncmp(name2, "refs/got/backup/", 16) == 0;
150 if (!isbackup1 && isbackup2) {
151 *cmp = -1;
152 return NULL;
153 } else if (isbackup1 && !isbackup2) {
154 *cmp = 1;
155 return NULL;
158 *cmp = got_path_cmp(name1, name2, strlen(name1), strlen(name2));
159 return NULL;
162 static const struct got_error *
163 tog_load_refs(struct got_repository *repo, int sort_by_date)
165 const struct got_error *err;
167 err = got_ref_list(&tog_refs, repo, NULL, sort_by_date ?
168 got_ref_cmp_by_commit_timestamp_descending : tog_ref_cmp_by_name,
169 repo);
170 if (err)
171 return err;
173 return got_reflist_object_id_map_create(&tog_refs_idmap, &tog_refs,
174 repo);
177 static void
178 tog_free_refs(void)
180 if (tog_refs_idmap) {
181 got_reflist_object_id_map_free(tog_refs_idmap);
182 tog_refs_idmap = NULL;
184 got_ref_list_free(&tog_refs);
187 static const struct got_error *
188 add_color(struct tog_colors *colors, const char *pattern,
189 int idx, short color)
191 const struct got_error *err = NULL;
192 struct tog_color *tc;
193 int regerr = 0;
195 if (idx < 1 || idx > COLOR_PAIRS - 1)
196 return NULL;
198 init_pair(idx, color, -1);
200 tc = calloc(1, sizeof(*tc));
201 if (tc == NULL)
202 return got_error_from_errno("calloc");
203 regerr = regcomp(&tc->regex, pattern,
204 REG_EXTENDED | REG_NOSUB | REG_NEWLINE);
205 if (regerr) {
206 static char regerr_msg[512];
207 static char err_msg[512];
208 regerror(regerr, &tc->regex, regerr_msg,
209 sizeof(regerr_msg));
210 snprintf(err_msg, sizeof(err_msg), "regcomp: %s",
211 regerr_msg);
212 err = got_error_msg(GOT_ERR_REGEX, err_msg);
213 free(tc);
214 return err;
216 tc->colorpair = idx;
217 STAILQ_INSERT_HEAD(colors, tc, entry);
218 return NULL;
221 static void
222 free_colors(struct tog_colors *colors)
224 struct tog_color *tc;
226 while (!STAILQ_EMPTY(colors)) {
227 tc = STAILQ_FIRST(colors);
228 STAILQ_REMOVE_HEAD(colors, entry);
229 regfree(&tc->regex);
230 free(tc);
234 struct tog_color *
235 get_color(struct tog_colors *colors, int colorpair)
237 struct tog_color *tc = NULL;
239 STAILQ_FOREACH(tc, colors, entry) {
240 if (tc->colorpair == colorpair)
241 return tc;
244 return NULL;
247 static int
248 default_color_value(const char *envvar)
250 if (strcmp(envvar, "TOG_COLOR_DIFF_MINUS") == 0)
251 return COLOR_MAGENTA;
252 if (strcmp(envvar, "TOG_COLOR_DIFF_PLUS") == 0)
253 return COLOR_CYAN;
254 if (strcmp(envvar, "TOG_COLOR_DIFF_CHUNK_HEADER") == 0)
255 return COLOR_YELLOW;
256 if (strcmp(envvar, "TOG_COLOR_DIFF_META") == 0)
257 return COLOR_GREEN;
258 if (strcmp(envvar, "TOG_COLOR_TREE_SUBMODULE") == 0)
259 return COLOR_MAGENTA;
260 if (strcmp(envvar, "TOG_COLOR_TREE_SYMLINK") == 0)
261 return COLOR_MAGENTA;
262 if (strcmp(envvar, "TOG_COLOR_TREE_DIRECTORY") == 0)
263 return COLOR_CYAN;
264 if (strcmp(envvar, "TOG_COLOR_TREE_EXECUTABLE") == 0)
265 return COLOR_GREEN;
266 if (strcmp(envvar, "TOG_COLOR_COMMIT") == 0)
267 return COLOR_GREEN;
268 if (strcmp(envvar, "TOG_COLOR_AUTHOR") == 0)
269 return COLOR_CYAN;
270 if (strcmp(envvar, "TOG_COLOR_DATE") == 0)
271 return COLOR_YELLOW;
272 if (strcmp(envvar, "TOG_COLOR_REFS_HEADS") == 0)
273 return COLOR_GREEN;
274 if (strcmp(envvar, "TOG_COLOR_REFS_TAGS") == 0)
275 return COLOR_MAGENTA;
276 if (strcmp(envvar, "TOG_COLOR_REFS_REMOTES") == 0)
277 return COLOR_YELLOW;
278 if (strcmp(envvar, "TOG_COLOR_REFS_BACKUP") == 0)
279 return COLOR_CYAN;
281 return -1;
284 static int
285 get_color_value(const char *envvar)
287 const char *val = getenv(envvar);
289 if (val == NULL)
290 return default_color_value(envvar);
292 if (strcasecmp(val, "black") == 0)
293 return COLOR_BLACK;
294 if (strcasecmp(val, "red") == 0)
295 return COLOR_RED;
296 if (strcasecmp(val, "green") == 0)
297 return COLOR_GREEN;
298 if (strcasecmp(val, "yellow") == 0)
299 return COLOR_YELLOW;
300 if (strcasecmp(val, "blue") == 0)
301 return COLOR_BLUE;
302 if (strcasecmp(val, "magenta") == 0)
303 return COLOR_MAGENTA;
304 if (strcasecmp(val, "cyan") == 0)
305 return COLOR_CYAN;
306 if (strcasecmp(val, "white") == 0)
307 return COLOR_WHITE;
308 if (strcasecmp(val, "default") == 0)
309 return -1;
311 return default_color_value(envvar);
315 struct tog_diff_view_state {
316 struct got_object_id *id1, *id2;
317 const char *label1, *label2;
318 FILE *f, *f1, *f2;
319 int first_displayed_line;
320 int last_displayed_line;
321 int eof;
322 int diff_context;
323 int ignore_whitespace;
324 int force_text_diff;
325 struct got_repository *repo;
326 struct tog_colors colors;
327 size_t nlines;
328 off_t *line_offsets;
329 int matched_line;
330 int selected_line;
332 /* passed from log view; may be NULL */
333 struct tog_view *log_view;
334 };
336 pthread_mutex_t tog_mutex = PTHREAD_MUTEX_INITIALIZER;
338 struct tog_log_thread_args {
339 pthread_cond_t need_commits;
340 pthread_cond_t commit_loaded;
341 int commits_needed;
342 int load_all;
343 struct got_commit_graph *graph;
344 struct commit_queue *commits;
345 const char *in_repo_path;
346 struct got_object_id *start_id;
347 struct got_repository *repo;
348 int *pack_fds;
349 int log_complete;
350 sig_atomic_t *quit;
351 struct commit_queue_entry **first_displayed_entry;
352 struct commit_queue_entry **selected_entry;
353 int *searching;
354 int *search_next_done;
355 regex_t *regex;
356 };
358 struct tog_log_view_state {
359 struct commit_queue commits;
360 struct commit_queue_entry *first_displayed_entry;
361 struct commit_queue_entry *last_displayed_entry;
362 struct commit_queue_entry *selected_entry;
363 int selected;
364 char *in_repo_path;
365 char *head_ref_name;
366 int log_branches;
367 struct got_repository *repo;
368 struct got_object_id *start_id;
369 sig_atomic_t quit;
370 pthread_t thread;
371 struct tog_log_thread_args thread_args;
372 struct commit_queue_entry *matched_entry;
373 struct commit_queue_entry *search_entry;
374 struct tog_colors colors;
375 };
377 #define TOG_COLOR_DIFF_MINUS 1
378 #define TOG_COLOR_DIFF_PLUS 2
379 #define TOG_COLOR_DIFF_CHUNK_HEADER 3
380 #define TOG_COLOR_DIFF_META 4
381 #define TOG_COLOR_TREE_SUBMODULE 5
382 #define TOG_COLOR_TREE_SYMLINK 6
383 #define TOG_COLOR_TREE_DIRECTORY 7
384 #define TOG_COLOR_TREE_EXECUTABLE 8
385 #define TOG_COLOR_COMMIT 9
386 #define TOG_COLOR_AUTHOR 10
387 #define TOG_COLOR_DATE 11
388 #define TOG_COLOR_REFS_HEADS 12
389 #define TOG_COLOR_REFS_TAGS 13
390 #define TOG_COLOR_REFS_REMOTES 14
391 #define TOG_COLOR_REFS_BACKUP 15
393 struct tog_blame_cb_args {
394 struct tog_blame_line *lines; /* one per line */
395 int nlines;
397 struct tog_view *view;
398 struct got_object_id *commit_id;
399 int *quit;
400 };
402 struct tog_blame_thread_args {
403 const char *path;
404 struct got_repository *repo;
405 struct tog_blame_cb_args *cb_args;
406 int *complete;
407 got_cancel_cb cancel_cb;
408 void *cancel_arg;
409 };
411 struct tog_blame {
412 FILE *f;
413 off_t filesize;
414 struct tog_blame_line *lines;
415 int nlines;
416 off_t *line_offsets;
417 pthread_t thread;
418 struct tog_blame_thread_args thread_args;
419 struct tog_blame_cb_args cb_args;
420 const char *path;
421 int *pack_fds;
422 };
424 struct tog_blame_view_state {
425 int first_displayed_line;
426 int last_displayed_line;
427 int selected_line;
428 int blame_complete;
429 int eof;
430 int done;
431 struct got_object_id_queue blamed_commits;
432 struct got_object_qid *blamed_commit;
433 char *path;
434 struct got_repository *repo;
435 struct got_object_id *commit_id;
436 struct tog_blame blame;
437 int matched_line;
438 struct tog_colors colors;
439 };
441 struct tog_parent_tree {
442 TAILQ_ENTRY(tog_parent_tree) entry;
443 struct got_tree_object *tree;
444 struct got_tree_entry *first_displayed_entry;
445 struct got_tree_entry *selected_entry;
446 int selected;
447 };
449 TAILQ_HEAD(tog_parent_trees, tog_parent_tree);
451 struct tog_tree_view_state {
452 char *tree_label;
453 struct got_object_id *commit_id;/* commit which this tree belongs to */
454 struct got_tree_object *root; /* the commit's root tree entry */
455 struct got_tree_object *tree; /* currently displayed (sub-)tree */
456 struct got_tree_entry *first_displayed_entry;
457 struct got_tree_entry *last_displayed_entry;
458 struct got_tree_entry *selected_entry;
459 int ndisplayed, selected, show_ids;
460 struct tog_parent_trees parents; /* parent trees of current sub-tree */
461 char *head_ref_name;
462 struct got_repository *repo;
463 struct got_tree_entry *matched_entry;
464 struct tog_colors colors;
465 };
467 struct tog_reflist_entry {
468 TAILQ_ENTRY(tog_reflist_entry) entry;
469 struct got_reference *ref;
470 int idx;
471 };
473 TAILQ_HEAD(tog_reflist_head, tog_reflist_entry);
475 struct tog_ref_view_state {
476 struct tog_reflist_head refs;
477 struct tog_reflist_entry *first_displayed_entry;
478 struct tog_reflist_entry *last_displayed_entry;
479 struct tog_reflist_entry *selected_entry;
480 int nrefs, ndisplayed, selected, show_ids, sort_by_date;
481 struct got_repository *repo;
482 struct tog_reflist_entry *matched_entry;
483 struct tog_colors colors;
484 };
486 /*
487 * We implement two types of views: parent views and child views.
489 * The 'Tab' key switches focus between a parent view and its child view.
490 * Child views are shown side-by-side to their parent view, provided
491 * there is enough screen estate.
493 * When a new view is opened from within a parent view, this new view
494 * becomes a child view of the parent view, replacing any existing child.
496 * When a new view is opened from within a child view, this new view
497 * becomes a parent view which will obscure the views below until the
498 * user quits the new parent view by typing 'q'.
500 * This list of views contains parent views only.
501 * Child views are only pointed to by their parent view.
502 */
503 TAILQ_HEAD(tog_view_list_head, tog_view);
505 struct tog_view {
506 TAILQ_ENTRY(tog_view) entry;
507 WINDOW *window;
508 PANEL *panel;
509 int nlines, ncols, begin_y, begin_x;
510 int lines, cols; /* copies of LINES and COLS */
511 int focussed; /* Only set on one parent or child view at a time. */
512 int dying;
513 struct tog_view *parent;
514 struct tog_view *child;
516 /*
517 * This flag is initially set on parent views when a new child view
518 * is created. It gets toggled when the 'Tab' key switches focus
519 * between parent and child.
520 * The flag indicates whether focus should be passed on to our child
521 * view if this parent view gets picked for focus after another parent
522 * view was closed. This prevents child views from losing focus in such
523 * situations.
524 */
525 int focus_child;
527 /* type-specific state */
528 enum tog_view_type type;
529 union {
530 struct tog_diff_view_state diff;
531 struct tog_log_view_state log;
532 struct tog_blame_view_state blame;
533 struct tog_tree_view_state tree;
534 struct tog_ref_view_state ref;
535 } state;
537 const struct got_error *(*show)(struct tog_view *);
538 const struct got_error *(*input)(struct tog_view **,
539 struct tog_view *, int);
540 const struct got_error *(*close)(struct tog_view *);
542 const struct got_error *(*search_start)(struct tog_view *);
543 const struct got_error *(*search_next)(struct tog_view *);
544 int search_started;
545 int searching;
546 #define TOG_SEARCH_FORWARD 1
547 #define TOG_SEARCH_BACKWARD 2
548 int search_next_done;
549 #define TOG_SEARCH_HAVE_MORE 1
550 #define TOG_SEARCH_NO_MORE 2
551 #define TOG_SEARCH_HAVE_NONE 3
552 regex_t regex;
553 regmatch_t regmatch;
554 };
556 static const struct got_error *open_diff_view(struct tog_view *,
557 struct got_object_id *, struct got_object_id *,
558 const char *, const char *, int, int, int, struct tog_view *,
559 struct got_repository *);
560 static const struct got_error *show_diff_view(struct tog_view *);
561 static const struct got_error *input_diff_view(struct tog_view **,
562 struct tog_view *, int);
563 static const struct got_error* close_diff_view(struct tog_view *);
564 static const struct got_error *search_start_diff_view(struct tog_view *);
565 static const struct got_error *search_next_diff_view(struct tog_view *);
567 static const struct got_error *open_log_view(struct tog_view *,
568 struct got_object_id *, struct got_repository *,
569 const char *, const char *, int);
570 static const struct got_error * show_log_view(struct tog_view *);
571 static const struct got_error *input_log_view(struct tog_view **,
572 struct tog_view *, int);
573 static const struct got_error *close_log_view(struct tog_view *);
574 static const struct got_error *search_start_log_view(struct tog_view *);
575 static const struct got_error *search_next_log_view(struct tog_view *);
577 static const struct got_error *open_blame_view(struct tog_view *, char *,
578 struct got_object_id *, struct got_repository *);
579 static const struct got_error *show_blame_view(struct tog_view *);
580 static const struct got_error *input_blame_view(struct tog_view **,
581 struct tog_view *, int);
582 static const struct got_error *close_blame_view(struct tog_view *);
583 static const struct got_error *search_start_blame_view(struct tog_view *);
584 static const struct got_error *search_next_blame_view(struct tog_view *);
586 static const struct got_error *open_tree_view(struct tog_view *,
587 struct got_object_id *, const char *, struct got_repository *);
588 static const struct got_error *show_tree_view(struct tog_view *);
589 static const struct got_error *input_tree_view(struct tog_view **,
590 struct tog_view *, int);
591 static const struct got_error *close_tree_view(struct tog_view *);
592 static const struct got_error *search_start_tree_view(struct tog_view *);
593 static const struct got_error *search_next_tree_view(struct tog_view *);
595 static const struct got_error *open_ref_view(struct tog_view *,
596 struct got_repository *);
597 static const struct got_error *show_ref_view(struct tog_view *);
598 static const struct got_error *input_ref_view(struct tog_view **,
599 struct tog_view *, int);
600 static const struct got_error *close_ref_view(struct tog_view *);
601 static const struct got_error *search_start_ref_view(struct tog_view *);
602 static const struct got_error *search_next_ref_view(struct tog_view *);
604 static volatile sig_atomic_t tog_sigwinch_received;
605 static volatile sig_atomic_t tog_sigpipe_received;
606 static volatile sig_atomic_t tog_sigcont_received;
607 static volatile sig_atomic_t tog_sigint_received;
608 static volatile sig_atomic_t tog_sigterm_received;
610 static void
611 tog_sigwinch(int signo)
613 tog_sigwinch_received = 1;
616 static void
617 tog_sigpipe(int signo)
619 tog_sigpipe_received = 1;
622 static void
623 tog_sigcont(int signo)
625 tog_sigcont_received = 1;
628 static void
629 tog_sigint(int signo)
631 tog_sigint_received = 1;
634 static void
635 tog_sigterm(int signo)
637 tog_sigterm_received = 1;
640 static int
641 tog_fatal_signal_received()
643 return (tog_sigpipe_received ||
644 tog_sigint_received || tog_sigint_received);
648 static const struct got_error *
649 view_close(struct tog_view *view)
651 const struct got_error *err = NULL;
653 if (view->child) {
654 view_close(view->child);
655 view->child = NULL;
657 if (view->close)
658 err = view->close(view);
659 if (view->panel)
660 del_panel(view->panel);
661 if (view->window)
662 delwin(view->window);
663 free(view);
664 return err;
667 static struct tog_view *
668 view_open(int nlines, int ncols, int begin_y, int begin_x,
669 enum tog_view_type type)
671 struct tog_view *view = calloc(1, sizeof(*view));
673 if (view == NULL)
674 return NULL;
676 view->type = type;
677 view->lines = LINES;
678 view->cols = COLS;
679 view->nlines = nlines ? nlines : LINES - begin_y;
680 view->ncols = ncols ? ncols : COLS - begin_x;
681 view->begin_y = begin_y;
682 view->begin_x = begin_x;
683 view->window = newwin(nlines, ncols, begin_y, begin_x);
684 if (view->window == NULL) {
685 view_close(view);
686 return NULL;
688 view->panel = new_panel(view->window);
689 if (view->panel == NULL ||
690 set_panel_userptr(view->panel, view) != OK) {
691 view_close(view);
692 return NULL;
695 keypad(view->window, TRUE);
696 return view;
699 static int
700 view_split_begin_x(int begin_x)
702 if (begin_x > 0 || COLS < 120)
703 return 0;
704 return (COLS - MAX(COLS / 2, 80));
707 static const struct got_error *view_resize(struct tog_view *);
709 static const struct got_error *
710 view_splitscreen(struct tog_view *view)
712 const struct got_error *err = NULL;
714 view->begin_y = 0;
715 view->begin_x = view_split_begin_x(0);
716 view->nlines = LINES;
717 view->ncols = COLS - view->begin_x;
718 view->lines = LINES;
719 view->cols = COLS;
720 err = view_resize(view);
721 if (err)
722 return err;
724 if (mvwin(view->window, view->begin_y, view->begin_x) == ERR)
725 return got_error_from_errno("mvwin");
727 return NULL;
730 static const struct got_error *
731 view_fullscreen(struct tog_view *view)
733 const struct got_error *err = NULL;
735 view->begin_x = 0;
736 view->begin_y = 0;
737 view->nlines = LINES;
738 view->ncols = COLS;
739 view->lines = LINES;
740 view->cols = COLS;
741 err = view_resize(view);
742 if (err)
743 return err;
745 if (mvwin(view->window, view->begin_y, view->begin_x) == ERR)
746 return got_error_from_errno("mvwin");
748 return NULL;
751 static int
752 view_is_parent_view(struct tog_view *view)
754 return view->parent == NULL;
757 static const struct got_error *
758 view_resize(struct tog_view *view)
760 int nlines, ncols;
762 if (view->lines > LINES)
763 nlines = view->nlines - (view->lines - LINES);
764 else
765 nlines = view->nlines + (LINES - view->lines);
767 if (view->cols > COLS)
768 ncols = view->ncols - (view->cols - COLS);
769 else
770 ncols = view->ncols + (COLS - view->cols);
772 if (wresize(view->window, nlines, ncols) == ERR)
773 return got_error_from_errno("wresize");
774 if (replace_panel(view->panel, view->window) == ERR)
775 return got_error_from_errno("replace_panel");
776 wclear(view->window);
778 view->nlines = nlines;
779 view->ncols = ncols;
780 view->lines = LINES;
781 view->cols = COLS;
783 if (view->child) {
784 view->child->begin_x = view_split_begin_x(view->begin_x);
785 if (view->child->begin_x == 0) {
786 view_fullscreen(view->child);
787 if (view->child->focussed)
788 show_panel(view->child->panel);
789 else
790 show_panel(view->panel);
791 } else {
792 view_splitscreen(view->child);
793 show_panel(view->child->panel);
797 return NULL;
800 static const struct got_error *
801 view_close_child(struct tog_view *view)
803 const struct got_error *err = NULL;
805 if (view->child == NULL)
806 return NULL;
808 err = view_close(view->child);
809 view->child = NULL;
810 return err;
813 static void
814 view_set_child(struct tog_view *view, struct tog_view *child)
816 view->child = child;
817 child->parent = view;
820 static int
821 view_is_splitscreen(struct tog_view *view)
823 return view->begin_x > 0;
826 static void
827 tog_resizeterm(void)
829 int cols, lines;
830 struct winsize size;
832 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) < 0) {
833 cols = 80; /* Default */
834 lines = 24;
835 } else {
836 cols = size.ws_col;
837 lines = size.ws_row;
839 resize_term(lines, cols);
842 static const struct got_error *
843 view_search_start(struct tog_view *view)
845 const struct got_error *err = NULL;
846 char pattern[1024];
847 int ret;
849 if (view->search_started) {
850 regfree(&view->regex);
851 view->searching = 0;
852 memset(&view->regmatch, 0, sizeof(view->regmatch));
854 view->search_started = 0;
856 if (view->nlines < 1)
857 return NULL;
859 mvwaddstr(view->window, view->begin_y + view->nlines - 1, 0, "/");
860 wclrtoeol(view->window);
862 nocbreak();
863 echo();
864 ret = wgetnstr(view->window, pattern, sizeof(pattern));
865 cbreak();
866 noecho();
867 if (ret == ERR)
868 return NULL;
870 if (regcomp(&view->regex, pattern, REG_EXTENDED | REG_NEWLINE) == 0) {
871 err = view->search_start(view);
872 if (err) {
873 regfree(&view->regex);
874 return err;
876 view->search_started = 1;
877 view->searching = TOG_SEARCH_FORWARD;
878 view->search_next_done = 0;
879 view->search_next(view);
882 return NULL;
885 static const struct got_error *
886 view_input(struct tog_view **new, int *done, struct tog_view *view,
887 struct tog_view_list_head *views)
889 const struct got_error *err = NULL;
890 struct tog_view *v;
891 int ch, errcode;
893 *new = NULL;
895 /* Clear "no matches" indicator. */
896 if (view->search_next_done == TOG_SEARCH_NO_MORE ||
897 view->search_next_done == TOG_SEARCH_HAVE_NONE)
898 view->search_next_done = TOG_SEARCH_HAVE_MORE;
900 if (view->searching && !view->search_next_done) {
901 errcode = pthread_mutex_unlock(&tog_mutex);
902 if (errcode)
903 return got_error_set_errno(errcode,
904 "pthread_mutex_unlock");
905 sched_yield();
906 errcode = pthread_mutex_lock(&tog_mutex);
907 if (errcode)
908 return got_error_set_errno(errcode,
909 "pthread_mutex_lock");
910 view->search_next(view);
911 return NULL;
914 nodelay(stdscr, FALSE);
915 /* Allow threads to make progress while we are waiting for input. */
916 errcode = pthread_mutex_unlock(&tog_mutex);
917 if (errcode)
918 return got_error_set_errno(errcode, "pthread_mutex_unlock");
919 ch = wgetch(view->window);
920 errcode = pthread_mutex_lock(&tog_mutex);
921 if (errcode)
922 return got_error_set_errno(errcode, "pthread_mutex_lock");
923 nodelay(stdscr, TRUE);
925 if (tog_sigwinch_received || tog_sigcont_received) {
926 tog_resizeterm();
927 tog_sigwinch_received = 0;
928 tog_sigcont_received = 0;
929 TAILQ_FOREACH(v, views, entry) {
930 err = view_resize(v);
931 if (err)
932 return err;
933 err = v->input(new, v, KEY_RESIZE);
934 if (err)
935 return err;
936 if (v->child) {
937 err = view_resize(v->child);
938 if (err)
939 return err;
940 err = v->child->input(new, v->child,
941 KEY_RESIZE);
942 if (err)
943 return err;
948 switch (ch) {
949 case '\t':
950 if (view->child) {
951 view->focussed = 0;
952 view->child->focussed = 1;
953 view->focus_child = 1;
954 } else if (view->parent) {
955 view->focussed = 0;
956 view->parent->focussed = 1;
957 view->parent->focus_child = 0;
959 break;
960 case 'q':
961 err = view->input(new, view, ch);
962 view->dying = 1;
963 break;
964 case 'Q':
965 *done = 1;
966 break;
967 case 'f':
968 if (view_is_parent_view(view)) {
969 if (view->child == NULL)
970 break;
971 if (view_is_splitscreen(view->child)) {
972 view->focussed = 0;
973 view->child->focussed = 1;
974 err = view_fullscreen(view->child);
975 } else
976 err = view_splitscreen(view->child);
977 if (err)
978 break;
979 err = view->child->input(new, view->child,
980 KEY_RESIZE);
981 } else {
982 if (view_is_splitscreen(view)) {
983 view->parent->focussed = 0;
984 view->focussed = 1;
985 err = view_fullscreen(view);
986 } else {
987 err = view_splitscreen(view);
989 if (err)
990 break;
991 err = view->input(new, view, KEY_RESIZE);
993 break;
994 case KEY_RESIZE:
995 break;
996 case '/':
997 if (view->search_start)
998 view_search_start(view);
999 else
1000 err = view->input(new, view, ch);
1001 break;
1002 case 'N':
1003 case 'n':
1004 if (view->search_started && view->search_next) {
1005 view->searching = (ch == 'n' ?
1006 TOG_SEARCH_FORWARD : TOG_SEARCH_BACKWARD);
1007 view->search_next_done = 0;
1008 view->search_next(view);
1009 } else
1010 err = view->input(new, view, ch);
1011 break;
1012 default:
1013 err = view->input(new, view, ch);
1014 break;
1017 return err;
1020 void
1021 view_vborder(struct tog_view *view)
1023 PANEL *panel;
1024 const struct tog_view *view_above;
1026 if (view->parent)
1027 return view_vborder(view->parent);
1029 panel = panel_above(view->panel);
1030 if (panel == NULL)
1031 return;
1033 view_above = panel_userptr(panel);
1034 mvwvline(view->window, view->begin_y, view_above->begin_x - 1,
1035 got_locale_is_utf8() ? ACS_VLINE : '|', view->nlines);
1038 int
1039 view_needs_focus_indication(struct tog_view *view)
1041 if (view_is_parent_view(view)) {
1042 if (view->child == NULL || view->child->focussed)
1043 return 0;
1044 if (!view_is_splitscreen(view->child))
1045 return 0;
1046 } else if (!view_is_splitscreen(view))
1047 return 0;
1049 return view->focussed;
1052 static const struct got_error *
1053 view_loop(struct tog_view *view)
1055 const struct got_error *err = NULL;
1056 struct tog_view_list_head views;
1057 struct tog_view *new_view;
1058 int fast_refresh = 10;
1059 int done = 0, errcode;
1061 errcode = pthread_mutex_lock(&tog_mutex);
1062 if (errcode)
1063 return got_error_set_errno(errcode, "pthread_mutex_lock");
1065 TAILQ_INIT(&views);
1066 TAILQ_INSERT_HEAD(&views, view, entry);
1068 view->focussed = 1;
1069 err = view->show(view);
1070 if (err)
1071 return err;
1072 update_panels();
1073 doupdate();
1074 while (!TAILQ_EMPTY(&views) && !done && !tog_fatal_signal_received()) {
1075 /* Refresh fast during initialization, then become slower. */
1076 if (fast_refresh && fast_refresh-- == 0)
1077 halfdelay(10); /* switch to once per second */
1079 err = view_input(&new_view, &done, view, &views);
1080 if (err)
1081 break;
1082 if (view->dying) {
1083 struct tog_view *v, *prev = NULL;
1085 if (view_is_parent_view(view))
1086 prev = TAILQ_PREV(view, tog_view_list_head,
1087 entry);
1088 else if (view->parent)
1089 prev = view->parent;
1091 if (view->parent) {
1092 view->parent->child = NULL;
1093 view->parent->focus_child = 0;
1094 } else
1095 TAILQ_REMOVE(&views, view, entry);
1097 err = view_close(view);
1098 if (err)
1099 goto done;
1101 view = NULL;
1102 TAILQ_FOREACH(v, &views, entry) {
1103 if (v->focussed)
1104 break;
1106 if (view == NULL && new_view == NULL) {
1107 /* No view has focus. Try to pick one. */
1108 if (prev)
1109 view = prev;
1110 else if (!TAILQ_EMPTY(&views)) {
1111 view = TAILQ_LAST(&views,
1112 tog_view_list_head);
1114 if (view) {
1115 if (view->focus_child) {
1116 view->child->focussed = 1;
1117 view = view->child;
1118 } else
1119 view->focussed = 1;
1123 if (new_view) {
1124 struct tog_view *v, *t;
1125 /* Only allow one parent view per type. */
1126 TAILQ_FOREACH_SAFE(v, &views, entry, t) {
1127 if (v->type != new_view->type)
1128 continue;
1129 TAILQ_REMOVE(&views, v, entry);
1130 err = view_close(v);
1131 if (err)
1132 goto done;
1133 break;
1135 TAILQ_INSERT_TAIL(&views, new_view, entry);
1136 view = new_view;
1138 if (view) {
1139 if (view_is_parent_view(view)) {
1140 if (view->child && view->child->focussed)
1141 view = view->child;
1142 } else {
1143 if (view->parent && view->parent->focussed)
1144 view = view->parent;
1146 show_panel(view->panel);
1147 if (view->child && view_is_splitscreen(view->child))
1148 show_panel(view->child->panel);
1149 if (view->parent && view_is_splitscreen(view)) {
1150 err = view->parent->show(view->parent);
1151 if (err)
1152 goto done;
1154 err = view->show(view);
1155 if (err)
1156 goto done;
1157 if (view->child) {
1158 err = view->child->show(view->child);
1159 if (err)
1160 goto done;
1162 update_panels();
1163 doupdate();
1166 done:
1167 while (!TAILQ_EMPTY(&views)) {
1168 view = TAILQ_FIRST(&views);
1169 TAILQ_REMOVE(&views, view, entry);
1170 view_close(view);
1173 errcode = pthread_mutex_unlock(&tog_mutex);
1174 if (errcode && err == NULL)
1175 err = got_error_set_errno(errcode, "pthread_mutex_unlock");
1177 return err;
1180 __dead static void
1181 usage_log(void)
1183 endwin();
1184 fprintf(stderr,
1185 "usage: %s log [-b] [-c commit] [-r repository-path] [path]\n",
1186 getprogname());
1187 exit(1);
1190 /* Create newly allocated wide-character string equivalent to a byte string. */
1191 static const struct got_error *
1192 mbs2ws(wchar_t **ws, size_t *wlen, const char *s)
1194 char *vis = NULL;
1195 const struct got_error *err = NULL;
1197 *ws = NULL;
1198 *wlen = mbstowcs(NULL, s, 0);
1199 if (*wlen == (size_t)-1) {
1200 int vislen;
1201 if (errno != EILSEQ)
1202 return got_error_from_errno("mbstowcs");
1204 /* byte string invalid in current encoding; try to "fix" it */
1205 err = got_mbsavis(&vis, &vislen, s);
1206 if (err)
1207 return err;
1208 *wlen = mbstowcs(NULL, vis, 0);
1209 if (*wlen == (size_t)-1) {
1210 err = got_error_from_errno("mbstowcs"); /* give up */
1211 goto done;
1215 *ws = calloc(*wlen + 1, sizeof(**ws));
1216 if (*ws == NULL) {
1217 err = got_error_from_errno("calloc");
1218 goto done;
1221 if (mbstowcs(*ws, vis ? vis : s, *wlen) != *wlen)
1222 err = got_error_from_errno("mbstowcs");
1223 done:
1224 free(vis);
1225 if (err) {
1226 free(*ws);
1227 *ws = NULL;
1228 *wlen = 0;
1230 return err;
1233 /* Format a line for display, ensuring that it won't overflow a width limit. */
1234 static const struct got_error *
1235 format_line(wchar_t **wlinep, int *widthp, const char *line, int wlimit,
1236 int col_tab_align)
1238 const struct got_error *err = NULL;
1239 int cols = 0;
1240 wchar_t *wline = NULL;
1241 size_t wlen;
1242 int i;
1244 *wlinep = NULL;
1245 *widthp = 0;
1247 err = mbs2ws(&wline, &wlen, line);
1248 if (err)
1249 return err;
1251 if (wlen > 0 && wline[wlen - 1] == L'\n') {
1252 wline[wlen - 1] = L'\0';
1253 wlen--;
1255 if (wlen > 0 && wline[wlen - 1] == L'\r') {
1256 wline[wlen - 1] = L'\0';
1257 wlen--;
1260 i = 0;
1261 while (i < wlen) {
1262 int width = wcwidth(wline[i]);
1264 if (width == 0) {
1265 i++;
1266 continue;
1269 if (width == 1 || width == 2) {
1270 if (cols + width > wlimit)
1271 break;
1272 cols += width;
1273 i++;
1274 } else if (width == -1) {
1275 if (wline[i] == L'\t') {
1276 width = TABSIZE -
1277 ((cols + col_tab_align) % TABSIZE);
1278 } else {
1279 width = 1;
1280 wline[i] = L'.';
1282 if (cols + width > wlimit)
1283 break;
1284 cols += width;
1285 i++;
1286 } else {
1287 err = got_error_from_errno("wcwidth");
1288 goto done;
1291 wline[i] = L'\0';
1292 if (widthp)
1293 *widthp = cols;
1294 done:
1295 if (err)
1296 free(wline);
1297 else
1298 *wlinep = wline;
1299 return err;
1302 static const struct got_error*
1303 build_refs_str(char **refs_str, struct got_reflist_head *refs,
1304 struct got_object_id *id, struct got_repository *repo)
1306 static const struct got_error *err = NULL;
1307 struct got_reflist_entry *re;
1308 char *s;
1309 const char *name;
1311 *refs_str = NULL;
1313 TAILQ_FOREACH(re, refs, entry) {
1314 struct got_tag_object *tag = NULL;
1315 struct got_object_id *ref_id;
1316 int cmp;
1318 name = got_ref_get_name(re->ref);
1319 if (strcmp(name, GOT_REF_HEAD) == 0)
1320 continue;
1321 if (strncmp(name, "refs/", 5) == 0)
1322 name += 5;
1323 if (strncmp(name, "got/", 4) == 0 &&
1324 strncmp(name, "got/backup/", 11) != 0)
1325 continue;
1326 if (strncmp(name, "heads/", 6) == 0)
1327 name += 6;
1328 if (strncmp(name, "remotes/", 8) == 0) {
1329 name += 8;
1330 s = strstr(name, "/" GOT_REF_HEAD);
1331 if (s != NULL && s[strlen(s)] == '\0')
1332 continue;
1334 err = got_ref_resolve(&ref_id, repo, re->ref);
1335 if (err)
1336 break;
1337 if (strncmp(name, "tags/", 5) == 0) {
1338 err = got_object_open_as_tag(&tag, repo, ref_id);
1339 if (err) {
1340 if (err->code != GOT_ERR_OBJ_TYPE) {
1341 free(ref_id);
1342 break;
1344 /* Ref points at something other than a tag. */
1345 err = NULL;
1346 tag = NULL;
1349 cmp = got_object_id_cmp(tag ?
1350 got_object_tag_get_object_id(tag) : ref_id, id);
1351 free(ref_id);
1352 if (tag)
1353 got_object_tag_close(tag);
1354 if (cmp != 0)
1355 continue;
1356 s = *refs_str;
1357 if (asprintf(refs_str, "%s%s%s", s ? s : "",
1358 s ? ", " : "", name) == -1) {
1359 err = got_error_from_errno("asprintf");
1360 free(s);
1361 *refs_str = NULL;
1362 break;
1364 free(s);
1367 return err;
1370 static const struct got_error *
1371 format_author(wchar_t **wauthor, int *author_width, char *author, int limit,
1372 int col_tab_align)
1374 char *smallerthan;
1376 smallerthan = strchr(author, '<');
1377 if (smallerthan && smallerthan[1] != '\0')
1378 author = smallerthan + 1;
1379 author[strcspn(author, "@>")] = '\0';
1380 return format_line(wauthor, author_width, author, limit, col_tab_align);
1383 static const struct got_error *
1384 draw_commit(struct tog_view *view, struct got_commit_object *commit,
1385 struct got_object_id *id, const size_t date_display_cols,
1386 int author_display_cols)
1388 struct tog_log_view_state *s = &view->state.log;
1389 const struct got_error *err = NULL;
1390 char datebuf[12]; /* YYYY-MM-DD + SPACE + NUL */
1391 char *logmsg0 = NULL, *logmsg = NULL;
1392 char *author = NULL;
1393 wchar_t *wlogmsg = NULL, *wauthor = NULL;
1394 int author_width, logmsg_width;
1395 char *newline, *line = NULL;
1396 int col, limit;
1397 const int avail = view->ncols;
1398 struct tm tm;
1399 time_t committer_time;
1400 struct tog_color *tc;
1402 committer_time = got_object_commit_get_committer_time(commit);
1403 if (gmtime_r(&committer_time, &tm) == NULL)
1404 return got_error_from_errno("gmtime_r");
1405 if (strftime(datebuf, sizeof(datebuf), "%G-%m-%d ", &tm) == 0)
1406 return got_error(GOT_ERR_NO_SPACE);
1408 if (avail <= date_display_cols)
1409 limit = MIN(sizeof(datebuf) - 1, avail);
1410 else
1411 limit = MIN(date_display_cols, sizeof(datebuf) - 1);
1412 tc = get_color(&s->colors, TOG_COLOR_DATE);
1413 if (tc)
1414 wattr_on(view->window,
1415 COLOR_PAIR(tc->colorpair), NULL);
1416 waddnstr(view->window, datebuf, limit);
1417 if (tc)
1418 wattr_off(view->window,
1419 COLOR_PAIR(tc->colorpair), NULL);
1420 col = limit;
1421 if (col > avail)
1422 goto done;
1424 if (avail >= 120) {
1425 char *id_str;
1426 err = got_object_id_str(&id_str, id);
1427 if (err)
1428 goto done;
1429 tc = get_color(&s->colors, TOG_COLOR_COMMIT);
1430 if (tc)
1431 wattr_on(view->window,
1432 COLOR_PAIR(tc->colorpair), NULL);
1433 wprintw(view->window, "%.8s ", id_str);
1434 if (tc)
1435 wattr_off(view->window,
1436 COLOR_PAIR(tc->colorpair), NULL);
1437 free(id_str);
1438 col += 9;
1439 if (col > avail)
1440 goto done;
1443 author = strdup(got_object_commit_get_author(commit));
1444 if (author == NULL) {
1445 err = got_error_from_errno("strdup");
1446 goto done;
1448 err = format_author(&wauthor, &author_width, author, avail - col, col);
1449 if (err)
1450 goto done;
1451 tc = get_color(&s->colors, TOG_COLOR_AUTHOR);
1452 if (tc)
1453 wattr_on(view->window,
1454 COLOR_PAIR(tc->colorpair), NULL);
1455 waddwstr(view->window, wauthor);
1456 if (tc)
1457 wattr_off(view->window,
1458 COLOR_PAIR(tc->colorpair), NULL);
1459 col += author_width;
1460 while (col < avail && author_width < author_display_cols + 2) {
1461 waddch(view->window, ' ');
1462 col++;
1463 author_width++;
1465 if (col > avail)
1466 goto done;
1468 err = got_object_commit_get_logmsg(&logmsg0, commit);
1469 if (err)
1470 goto done;
1471 logmsg = logmsg0;
1472 while (*logmsg == '\n')
1473 logmsg++;
1474 newline = strchr(logmsg, '\n');
1475 if (newline)
1476 *newline = '\0';
1477 limit = avail - col;
1478 err = format_line(&wlogmsg, &logmsg_width, logmsg, limit, col);
1479 if (err)
1480 goto done;
1481 waddwstr(view->window, wlogmsg);
1482 col += logmsg_width;
1483 while (col < avail) {
1484 waddch(view->window, ' ');
1485 col++;
1487 done:
1488 free(logmsg0);
1489 free(wlogmsg);
1490 free(author);
1491 free(wauthor);
1492 free(line);
1493 return err;
1496 static struct commit_queue_entry *
1497 alloc_commit_queue_entry(struct got_commit_object *commit,
1498 struct got_object_id *id)
1500 struct commit_queue_entry *entry;
1502 entry = calloc(1, sizeof(*entry));
1503 if (entry == NULL)
1504 return NULL;
1506 entry->id = id;
1507 entry->commit = commit;
1508 return entry;
1511 static void
1512 pop_commit(struct commit_queue *commits)
1514 struct commit_queue_entry *entry;
1516 entry = TAILQ_FIRST(&commits->head);
1517 TAILQ_REMOVE(&commits->head, entry, entry);
1518 got_object_commit_close(entry->commit);
1519 commits->ncommits--;
1520 /* Don't free entry->id! It is owned by the commit graph. */
1521 free(entry);
1524 static void
1525 free_commits(struct commit_queue *commits)
1527 while (!TAILQ_EMPTY(&commits->head))
1528 pop_commit(commits);
1531 static const struct got_error *
1532 match_commit(int *have_match, struct got_object_id *id,
1533 struct got_commit_object *commit, regex_t *regex)
1535 const struct got_error *err = NULL;
1536 regmatch_t regmatch;
1537 char *id_str = NULL, *logmsg = NULL;
1539 *have_match = 0;
1541 err = got_object_id_str(&id_str, id);
1542 if (err)
1543 return err;
1545 err = got_object_commit_get_logmsg(&logmsg, commit);
1546 if (err)
1547 goto done;
1549 if (regexec(regex, got_object_commit_get_author(commit), 1,
1550 &regmatch, 0) == 0 ||
1551 regexec(regex, got_object_commit_get_committer(commit), 1,
1552 &regmatch, 0) == 0 ||
1553 regexec(regex, id_str, 1, &regmatch, 0) == 0 ||
1554 regexec(regex, logmsg, 1, &regmatch, 0) == 0)
1555 *have_match = 1;
1556 done:
1557 free(id_str);
1558 free(logmsg);
1559 return err;
1562 static const struct got_error *
1563 queue_commits(struct tog_log_thread_args *a)
1565 const struct got_error *err = NULL;
1568 * We keep all commits open throughout the lifetime of the log
1569 * view in order to avoid having to re-fetch commits from disk
1570 * while updating the display.
1572 do {
1573 struct got_object_id *id;
1574 struct got_commit_object *commit;
1575 struct commit_queue_entry *entry;
1576 int errcode;
1578 err = got_commit_graph_iter_next(&id, a->graph, a->repo,
1579 NULL, NULL);
1580 if (err || id == NULL)
1581 break;
1583 err = got_object_open_as_commit(&commit, a->repo, id);
1584 if (err)
1585 break;
1586 entry = alloc_commit_queue_entry(commit, id);
1587 if (entry == NULL) {
1588 err = got_error_from_errno("alloc_commit_queue_entry");
1589 break;
1592 errcode = pthread_mutex_lock(&tog_mutex);
1593 if (errcode) {
1594 err = got_error_set_errno(errcode,
1595 "pthread_mutex_lock");
1596 break;
1599 entry->idx = a->commits->ncommits;
1600 TAILQ_INSERT_TAIL(&a->commits->head, entry, entry);
1601 a->commits->ncommits++;
1603 if (*a->searching == TOG_SEARCH_FORWARD &&
1604 !*a->search_next_done) {
1605 int have_match;
1606 err = match_commit(&have_match, id, commit, a->regex);
1607 if (err)
1608 break;
1609 if (have_match)
1610 *a->search_next_done = TOG_SEARCH_HAVE_MORE;
1613 errcode = pthread_mutex_unlock(&tog_mutex);
1614 if (errcode && err == NULL)
1615 err = got_error_set_errno(errcode,
1616 "pthread_mutex_unlock");
1617 if (err)
1618 break;
1619 } while (*a->searching == TOG_SEARCH_FORWARD && !*a->search_next_done);
1621 return err;
1624 static void
1625 select_commit(struct tog_log_view_state *s)
1627 struct commit_queue_entry *entry;
1628 int ncommits = 0;
1630 entry = s->first_displayed_entry;
1631 while (entry) {
1632 if (ncommits == s->selected) {
1633 s->selected_entry = entry;
1634 break;
1636 entry = TAILQ_NEXT(entry, entry);
1637 ncommits++;
1641 static const struct got_error *
1642 draw_commits(struct tog_view *view)
1644 const struct got_error *err = NULL;
1645 struct tog_log_view_state *s = &view->state.log;
1646 struct commit_queue_entry *entry = s->selected_entry;
1647 const int limit = view->nlines;
1648 int width;
1649 int ncommits, author_cols = 4;
1650 char *id_str = NULL, *header = NULL, *ncommits_str = NULL;
1651 char *refs_str = NULL;
1652 wchar_t *wline;
1653 struct tog_color *tc;
1654 static const size_t date_display_cols = 12;
1656 if (s->selected_entry &&
1657 !(view->searching && view->search_next_done == 0)) {
1658 struct got_reflist_head *refs;
1659 err = got_object_id_str(&id_str, s->selected_entry->id);
1660 if (err)
1661 return err;
1662 refs = got_reflist_object_id_map_lookup(tog_refs_idmap,
1663 s->selected_entry->id);
1664 if (refs) {
1665 err = build_refs_str(&refs_str, refs,
1666 s->selected_entry->id, s->repo);
1667 if (err)
1668 goto done;
1672 if (s->thread_args.commits_needed == 0)
1673 halfdelay(10); /* disable fast refresh */
1675 if (s->thread_args.commits_needed > 0 || s->thread_args.load_all) {
1676 if (asprintf(&ncommits_str, " [%d/%d] %s",
1677 entry ? entry->idx + 1 : 0, s->commits.ncommits,
1678 (view->searching && !view->search_next_done) ?
1679 "searching..." : "loading...") == -1) {
1680 err = got_error_from_errno("asprintf");
1681 goto done;
1683 } else {
1684 const char *search_str = NULL;
1686 if (view->searching) {
1687 if (view->search_next_done == TOG_SEARCH_NO_MORE)
1688 search_str = "no more matches";
1689 else if (view->search_next_done == TOG_SEARCH_HAVE_NONE)
1690 search_str = "no matches found";
1691 else if (!view->search_next_done)
1692 search_str = "searching...";
1695 if (asprintf(&ncommits_str, " [%d/%d] %s",
1696 entry ? entry->idx + 1 : 0, s->commits.ncommits,
1697 search_str ? search_str :
1698 (refs_str ? refs_str : "")) == -1) {
1699 err = got_error_from_errno("asprintf");
1700 goto done;
1704 if (s->in_repo_path && strcmp(s->in_repo_path, "/") != 0) {
1705 if (asprintf(&header, "commit %s %s%s",
1706 id_str ? id_str : "........................................",
1707 s->in_repo_path, ncommits_str) == -1) {
1708 err = got_error_from_errno("asprintf");
1709 header = NULL;
1710 goto done;
1712 } else if (asprintf(&header, "commit %s%s",
1713 id_str ? id_str : "........................................",
1714 ncommits_str) == -1) {
1715 err = got_error_from_errno("asprintf");
1716 header = NULL;
1717 goto done;
1719 err = format_line(&wline, &width, header, view->ncols, 0);
1720 if (err)
1721 goto done;
1723 werase(view->window);
1725 if (view_needs_focus_indication(view))
1726 wstandout(view->window);
1727 tc = get_color(&s->colors, TOG_COLOR_COMMIT);
1728 if (tc)
1729 wattr_on(view->window,
1730 COLOR_PAIR(tc->colorpair), NULL);
1731 waddwstr(view->window, wline);
1732 if (tc)
1733 wattr_off(view->window,
1734 COLOR_PAIR(tc->colorpair), NULL);
1735 while (width < view->ncols) {
1736 waddch(view->window, ' ');
1737 width++;
1739 if (view_needs_focus_indication(view))
1740 wstandend(view->window);
1741 free(wline);
1742 if (limit <= 1)
1743 goto done;
1745 /* Grow author column size if necessary. */
1746 entry = s->first_displayed_entry;
1747 ncommits = 0;
1748 while (entry) {
1749 char *author;
1750 wchar_t *wauthor;
1751 int width;
1752 if (ncommits >= limit - 1)
1753 break;
1754 author = strdup(got_object_commit_get_author(entry->commit));
1755 if (author == NULL) {
1756 err = got_error_from_errno("strdup");
1757 goto done;
1759 err = format_author(&wauthor, &width, author, COLS,
1760 date_display_cols);
1761 if (author_cols < width)
1762 author_cols = width;
1763 free(wauthor);
1764 free(author);
1765 ncommits++;
1766 entry = TAILQ_NEXT(entry, entry);
1769 entry = s->first_displayed_entry;
1770 s->last_displayed_entry = s->first_displayed_entry;
1771 ncommits = 0;
1772 while (entry) {
1773 if (ncommits >= limit - 1)
1774 break;
1775 if (ncommits == s->selected)
1776 wstandout(view->window);
1777 err = draw_commit(view, entry->commit, entry->id,
1778 date_display_cols, author_cols);
1779 if (ncommits == s->selected)
1780 wstandend(view->window);
1781 if (err)
1782 goto done;
1783 ncommits++;
1784 s->last_displayed_entry = entry;
1785 entry = TAILQ_NEXT(entry, entry);
1788 view_vborder(view);
1789 update_panels();
1790 doupdate();
1791 done:
1792 free(id_str);
1793 free(refs_str);
1794 free(ncommits_str);
1795 free(header);
1796 return err;
1799 static void
1800 log_scroll_up(struct tog_log_view_state *s, int maxscroll)
1802 struct commit_queue_entry *entry;
1803 int nscrolled = 0;
1805 entry = TAILQ_FIRST(&s->commits.head);
1806 if (s->first_displayed_entry == entry)
1807 return;
1809 entry = s->first_displayed_entry;
1810 while (entry && nscrolled < maxscroll) {
1811 entry = TAILQ_PREV(entry, commit_queue_head, entry);
1812 if (entry) {
1813 s->first_displayed_entry = entry;
1814 nscrolled++;
1819 static const struct got_error *
1820 trigger_log_thread(struct tog_view *view, int wait)
1822 struct tog_log_thread_args *ta = &view->state.log.thread_args;
1823 int errcode;
1825 halfdelay(1); /* fast refresh while loading commits */
1827 while (ta->commits_needed > 0 || ta->load_all) {
1828 if (ta->log_complete)
1829 break;
1831 /* Wake the log thread. */
1832 errcode = pthread_cond_signal(&ta->need_commits);
1833 if (errcode)
1834 return got_error_set_errno(errcode,
1835 "pthread_cond_signal");
1838 * The mutex will be released while the view loop waits
1839 * in wgetch(), at which time the log thread will run.
1841 if (!wait)
1842 break;
1844 /* Display progress update in log view. */
1845 show_log_view(view);
1846 update_panels();
1847 doupdate();
1849 /* Wait right here while next commit is being loaded. */
1850 errcode = pthread_cond_wait(&ta->commit_loaded, &tog_mutex);
1851 if (errcode)
1852 return got_error_set_errno(errcode,
1853 "pthread_cond_wait");
1855 /* Display progress update in log view. */
1856 show_log_view(view);
1857 update_panels();
1858 doupdate();
1861 return NULL;
1864 static const struct got_error *
1865 log_scroll_down(struct tog_view *view, int maxscroll)
1867 struct tog_log_view_state *s = &view->state.log;
1868 const struct got_error *err = NULL;
1869 struct commit_queue_entry *pentry;
1870 int nscrolled = 0, ncommits_needed;
1872 if (s->last_displayed_entry == NULL)
1873 return NULL;
1875 ncommits_needed = s->last_displayed_entry->idx + 1 + maxscroll;
1876 if (s->commits.ncommits < ncommits_needed &&
1877 !s->thread_args.log_complete) {
1879 * Ask the log thread for required amount of commits.
1881 s->thread_args.commits_needed += maxscroll;
1882 err = trigger_log_thread(view, 1);
1883 if (err)
1884 return err;
1887 do {
1888 pentry = TAILQ_NEXT(s->last_displayed_entry, entry);
1889 if (pentry == NULL)
1890 break;
1892 s->last_displayed_entry = pentry;
1894 pentry = TAILQ_NEXT(s->first_displayed_entry, entry);
1895 if (pentry == NULL)
1896 break;
1897 s->first_displayed_entry = pentry;
1898 } while (++nscrolled < maxscroll);
1900 return err;
1903 static const struct got_error *
1904 open_diff_view_for_commit(struct tog_view **new_view, int begin_x,
1905 struct got_commit_object *commit, struct got_object_id *commit_id,
1906 struct tog_view *log_view, struct got_repository *repo)
1908 const struct got_error *err;
1909 struct got_object_qid *parent_id;
1910 struct tog_view *diff_view;
1912 diff_view = view_open(0, 0, 0, begin_x, TOG_VIEW_DIFF);
1913 if (diff_view == NULL)
1914 return got_error_from_errno("view_open");
1916 parent_id = STAILQ_FIRST(got_object_commit_get_parent_ids(commit));
1917 err = open_diff_view(diff_view, parent_id ? &parent_id->id : NULL,
1918 commit_id, NULL, NULL, 3, 0, 0, log_view, repo);
1919 if (err == NULL)
1920 *new_view = diff_view;
1921 return err;
1924 static const struct got_error *
1925 tree_view_visit_subtree(struct tog_tree_view_state *s,
1926 struct got_tree_object *subtree)
1928 struct tog_parent_tree *parent;
1930 parent = calloc(1, sizeof(*parent));
1931 if (parent == NULL)
1932 return got_error_from_errno("calloc");
1934 parent->tree = s->tree;
1935 parent->first_displayed_entry = s->first_displayed_entry;
1936 parent->selected_entry = s->selected_entry;
1937 parent->selected = s->selected;
1938 TAILQ_INSERT_HEAD(&s->parents, parent, entry);
1939 s->tree = subtree;
1940 s->selected = 0;
1941 s->first_displayed_entry = NULL;
1942 return NULL;
1945 static const struct got_error *
1946 tree_view_walk_path(struct tog_tree_view_state *s,
1947 struct got_commit_object *commit, const char *path)
1949 const struct got_error *err = NULL;
1950 struct got_tree_object *tree = NULL;
1951 const char *p;
1952 char *slash, *subpath = NULL;
1954 /* Walk the path and open corresponding tree objects. */
1955 p = path;
1956 while (*p) {
1957 struct got_tree_entry *te;
1958 struct got_object_id *tree_id;
1959 char *te_name;
1961 while (p[0] == '/')
1962 p++;
1964 /* Ensure the correct subtree entry is selected. */
1965 slash = strchr(p, '/');
1966 if (slash == NULL)
1967 te_name = strdup(p);
1968 else
1969 te_name = strndup(p, slash - p);
1970 if (te_name == NULL) {
1971 err = got_error_from_errno("strndup");
1972 break;
1974 te = got_object_tree_find_entry(s->tree, te_name);
1975 if (te == NULL) {
1976 err = got_error_path(te_name, GOT_ERR_NO_TREE_ENTRY);
1977 free(te_name);
1978 break;
1980 free(te_name);
1981 s->first_displayed_entry = s->selected_entry = te;
1983 if (!S_ISDIR(got_tree_entry_get_mode(s->selected_entry)))
1984 break; /* jump to this file's entry */
1986 slash = strchr(p, '/');
1987 if (slash)
1988 subpath = strndup(path, slash - path);
1989 else
1990 subpath = strdup(path);
1991 if (subpath == NULL) {
1992 err = got_error_from_errno("strdup");
1993 break;
1996 err = got_object_id_by_path(&tree_id, s->repo, commit,
1997 subpath);
1998 if (err)
1999 break;
2001 err = got_object_open_as_tree(&tree, s->repo, tree_id);
2002 free(tree_id);
2003 if (err)
2004 break;
2006 err = tree_view_visit_subtree(s, tree);
2007 if (err) {
2008 got_object_tree_close(tree);
2009 break;
2011 if (slash == NULL)
2012 break;
2013 free(subpath);
2014 subpath = NULL;
2015 p = slash;
2018 free(subpath);
2019 return err;
2022 static const struct got_error *
2023 browse_commit_tree(struct tog_view **new_view, int begin_x,
2024 struct commit_queue_entry *entry, const char *path,
2025 const char *head_ref_name, struct got_repository *repo)
2027 const struct got_error *err = NULL;
2028 struct tog_tree_view_state *s;
2029 struct tog_view *tree_view;
2031 tree_view = view_open(0, 0, 0, begin_x, TOG_VIEW_TREE);
2032 if (tree_view == NULL)
2033 return got_error_from_errno("view_open");
2035 err = open_tree_view(tree_view, entry->id, head_ref_name, repo);
2036 if (err)
2037 return err;
2038 s = &tree_view->state.tree;
2040 *new_view = tree_view;
2042 if (got_path_is_root_dir(path))
2043 return NULL;
2045 return tree_view_walk_path(s, entry->commit, path);
2048 static const struct got_error *
2049 block_signals_used_by_main_thread(void)
2051 sigset_t sigset;
2052 int errcode;
2054 if (sigemptyset(&sigset) == -1)
2055 return got_error_from_errno("sigemptyset");
2057 /* tog handles SIGWINCH, SIGCONT, SIGINT, SIGTERM */
2058 if (sigaddset(&sigset, SIGWINCH) == -1)
2059 return got_error_from_errno("sigaddset");
2060 if (sigaddset(&sigset, SIGCONT) == -1)
2061 return got_error_from_errno("sigaddset");
2062 if (sigaddset(&sigset, SIGINT) == -1)
2063 return got_error_from_errno("sigaddset");
2064 if (sigaddset(&sigset, SIGTERM) == -1)
2065 return got_error_from_errno("sigaddset");
2067 /* ncurses handles SIGTSTP */
2068 if (sigaddset(&sigset, SIGTSTP) == -1)
2069 return got_error_from_errno("sigaddset");
2071 errcode = pthread_sigmask(SIG_BLOCK, &sigset, NULL);
2072 if (errcode)
2073 return got_error_set_errno(errcode, "pthread_sigmask");
2075 return NULL;
2078 static void *
2079 log_thread(void *arg)
2081 const struct got_error *err = NULL;
2082 int errcode = 0;
2083 struct tog_log_thread_args *a = arg;
2084 int done = 0;
2086 err = block_signals_used_by_main_thread();
2087 if (err)
2088 return (void *)err;
2090 while (!done && !err && !tog_fatal_signal_received()) {
2091 err = queue_commits(a);
2092 if (err) {
2093 if (err->code != GOT_ERR_ITER_COMPLETED)
2094 return (void *)err;
2095 err = NULL;
2096 done = 1;
2097 } else if (a->commits_needed > 0 && !a->load_all)
2098 a->commits_needed--;
2100 errcode = pthread_mutex_lock(&tog_mutex);
2101 if (errcode) {
2102 err = got_error_set_errno(errcode,
2103 "pthread_mutex_lock");
2104 break;
2105 } else if (*a->quit)
2106 done = 1;
2107 else if (*a->first_displayed_entry == NULL) {
2108 *a->first_displayed_entry =
2109 TAILQ_FIRST(&a->commits->head);
2110 *a->selected_entry = *a->first_displayed_entry;
2113 errcode = pthread_cond_signal(&a->commit_loaded);
2114 if (errcode) {
2115 err = got_error_set_errno(errcode,
2116 "pthread_cond_signal");
2117 pthread_mutex_unlock(&tog_mutex);
2118 break;
2121 if (done)
2122 a->commits_needed = 0;
2123 else {
2124 if (a->commits_needed == 0 && !a->load_all) {
2125 errcode = pthread_cond_wait(&a->need_commits,
2126 &tog_mutex);
2127 if (errcode)
2128 err = got_error_set_errno(errcode,
2129 "pthread_cond_wait");
2130 if (*a->quit)
2131 done = 1;
2135 errcode = pthread_mutex_unlock(&tog_mutex);
2136 if (errcode && err == NULL)
2137 err = got_error_set_errno(errcode,
2138 "pthread_mutex_unlock");
2140 a->log_complete = 1;
2141 return (void *)err;
2144 static const struct got_error *
2145 stop_log_thread(struct tog_log_view_state *s)
2147 const struct got_error *err = NULL;
2148 int errcode;
2150 if (s->thread) {
2151 s->quit = 1;
2152 errcode = pthread_cond_signal(&s->thread_args.need_commits);
2153 if (errcode)
2154 return got_error_set_errno(errcode,
2155 "pthread_cond_signal");
2156 errcode = pthread_mutex_unlock(&tog_mutex);
2157 if (errcode)
2158 return got_error_set_errno(errcode,
2159 "pthread_mutex_unlock");
2160 errcode = pthread_join(s->thread, (void **)&err);
2161 if (errcode)
2162 return got_error_set_errno(errcode, "pthread_join");
2163 errcode = pthread_mutex_lock(&tog_mutex);
2164 if (errcode)
2165 return got_error_set_errno(errcode,
2166 "pthread_mutex_lock");
2167 s->thread = 0; //NULL;
2170 if (s->thread_args.repo) {
2171 err = got_repo_close(s->thread_args.repo);
2172 s->thread_args.repo = NULL;
2175 if (s->thread_args.pack_fds) {
2176 const struct got_error *pack_err =
2177 got_repo_pack_fds_close(s->thread_args.pack_fds);
2178 if (err == NULL)
2179 err = pack_err;
2180 s->thread_args.pack_fds = NULL;
2183 if (s->thread_args.graph) {
2184 got_commit_graph_close(s->thread_args.graph);
2185 s->thread_args.graph = NULL;
2188 return err;
2191 static const struct got_error *
2192 close_log_view(struct tog_view *view)
2194 const struct got_error *err = NULL;
2195 struct tog_log_view_state *s = &view->state.log;
2196 int errcode;
2198 err = stop_log_thread(s);
2200 errcode = pthread_cond_destroy(&s->thread_args.need_commits);
2201 if (errcode && err == NULL)
2202 err = got_error_set_errno(errcode, "pthread_cond_destroy");
2204 errcode = pthread_cond_destroy(&s->thread_args.commit_loaded);
2205 if (errcode && err == NULL)
2206 err = got_error_set_errno(errcode, "pthread_cond_destroy");
2208 free_commits(&s->commits);
2209 free(s->in_repo_path);
2210 s->in_repo_path = NULL;
2211 free(s->start_id);
2212 s->start_id = NULL;
2213 free(s->head_ref_name);
2214 s->head_ref_name = NULL;
2215 return err;
2218 static const struct got_error *
2219 search_start_log_view(struct tog_view *view)
2221 struct tog_log_view_state *s = &view->state.log;
2223 s->matched_entry = NULL;
2224 s->search_entry = NULL;
2225 return NULL;
2228 static const struct got_error *
2229 search_next_log_view(struct tog_view *view)
2231 const struct got_error *err = NULL;
2232 struct tog_log_view_state *s = &view->state.log;
2233 struct commit_queue_entry *entry;
2235 /* Display progress update in log view. */
2236 show_log_view(view);
2237 update_panels();
2238 doupdate();
2240 if (s->search_entry) {
2241 int errcode, ch;
2242 errcode = pthread_mutex_unlock(&tog_mutex);
2243 if (errcode)
2244 return got_error_set_errno(errcode,
2245 "pthread_mutex_unlock");
2246 ch = wgetch(view->window);
2247 errcode = pthread_mutex_lock(&tog_mutex);
2248 if (errcode)
2249 return got_error_set_errno(errcode,
2250 "pthread_mutex_lock");
2251 if (ch == KEY_BACKSPACE) {
2252 view->search_next_done = TOG_SEARCH_HAVE_MORE;
2253 return NULL;
2255 if (view->searching == TOG_SEARCH_FORWARD)
2256 entry = TAILQ_NEXT(s->search_entry, entry);
2257 else
2258 entry = TAILQ_PREV(s->search_entry,
2259 commit_queue_head, entry);
2260 } else if (s->matched_entry) {
2261 if (view->searching == TOG_SEARCH_FORWARD)
2262 entry = TAILQ_NEXT(s->matched_entry, entry);
2263 else
2264 entry = TAILQ_PREV(s->matched_entry,
2265 commit_queue_head, entry);
2266 } else {
2267 entry = s->selected_entry;
2270 while (1) {
2271 int have_match = 0;
2273 if (entry == NULL) {
2274 if (s->thread_args.log_complete ||
2275 view->searching == TOG_SEARCH_BACKWARD) {
2276 view->search_next_done =
2277 (s->matched_entry == NULL ?
2278 TOG_SEARCH_HAVE_NONE : TOG_SEARCH_NO_MORE);
2279 s->search_entry = NULL;
2280 return NULL;
2283 * Poke the log thread for more commits and return,
2284 * allowing the main loop to make progress. Search
2285 * will resume at s->search_entry once we come back.
2287 s->thread_args.commits_needed++;
2288 return trigger_log_thread(view, 0);
2291 err = match_commit(&have_match, entry->id, entry->commit,
2292 &view->regex);
2293 if (err)
2294 break;
2295 if (have_match) {
2296 view->search_next_done = TOG_SEARCH_HAVE_MORE;
2297 s->matched_entry = entry;
2298 break;
2301 s->search_entry = entry;
2302 if (view->searching == TOG_SEARCH_FORWARD)
2303 entry = TAILQ_NEXT(entry, entry);
2304 else
2305 entry = TAILQ_PREV(entry, commit_queue_head, entry);
2308 if (s->matched_entry) {
2309 int cur = s->selected_entry->idx;
2310 while (cur < s->matched_entry->idx) {
2311 err = input_log_view(NULL, view, KEY_DOWN);
2312 if (err)
2313 return err;
2314 cur++;
2316 while (cur > s->matched_entry->idx) {
2317 err = input_log_view(NULL, view, KEY_UP);
2318 if (err)
2319 return err;
2320 cur--;
2324 s->search_entry = NULL;
2326 return NULL;
2329 static const struct got_error *
2330 open_log_view(struct tog_view *view, struct got_object_id *start_id,
2331 struct got_repository *repo, const char *head_ref_name,
2332 const char *in_repo_path, int log_branches)
2334 const struct got_error *err = NULL;
2335 struct tog_log_view_state *s = &view->state.log;
2336 struct got_repository *thread_repo = NULL;
2337 struct got_commit_graph *thread_graph = NULL;
2338 int errcode;
2340 if (in_repo_path != s->in_repo_path) {
2341 free(s->in_repo_path);
2342 s->in_repo_path = strdup(in_repo_path);
2343 if (s->in_repo_path == NULL)
2344 return got_error_from_errno("strdup");
2347 /* The commit queue only contains commits being displayed. */
2348 TAILQ_INIT(&s->commits.head);
2349 s->commits.ncommits = 0;
2351 s->repo = repo;
2352 if (head_ref_name) {
2353 s->head_ref_name = strdup(head_ref_name);
2354 if (s->head_ref_name == NULL) {
2355 err = got_error_from_errno("strdup");
2356 goto done;
2359 s->start_id = got_object_id_dup(start_id);
2360 if (s->start_id == NULL) {
2361 err = got_error_from_errno("got_object_id_dup");
2362 goto done;
2364 s->log_branches = log_branches;
2366 STAILQ_INIT(&s->colors);
2367 if (has_colors() && getenv("TOG_COLORS") != NULL) {
2368 err = add_color(&s->colors, "^$", TOG_COLOR_COMMIT,
2369 get_color_value("TOG_COLOR_COMMIT"));
2370 if (err)
2371 goto done;
2372 err = add_color(&s->colors, "^$", TOG_COLOR_AUTHOR,
2373 get_color_value("TOG_COLOR_AUTHOR"));
2374 if (err) {
2375 free_colors(&s->colors);
2376 goto done;
2378 err = add_color(&s->colors, "^$", TOG_COLOR_DATE,
2379 get_color_value("TOG_COLOR_DATE"));
2380 if (err) {
2381 free_colors(&s->colors);
2382 goto done;
2386 view->show = show_log_view;
2387 view->input = input_log_view;
2388 view->close = close_log_view;
2389 view->search_start = search_start_log_view;
2390 view->search_next = search_next_log_view;
2392 if (s->thread_args.pack_fds == NULL) {
2393 err = got_repo_pack_fds_open(&s->thread_args.pack_fds);
2394 if (err)
2395 goto done;
2397 err = got_repo_open(&thread_repo, got_repo_get_path(repo), NULL,
2398 s->thread_args.pack_fds);
2399 if (err)
2400 goto done;
2401 err = got_commit_graph_open(&thread_graph, s->in_repo_path,
2402 !s->log_branches);
2403 if (err)
2404 goto done;
2405 err = got_commit_graph_iter_start(thread_graph, s->start_id,
2406 s->repo, NULL, NULL);
2407 if (err)
2408 goto done;
2410 errcode = pthread_cond_init(&s->thread_args.need_commits, NULL);
2411 if (errcode) {
2412 err = got_error_set_errno(errcode, "pthread_cond_init");
2413 goto done;
2415 errcode = pthread_cond_init(&s->thread_args.commit_loaded, NULL);
2416 if (errcode) {
2417 err = got_error_set_errno(errcode, "pthread_cond_init");
2418 goto done;
2421 s->thread_args.commits_needed = view->nlines;
2422 s->thread_args.graph = thread_graph;
2423 s->thread_args.commits = &s->commits;
2424 s->thread_args.in_repo_path = s->in_repo_path;
2425 s->thread_args.start_id = s->start_id;
2426 s->thread_args.repo = thread_repo;
2427 s->thread_args.log_complete = 0;
2428 s->thread_args.quit = &s->quit;
2429 s->thread_args.first_displayed_entry = &s->first_displayed_entry;
2430 s->thread_args.selected_entry = &s->selected_entry;
2431 s->thread_args.searching = &view->searching;
2432 s->thread_args.search_next_done = &view->search_next_done;
2433 s->thread_args.regex = &view->regex;
2434 done:
2435 if (err)
2436 close_log_view(view);
2437 return err;
2440 static const struct got_error *
2441 show_log_view(struct tog_view *view)
2443 const struct got_error *err;
2444 struct tog_log_view_state *s = &view->state.log;
2446 if (s->thread == 0) { //NULL) {
2447 int errcode = pthread_create(&s->thread, NULL, log_thread,
2448 &s->thread_args);
2449 if (errcode)
2450 return got_error_set_errno(errcode, "pthread_create");
2451 if (s->thread_args.commits_needed > 0) {
2452 err = trigger_log_thread(view, 1);
2453 if (err)
2454 return err;
2458 return draw_commits(view);
2461 static const struct got_error *
2462 input_log_view(struct tog_view **new_view, struct tog_view *view, int ch)
2464 const struct got_error *err = NULL;
2465 struct tog_log_view_state *s = &view->state.log;
2466 struct tog_view *diff_view = NULL, *tree_view = NULL;
2467 struct tog_view *ref_view = NULL;
2468 struct commit_queue_entry *entry;
2469 int begin_x = 0, n, nscroll = view->nlines - 1;
2471 if (s->thread_args.load_all) {
2472 if (ch == KEY_BACKSPACE)
2473 s->thread_args.load_all = 0;
2474 else if (s->thread_args.log_complete) {
2475 s->thread_args.load_all = 0;
2476 log_scroll_down(view, s->commits.ncommits);
2477 s->selected = MIN(view->nlines - 2,
2478 s->commits.ncommits - 1);
2479 select_commit(s);
2481 return NULL;
2484 switch (ch) {
2485 case 'q':
2486 s->quit = 1;
2487 break;
2488 case 'k':
2489 case KEY_UP:
2490 case '<':
2491 case ',':
2492 case CTRL('p'):
2493 if (s->first_displayed_entry == NULL)
2494 break;
2495 if (s->selected > 0)
2496 s->selected--;
2497 else
2498 log_scroll_up(s, 1);
2499 select_commit(s);
2500 break;
2501 case 'g':
2502 case KEY_HOME:
2503 s->selected = 0;
2504 s->first_displayed_entry = TAILQ_FIRST(&s->commits.head);
2505 select_commit(s);
2506 break;
2507 case CTRL('u'):
2508 case 'u':
2509 nscroll /= 2;
2510 /* FALL THROUGH */
2511 case KEY_PPAGE:
2512 case CTRL('b'):
2513 if (s->first_displayed_entry == NULL)
2514 break;
2515 if (TAILQ_FIRST(&s->commits.head) == s->first_displayed_entry)
2516 s->selected = MAX(0, s->selected - nscroll - 1);
2517 else
2518 log_scroll_up(s, nscroll);
2519 select_commit(s);
2520 break;
2521 case 'j':
2522 case KEY_DOWN:
2523 case '>':
2524 case '.':
2525 case CTRL('n'):
2526 if (s->first_displayed_entry == NULL)
2527 break;
2528 if (s->selected < MIN(view->nlines - 2,
2529 s->commits.ncommits - 1))
2530 s->selected++;
2531 else {
2532 err = log_scroll_down(view, 1);
2533 if (err)
2534 break;
2536 select_commit(s);
2537 break;
2538 case 'G':
2539 case KEY_END: {
2540 /* We don't know yet how many commits, so we're forced to
2541 * traverse them all. */
2542 if (!s->thread_args.log_complete) {
2543 s->thread_args.load_all = 1;
2544 return trigger_log_thread(view, 0);
2547 s->selected = 0;
2548 entry = TAILQ_LAST(&s->commits.head, commit_queue_head);
2549 for (n = 0; n < view->nlines - 1; n++) {
2550 if (entry == NULL)
2551 break;
2552 s->first_displayed_entry = entry;
2553 entry = TAILQ_PREV(entry, commit_queue_head, entry);
2555 if (n > 0)
2556 s->selected = n - 1;
2557 select_commit(s);
2558 break;
2560 case CTRL('d'):
2561 case 'd':
2562 nscroll /= 2;
2563 /* FALL THROUGH */
2564 case KEY_NPAGE:
2565 case CTRL('f'): {
2566 struct commit_queue_entry *first;
2567 first = s->first_displayed_entry;
2568 if (first == NULL)
2569 break;
2570 err = log_scroll_down(view, nscroll);
2571 if (err)
2572 break;
2573 if (first == s->first_displayed_entry &&
2574 s->selected < MIN(view->nlines - 2,
2575 s->commits.ncommits - 1)) {
2576 /* can't scroll further down */
2577 s->selected += MIN(s->last_displayed_entry->idx -
2578 s->selected_entry->idx, nscroll + 1);
2580 select_commit(s);
2581 break;
2583 case KEY_RESIZE:
2584 if (s->selected > view->nlines - 2)
2585 s->selected = view->nlines - 2;
2586 if (s->selected > s->commits.ncommits - 1)
2587 s->selected = s->commits.ncommits - 1;
2588 select_commit(s);
2589 if (s->commits.ncommits < view->nlines - 1 &&
2590 !s->thread_args.log_complete) {
2591 s->thread_args.commits_needed += (view->nlines - 1) -
2592 s->commits.ncommits;
2593 err = trigger_log_thread(view, 1);
2595 break;
2596 case KEY_ENTER:
2597 case ' ':
2598 case '\r':
2599 if (s->selected_entry == NULL)
2600 break;
2601 if (view_is_parent_view(view))
2602 begin_x = view_split_begin_x(view->begin_x);
2603 err = open_diff_view_for_commit(&diff_view, begin_x,
2604 s->selected_entry->commit, s->selected_entry->id,
2605 view, s->repo);
2606 if (err)
2607 break;
2608 view->focussed = 0;
2609 diff_view->focussed = 1;
2610 if (view_is_parent_view(view)) {
2611 err = view_close_child(view);
2612 if (err)
2613 return err;
2614 view_set_child(view, diff_view);
2615 view->focus_child = 1;
2616 } else
2617 *new_view = diff_view;
2618 break;
2619 case 't':
2620 if (s->selected_entry == NULL)
2621 break;
2622 if (view_is_parent_view(view))
2623 begin_x = view_split_begin_x(view->begin_x);
2624 err = browse_commit_tree(&tree_view, begin_x,
2625 s->selected_entry, s->in_repo_path, s->head_ref_name,
2626 s->repo);
2627 if (err)
2628 break;
2629 view->focussed = 0;
2630 tree_view->focussed = 1;
2631 if (view_is_parent_view(view)) {
2632 err = view_close_child(view);
2633 if (err)
2634 return err;
2635 view_set_child(view, tree_view);
2636 view->focus_child = 1;
2637 } else
2638 *new_view = tree_view;
2639 break;
2640 case KEY_BACKSPACE:
2641 case CTRL('l'):
2642 case 'B':
2643 if (ch == KEY_BACKSPACE &&
2644 got_path_is_root_dir(s->in_repo_path))
2645 break;
2646 err = stop_log_thread(s);
2647 if (err)
2648 return err;
2649 if (ch == KEY_BACKSPACE) {
2650 char *parent_path;
2651 err = got_path_dirname(&parent_path, s->in_repo_path);
2652 if (err)
2653 return err;
2654 free(s->in_repo_path);
2655 s->in_repo_path = parent_path;
2656 s->thread_args.in_repo_path = s->in_repo_path;
2657 } else if (ch == CTRL('l')) {
2658 struct got_object_id *start_id;
2659 err = got_repo_match_object_id(&start_id, NULL,
2660 s->head_ref_name ? s->head_ref_name : GOT_REF_HEAD,
2661 GOT_OBJ_TYPE_COMMIT, &tog_refs, s->repo);
2662 if (err)
2663 return err;
2664 free(s->start_id);
2665 s->start_id = start_id;
2666 s->thread_args.start_id = s->start_id;
2667 } else /* 'B' */
2668 s->log_branches = !s->log_branches;
2670 err = got_repo_open(&s->thread_args.repo,
2671 got_repo_get_path(s->repo), NULL,
2672 s->thread_args.pack_fds);
2673 if (err)
2674 return err;
2675 tog_free_refs();
2676 err = tog_load_refs(s->repo, 0);
2677 if (err)
2678 return err;
2679 err = got_commit_graph_open(&s->thread_args.graph,
2680 s->in_repo_path, !s->log_branches);
2681 if (err)
2682 return err;
2683 err = got_commit_graph_iter_start(s->thread_args.graph,
2684 s->start_id, s->repo, NULL, NULL);
2685 if (err)
2686 return err;
2687 free_commits(&s->commits);
2688 s->first_displayed_entry = NULL;
2689 s->last_displayed_entry = NULL;
2690 s->selected_entry = NULL;
2691 s->selected = 0;
2692 s->thread_args.log_complete = 0;
2693 s->quit = 0;
2694 s->thread_args.commits_needed = view->nlines;
2695 break;
2696 case 'r':
2697 if (view_is_parent_view(view))
2698 begin_x = view_split_begin_x(view->begin_x);
2699 ref_view = view_open(view->nlines, view->ncols,
2700 view->begin_y, begin_x, TOG_VIEW_REF);
2701 if (ref_view == NULL)
2702 return got_error_from_errno("view_open");
2703 err = open_ref_view(ref_view, s->repo);
2704 if (err) {
2705 view_close(ref_view);
2706 return err;
2708 view->focussed = 0;
2709 ref_view->focussed = 1;
2710 if (view_is_parent_view(view)) {
2711 err = view_close_child(view);
2712 if (err)
2713 return err;
2714 view_set_child(view, ref_view);
2715 view->focus_child = 1;
2716 } else
2717 *new_view = ref_view;
2718 break;
2719 default:
2720 break;
2723 return err;
2726 static const struct got_error *
2727 apply_unveil(const char *repo_path, const char *worktree_path)
2729 const struct got_error *error;
2731 #ifdef PROFILE
2732 if (unveil("gmon.out", "rwc") != 0)
2733 return got_error_from_errno2("unveil", "gmon.out");
2734 #endif
2735 if (repo_path && unveil(repo_path, "r") != 0)
2736 return got_error_from_errno2("unveil", repo_path);
2738 if (worktree_path && unveil(worktree_path, "rwc") != 0)
2739 return got_error_from_errno2("unveil", worktree_path);
2741 if (unveil(GOT_TMPDIR_STR, "rwc") != 0)
2742 return got_error_from_errno2("unveil", GOT_TMPDIR_STR);
2744 error = got_privsep_unveil_exec_helpers();
2745 if (error != NULL)
2746 return error;
2748 if (unveil(NULL, NULL) != 0)
2749 return got_error_from_errno("unveil");
2751 return NULL;
2754 static void
2755 init_curses(void)
2758 * Override default signal handlers before starting ncurses.
2759 * This should prevent ncurses from installing its own
2760 * broken cleanup() signal handler.
2762 signal(SIGWINCH, tog_sigwinch);
2763 signal(SIGPIPE, tog_sigpipe);
2764 signal(SIGCONT, tog_sigcont);
2765 signal(SIGINT, tog_sigint);
2766 signal(SIGTERM, tog_sigterm);
2768 initscr();
2769 cbreak();
2770 halfdelay(1); /* Do fast refresh while initial view is loading. */
2771 noecho();
2772 nonl();
2773 intrflush(stdscr, FALSE);
2774 keypad(stdscr, TRUE);
2775 curs_set(0);
2776 if (getenv("TOG_COLORS") != NULL) {
2777 start_color();
2778 use_default_colors();
2782 static const struct got_error *
2783 get_in_repo_path_from_argv0(char **in_repo_path, int argc, char *argv[],
2784 struct got_repository *repo, struct got_worktree *worktree)
2786 const struct got_error *err = NULL;
2788 if (argc == 0) {
2789 *in_repo_path = strdup("/");
2790 if (*in_repo_path == NULL)
2791 return got_error_from_errno("strdup");
2792 return NULL;
2795 if (worktree) {
2796 const char *prefix = got_worktree_get_path_prefix(worktree);
2797 char *p;
2799 err = got_worktree_resolve_path(&p, worktree, argv[0]);
2800 if (err)
2801 return err;
2802 if (asprintf(in_repo_path, "%s%s%s", prefix,
2803 (p[0] != '\0' && !got_path_is_root_dir(prefix)) ? "/" : "",
2804 p) == -1) {
2805 err = got_error_from_errno("asprintf");
2806 *in_repo_path = NULL;
2808 free(p);
2809 } else
2810 err = got_repo_map_path(in_repo_path, repo, argv[0]);
2812 return err;
2815 static const struct got_error *
2816 cmd_log(int argc, char *argv[])
2818 const struct got_error *error;
2819 struct got_repository *repo = NULL;
2820 struct got_worktree *worktree = NULL;
2821 struct got_object_id *start_id = NULL;
2822 char *in_repo_path = NULL, *repo_path = NULL, *cwd = NULL;
2823 char *start_commit = NULL, *label = NULL;
2824 struct got_reference *ref = NULL;
2825 const char *head_ref_name = NULL;
2826 int ch, log_branches = 0;
2827 struct tog_view *view;
2828 int *pack_fds = NULL;
2830 while ((ch = getopt(argc, argv, "bc:r:")) != -1) {
2831 switch (ch) {
2832 case 'b':
2833 log_branches = 1;
2834 break;
2835 case 'c':
2836 start_commit = optarg;
2837 break;
2838 case 'r':
2839 repo_path = realpath(optarg, NULL);
2840 if (repo_path == NULL)
2841 return got_error_from_errno2("realpath",
2842 optarg);
2843 break;
2844 default:
2845 usage_log();
2846 /* NOTREACHED */
2850 argc -= optind;
2851 argv += optind;
2853 if (argc > 1)
2854 usage_log();
2856 error = got_repo_pack_fds_open(&pack_fds);
2857 if (error != NULL)
2858 goto done;
2860 if (repo_path == NULL) {
2861 cwd = getcwd(NULL, 0);
2862 if (cwd == NULL)
2863 return got_error_from_errno("getcwd");
2864 error = got_worktree_open(&worktree, cwd);
2865 if (error && error->code != GOT_ERR_NOT_WORKTREE)
2866 goto done;
2867 if (worktree)
2868 repo_path =
2869 strdup(got_worktree_get_repo_path(worktree));
2870 else
2871 repo_path = strdup(cwd);
2872 if (repo_path == NULL) {
2873 error = got_error_from_errno("strdup");
2874 goto done;
2878 error = got_repo_open(&repo, repo_path, NULL, pack_fds);
2879 if (error != NULL)
2880 goto done;
2882 error = get_in_repo_path_from_argv0(&in_repo_path, argc, argv,
2883 repo, worktree);
2884 if (error)
2885 goto done;
2887 init_curses();
2889 error = apply_unveil(got_repo_get_path(repo),
2890 worktree ? got_worktree_get_root_path(worktree) : NULL);
2891 if (error)
2892 goto done;
2894 /* already loaded by tog_log_with_path()? */
2895 if (TAILQ_EMPTY(&tog_refs)) {
2896 error = tog_load_refs(repo, 0);
2897 if (error)
2898 goto done;
2901 if (start_commit == NULL) {
2902 error = got_repo_match_object_id(&start_id, &label,
2903 worktree ? got_worktree_get_head_ref_name(worktree) :
2904 GOT_REF_HEAD, GOT_OBJ_TYPE_COMMIT, &tog_refs, repo);
2905 if (error)
2906 goto done;
2907 head_ref_name = label;
2908 } else {
2909 error = got_ref_open(&ref, repo, start_commit, 0);
2910 if (error == NULL)
2911 head_ref_name = got_ref_get_name(ref);
2912 else if (error->code != GOT_ERR_NOT_REF)
2913 goto done;
2914 error = got_repo_match_object_id(&start_id, NULL,
2915 start_commit, GOT_OBJ_TYPE_COMMIT, &tog_refs, repo);
2916 if (error)
2917 goto done;
2920 view = view_open(0, 0, 0, 0, TOG_VIEW_LOG);
2921 if (view == NULL) {
2922 error = got_error_from_errno("view_open");
2923 goto done;
2925 error = open_log_view(view, start_id, repo, head_ref_name,
2926 in_repo_path, log_branches);
2927 if (error)
2928 goto done;
2929 if (worktree) {
2930 /* Release work tree lock. */
2931 got_worktree_close(worktree);
2932 worktree = NULL;
2934 error = view_loop(view);
2935 done:
2936 free(in_repo_path);
2937 free(repo_path);
2938 free(cwd);
2939 free(start_id);
2940 free(label);
2941 if (ref)
2942 got_ref_close(ref);
2943 if (repo) {
2944 const struct got_error *close_err = got_repo_close(repo);
2945 if (error == NULL)
2946 error = close_err;
2948 if (worktree)
2949 got_worktree_close(worktree);
2950 if (pack_fds) {
2951 const struct got_error *pack_err =
2952 got_repo_pack_fds_close(pack_fds);
2953 if (error == NULL)
2954 error = pack_err;
2956 tog_free_refs();
2957 return error;
2960 __dead static void
2961 usage_diff(void)
2963 endwin();
2964 fprintf(stderr, "usage: %s diff [-a] [-C number] [-r repository-path] "
2965 "[-w] object1 object2\n", getprogname());
2966 exit(1);
2969 static int
2970 match_line(const char *line, regex_t *regex, size_t nmatch,
2971 regmatch_t *regmatch)
2973 return regexec(regex, line, nmatch, regmatch, 0) == 0;
2976 struct tog_color *
2977 match_color(struct tog_colors *colors, const char *line)
2979 struct tog_color *tc = NULL;
2981 STAILQ_FOREACH(tc, colors, entry) {
2982 if (match_line(line, &tc->regex, 0, NULL))
2983 return tc;
2986 return NULL;
2989 static const struct got_error *
2990 add_matched_line(int *wtotal, const char *line, int wlimit, int col_tab_align,
2991 WINDOW *window, regmatch_t *regmatch)
2993 const struct got_error *err = NULL;
2994 wchar_t *wline;
2995 int width;
2996 char *s;
2998 *wtotal = 0;
3000 s = strndup(line, regmatch->rm_so);
3001 if (s == NULL)
3002 return got_error_from_errno("strndup");
3004 err = format_line(&wline, &width, s, wlimit, col_tab_align);
3005 if (err) {
3006 free(s);
3007 return err;
3009 waddwstr(window, wline);
3010 free(wline);
3011 free(s);
3012 wlimit -= width;
3013 *wtotal += width;
3015 if (wlimit > 0) {
3016 s = strndup(line + regmatch->rm_so,
3017 regmatch->rm_eo - regmatch->rm_so);
3018 if (s == NULL) {
3019 err = got_error_from_errno("strndup");
3020 free(s);
3021 return err;
3023 err = format_line(&wline, &width, s, wlimit, col_tab_align);
3024 if (err) {
3025 free(s);
3026 return err;
3028 wattr_on(window, A_STANDOUT, NULL);
3029 waddwstr(window, wline);
3030 wattr_off(window, A_STANDOUT, NULL);
3031 free(wline);
3032 free(s);
3033 wlimit -= width;
3034 *wtotal += width;
3037 if (wlimit > 0 && strlen(line) > regmatch->rm_eo) {
3038 err = format_line(&wline, &width,
3039 line + regmatch->rm_eo, wlimit, col_tab_align);
3040 if (err)
3041 return err;
3042 waddwstr(window, wline);
3043 free(wline);
3044 *wtotal += width;
3047 return NULL;
3050 static const struct got_error *
3051 draw_file(struct tog_view *view, const char *header)
3053 struct tog_diff_view_state *s = &view->state.diff;
3054 regmatch_t *regmatch = &view->regmatch;
3055 const struct got_error *err;
3056 int nprinted = 0;
3057 char *line;
3058 size_t linesize = 0;
3059 ssize_t linelen;
3060 struct tog_color *tc;
3061 wchar_t *wline;
3062 int width;
3063 int max_lines = view->nlines;
3064 int nlines = s->nlines;
3065 off_t line_offset;
3067 line_offset = s->line_offsets[s->first_displayed_line - 1];
3068 if (fseeko(s->f, line_offset, SEEK_SET) == -1)
3069 return got_error_from_errno("fseek");
3071 werase(view->window);
3073 if (header) {
3074 if (asprintf(&line, "[%d/%d] %s",
3075 s->first_displayed_line - 1 + s->selected_line, nlines,
3076 header) == -1)
3077 return got_error_from_errno("asprintf");
3078 err = format_line(&wline, &width, line, view->ncols, 0);
3079 free(line);
3080 if (err)
3081 return err;
3083 if (view_needs_focus_indication(view))
3084 wstandout(view->window);
3085 waddwstr(view->window, wline);
3086 free(wline);
3087 wline = NULL;
3088 if (view_needs_focus_indication(view))
3089 wstandend(view->window);
3090 if (width <= view->ncols - 1)
3091 waddch(view->window, '\n');
3093 if (max_lines <= 1)
3094 return NULL;
3095 max_lines--;
3098 s->eof = 0;
3099 line = NULL;
3100 while (max_lines > 0 && nprinted < max_lines) {
3101 linelen = getline(&line, &linesize, s->f);
3102 if (linelen == -1) {
3103 if (feof(s->f)) {
3104 s->eof = 1;
3105 break;
3107 free(line);
3108 return got_ferror(s->f, GOT_ERR_IO);
3111 tc = match_color(&s->colors, line);
3112 if (tc)
3113 wattr_on(view->window,
3114 COLOR_PAIR(tc->colorpair), NULL);
3115 if (s->first_displayed_line + nprinted == s->matched_line &&
3116 regmatch->rm_so >= 0 && regmatch->rm_so < regmatch->rm_eo) {
3117 err = add_matched_line(&width, line, view->ncols, 0,
3118 view->window, regmatch);
3119 if (err) {
3120 free(line);
3121 return err;
3123 } else {
3124 err = format_line(&wline, &width, line, view->ncols, 0);
3125 if (err) {
3126 free(line);
3127 return err;
3129 waddwstr(view->window, wline);
3130 free(wline);
3131 wline = NULL;
3133 if (tc)
3134 wattr_off(view->window,
3135 COLOR_PAIR(tc->colorpair), NULL);
3136 if (width <= view->ncols - 1)
3137 waddch(view->window, '\n');
3138 nprinted++;
3140 free(line);
3141 if (nprinted >= 1)
3142 s->last_displayed_line = s->first_displayed_line +
3143 (nprinted - 1);
3144 else
3145 s->last_displayed_line = s->first_displayed_line;
3147 view_vborder(view);
3149 if (s->eof) {
3150 while (nprinted < view->nlines) {
3151 waddch(view->window, '\n');
3152 nprinted++;
3155 err = format_line(&wline, &width, TOG_EOF_STRING, view->ncols, 0);
3156 if (err) {
3157 return err;
3160 wstandout(view->window);
3161 waddwstr(view->window, wline);
3162 free(wline);
3163 wline = NULL;
3164 wstandend(view->window);
3167 return NULL;
3170 static char *
3171 get_datestr(time_t *time, char *datebuf)
3173 struct tm mytm, *tm;
3174 char *p, *s;
3176 tm = gmtime_r(time, &mytm);
3177 if (tm == NULL)
3178 return NULL;
3179 s = asctime_r(tm, datebuf);
3180 if (s == NULL)
3181 return NULL;
3182 p = strchr(s, '\n');
3183 if (p)
3184 *p = '\0';
3185 return s;
3188 static const struct got_error *
3189 get_changed_paths(struct got_pathlist_head *paths,
3190 struct got_commit_object *commit, struct got_repository *repo)
3192 const struct got_error *err = NULL;
3193 struct got_object_id *tree_id1 = NULL, *tree_id2 = NULL;
3194 struct got_tree_object *tree1 = NULL, *tree2 = NULL;
3195 struct got_object_qid *qid;
3197 qid = STAILQ_FIRST(got_object_commit_get_parent_ids(commit));
3198 if (qid != NULL) {
3199 struct got_commit_object *pcommit;
3200 err = got_object_open_as_commit(&pcommit, repo,
3201 &qid->id);
3202 if (err)
3203 return err;
3205 tree_id1 = got_object_id_dup(
3206 got_object_commit_get_tree_id(pcommit));
3207 if (tree_id1 == NULL) {
3208 got_object_commit_close(pcommit);
3209 return got_error_from_errno("got_object_id_dup");
3211 got_object_commit_close(pcommit);
3215 if (tree_id1) {
3216 err = got_object_open_as_tree(&tree1, repo, tree_id1);
3217 if (err)
3218 goto done;
3221 tree_id2 = got_object_commit_get_tree_id(commit);
3222 err = got_object_open_as_tree(&tree2, repo, tree_id2);
3223 if (err)
3224 goto done;
3226 err = got_diff_tree(tree1, tree2, NULL, NULL, "", "", repo,
3227 got_diff_tree_collect_changed_paths, paths, 0);
3228 done:
3229 if (tree1)
3230 got_object_tree_close(tree1);
3231 if (tree2)
3232 got_object_tree_close(tree2);
3233 free(tree_id1);
3234 return err;
3237 static const struct got_error *
3238 add_line_offset(off_t **line_offsets, size_t *nlines, off_t off)
3240 off_t *p;
3242 p = reallocarray(*line_offsets, *nlines + 1, sizeof(off_t));
3243 if (p == NULL)
3244 return got_error_from_errno("reallocarray");
3245 *line_offsets = p;
3246 (*line_offsets)[*nlines] = off;
3247 (*nlines)++;
3248 return NULL;
3251 static const struct got_error *
3252 write_commit_info(off_t **line_offsets, size_t *nlines,
3253 struct got_object_id *commit_id, struct got_reflist_head *refs,
3254 struct got_repository *repo, FILE *outfile)
3256 const struct got_error *err = NULL;
3257 char datebuf[26], *datestr;
3258 struct got_commit_object *commit;
3259 char *id_str = NULL, *logmsg = NULL, *s = NULL, *line;
3260 time_t committer_time;
3261 const char *author, *committer;
3262 char *refs_str = NULL;
3263 struct got_pathlist_head changed_paths;
3264 struct got_pathlist_entry *pe;
3265 off_t outoff = 0;
3266 int n;
3268 TAILQ_INIT(&changed_paths);
3270 if (refs) {
3271 err = build_refs_str(&refs_str, refs, commit_id, repo);
3272 if (err)
3273 return err;
3276 err = got_object_open_as_commit(&commit, repo, commit_id);
3277 if (err)
3278 return err;
3280 err = got_object_id_str(&id_str, commit_id);
3281 if (err) {
3282 err = got_error_from_errno("got_object_id_str");
3283 goto done;
3286 err = add_line_offset(line_offsets, nlines, 0);
3287 if (err)
3288 goto done;
3290 n = fprintf(outfile, "commit %s%s%s%s\n", id_str, refs_str ? " (" : "",
3291 refs_str ? refs_str : "", refs_str ? ")" : "");
3292 if (n < 0) {
3293 err = got_error_from_errno("fprintf");
3294 goto done;
3296 outoff += n;
3297 err = add_line_offset(line_offsets, nlines, outoff);
3298 if (err)
3299 goto done;
3301 n = fprintf(outfile, "from: %s\n",
3302 got_object_commit_get_author(commit));
3303 if (n < 0) {
3304 err = got_error_from_errno("fprintf");
3305 goto done;
3307 outoff += n;
3308 err = add_line_offset(line_offsets, nlines, outoff);
3309 if (err)
3310 goto done;
3312 committer_time = got_object_commit_get_committer_time(commit);
3313 datestr = get_datestr(&committer_time, datebuf);
3314 if (datestr) {
3315 n = fprintf(outfile, "date: %s UTC\n", datestr);
3316 if (n < 0) {
3317 err = got_error_from_errno("fprintf");
3318 goto done;
3320 outoff += n;
3321 err = add_line_offset(line_offsets, nlines, outoff);
3322 if (err)
3323 goto done;
3325 author = got_object_commit_get_author(commit);
3326 committer = got_object_commit_get_committer(commit);
3327 if (strcmp(author, committer) != 0) {
3328 n = fprintf(outfile, "via: %s\n", committer);
3329 if (n < 0) {
3330 err = got_error_from_errno("fprintf");
3331 goto done;
3333 outoff += n;
3334 err = add_line_offset(line_offsets, nlines, outoff);
3335 if (err)
3336 goto done;
3338 if (got_object_commit_get_nparents(commit) > 1) {
3339 const struct got_object_id_queue *parent_ids;
3340 struct got_object_qid *qid;
3341 int pn = 1;
3342 parent_ids = got_object_commit_get_parent_ids(commit);
3343 STAILQ_FOREACH(qid, parent_ids, entry) {
3344 err = got_object_id_str(&id_str, &qid->id);
3345 if (err)
3346 goto done;
3347 n = fprintf(outfile, "parent %d: %s\n", pn++, id_str);
3348 if (n < 0) {
3349 err = got_error_from_errno("fprintf");
3350 goto done;
3352 outoff += n;
3353 err = add_line_offset(line_offsets, nlines, outoff);
3354 if (err)
3355 goto done;
3356 free(id_str);
3357 id_str = NULL;
3361 err = got_object_commit_get_logmsg(&logmsg, commit);
3362 if (err)
3363 goto done;
3364 s = logmsg;
3365 while ((line = strsep(&s, "\n")) != NULL) {
3366 n = fprintf(outfile, "%s\n", line);
3367 if (n < 0) {
3368 err = got_error_from_errno("fprintf");
3369 goto done;
3371 outoff += n;
3372 err = add_line_offset(line_offsets, nlines, outoff);
3373 if (err)
3374 goto done;
3377 err = get_changed_paths(&changed_paths, commit, repo);
3378 if (err)
3379 goto done;
3380 TAILQ_FOREACH(pe, &changed_paths, entry) {
3381 struct got_diff_changed_path *cp = pe->data;
3382 n = fprintf(outfile, "%c %s\n", cp->status, pe->path);
3383 if (n < 0) {
3384 err = got_error_from_errno("fprintf");
3385 goto done;
3387 outoff += n;
3388 err = add_line_offset(line_offsets, nlines, outoff);
3389 if (err)
3390 goto done;
3391 free((char *)pe->path);
3392 free(pe->data);
3395 fputc('\n', outfile);
3396 outoff++;
3397 err = add_line_offset(line_offsets, nlines, outoff);
3398 done:
3399 got_pathlist_free(&changed_paths);
3400 free(id_str);
3401 free(logmsg);
3402 free(refs_str);
3403 got_object_commit_close(commit);
3404 if (err) {
3405 free(*line_offsets);
3406 *line_offsets = NULL;
3407 *nlines = 0;
3409 return err;
3412 static const struct got_error *
3413 create_diff(struct tog_diff_view_state *s)
3415 const struct got_error *err = NULL;
3416 FILE *f = NULL;
3417 int obj_type;
3419 free(s->line_offsets);
3420 s->line_offsets = malloc(sizeof(off_t));
3421 if (s->line_offsets == NULL)
3422 return got_error_from_errno("malloc");
3423 s->nlines = 0;
3425 f = got_opentemp();
3426 if (f == NULL) {
3427 err = got_error_from_errno("got_opentemp");
3428 goto done;
3430 if (s->f && fclose(s->f) == EOF) {
3431 err = got_error_from_errno("fclose");
3432 goto done;
3434 s->f = f;
3436 if (s->id1)
3437 err = got_object_get_type(&obj_type, s->repo, s->id1);
3438 else
3439 err = got_object_get_type(&obj_type, s->repo, s->id2);
3440 if (err)
3441 goto done;
3443 switch (obj_type) {
3444 case GOT_OBJ_TYPE_BLOB:
3445 err = got_diff_objects_as_blobs(&s->line_offsets, &s->nlines,
3446 s->f1, s->f2, s->id1, s->id2, s->label1, s->label2,
3447 s->diff_context, s->ignore_whitespace, s->force_text_diff,
3448 s->repo, s->f);
3449 break;
3450 case GOT_OBJ_TYPE_TREE:
3451 err = got_diff_objects_as_trees(&s->line_offsets, &s->nlines,
3452 s->f1, s->f2, s->id1, s->id2, NULL, "", "", s->diff_context,
3453 s->ignore_whitespace, s->force_text_diff, s->repo, s->f);
3454 break;
3455 case GOT_OBJ_TYPE_COMMIT: {
3456 const struct got_object_id_queue *parent_ids;
3457 struct got_object_qid *pid;
3458 struct got_commit_object *commit2;
3459 struct got_reflist_head *refs;
3461 err = got_object_open_as_commit(&commit2, s->repo, s->id2);
3462 if (err)
3463 goto done;
3464 refs = got_reflist_object_id_map_lookup(tog_refs_idmap, s->id2);
3465 /* Show commit info if we're diffing to a parent/root commit. */
3466 if (s->id1 == NULL) {
3467 err = write_commit_info(&s->line_offsets, &s->nlines,
3468 s->id2, refs, s->repo, s->f);
3469 if (err)
3470 goto done;
3471 } else {
3472 parent_ids = got_object_commit_get_parent_ids(commit2);
3473 STAILQ_FOREACH(pid, parent_ids, entry) {
3474 if (got_object_id_cmp(s->id1, &pid->id) == 0) {
3475 err = write_commit_info(
3476 &s->line_offsets, &s->nlines,
3477 s->id2, refs, s->repo, s->f);
3478 if (err)
3479 goto done;
3480 break;
3484 got_object_commit_close(commit2);
3486 err = got_diff_objects_as_commits(&s->line_offsets, &s->nlines,
3487 s->f1, s->f2, s->id1, s->id2, NULL, s->diff_context,
3488 s->ignore_whitespace, s->force_text_diff, s->repo, s->f);
3489 break;
3491 default:
3492 err = got_error(GOT_ERR_OBJ_TYPE);
3493 break;
3495 if (err)
3496 goto done;
3497 done:
3498 if (s->f && fflush(s->f) != 0 && err == NULL)
3499 err = got_error_from_errno("fflush");
3500 return err;
3503 static void
3504 diff_view_indicate_progress(struct tog_view *view)
3506 mvwaddstr(view->window, 0, 0, "diffing...");
3507 update_panels();
3508 doupdate();
3511 static const struct got_error *
3512 search_start_diff_view(struct tog_view *view)
3514 struct tog_diff_view_state *s = &view->state.diff;
3516 s->matched_line = 0;
3517 return NULL;
3520 static const struct got_error *
3521 search_next_diff_view(struct tog_view *view)
3523 struct tog_diff_view_state *s = &view->state.diff;
3524 int lineno;
3525 char *line = NULL;
3526 size_t linesize = 0;
3527 ssize_t linelen;
3529 if (!view->searching) {
3530 view->search_next_done = TOG_SEARCH_HAVE_MORE;
3531 return NULL;
3534 if (s->matched_line) {
3535 if (view->searching == TOG_SEARCH_FORWARD)
3536 lineno = s->matched_line + 1;
3537 else
3538 lineno = s->matched_line - 1;
3539 } else
3540 lineno = s->first_displayed_line;
3542 while (1) {
3543 off_t offset;
3545 if (lineno <= 0 || lineno > s->nlines) {
3546 if (s->matched_line == 0) {
3547 view->search_next_done = TOG_SEARCH_HAVE_MORE;
3548 break;
3551 if (view->searching == TOG_SEARCH_FORWARD)
3552 lineno = 1;
3553 else
3554 lineno = s->nlines;
3557 offset = s->line_offsets[lineno - 1];
3558 if (fseeko(s->f, offset, SEEK_SET) != 0) {
3559 free(line);
3560 return got_error_from_errno("fseeko");
3562 linelen = getline(&line, &linesize, s->f);
3563 if (linelen != -1 &&
3564 match_line(line, &view->regex, 1, &view->regmatch)) {
3565 view->search_next_done = TOG_SEARCH_HAVE_MORE;
3566 s->matched_line = lineno;
3567 break;
3569 if (view->searching == TOG_SEARCH_FORWARD)
3570 lineno++;
3571 else
3572 lineno--;
3574 free(line);
3576 if (s->matched_line) {
3577 s->first_displayed_line = s->matched_line;
3578 s->selected_line = 1;
3581 return NULL;
3584 static const struct got_error *
3585 close_diff_view(struct tog_view *view)
3587 const struct got_error *err = NULL;
3588 struct tog_diff_view_state *s = &view->state.diff;
3590 free(s->id1);
3591 s->id1 = NULL;
3592 free(s->id2);
3593 s->id2 = NULL;
3594 if (s->f && fclose(s->f) == EOF)
3595 err = got_error_from_errno("fclose");
3596 s->f = NULL;
3597 if (s->f1 && fclose(s->f1) == EOF)
3598 err = got_error_from_errno("fclose");
3599 s->f1 = NULL;
3600 if (s->f2 && fclose(s->f2) == EOF)
3601 err = got_error_from_errno("fclose");
3602 s->f2 = NULL;
3603 free_colors(&s->colors);
3604 free(s->line_offsets);
3605 s->line_offsets = NULL;
3606 s->nlines = 0;
3607 return err;
3610 static const struct got_error *
3611 open_diff_view(struct tog_view *view, struct got_object_id *id1,
3612 struct got_object_id *id2, const char *label1, const char *label2,
3613 int diff_context, int ignore_whitespace, int force_text_diff,
3614 struct tog_view *log_view, struct got_repository *repo)
3616 const struct got_error *err;
3617 struct tog_diff_view_state *s = &view->state.diff;
3619 memset(s, 0, sizeof(*s));
3621 if (id1 != NULL && id2 != NULL) {
3622 int type1, type2;
3623 err = got_object_get_type(&type1, repo, id1);
3624 if (err)
3625 return err;
3626 err = got_object_get_type(&type2, repo, id2);
3627 if (err)
3628 return err;
3630 if (type1 != type2)
3631 return got_error(GOT_ERR_OBJ_TYPE);
3633 s->first_displayed_line = 1;
3634 s->last_displayed_line = view->nlines;
3635 s->selected_line = 1;
3636 s->repo = repo;
3637 s->id1 = id1;
3638 s->id2 = id2;
3639 s->label1 = label1;
3640 s->label2 = label2;
3642 if (id1) {
3643 s->id1 = got_object_id_dup(id1);
3644 if (s->id1 == NULL)
3645 return got_error_from_errno("got_object_id_dup");
3646 s->f1 = got_opentemp();
3647 if (s->f1 == NULL) {
3648 err = got_error_from_errno("got_opentemp");
3649 goto done;
3651 } else
3652 s->id1 = NULL;
3654 s->id2 = got_object_id_dup(id2);
3655 if (s->id2 == NULL) {
3656 err = got_error_from_errno("got_object_id_dup");
3657 goto done;
3660 s->f2 = got_opentemp();
3661 if (s->f2 == NULL) {
3662 err = got_error_from_errno("got_opentemp");
3663 goto done;
3666 s->first_displayed_line = 1;
3667 s->last_displayed_line = view->nlines;
3668 s->diff_context = diff_context;
3669 s->ignore_whitespace = ignore_whitespace;
3670 s->force_text_diff = force_text_diff;
3671 s->log_view = log_view;
3672 s->repo = repo;
3674 STAILQ_INIT(&s->colors);
3675 if (has_colors() && getenv("TOG_COLORS") != NULL) {
3676 err = add_color(&s->colors,
3677 "^-", TOG_COLOR_DIFF_MINUS,
3678 get_color_value("TOG_COLOR_DIFF_MINUS"));
3679 if (err)
3680 goto done;
3681 err = add_color(&s->colors, "^\\+",
3682 TOG_COLOR_DIFF_PLUS,
3683 get_color_value("TOG_COLOR_DIFF_PLUS"));
3684 if (err)
3685 goto done;
3686 err = add_color(&s->colors,
3687 "^@@", TOG_COLOR_DIFF_CHUNK_HEADER,
3688 get_color_value("TOG_COLOR_DIFF_CHUNK_HEADER"));
3689 if (err)
3690 goto done;
3692 err = add_color(&s->colors,
3693 "^(commit [0-9a-f]|parent [0-9]|(blob|file) [-+] |"
3694 "[MDmA] [^ ])", TOG_COLOR_DIFF_META,
3695 get_color_value("TOG_COLOR_DIFF_META"));
3696 if (err)
3697 goto done;
3699 err = add_color(&s->colors,
3700 "^(from|via): ", TOG_COLOR_AUTHOR,
3701 get_color_value("TOG_COLOR_AUTHOR"));
3702 if (err)
3703 goto done;
3705 err = add_color(&s->colors,
3706 "^date: ", TOG_COLOR_DATE,
3707 get_color_value("TOG_COLOR_DATE"));
3708 if (err)
3709 goto done;
3712 if (log_view && view_is_splitscreen(view))
3713 show_log_view(log_view); /* draw vborder */
3714 diff_view_indicate_progress(view);
3716 err = create_diff(s);
3718 view->show = show_diff_view;
3719 view->input = input_diff_view;
3720 view->close = close_diff_view;
3721 view->search_start = search_start_diff_view;
3722 view->search_next = search_next_diff_view;
3723 done:
3724 if (err)
3725 close_diff_view(view);
3726 return err;
3729 static const struct got_error *
3730 show_diff_view(struct tog_view *view)
3732 const struct got_error *err;
3733 struct tog_diff_view_state *s = &view->state.diff;
3734 char *id_str1 = NULL, *id_str2, *header;
3735 const char *label1, *label2;
3737 if (s->id1) {
3738 err = got_object_id_str(&id_str1, s->id1);
3739 if (err)
3740 return err;
3741 label1 = s->label1 ? : id_str1;
3742 } else
3743 label1 = "/dev/null";
3745 err = got_object_id_str(&id_str2, s->id2);
3746 if (err)
3747 return err;
3748 label2 = s->label2 ? : id_str2;
3750 if (asprintf(&header, "diff %s %s", label1, label2) == -1) {
3751 err = got_error_from_errno("asprintf");
3752 free(id_str1);
3753 free(id_str2);
3754 return err;
3756 free(id_str1);
3757 free(id_str2);
3759 err = draw_file(view, header);
3760 free(header);
3761 return err;
3764 static const struct got_error *
3765 set_selected_commit(struct tog_diff_view_state *s,
3766 struct commit_queue_entry *entry)
3768 const struct got_error *err;
3769 const struct got_object_id_queue *parent_ids;
3770 struct got_commit_object *selected_commit;
3771 struct got_object_qid *pid;
3773 free(s->id2);
3774 s->id2 = got_object_id_dup(entry->id);
3775 if (s->id2 == NULL)
3776 return got_error_from_errno("got_object_id_dup");
3778 err = got_object_open_as_commit(&selected_commit, s->repo, entry->id);
3779 if (err)
3780 return err;
3781 parent_ids = got_object_commit_get_parent_ids(selected_commit);
3782 free(s->id1);
3783 pid = STAILQ_FIRST(parent_ids);
3784 s->id1 = pid ? got_object_id_dup(&pid->id) : NULL;
3785 got_object_commit_close(selected_commit);
3786 return NULL;
3789 static const struct got_error *
3790 input_diff_view(struct tog_view **new_view, struct tog_view *view, int ch)
3792 const struct got_error *err = NULL;
3793 struct tog_diff_view_state *s = &view->state.diff;
3794 struct tog_log_view_state *ls;
3795 struct commit_queue_entry *old_selected_entry;
3796 char *line = NULL;
3797 size_t linesize = 0;
3798 ssize_t linelen;
3799 int i, nscroll = view->nlines - 1;
3801 switch (ch) {
3802 case 'a':
3803 case 'w':
3804 if (ch == 'a')
3805 s->force_text_diff = !s->force_text_diff;
3806 if (ch == 'w')
3807 s->ignore_whitespace = !s->ignore_whitespace;
3808 wclear(view->window);
3809 s->first_displayed_line = 1;
3810 s->last_displayed_line = view->nlines;
3811 s->matched_line = 0;
3812 diff_view_indicate_progress(view);
3813 err = create_diff(s);
3814 break;
3815 case 'g':
3816 case KEY_HOME:
3817 s->first_displayed_line = 1;
3818 break;
3819 case 'G':
3820 case KEY_END:
3821 if (s->eof)
3822 break;
3824 s->first_displayed_line = (s->nlines - view->nlines) + 2;
3825 s->eof = 1;
3826 break;
3827 case 'k':
3828 case KEY_UP:
3829 case CTRL('p'):
3830 if (s->first_displayed_line > 1)
3831 s->first_displayed_line--;
3832 break;
3833 case CTRL('u'):
3834 case 'u':
3835 nscroll /= 2;
3836 /* FALL THROUGH */
3837 case KEY_PPAGE:
3838 case CTRL('b'):
3839 if (s->first_displayed_line == 1)
3840 break;
3841 i = 0;
3842 while (i++ < nscroll && s->first_displayed_line > 1)
3843 s->first_displayed_line--;
3844 break;
3845 case 'j':
3846 case KEY_DOWN:
3847 case CTRL('n'):
3848 if (!s->eof)
3849 s->first_displayed_line++;
3850 break;
3851 case CTRL('d'):
3852 case 'd':
3853 nscroll /= 2;
3854 /* FALL THROUGH */
3855 case KEY_NPAGE:
3856 case CTRL('f'):
3857 case ' ':
3858 if (s->eof)
3859 break;
3860 i = 0;
3861 while (!s->eof && i++ < nscroll) {
3862 linelen = getline(&line, &linesize, s->f);
3863 s->first_displayed_line++;
3864 if (linelen == -1) {
3865 if (feof(s->f)) {
3866 s->eof = 1;
3867 } else
3868 err = got_ferror(s->f, GOT_ERR_IO);
3869 break;
3872 free(line);
3873 break;
3874 case '[':
3875 if (s->diff_context > 0) {
3876 s->diff_context--;
3877 s->matched_line = 0;
3878 diff_view_indicate_progress(view);
3879 err = create_diff(s);
3880 if (s->first_displayed_line + view->nlines - 1 >
3881 s->nlines) {
3882 s->first_displayed_line = 1;
3883 s->last_displayed_line = view->nlines;
3886 break;
3887 case ']':
3888 if (s->diff_context < GOT_DIFF_MAX_CONTEXT) {
3889 s->diff_context++;
3890 s->matched_line = 0;
3891 diff_view_indicate_progress(view);
3892 err = create_diff(s);
3894 break;
3895 case '<':
3896 case ',':
3897 if (s->log_view == NULL)
3898 break;
3899 ls = &s->log_view->state.log;
3900 old_selected_entry = ls->selected_entry;
3902 err = input_log_view(NULL, s->log_view, KEY_UP);
3903 if (err)
3904 break;
3906 if (old_selected_entry == ls->selected_entry)
3907 break;
3909 err = set_selected_commit(s, ls->selected_entry);
3910 if (err)
3911 break;
3913 s->first_displayed_line = 1;
3914 s->last_displayed_line = view->nlines;
3915 s->matched_line = 0;
3917 diff_view_indicate_progress(view);
3918 err = create_diff(s);
3919 break;
3920 case '>':
3921 case '.':
3922 if (s->log_view == NULL)
3923 break;
3924 ls = &s->log_view->state.log;
3925 old_selected_entry = ls->selected_entry;
3927 err = input_log_view(NULL, s->log_view, KEY_DOWN);
3928 if (err)
3929 break;
3931 if (old_selected_entry == ls->selected_entry)
3932 break;
3934 err = set_selected_commit(s, ls->selected_entry);
3935 if (err)
3936 break;
3938 s->first_displayed_line = 1;
3939 s->last_displayed_line = view->nlines;
3940 s->matched_line = 0;
3942 diff_view_indicate_progress(view);
3943 err = create_diff(s);
3944 break;
3945 default:
3946 break;
3949 return err;
3952 static const struct got_error *
3953 cmd_diff(int argc, char *argv[])
3955 const struct got_error *error = NULL;
3956 struct got_repository *repo = NULL;
3957 struct got_worktree *worktree = NULL;
3958 struct got_object_id *id1 = NULL, *id2 = NULL;
3959 char *repo_path = NULL, *cwd = NULL;
3960 char *id_str1 = NULL, *id_str2 = NULL;
3961 char *label1 = NULL, *label2 = NULL;
3962 int diff_context = 3, ignore_whitespace = 0;
3963 int ch, force_text_diff = 0;
3964 const char *errstr;
3965 struct tog_view *view;
3966 int *pack_fds = NULL;
3968 while ((ch = getopt(argc, argv, "aC:r:w")) != -1) {
3969 switch (ch) {
3970 case 'a':
3971 force_text_diff = 1;
3972 break;
3973 case 'C':
3974 diff_context = strtonum(optarg, 0, GOT_DIFF_MAX_CONTEXT,
3975 &errstr);
3976 if (errstr != NULL)
3977 errx(1, "number of context lines is %s: %s",
3978 errstr, errstr);
3979 break;
3980 case 'r':
3981 repo_path = realpath(optarg, NULL);
3982 if (repo_path == NULL)
3983 return got_error_from_errno2("realpath",
3984 optarg);
3985 got_path_strip_trailing_slashes(repo_path);
3986 break;
3987 case 'w':
3988 ignore_whitespace = 1;
3989 break;
3990 default:
3991 usage_diff();
3992 /* NOTREACHED */
3996 argc -= optind;
3997 argv += optind;
3999 if (argc == 0) {
4000 usage_diff(); /* TODO show local worktree changes */
4001 } else if (argc == 2) {
4002 id_str1 = argv[0];
4003 id_str2 = argv[1];
4004 } else
4005 usage_diff();
4007 error = got_repo_pack_fds_open(&pack_fds);
4008 if (error)
4009 goto done;
4011 if (repo_path == NULL) {
4012 cwd = getcwd(NULL, 0);
4013 if (cwd == NULL)
4014 return got_error_from_errno("getcwd");
4015 error = got_worktree_open(&worktree, cwd);
4016 if (error && error->code != GOT_ERR_NOT_WORKTREE)
4017 goto done;
4018 if (worktree)
4019 repo_path =
4020 strdup(got_worktree_get_repo_path(worktree));
4021 else
4022 repo_path = strdup(cwd);
4023 if (repo_path == NULL) {
4024 error = got_error_from_errno("strdup");
4025 goto done;
4029 error = got_repo_open(&repo, repo_path, NULL, pack_fds);
4030 if (error)
4031 goto done;
4033 init_curses();
4035 error = apply_unveil(got_repo_get_path(repo), NULL);
4036 if (error)
4037 goto done;
4039 error = tog_load_refs(repo, 0);
4040 if (error)
4041 goto done;
4043 error = got_repo_match_object_id(&id1, &label1, id_str1,
4044 GOT_OBJ_TYPE_ANY, &tog_refs, repo);
4045 if (error)
4046 goto done;
4048 error = got_repo_match_object_id(&id2, &label2, id_str2,
4049 GOT_OBJ_TYPE_ANY, &tog_refs, repo);
4050 if (error)
4051 goto done;
4053 view = view_open(0, 0, 0, 0, TOG_VIEW_DIFF);
4054 if (view == NULL) {
4055 error = got_error_from_errno("view_open");
4056 goto done;
4058 error = open_diff_view(view, id1, id2, label1, label2, diff_context,
4059 ignore_whitespace, force_text_diff, NULL, repo);
4060 if (error)
4061 goto done;
4062 error = view_loop(view);
4063 done:
4064 free(label1);
4065 free(label2);
4066 free(repo_path);
4067 free(cwd);
4068 if (repo) {
4069 const struct got_error *close_err = got_repo_close(repo);
4070 if (error == NULL)
4071 error = close_err;
4073 if (worktree)
4074 got_worktree_close(worktree);
4075 if (pack_fds) {
4076 const struct got_error *pack_err =
4077 got_repo_pack_fds_close(pack_fds);
4078 if (error == NULL)
4079 error = pack_err;
4081 tog_free_refs();
4082 return error;
4085 __dead static void
4086 usage_blame(void)
4088 endwin();
4089 fprintf(stderr, "usage: %s blame [-c commit] [-r repository-path] path\n",
4090 getprogname());
4091 exit(1);
4094 struct tog_blame_line {
4095 int annotated;
4096 struct got_object_id *id;
4099 static const struct got_error *
4100 draw_blame(struct tog_view *view)
4102 struct tog_blame_view_state *s = &view->state.blame;
4103 struct tog_blame *blame = &s->blame;
4104 regmatch_t *regmatch = &view->regmatch;
4105 const struct got_error *err;
4106 int lineno = 0, nprinted = 0;
4107 char *line = NULL;
4108 size_t linesize = 0;
4109 ssize_t linelen;
4110 wchar_t *wline;
4111 int width;
4112 struct tog_blame_line *blame_line;
4113 struct got_object_id *prev_id = NULL;
4114 char *id_str;
4115 struct tog_color *tc;
4117 err = got_object_id_str(&id_str, &s->blamed_commit->id);
4118 if (err)
4119 return err;
4121 rewind(blame->f);
4122 werase(view->window);
4124 if (asprintf(&line, "commit %s", id_str) == -1) {
4125 err = got_error_from_errno("asprintf");
4126 free(id_str);
4127 return err;
4130 err = format_line(&wline, &width, line, view->ncols, 0);
4131 free(line);
4132 line = NULL;
4133 if (err)
4134 return err;
4135 if (view_needs_focus_indication(view))
4136 wstandout(view->window);
4137 tc = get_color(&s->colors, TOG_COLOR_COMMIT);
4138 if (tc)
4139 wattr_on(view->window,
4140 COLOR_PAIR(tc->colorpair), NULL);
4141 waddwstr(view->window, wline);
4142 if (tc)
4143 wattr_off(view->window,
4144 COLOR_PAIR(tc->colorpair), NULL);
4145 if (view_needs_focus_indication(view))
4146 wstandend(view->window);
4147 free(wline);
4148 wline = NULL;
4149 if (width < view->ncols - 1)
4150 waddch(view->window, '\n');
4152 if (asprintf(&line, "[%d/%d] %s%s",
4153 s->first_displayed_line - 1 + s->selected_line, blame->nlines,
4154 s->blame_complete ? "" : "annotating... ", s->path) == -1) {
4155 free(id_str);
4156 return got_error_from_errno("asprintf");
4158 free(id_str);
4159 err = format_line(&wline, &width, line, view->ncols, 0);
4160 free(line);
4161 line = NULL;
4162 if (err)
4163 return err;
4164 waddwstr(view->window, wline);
4165 free(wline);
4166 wline = NULL;
4167 if (width < view->ncols - 1)
4168 waddch(view->window, '\n');
4170 s->eof = 0;
4171 while (nprinted < view->nlines - 2) {
4172 linelen = getline(&line, &linesize, blame->f);
4173 if (linelen == -1) {
4174 if (feof(blame->f)) {
4175 s->eof = 1;
4176 break;
4178 free(line);
4179 return got_ferror(blame->f, GOT_ERR_IO);
4181 if (++lineno < s->first_displayed_line)
4182 continue;
4184 if (view->focussed && nprinted == s->selected_line - 1)
4185 wstandout(view->window);
4187 if (blame->nlines > 0) {
4188 blame_line = &blame->lines[lineno - 1];
4189 if (blame_line->annotated && prev_id &&
4190 got_object_id_cmp(prev_id, blame_line->id) == 0 &&
4191 !(view->focussed &&
4192 nprinted == s->selected_line - 1)) {
4193 waddstr(view->window, " ");
4194 } else if (blame_line->annotated) {
4195 char *id_str;
4196 err = got_object_id_str(&id_str, blame_line->id);
4197 if (err) {
4198 free(line);
4199 return err;
4201 tc = get_color(&s->colors, TOG_COLOR_COMMIT);
4202 if (tc)
4203 wattr_on(view->window,
4204 COLOR_PAIR(tc->colorpair), NULL);
4205 wprintw(view->window, "%.8s", id_str);
4206 if (tc)
4207 wattr_off(view->window,
4208 COLOR_PAIR(tc->colorpair), NULL);
4209 free(id_str);
4210 prev_id = blame_line->id;
4211 } else {
4212 waddstr(view->window, "........");
4213 prev_id = NULL;
4215 } else {
4216 waddstr(view->window, "........");
4217 prev_id = NULL;
4220 if (view->focussed && nprinted == s->selected_line - 1)
4221 wstandend(view->window);
4222 waddstr(view->window, " ");
4224 if (view->ncols <= 9) {
4225 width = 9;
4226 wline = wcsdup(L"");
4227 if (wline == NULL) {
4228 err = got_error_from_errno("wcsdup");
4229 free(line);
4230 return err;
4232 } else if (s->first_displayed_line + nprinted ==
4233 s->matched_line &&
4234 regmatch->rm_so >= 0 && regmatch->rm_so < regmatch->rm_eo) {
4235 err = add_matched_line(&width, line, view->ncols - 9, 9,
4236 view->window, regmatch);
4237 if (err) {
4238 free(line);
4239 return err;
4241 width += 9;
4242 } else {
4243 err = format_line(&wline, &width, line,
4244 view->ncols - 9, 9);
4245 waddwstr(view->window, wline);
4246 free(wline);
4247 wline = NULL;
4248 width += 9;
4251 if (width <= view->ncols - 1)
4252 waddch(view->window, '\n');
4253 if (++nprinted == 1)
4254 s->first_displayed_line = lineno;
4256 free(line);
4257 s->last_displayed_line = lineno;
4259 view_vborder(view);
4261 return NULL;
4264 static const struct got_error *
4265 blame_cb(void *arg, int nlines, int lineno,
4266 struct got_commit_object *commit, struct got_object_id *id)
4268 const struct got_error *err = NULL;
4269 struct tog_blame_cb_args *a = arg;
4270 struct tog_blame_line *line;
4271 int errcode;
4273 if (nlines != a->nlines ||
4274 (lineno != -1 && lineno < 1) || lineno > a->nlines)
4275 return got_error(GOT_ERR_RANGE);
4277 errcode = pthread_mutex_lock(&tog_mutex);
4278 if (errcode)
4279 return got_error_set_errno(errcode, "pthread_mutex_lock");
4281 if (*a->quit) { /* user has quit the blame view */
4282 err = got_error(GOT_ERR_ITER_COMPLETED);
4283 goto done;
4286 if (lineno == -1)
4287 goto done; /* no change in this commit */
4289 line = &a->lines[lineno - 1];
4290 if (line->annotated)
4291 goto done;
4293 line->id = got_object_id_dup(id);
4294 if (line->id == NULL) {
4295 err = got_error_from_errno("got_object_id_dup");
4296 goto done;
4298 line->annotated = 1;
4299 done:
4300 errcode = pthread_mutex_unlock(&tog_mutex);
4301 if (errcode)
4302 err = got_error_set_errno(errcode, "pthread_mutex_unlock");
4303 return err;
4306 static void *
4307 blame_thread(void *arg)
4309 const struct got_error *err, *close_err;
4310 struct tog_blame_thread_args *ta = arg;
4311 struct tog_blame_cb_args *a = ta->cb_args;
4312 int errcode;
4314 err = block_signals_used_by_main_thread();
4315 if (err)
4316 return (void *)err;
4318 err = got_blame(ta->path, a->commit_id, ta->repo,
4319 blame_cb, ta->cb_args, ta->cancel_cb, ta->cancel_arg);
4320 if (err && err->code == GOT_ERR_CANCELLED)
4321 err = NULL;
4323 errcode = pthread_mutex_lock(&tog_mutex);
4324 if (errcode)
4325 return (void *)got_error_set_errno(errcode,
4326 "pthread_mutex_lock");
4328 close_err = got_repo_close(ta->repo);
4329 if (err == NULL)
4330 err = close_err;
4331 ta->repo = NULL;
4332 *ta->complete = 1;
4334 errcode = pthread_mutex_unlock(&tog_mutex);
4335 if (errcode && err == NULL)
4336 err = got_error_set_errno(errcode, "pthread_mutex_unlock");
4338 return (void *)err;
4341 static struct got_object_id *
4342 get_selected_commit_id(struct tog_blame_line *lines, int nlines,
4343 int first_displayed_line, int selected_line)
4345 struct tog_blame_line *line;
4347 if (nlines <= 0)
4348 return NULL;
4350 line = &lines[first_displayed_line - 1 + selected_line - 1];
4351 if (!line->annotated)
4352 return NULL;
4354 return line->id;
4357 static const struct got_error *
4358 stop_blame(struct tog_blame *blame)
4360 const struct got_error *err = NULL;
4361 int i;
4363 if (blame->thread) {
4364 int errcode;
4365 errcode = pthread_mutex_unlock(&tog_mutex);
4366 if (errcode)
4367 return got_error_set_errno(errcode,
4368 "pthread_mutex_unlock");
4369 errcode = pthread_join(blame->thread, (void **)&err);
4370 if (errcode)
4371 return got_error_set_errno(errcode, "pthread_join");
4372 errcode = pthread_mutex_lock(&tog_mutex);
4373 if (errcode)
4374 return got_error_set_errno(errcode,
4375 "pthread_mutex_lock");
4376 if (err && err->code == GOT_ERR_ITER_COMPLETED)
4377 err = NULL;
4378 blame->thread = 0; //NULL;
4380 if (blame->thread_args.repo) {
4381 const struct got_error *close_err;
4382 close_err = got_repo_close(blame->thread_args.repo);
4383 if (err == NULL)
4384 err = close_err;
4385 blame->thread_args.repo = NULL;
4387 if (blame->f) {
4388 if (fclose(blame->f) == EOF && err == NULL)
4389 err = got_error_from_errno("fclose");
4390 blame->f = NULL;
4392 if (blame->lines) {
4393 for (i = 0; i < blame->nlines; i++)
4394 free(blame->lines[i].id);
4395 free(blame->lines);
4396 blame->lines = NULL;
4398 free(blame->cb_args.commit_id);
4399 blame->cb_args.commit_id = NULL;
4400 if (blame->pack_fds) {
4401 const struct got_error *pack_err =
4402 got_repo_pack_fds_close(blame->pack_fds);
4403 if (err == NULL)
4404 err = pack_err;
4405 blame->pack_fds = NULL;
4407 return err;
4410 static const struct got_error *
4411 cancel_blame_view(void *arg)
4413 const struct got_error *err = NULL;
4414 int *done = arg;
4415 int errcode;
4417 errcode = pthread_mutex_lock(&tog_mutex);
4418 if (errcode)
4419 return got_error_set_errno(errcode,
4420 "pthread_mutex_unlock");
4422 if (*done)
4423 err = got_error(GOT_ERR_CANCELLED);
4425 errcode = pthread_mutex_unlock(&tog_mutex);
4426 if (errcode)
4427 return got_error_set_errno(errcode,
4428 "pthread_mutex_lock");
4430 return err;
4433 static const struct got_error *
4434 run_blame(struct tog_view *view)
4436 struct tog_blame_view_state *s = &view->state.blame;
4437 struct tog_blame *blame = &s->blame;
4438 const struct got_error *err = NULL;
4439 struct got_commit_object *commit = NULL;
4440 struct got_blob_object *blob = NULL;
4441 struct got_repository *thread_repo = NULL;
4442 struct got_object_id *obj_id = NULL;
4443 int obj_type;
4444 int *pack_fds = NULL;
4446 err = got_object_open_as_commit(&commit, s->repo,
4447 &s->blamed_commit->id);
4448 if (err)
4449 return err;
4451 err = got_object_id_by_path(&obj_id, s->repo, commit, s->path);
4452 if (err)
4453 goto done;
4455 err = got_object_get_type(&obj_type, s->repo, obj_id);
4456 if (err)
4457 goto done;
4459 if (obj_type != GOT_OBJ_TYPE_BLOB) {
4460 err = got_error(GOT_ERR_OBJ_TYPE);
4461 goto done;
4464 err = got_object_open_as_blob(&blob, s->repo, obj_id, 8192);
4465 if (err)
4466 goto done;
4467 blame->f = got_opentemp();
4468 if (blame->f == NULL) {
4469 err = got_error_from_errno("got_opentemp");
4470 goto done;
4472 err = got_object_blob_dump_to_file(&blame->filesize, &blame->nlines,
4473 &blame->line_offsets, blame->f, blob);
4474 if (err)
4475 goto done;
4476 if (blame->nlines == 0) {
4477 s->blame_complete = 1;
4478 goto done;
4481 /* Don't include \n at EOF in the blame line count. */
4482 if (blame->line_offsets[blame->nlines - 1] == blame->filesize)
4483 blame->nlines--;
4485 blame->lines = calloc(blame->nlines, sizeof(*blame->lines));
4486 if (blame->lines == NULL) {
4487 err = got_error_from_errno("calloc");
4488 goto done;
4491 err = got_repo_pack_fds_open(&pack_fds);
4492 if (err)
4493 goto done;
4494 err = got_repo_open(&thread_repo, got_repo_get_path(s->repo), NULL,
4495 pack_fds);
4496 if (err)
4497 goto done;
4499 blame->pack_fds = pack_fds;
4500 blame->cb_args.view = view;
4501 blame->cb_args.lines = blame->lines;
4502 blame->cb_args.nlines = blame->nlines;
4503 blame->cb_args.commit_id = got_object_id_dup(&s->blamed_commit->id);
4504 if (blame->cb_args.commit_id == NULL) {
4505 err = got_error_from_errno("got_object_id_dup");
4506 goto done;
4508 blame->cb_args.quit = &s->done;
4510 blame->thread_args.path = s->path;
4511 blame->thread_args.repo = thread_repo;
4512 blame->thread_args.cb_args = &blame->cb_args;
4513 blame->thread_args.complete = &s->blame_complete;
4514 blame->thread_args.cancel_cb = cancel_blame_view;
4515 blame->thread_args.cancel_arg = &s->done;
4516 s->blame_complete = 0;
4518 if (s->first_displayed_line + view->nlines - 1 > blame->nlines) {
4519 s->first_displayed_line = 1;
4520 s->last_displayed_line = view->nlines;
4521 s->selected_line = 1;
4523 s->matched_line = 0;
4525 done:
4526 if (commit)
4527 got_object_commit_close(commit);
4528 if (blob)
4529 got_object_blob_close(blob);
4530 free(obj_id);
4531 if (err)
4532 stop_blame(blame);
4533 return err;
4536 static const struct got_error *
4537 open_blame_view(struct tog_view *view, char *path,
4538 struct got_object_id *commit_id, struct got_repository *repo)
4540 const struct got_error *err = NULL;
4541 struct tog_blame_view_state *s = &view->state.blame;
4543 STAILQ_INIT(&s->blamed_commits);
4545 s->path = strdup(path);
4546 if (s->path == NULL)
4547 return got_error_from_errno("strdup");
4549 err = got_object_qid_alloc(&s->blamed_commit, commit_id);
4550 if (err) {
4551 free(s->path);
4552 return err;
4555 STAILQ_INSERT_HEAD(&s->blamed_commits, s->blamed_commit, entry);
4556 s->first_displayed_line = 1;
4557 s->last_displayed_line = view->nlines;
4558 s->selected_line = 1;
4559 s->blame_complete = 0;
4560 s->repo = repo;
4561 s->commit_id = commit_id;
4562 memset(&s->blame, 0, sizeof(s->blame));
4564 STAILQ_INIT(&s->colors);
4565 if (has_colors() && getenv("TOG_COLORS") != NULL) {
4566 err = add_color(&s->colors, "^", TOG_COLOR_COMMIT,
4567 get_color_value("TOG_COLOR_COMMIT"));
4568 if (err)
4569 return err;
4572 view->show = show_blame_view;
4573 view->input = input_blame_view;
4574 view->close = close_blame_view;
4575 view->search_start = search_start_blame_view;
4576 view->search_next = search_next_blame_view;
4578 return run_blame(view);
4581 static const struct got_error *
4582 close_blame_view(struct tog_view *view)
4584 const struct got_error *err = NULL;
4585 struct tog_blame_view_state *s = &view->state.blame;
4587 if (s->blame.thread)
4588 err = stop_blame(&s->blame);
4590 while (!STAILQ_EMPTY(&s->blamed_commits)) {
4591 struct got_object_qid *blamed_commit;
4592 blamed_commit = STAILQ_FIRST(&s->blamed_commits);
4593 STAILQ_REMOVE_HEAD(&s->blamed_commits, entry);
4594 got_object_qid_free(blamed_commit);
4597 free(s->path);
4598 free_colors(&s->colors);
4599 return err;
4602 static const struct got_error *
4603 search_start_blame_view(struct tog_view *view)
4605 struct tog_blame_view_state *s = &view->state.blame;
4607 s->matched_line = 0;
4608 return NULL;
4611 static const struct got_error *
4612 search_next_blame_view(struct tog_view *view)
4614 struct tog_blame_view_state *s = &view->state.blame;
4615 int lineno;
4616 char *line = NULL;
4617 size_t linesize = 0;
4618 ssize_t linelen;
4620 if (!view->searching) {
4621 view->search_next_done = TOG_SEARCH_HAVE_MORE;
4622 return NULL;
4625 if (s->matched_line) {
4626 if (view->searching == TOG_SEARCH_FORWARD)
4627 lineno = s->matched_line + 1;
4628 else
4629 lineno = s->matched_line - 1;
4630 } else
4631 lineno = s->first_displayed_line - 1 + s->selected_line;
4633 while (1) {
4634 off_t offset;
4636 if (lineno <= 0 || lineno > s->blame.nlines) {
4637 if (s->matched_line == 0) {
4638 view->search_next_done = TOG_SEARCH_HAVE_MORE;
4639 break;
4642 if (view->searching == TOG_SEARCH_FORWARD)
4643 lineno = 1;
4644 else
4645 lineno = s->blame.nlines;
4648 offset = s->blame.line_offsets[lineno - 1];
4649 if (fseeko(s->blame.f, offset, SEEK_SET) != 0) {
4650 free(line);
4651 return got_error_from_errno("fseeko");
4653 linelen = getline(&line, &linesize, s->blame.f);
4654 if (linelen != -1 &&
4655 match_line(line, &view->regex, 1, &view->regmatch)) {
4656 view->search_next_done = TOG_SEARCH_HAVE_MORE;
4657 s->matched_line = lineno;
4658 break;
4660 if (view->searching == TOG_SEARCH_FORWARD)
4661 lineno++;
4662 else
4663 lineno--;
4665 free(line);
4667 if (s->matched_line) {
4668 s->first_displayed_line = s->matched_line;
4669 s->selected_line = 1;
4672 return NULL;
4675 static const struct got_error *
4676 show_blame_view(struct tog_view *view)
4678 const struct got_error *err = NULL;
4679 struct tog_blame_view_state *s = &view->state.blame;
4680 int errcode;
4682 if (s->blame.thread == 0 && !s->blame_complete) {
4683 errcode = pthread_create(&s->blame.thread, NULL, blame_thread,
4684 &s->blame.thread_args);
4685 if (errcode)
4686 return got_error_set_errno(errcode, "pthread_create");
4688 halfdelay(1); /* fast refresh while annotating */
4691 if (s->blame_complete)
4692 halfdelay(10); /* disable fast refresh */
4694 err = draw_blame(view);
4696 view_vborder(view);
4697 return err;
4700 static const struct got_error *
4701 input_blame_view(struct tog_view **new_view, struct tog_view *view, int ch)
4703 const struct got_error *err = NULL, *thread_err = NULL;
4704 struct tog_view *diff_view;
4705 struct tog_blame_view_state *s = &view->state.blame;
4706 int begin_x = 0, nscroll = view->nlines - 2;
4708 switch (ch) {
4709 case 'q':
4710 s->done = 1;
4711 break;
4712 case 'g':
4713 case KEY_HOME:
4714 s->selected_line = 1;
4715 s->first_displayed_line = 1;
4716 break;
4717 case 'G':
4718 case KEY_END:
4719 if (s->blame.nlines < view->nlines - 2) {
4720 s->selected_line = s->blame.nlines;
4721 s->first_displayed_line = 1;
4722 } else {
4723 s->selected_line = view->nlines - 2;
4724 s->first_displayed_line = s->blame.nlines -
4725 (view->nlines - 3);
4727 break;
4728 case 'k':
4729 case KEY_UP:
4730 case CTRL('p'):
4731 if (s->selected_line > 1)
4732 s->selected_line--;
4733 else if (s->selected_line == 1 &&
4734 s->first_displayed_line > 1)
4735 s->first_displayed_line--;
4736 break;
4737 case CTRL('u'):
4738 case 'u':
4739 nscroll /= 2;
4740 /* FALL THROUGH */
4741 case KEY_PPAGE:
4742 case CTRL('b'):
4743 if (s->first_displayed_line == 1) {
4744 s->selected_line = MAX(1, s->selected_line - nscroll);
4745 break;
4747 if (s->first_displayed_line > nscroll)
4748 s->first_displayed_line -= nscroll;
4749 else
4750 s->first_displayed_line = 1;
4751 break;
4752 case 'j':
4753 case KEY_DOWN:
4754 case CTRL('n'):
4755 if (s->selected_line < view->nlines - 2 &&
4756 s->first_displayed_line +
4757 s->selected_line <= s->blame.nlines)
4758 s->selected_line++;
4759 else if (s->last_displayed_line <
4760 s->blame.nlines)
4761 s->first_displayed_line++;
4762 break;
4763 case 'b':
4764 case 'p': {
4765 struct got_object_id *id = NULL;
4766 id = get_selected_commit_id(s->blame.lines, s->blame.nlines,
4767 s->first_displayed_line, s->selected_line);
4768 if (id == NULL)
4769 break;
4770 if (ch == 'p') {
4771 struct got_commit_object *commit, *pcommit;
4772 struct got_object_qid *pid;
4773 struct got_object_id *blob_id = NULL;
4774 int obj_type;
4775 err = got_object_open_as_commit(&commit,
4776 s->repo, id);
4777 if (err)
4778 break;
4779 pid = STAILQ_FIRST(
4780 got_object_commit_get_parent_ids(commit));
4781 if (pid == NULL) {
4782 got_object_commit_close(commit);
4783 break;
4785 /* Check if path history ends here. */
4786 err = got_object_open_as_commit(&pcommit,
4787 s->repo, &pid->id);
4788 if (err)
4789 break;
4790 err = got_object_id_by_path(&blob_id, s->repo,
4791 pcommit, s->path);
4792 got_object_commit_close(pcommit);
4793 if (err) {
4794 if (err->code == GOT_ERR_NO_TREE_ENTRY)
4795 err = NULL;
4796 got_object_commit_close(commit);
4797 break;
4799 err = got_object_get_type(&obj_type, s->repo,
4800 blob_id);
4801 free(blob_id);
4802 /* Can't blame non-blob type objects. */
4803 if (obj_type != GOT_OBJ_TYPE_BLOB) {
4804 got_object_commit_close(commit);
4805 break;
4807 err = got_object_qid_alloc(&s->blamed_commit,
4808 &pid->id);
4809 got_object_commit_close(commit);
4810 } else {
4811 if (got_object_id_cmp(id,
4812 &s->blamed_commit->id) == 0)
4813 break;
4814 err = got_object_qid_alloc(&s->blamed_commit,
4815 id);
4817 if (err)
4818 break;
4819 s->done = 1;
4820 thread_err = stop_blame(&s->blame);
4821 s->done = 0;
4822 if (thread_err)
4823 break;
4824 STAILQ_INSERT_HEAD(&s->blamed_commits,
4825 s->blamed_commit, entry);
4826 err = run_blame(view);
4827 if (err)
4828 break;
4829 break;
4831 case 'B': {
4832 struct got_object_qid *first;
4833 first = STAILQ_FIRST(&s->blamed_commits);
4834 if (!got_object_id_cmp(&first->id, s->commit_id))
4835 break;
4836 s->done = 1;
4837 thread_err = stop_blame(&s->blame);
4838 s->done = 0;
4839 if (thread_err)
4840 break;
4841 STAILQ_REMOVE_HEAD(&s->blamed_commits, entry);
4842 got_object_qid_free(s->blamed_commit);
4843 s->blamed_commit =
4844 STAILQ_FIRST(&s->blamed_commits);
4845 err = run_blame(view);
4846 if (err)
4847 break;
4848 break;
4850 case KEY_ENTER:
4851 case '\r': {
4852 struct got_object_id *id = NULL;
4853 struct got_object_qid *pid;
4854 struct got_commit_object *commit = NULL;
4855 id = get_selected_commit_id(s->blame.lines, s->blame.nlines,
4856 s->first_displayed_line, s->selected_line);
4857 if (id == NULL)
4858 break;
4859 err = got_object_open_as_commit(&commit, s->repo, id);
4860 if (err)
4861 break;
4862 pid = STAILQ_FIRST(
4863 got_object_commit_get_parent_ids(commit));
4864 if (view_is_parent_view(view))
4865 begin_x = view_split_begin_x(view->begin_x);
4866 diff_view = view_open(0, 0, 0, begin_x, TOG_VIEW_DIFF);
4867 if (diff_view == NULL) {
4868 got_object_commit_close(commit);
4869 err = got_error_from_errno("view_open");
4870 break;
4872 err = open_diff_view(diff_view, pid ? &pid->id : NULL,
4873 id, NULL, NULL, 3, 0, 0, NULL, s->repo);
4874 got_object_commit_close(commit);
4875 if (err) {
4876 view_close(diff_view);
4877 break;
4879 view->focussed = 0;
4880 diff_view->focussed = 1;
4881 if (view_is_parent_view(view)) {
4882 err = view_close_child(view);
4883 if (err)
4884 break;
4885 view_set_child(view, diff_view);
4886 view->focus_child = 1;
4887 } else
4888 *new_view = diff_view;
4889 if (err)
4890 break;
4891 break;
4893 case CTRL('d'):
4894 case 'd':
4895 nscroll /= 2;
4896 /* FALL THROUGH */
4897 case KEY_NPAGE:
4898 case CTRL('f'):
4899 case ' ':
4900 if (s->last_displayed_line >= s->blame.nlines &&
4901 s->selected_line >= MIN(s->blame.nlines,
4902 view->nlines - 2)) {
4903 break;
4905 if (s->last_displayed_line >= s->blame.nlines &&
4906 s->selected_line < view->nlines - 2) {
4907 s->selected_line +=
4908 MIN(nscroll, s->last_displayed_line -
4909 s->first_displayed_line - s->selected_line + 1);
4911 if (s->last_displayed_line + nscroll <= s->blame.nlines)
4912 s->first_displayed_line += nscroll;
4913 else
4914 s->first_displayed_line =
4915 s->blame.nlines - (view->nlines - 3);
4916 break;
4917 case KEY_RESIZE:
4918 if (s->selected_line > view->nlines - 2) {
4919 s->selected_line = MIN(s->blame.nlines,
4920 view->nlines - 2);
4922 break;
4923 default:
4924 break;
4926 return thread_err ? thread_err : err;
4929 static const struct got_error *
4930 cmd_blame(int argc, char *argv[])
4932 const struct got_error *error;
4933 struct got_repository *repo = NULL;
4934 struct got_worktree *worktree = NULL;
4935 char *cwd = NULL, *repo_path = NULL, *in_repo_path = NULL;
4936 char *link_target = NULL;
4937 struct got_object_id *commit_id = NULL;
4938 struct got_commit_object *commit = NULL;
4939 char *commit_id_str = NULL;
4940 int ch;
4941 struct tog_view *view;
4942 int *pack_fds = NULL;
4944 while ((ch = getopt(argc, argv, "c:r:")) != -1) {
4945 switch (ch) {
4946 case 'c':
4947 commit_id_str = optarg;
4948 break;
4949 case 'r':
4950 repo_path = realpath(optarg, NULL);
4951 if (repo_path == NULL)
4952 return got_error_from_errno2("realpath",
4953 optarg);
4954 break;
4955 default:
4956 usage_blame();
4957 /* NOTREACHED */
4961 argc -= optind;
4962 argv += optind;
4964 if (argc != 1)
4965 usage_blame();
4967 error = got_repo_pack_fds_open(&pack_fds);
4968 if (error != NULL)
4969 goto done;
4971 if (repo_path == NULL) {
4972 cwd = getcwd(NULL, 0);
4973 if (cwd == NULL)
4974 return got_error_from_errno("getcwd");
4975 error = got_worktree_open(&worktree, cwd);
4976 if (error && error->code != GOT_ERR_NOT_WORKTREE)
4977 goto done;
4978 if (worktree)
4979 repo_path =
4980 strdup(got_worktree_get_repo_path(worktree));
4981 else
4982 repo_path = strdup(cwd);
4983 if (repo_path == NULL) {
4984 error = got_error_from_errno("strdup");
4985 goto done;
4989 error = got_repo_open(&repo, repo_path, NULL, pack_fds);
4990 if (error != NULL)
4991 goto done;
4993 error = get_in_repo_path_from_argv0(&in_repo_path, argc, argv, repo,
4994 worktree);
4995 if (error)
4996 goto done;
4998 init_curses();
5000 error = apply_unveil(got_repo_get_path(repo), NULL);
5001 if (error)
5002 goto done;
5004 error = tog_load_refs(repo, 0);
5005 if (error)
5006 goto done;
5008 if (commit_id_str == NULL) {
5009 struct got_reference *head_ref;
5010 error = got_ref_open(&head_ref, repo, worktree ?
5011 got_worktree_get_head_ref_name(worktree) : GOT_REF_HEAD, 0);
5012 if (error != NULL)
5013 goto done;
5014 error = got_ref_resolve(&commit_id, repo, head_ref);
5015 got_ref_close(head_ref);
5016 } else {
5017 error = got_repo_match_object_id(&commit_id, NULL,
5018 commit_id_str, GOT_OBJ_TYPE_COMMIT, &tog_refs, repo);
5020 if (error != NULL)
5021 goto done;
5023 view = view_open(0, 0, 0, 0, TOG_VIEW_BLAME);
5024 if (view == NULL) {
5025 error = got_error_from_errno("view_open");
5026 goto done;
5029 error = got_object_open_as_commit(&commit, repo, commit_id);
5030 if (error)
5031 goto done;
5033 error = got_object_resolve_symlinks(&link_target, in_repo_path,
5034 commit, repo);
5035 if (error)
5036 goto done;
5038 error = open_blame_view(view, link_target ? link_target : in_repo_path,
5039 commit_id, repo);
5040 if (error)
5041 goto done;
5042 if (worktree) {
5043 /* Release work tree lock. */
5044 got_worktree_close(worktree);
5045 worktree = NULL;
5047 error = view_loop(view);
5048 done:
5049 free(repo_path);
5050 free(in_repo_path);
5051 free(link_target);
5052 free(cwd);
5053 free(commit_id);
5054 if (commit)
5055 got_object_commit_close(commit);
5056 if (worktree)
5057 got_worktree_close(worktree);
5058 if (repo) {
5059 const struct got_error *close_err = got_repo_close(repo);
5060 if (error == NULL)
5061 error = close_err;
5063 if (pack_fds) {
5064 const struct got_error *pack_err =
5065 got_repo_pack_fds_close(pack_fds);
5066 if (error == NULL)
5067 error = pack_err;
5069 tog_free_refs();
5070 return error;
5073 static const struct got_error *
5074 draw_tree_entries(struct tog_view *view, const char *parent_path)
5076 struct tog_tree_view_state *s = &view->state.tree;
5077 const struct got_error *err = NULL;
5078 struct got_tree_entry *te;
5079 wchar_t *wline;
5080 struct tog_color *tc;
5081 int width, n, i, nentries;
5082 int limit = view->nlines;
5084 s->ndisplayed = 0;
5086 werase(view->window);
5088 if (limit == 0)
5089 return NULL;
5091 err = format_line(&wline, &width, s->tree_label, view->ncols, 0);
5092 if (err)
5093 return err;
5094 if (view_needs_focus_indication(view))
5095 wstandout(view->window);
5096 tc = get_color(&s->colors, TOG_COLOR_COMMIT);
5097 if (tc)
5098 wattr_on(view->window,
5099 COLOR_PAIR(tc->colorpair), NULL);
5100 waddwstr(view->window, wline);
5101 if (tc)
5102 wattr_off(view->window,
5103 COLOR_PAIR(tc->colorpair), NULL);
5104 if (view_needs_focus_indication(view))
5105 wstandend(view->window);
5106 free(wline);
5107 wline = NULL;
5108 if (width < view->ncols - 1)
5109 waddch(view->window, '\n');
5110 if (--limit <= 0)
5111 return NULL;
5112 err = format_line(&wline, &width, parent_path, view->ncols, 0);
5113 if (err)
5114 return err;
5115 waddwstr(view->window, wline);
5116 free(wline);
5117 wline = NULL;
5118 if (width < view->ncols - 1)
5119 waddch(view->window, '\n');
5120 if (--limit <= 0)
5121 return NULL;
5122 waddch(view->window, '\n');
5123 if (--limit <= 0)
5124 return NULL;
5126 if (s->first_displayed_entry == NULL) {
5127 te = got_object_tree_get_first_entry(s->tree);
5128 if (s->selected == 0) {
5129 if (view->focussed)
5130 wstandout(view->window);
5131 s->selected_entry = NULL;
5133 waddstr(view->window, " ..\n"); /* parent directory */
5134 if (s->selected == 0 && view->focussed)
5135 wstandend(view->window);
5136 s->ndisplayed++;
5137 if (--limit <= 0)
5138 return NULL;
5139 n = 1;
5140 } else {
5141 n = 0;
5142 te = s->first_displayed_entry;
5145 nentries = got_object_tree_get_nentries(s->tree);
5146 for (i = got_tree_entry_get_index(te); i < nentries; i++) {
5147 char *line = NULL, *id_str = NULL, *link_target = NULL;
5148 const char *modestr = "";
5149 mode_t mode;
5151 te = got_object_tree_get_entry(s->tree, i);
5152 mode = got_tree_entry_get_mode(te);
5154 if (s->show_ids) {
5155 err = got_object_id_str(&id_str,
5156 got_tree_entry_get_id(te));
5157 if (err)
5158 return got_error_from_errno(
5159 "got_object_id_str");
5161 if (got_object_tree_entry_is_submodule(te))
5162 modestr = "$";
5163 else if (S_ISLNK(mode)) {
5164 int i;
5166 err = got_tree_entry_get_symlink_target(&link_target,
5167 te, s->repo);
5168 if (err) {
5169 free(id_str);
5170 return err;
5172 for (i = 0; i < strlen(link_target); i++) {
5173 if (!isprint((unsigned char)link_target[i]))
5174 link_target[i] = '?';
5176 modestr = "@";
5178 else if (S_ISDIR(mode))
5179 modestr = "/";
5180 else if (mode & S_IXUSR)
5181 modestr = "*";
5182 if (asprintf(&line, "%s %s%s%s%s", id_str ? id_str : "",
5183 got_tree_entry_get_name(te), modestr,
5184 link_target ? " -> ": "",
5185 link_target ? link_target : "") == -1) {
5186 free(id_str);
5187 free(link_target);
5188 return got_error_from_errno("asprintf");
5190 free(id_str);
5191 free(link_target);
5192 err = format_line(&wline, &width, line, view->ncols, 0);
5193 if (err) {
5194 free(line);
5195 break;
5197 if (n == s->selected) {
5198 if (view->focussed)
5199 wstandout(view->window);
5200 s->selected_entry = te;
5202 tc = match_color(&s->colors, line);
5203 if (tc)
5204 wattr_on(view->window,
5205 COLOR_PAIR(tc->colorpair), NULL);
5206 waddwstr(view->window, wline);
5207 if (tc)
5208 wattr_off(view->window,
5209 COLOR_PAIR(tc->colorpair), NULL);
5210 if (width < view->ncols - 1)
5211 waddch(view->window, '\n');
5212 if (n == s->selected && view->focussed)
5213 wstandend(view->window);
5214 free(line);
5215 free(wline);
5216 wline = NULL;
5217 n++;
5218 s->ndisplayed++;
5219 s->last_displayed_entry = te;
5220 if (--limit <= 0)
5221 break;
5224 return err;
5227 static void
5228 tree_scroll_up(struct tog_tree_view_state *s, int maxscroll)
5230 struct got_tree_entry *te;
5231 int isroot = s->tree == s->root;
5232 int i = 0;
5234 if (s->first_displayed_entry == NULL)
5235 return;
5237 te = got_tree_entry_get_prev(s->tree, s->first_displayed_entry);
5238 while (i++ < maxscroll) {
5239 if (te == NULL) {
5240 if (!isroot)
5241 s->first_displayed_entry = NULL;
5242 break;
5244 s->first_displayed_entry = te;
5245 te = got_tree_entry_get_prev(s->tree, te);
5249 static void
5250 tree_scroll_down(struct tog_tree_view_state *s, int maxscroll)
5252 struct got_tree_entry *next, *last;
5253 int n = 0;
5255 if (s->first_displayed_entry)
5256 next = got_tree_entry_get_next(s->tree,
5257 s->first_displayed_entry);
5258 else
5259 next = got_object_tree_get_first_entry(s->tree);
5261 last = s->last_displayed_entry;
5262 while (next && last && n++ < maxscroll) {
5263 last = got_tree_entry_get_next(s->tree, last);
5264 if (last) {
5265 s->first_displayed_entry = next;
5266 next = got_tree_entry_get_next(s->tree, next);
5271 static const struct got_error *
5272 tree_entry_path(char **path, struct tog_parent_trees *parents,
5273 struct got_tree_entry *te)
5275 const struct got_error *err = NULL;
5276 struct tog_parent_tree *pt;
5277 size_t len = 2; /* for leading slash and NUL */
5279 TAILQ_FOREACH(pt, parents, entry)
5280 len += strlen(got_tree_entry_get_name(pt->selected_entry))
5281 + 1 /* slash */;
5282 if (te)
5283 len += strlen(got_tree_entry_get_name(te));
5285 *path = calloc(1, len);
5286 if (path == NULL)
5287 return got_error_from_errno("calloc");
5289 (*path)[0] = '/';
5290 pt = TAILQ_LAST(parents, tog_parent_trees);
5291 while (pt) {
5292 const char *name = got_tree_entry_get_name(pt->selected_entry);
5293 if (strlcat(*path, name, len) >= len) {
5294 err = got_error(GOT_ERR_NO_SPACE);
5295 goto done;
5297 if (strlcat(*path, "/", len) >= len) {
5298 err = got_error(GOT_ERR_NO_SPACE);
5299 goto done;
5301 pt = TAILQ_PREV(pt, tog_parent_trees, entry);
5303 if (te) {
5304 if (strlcat(*path, got_tree_entry_get_name(te), len) >= len) {
5305 err = got_error(GOT_ERR_NO_SPACE);
5306 goto done;
5309 done:
5310 if (err) {
5311 free(*path);
5312 *path = NULL;
5314 return err;
5317 static const struct got_error *
5318 blame_tree_entry(struct tog_view **new_view, int begin_x,
5319 struct got_tree_entry *te, struct tog_parent_trees *parents,
5320 struct got_object_id *commit_id, struct got_repository *repo)
5322 const struct got_error *err = NULL;
5323 char *path;
5324 struct tog_view *blame_view;
5326 *new_view = NULL;
5328 err = tree_entry_path(&path, parents, te);
5329 if (err)
5330 return err;
5332 blame_view = view_open(0, 0, 0, begin_x, TOG_VIEW_BLAME);
5333 if (blame_view == NULL) {
5334 err = got_error_from_errno("view_open");
5335 goto done;
5338 err = open_blame_view(blame_view, path, commit_id, repo);
5339 if (err) {
5340 if (err->code == GOT_ERR_CANCELLED)
5341 err = NULL;
5342 view_close(blame_view);
5343 } else
5344 *new_view = blame_view;
5345 done:
5346 free(path);
5347 return err;
5350 static const struct got_error *
5351 log_selected_tree_entry(struct tog_view **new_view, int begin_x,
5352 struct tog_tree_view_state *s)
5354 struct tog_view *log_view;
5355 const struct got_error *err = NULL;
5356 char *path;
5358 *new_view = NULL;
5360 log_view = view_open(0, 0, 0, begin_x, TOG_VIEW_LOG);
5361 if (log_view == NULL)
5362 return got_error_from_errno("view_open");
5364 err = tree_entry_path(&path, &s->parents, s->selected_entry);
5365 if (err)
5366 return err;
5368 err = open_log_view(log_view, s->commit_id, s->repo, s->head_ref_name,
5369 path, 0);
5370 if (err)
5371 view_close(log_view);
5372 else
5373 *new_view = log_view;
5374 free(path);
5375 return err;
5378 static const struct got_error *
5379 open_tree_view(struct tog_view *view, struct got_object_id *commit_id,
5380 const char *head_ref_name, struct got_repository *repo)
5382 const struct got_error *err = NULL;
5383 char *commit_id_str = NULL;
5384 struct tog_tree_view_state *s = &view->state.tree;
5385 struct got_commit_object *commit = NULL;
5387 TAILQ_INIT(&s->parents);
5388 STAILQ_INIT(&s->colors);
5390 s->commit_id = got_object_id_dup(commit_id);
5391 if (s->commit_id == NULL)
5392 return got_error_from_errno("got_object_id_dup");
5394 err = got_object_open_as_commit(&commit, repo, commit_id);
5395 if (err)
5396 goto done;
5399 * The root is opened here and will be closed when the view is closed.
5400 * Any visited subtrees and their path-wise parents are opened and
5401 * closed on demand.
5403 err = got_object_open_as_tree(&s->root, repo,
5404 got_object_commit_get_tree_id(commit));
5405 if (err)
5406 goto done;
5407 s->tree = s->root;
5409 err = got_object_id_str(&commit_id_str, commit_id);
5410 if (err != NULL)
5411 goto done;
5413 if (asprintf(&s->tree_label, "commit %s", commit_id_str) == -1) {
5414 err = got_error_from_errno("asprintf");
5415 goto done;
5418 s->first_displayed_entry = got_object_tree_get_entry(s->tree, 0);
5419 s->selected_entry = got_object_tree_get_entry(s->tree, 0);
5420 if (head_ref_name) {
5421 s->head_ref_name = strdup(head_ref_name);
5422 if (s->head_ref_name == NULL) {
5423 err = got_error_from_errno("strdup");
5424 goto done;
5427 s->repo = repo;
5429 if (has_colors() && getenv("TOG_COLORS") != NULL) {
5430 err = add_color(&s->colors, "\\$$",
5431 TOG_COLOR_TREE_SUBMODULE,
5432 get_color_value("TOG_COLOR_TREE_SUBMODULE"));
5433 if (err)
5434 goto done;
5435 err = add_color(&s->colors, "@$", TOG_COLOR_TREE_SYMLINK,
5436 get_color_value("TOG_COLOR_TREE_SYMLINK"));
5437 if (err)
5438 goto done;
5439 err = add_color(&s->colors, "/$",
5440 TOG_COLOR_TREE_DIRECTORY,
5441 get_color_value("TOG_COLOR_TREE_DIRECTORY"));
5442 if (err)
5443 goto done;
5445 err = add_color(&s->colors, "\\*$",
5446 TOG_COLOR_TREE_EXECUTABLE,
5447 get_color_value("TOG_COLOR_TREE_EXECUTABLE"));
5448 if (err)
5449 goto done;
5451 err = add_color(&s->colors, "^$", TOG_COLOR_COMMIT,
5452 get_color_value("TOG_COLOR_COMMIT"));
5453 if (err)
5454 goto done;
5457 view->show = show_tree_view;
5458 view->input = input_tree_view;
5459 view->close = close_tree_view;
5460 view->search_start = search_start_tree_view;
5461 view->search_next = search_next_tree_view;
5462 done:
5463 free(commit_id_str);
5464 if (commit)
5465 got_object_commit_close(commit);
5466 if (err)
5467 close_tree_view(view);
5468 return err;
5471 static const struct got_error *
5472 close_tree_view(struct tog_view *view)
5474 struct tog_tree_view_state *s = &view->state.tree;
5476 free_colors(&s->colors);
5477 free(s->tree_label);
5478 s->tree_label = NULL;
5479 free(s->commit_id);
5480 s->commit_id = NULL;
5481 free(s->head_ref_name);
5482 s->head_ref_name = NULL;
5483 while (!TAILQ_EMPTY(&s->parents)) {
5484 struct tog_parent_tree *parent;
5485 parent = TAILQ_FIRST(&s->parents);
5486 TAILQ_REMOVE(&s->parents, parent, entry);
5487 if (parent->tree != s->root)
5488 got_object_tree_close(parent->tree);
5489 free(parent);
5492 if (s->tree != NULL && s->tree != s->root)
5493 got_object_tree_close(s->tree);
5494 if (s->root)
5495 got_object_tree_close(s->root);
5496 return NULL;
5499 static const struct got_error *
5500 search_start_tree_view(struct tog_view *view)
5502 struct tog_tree_view_state *s = &view->state.tree;
5504 s->matched_entry = NULL;
5505 return NULL;
5508 static int
5509 match_tree_entry(struct got_tree_entry *te, regex_t *regex)
5511 regmatch_t regmatch;
5513 return regexec(regex, got_tree_entry_get_name(te), 1, &regmatch,
5514 0) == 0;
5517 static const struct got_error *
5518 search_next_tree_view(struct tog_view *view)
5520 struct tog_tree_view_state *s = &view->state.tree;
5521 struct got_tree_entry *te = NULL;
5523 if (!view->searching) {
5524 view->search_next_done = TOG_SEARCH_HAVE_MORE;
5525 return NULL;
5528 if (s->matched_entry) {
5529 if (view->searching == TOG_SEARCH_FORWARD) {
5530 if (s->selected_entry)
5531 te = got_tree_entry_get_next(s->tree,
5532 s->selected_entry);
5533 else
5534 te = got_object_tree_get_first_entry(s->tree);
5535 } else {
5536 if (s->selected_entry == NULL)
5537 te = got_object_tree_get_last_entry(s->tree);
5538 else
5539 te = got_tree_entry_get_prev(s->tree,
5540 s->selected_entry);
5542 } else {
5543 if (s->selected_entry)
5544 te = s->selected_entry;
5545 else if (view->searching == TOG_SEARCH_FORWARD)
5546 te = got_object_tree_get_first_entry(s->tree);
5547 else
5548 te = got_object_tree_get_last_entry(s->tree);
5551 while (1) {
5552 if (te == NULL) {
5553 if (s->matched_entry == NULL) {
5554 view->search_next_done = TOG_SEARCH_HAVE_MORE;
5555 return NULL;
5557 if (view->searching == TOG_SEARCH_FORWARD)
5558 te = got_object_tree_get_first_entry(s->tree);
5559 else
5560 te = got_object_tree_get_last_entry(s->tree);
5563 if (match_tree_entry(te, &view->regex)) {
5564 view->search_next_done = TOG_SEARCH_HAVE_MORE;
5565 s->matched_entry = te;
5566 break;
5569 if (view->searching == TOG_SEARCH_FORWARD)
5570 te = got_tree_entry_get_next(s->tree, te);
5571 else
5572 te = got_tree_entry_get_prev(s->tree, te);
5575 if (s->matched_entry) {
5576 s->first_displayed_entry = s->matched_entry;
5577 s->selected = 0;
5580 return NULL;
5583 static const struct got_error *
5584 show_tree_view(struct tog_view *view)
5586 const struct got_error *err = NULL;
5587 struct tog_tree_view_state *s = &view->state.tree;
5588 char *parent_path;
5590 err = tree_entry_path(&parent_path, &s->parents, NULL);
5591 if (err)
5592 return err;
5594 err = draw_tree_entries(view, parent_path);
5595 free(parent_path);
5597 view_vborder(view);
5598 return err;
5601 static const struct got_error *
5602 input_tree_view(struct tog_view **new_view, struct tog_view *view, int ch)
5604 const struct got_error *err = NULL;
5605 struct tog_tree_view_state *s = &view->state.tree;
5606 struct tog_view *log_view, *ref_view;
5607 struct got_tree_entry *te;
5608 int begin_x = 0, n, nscroll = view->nlines - 3;
5610 switch (ch) {
5611 case 'i':
5612 s->show_ids = !s->show_ids;
5613 break;
5614 case 'l':
5615 if (!s->selected_entry)
5616 break;
5617 if (view_is_parent_view(view))
5618 begin_x = view_split_begin_x(view->begin_x);
5619 err = log_selected_tree_entry(&log_view, begin_x, s);
5620 view->focussed = 0;
5621 log_view->focussed = 1;
5622 if (view_is_parent_view(view)) {
5623 err = view_close_child(view);
5624 if (err)
5625 return err;
5626 view_set_child(view, log_view);
5627 view->focus_child = 1;
5628 } else
5629 *new_view = log_view;
5630 break;
5631 case 'r':
5632 if (view_is_parent_view(view))
5633 begin_x = view_split_begin_x(view->begin_x);
5634 ref_view = view_open(view->nlines, view->ncols,
5635 view->begin_y, begin_x, TOG_VIEW_REF);
5636 if (ref_view == NULL)
5637 return got_error_from_errno("view_open");
5638 err = open_ref_view(ref_view, s->repo);
5639 if (err) {
5640 view_close(ref_view);
5641 return err;
5643 view->focussed = 0;
5644 ref_view->focussed = 1;
5645 if (view_is_parent_view(view)) {
5646 err = view_close_child(view);
5647 if (err)
5648 return err;
5649 view_set_child(view, ref_view);
5650 view->focus_child = 1;
5651 } else
5652 *new_view = ref_view;
5653 break;
5654 case 'g':
5655 case KEY_HOME:
5656 s->selected = 0;
5657 if (s->tree == s->root)
5658 s->first_displayed_entry =
5659 got_object_tree_get_first_entry(s->tree);
5660 else
5661 s->first_displayed_entry = NULL;
5662 break;
5663 case 'G':
5664 case KEY_END:
5665 s->selected = 0;
5666 te = got_object_tree_get_last_entry(s->tree);
5667 for (n = 0; n < view->nlines - 3; n++) {
5668 if (te == NULL) {
5669 if(s->tree != s->root) {
5670 s->first_displayed_entry = NULL;
5671 n++;
5673 break;
5675 s->first_displayed_entry = te;
5676 te = got_tree_entry_get_prev(s->tree, te);
5678 if (n > 0)
5679 s->selected = n - 1;
5680 break;
5681 case 'k':
5682 case KEY_UP:
5683 case CTRL('p'):
5684 if (s->selected > 0) {
5685 s->selected--;
5686 break;
5688 tree_scroll_up(s, 1);
5689 break;
5690 case CTRL('u'):
5691 case 'u':
5692 nscroll /= 2;
5693 /* FALL THROUGH */
5694 case KEY_PPAGE:
5695 case CTRL('b'):
5696 if (s->tree == s->root) {
5697 if (got_object_tree_get_first_entry(s->tree) ==
5698 s->first_displayed_entry)
5699 s->selected -= MIN(s->selected, nscroll);
5700 } else {
5701 if (s->first_displayed_entry == NULL)
5702 s->selected -= MIN(s->selected, nscroll);
5704 tree_scroll_up(s, MAX(0, nscroll));
5705 break;
5706 case 'j':
5707 case KEY_DOWN:
5708 case CTRL('n'):
5709 if (s->selected < s->ndisplayed - 1) {
5710 s->selected++;
5711 break;
5713 if (got_tree_entry_get_next(s->tree, s->last_displayed_entry)
5714 == NULL)
5715 /* can't scroll any further */
5716 break;
5717 tree_scroll_down(s, 1);
5718 break;
5719 case CTRL('d'):
5720 case 'd':
5721 nscroll /= 2;
5722 /* FALL THROUGH */
5723 case KEY_NPAGE:
5724 case CTRL('f'):
5725 if (got_tree_entry_get_next(s->tree, s->last_displayed_entry)
5726 == NULL) {
5727 /* can't scroll any further; move cursor down */
5728 if (s->selected < s->ndisplayed - 1)
5729 s->selected += MIN(nscroll,
5730 s->ndisplayed - s->selected - 1);
5731 break;
5733 tree_scroll_down(s, nscroll);
5734 break;
5735 case KEY_ENTER:
5736 case '\r':
5737 case KEY_BACKSPACE:
5738 if (s->selected_entry == NULL || ch == KEY_BACKSPACE) {
5739 struct tog_parent_tree *parent;
5740 /* user selected '..' */
5741 if (s->tree == s->root)
5742 break;
5743 parent = TAILQ_FIRST(&s->parents);
5744 TAILQ_REMOVE(&s->parents, parent,
5745 entry);
5746 got_object_tree_close(s->tree);
5747 s->tree = parent->tree;
5748 s->first_displayed_entry =
5749 parent->first_displayed_entry;
5750 s->selected_entry =
5751 parent->selected_entry;
5752 s->selected = parent->selected;
5753 free(parent);
5754 } else if (S_ISDIR(got_tree_entry_get_mode(
5755 s->selected_entry))) {
5756 struct got_tree_object *subtree;
5757 err = got_object_open_as_tree(&subtree, s->repo,
5758 got_tree_entry_get_id(s->selected_entry));
5759 if (err)
5760 break;
5761 err = tree_view_visit_subtree(s, subtree);
5762 if (err) {
5763 got_object_tree_close(subtree);
5764 break;
5766 } else if (S_ISREG(got_tree_entry_get_mode(
5767 s->selected_entry))) {
5768 struct tog_view *blame_view;
5769 int begin_x = view_is_parent_view(view) ?
5770 view_split_begin_x(view->begin_x) : 0;
5772 err = blame_tree_entry(&blame_view, begin_x,
5773 s->selected_entry, &s->parents,
5774 s->commit_id, s->repo);
5775 if (err)
5776 break;
5777 view->focussed = 0;
5778 blame_view->focussed = 1;
5779 if (view_is_parent_view(view)) {
5780 err = view_close_child(view);
5781 if (err)
5782 return err;
5783 view_set_child(view, blame_view);
5784 view->focus_child = 1;
5785 } else
5786 *new_view = blame_view;
5788 break;
5789 case KEY_RESIZE:
5790 if (view->nlines >= 4 && s->selected >= view->nlines - 3)
5791 s->selected = view->nlines - 4;
5792 break;
5793 default:
5794 break;
5797 return err;
5800 __dead static void
5801 usage_tree(void)
5803 endwin();
5804 fprintf(stderr, "usage: %s tree [-c commit] [-r repository-path] [path]\n",
5805 getprogname());
5806 exit(1);
5809 static const struct got_error *
5810 cmd_tree(int argc, char *argv[])
5812 const struct got_error *error;
5813 struct got_repository *repo = NULL;
5814 struct got_worktree *worktree = NULL;
5815 char *cwd = NULL, *repo_path = NULL, *in_repo_path = NULL;
5816 struct got_object_id *commit_id = NULL;
5817 struct got_commit_object *commit = NULL;
5818 const char *commit_id_arg = NULL;
5819 char *label = NULL;
5820 struct got_reference *ref = NULL;
5821 const char *head_ref_name = NULL;
5822 int ch;
5823 struct tog_view *view;
5824 int *pack_fds = NULL;
5826 while ((ch = getopt(argc, argv, "c:r:")) != -1) {
5827 switch (ch) {
5828 case 'c':
5829 commit_id_arg = optarg;
5830 break;
5831 case 'r':
5832 repo_path = realpath(optarg, NULL);
5833 if (repo_path == NULL)
5834 return got_error_from_errno2("realpath",
5835 optarg);
5836 break;
5837 default:
5838 usage_tree();
5839 /* NOTREACHED */
5843 argc -= optind;
5844 argv += optind;
5846 if (argc > 1)
5847 usage_tree();
5849 error = got_repo_pack_fds_open(&pack_fds);
5850 if (error != NULL)
5851 goto done;
5853 if (repo_path == NULL) {
5854 cwd = getcwd(NULL, 0);
5855 if (cwd == NULL)
5856 return got_error_from_errno("getcwd");
5857 error = got_worktree_open(&worktree, cwd);
5858 if (error && error->code != GOT_ERR_NOT_WORKTREE)
5859 goto done;
5860 if (worktree)
5861 repo_path =
5862 strdup(got_worktree_get_repo_path(worktree));
5863 else
5864 repo_path = strdup(cwd);
5865 if (repo_path == NULL) {
5866 error = got_error_from_errno("strdup");
5867 goto done;
5871 error = got_repo_open(&repo, repo_path, NULL, pack_fds);
5872 if (error != NULL)
5873 goto done;
5875 error = get_in_repo_path_from_argv0(&in_repo_path, argc, argv,
5876 repo, worktree);
5877 if (error)
5878 goto done;
5880 init_curses();
5882 error = apply_unveil(got_repo_get_path(repo), NULL);
5883 if (error)
5884 goto done;
5886 error = tog_load_refs(repo, 0);
5887 if (error)
5888 goto done;
5890 if (commit_id_arg == NULL) {
5891 error = got_repo_match_object_id(&commit_id, &label,
5892 worktree ? got_worktree_get_head_ref_name(worktree) :
5893 GOT_REF_HEAD, GOT_OBJ_TYPE_COMMIT, &tog_refs, repo);
5894 if (error)
5895 goto done;
5896 head_ref_name = label;
5897 } else {
5898 error = got_ref_open(&ref, repo, commit_id_arg, 0);
5899 if (error == NULL)
5900 head_ref_name = got_ref_get_name(ref);
5901 else if (error->code != GOT_ERR_NOT_REF)
5902 goto done;
5903 error = got_repo_match_object_id(&commit_id, NULL,
5904 commit_id_arg, GOT_OBJ_TYPE_COMMIT, &tog_refs, repo);
5905 if (error)
5906 goto done;
5909 error = got_object_open_as_commit(&commit, repo, commit_id);
5910 if (error)
5911 goto done;
5913 view = view_open(0, 0, 0, 0, TOG_VIEW_TREE);
5914 if (view == NULL) {
5915 error = got_error_from_errno("view_open");
5916 goto done;
5918 error = open_tree_view(view, commit_id, head_ref_name, repo);
5919 if (error)
5920 goto done;
5921 if (!got_path_is_root_dir(in_repo_path)) {
5922 error = tree_view_walk_path(&view->state.tree, commit,
5923 in_repo_path);
5924 if (error)
5925 goto done;
5928 if (worktree) {
5929 /* Release work tree lock. */
5930 got_worktree_close(worktree);
5931 worktree = NULL;
5933 error = view_loop(view);
5934 done:
5935 free(repo_path);
5936 free(cwd);
5937 free(commit_id);
5938 free(label);
5939 if (ref)
5940 got_ref_close(ref);
5941 if (repo) {
5942 const struct got_error *close_err = got_repo_close(repo);
5943 if (error == NULL)
5944 error = close_err;
5946 if (pack_fds) {
5947 const struct got_error *pack_err =
5948 got_repo_pack_fds_close(pack_fds);
5949 if (error == NULL)
5950 error = pack_err;
5952 tog_free_refs();
5953 return error;
5956 static const struct got_error *
5957 ref_view_load_refs(struct tog_ref_view_state *s)
5959 struct got_reflist_entry *sre;
5960 struct tog_reflist_entry *re;
5962 s->nrefs = 0;
5963 TAILQ_FOREACH(sre, &tog_refs, entry) {
5964 if (strncmp(got_ref_get_name(sre->ref),
5965 "refs/got/", 9) == 0 &&
5966 strncmp(got_ref_get_name(sre->ref),
5967 "refs/got/backup/", 16) != 0)
5968 continue;
5970 re = malloc(sizeof(*re));
5971 if (re == NULL)
5972 return got_error_from_errno("malloc");
5974 re->ref = got_ref_dup(sre->ref);
5975 if (re->ref == NULL)
5976 return got_error_from_errno("got_ref_dup");
5977 re->idx = s->nrefs++;
5978 TAILQ_INSERT_TAIL(&s->refs, re, entry);
5981 s->first_displayed_entry = TAILQ_FIRST(&s->refs);
5982 return NULL;
5985 void
5986 ref_view_free_refs(struct tog_ref_view_state *s)
5988 struct tog_reflist_entry *re;
5990 while (!TAILQ_EMPTY(&s->refs)) {
5991 re = TAILQ_FIRST(&s->refs);
5992 TAILQ_REMOVE(&s->refs, re, entry);
5993 got_ref_close(re->ref);
5994 free(re);
5998 static const struct got_error *
5999 open_ref_view(struct tog_view *view, struct got_repository *repo)
6001 const struct got_error *err = NULL;
6002 struct tog_ref_view_state *s = &view->state.ref;
6004 s->selected_entry = 0;
6005 s->repo = repo;
6007 TAILQ_INIT(&s->refs);
6008 STAILQ_INIT(&s->colors);
6010 err = ref_view_load_refs(s);
6011 if (err)
6012 return err;
6014 if (has_colors() && getenv("TOG_COLORS") != NULL) {
6015 err = add_color(&s->colors, "^refs/heads/",
6016 TOG_COLOR_REFS_HEADS,
6017 get_color_value("TOG_COLOR_REFS_HEADS"));
6018 if (err)
6019 goto done;
6021 err = add_color(&s->colors, "^refs/tags/",
6022 TOG_COLOR_REFS_TAGS,
6023 get_color_value("TOG_COLOR_REFS_TAGS"));
6024 if (err)
6025 goto done;
6027 err = add_color(&s->colors, "^refs/remotes/",
6028 TOG_COLOR_REFS_REMOTES,
6029 get_color_value("TOG_COLOR_REFS_REMOTES"));
6030 if (err)
6031 goto done;
6033 err = add_color(&s->colors, "^refs/got/backup/",
6034 TOG_COLOR_REFS_BACKUP,
6035 get_color_value("TOG_COLOR_REFS_BACKUP"));
6036 if (err)
6037 goto done;
6040 view->show = show_ref_view;
6041 view->input = input_ref_view;
6042 view->close = close_ref_view;
6043 view->search_start = search_start_ref_view;
6044 view->search_next = search_next_ref_view;
6045 done:
6046 if (err)
6047 free_colors(&s->colors);
6048 return err;
6051 static const struct got_error *
6052 close_ref_view(struct tog_view *view)
6054 struct tog_ref_view_state *s = &view->state.ref;
6056 ref_view_free_refs(s);
6057 free_colors(&s->colors);
6059 return NULL;
6062 static const struct got_error *
6063 resolve_reflist_entry(struct got_object_id **commit_id,
6064 struct tog_reflist_entry *re, struct got_repository *repo)
6066 const struct got_error *err = NULL;
6067 struct got_object_id *obj_id;
6068 struct got_tag_object *tag = NULL;
6069 int obj_type;
6071 *commit_id = NULL;
6073 err = got_ref_resolve(&obj_id, repo, re->ref);
6074 if (err)
6075 return err;
6077 err = got_object_get_type(&obj_type, repo, obj_id);
6078 if (err)
6079 goto done;
6081 switch (obj_type) {
6082 case GOT_OBJ_TYPE_COMMIT:
6083 *commit_id = obj_id;
6084 break;
6085 case GOT_OBJ_TYPE_TAG:
6086 err = got_object_open_as_tag(&tag, repo, obj_id);
6087 if (err)
6088 goto done;
6089 free(obj_id);
6090 err = got_object_get_type(&obj_type, repo,
6091 got_object_tag_get_object_id(tag));
6092 if (err)
6093 goto done;
6094 if (obj_type != GOT_OBJ_TYPE_COMMIT) {
6095 err = got_error(GOT_ERR_OBJ_TYPE);
6096 goto done;
6098 *commit_id = got_object_id_dup(
6099 got_object_tag_get_object_id(tag));
6100 if (*commit_id == NULL) {
6101 err = got_error_from_errno("got_object_id_dup");
6102 goto done;
6104 break;
6105 default:
6106 err = got_error(GOT_ERR_OBJ_TYPE);
6107 break;
6110 done:
6111 if (tag)
6112 got_object_tag_close(tag);
6113 if (err) {
6114 free(*commit_id);
6115 *commit_id = NULL;
6117 return err;
6120 static const struct got_error *
6121 log_ref_entry(struct tog_view **new_view, int begin_x,
6122 struct tog_reflist_entry *re, struct got_repository *repo)
6124 struct tog_view *log_view;
6125 const struct got_error *err = NULL;
6126 struct got_object_id *commit_id = NULL;
6128 *new_view = NULL;
6130 err = resolve_reflist_entry(&commit_id, re, repo);
6131 if (err) {
6132 if (err->code != GOT_ERR_OBJ_TYPE)
6133 return err;
6134 else
6135 return NULL;
6138 log_view = view_open(0, 0, 0, begin_x, TOG_VIEW_LOG);
6139 if (log_view == NULL) {
6140 err = got_error_from_errno("view_open");
6141 goto done;
6144 err = open_log_view(log_view, commit_id, repo,
6145 got_ref_get_name(re->ref), "", 0);
6146 done:
6147 if (err)
6148 view_close(log_view);
6149 else
6150 *new_view = log_view;
6151 free(commit_id);
6152 return err;
6155 static void
6156 ref_scroll_up(struct tog_ref_view_state *s, int maxscroll)
6158 struct tog_reflist_entry *re;
6159 int i = 0;
6161 if (s->first_displayed_entry == TAILQ_FIRST(&s->refs))
6162 return;
6164 re = TAILQ_PREV(s->first_displayed_entry, tog_reflist_head, entry);
6165 while (i++ < maxscroll) {
6166 if (re == NULL)
6167 break;
6168 s->first_displayed_entry = re;
6169 re = TAILQ_PREV(re, tog_reflist_head, entry);
6173 static void
6174 ref_scroll_down(struct tog_ref_view_state *s, int maxscroll)
6176 struct tog_reflist_entry *next, *last;
6177 int n = 0;
6179 if (s->first_displayed_entry)
6180 next = TAILQ_NEXT(s->first_displayed_entry, entry);
6181 else
6182 next = TAILQ_FIRST(&s->refs);
6184 last = s->last_displayed_entry;
6185 while (next && last && n++ < maxscroll) {
6186 last = TAILQ_NEXT(last, entry);
6187 if (last) {
6188 s->first_displayed_entry = next;
6189 next = TAILQ_NEXT(next, entry);
6194 static const struct got_error *
6195 search_start_ref_view(struct tog_view *view)
6197 struct tog_ref_view_state *s = &view->state.ref;
6199 s->matched_entry = NULL;
6200 return NULL;
6203 static int
6204 match_reflist_entry(struct tog_reflist_entry *re, regex_t *regex)
6206 regmatch_t regmatch;
6208 return regexec(regex, got_ref_get_name(re->ref), 1, &regmatch,
6209 0) == 0;
6212 static const struct got_error *
6213 search_next_ref_view(struct tog_view *view)
6215 struct tog_ref_view_state *s = &view->state.ref;
6216 struct tog_reflist_entry *re = NULL;
6218 if (!view->searching) {
6219 view->search_next_done = TOG_SEARCH_HAVE_MORE;
6220 return NULL;
6223 if (s->matched_entry) {
6224 if (view->searching == TOG_SEARCH_FORWARD) {
6225 if (s->selected_entry)
6226 re = TAILQ_NEXT(s->selected_entry, entry);
6227 else
6228 re = TAILQ_PREV(s->selected_entry,
6229 tog_reflist_head, entry);
6230 } else {
6231 if (s->selected_entry == NULL)
6232 re = TAILQ_LAST(&s->refs, tog_reflist_head);
6233 else
6234 re = TAILQ_PREV(s->selected_entry,
6235 tog_reflist_head, entry);
6237 } else {
6238 if (s->selected_entry)
6239 re = s->selected_entry;
6240 else if (view->searching == TOG_SEARCH_FORWARD)
6241 re = TAILQ_FIRST(&s->refs);
6242 else
6243 re = TAILQ_LAST(&s->refs, tog_reflist_head);
6246 while (1) {
6247 if (re == NULL) {
6248 if (s->matched_entry == NULL) {
6249 view->search_next_done = TOG_SEARCH_HAVE_MORE;
6250 return NULL;
6252 if (view->searching == TOG_SEARCH_FORWARD)
6253 re = TAILQ_FIRST(&s->refs);
6254 else
6255 re = TAILQ_LAST(&s->refs, tog_reflist_head);
6258 if (match_reflist_entry(re, &view->regex)) {
6259 view->search_next_done = TOG_SEARCH_HAVE_MORE;
6260 s->matched_entry = re;
6261 break;
6264 if (view->searching == TOG_SEARCH_FORWARD)
6265 re = TAILQ_NEXT(re, entry);
6266 else
6267 re = TAILQ_PREV(re, tog_reflist_head, entry);
6270 if (s->matched_entry) {
6271 s->first_displayed_entry = s->matched_entry;
6272 s->selected = 0;
6275 return NULL;
6278 static const struct got_error *
6279 show_ref_view(struct tog_view *view)
6281 const struct got_error *err = NULL;
6282 struct tog_ref_view_state *s = &view->state.ref;
6283 struct tog_reflist_entry *re;
6284 char *line = NULL;
6285 wchar_t *wline;
6286 struct tog_color *tc;
6287 int width, n;
6288 int limit = view->nlines;
6290 werase(view->window);
6292 s->ndisplayed = 0;
6294 if (limit == 0)
6295 return NULL;
6297 re = s->first_displayed_entry;
6299 if (asprintf(&line, "references [%d/%d]", re->idx + s->selected + 1,
6300 s->nrefs) == -1)
6301 return got_error_from_errno("asprintf");
6303 err = format_line(&wline, &width, line, view->ncols, 0);
6304 if (err) {
6305 free(line);
6306 return err;
6308 if (view_needs_focus_indication(view))
6309 wstandout(view->window);
6310 waddwstr(view->window, wline);
6311 if (view_needs_focus_indication(view))
6312 wstandend(view->window);
6313 free(wline);
6314 wline = NULL;
6315 free(line);
6316 line = NULL;
6317 if (width < view->ncols - 1)
6318 waddch(view->window, '\n');
6319 if (--limit <= 0)
6320 return NULL;
6322 n = 0;
6323 while (re && limit > 0) {
6324 char *line = NULL;
6326 if (got_ref_is_symbolic(re->ref)) {
6327 if (asprintf(&line, "%s -> %s",
6328 got_ref_get_name(re->ref),
6329 got_ref_get_symref_target(re->ref)) == -1)
6330 return got_error_from_errno("asprintf");
6331 } else if (s->show_ids) {
6332 struct got_object_id *id;
6333 char *id_str;
6334 err = got_ref_resolve(&id, s->repo, re->ref);
6335 if (err)
6336 return err;
6337 err = got_object_id_str(&id_str, id);
6338 if (err) {
6339 free(id);
6340 return err;
6342 if (asprintf(&line, "%s: %s",
6343 got_ref_get_name(re->ref), id_str) == -1) {
6344 err = got_error_from_errno("asprintf");
6345 free(id);
6346 free(id_str);
6347 return err;
6349 free(id);
6350 free(id_str);
6351 } else {
6352 line = strdup(got_ref_get_name(re->ref));
6353 if (line == NULL)
6354 return got_error_from_errno("strdup");
6357 err = format_line(&wline, &width, line, view->ncols, 0);
6358 if (err) {
6359 free(line);
6360 return err;
6362 if (n == s->selected) {
6363 if (view->focussed)
6364 wstandout(view->window);
6365 s->selected_entry = re;
6367 tc = match_color(&s->colors, got_ref_get_name(re->ref));
6368 if (tc)
6369 wattr_on(view->window,
6370 COLOR_PAIR(tc->colorpair), NULL);
6371 waddwstr(view->window, wline);
6372 if (tc)
6373 wattr_off(view->window,
6374 COLOR_PAIR(tc->colorpair), NULL);
6375 if (width < view->ncols - 1)
6376 waddch(view->window, '\n');
6377 if (n == s->selected && view->focussed)
6378 wstandend(view->window);
6379 free(line);
6380 free(wline);
6381 wline = NULL;
6382 n++;
6383 s->ndisplayed++;
6384 s->last_displayed_entry = re;
6386 limit--;
6387 re = TAILQ_NEXT(re, entry);
6390 view_vborder(view);
6391 return err;
6394 static const struct got_error *
6395 browse_ref_tree(struct tog_view **new_view, int begin_x,
6396 struct tog_reflist_entry *re, struct got_repository *repo)
6398 const struct got_error *err = NULL;
6399 struct got_object_id *commit_id = NULL;
6400 struct tog_view *tree_view;
6402 *new_view = NULL;
6404 err = resolve_reflist_entry(&commit_id, re, repo);
6405 if (err) {
6406 if (err->code != GOT_ERR_OBJ_TYPE)
6407 return err;
6408 else
6409 return NULL;
6413 tree_view = view_open(0, 0, 0, begin_x, TOG_VIEW_TREE);
6414 if (tree_view == NULL) {
6415 err = got_error_from_errno("view_open");
6416 goto done;
6419 err = open_tree_view(tree_view, commit_id,
6420 got_ref_get_name(re->ref), repo);
6421 if (err)
6422 goto done;
6424 *new_view = tree_view;
6425 done:
6426 free(commit_id);
6427 return err;
6429 static const struct got_error *
6430 input_ref_view(struct tog_view **new_view, struct tog_view *view, int ch)
6432 const struct got_error *err = NULL;
6433 struct tog_ref_view_state *s = &view->state.ref;
6434 struct tog_view *log_view, *tree_view;
6435 struct tog_reflist_entry *re;
6436 int begin_x = 0, n, nscroll = view->nlines - 1;
6438 switch (ch) {
6439 case 'i':
6440 s->show_ids = !s->show_ids;
6441 break;
6442 case 'o':
6443 s->sort_by_date = !s->sort_by_date;
6444 err = got_reflist_sort(&tog_refs, s->sort_by_date ?
6445 got_ref_cmp_by_commit_timestamp_descending :
6446 tog_ref_cmp_by_name, s->repo);
6447 if (err)
6448 break;
6449 got_reflist_object_id_map_free(tog_refs_idmap);
6450 err = got_reflist_object_id_map_create(&tog_refs_idmap,
6451 &tog_refs, s->repo);
6452 if (err)
6453 break;
6454 ref_view_free_refs(s);
6455 err = ref_view_load_refs(s);
6456 break;
6457 case KEY_ENTER:
6458 case '\r':
6459 if (!s->selected_entry)
6460 break;
6461 if (view_is_parent_view(view))
6462 begin_x = view_split_begin_x(view->begin_x);
6463 err = log_ref_entry(&log_view, begin_x, s->selected_entry,
6464 s->repo);
6465 view->focussed = 0;
6466 log_view->focussed = 1;
6467 if (view_is_parent_view(view)) {
6468 err = view_close_child(view);
6469 if (err)
6470 return err;
6471 view_set_child(view, log_view);
6472 view->focus_child = 1;
6473 } else
6474 *new_view = log_view;
6475 break;
6476 case 't':
6477 if (!s->selected_entry)
6478 break;
6479 if (view_is_parent_view(view))
6480 begin_x = view_split_begin_x(view->begin_x);
6481 err = browse_ref_tree(&tree_view, begin_x, s->selected_entry,
6482 s->repo);
6483 if (err || tree_view == NULL)
6484 break;
6485 view->focussed = 0;
6486 tree_view->focussed = 1;
6487 if (view_is_parent_view(view)) {
6488 err = view_close_child(view);
6489 if (err)
6490 return err;
6491 view_set_child(view, tree_view);
6492 view->focus_child = 1;
6493 } else
6494 *new_view = tree_view;
6495 break;
6496 case 'g':
6497 case KEY_HOME:
6498 s->selected = 0;
6499 s->first_displayed_entry = TAILQ_FIRST(&s->refs);
6500 break;
6501 case 'G':
6502 case KEY_END:
6503 s->selected = 0;
6504 re = TAILQ_LAST(&s->refs, tog_reflist_head);
6505 for (n = 0; n < view->nlines - 1; n++) {
6506 if (re == NULL)
6507 break;
6508 s->first_displayed_entry = re;
6509 re = TAILQ_PREV(re, tog_reflist_head, entry);
6511 if (n > 0)
6512 s->selected = n - 1;
6513 break;
6514 case 'k':
6515 case KEY_UP:
6516 case CTRL('p'):
6517 if (s->selected > 0) {
6518 s->selected--;
6519 break;
6521 ref_scroll_up(s, 1);
6522 break;
6523 case CTRL('u'):
6524 case 'u':
6525 nscroll /= 2;
6526 /* FALL THROUGH */
6527 case KEY_PPAGE:
6528 case CTRL('b'):
6529 if (s->first_displayed_entry == TAILQ_FIRST(&s->refs))
6530 s->selected -= MIN(nscroll, s->selected);
6531 ref_scroll_up(s, MAX(0, nscroll));
6532 break;
6533 case 'j':
6534 case KEY_DOWN:
6535 case CTRL('n'):
6536 if (s->selected < s->ndisplayed - 1) {
6537 s->selected++;
6538 break;
6540 if (TAILQ_NEXT(s->last_displayed_entry, entry) == NULL)
6541 /* can't scroll any further */
6542 break;
6543 ref_scroll_down(s, 1);
6544 break;
6545 case CTRL('d'):
6546 case 'd':
6547 nscroll /= 2;
6548 /* FALL THROUGH */
6549 case KEY_NPAGE:
6550 case CTRL('f'):
6551 if (TAILQ_NEXT(s->last_displayed_entry, entry) == NULL) {
6552 /* can't scroll any further; move cursor down */
6553 if (s->selected < s->ndisplayed - 1)
6554 s->selected += MIN(nscroll,
6555 s->ndisplayed - s->selected - 1);
6556 break;
6558 ref_scroll_down(s, nscroll);
6559 break;
6560 case CTRL('l'):
6561 tog_free_refs();
6562 err = tog_load_refs(s->repo, s->sort_by_date);
6563 if (err)
6564 break;
6565 ref_view_free_refs(s);
6566 err = ref_view_load_refs(s);
6567 break;
6568 case KEY_RESIZE:
6569 if (view->nlines >= 2 && s->selected >= view->nlines - 1)
6570 s->selected = view->nlines - 2;
6571 break;
6572 default:
6573 break;
6576 return err;
6579 __dead static void
6580 usage_ref(void)
6582 endwin();
6583 fprintf(stderr, "usage: %s ref [-r repository-path]\n",
6584 getprogname());
6585 exit(1);
6588 static const struct got_error *
6589 cmd_ref(int argc, char *argv[])
6591 const struct got_error *error;
6592 struct got_repository *repo = NULL;
6593 struct got_worktree *worktree = NULL;
6594 char *cwd = NULL, *repo_path = NULL;
6595 int ch;
6596 struct tog_view *view;
6597 int *pack_fds = NULL;
6599 while ((ch = getopt(argc, argv, "r:")) != -1) {
6600 switch (ch) {
6601 case 'r':
6602 repo_path = realpath(optarg, NULL);
6603 if (repo_path == NULL)
6604 return got_error_from_errno2("realpath",
6605 optarg);
6606 break;
6607 default:
6608 usage_ref();
6609 /* NOTREACHED */
6613 argc -= optind;
6614 argv += optind;
6616 if (argc > 1)
6617 usage_ref();
6619 error = got_repo_pack_fds_open(&pack_fds);
6620 if (error != NULL)
6621 goto done;
6623 if (repo_path == NULL) {
6624 cwd = getcwd(NULL, 0);
6625 if (cwd == NULL)
6626 return got_error_from_errno("getcwd");
6627 error = got_worktree_open(&worktree, cwd);
6628 if (error && error->code != GOT_ERR_NOT_WORKTREE)
6629 goto done;
6630 if (worktree)
6631 repo_path =
6632 strdup(got_worktree_get_repo_path(worktree));
6633 else
6634 repo_path = strdup(cwd);
6635 if (repo_path == NULL) {
6636 error = got_error_from_errno("strdup");
6637 goto done;
6641 error = got_repo_open(&repo, repo_path, NULL, pack_fds);
6642 if (error != NULL)
6643 goto done;
6645 init_curses();
6647 error = apply_unveil(got_repo_get_path(repo), NULL);
6648 if (error)
6649 goto done;
6651 error = tog_load_refs(repo, 0);
6652 if (error)
6653 goto done;
6655 view = view_open(0, 0, 0, 0, TOG_VIEW_REF);
6656 if (view == NULL) {
6657 error = got_error_from_errno("view_open");
6658 goto done;
6661 error = open_ref_view(view, repo);
6662 if (error)
6663 goto done;
6665 if (worktree) {
6666 /* Release work tree lock. */
6667 got_worktree_close(worktree);
6668 worktree = NULL;
6670 error = view_loop(view);
6671 done:
6672 free(repo_path);
6673 free(cwd);
6674 if (repo) {
6675 const struct got_error *close_err = got_repo_close(repo);
6676 if (close_err)
6677 error = close_err;
6679 if (pack_fds) {
6680 const struct got_error *pack_err =
6681 got_repo_pack_fds_close(pack_fds);
6682 if (error == NULL)
6683 error = pack_err;
6685 tog_free_refs();
6686 return error;
6689 static void
6690 list_commands(FILE *fp)
6692 size_t i;
6694 fprintf(fp, "commands:");
6695 for (i = 0; i < nitems(tog_commands); i++) {
6696 const struct tog_cmd *cmd = &tog_commands[i];
6697 fprintf(fp, " %s", cmd->name);
6699 fputc('\n', fp);
6702 __dead static void
6703 usage(int hflag, int status)
6705 FILE *fp = (status == 0) ? stdout : stderr;
6707 fprintf(fp, "usage: %s [-h] [-V | --version] [command] [arg ...]\n",
6708 getprogname());
6709 if (hflag) {
6710 fprintf(fp, "lazy usage: %s path\n", getprogname());
6711 list_commands(fp);
6713 exit(status);
6716 static char **
6717 make_argv(int argc, ...)
6719 va_list ap;
6720 char **argv;
6721 int i;
6723 va_start(ap, argc);
6725 argv = calloc(argc, sizeof(char *));
6726 if (argv == NULL)
6727 err(1, "calloc");
6728 for (i = 0; i < argc; i++) {
6729 argv[i] = strdup(va_arg(ap, char *));
6730 if (argv[i] == NULL)
6731 err(1, "strdup");
6734 va_end(ap);
6735 return argv;
6739 * Try to convert 'tog path' into a 'tog log path' command.
6740 * The user could simply have mistyped the command rather than knowingly
6741 * provided a path. So check whether argv[0] can in fact be resolved
6742 * to a path in the HEAD commit and print a special error if not.
6743 * This hack is for mpi@ <3
6745 static const struct got_error *
6746 tog_log_with_path(int argc, char *argv[])
6748 const struct got_error *error = NULL, *close_err;
6749 const struct tog_cmd *cmd = NULL;
6750 struct got_repository *repo = NULL;
6751 struct got_worktree *worktree = NULL;
6752 struct got_object_id *commit_id = NULL, *id = NULL;
6753 struct got_commit_object *commit = NULL;
6754 char *cwd = NULL, *repo_path = NULL, *in_repo_path = NULL;
6755 char *commit_id_str = NULL, **cmd_argv = NULL;
6756 int *pack_fds = NULL;
6758 cwd = getcwd(NULL, 0);
6759 if (cwd == NULL)
6760 return got_error_from_errno("getcwd");
6762 error = got_repo_pack_fds_open(&pack_fds);
6763 if (error != NULL)
6764 goto done;
6766 error = got_worktree_open(&worktree, cwd);
6767 if (error && error->code != GOT_ERR_NOT_WORKTREE)
6768 goto done;
6770 if (worktree)
6771 repo_path = strdup(got_worktree_get_repo_path(worktree));
6772 else
6773 repo_path = strdup(cwd);
6774 if (repo_path == NULL) {
6775 error = got_error_from_errno("strdup");
6776 goto done;
6779 error = got_repo_open(&repo, repo_path, NULL, pack_fds);
6780 if (error != NULL)
6781 goto done;
6783 error = get_in_repo_path_from_argv0(&in_repo_path, argc, argv,
6784 repo, worktree);
6785 if (error)
6786 goto done;
6788 error = tog_load_refs(repo, 0);
6789 if (error)
6790 goto done;
6791 error = got_repo_match_object_id(&commit_id, NULL, worktree ?
6792 got_worktree_get_head_ref_name(worktree) : GOT_REF_HEAD,
6793 GOT_OBJ_TYPE_COMMIT, &tog_refs, repo);
6794 if (error)
6795 goto done;
6797 if (worktree) {
6798 got_worktree_close(worktree);
6799 worktree = NULL;
6802 error = got_object_open_as_commit(&commit, repo, commit_id);
6803 if (error)
6804 goto done;
6806 error = got_object_id_by_path(&id, repo, commit, in_repo_path);
6807 if (error) {
6808 if (error->code != GOT_ERR_NO_TREE_ENTRY)
6809 goto done;
6810 fprintf(stderr, "%s: '%s' is no known command or path\n",
6811 getprogname(), argv[0]);
6812 usage(1, 1);
6813 /* not reached */
6816 close_err = got_repo_close(repo);
6817 if (error == NULL)
6818 error = close_err;
6819 repo = NULL;
6821 error = got_object_id_str(&commit_id_str, commit_id);
6822 if (error)
6823 goto done;
6825 cmd = &tog_commands[0]; /* log */
6826 argc = 4;
6827 cmd_argv = make_argv(argc, cmd->name, "-c", commit_id_str, argv[0]);
6828 error = cmd->cmd_main(argc, cmd_argv);
6829 done:
6830 if (repo) {
6831 close_err = got_repo_close(repo);
6832 if (error == NULL)
6833 error = close_err;
6835 if (commit)
6836 got_object_commit_close(commit);
6837 if (worktree)
6838 got_worktree_close(worktree);
6839 if (pack_fds) {
6840 const struct got_error *pack_err =
6841 got_repo_pack_fds_close(pack_fds);
6842 if (error == NULL)
6843 error = pack_err;
6845 free(id);
6846 free(commit_id_str);
6847 free(commit_id);
6848 free(cwd);
6849 free(repo_path);
6850 free(in_repo_path);
6851 if (cmd_argv) {
6852 int i;
6853 for (i = 0; i < argc; i++)
6854 free(cmd_argv[i]);
6855 free(cmd_argv);
6857 tog_free_refs();
6858 return error;
6861 int
6862 main(int argc, char *argv[])
6864 const struct got_error *error = NULL;
6865 const struct tog_cmd *cmd = NULL;
6866 int ch, hflag = 0, Vflag = 0;
6867 char **cmd_argv = NULL;
6868 static const struct option longopts[] = {
6869 { "version", no_argument, NULL, 'V' },
6870 { NULL, 0, NULL, 0}
6873 setlocale(LC_CTYPE, "");
6875 while ((ch = getopt_long(argc, argv, "+hV", longopts, NULL)) != -1) {
6876 switch (ch) {
6877 case 'h':
6878 hflag = 1;
6879 break;
6880 case 'V':
6881 Vflag = 1;
6882 break;
6883 default:
6884 usage(hflag, 1);
6885 /* NOTREACHED */
6889 argc -= optind;
6890 argv += optind;
6891 optind = 1;
6892 optreset = 1;
6894 if (Vflag) {
6895 got_version_print_str();
6896 return 0;
6899 #ifndef PROFILE
6900 if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd unveil",
6901 NULL) == -1)
6902 err(1, "pledge");
6903 #endif
6905 if (argc == 0) {
6906 if (hflag)
6907 usage(hflag, 0);
6908 /* Build an argument vector which runs a default command. */
6909 cmd = &tog_commands[0];
6910 argc = 1;
6911 cmd_argv = make_argv(argc, cmd->name);
6912 } else {
6913 size_t i;
6915 /* Did the user specify a command? */
6916 for (i = 0; i < nitems(tog_commands); i++) {
6917 if (strncmp(tog_commands[i].name, argv[0],
6918 strlen(argv[0])) == 0) {
6919 cmd = &tog_commands[i];
6920 break;
6925 if (cmd == NULL) {
6926 if (argc != 1)
6927 usage(0, 1);
6928 /* No command specified; try log with a path */
6929 error = tog_log_with_path(argc, argv);
6930 } else {
6931 if (hflag)
6932 cmd->cmd_usage();
6933 else
6934 error = cmd->cmd_main(argc, cmd_argv ? cmd_argv : argv);
6937 endwin();
6938 putchar('\n');
6939 if (cmd_argv) {
6940 int i;
6941 for (i = 0; i < argc; i++)
6942 free(cmd_argv[i]);
6943 free(cmd_argv);
6946 if (error && error->code != GOT_ERR_CANCELLED)
6947 fprintf(stderr, "%s: %s\n", getprogname(), error->msg);
6948 return 0;