add more info about the test suite to README.portable
[got-portable.git] / tog / tog.c
blob30793bea899b9efce485dce4feba761c81486309
1 /*
2 * Copyright (c) 2018, 2019, 2020 Stefan Sperling <stsp@openbsd.org>
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.
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.
17 #include "got_compat.h"
19 #include <sys/queue.h>
20 #include <sys/stat.h>
21 #include <sys/ioctl.h>
23 #include <ctype.h>
24 #include <errno.h>
25 #include <fcntl.h>
26 #if defined(__FreeBSD__) || defined(__APPLE__)
27 #define _XOPEN_SOURCE_EXTENDED /* for ncurses wide-character functions */
28 #endif
29 #include <curses.h>
30 #include <panel.h>
31 #include <locale.h>
32 #include <signal.h>
33 #include <stdlib.h>
34 #include <stdarg.h>
35 #include <stdio.h>
36 #include <getopt.h>
37 #include <string.h>
38 #include <err.h>
39 #include <unistd.h>
40 #include <limits.h>
41 #include <wchar.h>
42 #include <time.h>
43 #include <pthread.h>
44 #include <libgen.h>
45 #include <regex.h>
46 #include <sched.h>
48 #include "got_version.h"
49 #include "got_error.h"
50 #include "got_object.h"
51 #include "got_reference.h"
52 #include "got_repository.h"
53 #include "got_gotconfig.h"
54 #include "got_diff.h"
55 #include "got_opentemp.h"
56 #include "got_utf8.h"
57 #include "got_cancel.h"
58 #include "got_commit_graph.h"
59 #include "got_blame.h"
60 #include "got_privsep.h"
61 #include "got_path.h"
62 #include "got_worktree.h"
63 #include "got_keyword.h"
65 #ifndef MIN
66 #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b))
67 #endif
69 #ifndef MAX
70 #define MAX(_a,_b) ((_a) > (_b) ? (_a) : (_b))
71 #endif
73 #ifndef CTRL
74 #define CTRL(x) ((x) & 0x1f)
75 #endif
77 #ifndef nitems
78 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
79 #endif
81 struct tog_cmd {
82 const char *name;
83 const struct got_error *(*cmd_main)(int, char *[]);
84 void (*cmd_usage)(void);
87 __dead static void usage(int, int);
88 __dead static void usage_log(void);
89 __dead static void usage_diff(void);
90 __dead static void usage_blame(void);
91 __dead static void usage_tree(void);
92 __dead static void usage_ref(void);
94 static const struct got_error* cmd_log(int, char *[]);
95 static const struct got_error* cmd_diff(int, char *[]);
96 static const struct got_error* cmd_blame(int, char *[]);
97 static const struct got_error* cmd_tree(int, char *[]);
98 static const struct got_error* cmd_ref(int, char *[]);
100 static const struct tog_cmd tog_commands[] = {
101 { "log", cmd_log, usage_log },
102 { "diff", cmd_diff, usage_diff },
103 { "blame", cmd_blame, usage_blame },
104 { "tree", cmd_tree, usage_tree },
105 { "ref", cmd_ref, usage_ref },
108 enum tog_view_type {
109 TOG_VIEW_DIFF,
110 TOG_VIEW_LOG,
111 TOG_VIEW_BLAME,
112 TOG_VIEW_TREE,
113 TOG_VIEW_REF,
114 TOG_VIEW_HELP
117 /* Match _DIFF to _HELP with enum tog_view_type TOG_VIEW_* counterparts. */
118 enum tog_keymap_type {
119 TOG_KEYMAP_KEYS = -2,
120 TOG_KEYMAP_GLOBAL,
121 TOG_KEYMAP_DIFF,
122 TOG_KEYMAP_LOG,
123 TOG_KEYMAP_BLAME,
124 TOG_KEYMAP_TREE,
125 TOG_KEYMAP_REF,
126 TOG_KEYMAP_HELP
129 enum tog_view_mode {
130 TOG_VIEW_SPLIT_NONE,
131 TOG_VIEW_SPLIT_VERT,
132 TOG_VIEW_SPLIT_HRZN
135 #define HSPLIT_SCALE 0.3f /* default horizontal split scale */
137 #define TOG_EOF_STRING "(END)"
139 struct commit_queue_entry {
140 TAILQ_ENTRY(commit_queue_entry) entry;
141 struct got_object_id *id;
142 struct got_commit_object *commit;
143 int worktree_entry;
144 int idx;
146 TAILQ_HEAD(commit_queue_head, commit_queue_entry);
147 struct commit_queue {
148 int ncommits;
149 struct commit_queue_head head;
152 struct tog_color {
153 STAILQ_ENTRY(tog_color) entry;
154 regex_t regex;
155 short colorpair;
157 STAILQ_HEAD(tog_colors, tog_color);
159 static struct got_reflist_head tog_refs = TAILQ_HEAD_INITIALIZER(tog_refs);
160 static struct got_reflist_object_id_map *tog_refs_idmap;
161 static struct {
162 struct got_object_id *id;
163 int idx;
164 char marker;
165 } tog_base_commit;
166 static enum got_diff_algorithm tog_diff_algo = GOT_DIFF_ALGORITHM_PATIENCE;
168 static const struct got_error *
169 tog_ref_cmp_by_name(void *arg, int *cmp, struct got_reference *re1,
170 struct got_reference* re2)
172 const char *name1 = got_ref_get_name(re1);
173 const char *name2 = got_ref_get_name(re2);
174 int isbackup1, isbackup2;
176 /* Sort backup refs towards the bottom of the list. */
177 isbackup1 = strncmp(name1, "refs/got/backup/", 16) == 0;
178 isbackup2 = strncmp(name2, "refs/got/backup/", 16) == 0;
179 if (!isbackup1 && isbackup2) {
180 *cmp = -1;
181 return NULL;
182 } else if (isbackup1 && !isbackup2) {
183 *cmp = 1;
184 return NULL;
187 *cmp = got_path_cmp(name1, name2, strlen(name1), strlen(name2));
188 return NULL;
191 static const struct got_error *
192 tog_load_refs(struct got_repository *repo, int sort_by_date)
194 const struct got_error *err;
196 err = got_ref_list(&tog_refs, repo, NULL, sort_by_date ?
197 got_ref_cmp_by_commit_timestamp_descending : tog_ref_cmp_by_name,
198 repo);
199 if (err)
200 return err;
202 return got_reflist_object_id_map_create(&tog_refs_idmap, &tog_refs,
203 repo);
206 static void
207 tog_free_refs(void)
209 if (tog_refs_idmap) {
210 got_reflist_object_id_map_free(tog_refs_idmap);
211 tog_refs_idmap = NULL;
213 got_ref_list_free(&tog_refs);
216 static const struct got_error *
217 add_color(struct tog_colors *colors, const char *pattern,
218 int idx, short color)
220 const struct got_error *err = NULL;
221 struct tog_color *tc;
222 int regerr = 0;
224 if (idx < 1 || idx > COLOR_PAIRS - 1)
225 return NULL;
227 init_pair(idx, color, -1);
229 tc = calloc(1, sizeof(*tc));
230 if (tc == NULL)
231 return got_error_from_errno("calloc");
232 regerr = regcomp(&tc->regex, pattern,
233 REG_EXTENDED | REG_NOSUB | REG_NEWLINE);
234 if (regerr) {
235 static char regerr_msg[512];
236 static char err_msg[512];
237 regerror(regerr, &tc->regex, regerr_msg,
238 sizeof(regerr_msg));
239 snprintf(err_msg, sizeof(err_msg), "regcomp: %s",
240 regerr_msg);
241 err = got_error_msg(GOT_ERR_REGEX, err_msg);
242 free(tc);
243 return err;
245 tc->colorpair = idx;
246 STAILQ_INSERT_HEAD(colors, tc, entry);
247 return NULL;
250 static void
251 free_colors(struct tog_colors *colors)
253 struct tog_color *tc;
255 while (!STAILQ_EMPTY(colors)) {
256 tc = STAILQ_FIRST(colors);
257 STAILQ_REMOVE_HEAD(colors, entry);
258 regfree(&tc->regex);
259 free(tc);
263 static struct tog_color *
264 get_color(struct tog_colors *colors, int colorpair)
266 struct tog_color *tc = NULL;
268 STAILQ_FOREACH(tc, colors, entry) {
269 if (tc->colorpair == colorpair)
270 return tc;
273 return NULL;
276 static int
277 default_color_value(const char *envvar)
279 if (strcmp(envvar, "TOG_COLOR_DIFF_MINUS") == 0)
280 return COLOR_MAGENTA;
281 if (strcmp(envvar, "TOG_COLOR_DIFF_PLUS") == 0)
282 return COLOR_CYAN;
283 if (strcmp(envvar, "TOG_COLOR_DIFF_CHUNK_HEADER") == 0)
284 return COLOR_YELLOW;
285 if (strcmp(envvar, "TOG_COLOR_DIFF_META") == 0)
286 return COLOR_GREEN;
287 if (strcmp(envvar, "TOG_COLOR_TREE_SUBMODULE") == 0)
288 return COLOR_MAGENTA;
289 if (strcmp(envvar, "TOG_COLOR_TREE_SYMLINK") == 0)
290 return COLOR_MAGENTA;
291 if (strcmp(envvar, "TOG_COLOR_TREE_DIRECTORY") == 0)
292 return COLOR_CYAN;
293 if (strcmp(envvar, "TOG_COLOR_TREE_EXECUTABLE") == 0)
294 return COLOR_GREEN;
295 if (strcmp(envvar, "TOG_COLOR_COMMIT") == 0)
296 return COLOR_GREEN;
297 if (strcmp(envvar, "TOG_COLOR_AUTHOR") == 0)
298 return COLOR_CYAN;
299 if (strcmp(envvar, "TOG_COLOR_DATE") == 0)
300 return COLOR_YELLOW;
301 if (strcmp(envvar, "TOG_COLOR_REFS_HEADS") == 0)
302 return COLOR_GREEN;
303 if (strcmp(envvar, "TOG_COLOR_REFS_TAGS") == 0)
304 return COLOR_MAGENTA;
305 if (strcmp(envvar, "TOG_COLOR_REFS_REMOTES") == 0)
306 return COLOR_YELLOW;
307 if (strcmp(envvar, "TOG_COLOR_REFS_BACKUP") == 0)
308 return COLOR_CYAN;
310 return -1;
313 static int
314 get_color_value(const char *envvar)
316 const char *val = getenv(envvar);
318 if (val == NULL)
319 return default_color_value(envvar);
321 if (strcasecmp(val, "black") == 0)
322 return COLOR_BLACK;
323 if (strcasecmp(val, "red") == 0)
324 return COLOR_RED;
325 if (strcasecmp(val, "green") == 0)
326 return COLOR_GREEN;
327 if (strcasecmp(val, "yellow") == 0)
328 return COLOR_YELLOW;
329 if (strcasecmp(val, "blue") == 0)
330 return COLOR_BLUE;
331 if (strcasecmp(val, "magenta") == 0)
332 return COLOR_MAGENTA;
333 if (strcasecmp(val, "cyan") == 0)
334 return COLOR_CYAN;
335 if (strcasecmp(val, "white") == 0)
336 return COLOR_WHITE;
337 if (strcasecmp(val, "default") == 0)
338 return -1;
340 return default_color_value(envvar);
343 struct diff_worktree_arg {
344 struct got_repository *repo;
345 struct got_worktree *worktree;
346 struct got_diff_line **lines;
347 struct got_diffstat_cb_arg *diffstat;
348 FILE *outfile;
349 FILE *f1;
350 FILE *f2;
351 const char *id_str;
352 size_t *nlines;
353 int diff_context;
354 int header_shown;
355 int diff_staged;
356 int ignore_whitespace;
357 int force_text_diff;
358 enum got_diff_algorithm diff_algo;
361 struct tog_diff_view_state {
362 struct got_object_id *id1, *id2;
363 const char *label1, *label2;
364 const char *worktree_root;
365 char *action;
366 FILE *f, *f1, *f2;
367 int fd1, fd2;
368 int lineno;
369 int first_displayed_line;
370 int last_displayed_line;
371 int eof;
372 int diff_context;
373 int ignore_whitespace;
374 int force_text_diff;
375 int diff_worktree;
376 int diff_staged;
377 struct got_repository *repo;
378 struct got_pathlist_head *paths;
379 struct got_diff_line *lines;
380 size_t nlines;
381 int matched_line;
382 int selected_line;
384 /* passed from log or blame view; may be NULL */
385 struct tog_view *parent_view;
388 #define TOG_WORKTREE_CHANGES_LOCAL_MSG "work tree changes"
389 #define TOG_WORKTREE_CHANGES_STAGED_MSG "staged work tree changes"
391 #define TOG_WORKTREE_CHANGES_LOCAL (1 << 0)
392 #define TOG_WORKTREE_CHANGES_STAGED (1 << 1)
393 #define TOG_WORKTREE_CHANGES_ALL \
394 (TOG_WORKTREE_CHANGES_LOCAL | TOG_WORKTREE_CHANGES_STAGED)
396 struct tog_worktree_ctx {
397 char *wt_ref;
398 char *wt_author;
399 char *wt_root;
400 int wt_state;
401 int active;
404 pthread_mutex_t tog_mutex = PTHREAD_MUTEX_INITIALIZER;
405 static volatile sig_atomic_t tog_thread_error;
407 struct tog_log_thread_args {
408 pthread_cond_t need_commits;
409 pthread_cond_t commit_loaded;
410 int commits_needed;
411 int load_all;
412 struct got_commit_graph *graph;
413 struct commit_queue *real_commits;
414 struct tog_worktree_ctx wctx;
415 const char *in_repo_path;
416 struct got_object_id *start_id;
417 struct got_repository *repo;
418 int *pack_fds;
419 int log_complete;
420 pthread_cond_t log_loaded;
421 sig_atomic_t *quit;
422 struct commit_queue_entry **first_displayed_entry;
423 struct commit_queue_entry **last_displayed_entry;
424 struct commit_queue_entry **selected_entry;
425 int *selected;
426 int *searching;
427 int *search_next_done;
428 regex_t *regex;
429 int *limiting;
430 int limit_match;
431 regex_t *limit_regex;
432 struct commit_queue *limit_commits;
433 struct got_worktree *worktree;
434 int need_commit_marker;
435 int need_wt_status;
436 int *view_nlines;
439 struct tog_log_view_state {
440 struct commit_queue *commits;
441 struct commit_queue_entry *first_displayed_entry;
442 struct commit_queue_entry *last_displayed_entry;
443 struct commit_queue_entry *selected_entry;
444 struct commit_queue_entry *marked_entry;
445 struct commit_queue real_commits;
446 int selected;
447 char *in_repo_path;
448 char *head_ref_name;
449 int log_branches;
450 struct got_repository *repo;
451 struct got_object_id *start_id;
452 sig_atomic_t quit;
453 pthread_t thread;
454 struct tog_log_thread_args thread_args;
455 struct commit_queue_entry *matched_entry;
456 struct commit_queue_entry *search_entry;
457 struct tog_colors colors;
458 int use_committer;
459 int limit_view;
460 regex_t limit_regex;
461 struct commit_queue limit_commits;
464 #define TOG_COLOR_DIFF_MINUS 1
465 #define TOG_COLOR_DIFF_PLUS 2
466 #define TOG_COLOR_DIFF_CHUNK_HEADER 3
467 #define TOG_COLOR_DIFF_META 4
468 #define TOG_COLOR_TREE_SUBMODULE 5
469 #define TOG_COLOR_TREE_SYMLINK 6
470 #define TOG_COLOR_TREE_DIRECTORY 7
471 #define TOG_COLOR_TREE_EXECUTABLE 8
472 #define TOG_COLOR_COMMIT 9
473 #define TOG_COLOR_AUTHOR 10
474 #define TOG_COLOR_DATE 11
475 #define TOG_COLOR_REFS_HEADS 12
476 #define TOG_COLOR_REFS_TAGS 13
477 #define TOG_COLOR_REFS_REMOTES 14
478 #define TOG_COLOR_REFS_BACKUP 15
480 struct tog_blame_cb_args {
481 struct tog_blame_line *lines; /* one per line */
482 int nlines;
484 struct tog_view *view;
485 struct got_object_id *commit_id;
486 int *quit;
489 struct tog_blame_thread_args {
490 const char *path;
491 struct got_repository *repo;
492 struct tog_blame_cb_args *cb_args;
493 int *complete;
494 got_cancel_cb cancel_cb;
495 void *cancel_arg;
496 pthread_cond_t blame_complete;
499 struct tog_blame {
500 FILE *f;
501 off_t filesize;
502 struct tog_blame_line *lines;
503 int nlines;
504 off_t *line_offsets;
505 pthread_t thread;
506 struct tog_blame_thread_args thread_args;
507 struct tog_blame_cb_args cb_args;
508 const char *path;
509 int *pack_fds;
512 struct tog_blame_view_state {
513 int first_displayed_line;
514 int last_displayed_line;
515 int selected_line;
516 int last_diffed_line;
517 int blame_complete;
518 int eof;
519 int done;
520 struct got_object_id_queue blamed_commits;
521 struct got_object_qid *blamed_commit;
522 char *path;
523 struct got_repository *repo;
524 struct got_object_id *commit_id;
525 struct got_object_id *id_to_log;
526 struct tog_blame blame;
527 int matched_line;
528 struct tog_colors colors;
531 struct tog_parent_tree {
532 TAILQ_ENTRY(tog_parent_tree) entry;
533 struct got_tree_object *tree;
534 struct got_tree_entry *first_displayed_entry;
535 struct got_tree_entry *selected_entry;
536 int selected;
539 TAILQ_HEAD(tog_parent_trees, tog_parent_tree);
541 struct tog_tree_view_state {
542 char *tree_label;
543 struct got_object_id *commit_id;/* commit which this tree belongs to */
544 struct got_tree_object *root; /* the commit's root tree entry */
545 struct got_tree_object *tree; /* currently displayed (sub-)tree */
546 struct got_tree_entry *first_displayed_entry;
547 struct got_tree_entry *last_displayed_entry;
548 struct got_tree_entry *selected_entry;
549 int ndisplayed, selected, show_ids;
550 struct tog_parent_trees parents; /* parent trees of current sub-tree */
551 char *head_ref_name;
552 struct got_repository *repo;
553 struct got_tree_entry *matched_entry;
554 struct tog_colors colors;
557 struct tog_reflist_entry {
558 TAILQ_ENTRY(tog_reflist_entry) entry;
559 struct got_reference *ref;
560 int idx;
563 TAILQ_HEAD(tog_reflist_head, tog_reflist_entry);
565 struct tog_ref_view_state {
566 struct tog_reflist_head refs;
567 struct tog_reflist_entry *first_displayed_entry;
568 struct tog_reflist_entry *last_displayed_entry;
569 struct tog_reflist_entry *selected_entry;
570 int nrefs, ndisplayed, selected, show_date, show_ids, sort_by_date;
571 struct got_repository *repo;
572 struct tog_reflist_entry *matched_entry;
573 struct tog_colors colors;
576 struct tog_help_view_state {
577 FILE *f;
578 off_t *line_offsets;
579 size_t nlines;
580 int lineno;
581 int first_displayed_line;
582 int last_displayed_line;
583 int eof;
584 int matched_line;
585 int selected_line;
586 int all;
587 enum tog_keymap_type type;
590 #define GENERATE_HELP \
591 KEYMAP_("Global", TOG_KEYMAP_GLOBAL), \
592 KEY_("H F1", "Open view-specific help (double tap for all help)"), \
593 KEY_("k C-p Up", "Move cursor or page up one line"), \
594 KEY_("j C-n Down", "Move cursor or page down one line"), \
595 KEY_("C-b b PgUp", "Scroll the view up one page"), \
596 KEY_("C-f f PgDn Space", "Scroll the view down one page"), \
597 KEY_("C-u u", "Scroll the view up one half page"), \
598 KEY_("C-d d", "Scroll the view down one half page"), \
599 KEY_("g", "Go to line N (default: first line)"), \
600 KEY_("Home =", "Go to the first line"), \
601 KEY_("G", "Go to line N (default: last line)"), \
602 KEY_("End *", "Go to the last line"), \
603 KEY_("l Right", "Scroll the view right"), \
604 KEY_("h Left", "Scroll the view left"), \
605 KEY_("$", "Scroll view to the rightmost position"), \
606 KEY_("0", "Scroll view to the leftmost position"), \
607 KEY_("-", "Decrease size of the focussed split"), \
608 KEY_("+", "Increase size of the focussed split"), \
609 KEY_("Tab", "Switch focus between views"), \
610 KEY_("F", "Toggle fullscreen mode"), \
611 KEY_("S", "Switch split-screen layout"), \
612 KEY_("/", "Open prompt to enter search term"), \
613 KEY_("n", "Find next line/token matching the current search term"), \
614 KEY_("N", "Find previous line/token matching the current search term"),\
615 KEY_("q", "Quit the focussed view; Quit help screen"), \
616 KEY_("Q", "Quit tog"), \
618 KEYMAP_("Log view", TOG_KEYMAP_LOG), \
619 KEY_("< ,", "Move cursor up one commit"), \
620 KEY_("> .", "Move cursor down one commit"), \
621 KEY_("Enter", "Open diff view of the selected commit"), \
622 KEY_("B", "Reload the log view and toggle display of merged commits"), \
623 KEY_("R", "Open ref view of all repository references"), \
624 KEY_("T", "Display tree view of the repository from the selected" \
625 " commit"), \
626 KEY_("m", "Mark or unmark the selected entry for diffing with the " \
627 "next selected commit"), \
628 KEY_("@", "Toggle between displaying author and committer name"), \
629 KEY_("&", "Open prompt to enter term to limit commits displayed"), \
630 KEY_("C-g Backspace", "Cancel current search or log operation"), \
631 KEY_("C-l", "Reload the log view with new repository commits or " \
632 "work tree changes"), \
634 KEYMAP_("Diff view", TOG_KEYMAP_DIFF), \
635 KEY_("K < ,", "Display diff of next line in the file/log entry"), \
636 KEY_("J > .", "Display diff of previous line in the file/log entry"), \
637 KEY_("A", "Toggle between Myers and Patience diff algorithm"), \
638 KEY_("a", "Toggle treatment of file as ASCII irrespective of binary" \
639 " data"), \
640 KEY_("p", "Write diff to a patch file in /tmp"), \
641 KEY_("(", "Go to the previous file in the diff"), \
642 KEY_(")", "Go to the next file in the diff"), \
643 KEY_("{", "Go to the previous hunk in the diff"), \
644 KEY_("}", "Go to the next hunk in the diff"), \
645 KEY_("[", "Decrease the number of context lines"), \
646 KEY_("]", "Increase the number of context lines"), \
647 KEY_("w", "Toggle ignore whitespace-only changes in the diff"), \
649 KEYMAP_("Blame view", TOG_KEYMAP_BLAME), \
650 KEY_("Enter", "Display diff view of the selected line's commit"), \
651 KEY_("A", "Toggle diff algorithm between Myers and Patience"), \
652 KEY_("L", "Open log view for the currently selected annotated line"), \
653 KEY_("C", "Reload view with the previously blamed commit"), \
654 KEY_("c", "Reload view with the version of the file found in the" \
655 " selected line's commit"), \
656 KEY_("p", "Reload view with the version of the file found in the" \
657 " selected line's parent commit"), \
659 KEYMAP_("Tree view", TOG_KEYMAP_TREE), \
660 KEY_("Enter", "Enter selected directory or open blame view of the" \
661 " selected file"), \
662 KEY_("L", "Open log view for the selected entry"), \
663 KEY_("R", "Open ref view of all repository references"), \
664 KEY_("i", "Show object IDs for all tree entries"), \
665 KEY_("Backspace", "Return to the parent directory"), \
667 KEYMAP_("Ref view", TOG_KEYMAP_REF), \
668 KEY_("Enter", "Display log view of the selected reference"), \
669 KEY_("T", "Display tree view of the selected reference"), \
670 KEY_("i", "Toggle display of IDs for all non-symbolic references"), \
671 KEY_("m", "Toggle display of last modified date for each reference"), \
672 KEY_("o", "Toggle reference sort order (name -> timestamp)"), \
673 KEY_("C-l", "Reload view with all repository references")
675 struct tog_key_map {
676 const char *keys;
677 const char *info;
678 enum tog_keymap_type type;
681 /* curses io for tog regress */
682 struct tog_io {
683 FILE *cin;
684 FILE *cout;
685 FILE *f;
686 FILE *sdump;
687 char *input_str;
688 int wait_for_ui;
689 } tog_io;
690 static int using_mock_io;
692 #define TOG_KEY_SCRDUMP SHRT_MIN
695 * We implement two types of views: parent views and child views.
697 * The 'Tab' key switches focus between a parent view and its child view.
698 * Child views are shown side-by-side to their parent view, provided
699 * there is enough screen estate.
701 * When a new view is opened from within a parent view, this new view
702 * becomes a child view of the parent view, replacing any existing child.
704 * When a new view is opened from within a child view, this new view
705 * becomes a parent view which will obscure the views below until the
706 * user quits the new parent view by typing 'q'.
708 * This list of views contains parent views only.
709 * Child views are only pointed to by their parent view.
711 TAILQ_HEAD(tog_view_list_head, tog_view);
713 struct tog_view {
714 TAILQ_ENTRY(tog_view) entry;
715 WINDOW *window;
716 PANEL *panel;
717 int nlines, ncols, begin_y, begin_x; /* based on split height/width */
718 int resized_y, resized_x; /* begin_y/x based on user resizing */
719 int maxx, x; /* max column and current start column */
720 int lines, cols; /* copies of LINES and COLS */
721 int nscrolled, offset; /* lines scrolled and hsplit line offset */
722 int gline, hiline; /* navigate to and highlight this nG line */
723 int ch, count; /* current keymap and count prefix */
724 int resized; /* set when in a resize event */
725 int focussed; /* Only set on one parent or child view at a time. */
726 int dying;
727 struct tog_view *parent;
728 struct tog_view *child;
731 * This flag is initially set on parent views when a new child view
732 * is created. It gets toggled when the 'Tab' key switches focus
733 * between parent and child.
734 * The flag indicates whether focus should be passed on to our child
735 * view if this parent view gets picked for focus after another parent
736 * view was closed. This prevents child views from losing focus in such
737 * situations.
739 int focus_child;
741 enum tog_view_mode mode;
742 /* type-specific state */
743 enum tog_view_type type;
744 union {
745 struct tog_diff_view_state diff;
746 struct tog_log_view_state log;
747 struct tog_blame_view_state blame;
748 struct tog_tree_view_state tree;
749 struct tog_ref_view_state ref;
750 struct tog_help_view_state help;
751 } state;
753 const struct got_error *(*show)(struct tog_view *);
754 const struct got_error *(*input)(struct tog_view **,
755 struct tog_view *, int);
756 const struct got_error *(*reset)(struct tog_view *);
757 const struct got_error *(*resize)(struct tog_view *, int);
758 const struct got_error *(*close)(struct tog_view *);
760 const struct got_error *(*search_start)(struct tog_view *);
761 const struct got_error *(*search_next)(struct tog_view *);
762 void (*search_setup)(struct tog_view *, FILE **, off_t **, size_t *,
763 int **, int **, int **, int **);
764 int search_started;
765 int searching;
766 #define TOG_SEARCH_FORWARD 1
767 #define TOG_SEARCH_BACKWARD 2
768 int search_next_done;
769 #define TOG_SEARCH_HAVE_MORE 1
770 #define TOG_SEARCH_NO_MORE 2
771 #define TOG_SEARCH_HAVE_NONE 3
772 regex_t regex;
773 regmatch_t regmatch;
774 const char *action;
777 static const struct got_error *open_diff_view(struct tog_view *,
778 struct got_object_id *, struct got_object_id *, const char *, const char *,
779 int, int, int, int, int, const char *, struct tog_view *,
780 struct got_repository *, struct got_pathlist_head *);
781 static const struct got_error *show_diff_view(struct tog_view *);
782 static const struct got_error *input_diff_view(struct tog_view **,
783 struct tog_view *, int);
784 static const struct got_error *reset_diff_view(struct tog_view *);
785 static const struct got_error* close_diff_view(struct tog_view *);
786 static const struct got_error *search_start_diff_view(struct tog_view *);
787 static void search_setup_diff_view(struct tog_view *, FILE **, off_t **,
788 size_t *, int **, int **, int **, int **);
789 static const struct got_error *search_next_view_match(struct tog_view *);
791 static const struct got_error *open_log_view(struct tog_view *,
792 struct got_object_id *, struct got_repository *,
793 const char *, const char *, int, struct got_worktree *);
794 static const struct got_error * show_log_view(struct tog_view *);
795 static const struct got_error *input_log_view(struct tog_view **,
796 struct tog_view *, int);
797 static const struct got_error *resize_log_view(struct tog_view *, int);
798 static const struct got_error *close_log_view(struct tog_view *);
799 static const struct got_error *search_start_log_view(struct tog_view *);
800 static const struct got_error *search_next_log_view(struct tog_view *);
802 static const struct got_error *open_blame_view(struct tog_view *, char *,
803 struct got_object_id *, struct got_repository *);
804 static const struct got_error *show_blame_view(struct tog_view *);
805 static const struct got_error *input_blame_view(struct tog_view **,
806 struct tog_view *, int);
807 static const struct got_error *reset_blame_view(struct tog_view *);
808 static const struct got_error *close_blame_view(struct tog_view *);
809 static const struct got_error *search_start_blame_view(struct tog_view *);
810 static void search_setup_blame_view(struct tog_view *, FILE **, off_t **,
811 size_t *, int **, int **, int **, int **);
813 static const struct got_error *open_tree_view(struct tog_view *,
814 struct got_object_id *, const char *, struct got_repository *);
815 static const struct got_error *show_tree_view(struct tog_view *);
816 static const struct got_error *input_tree_view(struct tog_view **,
817 struct tog_view *, int);
818 static const struct got_error *close_tree_view(struct tog_view *);
819 static const struct got_error *search_start_tree_view(struct tog_view *);
820 static const struct got_error *search_next_tree_view(struct tog_view *);
822 static const struct got_error *open_ref_view(struct tog_view *,
823 struct got_repository *);
824 static const struct got_error *show_ref_view(struct tog_view *);
825 static const struct got_error *input_ref_view(struct tog_view **,
826 struct tog_view *, int);
827 static const struct got_error *close_ref_view(struct tog_view *);
828 static const struct got_error *search_start_ref_view(struct tog_view *);
829 static const struct got_error *search_next_ref_view(struct tog_view *);
831 static const struct got_error *open_help_view(struct tog_view *,
832 struct tog_view *);
833 static const struct got_error *show_help_view(struct tog_view *);
834 static const struct got_error *input_help_view(struct tog_view **,
835 struct tog_view *, int);
836 static const struct got_error *reset_help_view(struct tog_view *);
837 static const struct got_error* close_help_view(struct tog_view *);
838 static const struct got_error *search_start_help_view(struct tog_view *);
839 static void search_setup_help_view(struct tog_view *, FILE **, off_t **,
840 size_t *, int **, int **, int **, int **);
842 static volatile sig_atomic_t tog_sigwinch_received;
843 static volatile sig_atomic_t tog_sigpipe_received;
844 static volatile sig_atomic_t tog_sigcont_received;
845 static volatile sig_atomic_t tog_sigint_received;
846 static volatile sig_atomic_t tog_sigterm_received;
848 static void
849 tog_sigwinch(int signo)
851 tog_sigwinch_received = 1;
854 static void
855 tog_sigpipe(int signo)
857 tog_sigpipe_received = 1;
860 static void
861 tog_sigcont(int signo)
863 tog_sigcont_received = 1;
866 static void
867 tog_sigint(int signo)
869 tog_sigint_received = 1;
872 static void
873 tog_sigterm(int signo)
875 tog_sigterm_received = 1;
878 static int
879 tog_fatal_signal_received(void)
881 return (tog_sigpipe_received ||
882 tog_sigint_received || tog_sigterm_received);
885 static const struct got_error *
886 view_close(struct tog_view *view)
888 const struct got_error *err = NULL, *child_err = NULL;
890 if (view->child) {
891 child_err = view_close(view->child);
892 view->child = NULL;
894 if (view->close)
895 err = view->close(view);
896 if (view->panel) {
897 del_panel(view->panel);
898 view->panel = NULL;
900 if (view->window) {
901 delwin(view->window);
902 view->window = NULL;
904 free(view);
905 return err ? err : child_err;
908 static struct tog_view *
909 view_open(int nlines, int ncols, int begin_y, int begin_x,
910 enum tog_view_type type)
912 struct tog_view *view = calloc(1, sizeof(*view));
914 if (view == NULL)
915 return NULL;
917 view->type = type;
918 view->lines = LINES;
919 view->cols = COLS;
920 view->nlines = nlines ? nlines : LINES - begin_y;
921 view->ncols = ncols ? ncols : COLS - begin_x;
922 view->begin_y = begin_y;
923 view->begin_x = begin_x;
924 view->window = newwin(nlines, ncols, begin_y, begin_x);
925 if (view->window == NULL) {
926 view_close(view);
927 return NULL;
929 view->panel = new_panel(view->window);
930 if (view->panel == NULL ||
931 set_panel_userptr(view->panel, view) != OK) {
932 view_close(view);
933 return NULL;
936 keypad(view->window, TRUE);
937 return view;
940 static int
941 view_split_begin_x(int begin_x)
943 if (begin_x > 0 || COLS < 120)
944 return 0;
945 return (COLS - MAX(COLS / 2, 80));
948 /* XXX Stub till we decide what to do. */
949 static int
950 view_split_begin_y(int lines)
952 return lines * HSPLIT_SCALE;
955 static const struct got_error *view_resize(struct tog_view *);
957 static const struct got_error *
958 view_splitscreen(struct tog_view *view)
960 const struct got_error *err = NULL;
962 if (!view->resized && view->mode == TOG_VIEW_SPLIT_HRZN) {
963 if (view->resized_y && view->resized_y < view->lines)
964 view->begin_y = view->resized_y;
965 else
966 view->begin_y = view_split_begin_y(view->nlines);
967 view->begin_x = 0;
968 } else if (!view->resized) {
969 if (view->resized_x && view->resized_x < view->cols - 1 &&
970 view->cols > 119)
971 view->begin_x = view->resized_x;
972 else
973 view->begin_x = view_split_begin_x(0);
974 view->begin_y = 0;
976 view->nlines = LINES - view->begin_y;
977 view->ncols = COLS - view->begin_x;
978 view->lines = LINES;
979 view->cols = COLS;
980 err = view_resize(view);
981 if (err)
982 return err;
984 if (view->parent && view->mode == TOG_VIEW_SPLIT_HRZN)
985 view->parent->nlines = view->begin_y;
987 if (mvwin(view->window, view->begin_y, view->begin_x) == ERR)
988 return got_error_from_errno("mvwin");
990 return NULL;
993 static const struct got_error *
994 view_fullscreen(struct tog_view *view)
996 const struct got_error *err = NULL;
998 view->begin_x = 0;
999 view->begin_y = view->resized ? view->begin_y : 0;
1000 view->nlines = view->resized ? view->nlines : LINES;
1001 view->ncols = COLS;
1002 view->lines = LINES;
1003 view->cols = COLS;
1004 err = view_resize(view);
1005 if (err)
1006 return err;
1008 if (mvwin(view->window, view->begin_y, view->begin_x) == ERR)
1009 return got_error_from_errno("mvwin");
1011 return NULL;
1014 static int
1015 view_is_parent_view(struct tog_view *view)
1017 return view->parent == NULL;
1020 static int
1021 view_is_splitscreen(struct tog_view *view)
1023 return view->begin_x > 0 || view->begin_y > 0;
1026 static int
1027 view_is_fullscreen(struct tog_view *view)
1029 return view->nlines == LINES && view->ncols == COLS;
1032 static int
1033 view_is_hsplit_top(struct tog_view *view)
1035 return view->mode == TOG_VIEW_SPLIT_HRZN && view->child &&
1036 view_is_splitscreen(view->child);
1039 static void
1040 view_border(struct tog_view *view)
1042 PANEL *panel;
1043 const struct tog_view *view_above;
1045 if (view->parent)
1046 return view_border(view->parent);
1048 panel = panel_above(view->panel);
1049 if (panel == NULL)
1050 return;
1052 view_above = panel_userptr(panel);
1053 if (view->mode == TOG_VIEW_SPLIT_HRZN)
1054 mvwhline(view->window, view_above->begin_y - 1,
1055 view->begin_x, ACS_HLINE, view->ncols);
1056 else
1057 mvwvline(view->window, view->begin_y, view_above->begin_x - 1,
1058 ACS_VLINE, view->nlines);
1061 static const struct got_error *view_init_hsplit(struct tog_view *, int);
1062 static const struct got_error *request_log_commits(struct tog_view *);
1063 static const struct got_error *offset_selection_down(struct tog_view *);
1064 static void offset_selection_up(struct tog_view *);
1065 static void view_get_split(struct tog_view *, int *, int *);
1067 static const struct got_error *
1068 view_resize(struct tog_view *view)
1070 const struct got_error *err = NULL;
1071 int dif, nlines, ncols;
1073 dif = LINES - view->lines; /* line difference */
1075 if (view->lines > LINES)
1076 nlines = view->nlines - (view->lines - LINES);
1077 else
1078 nlines = view->nlines + (LINES - view->lines);
1079 if (view->cols > COLS)
1080 ncols = view->ncols - (view->cols - COLS);
1081 else
1082 ncols = view->ncols + (COLS - view->cols);
1084 if (view->child) {
1085 int hs = view->child->begin_y;
1087 if (!view_is_fullscreen(view))
1088 view->child->begin_x = view_split_begin_x(view->begin_x);
1089 if (view->mode == TOG_VIEW_SPLIT_HRZN ||
1090 view->child->begin_x == 0) {
1091 ncols = COLS;
1093 view_fullscreen(view->child);
1094 if (view->child->focussed)
1095 show_panel(view->child->panel);
1096 else
1097 show_panel(view->panel);
1098 } else {
1099 ncols = view->child->begin_x;
1101 view_splitscreen(view->child);
1102 show_panel(view->child->panel);
1105 * XXX This is ugly and needs to be moved into the above
1106 * logic but "works" for now and my attempts at moving it
1107 * break either 'tab' or 'F' key maps in horizontal splits.
1109 if (hs) {
1110 err = view_splitscreen(view->child);
1111 if (err)
1112 return err;
1113 if (dif < 0) { /* top split decreased */
1114 err = offset_selection_down(view);
1115 if (err)
1116 return err;
1118 view_border(view);
1119 update_panels();
1120 doupdate();
1121 show_panel(view->child->panel);
1122 nlines = view->nlines;
1124 } else if (view->parent == NULL)
1125 ncols = COLS;
1127 if (view->resize && dif > 0) {
1128 err = view->resize(view, dif);
1129 if (err)
1130 return err;
1133 if (wresize(view->window, nlines, ncols) == ERR)
1134 return got_error_from_errno("wresize");
1135 if (replace_panel(view->panel, view->window) == ERR)
1136 return got_error_from_errno("replace_panel");
1137 wclear(view->window);
1139 view->nlines = nlines;
1140 view->ncols = ncols;
1141 view->lines = LINES;
1142 view->cols = COLS;
1144 return NULL;
1147 static const struct got_error *
1148 resize_log_view(struct tog_view *view, int increase)
1150 struct tog_log_view_state *s = &view->state.log;
1151 const struct got_error *err = NULL;
1152 int n = 0;
1154 if (s->selected_entry)
1155 n = s->selected_entry->idx + view->lines - s->selected;
1158 * Request commits to account for the increased
1159 * height so we have enough to populate the view.
1161 if (s->commits->ncommits < n) {
1162 view->nscrolled = n - s->commits->ncommits + increase + 1;
1163 err = request_log_commits(view);
1166 return err;
1169 static void
1170 view_adjust_offset(struct tog_view *view, int n)
1172 if (n == 0)
1173 return;
1175 if (view->parent && view->parent->offset) {
1176 if (view->parent->offset + n >= 0)
1177 view->parent->offset += n;
1178 else
1179 view->parent->offset = 0;
1180 } else if (view->offset) {
1181 if (view->offset - n >= 0)
1182 view->offset -= n;
1183 else
1184 view->offset = 0;
1188 static const struct got_error *
1189 view_resize_split(struct tog_view *view, int resize)
1191 const struct got_error *err = NULL;
1192 struct tog_view *v = NULL;
1194 if (view->parent)
1195 v = view->parent;
1196 else
1197 v = view;
1199 if (!v->child || !view_is_splitscreen(v->child))
1200 return NULL;
1202 v->resized = v->child->resized = resize; /* lock for resize event */
1204 if (view->mode == TOG_VIEW_SPLIT_HRZN) {
1205 if (v->child->resized_y)
1206 v->child->begin_y = v->child->resized_y;
1207 if (view->parent)
1208 v->child->begin_y -= resize;
1209 else
1210 v->child->begin_y += resize;
1211 if (v->child->begin_y < 3) {
1212 view->count = 0;
1213 v->child->begin_y = 3;
1214 } else if (v->child->begin_y > LINES - 1) {
1215 view->count = 0;
1216 v->child->begin_y = LINES - 1;
1218 v->ncols = COLS;
1219 v->child->ncols = COLS;
1220 view_adjust_offset(view, resize);
1221 err = view_init_hsplit(v, v->child->begin_y);
1222 if (err)
1223 return err;
1224 v->child->resized_y = v->child->begin_y;
1225 } else {
1226 if (v->child->resized_x)
1227 v->child->begin_x = v->child->resized_x;
1228 if (view->parent)
1229 v->child->begin_x -= resize;
1230 else
1231 v->child->begin_x += resize;
1232 if (v->child->begin_x < 11) {
1233 view->count = 0;
1234 v->child->begin_x = 11;
1235 } else if (v->child->begin_x > COLS - 1) {
1236 view->count = 0;
1237 v->child->begin_x = COLS - 1;
1239 v->child->resized_x = v->child->begin_x;
1242 v->child->mode = v->mode;
1243 v->child->nlines = v->lines - v->child->begin_y;
1244 v->child->ncols = v->cols - v->child->begin_x;
1245 v->focus_child = 1;
1247 err = view_fullscreen(v);
1248 if (err)
1249 return err;
1250 err = view_splitscreen(v->child);
1251 if (err)
1252 return err;
1254 if (v->mode == TOG_VIEW_SPLIT_HRZN) {
1255 err = offset_selection_down(v->child);
1256 if (err)
1257 return err;
1260 if (v->resize)
1261 err = v->resize(v, 0);
1262 else if (v->child->resize)
1263 err = v->child->resize(v->child, 0);
1265 v->resized = v->child->resized = 0;
1267 return err;
1270 static void
1271 view_transfer_size(struct tog_view *dst, struct tog_view *src)
1273 struct tog_view *v = src->child ? src->child : src;
1275 dst->resized_x = v->resized_x;
1276 dst->resized_y = v->resized_y;
1279 static const struct got_error *
1280 view_close_child(struct tog_view *view)
1282 const struct got_error *err = NULL;
1284 if (view->child == NULL)
1285 return NULL;
1287 err = view_close(view->child);
1288 view->child = NULL;
1289 return err;
1292 static const struct got_error *
1293 view_set_child(struct tog_view *view, struct tog_view *child)
1295 const struct got_error *err = NULL;
1297 view->child = child;
1298 child->parent = view;
1300 err = view_resize(view);
1301 if (err)
1302 return err;
1304 if (view->child->resized_x || view->child->resized_y)
1305 err = view_resize_split(view, 0);
1307 return err;
1310 static const struct got_error *view_dispatch_request(struct tog_view **,
1311 struct tog_view *, enum tog_view_type, int, int);
1313 static const struct got_error *
1314 view_request_new(struct tog_view **requested, struct tog_view *view,
1315 enum tog_view_type request)
1317 struct tog_view *new_view = NULL;
1318 const struct got_error *err;
1319 int y = 0, x = 0;
1321 *requested = NULL;
1323 if (view_is_parent_view(view) && request != TOG_VIEW_HELP)
1324 view_get_split(view, &y, &x);
1326 err = view_dispatch_request(&new_view, view, request, y, x);
1327 if (err) {
1329 * The ref view expects its selected entry to resolve to
1330 * a commit object id to open either a log or tree view.
1332 if (err->code != GOT_ERR_OBJ_TYPE)
1333 return err;
1334 view->action = "commit reference required";
1335 return NULL;
1338 if (view_is_parent_view(view) && view->mode == TOG_VIEW_SPLIT_HRZN &&
1339 request != TOG_VIEW_HELP) {
1340 err = view_init_hsplit(view, y);
1341 if (err)
1342 return err;
1345 view->focussed = 0;
1346 new_view->focussed = 1;
1347 new_view->mode = view->mode;
1348 new_view->nlines = request == TOG_VIEW_HELP ?
1349 view->lines : view->lines - y;
1351 if (view_is_parent_view(view) && request != TOG_VIEW_HELP) {
1352 view_transfer_size(new_view, view);
1353 err = view_close_child(view);
1354 if (err)
1355 return err;
1356 err = view_set_child(view, new_view);
1357 if (err)
1358 return err;
1359 view->focus_child = 1;
1360 } else
1361 *requested = new_view;
1363 return NULL;
1366 static void
1367 tog_resizeterm(void)
1369 int cols, lines;
1370 struct winsize size;
1372 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) < 0) {
1373 cols = 80; /* Default */
1374 lines = 24;
1375 } else {
1376 cols = size.ws_col;
1377 lines = size.ws_row;
1379 resize_term(lines, cols);
1382 static const struct got_error *
1383 view_search_start(struct tog_view *view, int fast_refresh)
1385 const struct got_error *err = NULL;
1386 struct tog_view *v = view;
1387 char pattern[1024];
1388 int ret;
1390 if (view->search_started) {
1391 regfree(&view->regex);
1392 view->searching = 0;
1393 memset(&view->regmatch, 0, sizeof(view->regmatch));
1395 view->search_started = 0;
1397 if (view->nlines < 1)
1398 return NULL;
1400 if (view_is_hsplit_top(view))
1401 v = view->child;
1402 else if (view->mode == TOG_VIEW_SPLIT_VERT && view->parent)
1403 v = view->parent;
1405 if (tog_io.input_str != NULL) {
1406 if (strlcpy(pattern, tog_io.input_str, sizeof(pattern)) >=
1407 sizeof(pattern))
1408 return got_error(GOT_ERR_NO_SPACE);
1409 } else {
1410 mvwaddstr(v->window, v->nlines - 1, 0, "/");
1411 wclrtoeol(v->window);
1412 nodelay(v->window, FALSE); /* block for search term input */
1413 nocbreak();
1414 echo();
1415 ret = wgetnstr(v->window, pattern, sizeof(pattern));
1416 wrefresh(v->window);
1417 cbreak();
1418 noecho();
1419 nodelay(v->window, TRUE);
1420 if (!fast_refresh && !using_mock_io)
1421 halfdelay(10);
1422 if (ret == ERR)
1423 return NULL;
1426 if (regcomp(&view->regex, pattern, REG_EXTENDED | REG_NEWLINE) == 0) {
1427 err = view->search_start(view);
1428 if (err) {
1429 regfree(&view->regex);
1430 return err;
1432 view->search_started = 1;
1433 view->searching = TOG_SEARCH_FORWARD;
1434 view->search_next_done = 0;
1435 view->search_next(view);
1438 return NULL;
1441 /* Switch split mode. If view is a parent or child, draw the new splitscreen. */
1442 static const struct got_error *
1443 switch_split(struct tog_view *view)
1445 const struct got_error *err = NULL;
1446 struct tog_view *v = NULL;
1448 if (view->parent)
1449 v = view->parent;
1450 else
1451 v = view;
1453 if (v->mode == TOG_VIEW_SPLIT_HRZN)
1454 v->mode = TOG_VIEW_SPLIT_VERT;
1455 else
1456 v->mode = TOG_VIEW_SPLIT_HRZN;
1458 if (!v->child)
1459 return NULL;
1460 else if (v->mode == TOG_VIEW_SPLIT_VERT && v->cols < 120)
1461 v->mode = TOG_VIEW_SPLIT_NONE;
1463 view_get_split(v, &v->child->begin_y, &v->child->begin_x);
1464 if (v->mode == TOG_VIEW_SPLIT_HRZN && v->child->resized_y)
1465 v->child->begin_y = v->child->resized_y;
1466 else if (v->mode == TOG_VIEW_SPLIT_VERT && v->child->resized_x)
1467 v->child->begin_x = v->child->resized_x;
1470 if (v->mode == TOG_VIEW_SPLIT_HRZN) {
1471 v->ncols = COLS;
1472 v->child->ncols = COLS;
1473 v->child->nscrolled = LINES - v->child->nlines;
1475 err = view_init_hsplit(v, v->child->begin_y);
1476 if (err)
1477 return err;
1479 v->child->mode = v->mode;
1480 v->child->nlines = v->lines - v->child->begin_y;
1481 v->focus_child = 1;
1483 err = view_fullscreen(v);
1484 if (err)
1485 return err;
1486 err = view_splitscreen(v->child);
1487 if (err)
1488 return err;
1490 if (v->mode == TOG_VIEW_SPLIT_NONE)
1491 v->mode = TOG_VIEW_SPLIT_VERT;
1492 if (v->mode == TOG_VIEW_SPLIT_HRZN) {
1493 err = offset_selection_down(v);
1494 if (err)
1495 return err;
1496 err = offset_selection_down(v->child);
1497 if (err)
1498 return err;
1499 } else {
1500 offset_selection_up(v);
1501 offset_selection_up(v->child);
1503 if (v->resize)
1504 err = v->resize(v, 0);
1505 else if (v->child->resize)
1506 err = v->child->resize(v->child, 0);
1508 return err;
1512 * Strip trailing whitespace from str starting at byte *n;
1513 * if *n < 0, use strlen(str). Return new str length in *n.
1515 static void
1516 strip_trailing_ws(char *str, int *n)
1518 size_t x = *n;
1520 if (str == NULL || *str == '\0')
1521 return;
1523 if (x < 0)
1524 x = strlen(str);
1526 while (x-- > 0 && isspace((unsigned char)str[x]))
1527 str[x] = '\0';
1529 *n = x + 1;
1533 * Extract visible substring of line y from the curses screen
1534 * and strip trailing whitespace. If vline is set, overwrite
1535 * line[vline] with '|' because the ACS_VLINE character is
1536 * written out as 'x'. Write the line to file f.
1538 static const struct got_error *
1539 view_write_line(FILE *f, int y, int vline)
1541 char line[COLS * MB_LEN_MAX]; /* allow for multibyte chars */
1542 int r, w;
1544 r = mvwinnstr(curscr, y, 0, line, sizeof(line));
1545 if (r == ERR)
1546 return got_error_fmt(GOT_ERR_RANGE,
1547 "failed to extract line %d", y);
1550 * In some views, lines are padded with blanks to COLS width.
1551 * Strip them so we can diff without the -b flag when testing.
1553 strip_trailing_ws(line, &r);
1555 if (vline > 0)
1556 line[vline] = '|';
1558 w = fprintf(f, "%s\n", line);
1559 if (w != r + 1) /* \n */
1560 return got_ferror(f, GOT_ERR_IO);
1562 return NULL;
1566 * Capture the visible curses screen by writing each line to the
1567 * file at the path set via the TOG_SCR_DUMP environment variable.
1569 static const struct got_error *
1570 screendump(struct tog_view *view)
1572 const struct got_error *err;
1573 int i;
1575 err = got_opentemp_truncate(tog_io.sdump);
1576 if (err)
1577 return err;
1579 if ((view->child && view->child->begin_x) ||
1580 (view->parent && view->begin_x)) {
1581 int ncols = view->child ? view->ncols : view->parent->ncols;
1583 /* vertical splitscreen */
1584 for (i = 0; i < view->nlines; ++i) {
1585 err = view_write_line(tog_io.sdump, i, ncols - 1);
1586 if (err)
1587 goto done;
1589 } else {
1590 int hline = 0;
1592 /* fullscreen or horizontal splitscreen */
1593 if ((view->child && view->child->begin_y) ||
1594 (view->parent && view->begin_y)) /* hsplit */
1595 hline = view->child ?
1596 view->child->begin_y : view->begin_y;
1598 for (i = 0; i < view->lines; i++) {
1599 if (hline && i == hline - 1) {
1600 int c;
1602 /* ACS_HLINE writes out as 'q', overwrite it */
1603 for (c = 0; c < view->cols; ++c)
1604 fputc('-', tog_io.sdump);
1605 fputc('\n', tog_io.sdump);
1606 continue;
1609 err = view_write_line(tog_io.sdump, i, 0);
1610 if (err)
1611 goto done;
1615 done:
1616 return err;
1620 * Compute view->count from numeric input. Assign total to view->count and
1621 * return first non-numeric key entered.
1623 static int
1624 get_compound_key(struct tog_view *view, int c)
1626 struct tog_view *v = view;
1627 int x, n = 0;
1629 if (view_is_hsplit_top(view))
1630 v = view->child;
1631 else if (view->mode == TOG_VIEW_SPLIT_VERT && view->parent)
1632 v = view->parent;
1634 view->count = 0;
1635 cbreak(); /* block for input */
1636 nodelay(view->window, FALSE);
1637 wmove(v->window, v->nlines - 1, 0);
1638 wclrtoeol(v->window);
1639 waddch(v->window, ':');
1641 do {
1642 x = getcurx(v->window);
1643 if (x != ERR && x < view->ncols) {
1644 waddch(v->window, c);
1645 wrefresh(v->window);
1649 * Don't overflow. Max valid request should be the greatest
1650 * between the longest and total lines; cap at 10 million.
1652 if (n >= 9999999)
1653 n = 9999999;
1654 else
1655 n = n * 10 + (c - '0');
1656 } while (((c = wgetch(view->window))) >= '0' && c <= '9' && c != ERR);
1658 if (c == 'G' || c == 'g') { /* nG key map */
1659 view->gline = view->hiline = n;
1660 n = 0;
1661 c = 0;
1664 /* Massage excessive or inapplicable values at the input handler. */
1665 view->count = n;
1667 return c;
1670 static void
1671 action_report(struct tog_view *view)
1673 struct tog_view *v = view;
1675 if (view_is_hsplit_top(view))
1676 v = view->child;
1677 else if (view->mode == TOG_VIEW_SPLIT_VERT && view->parent)
1678 v = view->parent;
1680 wmove(v->window, v->nlines - 1, 0);
1681 wclrtoeol(v->window);
1682 wprintw(v->window, ":%s", view->action);
1683 wrefresh(v->window);
1686 * Clear action status report. Only clear in blame view
1687 * once annotating is complete, otherwise it's too fast.
1688 * In diff view, let its state control view->action lifetime.
1690 if (view->type == TOG_VIEW_BLAME) {
1691 if (view->state.blame.blame_complete)
1692 view->action = NULL;
1693 } else if (view->type == TOG_VIEW_DIFF) {
1694 view->action = view->state.diff.action;
1695 } else
1696 view->action = NULL;
1700 * Read the next line from the test script and assign
1701 * key instruction to *ch. If at EOF, set the *done flag.
1703 static const struct got_error *
1704 tog_read_script_key(FILE *script, struct tog_view *view, int *ch, int *done)
1706 const struct got_error *err = NULL;
1707 char *line = NULL;
1708 size_t linesz = 0;
1709 ssize_t n;
1712 if (view->count && --view->count) {
1713 *ch = view->ch;
1714 return NULL;
1715 } else
1716 *ch = -1;
1718 if ((n = getline(&line, &linesz, script)) == -1) {
1719 if (feof(script)) {
1720 *done = 1;
1721 goto done;
1722 } else {
1723 err = got_ferror(script, GOT_ERR_IO);
1724 goto done;
1728 if (strncasecmp(line, "WAIT_FOR_UI", 11) == 0)
1729 tog_io.wait_for_ui = 1;
1730 else if (strncasecmp(line, "KEY_ENTER", 9) == 0)
1731 *ch = KEY_ENTER;
1732 else if (strncasecmp(line, "KEY_RIGHT", 9) == 0)
1733 *ch = KEY_RIGHT;
1734 else if (strncasecmp(line, "KEY_LEFT", 8) == 0)
1735 *ch = KEY_LEFT;
1736 else if (strncasecmp(line, "KEY_DOWN", 8) == 0)
1737 *ch = KEY_DOWN;
1738 else if (strncasecmp(line, "KEY_UP", 6) == 0)
1739 *ch = KEY_UP;
1740 else if (strncasecmp(line, "TAB", 3) == 0)
1741 *ch = '\t';
1742 else if (strncasecmp(line, "SCREENDUMP", 10) == 0)
1743 *ch = TOG_KEY_SCRDUMP;
1744 else if (isdigit((unsigned char)*line)) {
1745 char *t = line;
1747 while (isdigit((unsigned char)*t))
1748 ++t;
1749 view->ch = *ch = *t;
1750 *t = '\0';
1751 /* ignore error, view->count is 0 if instruction is invalid */
1752 view->count = strtonum(line, 0, INT_MAX, NULL);
1753 } else {
1754 *ch = *line;
1755 if (n > 2 && (*ch == '/' || *ch == '&')) {
1756 /* skip leading keymap and trim trailing newline */
1757 tog_io.input_str = strndup(line + 1, n - 2);
1758 if (tog_io.input_str == NULL) {
1759 err = got_error_from_errno("strndup");
1760 goto done;
1765 done:
1766 free(line);
1767 return err;
1770 static void
1771 log_mark_clear(struct tog_log_view_state *s)
1773 s->marked_entry = NULL;
1776 static const struct got_error *
1777 view_input(struct tog_view **new, int *done, struct tog_view *view,
1778 struct tog_view_list_head *views, int fast_refresh)
1780 const struct got_error *err = NULL;
1781 struct tog_view *v;
1782 int ch, errcode;
1784 *new = NULL;
1786 if (view->action)
1787 action_report(view);
1789 /* Clear "no matches" indicator. */
1790 if (view->search_next_done == TOG_SEARCH_NO_MORE ||
1791 view->search_next_done == TOG_SEARCH_HAVE_NONE) {
1792 view->search_next_done = TOG_SEARCH_HAVE_MORE;
1793 view->count = 0;
1796 if (view->searching && !view->search_next_done) {
1797 errcode = pthread_mutex_unlock(&tog_mutex);
1798 if (errcode)
1799 return got_error_set_errno(errcode,
1800 "pthread_mutex_unlock");
1801 sched_yield();
1802 errcode = pthread_mutex_lock(&tog_mutex);
1803 if (errcode)
1804 return got_error_set_errno(errcode,
1805 "pthread_mutex_lock");
1806 view->search_next(view);
1807 return NULL;
1810 /* Allow threads to make progress while we are waiting for input. */
1811 errcode = pthread_mutex_unlock(&tog_mutex);
1812 if (errcode)
1813 return got_error_set_errno(errcode, "pthread_mutex_unlock");
1815 if (using_mock_io) {
1816 err = tog_read_script_key(tog_io.f, view, &ch, done);
1817 if (err) {
1818 errcode = pthread_mutex_lock(&tog_mutex);
1819 return err;
1821 } else if (view->count && --view->count) {
1822 cbreak();
1823 nodelay(view->window, TRUE);
1824 ch = wgetch(view->window);
1825 /* let C-g or backspace abort unfinished count */
1826 if (ch == CTRL('g') || ch == KEY_BACKSPACE)
1827 view->count = 0;
1828 else
1829 ch = view->ch;
1830 } else {
1831 ch = wgetch(view->window);
1832 if (ch >= '1' && ch <= '9')
1833 view->ch = ch = get_compound_key(view, ch);
1835 if (view->hiline && ch != ERR && ch != 0)
1836 view->hiline = 0; /* key pressed, clear line highlight */
1837 wtimeout(view->window, fast_refresh ? 100 : 1000); /* milliseconds */
1838 errcode = pthread_mutex_lock(&tog_mutex);
1839 if (errcode)
1840 return got_error_set_errno(errcode, "pthread_mutex_lock");
1842 if (tog_sigwinch_received || tog_sigcont_received) {
1843 tog_resizeterm();
1844 tog_sigwinch_received = 0;
1845 tog_sigcont_received = 0;
1846 TAILQ_FOREACH(v, views, entry) {
1847 err = view_resize(v);
1848 if (err)
1849 return err;
1850 err = v->input(new, v, KEY_RESIZE);
1851 if (err)
1852 return err;
1853 if (v->child) {
1854 err = view_resize(v->child);
1855 if (err)
1856 return err;
1857 err = v->child->input(new, v->child,
1858 KEY_RESIZE);
1859 if (err)
1860 return err;
1861 if (v->child->resized_x || v->child->resized_y) {
1862 err = view_resize_split(v, 0);
1863 if (err)
1864 return err;
1870 switch (ch) {
1871 case '?':
1872 case 'H':
1873 case KEY_F(1):
1874 if (view->type == TOG_VIEW_HELP)
1875 err = view->reset(view);
1876 else
1877 err = view_request_new(new, view, TOG_VIEW_HELP);
1878 break;
1879 case '\t':
1880 view->count = 0;
1881 if (view->child) {
1882 view->focussed = 0;
1883 view->child->focussed = 1;
1884 view->focus_child = 1;
1885 } else if (view->parent) {
1886 view->focussed = 0;
1887 view->parent->focussed = 1;
1888 view->parent->focus_child = 0;
1889 if (!view_is_splitscreen(view)) {
1890 if (view->parent->resize) {
1891 err = view->parent->resize(view->parent,
1893 if (err)
1894 return err;
1896 offset_selection_up(view->parent);
1897 err = view_fullscreen(view->parent);
1898 if (err)
1899 return err;
1902 break;
1903 case 'q':
1904 if (view->parent != NULL) {
1905 if (view->parent->type == TOG_VIEW_LOG)
1906 log_mark_clear(&view->parent->state.log);
1908 if (view->mode == TOG_VIEW_SPLIT_HRZN) {
1909 if (view->parent->resize) {
1911 * Might need more commits
1912 * to fill fullscreen.
1914 err = view->parent->resize(
1915 view->parent, 0);
1916 if (err)
1917 break;
1919 offset_selection_up(view->parent);
1922 err = view->input(new, view, ch);
1923 view->dying = 1;
1924 break;
1925 case 'Q':
1926 *done = 1;
1927 break;
1928 case 'F':
1929 view->count = 0;
1930 if (view_is_parent_view(view)) {
1931 if (view->child == NULL)
1932 break;
1933 if (view_is_splitscreen(view->child)) {
1934 view->focussed = 0;
1935 view->child->focussed = 1;
1936 err = view_fullscreen(view->child);
1937 } else {
1938 err = view_splitscreen(view->child);
1939 if (!err)
1940 err = view_resize_split(view, 0);
1942 if (err)
1943 break;
1944 err = view->child->input(new, view->child,
1945 KEY_RESIZE);
1946 } else {
1947 if (view_is_splitscreen(view)) {
1948 view->parent->focussed = 0;
1949 view->focussed = 1;
1950 err = view_fullscreen(view);
1951 } else {
1952 err = view_splitscreen(view);
1953 if (!err && view->mode != TOG_VIEW_SPLIT_HRZN)
1954 err = view_resize(view->parent);
1955 if (!err)
1956 err = view_resize_split(view, 0);
1958 if (err)
1959 break;
1960 err = view->input(new, view, KEY_RESIZE);
1962 if (err)
1963 break;
1964 if (view->resize) {
1965 err = view->resize(view, 0);
1966 if (err)
1967 break;
1969 if (view->parent) {
1970 if (view->parent->resize) {
1971 err = view->parent->resize(view->parent, 0);
1972 if (err != NULL)
1973 break;
1975 err = offset_selection_down(view->parent);
1976 if (err != NULL)
1977 break;
1979 err = offset_selection_down(view);
1980 break;
1981 case 'S':
1982 view->count = 0;
1983 err = switch_split(view);
1984 break;
1985 case '-':
1986 err = view_resize_split(view, -1);
1987 break;
1988 case '+':
1989 err = view_resize_split(view, 1);
1990 break;
1991 case KEY_RESIZE:
1992 break;
1993 case '/':
1994 view->count = 0;
1995 if (view->search_start)
1996 view_search_start(view, fast_refresh);
1997 else
1998 err = view->input(new, view, ch);
1999 break;
2000 case 'N':
2001 case 'n':
2002 if (view->search_started && view->search_next) {
2003 view->searching = (ch == 'n' ?
2004 TOG_SEARCH_FORWARD : TOG_SEARCH_BACKWARD);
2005 view->search_next_done = 0;
2006 view->search_next(view);
2007 } else
2008 err = view->input(new, view, ch);
2009 break;
2010 case 'A':
2011 if (tog_diff_algo == GOT_DIFF_ALGORITHM_MYERS) {
2012 tog_diff_algo = GOT_DIFF_ALGORITHM_PATIENCE;
2013 view->action = "Patience diff algorithm";
2014 } else {
2015 tog_diff_algo = GOT_DIFF_ALGORITHM_MYERS;
2016 view->action = "Myers diff algorithm";
2018 TAILQ_FOREACH(v, views, entry) {
2019 if (v->reset) {
2020 err = v->reset(v);
2021 if (err)
2022 return err;
2024 if (v->child && v->child->reset) {
2025 err = v->child->reset(v->child);
2026 if (err)
2027 return err;
2030 break;
2031 case TOG_KEY_SCRDUMP:
2032 err = screendump(view);
2033 break;
2034 default:
2035 err = view->input(new, view, ch);
2036 break;
2039 return err;
2042 static int
2043 view_needs_focus_indication(struct tog_view *view)
2045 if (view_is_parent_view(view)) {
2046 if (view->child == NULL || view->child->focussed)
2047 return 0;
2048 if (!view_is_splitscreen(view->child))
2049 return 0;
2050 } else if (!view_is_splitscreen(view))
2051 return 0;
2053 return view->focussed;
2056 static const struct got_error *
2057 tog_io_close(void)
2059 const struct got_error *err = NULL;
2061 if (tog_io.cin && fclose(tog_io.cin) == EOF)
2062 err = got_ferror(tog_io.cin, GOT_ERR_IO);
2063 if (tog_io.cout && fclose(tog_io.cout) == EOF && err == NULL)
2064 err = got_ferror(tog_io.cout, GOT_ERR_IO);
2065 if (tog_io.f && fclose(tog_io.f) == EOF && err == NULL)
2066 err = got_ferror(tog_io.f, GOT_ERR_IO);
2067 if (tog_io.sdump && fclose(tog_io.sdump) == EOF && err == NULL)
2068 err = got_ferror(tog_io.sdump, GOT_ERR_IO);
2069 if (tog_io.input_str != NULL)
2070 free(tog_io.input_str);
2072 return err;
2075 static const struct got_error *
2076 view_loop(struct tog_view *view)
2078 const struct got_error *err = NULL;
2079 struct tog_view_list_head views;
2080 struct tog_view *new_view;
2081 char *mode;
2082 int fast_refresh = 10;
2083 int done = 0, errcode;
2085 mode = getenv("TOG_VIEW_SPLIT_MODE");
2086 if (!mode || !(*mode == 'h' || *mode == 'H'))
2087 view->mode = TOG_VIEW_SPLIT_VERT;
2088 else
2089 view->mode = TOG_VIEW_SPLIT_HRZN;
2091 errcode = pthread_mutex_lock(&tog_mutex);
2092 if (errcode)
2093 return got_error_set_errno(errcode, "pthread_mutex_lock");
2095 TAILQ_INIT(&views);
2096 TAILQ_INSERT_HEAD(&views, view, entry);
2098 view->focussed = 1;
2099 err = view->show(view);
2100 if (err)
2101 return err;
2102 update_panels();
2103 doupdate();
2104 while (!TAILQ_EMPTY(&views) && !done && !tog_thread_error &&
2105 !tog_fatal_signal_received()) {
2106 /* Refresh fast during initialization, then become slower. */
2107 if (fast_refresh && --fast_refresh == 0 && !using_mock_io)
2108 halfdelay(10); /* switch to once per second */
2110 err = view_input(&new_view, &done, view, &views, fast_refresh);
2111 if (err)
2112 break;
2114 if (view->dying && view == TAILQ_FIRST(&views) &&
2115 TAILQ_NEXT(view, entry) == NULL)
2116 done = 1;
2117 if (done) {
2118 struct tog_view *v;
2121 * When we quit, scroll the screen up a single line
2122 * so we don't lose any information.
2124 TAILQ_FOREACH(v, &views, entry) {
2125 wmove(v->window, 0, 0);
2126 wdeleteln(v->window);
2127 wnoutrefresh(v->window);
2128 if (v->child && !view_is_fullscreen(v)) {
2129 wmove(v->child->window, 0, 0);
2130 wdeleteln(v->child->window);
2131 wnoutrefresh(v->child->window);
2134 doupdate();
2137 if (view->dying) {
2138 struct tog_view *v, *prev = NULL;
2140 if (view_is_parent_view(view))
2141 prev = TAILQ_PREV(view, tog_view_list_head,
2142 entry);
2143 else if (view->parent)
2144 prev = view->parent;
2146 if (view->parent) {
2147 view->parent->child = NULL;
2148 view->parent->focus_child = 0;
2149 /* Restore fullscreen line height. */
2150 view->parent->nlines = view->parent->lines;
2151 err = view_resize(view->parent);
2152 if (err)
2153 break;
2154 /* Make resized splits persist. */
2155 view_transfer_size(view->parent, view);
2156 } else
2157 TAILQ_REMOVE(&views, view, entry);
2159 err = view_close(view);
2160 if (err)
2161 goto done;
2163 view = NULL;
2164 TAILQ_FOREACH(v, &views, entry) {
2165 if (v->focussed)
2166 break;
2168 if (view == NULL && new_view == NULL) {
2169 /* No view has focus. Try to pick one. */
2170 if (prev)
2171 view = prev;
2172 else if (!TAILQ_EMPTY(&views)) {
2173 view = TAILQ_LAST(&views,
2174 tog_view_list_head);
2176 if (view) {
2177 if (view->focus_child) {
2178 view->child->focussed = 1;
2179 view = view->child;
2180 } else
2181 view->focussed = 1;
2185 if (new_view) {
2186 struct tog_view *v, *t;
2187 /* Only allow one parent view per type. */
2188 TAILQ_FOREACH_SAFE(v, &views, entry, t) {
2189 if (v->type != new_view->type)
2190 continue;
2191 TAILQ_REMOVE(&views, v, entry);
2192 err = view_close(v);
2193 if (err)
2194 goto done;
2195 break;
2197 TAILQ_INSERT_TAIL(&views, new_view, entry);
2198 view = new_view;
2200 if (view && !done) {
2201 if (view_is_parent_view(view)) {
2202 if (view->child && view->child->focussed)
2203 view = view->child;
2204 } else {
2205 if (view->parent && view->parent->focussed)
2206 view = view->parent;
2208 show_panel(view->panel);
2209 if (view->child && view_is_splitscreen(view->child))
2210 show_panel(view->child->panel);
2211 if (view->parent && view_is_splitscreen(view)) {
2212 err = view->parent->show(view->parent);
2213 if (err)
2214 goto done;
2216 err = view->show(view);
2217 if (err)
2218 goto done;
2219 if (view->child) {
2220 err = view->child->show(view->child);
2221 if (err)
2222 goto done;
2224 update_panels();
2225 doupdate();
2228 done:
2229 while (!TAILQ_EMPTY(&views)) {
2230 const struct got_error *close_err;
2231 view = TAILQ_FIRST(&views);
2232 TAILQ_REMOVE(&views, view, entry);
2233 close_err = view_close(view);
2234 if (close_err && err == NULL)
2235 err = close_err;
2238 errcode = pthread_mutex_unlock(&tog_mutex);
2239 if (errcode && err == NULL)
2240 err = got_error_set_errno(errcode, "pthread_mutex_unlock");
2242 return err;
2245 __dead static void
2246 usage_log(void)
2248 endwin();
2249 fprintf(stderr,
2250 "usage: %s log [-b] [-c commit] [-r repository-path] [path]\n",
2251 getprogname());
2252 exit(1);
2255 /* Create newly allocated wide-character string equivalent to a byte string. */
2256 static const struct got_error *
2257 mbs2ws(wchar_t **ws, size_t *wlen, const char *s)
2259 char *vis = NULL;
2260 const struct got_error *err = NULL;
2262 *ws = NULL;
2263 *wlen = mbstowcs(NULL, s, 0);
2264 if (*wlen == (size_t)-1) {
2265 int vislen;
2266 if (errno != EILSEQ)
2267 return got_error_from_errno("mbstowcs");
2269 /* byte string invalid in current encoding; try to "fix" it */
2270 err = got_mbsavis(&vis, &vislen, s);
2271 if (err)
2272 return err;
2273 *wlen = mbstowcs(NULL, vis, 0);
2274 if (*wlen == (size_t)-1) {
2275 err = got_error_from_errno("mbstowcs"); /* give up */
2276 goto done;
2280 *ws = calloc(*wlen + 1, sizeof(**ws));
2281 if (*ws == NULL) {
2282 err = got_error_from_errno("calloc");
2283 goto done;
2286 if (mbstowcs(*ws, vis ? vis : s, *wlen) != *wlen)
2287 err = got_error_from_errno("mbstowcs");
2288 done:
2289 free(vis);
2290 if (err) {
2291 free(*ws);
2292 *ws = NULL;
2293 *wlen = 0;
2295 return err;
2298 static const struct got_error *
2299 expand_tab(char **ptr, const char *src)
2301 char *dst;
2302 size_t len, n, idx = 0, sz = 0;
2304 *ptr = NULL;
2305 n = len = strlen(src);
2306 dst = malloc(n + 1);
2307 if (dst == NULL)
2308 return got_error_from_errno("malloc");
2310 while (idx < len && src[idx]) {
2311 const char c = src[idx];
2313 if (c == '\t') {
2314 size_t nb = TABSIZE - sz % TABSIZE;
2315 char *p;
2317 p = realloc(dst, n + nb);
2318 if (p == NULL) {
2319 free(dst);
2320 return got_error_from_errno("realloc");
2323 dst = p;
2324 n += nb;
2325 memset(dst + sz, ' ', nb);
2326 sz += nb;
2327 } else
2328 dst[sz++] = src[idx];
2329 ++idx;
2332 dst[sz] = '\0';
2333 *ptr = dst;
2334 return NULL;
2338 * Advance at most n columns from wline starting at offset off.
2339 * Return the index to the first character after the span operation.
2340 * Return the combined column width of all spanned wide characters in
2341 * *rcol.
2343 static int
2344 span_wline(int *rcol, int off, wchar_t *wline, int n, int col_tab_align)
2346 int width, i, cols = 0;
2348 if (n == 0) {
2349 *rcol = cols;
2350 return off;
2353 for (i = off; wline[i] != L'\0'; ++i) {
2354 if (wline[i] == L'\t')
2355 width = TABSIZE - ((cols + col_tab_align) % TABSIZE);
2356 else
2357 width = wcwidth(wline[i]);
2359 if (width == -1) {
2360 width = 1;
2361 wline[i] = L'.';
2364 if (cols + width > n)
2365 break;
2366 cols += width;
2369 *rcol = cols;
2370 return i;
2374 * Format a line for display, ensuring that it won't overflow a width limit.
2375 * With scrolling, the width returned refers to the scrolled version of the
2376 * line, which starts at (*wlinep)[*scrollxp]. The caller must free *wlinep.
2378 static const struct got_error *
2379 format_line(wchar_t **wlinep, int *widthp, int *scrollxp,
2380 const char *line, int nscroll, int wlimit, int col_tab_align, int expand)
2382 const struct got_error *err = NULL;
2383 int cols;
2384 wchar_t *wline = NULL;
2385 char *exstr = NULL;
2386 size_t wlen;
2387 int i, scrollx;
2389 *wlinep = NULL;
2390 *widthp = 0;
2392 if (expand) {
2393 err = expand_tab(&exstr, line);
2394 if (err)
2395 return err;
2398 err = mbs2ws(&wline, &wlen, expand ? exstr : line);
2399 free(exstr);
2400 if (err)
2401 return err;
2403 if (wlen > 0 && wline[wlen - 1] == L'\n') {
2404 wline[wlen - 1] = L'\0';
2405 wlen--;
2407 if (wlen > 0 && wline[wlen - 1] == L'\r') {
2408 wline[wlen - 1] = L'\0';
2409 wlen--;
2412 scrollx = span_wline(&cols, 0, wline, nscroll, col_tab_align);
2414 i = span_wline(&cols, scrollx, wline, wlimit, col_tab_align);
2415 wline[i] = L'\0';
2417 if (widthp)
2418 *widthp = cols;
2419 if (scrollxp)
2420 *scrollxp = scrollx;
2421 if (err)
2422 free(wline);
2423 else
2424 *wlinep = wline;
2425 return err;
2428 static const struct got_error*
2429 build_refs_str(char **refs_str, struct got_reflist_head *refs,
2430 struct got_object_id *id, struct got_repository *repo)
2432 static const struct got_error *err = NULL;
2433 struct got_reflist_entry *re;
2434 char *s;
2435 const char *name;
2437 *refs_str = NULL;
2439 if (refs == NULL)
2440 return NULL;
2442 TAILQ_FOREACH(re, refs, entry) {
2443 struct got_tag_object *tag = NULL;
2444 struct got_object_id *ref_id;
2445 int cmp;
2447 name = got_ref_get_name(re->ref);
2448 if (strcmp(name, GOT_REF_HEAD) == 0)
2449 continue;
2450 if (strncmp(name, "refs/", 5) == 0)
2451 name += 5;
2452 if (strncmp(name, "got/", 4) == 0)
2453 continue;
2454 if (strncmp(name, "heads/", 6) == 0)
2455 name += 6;
2456 if (strncmp(name, "remotes/", 8) == 0) {
2457 name += 8;
2458 s = strstr(name, "/" GOT_REF_HEAD);
2459 if (s != NULL && strcmp(s, "/" GOT_REF_HEAD) == 0)
2460 continue;
2462 err = got_ref_resolve(&ref_id, repo, re->ref);
2463 if (err)
2464 break;
2465 if (strncmp(name, "tags/", 5) == 0) {
2466 err = got_object_open_as_tag(&tag, repo, ref_id);
2467 if (err) {
2468 if (err->code != GOT_ERR_OBJ_TYPE) {
2469 free(ref_id);
2470 break;
2472 /* Ref points at something other than a tag. */
2473 err = NULL;
2474 tag = NULL;
2477 cmp = got_object_id_cmp(tag ?
2478 got_object_tag_get_object_id(tag) : ref_id, id);
2479 free(ref_id);
2480 if (tag)
2481 got_object_tag_close(tag);
2482 if (cmp != 0)
2483 continue;
2484 s = *refs_str;
2485 if (asprintf(refs_str, "%s%s%s", s ? s : "",
2486 s ? ", " : "", name) == -1) {
2487 err = got_error_from_errno("asprintf");
2488 free(s);
2489 *refs_str = NULL;
2490 break;
2492 free(s);
2495 return err;
2498 static const struct got_error *
2499 format_author(wchar_t **wauthor, int *author_width, char *author, int limit,
2500 int col_tab_align)
2502 char *smallerthan;
2504 smallerthan = strchr(author, '<');
2505 if (smallerthan && smallerthan[1] != '\0')
2506 author = smallerthan + 1;
2507 author[strcspn(author, "@>")] = '\0';
2508 return format_line(wauthor, author_width, NULL, author, 0, limit,
2509 col_tab_align, 0);
2512 static const struct got_error *
2513 draw_commit_marker(struct tog_view *view, char c)
2515 struct tog_color *tc;
2517 if (view->type != TOG_VIEW_LOG)
2518 return got_error_msg(GOT_ERR_NOT_IMPL, "view not supported");
2520 tc = get_color(&view->state.log.colors, TOG_COLOR_COMMIT);
2521 if (tc != NULL)
2522 wattr_on(view->window, COLOR_PAIR(tc->colorpair), NULL);
2523 if (waddch(view->window, c) == ERR)
2524 return got_error_msg(GOT_ERR_IO, "waddch");
2525 if (tc != NULL)
2526 wattr_off(view->window, COLOR_PAIR(tc->colorpair), NULL);
2528 return NULL;
2531 static void
2532 tog_waddwstr(struct tog_view *view, wchar_t *wstr, int width,
2533 int *col, int color, int toeol)
2535 struct tog_color *tc;
2536 int x;
2538 x = col != NULL ? *col : getcurx(view->window);
2539 tc = color > 0 ? get_color(&view->state.log.colors, color) : NULL;
2541 if (tc != NULL)
2542 wattr_on(view->window, COLOR_PAIR(tc->colorpair), NULL);
2543 waddwstr(view->window, wstr);
2544 x += MAX(width, 0);
2545 if (toeol) {
2546 while (x < view->ncols) {
2547 waddch(view->window, ' ');
2548 ++x;
2551 if (tc != NULL)
2552 wattr_off(view->window, COLOR_PAIR(tc->colorpair), NULL);
2553 if (col != NULL)
2554 *col = x;
2557 static void
2558 tog_waddnstr(struct tog_view *view, const char *str, int limit, int color)
2560 struct tog_color *tc;
2562 if (limit == 0)
2563 limit = view->ncols - getcurx(view->window);
2565 tc = get_color(&view->state.log.colors, color);
2566 if (tc != NULL)
2567 wattr_on(view->window, COLOR_PAIR(tc->colorpair), NULL);
2568 waddnstr(view->window, str, limit);
2569 if (tc != NULL)
2570 wattr_off(view->window, COLOR_PAIR(tc->colorpair), NULL);
2573 static const struct got_error *
2574 draw_author(struct tog_view *view, char *author, int author_display_cols,
2575 int limit, int *col, int color, int marker_column,
2576 struct commit_queue_entry *entry)
2578 const struct got_error *err;
2579 struct tog_log_view_state *s = &view->state.log;
2580 struct tog_color *tc;
2581 wchar_t *wauthor;
2582 int author_width;
2584 err = format_author(&wauthor, &author_width, author, limit, *col);
2585 if (err != NULL)
2586 return err;
2587 if ((tc = get_color(&s->colors, color)) != NULL)
2588 wattr_on(view->window, COLOR_PAIR(tc->colorpair), NULL);
2589 waddwstr(view->window, wauthor);
2590 free(wauthor);
2592 *col += author_width;
2593 while (*col < limit && author_width < author_display_cols + 2) {
2594 if (entry != NULL && s->marked_entry == entry &&
2595 author_width == marker_column) {
2596 err = draw_commit_marker(view, '>');
2597 if (err != NULL)
2598 return err;
2599 } else if (entry != NULL &&
2600 tog_base_commit.marker != GOT_WORKTREE_STATE_UNKNOWN &&
2601 author_width == marker_column &&
2602 entry->idx == tog_base_commit.idx && !s->limit_view) {
2603 err = draw_commit_marker(view, tog_base_commit.marker);
2604 if (err != NULL)
2605 return err;
2606 } else
2607 waddch(view->window, ' ');
2608 ++(*col);
2609 ++(author_width);
2611 if (tc != NULL)
2612 wattr_off(view->window, COLOR_PAIR(tc->colorpair), NULL);
2614 return NULL;
2617 static const struct got_error *
2618 draw_idstr(struct tog_view *view, const char *id_str, int color)
2620 char *str = NULL;
2622 if (strlen(id_str) > 9 && asprintf(&str, "%.8s ", id_str) == -1)
2623 return got_error_from_errno("asprintf");
2625 tog_waddnstr(view, str != NULL ? str : id_str, 0, color);
2626 free(str);
2627 return NULL;
2630 static const struct got_error *
2631 draw_ymd(struct tog_view *view, time_t t, int *limit, int avail,
2632 int date_display_cols)
2634 struct tm tm;
2635 char datebuf[12]; /* YYYY-MM-DD + SPACE + NUL */
2637 if (gmtime_r(&t, &tm) == NULL)
2638 return got_error_from_errno("gmtime_r");
2639 if (strftime(datebuf, sizeof(datebuf), "%F ", &tm) == 0)
2640 return got_error(GOT_ERR_NO_SPACE);
2642 if (avail <= date_display_cols)
2643 *limit = MIN(sizeof(datebuf) - 1, avail);
2644 else
2645 *limit = MIN(date_display_cols, sizeof(datebuf) - 1);
2647 tog_waddnstr(view, datebuf, *limit, TOG_COLOR_DATE);
2648 return NULL;
2651 static const struct got_error *
2652 draw_worktree_entry(struct tog_view *view, int wt_entry,
2653 const size_t date_display_cols, int author_display_cols)
2655 const struct got_error *err = NULL;
2656 struct tog_log_view_state *s = &view->state.log;
2657 wchar_t *wmsg = NULL;
2658 char *author, *msg = NULL;
2659 char *base_commit_id = NULL;
2660 const char *p = TOG_WORKTREE_CHANGES_LOCAL_MSG;
2661 int col, limit, scrollx, width;
2662 const int avail = view->ncols;
2664 err = draw_ymd(view, time(NULL), &col, avail, date_display_cols);
2665 if (err != NULL)
2666 return err;
2667 if (col > avail)
2668 return NULL;
2669 if (avail >= 120) {
2670 err = draw_idstr(view, "........ ", TOG_COLOR_COMMIT);
2671 if (err != NULL)
2672 return err;
2673 col += 9;
2674 if (col > avail)
2675 return NULL;
2678 author = strdup(s->thread_args.wctx.wt_author);
2679 if (author == NULL)
2680 return got_error_from_errno("strdup");
2682 err = draw_author(view, author, author_display_cols, avail - col,
2683 &col, TOG_COLOR_AUTHOR, 0, NULL);
2684 if (err != NULL)
2685 goto done;
2686 if (col > avail)
2687 goto done;
2689 err = got_object_id_str(&base_commit_id, tog_base_commit.id);
2690 if (err != NULL)
2691 goto done;
2692 if (wt_entry & TOG_WORKTREE_CHANGES_STAGED)
2693 p = TOG_WORKTREE_CHANGES_STAGED_MSG;
2694 if (asprintf(&msg, "%s based on [%.10s]", p, base_commit_id) == -1) {
2695 err = got_error_from_errno("asprintf");
2696 goto done;
2699 limit = avail - col;
2700 if (view->child != NULL && !view_is_hsplit_top(view) && limit > 0)
2701 limit--; /* for the border */
2703 err = format_line(&wmsg, &width, &scrollx, msg, view->x, limit, col, 1);
2704 if (err != NULL)
2705 goto done;
2706 tog_waddwstr(view, &wmsg[scrollx], width, &col, 0, 1);
2708 done:
2709 free(msg);
2710 free(wmsg);
2711 free(author);
2712 free(base_commit_id);
2713 return err;
2716 static const struct got_error *
2717 draw_commit(struct tog_view *view, struct commit_queue_entry *entry,
2718 const size_t date_display_cols, int author_display_cols)
2720 struct tog_log_view_state *s = &view->state.log;
2721 const struct got_error *err = NULL;
2722 struct got_commit_object *commit = entry->commit;
2723 struct got_object_id *id = entry->id;
2724 char *author, *newline, *logmsg, *logmsg0 = NULL, *refs_str = NULL;
2725 wchar_t *wrefstr = NULL, *wlogmsg = NULL;
2726 int refstr_width, logmsg_width, col, limit, scrollx, logmsg_x;
2727 const int avail = view->ncols, marker_column = author_display_cols + 1;
2728 time_t committer_time;
2729 struct got_reflist_head *refs;
2731 if (tog_base_commit.id != NULL && tog_base_commit.idx == -1 &&
2732 got_object_id_cmp(id, tog_base_commit.id) == 0)
2733 tog_base_commit.idx = entry->idx;
2734 if (tog_io.wait_for_ui && s->thread_args.need_commit_marker) {
2735 int rc;
2737 rc = pthread_cond_wait(&s->thread_args.log_loaded, &tog_mutex);
2738 if (rc)
2739 return got_error_set_errno(rc, "pthread_cond_wait");
2742 committer_time = got_object_commit_get_committer_time(commit);
2743 err = draw_ymd(view, committer_time, &col, avail, date_display_cols);
2744 if (err != NULL)
2745 return err;
2746 if (col > avail)
2747 return NULL;
2749 if (avail >= 120) {
2750 char *id_str;
2752 err = got_object_id_str(&id_str, id);
2753 if (err)
2754 return err;
2755 err = draw_idstr(view, id_str, TOG_COLOR_COMMIT);
2756 free(id_str);
2757 if (err != NULL)
2758 return err;
2759 col += 9;
2760 if (col > avail)
2761 return NULL;
2764 if (s->use_committer)
2765 author = strdup(got_object_commit_get_committer(commit));
2766 else
2767 author = strdup(got_object_commit_get_author(commit));
2768 if (author == NULL)
2769 return got_error_from_errno("strdup");
2771 err = draw_author(view, author, author_display_cols,
2772 avail - col, &col, TOG_COLOR_AUTHOR, marker_column, entry);
2773 if (err != NULL)
2774 goto done;
2775 if (col > avail)
2776 goto done;
2778 err = got_object_commit_get_logmsg(&logmsg0, commit);
2779 if (err)
2780 goto done;
2781 logmsg = logmsg0;
2782 while (*logmsg == '\n')
2783 logmsg++;
2784 newline = strchr(logmsg, '\n');
2785 if (newline)
2786 *newline = '\0';
2788 limit = avail - col;
2789 if (view->child && !view_is_hsplit_top(view) && limit > 0)
2790 limit--; /* for the border */
2792 /* Prepend reference labels to log message if possible .*/
2793 refs = got_reflist_object_id_map_lookup(tog_refs_idmap, id);
2794 err = build_refs_str(&refs_str, refs, id, s->repo);
2795 if (err)
2796 goto done;
2797 if (refs_str) {
2798 char *rs;
2800 if (asprintf(&rs, "[%s]", refs_str) == -1) {
2801 err = got_error_from_errno("asprintf");
2802 goto done;
2804 err = format_line(&wrefstr, &refstr_width,
2805 &scrollx, rs, view->x, limit, col, 1);
2806 free(rs);
2807 if (err)
2808 goto done;
2809 tog_waddwstr(view, &wrefstr[scrollx], refstr_width,
2810 &col, TOG_COLOR_COMMIT, 0);
2811 if (col > avail)
2812 goto done;
2814 if (col < avail) {
2815 waddch(view->window, ' ');
2816 col++;
2819 if (refstr_width > 0)
2820 logmsg_x = 0;
2821 else {
2822 int unscrolled_refstr_width;
2823 size_t len = wcslen(wrefstr);
2826 * No need to check for -1 return value here since
2827 * unprintables have been replaced by span_wline().
2829 unscrolled_refstr_width = wcswidth(wrefstr, len);
2830 unscrolled_refstr_width += 1; /* trailing space */
2831 logmsg_x = view->x - unscrolled_refstr_width;
2834 limit = avail - col;
2835 if (view->child && !view_is_hsplit_top(view) && limit > 0)
2836 limit--; /* for the border */
2837 } else
2838 logmsg_x = view->x;
2840 err = format_line(&wlogmsg, &logmsg_width, &scrollx, logmsg, logmsg_x,
2841 limit, col, 1);
2842 if (err)
2843 goto done;
2844 tog_waddwstr(view, &wlogmsg[scrollx], logmsg_width, &col, 0, 1);
2846 done:
2847 free(logmsg0);
2848 free(wlogmsg);
2849 free(wrefstr);
2850 free(refs_str);
2851 free(author);
2852 return err;
2855 static struct commit_queue_entry *
2856 alloc_commit_queue_entry(struct got_commit_object *commit,
2857 struct got_object_id *id)
2859 struct commit_queue_entry *entry;
2860 struct got_object_id *dup;
2862 entry = calloc(1, sizeof(*entry));
2863 if (entry == NULL)
2864 return NULL;
2866 dup = got_object_id_dup(id);
2867 if (dup == NULL) {
2868 free(entry);
2869 return NULL;
2872 entry->id = dup;
2873 entry->commit = commit;
2874 return entry;
2877 static void
2878 pop_commit(struct commit_queue *commits)
2880 struct commit_queue_entry *entry;
2882 entry = TAILQ_FIRST(&commits->head);
2883 TAILQ_REMOVE(&commits->head, entry, entry);
2884 if (entry->worktree_entry == 0)
2885 got_object_commit_close(entry->commit);
2886 commits->ncommits--;
2887 free(entry->id);
2888 free(entry);
2891 static void
2892 free_commits(struct commit_queue *commits)
2894 while (!TAILQ_EMPTY(&commits->head))
2895 pop_commit(commits);
2898 static const struct got_error *
2899 match_commit(int *have_match, struct got_object_id *id,
2900 struct got_commit_object *commit, regex_t *regex)
2902 const struct got_error *err = NULL;
2903 regmatch_t regmatch;
2904 char *id_str = NULL, *logmsg = NULL;
2906 *have_match = 0;
2908 err = got_object_id_str(&id_str, id);
2909 if (err)
2910 return err;
2912 err = got_object_commit_get_logmsg(&logmsg, commit);
2913 if (err)
2914 goto done;
2916 if (regexec(regex, got_object_commit_get_author(commit), 1,
2917 &regmatch, 0) == 0 ||
2918 regexec(regex, got_object_commit_get_committer(commit), 1,
2919 &regmatch, 0) == 0 ||
2920 regexec(regex, id_str, 1, &regmatch, 0) == 0 ||
2921 regexec(regex, logmsg, 1, &regmatch, 0) == 0)
2922 *have_match = 1;
2923 done:
2924 free(id_str);
2925 free(logmsg);
2926 return err;
2929 static const struct got_error *
2930 queue_commits(struct tog_log_thread_args *a)
2932 const struct got_error *err = NULL;
2935 * We keep all commits open throughout the lifetime of the log
2936 * view in order to avoid having to re-fetch commits from disk
2937 * while updating the display.
2939 do {
2940 struct got_object_id id;
2941 struct got_commit_object *commit;
2942 struct commit_queue_entry *entry;
2943 int limit_match = 0;
2944 int errcode;
2946 err = got_commit_graph_iter_next(&id, a->graph, a->repo,
2947 NULL, NULL);
2948 if (err)
2949 break;
2951 err = got_object_open_as_commit(&commit, a->repo, &id);
2952 if (err)
2953 break;
2954 entry = alloc_commit_queue_entry(commit, &id);
2955 if (entry == NULL) {
2956 err = got_error_from_errno("alloc_commit_queue_entry");
2957 break;
2960 errcode = pthread_mutex_lock(&tog_mutex);
2961 if (errcode) {
2962 err = got_error_set_errno(errcode,
2963 "pthread_mutex_lock");
2964 break;
2967 entry->idx = a->real_commits->ncommits;
2968 TAILQ_INSERT_TAIL(&a->real_commits->head, entry, entry);
2969 a->real_commits->ncommits++;
2971 if (*a->limiting) {
2972 err = match_commit(&limit_match, &id, commit,
2973 a->limit_regex);
2974 if (err)
2975 break;
2977 if (limit_match) {
2978 struct commit_queue_entry *matched;
2980 matched = alloc_commit_queue_entry(
2981 entry->commit, entry->id);
2982 if (matched == NULL) {
2983 err = got_error_from_errno(
2984 "alloc_commit_queue_entry");
2985 break;
2987 matched->commit = entry->commit;
2988 got_object_commit_retain(entry->commit);
2990 matched->idx = a->limit_commits->ncommits;
2991 TAILQ_INSERT_TAIL(&a->limit_commits->head,
2992 matched, entry);
2993 a->limit_commits->ncommits++;
2997 * This is how we signal log_thread() that we
2998 * have found a match, and that it should be
2999 * counted as a new entry for the view.
3001 a->limit_match = limit_match;
3004 if (*a->searching == TOG_SEARCH_FORWARD &&
3005 !*a->search_next_done) {
3006 int have_match;
3007 err = match_commit(&have_match, &id, commit, a->regex);
3008 if (err)
3009 break;
3011 if (*a->limiting) {
3012 if (limit_match && have_match)
3013 *a->search_next_done =
3014 TOG_SEARCH_HAVE_MORE;
3015 } else if (have_match)
3016 *a->search_next_done = TOG_SEARCH_HAVE_MORE;
3019 errcode = pthread_mutex_unlock(&tog_mutex);
3020 if (errcode && err == NULL)
3021 err = got_error_set_errno(errcode,
3022 "pthread_mutex_unlock");
3023 if (err)
3024 break;
3025 } while (*a->searching == TOG_SEARCH_FORWARD && !*a->search_next_done);
3027 return err;
3030 static void
3031 select_commit(struct tog_log_view_state *s)
3033 struct commit_queue_entry *entry;
3034 int ncommits = 0;
3036 entry = s->first_displayed_entry;
3037 while (entry) {
3038 if (ncommits == s->selected) {
3039 s->selected_entry = entry;
3040 break;
3042 entry = TAILQ_NEXT(entry, entry);
3043 ncommits++;
3047 /* lifted from got.c:652 (TODO make lib routine) */
3048 static const struct got_error *
3049 get_author(char **author, struct got_repository *repo,
3050 struct got_worktree *worktree)
3052 const struct got_error *err = NULL;
3053 const char *got_author = NULL, *name, *email;
3054 const struct got_gotconfig *worktree_conf = NULL, *repo_conf = NULL;
3056 *author = NULL;
3058 if (worktree)
3059 worktree_conf = got_worktree_get_gotconfig(worktree);
3060 repo_conf = got_repo_get_gotconfig(repo);
3063 * Priority of potential author information sources, from most
3064 * significant to least significant:
3065 * 1) work tree's .got/got.conf file
3066 * 2) repository's got.conf file
3067 * 3) repository's git config file
3068 * 4) environment variables
3069 * 5) global git config files (in user's home directory or /etc)
3072 if (worktree_conf)
3073 got_author = got_gotconfig_get_author(worktree_conf);
3074 if (got_author == NULL)
3075 got_author = got_gotconfig_get_author(repo_conf);
3076 if (got_author == NULL) {
3077 name = got_repo_get_gitconfig_author_name(repo);
3078 email = got_repo_get_gitconfig_author_email(repo);
3079 if (name && email) {
3080 if (asprintf(author, "%s <%s>", name, email) == -1)
3081 return got_error_from_errno("asprintf");
3082 return NULL;
3085 got_author = getenv("GOT_AUTHOR");
3086 if (got_author == NULL) {
3087 name = got_repo_get_global_gitconfig_author_name(repo);
3088 email = got_repo_get_global_gitconfig_author_email(
3089 repo);
3090 if (name && email) {
3091 if (asprintf(author, "%s <%s>", name, email)
3092 == -1)
3093 return got_error_from_errno("asprintf");
3094 return NULL;
3096 /* TODO: Look up user in password database? */
3097 return got_error(GOT_ERR_COMMIT_NO_AUTHOR);
3101 *author = strdup(got_author);
3102 if (*author == NULL)
3103 err = got_error_from_errno("strdup");
3104 return err;
3107 static const struct got_error *
3108 push_worktree_entry(struct tog_log_thread_args *ta, int wt_entry,
3109 struct got_worktree *worktree)
3111 struct commit_queue_entry *e, *entry;
3112 int rc;
3114 entry = calloc(1, sizeof(*entry));
3115 if (entry == NULL)
3116 return got_error_from_errno("calloc");
3118 entry->idx = 0;
3119 entry->worktree_entry = wt_entry;
3121 rc = pthread_mutex_lock(&tog_mutex);
3122 if (rc != 0) {
3123 free(entry);
3124 return got_error_set_errno(rc, "pthread_mutex_lock");
3127 TAILQ_FOREACH(e, &ta->real_commits->head, entry)
3128 ++e->idx;
3130 TAILQ_INSERT_HEAD(&ta->real_commits->head, entry, entry);
3131 ta->wctx.wt_state |= wt_entry;
3132 ++ta->real_commits->ncommits;
3133 ++tog_base_commit.idx;
3135 rc = pthread_mutex_unlock(&tog_mutex);
3136 if (rc != 0)
3137 return got_error_set_errno(rc, "pthread_mutex_unlock");
3139 return NULL;
3142 static const struct got_error *
3143 check_cancelled(void *arg)
3145 if (tog_sigint_received || tog_sigpipe_received)
3146 return got_error(GOT_ERR_CANCELLED);
3147 return NULL;
3150 static const struct got_error *
3151 check_local_changes(void *arg, unsigned char status,
3152 unsigned char staged_status, const char *path,
3153 struct got_object_id *blob_id, struct got_object_id *staged_blob_id,
3154 struct got_object_id *commit_id, int dirfd, const char *de_name)
3156 int *have_local_changes = arg;
3158 switch (status) {
3159 case GOT_STATUS_ADD:
3160 case GOT_STATUS_DELETE:
3161 case GOT_STATUS_MODIFY:
3162 case GOT_STATUS_CONFLICT:
3163 *have_local_changes |= TOG_WORKTREE_CHANGES_LOCAL;
3164 default:
3165 break;
3168 switch (staged_status) {
3169 case GOT_STATUS_ADD:
3170 case GOT_STATUS_DELETE:
3171 case GOT_STATUS_MODIFY:
3172 *have_local_changes |= TOG_WORKTREE_CHANGES_STAGED;
3173 default:
3174 break;
3177 return NULL;
3180 static const struct got_error *
3181 tog_worktree_status(struct tog_log_thread_args *ta)
3183 const struct got_error *err, *close_err;
3184 struct tog_worktree_ctx *wctx = &ta->wctx;
3185 struct got_worktree *wt = ta->worktree;
3186 struct got_pathlist_head paths;
3187 char *cwd = NULL;
3188 int wt_state = 0;
3190 RB_INIT(&paths);
3192 if (wt == NULL) {
3193 cwd = getcwd(NULL, 0);
3194 if (cwd == NULL)
3195 return got_error_from_errno("getcwd");
3197 err = got_worktree_open(&wt, cwd, NULL);
3198 if (err != NULL) {
3199 if (err->code == GOT_ERR_NOT_WORKTREE) {
3201 * Shouldn't happen; this routine should only
3202 * be called if tog is invoked in a worktree.
3204 wctx->active = 0;
3205 err = NULL;
3206 } else if (err->code == GOT_ERR_WORKTREE_BUSY)
3207 err = NULL; /* retry next redraw */
3208 goto done;
3212 err = got_pathlist_insert(NULL, &paths, "", NULL);
3213 if (err != NULL)
3214 goto done;
3216 err = got_worktree_status(wt, &paths, ta->repo, 0,
3217 check_local_changes, &wt_state, check_cancelled, NULL);
3218 if (err != NULL) {
3219 if (err->code != GOT_ERR_CANCELLED)
3220 goto done;
3221 err = NULL;
3224 if (wt_state != 0) {
3225 err = get_author(&wctx->wt_author, ta->repo, wt);
3226 if (err != NULL) {
3227 if (err->code != GOT_ERR_COMMIT_NO_AUTHOR)
3228 goto done;
3229 if ((wctx->wt_author = strdup("")) == NULL) {
3230 err = got_error_from_errno("strdup");
3231 goto done;
3235 wctx->wt_root = strdup(got_worktree_get_root_path(wt));
3236 if (wctx->wt_root == NULL) {
3237 err = got_error_from_errno("strdup");
3238 goto done;
3241 wctx->wt_ref = strdup(got_worktree_get_head_ref_name(wt));
3242 if (wctx->wt_ref == NULL) {
3243 err = got_error_from_errno("strdup");
3244 goto done;
3249 * Push staged entry first so it's the second log entry
3250 * if there are both staged and unstaged work tree changes.
3252 if (wt_state & TOG_WORKTREE_CHANGES_STAGED &&
3253 (wctx->wt_state & TOG_WORKTREE_CHANGES_STAGED) == 0) {
3254 err = push_worktree_entry(ta, TOG_WORKTREE_CHANGES_STAGED, wt);
3255 if (err != NULL)
3256 goto done;
3258 if (wt_state & TOG_WORKTREE_CHANGES_LOCAL &&
3259 (wctx->wt_state & TOG_WORKTREE_CHANGES_LOCAL) == 0) {
3260 err = push_worktree_entry(ta, TOG_WORKTREE_CHANGES_LOCAL, wt);
3261 if (err != NULL)
3262 goto done;
3265 done:
3266 got_pathlist_free(&paths, GOT_PATHLIST_FREE_NONE);
3267 if (ta->worktree == NULL && wt != NULL) {
3268 close_err = got_worktree_close(wt);
3269 if (close_err != NULL && err == NULL)
3270 err = close_err;
3272 free(cwd);
3273 return err;
3276 static const struct got_error *
3277 worktree_headref_str(char **ret, const char *ref)
3279 if (strncmp(ref, "refs/heads/", 11) == 0)
3280 *ret = strdup(ref + 11);
3281 else
3282 *ret = strdup(ref);
3283 if (*ret == NULL)
3284 return got_error_from_errno("strdup");
3286 return NULL;
3289 static const struct got_error *
3290 fmtindex(char **index, int *ncommits, int wt_state,
3291 struct commit_queue_entry *entry, int limit_view)
3293 int idx = 0;
3295 if (!limit_view) {
3296 if (*ncommits > 0 && wt_state & TOG_WORKTREE_CHANGES_LOCAL)
3297 --(*ncommits);
3298 if (*ncommits > 0 && wt_state & TOG_WORKTREE_CHANGES_STAGED)
3299 --(*ncommits);
3302 if (entry != NULL && entry->worktree_entry == 0) {
3304 * Display 1-based index of selected commit entries only.
3305 * If a work tree entry is selected, show an index of 0.
3307 idx = entry->idx;
3308 if (wt_state == 0 || limit_view)
3309 ++idx;
3310 else if (wt_state > TOG_WORKTREE_CHANGES_STAGED)
3311 --idx;
3313 if (asprintf(index, " [%d/%d] ", idx, *ncommits) == -1) {
3314 *index = NULL;
3315 return got_error_from_errno("asprintf");
3318 return NULL;
3321 static const struct got_error *
3322 fmtheader(char **header, int *ncommits, struct commit_queue_entry *entry,
3323 struct tog_view *view)
3325 const struct got_error *err;
3326 struct tog_log_view_state *s = &view->state.log;
3327 struct tog_worktree_ctx *wctx = &s->thread_args.wctx;
3328 struct got_reflist_head *refs;
3329 char *id_str = NULL, *index = NULL;
3330 char *wthdr = NULL, *ncommits_str = NULL;
3331 char *refs_str = NULL;
3332 int wt_entry;
3334 *header = NULL;
3335 wt_entry = entry != NULL ? entry->worktree_entry : 0;
3337 if (entry && !(view->searching && view->search_next_done == 0)) {
3338 if (entry->worktree_entry == 0) {
3339 err = got_object_id_str(&id_str, entry->id);
3340 if (err != NULL)
3341 return err;
3342 refs = got_reflist_object_id_map_lookup(tog_refs_idmap,
3343 entry->id);
3344 err = build_refs_str(&refs_str, refs,
3345 entry->id, s->repo);
3346 if (err != NULL)
3347 goto done;
3348 } else {
3349 err = worktree_headref_str(&refs_str, wctx->wt_ref);
3350 if (err != NULL)
3351 return err;
3355 err = fmtindex(&index, ncommits, wctx->wt_state, entry, s->limit_view);
3356 if (err != NULL)
3357 goto done;
3359 if (s->thread_args.commits_needed > 0 || s->thread_args.load_all) {
3360 if (asprintf(&ncommits_str, "%s%s", index,
3361 (view->searching && !view->search_next_done) ?
3362 "searching..." : "loading...") == -1) {
3363 err = got_error_from_errno("asprintf");
3364 goto done;
3366 } else {
3367 const char *search_str = NULL;
3368 const char *limit_str = NULL;
3370 if (view->searching) {
3371 if (view->search_next_done == TOG_SEARCH_NO_MORE)
3372 search_str = "no more matches";
3373 else if (view->search_next_done == TOG_SEARCH_HAVE_NONE)
3374 search_str = "no matches found";
3375 else if (!view->search_next_done)
3376 search_str = "searching...";
3379 if (s->limit_view && ncommits == 0)
3380 limit_str = "no matches found";
3382 if (asprintf(&ncommits_str, "%s%s %s", index,
3383 search_str ? search_str : (refs_str ? refs_str : ""),
3384 limit_str ? limit_str : "") == -1) {
3385 err = got_error_from_errno("asprintf");
3386 goto done;
3390 if (wt_entry != 0) {
3391 const char *t = "", *p = TOG_WORKTREE_CHANGES_LOCAL_MSG;
3393 if (wt_entry == TOG_WORKTREE_CHANGES_STAGED) {
3394 p = TOG_WORKTREE_CHANGES_STAGED_MSG;
3395 t = "-s ";
3397 if (asprintf(&wthdr, "%s%s (%s)", t, wctx->wt_root, p) == -1) {
3398 err = got_error_from_errno("asprintf");
3399 goto done;
3403 if (s->in_repo_path != NULL && strcmp(s->in_repo_path, "/") != 0) {
3404 if (asprintf(header, "%s%s %s%s",
3405 wt_entry == 0 ? "commit " : "diff ",
3406 wt_entry == 0 ? id_str ? id_str :
3407 "........................................" :
3408 wthdr != NULL ? wthdr : "", s->in_repo_path,
3409 ncommits_str) == -1)
3410 err = got_error_from_errno("asprintf");
3411 } else if (asprintf(header, "%s%s%s",
3412 wt_entry == 0 ? "commit " : "diff ",
3413 wt_entry == 0 ? id_str ? id_str :
3414 "........................................" :
3415 wthdr != NULL ? wthdr : "", ncommits_str) == -1)
3416 err = got_error_from_errno("asprintf");
3417 if (err != NULL)
3418 *header = NULL;
3420 done:
3421 free(wthdr);
3422 free(index);
3423 free(id_str);
3424 free(refs_str);
3425 free(ncommits_str);
3426 return err;
3429 static const struct got_error *
3430 draw_commits(struct tog_view *view)
3432 const struct got_error *err;
3433 struct tog_log_view_state *s = &view->state.log;
3434 struct commit_queue_entry *entry = s->selected_entry;
3435 int width, limit = view->nlines;
3436 int ncommits = s->commits->ncommits, author_cols = 4, refstr_cols;
3437 char *header;
3438 wchar_t *wline;
3439 static const size_t date_display_cols = 12;
3441 if (view_is_hsplit_top(view))
3442 --limit; /* account for border */
3444 if (s->thread_args.commits_needed == 0 &&
3445 s->thread_args.need_wt_status == 0 &&
3446 s->thread_args.need_commit_marker == 0 && !using_mock_io)
3447 halfdelay(10); /* disable fast refresh */
3449 err = fmtheader(&header, &ncommits, entry, view);
3450 if (err != NULL)
3451 return err;
3453 err = format_line(&wline, &width, NULL, header, 0, view->ncols, 0, 0);
3454 free(header);
3455 if (err)
3456 return err;
3458 werase(view->window);
3460 if (view_needs_focus_indication(view))
3461 wstandout(view->window);
3462 tog_waddwstr(view, wline, width, NULL, TOG_COLOR_COMMIT, 1);
3463 if (view_needs_focus_indication(view))
3464 wstandend(view->window);
3465 free(wline);
3466 if (limit <= 1)
3467 return NULL;
3469 /* Grow author column size if necessary, and set view->maxx. */
3470 entry = s->first_displayed_entry;
3471 ncommits = 0;
3472 view->maxx = 0;
3473 while (entry) {
3474 struct got_reflist_head *refs;
3475 struct got_commit_object *c = entry->commit;
3476 char *author, *eol, *msg, *msg0, *refs_str;
3477 wchar_t *wauthor, *wmsg;
3478 int width;
3480 if (ncommits >= limit - 1)
3481 break;
3482 if (entry->worktree_entry != 0)
3483 author = strdup(s->thread_args.wctx.wt_author);
3484 else if (s->use_committer)
3485 author = strdup(got_object_commit_get_committer(c));
3486 else
3487 author = strdup(got_object_commit_get_author(c));
3488 if (author == NULL)
3489 return got_error_from_errno("strdup");
3491 err = format_author(&wauthor, &width, author, COLS,
3492 date_display_cols);
3493 if (author_cols < width)
3494 author_cols = width;
3495 free(wauthor);
3496 free(author);
3497 if (err)
3498 return err;
3499 if (entry->worktree_entry != 0) {
3500 if (entry->worktree_entry == TOG_WORKTREE_CHANGES_LOCAL)
3501 width = sizeof(TOG_WORKTREE_CHANGES_LOCAL_MSG);
3502 else
3503 width = sizeof(TOG_WORKTREE_CHANGES_STAGED_MSG);
3504 view->maxx = MAX(view->maxx, width - 1);
3505 entry = TAILQ_NEXT(entry, entry);
3506 ++ncommits;
3507 continue;
3509 refs = got_reflist_object_id_map_lookup(tog_refs_idmap,
3510 entry->id);
3511 err = build_refs_str(&refs_str, refs, entry->id, s->repo);
3512 if (err)
3513 return err;
3514 if (refs_str) {
3515 wchar_t *ws;
3517 err = format_line(&ws, &width, NULL, refs_str,
3518 0, INT_MAX, date_display_cols + author_cols, 0);
3519 free(ws);
3520 free(refs_str);
3521 refs_str = NULL;
3522 if (err)
3523 return err;
3524 refstr_cols = width + 3; /* account for [ ] + space */
3525 } else
3526 refstr_cols = 0;
3527 err = got_object_commit_get_logmsg(&msg0, c);
3528 if (err)
3529 return err;
3530 msg = msg0;
3531 while (*msg == '\n')
3532 ++msg;
3533 if ((eol = strchr(msg, '\n')))
3534 *eol = '\0';
3535 err = format_line(&wmsg, &width, NULL, msg, 0, INT_MAX,
3536 date_display_cols + author_cols + refstr_cols, 0);
3537 free(msg0);
3538 free(wmsg);
3539 if (err)
3540 return err;
3541 view->maxx = MAX(view->maxx, width + refstr_cols);
3542 ncommits++;
3543 entry = TAILQ_NEXT(entry, entry);
3546 entry = s->first_displayed_entry;
3547 s->last_displayed_entry = s->first_displayed_entry;
3548 ncommits = 0;
3549 while (entry) {
3550 if (ncommits >= limit - 1)
3551 break;
3552 if (ncommits == s->selected)
3553 wstandout(view->window);
3554 if (entry->worktree_entry == 0)
3555 err = draw_commit(view, entry,
3556 date_display_cols, author_cols);
3557 else
3558 err = draw_worktree_entry(view, entry->worktree_entry,
3559 date_display_cols, author_cols);
3560 if (ncommits == s->selected)
3561 wstandend(view->window);
3562 if (err)
3563 return err;
3564 ncommits++;
3565 s->last_displayed_entry = entry;
3566 entry = TAILQ_NEXT(entry, entry);
3569 view_border(view);
3570 return NULL;
3573 static void
3574 log_scroll_up(struct tog_log_view_state *s, int maxscroll)
3576 struct commit_queue_entry *entry;
3577 int nscrolled = 0;
3579 entry = TAILQ_FIRST(&s->commits->head);
3580 if (s->first_displayed_entry == entry)
3581 return;
3583 entry = s->first_displayed_entry;
3584 while (entry && nscrolled < maxscroll) {
3585 entry = TAILQ_PREV(entry, commit_queue_head, entry);
3586 if (entry) {
3587 s->first_displayed_entry = entry;
3588 nscrolled++;
3593 static const struct got_error *
3594 trigger_log_thread(struct tog_view *view, int wait)
3596 const struct got_error *err;
3597 struct tog_log_thread_args *ta = &view->state.log.thread_args;
3598 int errcode;
3600 if (!using_mock_io)
3601 halfdelay(1); /* fast refresh while loading commits */
3603 while (!ta->log_complete && !tog_thread_error &&
3604 (ta->commits_needed > 0 || ta->load_all)) {
3605 /* Wake the log thread. */
3606 errcode = pthread_cond_signal(&ta->need_commits);
3607 if (errcode)
3608 return got_error_set_errno(errcode,
3609 "pthread_cond_signal");
3612 * The mutex will be released while the view loop waits
3613 * in wgetch(), at which time the log thread will run.
3615 if (!wait)
3616 break;
3618 /* Display progress update in log view. */
3619 err = show_log_view(view);
3620 if (err != NULL)
3621 return err;
3622 update_panels();
3623 doupdate();
3625 /* Wait right here while next commit is being loaded. */
3626 errcode = pthread_cond_wait(&ta->commit_loaded, &tog_mutex);
3627 if (errcode)
3628 return got_error_set_errno(errcode,
3629 "pthread_cond_wait");
3631 /* Display progress update in log view. */
3632 err = show_log_view(view);
3633 if (err != NULL)
3634 return err;
3635 update_panels();
3636 doupdate();
3639 return NULL;
3642 static const struct got_error *
3643 request_log_commits(struct tog_view *view)
3645 struct tog_log_view_state *state = &view->state.log;
3646 const struct got_error *err = NULL;
3648 if (state->thread_args.log_complete)
3649 return NULL;
3651 state->thread_args.commits_needed += view->nscrolled;
3652 err = trigger_log_thread(view, 1);
3653 view->nscrolled = 0;
3655 return err;
3658 static const struct got_error *
3659 log_scroll_down(struct tog_view *view, int maxscroll)
3661 struct tog_log_view_state *s = &view->state.log;
3662 const struct got_error *err = NULL;
3663 struct commit_queue_entry *pentry;
3664 int nscrolled = 0, ncommits_needed;
3666 if (s->last_displayed_entry == NULL)
3667 return NULL;
3669 ncommits_needed = s->last_displayed_entry->idx + 2 + maxscroll;
3670 if (s->commits->ncommits < ncommits_needed &&
3671 !s->thread_args.log_complete) {
3673 * Ask the log thread for required amount of commits.
3675 s->thread_args.commits_needed +=
3676 ncommits_needed - s->commits->ncommits;
3677 err = trigger_log_thread(view, 1);
3678 if (err)
3679 return err;
3682 do {
3683 pentry = TAILQ_NEXT(s->last_displayed_entry, entry);
3684 if (pentry == NULL && view->mode != TOG_VIEW_SPLIT_HRZN)
3685 break;
3687 s->last_displayed_entry = pentry ?
3688 pentry : s->last_displayed_entry;
3690 pentry = TAILQ_NEXT(s->first_displayed_entry, entry);
3691 if (pentry == NULL)
3692 break;
3693 s->first_displayed_entry = pentry;
3694 } while (++nscrolled < maxscroll);
3696 if (view->mode == TOG_VIEW_SPLIT_HRZN && !s->thread_args.log_complete)
3697 view->nscrolled += nscrolled;
3698 else
3699 view->nscrolled = 0;
3701 return err;
3704 static const struct got_error *
3705 open_diff_view_for_commit(struct tog_view **new_view, int begin_y, int begin_x,
3706 struct commit_queue_entry *entry, struct tog_view *log_view,
3707 struct got_repository *repo)
3709 const struct got_error *err;
3710 struct got_object_qid *p;
3711 struct got_object_id *parent_id;
3712 struct tog_view *diff_view;
3713 struct tog_log_view_state *ls = NULL;
3714 const char *worktree_root = NULL;
3716 diff_view = view_open(0, 0, begin_y, begin_x, TOG_VIEW_DIFF);
3717 if (diff_view == NULL)
3718 return got_error_from_errno("view_open");
3720 if (log_view != NULL) {
3721 ls = &log_view->state.log;
3722 worktree_root = ls->thread_args.wctx.wt_root;
3725 if (ls != NULL && ls->marked_entry != NULL &&
3726 ls->marked_entry != ls->selected_entry)
3727 parent_id = ls->marked_entry->id;
3728 else if (entry->worktree_entry == 0 &&
3729 (p = STAILQ_FIRST(got_object_commit_get_parent_ids(entry->commit))))
3730 parent_id = &p->id;
3731 else
3732 parent_id = NULL;
3734 err = open_diff_view(diff_view, parent_id, entry->id, NULL, NULL, 3, 0,
3735 0, 0, entry->worktree_entry, worktree_root, log_view, repo, NULL);
3736 if (err == NULL)
3737 *new_view = diff_view;
3738 return err;
3741 static const struct got_error *
3742 tree_view_visit_subtree(struct tog_tree_view_state *s,
3743 struct got_tree_object *subtree)
3745 struct tog_parent_tree *parent;
3747 parent = calloc(1, sizeof(*parent));
3748 if (parent == NULL)
3749 return got_error_from_errno("calloc");
3751 parent->tree = s->tree;
3752 parent->first_displayed_entry = s->first_displayed_entry;
3753 parent->selected_entry = s->selected_entry;
3754 parent->selected = s->selected;
3755 TAILQ_INSERT_HEAD(&s->parents, parent, entry);
3756 s->tree = subtree;
3757 s->selected = 0;
3758 s->first_displayed_entry = NULL;
3759 return NULL;
3762 static const struct got_error *
3763 tree_view_walk_path(struct tog_tree_view_state *s,
3764 struct got_commit_object *commit, const char *path)
3766 const struct got_error *err = NULL;
3767 struct got_tree_object *tree = NULL;
3768 const char *p;
3769 char *slash, *subpath = NULL;
3771 /* Walk the path and open corresponding tree objects. */
3772 p = path;
3773 while (*p) {
3774 struct got_tree_entry *te;
3775 struct got_object_id *tree_id;
3776 char *te_name;
3778 while (p[0] == '/')
3779 p++;
3781 /* Ensure the correct subtree entry is selected. */
3782 slash = strchr(p, '/');
3783 if (slash == NULL)
3784 te_name = strdup(p);
3785 else
3786 te_name = strndup(p, slash - p);
3787 if (te_name == NULL) {
3788 err = got_error_from_errno("strndup");
3789 break;
3791 te = got_object_tree_find_entry(s->tree, te_name);
3792 if (te == NULL) {
3793 err = got_error_path(te_name, GOT_ERR_NO_TREE_ENTRY);
3794 free(te_name);
3795 break;
3797 free(te_name);
3798 s->first_displayed_entry = s->selected_entry = te;
3800 if (!S_ISDIR(got_tree_entry_get_mode(s->selected_entry)))
3801 break; /* jump to this file's entry */
3803 slash = strchr(p, '/');
3804 if (slash)
3805 subpath = strndup(path, slash - path);
3806 else
3807 subpath = strdup(path);
3808 if (subpath == NULL) {
3809 err = got_error_from_errno("strdup");
3810 break;
3813 err = got_object_id_by_path(&tree_id, s->repo, commit,
3814 subpath);
3815 if (err)
3816 break;
3818 err = got_object_open_as_tree(&tree, s->repo, tree_id);
3819 free(tree_id);
3820 if (err)
3821 break;
3823 err = tree_view_visit_subtree(s, tree);
3824 if (err) {
3825 got_object_tree_close(tree);
3826 break;
3828 if (slash == NULL)
3829 break;
3830 free(subpath);
3831 subpath = NULL;
3832 p = slash;
3835 free(subpath);
3836 return err;
3839 static const struct got_error *
3840 browse_commit_tree(struct tog_view **new_view, int begin_y, int begin_x,
3841 struct commit_queue_entry *entry, const char *path,
3842 const char *head_ref_name, struct got_repository *repo)
3844 const struct got_error *err = NULL;
3845 struct tog_tree_view_state *s;
3846 struct tog_view *tree_view;
3847 struct got_commit_object *commit = NULL;
3848 struct got_object_id *commit_id;
3850 *new_view = NULL;
3852 if (entry->id != NULL)
3853 commit_id = entry->id;
3854 else if (entry->worktree_entry)
3855 commit_id = tog_base_commit.id;
3856 else /* cannot happen */
3857 return got_error(GOT_ERR_NOT_WORKTREE);
3859 tree_view = view_open(0, 0, begin_y, begin_x, TOG_VIEW_TREE);
3860 if (tree_view == NULL)
3861 return got_error_from_errno("view_open");
3863 err = open_tree_view(tree_view, commit_id, head_ref_name, repo);
3864 if (err)
3865 return err;
3866 s = &tree_view->state.tree;
3868 *new_view = tree_view;
3870 if (got_path_is_root_dir(path))
3871 return NULL;
3873 if (entry->worktree_entry) {
3874 err = got_object_open_as_commit(&commit, repo, commit_id);
3875 if (err != NULL)
3876 goto done;
3879 err = tree_view_walk_path(s, commit ? commit : entry->commit, path);
3881 done:
3882 if (commit != NULL)
3883 got_object_commit_close(commit);
3884 if (err != NULL) {
3885 view_close(tree_view);
3886 *new_view = NULL;
3888 return err;
3892 * If work tree entries have been pushed onto the commit queue and the
3893 * first commit entry is still displayed, scroll the view so the new
3894 * work tree entries are visible. If the selection cursor is still on
3895 * the first commit entry, keep the cursor in place such that the first
3896 * work tree entry is selected, otherwise move the selection cursor so
3897 * the currently selected commit stays selected if it remains on screen.
3899 static void
3900 worktree_entries_reveal(struct tog_log_thread_args *a)
3902 struct commit_queue_entry **first = a->first_displayed_entry;
3903 struct commit_queue_entry **select = a->selected_entry;
3904 int *cursor = a->selected;
3905 int wts = a->wctx.wt_state;
3907 #define select_worktree_entry(_first, _selected) do { \
3908 *_first = TAILQ_FIRST(&a->real_commits->head); \
3909 *_selected = *_first; \
3910 } while (0)
3912 if (first == NULL)
3913 select_worktree_entry(first, select);
3914 else if (*select == *first) {
3915 if (wts == TOG_WORKTREE_CHANGES_LOCAL && (*first)->idx == 1)
3916 select_worktree_entry(first, select);
3917 else if (wts == TOG_WORKTREE_CHANGES_STAGED &&
3918 (*first)->idx == 1)
3919 select_worktree_entry(first, select);
3920 else if (wts & TOG_WORKTREE_CHANGES_ALL && (*first)->idx == 2)
3921 select_worktree_entry(first, select);
3922 } else if (wts & TOG_WORKTREE_CHANGES_ALL && (*first)->idx == 2) {
3923 *first = TAILQ_FIRST(&a->real_commits->head);
3924 if (*cursor + 2 < *a->view_nlines - 1)
3925 (*cursor) += 2;
3926 else if (*cursor + 1 < *a->view_nlines - 1) {
3927 *select = TAILQ_PREV(*select, commit_queue_head, entry);
3928 ++(*cursor);
3929 } else {
3930 *select = TAILQ_PREV(*select, commit_queue_head, entry);
3931 *select = TAILQ_PREV(*select, commit_queue_head, entry);
3933 } else if (wts != 0 && (*first)->idx == 1) {
3934 *first = TAILQ_FIRST(&a->real_commits->head);
3935 if (*cursor + 1 < *a->view_nlines - 1)
3936 ++(*cursor);
3937 else
3938 *select = TAILQ_PREV(*select, commit_queue_head, entry);
3940 #undef select_worktree_entry
3943 static const struct got_error *
3944 block_signals_used_by_main_thread(void)
3946 sigset_t sigset;
3947 int errcode;
3949 if (sigemptyset(&sigset) == -1)
3950 return got_error_from_errno("sigemptyset");
3952 /* tog handles SIGWINCH, SIGCONT, SIGINT, SIGTERM */
3953 if (sigaddset(&sigset, SIGWINCH) == -1)
3954 return got_error_from_errno("sigaddset");
3955 if (sigaddset(&sigset, SIGCONT) == -1)
3956 return got_error_from_errno("sigaddset");
3957 if (sigaddset(&sigset, SIGINT) == -1)
3958 return got_error_from_errno("sigaddset");
3959 if (sigaddset(&sigset, SIGTERM) == -1)
3960 return got_error_from_errno("sigaddset");
3962 /* ncurses handles SIGTSTP */
3963 if (sigaddset(&sigset, SIGTSTP) == -1)
3964 return got_error_from_errno("sigaddset");
3966 errcode = pthread_sigmask(SIG_BLOCK, &sigset, NULL);
3967 if (errcode)
3968 return got_error_set_errno(errcode, "pthread_sigmask");
3970 return NULL;
3973 static void *
3974 log_thread(void *arg)
3976 const struct got_error *err = NULL;
3977 int errcode = 0;
3978 struct tog_log_thread_args *a = arg;
3979 int done = 0;
3982 * Sync startup with main thread such that we begin our
3983 * work once view_input() has released the mutex.
3985 errcode = pthread_mutex_lock(&tog_mutex);
3986 if (errcode) {
3987 err = got_error_set_errno(errcode, "pthread_mutex_lock");
3988 return (void *)err;
3991 err = block_signals_used_by_main_thread();
3992 if (err) {
3993 pthread_mutex_unlock(&tog_mutex);
3994 goto done;
3997 while (!done && !err && !tog_fatal_signal_received()) {
3998 errcode = pthread_mutex_unlock(&tog_mutex);
3999 if (errcode) {
4000 err = got_error_set_errno(errcode,
4001 "pthread_mutex_unlock");
4002 goto done;
4004 err = queue_commits(a);
4005 if (err) {
4006 if (err->code != GOT_ERR_ITER_COMPLETED)
4007 goto done;
4008 err = NULL;
4009 done = 1;
4010 a->commits_needed = 0;
4011 } else if (a->commits_needed > 0 && !a->load_all) {
4012 if (*a->limiting) {
4013 if (a->limit_match)
4014 a->commits_needed--;
4015 } else
4016 a->commits_needed--;
4019 errcode = pthread_mutex_lock(&tog_mutex);
4020 if (errcode) {
4021 err = got_error_set_errno(errcode,
4022 "pthread_mutex_lock");
4023 goto done;
4024 } else if (*a->quit)
4025 done = 1;
4026 else if (*a->limiting && *a->first_displayed_entry == NULL) {
4027 *a->first_displayed_entry =
4028 TAILQ_FIRST(&a->limit_commits->head);
4029 *a->selected_entry = *a->first_displayed_entry;
4030 } else if (*a->first_displayed_entry == NULL) {
4031 *a->first_displayed_entry =
4032 TAILQ_FIRST(&a->real_commits->head);
4033 *a->selected_entry = *a->first_displayed_entry;
4035 if (done)
4036 a->log_complete = 1;
4038 errcode = pthread_cond_signal(&a->commit_loaded);
4039 if (errcode) {
4040 err = got_error_set_errno(errcode,
4041 "pthread_cond_signal");
4042 pthread_mutex_unlock(&tog_mutex);
4043 goto done;
4046 if (a->commits_needed == 0 && a->need_wt_status) {
4047 errcode = pthread_mutex_unlock(&tog_mutex);
4048 if (errcode) {
4049 err = got_error_set_errno(errcode,
4050 "pthread_mutex_unlock");
4051 goto done;
4053 err = tog_worktree_status(a);
4054 if (err != NULL)
4055 goto done;
4056 errcode = pthread_mutex_lock(&tog_mutex);
4057 if (errcode) {
4058 err = got_error_set_errno(errcode,
4059 "pthread_mutex_lock");
4060 goto done;
4062 if (a->wctx.wt_state != 0)
4063 worktree_entries_reveal(a);
4064 a->need_wt_status = 0;
4067 if (a->commits_needed == 0 &&
4068 a->need_commit_marker && a->worktree) {
4069 errcode = pthread_mutex_unlock(&tog_mutex);
4070 if (errcode) {
4071 err = got_error_set_errno(errcode,
4072 "pthread_mutex_unlock");
4073 goto done;
4075 err = got_worktree_get_state(&tog_base_commit.marker,
4076 a->repo, a->worktree, NULL, NULL);
4077 if (err)
4078 goto done;
4079 errcode = pthread_mutex_lock(&tog_mutex);
4080 if (errcode) {
4081 err = got_error_set_errno(errcode,
4082 "pthread_mutex_lock");
4083 goto done;
4085 a->need_commit_marker = 0;
4087 * The main thread did not close this
4088 * work tree yet. Close it now.
4090 got_worktree_close(a->worktree);
4091 a->worktree = NULL;
4093 if (*a->quit)
4094 done = 1;
4097 if (done)
4098 a->commits_needed = 0;
4099 else {
4100 if (a->commits_needed == 0 && !a->load_all) {
4101 if (tog_io.wait_for_ui) {
4102 errcode = pthread_cond_signal(
4103 &a->log_loaded);
4104 if (errcode) {
4105 err = got_error_set_errno(
4106 errcode,
4107 "pthread_cond_signal");
4108 pthread_mutex_unlock(
4109 &tog_mutex);
4110 goto done;
4114 errcode = pthread_cond_wait(&a->need_commits,
4115 &tog_mutex);
4116 if (errcode) {
4117 err = got_error_set_errno(errcode,
4118 "pthread_cond_wait");
4119 pthread_mutex_unlock(&tog_mutex);
4120 goto done;
4122 if (*a->quit)
4123 done = 1;
4127 a->log_complete = 1;
4128 if (tog_io.wait_for_ui) {
4129 errcode = pthread_cond_signal(&a->log_loaded);
4130 if (errcode && err == NULL)
4131 err = got_error_set_errno(errcode,
4132 "pthread_cond_signal");
4135 errcode = pthread_mutex_unlock(&tog_mutex);
4136 if (errcode)
4137 err = got_error_set_errno(errcode, "pthread_mutex_unlock");
4138 done:
4139 if (err) {
4140 tog_thread_error = 1;
4141 pthread_cond_signal(&a->commit_loaded);
4142 if (a->worktree) {
4143 got_worktree_close(a->worktree);
4144 a->worktree = NULL;
4147 return (void *)err;
4150 static const struct got_error *
4151 stop_log_thread(struct tog_log_view_state *s)
4153 const struct got_error *err = NULL, *thread_err = NULL;
4154 int errcode;
4156 if (s->thread) {
4157 s->quit = 1;
4158 errcode = pthread_cond_signal(&s->thread_args.need_commits);
4159 if (errcode)
4160 return got_error_set_errno(errcode,
4161 "pthread_cond_signal");
4162 errcode = pthread_mutex_unlock(&tog_mutex);
4163 if (errcode)
4164 return got_error_set_errno(errcode,
4165 "pthread_mutex_unlock");
4166 errcode = pthread_join(s->thread, (void **)&thread_err);
4167 if (errcode)
4168 return got_error_set_errno(errcode, "pthread_join");
4169 errcode = pthread_mutex_lock(&tog_mutex);
4170 if (errcode)
4171 return got_error_set_errno(errcode,
4172 "pthread_mutex_lock");
4173 s->thread = 0; //NULL;
4176 if (s->thread_args.repo) {
4177 err = got_repo_close(s->thread_args.repo);
4178 s->thread_args.repo = NULL;
4181 if (s->thread_args.pack_fds) {
4182 const struct got_error *pack_err =
4183 got_repo_pack_fds_close(s->thread_args.pack_fds);
4184 if (err == NULL)
4185 err = pack_err;
4186 s->thread_args.pack_fds = NULL;
4189 if (s->thread_args.graph) {
4190 got_commit_graph_close(s->thread_args.graph);
4191 s->thread_args.graph = NULL;
4194 return err ? err : thread_err;
4197 static void
4198 worktree_ctx_close(struct tog_log_thread_args *ta)
4200 struct tog_worktree_ctx *wctx = &ta->wctx;
4202 if (wctx->active) {
4203 free(wctx->wt_author);
4204 wctx->wt_author = NULL;
4205 free(wctx->wt_root);
4206 wctx->wt_root = NULL;
4207 free(wctx->wt_ref);
4208 wctx->wt_ref = NULL;
4209 wctx->wt_state = 0;
4210 ta->need_wt_status = 1;
4214 static const struct got_error *
4215 close_log_view(struct tog_view *view)
4217 const struct got_error *err = NULL;
4218 struct tog_log_view_state *s = &view->state.log;
4219 int errcode;
4221 log_mark_clear(s);
4223 err = stop_log_thread(s);
4225 errcode = pthread_cond_destroy(&s->thread_args.need_commits);
4226 if (errcode && err == NULL)
4227 err = got_error_set_errno(errcode, "pthread_cond_destroy");
4229 errcode = pthread_cond_destroy(&s->thread_args.commit_loaded);
4230 if (errcode && err == NULL)
4231 err = got_error_set_errno(errcode, "pthread_cond_destroy");
4233 if (using_mock_io) {
4234 errcode = pthread_cond_destroy(&s->thread_args.log_loaded);
4235 if (errcode && err == NULL)
4236 err = got_error_set_errno(errcode,
4237 "pthread_cond_destroy");
4240 free_commits(&s->limit_commits);
4241 free_commits(&s->real_commits);
4242 free_colors(&s->colors);
4243 free(s->in_repo_path);
4244 s->in_repo_path = NULL;
4245 free(s->start_id);
4246 s->start_id = NULL;
4247 free(s->head_ref_name);
4248 s->head_ref_name = NULL;
4249 worktree_ctx_close(&s->thread_args);
4250 return err;
4254 * We use two queues to implement the limit feature: first consists of
4255 * commits matching the current limit_regex; second is the real queue
4256 * of all known commits (real_commits). When the user starts limiting,
4257 * we swap queues such that all movement and displaying functionality
4258 * works with very slight change.
4260 static const struct got_error *
4261 limit_log_view(struct tog_view *view)
4263 struct tog_log_view_state *s = &view->state.log;
4264 struct commit_queue_entry *entry;
4265 struct tog_view *v = view;
4266 const struct got_error *err = NULL;
4267 char pattern[1024];
4268 int ret;
4270 if (view_is_hsplit_top(view))
4271 v = view->child;
4272 else if (view->mode == TOG_VIEW_SPLIT_VERT && view->parent)
4273 v = view->parent;
4275 if (tog_io.input_str != NULL) {
4276 if (strlcpy(pattern, tog_io.input_str, sizeof(pattern)) >=
4277 sizeof(pattern))
4278 return got_error(GOT_ERR_NO_SPACE);
4279 } else {
4280 wmove(v->window, v->nlines - 1, 0);
4281 wclrtoeol(v->window);
4282 mvwaddstr(v->window, v->nlines - 1, 0, "&/");
4283 nodelay(v->window, FALSE);
4284 nocbreak();
4285 echo();
4286 ret = wgetnstr(v->window, pattern, sizeof(pattern));
4287 cbreak();
4288 noecho();
4289 nodelay(v->window, TRUE);
4290 if (ret == ERR)
4291 return NULL;
4294 if (*pattern == '\0') {
4296 * Safety measure for the situation where the user
4297 * resets limit without previously limiting anything.
4299 if (!s->limit_view)
4300 return NULL;
4303 * User could have pressed Ctrl+L, which refreshed the
4304 * commit queues, it means we can't save previously
4305 * (before limit took place) displayed entries,
4306 * because they would point to already free'ed memory,
4307 * so we are forced to always select first entry of
4308 * the queue.
4310 s->commits = &s->real_commits;
4311 s->first_displayed_entry = TAILQ_FIRST(&s->real_commits.head);
4312 s->selected_entry = s->first_displayed_entry;
4313 s->selected = 0;
4314 s->limit_view = 0;
4316 return NULL;
4319 if (regcomp(&s->limit_regex, pattern, REG_EXTENDED | REG_NEWLINE))
4320 return NULL;
4322 s->limit_view = 1;
4324 /* Clear the screen while loading limit view */
4325 s->first_displayed_entry = NULL;
4326 s->last_displayed_entry = NULL;
4327 s->selected_entry = NULL;
4328 s->commits = &s->limit_commits;
4330 /* Prepare limit queue for new search */
4331 free_commits(&s->limit_commits);
4332 s->limit_commits.ncommits = 0;
4334 /* First process commits, which are in queue already */
4335 TAILQ_FOREACH(entry, &s->real_commits.head, entry) {
4336 int have_match = 0;
4338 if (entry->worktree_entry == 0) {
4339 err = match_commit(&have_match, entry->id,
4340 entry->commit, &s->limit_regex);
4341 if (err)
4342 return err;
4345 if (have_match) {
4346 struct commit_queue_entry *matched;
4348 matched = alloc_commit_queue_entry(entry->commit,
4349 entry->id);
4350 if (matched == NULL) {
4351 err = got_error_from_errno(
4352 "alloc_commit_queue_entry");
4353 break;
4355 matched->commit = entry->commit;
4356 got_object_commit_retain(entry->commit);
4358 matched->idx = s->limit_commits.ncommits;
4359 TAILQ_INSERT_TAIL(&s->limit_commits.head,
4360 matched, entry);
4361 s->limit_commits.ncommits++;
4365 /* Second process all the commits, until we fill the screen */
4366 if (s->limit_commits.ncommits < view->nlines - 1 &&
4367 !s->thread_args.log_complete) {
4368 s->thread_args.commits_needed +=
4369 view->nlines - s->limit_commits.ncommits - 1;
4370 err = trigger_log_thread(view, 1);
4371 if (err)
4372 return err;
4375 s->first_displayed_entry = TAILQ_FIRST(&s->commits->head);
4376 s->selected_entry = TAILQ_FIRST(&s->commits->head);
4377 s->selected = 0;
4379 return NULL;
4382 static const struct got_error *
4383 search_start_log_view(struct tog_view *view)
4385 struct tog_log_view_state *s = &view->state.log;
4387 s->matched_entry = NULL;
4388 s->search_entry = NULL;
4389 return NULL;
4392 static const struct got_error *
4393 search_next_log_view(struct tog_view *view)
4395 const struct got_error *err = NULL;
4396 struct tog_log_view_state *s = &view->state.log;
4397 struct commit_queue_entry *entry;
4399 /* Display progress update in log view. */
4400 err = show_log_view(view);
4401 if (err != NULL)
4402 return err;
4403 update_panels();
4404 doupdate();
4406 if (s->search_entry) {
4407 if (!using_mock_io) {
4408 int errcode, ch;
4410 errcode = pthread_mutex_unlock(&tog_mutex);
4411 if (errcode)
4412 return got_error_set_errno(errcode,
4413 "pthread_mutex_unlock");
4414 ch = wgetch(view->window);
4415 errcode = pthread_mutex_lock(&tog_mutex);
4416 if (errcode)
4417 return got_error_set_errno(errcode,
4418 "pthread_mutex_lock");
4419 if (ch == CTRL('g') || ch == KEY_BACKSPACE) {
4420 view->search_next_done = TOG_SEARCH_HAVE_MORE;
4421 return NULL;
4424 if (view->searching == TOG_SEARCH_FORWARD)
4425 entry = TAILQ_NEXT(s->search_entry, entry);
4426 else
4427 entry = TAILQ_PREV(s->search_entry,
4428 commit_queue_head, entry);
4429 } else if (s->matched_entry) {
4431 * If the user has moved the cursor after we hit a match,
4432 * the position from where we should continue searching
4433 * might have changed.
4435 if (view->searching == TOG_SEARCH_FORWARD)
4436 entry = TAILQ_NEXT(s->selected_entry, entry);
4437 else
4438 entry = TAILQ_PREV(s->selected_entry, commit_queue_head,
4439 entry);
4440 } else {
4441 entry = s->selected_entry;
4444 while (1) {
4445 int have_match = 0;
4447 if (entry == NULL) {
4448 if (s->thread_args.log_complete ||
4449 view->searching == TOG_SEARCH_BACKWARD) {
4450 view->search_next_done =
4451 (s->matched_entry == NULL ?
4452 TOG_SEARCH_HAVE_NONE : TOG_SEARCH_NO_MORE);
4453 s->search_entry = NULL;
4454 return NULL;
4457 * Poke the log thread for more commits and return,
4458 * allowing the main loop to make progress. Search
4459 * will resume at s->search_entry once we come back.
4461 s->search_entry = s->selected_entry;
4462 s->thread_args.commits_needed++;
4463 return trigger_log_thread(view, 0);
4466 if (entry->worktree_entry == 0) {
4467 err = match_commit(&have_match, entry->id,
4468 entry->commit, &view->regex);
4469 if (err)
4470 break;
4471 if (have_match) {
4472 view->search_next_done = TOG_SEARCH_HAVE_MORE;
4473 s->matched_entry = entry;
4474 break;
4478 s->search_entry = entry;
4479 if (view->searching == TOG_SEARCH_FORWARD)
4480 entry = TAILQ_NEXT(entry, entry);
4481 else
4482 entry = TAILQ_PREV(entry, commit_queue_head, entry);
4485 if (s->matched_entry) {
4486 int cur = s->selected_entry->idx;
4487 while (cur < s->matched_entry->idx) {
4488 err = input_log_view(NULL, view, KEY_DOWN);
4489 if (err)
4490 return err;
4491 cur++;
4493 while (cur > s->matched_entry->idx) {
4494 err = input_log_view(NULL, view, KEY_UP);
4495 if (err)
4496 return err;
4497 cur--;
4501 s->search_entry = NULL;
4503 return NULL;
4506 static const struct got_error *
4507 open_log_view(struct tog_view *view, struct got_object_id *start_id,
4508 struct got_repository *repo, const char *head_ref_name,
4509 const char *in_repo_path, int log_branches,
4510 struct got_worktree *worktree)
4512 const struct got_error *err = NULL;
4513 struct tog_log_view_state *s = &view->state.log;
4514 struct got_repository *thread_repo = NULL;
4515 struct got_commit_graph *thread_graph = NULL;
4516 int errcode;
4518 if (in_repo_path != s->in_repo_path) {
4519 free(s->in_repo_path);
4520 s->in_repo_path = strdup(in_repo_path);
4521 if (s->in_repo_path == NULL) {
4522 err = got_error_from_errno("strdup");
4523 goto done;
4527 /* The commit queue only contains commits being displayed. */
4528 TAILQ_INIT(&s->real_commits.head);
4529 s->real_commits.ncommits = 0;
4530 s->commits = &s->real_commits;
4532 TAILQ_INIT(&s->limit_commits.head);
4533 s->limit_view = 0;
4534 s->limit_commits.ncommits = 0;
4536 s->repo = repo;
4537 if (head_ref_name) {
4538 s->head_ref_name = strdup(head_ref_name);
4539 if (s->head_ref_name == NULL) {
4540 err = got_error_from_errno("strdup");
4541 goto done;
4544 s->start_id = got_object_id_dup(start_id);
4545 if (s->start_id == NULL) {
4546 err = got_error_from_errno("got_object_id_dup");
4547 goto done;
4549 s->log_branches = log_branches;
4550 s->use_committer = 1;
4552 STAILQ_INIT(&s->colors);
4553 if (has_colors() && getenv("TOG_COLORS") != NULL) {
4554 err = add_color(&s->colors, "^$", TOG_COLOR_COMMIT,
4555 get_color_value("TOG_COLOR_COMMIT"));
4556 if (err)
4557 goto done;
4558 err = add_color(&s->colors, "^$", TOG_COLOR_AUTHOR,
4559 get_color_value("TOG_COLOR_AUTHOR"));
4560 if (err)
4561 goto done;
4562 err = add_color(&s->colors, "^$", TOG_COLOR_DATE,
4563 get_color_value("TOG_COLOR_DATE"));
4564 if (err)
4565 goto done;
4568 view->show = show_log_view;
4569 view->input = input_log_view;
4570 view->resize = resize_log_view;
4571 view->close = close_log_view;
4572 view->search_start = search_start_log_view;
4573 view->search_next = search_next_log_view;
4575 if (s->thread_args.pack_fds == NULL) {
4576 err = got_repo_pack_fds_open(&s->thread_args.pack_fds);
4577 if (err)
4578 goto done;
4580 err = got_repo_open(&thread_repo, got_repo_get_path(repo), NULL,
4581 s->thread_args.pack_fds);
4582 if (err)
4583 goto done;
4584 err = got_commit_graph_open(&thread_graph, s->in_repo_path,
4585 !s->log_branches);
4586 if (err)
4587 goto done;
4588 err = got_commit_graph_bfsort(thread_graph, s->start_id,
4589 s->repo, NULL, NULL);
4590 if (err)
4591 goto done;
4593 errcode = pthread_cond_init(&s->thread_args.need_commits, NULL);
4594 if (errcode) {
4595 err = got_error_set_errno(errcode, "pthread_cond_init");
4596 goto done;
4598 errcode = pthread_cond_init(&s->thread_args.commit_loaded, NULL);
4599 if (errcode) {
4600 err = got_error_set_errno(errcode, "pthread_cond_init");
4601 goto done;
4604 if (using_mock_io) {
4605 int rc;
4607 rc = pthread_cond_init(&s->thread_args.log_loaded, NULL);
4608 if (rc)
4609 return got_error_set_errno(rc, "pthread_cond_init");
4612 s->thread_args.view_nlines = &view->nlines;
4613 s->thread_args.commits_needed = view->nlines;
4614 s->thread_args.graph = thread_graph;
4615 s->thread_args.real_commits = &s->real_commits;
4616 s->thread_args.limit_commits = &s->limit_commits;
4617 s->thread_args.in_repo_path = s->in_repo_path;
4618 s->thread_args.start_id = s->start_id;
4619 s->thread_args.repo = thread_repo;
4620 s->thread_args.log_complete = 0;
4621 s->thread_args.quit = &s->quit;
4622 s->thread_args.first_displayed_entry = &s->first_displayed_entry;
4623 s->thread_args.last_displayed_entry = &s->last_displayed_entry;
4624 s->thread_args.selected_entry = &s->selected_entry;
4625 s->thread_args.selected = &s->selected;
4626 s->thread_args.searching = &view->searching;
4627 s->thread_args.search_next_done = &view->search_next_done;
4628 s->thread_args.regex = &view->regex;
4629 s->thread_args.limiting = &s->limit_view;
4630 s->thread_args.limit_regex = &s->limit_regex;
4631 s->thread_args.limit_commits = &s->limit_commits;
4632 s->thread_args.worktree = worktree;
4633 if (worktree) {
4634 s->thread_args.wctx.active = 1;
4635 s->thread_args.need_wt_status = 1;
4636 s->thread_args.need_commit_marker = 1;
4639 done:
4640 if (err) {
4641 if (view->close == NULL)
4642 close_log_view(view);
4643 view_close(view);
4645 return err;
4648 static const struct got_error *
4649 show_log_view(struct tog_view *view)
4651 const struct got_error *err;
4652 struct tog_log_view_state *s = &view->state.log;
4654 if (s->thread == 0) { //NULL) {
4655 int errcode = pthread_create(&s->thread, NULL, log_thread,
4656 &s->thread_args);
4657 if (errcode)
4658 return got_error_set_errno(errcode, "pthread_create");
4659 if (s->thread_args.commits_needed > 0) {
4660 err = trigger_log_thread(view, 1);
4661 if (err)
4662 return err;
4666 return draw_commits(view);
4669 static void
4670 log_move_cursor_up(struct tog_view *view, int page, int home)
4672 struct tog_log_view_state *s = &view->state.log;
4674 if (s->first_displayed_entry == NULL)
4675 return;
4676 if (s->selected_entry->idx == 0)
4677 view->count = 0;
4679 if ((page && TAILQ_FIRST(&s->commits->head) == s->first_displayed_entry)
4680 || home)
4681 s->selected = home ? 0 : MAX(0, s->selected - page - 1);
4683 if (!page && !home && s->selected > 0)
4684 --s->selected;
4685 else
4686 log_scroll_up(s, home ? s->commits->ncommits : MAX(page, 1));
4688 select_commit(s);
4689 return;
4692 static const struct got_error *
4693 log_move_cursor_down(struct tog_view *view, int page)
4695 struct tog_log_view_state *s = &view->state.log;
4696 const struct got_error *err = NULL;
4697 int eos = view->nlines - 2;
4699 if (s->first_displayed_entry == NULL)
4700 return NULL;
4702 if (s->thread_args.log_complete &&
4703 s->selected_entry->idx >= s->commits->ncommits - 1)
4704 return NULL;
4706 if (view_is_hsplit_top(view))
4707 --eos; /* border consumes the last line */
4709 if (!page) {
4710 if (s->selected < MIN(eos, s->commits->ncommits - 1))
4711 ++s->selected;
4712 else
4713 err = log_scroll_down(view, 1);
4714 } else if (s->thread_args.load_all && s->thread_args.log_complete) {
4715 struct commit_queue_entry *entry;
4716 int n;
4718 s->selected = 0;
4719 entry = TAILQ_LAST(&s->commits->head, commit_queue_head);
4720 s->last_displayed_entry = entry;
4721 for (n = 0; n <= eos; n++) {
4722 if (entry == NULL)
4723 break;
4724 s->first_displayed_entry = entry;
4725 entry = TAILQ_PREV(entry, commit_queue_head, entry);
4727 if (n > 0)
4728 s->selected = n - 1;
4729 } else {
4730 if (s->last_displayed_entry->idx == s->commits->ncommits - 1 &&
4731 s->thread_args.log_complete)
4732 s->selected += MIN(page,
4733 s->commits->ncommits - s->selected_entry->idx - 1);
4734 else
4735 err = log_scroll_down(view, page);
4737 if (err)
4738 return err;
4741 * We might necessarily overshoot in horizontal
4742 * splits; if so, select the last displayed commit.
4744 if (view_is_hsplit_top(view) && s->first_displayed_entry &&
4745 s->last_displayed_entry) {
4746 s->selected = MIN(s->selected,
4747 s->last_displayed_entry->idx -
4748 s->first_displayed_entry->idx);
4751 select_commit(s);
4753 if (s->thread_args.log_complete &&
4754 s->selected_entry->idx == s->commits->ncommits - 1)
4755 view->count = 0;
4757 return NULL;
4760 static void
4761 view_get_split(struct tog_view *view, int *y, int *x)
4763 *x = 0;
4764 *y = 0;
4766 if (view->mode == TOG_VIEW_SPLIT_HRZN) {
4767 if (view->child && view->child->resized_y)
4768 *y = view->child->resized_y;
4769 else if (view->resized_y)
4770 *y = view->resized_y;
4771 else
4772 *y = view_split_begin_y(view->lines);
4773 } else if (view->mode == TOG_VIEW_SPLIT_VERT) {
4774 if (view->child && view->child->resized_x)
4775 *x = view->child->resized_x;
4776 else if (view->resized_x)
4777 *x = view->resized_x;
4778 else
4779 *x = view_split_begin_x(view->begin_x);
4783 /* Split view horizontally at y and offset view->state->selected line. */
4784 static const struct got_error *
4785 view_init_hsplit(struct tog_view *view, int y)
4787 const struct got_error *err = NULL;
4789 view->nlines = y;
4790 view->ncols = COLS;
4791 err = view_resize(view);
4792 if (err)
4793 return err;
4795 err = offset_selection_down(view);
4797 return err;
4800 static const struct got_error *
4801 log_goto_line(struct tog_view *view, int nlines)
4803 const struct got_error *err = NULL;
4804 struct tog_log_view_state *s = &view->state.log;
4805 int g, idx = s->selected_entry->idx;
4807 if (s->first_displayed_entry == NULL || s->last_displayed_entry == NULL)
4808 return NULL;
4810 g = view->gline;
4811 view->gline = 0;
4813 if (g >= s->first_displayed_entry->idx + 1 &&
4814 g <= s->last_displayed_entry->idx + 1 &&
4815 g - s->first_displayed_entry->idx - 1 < nlines) {
4816 s->selected = g - s->first_displayed_entry->idx - 1;
4817 select_commit(s);
4818 return NULL;
4821 if (idx + 1 < g) {
4822 err = log_move_cursor_down(view, g - idx - 1);
4823 if (!err && g > s->selected_entry->idx + 1)
4824 err = log_move_cursor_down(view,
4825 g - s->first_displayed_entry->idx - 1);
4826 if (err)
4827 return err;
4828 } else if (idx + 1 > g)
4829 log_move_cursor_up(view, idx - g + 1, 0);
4831 if (g < nlines && s->first_displayed_entry->idx == 0)
4832 s->selected = g - 1;
4834 select_commit(s);
4835 return NULL;
4839 static void
4840 horizontal_scroll_input(struct tog_view *view, int ch)
4843 switch (ch) {
4844 case KEY_LEFT:
4845 case 'h':
4846 view->x -= MIN(view->x, 2);
4847 if (view->x <= 0)
4848 view->count = 0;
4849 break;
4850 case KEY_RIGHT:
4851 case 'l':
4852 if (view->x + view->ncols / 2 < view->maxx)
4853 view->x += 2;
4854 else
4855 view->count = 0;
4856 break;
4857 case '0':
4858 view->x = 0;
4859 break;
4860 case '$':
4861 view->x = MAX(view->maxx - view->ncols / 2, 0);
4862 view->count = 0;
4863 break;
4864 default:
4865 break;
4869 static void
4870 log_mark_commit(struct tog_log_view_state *s)
4872 if (s->selected_entry == s->marked_entry)
4873 s->marked_entry = NULL;
4874 else
4875 s->marked_entry = s->selected_entry;
4878 static const struct got_error *
4879 input_log_view(struct tog_view **new_view, struct tog_view *view, int ch)
4881 const struct got_error *err = NULL;
4882 struct tog_log_view_state *s = &view->state.log;
4883 int eos, nscroll;
4885 if (s->thread_args.load_all) {
4886 if (ch == CTRL('g') || ch == KEY_BACKSPACE)
4887 s->thread_args.load_all = 0;
4888 else if (s->thread_args.log_complete) {
4889 err = log_move_cursor_down(view, s->commits->ncommits);
4890 s->thread_args.load_all = 0;
4892 if (err)
4893 return err;
4896 eos = nscroll = view->nlines - 1;
4897 if (view_is_hsplit_top(view))
4898 --eos; /* border */
4900 if (view->gline)
4901 return log_goto_line(view, eos);
4903 switch (ch) {
4904 case '&':
4905 err = limit_log_view(view);
4906 break;
4907 case 'q':
4908 s->quit = 1;
4909 break;
4910 case '0':
4911 case '$':
4912 case KEY_RIGHT:
4913 case 'l':
4914 case KEY_LEFT:
4915 case 'h':
4916 horizontal_scroll_input(view, ch);
4917 break;
4918 case 'k':
4919 case KEY_UP:
4920 case '<':
4921 case ',':
4922 case CTRL('p'):
4923 log_move_cursor_up(view, 0, 0);
4924 break;
4925 case 'g':
4926 case '=':
4927 case KEY_HOME:
4928 log_move_cursor_up(view, 0, 1);
4929 view->count = 0;
4930 break;
4931 case CTRL('u'):
4932 case 'u':
4933 nscroll /= 2;
4934 /* FALL THROUGH */
4935 case KEY_PPAGE:
4936 case CTRL('b'):
4937 case 'b':
4938 log_move_cursor_up(view, nscroll, 0);
4939 break;
4940 case 'j':
4941 case KEY_DOWN:
4942 case '>':
4943 case '.':
4944 case CTRL('n'):
4945 err = log_move_cursor_down(view, 0);
4946 break;
4947 case '@':
4948 s->use_committer = !s->use_committer;
4949 view->action = s->use_committer ?
4950 "show committer" : "show commit author";
4951 break;
4952 case 'G':
4953 case '*':
4954 case KEY_END: {
4955 /* We don't know yet how many commits, so we're forced to
4956 * traverse them all. */
4957 view->count = 0;
4958 s->thread_args.load_all = 1;
4959 if (!s->thread_args.log_complete)
4960 return trigger_log_thread(view, 0);
4961 err = log_move_cursor_down(view, s->commits->ncommits);
4962 s->thread_args.load_all = 0;
4963 break;
4965 case CTRL('d'):
4966 case 'd':
4967 nscroll /= 2;
4968 /* FALL THROUGH */
4969 case KEY_NPAGE:
4970 case CTRL('f'):
4971 case 'f':
4972 case ' ':
4973 err = log_move_cursor_down(view, nscroll);
4974 break;
4975 case KEY_RESIZE:
4976 if (s->selected > view->nlines - 2)
4977 s->selected = view->nlines - 2;
4978 if (s->selected > s->commits->ncommits - 1)
4979 s->selected = s->commits->ncommits - 1;
4980 select_commit(s);
4981 if (s->commits->ncommits < view->nlines - 1 &&
4982 !s->thread_args.log_complete) {
4983 s->thread_args.commits_needed += (view->nlines - 1) -
4984 s->commits->ncommits;
4985 err = trigger_log_thread(view, 1);
4987 break;
4988 case KEY_ENTER:
4989 case '\r':
4990 view->count = 0;
4991 if (s->selected_entry == NULL)
4992 break;
4993 err = view_request_new(new_view, view, TOG_VIEW_DIFF);
4994 break;
4995 case 'T':
4996 view->count = 0;
4997 if (s->selected_entry == NULL)
4998 break;
4999 err = view_request_new(new_view, view, TOG_VIEW_TREE);
5000 break;
5001 case KEY_BACKSPACE:
5002 case CTRL('l'):
5003 case 'B':
5004 view->count = 0;
5005 if (ch == KEY_BACKSPACE &&
5006 got_path_is_root_dir(s->in_repo_path))
5007 break;
5008 err = stop_log_thread(s);
5009 if (err)
5010 return err;
5011 if (ch == KEY_BACKSPACE) {
5012 char *parent_path;
5013 err = got_path_dirname(&parent_path, s->in_repo_path);
5014 if (err)
5015 return err;
5016 free(s->in_repo_path);
5017 s->in_repo_path = parent_path;
5018 s->thread_args.in_repo_path = s->in_repo_path;
5019 } else if (ch == CTRL('l')) {
5020 struct got_object_id *start_id;
5021 err = got_repo_match_object_id(&start_id, NULL,
5022 s->head_ref_name ? s->head_ref_name : GOT_REF_HEAD,
5023 GOT_OBJ_TYPE_COMMIT, &tog_refs, s->repo);
5024 if (err) {
5025 if (s->head_ref_name == NULL ||
5026 err->code != GOT_ERR_NOT_REF)
5027 return err;
5028 /* Try to cope with deleted references. */
5029 free(s->head_ref_name);
5030 s->head_ref_name = NULL;
5031 err = got_repo_match_object_id(&start_id,
5032 NULL, GOT_REF_HEAD, GOT_OBJ_TYPE_COMMIT,
5033 &tog_refs, s->repo);
5034 if (err)
5035 return err;
5037 free(s->start_id);
5038 s->start_id = start_id;
5039 s->thread_args.start_id = s->start_id;
5040 } else /* 'B' */
5041 s->log_branches = !s->log_branches;
5043 if (s->thread_args.pack_fds == NULL) {
5044 err = got_repo_pack_fds_open(&s->thread_args.pack_fds);
5045 if (err)
5046 return err;
5048 err = got_repo_open(&s->thread_args.repo,
5049 got_repo_get_path(s->repo), NULL,
5050 s->thread_args.pack_fds);
5051 if (err)
5052 return err;
5053 tog_free_refs();
5054 err = tog_load_refs(s->repo, 0);
5055 if (err)
5056 return err;
5057 err = got_commit_graph_open(&s->thread_args.graph,
5058 s->in_repo_path, !s->log_branches);
5059 if (err)
5060 return err;
5061 err = got_commit_graph_bfsort(s->thread_args.graph,
5062 s->start_id, s->repo, NULL, NULL);
5063 if (err)
5064 return err;
5065 free_commits(&s->real_commits);
5066 free_commits(&s->limit_commits);
5067 s->first_displayed_entry = NULL;
5068 s->last_displayed_entry = NULL;
5069 s->selected_entry = NULL;
5070 s->selected = 0;
5071 s->thread_args.log_complete = 0;
5072 s->quit = 0;
5073 s->thread_args.commits_needed = view->lines;
5074 s->matched_entry = NULL;
5075 s->search_entry = NULL;
5076 tog_base_commit.idx = -1;
5077 worktree_ctx_close(&s->thread_args);
5078 view->offset = 0;
5079 break;
5080 case 'm':
5081 if (s->selected_entry->worktree_entry == 0)
5082 log_mark_commit(s);
5083 break;
5084 case 'R':
5085 view->count = 0;
5086 err = view_request_new(new_view, view, TOG_VIEW_REF);
5087 break;
5088 default:
5089 view->count = 0;
5090 break;
5093 return err;
5096 static const struct got_error *
5097 apply_unveil(const char *repo_path, const char *worktree_path)
5099 const struct got_error *error;
5101 #ifdef PROFILE
5102 if (unveil("gmon.out", "rwc") != 0)
5103 return got_error_from_errno2("unveil", "gmon.out");
5104 #endif
5105 if (repo_path && unveil(repo_path, "r") != 0)
5106 return got_error_from_errno2("unveil", repo_path);
5108 if (worktree_path && unveil(worktree_path, "rwc") != 0)
5109 return got_error_from_errno2("unveil", worktree_path);
5111 if (unveil(GOT_TMPDIR_STR, "rwc") != 0)
5112 return got_error_from_errno2("unveil", GOT_TMPDIR_STR);
5114 error = got_privsep_unveil_exec_helpers();
5115 if (error != NULL)
5116 return error;
5118 if (unveil(NULL, NULL) != 0)
5119 return got_error_from_errno("unveil");
5121 return NULL;
5124 static const struct got_error *
5125 init_mock_term(const char *test_script_path)
5127 const struct got_error *err = NULL;
5128 const char *screen_dump_path;
5129 int in;
5131 if (test_script_path == NULL || *test_script_path == '\0')
5132 return got_error_msg(GOT_ERR_IO, "TOG_TEST_SCRIPT not defined");
5134 tog_io.f = fopen(test_script_path, "re");
5135 if (tog_io.f == NULL) {
5136 err = got_error_from_errno_fmt("fopen: %s",
5137 test_script_path);
5138 goto done;
5141 /* test mode, we don't want any output */
5142 tog_io.cout = fopen("/dev/null", "w+");
5143 if (tog_io.cout == NULL) {
5144 err = got_error_from_errno2("fopen", "/dev/null");
5145 goto done;
5148 in = dup(fileno(tog_io.cout));
5149 if (in == -1) {
5150 err = got_error_from_errno("dup");
5151 goto done;
5153 tog_io.cin = fdopen(in, "r");
5154 if (tog_io.cin == NULL) {
5155 err = got_error_from_errno("fdopen");
5156 close(in);
5157 goto done;
5160 screen_dump_path = getenv("TOG_SCR_DUMP");
5161 if (screen_dump_path == NULL || *screen_dump_path == '\0')
5162 return got_error_msg(GOT_ERR_IO, "TOG_SCR_DUMP not defined");
5163 tog_io.sdump = fopen(screen_dump_path, "we");
5164 if (tog_io.sdump == NULL) {
5165 err = got_error_from_errno2("fopen", screen_dump_path);
5166 goto done;
5169 if (fseeko(tog_io.f, 0L, SEEK_SET) == -1) {
5170 err = got_error_from_errno("fseeko");
5171 goto done;
5174 if (newterm(NULL, tog_io.cout, tog_io.cin) == NULL)
5175 err = got_error_msg(GOT_ERR_IO,
5176 "newterm: failed to initialise curses");
5178 using_mock_io = 1;
5180 done:
5181 if (err)
5182 tog_io_close();
5183 return err;
5186 static void
5187 init_curses(void)
5189 if (using_mock_io) /* In test mode we use a fake terminal */
5190 return;
5192 initscr();
5194 cbreak();
5195 halfdelay(1); /* Fast refresh while initial view is loading. */
5196 noecho();
5197 nonl();
5198 intrflush(stdscr, FALSE);
5199 keypad(stdscr, TRUE);
5200 curs_set(0);
5201 if (getenv("TOG_COLORS") != NULL) {
5202 start_color();
5203 use_default_colors();
5206 return;
5209 static const struct got_error *
5210 set_tog_base_commit(struct got_repository *repo, struct got_worktree *worktree)
5212 tog_base_commit.id = got_object_id_dup(
5213 got_worktree_get_base_commit_id(worktree));
5214 if (tog_base_commit.id == NULL)
5215 return got_error_from_errno( "got_object_id_dup");
5217 return NULL;
5220 static const struct got_error *
5221 get_in_repo_path_from_argv0(char **in_repo_path, int argc, char *argv[],
5222 struct got_repository *repo, struct got_worktree *worktree)
5224 const struct got_error *err = NULL;
5226 if (argc == 0) {
5227 *in_repo_path = strdup("/");
5228 if (*in_repo_path == NULL)
5229 return got_error_from_errno("strdup");
5230 return NULL;
5233 if (worktree) {
5234 const char *prefix = got_worktree_get_path_prefix(worktree);
5235 char *p;
5237 err = got_worktree_resolve_path(&p, worktree, argv[0]);
5238 if (err)
5239 return err;
5240 if (asprintf(in_repo_path, "%s%s%s", prefix,
5241 (p[0] != '\0' && !got_path_is_root_dir(prefix)) ? "/" : "",
5242 p) == -1) {
5243 err = got_error_from_errno("asprintf");
5244 *in_repo_path = NULL;
5246 free(p);
5247 } else
5248 err = got_repo_map_path(in_repo_path, repo, argv[0]);
5250 return err;
5253 static const struct got_error *
5254 cmd_log(int argc, char *argv[])
5256 const struct got_error *error;
5257 struct got_repository *repo = NULL;
5258 struct got_worktree *worktree = NULL;
5259 struct got_object_id *start_id = NULL;
5260 char *in_repo_path = NULL, *repo_path = NULL, *cwd = NULL;
5261 char *keyword_idstr = NULL, *start_commit = NULL, *label = NULL;
5262 struct got_reference *ref = NULL;
5263 const char *head_ref_name = NULL;
5264 int ch, log_branches = 0;
5265 struct tog_view *view;
5266 int *pack_fds = NULL;
5268 while ((ch = getopt(argc, argv, "bc:r:")) != -1) {
5269 switch (ch) {
5270 case 'b':
5271 log_branches = 1;
5272 break;
5273 case 'c':
5274 start_commit = optarg;
5275 break;
5276 case 'r':
5277 repo_path = realpath(optarg, NULL);
5278 if (repo_path == NULL)
5279 return got_error_from_errno2("realpath",
5280 optarg);
5281 break;
5282 default:
5283 usage_log();
5284 /* NOTREACHED */
5288 argc -= optind;
5289 argv += optind;
5291 if (argc > 1)
5292 usage_log();
5294 error = got_repo_pack_fds_open(&pack_fds);
5295 if (error != NULL)
5296 goto done;
5298 if (repo_path == NULL) {
5299 cwd = getcwd(NULL, 0);
5300 if (cwd == NULL) {
5301 error = got_error_from_errno("getcwd");
5302 goto done;
5304 error = got_worktree_open(&worktree, cwd, NULL);
5305 if (error && error->code != GOT_ERR_NOT_WORKTREE)
5306 goto done;
5307 if (worktree)
5308 repo_path =
5309 strdup(got_worktree_get_repo_path(worktree));
5310 else
5311 repo_path = strdup(cwd);
5312 if (repo_path == NULL) {
5313 error = got_error_from_errno("strdup");
5314 goto done;
5318 error = got_repo_open(&repo, repo_path, NULL, pack_fds);
5319 if (error != NULL)
5320 goto done;
5322 error = get_in_repo_path_from_argv0(&in_repo_path, argc, argv,
5323 repo, worktree);
5324 if (error)
5325 goto done;
5327 init_curses();
5329 error = apply_unveil(got_repo_get_path(repo),
5330 worktree ? got_worktree_get_root_path(worktree) : NULL);
5331 if (error)
5332 goto done;
5334 /* already loaded by tog_log_with_path()? */
5335 if (TAILQ_EMPTY(&tog_refs)) {
5336 error = tog_load_refs(repo, 0);
5337 if (error)
5338 goto done;
5341 if (start_commit == NULL) {
5342 error = got_repo_match_object_id(&start_id, &label,
5343 worktree ? got_worktree_get_head_ref_name(worktree) :
5344 GOT_REF_HEAD, GOT_OBJ_TYPE_COMMIT, &tog_refs, repo);
5345 if (error)
5346 goto done;
5347 head_ref_name = label;
5348 } else {
5349 error = got_keyword_to_idstr(&keyword_idstr, start_commit,
5350 repo, worktree);
5351 if (error != NULL)
5352 goto done;
5353 if (keyword_idstr != NULL)
5354 start_commit = keyword_idstr;
5356 error = got_ref_open(&ref, repo, start_commit, 0);
5357 if (error == NULL)
5358 head_ref_name = got_ref_get_name(ref);
5359 else if (error->code != GOT_ERR_NOT_REF)
5360 goto done;
5361 error = got_repo_match_object_id(&start_id, NULL,
5362 start_commit, GOT_OBJ_TYPE_COMMIT, &tog_refs, repo);
5363 if (error)
5364 goto done;
5367 view = view_open(0, 0, 0, 0, TOG_VIEW_LOG);
5368 if (view == NULL) {
5369 error = got_error_from_errno("view_open");
5370 goto done;
5373 if (worktree) {
5374 error = set_tog_base_commit(repo, worktree);
5375 if (error != NULL)
5376 goto done;
5379 error = open_log_view(view, start_id, repo, head_ref_name,
5380 in_repo_path, log_branches, worktree);
5381 if (error)
5382 goto done;
5384 if (worktree) {
5385 /* The work tree will be closed by the log thread. */
5386 worktree = NULL;
5389 error = view_loop(view);
5391 done:
5392 free(tog_base_commit.id);
5393 free(keyword_idstr);
5394 free(in_repo_path);
5395 free(repo_path);
5396 free(cwd);
5397 free(start_id);
5398 free(label);
5399 if (ref)
5400 got_ref_close(ref);
5401 if (repo) {
5402 const struct got_error *close_err = got_repo_close(repo);
5403 if (error == NULL)
5404 error = close_err;
5406 if (worktree)
5407 got_worktree_close(worktree);
5408 if (pack_fds) {
5409 const struct got_error *pack_err =
5410 got_repo_pack_fds_close(pack_fds);
5411 if (error == NULL)
5412 error = pack_err;
5414 tog_free_refs();
5415 return error;
5418 __dead static void
5419 usage_diff(void)
5421 endwin();
5422 fprintf(stderr, "usage: %s diff [-asw] [-C number] [-c commit] "
5423 "[-r repository-path] [object1 object2 | path ...]\n",
5424 getprogname());
5425 exit(1);
5428 static int
5429 match_line(const char *line, regex_t *regex, size_t nmatch,
5430 regmatch_t *regmatch)
5432 return regexec(regex, line, nmatch, regmatch, 0) == 0;
5435 static struct tog_color *
5436 match_color(struct tog_colors *colors, const char *line)
5438 struct tog_color *tc = NULL;
5440 STAILQ_FOREACH(tc, colors, entry) {
5441 if (match_line(line, &tc->regex, 0, NULL))
5442 return tc;
5445 return NULL;
5448 static const struct got_error *
5449 add_matched_line(int *wtotal, const char *line, int wlimit, int col_tab_align,
5450 WINDOW *window, int skipcol, regmatch_t *regmatch)
5452 const struct got_error *err = NULL;
5453 char *exstr = NULL;
5454 wchar_t *wline = NULL;
5455 int rme, rms, n, width, scrollx;
5456 int width0 = 0, width1 = 0, width2 = 0;
5457 char *seg0 = NULL, *seg1 = NULL, *seg2 = NULL;
5459 *wtotal = 0;
5461 rms = regmatch->rm_so;
5462 rme = regmatch->rm_eo;
5464 err = expand_tab(&exstr, line);
5465 if (err)
5466 return err;
5468 /* Split the line into 3 segments, according to match offsets. */
5469 seg0 = strndup(exstr, rms);
5470 if (seg0 == NULL) {
5471 err = got_error_from_errno("strndup");
5472 goto done;
5474 seg1 = strndup(exstr + rms, rme - rms);
5475 if (seg1 == NULL) {
5476 err = got_error_from_errno("strndup");
5477 goto done;
5479 seg2 = strdup(exstr + rme);
5480 if (seg2 == NULL) {
5481 err = got_error_from_errno("strndup");
5482 goto done;
5485 /* draw up to matched token if we haven't scrolled past it */
5486 err = format_line(&wline, &width0, NULL, seg0, 0, wlimit,
5487 col_tab_align, 1);
5488 if (err)
5489 goto done;
5490 n = MAX(width0 - skipcol, 0);
5491 if (n) {
5492 free(wline);
5493 err = format_line(&wline, &width, &scrollx, seg0, skipcol,
5494 wlimit, col_tab_align, 1);
5495 if (err)
5496 goto done;
5497 waddwstr(window, &wline[scrollx]);
5498 wlimit -= width;
5499 *wtotal += width;
5502 if (wlimit > 0) {
5503 int i = 0, w = 0;
5504 size_t wlen;
5506 free(wline);
5507 err = format_line(&wline, &width1, NULL, seg1, 0, wlimit,
5508 col_tab_align, 1);
5509 if (err)
5510 goto done;
5511 wlen = wcslen(wline);
5512 while (i < wlen) {
5513 width = wcwidth(wline[i]);
5514 if (width == -1) {
5515 /* should not happen, tabs are expanded */
5516 err = got_error(GOT_ERR_RANGE);
5517 goto done;
5519 if (width0 + w + width > skipcol)
5520 break;
5521 w += width;
5522 i++;
5524 /* draw (visible part of) matched token (if scrolled into it) */
5525 if (width1 - w > 0) {
5526 wattron(window, A_STANDOUT);
5527 waddwstr(window, &wline[i]);
5528 wattroff(window, A_STANDOUT);
5529 wlimit -= (width1 - w);
5530 *wtotal += (width1 - w);
5534 if (wlimit > 0) { /* draw rest of line */
5535 free(wline);
5536 if (skipcol > width0 + width1) {
5537 err = format_line(&wline, &width2, &scrollx, seg2,
5538 skipcol - (width0 + width1), wlimit,
5539 col_tab_align, 1);
5540 if (err)
5541 goto done;
5542 waddwstr(window, &wline[scrollx]);
5543 } else {
5544 err = format_line(&wline, &width2, NULL, seg2, 0,
5545 wlimit, col_tab_align, 1);
5546 if (err)
5547 goto done;
5548 waddwstr(window, wline);
5550 *wtotal += width2;
5552 done:
5553 free(wline);
5554 free(exstr);
5555 free(seg0);
5556 free(seg1);
5557 free(seg2);
5558 return err;
5561 static int
5562 gotoline(struct tog_view *view, int *lineno, int *nprinted)
5564 FILE *f = NULL;
5565 int *eof, *first, *selected;
5567 if (view->type == TOG_VIEW_DIFF) {
5568 struct tog_diff_view_state *s = &view->state.diff;
5570 first = &s->first_displayed_line;
5571 selected = first;
5572 eof = &s->eof;
5573 f = s->f;
5574 } else if (view->type == TOG_VIEW_HELP) {
5575 struct tog_help_view_state *s = &view->state.help;
5577 first = &s->first_displayed_line;
5578 selected = first;
5579 eof = &s->eof;
5580 f = s->f;
5581 } else if (view->type == TOG_VIEW_BLAME) {
5582 struct tog_blame_view_state *s = &view->state.blame;
5584 first = &s->first_displayed_line;
5585 selected = &s->selected_line;
5586 eof = &s->eof;
5587 f = s->blame.f;
5588 } else
5589 return 0;
5591 /* Center gline in the middle of the page like vi(1). */
5592 if (*lineno < view->gline - (view->nlines - 3) / 2)
5593 return 0;
5594 if (*first != 1 && (*lineno > view->gline - (view->nlines - 3) / 2)) {
5595 rewind(f);
5596 *eof = 0;
5597 *first = 1;
5598 *lineno = 0;
5599 *nprinted = 0;
5600 return 0;
5603 *selected = view->gline <= (view->nlines - 3) / 2 ?
5604 view->gline : (view->nlines - 3) / 2 + 1;
5605 view->gline = 0;
5607 return 1;
5610 static const struct got_error *
5611 draw_file(struct tog_view *view, const char *header)
5613 struct tog_diff_view_state *s = &view->state.diff;
5614 regmatch_t *regmatch = &view->regmatch;
5615 const struct got_error *err;
5616 int nprinted = 0;
5617 char *line;
5618 size_t linesize = 0;
5619 ssize_t linelen;
5620 wchar_t *wline;
5621 int width;
5622 int max_lines = view->nlines;
5623 int nlines = s->nlines;
5624 off_t line_offset;
5626 s->lineno = s->first_displayed_line - 1;
5627 line_offset = s->lines[s->first_displayed_line - 1].offset;
5628 if (fseeko(s->f, line_offset, SEEK_SET) == -1)
5629 return got_error_from_errno("fseek");
5631 werase(view->window);
5633 if (view->gline > s->nlines - 1)
5634 view->gline = s->nlines - 1;
5636 if (header) {
5637 int ln = view->gline ? view->gline <= (view->nlines - 3) / 2 ?
5638 1 : view->gline - (view->nlines - 3) / 2 :
5639 s->lineno + s->selected_line;
5641 if (asprintf(&line, "[%d/%d] %s", ln, nlines, header) == -1)
5642 return got_error_from_errno("asprintf");
5643 err = format_line(&wline, &width, NULL, line, 0, view->ncols,
5644 0, 0);
5645 free(line);
5646 if (err)
5647 return err;
5649 if (view_needs_focus_indication(view))
5650 wstandout(view->window);
5651 waddwstr(view->window, wline);
5652 free(wline);
5653 wline = NULL;
5654 while (width++ < view->ncols)
5655 waddch(view->window, ' ');
5656 if (view_needs_focus_indication(view))
5657 wstandend(view->window);
5659 if (max_lines <= 1)
5660 return NULL;
5661 max_lines--;
5664 s->eof = 0;
5665 view->maxx = 0;
5666 line = NULL;
5667 while (max_lines > 0 && nprinted < max_lines) {
5668 enum got_diff_line_type linetype;
5669 attr_t attr = 0;
5671 linelen = getline(&line, &linesize, s->f);
5672 if (linelen == -1) {
5673 if (feof(s->f)) {
5674 s->eof = 1;
5675 break;
5677 free(line);
5678 return got_ferror(s->f, GOT_ERR_IO);
5681 if (++s->lineno < s->first_displayed_line)
5682 continue;
5683 if (view->gline && !gotoline(view, &s->lineno, &nprinted))
5684 continue;
5685 if (s->lineno == view->hiline)
5686 attr = A_STANDOUT;
5688 /* Set view->maxx based on full line length. */
5689 err = format_line(&wline, &width, NULL, line, 0, INT_MAX, 0,
5690 view->x ? 1 : 0);
5691 if (err) {
5692 free(line);
5693 return err;
5695 view->maxx = MAX(view->maxx, width);
5696 free(wline);
5697 wline = NULL;
5699 linetype = s->lines[s->lineno].type;
5700 if (linetype > GOT_DIFF_LINE_LOGMSG &&
5701 linetype < GOT_DIFF_LINE_CONTEXT)
5702 attr |= COLOR_PAIR(linetype);
5703 if (attr)
5704 wattron(view->window, attr);
5705 if (s->first_displayed_line + nprinted == s->matched_line &&
5706 regmatch->rm_so >= 0 && regmatch->rm_so < regmatch->rm_eo) {
5707 err = add_matched_line(&width, line, view->ncols, 0,
5708 view->window, view->x, regmatch);
5709 if (err) {
5710 free(line);
5711 return err;
5713 } else {
5714 int skip;
5715 err = format_line(&wline, &width, &skip, line,
5716 view->x, view->ncols, 0, view->x ? 1 : 0);
5717 if (err) {
5718 free(line);
5719 return err;
5721 waddwstr(view->window, &wline[skip]);
5722 free(wline);
5723 wline = NULL;
5725 if (s->lineno == view->hiline) {
5726 /* highlight full gline length */
5727 while (width++ < view->ncols)
5728 waddch(view->window, ' ');
5729 } else {
5730 if (width <= view->ncols - 1)
5731 waddch(view->window, '\n');
5733 if (attr)
5734 wattroff(view->window, attr);
5735 if (++nprinted == 1)
5736 s->first_displayed_line = s->lineno;
5738 free(line);
5739 if (nprinted >= 1)
5740 s->last_displayed_line = s->first_displayed_line +
5741 (nprinted - 1);
5742 else
5743 s->last_displayed_line = s->first_displayed_line;
5745 view_border(view);
5747 if (s->eof) {
5748 while (nprinted < view->nlines) {
5749 waddch(view->window, '\n');
5750 nprinted++;
5753 err = format_line(&wline, &width, NULL, TOG_EOF_STRING, 0,
5754 view->ncols, 0, 0);
5755 if (err) {
5756 return err;
5759 wstandout(view->window);
5760 waddwstr(view->window, wline);
5761 free(wline);
5762 wline = NULL;
5763 wstandend(view->window);
5766 return NULL;
5769 static char *
5770 get_datestr(time_t *time, char *datebuf)
5772 struct tm mytm, *tm;
5773 char *p, *s;
5775 tm = gmtime_r(time, &mytm);
5776 if (tm == NULL)
5777 return NULL;
5778 s = asctime_r(tm, datebuf);
5779 if (s == NULL)
5780 return NULL;
5781 p = strchr(s, '\n');
5782 if (p)
5783 *p = '\0';
5784 return s;
5787 static const struct got_error *
5788 add_line_metadata(struct got_diff_line **lines, size_t *nlines,
5789 off_t off, uint8_t type)
5791 struct got_diff_line *p;
5793 p = reallocarray(*lines, *nlines + 1, sizeof(**lines));
5794 if (p == NULL)
5795 return got_error_from_errno("reallocarray");
5796 *lines = p;
5797 (*lines)[*nlines].offset = off;
5798 (*lines)[*nlines].type = type;
5799 (*nlines)++;
5801 return NULL;
5804 static const struct got_error *
5805 cat_diff(FILE *dst, FILE *src, struct got_diff_line **d_lines, size_t *d_nlines,
5806 struct got_diff_line *s_lines, size_t s_nlines)
5808 struct got_diff_line *p;
5809 char buf[BUFSIZ];
5810 size_t i, r;
5812 if (fseeko(src, 0L, SEEK_SET) == -1)
5813 return got_error_from_errno("fseeko");
5815 for (;;) {
5816 r = fread(buf, 1, sizeof(buf), src);
5817 if (r == 0) {
5818 if (ferror(src))
5819 return got_error_from_errno("fread");
5820 if (feof(src))
5821 break;
5823 if (fwrite(buf, 1, r, dst) != r)
5824 return got_ferror(dst, GOT_ERR_IO);
5827 if (s_nlines == 0 && *d_nlines == 0)
5828 return NULL;
5831 * If commit info was in dst, increment line offsets
5832 * of the appended diff content, but skip s_lines[0]
5833 * because offset zero is already in *d_lines.
5835 if (*d_nlines > 0) {
5836 for (i = 1; i < s_nlines; ++i)
5837 s_lines[i].offset += (*d_lines)[*d_nlines - 1].offset;
5839 if (s_nlines > 0) {
5840 --s_nlines;
5841 ++s_lines;
5845 p = reallocarray(*d_lines, *d_nlines + s_nlines, sizeof(*p));
5846 if (p == NULL) {
5847 /* d_lines is freed in close_diff_view() */
5848 return got_error_from_errno("reallocarray");
5851 *d_lines = p;
5853 memcpy(*d_lines + *d_nlines, s_lines, s_nlines * sizeof(*s_lines));
5854 *d_nlines += s_nlines;
5856 return NULL;
5859 static const struct got_error *
5860 write_diffstat(FILE *outfile, struct got_diff_line **lines, size_t *nlines,
5861 struct got_diffstat_cb_arg *dsa)
5863 const struct got_error *err;
5864 struct got_pathlist_entry *pe;
5865 off_t offset;
5866 int n;
5868 if (*nlines == 0) {
5869 err = add_line_metadata(lines, nlines, 0, GOT_DIFF_LINE_NONE);
5870 if (err != NULL)
5871 return err;
5872 offset = 0;
5873 } else
5874 offset = (*lines)[*nlines - 1].offset;
5876 RB_FOREACH(pe, got_pathlist_head, dsa->paths) {
5877 struct got_diff_changed_path *cp = pe->data;
5878 int pad = dsa->max_path_len - pe->path_len + 1;
5880 n = fprintf(outfile, "%c %s%*c | %*d+ %*d-\n", cp->status,
5881 pe->path, pad, ' ', dsa->add_cols + 1, cp->add,
5882 dsa->rm_cols + 1, cp->rm);
5883 if (n < 0)
5884 return got_error_from_errno("fprintf");
5886 offset += n;
5887 err = add_line_metadata(lines, nlines, offset,
5888 GOT_DIFF_LINE_CHANGES);
5889 if (err != NULL)
5890 return err;
5893 if (fputc('\n', outfile) == EOF)
5894 return got_error_from_errno("fputc");
5896 offset++;
5897 err = add_line_metadata(lines, nlines, offset, GOT_DIFF_LINE_NONE);
5898 if (err != NULL)
5899 return err;
5901 n = fprintf(outfile,
5902 "%d file%s changed, %d insertion%s(+), %d deletion%s(-)\n",
5903 dsa->nfiles, dsa->nfiles > 1 ? "s" : "", dsa->ins,
5904 dsa->ins != 1 ? "s" : "", dsa->del, dsa->del != 1 ? "s" : "");
5905 if (n < 0)
5906 return got_error_from_errno("fprintf");
5908 offset += n;
5909 err = add_line_metadata(lines, nlines, offset, GOT_DIFF_LINE_NONE);
5910 if (err != NULL)
5911 return err;
5913 if (fputc('\n', outfile) == EOF)
5914 return got_error_from_errno("fputc");
5916 offset++;
5917 return add_line_metadata(lines, nlines, offset, GOT_DIFF_LINE_NONE);
5920 static const struct got_error *
5921 write_commit_info(struct got_diff_line **lines, size_t *nlines,
5922 struct got_object_id *commit_id, struct got_reflist_head *refs,
5923 struct got_repository *repo, int ignore_ws, int force_text_diff,
5924 struct got_diffstat_cb_arg *dsa, FILE *outfile)
5926 const struct got_error *err = NULL;
5927 char datebuf[26], *datestr;
5928 struct got_commit_object *commit;
5929 char *id_str = NULL, *logmsg = NULL, *s = NULL, *line;
5930 time_t committer_time;
5931 const char *author, *committer;
5932 char *refs_str = NULL;
5933 off_t outoff = 0;
5934 int n;
5936 err = build_refs_str(&refs_str, refs, commit_id, repo);
5937 if (err)
5938 return err;
5940 err = got_object_open_as_commit(&commit, repo, commit_id);
5941 if (err)
5942 return err;
5944 err = got_object_id_str(&id_str, commit_id);
5945 if (err) {
5946 err = got_error_from_errno("got_object_id_str");
5947 goto done;
5950 err = add_line_metadata(lines, nlines, 0, GOT_DIFF_LINE_NONE);
5951 if (err)
5952 goto done;
5954 n = fprintf(outfile, "commit %s%s%s%s\n", id_str, refs_str ? " (" : "",
5955 refs_str ? refs_str : "", refs_str ? ")" : "");
5956 if (n < 0) {
5957 err = got_error_from_errno("fprintf");
5958 goto done;
5960 outoff += n;
5961 err = add_line_metadata(lines, nlines, outoff, GOT_DIFF_LINE_META);
5962 if (err)
5963 goto done;
5965 n = fprintf(outfile, "from: %s\n",
5966 got_object_commit_get_author(commit));
5967 if (n < 0) {
5968 err = got_error_from_errno("fprintf");
5969 goto done;
5971 outoff += n;
5972 err = add_line_metadata(lines, nlines, outoff, GOT_DIFF_LINE_AUTHOR);
5973 if (err)
5974 goto done;
5976 author = got_object_commit_get_author(commit);
5977 committer = got_object_commit_get_committer(commit);
5978 if (strcmp(author, committer) != 0) {
5979 n = fprintf(outfile, "via: %s\n", committer);
5980 if (n < 0) {
5981 err = got_error_from_errno("fprintf");
5982 goto done;
5984 outoff += n;
5985 err = add_line_metadata(lines, nlines, outoff,
5986 GOT_DIFF_LINE_AUTHOR);
5987 if (err)
5988 goto done;
5990 committer_time = got_object_commit_get_committer_time(commit);
5991 datestr = get_datestr(&committer_time, datebuf);
5992 if (datestr) {
5993 n = fprintf(outfile, "date: %s UTC\n", datestr);
5994 if (n < 0) {
5995 err = got_error_from_errno("fprintf");
5996 goto done;
5998 outoff += n;
5999 err = add_line_metadata(lines, nlines, outoff,
6000 GOT_DIFF_LINE_DATE);
6001 if (err)
6002 goto done;
6004 if (got_object_commit_get_nparents(commit) > 1) {
6005 const struct got_object_id_queue *parent_ids;
6006 struct got_object_qid *qid;
6007 int pn = 1;
6008 parent_ids = got_object_commit_get_parent_ids(commit);
6009 STAILQ_FOREACH(qid, parent_ids, entry) {
6010 err = got_object_id_str(&id_str, &qid->id);
6011 if (err)
6012 goto done;
6013 n = fprintf(outfile, "parent %d: %s\n", pn++, id_str);
6014 if (n < 0) {
6015 err = got_error_from_errno("fprintf");
6016 goto done;
6018 outoff += n;
6019 err = add_line_metadata(lines, nlines, outoff,
6020 GOT_DIFF_LINE_META);
6021 if (err)
6022 goto done;
6023 free(id_str);
6024 id_str = NULL;
6028 err = got_object_commit_get_logmsg(&logmsg, commit);
6029 if (err)
6030 goto done;
6031 s = logmsg;
6032 while ((line = strsep(&s, "\n")) != NULL) {
6033 n = fprintf(outfile, "%s\n", line);
6034 if (n < 0) {
6035 err = got_error_from_errno("fprintf");
6036 goto done;
6038 outoff += n;
6039 err = add_line_metadata(lines, nlines, outoff,
6040 GOT_DIFF_LINE_LOGMSG);
6041 if (err)
6042 goto done;
6045 done:
6046 free(id_str);
6047 free(logmsg);
6048 free(refs_str);
6049 got_object_commit_close(commit);
6050 return err;
6053 static void
6054 evict_worktree_entry(struct tog_log_thread_args *ta, int victim)
6056 struct commit_queue_entry *e, *v = *ta->selected_entry;
6058 if (victim == 0)
6059 return; /* paranoid check */
6061 if (v->worktree_entry != victim) {
6062 TAILQ_FOREACH(v, &ta->real_commits->head, entry) {
6063 if (v->worktree_entry == victim)
6064 break;
6066 if (v == NULL)
6067 return;
6070 ta->wctx.wt_state &= ~victim;
6072 if (*ta->selected_entry == v)
6073 *ta->selected_entry = TAILQ_NEXT(v, entry);
6074 if (*ta->first_displayed_entry == v)
6075 *ta->first_displayed_entry = TAILQ_NEXT(v, entry);
6076 if (*ta->last_displayed_entry == v)
6077 *ta->last_displayed_entry = TAILQ_NEXT(v, entry);
6079 for (e = TAILQ_NEXT(v, entry); e != NULL; e = TAILQ_NEXT(e, entry))
6080 --e->idx;
6082 --tog_base_commit.idx;
6083 --ta->real_commits->ncommits;
6085 TAILQ_REMOVE(&ta->real_commits->head, v, entry);
6086 free(v);
6090 * Create a file which contains the target path of a symlink so we can feed
6091 * it as content to the diff engine.
6093 static const struct got_error *
6094 get_symlink_target_file(int *fd, int dirfd, const char *de_name,
6095 const char *abspath)
6097 const struct got_error *err = NULL;
6098 char target_path[PATH_MAX];
6099 ssize_t target_len, outlen;
6101 *fd = -1;
6103 if (dirfd != -1) {
6104 target_len = readlinkat(dirfd, de_name, target_path, PATH_MAX);
6105 if (target_len == -1)
6106 return got_error_from_errno2("readlinkat", abspath);
6107 } else {
6108 target_len = readlink(abspath, target_path, PATH_MAX);
6109 if (target_len == -1)
6110 return got_error_from_errno2("readlink", abspath);
6113 *fd = got_opentempfd();
6114 if (*fd == -1)
6115 return got_error_from_errno("got_opentempfd");
6117 outlen = write(*fd, target_path, target_len);
6118 if (outlen == -1) {
6119 err = got_error_from_errno("got_opentempfd");
6120 goto done;
6123 if (lseek(*fd, 0, SEEK_SET) == -1) {
6124 err = got_error_from_errno2("lseek", abspath);
6125 goto done;
6128 done:
6129 if (err) {
6130 close(*fd);
6131 *fd = -1;
6133 return err;
6136 static const struct got_error *
6137 emit_base_commit_header(FILE *f, struct got_diff_line **lines, size_t *nlines,
6138 struct got_object_id *commit_id, struct got_worktree *worktree)
6140 const struct got_error *err;
6141 struct got_object_id *base_commit_id;
6142 char *base_commit_idstr;
6143 int n;
6145 if (worktree == NULL) /* shouldn't happen */
6146 return got_error(GOT_ERR_NOT_WORKTREE);
6148 base_commit_id = got_worktree_get_base_commit_id(worktree);
6150 if (commit_id != NULL) {
6151 if (got_object_id_cmp(commit_id, base_commit_id) != 0)
6152 base_commit_id = commit_id;
6155 err = got_object_id_str(&base_commit_idstr, base_commit_id);
6156 if (err != NULL)
6157 return err;
6159 if ((n = fprintf(f, "commit - %s\n", base_commit_idstr)) < 0)
6160 err = got_error_from_errno("fprintf");
6161 free(base_commit_idstr);
6162 if (err != NULL)
6163 return err;
6165 return add_line_metadata(lines, nlines,
6166 (*lines)[*nlines - 1].offset + n, GOT_DIFF_LINE_META);
6169 static const struct got_error *
6170 tog_worktree_diff(void *arg, unsigned char status, unsigned char staged_status,
6171 const char *path, struct got_object_id *blob_id,
6172 struct got_object_id *staged_blob_id, struct got_object_id *commit_id,
6173 int dirfd, const char *de_name)
6175 const struct got_error *err = NULL;
6176 struct diff_worktree_arg *a = arg;
6177 struct got_blob_object *blob1 = NULL;
6178 struct stat sb;
6179 FILE *f2 = NULL;
6180 char *abspath = NULL, *label1 = NULL;
6181 off_t size1 = 0;
6182 off_t outoff = 0;
6183 int fd = -1, fd1 = -1, fd2 = -1;
6184 int n, f2_exists = 1;
6186 if (a->diff_staged) {
6187 if (staged_status != GOT_STATUS_MODIFY &&
6188 staged_status != GOT_STATUS_ADD &&
6189 staged_status != GOT_STATUS_DELETE)
6190 return NULL;
6191 } else {
6192 if (staged_status == GOT_STATUS_DELETE)
6193 return NULL;
6194 if (status == GOT_STATUS_NONEXISTENT)
6195 return got_error_set_errno(ENOENT, path);
6196 if (status != GOT_STATUS_MODIFY &&
6197 status != GOT_STATUS_ADD &&
6198 status != GOT_STATUS_DELETE &&
6199 status != GOT_STATUS_CONFLICT)
6200 return NULL;
6203 err = got_opentemp_truncate(a->f1);
6204 if (err != NULL)
6205 return got_error_from_errno("got_opentemp_truncate");
6206 err = got_opentemp_truncate(a->f2);
6207 if (err != NULL)
6208 return got_error_from_errno("got_opentemp_truncate");
6210 if (!a->header_shown) {
6211 n = fprintf(a->outfile, "path + %s%s\n",
6212 got_worktree_get_root_path(a->worktree),
6213 a->diff_staged ? " (staged changes)" : "");
6214 if (n < 0)
6215 return got_error_from_errno("fprintf");
6217 outoff += n;
6218 err = add_line_metadata(a->lines, a->nlines, outoff,
6219 GOT_DIFF_LINE_META);
6220 if (err != NULL)
6221 return err;
6223 a->header_shown = 1;
6226 err = emit_base_commit_header(a->outfile,
6227 a->lines, a->nlines, commit_id, a->worktree);
6228 if (err != NULL)
6229 return err;
6231 if (a->diff_staged) {
6232 const char *label1 = NULL, *label2 = NULL;
6234 switch (staged_status) {
6235 case GOT_STATUS_MODIFY:
6236 label1 = path;
6237 label2 = path;
6238 break;
6239 case GOT_STATUS_ADD:
6240 label2 = path;
6241 break;
6242 case GOT_STATUS_DELETE:
6243 label1 = path;
6244 break;
6245 default:
6246 return got_error(GOT_ERR_FILE_STATUS);
6249 fd1 = got_opentempfd();
6250 if (fd1 == -1)
6251 return got_error_from_errno("got_opentempfd");
6253 fd2 = got_opentempfd();
6254 if (fd2 == -1) {
6255 err = got_error_from_errno("got_opentempfd");
6256 goto done;
6259 err = got_diff_objects_as_blobs(a->lines, a->nlines,
6260 a->f1, a->f2, fd1, fd2, blob_id, staged_blob_id,
6261 label1, label2, a->diff_algo, a->diff_context,
6262 a->ignore_whitespace, a->force_text_diff,
6263 a->diffstat, a->repo, a->outfile);
6264 goto done;
6267 fd1 = got_opentempfd();
6268 if (fd1 == -1)
6269 return got_error_from_errno("got_opentempfd");
6271 if (staged_status == GOT_STATUS_ADD ||
6272 staged_status == GOT_STATUS_MODIFY) {
6273 char *id_str;
6275 err = got_object_open_as_blob(&blob1,
6276 a->repo, staged_blob_id, 8192, fd1);
6277 if (err != NULL)
6278 goto done;
6279 err = got_object_id_str(&id_str, staged_blob_id);
6280 if (err != NULL)
6281 goto done;
6282 if (asprintf(&label1, "%s (staged)", id_str) == -1) {
6283 err = got_error_from_errno("asprintf");
6284 free(id_str);
6285 goto done;
6287 free(id_str);
6288 } else if (status != GOT_STATUS_ADD) {
6289 err = got_object_open_as_blob(&blob1,
6290 a->repo, blob_id, 8192, fd1);
6291 if (err != NULL)
6292 goto done;
6295 if (status != GOT_STATUS_DELETE) {
6296 if (asprintf(&abspath, "%s/%s",
6297 got_worktree_get_root_path(a->worktree), path) == -1) {
6298 err = got_error_from_errno("asprintf");
6299 goto done;
6302 if (dirfd != -1) {
6303 fd = openat(dirfd, de_name,
6304 O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
6305 if (fd == -1) {
6306 if (!got_err_open_nofollow_on_symlink()) {
6307 err = got_error_from_errno2("openat",
6308 abspath);
6309 goto done;
6311 err = get_symlink_target_file(&fd,
6312 dirfd, de_name, abspath);
6313 if (err != NULL)
6314 goto done;
6316 } else {
6317 fd = open(abspath, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
6318 if (fd == -1) {
6319 if (!got_err_open_nofollow_on_symlink()) {
6320 err = got_error_from_errno2("open",
6321 abspath);
6322 goto done;
6324 err = get_symlink_target_file(&fd,
6325 dirfd, de_name, abspath);
6326 if (err != NULL)
6327 goto done;
6330 if (fstat(fd, &sb) == -1) {
6331 err = got_error_from_errno2("fstat", abspath);
6332 goto done;
6334 f2 = fdopen(fd, "r");
6335 if (f2 == NULL) {
6336 err = got_error_from_errno2("fdopen", abspath);
6337 goto done;
6339 fd = -1;
6340 } else {
6341 sb.st_size = 0;
6342 f2_exists = 0;
6345 if (blob1 != NULL) {
6346 err = got_object_blob_dump_to_file(&size1,
6347 NULL, NULL, a->f1, blob1);
6348 if (err != NULL)
6349 goto done;
6352 err = got_diff_blob_file(a->lines, a->nlines, blob1, a->f1, size1,
6353 label1, f2 != NULL ? f2 : a->f2, f2_exists, &sb, path,
6354 tog_diff_algo, a->diff_context, a->ignore_whitespace,
6355 a->force_text_diff, a->diffstat, a->outfile);
6357 done:
6358 if (fd != -1 && close(fd) == -1 && err == NULL)
6359 err = got_error_from_errno("close");
6360 if (fd1 != -1 && close(fd1) == -1 && err == NULL)
6361 err = got_error_from_errno("close");
6362 if (fd2 != -1 && close(fd2) == -1 && err == NULL)
6363 err = got_error_from_errno("close");
6364 if (blob1 != NULL)
6365 got_object_blob_close(blob1);
6366 if (f2 != NULL && fclose(f2) == EOF && err == NULL)
6367 err = got_error_from_errno("fclose");
6368 free(abspath);
6369 free(label1);
6370 return err;
6373 static const struct got_error *
6374 tog_diff_worktree(struct tog_diff_view_state *s, FILE *f,
6375 struct got_diff_line **lines, size_t *nlines,
6376 struct got_diffstat_cb_arg *dsa)
6378 const struct got_error *close_err, *err;
6379 struct got_worktree *worktree = NULL;
6380 struct diff_worktree_arg arg;
6381 struct got_pathlist_head pathlist;
6382 char *cwd, *id_str = NULL;
6384 RB_INIT(&pathlist);
6386 cwd = getcwd(NULL, 0);
6387 if (cwd == NULL)
6388 return got_error_from_errno("getcwd");
6390 err = add_line_metadata(lines, nlines, 0, GOT_DIFF_LINE_NONE);
6391 if (err != NULL)
6392 goto done;
6394 err = got_worktree_open(&worktree, cwd, NULL);
6395 if (err != NULL) {
6396 if (err->code == GOT_ERR_WORKTREE_BUSY) {
6397 int n;
6399 if ((n = fprintf(f, "%s\n", err->msg)) < 0) {
6400 err = got_ferror(f, GOT_ERR_IO);
6401 goto done;
6403 err = add_line_metadata(lines, nlines, n,
6404 GOT_DIFF_LINE_META);
6405 if (err != NULL)
6406 goto done;
6407 err = got_error(GOT_ERR_DIFF_NOCHANGES);
6409 goto done;
6412 err = got_object_id_str(&id_str,
6413 got_worktree_get_base_commit_id(worktree));
6414 if (err != NULL)
6415 goto done;
6417 err = got_repo_match_object_id(&s->id1, NULL, id_str,
6418 GOT_OBJ_TYPE_ANY, &tog_refs, s->repo);
6419 if (err != NULL)
6420 goto done;
6422 arg.id_str = id_str;
6423 arg.diff_algo = tog_diff_algo;
6424 arg.repo = s->repo;
6425 arg.worktree = worktree;
6426 arg.diffstat = dsa;
6427 arg.diff_context = s->diff_context;
6428 arg.diff_staged = s->diff_staged;
6429 arg.ignore_whitespace = s->ignore_whitespace;
6430 arg.force_text_diff = s->force_text_diff;
6431 arg.header_shown = 0;
6432 arg.lines = lines;
6433 arg.nlines = nlines;
6434 arg.f1 = s->f1;
6435 arg.f2 = s->f2;
6436 arg.outfile = f;
6438 if (s->paths == NULL) {
6439 err = got_pathlist_insert(NULL, &pathlist, "", NULL);
6440 if (err != NULL)
6441 goto done;
6444 err = got_worktree_status(worktree, s->paths ? s->paths : &pathlist,
6445 s->repo, 0, tog_worktree_diff, &arg, NULL, NULL);
6446 if (err != NULL)
6447 goto done;
6449 if (*nlines == 1) {
6450 const char *msg = TOG_WORKTREE_CHANGES_LOCAL_MSG;
6451 int n, victim = TOG_WORKTREE_CHANGES_LOCAL;
6453 if (s->diff_staged) {
6454 victim = TOG_WORKTREE_CHANGES_STAGED;
6455 msg = TOG_WORKTREE_CHANGES_STAGED_MSG;
6457 if ((n = fprintf(f, "no %s\n", msg)) < 0) {
6458 err = got_ferror(f, GOT_ERR_IO);
6459 goto done;
6461 err = add_line_metadata(lines, nlines, n, GOT_DIFF_LINE_META);
6462 if (err != NULL)
6463 goto done;
6464 if (s->parent_view && s->parent_view->type == TOG_VIEW_LOG)
6465 evict_worktree_entry(
6466 &s->parent_view->state.log.thread_args, victim);
6467 err = got_error(GOT_ERR_DIFF_NOCHANGES);
6470 done:
6471 free(cwd);
6472 free(id_str);
6473 got_pathlist_free(&pathlist, GOT_PATHLIST_FREE_NONE);
6474 if (worktree != NULL) {
6475 if ((close_err = got_worktree_close(worktree)) != NULL) {
6476 if (err == NULL || err->code == GOT_ERR_DIFF_NOCHANGES)
6477 err = close_err;
6480 return err;
6483 static const struct got_error *
6484 tog_diff_objects(struct tog_diff_view_state *s, FILE *f,
6485 struct got_diff_line **lines, size_t *nlines,
6486 struct got_diffstat_cb_arg *dsa)
6488 const struct got_error *err;
6489 int obj_type;
6491 if (s->id1)
6492 err = got_object_get_type(&obj_type, s->repo, s->id1);
6493 else
6494 err = got_object_get_type(&obj_type, s->repo, s->id2);
6495 if (err != NULL)
6496 return err;
6498 switch (obj_type) {
6499 case GOT_OBJ_TYPE_BLOB:
6500 err = got_diff_objects_as_blobs(lines, nlines, s->f1, s->f2,
6501 s->fd1, s->fd2, s->id1, s->id2, NULL, NULL, tog_diff_algo,
6502 s->diff_context, s->ignore_whitespace, s->force_text_diff,
6503 dsa, s->repo, f);
6504 if (err != NULL)
6505 return err;
6506 break;
6507 case GOT_OBJ_TYPE_TREE:
6508 err = got_diff_objects_as_trees(lines, nlines,
6509 s->f1, s->f2, s->fd1, s->fd2, s->id1, s->id2,
6510 s->paths, "", "", tog_diff_algo, s->diff_context,
6511 s->ignore_whitespace, s->force_text_diff, dsa, s->repo, f);
6512 if (err != NULL)
6513 return err;
6514 break;
6515 case GOT_OBJ_TYPE_COMMIT: {
6516 const struct got_object_id_queue *parent_ids;
6517 struct got_commit_object *commit2;
6518 struct got_object_qid *pid;
6519 struct got_reflist_head *refs;
6521 err = got_diff_objects_as_commits(lines, nlines, s->f1, s->f2,
6522 s->fd1, s->fd2, s->id1, s->id2, s->paths, tog_diff_algo,
6523 s->diff_context, s->ignore_whitespace, s->force_text_diff,
6524 dsa, s->repo, f);
6525 if (err != NULL)
6526 return err;
6528 refs = got_reflist_object_id_map_lookup(tog_refs_idmap, s->id2);
6529 /* Show commit info if we're diffing to a parent/root commit. */
6530 if (s->id1 == NULL)
6531 return write_commit_info(&s->lines, &s->nlines, s->id2,
6532 refs, s->repo, s->ignore_whitespace,
6533 s->force_text_diff, dsa, s->f);
6535 err = got_object_open_as_commit(&commit2, s->repo,
6536 s->id2);
6537 if (err != NULL)
6538 return err;
6540 parent_ids = got_object_commit_get_parent_ids(commit2);
6541 STAILQ_FOREACH(pid, parent_ids, entry) {
6542 if (got_object_id_cmp(s->id1, &pid->id) == 0) {
6543 err = write_commit_info(&s->lines, &s->nlines,
6544 s->id2, refs, s->repo, s->ignore_whitespace,
6545 s->force_text_diff, dsa, s->f);
6546 break;
6549 if (commit2 != NULL)
6550 got_object_commit_close(commit2);
6551 if (err != NULL)
6552 return err;
6553 break;
6555 default:
6556 return got_error(GOT_ERR_OBJ_TYPE);
6559 return NULL;
6562 static const struct got_error *
6563 create_diff(struct tog_diff_view_state *s)
6565 const struct got_error *err = NULL;
6566 FILE *tmp_diff_file = NULL;
6567 struct got_diff_line *lines = NULL;
6568 struct got_pathlist_head changed_paths;
6569 struct got_diffstat_cb_arg dsa;
6570 size_t nlines = 0;
6572 RB_INIT(&changed_paths);
6573 memset(&dsa, 0, sizeof(dsa));
6574 dsa.paths = &changed_paths;
6575 dsa.diff_algo = tog_diff_algo;
6576 dsa.force_text = s->force_text_diff;
6577 dsa.ignore_ws = s->ignore_whitespace;
6579 free(s->lines);
6580 s->lines = malloc(sizeof(*s->lines));
6581 if (s->lines == NULL)
6582 return got_error_from_errno("malloc");
6583 s->nlines = 0;
6585 if (s->f && fclose(s->f) == EOF) {
6586 s->f = NULL;
6587 return got_error_from_errno("fclose");
6590 s->f = got_opentemp();
6591 if (s->f == NULL)
6592 return got_error_from_errno("got_opentemp");
6595 * The diffstat requires the diff to be built first, but we want the
6596 * diffstat to precede the diff when displayed. Build the diff first
6597 * in the temporary file and write the diffstat and/or commit info to
6598 * the persistent file (s->f) from which views are drawn, then append
6599 * the diff from the temp file to the diffstat/commit info in s->f.
6601 tmp_diff_file = got_opentemp();
6602 if (tmp_diff_file == NULL)
6603 return got_error_from_errno("got_opentemp");
6605 lines = malloc(sizeof(*lines));
6606 if (lines == NULL) {
6607 err = got_error_from_errno("malloc");
6608 goto done;
6611 if (s->parent_view != NULL && s->parent_view->type == TOG_VIEW_LOG) {
6612 struct tog_log_view_state *ls = &s->parent_view->state.log;
6613 struct commit_queue_entry *cqe = ls->selected_entry;
6615 if (cqe->worktree_entry != 0) {
6616 if (cqe->worktree_entry == TOG_WORKTREE_CHANGES_STAGED)
6617 s->diff_staged = 1;
6618 s->diff_worktree = 1;
6622 if (s->diff_worktree)
6623 err = tog_diff_worktree(s, tmp_diff_file,
6624 &lines, &nlines, &dsa);
6625 else
6626 err = tog_diff_objects(s, tmp_diff_file,
6627 &lines, &nlines, &dsa);
6628 if (err != NULL) {
6629 if (err->code != GOT_ERR_DIFF_NOCHANGES)
6630 goto done;
6631 } else {
6632 err = write_diffstat(s->f, &s->lines, &s->nlines, &dsa);
6633 if (err != NULL)
6634 goto done;
6637 err = cat_diff(s->f, tmp_diff_file, &s->lines, &s->nlines,
6638 lines, nlines);
6640 done:
6641 free(lines);
6642 got_pathlist_free(&changed_paths, GOT_PATHLIST_FREE_ALL);
6643 if (s->f && fflush(s->f) != 0 && err == NULL)
6644 err = got_error_from_errno("fflush");
6645 if (tmp_diff_file && fclose(tmp_diff_file) == EOF && err == NULL)
6646 err = got_error_from_errno("fclose");
6647 return err;
6650 static void
6651 diff_view_indicate_progress(struct tog_view *view)
6653 mvwaddstr(view->window, 0, 0, "diffing...");
6654 update_panels();
6655 doupdate();
6658 static const struct got_error *
6659 search_start_diff_view(struct tog_view *view)
6661 struct tog_diff_view_state *s = &view->state.diff;
6663 s->matched_line = 0;
6664 return NULL;
6667 static void
6668 search_setup_diff_view(struct tog_view *view, FILE **f, off_t **line_offsets,
6669 size_t *nlines, int **first, int **last, int **match, int **selected)
6671 struct tog_diff_view_state *s = &view->state.diff;
6673 *f = s->f;
6674 *nlines = s->nlines;
6675 *line_offsets = NULL;
6676 *match = &s->matched_line;
6677 *first = &s->first_displayed_line;
6678 *last = &s->last_displayed_line;
6679 *selected = &s->selected_line;
6682 static const struct got_error *
6683 search_next_view_match(struct tog_view *view)
6685 const struct got_error *err = NULL;
6686 FILE *f;
6687 int lineno;
6688 char *line = NULL;
6689 size_t linesize = 0;
6690 ssize_t linelen;
6691 off_t *line_offsets;
6692 size_t nlines = 0;
6693 int *first, *last, *match, *selected;
6695 if (!view->search_setup)
6696 return got_error_msg(GOT_ERR_NOT_IMPL,
6697 "view search not supported");
6698 view->search_setup(view, &f, &line_offsets, &nlines, &first, &last,
6699 &match, &selected);
6701 if (!view->searching) {
6702 view->search_next_done = TOG_SEARCH_HAVE_MORE;
6703 return NULL;
6706 if (*match) {
6707 if (view->searching == TOG_SEARCH_FORWARD)
6708 lineno = *first + 1;
6709 else
6710 lineno = *first - 1;
6711 } else
6712 lineno = *first - 1 + *selected;
6714 while (1) {
6715 off_t offset;
6717 if (lineno <= 0 || lineno > nlines) {
6718 if (*match == 0) {
6719 view->search_next_done = TOG_SEARCH_HAVE_MORE;
6720 break;
6723 if (view->searching == TOG_SEARCH_FORWARD)
6724 lineno = 1;
6725 else
6726 lineno = nlines;
6729 offset = view->type == TOG_VIEW_DIFF ?
6730 view->state.diff.lines[lineno - 1].offset :
6731 line_offsets[lineno - 1];
6732 if (fseeko(f, offset, SEEK_SET) != 0) {
6733 free(line);
6734 return got_error_from_errno("fseeko");
6736 linelen = getline(&line, &linesize, f);
6737 if (linelen != -1) {
6738 char *exstr;
6739 err = expand_tab(&exstr, line);
6740 if (err)
6741 break;
6742 if (match_line(exstr, &view->regex, 1,
6743 &view->regmatch)) {
6744 view->search_next_done = TOG_SEARCH_HAVE_MORE;
6745 *match = lineno;
6746 free(exstr);
6747 break;
6749 free(exstr);
6751 if (view->searching == TOG_SEARCH_FORWARD)
6752 lineno++;
6753 else
6754 lineno--;
6756 free(line);
6758 if (*match) {
6759 *first = *match;
6760 *selected = 1;
6763 return err;
6766 static const struct got_error *
6767 close_diff_view(struct tog_view *view)
6769 const struct got_error *err = NULL;
6770 struct tog_diff_view_state *s = &view->state.diff;
6772 free(s->id1);
6773 s->id1 = NULL;
6774 free(s->id2);
6775 s->id2 = NULL;
6776 free(s->action);
6777 s->action = NULL;
6778 if (s->f && fclose(s->f) == EOF)
6779 err = got_error_from_errno("fclose");
6780 s->f = NULL;
6781 if (s->f1 && fclose(s->f1) == EOF && err == NULL)
6782 err = got_error_from_errno("fclose");
6783 s->f1 = NULL;
6784 if (s->f2 && fclose(s->f2) == EOF && err == NULL)
6785 err = got_error_from_errno("fclose");
6786 s->f2 = NULL;
6787 if (s->fd1 != -1 && close(s->fd1) == -1 && err == NULL)
6788 err = got_error_from_errno("close");
6789 s->fd1 = -1;
6790 if (s->fd2 != -1 && close(s->fd2) == -1 && err == NULL)
6791 err = got_error_from_errno("close");
6792 s->fd2 = -1;
6793 free(s->lines);
6794 s->lines = NULL;
6795 s->nlines = 0;
6796 return err;
6799 static const struct got_error *
6800 open_diff_view(struct tog_view *view, struct got_object_id *id1,
6801 struct got_object_id *id2, const char *label1, const char *label2,
6802 int diff_context, int ignore_whitespace, int force_text_diff,
6803 int diff_staged, int diff_worktree, const char *worktree_root,
6804 struct tog_view *parent_view, struct got_repository *repo,
6805 struct got_pathlist_head *paths)
6807 const struct got_error *err;
6808 struct tog_diff_view_state *s = &view->state.diff;
6810 memset(s, 0, sizeof(*s));
6811 s->fd1 = -1;
6812 s->fd2 = -1;
6814 if (id1 != NULL && id2 != NULL) {
6815 int type1, type2;
6817 err = got_object_get_type(&type1, repo, id1);
6818 if (err)
6819 goto done;
6820 err = got_object_get_type(&type2, repo, id2);
6821 if (err)
6822 goto done;
6824 if (type1 != type2) {
6825 err = got_error(GOT_ERR_OBJ_TYPE);
6826 goto done;
6830 if (diff_worktree == 0) {
6831 if (id1) {
6832 s->id1 = got_object_id_dup(id1);
6833 if (s->id1 == NULL) {
6834 err = got_error_from_errno("got_object_id_dup");
6835 goto done;
6837 } else
6838 s->id1 = NULL;
6840 s->id2 = got_object_id_dup(id2);
6841 if (s->id2 == NULL) {
6842 err = got_error_from_errno("got_object_id_dup");
6843 goto done;
6847 s->f1 = got_opentemp();
6848 if (s->f1 == NULL) {
6849 err = got_error_from_errno("got_opentemp");
6850 goto done;
6853 s->f2 = got_opentemp();
6854 if (s->f2 == NULL) {
6855 err = got_error_from_errno("got_opentemp");
6856 goto done;
6859 s->fd1 = got_opentempfd();
6860 if (s->fd1 == -1) {
6861 err = got_error_from_errno("got_opentempfd");
6862 goto done;
6865 s->fd2 = got_opentempfd();
6866 if (s->fd2 == -1) {
6867 err = got_error_from_errno("got_opentempfd");
6868 goto done;
6871 s->first_displayed_line = 1;
6872 s->last_displayed_line = view->nlines;
6873 s->selected_line = 1;
6874 s->label1 = label1;
6875 s->label2 = label2;
6876 s->diff_context = diff_context;
6877 s->ignore_whitespace = ignore_whitespace;
6878 s->force_text_diff = force_text_diff;
6879 s->diff_worktree = diff_worktree;
6880 s->diff_staged = diff_staged;
6881 s->parent_view = parent_view;
6882 s->paths = paths;
6883 s->repo = repo;
6884 s->worktree_root = worktree_root;
6886 if (has_colors() && getenv("TOG_COLORS") != NULL && !using_mock_io) {
6887 int rc;
6889 rc = init_pair(GOT_DIFF_LINE_MINUS,
6890 get_color_value("TOG_COLOR_DIFF_MINUS"), -1);
6891 if (rc != ERR)
6892 rc = init_pair(GOT_DIFF_LINE_PLUS,
6893 get_color_value("TOG_COLOR_DIFF_PLUS"), -1);
6894 if (rc != ERR)
6895 rc = init_pair(GOT_DIFF_LINE_HUNK,
6896 get_color_value("TOG_COLOR_DIFF_CHUNK_HEADER"), -1);
6897 if (rc != ERR)
6898 rc = init_pair(GOT_DIFF_LINE_META,
6899 get_color_value("TOG_COLOR_DIFF_META"), -1);
6900 if (rc != ERR)
6901 rc = init_pair(GOT_DIFF_LINE_CHANGES,
6902 get_color_value("TOG_COLOR_DIFF_META"), -1);
6903 if (rc != ERR)
6904 rc = init_pair(GOT_DIFF_LINE_BLOB_MIN,
6905 get_color_value("TOG_COLOR_DIFF_META"), -1);
6906 if (rc != ERR)
6907 rc = init_pair(GOT_DIFF_LINE_BLOB_PLUS,
6908 get_color_value("TOG_COLOR_DIFF_META"), -1);
6909 if (rc != ERR)
6910 rc = init_pair(GOT_DIFF_LINE_AUTHOR,
6911 get_color_value("TOG_COLOR_AUTHOR"), -1);
6912 if (rc != ERR)
6913 rc = init_pair(GOT_DIFF_LINE_DATE,
6914 get_color_value("TOG_COLOR_DATE"), -1);
6915 if (rc == ERR) {
6916 err = got_error(GOT_ERR_RANGE);
6917 goto done;
6921 if (parent_view && parent_view->type == TOG_VIEW_LOG &&
6922 view_is_splitscreen(view)) {
6923 err = show_log_view(parent_view); /* draw border */
6924 if (err != NULL)
6925 goto done;
6927 diff_view_indicate_progress(view);
6929 err = create_diff(s);
6931 view->show = show_diff_view;
6932 view->input = input_diff_view;
6933 view->reset = reset_diff_view;
6934 view->close = close_diff_view;
6935 view->search_start = search_start_diff_view;
6936 view->search_setup = search_setup_diff_view;
6937 view->search_next = search_next_view_match;
6938 done:
6939 if (err) {
6940 if (view->close == NULL)
6941 close_diff_view(view);
6942 view_close(view);
6944 return err;
6947 static const struct got_error *
6948 show_diff_view(struct tog_view *view)
6950 const struct got_error *err;
6951 struct tog_diff_view_state *s = &view->state.diff;
6952 char *header;
6954 if (s->diff_worktree) {
6955 if (asprintf(&header, "diff %s%s",
6956 s->diff_staged ? "-s " : "", s->worktree_root) == -1)
6957 return got_error_from_errno("asprintf");
6958 } else {
6959 char *id_str2, *id_str1 = NULL;
6960 const char *label1, *label2;
6962 if (s->id1) {
6963 err = got_object_id_str(&id_str1, s->id1);
6964 if (err)
6965 return err;
6966 label1 = s->label1 ? s->label1 : id_str1;
6967 } else
6968 label1 = "/dev/null";
6970 err = got_object_id_str(&id_str2, s->id2);
6971 if (err)
6972 return err;
6973 label2 = s->label2 ? s->label2 : id_str2;
6975 if (asprintf(&header, "diff %s %s", label1, label2) == -1) {
6976 err = got_error_from_errno("asprintf");
6977 free(id_str1);
6978 free(id_str2);
6979 return err;
6981 free(id_str1);
6982 free(id_str2);
6985 err = draw_file(view, header);
6986 free(header);
6987 return err;
6990 static const struct got_error *
6991 diff_write_patch(struct tog_view *view)
6993 const struct got_error *err;
6994 struct tog_diff_view_state *s = &view->state.diff;
6995 struct got_object_id *id2 = s->id2;
6996 FILE *f = NULL;
6997 char buf[BUFSIZ], pathbase[PATH_MAX];
6998 char *idstr1, *idstr2 = NULL, *path = NULL;
6999 size_t r;
7000 off_t pos;
7001 int rc;
7003 if (s->action != NULL) {
7004 free(s->action);
7005 s->action = NULL;
7008 pos = ftello(s->f);
7009 if (pos == -1)
7010 return got_error_from_errno("ftello");
7011 if (fseeko(s->f, 0L, SEEK_SET) == -1)
7012 return got_error_from_errno("fseeko");
7014 if (s->id1 != NULL) {
7015 err = got_object_id_str(&idstr1, s->id1);
7016 if (err != NULL)
7017 return err;
7019 if (id2 == NULL) {
7020 if (s->diff_worktree == 0 || tog_base_commit.id == NULL) {
7021 /* illegal state that should not be possible */
7022 err = got_error(GOT_ERR_NOT_WORKTREE);
7023 goto done;
7025 id2 = tog_base_commit.id;
7027 err = got_object_id_str(&idstr2, id2);
7028 if (err != NULL)
7029 goto done;
7031 rc = snprintf(pathbase, sizeof(pathbase), "%s/tog-%.8s-%.8s",
7032 GOT_TMPDIR_STR, idstr1 != NULL ? idstr1 : "empty", idstr2);
7033 if (rc < 0 || (size_t)rc >= sizeof(pathbase)) {
7034 err = got_error(rc < 0 ? GOT_ERR_IO : GOT_ERR_NO_SPACE);
7035 goto done;
7038 err = got_opentemp_named(&path, &f, pathbase, ".diff");
7039 if (err != NULL)
7040 goto done;
7042 while ((r = fread(buf, 1, sizeof(buf), s->f)) > 0) {
7043 if (fwrite(buf, 1, r, f) != r) {
7044 err = got_ferror(f, GOT_ERR_IO);
7045 goto done;
7049 if (ferror(s->f)) {
7050 err = got_error_from_errno("fread");
7051 goto done;
7053 if (fseeko(s->f, pos, SEEK_SET) == -1) {
7054 err = got_error_from_errno("fseeko");
7055 goto done;
7058 if (fflush(f) == EOF) {
7059 err = got_error_from_errno2("fflush", path);
7060 goto done;
7063 if (asprintf(&s->action, "patch file written to %s", path) == -1) {
7064 err = got_error_from_errno("asprintf");
7065 goto done;
7068 view->action = s->action;
7070 done:
7071 if (f != NULL && fclose(f) == EOF && err == NULL)
7072 err = got_error_from_errno2("fclose", path);
7073 free(path);
7074 free(idstr1);
7075 free(idstr2);
7076 return err;
7079 static const struct got_error *
7080 set_selected_commit(struct tog_diff_view_state *s,
7081 struct commit_queue_entry *entry)
7083 const struct got_error *err;
7084 const struct got_object_id_queue *parent_ids;
7085 struct got_commit_object *selected_commit;
7086 struct got_object_qid *pid;
7088 free(s->id1);
7089 s->id1 = NULL;
7090 free(s->id2);
7091 s->id2 = NULL;
7093 if (entry->worktree_entry == 0) {
7094 s->id2 = got_object_id_dup(entry->id);
7095 if (s->id2 == NULL)
7096 return got_error_from_errno("got_object_id_dup");
7098 err = got_object_open_as_commit(&selected_commit,
7099 s->repo, entry->id);
7100 if (err)
7101 return err;
7102 parent_ids = got_object_commit_get_parent_ids(selected_commit);
7103 pid = STAILQ_FIRST(parent_ids);
7104 s->id1 = pid ? got_object_id_dup(&pid->id) : NULL;
7105 got_object_commit_close(selected_commit);
7108 return NULL;
7111 static const struct got_error *
7112 reset_diff_view(struct tog_view *view)
7114 struct tog_diff_view_state *s = &view->state.diff;
7116 view->count = 0;
7117 wclear(view->window);
7118 s->first_displayed_line = 1;
7119 s->last_displayed_line = view->nlines;
7120 s->matched_line = 0;
7121 if (s->action != NULL) {
7122 free(s->action);
7123 s->action = NULL;
7125 diff_view_indicate_progress(view);
7126 return create_diff(s);
7129 static void
7130 diff_prev_index(struct tog_diff_view_state *s, enum got_diff_line_type type)
7132 int start, i;
7134 i = start = s->first_displayed_line - 1;
7136 while (s->lines[i].type != type) {
7137 if (i == 0)
7138 i = s->nlines - 1;
7139 if (--i == start)
7140 return; /* do nothing, requested type not in file */
7143 s->selected_line = 1;
7144 s->first_displayed_line = i;
7147 static void
7148 diff_next_index(struct tog_diff_view_state *s, enum got_diff_line_type type)
7150 int start, i;
7152 i = start = s->first_displayed_line + 1;
7154 while (s->lines[i].type != type) {
7155 if (i == s->nlines - 1)
7156 i = 0;
7157 if (++i == start)
7158 return; /* do nothing, requested type not in file */
7161 s->selected_line = 1;
7162 s->first_displayed_line = i;
7165 static struct got_object_id *get_selected_commit_id(struct tog_blame_line *,
7166 int, int, int);
7167 static struct got_object_id *get_annotation_for_line(struct tog_blame_line *,
7168 int, int);
7170 static const struct got_error *
7171 input_diff_view(struct tog_view **new_view, struct tog_view *view, int ch)
7173 const struct got_error *err = NULL;
7174 struct tog_diff_view_state *s = &view->state.diff;
7175 struct tog_log_view_state *ls;
7176 struct commit_queue_entry *old_selected_entry;
7177 char *line = NULL;
7178 size_t linesize = 0;
7179 ssize_t linelen;
7180 int i, nscroll = view->nlines - 1, up = 0;
7182 s->lineno = s->first_displayed_line - 1 + s->selected_line;
7184 if (s->action != NULL && ch != ERR) {
7185 free(s->action);
7186 s->action = NULL;
7187 view->action = NULL;
7190 switch (ch) {
7191 case '0':
7192 case '$':
7193 case KEY_RIGHT:
7194 case 'l':
7195 case KEY_LEFT:
7196 case 'h':
7197 horizontal_scroll_input(view, ch);
7198 break;
7199 case 'a':
7200 case 'w':
7201 if (ch == 'a') {
7202 s->force_text_diff = !s->force_text_diff;
7203 view->action = s->force_text_diff ?
7204 "force ASCII text enabled" :
7205 "force ASCII text disabled";
7207 else if (ch == 'w') {
7208 s->ignore_whitespace = !s->ignore_whitespace;
7209 view->action = s->ignore_whitespace ?
7210 "ignore whitespace enabled" :
7211 "ignore whitespace disabled";
7213 err = reset_diff_view(view);
7214 break;
7215 case 'g':
7216 case KEY_HOME:
7217 s->first_displayed_line = 1;
7218 view->count = 0;
7219 break;
7220 case 'G':
7221 case KEY_END:
7222 view->count = 0;
7223 if (s->eof)
7224 break;
7226 s->first_displayed_line = (s->nlines - view->nlines) + 2;
7227 s->eof = 1;
7228 break;
7229 case 'k':
7230 case KEY_UP:
7231 case CTRL('p'):
7232 if (s->first_displayed_line > 1)
7233 s->first_displayed_line--;
7234 else
7235 view->count = 0;
7236 break;
7237 case CTRL('u'):
7238 case 'u':
7239 nscroll /= 2;
7240 /* FALL THROUGH */
7241 case KEY_PPAGE:
7242 case CTRL('b'):
7243 case 'b':
7244 if (s->first_displayed_line == 1) {
7245 view->count = 0;
7246 break;
7248 i = 0;
7249 while (i++ < nscroll && s->first_displayed_line > 1)
7250 s->first_displayed_line--;
7251 break;
7252 case 'j':
7253 case KEY_DOWN:
7254 case CTRL('n'):
7255 if (!s->eof)
7256 s->first_displayed_line++;
7257 else
7258 view->count = 0;
7259 break;
7260 case CTRL('d'):
7261 case 'd':
7262 nscroll /= 2;
7263 /* FALL THROUGH */
7264 case KEY_NPAGE:
7265 case CTRL('f'):
7266 case 'f':
7267 case ' ':
7268 if (s->eof) {
7269 view->count = 0;
7270 break;
7272 i = 0;
7273 while (!s->eof && i++ < nscroll) {
7274 linelen = getline(&line, &linesize, s->f);
7275 s->first_displayed_line++;
7276 if (linelen == -1) {
7277 if (feof(s->f)) {
7278 s->eof = 1;
7279 } else
7280 err = got_ferror(s->f, GOT_ERR_IO);
7281 break;
7284 free(line);
7285 break;
7286 case '(':
7287 diff_prev_index(s, GOT_DIFF_LINE_BLOB_MIN);
7288 break;
7289 case ')':
7290 diff_next_index(s, GOT_DIFF_LINE_BLOB_MIN);
7291 break;
7292 case '{':
7293 diff_prev_index(s, GOT_DIFF_LINE_HUNK);
7294 break;
7295 case '}':
7296 diff_next_index(s, GOT_DIFF_LINE_HUNK);
7297 break;
7298 case '[':
7299 if (s->diff_context > 0) {
7300 s->diff_context--;
7301 s->matched_line = 0;
7302 diff_view_indicate_progress(view);
7303 err = create_diff(s);
7304 if (s->first_displayed_line + view->nlines - 1 >
7305 s->nlines) {
7306 s->first_displayed_line = 1;
7307 s->last_displayed_line = view->nlines;
7309 } else
7310 view->count = 0;
7311 break;
7312 case ']':
7313 if (s->diff_context < GOT_DIFF_MAX_CONTEXT) {
7314 s->diff_context++;
7315 s->matched_line = 0;
7316 diff_view_indicate_progress(view);
7317 err = create_diff(s);
7318 } else
7319 view->count = 0;
7320 break;
7321 case '<':
7322 case ',':
7323 case 'K':
7324 up = 1;
7325 /* FALL THROUGH */
7326 case '>':
7327 case '.':
7328 case 'J':
7329 if (s->parent_view == NULL) {
7330 view->count = 0;
7331 break;
7333 s->parent_view->count = view->count;
7335 if (s->parent_view->type == TOG_VIEW_LOG) {
7336 ls = &s->parent_view->state.log;
7337 old_selected_entry = ls->selected_entry;
7339 err = input_log_view(NULL, s->parent_view,
7340 up ? KEY_UP : KEY_DOWN);
7341 if (err)
7342 break;
7343 view->count = s->parent_view->count;
7345 if (old_selected_entry == ls->selected_entry)
7346 break;
7348 log_mark_clear(ls);
7350 err = set_selected_commit(s, ls->selected_entry);
7351 if (err)
7352 break;
7354 if (s->worktree_root == NULL)
7355 s->worktree_root = ls->thread_args.wctx.wt_root;
7356 } else if (s->parent_view->type == TOG_VIEW_BLAME) {
7357 struct tog_blame_view_state *bs;
7358 struct got_object_id *id, *prev_id;
7360 bs = &s->parent_view->state.blame;
7361 prev_id = get_annotation_for_line(bs->blame.lines,
7362 bs->blame.nlines, bs->last_diffed_line);
7364 err = input_blame_view(&view, s->parent_view,
7365 up ? KEY_UP : KEY_DOWN);
7366 if (err)
7367 break;
7368 view->count = s->parent_view->count;
7370 if (prev_id == NULL)
7371 break;
7372 id = get_selected_commit_id(bs->blame.lines,
7373 bs->blame.nlines, bs->first_displayed_line,
7374 bs->selected_line);
7375 if (id == NULL)
7376 break;
7378 if (!got_object_id_cmp(prev_id, id))
7379 break;
7381 err = input_blame_view(&view, s->parent_view, KEY_ENTER);
7382 if (err)
7383 break;
7385 s->diff_staged = 0;
7386 s->diff_worktree = 0;
7387 s->first_displayed_line = 1;
7388 s->last_displayed_line = view->nlines;
7389 s->matched_line = 0;
7390 view->x = 0;
7392 diff_view_indicate_progress(view);
7393 err = create_diff(s);
7394 break;
7395 case 'p':
7396 err = diff_write_patch(view);
7397 break;
7398 default:
7399 view->count = 0;
7400 break;
7403 return err;
7406 static const struct got_error *
7407 get_worktree_paths_from_argv(struct got_pathlist_head *paths, int argc,
7408 char *argv[], struct got_worktree *worktree)
7410 const struct got_error *err = NULL;
7411 char *path;
7412 struct got_pathlist_entry *new;
7413 int i;
7415 if (argc == 0) {
7416 path = strdup("");
7417 if (path == NULL)
7418 return got_error_from_errno("strdup");
7419 return got_pathlist_insert(NULL, paths, path, NULL);
7422 for (i = 0; i < argc; i++) {
7423 err = got_worktree_resolve_path(&path, worktree, argv[i]);
7424 if (err)
7425 break;
7426 err = got_pathlist_insert(&new, paths, path, NULL);
7427 if (err != NULL || new == NULL) {
7428 free(path);
7429 if (err != NULL)
7430 break;
7434 return err;
7437 static const struct got_error *
7438 cmd_diff(int argc, char *argv[])
7440 const struct got_error *error;
7441 struct got_repository *repo = NULL;
7442 struct got_worktree *worktree = NULL;
7443 struct got_pathlist_head paths;
7444 struct got_object_id *ids[2] = { NULL, NULL };
7445 const char *commit_args[2] = { NULL, NULL };
7446 char *labels[2] = { NULL, NULL };
7447 char *repo_path = NULL, *worktree_path = NULL, *cwd = NULL;
7448 int type1 = GOT_OBJ_TYPE_ANY, type2 = GOT_OBJ_TYPE_ANY;
7449 int i, ncommit_args = 0, diff_context = 3, ignore_whitespace = 0;
7450 int ch, diff_staged = 0, diff_worktree = 0, force_text_diff = 0;
7451 const char *errstr;
7452 struct tog_view *view;
7453 int *pack_fds = NULL;
7455 RB_INIT(&paths);
7457 while ((ch = getopt(argc, argv, "aC:c:r:sw")) != -1) {
7458 switch (ch) {
7459 case 'a':
7460 force_text_diff = 1;
7461 break;
7462 case 'C':
7463 diff_context = strtonum(optarg, 0, GOT_DIFF_MAX_CONTEXT,
7464 &errstr);
7465 if (errstr != NULL)
7466 errx(1, "number of context lines is %s: %s",
7467 errstr, errstr);
7468 break;
7469 case 'c':
7470 if (ncommit_args >= 2)
7471 errx(1, "too many -c options used");
7472 commit_args[ncommit_args++] = optarg;
7473 break;
7474 case 'r':
7475 repo_path = realpath(optarg, NULL);
7476 if (repo_path == NULL)
7477 return got_error_from_errno2("realpath",
7478 optarg);
7479 got_path_strip_trailing_slashes(repo_path);
7480 break;
7481 case 's':
7482 diff_staged = 1;
7483 break;
7484 case 'w':
7485 ignore_whitespace = 1;
7486 break;
7487 default:
7488 usage_diff();
7489 /* NOTREACHED */
7493 argc -= optind;
7494 argv += optind;
7496 error = got_repo_pack_fds_open(&pack_fds);
7497 if (error)
7498 goto done;
7500 if (repo_path == NULL) {
7501 cwd = getcwd(NULL, 0);
7502 if (cwd == NULL)
7503 return got_error_from_errno("getcwd");
7504 error = got_worktree_open(&worktree, cwd, NULL);
7505 if (error && error->code != GOT_ERR_NOT_WORKTREE)
7506 goto done;
7507 if (worktree)
7508 repo_path =
7509 strdup(got_worktree_get_repo_path(worktree));
7510 else
7511 repo_path = strdup(cwd);
7512 if (repo_path == NULL) {
7513 error = got_error_from_errno("strdup");
7514 goto done;
7518 error = got_repo_open(&repo, repo_path, NULL, pack_fds);
7519 if (error)
7520 goto done;
7522 if (diff_staged && (worktree == NULL || ncommit_args > 0)) {
7523 error = got_error_msg(GOT_ERR_BAD_OPTION,
7524 "-s can only be used when diffing a work tree");
7525 goto done;
7528 init_curses();
7530 error = apply_unveil(got_repo_get_path(repo),
7531 worktree != NULL ? got_worktree_get_root_path(worktree) : NULL);
7532 if (error)
7533 goto done;
7535 if (argc == 2 || ncommit_args > 0) {
7536 int obj_type = (ncommit_args > 0 ?
7537 GOT_OBJ_TYPE_COMMIT : GOT_OBJ_TYPE_ANY);
7539 error = tog_load_refs(repo, 0);
7540 if (error != NULL)
7541 goto done;
7543 for (i = 0; i < (ncommit_args > 0 ? ncommit_args : argc); ++i) {
7544 const char *arg;
7545 char *keyword_idstr = NULL;
7547 if (ncommit_args > 0)
7548 arg = commit_args[i];
7549 else
7550 arg = argv[i];
7552 error = got_keyword_to_idstr(&keyword_idstr, arg,
7553 repo, worktree);
7554 if (error != NULL)
7555 goto done;
7556 if (keyword_idstr != NULL)
7557 arg = keyword_idstr;
7559 error = got_repo_match_object_id(&ids[i], &labels[i],
7560 arg, obj_type, &tog_refs, repo);
7561 free(keyword_idstr);
7562 if (error != NULL) {
7563 if (error->code != GOT_ERR_NOT_REF &&
7564 error->code != GOT_ERR_NO_OBJ)
7565 goto done;
7566 if (ncommit_args > 0)
7567 goto done;
7568 error = NULL;
7569 break;
7574 if (diff_staged && ids[0] != NULL) {
7575 error = got_error_msg(GOT_ERR_BAD_OPTION,
7576 "-s can only be used when diffing a work tree");
7577 goto done;
7580 if (ncommit_args == 0 && (ids[0] == NULL || ids[1] == NULL)) {
7581 if (worktree == NULL) {
7582 if (argc == 2 && ids[0] == NULL) {
7583 error = got_error_path(argv[0], GOT_ERR_NO_OBJ);
7584 goto done;
7585 } else if (argc == 2 && ids[1] == NULL) {
7586 error = got_error_path(argv[1], GOT_ERR_NO_OBJ);
7587 goto done;
7588 } else if (argc > 0) {
7589 error = got_error_fmt(GOT_ERR_NOT_WORKTREE,
7590 "%s", "specified paths cannot be resolved");
7591 goto done;
7592 } else {
7593 error = got_error(GOT_ERR_NOT_WORKTREE);
7594 goto done;
7598 error = get_worktree_paths_from_argv(&paths, argc, argv,
7599 worktree);
7600 if (error != NULL)
7601 goto done;
7603 worktree_path = strdup(got_worktree_get_root_path(worktree));
7604 if (worktree_path == NULL) {
7605 error = got_error_from_errno("strdup");
7606 goto done;
7608 diff_worktree = 1;
7611 if (ncommit_args == 1) { /* diff commit against its first parent */
7612 struct got_commit_object *commit;
7614 error = got_object_open_as_commit(&commit, repo, ids[0]);
7615 if (error != NULL)
7616 goto done;
7618 labels[1] = labels[0];
7619 ids[1] = ids[0];
7620 if (got_object_commit_get_nparents(commit) > 0) {
7621 const struct got_object_id_queue *pids;
7622 struct got_object_qid *pid;
7624 pids = got_object_commit_get_parent_ids(commit);
7625 pid = STAILQ_FIRST(pids);
7626 ids[0] = got_object_id_dup(&pid->id);
7627 if (ids[0] == NULL) {
7628 error = got_error_from_errno(
7629 "got_object_id_dup");
7630 got_object_commit_close(commit);
7631 goto done;
7633 error = got_object_id_str(&labels[0], ids[0]);
7634 if (error != NULL) {
7635 got_object_commit_close(commit);
7636 goto done;
7638 } else {
7639 ids[0] = NULL;
7640 labels[0] = strdup("/dev/null");
7641 if (labels[0] == NULL) {
7642 error = got_error_from_errno("strdup");
7643 got_object_commit_close(commit);
7644 goto done;
7648 got_object_commit_close(commit);
7651 if (ncommit_args == 0 && argc > 2) {
7652 error = got_error_msg(GOT_ERR_BAD_PATH,
7653 "path arguments cannot be used when diffing two objects");
7654 goto done;
7657 if (ids[0]) {
7658 error = got_object_get_type(&type1, repo, ids[0]);
7659 if (error != NULL)
7660 goto done;
7663 if (diff_worktree == 0) {
7664 error = got_object_get_type(&type2, repo, ids[1]);
7665 if (error != NULL)
7666 goto done;
7667 if (type1 != GOT_OBJ_TYPE_ANY && type1 != type2) {
7668 error = got_error(GOT_ERR_OBJ_TYPE);
7669 goto done;
7671 if (type1 == GOT_OBJ_TYPE_BLOB && argc > 2) {
7672 error = got_error_msg(GOT_ERR_OBJ_TYPE,
7673 "path arguments cannot be used when diffing blobs");
7674 goto done;
7678 for (i = 0; ncommit_args > 0 && i < argc; i++) {
7679 char *in_repo_path;
7680 struct got_pathlist_entry *new;
7682 if (worktree) {
7683 const char *prefix;
7684 char *p;
7686 error = got_worktree_resolve_path(&p, worktree,
7687 argv[i]);
7688 if (error != NULL)
7689 goto done;
7690 prefix = got_worktree_get_path_prefix(worktree);
7691 while (prefix[0] == '/')
7692 prefix++;
7693 if (asprintf(&in_repo_path, "%s%s%s", prefix,
7694 (p[0] != '\0' && prefix[0] != '\0') ? "/" : "",
7695 p) == -1) {
7696 error = got_error_from_errno("asprintf");
7697 free(p);
7698 goto done;
7700 free(p);
7701 } else {
7702 char *mapped_path, *s;
7704 error = got_repo_map_path(&mapped_path, repo, argv[i]);
7705 if (error != NULL)
7706 goto done;
7707 s = mapped_path;
7708 while (s[0] == '/')
7709 s++;
7710 in_repo_path = strdup(s);
7711 if (in_repo_path == NULL) {
7712 error = got_error_from_errno("asprintf");
7713 free(mapped_path);
7714 goto done;
7716 free(mapped_path);
7719 error = got_pathlist_insert(&new, &paths, in_repo_path, NULL);
7720 if (error != NULL || new == NULL)
7721 free(in_repo_path);
7722 if (error != NULL)
7723 goto done;
7726 view = view_open(0, 0, 0, 0, TOG_VIEW_DIFF);
7727 if (view == NULL) {
7728 error = got_error_from_errno("view_open");
7729 goto done;
7732 if (worktree) {
7733 error = set_tog_base_commit(repo, worktree);
7734 if (error != NULL)
7735 goto done;
7737 /* Release work tree lock. */
7738 got_worktree_close(worktree);
7739 worktree = NULL;
7742 error = open_diff_view(view, ids[0], ids[1], labels[0], labels[1],
7743 diff_context, ignore_whitespace, force_text_diff, diff_staged,
7744 diff_worktree, worktree_path, NULL, repo, &paths);
7745 if (error)
7746 goto done;
7748 error = view_loop(view);
7750 done:
7751 got_pathlist_free(&paths, GOT_PATHLIST_FREE_PATH);
7752 free(tog_base_commit.id);
7753 free(worktree_path);
7754 free(repo_path);
7755 free(labels[0]);
7756 free(labels[1]);
7757 free(ids[0]);
7758 free(ids[1]);
7759 free(cwd);
7760 if (repo) {
7761 const struct got_error *close_err = got_repo_close(repo);
7762 if (error == NULL)
7763 error = close_err;
7765 if (worktree)
7766 got_worktree_close(worktree);
7767 if (pack_fds) {
7768 const struct got_error *pack_err =
7769 got_repo_pack_fds_close(pack_fds);
7770 if (error == NULL)
7771 error = pack_err;
7773 tog_free_refs();
7774 return error;
7777 __dead static void
7778 usage_blame(void)
7780 endwin();
7781 fprintf(stderr,
7782 "usage: %s blame [-c commit] [-r repository-path] path\n",
7783 getprogname());
7784 exit(1);
7787 struct tog_blame_line {
7788 int annotated;
7789 struct got_object_id *id;
7792 static const struct got_error *
7793 draw_blame(struct tog_view *view)
7795 struct tog_blame_view_state *s = &view->state.blame;
7796 struct tog_blame *blame = &s->blame;
7797 regmatch_t *regmatch = &view->regmatch;
7798 const struct got_error *err;
7799 int lineno = 0, nprinted = 0;
7800 char *line = NULL;
7801 size_t linesize = 0;
7802 ssize_t linelen;
7803 wchar_t *wline;
7804 int width;
7805 struct tog_blame_line *blame_line;
7806 struct got_object_id *prev_id = NULL;
7807 char *id_str;
7808 struct tog_color *tc;
7810 err = got_object_id_str(&id_str, &s->blamed_commit->id);
7811 if (err)
7812 return err;
7814 rewind(blame->f);
7815 werase(view->window);
7817 if (asprintf(&line, "commit %s", id_str) == -1) {
7818 err = got_error_from_errno("asprintf");
7819 free(id_str);
7820 return err;
7823 err = format_line(&wline, &width, NULL, line, 0, view->ncols, 0, 0);
7824 free(line);
7825 line = NULL;
7826 if (err)
7827 return err;
7828 if (view_needs_focus_indication(view))
7829 wstandout(view->window);
7830 tc = get_color(&s->colors, TOG_COLOR_COMMIT);
7831 if (tc)
7832 wattr_on(view->window, COLOR_PAIR(tc->colorpair), NULL);
7833 waddwstr(view->window, wline);
7834 while (width++ < view->ncols)
7835 waddch(view->window, ' ');
7836 if (tc)
7837 wattr_off(view->window, COLOR_PAIR(tc->colorpair), NULL);
7838 if (view_needs_focus_indication(view))
7839 wstandend(view->window);
7840 free(wline);
7841 wline = NULL;
7843 if (view->gline > blame->nlines)
7844 view->gline = blame->nlines;
7846 if (tog_io.wait_for_ui) {
7847 struct tog_blame_thread_args *bta = &s->blame.thread_args;
7848 int rc;
7850 rc = pthread_cond_wait(&bta->blame_complete, &tog_mutex);
7851 if (rc)
7852 return got_error_set_errno(rc, "pthread_cond_wait");
7853 tog_io.wait_for_ui = 0;
7856 if (asprintf(&line, "[%d/%d] %s%s", view->gline ? view->gline :
7857 s->first_displayed_line - 1 + s->selected_line, blame->nlines,
7858 s->blame_complete ? "" : "annotating... ", s->path) == -1) {
7859 free(id_str);
7860 return got_error_from_errno("asprintf");
7862 free(id_str);
7863 err = format_line(&wline, &width, NULL, line, 0, view->ncols, 0, 0);
7864 free(line);
7865 line = NULL;
7866 if (err)
7867 return err;
7868 waddwstr(view->window, wline);
7869 free(wline);
7870 wline = NULL;
7871 if (width < view->ncols - 1)
7872 waddch(view->window, '\n');
7874 s->eof = 0;
7875 view->maxx = 0;
7876 while (nprinted < view->nlines - 2) {
7877 linelen = getline(&line, &linesize, blame->f);
7878 if (linelen == -1) {
7879 if (feof(blame->f)) {
7880 s->eof = 1;
7881 break;
7883 free(line);
7884 return got_ferror(blame->f, GOT_ERR_IO);
7886 if (++lineno < s->first_displayed_line)
7887 continue;
7888 if (view->gline && !gotoline(view, &lineno, &nprinted))
7889 continue;
7891 /* Set view->maxx based on full line length. */
7892 err = format_line(&wline, &width, NULL, line, 0, INT_MAX, 9, 1);
7893 if (err) {
7894 free(line);
7895 return err;
7897 free(wline);
7898 wline = NULL;
7899 view->maxx = MAX(view->maxx, width);
7901 if (nprinted == s->selected_line - 1)
7902 wstandout(view->window);
7904 if (blame->nlines > 0) {
7905 blame_line = &blame->lines[lineno - 1];
7906 if (blame_line->annotated && prev_id &&
7907 got_object_id_cmp(prev_id, blame_line->id) == 0 &&
7908 !(nprinted == s->selected_line - 1)) {
7909 waddstr(view->window, " ");
7910 } else if (blame_line->annotated) {
7911 char *id_str;
7912 err = got_object_id_str(&id_str,
7913 blame_line->id);
7914 if (err) {
7915 free(line);
7916 return err;
7918 tc = get_color(&s->colors, TOG_COLOR_COMMIT);
7919 if (tc)
7920 wattr_on(view->window,
7921 COLOR_PAIR(tc->colorpair), NULL);
7922 wprintw(view->window, "%.8s", id_str);
7923 if (tc)
7924 wattr_off(view->window,
7925 COLOR_PAIR(tc->colorpair), NULL);
7926 free(id_str);
7927 prev_id = blame_line->id;
7928 } else {
7929 waddstr(view->window, "........");
7930 prev_id = NULL;
7932 } else {
7933 waddstr(view->window, "........");
7934 prev_id = NULL;
7937 if (nprinted == s->selected_line - 1)
7938 wstandend(view->window);
7939 waddstr(view->window, " ");
7941 if (view->ncols <= 9) {
7942 width = 9;
7943 } else if (s->first_displayed_line + nprinted ==
7944 s->matched_line &&
7945 regmatch->rm_so >= 0 && regmatch->rm_so < regmatch->rm_eo) {
7946 err = add_matched_line(&width, line, view->ncols - 9, 9,
7947 view->window, view->x, regmatch);
7948 if (err) {
7949 free(line);
7950 return err;
7952 width += 9;
7953 } else {
7954 int skip;
7955 err = format_line(&wline, &width, &skip, line,
7956 view->x, view->ncols - 9, 9, 1);
7957 if (err) {
7958 free(line);
7959 return err;
7961 waddwstr(view->window, &wline[skip]);
7962 width += 9;
7963 free(wline);
7964 wline = NULL;
7967 if (width <= view->ncols - 1)
7968 waddch(view->window, '\n');
7969 if (++nprinted == 1)
7970 s->first_displayed_line = lineno;
7972 free(line);
7973 s->last_displayed_line = lineno;
7975 view_border(view);
7977 return NULL;
7980 static const struct got_error *
7981 blame_cb(void *arg, int nlines, int lineno,
7982 struct got_commit_object *commit, struct got_object_id *id)
7984 const struct got_error *err = NULL;
7985 struct tog_blame_cb_args *a = arg;
7986 struct tog_blame_line *line;
7987 int errcode;
7989 if (nlines != a->nlines ||
7990 (lineno != -1 && lineno < 1) || lineno > a->nlines)
7991 return got_error(GOT_ERR_RANGE);
7993 errcode = pthread_mutex_lock(&tog_mutex);
7994 if (errcode)
7995 return got_error_set_errno(errcode, "pthread_mutex_lock");
7997 if (*a->quit) { /* user has quit the blame view */
7998 err = got_error(GOT_ERR_ITER_COMPLETED);
7999 goto done;
8002 if (lineno == -1)
8003 goto done; /* no change in this commit */
8005 line = &a->lines[lineno - 1];
8006 if (line->annotated)
8007 goto done;
8009 line->id = got_object_id_dup(id);
8010 if (line->id == NULL) {
8011 err = got_error_from_errno("got_object_id_dup");
8012 goto done;
8014 line->annotated = 1;
8015 done:
8016 errcode = pthread_mutex_unlock(&tog_mutex);
8017 if (errcode)
8018 err = got_error_set_errno(errcode, "pthread_mutex_unlock");
8019 return err;
8022 static void *
8023 blame_thread(void *arg)
8025 const struct got_error *err, *close_err;
8026 struct tog_blame_thread_args *ta = arg;
8027 struct tog_blame_cb_args *a = ta->cb_args;
8028 int errcode, fd1 = -1, fd2 = -1;
8029 FILE *f1 = NULL, *f2 = NULL;
8031 fd1 = got_opentempfd();
8032 if (fd1 == -1)
8033 return (void *)got_error_from_errno("got_opentempfd");
8035 fd2 = got_opentempfd();
8036 if (fd2 == -1) {
8037 err = got_error_from_errno("got_opentempfd");
8038 goto done;
8041 f1 = got_opentemp();
8042 if (f1 == NULL) {
8043 err = (void *)got_error_from_errno("got_opentemp");
8044 goto done;
8046 f2 = got_opentemp();
8047 if (f2 == NULL) {
8048 err = (void *)got_error_from_errno("got_opentemp");
8049 goto done;
8052 err = block_signals_used_by_main_thread();
8053 if (err)
8054 goto done;
8056 err = got_blame(ta->path, a->commit_id, ta->repo,
8057 tog_diff_algo, blame_cb, ta->cb_args,
8058 ta->cancel_cb, ta->cancel_arg, fd1, fd2, f1, f2);
8059 if (err && err->code == GOT_ERR_CANCELLED)
8060 err = NULL;
8062 errcode = pthread_mutex_lock(&tog_mutex);
8063 if (errcode) {
8064 err = got_error_set_errno(errcode, "pthread_mutex_lock");
8065 goto done;
8068 close_err = got_repo_close(ta->repo);
8069 if (err == NULL)
8070 err = close_err;
8071 ta->repo = NULL;
8072 *ta->complete = 1;
8074 if (tog_io.wait_for_ui) {
8075 errcode = pthread_cond_signal(&ta->blame_complete);
8076 if (errcode && err == NULL)
8077 err = got_error_set_errno(errcode,
8078 "pthread_cond_signal");
8081 errcode = pthread_mutex_unlock(&tog_mutex);
8082 if (errcode && err == NULL)
8083 err = got_error_set_errno(errcode, "pthread_mutex_unlock");
8085 done:
8086 if (fd1 != -1 && close(fd1) == -1 && err == NULL)
8087 err = got_error_from_errno("close");
8088 if (fd2 != -1 && close(fd2) == -1 && err == NULL)
8089 err = got_error_from_errno("close");
8090 if (f1 && fclose(f1) == EOF && err == NULL)
8091 err = got_error_from_errno("fclose");
8092 if (f2 && fclose(f2) == EOF && err == NULL)
8093 err = got_error_from_errno("fclose");
8095 return (void *)err;
8098 static struct got_object_id *
8099 get_selected_commit_id(struct tog_blame_line *lines, int nlines,
8100 int first_displayed_line, int selected_line)
8102 struct tog_blame_line *line;
8104 if (nlines <= 0)
8105 return NULL;
8107 line = &lines[first_displayed_line - 1 + selected_line - 1];
8108 if (!line->annotated)
8109 return NULL;
8111 return line->id;
8114 static struct got_object_id *
8115 get_annotation_for_line(struct tog_blame_line *lines, int nlines,
8116 int lineno)
8118 struct tog_blame_line *line;
8120 if (nlines <= 0 || lineno >= nlines)
8121 return NULL;
8123 line = &lines[lineno - 1];
8124 if (!line->annotated)
8125 return NULL;
8127 return line->id;
8130 static const struct got_error *
8131 stop_blame(struct tog_blame *blame)
8133 const struct got_error *err = NULL;
8134 int i;
8136 if (blame->thread) {
8137 int errcode;
8138 errcode = pthread_mutex_unlock(&tog_mutex);
8139 if (errcode)
8140 return got_error_set_errno(errcode,
8141 "pthread_mutex_unlock");
8142 errcode = pthread_join(blame->thread, (void **)&err);
8143 if (errcode)
8144 return got_error_set_errno(errcode, "pthread_join");
8145 errcode = pthread_mutex_lock(&tog_mutex);
8146 if (errcode)
8147 return got_error_set_errno(errcode,
8148 "pthread_mutex_lock");
8149 if (err && err->code == GOT_ERR_ITER_COMPLETED)
8150 err = NULL;
8151 blame->thread = 0; //NULL;
8153 if (blame->thread_args.repo) {
8154 const struct got_error *close_err;
8155 close_err = got_repo_close(blame->thread_args.repo);
8156 if (err == NULL)
8157 err = close_err;
8158 blame->thread_args.repo = NULL;
8160 if (blame->f) {
8161 if (fclose(blame->f) == EOF && err == NULL)
8162 err = got_error_from_errno("fclose");
8163 blame->f = NULL;
8165 if (blame->lines) {
8166 for (i = 0; i < blame->nlines; i++)
8167 free(blame->lines[i].id);
8168 free(blame->lines);
8169 blame->lines = NULL;
8171 free(blame->cb_args.commit_id);
8172 blame->cb_args.commit_id = NULL;
8173 if (blame->pack_fds) {
8174 const struct got_error *pack_err =
8175 got_repo_pack_fds_close(blame->pack_fds);
8176 if (err == NULL)
8177 err = pack_err;
8178 blame->pack_fds = NULL;
8180 free(blame->line_offsets);
8181 blame->line_offsets = NULL;
8182 return err;
8185 static const struct got_error *
8186 cancel_blame_view(void *arg)
8188 const struct got_error *err = NULL;
8189 int *done = arg;
8190 int errcode;
8192 errcode = pthread_mutex_lock(&tog_mutex);
8193 if (errcode)
8194 return got_error_set_errno(errcode, "pthread_mutex_lock");
8196 if (*done)
8197 err = got_error(GOT_ERR_CANCELLED);
8199 errcode = pthread_mutex_unlock(&tog_mutex);
8200 if (errcode)
8201 return got_error_set_errno(errcode, "pthread_mutex_unlock");
8203 return err;
8206 static const struct got_error *
8207 run_blame(struct tog_view *view)
8209 struct tog_blame_view_state *s = &view->state.blame;
8210 struct tog_blame *blame = &s->blame;
8211 const struct got_error *err = NULL;
8212 struct got_commit_object *commit = NULL;
8213 struct got_blob_object *blob = NULL;
8214 struct got_repository *thread_repo = NULL;
8215 struct got_object_id *obj_id = NULL;
8216 int obj_type, fd = -1;
8217 int *pack_fds = NULL;
8219 err = got_object_open_as_commit(&commit, s->repo,
8220 &s->blamed_commit->id);
8221 if (err)
8222 return err;
8224 fd = got_opentempfd();
8225 if (fd == -1) {
8226 err = got_error_from_errno("got_opentempfd");
8227 goto done;
8230 err = got_object_id_by_path(&obj_id, s->repo, commit, s->path);
8231 if (err)
8232 goto done;
8234 err = got_object_get_type(&obj_type, s->repo, obj_id);
8235 if (err)
8236 goto done;
8238 if (obj_type != GOT_OBJ_TYPE_BLOB) {
8239 err = got_error(GOT_ERR_OBJ_TYPE);
8240 goto done;
8243 err = got_object_open_as_blob(&blob, s->repo, obj_id, 8192, fd);
8244 if (err)
8245 goto done;
8246 blame->f = got_opentemp();
8247 if (blame->f == NULL) {
8248 err = got_error_from_errno("got_opentemp");
8249 goto done;
8251 err = got_object_blob_dump_to_file(&blame->filesize, &blame->nlines,
8252 &blame->line_offsets, blame->f, blob);
8253 if (err)
8254 goto done;
8255 if (blame->nlines == 0) {
8256 s->blame_complete = 1;
8257 goto done;
8260 /* Don't include \n at EOF in the blame line count. */
8261 if (blame->line_offsets[blame->nlines - 1] == blame->filesize)
8262 blame->nlines--;
8264 blame->lines = calloc(blame->nlines, sizeof(*blame->lines));
8265 if (blame->lines == NULL) {
8266 err = got_error_from_errno("calloc");
8267 goto done;
8270 err = got_repo_pack_fds_open(&pack_fds);
8271 if (err)
8272 goto done;
8273 err = got_repo_open(&thread_repo, got_repo_get_path(s->repo), NULL,
8274 pack_fds);
8275 if (err)
8276 goto done;
8278 blame->pack_fds = pack_fds;
8279 blame->cb_args.view = view;
8280 blame->cb_args.lines = blame->lines;
8281 blame->cb_args.nlines = blame->nlines;
8282 blame->cb_args.commit_id = got_object_id_dup(&s->blamed_commit->id);
8283 if (blame->cb_args.commit_id == NULL) {
8284 err = got_error_from_errno("got_object_id_dup");
8285 goto done;
8287 blame->cb_args.quit = &s->done;
8289 blame->thread_args.path = s->path;
8290 blame->thread_args.repo = thread_repo;
8291 blame->thread_args.cb_args = &blame->cb_args;
8292 blame->thread_args.complete = &s->blame_complete;
8293 blame->thread_args.cancel_cb = cancel_blame_view;
8294 blame->thread_args.cancel_arg = &s->done;
8295 s->blame_complete = 0;
8297 if (s->first_displayed_line + view->nlines - 1 > blame->nlines) {
8298 s->first_displayed_line = 1;
8299 s->last_displayed_line = view->nlines;
8300 s->selected_line = 1;
8302 s->matched_line = 0;
8304 done:
8305 if (commit)
8306 got_object_commit_close(commit);
8307 if (fd != -1 && close(fd) == -1 && err == NULL)
8308 err = got_error_from_errno("close");
8309 if (blob)
8310 got_object_blob_close(blob);
8311 free(obj_id);
8312 if (err)
8313 stop_blame(blame);
8314 return err;
8317 static const struct got_error *
8318 open_blame_view(struct tog_view *view, char *path,
8319 struct got_object_id *commit_id, struct got_repository *repo)
8321 const struct got_error *err = NULL;
8322 struct tog_blame_view_state *s = &view->state.blame;
8324 STAILQ_INIT(&s->blamed_commits);
8326 s->path = strdup(path);
8327 if (s->path == NULL)
8328 return got_error_from_errno("strdup");
8330 err = got_object_qid_alloc(&s->blamed_commit, commit_id);
8331 if (err) {
8332 free(s->path);
8333 return err;
8336 STAILQ_INSERT_HEAD(&s->blamed_commits, s->blamed_commit, entry);
8337 s->first_displayed_line = 1;
8338 s->last_displayed_line = view->nlines;
8339 s->selected_line = 1;
8340 s->blame_complete = 0;
8341 s->repo = repo;
8342 s->commit_id = commit_id;
8343 memset(&s->blame, 0, sizeof(s->blame));
8345 STAILQ_INIT(&s->colors);
8346 if (has_colors() && getenv("TOG_COLORS") != NULL) {
8347 err = add_color(&s->colors, "^", TOG_COLOR_COMMIT,
8348 get_color_value("TOG_COLOR_COMMIT"));
8349 if (err)
8350 return err;
8353 view->show = show_blame_view;
8354 view->input = input_blame_view;
8355 view->reset = reset_blame_view;
8356 view->close = close_blame_view;
8357 view->search_start = search_start_blame_view;
8358 view->search_setup = search_setup_blame_view;
8359 view->search_next = search_next_view_match;
8361 if (using_mock_io) {
8362 struct tog_blame_thread_args *bta = &s->blame.thread_args;
8363 int rc;
8365 rc = pthread_cond_init(&bta->blame_complete, NULL);
8366 if (rc)
8367 return got_error_set_errno(rc, "pthread_cond_init");
8370 return run_blame(view);
8373 static const struct got_error *
8374 close_blame_view(struct tog_view *view)
8376 const struct got_error *err = NULL;
8377 struct tog_blame_view_state *s = &view->state.blame;
8379 if (s->blame.thread)
8380 err = stop_blame(&s->blame);
8382 while (!STAILQ_EMPTY(&s->blamed_commits)) {
8383 struct got_object_qid *blamed_commit;
8384 blamed_commit = STAILQ_FIRST(&s->blamed_commits);
8385 STAILQ_REMOVE_HEAD(&s->blamed_commits, entry);
8386 got_object_qid_free(blamed_commit);
8389 if (using_mock_io) {
8390 struct tog_blame_thread_args *bta = &s->blame.thread_args;
8391 int rc;
8393 rc = pthread_cond_destroy(&bta->blame_complete);
8394 if (rc && err == NULL)
8395 err = got_error_set_errno(rc, "pthread_cond_destroy");
8398 free(s->path);
8399 free_colors(&s->colors);
8400 return err;
8403 static const struct got_error *
8404 search_start_blame_view(struct tog_view *view)
8406 struct tog_blame_view_state *s = &view->state.blame;
8408 s->matched_line = 0;
8409 return NULL;
8412 static void
8413 search_setup_blame_view(struct tog_view *view, FILE **f, off_t **line_offsets,
8414 size_t *nlines, int **first, int **last, int **match, int **selected)
8416 struct tog_blame_view_state *s = &view->state.blame;
8418 *f = s->blame.f;
8419 *nlines = s->blame.nlines;
8420 *line_offsets = s->blame.line_offsets;
8421 *match = &s->matched_line;
8422 *first = &s->first_displayed_line;
8423 *last = &s->last_displayed_line;
8424 *selected = &s->selected_line;
8427 static const struct got_error *
8428 show_blame_view(struct tog_view *view)
8430 const struct got_error *err = NULL;
8431 struct tog_blame_view_state *s = &view->state.blame;
8432 int errcode;
8434 if (s->blame.thread == 0 && !s->blame_complete) {
8435 errcode = pthread_create(&s->blame.thread, NULL, blame_thread,
8436 &s->blame.thread_args);
8437 if (errcode)
8438 return got_error_set_errno(errcode, "pthread_create");
8440 if (!using_mock_io)
8441 halfdelay(1); /* fast refresh while annotating */
8444 if (s->blame_complete && !using_mock_io)
8445 halfdelay(10); /* disable fast refresh */
8447 err = draw_blame(view);
8449 view_border(view);
8450 return err;
8453 static const struct got_error *
8454 log_annotated_line(struct tog_view **new_view, int begin_y, int begin_x,
8455 struct got_repository *repo, struct got_object_id *id)
8457 struct tog_view *log_view;
8458 const struct got_error *err = NULL;
8460 *new_view = NULL;
8462 log_view = view_open(0, 0, begin_y, begin_x, TOG_VIEW_LOG);
8463 if (log_view == NULL)
8464 return got_error_from_errno("view_open");
8466 err = open_log_view(log_view, id, repo, GOT_REF_HEAD, "", 0, NULL);
8467 if (err)
8468 view_close(log_view);
8469 else
8470 *new_view = log_view;
8472 return err;
8475 static const struct got_error *
8476 input_blame_view(struct tog_view **new_view, struct tog_view *view, int ch)
8478 const struct got_error *err = NULL, *thread_err = NULL;
8479 struct tog_view *diff_view;
8480 struct tog_blame_view_state *s = &view->state.blame;
8481 int eos, nscroll, begin_y = 0, begin_x = 0;
8483 eos = nscroll = view->nlines - 2;
8484 if (view_is_hsplit_top(view))
8485 --eos; /* border */
8487 switch (ch) {
8488 case '0':
8489 case '$':
8490 case KEY_RIGHT:
8491 case 'l':
8492 case KEY_LEFT:
8493 case 'h':
8494 horizontal_scroll_input(view, ch);
8495 break;
8496 case 'q':
8497 s->done = 1;
8498 break;
8499 case 'g':
8500 case KEY_HOME:
8501 s->selected_line = 1;
8502 s->first_displayed_line = 1;
8503 view->count = 0;
8504 break;
8505 case 'G':
8506 case KEY_END:
8507 if (s->blame.nlines < eos) {
8508 s->selected_line = s->blame.nlines;
8509 s->first_displayed_line = 1;
8510 } else {
8511 s->selected_line = eos;
8512 s->first_displayed_line = s->blame.nlines - (eos - 1);
8514 view->count = 0;
8515 break;
8516 case 'k':
8517 case KEY_UP:
8518 case CTRL('p'):
8519 if (s->selected_line > 1)
8520 s->selected_line--;
8521 else if (s->selected_line == 1 &&
8522 s->first_displayed_line > 1)
8523 s->first_displayed_line--;
8524 else
8525 view->count = 0;
8526 break;
8527 case CTRL('u'):
8528 case 'u':
8529 nscroll /= 2;
8530 /* FALL THROUGH */
8531 case KEY_PPAGE:
8532 case CTRL('b'):
8533 case 'b':
8534 if (s->first_displayed_line == 1) {
8535 if (view->count > 1)
8536 nscroll += nscroll;
8537 s->selected_line = MAX(1, s->selected_line - nscroll);
8538 view->count = 0;
8539 break;
8541 if (s->first_displayed_line > nscroll)
8542 s->first_displayed_line -= nscroll;
8543 else
8544 s->first_displayed_line = 1;
8545 break;
8546 case 'j':
8547 case KEY_DOWN:
8548 case CTRL('n'):
8549 if (s->selected_line < eos && s->first_displayed_line +
8550 s->selected_line <= s->blame.nlines)
8551 s->selected_line++;
8552 else if (s->first_displayed_line < s->blame.nlines - (eos - 1))
8553 s->first_displayed_line++;
8554 else
8555 view->count = 0;
8556 break;
8557 case 'c':
8558 case 'p': {
8559 struct got_object_id *id = NULL;
8561 view->count = 0;
8562 id = get_selected_commit_id(s->blame.lines, s->blame.nlines,
8563 s->first_displayed_line, s->selected_line);
8564 if (id == NULL)
8565 break;
8566 if (ch == 'p') {
8567 struct got_commit_object *commit, *pcommit;
8568 struct got_object_qid *pid;
8569 struct got_object_id *blob_id = NULL;
8570 int obj_type;
8571 err = got_object_open_as_commit(&commit,
8572 s->repo, id);
8573 if (err)
8574 break;
8575 pid = STAILQ_FIRST(
8576 got_object_commit_get_parent_ids(commit));
8577 if (pid == NULL) {
8578 got_object_commit_close(commit);
8579 break;
8581 /* Check if path history ends here. */
8582 err = got_object_open_as_commit(&pcommit,
8583 s->repo, &pid->id);
8584 if (err)
8585 break;
8586 err = got_object_id_by_path(&blob_id, s->repo,
8587 pcommit, s->path);
8588 got_object_commit_close(pcommit);
8589 if (err) {
8590 if (err->code == GOT_ERR_NO_TREE_ENTRY)
8591 err = NULL;
8592 got_object_commit_close(commit);
8593 break;
8595 err = got_object_get_type(&obj_type, s->repo,
8596 blob_id);
8597 free(blob_id);
8598 /* Can't blame non-blob type objects. */
8599 if (obj_type != GOT_OBJ_TYPE_BLOB) {
8600 got_object_commit_close(commit);
8601 break;
8603 err = got_object_qid_alloc(&s->blamed_commit,
8604 &pid->id);
8605 got_object_commit_close(commit);
8606 } else {
8607 if (got_object_id_cmp(id,
8608 &s->blamed_commit->id) == 0)
8609 break;
8610 err = got_object_qid_alloc(&s->blamed_commit,
8611 id);
8613 if (err)
8614 break;
8615 s->done = 1;
8616 thread_err = stop_blame(&s->blame);
8617 s->done = 0;
8618 if (thread_err)
8619 break;
8620 STAILQ_INSERT_HEAD(&s->blamed_commits,
8621 s->blamed_commit, entry);
8622 err = run_blame(view);
8623 if (err)
8624 break;
8625 break;
8627 case 'C': {
8628 struct got_object_qid *first;
8630 view->count = 0;
8631 first = STAILQ_FIRST(&s->blamed_commits);
8632 if (!got_object_id_cmp(&first->id, s->commit_id))
8633 break;
8634 s->done = 1;
8635 thread_err = stop_blame(&s->blame);
8636 s->done = 0;
8637 if (thread_err)
8638 break;
8639 STAILQ_REMOVE_HEAD(&s->blamed_commits, entry);
8640 got_object_qid_free(s->blamed_commit);
8641 s->blamed_commit =
8642 STAILQ_FIRST(&s->blamed_commits);
8643 err = run_blame(view);
8644 if (err)
8645 break;
8646 break;
8648 case 'L':
8649 view->count = 0;
8650 s->id_to_log = get_selected_commit_id(s->blame.lines,
8651 s->blame.nlines, s->first_displayed_line, s->selected_line);
8652 if (s->id_to_log)
8653 err = view_request_new(new_view, view, TOG_VIEW_LOG);
8654 break;
8655 case KEY_ENTER:
8656 case '\r': {
8657 struct got_object_id *id = NULL;
8658 struct got_object_qid *pid;
8659 struct got_commit_object *commit = NULL;
8661 view->count = 0;
8662 id = get_selected_commit_id(s->blame.lines, s->blame.nlines,
8663 s->first_displayed_line, s->selected_line);
8664 if (id == NULL)
8665 break;
8666 err = got_object_open_as_commit(&commit, s->repo, id);
8667 if (err)
8668 break;
8669 pid = STAILQ_FIRST(got_object_commit_get_parent_ids(commit));
8670 if (*new_view) {
8671 /* traversed from diff view, release diff resources */
8672 err = close_diff_view(*new_view);
8673 if (err)
8674 break;
8675 diff_view = *new_view;
8676 } else {
8677 if (view_is_parent_view(view))
8678 view_get_split(view, &begin_y, &begin_x);
8680 diff_view = view_open(0, 0, begin_y, begin_x,
8681 TOG_VIEW_DIFF);
8682 if (diff_view == NULL) {
8683 got_object_commit_close(commit);
8684 err = got_error_from_errno("view_open");
8685 break;
8688 err = open_diff_view(diff_view, pid ? &pid->id : NULL,
8689 id, NULL, NULL, 3, 0, 0, 0, 0, NULL, view, s->repo, NULL);
8690 got_object_commit_close(commit);
8691 if (err)
8692 break;
8693 s->last_diffed_line = s->first_displayed_line - 1 +
8694 s->selected_line;
8695 if (*new_view)
8696 break; /* still open from active diff view */
8697 if (view_is_parent_view(view) &&
8698 view->mode == TOG_VIEW_SPLIT_HRZN) {
8699 err = view_init_hsplit(view, begin_y);
8700 if (err)
8701 break;
8704 view->focussed = 0;
8705 diff_view->focussed = 1;
8706 diff_view->mode = view->mode;
8707 diff_view->nlines = view->lines - begin_y;
8708 if (view_is_parent_view(view)) {
8709 view_transfer_size(diff_view, view);
8710 err = view_close_child(view);
8711 if (err)
8712 break;
8713 err = view_set_child(view, diff_view);
8714 if (err)
8715 break;
8716 view->focus_child = 1;
8717 } else
8718 *new_view = diff_view;
8719 if (err)
8720 break;
8721 break;
8723 case CTRL('d'):
8724 case 'd':
8725 nscroll /= 2;
8726 /* FALL THROUGH */
8727 case KEY_NPAGE:
8728 case CTRL('f'):
8729 case 'f':
8730 case ' ':
8731 if (s->last_displayed_line >= s->blame.nlines &&
8732 s->selected_line >= MIN(s->blame.nlines,
8733 view->nlines - 2)) {
8734 view->count = 0;
8735 break;
8737 if (s->last_displayed_line >= s->blame.nlines &&
8738 s->selected_line < view->nlines - 2) {
8739 s->selected_line +=
8740 MIN(nscroll, s->last_displayed_line -
8741 s->first_displayed_line - s->selected_line + 1);
8743 if (s->last_displayed_line + nscroll <= s->blame.nlines)
8744 s->first_displayed_line += nscroll;
8745 else
8746 s->first_displayed_line =
8747 s->blame.nlines - (view->nlines - 3);
8748 break;
8749 case KEY_RESIZE:
8750 if (s->selected_line > view->nlines - 2) {
8751 s->selected_line = MIN(s->blame.nlines,
8752 view->nlines - 2);
8754 break;
8755 default:
8756 view->count = 0;
8757 break;
8759 return thread_err ? thread_err : err;
8762 static const struct got_error *
8763 reset_blame_view(struct tog_view *view)
8765 const struct got_error *err;
8766 struct tog_blame_view_state *s = &view->state.blame;
8768 view->count = 0;
8769 s->done = 1;
8770 err = stop_blame(&s->blame);
8771 s->done = 0;
8772 if (err)
8773 return err;
8774 return run_blame(view);
8777 static const struct got_error *
8778 cmd_blame(int argc, char *argv[])
8780 const struct got_error *error;
8781 struct got_repository *repo = NULL;
8782 struct got_worktree *worktree = NULL;
8783 char *cwd = NULL, *repo_path = NULL, *in_repo_path = NULL;
8784 char *link_target = NULL;
8785 struct got_object_id *commit_id = NULL;
8786 struct got_commit_object *commit = NULL;
8787 char *keyword_idstr = NULL, *commit_id_str = NULL;
8788 int ch;
8789 struct tog_view *view = NULL;
8790 int *pack_fds = NULL;
8792 while ((ch = getopt(argc, argv, "c:r:")) != -1) {
8793 switch (ch) {
8794 case 'c':
8795 commit_id_str = optarg;
8796 break;
8797 case 'r':
8798 repo_path = realpath(optarg, NULL);
8799 if (repo_path == NULL)
8800 return got_error_from_errno2("realpath",
8801 optarg);
8802 break;
8803 default:
8804 usage_blame();
8805 /* NOTREACHED */
8809 argc -= optind;
8810 argv += optind;
8812 if (argc != 1)
8813 usage_blame();
8815 error = got_repo_pack_fds_open(&pack_fds);
8816 if (error != NULL)
8817 goto done;
8819 if (repo_path == NULL) {
8820 cwd = getcwd(NULL, 0);
8821 if (cwd == NULL)
8822 return got_error_from_errno("getcwd");
8823 error = got_worktree_open(&worktree, cwd, NULL);
8824 if (error && error->code != GOT_ERR_NOT_WORKTREE)
8825 goto done;
8826 if (worktree)
8827 repo_path =
8828 strdup(got_worktree_get_repo_path(worktree));
8829 else
8830 repo_path = strdup(cwd);
8831 if (repo_path == NULL) {
8832 error = got_error_from_errno("strdup");
8833 goto done;
8837 error = got_repo_open(&repo, repo_path, NULL, pack_fds);
8838 if (error != NULL)
8839 goto done;
8841 error = get_in_repo_path_from_argv0(&in_repo_path, argc, argv, repo,
8842 worktree);
8843 if (error)
8844 goto done;
8846 init_curses();
8848 error = apply_unveil(got_repo_get_path(repo), NULL);
8849 if (error)
8850 goto done;
8852 error = tog_load_refs(repo, 0);
8853 if (error)
8854 goto done;
8856 if (commit_id_str == NULL) {
8857 struct got_reference *head_ref;
8858 error = got_ref_open(&head_ref, repo, worktree ?
8859 got_worktree_get_head_ref_name(worktree) : GOT_REF_HEAD, 0);
8860 if (error != NULL)
8861 goto done;
8862 error = got_ref_resolve(&commit_id, repo, head_ref);
8863 got_ref_close(head_ref);
8864 } else {
8865 error = got_keyword_to_idstr(&keyword_idstr, commit_id_str,
8866 repo, worktree);
8867 if (error != NULL)
8868 goto done;
8869 if (keyword_idstr != NULL)
8870 commit_id_str = keyword_idstr;
8872 error = got_repo_match_object_id(&commit_id, NULL,
8873 commit_id_str, GOT_OBJ_TYPE_COMMIT, &tog_refs, repo);
8875 if (error != NULL)
8876 goto done;
8878 error = got_object_open_as_commit(&commit, repo, commit_id);
8879 if (error)
8880 goto done;
8882 error = got_object_resolve_symlinks(&link_target, in_repo_path,
8883 commit, repo);
8884 if (error)
8885 goto done;
8887 view = view_open(0, 0, 0, 0, TOG_VIEW_BLAME);
8888 if (view == NULL) {
8889 error = got_error_from_errno("view_open");
8890 goto done;
8892 error = open_blame_view(view, link_target ? link_target : in_repo_path,
8893 commit_id, repo);
8894 if (error != NULL) {
8895 if (view->close == NULL)
8896 close_blame_view(view);
8897 view_close(view);
8898 goto done;
8901 if (worktree) {
8902 error = set_tog_base_commit(repo, worktree);
8903 if (error != NULL)
8904 goto done;
8906 /* Release work tree lock. */
8907 got_worktree_close(worktree);
8908 worktree = NULL;
8911 error = view_loop(view);
8913 done:
8914 free(tog_base_commit.id);
8915 free(repo_path);
8916 free(in_repo_path);
8917 free(link_target);
8918 free(cwd);
8919 free(commit_id);
8920 free(keyword_idstr);
8921 if (commit)
8922 got_object_commit_close(commit);
8923 if (worktree)
8924 got_worktree_close(worktree);
8925 if (repo) {
8926 const struct got_error *close_err = got_repo_close(repo);
8927 if (error == NULL)
8928 error = close_err;
8930 if (pack_fds) {
8931 const struct got_error *pack_err =
8932 got_repo_pack_fds_close(pack_fds);
8933 if (error == NULL)
8934 error = pack_err;
8936 tog_free_refs();
8937 return error;
8940 static const struct got_error *
8941 draw_tree_entries(struct tog_view *view, const char *parent_path)
8943 struct tog_tree_view_state *s = &view->state.tree;
8944 const struct got_error *err = NULL;
8945 struct got_tree_entry *te;
8946 wchar_t *wline;
8947 char *index = NULL;
8948 struct tog_color *tc;
8949 int width, n, nentries, scrollx, i = 1;
8950 int limit = view->nlines;
8952 s->ndisplayed = 0;
8953 if (view_is_hsplit_top(view))
8954 --limit; /* border */
8956 werase(view->window);
8958 if (limit == 0)
8959 return NULL;
8961 err = format_line(&wline, &width, NULL, s->tree_label, 0, view->ncols,
8962 0, 0);
8963 if (err)
8964 return err;
8965 if (view_needs_focus_indication(view))
8966 wstandout(view->window);
8967 tc = get_color(&s->colors, TOG_COLOR_COMMIT);
8968 if (tc)
8969 wattr_on(view->window, COLOR_PAIR(tc->colorpair), NULL);
8970 waddwstr(view->window, wline);
8971 free(wline);
8972 wline = NULL;
8973 while (width++ < view->ncols)
8974 waddch(view->window, ' ');
8975 if (tc)
8976 wattr_off(view->window, COLOR_PAIR(tc->colorpair), NULL);
8977 if (view_needs_focus_indication(view))
8978 wstandend(view->window);
8979 if (--limit <= 0)
8980 return NULL;
8982 i += s->selected;
8983 if (s->first_displayed_entry) {
8984 i += got_tree_entry_get_index(s->first_displayed_entry);
8985 if (s->tree != s->root)
8986 ++i; /* account for ".." entry */
8988 nentries = got_object_tree_get_nentries(s->tree);
8989 if (asprintf(&index, "[%d/%d] %s",
8990 i, nentries + (s->tree == s->root ? 0 : 1), parent_path) == -1)
8991 return got_error_from_errno("asprintf");
8992 err = format_line(&wline, &width, NULL, index, 0, view->ncols, 0, 0);
8993 free(index);
8994 if (err)
8995 return err;
8996 waddwstr(view->window, wline);
8997 free(wline);
8998 wline = NULL;
8999 if (width < view->ncols - 1)
9000 waddch(view->window, '\n');
9001 if (--limit <= 0)
9002 return NULL;
9003 waddch(view->window, '\n');
9004 if (--limit <= 0)
9005 return NULL;
9007 if (s->first_displayed_entry == NULL) {
9008 te = got_object_tree_get_first_entry(s->tree);
9009 if (s->selected == 0) {
9010 if (view->focussed)
9011 wstandout(view->window);
9012 s->selected_entry = NULL;
9014 waddstr(view->window, " ..\n"); /* parent directory */
9015 if (s->selected == 0 && view->focussed)
9016 wstandend(view->window);
9017 s->ndisplayed++;
9018 if (--limit <= 0)
9019 return NULL;
9020 n = 1;
9021 } else {
9022 n = 0;
9023 te = s->first_displayed_entry;
9026 view->maxx = 0;
9027 for (i = got_tree_entry_get_index(te); i < nentries; i++) {
9028 char *line = NULL, *id_str = NULL, *link_target = NULL;
9029 const char *modestr = "";
9030 mode_t mode;
9032 te = got_object_tree_get_entry(s->tree, i);
9033 mode = got_tree_entry_get_mode(te);
9035 if (s->show_ids) {
9036 err = got_object_id_str(&id_str,
9037 got_tree_entry_get_id(te));
9038 if (err)
9039 return got_error_from_errno(
9040 "got_object_id_str");
9042 if (got_object_tree_entry_is_submodule(te))
9043 modestr = "$";
9044 else if (S_ISLNK(mode)) {
9045 int i;
9047 err = got_tree_entry_get_symlink_target(&link_target,
9048 te, s->repo);
9049 if (err) {
9050 free(id_str);
9051 return err;
9053 for (i = 0; link_target[i] != '\0'; i++) {
9054 if (!isprint((unsigned char)link_target[i]))
9055 link_target[i] = '?';
9057 modestr = "@";
9059 else if (S_ISDIR(mode))
9060 modestr = "/";
9061 else if (mode & S_IXUSR)
9062 modestr = "*";
9063 if (asprintf(&line, "%s %s%s%s%s", id_str ? id_str : "",
9064 got_tree_entry_get_name(te), modestr,
9065 link_target ? " -> ": "",
9066 link_target ? link_target : "") == -1) {
9067 free(id_str);
9068 free(link_target);
9069 return got_error_from_errno("asprintf");
9071 free(id_str);
9072 free(link_target);
9074 /* use full line width to determine view->maxx */
9075 err = format_line(&wline, &width, NULL, line, 0, INT_MAX, 0, 0);
9076 if (err) {
9077 free(line);
9078 break;
9080 view->maxx = MAX(view->maxx, width);
9081 free(wline);
9082 wline = NULL;
9084 err = format_line(&wline, &width, &scrollx, line, view->x,
9085 view->ncols, 0, 0);
9086 if (err) {
9087 free(line);
9088 break;
9090 if (n == s->selected) {
9091 if (view->focussed)
9092 wstandout(view->window);
9093 s->selected_entry = te;
9095 tc = match_color(&s->colors, line);
9096 if (tc)
9097 wattr_on(view->window,
9098 COLOR_PAIR(tc->colorpair), NULL);
9099 waddwstr(view->window, &wline[scrollx]);
9100 if (tc)
9101 wattr_off(view->window,
9102 COLOR_PAIR(tc->colorpair), NULL);
9103 if (width < view->ncols)
9104 waddch(view->window, '\n');
9105 if (n == s->selected && view->focussed)
9106 wstandend(view->window);
9107 free(line);
9108 free(wline);
9109 wline = NULL;
9110 n++;
9111 s->ndisplayed++;
9112 s->last_displayed_entry = te;
9113 if (--limit <= 0)
9114 break;
9117 return err;
9120 static void
9121 tree_scroll_up(struct tog_tree_view_state *s, int maxscroll)
9123 struct got_tree_entry *te;
9124 int isroot = s->tree == s->root;
9125 int i = 0;
9127 if (s->first_displayed_entry == NULL)
9128 return;
9130 te = got_tree_entry_get_prev(s->tree, s->first_displayed_entry);
9131 while (i++ < maxscroll) {
9132 if (te == NULL) {
9133 if (!isroot)
9134 s->first_displayed_entry = NULL;
9135 break;
9137 s->first_displayed_entry = te;
9138 te = got_tree_entry_get_prev(s->tree, te);
9142 static const struct got_error *
9143 tree_scroll_down(struct tog_view *view, int maxscroll)
9145 struct tog_tree_view_state *s = &view->state.tree;
9146 struct got_tree_entry *next, *last;
9147 int n = 0;
9149 if (s->first_displayed_entry)
9150 next = got_tree_entry_get_next(s->tree,
9151 s->first_displayed_entry);
9152 else
9153 next = got_object_tree_get_first_entry(s->tree);
9155 last = s->last_displayed_entry;
9156 while (next && n++ < maxscroll) {
9157 if (last) {
9158 s->last_displayed_entry = last;
9159 last = got_tree_entry_get_next(s->tree, last);
9161 if (last || (view->mode == TOG_VIEW_SPLIT_HRZN && next)) {
9162 s->first_displayed_entry = next;
9163 next = got_tree_entry_get_next(s->tree, next);
9167 return NULL;
9170 static const struct got_error *
9171 tree_entry_path(char **path, struct tog_parent_trees *parents,
9172 struct got_tree_entry *te)
9174 const struct got_error *err = NULL;
9175 struct tog_parent_tree *pt;
9176 size_t len = 2; /* for leading slash and NUL */
9178 TAILQ_FOREACH(pt, parents, entry)
9179 len += strlen(got_tree_entry_get_name(pt->selected_entry))
9180 + 1 /* slash */;
9181 if (te)
9182 len += strlen(got_tree_entry_get_name(te));
9184 *path = calloc(1, len);
9185 if (path == NULL)
9186 return got_error_from_errno("calloc");
9188 (*path)[0] = '/';
9189 pt = TAILQ_LAST(parents, tog_parent_trees);
9190 while (pt) {
9191 const char *name = got_tree_entry_get_name(pt->selected_entry);
9192 if (strlcat(*path, name, len) >= len) {
9193 err = got_error(GOT_ERR_NO_SPACE);
9194 goto done;
9196 if (strlcat(*path, "/", len) >= len) {
9197 err = got_error(GOT_ERR_NO_SPACE);
9198 goto done;
9200 pt = TAILQ_PREV(pt, tog_parent_trees, entry);
9202 if (te) {
9203 if (strlcat(*path, got_tree_entry_get_name(te), len) >= len) {
9204 err = got_error(GOT_ERR_NO_SPACE);
9205 goto done;
9208 done:
9209 if (err) {
9210 free(*path);
9211 *path = NULL;
9213 return err;
9216 static const struct got_error *
9217 blame_tree_entry(struct tog_view **new_view, int begin_y, int begin_x,
9218 struct got_tree_entry *te, struct tog_parent_trees *parents,
9219 struct got_object_id *commit_id, struct got_repository *repo)
9221 const struct got_error *err = NULL;
9222 char *path;
9223 struct tog_view *blame_view;
9225 *new_view = NULL;
9227 err = tree_entry_path(&path, parents, te);
9228 if (err)
9229 return err;
9231 blame_view = view_open(0, 0, begin_y, begin_x, TOG_VIEW_BLAME);
9232 if (blame_view == NULL) {
9233 err = got_error_from_errno("view_open");
9234 goto done;
9237 err = open_blame_view(blame_view, path, commit_id, repo);
9238 if (err) {
9239 if (err->code == GOT_ERR_CANCELLED)
9240 err = NULL;
9241 view_close(blame_view);
9242 } else
9243 *new_view = blame_view;
9244 done:
9245 free(path);
9246 return err;
9249 static const struct got_error *
9250 log_selected_tree_entry(struct tog_view **new_view, int begin_y, int begin_x,
9251 struct tog_tree_view_state *s)
9253 struct tog_view *log_view;
9254 const struct got_error *err = NULL;
9255 char *path;
9257 *new_view = NULL;
9259 log_view = view_open(0, 0, begin_y, begin_x, TOG_VIEW_LOG);
9260 if (log_view == NULL)
9261 return got_error_from_errno("view_open");
9263 err = tree_entry_path(&path, &s->parents, s->selected_entry);
9264 if (err)
9265 return err;
9267 err = open_log_view(log_view, s->commit_id, s->repo, s->head_ref_name,
9268 path, 0, NULL);
9269 if (err)
9270 view_close(log_view);
9271 else
9272 *new_view = log_view;
9273 free(path);
9274 return err;
9277 static const struct got_error *
9278 open_tree_view(struct tog_view *view, struct got_object_id *commit_id,
9279 const char *head_ref_name, struct got_repository *repo)
9281 const struct got_error *err = NULL;
9282 char *commit_id_str = NULL;
9283 struct tog_tree_view_state *s = &view->state.tree;
9284 struct got_commit_object *commit = NULL;
9286 TAILQ_INIT(&s->parents);
9287 STAILQ_INIT(&s->colors);
9289 s->commit_id = got_object_id_dup(commit_id);
9290 if (s->commit_id == NULL) {
9291 err = got_error_from_errno("got_object_id_dup");
9292 goto done;
9295 err = got_object_open_as_commit(&commit, repo, commit_id);
9296 if (err)
9297 goto done;
9300 * The root is opened here and will be closed when the view is closed.
9301 * Any visited subtrees and their path-wise parents are opened and
9302 * closed on demand.
9304 err = got_object_open_as_tree(&s->root, repo,
9305 got_object_commit_get_tree_id(commit));
9306 if (err)
9307 goto done;
9308 s->tree = s->root;
9310 err = got_object_id_str(&commit_id_str, commit_id);
9311 if (err != NULL)
9312 goto done;
9314 if (asprintf(&s->tree_label, "commit %s", commit_id_str) == -1) {
9315 err = got_error_from_errno("asprintf");
9316 goto done;
9319 s->first_displayed_entry = got_object_tree_get_entry(s->tree, 0);
9320 s->selected_entry = got_object_tree_get_entry(s->tree, 0);
9321 if (head_ref_name) {
9322 s->head_ref_name = strdup(head_ref_name);
9323 if (s->head_ref_name == NULL) {
9324 err = got_error_from_errno("strdup");
9325 goto done;
9328 s->repo = repo;
9330 if (has_colors() && getenv("TOG_COLORS") != NULL) {
9331 err = add_color(&s->colors, "\\$$",
9332 TOG_COLOR_TREE_SUBMODULE,
9333 get_color_value("TOG_COLOR_TREE_SUBMODULE"));
9334 if (err)
9335 goto done;
9336 err = add_color(&s->colors, "@$", TOG_COLOR_TREE_SYMLINK,
9337 get_color_value("TOG_COLOR_TREE_SYMLINK"));
9338 if (err)
9339 goto done;
9340 err = add_color(&s->colors, "/$",
9341 TOG_COLOR_TREE_DIRECTORY,
9342 get_color_value("TOG_COLOR_TREE_DIRECTORY"));
9343 if (err)
9344 goto done;
9346 err = add_color(&s->colors, "\\*$",
9347 TOG_COLOR_TREE_EXECUTABLE,
9348 get_color_value("TOG_COLOR_TREE_EXECUTABLE"));
9349 if (err)
9350 goto done;
9352 err = add_color(&s->colors, "^$", TOG_COLOR_COMMIT,
9353 get_color_value("TOG_COLOR_COMMIT"));
9354 if (err)
9355 goto done;
9358 view->show = show_tree_view;
9359 view->input = input_tree_view;
9360 view->close = close_tree_view;
9361 view->search_start = search_start_tree_view;
9362 view->search_next = search_next_tree_view;
9363 done:
9364 free(commit_id_str);
9365 if (commit)
9366 got_object_commit_close(commit);
9367 if (err) {
9368 if (view->close == NULL)
9369 close_tree_view(view);
9370 view_close(view);
9372 return err;
9375 static const struct got_error *
9376 close_tree_view(struct tog_view *view)
9378 struct tog_tree_view_state *s = &view->state.tree;
9380 free_colors(&s->colors);
9381 free(s->tree_label);
9382 s->tree_label = NULL;
9383 free(s->commit_id);
9384 s->commit_id = NULL;
9385 free(s->head_ref_name);
9386 s->head_ref_name = NULL;
9387 while (!TAILQ_EMPTY(&s->parents)) {
9388 struct tog_parent_tree *parent;
9389 parent = TAILQ_FIRST(&s->parents);
9390 TAILQ_REMOVE(&s->parents, parent, entry);
9391 if (parent->tree != s->root)
9392 got_object_tree_close(parent->tree);
9393 free(parent);
9396 if (s->tree != NULL && s->tree != s->root)
9397 got_object_tree_close(s->tree);
9398 if (s->root)
9399 got_object_tree_close(s->root);
9400 return NULL;
9403 static const struct got_error *
9404 search_start_tree_view(struct tog_view *view)
9406 struct tog_tree_view_state *s = &view->state.tree;
9408 s->matched_entry = NULL;
9409 return NULL;
9412 static int
9413 match_tree_entry(struct got_tree_entry *te, regex_t *regex)
9415 regmatch_t regmatch;
9417 return regexec(regex, got_tree_entry_get_name(te), 1, &regmatch,
9418 0) == 0;
9421 static const struct got_error *
9422 search_next_tree_view(struct tog_view *view)
9424 struct tog_tree_view_state *s = &view->state.tree;
9425 struct got_tree_entry *te = NULL;
9427 if (!view->searching) {
9428 view->search_next_done = TOG_SEARCH_HAVE_MORE;
9429 return NULL;
9432 if (s->matched_entry) {
9433 if (view->searching == TOG_SEARCH_FORWARD) {
9434 if (s->selected_entry)
9435 te = got_tree_entry_get_next(s->tree,
9436 s->selected_entry);
9437 else
9438 te = got_object_tree_get_first_entry(s->tree);
9439 } else {
9440 if (s->selected_entry == NULL)
9441 te = got_object_tree_get_last_entry(s->tree);
9442 else
9443 te = got_tree_entry_get_prev(s->tree,
9444 s->selected_entry);
9446 } else {
9447 if (s->selected_entry)
9448 te = s->selected_entry;
9449 else if (view->searching == TOG_SEARCH_FORWARD)
9450 te = got_object_tree_get_first_entry(s->tree);
9451 else
9452 te = got_object_tree_get_last_entry(s->tree);
9455 while (1) {
9456 if (te == NULL) {
9457 if (s->matched_entry == NULL) {
9458 view->search_next_done = TOG_SEARCH_HAVE_MORE;
9459 return NULL;
9461 if (view->searching == TOG_SEARCH_FORWARD)
9462 te = got_object_tree_get_first_entry(s->tree);
9463 else
9464 te = got_object_tree_get_last_entry(s->tree);
9467 if (match_tree_entry(te, &view->regex)) {
9468 view->search_next_done = TOG_SEARCH_HAVE_MORE;
9469 s->matched_entry = te;
9470 break;
9473 if (view->searching == TOG_SEARCH_FORWARD)
9474 te = got_tree_entry_get_next(s->tree, te);
9475 else
9476 te = got_tree_entry_get_prev(s->tree, te);
9479 if (s->matched_entry) {
9480 s->first_displayed_entry = s->matched_entry;
9481 s->selected = 0;
9484 return NULL;
9487 static const struct got_error *
9488 show_tree_view(struct tog_view *view)
9490 const struct got_error *err = NULL;
9491 struct tog_tree_view_state *s = &view->state.tree;
9492 char *parent_path;
9494 err = tree_entry_path(&parent_path, &s->parents, NULL);
9495 if (err)
9496 return err;
9498 err = draw_tree_entries(view, parent_path);
9499 free(parent_path);
9501 view_border(view);
9502 return err;
9505 static const struct got_error *
9506 tree_goto_line(struct tog_view *view, int nlines)
9508 const struct got_error *err = NULL;
9509 struct tog_tree_view_state *s = &view->state.tree;
9510 struct got_tree_entry **fte, **lte, **ste;
9511 int g, last, first = 1, i = 1;
9512 int root = s->tree == s->root;
9513 int off = root ? 1 : 2;
9515 g = view->gline;
9516 view->gline = 0;
9518 if (g == 0)
9519 g = 1;
9520 else if (g > got_object_tree_get_nentries(s->tree))
9521 g = got_object_tree_get_nentries(s->tree) + (root ? 0 : 1);
9523 fte = &s->first_displayed_entry;
9524 lte = &s->last_displayed_entry;
9525 ste = &s->selected_entry;
9527 if (*fte != NULL) {
9528 first = got_tree_entry_get_index(*fte);
9529 first += off; /* account for ".." */
9531 last = got_tree_entry_get_index(*lte);
9532 last += off;
9534 if (g >= first && g <= last && g - first < nlines) {
9535 s->selected = g - first;
9536 return NULL; /* gline is on the current page */
9539 if (*ste != NULL) {
9540 i = got_tree_entry_get_index(*ste);
9541 i += off;
9544 if (i < g) {
9545 err = tree_scroll_down(view, g - i);
9546 if (err)
9547 return err;
9548 if (got_tree_entry_get_index(*lte) >=
9549 got_object_tree_get_nentries(s->tree) - 1 &&
9550 first + s->selected < g &&
9551 s->selected < s->ndisplayed - 1) {
9552 first = got_tree_entry_get_index(*fte);
9553 first += off;
9554 s->selected = g - first;
9556 } else if (i > g)
9557 tree_scroll_up(s, i - g);
9559 if (g < nlines &&
9560 (*fte == NULL || (root && !got_tree_entry_get_index(*fte))))
9561 s->selected = g - 1;
9563 return NULL;
9566 static const struct got_error *
9567 input_tree_view(struct tog_view **new_view, struct tog_view *view, int ch)
9569 const struct got_error *err = NULL;
9570 struct tog_tree_view_state *s = &view->state.tree;
9571 struct got_tree_entry *te;
9572 int n, nscroll = view->nlines - 3;
9574 if (view->gline)
9575 return tree_goto_line(view, nscroll);
9577 switch (ch) {
9578 case '0':
9579 case '$':
9580 case KEY_RIGHT:
9581 case 'l':
9582 case KEY_LEFT:
9583 case 'h':
9584 horizontal_scroll_input(view, ch);
9585 break;
9586 case 'i':
9587 s->show_ids = !s->show_ids;
9588 view->count = 0;
9589 break;
9590 case 'L':
9591 view->count = 0;
9592 if (!s->selected_entry)
9593 break;
9594 err = view_request_new(new_view, view, TOG_VIEW_LOG);
9595 break;
9596 case 'R':
9597 view->count = 0;
9598 err = view_request_new(new_view, view, TOG_VIEW_REF);
9599 break;
9600 case 'g':
9601 case '=':
9602 case KEY_HOME:
9603 s->selected = 0;
9604 view->count = 0;
9605 if (s->tree == s->root)
9606 s->first_displayed_entry =
9607 got_object_tree_get_first_entry(s->tree);
9608 else
9609 s->first_displayed_entry = NULL;
9610 break;
9611 case 'G':
9612 case '*':
9613 case KEY_END: {
9614 int eos = view->nlines - 3;
9616 if (view->mode == TOG_VIEW_SPLIT_HRZN)
9617 --eos; /* border */
9618 s->selected = 0;
9619 view->count = 0;
9620 te = got_object_tree_get_last_entry(s->tree);
9621 for (n = 0; n < eos; n++) {
9622 if (te == NULL) {
9623 if (s->tree != s->root) {
9624 s->first_displayed_entry = NULL;
9625 n++;
9627 break;
9629 s->first_displayed_entry = te;
9630 te = got_tree_entry_get_prev(s->tree, te);
9632 if (n > 0)
9633 s->selected = n - 1;
9634 break;
9636 case 'k':
9637 case KEY_UP:
9638 case CTRL('p'):
9639 if (s->selected > 0) {
9640 s->selected--;
9641 break;
9643 tree_scroll_up(s, 1);
9644 if (s->selected_entry == NULL ||
9645 (s->tree == s->root && s->selected_entry ==
9646 got_object_tree_get_first_entry(s->tree)))
9647 view->count = 0;
9648 break;
9649 case CTRL('u'):
9650 case 'u':
9651 nscroll /= 2;
9652 /* FALL THROUGH */
9653 case KEY_PPAGE:
9654 case CTRL('b'):
9655 case 'b':
9656 if (s->tree == s->root) {
9657 if (got_object_tree_get_first_entry(s->tree) ==
9658 s->first_displayed_entry)
9659 s->selected -= MIN(s->selected, nscroll);
9660 } else {
9661 if (s->first_displayed_entry == NULL)
9662 s->selected -= MIN(s->selected, nscroll);
9664 tree_scroll_up(s, MAX(0, nscroll));
9665 if (s->selected_entry == NULL ||
9666 (s->tree == s->root && s->selected_entry ==
9667 got_object_tree_get_first_entry(s->tree)))
9668 view->count = 0;
9669 break;
9670 case 'j':
9671 case KEY_DOWN:
9672 case CTRL('n'):
9673 if (s->selected < s->ndisplayed - 1) {
9674 s->selected++;
9675 break;
9677 if (s->last_displayed_entry == NULL ||
9678 got_tree_entry_get_next(s->tree, s->last_displayed_entry)
9679 == NULL) {
9680 /* can't scroll any further */
9681 view->count = 0;
9682 break;
9684 tree_scroll_down(view, 1);
9685 break;
9686 case CTRL('d'):
9687 case 'd':
9688 nscroll /= 2;
9689 /* FALL THROUGH */
9690 case KEY_NPAGE:
9691 case CTRL('f'):
9692 case 'f':
9693 case ' ':
9694 if (s->last_displayed_entry == NULL ||
9695 got_tree_entry_get_next(s->tree, s->last_displayed_entry)
9696 == NULL) {
9697 /* can't scroll any further; move cursor down */
9698 if (s->selected < s->ndisplayed - 1)
9699 s->selected += MIN(nscroll,
9700 s->ndisplayed - s->selected - 1);
9701 else
9702 view->count = 0;
9703 break;
9705 tree_scroll_down(view, nscroll);
9706 break;
9707 case KEY_ENTER:
9708 case '\r':
9709 case KEY_BACKSPACE:
9710 if (s->selected_entry == NULL || ch == KEY_BACKSPACE) {
9711 struct tog_parent_tree *parent;
9712 /* user selected '..' */
9713 if (s->tree == s->root) {
9714 view->count = 0;
9715 break;
9717 parent = TAILQ_FIRST(&s->parents);
9718 TAILQ_REMOVE(&s->parents, parent,
9719 entry);
9720 got_object_tree_close(s->tree);
9721 s->tree = parent->tree;
9722 s->first_displayed_entry =
9723 parent->first_displayed_entry;
9724 s->selected_entry =
9725 parent->selected_entry;
9726 s->selected = parent->selected;
9727 if (s->selected > view->nlines - 3) {
9728 err = offset_selection_down(view);
9729 if (err)
9730 break;
9732 free(parent);
9733 } else if (S_ISDIR(got_tree_entry_get_mode(
9734 s->selected_entry))) {
9735 struct got_tree_object *subtree;
9736 view->count = 0;
9737 err = got_object_open_as_tree(&subtree, s->repo,
9738 got_tree_entry_get_id(s->selected_entry));
9739 if (err)
9740 break;
9741 err = tree_view_visit_subtree(s, subtree);
9742 if (err) {
9743 got_object_tree_close(subtree);
9744 break;
9746 } else if (S_ISREG(got_tree_entry_get_mode(s->selected_entry)))
9747 err = view_request_new(new_view, view, TOG_VIEW_BLAME);
9748 break;
9749 case KEY_RESIZE:
9750 if (view->nlines >= 4 && s->selected >= view->nlines - 3)
9751 s->selected = view->nlines - 4;
9752 view->count = 0;
9753 break;
9754 default:
9755 view->count = 0;
9756 break;
9759 return err;
9762 __dead static void
9763 usage_tree(void)
9765 endwin();
9766 fprintf(stderr,
9767 "usage: %s tree [-c commit] [-r repository-path] [path]\n",
9768 getprogname());
9769 exit(1);
9772 static const struct got_error *
9773 cmd_tree(int argc, char *argv[])
9775 const struct got_error *error;
9776 struct got_repository *repo = NULL;
9777 struct got_worktree *worktree = NULL;
9778 char *cwd = NULL, *repo_path = NULL, *in_repo_path = NULL;
9779 struct got_object_id *commit_id = NULL;
9780 struct got_commit_object *commit = NULL;
9781 const char *commit_id_arg = NULL;
9782 char *keyword_idstr = NULL, *label = NULL;
9783 struct got_reference *ref = NULL;
9784 const char *head_ref_name = NULL;
9785 int ch;
9786 struct tog_view *view;
9787 int *pack_fds = NULL;
9789 while ((ch = getopt(argc, argv, "c:r:")) != -1) {
9790 switch (ch) {
9791 case 'c':
9792 commit_id_arg = optarg;
9793 break;
9794 case 'r':
9795 repo_path = realpath(optarg, NULL);
9796 if (repo_path == NULL)
9797 return got_error_from_errno2("realpath",
9798 optarg);
9799 break;
9800 default:
9801 usage_tree();
9802 /* NOTREACHED */
9806 argc -= optind;
9807 argv += optind;
9809 if (argc > 1)
9810 usage_tree();
9812 error = got_repo_pack_fds_open(&pack_fds);
9813 if (error != NULL)
9814 goto done;
9816 if (repo_path == NULL) {
9817 cwd = getcwd(NULL, 0);
9818 if (cwd == NULL)
9819 return got_error_from_errno("getcwd");
9820 error = got_worktree_open(&worktree, cwd, NULL);
9821 if (error && error->code != GOT_ERR_NOT_WORKTREE)
9822 goto done;
9823 if (worktree)
9824 repo_path =
9825 strdup(got_worktree_get_repo_path(worktree));
9826 else
9827 repo_path = strdup(cwd);
9828 if (repo_path == NULL) {
9829 error = got_error_from_errno("strdup");
9830 goto done;
9834 error = got_repo_open(&repo, repo_path, NULL, pack_fds);
9835 if (error != NULL)
9836 goto done;
9838 error = get_in_repo_path_from_argv0(&in_repo_path, argc, argv,
9839 repo, worktree);
9840 if (error)
9841 goto done;
9843 init_curses();
9845 error = apply_unveil(got_repo_get_path(repo), NULL);
9846 if (error)
9847 goto done;
9849 error = tog_load_refs(repo, 0);
9850 if (error)
9851 goto done;
9853 if (commit_id_arg == NULL) {
9854 error = got_repo_match_object_id(&commit_id, &label,
9855 worktree ? got_worktree_get_head_ref_name(worktree) :
9856 GOT_REF_HEAD, GOT_OBJ_TYPE_COMMIT, &tog_refs, repo);
9857 if (error)
9858 goto done;
9859 head_ref_name = label;
9860 } else {
9861 error = got_keyword_to_idstr(&keyword_idstr, commit_id_arg,
9862 repo, worktree);
9863 if (error != NULL)
9864 goto done;
9865 if (keyword_idstr != NULL)
9866 commit_id_arg = keyword_idstr;
9868 error = got_ref_open(&ref, repo, commit_id_arg, 0);
9869 if (error == NULL)
9870 head_ref_name = got_ref_get_name(ref);
9871 else if (error->code != GOT_ERR_NOT_REF)
9872 goto done;
9873 error = got_repo_match_object_id(&commit_id, NULL,
9874 commit_id_arg, GOT_OBJ_TYPE_COMMIT, &tog_refs, repo);
9875 if (error)
9876 goto done;
9879 error = got_object_open_as_commit(&commit, repo, commit_id);
9880 if (error)
9881 goto done;
9883 view = view_open(0, 0, 0, 0, TOG_VIEW_TREE);
9884 if (view == NULL) {
9885 error = got_error_from_errno("view_open");
9886 goto done;
9888 error = open_tree_view(view, commit_id, head_ref_name, repo);
9889 if (error)
9890 goto done;
9891 if (!got_path_is_root_dir(in_repo_path)) {
9892 error = tree_view_walk_path(&view->state.tree, commit,
9893 in_repo_path);
9894 if (error)
9895 goto done;
9898 if (worktree) {
9899 error = set_tog_base_commit(repo, worktree);
9900 if (error != NULL)
9901 goto done;
9903 /* Release work tree lock. */
9904 got_worktree_close(worktree);
9905 worktree = NULL;
9908 error = view_loop(view);
9910 done:
9911 free(tog_base_commit.id);
9912 free(keyword_idstr);
9913 free(repo_path);
9914 free(cwd);
9915 free(commit_id);
9916 free(label);
9917 if (commit != NULL)
9918 got_object_commit_close(commit);
9919 if (ref)
9920 got_ref_close(ref);
9921 if (worktree != NULL)
9922 got_worktree_close(worktree);
9923 if (repo) {
9924 const struct got_error *close_err = got_repo_close(repo);
9925 if (error == NULL)
9926 error = close_err;
9928 if (pack_fds) {
9929 const struct got_error *pack_err =
9930 got_repo_pack_fds_close(pack_fds);
9931 if (error == NULL)
9932 error = pack_err;
9934 tog_free_refs();
9935 return error;
9938 static const struct got_error *
9939 ref_view_load_refs(struct tog_ref_view_state *s)
9941 struct got_reflist_entry *sre;
9942 struct tog_reflist_entry *re;
9944 s->nrefs = 0;
9945 TAILQ_FOREACH(sre, &tog_refs, entry) {
9946 if (strncmp(got_ref_get_name(sre->ref),
9947 "refs/got/", 9) == 0 &&
9948 strncmp(got_ref_get_name(sre->ref),
9949 "refs/got/backup/", 16) != 0)
9950 continue;
9952 re = malloc(sizeof(*re));
9953 if (re == NULL)
9954 return got_error_from_errno("malloc");
9956 re->ref = got_ref_dup(sre->ref);
9957 if (re->ref == NULL)
9958 return got_error_from_errno("got_ref_dup");
9959 re->idx = s->nrefs++;
9960 TAILQ_INSERT_TAIL(&s->refs, re, entry);
9963 s->first_displayed_entry = TAILQ_FIRST(&s->refs);
9964 return NULL;
9967 static void
9968 ref_view_free_refs(struct tog_ref_view_state *s)
9970 struct tog_reflist_entry *re;
9972 while (!TAILQ_EMPTY(&s->refs)) {
9973 re = TAILQ_FIRST(&s->refs);
9974 TAILQ_REMOVE(&s->refs, re, entry);
9975 got_ref_close(re->ref);
9976 free(re);
9980 static const struct got_error *
9981 open_ref_view(struct tog_view *view, struct got_repository *repo)
9983 const struct got_error *err = NULL;
9984 struct tog_ref_view_state *s = &view->state.ref;
9986 s->selected_entry = 0;
9987 s->repo = repo;
9989 TAILQ_INIT(&s->refs);
9990 STAILQ_INIT(&s->colors);
9992 err = ref_view_load_refs(s);
9993 if (err)
9994 goto done;
9996 if (has_colors() && getenv("TOG_COLORS") != NULL) {
9997 err = add_color(&s->colors, "^refs/heads/",
9998 TOG_COLOR_REFS_HEADS,
9999 get_color_value("TOG_COLOR_REFS_HEADS"));
10000 if (err)
10001 goto done;
10003 err = add_color(&s->colors, "^refs/tags/",
10004 TOG_COLOR_REFS_TAGS,
10005 get_color_value("TOG_COLOR_REFS_TAGS"));
10006 if (err)
10007 goto done;
10009 err = add_color(&s->colors, "^refs/remotes/",
10010 TOG_COLOR_REFS_REMOTES,
10011 get_color_value("TOG_COLOR_REFS_REMOTES"));
10012 if (err)
10013 goto done;
10015 err = add_color(&s->colors, "^refs/got/backup/",
10016 TOG_COLOR_REFS_BACKUP,
10017 get_color_value("TOG_COLOR_REFS_BACKUP"));
10018 if (err)
10019 goto done;
10022 view->show = show_ref_view;
10023 view->input = input_ref_view;
10024 view->close = close_ref_view;
10025 view->search_start = search_start_ref_view;
10026 view->search_next = search_next_ref_view;
10027 done:
10028 if (err) {
10029 if (view->close == NULL)
10030 close_ref_view(view);
10031 view_close(view);
10033 return err;
10036 static const struct got_error *
10037 close_ref_view(struct tog_view *view)
10039 struct tog_ref_view_state *s = &view->state.ref;
10041 ref_view_free_refs(s);
10042 free_colors(&s->colors);
10044 return NULL;
10047 static const struct got_error *
10048 resolve_reflist_entry(struct got_object_id **commit_id,
10049 struct tog_reflist_entry *re, struct got_repository *repo)
10051 const struct got_error *err = NULL;
10052 struct got_object_id *obj_id;
10053 struct got_tag_object *tag = NULL;
10054 int obj_type;
10056 *commit_id = NULL;
10058 err = got_ref_resolve(&obj_id, repo, re->ref);
10059 if (err)
10060 return err;
10062 err = got_object_get_type(&obj_type, repo, obj_id);
10063 if (err)
10064 goto done;
10066 switch (obj_type) {
10067 case GOT_OBJ_TYPE_COMMIT:
10068 break;
10069 case GOT_OBJ_TYPE_TAG:
10071 * Git allows nested tags that point to tags; keep peeling
10072 * till we reach the bottom, which is always a non-tag ref.
10074 do {
10075 if (tag != NULL)
10076 got_object_tag_close(tag);
10077 err = got_object_open_as_tag(&tag, repo, obj_id);
10078 if (err)
10079 goto done;
10080 free(obj_id);
10081 obj_id = got_object_id_dup(
10082 got_object_tag_get_object_id(tag));
10083 if (obj_id == NULL) {
10084 err = got_error_from_errno("got_object_id_dup");
10085 goto done;
10087 err = got_object_get_type(&obj_type, repo, obj_id);
10088 if (err)
10089 goto done;
10090 } while (obj_type == GOT_OBJ_TYPE_TAG);
10091 if (obj_type != GOT_OBJ_TYPE_COMMIT)
10092 err = got_error(GOT_ERR_OBJ_TYPE);
10093 break;
10094 default:
10095 err = got_error(GOT_ERR_OBJ_TYPE);
10096 break;
10099 done:
10100 if (tag)
10101 got_object_tag_close(tag);
10102 if (err == NULL)
10103 *commit_id = obj_id;
10104 else
10105 free(obj_id);
10106 return err;
10109 static const struct got_error *
10110 log_ref_entry(struct tog_view **new_view, int begin_y, int begin_x,
10111 struct tog_reflist_entry *re, struct got_repository *repo)
10113 struct tog_view *log_view;
10114 const struct got_error *err = NULL;
10115 struct got_object_id *commit_id = NULL;
10117 *new_view = NULL;
10119 err = resolve_reflist_entry(&commit_id, re, repo);
10120 if (err)
10121 return err;
10123 log_view = view_open(0, 0, begin_y, begin_x, TOG_VIEW_LOG);
10124 if (log_view == NULL) {
10125 err = got_error_from_errno("view_open");
10126 goto done;
10129 err = open_log_view(log_view, commit_id, repo,
10130 got_ref_get_name(re->ref), "", 0, NULL);
10131 done:
10132 if (err)
10133 view_close(log_view);
10134 else
10135 *new_view = log_view;
10136 free(commit_id);
10137 return err;
10140 static void
10141 ref_scroll_up(struct tog_ref_view_state *s, int maxscroll)
10143 struct tog_reflist_entry *re;
10144 int i = 0;
10146 if (s->first_displayed_entry == TAILQ_FIRST(&s->refs))
10147 return;
10149 re = TAILQ_PREV(s->first_displayed_entry, tog_reflist_head, entry);
10150 while (i++ < maxscroll) {
10151 if (re == NULL)
10152 break;
10153 s->first_displayed_entry = re;
10154 re = TAILQ_PREV(re, tog_reflist_head, entry);
10158 static const struct got_error *
10159 ref_scroll_down(struct tog_view *view, int maxscroll)
10161 struct tog_ref_view_state *s = &view->state.ref;
10162 struct tog_reflist_entry *next, *last;
10163 int n = 0;
10165 if (s->first_displayed_entry)
10166 next = TAILQ_NEXT(s->first_displayed_entry, entry);
10167 else
10168 next = TAILQ_FIRST(&s->refs);
10170 last = s->last_displayed_entry;
10171 while (next && n++ < maxscroll) {
10172 if (last) {
10173 s->last_displayed_entry = last;
10174 last = TAILQ_NEXT(last, entry);
10176 if (last || (view->mode == TOG_VIEW_SPLIT_HRZN)) {
10177 s->first_displayed_entry = next;
10178 next = TAILQ_NEXT(next, entry);
10182 return NULL;
10185 static const struct got_error *
10186 search_start_ref_view(struct tog_view *view)
10188 struct tog_ref_view_state *s = &view->state.ref;
10190 s->matched_entry = NULL;
10191 return NULL;
10194 static int
10195 match_reflist_entry(struct tog_reflist_entry *re, regex_t *regex)
10197 regmatch_t regmatch;
10199 return regexec(regex, got_ref_get_name(re->ref), 1, &regmatch,
10200 0) == 0;
10203 static const struct got_error *
10204 search_next_ref_view(struct tog_view *view)
10206 struct tog_ref_view_state *s = &view->state.ref;
10207 struct tog_reflist_entry *re = NULL;
10209 if (!view->searching) {
10210 view->search_next_done = TOG_SEARCH_HAVE_MORE;
10211 return NULL;
10214 if (s->matched_entry) {
10215 if (view->searching == TOG_SEARCH_FORWARD) {
10216 if (s->selected_entry)
10217 re = TAILQ_NEXT(s->selected_entry, entry);
10218 else
10219 re = TAILQ_PREV(s->selected_entry,
10220 tog_reflist_head, entry);
10221 } else {
10222 if (s->selected_entry == NULL)
10223 re = TAILQ_LAST(&s->refs, tog_reflist_head);
10224 else
10225 re = TAILQ_PREV(s->selected_entry,
10226 tog_reflist_head, entry);
10228 } else {
10229 if (s->selected_entry)
10230 re = s->selected_entry;
10231 else if (view->searching == TOG_SEARCH_FORWARD)
10232 re = TAILQ_FIRST(&s->refs);
10233 else
10234 re = TAILQ_LAST(&s->refs, tog_reflist_head);
10237 while (1) {
10238 if (re == NULL) {
10239 if (s->matched_entry == NULL) {
10240 view->search_next_done = TOG_SEARCH_HAVE_MORE;
10241 return NULL;
10243 if (view->searching == TOG_SEARCH_FORWARD)
10244 re = TAILQ_FIRST(&s->refs);
10245 else
10246 re = TAILQ_LAST(&s->refs, tog_reflist_head);
10249 if (match_reflist_entry(re, &view->regex)) {
10250 view->search_next_done = TOG_SEARCH_HAVE_MORE;
10251 s->matched_entry = re;
10252 break;
10255 if (view->searching == TOG_SEARCH_FORWARD)
10256 re = TAILQ_NEXT(re, entry);
10257 else
10258 re = TAILQ_PREV(re, tog_reflist_head, entry);
10261 if (s->matched_entry) {
10262 s->first_displayed_entry = s->matched_entry;
10263 s->selected = 0;
10266 return NULL;
10269 static const struct got_error *
10270 show_ref_view(struct tog_view *view)
10272 const struct got_error *err = NULL;
10273 struct tog_ref_view_state *s = &view->state.ref;
10274 struct tog_reflist_entry *re;
10275 char *line = NULL;
10276 wchar_t *wline;
10277 struct tog_color *tc;
10278 int width, n, scrollx;
10279 int limit = view->nlines;
10281 werase(view->window);
10283 s->ndisplayed = 0;
10284 if (view_is_hsplit_top(view))
10285 --limit; /* border */
10287 if (limit == 0)
10288 return NULL;
10290 re = s->first_displayed_entry;
10292 if (asprintf(&line, "references [%d/%d]", re->idx + s->selected + 1,
10293 s->nrefs) == -1)
10294 return got_error_from_errno("asprintf");
10296 err = format_line(&wline, &width, NULL, line, 0, view->ncols, 0, 0);
10297 if (err) {
10298 free(line);
10299 return err;
10301 if (view_needs_focus_indication(view))
10302 wstandout(view->window);
10303 waddwstr(view->window, wline);
10304 while (width++ < view->ncols)
10305 waddch(view->window, ' ');
10306 if (view_needs_focus_indication(view))
10307 wstandend(view->window);
10308 free(wline);
10309 wline = NULL;
10310 free(line);
10311 line = NULL;
10312 if (--limit <= 0)
10313 return NULL;
10315 n = 0;
10316 view->maxx = 0;
10317 while (re && limit > 0) {
10318 char *line = NULL;
10319 char ymd[13]; /* YYYY-MM-DD + " " + NUL */
10321 if (s->show_date) {
10322 struct got_commit_object *ci;
10323 struct got_tag_object *tag;
10324 struct got_object_id *id;
10325 struct tm tm;
10326 time_t t;
10328 err = got_ref_resolve(&id, s->repo, re->ref);
10329 if (err)
10330 return err;
10331 err = got_object_open_as_tag(&tag, s->repo, id);
10332 if (err) {
10333 if (err->code != GOT_ERR_OBJ_TYPE) {
10334 free(id);
10335 return err;
10337 err = got_object_open_as_commit(&ci, s->repo,
10338 id);
10339 if (err) {
10340 free(id);
10341 return err;
10343 t = got_object_commit_get_committer_time(ci);
10344 got_object_commit_close(ci);
10345 } else {
10346 t = got_object_tag_get_tagger_time(tag);
10347 got_object_tag_close(tag);
10349 free(id);
10350 if (gmtime_r(&t, &tm) == NULL)
10351 return got_error_from_errno("gmtime_r");
10352 if (strftime(ymd, sizeof(ymd), "%F ", &tm) == 0)
10353 return got_error(GOT_ERR_NO_SPACE);
10355 if (got_ref_is_symbolic(re->ref)) {
10356 if (asprintf(&line, "%s%s -> %s", s->show_date ?
10357 ymd : "", got_ref_get_name(re->ref),
10358 got_ref_get_symref_target(re->ref)) == -1)
10359 return got_error_from_errno("asprintf");
10360 } else if (s->show_ids) {
10361 struct got_object_id *id;
10362 char *id_str;
10363 err = got_ref_resolve(&id, s->repo, re->ref);
10364 if (err)
10365 return err;
10366 err = got_object_id_str(&id_str, id);
10367 if (err) {
10368 free(id);
10369 return err;
10371 if (asprintf(&line, "%s%s: %s", s->show_date ? ymd : "",
10372 got_ref_get_name(re->ref), id_str) == -1) {
10373 err = got_error_from_errno("asprintf");
10374 free(id);
10375 free(id_str);
10376 return err;
10378 free(id);
10379 free(id_str);
10380 } else if (asprintf(&line, "%s%s", s->show_date ? ymd : "",
10381 got_ref_get_name(re->ref)) == -1)
10382 return got_error_from_errno("asprintf");
10384 /* use full line width to determine view->maxx */
10385 err = format_line(&wline, &width, NULL, line, 0, INT_MAX, 0, 0);
10386 if (err) {
10387 free(line);
10388 return err;
10390 view->maxx = MAX(view->maxx, width);
10391 free(wline);
10392 wline = NULL;
10394 err = format_line(&wline, &width, &scrollx, line, view->x,
10395 view->ncols, 0, 0);
10396 if (err) {
10397 free(line);
10398 return err;
10400 if (n == s->selected) {
10401 if (view->focussed)
10402 wstandout(view->window);
10403 s->selected_entry = re;
10405 tc = match_color(&s->colors, got_ref_get_name(re->ref));
10406 if (tc)
10407 wattr_on(view->window,
10408 COLOR_PAIR(tc->colorpair), NULL);
10409 waddwstr(view->window, &wline[scrollx]);
10410 if (tc)
10411 wattr_off(view->window,
10412 COLOR_PAIR(tc->colorpair), NULL);
10413 if (width < view->ncols)
10414 waddch(view->window, '\n');
10415 if (n == s->selected && view->focussed)
10416 wstandend(view->window);
10417 free(line);
10418 free(wline);
10419 wline = NULL;
10420 n++;
10421 s->ndisplayed++;
10422 s->last_displayed_entry = re;
10424 limit--;
10425 re = TAILQ_NEXT(re, entry);
10428 view_border(view);
10429 return err;
10432 static const struct got_error *
10433 browse_ref_tree(struct tog_view **new_view, int begin_y, int begin_x,
10434 struct tog_reflist_entry *re, struct got_repository *repo)
10436 const struct got_error *err = NULL;
10437 struct got_object_id *commit_id = NULL;
10438 struct tog_view *tree_view;
10440 *new_view = NULL;
10442 err = resolve_reflist_entry(&commit_id, re, repo);
10443 if (err)
10444 return err;
10446 tree_view = view_open(0, 0, begin_y, begin_x, TOG_VIEW_TREE);
10447 if (tree_view == NULL) {
10448 err = got_error_from_errno("view_open");
10449 goto done;
10452 err = open_tree_view(tree_view, commit_id,
10453 got_ref_get_name(re->ref), repo);
10454 if (err)
10455 goto done;
10457 *new_view = tree_view;
10458 done:
10459 free(commit_id);
10460 return err;
10463 static const struct got_error *
10464 ref_goto_line(struct tog_view *view, int nlines)
10466 const struct got_error *err = NULL;
10467 struct tog_ref_view_state *s = &view->state.ref;
10468 int g, idx = s->selected_entry->idx;
10470 g = view->gline;
10471 view->gline = 0;
10473 if (g == 0)
10474 g = 1;
10475 else if (g > s->nrefs)
10476 g = s->nrefs;
10478 if (g >= s->first_displayed_entry->idx + 1 &&
10479 g <= s->last_displayed_entry->idx + 1 &&
10480 g - s->first_displayed_entry->idx - 1 < nlines) {
10481 s->selected = g - s->first_displayed_entry->idx - 1;
10482 return NULL;
10485 if (idx + 1 < g) {
10486 err = ref_scroll_down(view, g - idx - 1);
10487 if (err)
10488 return err;
10489 if (TAILQ_NEXT(s->last_displayed_entry, entry) == NULL &&
10490 s->first_displayed_entry->idx + s->selected < g &&
10491 s->selected < s->ndisplayed - 1)
10492 s->selected = g - s->first_displayed_entry->idx - 1;
10493 } else if (idx + 1 > g)
10494 ref_scroll_up(s, idx - g + 1);
10496 if (g < nlines && s->first_displayed_entry->idx == 0)
10497 s->selected = g - 1;
10499 return NULL;
10503 static const struct got_error *
10504 input_ref_view(struct tog_view **new_view, struct tog_view *view, int ch)
10506 const struct got_error *err = NULL;
10507 struct tog_ref_view_state *s = &view->state.ref;
10508 struct tog_reflist_entry *re;
10509 int n, nscroll = view->nlines - 1;
10511 if (view->gline)
10512 return ref_goto_line(view, nscroll);
10514 switch (ch) {
10515 case '0':
10516 case '$':
10517 case KEY_RIGHT:
10518 case 'l':
10519 case KEY_LEFT:
10520 case 'h':
10521 horizontal_scroll_input(view, ch);
10522 break;
10523 case 'i':
10524 s->show_ids = !s->show_ids;
10525 view->count = 0;
10526 break;
10527 case 'm':
10528 s->show_date = !s->show_date;
10529 view->count = 0;
10530 break;
10531 case 'o':
10532 s->sort_by_date = !s->sort_by_date;
10533 view->action = s->sort_by_date ? "sort by date" : "sort by name";
10534 view->count = 0;
10535 err = got_reflist_sort(&tog_refs, s->sort_by_date ?
10536 got_ref_cmp_by_commit_timestamp_descending :
10537 tog_ref_cmp_by_name, s->repo);
10538 if (err)
10539 break;
10540 got_reflist_object_id_map_free(tog_refs_idmap);
10541 err = got_reflist_object_id_map_create(&tog_refs_idmap,
10542 &tog_refs, s->repo);
10543 if (err)
10544 break;
10545 ref_view_free_refs(s);
10546 err = ref_view_load_refs(s);
10547 break;
10548 case KEY_ENTER:
10549 case '\r':
10550 view->count = 0;
10551 if (!s->selected_entry)
10552 break;
10553 err = view_request_new(new_view, view, TOG_VIEW_LOG);
10554 break;
10555 case 'T':
10556 view->count = 0;
10557 if (!s->selected_entry)
10558 break;
10559 err = view_request_new(new_view, view, TOG_VIEW_TREE);
10560 break;
10561 case 'g':
10562 case '=':
10563 case KEY_HOME:
10564 s->selected = 0;
10565 view->count = 0;
10566 s->first_displayed_entry = TAILQ_FIRST(&s->refs);
10567 break;
10568 case 'G':
10569 case '*':
10570 case KEY_END: {
10571 int eos = view->nlines - 1;
10573 if (view->mode == TOG_VIEW_SPLIT_HRZN)
10574 --eos; /* border */
10575 s->selected = 0;
10576 view->count = 0;
10577 re = TAILQ_LAST(&s->refs, tog_reflist_head);
10578 for (n = 0; n < eos; n++) {
10579 if (re == NULL)
10580 break;
10581 s->first_displayed_entry = re;
10582 re = TAILQ_PREV(re, tog_reflist_head, entry);
10584 if (n > 0)
10585 s->selected = n - 1;
10586 break;
10588 case 'k':
10589 case KEY_UP:
10590 case CTRL('p'):
10591 if (s->selected > 0) {
10592 s->selected--;
10593 break;
10595 ref_scroll_up(s, 1);
10596 if (s->selected_entry == TAILQ_FIRST(&s->refs))
10597 view->count = 0;
10598 break;
10599 case CTRL('u'):
10600 case 'u':
10601 nscroll /= 2;
10602 /* FALL THROUGH */
10603 case KEY_PPAGE:
10604 case CTRL('b'):
10605 case 'b':
10606 if (s->first_displayed_entry == TAILQ_FIRST(&s->refs))
10607 s->selected -= MIN(nscroll, s->selected);
10608 ref_scroll_up(s, MAX(0, nscroll));
10609 if (s->selected_entry == TAILQ_FIRST(&s->refs))
10610 view->count = 0;
10611 break;
10612 case 'j':
10613 case KEY_DOWN:
10614 case CTRL('n'):
10615 if (s->selected < s->ndisplayed - 1) {
10616 s->selected++;
10617 break;
10619 if (TAILQ_NEXT(s->last_displayed_entry, entry) == NULL) {
10620 /* can't scroll any further */
10621 view->count = 0;
10622 break;
10624 ref_scroll_down(view, 1);
10625 break;
10626 case CTRL('d'):
10627 case 'd':
10628 nscroll /= 2;
10629 /* FALL THROUGH */
10630 case KEY_NPAGE:
10631 case CTRL('f'):
10632 case 'f':
10633 case ' ':
10634 if (TAILQ_NEXT(s->last_displayed_entry, entry) == NULL) {
10635 /* can't scroll any further; move cursor down */
10636 if (s->selected < s->ndisplayed - 1)
10637 s->selected += MIN(nscroll,
10638 s->ndisplayed - s->selected - 1);
10639 if (view->count > 1 && s->selected < s->ndisplayed - 1)
10640 s->selected += s->ndisplayed - s->selected - 1;
10641 view->count = 0;
10642 break;
10644 ref_scroll_down(view, nscroll);
10645 break;
10646 case CTRL('l'):
10647 view->count = 0;
10648 tog_free_refs();
10649 err = tog_load_refs(s->repo, s->sort_by_date);
10650 if (err)
10651 break;
10652 ref_view_free_refs(s);
10653 err = ref_view_load_refs(s);
10654 break;
10655 case KEY_RESIZE:
10656 if (view->nlines >= 2 && s->selected >= view->nlines - 1)
10657 s->selected = view->nlines - 2;
10658 break;
10659 default:
10660 view->count = 0;
10661 break;
10664 return err;
10667 __dead static void
10668 usage_ref(void)
10670 endwin();
10671 fprintf(stderr, "usage: %s ref [-r repository-path]\n",
10672 getprogname());
10673 exit(1);
10676 static const struct got_error *
10677 cmd_ref(int argc, char *argv[])
10679 const struct got_error *error;
10680 struct got_repository *repo = NULL;
10681 struct got_worktree *worktree = NULL;
10682 char *cwd = NULL, *repo_path = NULL;
10683 int ch;
10684 struct tog_view *view;
10685 int *pack_fds = NULL;
10687 while ((ch = getopt(argc, argv, "r:")) != -1) {
10688 switch (ch) {
10689 case 'r':
10690 repo_path = realpath(optarg, NULL);
10691 if (repo_path == NULL)
10692 return got_error_from_errno2("realpath",
10693 optarg);
10694 break;
10695 default:
10696 usage_ref();
10697 /* NOTREACHED */
10701 argc -= optind;
10702 argv += optind;
10704 if (argc > 1)
10705 usage_ref();
10707 error = got_repo_pack_fds_open(&pack_fds);
10708 if (error != NULL)
10709 goto done;
10711 if (repo_path == NULL) {
10712 cwd = getcwd(NULL, 0);
10713 if (cwd == NULL)
10714 return got_error_from_errno("getcwd");
10715 error = got_worktree_open(&worktree, cwd, NULL);
10716 if (error && error->code != GOT_ERR_NOT_WORKTREE)
10717 goto done;
10718 if (worktree)
10719 repo_path =
10720 strdup(got_worktree_get_repo_path(worktree));
10721 else
10722 repo_path = strdup(cwd);
10723 if (repo_path == NULL) {
10724 error = got_error_from_errno("strdup");
10725 goto done;
10729 error = got_repo_open(&repo, repo_path, NULL, pack_fds);
10730 if (error != NULL)
10731 goto done;
10733 init_curses();
10735 error = apply_unveil(got_repo_get_path(repo), NULL);
10736 if (error)
10737 goto done;
10739 error = tog_load_refs(repo, 0);
10740 if (error)
10741 goto done;
10743 view = view_open(0, 0, 0, 0, TOG_VIEW_REF);
10744 if (view == NULL) {
10745 error = got_error_from_errno("view_open");
10746 goto done;
10749 error = open_ref_view(view, repo);
10750 if (error)
10751 goto done;
10753 if (worktree) {
10754 error = set_tog_base_commit(repo, worktree);
10755 if (error != NULL)
10756 goto done;
10758 /* Release work tree lock. */
10759 got_worktree_close(worktree);
10760 worktree = NULL;
10763 error = view_loop(view);
10765 done:
10766 free(tog_base_commit.id);
10767 free(repo_path);
10768 free(cwd);
10769 if (worktree != NULL)
10770 got_worktree_close(worktree);
10771 if (repo) {
10772 const struct got_error *close_err;
10774 close_err = got_repo_close(repo);
10775 if (close_err && error == NULL)
10776 error = close_err;
10778 if (pack_fds) {
10779 const struct got_error *pack_err;
10781 pack_err = got_repo_pack_fds_close(pack_fds);
10782 if (pack_err && error == NULL)
10783 error = pack_err;
10785 tog_free_refs();
10786 return error;
10789 static const struct got_error*
10790 win_draw_center(WINDOW *win, size_t y, size_t x, size_t maxx, int focus,
10791 const char *str)
10793 size_t len;
10795 if (win == NULL)
10796 win = stdscr;
10798 len = strlen(str);
10799 x = x ? x : maxx > len ? (maxx - len) / 2 : 0;
10801 if (focus)
10802 wstandout(win);
10803 if (mvwprintw(win, y, x, "%s", str) == ERR)
10804 return got_error_msg(GOT_ERR_RANGE, "mvwprintw");
10805 if (focus)
10806 wstandend(win);
10808 return NULL;
10811 static const struct got_error *
10812 add_line_offset(off_t **line_offsets, size_t *nlines, off_t off)
10814 off_t *p;
10816 p = reallocarray(*line_offsets, *nlines + 1, sizeof(off_t));
10817 if (p == NULL) {
10818 free(*line_offsets);
10819 *line_offsets = NULL;
10820 return got_error_from_errno("reallocarray");
10823 *line_offsets = p;
10824 (*line_offsets)[*nlines] = off;
10825 ++(*nlines);
10826 return NULL;
10829 static const struct got_error *
10830 max_key_str(int *ret, const struct tog_key_map *km, size_t n)
10832 *ret = 0;
10834 for (;n > 0; --n, ++km) {
10835 char *t0, *t, *k;
10836 size_t len = 1;
10838 if (km->keys == NULL)
10839 continue;
10841 t = t0 = strdup(km->keys);
10842 if (t0 == NULL)
10843 return got_error_from_errno("strdup");
10845 len += strlen(t);
10846 while ((k = strsep(&t, " ")) != NULL)
10847 len += strlen(k) > 1 ? 2 : 0;
10848 free(t0);
10849 *ret = MAX(*ret, len);
10852 return NULL;
10856 * Write keymap section headers, keys, and key info in km to f.
10857 * Save line offset to *off. If terminal has UTF8 encoding enabled,
10858 * wrap control and symbolic keys in guillemets, else use <>.
10860 static const struct got_error *
10861 format_help_line(off_t *off, FILE *f, const struct tog_key_map *km, int width)
10863 int n, len = width;
10865 if (km->keys) {
10866 static const char *u8_glyph[] = {
10867 "\xe2\x80\xb9", /* U+2039 (utf8 <) */
10868 "\xe2\x80\xba" /* U+203A (utf8 >) */
10870 char *t0, *t, *k;
10871 int cs, s, first = 1;
10873 cs = got_locale_is_utf8();
10875 t = t0 = strdup(km->keys);
10876 if (t0 == NULL)
10877 return got_error_from_errno("strdup");
10879 len = strlen(km->keys);
10880 while ((k = strsep(&t, " ")) != NULL) {
10881 s = strlen(k) > 1; /* control or symbolic key */
10882 n = fprintf(f, "%s%s%s%s%s", first ? " " : "",
10883 cs && s ? u8_glyph[0] : s ? "<" : "", k,
10884 cs && s ? u8_glyph[1] : s ? ">" : "", t ? " " : "");
10885 if (n < 0) {
10886 free(t0);
10887 return got_error_from_errno("fprintf");
10889 first = 0;
10890 len += s ? 2 : 0;
10891 *off += n;
10893 free(t0);
10895 n = fprintf(f, "%*s%s\n", width - len, width - len ? " " : "", km->info);
10896 if (n < 0)
10897 return got_error_from_errno("fprintf");
10898 *off += n;
10900 return NULL;
10903 static const struct got_error *
10904 format_help(struct tog_help_view_state *s)
10906 const struct got_error *err = NULL;
10907 off_t off = 0;
10908 int i, max, n, show = s->all;
10909 static const struct tog_key_map km[] = {
10910 #define KEYMAP_(info, type) { NULL, (info), type }
10911 #define KEY_(keys, info) { (keys), (info), TOG_KEYMAP_KEYS }
10912 GENERATE_HELP
10913 #undef KEYMAP_
10914 #undef KEY_
10917 err = add_line_offset(&s->line_offsets, &s->nlines, 0);
10918 if (err)
10919 return err;
10921 n = nitems(km);
10922 err = max_key_str(&max, km, n);
10923 if (err)
10924 return err;
10926 for (i = 0; i < n; ++i) {
10927 if (km[i].keys == NULL) {
10928 show = s->all;
10929 if (km[i].type == TOG_KEYMAP_GLOBAL ||
10930 km[i].type == s->type || s->all)
10931 show = 1;
10933 if (show) {
10934 err = format_help_line(&off, s->f, &km[i], max);
10935 if (err)
10936 return err;
10937 err = add_line_offset(&s->line_offsets, &s->nlines, off);
10938 if (err)
10939 return err;
10942 fputc('\n', s->f);
10943 ++off;
10944 err = add_line_offset(&s->line_offsets, &s->nlines, off);
10945 return err;
10948 static const struct got_error *
10949 create_help(struct tog_help_view_state *s)
10951 FILE *f;
10952 const struct got_error *err;
10954 free(s->line_offsets);
10955 s->line_offsets = NULL;
10956 s->nlines = 0;
10958 f = got_opentemp();
10959 if (f == NULL)
10960 return got_error_from_errno("got_opentemp");
10961 s->f = f;
10963 err = format_help(s);
10964 if (err)
10965 return err;
10967 if (s->f && fflush(s->f) != 0)
10968 return got_error_from_errno("fflush");
10970 return NULL;
10973 static const struct got_error *
10974 search_start_help_view(struct tog_view *view)
10976 view->state.help.matched_line = 0;
10977 return NULL;
10980 static void
10981 search_setup_help_view(struct tog_view *view, FILE **f, off_t **line_offsets,
10982 size_t *nlines, int **first, int **last, int **match, int **selected)
10984 struct tog_help_view_state *s = &view->state.help;
10986 *f = s->f;
10987 *nlines = s->nlines;
10988 *line_offsets = s->line_offsets;
10989 *match = &s->matched_line;
10990 *first = &s->first_displayed_line;
10991 *last = &s->last_displayed_line;
10992 *selected = &s->selected_line;
10995 static const struct got_error *
10996 show_help_view(struct tog_view *view)
10998 struct tog_help_view_state *s = &view->state.help;
10999 const struct got_error *err;
11000 regmatch_t *regmatch = &view->regmatch;
11001 wchar_t *wline;
11002 char *line;
11003 ssize_t linelen;
11004 size_t linesz = 0;
11005 int width, nprinted = 0, rc = 0;
11006 int eos = view->nlines;
11008 if (view_is_hsplit_top(view))
11009 --eos; /* account for border */
11011 s->lineno = 0;
11012 rewind(s->f);
11013 werase(view->window);
11015 if (view->gline > s->nlines - 1)
11016 view->gline = s->nlines - 1;
11018 err = win_draw_center(view->window, 0, 0, view->ncols,
11019 view_needs_focus_indication(view),
11020 "tog help (press q to return to tog)");
11021 if (err)
11022 return err;
11023 if (eos <= 1)
11024 return NULL;
11025 waddstr(view->window, "\n\n");
11026 eos -= 2;
11028 s->eof = 0;
11029 view->maxx = 0;
11030 line = NULL;
11031 while (eos > 0 && nprinted < eos) {
11032 attr_t attr = 0;
11034 linelen = getline(&line, &linesz, s->f);
11035 if (linelen == -1) {
11036 if (!feof(s->f)) {
11037 free(line);
11038 return got_ferror(s->f, GOT_ERR_IO);
11040 s->eof = 1;
11041 break;
11043 if (++s->lineno < s->first_displayed_line)
11044 continue;
11045 if (view->gline && !gotoline(view, &s->lineno, &nprinted))
11046 continue;
11047 if (s->lineno == view->hiline)
11048 attr = A_STANDOUT;
11050 err = format_line(&wline, &width, NULL, line, 0, INT_MAX, 0,
11051 view->x ? 1 : 0);
11052 if (err) {
11053 free(line);
11054 return err;
11056 view->maxx = MAX(view->maxx, width);
11057 free(wline);
11058 wline = NULL;
11060 if (attr)
11061 wattron(view->window, attr);
11062 if (s->first_displayed_line + nprinted == s->matched_line &&
11063 regmatch->rm_so >= 0 && regmatch->rm_so < regmatch->rm_eo) {
11064 err = add_matched_line(&width, line, view->ncols - 1, 0,
11065 view->window, view->x, regmatch);
11066 if (err) {
11067 free(line);
11068 return err;
11070 } else {
11071 int skip;
11073 err = format_line(&wline, &width, &skip, line,
11074 view->x, view->ncols, 0, view->x ? 1 : 0);
11075 if (err) {
11076 free(line);
11077 return err;
11079 waddwstr(view->window, &wline[skip]);
11080 free(wline);
11081 wline = NULL;
11083 if (s->lineno == view->hiline) {
11084 while (width++ < view->ncols)
11085 waddch(view->window, ' ');
11086 } else {
11087 if (width < view->ncols)
11088 waddch(view->window, '\n');
11090 if (attr)
11091 wattroff(view->window, attr);
11092 if (++nprinted == 1)
11093 s->first_displayed_line = s->lineno;
11095 free(line);
11096 if (nprinted > 0)
11097 s->last_displayed_line = s->first_displayed_line + nprinted - 1;
11098 else
11099 s->last_displayed_line = s->first_displayed_line;
11101 view_border(view);
11103 if (s->eof) {
11104 rc = waddnstr(view->window,
11105 "See the tog(1) manual page for full documentation",
11106 view->ncols - 1);
11107 if (rc == ERR)
11108 return got_error_msg(GOT_ERR_RANGE, "waddnstr");
11109 } else {
11110 wmove(view->window, view->nlines - 1, 0);
11111 wclrtoeol(view->window);
11112 wstandout(view->window);
11113 rc = waddnstr(view->window, "scroll down for more...",
11114 view->ncols - 1);
11115 if (rc == ERR)
11116 return got_error_msg(GOT_ERR_RANGE, "waddnstr");
11117 if (getcurx(view->window) < view->ncols - 6) {
11118 rc = wprintw(view->window, "[%.0f%%]",
11119 100.00 * s->last_displayed_line / s->nlines);
11120 if (rc == ERR)
11121 return got_error_msg(GOT_ERR_IO, "wprintw");
11123 wstandend(view->window);
11126 return NULL;
11129 static const struct got_error *
11130 input_help_view(struct tog_view **new_view, struct tog_view *view, int ch)
11132 struct tog_help_view_state *s = &view->state.help;
11133 const struct got_error *err = NULL;
11134 char *line = NULL;
11135 ssize_t linelen;
11136 size_t linesz = 0;
11137 int eos, nscroll;
11139 eos = nscroll = view->nlines;
11140 if (view_is_hsplit_top(view))
11141 --eos; /* border */
11143 s->lineno = s->first_displayed_line - 1 + s->selected_line;
11145 switch (ch) {
11146 case '0':
11147 case '$':
11148 case KEY_RIGHT:
11149 case 'l':
11150 case KEY_LEFT:
11151 case 'h':
11152 horizontal_scroll_input(view, ch);
11153 break;
11154 case 'g':
11155 case KEY_HOME:
11156 s->first_displayed_line = 1;
11157 view->count = 0;
11158 break;
11159 case 'G':
11160 case KEY_END:
11161 view->count = 0;
11162 if (s->eof)
11163 break;
11164 s->first_displayed_line = (s->nlines - eos) + 3;
11165 s->eof = 1;
11166 break;
11167 case 'k':
11168 case KEY_UP:
11169 if (s->first_displayed_line > 1)
11170 --s->first_displayed_line;
11171 else
11172 view->count = 0;
11173 break;
11174 case CTRL('u'):
11175 case 'u':
11176 nscroll /= 2;
11177 /* FALL THROUGH */
11178 case KEY_PPAGE:
11179 case CTRL('b'):
11180 case 'b':
11181 if (s->first_displayed_line == 1) {
11182 view->count = 0;
11183 break;
11185 while (--nscroll > 0 && s->first_displayed_line > 1)
11186 s->first_displayed_line--;
11187 break;
11188 case 'j':
11189 case KEY_DOWN:
11190 case CTRL('n'):
11191 if (!s->eof)
11192 ++s->first_displayed_line;
11193 else
11194 view->count = 0;
11195 break;
11196 case CTRL('d'):
11197 case 'd':
11198 nscroll /= 2;
11199 /* FALL THROUGH */
11200 case KEY_NPAGE:
11201 case CTRL('f'):
11202 case 'f':
11203 case ' ':
11204 if (s->eof) {
11205 view->count = 0;
11206 break;
11208 while (!s->eof && --nscroll > 0) {
11209 linelen = getline(&line, &linesz, s->f);
11210 s->first_displayed_line++;
11211 if (linelen == -1) {
11212 if (feof(s->f))
11213 s->eof = 1;
11214 else
11215 err = got_ferror(s->f, GOT_ERR_IO);
11216 break;
11219 free(line);
11220 break;
11221 default:
11222 view->count = 0;
11223 break;
11226 return err;
11229 static const struct got_error *
11230 close_help_view(struct tog_view *view)
11232 struct tog_help_view_state *s = &view->state.help;
11234 free(s->line_offsets);
11235 s->line_offsets = NULL;
11236 if (fclose(s->f) == EOF)
11237 return got_error_from_errno("fclose");
11239 return NULL;
11242 static const struct got_error *
11243 reset_help_view(struct tog_view *view)
11245 struct tog_help_view_state *s = &view->state.help;
11248 if (s->f && fclose(s->f) == EOF)
11249 return got_error_from_errno("fclose");
11251 wclear(view->window);
11252 view->count = 0;
11253 view->x = 0;
11254 s->all = !s->all;
11255 s->first_displayed_line = 1;
11256 s->last_displayed_line = view->nlines;
11257 s->matched_line = 0;
11259 return create_help(s);
11262 static const struct got_error *
11263 open_help_view(struct tog_view *view, struct tog_view *parent)
11265 const struct got_error *err = NULL;
11266 struct tog_help_view_state *s = &view->state.help;
11268 s->type = (enum tog_keymap_type)parent->type;
11269 s->first_displayed_line = 1;
11270 s->last_displayed_line = view->nlines;
11271 s->selected_line = 1;
11273 view->show = show_help_view;
11274 view->input = input_help_view;
11275 view->reset = reset_help_view;
11276 view->close = close_help_view;
11277 view->search_start = search_start_help_view;
11278 view->search_setup = search_setup_help_view;
11279 view->search_next = search_next_view_match;
11281 err = create_help(s);
11282 return err;
11285 static const struct got_error *
11286 view_dispatch_request(struct tog_view **new_view, struct tog_view *view,
11287 enum tog_view_type request, int y, int x)
11289 const struct got_error *err = NULL;
11291 *new_view = NULL;
11293 switch (request) {
11294 case TOG_VIEW_DIFF:
11295 if (view->type == TOG_VIEW_LOG) {
11296 struct tog_log_view_state *s = &view->state.log;
11298 err = open_diff_view_for_commit(new_view, y, x,
11299 s->selected_entry, view, s->repo);
11300 } else
11301 return got_error_msg(GOT_ERR_NOT_IMPL,
11302 "parent/child view pair not supported");
11303 break;
11304 case TOG_VIEW_BLAME:
11305 if (view->type == TOG_VIEW_TREE) {
11306 struct tog_tree_view_state *s = &view->state.tree;
11308 err = blame_tree_entry(new_view, y, x,
11309 s->selected_entry, &s->parents, s->commit_id,
11310 s->repo);
11311 } else
11312 return got_error_msg(GOT_ERR_NOT_IMPL,
11313 "parent/child view pair not supported");
11314 break;
11315 case TOG_VIEW_LOG:
11316 tog_base_commit.idx = -1;
11317 if (view->type == TOG_VIEW_BLAME)
11318 err = log_annotated_line(new_view, y, x,
11319 view->state.blame.repo, view->state.blame.id_to_log);
11320 else if (view->type == TOG_VIEW_TREE)
11321 err = log_selected_tree_entry(new_view, y, x,
11322 &view->state.tree);
11323 else if (view->type == TOG_VIEW_REF)
11324 err = log_ref_entry(new_view, y, x,
11325 view->state.ref.selected_entry,
11326 view->state.ref.repo);
11327 else
11328 return got_error_msg(GOT_ERR_NOT_IMPL,
11329 "parent/child view pair not supported");
11330 break;
11331 case TOG_VIEW_TREE:
11332 if (view->type == TOG_VIEW_LOG)
11333 err = browse_commit_tree(new_view, y, x,
11334 view->state.log.selected_entry,
11335 view->state.log.in_repo_path,
11336 view->state.log.head_ref_name,
11337 view->state.log.repo);
11338 else if (view->type == TOG_VIEW_REF)
11339 err = browse_ref_tree(new_view, y, x,
11340 view->state.ref.selected_entry,
11341 view->state.ref.repo);
11342 else
11343 return got_error_msg(GOT_ERR_NOT_IMPL,
11344 "parent/child view pair not supported");
11345 break;
11346 case TOG_VIEW_REF:
11347 *new_view = view_open(0, 0, y, x, TOG_VIEW_REF);
11348 if (*new_view == NULL)
11349 return got_error_from_errno("view_open");
11350 if (view->type == TOG_VIEW_LOG)
11351 err = open_ref_view(*new_view, view->state.log.repo);
11352 else if (view->type == TOG_VIEW_TREE)
11353 err = open_ref_view(*new_view, view->state.tree.repo);
11354 else
11355 err = got_error_msg(GOT_ERR_NOT_IMPL,
11356 "parent/child view pair not supported");
11357 if (err)
11358 view_close(*new_view);
11359 break;
11360 case TOG_VIEW_HELP:
11361 *new_view = view_open(0, 0, 0, 0, TOG_VIEW_HELP);
11362 if (*new_view == NULL)
11363 return got_error_from_errno("view_open");
11364 err = open_help_view(*new_view, view);
11365 if (err)
11366 view_close(*new_view);
11367 break;
11368 default:
11369 return got_error_msg(GOT_ERR_NOT_IMPL, "invalid view");
11372 return err;
11376 * If view was scrolled down to move the selected line into view when opening a
11377 * horizontal split, scroll back up when closing the split/toggling fullscreen.
11379 static void
11380 offset_selection_up(struct tog_view *view)
11382 switch (view->type) {
11383 case TOG_VIEW_BLAME: {
11384 struct tog_blame_view_state *s = &view->state.blame;
11385 if (s->first_displayed_line == 1) {
11386 s->selected_line = MAX(s->selected_line - view->offset,
11388 break;
11390 if (s->first_displayed_line > view->offset)
11391 s->first_displayed_line -= view->offset;
11392 else
11393 s->first_displayed_line = 1;
11394 s->selected_line += view->offset;
11395 break;
11397 case TOG_VIEW_LOG:
11398 log_scroll_up(&view->state.log, view->offset);
11399 view->state.log.selected += view->offset;
11400 break;
11401 case TOG_VIEW_REF:
11402 ref_scroll_up(&view->state.ref, view->offset);
11403 view->state.ref.selected += view->offset;
11404 break;
11405 case TOG_VIEW_TREE:
11406 tree_scroll_up(&view->state.tree, view->offset);
11407 view->state.tree.selected += view->offset;
11408 break;
11409 default:
11410 break;
11413 view->offset = 0;
11417 * If the selected line is in the section of screen covered by the bottom split,
11418 * scroll down offset lines to move it into view and index its new position.
11420 static const struct got_error *
11421 offset_selection_down(struct tog_view *view)
11423 const struct got_error *err = NULL;
11424 const struct got_error *(*scrolld)(struct tog_view *, int);
11425 int *selected = NULL;
11426 int header, offset;
11428 switch (view->type) {
11429 case TOG_VIEW_BLAME: {
11430 struct tog_blame_view_state *s = &view->state.blame;
11431 header = 3;
11432 scrolld = NULL;
11433 if (s->selected_line > view->nlines - header) {
11434 offset = abs(view->nlines - s->selected_line - header);
11435 s->first_displayed_line += offset;
11436 s->selected_line -= offset;
11437 view->offset = offset;
11439 break;
11441 case TOG_VIEW_LOG: {
11442 struct tog_log_view_state *s = &view->state.log;
11443 scrolld = &log_scroll_down;
11444 header = view_is_parent_view(view) ? 3 : 2;
11445 selected = &s->selected;
11446 break;
11448 case TOG_VIEW_REF: {
11449 struct tog_ref_view_state *s = &view->state.ref;
11450 scrolld = &ref_scroll_down;
11451 header = 3;
11452 selected = &s->selected;
11453 break;
11455 case TOG_VIEW_TREE: {
11456 struct tog_tree_view_state *s = &view->state.tree;
11457 scrolld = &tree_scroll_down;
11458 header = 5;
11459 selected = &s->selected;
11460 break;
11462 default:
11463 selected = NULL;
11464 scrolld = NULL;
11465 header = 0;
11466 break;
11469 if (selected && *selected > view->nlines - header) {
11470 offset = abs(view->nlines - *selected - header);
11471 view->offset = offset;
11472 if (scrolld && offset) {
11473 err = scrolld(view, offset);
11474 *selected -= MIN(*selected, offset);
11478 return err;
11481 static void
11482 list_commands(FILE *fp)
11484 size_t i;
11486 fprintf(fp, "commands:");
11487 for (i = 0; i < nitems(tog_commands); i++) {
11488 const struct tog_cmd *cmd = &tog_commands[i];
11489 fprintf(fp, " %s", cmd->name);
11491 fputc('\n', fp);
11494 __dead static void
11495 usage(int hflag, int status)
11497 FILE *fp = (status == 0) ? stdout : stderr;
11499 fprintf(fp, "usage: %s [-hV] command [arg ...]\n",
11500 getprogname());
11501 if (hflag) {
11502 fprintf(fp, "lazy usage: %s path\n", getprogname());
11503 list_commands(fp);
11505 exit(status);
11508 static char **
11509 make_argv(int argc, ...)
11511 va_list ap;
11512 char **argv;
11513 int i;
11515 va_start(ap, argc);
11517 argv = calloc(argc, sizeof(char *));
11518 if (argv == NULL)
11519 err(1, "calloc");
11520 for (i = 0; i < argc; i++) {
11521 argv[i] = strdup(va_arg(ap, char *));
11522 if (argv[i] == NULL)
11523 err(1, "strdup");
11526 va_end(ap);
11527 return argv;
11531 * Try to convert 'tog path' into a 'tog log path' command.
11532 * The user could simply have mistyped the command rather than knowingly
11533 * provided a path. So check whether argv[0] can in fact be resolved
11534 * to a path in the HEAD commit and print a special error if not.
11535 * This hack is for mpi@ <3
11537 static const struct got_error *
11538 tog_log_with_path(int argc, char *argv[])
11540 const struct got_error *error = NULL, *close_err;
11541 const struct tog_cmd *cmd = NULL;
11542 struct got_repository *repo = NULL;
11543 struct got_worktree *worktree = NULL;
11544 struct got_object_id *commit_id = NULL, *id = NULL;
11545 struct got_commit_object *commit = NULL;
11546 char *cwd = NULL, *repo_path = NULL, *in_repo_path = NULL;
11547 char *commit_id_str = NULL, **cmd_argv = NULL;
11548 int *pack_fds = NULL;
11550 cwd = getcwd(NULL, 0);
11551 if (cwd == NULL)
11552 return got_error_from_errno("getcwd");
11554 error = got_repo_pack_fds_open(&pack_fds);
11555 if (error != NULL)
11556 goto done;
11558 error = got_worktree_open(&worktree, cwd, NULL);
11559 if (error && error->code != GOT_ERR_NOT_WORKTREE)
11560 goto done;
11562 if (worktree)
11563 repo_path = strdup(got_worktree_get_repo_path(worktree));
11564 else
11565 repo_path = strdup(cwd);
11566 if (repo_path == NULL) {
11567 error = got_error_from_errno("strdup");
11568 goto done;
11571 error = got_repo_open(&repo, repo_path, NULL, pack_fds);
11572 if (error != NULL)
11573 goto done;
11575 error = get_in_repo_path_from_argv0(&in_repo_path, argc, argv,
11576 repo, worktree);
11577 if (error)
11578 goto done;
11580 error = tog_load_refs(repo, 0);
11581 if (error)
11582 goto done;
11583 error = got_repo_match_object_id(&commit_id, NULL, worktree ?
11584 got_worktree_get_head_ref_name(worktree) : GOT_REF_HEAD,
11585 GOT_OBJ_TYPE_COMMIT, &tog_refs, repo);
11586 if (error)
11587 goto done;
11589 if (worktree) {
11590 got_worktree_close(worktree);
11591 worktree = NULL;
11594 error = got_object_open_as_commit(&commit, repo, commit_id);
11595 if (error)
11596 goto done;
11598 error = got_object_id_by_path(&id, repo, commit, in_repo_path);
11599 if (error) {
11600 if (error->code != GOT_ERR_NO_TREE_ENTRY)
11601 goto done;
11602 fprintf(stderr, "%s: '%s' is no known command or path\n",
11603 getprogname(), argv[0]);
11604 usage(1, 1);
11605 /* not reached */
11608 error = got_object_id_str(&commit_id_str, commit_id);
11609 if (error)
11610 goto done;
11612 cmd = &tog_commands[0]; /* log */
11613 argc = 4;
11614 cmd_argv = make_argv(argc, cmd->name, "-c", commit_id_str, argv[0]);
11615 error = cmd->cmd_main(argc, cmd_argv);
11616 done:
11617 if (repo) {
11618 close_err = got_repo_close(repo);
11619 if (error == NULL)
11620 error = close_err;
11622 if (commit)
11623 got_object_commit_close(commit);
11624 if (worktree)
11625 got_worktree_close(worktree);
11626 if (pack_fds) {
11627 const struct got_error *pack_err =
11628 got_repo_pack_fds_close(pack_fds);
11629 if (error == NULL)
11630 error = pack_err;
11632 free(id);
11633 free(commit_id_str);
11634 free(commit_id);
11635 free(cwd);
11636 free(repo_path);
11637 free(in_repo_path);
11638 if (cmd_argv) {
11639 int i;
11640 for (i = 0; i < argc; i++)
11641 free(cmd_argv[i]);
11642 free(cmd_argv);
11644 tog_free_refs();
11645 return error;
11649 main(int argc, char *argv[])
11651 const struct got_error *io_err, *error = NULL;
11652 const struct tog_cmd *cmd = NULL;
11653 int ch, hflag = 0, Vflag = 0;
11654 char **cmd_argv = NULL;
11655 static const struct option longopts[] = {
11656 { "version", no_argument, NULL, 'V' },
11657 { NULL, 0, NULL, 0}
11659 char *diff_algo_str = NULL;
11660 const char *test_script_path;
11662 setlocale(LC_CTYPE, "");
11665 * Override default signal handlers before starting ncurses.
11666 * This should prevent ncurses from installing its own
11667 * broken cleanup() signal handler.
11669 signal(SIGWINCH, tog_sigwinch);
11670 signal(SIGPIPE, tog_sigpipe);
11671 signal(SIGCONT, tog_sigcont);
11672 signal(SIGINT, tog_sigint);
11673 signal(SIGTERM, tog_sigterm);
11676 * Test mode init must happen before pledge() because "tty" will
11677 * not allow TTY-related ioctls to occur via regular files.
11679 test_script_path = getenv("TOG_TEST_SCRIPT");
11680 if (test_script_path != NULL) {
11681 error = init_mock_term(test_script_path);
11682 if (error) {
11683 fprintf(stderr, "%s: %s\n", getprogname(), error->msg);
11684 return 1;
11686 } else if (!isatty(STDIN_FILENO))
11687 errx(1, "standard input is not a tty");
11689 #if !defined(PROFILE)
11690 if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd unveil",
11691 NULL) == -1)
11692 err(1, "pledge");
11693 #endif
11695 while ((ch = getopt_long(argc, argv, "+hV", longopts, NULL)) != -1) {
11696 switch (ch) {
11697 case 'h':
11698 hflag = 1;
11699 break;
11700 case 'V':
11701 Vflag = 1;
11702 break;
11703 default:
11704 usage(hflag, 1);
11705 /* NOTREACHED */
11709 argc -= optind;
11710 argv += optind;
11711 optind = 1;
11712 optreset = 1;
11714 if (Vflag) {
11715 got_version_print_str();
11716 return 0;
11719 if (argc == 0) {
11720 if (hflag)
11721 usage(hflag, 0);
11722 /* Build an argument vector which runs a default command. */
11723 cmd = &tog_commands[0];
11724 argc = 1;
11725 cmd_argv = make_argv(argc, cmd->name);
11726 } else {
11727 size_t i;
11729 /* Did the user specify a command? */
11730 for (i = 0; i < nitems(tog_commands); i++) {
11731 if (strncmp(tog_commands[i].name, argv[0],
11732 strlen(argv[0])) == 0) {
11733 cmd = &tog_commands[i];
11734 break;
11739 diff_algo_str = getenv("TOG_DIFF_ALGORITHM");
11740 if (diff_algo_str) {
11741 if (strcasecmp(diff_algo_str, "patience") == 0)
11742 tog_diff_algo = GOT_DIFF_ALGORITHM_PATIENCE;
11743 if (strcasecmp(diff_algo_str, "myers") == 0)
11744 tog_diff_algo = GOT_DIFF_ALGORITHM_MYERS;
11747 tog_base_commit.idx = -1;
11748 tog_base_commit.marker = GOT_WORKTREE_STATE_UNKNOWN;
11750 if (cmd == NULL) {
11751 if (argc != 1)
11752 usage(0, 1);
11753 /* No command specified; try log with a path */
11754 error = tog_log_with_path(argc, argv);
11755 } else {
11756 if (hflag)
11757 cmd->cmd_usage();
11758 else
11759 error = cmd->cmd_main(argc, cmd_argv ? cmd_argv : argv);
11762 if (using_mock_io) {
11763 io_err = tog_io_close();
11764 if (error == NULL)
11765 error = io_err;
11767 endwin();
11768 if (cmd_argv) {
11769 int i;
11770 for (i = 0; i < argc; i++)
11771 free(cmd_argv[i]);
11772 free(cmd_argv);
11775 if (error && error->code != GOT_ERR_CANCELLED &&
11776 error->code != GOT_ERR_EOF &&
11777 error->code != GOT_ERR_PRIVSEP_EXIT &&
11778 error->code != GOT_ERR_PRIVSEP_PIPE &&
11779 !(error->code == GOT_ERR_ERRNO && errno == EINTR)) {
11780 fprintf(stderr, "%s: %s\n", getprogname(), error->msg);
11781 return 1;
11783 return 0;