skip full content comparison in 'got status' if file sizes differ
[got-portable.git] / tog / tog.c
bloba5a632533613cb01b96fee51cdd4d7033964b7e2
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 view->count = 0;
1875 if (view->type == TOG_VIEW_HELP)
1876 err = view->reset(view);
1877 else
1878 err = view_request_new(new, view, TOG_VIEW_HELP);
1879 break;
1880 case '\t':
1881 view->count = 0;
1882 if (view->child) {
1883 view->focussed = 0;
1884 view->child->focussed = 1;
1885 view->focus_child = 1;
1886 } else if (view->parent) {
1887 view->focussed = 0;
1888 view->parent->focussed = 1;
1889 view->parent->focus_child = 0;
1890 if (!view_is_splitscreen(view)) {
1891 if (view->parent->resize) {
1892 err = view->parent->resize(view->parent,
1894 if (err)
1895 return err;
1897 offset_selection_up(view->parent);
1898 err = view_fullscreen(view->parent);
1899 if (err)
1900 return err;
1903 break;
1904 case 'q':
1905 if (view->parent != NULL) {
1906 if (view->parent->type == TOG_VIEW_LOG)
1907 log_mark_clear(&view->parent->state.log);
1909 if (view->mode == TOG_VIEW_SPLIT_HRZN) {
1910 if (view->parent->resize) {
1912 * Might need more commits
1913 * to fill fullscreen.
1915 err = view->parent->resize(
1916 view->parent, 0);
1917 if (err)
1918 break;
1920 offset_selection_up(view->parent);
1923 err = view->input(new, view, ch);
1924 view->dying = 1;
1925 break;
1926 case 'Q':
1927 *done = 1;
1928 break;
1929 case 'F':
1930 view->count = 0;
1931 if (view_is_parent_view(view)) {
1932 if (view->child == NULL)
1933 break;
1934 if (view_is_splitscreen(view->child)) {
1935 view->focussed = 0;
1936 view->child->focussed = 1;
1937 err = view_fullscreen(view->child);
1938 } else {
1939 err = view_splitscreen(view->child);
1940 if (!err)
1941 err = view_resize_split(view, 0);
1943 if (err)
1944 break;
1945 err = view->child->input(new, view->child,
1946 KEY_RESIZE);
1947 } else {
1948 if (view_is_splitscreen(view)) {
1949 view->parent->focussed = 0;
1950 view->focussed = 1;
1951 err = view_fullscreen(view);
1952 } else {
1953 err = view_splitscreen(view);
1954 if (!err && view->mode != TOG_VIEW_SPLIT_HRZN)
1955 err = view_resize(view->parent);
1956 if (!err)
1957 err = view_resize_split(view, 0);
1959 if (err)
1960 break;
1961 err = view->input(new, view, KEY_RESIZE);
1963 if (err)
1964 break;
1965 if (view->resize) {
1966 err = view->resize(view, 0);
1967 if (err)
1968 break;
1970 if (view->parent) {
1971 if (view->parent->resize) {
1972 err = view->parent->resize(view->parent, 0);
1973 if (err != NULL)
1974 break;
1976 err = offset_selection_down(view->parent);
1977 if (err != NULL)
1978 break;
1980 err = offset_selection_down(view);
1981 break;
1982 case 'S':
1983 view->count = 0;
1984 err = switch_split(view);
1985 break;
1986 case '-':
1987 err = view_resize_split(view, -1);
1988 break;
1989 case '+':
1990 err = view_resize_split(view, 1);
1991 break;
1992 case KEY_RESIZE:
1993 break;
1994 case '/':
1995 view->count = 0;
1996 if (view->search_start)
1997 view_search_start(view, fast_refresh);
1998 else
1999 err = view->input(new, view, ch);
2000 break;
2001 case 'N':
2002 case 'n':
2003 if (view->search_started && view->search_next) {
2004 view->searching = (ch == 'n' ?
2005 TOG_SEARCH_FORWARD : TOG_SEARCH_BACKWARD);
2006 view->search_next_done = 0;
2007 view->search_next(view);
2008 } else
2009 err = view->input(new, view, ch);
2010 break;
2011 case 'A':
2012 if (tog_diff_algo == GOT_DIFF_ALGORITHM_MYERS) {
2013 tog_diff_algo = GOT_DIFF_ALGORITHM_PATIENCE;
2014 view->action = "Patience diff algorithm";
2015 } else {
2016 tog_diff_algo = GOT_DIFF_ALGORITHM_MYERS;
2017 view->action = "Myers diff algorithm";
2019 TAILQ_FOREACH(v, views, entry) {
2020 if (v->reset) {
2021 err = v->reset(v);
2022 if (err)
2023 return err;
2025 if (v->child && v->child->reset) {
2026 err = v->child->reset(v->child);
2027 if (err)
2028 return err;
2031 break;
2032 case TOG_KEY_SCRDUMP:
2033 err = screendump(view);
2034 break;
2035 default:
2036 err = view->input(new, view, ch);
2037 break;
2040 return err;
2043 static int
2044 view_needs_focus_indication(struct tog_view *view)
2046 if (view_is_parent_view(view)) {
2047 if (view->child == NULL || view->child->focussed)
2048 return 0;
2049 if (!view_is_splitscreen(view->child))
2050 return 0;
2051 } else if (!view_is_splitscreen(view))
2052 return 0;
2054 return view->focussed;
2057 static const struct got_error *
2058 tog_io_close(void)
2060 const struct got_error *err = NULL;
2062 if (tog_io.cin && fclose(tog_io.cin) == EOF)
2063 err = got_ferror(tog_io.cin, GOT_ERR_IO);
2064 if (tog_io.cout && fclose(tog_io.cout) == EOF && err == NULL)
2065 err = got_ferror(tog_io.cout, GOT_ERR_IO);
2066 if (tog_io.f && fclose(tog_io.f) == EOF && err == NULL)
2067 err = got_ferror(tog_io.f, GOT_ERR_IO);
2068 if (tog_io.sdump && fclose(tog_io.sdump) == EOF && err == NULL)
2069 err = got_ferror(tog_io.sdump, GOT_ERR_IO);
2070 if (tog_io.input_str != NULL)
2071 free(tog_io.input_str);
2073 return err;
2076 static const struct got_error *
2077 view_loop(struct tog_view *view)
2079 const struct got_error *err = NULL;
2080 struct tog_view_list_head views;
2081 struct tog_view *new_view;
2082 char *mode;
2083 int fast_refresh = 10;
2084 int done = 0, errcode;
2086 mode = getenv("TOG_VIEW_SPLIT_MODE");
2087 if (!mode || !(*mode == 'h' || *mode == 'H'))
2088 view->mode = TOG_VIEW_SPLIT_VERT;
2089 else
2090 view->mode = TOG_VIEW_SPLIT_HRZN;
2092 errcode = pthread_mutex_lock(&tog_mutex);
2093 if (errcode)
2094 return got_error_set_errno(errcode, "pthread_mutex_lock");
2096 TAILQ_INIT(&views);
2097 TAILQ_INSERT_HEAD(&views, view, entry);
2099 view->focussed = 1;
2100 err = view->show(view);
2101 if (err)
2102 return err;
2103 update_panels();
2104 doupdate();
2105 while (!TAILQ_EMPTY(&views) && !done && !tog_thread_error &&
2106 !tog_fatal_signal_received()) {
2107 /* Refresh fast during initialization, then become slower. */
2108 if (fast_refresh && --fast_refresh == 0 && !using_mock_io)
2109 halfdelay(10); /* switch to once per second */
2111 err = view_input(&new_view, &done, view, &views, fast_refresh);
2112 if (err)
2113 break;
2115 if (view->dying && view == TAILQ_FIRST(&views) &&
2116 TAILQ_NEXT(view, entry) == NULL)
2117 done = 1;
2118 if (done) {
2119 struct tog_view *v;
2122 * When we quit, scroll the screen up a single line
2123 * so we don't lose any information.
2125 TAILQ_FOREACH(v, &views, entry) {
2126 wmove(v->window, 0, 0);
2127 wdeleteln(v->window);
2128 wnoutrefresh(v->window);
2129 if (v->child && !view_is_fullscreen(v)) {
2130 wmove(v->child->window, 0, 0);
2131 wdeleteln(v->child->window);
2132 wnoutrefresh(v->child->window);
2135 doupdate();
2138 if (view->dying) {
2139 struct tog_view *v, *prev = NULL;
2141 if (view_is_parent_view(view))
2142 prev = TAILQ_PREV(view, tog_view_list_head,
2143 entry);
2144 else if (view->parent)
2145 prev = view->parent;
2147 if (view->parent) {
2148 view->parent->child = NULL;
2149 view->parent->focus_child = 0;
2150 /* Restore fullscreen line height. */
2151 view->parent->nlines = view->parent->lines;
2152 err = view_resize(view->parent);
2153 if (err)
2154 break;
2155 /* Make resized splits persist. */
2156 view_transfer_size(view->parent, view);
2157 } else
2158 TAILQ_REMOVE(&views, view, entry);
2160 err = view_close(view);
2161 if (err)
2162 goto done;
2164 view = NULL;
2165 TAILQ_FOREACH(v, &views, entry) {
2166 if (v->focussed)
2167 break;
2169 if (view == NULL && new_view == NULL) {
2170 /* No view has focus. Try to pick one. */
2171 if (prev)
2172 view = prev;
2173 else if (!TAILQ_EMPTY(&views)) {
2174 view = TAILQ_LAST(&views,
2175 tog_view_list_head);
2177 if (view) {
2178 if (view->focus_child) {
2179 view->child->focussed = 1;
2180 view = view->child;
2181 } else
2182 view->focussed = 1;
2186 if (new_view) {
2187 struct tog_view *v, *t;
2188 /* Only allow one parent view per type. */
2189 TAILQ_FOREACH_SAFE(v, &views, entry, t) {
2190 if (v->type != new_view->type)
2191 continue;
2192 TAILQ_REMOVE(&views, v, entry);
2193 err = view_close(v);
2194 if (err)
2195 goto done;
2196 break;
2198 TAILQ_INSERT_TAIL(&views, new_view, entry);
2199 view = new_view;
2201 if (view && !done) {
2202 if (view_is_parent_view(view)) {
2203 if (view->child && view->child->focussed)
2204 view = view->child;
2205 } else {
2206 if (view->parent && view->parent->focussed)
2207 view = view->parent;
2209 show_panel(view->panel);
2210 if (view->child && view_is_splitscreen(view->child))
2211 show_panel(view->child->panel);
2212 if (view->parent && view_is_splitscreen(view)) {
2213 err = view->parent->show(view->parent);
2214 if (err)
2215 goto done;
2217 err = view->show(view);
2218 if (err)
2219 goto done;
2220 if (view->child) {
2221 err = view->child->show(view->child);
2222 if (err)
2223 goto done;
2225 update_panels();
2226 doupdate();
2229 done:
2230 while (!TAILQ_EMPTY(&views)) {
2231 const struct got_error *close_err;
2232 view = TAILQ_FIRST(&views);
2233 TAILQ_REMOVE(&views, view, entry);
2234 close_err = view_close(view);
2235 if (close_err && err == NULL)
2236 err = close_err;
2239 errcode = pthread_mutex_unlock(&tog_mutex);
2240 if (errcode && err == NULL)
2241 err = got_error_set_errno(errcode, "pthread_mutex_unlock");
2243 return err;
2246 __dead static void
2247 usage_log(void)
2249 endwin();
2250 fprintf(stderr,
2251 "usage: %s log [-b] [-c commit] [-r repository-path] [path]\n",
2252 getprogname());
2253 exit(1);
2256 /* Create newly allocated wide-character string equivalent to a byte string. */
2257 static const struct got_error *
2258 mbs2ws(wchar_t **ws, size_t *wlen, const char *s)
2260 char *vis = NULL;
2261 const struct got_error *err = NULL;
2263 *ws = NULL;
2264 *wlen = mbstowcs(NULL, s, 0);
2265 if (*wlen == (size_t)-1) {
2266 int vislen;
2267 if (errno != EILSEQ)
2268 return got_error_from_errno("mbstowcs");
2270 /* byte string invalid in current encoding; try to "fix" it */
2271 err = got_mbsavis(&vis, &vislen, s);
2272 if (err)
2273 return err;
2274 *wlen = mbstowcs(NULL, vis, 0);
2275 if (*wlen == (size_t)-1) {
2276 err = got_error_from_errno("mbstowcs"); /* give up */
2277 goto done;
2281 *ws = calloc(*wlen + 1, sizeof(**ws));
2282 if (*ws == NULL) {
2283 err = got_error_from_errno("calloc");
2284 goto done;
2287 if (mbstowcs(*ws, vis ? vis : s, *wlen) != *wlen)
2288 err = got_error_from_errno("mbstowcs");
2289 done:
2290 free(vis);
2291 if (err) {
2292 free(*ws);
2293 *ws = NULL;
2294 *wlen = 0;
2296 return err;
2299 static const struct got_error *
2300 expand_tab(char **ptr, const char *src)
2302 char *dst;
2303 size_t len, n, idx = 0, sz = 0;
2305 *ptr = NULL;
2306 n = len = strlen(src);
2307 dst = malloc(n + 1);
2308 if (dst == NULL)
2309 return got_error_from_errno("malloc");
2311 while (idx < len && src[idx]) {
2312 const char c = src[idx];
2314 if (c == '\t') {
2315 size_t nb = TABSIZE - sz % TABSIZE;
2316 char *p;
2318 p = realloc(dst, n + nb);
2319 if (p == NULL) {
2320 free(dst);
2321 return got_error_from_errno("realloc");
2324 dst = p;
2325 n += nb;
2326 memset(dst + sz, ' ', nb);
2327 sz += nb;
2328 } else
2329 dst[sz++] = src[idx];
2330 ++idx;
2333 dst[sz] = '\0';
2334 *ptr = dst;
2335 return NULL;
2339 * Advance at most n columns from wline starting at offset off.
2340 * Return the index to the first character after the span operation.
2341 * Return the combined column width of all spanned wide characters in
2342 * *rcol.
2344 static int
2345 span_wline(int *rcol, int off, wchar_t *wline, int n, int col_tab_align)
2347 int width, i, cols = 0;
2349 if (n == 0) {
2350 *rcol = cols;
2351 return off;
2354 for (i = off; wline[i] != L'\0'; ++i) {
2355 if (wline[i] == L'\t')
2356 width = TABSIZE - ((cols + col_tab_align) % TABSIZE);
2357 else
2358 width = wcwidth(wline[i]);
2360 if (width == -1) {
2361 width = 1;
2362 wline[i] = L'.';
2365 if (cols + width > n)
2366 break;
2367 cols += width;
2370 *rcol = cols;
2371 return i;
2375 * Format a line for display, ensuring that it won't overflow a width limit.
2376 * With scrolling, the width returned refers to the scrolled version of the
2377 * line, which starts at (*wlinep)[*scrollxp]. The caller must free *wlinep.
2379 static const struct got_error *
2380 format_line(wchar_t **wlinep, int *widthp, int *scrollxp,
2381 const char *line, int nscroll, int wlimit, int col_tab_align, int expand)
2383 const struct got_error *err = NULL;
2384 int cols;
2385 wchar_t *wline = NULL;
2386 char *exstr = NULL;
2387 size_t wlen;
2388 int i, scrollx;
2390 *wlinep = NULL;
2391 *widthp = 0;
2393 if (expand) {
2394 err = expand_tab(&exstr, line);
2395 if (err)
2396 return err;
2399 err = mbs2ws(&wline, &wlen, expand ? exstr : line);
2400 free(exstr);
2401 if (err)
2402 return err;
2404 if (wlen > 0 && wline[wlen - 1] == L'\n') {
2405 wline[wlen - 1] = L'\0';
2406 wlen--;
2408 if (wlen > 0 && wline[wlen - 1] == L'\r') {
2409 wline[wlen - 1] = L'\0';
2410 wlen--;
2413 scrollx = span_wline(&cols, 0, wline, nscroll, col_tab_align);
2415 i = span_wline(&cols, scrollx, wline, wlimit, col_tab_align);
2416 wline[i] = L'\0';
2418 if (widthp)
2419 *widthp = cols;
2420 if (scrollxp)
2421 *scrollxp = scrollx;
2422 if (err)
2423 free(wline);
2424 else
2425 *wlinep = wline;
2426 return err;
2429 static const struct got_error*
2430 build_refs_str(char **refs_str, struct got_reflist_head *refs,
2431 struct got_object_id *id, struct got_repository *repo)
2433 static const struct got_error *err = NULL;
2434 struct got_reflist_entry *re;
2435 char *s;
2436 const char *name;
2438 *refs_str = NULL;
2440 if (refs == NULL)
2441 return NULL;
2443 TAILQ_FOREACH(re, refs, entry) {
2444 struct got_tag_object *tag = NULL;
2445 struct got_object_id *ref_id;
2446 int cmp;
2448 name = got_ref_get_name(re->ref);
2449 if (strcmp(name, GOT_REF_HEAD) == 0)
2450 continue;
2451 if (strncmp(name, "refs/", 5) == 0)
2452 name += 5;
2453 if (strncmp(name, "got/", 4) == 0)
2454 continue;
2455 if (strncmp(name, "heads/", 6) == 0)
2456 name += 6;
2457 if (strncmp(name, "remotes/", 8) == 0) {
2458 name += 8;
2459 s = strstr(name, "/" GOT_REF_HEAD);
2460 if (s != NULL && strcmp(s, "/" GOT_REF_HEAD) == 0)
2461 continue;
2463 err = got_ref_resolve(&ref_id, repo, re->ref);
2464 if (err)
2465 break;
2466 if (strncmp(name, "tags/", 5) == 0) {
2467 err = got_object_open_as_tag(&tag, repo, ref_id);
2468 if (err) {
2469 if (err->code != GOT_ERR_OBJ_TYPE) {
2470 free(ref_id);
2471 break;
2473 /* Ref points at something other than a tag. */
2474 err = NULL;
2475 tag = NULL;
2478 cmp = got_object_id_cmp(tag ?
2479 got_object_tag_get_object_id(tag) : ref_id, id);
2480 free(ref_id);
2481 if (tag)
2482 got_object_tag_close(tag);
2483 if (cmp != 0)
2484 continue;
2485 s = *refs_str;
2486 if (asprintf(refs_str, "%s%s%s", s ? s : "",
2487 s ? ", " : "", name) == -1) {
2488 err = got_error_from_errno("asprintf");
2489 free(s);
2490 *refs_str = NULL;
2491 break;
2493 free(s);
2496 return err;
2499 static const struct got_error *
2500 format_author(wchar_t **wauthor, int *author_width, char *author, int limit,
2501 int col_tab_align)
2503 char *smallerthan;
2505 smallerthan = strchr(author, '<');
2506 if (smallerthan && smallerthan[1] != '\0')
2507 author = smallerthan + 1;
2508 author[strcspn(author, "@>")] = '\0';
2509 return format_line(wauthor, author_width, NULL, author, 0, limit,
2510 col_tab_align, 0);
2513 static const struct got_error *
2514 draw_commit_marker(struct tog_view *view, char c)
2516 struct tog_color *tc;
2518 if (view->type != TOG_VIEW_LOG)
2519 return got_error_msg(GOT_ERR_NOT_IMPL, "view not supported");
2521 tc = get_color(&view->state.log.colors, TOG_COLOR_COMMIT);
2522 if (tc != NULL)
2523 wattr_on(view->window, COLOR_PAIR(tc->colorpair), NULL);
2524 if (waddch(view->window, c) == ERR)
2525 return got_error_msg(GOT_ERR_IO, "waddch");
2526 if (tc != NULL)
2527 wattr_off(view->window, COLOR_PAIR(tc->colorpair), NULL);
2529 return NULL;
2532 static void
2533 tog_waddwstr(struct tog_view *view, wchar_t *wstr, int width,
2534 int *col, int color, int toeol)
2536 struct tog_color *tc;
2537 int x;
2539 x = col != NULL ? *col : getcurx(view->window);
2540 tc = color > 0 ? get_color(&view->state.log.colors, color) : NULL;
2542 if (tc != NULL)
2543 wattr_on(view->window, COLOR_PAIR(tc->colorpair), NULL);
2544 waddwstr(view->window, wstr);
2545 x += MAX(width, 0);
2546 if (toeol) {
2547 while (x < view->ncols) {
2548 waddch(view->window, ' ');
2549 ++x;
2552 if (tc != NULL)
2553 wattr_off(view->window, COLOR_PAIR(tc->colorpair), NULL);
2554 if (col != NULL)
2555 *col = x;
2558 static void
2559 tog_waddnstr(struct tog_view *view, const char *str, int limit, int color)
2561 struct tog_color *tc;
2563 if (limit == 0)
2564 limit = view->ncols - getcurx(view->window);
2566 tc = get_color(&view->state.log.colors, color);
2567 if (tc != NULL)
2568 wattr_on(view->window, COLOR_PAIR(tc->colorpair), NULL);
2569 waddnstr(view->window, str, limit);
2570 if (tc != NULL)
2571 wattr_off(view->window, COLOR_PAIR(tc->colorpair), NULL);
2574 static const struct got_error *
2575 draw_author(struct tog_view *view, char *author, int author_display_cols,
2576 int limit, int *col, int color, int marker_column,
2577 struct commit_queue_entry *entry)
2579 const struct got_error *err;
2580 struct tog_log_view_state *s = &view->state.log;
2581 struct tog_color *tc;
2582 wchar_t *wauthor;
2583 int author_width;
2585 err = format_author(&wauthor, &author_width, author, limit, *col);
2586 if (err != NULL)
2587 return err;
2588 if ((tc = get_color(&s->colors, color)) != NULL)
2589 wattr_on(view->window, COLOR_PAIR(tc->colorpair), NULL);
2590 waddwstr(view->window, wauthor);
2591 free(wauthor);
2593 *col += author_width;
2594 while (*col < limit && author_width < author_display_cols + 2) {
2595 if (entry != NULL && s->marked_entry == entry &&
2596 author_width == marker_column) {
2597 err = draw_commit_marker(view, '>');
2598 if (err != NULL)
2599 return err;
2600 } else if (entry != NULL &&
2601 tog_base_commit.marker != GOT_WORKTREE_STATE_UNKNOWN &&
2602 author_width == marker_column &&
2603 entry->idx == tog_base_commit.idx && !s->limit_view) {
2604 err = draw_commit_marker(view, tog_base_commit.marker);
2605 if (err != NULL)
2606 return err;
2607 } else
2608 waddch(view->window, ' ');
2609 ++(*col);
2610 ++(author_width);
2612 if (tc != NULL)
2613 wattr_off(view->window, COLOR_PAIR(tc->colorpair), NULL);
2615 return NULL;
2618 static const struct got_error *
2619 draw_idstr(struct tog_view *view, const char *id_str, int color)
2621 char *str = NULL;
2623 if (strlen(id_str) > 9 && asprintf(&str, "%.8s ", id_str) == -1)
2624 return got_error_from_errno("asprintf");
2626 tog_waddnstr(view, str != NULL ? str : id_str, 0, color);
2627 free(str);
2628 return NULL;
2631 static const struct got_error *
2632 draw_ymd(struct tog_view *view, time_t t, int *limit, int avail,
2633 int date_display_cols)
2635 struct tm tm;
2636 char datebuf[12]; /* YYYY-MM-DD + SPACE + NUL */
2638 if (gmtime_r(&t, &tm) == NULL)
2639 return got_error_from_errno("gmtime_r");
2640 if (strftime(datebuf, sizeof(datebuf), "%F ", &tm) == 0)
2641 return got_error(GOT_ERR_NO_SPACE);
2643 if (avail <= date_display_cols)
2644 *limit = MIN(sizeof(datebuf) - 1, avail);
2645 else
2646 *limit = MIN(date_display_cols, sizeof(datebuf) - 1);
2648 tog_waddnstr(view, datebuf, *limit, TOG_COLOR_DATE);
2649 return NULL;
2652 static const struct got_error *
2653 draw_worktree_entry(struct tog_view *view, int wt_entry,
2654 const size_t date_display_cols, int author_display_cols)
2656 const struct got_error *err = NULL;
2657 struct tog_log_view_state *s = &view->state.log;
2658 wchar_t *wmsg = NULL;
2659 char *author, *msg = NULL;
2660 char *base_commit_id = NULL;
2661 const char *p = TOG_WORKTREE_CHANGES_LOCAL_MSG;
2662 int col, limit, scrollx, width;
2663 const int avail = view->ncols;
2665 err = draw_ymd(view, time(NULL), &col, avail, date_display_cols);
2666 if (err != NULL)
2667 return err;
2668 if (col > avail)
2669 return NULL;
2670 if (avail >= 120) {
2671 err = draw_idstr(view, "........ ", TOG_COLOR_COMMIT);
2672 if (err != NULL)
2673 return err;
2674 col += 9;
2675 if (col > avail)
2676 return NULL;
2679 author = strdup(s->thread_args.wctx.wt_author);
2680 if (author == NULL)
2681 return got_error_from_errno("strdup");
2683 err = draw_author(view, author, author_display_cols, avail - col,
2684 &col, TOG_COLOR_AUTHOR, 0, NULL);
2685 if (err != NULL)
2686 goto done;
2687 if (col > avail)
2688 goto done;
2690 err = got_object_id_str(&base_commit_id, tog_base_commit.id);
2691 if (err != NULL)
2692 goto done;
2693 if (wt_entry & TOG_WORKTREE_CHANGES_STAGED)
2694 p = TOG_WORKTREE_CHANGES_STAGED_MSG;
2695 if (asprintf(&msg, "%s based on [%.10s]", p, base_commit_id) == -1) {
2696 err = got_error_from_errno("asprintf");
2697 goto done;
2700 limit = avail - col;
2701 if (view->child != NULL && !view_is_hsplit_top(view) && limit > 0)
2702 limit--; /* for the border */
2704 err = format_line(&wmsg, &width, &scrollx, msg, view->x, limit, col, 1);
2705 if (err != NULL)
2706 goto done;
2707 tog_waddwstr(view, &wmsg[scrollx], width, &col, 0, 1);
2709 done:
2710 free(msg);
2711 free(wmsg);
2712 free(author);
2713 free(base_commit_id);
2714 return err;
2717 static const struct got_error *
2718 draw_commit(struct tog_view *view, struct commit_queue_entry *entry,
2719 const size_t date_display_cols, int author_display_cols)
2721 struct tog_log_view_state *s = &view->state.log;
2722 const struct got_error *err = NULL;
2723 struct got_commit_object *commit = entry->commit;
2724 struct got_object_id *id = entry->id;
2725 char *author, *newline, *logmsg, *logmsg0 = NULL, *refs_str = NULL;
2726 wchar_t *wrefstr = NULL, *wlogmsg = NULL;
2727 int refstr_width, logmsg_width, col, limit, scrollx, logmsg_x;
2728 const int avail = view->ncols, marker_column = author_display_cols + 1;
2729 time_t committer_time;
2730 struct got_reflist_head *refs;
2732 if (tog_base_commit.id != NULL && tog_base_commit.idx == -1 &&
2733 got_object_id_cmp(id, tog_base_commit.id) == 0)
2734 tog_base_commit.idx = entry->idx;
2735 if (tog_io.wait_for_ui && s->thread_args.need_commit_marker) {
2736 int rc;
2738 rc = pthread_cond_wait(&s->thread_args.log_loaded, &tog_mutex);
2739 if (rc)
2740 return got_error_set_errno(rc, "pthread_cond_wait");
2743 committer_time = got_object_commit_get_committer_time(commit);
2744 err = draw_ymd(view, committer_time, &col, avail, date_display_cols);
2745 if (err != NULL)
2746 return err;
2747 if (col > avail)
2748 return NULL;
2750 if (avail >= 120) {
2751 char *id_str;
2753 err = got_object_id_str(&id_str, id);
2754 if (err)
2755 return err;
2756 err = draw_idstr(view, id_str, TOG_COLOR_COMMIT);
2757 free(id_str);
2758 if (err != NULL)
2759 return err;
2760 col += 9;
2761 if (col > avail)
2762 return NULL;
2765 if (s->use_committer)
2766 author = strdup(got_object_commit_get_committer(commit));
2767 else
2768 author = strdup(got_object_commit_get_author(commit));
2769 if (author == NULL)
2770 return got_error_from_errno("strdup");
2772 err = draw_author(view, author, author_display_cols,
2773 avail - col, &col, TOG_COLOR_AUTHOR, marker_column, entry);
2774 if (err != NULL)
2775 goto done;
2776 if (col > avail)
2777 goto done;
2779 err = got_object_commit_get_logmsg(&logmsg0, commit);
2780 if (err)
2781 goto done;
2782 logmsg = logmsg0;
2783 while (*logmsg == '\n')
2784 logmsg++;
2785 newline = strchr(logmsg, '\n');
2786 if (newline)
2787 *newline = '\0';
2789 limit = avail - col;
2790 if (view->child && !view_is_hsplit_top(view) && limit > 0)
2791 limit--; /* for the border */
2793 /* Prepend reference labels to log message if possible .*/
2794 refs = got_reflist_object_id_map_lookup(tog_refs_idmap, id);
2795 err = build_refs_str(&refs_str, refs, id, s->repo);
2796 if (err)
2797 goto done;
2798 if (refs_str) {
2799 char *rs;
2801 if (asprintf(&rs, "[%s]", refs_str) == -1) {
2802 err = got_error_from_errno("asprintf");
2803 goto done;
2805 err = format_line(&wrefstr, &refstr_width,
2806 &scrollx, rs, view->x, limit, col, 1);
2807 free(rs);
2808 if (err)
2809 goto done;
2810 tog_waddwstr(view, &wrefstr[scrollx], refstr_width,
2811 &col, TOG_COLOR_COMMIT, 0);
2812 if (col > avail)
2813 goto done;
2815 if (col < avail) {
2816 waddch(view->window, ' ');
2817 col++;
2820 if (refstr_width > 0)
2821 logmsg_x = 0;
2822 else {
2823 int unscrolled_refstr_width;
2824 size_t len = wcslen(wrefstr);
2827 * No need to check for -1 return value here since
2828 * unprintables have been replaced by span_wline().
2830 unscrolled_refstr_width = wcswidth(wrefstr, len);
2831 unscrolled_refstr_width += 1; /* trailing space */
2832 logmsg_x = view->x - unscrolled_refstr_width;
2835 limit = avail - col;
2836 if (view->child && !view_is_hsplit_top(view) && limit > 0)
2837 limit--; /* for the border */
2838 } else
2839 logmsg_x = view->x;
2841 err = format_line(&wlogmsg, &logmsg_width, &scrollx, logmsg, logmsg_x,
2842 limit, col, 1);
2843 if (err)
2844 goto done;
2845 tog_waddwstr(view, &wlogmsg[scrollx], logmsg_width, &col, 0, 1);
2847 done:
2848 free(logmsg0);
2849 free(wlogmsg);
2850 free(wrefstr);
2851 free(refs_str);
2852 free(author);
2853 return err;
2856 static struct commit_queue_entry *
2857 alloc_commit_queue_entry(struct got_commit_object *commit,
2858 struct got_object_id *id)
2860 struct commit_queue_entry *entry;
2861 struct got_object_id *dup;
2863 entry = calloc(1, sizeof(*entry));
2864 if (entry == NULL)
2865 return NULL;
2867 dup = got_object_id_dup(id);
2868 if (dup == NULL) {
2869 free(entry);
2870 return NULL;
2873 entry->id = dup;
2874 entry->commit = commit;
2875 return entry;
2878 static void
2879 pop_commit(struct commit_queue *commits)
2881 struct commit_queue_entry *entry;
2883 entry = TAILQ_FIRST(&commits->head);
2884 TAILQ_REMOVE(&commits->head, entry, entry);
2885 if (entry->worktree_entry == 0)
2886 got_object_commit_close(entry->commit);
2887 commits->ncommits--;
2888 free(entry->id);
2889 free(entry);
2892 static void
2893 free_commits(struct commit_queue *commits)
2895 while (!TAILQ_EMPTY(&commits->head))
2896 pop_commit(commits);
2899 static const struct got_error *
2900 match_commit(int *have_match, struct got_object_id *id,
2901 struct got_commit_object *commit, regex_t *regex)
2903 const struct got_error *err = NULL;
2904 regmatch_t regmatch;
2905 char *id_str = NULL, *logmsg = NULL;
2907 *have_match = 0;
2909 err = got_object_id_str(&id_str, id);
2910 if (err)
2911 return err;
2913 err = got_object_commit_get_logmsg(&logmsg, commit);
2914 if (err)
2915 goto done;
2917 if (regexec(regex, got_object_commit_get_author(commit), 1,
2918 &regmatch, 0) == 0 ||
2919 regexec(regex, got_object_commit_get_committer(commit), 1,
2920 &regmatch, 0) == 0 ||
2921 regexec(regex, id_str, 1, &regmatch, 0) == 0 ||
2922 regexec(regex, logmsg, 1, &regmatch, 0) == 0)
2923 *have_match = 1;
2924 done:
2925 free(id_str);
2926 free(logmsg);
2927 return err;
2930 static const struct got_error *
2931 queue_commits(struct tog_log_thread_args *a)
2933 const struct got_error *err = NULL;
2936 * We keep all commits open throughout the lifetime of the log
2937 * view in order to avoid having to re-fetch commits from disk
2938 * while updating the display.
2940 do {
2941 struct got_object_id id;
2942 struct got_commit_object *commit;
2943 struct commit_queue_entry *entry;
2944 int limit_match = 0;
2945 int errcode;
2947 err = got_commit_graph_iter_next(&id, a->graph, a->repo,
2948 NULL, NULL);
2949 if (err)
2950 break;
2952 err = got_object_open_as_commit(&commit, a->repo, &id);
2953 if (err)
2954 break;
2955 entry = alloc_commit_queue_entry(commit, &id);
2956 if (entry == NULL) {
2957 err = got_error_from_errno("alloc_commit_queue_entry");
2958 break;
2961 errcode = pthread_mutex_lock(&tog_mutex);
2962 if (errcode) {
2963 err = got_error_set_errno(errcode,
2964 "pthread_mutex_lock");
2965 break;
2968 entry->idx = a->real_commits->ncommits;
2969 TAILQ_INSERT_TAIL(&a->real_commits->head, entry, entry);
2970 a->real_commits->ncommits++;
2972 if (*a->limiting) {
2973 err = match_commit(&limit_match, &id, commit,
2974 a->limit_regex);
2975 if (err)
2976 break;
2978 if (limit_match) {
2979 struct commit_queue_entry *matched;
2981 matched = alloc_commit_queue_entry(
2982 entry->commit, entry->id);
2983 if (matched == NULL) {
2984 err = got_error_from_errno(
2985 "alloc_commit_queue_entry");
2986 break;
2988 matched->commit = entry->commit;
2989 got_object_commit_retain(entry->commit);
2991 matched->idx = a->limit_commits->ncommits;
2992 TAILQ_INSERT_TAIL(&a->limit_commits->head,
2993 matched, entry);
2994 a->limit_commits->ncommits++;
2998 * This is how we signal log_thread() that we
2999 * have found a match, and that it should be
3000 * counted as a new entry for the view.
3002 a->limit_match = limit_match;
3005 if (*a->searching == TOG_SEARCH_FORWARD &&
3006 !*a->search_next_done) {
3007 int have_match;
3008 err = match_commit(&have_match, &id, commit, a->regex);
3009 if (err)
3010 break;
3012 if (*a->limiting) {
3013 if (limit_match && have_match)
3014 *a->search_next_done =
3015 TOG_SEARCH_HAVE_MORE;
3016 } else if (have_match)
3017 *a->search_next_done = TOG_SEARCH_HAVE_MORE;
3020 errcode = pthread_mutex_unlock(&tog_mutex);
3021 if (errcode && err == NULL)
3022 err = got_error_set_errno(errcode,
3023 "pthread_mutex_unlock");
3024 if (err)
3025 break;
3026 } while (*a->searching == TOG_SEARCH_FORWARD && !*a->search_next_done);
3028 return err;
3031 static void
3032 select_commit(struct tog_log_view_state *s)
3034 struct commit_queue_entry *entry;
3035 int ncommits = 0;
3037 entry = s->first_displayed_entry;
3038 while (entry) {
3039 if (ncommits == s->selected) {
3040 s->selected_entry = entry;
3041 break;
3043 entry = TAILQ_NEXT(entry, entry);
3044 ncommits++;
3048 /* lifted from got.c:652 (TODO make lib routine) */
3049 static const struct got_error *
3050 get_author(char **author, struct got_repository *repo,
3051 struct got_worktree *worktree)
3053 const struct got_error *err = NULL;
3054 const char *got_author = NULL, *name, *email;
3055 const struct got_gotconfig *worktree_conf = NULL, *repo_conf = NULL;
3057 *author = NULL;
3059 if (worktree)
3060 worktree_conf = got_worktree_get_gotconfig(worktree);
3061 repo_conf = got_repo_get_gotconfig(repo);
3064 * Priority of potential author information sources, from most
3065 * significant to least significant:
3066 * 1) work tree's .got/got.conf file
3067 * 2) repository's got.conf file
3068 * 3) repository's git config file
3069 * 4) environment variables
3070 * 5) global git config files (in user's home directory or /etc)
3073 if (worktree_conf)
3074 got_author = got_gotconfig_get_author(worktree_conf);
3075 if (got_author == NULL)
3076 got_author = got_gotconfig_get_author(repo_conf);
3077 if (got_author == NULL) {
3078 name = got_repo_get_gitconfig_author_name(repo);
3079 email = got_repo_get_gitconfig_author_email(repo);
3080 if (name && email) {
3081 if (asprintf(author, "%s <%s>", name, email) == -1)
3082 return got_error_from_errno("asprintf");
3083 return NULL;
3086 got_author = getenv("GOT_AUTHOR");
3087 if (got_author == NULL) {
3088 name = got_repo_get_global_gitconfig_author_name(repo);
3089 email = got_repo_get_global_gitconfig_author_email(
3090 repo);
3091 if (name && email) {
3092 if (asprintf(author, "%s <%s>", name, email)
3093 == -1)
3094 return got_error_from_errno("asprintf");
3095 return NULL;
3097 /* TODO: Look up user in password database? */
3098 return got_error(GOT_ERR_COMMIT_NO_AUTHOR);
3102 *author = strdup(got_author);
3103 if (*author == NULL)
3104 err = got_error_from_errno("strdup");
3105 return err;
3108 static const struct got_error *
3109 push_worktree_entry(struct tog_log_thread_args *ta, int wt_entry,
3110 struct got_worktree *worktree)
3112 struct commit_queue_entry *e, *entry;
3113 int rc;
3115 entry = calloc(1, sizeof(*entry));
3116 if (entry == NULL)
3117 return got_error_from_errno("calloc");
3119 entry->idx = 0;
3120 entry->worktree_entry = wt_entry;
3122 rc = pthread_mutex_lock(&tog_mutex);
3123 if (rc != 0) {
3124 free(entry);
3125 return got_error_set_errno(rc, "pthread_mutex_lock");
3128 TAILQ_FOREACH(e, &ta->real_commits->head, entry)
3129 ++e->idx;
3131 TAILQ_INSERT_HEAD(&ta->real_commits->head, entry, entry);
3132 ta->wctx.wt_state |= wt_entry;
3133 ++ta->real_commits->ncommits;
3134 ++tog_base_commit.idx;
3136 rc = pthread_mutex_unlock(&tog_mutex);
3137 if (rc != 0)
3138 return got_error_set_errno(rc, "pthread_mutex_unlock");
3140 return NULL;
3143 static const struct got_error *
3144 check_cancelled(void *arg)
3146 if (tog_sigint_received || tog_sigpipe_received)
3147 return got_error(GOT_ERR_CANCELLED);
3148 return NULL;
3151 static const struct got_error *
3152 check_local_changes(void *arg, unsigned char status,
3153 unsigned char staged_status, const char *path,
3154 struct got_object_id *blob_id, struct got_object_id *staged_blob_id,
3155 struct got_object_id *commit_id, int dirfd, const char *de_name)
3157 int *have_local_changes = arg;
3159 switch (status) {
3160 case GOT_STATUS_ADD:
3161 case GOT_STATUS_DELETE:
3162 case GOT_STATUS_MODIFY:
3163 case GOT_STATUS_CONFLICT:
3164 *have_local_changes |= TOG_WORKTREE_CHANGES_LOCAL;
3165 default:
3166 break;
3169 switch (staged_status) {
3170 case GOT_STATUS_ADD:
3171 case GOT_STATUS_DELETE:
3172 case GOT_STATUS_MODIFY:
3173 *have_local_changes |= TOG_WORKTREE_CHANGES_STAGED;
3174 default:
3175 break;
3178 return NULL;
3181 static const struct got_error *
3182 tog_worktree_status(struct tog_log_thread_args *ta)
3184 const struct got_error *err, *close_err;
3185 struct tog_worktree_ctx *wctx = &ta->wctx;
3186 struct got_worktree *wt = ta->worktree;
3187 struct got_pathlist_head paths;
3188 char *cwd = NULL;
3189 int wt_state = 0;
3191 RB_INIT(&paths);
3193 if (wt == NULL) {
3194 cwd = getcwd(NULL, 0);
3195 if (cwd == NULL)
3196 return got_error_from_errno("getcwd");
3198 err = got_worktree_open(&wt, cwd, NULL);
3199 if (err != NULL) {
3200 if (err->code == GOT_ERR_NOT_WORKTREE) {
3202 * Shouldn't happen; this routine should only
3203 * be called if tog is invoked in a worktree.
3205 wctx->active = 0;
3206 err = NULL;
3207 } else if (err->code == GOT_ERR_WORKTREE_BUSY)
3208 err = NULL; /* retry next redraw */
3209 goto done;
3213 err = got_pathlist_insert(NULL, &paths, "", NULL);
3214 if (err != NULL)
3215 goto done;
3217 err = got_worktree_status(wt, &paths, ta->repo, 0,
3218 check_local_changes, &wt_state, check_cancelled, NULL);
3219 if (err != NULL) {
3220 if (err->code != GOT_ERR_CANCELLED)
3221 goto done;
3222 err = NULL;
3225 if (wt_state != 0) {
3226 err = get_author(&wctx->wt_author, ta->repo, wt);
3227 if (err != NULL) {
3228 if (err->code != GOT_ERR_COMMIT_NO_AUTHOR)
3229 goto done;
3230 if ((wctx->wt_author = strdup("")) == NULL) {
3231 err = got_error_from_errno("strdup");
3232 goto done;
3236 wctx->wt_root = strdup(got_worktree_get_root_path(wt));
3237 if (wctx->wt_root == NULL) {
3238 err = got_error_from_errno("strdup");
3239 goto done;
3242 wctx->wt_ref = strdup(got_worktree_get_head_ref_name(wt));
3243 if (wctx->wt_ref == NULL) {
3244 err = got_error_from_errno("strdup");
3245 goto done;
3250 * Push staged entry first so it's the second log entry
3251 * if there are both staged and unstaged work tree changes.
3253 if (wt_state & TOG_WORKTREE_CHANGES_STAGED &&
3254 (wctx->wt_state & TOG_WORKTREE_CHANGES_STAGED) == 0) {
3255 err = push_worktree_entry(ta, TOG_WORKTREE_CHANGES_STAGED, wt);
3256 if (err != NULL)
3257 goto done;
3259 if (wt_state & TOG_WORKTREE_CHANGES_LOCAL &&
3260 (wctx->wt_state & TOG_WORKTREE_CHANGES_LOCAL) == 0) {
3261 err = push_worktree_entry(ta, TOG_WORKTREE_CHANGES_LOCAL, wt);
3262 if (err != NULL)
3263 goto done;
3266 done:
3267 got_pathlist_free(&paths, GOT_PATHLIST_FREE_NONE);
3268 if (ta->worktree == NULL && wt != NULL) {
3269 close_err = got_worktree_close(wt);
3270 if (close_err != NULL && err == NULL)
3271 err = close_err;
3273 free(cwd);
3274 return err;
3277 static const struct got_error *
3278 worktree_headref_str(char **ret, const char *ref)
3280 if (strncmp(ref, "refs/heads/", 11) == 0)
3281 *ret = strdup(ref + 11);
3282 else
3283 *ret = strdup(ref);
3284 if (*ret == NULL)
3285 return got_error_from_errno("strdup");
3287 return NULL;
3290 static const struct got_error *
3291 fmtindex(char **index, int *ncommits, int wt_state,
3292 struct commit_queue_entry *entry, int limit_view)
3294 int idx = 0;
3296 if (!limit_view) {
3297 if (*ncommits > 0 && wt_state & TOG_WORKTREE_CHANGES_LOCAL)
3298 --(*ncommits);
3299 if (*ncommits > 0 && wt_state & TOG_WORKTREE_CHANGES_STAGED)
3300 --(*ncommits);
3303 if (entry != NULL && entry->worktree_entry == 0) {
3305 * Display 1-based index of selected commit entries only.
3306 * If a work tree entry is selected, show an index of 0.
3308 idx = entry->idx;
3309 if (wt_state == 0 || limit_view)
3310 ++idx;
3311 else if (wt_state > TOG_WORKTREE_CHANGES_STAGED)
3312 --idx;
3314 if (asprintf(index, " [%d/%d] ", idx, *ncommits) == -1) {
3315 *index = NULL;
3316 return got_error_from_errno("asprintf");
3319 return NULL;
3322 static const struct got_error *
3323 fmtheader(char **header, int *ncommits, struct commit_queue_entry *entry,
3324 struct tog_view *view)
3326 const struct got_error *err;
3327 struct tog_log_view_state *s = &view->state.log;
3328 struct tog_worktree_ctx *wctx = &s->thread_args.wctx;
3329 struct got_reflist_head *refs;
3330 char *id_str = NULL, *index = NULL;
3331 char *wthdr = NULL, *ncommits_str = NULL;
3332 char *refs_str = NULL;
3333 int wt_entry;
3335 *header = NULL;
3336 wt_entry = entry != NULL ? entry->worktree_entry : 0;
3338 if (entry && !(view->searching && view->search_next_done == 0)) {
3339 if (entry->worktree_entry == 0) {
3340 err = got_object_id_str(&id_str, entry->id);
3341 if (err != NULL)
3342 return err;
3343 refs = got_reflist_object_id_map_lookup(tog_refs_idmap,
3344 entry->id);
3345 err = build_refs_str(&refs_str, refs,
3346 entry->id, s->repo);
3347 if (err != NULL)
3348 goto done;
3349 } else {
3350 err = worktree_headref_str(&refs_str, wctx->wt_ref);
3351 if (err != NULL)
3352 return err;
3356 err = fmtindex(&index, ncommits, wctx->wt_state, entry, s->limit_view);
3357 if (err != NULL)
3358 goto done;
3360 if (s->thread_args.commits_needed > 0 || s->thread_args.load_all) {
3361 if (asprintf(&ncommits_str, "%s%s", index,
3362 (view->searching && !view->search_next_done) ?
3363 "searching..." : "loading...") == -1) {
3364 err = got_error_from_errno("asprintf");
3365 goto done;
3367 } else {
3368 const char *search_str = NULL;
3369 const char *limit_str = NULL;
3371 if (view->searching) {
3372 if (view->search_next_done == TOG_SEARCH_NO_MORE)
3373 search_str = "no more matches";
3374 else if (view->search_next_done == TOG_SEARCH_HAVE_NONE)
3375 search_str = "no matches found";
3376 else if (!view->search_next_done)
3377 search_str = "searching...";
3380 if (s->limit_view && ncommits == 0)
3381 limit_str = "no matches found";
3383 if (asprintf(&ncommits_str, "%s%s %s", index,
3384 search_str ? search_str : (refs_str ? refs_str : ""),
3385 limit_str ? limit_str : "") == -1) {
3386 err = got_error_from_errno("asprintf");
3387 goto done;
3391 if (wt_entry != 0) {
3392 const char *t = "", *p = TOG_WORKTREE_CHANGES_LOCAL_MSG;
3394 if (wt_entry == TOG_WORKTREE_CHANGES_STAGED) {
3395 p = TOG_WORKTREE_CHANGES_STAGED_MSG;
3396 t = "-s ";
3398 if (asprintf(&wthdr, "%s%s (%s)", t, wctx->wt_root, p) == -1) {
3399 err = got_error_from_errno("asprintf");
3400 goto done;
3404 if (s->in_repo_path != NULL && strcmp(s->in_repo_path, "/") != 0) {
3405 if (asprintf(header, "%s%s %s%s",
3406 wt_entry == 0 ? "commit " : "diff ",
3407 wt_entry == 0 ? id_str ? id_str :
3408 "........................................" :
3409 wthdr != NULL ? wthdr : "", s->in_repo_path,
3410 ncommits_str) == -1)
3411 err = got_error_from_errno("asprintf");
3412 } else if (asprintf(header, "%s%s%s",
3413 wt_entry == 0 ? "commit " : "diff ",
3414 wt_entry == 0 ? id_str ? id_str :
3415 "........................................" :
3416 wthdr != NULL ? wthdr : "", ncommits_str) == -1)
3417 err = got_error_from_errno("asprintf");
3418 if (err != NULL)
3419 *header = NULL;
3421 done:
3422 free(wthdr);
3423 free(index);
3424 free(id_str);
3425 free(refs_str);
3426 free(ncommits_str);
3427 return err;
3430 static const struct got_error *
3431 draw_commits(struct tog_view *view)
3433 const struct got_error *err;
3434 struct tog_log_view_state *s = &view->state.log;
3435 struct commit_queue_entry *entry = s->selected_entry;
3436 int width, limit = view->nlines;
3437 int ncommits = s->commits->ncommits, author_cols = 4, refstr_cols;
3438 char *header;
3439 wchar_t *wline;
3440 static const size_t date_display_cols = 12;
3442 if (view_is_hsplit_top(view))
3443 --limit; /* account for border */
3445 if (s->thread_args.commits_needed == 0 &&
3446 s->thread_args.need_wt_status == 0 &&
3447 s->thread_args.need_commit_marker == 0 && !using_mock_io)
3448 halfdelay(10); /* disable fast refresh */
3450 err = fmtheader(&header, &ncommits, entry, view);
3451 if (err != NULL)
3452 return err;
3454 err = format_line(&wline, &width, NULL, header, 0, view->ncols, 0, 0);
3455 free(header);
3456 if (err)
3457 return err;
3459 werase(view->window);
3461 if (view_needs_focus_indication(view))
3462 wstandout(view->window);
3463 tog_waddwstr(view, wline, width, NULL, TOG_COLOR_COMMIT, 1);
3464 if (view_needs_focus_indication(view))
3465 wstandend(view->window);
3466 free(wline);
3467 if (limit <= 1)
3468 return NULL;
3470 /* Grow author column size if necessary, and set view->maxx. */
3471 entry = s->first_displayed_entry;
3472 ncommits = 0;
3473 view->maxx = 0;
3474 while (entry) {
3475 struct got_reflist_head *refs;
3476 struct got_commit_object *c = entry->commit;
3477 char *author, *eol, *msg, *msg0, *refs_str;
3478 wchar_t *wauthor, *wmsg;
3479 int width;
3481 if (ncommits >= limit - 1)
3482 break;
3483 if (entry->worktree_entry != 0)
3484 author = strdup(s->thread_args.wctx.wt_author);
3485 else if (s->use_committer)
3486 author = strdup(got_object_commit_get_committer(c));
3487 else
3488 author = strdup(got_object_commit_get_author(c));
3489 if (author == NULL)
3490 return got_error_from_errno("strdup");
3492 err = format_author(&wauthor, &width, author, COLS,
3493 date_display_cols);
3494 if (author_cols < width)
3495 author_cols = width;
3496 free(wauthor);
3497 free(author);
3498 if (err)
3499 return err;
3500 if (entry->worktree_entry != 0) {
3501 if (entry->worktree_entry == TOG_WORKTREE_CHANGES_LOCAL)
3502 width = sizeof(TOG_WORKTREE_CHANGES_LOCAL_MSG);
3503 else
3504 width = sizeof(TOG_WORKTREE_CHANGES_STAGED_MSG);
3505 view->maxx = MAX(view->maxx, width - 1);
3506 entry = TAILQ_NEXT(entry, entry);
3507 ++ncommits;
3508 continue;
3510 refs = got_reflist_object_id_map_lookup(tog_refs_idmap,
3511 entry->id);
3512 err = build_refs_str(&refs_str, refs, entry->id, s->repo);
3513 if (err)
3514 return err;
3515 if (refs_str) {
3516 wchar_t *ws;
3518 err = format_line(&ws, &width, NULL, refs_str,
3519 0, INT_MAX, date_display_cols + author_cols, 0);
3520 free(ws);
3521 free(refs_str);
3522 refs_str = NULL;
3523 if (err)
3524 return err;
3525 refstr_cols = width + 3; /* account for [ ] + space */
3526 } else
3527 refstr_cols = 0;
3528 err = got_object_commit_get_logmsg(&msg0, c);
3529 if (err)
3530 return err;
3531 msg = msg0;
3532 while (*msg == '\n')
3533 ++msg;
3534 if ((eol = strchr(msg, '\n')))
3535 *eol = '\0';
3536 err = format_line(&wmsg, &width, NULL, msg, 0, INT_MAX,
3537 date_display_cols + author_cols + refstr_cols, 0);
3538 free(msg0);
3539 free(wmsg);
3540 if (err)
3541 return err;
3542 view->maxx = MAX(view->maxx, width + refstr_cols);
3543 ncommits++;
3544 entry = TAILQ_NEXT(entry, entry);
3547 entry = s->first_displayed_entry;
3548 s->last_displayed_entry = s->first_displayed_entry;
3549 ncommits = 0;
3550 while (entry) {
3551 if (ncommits >= limit - 1)
3552 break;
3553 if (ncommits == s->selected)
3554 wstandout(view->window);
3555 if (entry->worktree_entry == 0)
3556 err = draw_commit(view, entry,
3557 date_display_cols, author_cols);
3558 else
3559 err = draw_worktree_entry(view, entry->worktree_entry,
3560 date_display_cols, author_cols);
3561 if (ncommits == s->selected)
3562 wstandend(view->window);
3563 if (err)
3564 return err;
3565 ncommits++;
3566 s->last_displayed_entry = entry;
3567 entry = TAILQ_NEXT(entry, entry);
3570 view_border(view);
3571 return NULL;
3574 static void
3575 log_scroll_up(struct tog_log_view_state *s, int maxscroll)
3577 struct commit_queue_entry *entry;
3578 int nscrolled = 0;
3580 entry = TAILQ_FIRST(&s->commits->head);
3581 if (s->first_displayed_entry == entry)
3582 return;
3584 entry = s->first_displayed_entry;
3585 while (entry && nscrolled < maxscroll) {
3586 entry = TAILQ_PREV(entry, commit_queue_head, entry);
3587 if (entry) {
3588 s->first_displayed_entry = entry;
3589 nscrolled++;
3594 static const struct got_error *
3595 trigger_log_thread(struct tog_view *view, int wait)
3597 const struct got_error *err;
3598 struct tog_log_thread_args *ta = &view->state.log.thread_args;
3599 int errcode;
3601 if (!using_mock_io)
3602 halfdelay(1); /* fast refresh while loading commits */
3604 while (!ta->log_complete && !tog_thread_error &&
3605 (ta->commits_needed > 0 || ta->load_all)) {
3606 /* Wake the log thread. */
3607 errcode = pthread_cond_signal(&ta->need_commits);
3608 if (errcode)
3609 return got_error_set_errno(errcode,
3610 "pthread_cond_signal");
3613 * The mutex will be released while the view loop waits
3614 * in wgetch(), at which time the log thread will run.
3616 if (!wait)
3617 break;
3619 /* Display progress update in log view. */
3620 err = show_log_view(view);
3621 if (err != NULL)
3622 return err;
3623 update_panels();
3624 doupdate();
3626 /* Wait right here while next commit is being loaded. */
3627 errcode = pthread_cond_wait(&ta->commit_loaded, &tog_mutex);
3628 if (errcode)
3629 return got_error_set_errno(errcode,
3630 "pthread_cond_wait");
3632 /* Display progress update in log view. */
3633 err = show_log_view(view);
3634 if (err != NULL)
3635 return err;
3636 update_panels();
3637 doupdate();
3640 return NULL;
3643 static const struct got_error *
3644 request_log_commits(struct tog_view *view)
3646 struct tog_log_view_state *state = &view->state.log;
3647 const struct got_error *err = NULL;
3649 if (state->thread_args.log_complete)
3650 return NULL;
3652 state->thread_args.commits_needed += view->nscrolled;
3653 err = trigger_log_thread(view, 1);
3654 view->nscrolled = 0;
3656 return err;
3659 static const struct got_error *
3660 log_scroll_down(struct tog_view *view, int maxscroll)
3662 struct tog_log_view_state *s = &view->state.log;
3663 const struct got_error *err = NULL;
3664 struct commit_queue_entry *pentry;
3665 int nscrolled = 0, ncommits_needed;
3667 if (s->last_displayed_entry == NULL)
3668 return NULL;
3670 ncommits_needed = s->last_displayed_entry->idx + 2 + maxscroll;
3671 if (s->commits->ncommits < ncommits_needed &&
3672 !s->thread_args.log_complete) {
3674 * Ask the log thread for required amount of commits.
3676 s->thread_args.commits_needed +=
3677 ncommits_needed - s->commits->ncommits;
3678 err = trigger_log_thread(view, 1);
3679 if (err)
3680 return err;
3683 do {
3684 pentry = TAILQ_NEXT(s->last_displayed_entry, entry);
3685 if (pentry == NULL && view->mode != TOG_VIEW_SPLIT_HRZN)
3686 break;
3688 s->last_displayed_entry = pentry ?
3689 pentry : s->last_displayed_entry;
3691 pentry = TAILQ_NEXT(s->first_displayed_entry, entry);
3692 if (pentry == NULL)
3693 break;
3694 s->first_displayed_entry = pentry;
3695 } while (++nscrolled < maxscroll);
3697 if (view->mode == TOG_VIEW_SPLIT_HRZN && !s->thread_args.log_complete)
3698 view->nscrolled += nscrolled;
3699 else
3700 view->nscrolled = 0;
3702 return err;
3705 static const struct got_error *
3706 open_diff_view_for_commit(struct tog_view **new_view, int begin_y, int begin_x,
3707 struct commit_queue_entry *entry, struct tog_view *log_view,
3708 struct got_repository *repo)
3710 const struct got_error *err;
3711 struct got_object_qid *p;
3712 struct got_object_id *parent_id;
3713 struct tog_view *diff_view;
3714 struct tog_log_view_state *ls = NULL;
3715 const char *worktree_root = NULL;
3717 diff_view = view_open(0, 0, begin_y, begin_x, TOG_VIEW_DIFF);
3718 if (diff_view == NULL)
3719 return got_error_from_errno("view_open");
3721 if (log_view != NULL) {
3722 ls = &log_view->state.log;
3723 worktree_root = ls->thread_args.wctx.wt_root;
3726 if (ls != NULL && ls->marked_entry != NULL &&
3727 ls->marked_entry != ls->selected_entry)
3728 parent_id = ls->marked_entry->id;
3729 else if (entry->worktree_entry == 0 &&
3730 (p = STAILQ_FIRST(got_object_commit_get_parent_ids(entry->commit))))
3731 parent_id = &p->id;
3732 else
3733 parent_id = NULL;
3735 err = open_diff_view(diff_view, parent_id, entry->id, NULL, NULL, 3, 0,
3736 0, 0, entry->worktree_entry, worktree_root, log_view, repo, NULL);
3737 if (err == NULL)
3738 *new_view = diff_view;
3739 return err;
3742 static const struct got_error *
3743 tree_view_visit_subtree(struct tog_tree_view_state *s,
3744 struct got_tree_object *subtree)
3746 struct tog_parent_tree *parent;
3748 parent = calloc(1, sizeof(*parent));
3749 if (parent == NULL)
3750 return got_error_from_errno("calloc");
3752 parent->tree = s->tree;
3753 parent->first_displayed_entry = s->first_displayed_entry;
3754 parent->selected_entry = s->selected_entry;
3755 parent->selected = s->selected;
3756 TAILQ_INSERT_HEAD(&s->parents, parent, entry);
3757 s->tree = subtree;
3758 s->selected = 0;
3759 s->first_displayed_entry = NULL;
3760 return NULL;
3763 static const struct got_error *
3764 tree_view_walk_path(struct tog_tree_view_state *s,
3765 struct got_commit_object *commit, const char *path)
3767 const struct got_error *err = NULL;
3768 struct got_tree_object *tree = NULL;
3769 const char *p;
3770 char *slash, *subpath = NULL;
3772 /* Walk the path and open corresponding tree objects. */
3773 p = path;
3774 while (*p) {
3775 struct got_tree_entry *te;
3776 struct got_object_id *tree_id;
3777 char *te_name;
3779 while (p[0] == '/')
3780 p++;
3782 /* Ensure the correct subtree entry is selected. */
3783 slash = strchr(p, '/');
3784 if (slash == NULL)
3785 te_name = strdup(p);
3786 else
3787 te_name = strndup(p, slash - p);
3788 if (te_name == NULL) {
3789 err = got_error_from_errno("strndup");
3790 break;
3792 te = got_object_tree_find_entry(s->tree, te_name);
3793 if (te == NULL) {
3794 err = got_error_path(te_name, GOT_ERR_NO_TREE_ENTRY);
3795 free(te_name);
3796 break;
3798 free(te_name);
3799 s->first_displayed_entry = s->selected_entry = te;
3801 if (!S_ISDIR(got_tree_entry_get_mode(s->selected_entry)))
3802 break; /* jump to this file's entry */
3804 slash = strchr(p, '/');
3805 if (slash)
3806 subpath = strndup(path, slash - path);
3807 else
3808 subpath = strdup(path);
3809 if (subpath == NULL) {
3810 err = got_error_from_errno("strdup");
3811 break;
3814 err = got_object_id_by_path(&tree_id, s->repo, commit,
3815 subpath);
3816 if (err)
3817 break;
3819 err = got_object_open_as_tree(&tree, s->repo, tree_id);
3820 free(tree_id);
3821 if (err)
3822 break;
3824 err = tree_view_visit_subtree(s, tree);
3825 if (err) {
3826 got_object_tree_close(tree);
3827 break;
3829 if (slash == NULL)
3830 break;
3831 free(subpath);
3832 subpath = NULL;
3833 p = slash;
3836 free(subpath);
3837 return err;
3840 static const struct got_error *
3841 browse_commit_tree(struct tog_view **new_view, int begin_y, int begin_x,
3842 struct commit_queue_entry *entry, const char *path,
3843 const char *head_ref_name, struct got_repository *repo)
3845 const struct got_error *err = NULL;
3846 struct tog_tree_view_state *s;
3847 struct tog_view *tree_view;
3848 struct got_commit_object *commit = NULL;
3849 struct got_object_id *commit_id;
3851 *new_view = NULL;
3853 if (entry->id != NULL)
3854 commit_id = entry->id;
3855 else if (entry->worktree_entry)
3856 commit_id = tog_base_commit.id;
3857 else /* cannot happen */
3858 return got_error(GOT_ERR_NOT_WORKTREE);
3860 tree_view = view_open(0, 0, begin_y, begin_x, TOG_VIEW_TREE);
3861 if (tree_view == NULL)
3862 return got_error_from_errno("view_open");
3864 err = open_tree_view(tree_view, commit_id, head_ref_name, repo);
3865 if (err)
3866 return err;
3867 s = &tree_view->state.tree;
3869 *new_view = tree_view;
3871 if (got_path_is_root_dir(path))
3872 return NULL;
3874 if (entry->worktree_entry) {
3875 err = got_object_open_as_commit(&commit, repo, commit_id);
3876 if (err != NULL)
3877 goto done;
3880 err = tree_view_walk_path(s, commit ? commit : entry->commit, path);
3882 done:
3883 if (commit != NULL)
3884 got_object_commit_close(commit);
3885 if (err != NULL) {
3886 view_close(tree_view);
3887 *new_view = NULL;
3889 return err;
3893 * If work tree entries have been pushed onto the commit queue and the
3894 * first commit entry is still displayed, scroll the view so the new
3895 * work tree entries are visible. If the selection cursor is still on
3896 * the first commit entry, keep the cursor in place such that the first
3897 * work tree entry is selected, otherwise move the selection cursor so
3898 * the currently selected commit stays selected if it remains on screen.
3900 static void
3901 worktree_entries_reveal(struct tog_log_thread_args *a)
3903 struct commit_queue_entry **first = a->first_displayed_entry;
3904 struct commit_queue_entry **select = a->selected_entry;
3905 int *cursor = a->selected;
3906 int wts = a->wctx.wt_state;
3908 #define select_worktree_entry(_first, _selected) do { \
3909 *_first = TAILQ_FIRST(&a->real_commits->head); \
3910 *_selected = *_first; \
3911 } while (0)
3913 if (first == NULL)
3914 select_worktree_entry(first, select);
3915 else if (*select == *first) {
3916 if (wts == TOG_WORKTREE_CHANGES_LOCAL && (*first)->idx == 1)
3917 select_worktree_entry(first, select);
3918 else if (wts == TOG_WORKTREE_CHANGES_STAGED &&
3919 (*first)->idx == 1)
3920 select_worktree_entry(first, select);
3921 else if (wts & TOG_WORKTREE_CHANGES_ALL && (*first)->idx == 2)
3922 select_worktree_entry(first, select);
3923 } else if (wts & TOG_WORKTREE_CHANGES_ALL && (*first)->idx == 2) {
3924 *first = TAILQ_FIRST(&a->real_commits->head);
3925 if (*cursor + 2 < *a->view_nlines - 1)
3926 (*cursor) += 2;
3927 else if (*cursor + 1 < *a->view_nlines - 1) {
3928 *select = TAILQ_PREV(*select, commit_queue_head, entry);
3929 ++(*cursor);
3930 } else {
3931 *select = TAILQ_PREV(*select, commit_queue_head, entry);
3932 *select = TAILQ_PREV(*select, commit_queue_head, entry);
3934 } else if (wts != 0 && (*first)->idx == 1) {
3935 *first = TAILQ_FIRST(&a->real_commits->head);
3936 if (*cursor + 1 < *a->view_nlines - 1)
3937 ++(*cursor);
3938 else
3939 *select = TAILQ_PREV(*select, commit_queue_head, entry);
3941 #undef select_worktree_entry
3944 static const struct got_error *
3945 block_signals_used_by_main_thread(void)
3947 sigset_t sigset;
3948 int errcode;
3950 if (sigemptyset(&sigset) == -1)
3951 return got_error_from_errno("sigemptyset");
3953 /* tog handles SIGWINCH, SIGCONT, SIGINT, SIGTERM */
3954 if (sigaddset(&sigset, SIGWINCH) == -1)
3955 return got_error_from_errno("sigaddset");
3956 if (sigaddset(&sigset, SIGCONT) == -1)
3957 return got_error_from_errno("sigaddset");
3958 if (sigaddset(&sigset, SIGINT) == -1)
3959 return got_error_from_errno("sigaddset");
3960 if (sigaddset(&sigset, SIGTERM) == -1)
3961 return got_error_from_errno("sigaddset");
3963 /* ncurses handles SIGTSTP */
3964 if (sigaddset(&sigset, SIGTSTP) == -1)
3965 return got_error_from_errno("sigaddset");
3967 errcode = pthread_sigmask(SIG_BLOCK, &sigset, NULL);
3968 if (errcode)
3969 return got_error_set_errno(errcode, "pthread_sigmask");
3971 return NULL;
3974 static void *
3975 log_thread(void *arg)
3977 const struct got_error *err = NULL;
3978 int errcode = 0;
3979 struct tog_log_thread_args *a = arg;
3980 int done = 0;
3983 * Sync startup with main thread such that we begin our
3984 * work once view_input() has released the mutex.
3986 errcode = pthread_mutex_lock(&tog_mutex);
3987 if (errcode) {
3988 err = got_error_set_errno(errcode, "pthread_mutex_lock");
3989 return (void *)err;
3992 err = block_signals_used_by_main_thread();
3993 if (err) {
3994 pthread_mutex_unlock(&tog_mutex);
3995 goto done;
3998 while (!done && !err && !tog_fatal_signal_received()) {
3999 errcode = pthread_mutex_unlock(&tog_mutex);
4000 if (errcode) {
4001 err = got_error_set_errno(errcode,
4002 "pthread_mutex_unlock");
4003 goto done;
4005 err = queue_commits(a);
4006 if (err) {
4007 if (err->code != GOT_ERR_ITER_COMPLETED)
4008 goto done;
4009 err = NULL;
4010 done = 1;
4011 a->commits_needed = 0;
4012 } else if (a->commits_needed > 0 && !a->load_all) {
4013 if (*a->limiting) {
4014 if (a->limit_match)
4015 a->commits_needed--;
4016 } else
4017 a->commits_needed--;
4020 errcode = pthread_mutex_lock(&tog_mutex);
4021 if (errcode) {
4022 err = got_error_set_errno(errcode,
4023 "pthread_mutex_lock");
4024 goto done;
4025 } else if (*a->quit)
4026 done = 1;
4027 else if (*a->limiting && *a->first_displayed_entry == NULL) {
4028 *a->first_displayed_entry =
4029 TAILQ_FIRST(&a->limit_commits->head);
4030 *a->selected_entry = *a->first_displayed_entry;
4031 } else if (*a->first_displayed_entry == NULL) {
4032 *a->first_displayed_entry =
4033 TAILQ_FIRST(&a->real_commits->head);
4034 *a->selected_entry = *a->first_displayed_entry;
4036 if (done)
4037 a->log_complete = 1;
4039 errcode = pthread_cond_signal(&a->commit_loaded);
4040 if (errcode) {
4041 err = got_error_set_errno(errcode,
4042 "pthread_cond_signal");
4043 pthread_mutex_unlock(&tog_mutex);
4044 goto done;
4047 if (a->commits_needed == 0 && a->need_wt_status) {
4048 errcode = pthread_mutex_unlock(&tog_mutex);
4049 if (errcode) {
4050 err = got_error_set_errno(errcode,
4051 "pthread_mutex_unlock");
4052 goto done;
4054 err = tog_worktree_status(a);
4055 if (err != NULL)
4056 goto done;
4057 errcode = pthread_mutex_lock(&tog_mutex);
4058 if (errcode) {
4059 err = got_error_set_errno(errcode,
4060 "pthread_mutex_lock");
4061 goto done;
4063 if (a->wctx.wt_state != 0)
4064 worktree_entries_reveal(a);
4065 a->need_wt_status = 0;
4068 if (a->commits_needed == 0 &&
4069 a->need_commit_marker && a->worktree) {
4070 errcode = pthread_mutex_unlock(&tog_mutex);
4071 if (errcode) {
4072 err = got_error_set_errno(errcode,
4073 "pthread_mutex_unlock");
4074 goto done;
4076 err = got_worktree_get_state(&tog_base_commit.marker,
4077 a->repo, a->worktree, NULL, NULL);
4078 if (err)
4079 goto done;
4080 errcode = pthread_mutex_lock(&tog_mutex);
4081 if (errcode) {
4082 err = got_error_set_errno(errcode,
4083 "pthread_mutex_lock");
4084 goto done;
4086 a->need_commit_marker = 0;
4088 * The main thread did not close this
4089 * work tree yet. Close it now.
4091 got_worktree_close(a->worktree);
4092 a->worktree = NULL;
4094 if (*a->quit)
4095 done = 1;
4098 if (done)
4099 a->commits_needed = 0;
4100 else {
4101 if (a->commits_needed == 0 && !a->load_all) {
4102 if (tog_io.wait_for_ui) {
4103 errcode = pthread_cond_signal(
4104 &a->log_loaded);
4105 if (errcode) {
4106 err = got_error_set_errno(
4107 errcode,
4108 "pthread_cond_signal");
4109 pthread_mutex_unlock(
4110 &tog_mutex);
4111 goto done;
4115 errcode = pthread_cond_wait(&a->need_commits,
4116 &tog_mutex);
4117 if (errcode) {
4118 err = got_error_set_errno(errcode,
4119 "pthread_cond_wait");
4120 pthread_mutex_unlock(&tog_mutex);
4121 goto done;
4123 if (*a->quit)
4124 done = 1;
4128 a->log_complete = 1;
4129 if (tog_io.wait_for_ui) {
4130 errcode = pthread_cond_signal(&a->log_loaded);
4131 if (errcode && err == NULL)
4132 err = got_error_set_errno(errcode,
4133 "pthread_cond_signal");
4136 errcode = pthread_mutex_unlock(&tog_mutex);
4137 if (errcode)
4138 err = got_error_set_errno(errcode, "pthread_mutex_unlock");
4139 done:
4140 if (err) {
4141 tog_thread_error = 1;
4142 pthread_cond_signal(&a->commit_loaded);
4143 if (a->worktree) {
4144 got_worktree_close(a->worktree);
4145 a->worktree = NULL;
4148 return (void *)err;
4151 static const struct got_error *
4152 stop_log_thread(struct tog_log_view_state *s)
4154 const struct got_error *err = NULL, *thread_err = NULL;
4155 int errcode;
4157 if (s->thread) {
4158 s->quit = 1;
4159 errcode = pthread_cond_signal(&s->thread_args.need_commits);
4160 if (errcode)
4161 return got_error_set_errno(errcode,
4162 "pthread_cond_signal");
4163 errcode = pthread_mutex_unlock(&tog_mutex);
4164 if (errcode)
4165 return got_error_set_errno(errcode,
4166 "pthread_mutex_unlock");
4167 errcode = pthread_join(s->thread, (void **)&thread_err);
4168 if (errcode)
4169 return got_error_set_errno(errcode, "pthread_join");
4170 errcode = pthread_mutex_lock(&tog_mutex);
4171 if (errcode)
4172 return got_error_set_errno(errcode,
4173 "pthread_mutex_lock");
4174 s->thread = 0; //NULL;
4177 if (s->thread_args.repo) {
4178 err = got_repo_close(s->thread_args.repo);
4179 s->thread_args.repo = NULL;
4182 if (s->thread_args.pack_fds) {
4183 const struct got_error *pack_err =
4184 got_repo_pack_fds_close(s->thread_args.pack_fds);
4185 if (err == NULL)
4186 err = pack_err;
4187 s->thread_args.pack_fds = NULL;
4190 if (s->thread_args.graph) {
4191 got_commit_graph_close(s->thread_args.graph);
4192 s->thread_args.graph = NULL;
4195 return err ? err : thread_err;
4198 static void
4199 worktree_ctx_close(struct tog_log_thread_args *ta)
4201 struct tog_worktree_ctx *wctx = &ta->wctx;
4203 if (wctx->active) {
4204 free(wctx->wt_author);
4205 wctx->wt_author = NULL;
4206 free(wctx->wt_root);
4207 wctx->wt_root = NULL;
4208 free(wctx->wt_ref);
4209 wctx->wt_ref = NULL;
4210 wctx->wt_state = 0;
4211 ta->need_wt_status = 1;
4215 static const struct got_error *
4216 close_log_view(struct tog_view *view)
4218 const struct got_error *err = NULL;
4219 struct tog_log_view_state *s = &view->state.log;
4220 int errcode;
4222 log_mark_clear(s);
4224 err = stop_log_thread(s);
4226 errcode = pthread_cond_destroy(&s->thread_args.need_commits);
4227 if (errcode && err == NULL)
4228 err = got_error_set_errno(errcode, "pthread_cond_destroy");
4230 errcode = pthread_cond_destroy(&s->thread_args.commit_loaded);
4231 if (errcode && err == NULL)
4232 err = got_error_set_errno(errcode, "pthread_cond_destroy");
4234 if (using_mock_io) {
4235 errcode = pthread_cond_destroy(&s->thread_args.log_loaded);
4236 if (errcode && err == NULL)
4237 err = got_error_set_errno(errcode,
4238 "pthread_cond_destroy");
4241 free_commits(&s->limit_commits);
4242 free_commits(&s->real_commits);
4243 free_colors(&s->colors);
4244 free(s->in_repo_path);
4245 s->in_repo_path = NULL;
4246 free(s->start_id);
4247 s->start_id = NULL;
4248 free(s->head_ref_name);
4249 s->head_ref_name = NULL;
4250 worktree_ctx_close(&s->thread_args);
4251 return err;
4255 * We use two queues to implement the limit feature: first consists of
4256 * commits matching the current limit_regex; second is the real queue
4257 * of all known commits (real_commits). When the user starts limiting,
4258 * we swap queues such that all movement and displaying functionality
4259 * works with very slight change.
4261 static const struct got_error *
4262 limit_log_view(struct tog_view *view)
4264 struct tog_log_view_state *s = &view->state.log;
4265 struct commit_queue_entry *entry;
4266 struct tog_view *v = view;
4267 const struct got_error *err = NULL;
4268 char pattern[1024];
4269 int ret;
4271 if (view_is_hsplit_top(view))
4272 v = view->child;
4273 else if (view->mode == TOG_VIEW_SPLIT_VERT && view->parent)
4274 v = view->parent;
4276 if (tog_io.input_str != NULL) {
4277 if (strlcpy(pattern, tog_io.input_str, sizeof(pattern)) >=
4278 sizeof(pattern))
4279 return got_error(GOT_ERR_NO_SPACE);
4280 } else {
4281 wmove(v->window, v->nlines - 1, 0);
4282 wclrtoeol(v->window);
4283 mvwaddstr(v->window, v->nlines - 1, 0, "&/");
4284 nodelay(v->window, FALSE);
4285 nocbreak();
4286 echo();
4287 ret = wgetnstr(v->window, pattern, sizeof(pattern));
4288 cbreak();
4289 noecho();
4290 nodelay(v->window, TRUE);
4291 if (ret == ERR)
4292 return NULL;
4295 if (*pattern == '\0') {
4297 * Safety measure for the situation where the user
4298 * resets limit without previously limiting anything.
4300 if (!s->limit_view)
4301 return NULL;
4304 * User could have pressed Ctrl+L, which refreshed the
4305 * commit queues, it means we can't save previously
4306 * (before limit took place) displayed entries,
4307 * because they would point to already free'ed memory,
4308 * so we are forced to always select first entry of
4309 * the queue.
4311 s->commits = &s->real_commits;
4312 s->first_displayed_entry = TAILQ_FIRST(&s->real_commits.head);
4313 s->selected_entry = s->first_displayed_entry;
4314 s->selected = 0;
4315 s->limit_view = 0;
4317 return NULL;
4320 if (regcomp(&s->limit_regex, pattern, REG_EXTENDED | REG_NEWLINE))
4321 return NULL;
4323 s->limit_view = 1;
4325 /* Clear the screen while loading limit view */
4326 s->first_displayed_entry = NULL;
4327 s->last_displayed_entry = NULL;
4328 s->selected_entry = NULL;
4329 s->commits = &s->limit_commits;
4331 /* Prepare limit queue for new search */
4332 free_commits(&s->limit_commits);
4333 s->limit_commits.ncommits = 0;
4335 /* First process commits, which are in queue already */
4336 TAILQ_FOREACH(entry, &s->real_commits.head, entry) {
4337 int have_match = 0;
4339 if (entry->worktree_entry == 0) {
4340 err = match_commit(&have_match, entry->id,
4341 entry->commit, &s->limit_regex);
4342 if (err)
4343 return err;
4346 if (have_match) {
4347 struct commit_queue_entry *matched;
4349 matched = alloc_commit_queue_entry(entry->commit,
4350 entry->id);
4351 if (matched == NULL) {
4352 err = got_error_from_errno(
4353 "alloc_commit_queue_entry");
4354 break;
4356 matched->commit = entry->commit;
4357 got_object_commit_retain(entry->commit);
4359 matched->idx = s->limit_commits.ncommits;
4360 TAILQ_INSERT_TAIL(&s->limit_commits.head,
4361 matched, entry);
4362 s->limit_commits.ncommits++;
4366 /* Second process all the commits, until we fill the screen */
4367 if (s->limit_commits.ncommits < view->nlines - 1 &&
4368 !s->thread_args.log_complete) {
4369 s->thread_args.commits_needed +=
4370 view->nlines - s->limit_commits.ncommits - 1;
4371 err = trigger_log_thread(view, 1);
4372 if (err)
4373 return err;
4376 s->first_displayed_entry = TAILQ_FIRST(&s->commits->head);
4377 s->selected_entry = TAILQ_FIRST(&s->commits->head);
4378 s->selected = 0;
4380 return NULL;
4383 static const struct got_error *
4384 search_start_log_view(struct tog_view *view)
4386 struct tog_log_view_state *s = &view->state.log;
4388 s->matched_entry = NULL;
4389 s->search_entry = NULL;
4390 return NULL;
4393 static const struct got_error *
4394 search_next_log_view(struct tog_view *view)
4396 const struct got_error *err = NULL;
4397 struct tog_log_view_state *s = &view->state.log;
4398 struct commit_queue_entry *entry;
4400 /* Display progress update in log view. */
4401 err = show_log_view(view);
4402 if (err != NULL)
4403 return err;
4404 update_panels();
4405 doupdate();
4407 if (s->search_entry) {
4408 if (!using_mock_io) {
4409 int errcode, ch;
4411 errcode = pthread_mutex_unlock(&tog_mutex);
4412 if (errcode)
4413 return got_error_set_errno(errcode,
4414 "pthread_mutex_unlock");
4415 ch = wgetch(view->window);
4416 errcode = pthread_mutex_lock(&tog_mutex);
4417 if (errcode)
4418 return got_error_set_errno(errcode,
4419 "pthread_mutex_lock");
4420 if (ch == CTRL('g') || ch == KEY_BACKSPACE) {
4421 view->search_next_done = TOG_SEARCH_HAVE_MORE;
4422 return NULL;
4425 if (view->searching == TOG_SEARCH_FORWARD)
4426 entry = TAILQ_NEXT(s->search_entry, entry);
4427 else
4428 entry = TAILQ_PREV(s->search_entry,
4429 commit_queue_head, entry);
4430 } else if (s->matched_entry) {
4432 * If the user has moved the cursor after we hit a match,
4433 * the position from where we should continue searching
4434 * might have changed.
4436 if (view->searching == TOG_SEARCH_FORWARD)
4437 entry = TAILQ_NEXT(s->selected_entry, entry);
4438 else
4439 entry = TAILQ_PREV(s->selected_entry, commit_queue_head,
4440 entry);
4441 } else {
4442 entry = s->selected_entry;
4445 while (1) {
4446 int have_match = 0;
4448 if (entry == NULL) {
4449 if (s->thread_args.log_complete ||
4450 view->searching == TOG_SEARCH_BACKWARD) {
4451 view->search_next_done =
4452 (s->matched_entry == NULL ?
4453 TOG_SEARCH_HAVE_NONE : TOG_SEARCH_NO_MORE);
4454 s->search_entry = NULL;
4455 return NULL;
4458 * Poke the log thread for more commits and return,
4459 * allowing the main loop to make progress. Search
4460 * will resume at s->search_entry once we come back.
4462 s->search_entry = s->selected_entry;
4463 s->thread_args.commits_needed++;
4464 return trigger_log_thread(view, 0);
4467 if (entry->worktree_entry == 0) {
4468 err = match_commit(&have_match, entry->id,
4469 entry->commit, &view->regex);
4470 if (err)
4471 break;
4472 if (have_match) {
4473 view->search_next_done = TOG_SEARCH_HAVE_MORE;
4474 s->matched_entry = entry;
4475 break;
4479 s->search_entry = entry;
4480 if (view->searching == TOG_SEARCH_FORWARD)
4481 entry = TAILQ_NEXT(entry, entry);
4482 else
4483 entry = TAILQ_PREV(entry, commit_queue_head, entry);
4486 if (s->matched_entry) {
4487 int cur = s->selected_entry->idx;
4488 while (cur < s->matched_entry->idx) {
4489 err = input_log_view(NULL, view, KEY_DOWN);
4490 if (err)
4491 return err;
4492 cur++;
4494 while (cur > s->matched_entry->idx) {
4495 err = input_log_view(NULL, view, KEY_UP);
4496 if (err)
4497 return err;
4498 cur--;
4502 s->search_entry = NULL;
4504 return NULL;
4507 static const struct got_error *
4508 open_log_view(struct tog_view *view, struct got_object_id *start_id,
4509 struct got_repository *repo, const char *head_ref_name,
4510 const char *in_repo_path, int log_branches,
4511 struct got_worktree *worktree)
4513 const struct got_error *err = NULL;
4514 struct tog_log_view_state *s = &view->state.log;
4515 struct got_repository *thread_repo = NULL;
4516 struct got_commit_graph *thread_graph = NULL;
4517 int errcode;
4519 if (in_repo_path != s->in_repo_path) {
4520 free(s->in_repo_path);
4521 s->in_repo_path = strdup(in_repo_path);
4522 if (s->in_repo_path == NULL) {
4523 err = got_error_from_errno("strdup");
4524 goto done;
4528 /* The commit queue only contains commits being displayed. */
4529 TAILQ_INIT(&s->real_commits.head);
4530 s->real_commits.ncommits = 0;
4531 s->commits = &s->real_commits;
4533 TAILQ_INIT(&s->limit_commits.head);
4534 s->limit_view = 0;
4535 s->limit_commits.ncommits = 0;
4537 s->repo = repo;
4538 if (head_ref_name) {
4539 s->head_ref_name = strdup(head_ref_name);
4540 if (s->head_ref_name == NULL) {
4541 err = got_error_from_errno("strdup");
4542 goto done;
4545 s->start_id = got_object_id_dup(start_id);
4546 if (s->start_id == NULL) {
4547 err = got_error_from_errno("got_object_id_dup");
4548 goto done;
4550 s->log_branches = log_branches;
4551 s->use_committer = 1;
4553 STAILQ_INIT(&s->colors);
4554 if (has_colors() && getenv("TOG_COLORS") != NULL) {
4555 err = add_color(&s->colors, "^$", TOG_COLOR_COMMIT,
4556 get_color_value("TOG_COLOR_COMMIT"));
4557 if (err)
4558 goto done;
4559 err = add_color(&s->colors, "^$", TOG_COLOR_AUTHOR,
4560 get_color_value("TOG_COLOR_AUTHOR"));
4561 if (err)
4562 goto done;
4563 err = add_color(&s->colors, "^$", TOG_COLOR_DATE,
4564 get_color_value("TOG_COLOR_DATE"));
4565 if (err)
4566 goto done;
4569 view->show = show_log_view;
4570 view->input = input_log_view;
4571 view->resize = resize_log_view;
4572 view->close = close_log_view;
4573 view->search_start = search_start_log_view;
4574 view->search_next = search_next_log_view;
4576 if (s->thread_args.pack_fds == NULL) {
4577 err = got_repo_pack_fds_open(&s->thread_args.pack_fds);
4578 if (err)
4579 goto done;
4581 err = got_repo_open(&thread_repo, got_repo_get_path(repo), NULL,
4582 s->thread_args.pack_fds);
4583 if (err)
4584 goto done;
4585 err = got_commit_graph_open(&thread_graph, s->in_repo_path,
4586 !s->log_branches);
4587 if (err)
4588 goto done;
4589 err = got_commit_graph_bfsort(thread_graph, s->start_id,
4590 s->repo, NULL, NULL);
4591 if (err)
4592 goto done;
4594 errcode = pthread_cond_init(&s->thread_args.need_commits, NULL);
4595 if (errcode) {
4596 err = got_error_set_errno(errcode, "pthread_cond_init");
4597 goto done;
4599 errcode = pthread_cond_init(&s->thread_args.commit_loaded, NULL);
4600 if (errcode) {
4601 err = got_error_set_errno(errcode, "pthread_cond_init");
4602 goto done;
4605 if (using_mock_io) {
4606 int rc;
4608 rc = pthread_cond_init(&s->thread_args.log_loaded, NULL);
4609 if (rc)
4610 return got_error_set_errno(rc, "pthread_cond_init");
4613 s->thread_args.view_nlines = &view->nlines;
4614 s->thread_args.commits_needed = view->nlines;
4615 s->thread_args.graph = thread_graph;
4616 s->thread_args.real_commits = &s->real_commits;
4617 s->thread_args.limit_commits = &s->limit_commits;
4618 s->thread_args.in_repo_path = s->in_repo_path;
4619 s->thread_args.start_id = s->start_id;
4620 s->thread_args.repo = thread_repo;
4621 s->thread_args.log_complete = 0;
4622 s->thread_args.quit = &s->quit;
4623 s->thread_args.first_displayed_entry = &s->first_displayed_entry;
4624 s->thread_args.last_displayed_entry = &s->last_displayed_entry;
4625 s->thread_args.selected_entry = &s->selected_entry;
4626 s->thread_args.selected = &s->selected;
4627 s->thread_args.searching = &view->searching;
4628 s->thread_args.search_next_done = &view->search_next_done;
4629 s->thread_args.regex = &view->regex;
4630 s->thread_args.limiting = &s->limit_view;
4631 s->thread_args.limit_regex = &s->limit_regex;
4632 s->thread_args.limit_commits = &s->limit_commits;
4633 s->thread_args.worktree = worktree;
4634 if (worktree) {
4635 s->thread_args.wctx.active = 1;
4636 s->thread_args.need_wt_status = 1;
4637 s->thread_args.need_commit_marker = 1;
4640 done:
4641 if (err) {
4642 if (view->close == NULL)
4643 close_log_view(view);
4644 view_close(view);
4646 return err;
4649 static const struct got_error *
4650 show_log_view(struct tog_view *view)
4652 const struct got_error *err;
4653 struct tog_log_view_state *s = &view->state.log;
4655 if (s->thread == 0) { //NULL) {
4656 int errcode = pthread_create(&s->thread, NULL, log_thread,
4657 &s->thread_args);
4658 if (errcode)
4659 return got_error_set_errno(errcode, "pthread_create");
4660 if (s->thread_args.commits_needed > 0) {
4661 err = trigger_log_thread(view, 1);
4662 if (err)
4663 return err;
4667 return draw_commits(view);
4670 static void
4671 log_move_cursor_up(struct tog_view *view, int page, int home)
4673 struct tog_log_view_state *s = &view->state.log;
4675 if (s->first_displayed_entry == NULL)
4676 return;
4677 if (s->selected_entry->idx == 0)
4678 view->count = 0;
4680 if ((page && TAILQ_FIRST(&s->commits->head) == s->first_displayed_entry)
4681 || home)
4682 s->selected = home ? 0 : MAX(0, s->selected - page - 1);
4684 if (!page && !home && s->selected > 0)
4685 --s->selected;
4686 else
4687 log_scroll_up(s, home ? s->commits->ncommits : MAX(page, 1));
4689 select_commit(s);
4690 return;
4693 static const struct got_error *
4694 log_move_cursor_down(struct tog_view *view, int page)
4696 struct tog_log_view_state *s = &view->state.log;
4697 const struct got_error *err = NULL;
4698 int eos = view->nlines - 2;
4700 if (s->first_displayed_entry == NULL)
4701 return NULL;
4703 if (s->thread_args.log_complete &&
4704 s->selected_entry->idx >= s->commits->ncommits - 1)
4705 return NULL;
4707 if (view_is_hsplit_top(view))
4708 --eos; /* border consumes the last line */
4710 if (!page) {
4711 if (s->selected < MIN(eos, s->commits->ncommits - 1))
4712 ++s->selected;
4713 else
4714 err = log_scroll_down(view, 1);
4715 } else if (s->thread_args.load_all && s->thread_args.log_complete) {
4716 struct commit_queue_entry *entry;
4717 int n;
4719 s->selected = 0;
4720 entry = TAILQ_LAST(&s->commits->head, commit_queue_head);
4721 s->last_displayed_entry = entry;
4722 for (n = 0; n <= eos; n++) {
4723 if (entry == NULL)
4724 break;
4725 s->first_displayed_entry = entry;
4726 entry = TAILQ_PREV(entry, commit_queue_head, entry);
4728 if (n > 0)
4729 s->selected = n - 1;
4730 } else {
4731 if (s->last_displayed_entry->idx == s->commits->ncommits - 1 &&
4732 s->thread_args.log_complete)
4733 s->selected += MIN(page,
4734 s->commits->ncommits - s->selected_entry->idx - 1);
4735 else
4736 err = log_scroll_down(view, page);
4738 if (err)
4739 return err;
4742 * We might necessarily overshoot in horizontal
4743 * splits; if so, select the last displayed commit.
4745 if (view_is_hsplit_top(view) && s->first_displayed_entry &&
4746 s->last_displayed_entry) {
4747 s->selected = MIN(s->selected,
4748 s->last_displayed_entry->idx -
4749 s->first_displayed_entry->idx);
4752 select_commit(s);
4754 if (s->thread_args.log_complete &&
4755 s->selected_entry->idx == s->commits->ncommits - 1)
4756 view->count = 0;
4758 return NULL;
4761 static void
4762 view_get_split(struct tog_view *view, int *y, int *x)
4764 *x = 0;
4765 *y = 0;
4767 if (view->mode == TOG_VIEW_SPLIT_HRZN) {
4768 if (view->child && view->child->resized_y)
4769 *y = view->child->resized_y;
4770 else if (view->resized_y)
4771 *y = view->resized_y;
4772 else
4773 *y = view_split_begin_y(view->lines);
4774 } else if (view->mode == TOG_VIEW_SPLIT_VERT) {
4775 if (view->child && view->child->resized_x)
4776 *x = view->child->resized_x;
4777 else if (view->resized_x)
4778 *x = view->resized_x;
4779 else
4780 *x = view_split_begin_x(view->begin_x);
4784 /* Split view horizontally at y and offset view->state->selected line. */
4785 static const struct got_error *
4786 view_init_hsplit(struct tog_view *view, int y)
4788 const struct got_error *err = NULL;
4790 view->nlines = y;
4791 view->ncols = COLS;
4792 err = view_resize(view);
4793 if (err)
4794 return err;
4796 err = offset_selection_down(view);
4798 return err;
4801 static const struct got_error *
4802 log_goto_line(struct tog_view *view, int nlines)
4804 const struct got_error *err = NULL;
4805 struct tog_log_view_state *s = &view->state.log;
4806 int g, idx = s->selected_entry->idx;
4808 if (s->first_displayed_entry == NULL || s->last_displayed_entry == NULL)
4809 return NULL;
4811 g = view->gline;
4812 view->gline = 0;
4814 if (g >= s->first_displayed_entry->idx + 1 &&
4815 g <= s->last_displayed_entry->idx + 1 &&
4816 g - s->first_displayed_entry->idx - 1 < nlines) {
4817 s->selected = g - s->first_displayed_entry->idx - 1;
4818 select_commit(s);
4819 return NULL;
4822 if (idx + 1 < g) {
4823 err = log_move_cursor_down(view, g - idx - 1);
4824 if (!err && g > s->selected_entry->idx + 1)
4825 err = log_move_cursor_down(view,
4826 g - s->first_displayed_entry->idx - 1);
4827 if (err)
4828 return err;
4829 } else if (idx + 1 > g)
4830 log_move_cursor_up(view, idx - g + 1, 0);
4832 if (g < nlines && s->first_displayed_entry->idx == 0)
4833 s->selected = g - 1;
4835 select_commit(s);
4836 return NULL;
4840 static void
4841 horizontal_scroll_input(struct tog_view *view, int ch)
4844 switch (ch) {
4845 case KEY_LEFT:
4846 case 'h':
4847 view->x -= MIN(view->x, 2);
4848 if (view->x <= 0)
4849 view->count = 0;
4850 break;
4851 case KEY_RIGHT:
4852 case 'l':
4853 if (view->x + view->ncols / 2 < view->maxx)
4854 view->x += 2;
4855 else
4856 view->count = 0;
4857 break;
4858 case '0':
4859 view->x = 0;
4860 break;
4861 case '$':
4862 view->x = MAX(view->maxx - view->ncols / 2, 0);
4863 view->count = 0;
4864 break;
4865 default:
4866 break;
4870 static void
4871 log_mark_commit(struct tog_log_view_state *s)
4873 if (s->selected_entry == s->marked_entry)
4874 s->marked_entry = NULL;
4875 else
4876 s->marked_entry = s->selected_entry;
4879 static const struct got_error *
4880 input_log_view(struct tog_view **new_view, struct tog_view *view, int ch)
4882 const struct got_error *err = NULL;
4883 struct tog_log_view_state *s = &view->state.log;
4884 int eos, nscroll;
4886 if (s->thread_args.load_all) {
4887 if (ch == CTRL('g') || ch == KEY_BACKSPACE)
4888 s->thread_args.load_all = 0;
4889 else if (s->thread_args.log_complete) {
4890 err = log_move_cursor_down(view, s->commits->ncommits);
4891 s->thread_args.load_all = 0;
4893 if (err)
4894 return err;
4897 eos = nscroll = view->nlines - 1;
4898 if (view_is_hsplit_top(view))
4899 --eos; /* border */
4901 if (view->gline)
4902 return log_goto_line(view, eos);
4904 switch (ch) {
4905 case '&':
4906 view->count = 0;
4907 err = limit_log_view(view);
4908 break;
4909 case 'q':
4910 s->quit = 1;
4911 break;
4912 case '0':
4913 case '$':
4914 case KEY_RIGHT:
4915 case 'l':
4916 case KEY_LEFT:
4917 case 'h':
4918 horizontal_scroll_input(view, ch);
4919 break;
4920 case 'k':
4921 case KEY_UP:
4922 case '<':
4923 case ',':
4924 case CTRL('p'):
4925 log_move_cursor_up(view, 0, 0);
4926 break;
4927 case 'g':
4928 case '=':
4929 case KEY_HOME:
4930 log_move_cursor_up(view, 0, 1);
4931 view->count = 0;
4932 break;
4933 case CTRL('u'):
4934 case 'u':
4935 nscroll /= 2;
4936 /* FALL THROUGH */
4937 case KEY_PPAGE:
4938 case CTRL('b'):
4939 case 'b':
4940 log_move_cursor_up(view, nscroll, 0);
4941 break;
4942 case 'j':
4943 case KEY_DOWN:
4944 case '>':
4945 case '.':
4946 case CTRL('n'):
4947 err = log_move_cursor_down(view, 0);
4948 break;
4949 case '@':
4950 s->use_committer = !s->use_committer;
4951 view->action = s->use_committer ?
4952 "show committer" : "show commit author";
4953 break;
4954 case 'G':
4955 case '*':
4956 case KEY_END: {
4957 /* We don't know yet how many commits, so we're forced to
4958 * traverse them all. */
4959 view->count = 0;
4960 s->thread_args.load_all = 1;
4961 if (!s->thread_args.log_complete)
4962 return trigger_log_thread(view, 0);
4963 err = log_move_cursor_down(view, s->commits->ncommits);
4964 s->thread_args.load_all = 0;
4965 break;
4967 case CTRL('d'):
4968 case 'd':
4969 nscroll /= 2;
4970 /* FALL THROUGH */
4971 case KEY_NPAGE:
4972 case CTRL('f'):
4973 case 'f':
4974 case ' ':
4975 err = log_move_cursor_down(view, nscroll);
4976 break;
4977 case KEY_RESIZE:
4978 if (s->selected > view->nlines - 2)
4979 s->selected = view->nlines - 2;
4980 if (s->selected > s->commits->ncommits - 1)
4981 s->selected = s->commits->ncommits - 1;
4982 select_commit(s);
4983 if (s->commits->ncommits < view->nlines - 1 &&
4984 !s->thread_args.log_complete) {
4985 s->thread_args.commits_needed += (view->nlines - 1) -
4986 s->commits->ncommits;
4987 err = trigger_log_thread(view, 1);
4989 break;
4990 case KEY_ENTER:
4991 case '\r':
4992 view->count = 0;
4993 if (s->selected_entry == NULL)
4994 break;
4995 err = view_request_new(new_view, view, TOG_VIEW_DIFF);
4996 break;
4997 case 'T':
4998 view->count = 0;
4999 if (s->selected_entry == NULL)
5000 break;
5001 err = view_request_new(new_view, view, TOG_VIEW_TREE);
5002 break;
5003 case KEY_BACKSPACE:
5004 case CTRL('l'):
5005 case 'B':
5006 view->count = 0;
5007 if (ch == KEY_BACKSPACE &&
5008 got_path_is_root_dir(s->in_repo_path))
5009 break;
5010 err = stop_log_thread(s);
5011 if (err)
5012 return err;
5013 if (ch == KEY_BACKSPACE) {
5014 char *parent_path;
5015 err = got_path_dirname(&parent_path, s->in_repo_path);
5016 if (err)
5017 return err;
5018 free(s->in_repo_path);
5019 s->in_repo_path = parent_path;
5020 s->thread_args.in_repo_path = s->in_repo_path;
5021 } else if (ch == CTRL('l')) {
5022 struct got_object_id *start_id;
5023 err = got_repo_match_object_id(&start_id, NULL,
5024 s->head_ref_name ? s->head_ref_name : GOT_REF_HEAD,
5025 GOT_OBJ_TYPE_COMMIT, &tog_refs, s->repo);
5026 if (err) {
5027 if (s->head_ref_name == NULL ||
5028 err->code != GOT_ERR_NOT_REF)
5029 return err;
5030 /* Try to cope with deleted references. */
5031 free(s->head_ref_name);
5032 s->head_ref_name = NULL;
5033 err = got_repo_match_object_id(&start_id,
5034 NULL, GOT_REF_HEAD, GOT_OBJ_TYPE_COMMIT,
5035 &tog_refs, s->repo);
5036 if (err)
5037 return err;
5039 free(s->start_id);
5040 s->start_id = start_id;
5041 s->thread_args.start_id = s->start_id;
5042 } else /* 'B' */
5043 s->log_branches = !s->log_branches;
5045 if (s->thread_args.pack_fds == NULL) {
5046 err = got_repo_pack_fds_open(&s->thread_args.pack_fds);
5047 if (err)
5048 return err;
5050 err = got_repo_open(&s->thread_args.repo,
5051 got_repo_get_path(s->repo), NULL,
5052 s->thread_args.pack_fds);
5053 if (err)
5054 return err;
5055 tog_free_refs();
5056 err = tog_load_refs(s->repo, 0);
5057 if (err)
5058 return err;
5059 err = got_commit_graph_open(&s->thread_args.graph,
5060 s->in_repo_path, !s->log_branches);
5061 if (err)
5062 return err;
5063 err = got_commit_graph_bfsort(s->thread_args.graph,
5064 s->start_id, s->repo, NULL, NULL);
5065 if (err)
5066 return err;
5067 free_commits(&s->real_commits);
5068 free_commits(&s->limit_commits);
5069 s->first_displayed_entry = NULL;
5070 s->last_displayed_entry = NULL;
5071 s->selected_entry = NULL;
5072 s->selected = 0;
5073 s->thread_args.log_complete = 0;
5074 s->quit = 0;
5075 s->thread_args.commits_needed = view->lines;
5076 s->matched_entry = NULL;
5077 s->search_entry = NULL;
5078 tog_base_commit.idx = -1;
5079 worktree_ctx_close(&s->thread_args);
5080 view->offset = 0;
5081 break;
5082 case 'm':
5083 if (s->selected_entry->worktree_entry == 0)
5084 log_mark_commit(s);
5085 break;
5086 case 'R':
5087 view->count = 0;
5088 err = view_request_new(new_view, view, TOG_VIEW_REF);
5089 break;
5090 default:
5091 view->count = 0;
5092 break;
5095 return err;
5098 static const struct got_error *
5099 apply_unveil(const char *repo_path, const char *worktree_path)
5101 const struct got_error *error;
5103 #ifdef PROFILE
5104 if (unveil("gmon.out", "rwc") != 0)
5105 return got_error_from_errno2("unveil", "gmon.out");
5106 #endif
5107 if (repo_path && unveil(repo_path, "r") != 0)
5108 return got_error_from_errno2("unveil", repo_path);
5110 if (worktree_path && unveil(worktree_path, "rwc") != 0)
5111 return got_error_from_errno2("unveil", worktree_path);
5113 if (unveil(GOT_TMPDIR_STR, "rwc") != 0)
5114 return got_error_from_errno2("unveil", GOT_TMPDIR_STR);
5116 error = got_privsep_unveil_exec_helpers();
5117 if (error != NULL)
5118 return error;
5120 if (unveil(NULL, NULL) != 0)
5121 return got_error_from_errno("unveil");
5123 return NULL;
5126 static const struct got_error *
5127 init_mock_term(const char *test_script_path)
5129 const struct got_error *err = NULL;
5130 const char *screen_dump_path;
5131 int in;
5133 if (test_script_path == NULL || *test_script_path == '\0')
5134 return got_error_msg(GOT_ERR_IO, "TOG_TEST_SCRIPT not defined");
5136 tog_io.f = fopen(test_script_path, "re");
5137 if (tog_io.f == NULL) {
5138 err = got_error_from_errno_fmt("fopen: %s",
5139 test_script_path);
5140 goto done;
5143 /* test mode, we don't want any output */
5144 tog_io.cout = fopen("/dev/null", "w+");
5145 if (tog_io.cout == NULL) {
5146 err = got_error_from_errno2("fopen", "/dev/null");
5147 goto done;
5150 in = dup(fileno(tog_io.cout));
5151 if (in == -1) {
5152 err = got_error_from_errno("dup");
5153 goto done;
5155 tog_io.cin = fdopen(in, "r");
5156 if (tog_io.cin == NULL) {
5157 err = got_error_from_errno("fdopen");
5158 close(in);
5159 goto done;
5162 screen_dump_path = getenv("TOG_SCR_DUMP");
5163 if (screen_dump_path == NULL || *screen_dump_path == '\0')
5164 return got_error_msg(GOT_ERR_IO, "TOG_SCR_DUMP not defined");
5165 tog_io.sdump = fopen(screen_dump_path, "we");
5166 if (tog_io.sdump == NULL) {
5167 err = got_error_from_errno2("fopen", screen_dump_path);
5168 goto done;
5171 if (fseeko(tog_io.f, 0L, SEEK_SET) == -1) {
5172 err = got_error_from_errno("fseeko");
5173 goto done;
5176 if (newterm(NULL, tog_io.cout, tog_io.cin) == NULL)
5177 err = got_error_msg(GOT_ERR_IO,
5178 "newterm: failed to initialise curses");
5180 using_mock_io = 1;
5182 done:
5183 if (err)
5184 tog_io_close();
5185 return err;
5188 static void
5189 init_curses(void)
5191 if (using_mock_io) /* In test mode we use a fake terminal */
5192 return;
5194 initscr();
5196 cbreak();
5197 halfdelay(1); /* Fast refresh while initial view is loading. */
5198 noecho();
5199 nonl();
5200 intrflush(stdscr, FALSE);
5201 keypad(stdscr, TRUE);
5202 curs_set(0);
5203 if (getenv("TOG_COLORS") != NULL) {
5204 start_color();
5205 use_default_colors();
5208 return;
5211 static const struct got_error *
5212 set_tog_base_commit(struct got_repository *repo, struct got_worktree *worktree)
5214 tog_base_commit.id = got_object_id_dup(
5215 got_worktree_get_base_commit_id(worktree));
5216 if (tog_base_commit.id == NULL)
5217 return got_error_from_errno( "got_object_id_dup");
5219 return NULL;
5222 static const struct got_error *
5223 get_in_repo_path_from_argv0(char **in_repo_path, int argc, char *argv[],
5224 struct got_repository *repo, struct got_worktree *worktree)
5226 const struct got_error *err = NULL;
5228 if (argc == 0) {
5229 *in_repo_path = strdup("/");
5230 if (*in_repo_path == NULL)
5231 return got_error_from_errno("strdup");
5232 return NULL;
5235 if (worktree) {
5236 const char *prefix = got_worktree_get_path_prefix(worktree);
5237 char *p;
5239 err = got_worktree_resolve_path(&p, worktree, argv[0]);
5240 if (err)
5241 return err;
5242 if (asprintf(in_repo_path, "%s%s%s", prefix,
5243 (p[0] != '\0' && !got_path_is_root_dir(prefix)) ? "/" : "",
5244 p) == -1) {
5245 err = got_error_from_errno("asprintf");
5246 *in_repo_path = NULL;
5248 free(p);
5249 } else
5250 err = got_repo_map_path(in_repo_path, repo, argv[0]);
5252 return err;
5255 static const struct got_error *
5256 cmd_log(int argc, char *argv[])
5258 const struct got_error *error;
5259 struct got_repository *repo = NULL;
5260 struct got_worktree *worktree = NULL;
5261 struct got_object_id *start_id = NULL;
5262 char *in_repo_path = NULL, *repo_path = NULL, *cwd = NULL;
5263 char *keyword_idstr = NULL, *start_commit = NULL, *label = NULL;
5264 struct got_reference *ref = NULL;
5265 const char *head_ref_name = NULL;
5266 int ch, log_branches = 0;
5267 struct tog_view *view;
5268 int *pack_fds = NULL;
5270 while ((ch = getopt(argc, argv, "bc:r:")) != -1) {
5271 switch (ch) {
5272 case 'b':
5273 log_branches = 1;
5274 break;
5275 case 'c':
5276 start_commit = optarg;
5277 break;
5278 case 'r':
5279 repo_path = realpath(optarg, NULL);
5280 if (repo_path == NULL)
5281 return got_error_from_errno2("realpath",
5282 optarg);
5283 break;
5284 default:
5285 usage_log();
5286 /* NOTREACHED */
5290 argc -= optind;
5291 argv += optind;
5293 if (argc > 1)
5294 usage_log();
5296 error = got_repo_pack_fds_open(&pack_fds);
5297 if (error != NULL)
5298 goto done;
5300 if (repo_path == NULL) {
5301 cwd = getcwd(NULL, 0);
5302 if (cwd == NULL) {
5303 error = got_error_from_errno("getcwd");
5304 goto done;
5306 error = got_worktree_open(&worktree, cwd, NULL);
5307 if (error && error->code != GOT_ERR_NOT_WORKTREE)
5308 goto done;
5309 if (worktree)
5310 repo_path =
5311 strdup(got_worktree_get_repo_path(worktree));
5312 else
5313 repo_path = strdup(cwd);
5314 if (repo_path == NULL) {
5315 error = got_error_from_errno("strdup");
5316 goto done;
5320 error = got_repo_open(&repo, repo_path, NULL, pack_fds);
5321 if (error != NULL)
5322 goto done;
5324 error = get_in_repo_path_from_argv0(&in_repo_path, argc, argv,
5325 repo, worktree);
5326 if (error)
5327 goto done;
5329 init_curses();
5331 error = apply_unveil(got_repo_get_path(repo),
5332 worktree ? got_worktree_get_root_path(worktree) : NULL);
5333 if (error)
5334 goto done;
5336 /* already loaded by tog_log_with_path()? */
5337 if (TAILQ_EMPTY(&tog_refs)) {
5338 error = tog_load_refs(repo, 0);
5339 if (error)
5340 goto done;
5343 if (start_commit == NULL) {
5344 error = got_repo_match_object_id(&start_id, &label,
5345 worktree ? got_worktree_get_head_ref_name(worktree) :
5346 GOT_REF_HEAD, GOT_OBJ_TYPE_COMMIT, &tog_refs, repo);
5347 if (error)
5348 goto done;
5349 head_ref_name = label;
5350 } else {
5351 error = got_keyword_to_idstr(&keyword_idstr, start_commit,
5352 repo, worktree);
5353 if (error != NULL)
5354 goto done;
5355 if (keyword_idstr != NULL)
5356 start_commit = keyword_idstr;
5358 error = got_ref_open(&ref, repo, start_commit, 0);
5359 if (error == NULL)
5360 head_ref_name = got_ref_get_name(ref);
5361 else if (error->code != GOT_ERR_NOT_REF)
5362 goto done;
5363 error = got_repo_match_object_id(&start_id, NULL,
5364 start_commit, GOT_OBJ_TYPE_COMMIT, &tog_refs, repo);
5365 if (error)
5366 goto done;
5369 view = view_open(0, 0, 0, 0, TOG_VIEW_LOG);
5370 if (view == NULL) {
5371 error = got_error_from_errno("view_open");
5372 goto done;
5375 if (worktree) {
5376 error = set_tog_base_commit(repo, worktree);
5377 if (error != NULL)
5378 goto done;
5381 error = open_log_view(view, start_id, repo, head_ref_name,
5382 in_repo_path, log_branches, worktree);
5383 if (error)
5384 goto done;
5386 if (worktree) {
5387 /* The work tree will be closed by the log thread. */
5388 worktree = NULL;
5391 error = view_loop(view);
5393 done:
5394 free(tog_base_commit.id);
5395 free(keyword_idstr);
5396 free(in_repo_path);
5397 free(repo_path);
5398 free(cwd);
5399 free(start_id);
5400 free(label);
5401 if (ref)
5402 got_ref_close(ref);
5403 if (repo) {
5404 const struct got_error *close_err = got_repo_close(repo);
5405 if (error == NULL)
5406 error = close_err;
5408 if (worktree)
5409 got_worktree_close(worktree);
5410 if (pack_fds) {
5411 const struct got_error *pack_err =
5412 got_repo_pack_fds_close(pack_fds);
5413 if (error == NULL)
5414 error = pack_err;
5416 tog_free_refs();
5417 return error;
5420 __dead static void
5421 usage_diff(void)
5423 endwin();
5424 fprintf(stderr, "usage: %s diff [-asw] [-C number] [-c commit] "
5425 "[-r repository-path] [object1 object2 | path ...]\n",
5426 getprogname());
5427 exit(1);
5430 static int
5431 match_line(const char *line, regex_t *regex, size_t nmatch,
5432 regmatch_t *regmatch)
5434 return regexec(regex, line, nmatch, regmatch, 0) == 0;
5437 static struct tog_color *
5438 match_color(struct tog_colors *colors, const char *line)
5440 struct tog_color *tc = NULL;
5442 STAILQ_FOREACH(tc, colors, entry) {
5443 if (match_line(line, &tc->regex, 0, NULL))
5444 return tc;
5447 return NULL;
5450 static const struct got_error *
5451 add_matched_line(int *wtotal, const char *line, int wlimit, int col_tab_align,
5452 WINDOW *window, int skipcol, regmatch_t *regmatch)
5454 const struct got_error *err = NULL;
5455 char *exstr = NULL;
5456 wchar_t *wline = NULL;
5457 int rme, rms, n, width, scrollx;
5458 int width0 = 0, width1 = 0, width2 = 0;
5459 char *seg0 = NULL, *seg1 = NULL, *seg2 = NULL;
5461 *wtotal = 0;
5463 rms = regmatch->rm_so;
5464 rme = regmatch->rm_eo;
5466 err = expand_tab(&exstr, line);
5467 if (err)
5468 return err;
5470 /* Split the line into 3 segments, according to match offsets. */
5471 seg0 = strndup(exstr, rms);
5472 if (seg0 == NULL) {
5473 err = got_error_from_errno("strndup");
5474 goto done;
5476 seg1 = strndup(exstr + rms, rme - rms);
5477 if (seg1 == NULL) {
5478 err = got_error_from_errno("strndup");
5479 goto done;
5481 seg2 = strdup(exstr + rme);
5482 if (seg2 == NULL) {
5483 err = got_error_from_errno("strndup");
5484 goto done;
5487 /* draw up to matched token if we haven't scrolled past it */
5488 err = format_line(&wline, &width0, NULL, seg0, 0, wlimit,
5489 col_tab_align, 1);
5490 if (err)
5491 goto done;
5492 n = MAX(width0 - skipcol, 0);
5493 if (n) {
5494 free(wline);
5495 err = format_line(&wline, &width, &scrollx, seg0, skipcol,
5496 wlimit, col_tab_align, 1);
5497 if (err)
5498 goto done;
5499 waddwstr(window, &wline[scrollx]);
5500 wlimit -= width;
5501 *wtotal += width;
5504 if (wlimit > 0) {
5505 int i = 0, w = 0;
5506 size_t wlen;
5508 free(wline);
5509 err = format_line(&wline, &width1, NULL, seg1, 0, wlimit,
5510 col_tab_align, 1);
5511 if (err)
5512 goto done;
5513 wlen = wcslen(wline);
5514 while (i < wlen) {
5515 width = wcwidth(wline[i]);
5516 if (width == -1) {
5517 /* should not happen, tabs are expanded */
5518 err = got_error(GOT_ERR_RANGE);
5519 goto done;
5521 if (width0 + w + width > skipcol)
5522 break;
5523 w += width;
5524 i++;
5526 /* draw (visible part of) matched token (if scrolled into it) */
5527 if (width1 - w > 0) {
5528 wattron(window, A_STANDOUT);
5529 waddwstr(window, &wline[i]);
5530 wattroff(window, A_STANDOUT);
5531 wlimit -= (width1 - w);
5532 *wtotal += (width1 - w);
5536 if (wlimit > 0) { /* draw rest of line */
5537 free(wline);
5538 if (skipcol > width0 + width1) {
5539 err = format_line(&wline, &width2, &scrollx, seg2,
5540 skipcol - (width0 + width1), wlimit,
5541 col_tab_align, 1);
5542 if (err)
5543 goto done;
5544 waddwstr(window, &wline[scrollx]);
5545 } else {
5546 err = format_line(&wline, &width2, NULL, seg2, 0,
5547 wlimit, col_tab_align, 1);
5548 if (err)
5549 goto done;
5550 waddwstr(window, wline);
5552 *wtotal += width2;
5554 done:
5555 free(wline);
5556 free(exstr);
5557 free(seg0);
5558 free(seg1);
5559 free(seg2);
5560 return err;
5563 static int
5564 gotoline(struct tog_view *view, int *lineno, int *nprinted)
5566 FILE *f = NULL;
5567 int *eof, *first, *selected;
5569 if (view->type == TOG_VIEW_DIFF) {
5570 struct tog_diff_view_state *s = &view->state.diff;
5572 first = &s->first_displayed_line;
5573 selected = first;
5574 eof = &s->eof;
5575 f = s->f;
5576 } else if (view->type == TOG_VIEW_HELP) {
5577 struct tog_help_view_state *s = &view->state.help;
5579 first = &s->first_displayed_line;
5580 selected = first;
5581 eof = &s->eof;
5582 f = s->f;
5583 } else if (view->type == TOG_VIEW_BLAME) {
5584 struct tog_blame_view_state *s = &view->state.blame;
5586 first = &s->first_displayed_line;
5587 selected = &s->selected_line;
5588 eof = &s->eof;
5589 f = s->blame.f;
5590 } else
5591 return 0;
5593 /* Center gline in the middle of the page like vi(1). */
5594 if (*lineno < view->gline - (view->nlines - 3) / 2)
5595 return 0;
5596 if (*first != 1 && (*lineno > view->gline - (view->nlines - 3) / 2)) {
5597 rewind(f);
5598 *eof = 0;
5599 *first = 1;
5600 *lineno = 0;
5601 *nprinted = 0;
5602 return 0;
5605 *selected = view->gline <= (view->nlines - 3) / 2 ?
5606 view->gline : (view->nlines - 3) / 2 + 1;
5607 view->gline = 0;
5609 return 1;
5612 static const struct got_error *
5613 draw_file(struct tog_view *view, const char *header)
5615 struct tog_diff_view_state *s = &view->state.diff;
5616 regmatch_t *regmatch = &view->regmatch;
5617 const struct got_error *err;
5618 int nprinted = 0;
5619 char *line;
5620 size_t linesize = 0;
5621 ssize_t linelen;
5622 wchar_t *wline;
5623 int width;
5624 int max_lines = view->nlines;
5625 int nlines = s->nlines;
5626 off_t line_offset;
5628 s->lineno = s->first_displayed_line - 1;
5629 line_offset = s->lines[s->first_displayed_line - 1].offset;
5630 if (fseeko(s->f, line_offset, SEEK_SET) == -1)
5631 return got_error_from_errno("fseek");
5633 werase(view->window);
5635 if (view->gline > s->nlines - 1)
5636 view->gline = s->nlines - 1;
5638 if (header) {
5639 int ln = view->gline ? view->gline <= (view->nlines - 3) / 2 ?
5640 1 : view->gline - (view->nlines - 3) / 2 :
5641 s->lineno + s->selected_line;
5643 if (asprintf(&line, "[%d/%d] %s", ln, nlines, header) == -1)
5644 return got_error_from_errno("asprintf");
5645 err = format_line(&wline, &width, NULL, line, 0, view->ncols,
5646 0, 0);
5647 free(line);
5648 if (err)
5649 return err;
5651 if (view_needs_focus_indication(view))
5652 wstandout(view->window);
5653 waddwstr(view->window, wline);
5654 free(wline);
5655 wline = NULL;
5656 while (width++ < view->ncols)
5657 waddch(view->window, ' ');
5658 if (view_needs_focus_indication(view))
5659 wstandend(view->window);
5661 if (max_lines <= 1)
5662 return NULL;
5663 max_lines--;
5666 s->eof = 0;
5667 view->maxx = 0;
5668 line = NULL;
5669 while (max_lines > 0 && nprinted < max_lines) {
5670 enum got_diff_line_type linetype;
5671 attr_t attr = 0;
5673 linelen = getline(&line, &linesize, s->f);
5674 if (linelen == -1) {
5675 if (feof(s->f)) {
5676 s->eof = 1;
5677 break;
5679 free(line);
5680 return got_ferror(s->f, GOT_ERR_IO);
5683 if (++s->lineno < s->first_displayed_line)
5684 continue;
5685 if (view->gline && !gotoline(view, &s->lineno, &nprinted))
5686 continue;
5687 if (s->lineno == view->hiline)
5688 attr = A_STANDOUT;
5690 /* Set view->maxx based on full line length. */
5691 err = format_line(&wline, &width, NULL, line, 0, INT_MAX, 0,
5692 view->x ? 1 : 0);
5693 if (err) {
5694 free(line);
5695 return err;
5697 view->maxx = MAX(view->maxx, width);
5698 free(wline);
5699 wline = NULL;
5701 linetype = s->lines[s->lineno].type;
5702 if (linetype > GOT_DIFF_LINE_LOGMSG &&
5703 linetype < GOT_DIFF_LINE_CONTEXT)
5704 attr |= COLOR_PAIR(linetype);
5705 if (attr)
5706 wattron(view->window, attr);
5707 if (s->first_displayed_line + nprinted == s->matched_line &&
5708 regmatch->rm_so >= 0 && regmatch->rm_so < regmatch->rm_eo) {
5709 err = add_matched_line(&width, line, view->ncols, 0,
5710 view->window, view->x, regmatch);
5711 if (err) {
5712 free(line);
5713 return err;
5715 } else {
5716 int skip;
5717 err = format_line(&wline, &width, &skip, line,
5718 view->x, view->ncols, 0, view->x ? 1 : 0);
5719 if (err) {
5720 free(line);
5721 return err;
5723 waddwstr(view->window, &wline[skip]);
5724 free(wline);
5725 wline = NULL;
5727 if (s->lineno == view->hiline) {
5728 /* highlight full gline length */
5729 while (width++ < view->ncols)
5730 waddch(view->window, ' ');
5731 } else {
5732 if (width <= view->ncols - 1)
5733 waddch(view->window, '\n');
5735 if (attr)
5736 wattroff(view->window, attr);
5737 if (++nprinted == 1)
5738 s->first_displayed_line = s->lineno;
5740 free(line);
5741 if (nprinted >= 1)
5742 s->last_displayed_line = s->first_displayed_line +
5743 (nprinted - 1);
5744 else
5745 s->last_displayed_line = s->first_displayed_line;
5747 view_border(view);
5749 if (s->eof) {
5750 while (nprinted < view->nlines) {
5751 waddch(view->window, '\n');
5752 nprinted++;
5755 err = format_line(&wline, &width, NULL, TOG_EOF_STRING, 0,
5756 view->ncols, 0, 0);
5757 if (err) {
5758 return err;
5761 wstandout(view->window);
5762 waddwstr(view->window, wline);
5763 free(wline);
5764 wline = NULL;
5765 wstandend(view->window);
5768 return NULL;
5771 static char *
5772 get_datestr(time_t *time, char *datebuf)
5774 struct tm mytm, *tm;
5775 char *p, *s;
5777 tm = gmtime_r(time, &mytm);
5778 if (tm == NULL)
5779 return NULL;
5780 s = asctime_r(tm, datebuf);
5781 if (s == NULL)
5782 return NULL;
5783 p = strchr(s, '\n');
5784 if (p)
5785 *p = '\0';
5786 return s;
5789 static const struct got_error *
5790 add_line_metadata(struct got_diff_line **lines, size_t *nlines,
5791 off_t off, uint8_t type)
5793 struct got_diff_line *p;
5795 p = reallocarray(*lines, *nlines + 1, sizeof(**lines));
5796 if (p == NULL)
5797 return got_error_from_errno("reallocarray");
5798 *lines = p;
5799 (*lines)[*nlines].offset = off;
5800 (*lines)[*nlines].type = type;
5801 (*nlines)++;
5803 return NULL;
5806 static const struct got_error *
5807 cat_diff(FILE *dst, FILE *src, struct got_diff_line **d_lines, size_t *d_nlines,
5808 struct got_diff_line *s_lines, size_t s_nlines)
5810 struct got_diff_line *p;
5811 char buf[BUFSIZ];
5812 size_t i, r;
5814 if (fseeko(src, 0L, SEEK_SET) == -1)
5815 return got_error_from_errno("fseeko");
5817 for (;;) {
5818 r = fread(buf, 1, sizeof(buf), src);
5819 if (r == 0) {
5820 if (ferror(src))
5821 return got_error_from_errno("fread");
5822 if (feof(src))
5823 break;
5825 if (fwrite(buf, 1, r, dst) != r)
5826 return got_ferror(dst, GOT_ERR_IO);
5829 if (s_nlines == 0 && *d_nlines == 0)
5830 return NULL;
5833 * If commit info was in dst, increment line offsets
5834 * of the appended diff content, but skip s_lines[0]
5835 * because offset zero is already in *d_lines.
5837 if (*d_nlines > 0) {
5838 for (i = 1; i < s_nlines; ++i)
5839 s_lines[i].offset += (*d_lines)[*d_nlines - 1].offset;
5841 if (s_nlines > 0) {
5842 --s_nlines;
5843 ++s_lines;
5847 p = reallocarray(*d_lines, *d_nlines + s_nlines, sizeof(*p));
5848 if (p == NULL) {
5849 /* d_lines is freed in close_diff_view() */
5850 return got_error_from_errno("reallocarray");
5853 *d_lines = p;
5855 memcpy(*d_lines + *d_nlines, s_lines, s_nlines * sizeof(*s_lines));
5856 *d_nlines += s_nlines;
5858 return NULL;
5861 static const struct got_error *
5862 write_diffstat(FILE *outfile, struct got_diff_line **lines, size_t *nlines,
5863 struct got_diffstat_cb_arg *dsa)
5865 const struct got_error *err;
5866 struct got_pathlist_entry *pe;
5867 off_t offset;
5868 int n;
5870 if (*nlines == 0) {
5871 err = add_line_metadata(lines, nlines, 0, GOT_DIFF_LINE_NONE);
5872 if (err != NULL)
5873 return err;
5874 offset = 0;
5875 } else
5876 offset = (*lines)[*nlines - 1].offset;
5878 RB_FOREACH(pe, got_pathlist_head, dsa->paths) {
5879 struct got_diff_changed_path *cp = pe->data;
5880 int pad = dsa->max_path_len - pe->path_len + 1;
5882 n = fprintf(outfile, "%c %s%*c | %*d+ %*d-\n", cp->status,
5883 pe->path, pad, ' ', dsa->add_cols + 1, cp->add,
5884 dsa->rm_cols + 1, cp->rm);
5885 if (n < 0)
5886 return got_error_from_errno("fprintf");
5888 offset += n;
5889 err = add_line_metadata(lines, nlines, offset,
5890 GOT_DIFF_LINE_CHANGES);
5891 if (err != NULL)
5892 return err;
5895 if (fputc('\n', outfile) == EOF)
5896 return got_error_from_errno("fputc");
5898 offset++;
5899 err = add_line_metadata(lines, nlines, offset, GOT_DIFF_LINE_NONE);
5900 if (err != NULL)
5901 return err;
5903 n = fprintf(outfile,
5904 "%d file%s changed, %d insertion%s(+), %d deletion%s(-)\n",
5905 dsa->nfiles, dsa->nfiles > 1 ? "s" : "", dsa->ins,
5906 dsa->ins != 1 ? "s" : "", dsa->del, dsa->del != 1 ? "s" : "");
5907 if (n < 0)
5908 return got_error_from_errno("fprintf");
5910 offset += n;
5911 err = add_line_metadata(lines, nlines, offset, GOT_DIFF_LINE_NONE);
5912 if (err != NULL)
5913 return err;
5915 if (fputc('\n', outfile) == EOF)
5916 return got_error_from_errno("fputc");
5918 offset++;
5919 return add_line_metadata(lines, nlines, offset, GOT_DIFF_LINE_NONE);
5922 static const struct got_error *
5923 write_commit_info(struct got_diff_line **lines, size_t *nlines,
5924 struct got_object_id *commit_id, struct got_reflist_head *refs,
5925 struct got_repository *repo, int ignore_ws, int force_text_diff,
5926 struct got_diffstat_cb_arg *dsa, FILE *outfile)
5928 const struct got_error *err = NULL;
5929 char datebuf[26], *datestr;
5930 struct got_commit_object *commit;
5931 char *id_str = NULL, *logmsg = NULL, *s = NULL, *line;
5932 time_t committer_time;
5933 const char *author, *committer;
5934 char *refs_str = NULL;
5935 off_t outoff = 0;
5936 int n;
5938 err = build_refs_str(&refs_str, refs, commit_id, repo);
5939 if (err)
5940 return err;
5942 err = got_object_open_as_commit(&commit, repo, commit_id);
5943 if (err)
5944 return err;
5946 err = got_object_id_str(&id_str, commit_id);
5947 if (err) {
5948 err = got_error_from_errno("got_object_id_str");
5949 goto done;
5952 err = add_line_metadata(lines, nlines, 0, GOT_DIFF_LINE_NONE);
5953 if (err)
5954 goto done;
5956 n = fprintf(outfile, "commit %s%s%s%s\n", id_str, refs_str ? " (" : "",
5957 refs_str ? refs_str : "", refs_str ? ")" : "");
5958 if (n < 0) {
5959 err = got_error_from_errno("fprintf");
5960 goto done;
5962 outoff += n;
5963 err = add_line_metadata(lines, nlines, outoff, GOT_DIFF_LINE_META);
5964 if (err)
5965 goto done;
5967 n = fprintf(outfile, "from: %s\n",
5968 got_object_commit_get_author(commit));
5969 if (n < 0) {
5970 err = got_error_from_errno("fprintf");
5971 goto done;
5973 outoff += n;
5974 err = add_line_metadata(lines, nlines, outoff, GOT_DIFF_LINE_AUTHOR);
5975 if (err)
5976 goto done;
5978 author = got_object_commit_get_author(commit);
5979 committer = got_object_commit_get_committer(commit);
5980 if (strcmp(author, committer) != 0) {
5981 n = fprintf(outfile, "via: %s\n", committer);
5982 if (n < 0) {
5983 err = got_error_from_errno("fprintf");
5984 goto done;
5986 outoff += n;
5987 err = add_line_metadata(lines, nlines, outoff,
5988 GOT_DIFF_LINE_AUTHOR);
5989 if (err)
5990 goto done;
5992 committer_time = got_object_commit_get_committer_time(commit);
5993 datestr = get_datestr(&committer_time, datebuf);
5994 if (datestr) {
5995 n = fprintf(outfile, "date: %s UTC\n", datestr);
5996 if (n < 0) {
5997 err = got_error_from_errno("fprintf");
5998 goto done;
6000 outoff += n;
6001 err = add_line_metadata(lines, nlines, outoff,
6002 GOT_DIFF_LINE_DATE);
6003 if (err)
6004 goto done;
6006 if (got_object_commit_get_nparents(commit) > 1) {
6007 const struct got_object_id_queue *parent_ids;
6008 struct got_object_qid *qid;
6009 int pn = 1;
6010 parent_ids = got_object_commit_get_parent_ids(commit);
6011 STAILQ_FOREACH(qid, parent_ids, entry) {
6012 err = got_object_id_str(&id_str, &qid->id);
6013 if (err)
6014 goto done;
6015 n = fprintf(outfile, "parent %d: %s\n", pn++, id_str);
6016 if (n < 0) {
6017 err = got_error_from_errno("fprintf");
6018 goto done;
6020 outoff += n;
6021 err = add_line_metadata(lines, nlines, outoff,
6022 GOT_DIFF_LINE_META);
6023 if (err)
6024 goto done;
6025 free(id_str);
6026 id_str = NULL;
6030 err = got_object_commit_get_logmsg(&logmsg, commit);
6031 if (err)
6032 goto done;
6033 s = logmsg;
6034 while ((line = strsep(&s, "\n")) != NULL) {
6035 n = fprintf(outfile, "%s\n", line);
6036 if (n < 0) {
6037 err = got_error_from_errno("fprintf");
6038 goto done;
6040 outoff += n;
6041 err = add_line_metadata(lines, nlines, outoff,
6042 GOT_DIFF_LINE_LOGMSG);
6043 if (err)
6044 goto done;
6047 done:
6048 free(id_str);
6049 free(logmsg);
6050 free(refs_str);
6051 got_object_commit_close(commit);
6052 return err;
6055 static void
6056 evict_worktree_entry(struct tog_log_thread_args *ta, int victim)
6058 struct commit_queue_entry *e, *v = *ta->selected_entry;
6060 if (victim == 0)
6061 return; /* paranoid check */
6063 if (v->worktree_entry != victim) {
6064 TAILQ_FOREACH(v, &ta->real_commits->head, entry) {
6065 if (v->worktree_entry == victim)
6066 break;
6068 if (v == NULL)
6069 return;
6072 ta->wctx.wt_state &= ~victim;
6074 if (*ta->selected_entry == v)
6075 *ta->selected_entry = TAILQ_NEXT(v, entry);
6076 if (*ta->first_displayed_entry == v)
6077 *ta->first_displayed_entry = TAILQ_NEXT(v, entry);
6078 if (*ta->last_displayed_entry == v)
6079 *ta->last_displayed_entry = TAILQ_NEXT(v, entry);
6081 for (e = TAILQ_NEXT(v, entry); e != NULL; e = TAILQ_NEXT(e, entry))
6082 --e->idx;
6084 --tog_base_commit.idx;
6085 --ta->real_commits->ncommits;
6087 TAILQ_REMOVE(&ta->real_commits->head, v, entry);
6088 free(v);
6092 * Create a file which contains the target path of a symlink so we can feed
6093 * it as content to the diff engine.
6095 static const struct got_error *
6096 get_symlink_target_file(int *fd, int dirfd, const char *de_name,
6097 const char *abspath)
6099 const struct got_error *err = NULL;
6100 char target_path[PATH_MAX];
6101 ssize_t target_len, outlen;
6103 *fd = -1;
6105 if (dirfd != -1) {
6106 target_len = readlinkat(dirfd, de_name, target_path, PATH_MAX);
6107 if (target_len == -1)
6108 return got_error_from_errno2("readlinkat", abspath);
6109 } else {
6110 target_len = readlink(abspath, target_path, PATH_MAX);
6111 if (target_len == -1)
6112 return got_error_from_errno2("readlink", abspath);
6115 *fd = got_opentempfd();
6116 if (*fd == -1)
6117 return got_error_from_errno("got_opentempfd");
6119 outlen = write(*fd, target_path, target_len);
6120 if (outlen == -1) {
6121 err = got_error_from_errno("got_opentempfd");
6122 goto done;
6125 if (lseek(*fd, 0, SEEK_SET) == -1) {
6126 err = got_error_from_errno2("lseek", abspath);
6127 goto done;
6130 done:
6131 if (err) {
6132 close(*fd);
6133 *fd = -1;
6135 return err;
6138 static const struct got_error *
6139 emit_base_commit_header(FILE *f, struct got_diff_line **lines, size_t *nlines,
6140 struct got_object_id *commit_id, struct got_worktree *worktree)
6142 const struct got_error *err;
6143 struct got_object_id *base_commit_id;
6144 char *base_commit_idstr;
6145 int n;
6147 if (worktree == NULL) /* shouldn't happen */
6148 return got_error(GOT_ERR_NOT_WORKTREE);
6150 base_commit_id = got_worktree_get_base_commit_id(worktree);
6152 if (commit_id != NULL) {
6153 if (got_object_id_cmp(commit_id, base_commit_id) != 0)
6154 base_commit_id = commit_id;
6157 err = got_object_id_str(&base_commit_idstr, base_commit_id);
6158 if (err != NULL)
6159 return err;
6161 if ((n = fprintf(f, "commit - %s\n", base_commit_idstr)) < 0)
6162 err = got_error_from_errno("fprintf");
6163 free(base_commit_idstr);
6164 if (err != NULL)
6165 return err;
6167 return add_line_metadata(lines, nlines,
6168 (*lines)[*nlines - 1].offset + n, GOT_DIFF_LINE_META);
6171 static const struct got_error *
6172 tog_worktree_diff(void *arg, unsigned char status, unsigned char staged_status,
6173 const char *path, struct got_object_id *blob_id,
6174 struct got_object_id *staged_blob_id, struct got_object_id *commit_id,
6175 int dirfd, const char *de_name)
6177 const struct got_error *err = NULL;
6178 struct diff_worktree_arg *a = arg;
6179 struct got_blob_object *blob1 = NULL;
6180 struct stat sb;
6181 FILE *f2 = NULL;
6182 char *abspath = NULL, *label1 = NULL;
6183 off_t size1 = 0;
6184 off_t outoff = 0;
6185 int fd = -1, fd1 = -1, fd2 = -1;
6186 int n, f2_exists = 1;
6188 if (a->diff_staged) {
6189 if (staged_status != GOT_STATUS_MODIFY &&
6190 staged_status != GOT_STATUS_ADD &&
6191 staged_status != GOT_STATUS_DELETE)
6192 return NULL;
6193 } else {
6194 if (staged_status == GOT_STATUS_DELETE)
6195 return NULL;
6196 if (status == GOT_STATUS_NONEXISTENT)
6197 return got_error_set_errno(ENOENT, path);
6198 if (status != GOT_STATUS_MODIFY &&
6199 status != GOT_STATUS_ADD &&
6200 status != GOT_STATUS_DELETE &&
6201 status != GOT_STATUS_CONFLICT)
6202 return NULL;
6205 err = got_opentemp_truncate(a->f1);
6206 if (err != NULL)
6207 return got_error_from_errno("got_opentemp_truncate");
6208 err = got_opentemp_truncate(a->f2);
6209 if (err != NULL)
6210 return got_error_from_errno("got_opentemp_truncate");
6212 if (!a->header_shown) {
6213 n = fprintf(a->outfile, "path + %s%s\n",
6214 got_worktree_get_root_path(a->worktree),
6215 a->diff_staged ? " (staged changes)" : "");
6216 if (n < 0)
6217 return got_error_from_errno("fprintf");
6219 outoff += n;
6220 err = add_line_metadata(a->lines, a->nlines, outoff,
6221 GOT_DIFF_LINE_META);
6222 if (err != NULL)
6223 return err;
6225 a->header_shown = 1;
6228 err = emit_base_commit_header(a->outfile,
6229 a->lines, a->nlines, commit_id, a->worktree);
6230 if (err != NULL)
6231 return err;
6233 if (a->diff_staged) {
6234 const char *label1 = NULL, *label2 = NULL;
6236 switch (staged_status) {
6237 case GOT_STATUS_MODIFY:
6238 label1 = path;
6239 label2 = path;
6240 break;
6241 case GOT_STATUS_ADD:
6242 label2 = path;
6243 break;
6244 case GOT_STATUS_DELETE:
6245 label1 = path;
6246 break;
6247 default:
6248 return got_error(GOT_ERR_FILE_STATUS);
6251 fd1 = got_opentempfd();
6252 if (fd1 == -1)
6253 return got_error_from_errno("got_opentempfd");
6255 fd2 = got_opentempfd();
6256 if (fd2 == -1) {
6257 err = got_error_from_errno("got_opentempfd");
6258 goto done;
6261 err = got_diff_objects_as_blobs(a->lines, a->nlines,
6262 a->f1, a->f2, fd1, fd2, blob_id, staged_blob_id,
6263 label1, label2, a->diff_algo, a->diff_context,
6264 a->ignore_whitespace, a->force_text_diff,
6265 a->diffstat, a->repo, a->outfile);
6266 goto done;
6269 fd1 = got_opentempfd();
6270 if (fd1 == -1)
6271 return got_error_from_errno("got_opentempfd");
6273 if (staged_status == GOT_STATUS_ADD ||
6274 staged_status == GOT_STATUS_MODIFY) {
6275 char *id_str;
6277 err = got_object_open_as_blob(&blob1,
6278 a->repo, staged_blob_id, 8192, fd1);
6279 if (err != NULL)
6280 goto done;
6281 err = got_object_id_str(&id_str, staged_blob_id);
6282 if (err != NULL)
6283 goto done;
6284 if (asprintf(&label1, "%s (staged)", id_str) == -1) {
6285 err = got_error_from_errno("asprintf");
6286 free(id_str);
6287 goto done;
6289 free(id_str);
6290 } else if (status != GOT_STATUS_ADD) {
6291 err = got_object_open_as_blob(&blob1,
6292 a->repo, blob_id, 8192, fd1);
6293 if (err != NULL)
6294 goto done;
6297 if (status != GOT_STATUS_DELETE) {
6298 if (asprintf(&abspath, "%s/%s",
6299 got_worktree_get_root_path(a->worktree), path) == -1) {
6300 err = got_error_from_errno("asprintf");
6301 goto done;
6304 if (dirfd != -1) {
6305 fd = openat(dirfd, de_name,
6306 O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
6307 if (fd == -1) {
6308 if (!got_err_open_nofollow_on_symlink()) {
6309 err = got_error_from_errno2("openat",
6310 abspath);
6311 goto done;
6313 err = get_symlink_target_file(&fd,
6314 dirfd, de_name, abspath);
6315 if (err != NULL)
6316 goto done;
6318 } else {
6319 fd = open(abspath, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
6320 if (fd == -1) {
6321 if (!got_err_open_nofollow_on_symlink()) {
6322 err = got_error_from_errno2("open",
6323 abspath);
6324 goto done;
6326 err = get_symlink_target_file(&fd,
6327 dirfd, de_name, abspath);
6328 if (err != NULL)
6329 goto done;
6332 if (fstat(fd, &sb) == -1) {
6333 err = got_error_from_errno2("fstat", abspath);
6334 goto done;
6336 f2 = fdopen(fd, "r");
6337 if (f2 == NULL) {
6338 err = got_error_from_errno2("fdopen", abspath);
6339 goto done;
6341 fd = -1;
6342 } else {
6343 sb.st_size = 0;
6344 f2_exists = 0;
6347 if (blob1 != NULL) {
6348 err = got_object_blob_dump_to_file(&size1,
6349 NULL, NULL, a->f1, blob1);
6350 if (err != NULL)
6351 goto done;
6354 err = got_diff_blob_file(a->lines, a->nlines, blob1, a->f1, size1,
6355 label1, f2 != NULL ? f2 : a->f2, f2_exists, &sb, path,
6356 tog_diff_algo, a->diff_context, a->ignore_whitespace,
6357 a->force_text_diff, a->diffstat, a->outfile);
6359 done:
6360 if (fd != -1 && close(fd) == -1 && err == NULL)
6361 err = got_error_from_errno("close");
6362 if (fd1 != -1 && close(fd1) == -1 && err == NULL)
6363 err = got_error_from_errno("close");
6364 if (fd2 != -1 && close(fd2) == -1 && err == NULL)
6365 err = got_error_from_errno("close");
6366 if (blob1 != NULL)
6367 got_object_blob_close(blob1);
6368 if (f2 != NULL && fclose(f2) == EOF && err == NULL)
6369 err = got_error_from_errno("fclose");
6370 free(abspath);
6371 free(label1);
6372 return err;
6375 static const struct got_error *
6376 tog_diff_worktree(struct tog_diff_view_state *s, FILE *f,
6377 struct got_diff_line **lines, size_t *nlines,
6378 struct got_diffstat_cb_arg *dsa)
6380 const struct got_error *close_err, *err;
6381 struct got_worktree *worktree = NULL;
6382 struct diff_worktree_arg arg;
6383 struct got_pathlist_head pathlist;
6384 char *cwd, *id_str = NULL;
6386 RB_INIT(&pathlist);
6388 cwd = getcwd(NULL, 0);
6389 if (cwd == NULL)
6390 return got_error_from_errno("getcwd");
6392 err = add_line_metadata(lines, nlines, 0, GOT_DIFF_LINE_NONE);
6393 if (err != NULL)
6394 goto done;
6396 err = got_worktree_open(&worktree, cwd, NULL);
6397 if (err != NULL) {
6398 if (err->code == GOT_ERR_WORKTREE_BUSY) {
6399 int n;
6401 if ((n = fprintf(f, "%s\n", err->msg)) < 0) {
6402 err = got_ferror(f, GOT_ERR_IO);
6403 goto done;
6405 err = add_line_metadata(lines, nlines, n,
6406 GOT_DIFF_LINE_META);
6407 if (err != NULL)
6408 goto done;
6409 err = got_error(GOT_ERR_DIFF_NOCHANGES);
6411 goto done;
6414 err = got_object_id_str(&id_str,
6415 got_worktree_get_base_commit_id(worktree));
6416 if (err != NULL)
6417 goto done;
6419 err = got_repo_match_object_id(&s->id1, NULL, id_str,
6420 GOT_OBJ_TYPE_ANY, &tog_refs, s->repo);
6421 if (err != NULL)
6422 goto done;
6424 arg.id_str = id_str;
6425 arg.diff_algo = tog_diff_algo;
6426 arg.repo = s->repo;
6427 arg.worktree = worktree;
6428 arg.diffstat = dsa;
6429 arg.diff_context = s->diff_context;
6430 arg.diff_staged = s->diff_staged;
6431 arg.ignore_whitespace = s->ignore_whitespace;
6432 arg.force_text_diff = s->force_text_diff;
6433 arg.header_shown = 0;
6434 arg.lines = lines;
6435 arg.nlines = nlines;
6436 arg.f1 = s->f1;
6437 arg.f2 = s->f2;
6438 arg.outfile = f;
6440 if (s->paths == NULL) {
6441 err = got_pathlist_insert(NULL, &pathlist, "", NULL);
6442 if (err != NULL)
6443 goto done;
6446 err = got_worktree_status(worktree, s->paths ? s->paths : &pathlist,
6447 s->repo, 0, tog_worktree_diff, &arg, NULL, NULL);
6448 if (err != NULL)
6449 goto done;
6451 if (*nlines == 1) {
6452 const char *msg = TOG_WORKTREE_CHANGES_LOCAL_MSG;
6453 int n, victim = TOG_WORKTREE_CHANGES_LOCAL;
6455 if (s->diff_staged) {
6456 victim = TOG_WORKTREE_CHANGES_STAGED;
6457 msg = TOG_WORKTREE_CHANGES_STAGED_MSG;
6459 if ((n = fprintf(f, "no %s\n", msg)) < 0) {
6460 err = got_ferror(f, GOT_ERR_IO);
6461 goto done;
6463 err = add_line_metadata(lines, nlines, n, GOT_DIFF_LINE_META);
6464 if (err != NULL)
6465 goto done;
6466 if (s->parent_view && s->parent_view->type == TOG_VIEW_LOG)
6467 evict_worktree_entry(
6468 &s->parent_view->state.log.thread_args, victim);
6469 err = got_error(GOT_ERR_DIFF_NOCHANGES);
6472 done:
6473 free(cwd);
6474 free(id_str);
6475 got_pathlist_free(&pathlist, GOT_PATHLIST_FREE_NONE);
6476 if (worktree != NULL) {
6477 if ((close_err = got_worktree_close(worktree)) != NULL) {
6478 if (err == NULL || err->code == GOT_ERR_DIFF_NOCHANGES)
6479 err = close_err;
6482 return err;
6485 static const struct got_error *
6486 tog_diff_objects(struct tog_diff_view_state *s, FILE *f,
6487 struct got_diff_line **lines, size_t *nlines,
6488 struct got_diffstat_cb_arg *dsa)
6490 const struct got_error *err;
6491 int obj_type;
6493 if (s->id1)
6494 err = got_object_get_type(&obj_type, s->repo, s->id1);
6495 else
6496 err = got_object_get_type(&obj_type, s->repo, s->id2);
6497 if (err != NULL)
6498 return err;
6500 switch (obj_type) {
6501 case GOT_OBJ_TYPE_BLOB:
6502 err = got_diff_objects_as_blobs(lines, nlines, s->f1, s->f2,
6503 s->fd1, s->fd2, s->id1, s->id2, NULL, NULL, tog_diff_algo,
6504 s->diff_context, s->ignore_whitespace, s->force_text_diff,
6505 dsa, s->repo, f);
6506 if (err != NULL)
6507 return err;
6508 break;
6509 case GOT_OBJ_TYPE_TREE:
6510 err = got_diff_objects_as_trees(lines, nlines,
6511 s->f1, s->f2, s->fd1, s->fd2, s->id1, s->id2,
6512 s->paths, "", "", tog_diff_algo, s->diff_context,
6513 s->ignore_whitespace, s->force_text_diff, dsa, s->repo, f);
6514 if (err != NULL)
6515 return err;
6516 break;
6517 case GOT_OBJ_TYPE_COMMIT: {
6518 const struct got_object_id_queue *parent_ids;
6519 struct got_commit_object *commit2;
6520 struct got_object_qid *pid;
6521 struct got_reflist_head *refs;
6523 err = got_diff_objects_as_commits(lines, nlines, s->f1, s->f2,
6524 s->fd1, s->fd2, s->id1, s->id2, s->paths, tog_diff_algo,
6525 s->diff_context, s->ignore_whitespace, s->force_text_diff,
6526 dsa, s->repo, f);
6527 if (err != NULL)
6528 return err;
6530 refs = got_reflist_object_id_map_lookup(tog_refs_idmap, s->id2);
6531 /* Show commit info if we're diffing to a parent/root commit. */
6532 if (s->id1 == NULL)
6533 return write_commit_info(&s->lines, &s->nlines, s->id2,
6534 refs, s->repo, s->ignore_whitespace,
6535 s->force_text_diff, dsa, s->f);
6537 err = got_object_open_as_commit(&commit2, s->repo,
6538 s->id2);
6539 if (err != NULL)
6540 return err;
6542 parent_ids = got_object_commit_get_parent_ids(commit2);
6543 STAILQ_FOREACH(pid, parent_ids, entry) {
6544 if (got_object_id_cmp(s->id1, &pid->id) == 0) {
6545 err = write_commit_info(&s->lines, &s->nlines,
6546 s->id2, refs, s->repo, s->ignore_whitespace,
6547 s->force_text_diff, dsa, s->f);
6548 break;
6551 if (commit2 != NULL)
6552 got_object_commit_close(commit2);
6553 if (err != NULL)
6554 return err;
6555 break;
6557 default:
6558 return got_error(GOT_ERR_OBJ_TYPE);
6561 return NULL;
6564 static const struct got_error *
6565 create_diff(struct tog_diff_view_state *s)
6567 const struct got_error *err = NULL;
6568 FILE *tmp_diff_file = NULL;
6569 struct got_diff_line *lines = NULL;
6570 struct got_pathlist_head changed_paths;
6571 struct got_diffstat_cb_arg dsa;
6572 size_t nlines = 0;
6574 RB_INIT(&changed_paths);
6575 memset(&dsa, 0, sizeof(dsa));
6576 dsa.paths = &changed_paths;
6577 dsa.diff_algo = tog_diff_algo;
6578 dsa.force_text = s->force_text_diff;
6579 dsa.ignore_ws = s->ignore_whitespace;
6581 free(s->lines);
6582 s->lines = malloc(sizeof(*s->lines));
6583 if (s->lines == NULL)
6584 return got_error_from_errno("malloc");
6585 s->nlines = 0;
6587 if (s->f && fclose(s->f) == EOF) {
6588 s->f = NULL;
6589 return got_error_from_errno("fclose");
6592 s->f = got_opentemp();
6593 if (s->f == NULL)
6594 return got_error_from_errno("got_opentemp");
6597 * The diffstat requires the diff to be built first, but we want the
6598 * diffstat to precede the diff when displayed. Build the diff first
6599 * in the temporary file and write the diffstat and/or commit info to
6600 * the persistent file (s->f) from which views are drawn, then append
6601 * the diff from the temp file to the diffstat/commit info in s->f.
6603 tmp_diff_file = got_opentemp();
6604 if (tmp_diff_file == NULL)
6605 return got_error_from_errno("got_opentemp");
6607 lines = malloc(sizeof(*lines));
6608 if (lines == NULL) {
6609 err = got_error_from_errno("malloc");
6610 goto done;
6613 if (s->parent_view != NULL && s->parent_view->type == TOG_VIEW_LOG) {
6614 struct tog_log_view_state *ls = &s->parent_view->state.log;
6615 struct commit_queue_entry *cqe = ls->selected_entry;
6617 if (cqe->worktree_entry != 0) {
6618 if (cqe->worktree_entry == TOG_WORKTREE_CHANGES_STAGED)
6619 s->diff_staged = 1;
6620 s->diff_worktree = 1;
6624 if (s->diff_worktree)
6625 err = tog_diff_worktree(s, tmp_diff_file,
6626 &lines, &nlines, &dsa);
6627 else
6628 err = tog_diff_objects(s, tmp_diff_file,
6629 &lines, &nlines, &dsa);
6630 if (err != NULL) {
6631 if (err->code != GOT_ERR_DIFF_NOCHANGES)
6632 goto done;
6633 } else {
6634 err = write_diffstat(s->f, &s->lines, &s->nlines, &dsa);
6635 if (err != NULL)
6636 goto done;
6639 err = cat_diff(s->f, tmp_diff_file, &s->lines, &s->nlines,
6640 lines, nlines);
6642 done:
6643 free(lines);
6644 got_pathlist_free(&changed_paths, GOT_PATHLIST_FREE_ALL);
6645 if (s->f && fflush(s->f) != 0 && err == NULL)
6646 err = got_error_from_errno("fflush");
6647 if (tmp_diff_file && fclose(tmp_diff_file) == EOF && err == NULL)
6648 err = got_error_from_errno("fclose");
6649 return err;
6652 static void
6653 diff_view_indicate_progress(struct tog_view *view)
6655 mvwaddstr(view->window, 0, 0, "diffing...");
6656 update_panels();
6657 doupdate();
6660 static const struct got_error *
6661 search_start_diff_view(struct tog_view *view)
6663 struct tog_diff_view_state *s = &view->state.diff;
6665 s->matched_line = 0;
6666 return NULL;
6669 static void
6670 search_setup_diff_view(struct tog_view *view, FILE **f, off_t **line_offsets,
6671 size_t *nlines, int **first, int **last, int **match, int **selected)
6673 struct tog_diff_view_state *s = &view->state.diff;
6675 *f = s->f;
6676 *nlines = s->nlines;
6677 *line_offsets = NULL;
6678 *match = &s->matched_line;
6679 *first = &s->first_displayed_line;
6680 *last = &s->last_displayed_line;
6681 *selected = &s->selected_line;
6684 static const struct got_error *
6685 search_next_view_match(struct tog_view *view)
6687 const struct got_error *err = NULL;
6688 FILE *f;
6689 int lineno;
6690 char *line = NULL;
6691 size_t linesize = 0;
6692 ssize_t linelen;
6693 off_t *line_offsets;
6694 size_t nlines = 0;
6695 int *first, *last, *match, *selected;
6697 if (!view->search_setup)
6698 return got_error_msg(GOT_ERR_NOT_IMPL,
6699 "view search not supported");
6700 view->search_setup(view, &f, &line_offsets, &nlines, &first, &last,
6701 &match, &selected);
6703 if (!view->searching) {
6704 view->search_next_done = TOG_SEARCH_HAVE_MORE;
6705 return NULL;
6708 if (*match) {
6709 if (view->searching == TOG_SEARCH_FORWARD)
6710 lineno = *first + 1;
6711 else
6712 lineno = *first - 1;
6713 } else
6714 lineno = *first - 1 + *selected;
6716 while (1) {
6717 off_t offset;
6719 if (lineno <= 0 || lineno > nlines) {
6720 if (*match == 0) {
6721 view->search_next_done = TOG_SEARCH_HAVE_MORE;
6722 break;
6725 if (view->searching == TOG_SEARCH_FORWARD)
6726 lineno = 1;
6727 else
6728 lineno = nlines;
6731 offset = view->type == TOG_VIEW_DIFF ?
6732 view->state.diff.lines[lineno - 1].offset :
6733 line_offsets[lineno - 1];
6734 if (fseeko(f, offset, SEEK_SET) != 0) {
6735 free(line);
6736 return got_error_from_errno("fseeko");
6738 linelen = getline(&line, &linesize, f);
6739 if (linelen != -1) {
6740 char *exstr;
6741 err = expand_tab(&exstr, line);
6742 if (err)
6743 break;
6744 if (match_line(exstr, &view->regex, 1,
6745 &view->regmatch)) {
6746 view->search_next_done = TOG_SEARCH_HAVE_MORE;
6747 *match = lineno;
6748 free(exstr);
6749 break;
6751 free(exstr);
6753 if (view->searching == TOG_SEARCH_FORWARD)
6754 lineno++;
6755 else
6756 lineno--;
6758 free(line);
6760 if (*match) {
6761 *first = *match;
6762 *selected = 1;
6765 return err;
6768 static const struct got_error *
6769 close_diff_view(struct tog_view *view)
6771 const struct got_error *err = NULL;
6772 struct tog_diff_view_state *s = &view->state.diff;
6774 free(s->id1);
6775 s->id1 = NULL;
6776 free(s->id2);
6777 s->id2 = NULL;
6778 free(s->action);
6779 s->action = NULL;
6780 if (s->f && fclose(s->f) == EOF)
6781 err = got_error_from_errno("fclose");
6782 s->f = NULL;
6783 if (s->f1 && fclose(s->f1) == EOF && err == NULL)
6784 err = got_error_from_errno("fclose");
6785 s->f1 = NULL;
6786 if (s->f2 && fclose(s->f2) == EOF && err == NULL)
6787 err = got_error_from_errno("fclose");
6788 s->f2 = NULL;
6789 if (s->fd1 != -1 && close(s->fd1) == -1 && err == NULL)
6790 err = got_error_from_errno("close");
6791 s->fd1 = -1;
6792 if (s->fd2 != -1 && close(s->fd2) == -1 && err == NULL)
6793 err = got_error_from_errno("close");
6794 s->fd2 = -1;
6795 free(s->lines);
6796 s->lines = NULL;
6797 s->nlines = 0;
6798 return err;
6801 static const struct got_error *
6802 open_diff_view(struct tog_view *view, struct got_object_id *id1,
6803 struct got_object_id *id2, const char *label1, const char *label2,
6804 int diff_context, int ignore_whitespace, int force_text_diff,
6805 int diff_staged, int diff_worktree, const char *worktree_root,
6806 struct tog_view *parent_view, struct got_repository *repo,
6807 struct got_pathlist_head *paths)
6809 const struct got_error *err;
6810 struct tog_diff_view_state *s = &view->state.diff;
6812 memset(s, 0, sizeof(*s));
6813 s->fd1 = -1;
6814 s->fd2 = -1;
6816 if (id1 != NULL && id2 != NULL) {
6817 int type1, type2;
6819 err = got_object_get_type(&type1, repo, id1);
6820 if (err)
6821 goto done;
6822 err = got_object_get_type(&type2, repo, id2);
6823 if (err)
6824 goto done;
6826 if (type1 != type2) {
6827 err = got_error(GOT_ERR_OBJ_TYPE);
6828 goto done;
6832 if (diff_worktree == 0) {
6833 if (id1) {
6834 s->id1 = got_object_id_dup(id1);
6835 if (s->id1 == NULL) {
6836 err = got_error_from_errno("got_object_id_dup");
6837 goto done;
6839 } else
6840 s->id1 = NULL;
6842 s->id2 = got_object_id_dup(id2);
6843 if (s->id2 == NULL) {
6844 err = got_error_from_errno("got_object_id_dup");
6845 goto done;
6849 s->f1 = got_opentemp();
6850 if (s->f1 == NULL) {
6851 err = got_error_from_errno("got_opentemp");
6852 goto done;
6855 s->f2 = got_opentemp();
6856 if (s->f2 == NULL) {
6857 err = got_error_from_errno("got_opentemp");
6858 goto done;
6861 s->fd1 = got_opentempfd();
6862 if (s->fd1 == -1) {
6863 err = got_error_from_errno("got_opentempfd");
6864 goto done;
6867 s->fd2 = got_opentempfd();
6868 if (s->fd2 == -1) {
6869 err = got_error_from_errno("got_opentempfd");
6870 goto done;
6873 s->first_displayed_line = 1;
6874 s->last_displayed_line = view->nlines;
6875 s->selected_line = 1;
6876 s->label1 = label1;
6877 s->label2 = label2;
6878 s->diff_context = diff_context;
6879 s->ignore_whitespace = ignore_whitespace;
6880 s->force_text_diff = force_text_diff;
6881 s->diff_worktree = diff_worktree;
6882 s->diff_staged = diff_staged;
6883 s->parent_view = parent_view;
6884 s->paths = paths;
6885 s->repo = repo;
6886 s->worktree_root = worktree_root;
6888 if (has_colors() && getenv("TOG_COLORS") != NULL && !using_mock_io) {
6889 int rc;
6891 rc = init_pair(GOT_DIFF_LINE_MINUS,
6892 get_color_value("TOG_COLOR_DIFF_MINUS"), -1);
6893 if (rc != ERR)
6894 rc = init_pair(GOT_DIFF_LINE_PLUS,
6895 get_color_value("TOG_COLOR_DIFF_PLUS"), -1);
6896 if (rc != ERR)
6897 rc = init_pair(GOT_DIFF_LINE_HUNK,
6898 get_color_value("TOG_COLOR_DIFF_CHUNK_HEADER"), -1);
6899 if (rc != ERR)
6900 rc = init_pair(GOT_DIFF_LINE_META,
6901 get_color_value("TOG_COLOR_DIFF_META"), -1);
6902 if (rc != ERR)
6903 rc = init_pair(GOT_DIFF_LINE_CHANGES,
6904 get_color_value("TOG_COLOR_DIFF_META"), -1);
6905 if (rc != ERR)
6906 rc = init_pair(GOT_DIFF_LINE_BLOB_MIN,
6907 get_color_value("TOG_COLOR_DIFF_META"), -1);
6908 if (rc != ERR)
6909 rc = init_pair(GOT_DIFF_LINE_BLOB_PLUS,
6910 get_color_value("TOG_COLOR_DIFF_META"), -1);
6911 if (rc != ERR)
6912 rc = init_pair(GOT_DIFF_LINE_AUTHOR,
6913 get_color_value("TOG_COLOR_AUTHOR"), -1);
6914 if (rc != ERR)
6915 rc = init_pair(GOT_DIFF_LINE_DATE,
6916 get_color_value("TOG_COLOR_DATE"), -1);
6917 if (rc == ERR) {
6918 err = got_error(GOT_ERR_RANGE);
6919 goto done;
6923 if (parent_view && parent_view->type == TOG_VIEW_LOG &&
6924 view_is_splitscreen(view)) {
6925 err = show_log_view(parent_view); /* draw border */
6926 if (err != NULL)
6927 goto done;
6929 diff_view_indicate_progress(view);
6931 err = create_diff(s);
6933 view->show = show_diff_view;
6934 view->input = input_diff_view;
6935 view->reset = reset_diff_view;
6936 view->close = close_diff_view;
6937 view->search_start = search_start_diff_view;
6938 view->search_setup = search_setup_diff_view;
6939 view->search_next = search_next_view_match;
6940 done:
6941 if (err) {
6942 if (view->close == NULL)
6943 close_diff_view(view);
6944 view_close(view);
6946 return err;
6949 static const struct got_error *
6950 show_diff_view(struct tog_view *view)
6952 const struct got_error *err;
6953 struct tog_diff_view_state *s = &view->state.diff;
6954 char *header;
6956 if (s->diff_worktree) {
6957 if (asprintf(&header, "diff %s%s",
6958 s->diff_staged ? "-s " : "", s->worktree_root) == -1)
6959 return got_error_from_errno("asprintf");
6960 } else {
6961 char *id_str2, *id_str1 = NULL;
6962 const char *label1, *label2;
6964 if (s->id1) {
6965 err = got_object_id_str(&id_str1, s->id1);
6966 if (err)
6967 return err;
6968 label1 = s->label1 ? s->label1 : id_str1;
6969 } else
6970 label1 = "/dev/null";
6972 err = got_object_id_str(&id_str2, s->id2);
6973 if (err)
6974 return err;
6975 label2 = s->label2 ? s->label2 : id_str2;
6977 if (asprintf(&header, "diff %s %s", label1, label2) == -1) {
6978 err = got_error_from_errno("asprintf");
6979 free(id_str1);
6980 free(id_str2);
6981 return err;
6983 free(id_str1);
6984 free(id_str2);
6987 err = draw_file(view, header);
6988 free(header);
6989 return err;
6992 static const struct got_error *
6993 diff_write_patch(struct tog_view *view)
6995 const struct got_error *err;
6996 struct tog_diff_view_state *s = &view->state.diff;
6997 struct got_object_id *id2 = s->id2;
6998 FILE *f = NULL;
6999 char buf[BUFSIZ], pathbase[PATH_MAX];
7000 char *idstr1, *idstr2 = NULL, *path = NULL;
7001 size_t r;
7002 off_t pos;
7003 int rc;
7005 if (s->action != NULL) {
7006 free(s->action);
7007 s->action = NULL;
7010 pos = ftello(s->f);
7011 if (pos == -1)
7012 return got_error_from_errno("ftello");
7013 if (fseeko(s->f, 0L, SEEK_SET) == -1)
7014 return got_error_from_errno("fseeko");
7016 if (s->id1 != NULL) {
7017 err = got_object_id_str(&idstr1, s->id1);
7018 if (err != NULL)
7019 return err;
7021 if (id2 == NULL) {
7022 if (s->diff_worktree == 0 || tog_base_commit.id == NULL) {
7023 /* illegal state that should not be possible */
7024 err = got_error(GOT_ERR_NOT_WORKTREE);
7025 goto done;
7027 id2 = tog_base_commit.id;
7029 err = got_object_id_str(&idstr2, id2);
7030 if (err != NULL)
7031 goto done;
7033 rc = snprintf(pathbase, sizeof(pathbase), "%s/tog-%.8s-%.8s",
7034 GOT_TMPDIR_STR, idstr1 != NULL ? idstr1 : "empty", idstr2);
7035 if (rc < 0 || (size_t)rc >= sizeof(pathbase)) {
7036 err = got_error(rc < 0 ? GOT_ERR_IO : GOT_ERR_NO_SPACE);
7037 goto done;
7040 err = got_opentemp_named(&path, &f, pathbase, ".diff");
7041 if (err != NULL)
7042 goto done;
7044 while ((r = fread(buf, 1, sizeof(buf), s->f)) > 0) {
7045 if (fwrite(buf, 1, r, f) != r) {
7046 err = got_ferror(f, GOT_ERR_IO);
7047 goto done;
7051 if (ferror(s->f)) {
7052 err = got_error_from_errno("fread");
7053 goto done;
7055 if (fseeko(s->f, pos, SEEK_SET) == -1) {
7056 err = got_error_from_errno("fseeko");
7057 goto done;
7060 if (fflush(f) == EOF) {
7061 err = got_error_from_errno2("fflush", path);
7062 goto done;
7065 if (asprintf(&s->action, "patch file written to %s", path) == -1) {
7066 err = got_error_from_errno("asprintf");
7067 goto done;
7070 view->action = s->action;
7072 done:
7073 if (f != NULL && fclose(f) == EOF && err == NULL)
7074 err = got_error_from_errno2("fclose", path);
7075 free(path);
7076 free(idstr1);
7077 free(idstr2);
7078 return err;
7081 static const struct got_error *
7082 set_selected_commit(struct tog_diff_view_state *s,
7083 struct commit_queue_entry *entry)
7085 const struct got_error *err;
7086 const struct got_object_id_queue *parent_ids;
7087 struct got_commit_object *selected_commit;
7088 struct got_object_qid *pid;
7090 free(s->id1);
7091 s->id1 = NULL;
7092 free(s->id2);
7093 s->id2 = NULL;
7095 if (entry->worktree_entry == 0) {
7096 s->id2 = got_object_id_dup(entry->id);
7097 if (s->id2 == NULL)
7098 return got_error_from_errno("got_object_id_dup");
7100 err = got_object_open_as_commit(&selected_commit,
7101 s->repo, entry->id);
7102 if (err)
7103 return err;
7104 parent_ids = got_object_commit_get_parent_ids(selected_commit);
7105 pid = STAILQ_FIRST(parent_ids);
7106 s->id1 = pid ? got_object_id_dup(&pid->id) : NULL;
7107 got_object_commit_close(selected_commit);
7110 return NULL;
7113 static const struct got_error *
7114 reset_diff_view(struct tog_view *view)
7116 struct tog_diff_view_state *s = &view->state.diff;
7118 view->count = 0;
7119 wclear(view->window);
7120 s->first_displayed_line = 1;
7121 s->last_displayed_line = view->nlines;
7122 s->matched_line = 0;
7123 if (s->action != NULL) {
7124 free(s->action);
7125 s->action = NULL;
7127 diff_view_indicate_progress(view);
7128 return create_diff(s);
7131 static void
7132 diff_prev_index(struct tog_diff_view_state *s, enum got_diff_line_type type)
7134 int start, i;
7136 i = start = s->first_displayed_line - 1;
7138 while (s->lines[i].type != type) {
7139 if (i == 0)
7140 i = s->nlines - 1;
7141 if (--i == start)
7142 return; /* do nothing, requested type not in file */
7145 s->selected_line = 1;
7146 s->first_displayed_line = i;
7149 static void
7150 diff_next_index(struct tog_diff_view_state *s, enum got_diff_line_type type)
7152 int start, i;
7154 i = start = s->first_displayed_line + 1;
7156 while (s->lines[i].type != type) {
7157 if (i == s->nlines - 1)
7158 i = 0;
7159 if (++i == start)
7160 return; /* do nothing, requested type not in file */
7163 s->selected_line = 1;
7164 s->first_displayed_line = i;
7167 static struct got_object_id *get_selected_commit_id(struct tog_blame_line *,
7168 int, int, int);
7169 static struct got_object_id *get_annotation_for_line(struct tog_blame_line *,
7170 int, int);
7172 static const struct got_error *
7173 input_diff_view(struct tog_view **new_view, struct tog_view *view, int ch)
7175 const struct got_error *err = NULL;
7176 struct tog_diff_view_state *s = &view->state.diff;
7177 struct tog_log_view_state *ls;
7178 struct commit_queue_entry *old_selected_entry;
7179 char *line = NULL;
7180 size_t linesize = 0;
7181 ssize_t linelen;
7182 int i, nscroll = view->nlines - 1, up = 0;
7184 s->lineno = s->first_displayed_line - 1 + s->selected_line;
7186 if (s->action != NULL && ch != ERR) {
7187 free(s->action);
7188 s->action = NULL;
7189 view->action = NULL;
7192 switch (ch) {
7193 case '0':
7194 case '$':
7195 case KEY_RIGHT:
7196 case 'l':
7197 case KEY_LEFT:
7198 case 'h':
7199 horizontal_scroll_input(view, ch);
7200 break;
7201 case 'a':
7202 case 'w':
7203 if (ch == 'a') {
7204 s->force_text_diff = !s->force_text_diff;
7205 view->action = s->force_text_diff ?
7206 "force ASCII text enabled" :
7207 "force ASCII text disabled";
7209 else if (ch == 'w') {
7210 s->ignore_whitespace = !s->ignore_whitespace;
7211 view->action = s->ignore_whitespace ?
7212 "ignore whitespace enabled" :
7213 "ignore whitespace disabled";
7215 err = reset_diff_view(view);
7216 break;
7217 case 'g':
7218 case KEY_HOME:
7219 s->first_displayed_line = 1;
7220 view->count = 0;
7221 break;
7222 case 'G':
7223 case KEY_END:
7224 view->count = 0;
7225 if (s->eof)
7226 break;
7228 s->first_displayed_line = (s->nlines - view->nlines) + 2;
7229 s->eof = 1;
7230 break;
7231 case 'k':
7232 case KEY_UP:
7233 case CTRL('p'):
7234 if (s->first_displayed_line > 1)
7235 s->first_displayed_line--;
7236 else
7237 view->count = 0;
7238 break;
7239 case CTRL('u'):
7240 case 'u':
7241 nscroll /= 2;
7242 /* FALL THROUGH */
7243 case KEY_PPAGE:
7244 case CTRL('b'):
7245 case 'b':
7246 if (s->first_displayed_line == 1) {
7247 view->count = 0;
7248 break;
7250 i = 0;
7251 while (i++ < nscroll && s->first_displayed_line > 1)
7252 s->first_displayed_line--;
7253 break;
7254 case 'j':
7255 case KEY_DOWN:
7256 case CTRL('n'):
7257 if (!s->eof)
7258 s->first_displayed_line++;
7259 else
7260 view->count = 0;
7261 break;
7262 case CTRL('d'):
7263 case 'd':
7264 nscroll /= 2;
7265 /* FALL THROUGH */
7266 case KEY_NPAGE:
7267 case CTRL('f'):
7268 case 'f':
7269 case ' ':
7270 if (s->eof) {
7271 view->count = 0;
7272 break;
7274 i = 0;
7275 while (!s->eof && i++ < nscroll) {
7276 linelen = getline(&line, &linesize, s->f);
7277 s->first_displayed_line++;
7278 if (linelen == -1) {
7279 if (feof(s->f)) {
7280 s->eof = 1;
7281 } else
7282 err = got_ferror(s->f, GOT_ERR_IO);
7283 break;
7286 free(line);
7287 break;
7288 case '(':
7289 diff_prev_index(s, GOT_DIFF_LINE_BLOB_MIN);
7290 break;
7291 case ')':
7292 diff_next_index(s, GOT_DIFF_LINE_BLOB_MIN);
7293 break;
7294 case '{':
7295 diff_prev_index(s, GOT_DIFF_LINE_HUNK);
7296 break;
7297 case '}':
7298 diff_next_index(s, GOT_DIFF_LINE_HUNK);
7299 break;
7300 case '[':
7301 if (s->diff_context > 0) {
7302 s->diff_context--;
7303 s->matched_line = 0;
7304 diff_view_indicate_progress(view);
7305 err = create_diff(s);
7306 if (s->first_displayed_line + view->nlines - 1 >
7307 s->nlines) {
7308 s->first_displayed_line = 1;
7309 s->last_displayed_line = view->nlines;
7311 } else
7312 view->count = 0;
7313 break;
7314 case ']':
7315 if (s->diff_context < GOT_DIFF_MAX_CONTEXT) {
7316 s->diff_context++;
7317 s->matched_line = 0;
7318 diff_view_indicate_progress(view);
7319 err = create_diff(s);
7320 } else
7321 view->count = 0;
7322 break;
7323 case '<':
7324 case ',':
7325 case 'K':
7326 up = 1;
7327 /* FALL THROUGH */
7328 case '>':
7329 case '.':
7330 case 'J':
7331 if (s->parent_view == NULL) {
7332 view->count = 0;
7333 break;
7335 s->parent_view->count = view->count;
7337 if (s->parent_view->type == TOG_VIEW_LOG) {
7338 ls = &s->parent_view->state.log;
7339 old_selected_entry = ls->selected_entry;
7341 err = input_log_view(NULL, s->parent_view,
7342 up ? KEY_UP : KEY_DOWN);
7343 if (err)
7344 break;
7345 view->count = s->parent_view->count;
7347 if (old_selected_entry == ls->selected_entry)
7348 break;
7350 log_mark_clear(ls);
7352 err = set_selected_commit(s, ls->selected_entry);
7353 if (err)
7354 break;
7356 if (s->worktree_root == NULL)
7357 s->worktree_root = ls->thread_args.wctx.wt_root;
7358 } else if (s->parent_view->type == TOG_VIEW_BLAME) {
7359 struct tog_blame_view_state *bs;
7360 struct got_object_id *id, *prev_id;
7362 bs = &s->parent_view->state.blame;
7363 prev_id = get_annotation_for_line(bs->blame.lines,
7364 bs->blame.nlines, bs->last_diffed_line);
7366 err = input_blame_view(&view, s->parent_view,
7367 up ? KEY_UP : KEY_DOWN);
7368 if (err)
7369 break;
7370 view->count = s->parent_view->count;
7372 if (prev_id == NULL)
7373 break;
7374 id = get_selected_commit_id(bs->blame.lines,
7375 bs->blame.nlines, bs->first_displayed_line,
7376 bs->selected_line);
7377 if (id == NULL)
7378 break;
7380 if (!got_object_id_cmp(prev_id, id))
7381 break;
7383 err = input_blame_view(&view, s->parent_view, KEY_ENTER);
7384 if (err)
7385 break;
7387 s->diff_staged = 0;
7388 s->diff_worktree = 0;
7389 s->first_displayed_line = 1;
7390 s->last_displayed_line = view->nlines;
7391 s->matched_line = 0;
7392 view->x = 0;
7394 diff_view_indicate_progress(view);
7395 err = create_diff(s);
7396 break;
7397 case 'p':
7398 view->count = 0;
7399 err = diff_write_patch(view);
7400 break;
7401 default:
7402 view->count = 0;
7403 break;
7406 return err;
7409 static const struct got_error *
7410 get_worktree_paths_from_argv(struct got_pathlist_head *paths, int argc,
7411 char *argv[], struct got_worktree *worktree)
7413 const struct got_error *err = NULL;
7414 char *path;
7415 struct got_pathlist_entry *new;
7416 int i;
7418 if (argc == 0) {
7419 path = strdup("");
7420 if (path == NULL)
7421 return got_error_from_errno("strdup");
7422 return got_pathlist_insert(NULL, paths, path, NULL);
7425 for (i = 0; i < argc; i++) {
7426 err = got_worktree_resolve_path(&path, worktree, argv[i]);
7427 if (err)
7428 break;
7429 err = got_pathlist_insert(&new, paths, path, NULL);
7430 if (err != NULL || new == NULL) {
7431 free(path);
7432 if (err != NULL)
7433 break;
7437 return err;
7440 static const struct got_error *
7441 cmd_diff(int argc, char *argv[])
7443 const struct got_error *error;
7444 struct got_repository *repo = NULL;
7445 struct got_worktree *worktree = NULL;
7446 struct got_pathlist_head paths;
7447 struct got_object_id *ids[2] = { NULL, NULL };
7448 const char *commit_args[2] = { NULL, NULL };
7449 char *labels[2] = { NULL, NULL };
7450 char *repo_path = NULL, *worktree_path = NULL, *cwd = NULL;
7451 int type1 = GOT_OBJ_TYPE_ANY, type2 = GOT_OBJ_TYPE_ANY;
7452 int i, ncommit_args = 0, diff_context = 3, ignore_whitespace = 0;
7453 int ch, diff_staged = 0, diff_worktree = 0, force_text_diff = 0;
7454 const char *errstr;
7455 struct tog_view *view;
7456 int *pack_fds = NULL;
7458 RB_INIT(&paths);
7460 while ((ch = getopt(argc, argv, "aC:c:r:sw")) != -1) {
7461 switch (ch) {
7462 case 'a':
7463 force_text_diff = 1;
7464 break;
7465 case 'C':
7466 diff_context = strtonum(optarg, 0, GOT_DIFF_MAX_CONTEXT,
7467 &errstr);
7468 if (errstr != NULL)
7469 errx(1, "number of context lines is %s: %s",
7470 errstr, errstr);
7471 break;
7472 case 'c':
7473 if (ncommit_args >= 2)
7474 errx(1, "too many -c options used");
7475 commit_args[ncommit_args++] = optarg;
7476 break;
7477 case 'r':
7478 repo_path = realpath(optarg, NULL);
7479 if (repo_path == NULL)
7480 return got_error_from_errno2("realpath",
7481 optarg);
7482 got_path_strip_trailing_slashes(repo_path);
7483 break;
7484 case 's':
7485 diff_staged = 1;
7486 break;
7487 case 'w':
7488 ignore_whitespace = 1;
7489 break;
7490 default:
7491 usage_diff();
7492 /* NOTREACHED */
7496 argc -= optind;
7497 argv += optind;
7499 error = got_repo_pack_fds_open(&pack_fds);
7500 if (error)
7501 goto done;
7503 if (repo_path == NULL) {
7504 cwd = getcwd(NULL, 0);
7505 if (cwd == NULL)
7506 return got_error_from_errno("getcwd");
7507 error = got_worktree_open(&worktree, cwd, NULL);
7508 if (error && error->code != GOT_ERR_NOT_WORKTREE)
7509 goto done;
7510 if (worktree)
7511 repo_path =
7512 strdup(got_worktree_get_repo_path(worktree));
7513 else
7514 repo_path = strdup(cwd);
7515 if (repo_path == NULL) {
7516 error = got_error_from_errno("strdup");
7517 goto done;
7521 error = got_repo_open(&repo, repo_path, NULL, pack_fds);
7522 if (error)
7523 goto done;
7525 if (diff_staged && (worktree == NULL || ncommit_args > 0)) {
7526 error = got_error_msg(GOT_ERR_BAD_OPTION,
7527 "-s can only be used when diffing a work tree");
7528 goto done;
7531 init_curses();
7533 error = apply_unveil(got_repo_get_path(repo),
7534 worktree != NULL ? got_worktree_get_root_path(worktree) : NULL);
7535 if (error)
7536 goto done;
7538 if (argc == 2 || ncommit_args > 0) {
7539 int obj_type = (ncommit_args > 0 ?
7540 GOT_OBJ_TYPE_COMMIT : GOT_OBJ_TYPE_ANY);
7542 error = tog_load_refs(repo, 0);
7543 if (error != NULL)
7544 goto done;
7546 for (i = 0; i < (ncommit_args > 0 ? ncommit_args : argc); ++i) {
7547 const char *arg;
7548 char *keyword_idstr = NULL;
7550 if (ncommit_args > 0)
7551 arg = commit_args[i];
7552 else
7553 arg = argv[i];
7555 error = got_keyword_to_idstr(&keyword_idstr, arg,
7556 repo, worktree);
7557 if (error != NULL)
7558 goto done;
7559 if (keyword_idstr != NULL)
7560 arg = keyword_idstr;
7562 error = got_repo_match_object_id(&ids[i], &labels[i],
7563 arg, obj_type, &tog_refs, repo);
7564 free(keyword_idstr);
7565 if (error != NULL) {
7566 if (error->code != GOT_ERR_NOT_REF &&
7567 error->code != GOT_ERR_NO_OBJ)
7568 goto done;
7569 if (ncommit_args > 0)
7570 goto done;
7571 error = NULL;
7572 break;
7577 if (diff_staged && ids[0] != NULL) {
7578 error = got_error_msg(GOT_ERR_BAD_OPTION,
7579 "-s can only be used when diffing a work tree");
7580 goto done;
7583 if (ncommit_args == 0 && (ids[0] == NULL || ids[1] == NULL)) {
7584 if (worktree == NULL) {
7585 if (argc == 2 && ids[0] == NULL) {
7586 error = got_error_path(argv[0], GOT_ERR_NO_OBJ);
7587 goto done;
7588 } else if (argc == 2 && ids[1] == NULL) {
7589 error = got_error_path(argv[1], GOT_ERR_NO_OBJ);
7590 goto done;
7591 } else if (argc > 0) {
7592 error = got_error_fmt(GOT_ERR_NOT_WORKTREE,
7593 "%s", "specified paths cannot be resolved");
7594 goto done;
7595 } else {
7596 error = got_error(GOT_ERR_NOT_WORKTREE);
7597 goto done;
7601 error = get_worktree_paths_from_argv(&paths, argc, argv,
7602 worktree);
7603 if (error != NULL)
7604 goto done;
7606 worktree_path = strdup(got_worktree_get_root_path(worktree));
7607 if (worktree_path == NULL) {
7608 error = got_error_from_errno("strdup");
7609 goto done;
7611 diff_worktree = 1;
7614 if (ncommit_args == 1) { /* diff commit against its first parent */
7615 struct got_commit_object *commit;
7617 error = got_object_open_as_commit(&commit, repo, ids[0]);
7618 if (error != NULL)
7619 goto done;
7621 labels[1] = labels[0];
7622 ids[1] = ids[0];
7623 if (got_object_commit_get_nparents(commit) > 0) {
7624 const struct got_object_id_queue *pids;
7625 struct got_object_qid *pid;
7627 pids = got_object_commit_get_parent_ids(commit);
7628 pid = STAILQ_FIRST(pids);
7629 ids[0] = got_object_id_dup(&pid->id);
7630 if (ids[0] == NULL) {
7631 error = got_error_from_errno(
7632 "got_object_id_dup");
7633 got_object_commit_close(commit);
7634 goto done;
7636 error = got_object_id_str(&labels[0], ids[0]);
7637 if (error != NULL) {
7638 got_object_commit_close(commit);
7639 goto done;
7641 } else {
7642 ids[0] = NULL;
7643 labels[0] = strdup("/dev/null");
7644 if (labels[0] == NULL) {
7645 error = got_error_from_errno("strdup");
7646 got_object_commit_close(commit);
7647 goto done;
7651 got_object_commit_close(commit);
7654 if (ncommit_args == 0 && argc > 2) {
7655 error = got_error_msg(GOT_ERR_BAD_PATH,
7656 "path arguments cannot be used when diffing two objects");
7657 goto done;
7660 if (ids[0]) {
7661 error = got_object_get_type(&type1, repo, ids[0]);
7662 if (error != NULL)
7663 goto done;
7666 if (diff_worktree == 0) {
7667 error = got_object_get_type(&type2, repo, ids[1]);
7668 if (error != NULL)
7669 goto done;
7670 if (type1 != GOT_OBJ_TYPE_ANY && type1 != type2) {
7671 error = got_error(GOT_ERR_OBJ_TYPE);
7672 goto done;
7674 if (type1 == GOT_OBJ_TYPE_BLOB && argc > 2) {
7675 error = got_error_msg(GOT_ERR_OBJ_TYPE,
7676 "path arguments cannot be used when diffing blobs");
7677 goto done;
7681 for (i = 0; ncommit_args > 0 && i < argc; i++) {
7682 char *in_repo_path;
7683 struct got_pathlist_entry *new;
7685 if (worktree) {
7686 const char *prefix;
7687 char *p;
7689 error = got_worktree_resolve_path(&p, worktree,
7690 argv[i]);
7691 if (error != NULL)
7692 goto done;
7693 prefix = got_worktree_get_path_prefix(worktree);
7694 while (prefix[0] == '/')
7695 prefix++;
7696 if (asprintf(&in_repo_path, "%s%s%s", prefix,
7697 (p[0] != '\0' && prefix[0] != '\0') ? "/" : "",
7698 p) == -1) {
7699 error = got_error_from_errno("asprintf");
7700 free(p);
7701 goto done;
7703 free(p);
7704 } else {
7705 char *mapped_path, *s;
7707 error = got_repo_map_path(&mapped_path, repo, argv[i]);
7708 if (error != NULL)
7709 goto done;
7710 s = mapped_path;
7711 while (s[0] == '/')
7712 s++;
7713 in_repo_path = strdup(s);
7714 if (in_repo_path == NULL) {
7715 error = got_error_from_errno("asprintf");
7716 free(mapped_path);
7717 goto done;
7719 free(mapped_path);
7722 error = got_pathlist_insert(&new, &paths, in_repo_path, NULL);
7723 if (error != NULL || new == NULL)
7724 free(in_repo_path);
7725 if (error != NULL)
7726 goto done;
7729 view = view_open(0, 0, 0, 0, TOG_VIEW_DIFF);
7730 if (view == NULL) {
7731 error = got_error_from_errno("view_open");
7732 goto done;
7735 if (worktree) {
7736 error = set_tog_base_commit(repo, worktree);
7737 if (error != NULL)
7738 goto done;
7740 /* Release work tree lock. */
7741 got_worktree_close(worktree);
7742 worktree = NULL;
7745 error = open_diff_view(view, ids[0], ids[1], labels[0], labels[1],
7746 diff_context, ignore_whitespace, force_text_diff, diff_staged,
7747 diff_worktree, worktree_path, NULL, repo, &paths);
7748 if (error)
7749 goto done;
7751 error = view_loop(view);
7753 done:
7754 got_pathlist_free(&paths, GOT_PATHLIST_FREE_PATH);
7755 free(tog_base_commit.id);
7756 free(worktree_path);
7757 free(repo_path);
7758 free(labels[0]);
7759 free(labels[1]);
7760 free(ids[0]);
7761 free(ids[1]);
7762 free(cwd);
7763 if (repo) {
7764 const struct got_error *close_err = got_repo_close(repo);
7765 if (error == NULL)
7766 error = close_err;
7768 if (worktree)
7769 got_worktree_close(worktree);
7770 if (pack_fds) {
7771 const struct got_error *pack_err =
7772 got_repo_pack_fds_close(pack_fds);
7773 if (error == NULL)
7774 error = pack_err;
7776 tog_free_refs();
7777 return error;
7780 __dead static void
7781 usage_blame(void)
7783 endwin();
7784 fprintf(stderr,
7785 "usage: %s blame [-c commit] [-r repository-path] path\n",
7786 getprogname());
7787 exit(1);
7790 struct tog_blame_line {
7791 int annotated;
7792 struct got_object_id *id;
7795 static const struct got_error *
7796 draw_blame(struct tog_view *view)
7798 struct tog_blame_view_state *s = &view->state.blame;
7799 struct tog_blame *blame = &s->blame;
7800 regmatch_t *regmatch = &view->regmatch;
7801 const struct got_error *err;
7802 int lineno = 0, nprinted = 0;
7803 char *line = NULL;
7804 size_t linesize = 0;
7805 ssize_t linelen;
7806 wchar_t *wline;
7807 int width;
7808 struct tog_blame_line *blame_line;
7809 struct got_object_id *prev_id = NULL;
7810 char *id_str;
7811 struct tog_color *tc;
7813 err = got_object_id_str(&id_str, &s->blamed_commit->id);
7814 if (err)
7815 return err;
7817 rewind(blame->f);
7818 werase(view->window);
7820 if (asprintf(&line, "commit %s", id_str) == -1) {
7821 err = got_error_from_errno("asprintf");
7822 free(id_str);
7823 return err;
7826 err = format_line(&wline, &width, NULL, line, 0, view->ncols, 0, 0);
7827 free(line);
7828 line = NULL;
7829 if (err)
7830 return err;
7831 if (view_needs_focus_indication(view))
7832 wstandout(view->window);
7833 tc = get_color(&s->colors, TOG_COLOR_COMMIT);
7834 if (tc)
7835 wattr_on(view->window, COLOR_PAIR(tc->colorpair), NULL);
7836 waddwstr(view->window, wline);
7837 while (width++ < view->ncols)
7838 waddch(view->window, ' ');
7839 if (tc)
7840 wattr_off(view->window, COLOR_PAIR(tc->colorpair), NULL);
7841 if (view_needs_focus_indication(view))
7842 wstandend(view->window);
7843 free(wline);
7844 wline = NULL;
7846 if (view->gline > blame->nlines)
7847 view->gline = blame->nlines;
7849 if (tog_io.wait_for_ui) {
7850 struct tog_blame_thread_args *bta = &s->blame.thread_args;
7851 int rc;
7853 rc = pthread_cond_wait(&bta->blame_complete, &tog_mutex);
7854 if (rc)
7855 return got_error_set_errno(rc, "pthread_cond_wait");
7856 tog_io.wait_for_ui = 0;
7859 if (asprintf(&line, "[%d/%d] %s%s", view->gline ? view->gline :
7860 s->first_displayed_line - 1 + s->selected_line, blame->nlines,
7861 s->blame_complete ? "" : "annotating... ", s->path) == -1) {
7862 free(id_str);
7863 return got_error_from_errno("asprintf");
7865 free(id_str);
7866 err = format_line(&wline, &width, NULL, line, 0, view->ncols, 0, 0);
7867 free(line);
7868 line = NULL;
7869 if (err)
7870 return err;
7871 waddwstr(view->window, wline);
7872 free(wline);
7873 wline = NULL;
7874 if (width < view->ncols - 1)
7875 waddch(view->window, '\n');
7877 s->eof = 0;
7878 view->maxx = 0;
7879 while (nprinted < view->nlines - 2) {
7880 linelen = getline(&line, &linesize, blame->f);
7881 if (linelen == -1) {
7882 if (feof(blame->f)) {
7883 s->eof = 1;
7884 break;
7886 free(line);
7887 return got_ferror(blame->f, GOT_ERR_IO);
7889 if (++lineno < s->first_displayed_line)
7890 continue;
7891 if (view->gline && !gotoline(view, &lineno, &nprinted))
7892 continue;
7894 /* Set view->maxx based on full line length. */
7895 err = format_line(&wline, &width, NULL, line, 0, INT_MAX, 9, 1);
7896 if (err) {
7897 free(line);
7898 return err;
7900 free(wline);
7901 wline = NULL;
7902 view->maxx = MAX(view->maxx, width);
7904 if (nprinted == s->selected_line - 1)
7905 wstandout(view->window);
7907 if (blame->nlines > 0) {
7908 blame_line = &blame->lines[lineno - 1];
7909 if (blame_line->annotated && prev_id &&
7910 got_object_id_cmp(prev_id, blame_line->id) == 0 &&
7911 !(nprinted == s->selected_line - 1)) {
7912 waddstr(view->window, " ");
7913 } else if (blame_line->annotated) {
7914 char *id_str;
7915 err = got_object_id_str(&id_str,
7916 blame_line->id);
7917 if (err) {
7918 free(line);
7919 return err;
7921 tc = get_color(&s->colors, TOG_COLOR_COMMIT);
7922 if (tc)
7923 wattr_on(view->window,
7924 COLOR_PAIR(tc->colorpair), NULL);
7925 wprintw(view->window, "%.8s", id_str);
7926 if (tc)
7927 wattr_off(view->window,
7928 COLOR_PAIR(tc->colorpair), NULL);
7929 free(id_str);
7930 prev_id = blame_line->id;
7931 } else {
7932 waddstr(view->window, "........");
7933 prev_id = NULL;
7935 } else {
7936 waddstr(view->window, "........");
7937 prev_id = NULL;
7940 if (nprinted == s->selected_line - 1)
7941 wstandend(view->window);
7942 waddstr(view->window, " ");
7944 if (view->ncols <= 9) {
7945 width = 9;
7946 } else if (s->first_displayed_line + nprinted ==
7947 s->matched_line &&
7948 regmatch->rm_so >= 0 && regmatch->rm_so < regmatch->rm_eo) {
7949 err = add_matched_line(&width, line, view->ncols - 9, 9,
7950 view->window, view->x, regmatch);
7951 if (err) {
7952 free(line);
7953 return err;
7955 width += 9;
7956 } else {
7957 int skip;
7958 err = format_line(&wline, &width, &skip, line,
7959 view->x, view->ncols - 9, 9, 1);
7960 if (err) {
7961 free(line);
7962 return err;
7964 waddwstr(view->window, &wline[skip]);
7965 width += 9;
7966 free(wline);
7967 wline = NULL;
7970 if (width <= view->ncols - 1)
7971 waddch(view->window, '\n');
7972 if (++nprinted == 1)
7973 s->first_displayed_line = lineno;
7975 free(line);
7976 s->last_displayed_line = lineno;
7978 view_border(view);
7980 return NULL;
7983 static const struct got_error *
7984 blame_cb(void *arg, int nlines, int lineno,
7985 struct got_commit_object *commit, struct got_object_id *id)
7987 const struct got_error *err = NULL;
7988 struct tog_blame_cb_args *a = arg;
7989 struct tog_blame_line *line;
7990 int errcode;
7992 if (nlines != a->nlines ||
7993 (lineno != -1 && lineno < 1) || lineno > a->nlines)
7994 return got_error(GOT_ERR_RANGE);
7996 errcode = pthread_mutex_lock(&tog_mutex);
7997 if (errcode)
7998 return got_error_set_errno(errcode, "pthread_mutex_lock");
8000 if (*a->quit) { /* user has quit the blame view */
8001 err = got_error(GOT_ERR_ITER_COMPLETED);
8002 goto done;
8005 if (lineno == -1)
8006 goto done; /* no change in this commit */
8008 line = &a->lines[lineno - 1];
8009 if (line->annotated)
8010 goto done;
8012 line->id = got_object_id_dup(id);
8013 if (line->id == NULL) {
8014 err = got_error_from_errno("got_object_id_dup");
8015 goto done;
8017 line->annotated = 1;
8018 done:
8019 errcode = pthread_mutex_unlock(&tog_mutex);
8020 if (errcode)
8021 err = got_error_set_errno(errcode, "pthread_mutex_unlock");
8022 return err;
8025 static void *
8026 blame_thread(void *arg)
8028 const struct got_error *err, *close_err;
8029 struct tog_blame_thread_args *ta = arg;
8030 struct tog_blame_cb_args *a = ta->cb_args;
8031 int errcode, fd1 = -1, fd2 = -1;
8032 FILE *f1 = NULL, *f2 = NULL;
8034 fd1 = got_opentempfd();
8035 if (fd1 == -1)
8036 return (void *)got_error_from_errno("got_opentempfd");
8038 fd2 = got_opentempfd();
8039 if (fd2 == -1) {
8040 err = got_error_from_errno("got_opentempfd");
8041 goto done;
8044 f1 = got_opentemp();
8045 if (f1 == NULL) {
8046 err = (void *)got_error_from_errno("got_opentemp");
8047 goto done;
8049 f2 = got_opentemp();
8050 if (f2 == NULL) {
8051 err = (void *)got_error_from_errno("got_opentemp");
8052 goto done;
8055 err = block_signals_used_by_main_thread();
8056 if (err)
8057 goto done;
8059 err = got_blame(ta->path, a->commit_id, ta->repo,
8060 tog_diff_algo, blame_cb, ta->cb_args,
8061 ta->cancel_cb, ta->cancel_arg, fd1, fd2, f1, f2);
8062 if (err && err->code == GOT_ERR_CANCELLED)
8063 err = NULL;
8065 errcode = pthread_mutex_lock(&tog_mutex);
8066 if (errcode) {
8067 err = got_error_set_errno(errcode, "pthread_mutex_lock");
8068 goto done;
8071 close_err = got_repo_close(ta->repo);
8072 if (err == NULL)
8073 err = close_err;
8074 ta->repo = NULL;
8075 *ta->complete = 1;
8077 if (tog_io.wait_for_ui) {
8078 errcode = pthread_cond_signal(&ta->blame_complete);
8079 if (errcode && err == NULL)
8080 err = got_error_set_errno(errcode,
8081 "pthread_cond_signal");
8084 errcode = pthread_mutex_unlock(&tog_mutex);
8085 if (errcode && err == NULL)
8086 err = got_error_set_errno(errcode, "pthread_mutex_unlock");
8088 done:
8089 if (fd1 != -1 && close(fd1) == -1 && err == NULL)
8090 err = got_error_from_errno("close");
8091 if (fd2 != -1 && close(fd2) == -1 && err == NULL)
8092 err = got_error_from_errno("close");
8093 if (f1 && fclose(f1) == EOF && err == NULL)
8094 err = got_error_from_errno("fclose");
8095 if (f2 && fclose(f2) == EOF && err == NULL)
8096 err = got_error_from_errno("fclose");
8098 return (void *)err;
8101 static struct got_object_id *
8102 get_selected_commit_id(struct tog_blame_line *lines, int nlines,
8103 int first_displayed_line, int selected_line)
8105 struct tog_blame_line *line;
8107 if (nlines <= 0)
8108 return NULL;
8110 line = &lines[first_displayed_line - 1 + selected_line - 1];
8111 if (!line->annotated)
8112 return NULL;
8114 return line->id;
8117 static struct got_object_id *
8118 get_annotation_for_line(struct tog_blame_line *lines, int nlines,
8119 int lineno)
8121 struct tog_blame_line *line;
8123 if (nlines <= 0 || lineno >= nlines)
8124 return NULL;
8126 line = &lines[lineno - 1];
8127 if (!line->annotated)
8128 return NULL;
8130 return line->id;
8133 static const struct got_error *
8134 stop_blame(struct tog_blame *blame)
8136 const struct got_error *err = NULL;
8137 int i;
8139 if (blame->thread) {
8140 int errcode;
8141 errcode = pthread_mutex_unlock(&tog_mutex);
8142 if (errcode)
8143 return got_error_set_errno(errcode,
8144 "pthread_mutex_unlock");
8145 errcode = pthread_join(blame->thread, (void **)&err);
8146 if (errcode)
8147 return got_error_set_errno(errcode, "pthread_join");
8148 errcode = pthread_mutex_lock(&tog_mutex);
8149 if (errcode)
8150 return got_error_set_errno(errcode,
8151 "pthread_mutex_lock");
8152 if (err && err->code == GOT_ERR_ITER_COMPLETED)
8153 err = NULL;
8154 blame->thread = 0; //NULL;
8156 if (blame->thread_args.repo) {
8157 const struct got_error *close_err;
8158 close_err = got_repo_close(blame->thread_args.repo);
8159 if (err == NULL)
8160 err = close_err;
8161 blame->thread_args.repo = NULL;
8163 if (blame->f) {
8164 if (fclose(blame->f) == EOF && err == NULL)
8165 err = got_error_from_errno("fclose");
8166 blame->f = NULL;
8168 if (blame->lines) {
8169 for (i = 0; i < blame->nlines; i++)
8170 free(blame->lines[i].id);
8171 free(blame->lines);
8172 blame->lines = NULL;
8174 free(blame->cb_args.commit_id);
8175 blame->cb_args.commit_id = NULL;
8176 if (blame->pack_fds) {
8177 const struct got_error *pack_err =
8178 got_repo_pack_fds_close(blame->pack_fds);
8179 if (err == NULL)
8180 err = pack_err;
8181 blame->pack_fds = NULL;
8183 free(blame->line_offsets);
8184 blame->line_offsets = NULL;
8185 return err;
8188 static const struct got_error *
8189 cancel_blame_view(void *arg)
8191 const struct got_error *err = NULL;
8192 int *done = arg;
8193 int errcode;
8195 errcode = pthread_mutex_lock(&tog_mutex);
8196 if (errcode)
8197 return got_error_set_errno(errcode, "pthread_mutex_lock");
8199 if (*done)
8200 err = got_error(GOT_ERR_CANCELLED);
8202 errcode = pthread_mutex_unlock(&tog_mutex);
8203 if (errcode)
8204 return got_error_set_errno(errcode, "pthread_mutex_unlock");
8206 return err;
8209 static const struct got_error *
8210 run_blame(struct tog_view *view)
8212 struct tog_blame_view_state *s = &view->state.blame;
8213 struct tog_blame *blame = &s->blame;
8214 const struct got_error *err = NULL;
8215 struct got_commit_object *commit = NULL;
8216 struct got_blob_object *blob = NULL;
8217 struct got_repository *thread_repo = NULL;
8218 struct got_object_id *obj_id = NULL;
8219 int obj_type, fd = -1;
8220 int *pack_fds = NULL;
8222 err = got_object_open_as_commit(&commit, s->repo,
8223 &s->blamed_commit->id);
8224 if (err)
8225 return err;
8227 fd = got_opentempfd();
8228 if (fd == -1) {
8229 err = got_error_from_errno("got_opentempfd");
8230 goto done;
8233 err = got_object_id_by_path(&obj_id, s->repo, commit, s->path);
8234 if (err)
8235 goto done;
8237 err = got_object_get_type(&obj_type, s->repo, obj_id);
8238 if (err)
8239 goto done;
8241 if (obj_type != GOT_OBJ_TYPE_BLOB) {
8242 err = got_error(GOT_ERR_OBJ_TYPE);
8243 goto done;
8246 err = got_object_open_as_blob(&blob, s->repo, obj_id, 8192, fd);
8247 if (err)
8248 goto done;
8249 blame->f = got_opentemp();
8250 if (blame->f == NULL) {
8251 err = got_error_from_errno("got_opentemp");
8252 goto done;
8254 err = got_object_blob_dump_to_file(&blame->filesize, &blame->nlines,
8255 &blame->line_offsets, blame->f, blob);
8256 if (err)
8257 goto done;
8258 if (blame->nlines == 0) {
8259 s->blame_complete = 1;
8260 goto done;
8263 /* Don't include \n at EOF in the blame line count. */
8264 if (blame->line_offsets[blame->nlines - 1] == blame->filesize)
8265 blame->nlines--;
8267 blame->lines = calloc(blame->nlines, sizeof(*blame->lines));
8268 if (blame->lines == NULL) {
8269 err = got_error_from_errno("calloc");
8270 goto done;
8273 err = got_repo_pack_fds_open(&pack_fds);
8274 if (err)
8275 goto done;
8276 err = got_repo_open(&thread_repo, got_repo_get_path(s->repo), NULL,
8277 pack_fds);
8278 if (err)
8279 goto done;
8281 blame->pack_fds = pack_fds;
8282 blame->cb_args.view = view;
8283 blame->cb_args.lines = blame->lines;
8284 blame->cb_args.nlines = blame->nlines;
8285 blame->cb_args.commit_id = got_object_id_dup(&s->blamed_commit->id);
8286 if (blame->cb_args.commit_id == NULL) {
8287 err = got_error_from_errno("got_object_id_dup");
8288 goto done;
8290 blame->cb_args.quit = &s->done;
8292 blame->thread_args.path = s->path;
8293 blame->thread_args.repo = thread_repo;
8294 blame->thread_args.cb_args = &blame->cb_args;
8295 blame->thread_args.complete = &s->blame_complete;
8296 blame->thread_args.cancel_cb = cancel_blame_view;
8297 blame->thread_args.cancel_arg = &s->done;
8298 s->blame_complete = 0;
8300 if (s->first_displayed_line + view->nlines - 1 > blame->nlines) {
8301 s->first_displayed_line = 1;
8302 s->last_displayed_line = view->nlines;
8303 s->selected_line = 1;
8305 s->matched_line = 0;
8307 done:
8308 if (commit)
8309 got_object_commit_close(commit);
8310 if (fd != -1 && close(fd) == -1 && err == NULL)
8311 err = got_error_from_errno("close");
8312 if (blob)
8313 got_object_blob_close(blob);
8314 free(obj_id);
8315 if (err)
8316 stop_blame(blame);
8317 return err;
8320 static const struct got_error *
8321 open_blame_view(struct tog_view *view, char *path,
8322 struct got_object_id *commit_id, struct got_repository *repo)
8324 const struct got_error *err = NULL;
8325 struct tog_blame_view_state *s = &view->state.blame;
8327 STAILQ_INIT(&s->blamed_commits);
8329 s->path = strdup(path);
8330 if (s->path == NULL)
8331 return got_error_from_errno("strdup");
8333 err = got_object_qid_alloc(&s->blamed_commit, commit_id);
8334 if (err) {
8335 free(s->path);
8336 return err;
8339 STAILQ_INSERT_HEAD(&s->blamed_commits, s->blamed_commit, entry);
8340 s->first_displayed_line = 1;
8341 s->last_displayed_line = view->nlines;
8342 s->selected_line = 1;
8343 s->blame_complete = 0;
8344 s->repo = repo;
8345 s->commit_id = commit_id;
8346 memset(&s->blame, 0, sizeof(s->blame));
8348 STAILQ_INIT(&s->colors);
8349 if (has_colors() && getenv("TOG_COLORS") != NULL) {
8350 err = add_color(&s->colors, "^", TOG_COLOR_COMMIT,
8351 get_color_value("TOG_COLOR_COMMIT"));
8352 if (err)
8353 return err;
8356 view->show = show_blame_view;
8357 view->input = input_blame_view;
8358 view->reset = reset_blame_view;
8359 view->close = close_blame_view;
8360 view->search_start = search_start_blame_view;
8361 view->search_setup = search_setup_blame_view;
8362 view->search_next = search_next_view_match;
8364 if (using_mock_io) {
8365 struct tog_blame_thread_args *bta = &s->blame.thread_args;
8366 int rc;
8368 rc = pthread_cond_init(&bta->blame_complete, NULL);
8369 if (rc)
8370 return got_error_set_errno(rc, "pthread_cond_init");
8373 return run_blame(view);
8376 static const struct got_error *
8377 close_blame_view(struct tog_view *view)
8379 const struct got_error *err = NULL;
8380 struct tog_blame_view_state *s = &view->state.blame;
8382 if (s->blame.thread)
8383 err = stop_blame(&s->blame);
8385 got_object_id_queue_free(&s->blamed_commits);
8387 if (using_mock_io) {
8388 struct tog_blame_thread_args *bta = &s->blame.thread_args;
8389 int rc;
8391 rc = pthread_cond_destroy(&bta->blame_complete);
8392 if (rc && err == NULL)
8393 err = got_error_set_errno(rc, "pthread_cond_destroy");
8396 free(s->path);
8397 free_colors(&s->colors);
8398 return err;
8401 static const struct got_error *
8402 search_start_blame_view(struct tog_view *view)
8404 struct tog_blame_view_state *s = &view->state.blame;
8406 s->matched_line = 0;
8407 return NULL;
8410 static void
8411 search_setup_blame_view(struct tog_view *view, FILE **f, off_t **line_offsets,
8412 size_t *nlines, int **first, int **last, int **match, int **selected)
8414 struct tog_blame_view_state *s = &view->state.blame;
8416 *f = s->blame.f;
8417 *nlines = s->blame.nlines;
8418 *line_offsets = s->blame.line_offsets;
8419 *match = &s->matched_line;
8420 *first = &s->first_displayed_line;
8421 *last = &s->last_displayed_line;
8422 *selected = &s->selected_line;
8425 static const struct got_error *
8426 show_blame_view(struct tog_view *view)
8428 const struct got_error *err = NULL;
8429 struct tog_blame_view_state *s = &view->state.blame;
8430 int errcode;
8432 if (s->blame.thread == 0 && !s->blame_complete) {
8433 errcode = pthread_create(&s->blame.thread, NULL, blame_thread,
8434 &s->blame.thread_args);
8435 if (errcode)
8436 return got_error_set_errno(errcode, "pthread_create");
8438 if (!using_mock_io)
8439 halfdelay(1); /* fast refresh while annotating */
8442 if (s->blame_complete && !using_mock_io)
8443 halfdelay(10); /* disable fast refresh */
8445 err = draw_blame(view);
8447 view_border(view);
8448 return err;
8451 static const struct got_error *
8452 log_annotated_line(struct tog_view **new_view, int begin_y, int begin_x,
8453 struct got_repository *repo, struct got_object_id *id)
8455 struct tog_view *log_view;
8456 const struct got_error *err = NULL;
8458 *new_view = NULL;
8460 log_view = view_open(0, 0, begin_y, begin_x, TOG_VIEW_LOG);
8461 if (log_view == NULL)
8462 return got_error_from_errno("view_open");
8464 err = open_log_view(log_view, id, repo, GOT_REF_HEAD, "", 0, NULL);
8465 if (err)
8466 view_close(log_view);
8467 else
8468 *new_view = log_view;
8470 return err;
8473 static const struct got_error *
8474 input_blame_view(struct tog_view **new_view, struct tog_view *view, int ch)
8476 const struct got_error *err = NULL, *thread_err = NULL;
8477 struct tog_view *diff_view;
8478 struct tog_blame_view_state *s = &view->state.blame;
8479 int eos, nscroll, begin_y = 0, begin_x = 0;
8481 eos = nscroll = view->nlines - 2;
8482 if (view_is_hsplit_top(view))
8483 --eos; /* border */
8485 switch (ch) {
8486 case '0':
8487 case '$':
8488 case KEY_RIGHT:
8489 case 'l':
8490 case KEY_LEFT:
8491 case 'h':
8492 horizontal_scroll_input(view, ch);
8493 break;
8494 case 'q':
8495 s->done = 1;
8496 break;
8497 case 'g':
8498 case KEY_HOME:
8499 s->selected_line = 1;
8500 s->first_displayed_line = 1;
8501 view->count = 0;
8502 break;
8503 case 'G':
8504 case KEY_END:
8505 if (s->blame.nlines < eos) {
8506 s->selected_line = s->blame.nlines;
8507 s->first_displayed_line = 1;
8508 } else {
8509 s->selected_line = eos;
8510 s->first_displayed_line = s->blame.nlines - (eos - 1);
8512 view->count = 0;
8513 break;
8514 case 'k':
8515 case KEY_UP:
8516 case CTRL('p'):
8517 if (s->selected_line > 1)
8518 s->selected_line--;
8519 else if (s->selected_line == 1 &&
8520 s->first_displayed_line > 1)
8521 s->first_displayed_line--;
8522 else
8523 view->count = 0;
8524 break;
8525 case CTRL('u'):
8526 case 'u':
8527 nscroll /= 2;
8528 /* FALL THROUGH */
8529 case KEY_PPAGE:
8530 case CTRL('b'):
8531 case 'b':
8532 if (s->first_displayed_line == 1) {
8533 if (view->count > 1)
8534 nscroll += nscroll;
8535 s->selected_line = MAX(1, s->selected_line - nscroll);
8536 view->count = 0;
8537 break;
8539 if (s->first_displayed_line > nscroll)
8540 s->first_displayed_line -= nscroll;
8541 else
8542 s->first_displayed_line = 1;
8543 break;
8544 case 'j':
8545 case KEY_DOWN:
8546 case CTRL('n'):
8547 if (s->selected_line < eos && s->first_displayed_line +
8548 s->selected_line <= s->blame.nlines)
8549 s->selected_line++;
8550 else if (s->first_displayed_line < s->blame.nlines - (eos - 1))
8551 s->first_displayed_line++;
8552 else
8553 view->count = 0;
8554 break;
8555 case 'c':
8556 case 'p': {
8557 struct got_object_id *id = NULL;
8559 view->count = 0;
8560 id = get_selected_commit_id(s->blame.lines, s->blame.nlines,
8561 s->first_displayed_line, s->selected_line);
8562 if (id == NULL)
8563 break;
8564 if (ch == 'p') {
8565 struct got_commit_object *commit, *pcommit;
8566 struct got_object_qid *pid;
8567 struct got_object_id *blob_id = NULL;
8568 int obj_type;
8569 err = got_object_open_as_commit(&commit,
8570 s->repo, id);
8571 if (err)
8572 break;
8573 pid = STAILQ_FIRST(
8574 got_object_commit_get_parent_ids(commit));
8575 if (pid == NULL) {
8576 got_object_commit_close(commit);
8577 break;
8579 /* Check if path history ends here. */
8580 err = got_object_open_as_commit(&pcommit,
8581 s->repo, &pid->id);
8582 if (err)
8583 break;
8584 err = got_object_id_by_path(&blob_id, s->repo,
8585 pcommit, s->path);
8586 got_object_commit_close(pcommit);
8587 if (err) {
8588 if (err->code == GOT_ERR_NO_TREE_ENTRY)
8589 err = NULL;
8590 got_object_commit_close(commit);
8591 break;
8593 err = got_object_get_type(&obj_type, s->repo,
8594 blob_id);
8595 free(blob_id);
8596 /* Can't blame non-blob type objects. */
8597 if (obj_type != GOT_OBJ_TYPE_BLOB) {
8598 got_object_commit_close(commit);
8599 break;
8601 err = got_object_qid_alloc(&s->blamed_commit,
8602 &pid->id);
8603 got_object_commit_close(commit);
8604 } else {
8605 if (got_object_id_cmp(id,
8606 &s->blamed_commit->id) == 0)
8607 break;
8608 err = got_object_qid_alloc(&s->blamed_commit,
8609 id);
8611 if (err)
8612 break;
8613 s->done = 1;
8614 thread_err = stop_blame(&s->blame);
8615 s->done = 0;
8616 if (thread_err)
8617 break;
8618 STAILQ_INSERT_HEAD(&s->blamed_commits,
8619 s->blamed_commit, entry);
8620 err = run_blame(view);
8621 if (err)
8622 break;
8623 break;
8625 case 'C': {
8626 struct got_object_qid *first;
8628 view->count = 0;
8629 first = STAILQ_FIRST(&s->blamed_commits);
8630 if (!got_object_id_cmp(&first->id, s->commit_id))
8631 break;
8632 s->done = 1;
8633 thread_err = stop_blame(&s->blame);
8634 s->done = 0;
8635 if (thread_err)
8636 break;
8637 STAILQ_REMOVE_HEAD(&s->blamed_commits, entry);
8638 got_object_qid_free(s->blamed_commit);
8639 s->blamed_commit =
8640 STAILQ_FIRST(&s->blamed_commits);
8641 err = run_blame(view);
8642 if (err)
8643 break;
8644 break;
8646 case 'L':
8647 view->count = 0;
8648 s->id_to_log = get_selected_commit_id(s->blame.lines,
8649 s->blame.nlines, s->first_displayed_line, s->selected_line);
8650 if (s->id_to_log)
8651 err = view_request_new(new_view, view, TOG_VIEW_LOG);
8652 break;
8653 case KEY_ENTER:
8654 case '\r': {
8655 struct got_object_id *id = NULL;
8656 struct got_object_qid *pid;
8657 struct got_commit_object *commit = NULL;
8659 view->count = 0;
8660 id = get_selected_commit_id(s->blame.lines, s->blame.nlines,
8661 s->first_displayed_line, s->selected_line);
8662 if (id == NULL)
8663 break;
8664 err = got_object_open_as_commit(&commit, s->repo, id);
8665 if (err)
8666 break;
8667 pid = STAILQ_FIRST(got_object_commit_get_parent_ids(commit));
8668 if (*new_view) {
8669 /* traversed from diff view, release diff resources */
8670 err = close_diff_view(*new_view);
8671 if (err)
8672 break;
8673 diff_view = *new_view;
8674 } else {
8675 if (view_is_parent_view(view))
8676 view_get_split(view, &begin_y, &begin_x);
8678 diff_view = view_open(0, 0, begin_y, begin_x,
8679 TOG_VIEW_DIFF);
8680 if (diff_view == NULL) {
8681 got_object_commit_close(commit);
8682 err = got_error_from_errno("view_open");
8683 break;
8686 err = open_diff_view(diff_view, pid ? &pid->id : NULL,
8687 id, NULL, NULL, 3, 0, 0, 0, 0, NULL, view, s->repo, NULL);
8688 got_object_commit_close(commit);
8689 if (err)
8690 break;
8691 s->last_diffed_line = s->first_displayed_line - 1 +
8692 s->selected_line;
8693 if (*new_view)
8694 break; /* still open from active diff view */
8695 if (view_is_parent_view(view) &&
8696 view->mode == TOG_VIEW_SPLIT_HRZN) {
8697 err = view_init_hsplit(view, begin_y);
8698 if (err)
8699 break;
8702 view->focussed = 0;
8703 diff_view->focussed = 1;
8704 diff_view->mode = view->mode;
8705 diff_view->nlines = view->lines - begin_y;
8706 if (view_is_parent_view(view)) {
8707 view_transfer_size(diff_view, view);
8708 err = view_close_child(view);
8709 if (err)
8710 break;
8711 err = view_set_child(view, diff_view);
8712 if (err)
8713 break;
8714 view->focus_child = 1;
8715 } else
8716 *new_view = diff_view;
8717 if (err)
8718 break;
8719 break;
8721 case CTRL('d'):
8722 case 'd':
8723 nscroll /= 2;
8724 /* FALL THROUGH */
8725 case KEY_NPAGE:
8726 case CTRL('f'):
8727 case 'f':
8728 case ' ':
8729 if (s->last_displayed_line >= s->blame.nlines &&
8730 s->selected_line >= MIN(s->blame.nlines,
8731 view->nlines - 2)) {
8732 view->count = 0;
8733 break;
8735 if (s->last_displayed_line >= s->blame.nlines &&
8736 s->selected_line < view->nlines - 2) {
8737 s->selected_line +=
8738 MIN(nscroll, s->last_displayed_line -
8739 s->first_displayed_line - s->selected_line + 1);
8741 if (s->last_displayed_line + nscroll <= s->blame.nlines)
8742 s->first_displayed_line += nscroll;
8743 else
8744 s->first_displayed_line =
8745 s->blame.nlines - (view->nlines - 3);
8746 break;
8747 case KEY_RESIZE:
8748 if (s->selected_line > view->nlines - 2) {
8749 s->selected_line = MIN(s->blame.nlines,
8750 view->nlines - 2);
8752 break;
8753 default:
8754 view->count = 0;
8755 break;
8757 return thread_err ? thread_err : err;
8760 static const struct got_error *
8761 reset_blame_view(struct tog_view *view)
8763 const struct got_error *err;
8764 struct tog_blame_view_state *s = &view->state.blame;
8766 view->count = 0;
8767 s->done = 1;
8768 err = stop_blame(&s->blame);
8769 s->done = 0;
8770 if (err)
8771 return err;
8772 return run_blame(view);
8775 static const struct got_error *
8776 cmd_blame(int argc, char *argv[])
8778 const struct got_error *error;
8779 struct got_repository *repo = NULL;
8780 struct got_worktree *worktree = NULL;
8781 char *cwd = NULL, *repo_path = NULL, *in_repo_path = NULL;
8782 char *link_target = NULL;
8783 struct got_object_id *commit_id = NULL;
8784 struct got_commit_object *commit = NULL;
8785 char *keyword_idstr = NULL, *commit_id_str = NULL;
8786 int ch;
8787 struct tog_view *view = NULL;
8788 int *pack_fds = NULL;
8790 while ((ch = getopt(argc, argv, "c:r:")) != -1) {
8791 switch (ch) {
8792 case 'c':
8793 commit_id_str = optarg;
8794 break;
8795 case 'r':
8796 repo_path = realpath(optarg, NULL);
8797 if (repo_path == NULL)
8798 return got_error_from_errno2("realpath",
8799 optarg);
8800 break;
8801 default:
8802 usage_blame();
8803 /* NOTREACHED */
8807 argc -= optind;
8808 argv += optind;
8810 if (argc != 1)
8811 usage_blame();
8813 error = got_repo_pack_fds_open(&pack_fds);
8814 if (error != NULL)
8815 goto done;
8817 if (repo_path == NULL) {
8818 cwd = getcwd(NULL, 0);
8819 if (cwd == NULL)
8820 return got_error_from_errno("getcwd");
8821 error = got_worktree_open(&worktree, cwd, NULL);
8822 if (error && error->code != GOT_ERR_NOT_WORKTREE)
8823 goto done;
8824 if (worktree)
8825 repo_path =
8826 strdup(got_worktree_get_repo_path(worktree));
8827 else
8828 repo_path = strdup(cwd);
8829 if (repo_path == NULL) {
8830 error = got_error_from_errno("strdup");
8831 goto done;
8835 error = got_repo_open(&repo, repo_path, NULL, pack_fds);
8836 if (error != NULL)
8837 goto done;
8839 error = get_in_repo_path_from_argv0(&in_repo_path, argc, argv, repo,
8840 worktree);
8841 if (error)
8842 goto done;
8844 init_curses();
8846 error = apply_unveil(got_repo_get_path(repo), NULL);
8847 if (error)
8848 goto done;
8850 error = tog_load_refs(repo, 0);
8851 if (error)
8852 goto done;
8854 if (commit_id_str == NULL) {
8855 struct got_reference *head_ref;
8856 error = got_ref_open(&head_ref, repo, worktree ?
8857 got_worktree_get_head_ref_name(worktree) : GOT_REF_HEAD, 0);
8858 if (error != NULL)
8859 goto done;
8860 error = got_ref_resolve(&commit_id, repo, head_ref);
8861 got_ref_close(head_ref);
8862 } else {
8863 error = got_keyword_to_idstr(&keyword_idstr, commit_id_str,
8864 repo, worktree);
8865 if (error != NULL)
8866 goto done;
8867 if (keyword_idstr != NULL)
8868 commit_id_str = keyword_idstr;
8870 error = got_repo_match_object_id(&commit_id, NULL,
8871 commit_id_str, GOT_OBJ_TYPE_COMMIT, &tog_refs, repo);
8873 if (error != NULL)
8874 goto done;
8876 error = got_object_open_as_commit(&commit, repo, commit_id);
8877 if (error)
8878 goto done;
8880 error = got_object_resolve_symlinks(&link_target, in_repo_path,
8881 commit, repo);
8882 if (error)
8883 goto done;
8885 view = view_open(0, 0, 0, 0, TOG_VIEW_BLAME);
8886 if (view == NULL) {
8887 error = got_error_from_errno("view_open");
8888 goto done;
8890 error = open_blame_view(view, link_target ? link_target : in_repo_path,
8891 commit_id, repo);
8892 if (error != NULL) {
8893 if (view->close == NULL)
8894 close_blame_view(view);
8895 view_close(view);
8896 goto done;
8899 if (worktree) {
8900 error = set_tog_base_commit(repo, worktree);
8901 if (error != NULL)
8902 goto done;
8904 /* Release work tree lock. */
8905 got_worktree_close(worktree);
8906 worktree = NULL;
8909 error = view_loop(view);
8911 done:
8912 free(tog_base_commit.id);
8913 free(repo_path);
8914 free(in_repo_path);
8915 free(link_target);
8916 free(cwd);
8917 free(commit_id);
8918 free(keyword_idstr);
8919 if (commit)
8920 got_object_commit_close(commit);
8921 if (worktree)
8922 got_worktree_close(worktree);
8923 if (repo) {
8924 const struct got_error *close_err = got_repo_close(repo);
8925 if (error == NULL)
8926 error = close_err;
8928 if (pack_fds) {
8929 const struct got_error *pack_err =
8930 got_repo_pack_fds_close(pack_fds);
8931 if (error == NULL)
8932 error = pack_err;
8934 tog_free_refs();
8935 return error;
8938 static const struct got_error *
8939 draw_tree_entries(struct tog_view *view, const char *parent_path)
8941 struct tog_tree_view_state *s = &view->state.tree;
8942 const struct got_error *err = NULL;
8943 struct got_tree_entry *te;
8944 wchar_t *wline;
8945 char *index = NULL;
8946 struct tog_color *tc;
8947 int width, n, nentries, scrollx, i = 1;
8948 int limit = view->nlines;
8950 s->ndisplayed = 0;
8951 if (view_is_hsplit_top(view))
8952 --limit; /* border */
8954 werase(view->window);
8956 if (limit == 0)
8957 return NULL;
8959 err = format_line(&wline, &width, NULL, s->tree_label, 0, view->ncols,
8960 0, 0);
8961 if (err)
8962 return err;
8963 if (view_needs_focus_indication(view))
8964 wstandout(view->window);
8965 tc = get_color(&s->colors, TOG_COLOR_COMMIT);
8966 if (tc)
8967 wattr_on(view->window, COLOR_PAIR(tc->colorpair), NULL);
8968 waddwstr(view->window, wline);
8969 free(wline);
8970 wline = NULL;
8971 while (width++ < view->ncols)
8972 waddch(view->window, ' ');
8973 if (tc)
8974 wattr_off(view->window, COLOR_PAIR(tc->colorpair), NULL);
8975 if (view_needs_focus_indication(view))
8976 wstandend(view->window);
8977 if (--limit <= 0)
8978 return NULL;
8980 i += s->selected;
8981 if (s->first_displayed_entry) {
8982 i += got_tree_entry_get_index(s->first_displayed_entry);
8983 if (s->tree != s->root)
8984 ++i; /* account for ".." entry */
8986 nentries = got_object_tree_get_nentries(s->tree);
8987 if (asprintf(&index, "[%d/%d] %s",
8988 i, nentries + (s->tree == s->root ? 0 : 1), parent_path) == -1)
8989 return got_error_from_errno("asprintf");
8990 err = format_line(&wline, &width, NULL, index, 0, view->ncols, 0, 0);
8991 free(index);
8992 if (err)
8993 return err;
8994 waddwstr(view->window, wline);
8995 free(wline);
8996 wline = NULL;
8997 if (width < view->ncols - 1)
8998 waddch(view->window, '\n');
8999 if (--limit <= 0)
9000 return NULL;
9001 waddch(view->window, '\n');
9002 if (--limit <= 0)
9003 return NULL;
9005 if (s->first_displayed_entry == NULL) {
9006 te = got_object_tree_get_first_entry(s->tree);
9007 if (s->selected == 0) {
9008 if (view->focussed)
9009 wstandout(view->window);
9010 s->selected_entry = NULL;
9012 waddstr(view->window, " ..\n"); /* parent directory */
9013 if (s->selected == 0 && view->focussed)
9014 wstandend(view->window);
9015 s->ndisplayed++;
9016 if (--limit <= 0)
9017 return NULL;
9018 n = 1;
9019 } else {
9020 n = 0;
9021 te = s->first_displayed_entry;
9024 view->maxx = 0;
9025 for (i = got_tree_entry_get_index(te); i < nentries; i++) {
9026 char *line = NULL, *id_str = NULL, *link_target = NULL;
9027 const char *modestr = "";
9028 mode_t mode;
9030 te = got_object_tree_get_entry(s->tree, i);
9031 mode = got_tree_entry_get_mode(te);
9033 if (s->show_ids) {
9034 err = got_object_id_str(&id_str,
9035 got_tree_entry_get_id(te));
9036 if (err)
9037 return got_error_from_errno(
9038 "got_object_id_str");
9040 if (got_object_tree_entry_is_submodule(te))
9041 modestr = "$";
9042 else if (S_ISLNK(mode)) {
9043 int i;
9045 err = got_tree_entry_get_symlink_target(&link_target,
9046 te, s->repo);
9047 if (err) {
9048 free(id_str);
9049 return err;
9051 for (i = 0; link_target[i] != '\0'; i++) {
9052 if (!isprint((unsigned char)link_target[i]))
9053 link_target[i] = '?';
9055 modestr = "@";
9057 else if (S_ISDIR(mode))
9058 modestr = "/";
9059 else if (mode & S_IXUSR)
9060 modestr = "*";
9061 if (asprintf(&line, "%s %s%s%s%s", id_str ? id_str : "",
9062 got_tree_entry_get_name(te), modestr,
9063 link_target ? " -> ": "",
9064 link_target ? link_target : "") == -1) {
9065 free(id_str);
9066 free(link_target);
9067 return got_error_from_errno("asprintf");
9069 free(id_str);
9070 free(link_target);
9072 /* use full line width to determine view->maxx */
9073 err = format_line(&wline, &width, NULL, line, 0, INT_MAX, 0, 0);
9074 if (err) {
9075 free(line);
9076 break;
9078 view->maxx = MAX(view->maxx, width);
9079 free(wline);
9080 wline = NULL;
9082 err = format_line(&wline, &width, &scrollx, line, view->x,
9083 view->ncols, 0, 0);
9084 if (err) {
9085 free(line);
9086 break;
9088 if (n == s->selected) {
9089 if (view->focussed)
9090 wstandout(view->window);
9091 s->selected_entry = te;
9093 tc = match_color(&s->colors, line);
9094 if (tc)
9095 wattr_on(view->window,
9096 COLOR_PAIR(tc->colorpair), NULL);
9097 waddwstr(view->window, &wline[scrollx]);
9098 if (tc)
9099 wattr_off(view->window,
9100 COLOR_PAIR(tc->colorpair), NULL);
9101 if (width < view->ncols)
9102 waddch(view->window, '\n');
9103 if (n == s->selected && view->focussed)
9104 wstandend(view->window);
9105 free(line);
9106 free(wline);
9107 wline = NULL;
9108 n++;
9109 s->ndisplayed++;
9110 s->last_displayed_entry = te;
9111 if (--limit <= 0)
9112 break;
9115 return err;
9118 static void
9119 tree_scroll_up(struct tog_tree_view_state *s, int maxscroll)
9121 struct got_tree_entry *te;
9122 int isroot = s->tree == s->root;
9123 int i = 0;
9125 if (s->first_displayed_entry == NULL)
9126 return;
9128 te = got_tree_entry_get_prev(s->tree, s->first_displayed_entry);
9129 while (i++ < maxscroll) {
9130 if (te == NULL) {
9131 if (!isroot)
9132 s->first_displayed_entry = NULL;
9133 break;
9135 s->first_displayed_entry = te;
9136 te = got_tree_entry_get_prev(s->tree, te);
9140 static const struct got_error *
9141 tree_scroll_down(struct tog_view *view, int maxscroll)
9143 struct tog_tree_view_state *s = &view->state.tree;
9144 struct got_tree_entry *next, *last;
9145 int n = 0;
9147 if (s->first_displayed_entry)
9148 next = got_tree_entry_get_next(s->tree,
9149 s->first_displayed_entry);
9150 else
9151 next = got_object_tree_get_first_entry(s->tree);
9153 last = s->last_displayed_entry;
9154 while (next && n++ < maxscroll) {
9155 if (last) {
9156 s->last_displayed_entry = last;
9157 last = got_tree_entry_get_next(s->tree, last);
9159 if (last || (view->mode == TOG_VIEW_SPLIT_HRZN && next)) {
9160 s->first_displayed_entry = next;
9161 next = got_tree_entry_get_next(s->tree, next);
9165 return NULL;
9168 static const struct got_error *
9169 tree_entry_path(char **path, struct tog_parent_trees *parents,
9170 struct got_tree_entry *te)
9172 const struct got_error *err = NULL;
9173 struct tog_parent_tree *pt;
9174 size_t len = 2; /* for leading slash and NUL */
9176 TAILQ_FOREACH(pt, parents, entry)
9177 len += strlen(got_tree_entry_get_name(pt->selected_entry))
9178 + 1 /* slash */;
9179 if (te)
9180 len += strlen(got_tree_entry_get_name(te));
9182 *path = calloc(1, len);
9183 if (path == NULL)
9184 return got_error_from_errno("calloc");
9186 (*path)[0] = '/';
9187 pt = TAILQ_LAST(parents, tog_parent_trees);
9188 while (pt) {
9189 const char *name = got_tree_entry_get_name(pt->selected_entry);
9190 if (strlcat(*path, name, len) >= len) {
9191 err = got_error(GOT_ERR_NO_SPACE);
9192 goto done;
9194 if (strlcat(*path, "/", len) >= len) {
9195 err = got_error(GOT_ERR_NO_SPACE);
9196 goto done;
9198 pt = TAILQ_PREV(pt, tog_parent_trees, entry);
9200 if (te) {
9201 if (strlcat(*path, got_tree_entry_get_name(te), len) >= len) {
9202 err = got_error(GOT_ERR_NO_SPACE);
9203 goto done;
9206 done:
9207 if (err) {
9208 free(*path);
9209 *path = NULL;
9211 return err;
9214 static const struct got_error *
9215 blame_tree_entry(struct tog_view **new_view, int begin_y, int begin_x,
9216 struct got_tree_entry *te, struct tog_parent_trees *parents,
9217 struct got_object_id *commit_id, struct got_repository *repo)
9219 const struct got_error *err = NULL;
9220 char *path;
9221 struct tog_view *blame_view;
9223 *new_view = NULL;
9225 err = tree_entry_path(&path, parents, te);
9226 if (err)
9227 return err;
9229 blame_view = view_open(0, 0, begin_y, begin_x, TOG_VIEW_BLAME);
9230 if (blame_view == NULL) {
9231 err = got_error_from_errno("view_open");
9232 goto done;
9235 err = open_blame_view(blame_view, path, commit_id, repo);
9236 if (err) {
9237 if (err->code == GOT_ERR_CANCELLED)
9238 err = NULL;
9239 view_close(blame_view);
9240 } else
9241 *new_view = blame_view;
9242 done:
9243 free(path);
9244 return err;
9247 static const struct got_error *
9248 log_selected_tree_entry(struct tog_view **new_view, int begin_y, int begin_x,
9249 struct tog_tree_view_state *s)
9251 struct tog_view *log_view;
9252 const struct got_error *err = NULL;
9253 char *path;
9255 *new_view = NULL;
9257 log_view = view_open(0, 0, begin_y, begin_x, TOG_VIEW_LOG);
9258 if (log_view == NULL)
9259 return got_error_from_errno("view_open");
9261 err = tree_entry_path(&path, &s->parents, s->selected_entry);
9262 if (err)
9263 return err;
9265 err = open_log_view(log_view, s->commit_id, s->repo, s->head_ref_name,
9266 path, 0, NULL);
9267 if (err)
9268 view_close(log_view);
9269 else
9270 *new_view = log_view;
9271 free(path);
9272 return err;
9275 static const struct got_error *
9276 open_tree_view(struct tog_view *view, struct got_object_id *commit_id,
9277 const char *head_ref_name, struct got_repository *repo)
9279 const struct got_error *err = NULL;
9280 char *commit_id_str = NULL;
9281 struct tog_tree_view_state *s = &view->state.tree;
9282 struct got_commit_object *commit = NULL;
9284 TAILQ_INIT(&s->parents);
9285 STAILQ_INIT(&s->colors);
9287 s->commit_id = got_object_id_dup(commit_id);
9288 if (s->commit_id == NULL) {
9289 err = got_error_from_errno("got_object_id_dup");
9290 goto done;
9293 err = got_object_open_as_commit(&commit, repo, commit_id);
9294 if (err)
9295 goto done;
9298 * The root is opened here and will be closed when the view is closed.
9299 * Any visited subtrees and their path-wise parents are opened and
9300 * closed on demand.
9302 err = got_object_open_as_tree(&s->root, repo,
9303 got_object_commit_get_tree_id(commit));
9304 if (err)
9305 goto done;
9306 s->tree = s->root;
9308 err = got_object_id_str(&commit_id_str, commit_id);
9309 if (err != NULL)
9310 goto done;
9312 if (asprintf(&s->tree_label, "commit %s", commit_id_str) == -1) {
9313 err = got_error_from_errno("asprintf");
9314 goto done;
9317 s->first_displayed_entry = got_object_tree_get_entry(s->tree, 0);
9318 s->selected_entry = got_object_tree_get_entry(s->tree, 0);
9319 if (head_ref_name) {
9320 s->head_ref_name = strdup(head_ref_name);
9321 if (s->head_ref_name == NULL) {
9322 err = got_error_from_errno("strdup");
9323 goto done;
9326 s->repo = repo;
9328 if (has_colors() && getenv("TOG_COLORS") != NULL) {
9329 err = add_color(&s->colors, "\\$$",
9330 TOG_COLOR_TREE_SUBMODULE,
9331 get_color_value("TOG_COLOR_TREE_SUBMODULE"));
9332 if (err)
9333 goto done;
9334 err = add_color(&s->colors, "@$", TOG_COLOR_TREE_SYMLINK,
9335 get_color_value("TOG_COLOR_TREE_SYMLINK"));
9336 if (err)
9337 goto done;
9338 err = add_color(&s->colors, "/$",
9339 TOG_COLOR_TREE_DIRECTORY,
9340 get_color_value("TOG_COLOR_TREE_DIRECTORY"));
9341 if (err)
9342 goto done;
9344 err = add_color(&s->colors, "\\*$",
9345 TOG_COLOR_TREE_EXECUTABLE,
9346 get_color_value("TOG_COLOR_TREE_EXECUTABLE"));
9347 if (err)
9348 goto done;
9350 err = add_color(&s->colors, "^$", TOG_COLOR_COMMIT,
9351 get_color_value("TOG_COLOR_COMMIT"));
9352 if (err)
9353 goto done;
9356 view->show = show_tree_view;
9357 view->input = input_tree_view;
9358 view->close = close_tree_view;
9359 view->search_start = search_start_tree_view;
9360 view->search_next = search_next_tree_view;
9361 done:
9362 free(commit_id_str);
9363 if (commit)
9364 got_object_commit_close(commit);
9365 if (err) {
9366 if (view->close == NULL)
9367 close_tree_view(view);
9368 view_close(view);
9370 return err;
9373 static const struct got_error *
9374 close_tree_view(struct tog_view *view)
9376 struct tog_tree_view_state *s = &view->state.tree;
9378 free_colors(&s->colors);
9379 free(s->tree_label);
9380 s->tree_label = NULL;
9381 free(s->commit_id);
9382 s->commit_id = NULL;
9383 free(s->head_ref_name);
9384 s->head_ref_name = NULL;
9385 while (!TAILQ_EMPTY(&s->parents)) {
9386 struct tog_parent_tree *parent;
9387 parent = TAILQ_FIRST(&s->parents);
9388 TAILQ_REMOVE(&s->parents, parent, entry);
9389 if (parent->tree != s->root)
9390 got_object_tree_close(parent->tree);
9391 free(parent);
9394 if (s->tree != NULL && s->tree != s->root)
9395 got_object_tree_close(s->tree);
9396 if (s->root)
9397 got_object_tree_close(s->root);
9398 return NULL;
9401 static const struct got_error *
9402 search_start_tree_view(struct tog_view *view)
9404 struct tog_tree_view_state *s = &view->state.tree;
9406 s->matched_entry = NULL;
9407 return NULL;
9410 static int
9411 match_tree_entry(struct got_tree_entry *te, regex_t *regex)
9413 regmatch_t regmatch;
9415 return regexec(regex, got_tree_entry_get_name(te), 1, &regmatch,
9416 0) == 0;
9419 static const struct got_error *
9420 search_next_tree_view(struct tog_view *view)
9422 struct tog_tree_view_state *s = &view->state.tree;
9423 struct got_tree_entry *te = NULL;
9425 if (!view->searching) {
9426 view->search_next_done = TOG_SEARCH_HAVE_MORE;
9427 return NULL;
9430 if (s->matched_entry) {
9431 if (view->searching == TOG_SEARCH_FORWARD) {
9432 if (s->selected_entry)
9433 te = got_tree_entry_get_next(s->tree,
9434 s->selected_entry);
9435 else
9436 te = got_object_tree_get_first_entry(s->tree);
9437 } else {
9438 if (s->selected_entry == NULL)
9439 te = got_object_tree_get_last_entry(s->tree);
9440 else
9441 te = got_tree_entry_get_prev(s->tree,
9442 s->selected_entry);
9444 } else {
9445 if (s->selected_entry)
9446 te = s->selected_entry;
9447 else if (view->searching == TOG_SEARCH_FORWARD)
9448 te = got_object_tree_get_first_entry(s->tree);
9449 else
9450 te = got_object_tree_get_last_entry(s->tree);
9453 while (1) {
9454 if (te == NULL) {
9455 if (s->matched_entry == NULL) {
9456 view->search_next_done = TOG_SEARCH_HAVE_MORE;
9457 return NULL;
9459 if (view->searching == TOG_SEARCH_FORWARD)
9460 te = got_object_tree_get_first_entry(s->tree);
9461 else
9462 te = got_object_tree_get_last_entry(s->tree);
9465 if (match_tree_entry(te, &view->regex)) {
9466 view->search_next_done = TOG_SEARCH_HAVE_MORE;
9467 s->matched_entry = te;
9468 break;
9471 if (view->searching == TOG_SEARCH_FORWARD)
9472 te = got_tree_entry_get_next(s->tree, te);
9473 else
9474 te = got_tree_entry_get_prev(s->tree, te);
9477 if (s->matched_entry) {
9478 s->first_displayed_entry = s->matched_entry;
9479 s->selected = 0;
9482 return NULL;
9485 static const struct got_error *
9486 show_tree_view(struct tog_view *view)
9488 const struct got_error *err = NULL;
9489 struct tog_tree_view_state *s = &view->state.tree;
9490 char *parent_path;
9492 err = tree_entry_path(&parent_path, &s->parents, NULL);
9493 if (err)
9494 return err;
9496 err = draw_tree_entries(view, parent_path);
9497 free(parent_path);
9499 view_border(view);
9500 return err;
9503 static const struct got_error *
9504 tree_goto_line(struct tog_view *view, int nlines)
9506 const struct got_error *err = NULL;
9507 struct tog_tree_view_state *s = &view->state.tree;
9508 struct got_tree_entry **fte, **lte, **ste;
9509 int g, last, first = 1, i = 1;
9510 int root = s->tree == s->root;
9511 int off = root ? 1 : 2;
9513 g = view->gline;
9514 view->gline = 0;
9516 if (g == 0)
9517 g = 1;
9518 else if (g > got_object_tree_get_nentries(s->tree))
9519 g = got_object_tree_get_nentries(s->tree) + (root ? 0 : 1);
9521 fte = &s->first_displayed_entry;
9522 lte = &s->last_displayed_entry;
9523 ste = &s->selected_entry;
9525 if (*fte != NULL) {
9526 first = got_tree_entry_get_index(*fte);
9527 first += off; /* account for ".." */
9529 last = got_tree_entry_get_index(*lte);
9530 last += off;
9532 if (g >= first && g <= last && g - first < nlines) {
9533 s->selected = g - first;
9534 return NULL; /* gline is on the current page */
9537 if (*ste != NULL) {
9538 i = got_tree_entry_get_index(*ste);
9539 i += off;
9542 if (i < g) {
9543 err = tree_scroll_down(view, g - i);
9544 if (err)
9545 return err;
9546 if (got_tree_entry_get_index(*lte) >=
9547 got_object_tree_get_nentries(s->tree) - 1 &&
9548 first + s->selected < g &&
9549 s->selected < s->ndisplayed - 1) {
9550 first = got_tree_entry_get_index(*fte);
9551 first += off;
9552 s->selected = g - first;
9554 } else if (i > g)
9555 tree_scroll_up(s, i - g);
9557 if (g < nlines &&
9558 (*fte == NULL || (root && !got_tree_entry_get_index(*fte))))
9559 s->selected = g - 1;
9561 return NULL;
9564 static const struct got_error *
9565 input_tree_view(struct tog_view **new_view, struct tog_view *view, int ch)
9567 const struct got_error *err = NULL;
9568 struct tog_tree_view_state *s = &view->state.tree;
9569 struct got_tree_entry *te;
9570 int n, nscroll = view->nlines - 3;
9572 if (view->gline)
9573 return tree_goto_line(view, nscroll);
9575 switch (ch) {
9576 case '0':
9577 case '$':
9578 case KEY_RIGHT:
9579 case 'l':
9580 case KEY_LEFT:
9581 case 'h':
9582 horizontal_scroll_input(view, ch);
9583 break;
9584 case 'i':
9585 s->show_ids = !s->show_ids;
9586 view->count = 0;
9587 break;
9588 case 'L':
9589 view->count = 0;
9590 if (!s->selected_entry)
9591 break;
9592 err = view_request_new(new_view, view, TOG_VIEW_LOG);
9593 break;
9594 case 'R':
9595 view->count = 0;
9596 err = view_request_new(new_view, view, TOG_VIEW_REF);
9597 break;
9598 case 'g':
9599 case '=':
9600 case KEY_HOME:
9601 s->selected = 0;
9602 view->count = 0;
9603 if (s->tree == s->root)
9604 s->first_displayed_entry =
9605 got_object_tree_get_first_entry(s->tree);
9606 else
9607 s->first_displayed_entry = NULL;
9608 break;
9609 case 'G':
9610 case '*':
9611 case KEY_END: {
9612 int eos = view->nlines - 3;
9614 if (view->mode == TOG_VIEW_SPLIT_HRZN)
9615 --eos; /* border */
9616 s->selected = 0;
9617 view->count = 0;
9618 te = got_object_tree_get_last_entry(s->tree);
9619 for (n = 0; n < eos; n++) {
9620 if (te == NULL) {
9621 if (s->tree != s->root) {
9622 s->first_displayed_entry = NULL;
9623 n++;
9625 break;
9627 s->first_displayed_entry = te;
9628 te = got_tree_entry_get_prev(s->tree, te);
9630 if (n > 0)
9631 s->selected = n - 1;
9632 break;
9634 case 'k':
9635 case KEY_UP:
9636 case CTRL('p'):
9637 if (s->selected > 0) {
9638 s->selected--;
9639 break;
9641 tree_scroll_up(s, 1);
9642 if (s->selected_entry == NULL ||
9643 (s->tree == s->root && s->selected_entry ==
9644 got_object_tree_get_first_entry(s->tree)))
9645 view->count = 0;
9646 break;
9647 case CTRL('u'):
9648 case 'u':
9649 nscroll /= 2;
9650 /* FALL THROUGH */
9651 case KEY_PPAGE:
9652 case CTRL('b'):
9653 case 'b':
9654 if (s->tree == s->root) {
9655 if (got_object_tree_get_first_entry(s->tree) ==
9656 s->first_displayed_entry)
9657 s->selected -= MIN(s->selected, nscroll);
9658 } else {
9659 if (s->first_displayed_entry == NULL)
9660 s->selected -= MIN(s->selected, nscroll);
9662 tree_scroll_up(s, MAX(0, nscroll));
9663 if (s->selected_entry == NULL ||
9664 (s->tree == s->root && s->selected_entry ==
9665 got_object_tree_get_first_entry(s->tree)))
9666 view->count = 0;
9667 break;
9668 case 'j':
9669 case KEY_DOWN:
9670 case CTRL('n'):
9671 if (s->selected < s->ndisplayed - 1) {
9672 s->selected++;
9673 break;
9675 if (s->last_displayed_entry == NULL ||
9676 got_tree_entry_get_next(s->tree, s->last_displayed_entry)
9677 == NULL) {
9678 /* can't scroll any further */
9679 view->count = 0;
9680 break;
9682 tree_scroll_down(view, 1);
9683 break;
9684 case CTRL('d'):
9685 case 'd':
9686 nscroll /= 2;
9687 /* FALL THROUGH */
9688 case KEY_NPAGE:
9689 case CTRL('f'):
9690 case 'f':
9691 case ' ':
9692 if (s->last_displayed_entry == NULL ||
9693 got_tree_entry_get_next(s->tree, s->last_displayed_entry)
9694 == NULL) {
9695 /* can't scroll any further; move cursor down */
9696 if (s->selected < s->ndisplayed - 1)
9697 s->selected += MIN(nscroll,
9698 s->ndisplayed - s->selected - 1);
9699 else
9700 view->count = 0;
9701 break;
9703 tree_scroll_down(view, nscroll);
9704 break;
9705 case KEY_ENTER:
9706 case '\r':
9707 case KEY_BACKSPACE:
9708 if (s->selected_entry == NULL || ch == KEY_BACKSPACE) {
9709 struct tog_parent_tree *parent;
9710 /* user selected '..' */
9711 if (s->tree == s->root) {
9712 view->count = 0;
9713 break;
9715 parent = TAILQ_FIRST(&s->parents);
9716 TAILQ_REMOVE(&s->parents, parent,
9717 entry);
9718 got_object_tree_close(s->tree);
9719 s->tree = parent->tree;
9720 s->first_displayed_entry =
9721 parent->first_displayed_entry;
9722 s->selected_entry =
9723 parent->selected_entry;
9724 s->selected = parent->selected;
9725 if (s->selected > view->nlines - 3) {
9726 err = offset_selection_down(view);
9727 if (err)
9728 break;
9730 free(parent);
9731 } else if (S_ISDIR(got_tree_entry_get_mode(
9732 s->selected_entry))) {
9733 struct got_tree_object *subtree;
9734 view->count = 0;
9735 err = got_object_open_as_tree(&subtree, s->repo,
9736 got_tree_entry_get_id(s->selected_entry));
9737 if (err)
9738 break;
9739 err = tree_view_visit_subtree(s, subtree);
9740 if (err) {
9741 got_object_tree_close(subtree);
9742 break;
9744 } else if (S_ISREG(got_tree_entry_get_mode(s->selected_entry)))
9745 err = view_request_new(new_view, view, TOG_VIEW_BLAME);
9746 break;
9747 case KEY_RESIZE:
9748 if (view->nlines >= 4 && s->selected >= view->nlines - 3)
9749 s->selected = view->nlines - 4;
9750 view->count = 0;
9751 break;
9752 default:
9753 view->count = 0;
9754 break;
9757 return err;
9760 __dead static void
9761 usage_tree(void)
9763 endwin();
9764 fprintf(stderr,
9765 "usage: %s tree [-c commit] [-r repository-path] [path]\n",
9766 getprogname());
9767 exit(1);
9770 static const struct got_error *
9771 cmd_tree(int argc, char *argv[])
9773 const struct got_error *error;
9774 struct got_repository *repo = NULL;
9775 struct got_worktree *worktree = NULL;
9776 char *cwd = NULL, *repo_path = NULL, *in_repo_path = NULL;
9777 struct got_object_id *commit_id = NULL;
9778 struct got_commit_object *commit = NULL;
9779 const char *commit_id_arg = NULL;
9780 char *keyword_idstr = NULL, *label = NULL;
9781 struct got_reference *ref = NULL;
9782 const char *head_ref_name = NULL;
9783 int ch;
9784 struct tog_view *view;
9785 int *pack_fds = NULL;
9787 while ((ch = getopt(argc, argv, "c:r:")) != -1) {
9788 switch (ch) {
9789 case 'c':
9790 commit_id_arg = optarg;
9791 break;
9792 case 'r':
9793 repo_path = realpath(optarg, NULL);
9794 if (repo_path == NULL)
9795 return got_error_from_errno2("realpath",
9796 optarg);
9797 break;
9798 default:
9799 usage_tree();
9800 /* NOTREACHED */
9804 argc -= optind;
9805 argv += optind;
9807 if (argc > 1)
9808 usage_tree();
9810 error = got_repo_pack_fds_open(&pack_fds);
9811 if (error != NULL)
9812 goto done;
9814 if (repo_path == NULL) {
9815 cwd = getcwd(NULL, 0);
9816 if (cwd == NULL)
9817 return got_error_from_errno("getcwd");
9818 error = got_worktree_open(&worktree, cwd, NULL);
9819 if (error && error->code != GOT_ERR_NOT_WORKTREE)
9820 goto done;
9821 if (worktree)
9822 repo_path =
9823 strdup(got_worktree_get_repo_path(worktree));
9824 else
9825 repo_path = strdup(cwd);
9826 if (repo_path == NULL) {
9827 error = got_error_from_errno("strdup");
9828 goto done;
9832 error = got_repo_open(&repo, repo_path, NULL, pack_fds);
9833 if (error != NULL)
9834 goto done;
9836 error = get_in_repo_path_from_argv0(&in_repo_path, argc, argv,
9837 repo, worktree);
9838 if (error)
9839 goto done;
9841 init_curses();
9843 error = apply_unveil(got_repo_get_path(repo), NULL);
9844 if (error)
9845 goto done;
9847 error = tog_load_refs(repo, 0);
9848 if (error)
9849 goto done;
9851 if (commit_id_arg == NULL) {
9852 error = got_repo_match_object_id(&commit_id, &label,
9853 worktree ? got_worktree_get_head_ref_name(worktree) :
9854 GOT_REF_HEAD, GOT_OBJ_TYPE_COMMIT, &tog_refs, repo);
9855 if (error)
9856 goto done;
9857 head_ref_name = label;
9858 } else {
9859 error = got_keyword_to_idstr(&keyword_idstr, commit_id_arg,
9860 repo, worktree);
9861 if (error != NULL)
9862 goto done;
9863 if (keyword_idstr != NULL)
9864 commit_id_arg = keyword_idstr;
9866 error = got_ref_open(&ref, repo, commit_id_arg, 0);
9867 if (error == NULL)
9868 head_ref_name = got_ref_get_name(ref);
9869 else if (error->code != GOT_ERR_NOT_REF)
9870 goto done;
9871 error = got_repo_match_object_id(&commit_id, NULL,
9872 commit_id_arg, GOT_OBJ_TYPE_COMMIT, &tog_refs, repo);
9873 if (error)
9874 goto done;
9877 error = got_object_open_as_commit(&commit, repo, commit_id);
9878 if (error)
9879 goto done;
9881 view = view_open(0, 0, 0, 0, TOG_VIEW_TREE);
9882 if (view == NULL) {
9883 error = got_error_from_errno("view_open");
9884 goto done;
9886 error = open_tree_view(view, commit_id, head_ref_name, repo);
9887 if (error)
9888 goto done;
9889 if (!got_path_is_root_dir(in_repo_path)) {
9890 error = tree_view_walk_path(&view->state.tree, commit,
9891 in_repo_path);
9892 if (error)
9893 goto done;
9896 if (worktree) {
9897 error = set_tog_base_commit(repo, worktree);
9898 if (error != NULL)
9899 goto done;
9901 /* Release work tree lock. */
9902 got_worktree_close(worktree);
9903 worktree = NULL;
9906 error = view_loop(view);
9908 done:
9909 free(tog_base_commit.id);
9910 free(keyword_idstr);
9911 free(repo_path);
9912 free(cwd);
9913 free(commit_id);
9914 free(label);
9915 if (commit != NULL)
9916 got_object_commit_close(commit);
9917 if (ref)
9918 got_ref_close(ref);
9919 if (worktree != NULL)
9920 got_worktree_close(worktree);
9921 if (repo) {
9922 const struct got_error *close_err = got_repo_close(repo);
9923 if (error == NULL)
9924 error = close_err;
9926 if (pack_fds) {
9927 const struct got_error *pack_err =
9928 got_repo_pack_fds_close(pack_fds);
9929 if (error == NULL)
9930 error = pack_err;
9932 tog_free_refs();
9933 return error;
9936 static const struct got_error *
9937 ref_view_load_refs(struct tog_ref_view_state *s)
9939 struct got_reflist_entry *sre;
9940 struct tog_reflist_entry *re;
9942 s->nrefs = 0;
9943 TAILQ_FOREACH(sre, &tog_refs, entry) {
9944 if (strncmp(got_ref_get_name(sre->ref),
9945 "refs/got/", 9) == 0 &&
9946 strncmp(got_ref_get_name(sre->ref),
9947 "refs/got/backup/", 16) != 0)
9948 continue;
9950 re = malloc(sizeof(*re));
9951 if (re == NULL)
9952 return got_error_from_errno("malloc");
9954 re->ref = got_ref_dup(sre->ref);
9955 if (re->ref == NULL)
9956 return got_error_from_errno("got_ref_dup");
9957 re->idx = s->nrefs++;
9958 TAILQ_INSERT_TAIL(&s->refs, re, entry);
9961 s->first_displayed_entry = TAILQ_FIRST(&s->refs);
9962 return NULL;
9965 static void
9966 ref_view_free_refs(struct tog_ref_view_state *s)
9968 struct tog_reflist_entry *re;
9970 while (!TAILQ_EMPTY(&s->refs)) {
9971 re = TAILQ_FIRST(&s->refs);
9972 TAILQ_REMOVE(&s->refs, re, entry);
9973 got_ref_close(re->ref);
9974 free(re);
9978 static const struct got_error *
9979 open_ref_view(struct tog_view *view, struct got_repository *repo)
9981 const struct got_error *err = NULL;
9982 struct tog_ref_view_state *s = &view->state.ref;
9984 s->selected_entry = 0;
9985 s->repo = repo;
9987 TAILQ_INIT(&s->refs);
9988 STAILQ_INIT(&s->colors);
9990 err = ref_view_load_refs(s);
9991 if (err)
9992 goto done;
9994 if (has_colors() && getenv("TOG_COLORS") != NULL) {
9995 err = add_color(&s->colors, "^refs/heads/",
9996 TOG_COLOR_REFS_HEADS,
9997 get_color_value("TOG_COLOR_REFS_HEADS"));
9998 if (err)
9999 goto done;
10001 err = add_color(&s->colors, "^refs/tags/",
10002 TOG_COLOR_REFS_TAGS,
10003 get_color_value("TOG_COLOR_REFS_TAGS"));
10004 if (err)
10005 goto done;
10007 err = add_color(&s->colors, "^refs/remotes/",
10008 TOG_COLOR_REFS_REMOTES,
10009 get_color_value("TOG_COLOR_REFS_REMOTES"));
10010 if (err)
10011 goto done;
10013 err = add_color(&s->colors, "^refs/got/backup/",
10014 TOG_COLOR_REFS_BACKUP,
10015 get_color_value("TOG_COLOR_REFS_BACKUP"));
10016 if (err)
10017 goto done;
10020 view->show = show_ref_view;
10021 view->input = input_ref_view;
10022 view->close = close_ref_view;
10023 view->search_start = search_start_ref_view;
10024 view->search_next = search_next_ref_view;
10025 done:
10026 if (err) {
10027 if (view->close == NULL)
10028 close_ref_view(view);
10029 view_close(view);
10031 return err;
10034 static const struct got_error *
10035 close_ref_view(struct tog_view *view)
10037 struct tog_ref_view_state *s = &view->state.ref;
10039 ref_view_free_refs(s);
10040 free_colors(&s->colors);
10042 return NULL;
10045 static const struct got_error *
10046 resolve_reflist_entry(struct got_object_id **commit_id,
10047 struct tog_reflist_entry *re, struct got_repository *repo)
10049 const struct got_error *err = NULL;
10050 struct got_object_id *obj_id;
10051 struct got_tag_object *tag = NULL;
10052 int obj_type;
10054 *commit_id = NULL;
10056 err = got_ref_resolve(&obj_id, repo, re->ref);
10057 if (err)
10058 return err;
10060 err = got_object_get_type(&obj_type, repo, obj_id);
10061 if (err)
10062 goto done;
10064 switch (obj_type) {
10065 case GOT_OBJ_TYPE_COMMIT:
10066 break;
10067 case GOT_OBJ_TYPE_TAG:
10069 * Git allows nested tags that point to tags; keep peeling
10070 * till we reach the bottom, which is always a non-tag ref.
10072 do {
10073 if (tag != NULL)
10074 got_object_tag_close(tag);
10075 err = got_object_open_as_tag(&tag, repo, obj_id);
10076 if (err)
10077 goto done;
10078 free(obj_id);
10079 obj_id = got_object_id_dup(
10080 got_object_tag_get_object_id(tag));
10081 if (obj_id == NULL) {
10082 err = got_error_from_errno("got_object_id_dup");
10083 goto done;
10085 err = got_object_get_type(&obj_type, repo, obj_id);
10086 if (err)
10087 goto done;
10088 } while (obj_type == GOT_OBJ_TYPE_TAG);
10089 if (obj_type != GOT_OBJ_TYPE_COMMIT)
10090 err = got_error(GOT_ERR_OBJ_TYPE);
10091 break;
10092 default:
10093 err = got_error(GOT_ERR_OBJ_TYPE);
10094 break;
10097 done:
10098 if (tag)
10099 got_object_tag_close(tag);
10100 if (err == NULL)
10101 *commit_id = obj_id;
10102 else
10103 free(obj_id);
10104 return err;
10107 static const struct got_error *
10108 log_ref_entry(struct tog_view **new_view, int begin_y, int begin_x,
10109 struct tog_reflist_entry *re, struct got_repository *repo)
10111 struct tog_view *log_view;
10112 const struct got_error *err = NULL;
10113 struct got_object_id *commit_id = NULL;
10115 *new_view = NULL;
10117 err = resolve_reflist_entry(&commit_id, re, repo);
10118 if (err)
10119 return err;
10121 log_view = view_open(0, 0, begin_y, begin_x, TOG_VIEW_LOG);
10122 if (log_view == NULL) {
10123 err = got_error_from_errno("view_open");
10124 goto done;
10127 err = open_log_view(log_view, commit_id, repo,
10128 got_ref_get_name(re->ref), "", 0, NULL);
10129 done:
10130 if (err)
10131 view_close(log_view);
10132 else
10133 *new_view = log_view;
10134 free(commit_id);
10135 return err;
10138 static void
10139 ref_scroll_up(struct tog_ref_view_state *s, int maxscroll)
10141 struct tog_reflist_entry *re;
10142 int i = 0;
10144 if (s->first_displayed_entry == TAILQ_FIRST(&s->refs))
10145 return;
10147 re = TAILQ_PREV(s->first_displayed_entry, tog_reflist_head, entry);
10148 while (i++ < maxscroll) {
10149 if (re == NULL)
10150 break;
10151 s->first_displayed_entry = re;
10152 re = TAILQ_PREV(re, tog_reflist_head, entry);
10156 static const struct got_error *
10157 ref_scroll_down(struct tog_view *view, int maxscroll)
10159 struct tog_ref_view_state *s = &view->state.ref;
10160 struct tog_reflist_entry *next, *last;
10161 int n = 0;
10163 if (s->first_displayed_entry)
10164 next = TAILQ_NEXT(s->first_displayed_entry, entry);
10165 else
10166 next = TAILQ_FIRST(&s->refs);
10168 last = s->last_displayed_entry;
10169 while (next && n++ < maxscroll) {
10170 if (last) {
10171 s->last_displayed_entry = last;
10172 last = TAILQ_NEXT(last, entry);
10174 if (last || (view->mode == TOG_VIEW_SPLIT_HRZN)) {
10175 s->first_displayed_entry = next;
10176 next = TAILQ_NEXT(next, entry);
10180 return NULL;
10183 static const struct got_error *
10184 search_start_ref_view(struct tog_view *view)
10186 struct tog_ref_view_state *s = &view->state.ref;
10188 s->matched_entry = NULL;
10189 return NULL;
10192 static int
10193 match_reflist_entry(struct tog_reflist_entry *re, regex_t *regex)
10195 regmatch_t regmatch;
10197 return regexec(regex, got_ref_get_name(re->ref), 1, &regmatch,
10198 0) == 0;
10201 static const struct got_error *
10202 search_next_ref_view(struct tog_view *view)
10204 struct tog_ref_view_state *s = &view->state.ref;
10205 struct tog_reflist_entry *re = NULL;
10207 if (!view->searching) {
10208 view->search_next_done = TOG_SEARCH_HAVE_MORE;
10209 return NULL;
10212 if (s->matched_entry) {
10213 if (view->searching == TOG_SEARCH_FORWARD) {
10214 if (s->selected_entry)
10215 re = TAILQ_NEXT(s->selected_entry, entry);
10216 else
10217 re = TAILQ_PREV(s->selected_entry,
10218 tog_reflist_head, entry);
10219 } else {
10220 if (s->selected_entry == NULL)
10221 re = TAILQ_LAST(&s->refs, tog_reflist_head);
10222 else
10223 re = TAILQ_PREV(s->selected_entry,
10224 tog_reflist_head, entry);
10226 } else {
10227 if (s->selected_entry)
10228 re = s->selected_entry;
10229 else if (view->searching == TOG_SEARCH_FORWARD)
10230 re = TAILQ_FIRST(&s->refs);
10231 else
10232 re = TAILQ_LAST(&s->refs, tog_reflist_head);
10235 while (1) {
10236 if (re == NULL) {
10237 if (s->matched_entry == NULL) {
10238 view->search_next_done = TOG_SEARCH_HAVE_MORE;
10239 return NULL;
10241 if (view->searching == TOG_SEARCH_FORWARD)
10242 re = TAILQ_FIRST(&s->refs);
10243 else
10244 re = TAILQ_LAST(&s->refs, tog_reflist_head);
10247 if (match_reflist_entry(re, &view->regex)) {
10248 view->search_next_done = TOG_SEARCH_HAVE_MORE;
10249 s->matched_entry = re;
10250 break;
10253 if (view->searching == TOG_SEARCH_FORWARD)
10254 re = TAILQ_NEXT(re, entry);
10255 else
10256 re = TAILQ_PREV(re, tog_reflist_head, entry);
10259 if (s->matched_entry) {
10260 s->first_displayed_entry = s->matched_entry;
10261 s->selected = 0;
10264 return NULL;
10267 static const struct got_error *
10268 show_ref_view(struct tog_view *view)
10270 const struct got_error *err = NULL;
10271 struct tog_ref_view_state *s = &view->state.ref;
10272 struct tog_reflist_entry *re;
10273 char *line = NULL;
10274 wchar_t *wline;
10275 struct tog_color *tc;
10276 int width, n, scrollx;
10277 int limit = view->nlines;
10279 werase(view->window);
10281 s->ndisplayed = 0;
10282 if (view_is_hsplit_top(view))
10283 --limit; /* border */
10285 if (limit == 0)
10286 return NULL;
10288 re = s->first_displayed_entry;
10290 if (asprintf(&line, "references [%d/%d]", re->idx + s->selected + 1,
10291 s->nrefs) == -1)
10292 return got_error_from_errno("asprintf");
10294 err = format_line(&wline, &width, NULL, line, 0, view->ncols, 0, 0);
10295 if (err) {
10296 free(line);
10297 return err;
10299 if (view_needs_focus_indication(view))
10300 wstandout(view->window);
10301 waddwstr(view->window, wline);
10302 while (width++ < view->ncols)
10303 waddch(view->window, ' ');
10304 if (view_needs_focus_indication(view))
10305 wstandend(view->window);
10306 free(wline);
10307 wline = NULL;
10308 free(line);
10309 line = NULL;
10310 if (--limit <= 0)
10311 return NULL;
10313 n = 0;
10314 view->maxx = 0;
10315 while (re && limit > 0) {
10316 char *line = NULL;
10317 char ymd[13]; /* YYYY-MM-DD + " " + NUL */
10319 if (s->show_date) {
10320 struct got_commit_object *ci;
10321 struct got_tag_object *tag;
10322 struct got_object_id *id;
10323 struct tm tm;
10324 time_t t;
10326 err = got_ref_resolve(&id, s->repo, re->ref);
10327 if (err)
10328 return err;
10329 err = got_object_open_as_tag(&tag, s->repo, id);
10330 if (err) {
10331 if (err->code != GOT_ERR_OBJ_TYPE) {
10332 free(id);
10333 return err;
10335 err = got_object_open_as_commit(&ci, s->repo,
10336 id);
10337 if (err) {
10338 free(id);
10339 return err;
10341 t = got_object_commit_get_committer_time(ci);
10342 got_object_commit_close(ci);
10343 } else {
10344 t = got_object_tag_get_tagger_time(tag);
10345 got_object_tag_close(tag);
10347 free(id);
10348 if (gmtime_r(&t, &tm) == NULL)
10349 return got_error_from_errno("gmtime_r");
10350 if (strftime(ymd, sizeof(ymd), "%F ", &tm) == 0)
10351 return got_error(GOT_ERR_NO_SPACE);
10353 if (got_ref_is_symbolic(re->ref)) {
10354 if (asprintf(&line, "%s%s -> %s", s->show_date ?
10355 ymd : "", got_ref_get_name(re->ref),
10356 got_ref_get_symref_target(re->ref)) == -1)
10357 return got_error_from_errno("asprintf");
10358 } else if (s->show_ids) {
10359 struct got_object_id *id;
10360 char *id_str;
10361 err = got_ref_resolve(&id, s->repo, re->ref);
10362 if (err)
10363 return err;
10364 err = got_object_id_str(&id_str, id);
10365 if (err) {
10366 free(id);
10367 return err;
10369 if (asprintf(&line, "%s%s: %s", s->show_date ? ymd : "",
10370 got_ref_get_name(re->ref), id_str) == -1) {
10371 err = got_error_from_errno("asprintf");
10372 free(id);
10373 free(id_str);
10374 return err;
10376 free(id);
10377 free(id_str);
10378 } else if (asprintf(&line, "%s%s", s->show_date ? ymd : "",
10379 got_ref_get_name(re->ref)) == -1)
10380 return got_error_from_errno("asprintf");
10382 /* use full line width to determine view->maxx */
10383 err = format_line(&wline, &width, NULL, line, 0, INT_MAX, 0, 0);
10384 if (err) {
10385 free(line);
10386 return err;
10388 view->maxx = MAX(view->maxx, width);
10389 free(wline);
10390 wline = NULL;
10392 err = format_line(&wline, &width, &scrollx, line, view->x,
10393 view->ncols, 0, 0);
10394 if (err) {
10395 free(line);
10396 return err;
10398 if (n == s->selected) {
10399 if (view->focussed)
10400 wstandout(view->window);
10401 s->selected_entry = re;
10403 tc = match_color(&s->colors, got_ref_get_name(re->ref));
10404 if (tc)
10405 wattr_on(view->window,
10406 COLOR_PAIR(tc->colorpair), NULL);
10407 waddwstr(view->window, &wline[scrollx]);
10408 if (tc)
10409 wattr_off(view->window,
10410 COLOR_PAIR(tc->colorpair), NULL);
10411 if (width < view->ncols)
10412 waddch(view->window, '\n');
10413 if (n == s->selected && view->focussed)
10414 wstandend(view->window);
10415 free(line);
10416 free(wline);
10417 wline = NULL;
10418 n++;
10419 s->ndisplayed++;
10420 s->last_displayed_entry = re;
10422 limit--;
10423 re = TAILQ_NEXT(re, entry);
10426 view_border(view);
10427 return err;
10430 static const struct got_error *
10431 browse_ref_tree(struct tog_view **new_view, int begin_y, int begin_x,
10432 struct tog_reflist_entry *re, struct got_repository *repo)
10434 const struct got_error *err = NULL;
10435 struct got_object_id *commit_id = NULL;
10436 struct tog_view *tree_view;
10438 *new_view = NULL;
10440 err = resolve_reflist_entry(&commit_id, re, repo);
10441 if (err)
10442 return err;
10444 tree_view = view_open(0, 0, begin_y, begin_x, TOG_VIEW_TREE);
10445 if (tree_view == NULL) {
10446 err = got_error_from_errno("view_open");
10447 goto done;
10450 err = open_tree_view(tree_view, commit_id,
10451 got_ref_get_name(re->ref), repo);
10452 if (err)
10453 goto done;
10455 *new_view = tree_view;
10456 done:
10457 free(commit_id);
10458 return err;
10461 static const struct got_error *
10462 ref_goto_line(struct tog_view *view, int nlines)
10464 const struct got_error *err = NULL;
10465 struct tog_ref_view_state *s = &view->state.ref;
10466 int g, idx = s->selected_entry->idx;
10468 g = view->gline;
10469 view->gline = 0;
10471 if (g == 0)
10472 g = 1;
10473 else if (g > s->nrefs)
10474 g = s->nrefs;
10476 if (g >= s->first_displayed_entry->idx + 1 &&
10477 g <= s->last_displayed_entry->idx + 1 &&
10478 g - s->first_displayed_entry->idx - 1 < nlines) {
10479 s->selected = g - s->first_displayed_entry->idx - 1;
10480 return NULL;
10483 if (idx + 1 < g) {
10484 err = ref_scroll_down(view, g - idx - 1);
10485 if (err)
10486 return err;
10487 if (TAILQ_NEXT(s->last_displayed_entry, entry) == NULL &&
10488 s->first_displayed_entry->idx + s->selected < g &&
10489 s->selected < s->ndisplayed - 1)
10490 s->selected = g - s->first_displayed_entry->idx - 1;
10491 } else if (idx + 1 > g)
10492 ref_scroll_up(s, idx - g + 1);
10494 if (g < nlines && s->first_displayed_entry->idx == 0)
10495 s->selected = g - 1;
10497 return NULL;
10501 static const struct got_error *
10502 input_ref_view(struct tog_view **new_view, struct tog_view *view, int ch)
10504 const struct got_error *err = NULL;
10505 struct tog_ref_view_state *s = &view->state.ref;
10506 struct tog_reflist_entry *re;
10507 int n, nscroll = view->nlines - 1;
10509 if (view->gline)
10510 return ref_goto_line(view, nscroll);
10512 switch (ch) {
10513 case '0':
10514 case '$':
10515 case KEY_RIGHT:
10516 case 'l':
10517 case KEY_LEFT:
10518 case 'h':
10519 horizontal_scroll_input(view, ch);
10520 break;
10521 case 'i':
10522 s->show_ids = !s->show_ids;
10523 view->count = 0;
10524 break;
10525 case 'm':
10526 s->show_date = !s->show_date;
10527 view->count = 0;
10528 break;
10529 case 'o':
10530 s->sort_by_date = !s->sort_by_date;
10531 view->action = s->sort_by_date ? "sort by date" : "sort by name";
10532 view->count = 0;
10533 err = got_reflist_sort(&tog_refs, s->sort_by_date ?
10534 got_ref_cmp_by_commit_timestamp_descending :
10535 tog_ref_cmp_by_name, s->repo);
10536 if (err)
10537 break;
10538 got_reflist_object_id_map_free(tog_refs_idmap);
10539 err = got_reflist_object_id_map_create(&tog_refs_idmap,
10540 &tog_refs, s->repo);
10541 if (err)
10542 break;
10543 ref_view_free_refs(s);
10544 err = ref_view_load_refs(s);
10545 break;
10546 case KEY_ENTER:
10547 case '\r':
10548 view->count = 0;
10549 if (!s->selected_entry)
10550 break;
10551 err = view_request_new(new_view, view, TOG_VIEW_LOG);
10552 break;
10553 case 'T':
10554 view->count = 0;
10555 if (!s->selected_entry)
10556 break;
10557 err = view_request_new(new_view, view, TOG_VIEW_TREE);
10558 break;
10559 case 'g':
10560 case '=':
10561 case KEY_HOME:
10562 s->selected = 0;
10563 view->count = 0;
10564 s->first_displayed_entry = TAILQ_FIRST(&s->refs);
10565 break;
10566 case 'G':
10567 case '*':
10568 case KEY_END: {
10569 int eos = view->nlines - 1;
10571 if (view->mode == TOG_VIEW_SPLIT_HRZN)
10572 --eos; /* border */
10573 s->selected = 0;
10574 view->count = 0;
10575 re = TAILQ_LAST(&s->refs, tog_reflist_head);
10576 for (n = 0; n < eos; n++) {
10577 if (re == NULL)
10578 break;
10579 s->first_displayed_entry = re;
10580 re = TAILQ_PREV(re, tog_reflist_head, entry);
10582 if (n > 0)
10583 s->selected = n - 1;
10584 break;
10586 case 'k':
10587 case KEY_UP:
10588 case CTRL('p'):
10589 if (s->selected > 0) {
10590 s->selected--;
10591 break;
10593 ref_scroll_up(s, 1);
10594 if (s->selected_entry == TAILQ_FIRST(&s->refs))
10595 view->count = 0;
10596 break;
10597 case CTRL('u'):
10598 case 'u':
10599 nscroll /= 2;
10600 /* FALL THROUGH */
10601 case KEY_PPAGE:
10602 case CTRL('b'):
10603 case 'b':
10604 if (s->first_displayed_entry == TAILQ_FIRST(&s->refs))
10605 s->selected -= MIN(nscroll, s->selected);
10606 ref_scroll_up(s, MAX(0, nscroll));
10607 if (s->selected_entry == TAILQ_FIRST(&s->refs))
10608 view->count = 0;
10609 break;
10610 case 'j':
10611 case KEY_DOWN:
10612 case CTRL('n'):
10613 if (s->selected < s->ndisplayed - 1) {
10614 s->selected++;
10615 break;
10617 if (TAILQ_NEXT(s->last_displayed_entry, entry) == NULL) {
10618 /* can't scroll any further */
10619 view->count = 0;
10620 break;
10622 ref_scroll_down(view, 1);
10623 break;
10624 case CTRL('d'):
10625 case 'd':
10626 nscroll /= 2;
10627 /* FALL THROUGH */
10628 case KEY_NPAGE:
10629 case CTRL('f'):
10630 case 'f':
10631 case ' ':
10632 if (TAILQ_NEXT(s->last_displayed_entry, entry) == NULL) {
10633 /* can't scroll any further; move cursor down */
10634 if (s->selected < s->ndisplayed - 1)
10635 s->selected += MIN(nscroll,
10636 s->ndisplayed - s->selected - 1);
10637 if (view->count > 1 && s->selected < s->ndisplayed - 1)
10638 s->selected += s->ndisplayed - s->selected - 1;
10639 view->count = 0;
10640 break;
10642 ref_scroll_down(view, nscroll);
10643 break;
10644 case CTRL('l'):
10645 view->count = 0;
10646 tog_free_refs();
10647 err = tog_load_refs(s->repo, s->sort_by_date);
10648 if (err)
10649 break;
10650 ref_view_free_refs(s);
10651 err = ref_view_load_refs(s);
10652 break;
10653 case KEY_RESIZE:
10654 if (view->nlines >= 2 && s->selected >= view->nlines - 1)
10655 s->selected = view->nlines - 2;
10656 break;
10657 default:
10658 view->count = 0;
10659 break;
10662 return err;
10665 __dead static void
10666 usage_ref(void)
10668 endwin();
10669 fprintf(stderr, "usage: %s ref [-r repository-path]\n",
10670 getprogname());
10671 exit(1);
10674 static const struct got_error *
10675 cmd_ref(int argc, char *argv[])
10677 const struct got_error *error;
10678 struct got_repository *repo = NULL;
10679 struct got_worktree *worktree = NULL;
10680 char *cwd = NULL, *repo_path = NULL;
10681 int ch;
10682 struct tog_view *view;
10683 int *pack_fds = NULL;
10685 while ((ch = getopt(argc, argv, "r:")) != -1) {
10686 switch (ch) {
10687 case 'r':
10688 repo_path = realpath(optarg, NULL);
10689 if (repo_path == NULL)
10690 return got_error_from_errno2("realpath",
10691 optarg);
10692 break;
10693 default:
10694 usage_ref();
10695 /* NOTREACHED */
10699 argc -= optind;
10700 argv += optind;
10702 if (argc > 1)
10703 usage_ref();
10705 error = got_repo_pack_fds_open(&pack_fds);
10706 if (error != NULL)
10707 goto done;
10709 if (repo_path == NULL) {
10710 cwd = getcwd(NULL, 0);
10711 if (cwd == NULL)
10712 return got_error_from_errno("getcwd");
10713 error = got_worktree_open(&worktree, cwd, NULL);
10714 if (error && error->code != GOT_ERR_NOT_WORKTREE)
10715 goto done;
10716 if (worktree)
10717 repo_path =
10718 strdup(got_worktree_get_repo_path(worktree));
10719 else
10720 repo_path = strdup(cwd);
10721 if (repo_path == NULL) {
10722 error = got_error_from_errno("strdup");
10723 goto done;
10727 error = got_repo_open(&repo, repo_path, NULL, pack_fds);
10728 if (error != NULL)
10729 goto done;
10731 init_curses();
10733 error = apply_unveil(got_repo_get_path(repo), NULL);
10734 if (error)
10735 goto done;
10737 error = tog_load_refs(repo, 0);
10738 if (error)
10739 goto done;
10741 view = view_open(0, 0, 0, 0, TOG_VIEW_REF);
10742 if (view == NULL) {
10743 error = got_error_from_errno("view_open");
10744 goto done;
10747 error = open_ref_view(view, repo);
10748 if (error)
10749 goto done;
10751 if (worktree) {
10752 error = set_tog_base_commit(repo, worktree);
10753 if (error != NULL)
10754 goto done;
10756 /* Release work tree lock. */
10757 got_worktree_close(worktree);
10758 worktree = NULL;
10761 error = view_loop(view);
10763 done:
10764 free(tog_base_commit.id);
10765 free(repo_path);
10766 free(cwd);
10767 if (worktree != NULL)
10768 got_worktree_close(worktree);
10769 if (repo) {
10770 const struct got_error *close_err;
10772 close_err = got_repo_close(repo);
10773 if (close_err && error == NULL)
10774 error = close_err;
10776 if (pack_fds) {
10777 const struct got_error *pack_err;
10779 pack_err = got_repo_pack_fds_close(pack_fds);
10780 if (pack_err && error == NULL)
10781 error = pack_err;
10783 tog_free_refs();
10784 return error;
10787 static const struct got_error*
10788 win_draw_center(WINDOW *win, size_t y, size_t x, size_t maxx, int focus,
10789 const char *str)
10791 size_t len;
10793 if (win == NULL)
10794 win = stdscr;
10796 len = strlen(str);
10797 x = x ? x : maxx > len ? (maxx - len) / 2 : 0;
10799 if (focus)
10800 wstandout(win);
10801 if (mvwprintw(win, y, x, "%s", str) == ERR)
10802 return got_error_msg(GOT_ERR_RANGE, "mvwprintw");
10803 if (focus)
10804 wstandend(win);
10806 return NULL;
10809 static const struct got_error *
10810 add_line_offset(off_t **line_offsets, size_t *nlines, off_t off)
10812 off_t *p;
10814 p = reallocarray(*line_offsets, *nlines + 1, sizeof(off_t));
10815 if (p == NULL) {
10816 free(*line_offsets);
10817 *line_offsets = NULL;
10818 return got_error_from_errno("reallocarray");
10821 *line_offsets = p;
10822 (*line_offsets)[*nlines] = off;
10823 ++(*nlines);
10824 return NULL;
10827 static const struct got_error *
10828 max_key_str(int *ret, const struct tog_key_map *km, size_t n)
10830 *ret = 0;
10832 for (;n > 0; --n, ++km) {
10833 char *t0, *t, *k;
10834 size_t len = 1;
10836 if (km->keys == NULL)
10837 continue;
10839 t = t0 = strdup(km->keys);
10840 if (t0 == NULL)
10841 return got_error_from_errno("strdup");
10843 len += strlen(t);
10844 while ((k = strsep(&t, " ")) != NULL)
10845 len += strlen(k) > 1 ? 2 : 0;
10846 free(t0);
10847 *ret = MAX(*ret, len);
10850 return NULL;
10854 * Write keymap section headers, keys, and key info in km to f.
10855 * Save line offset to *off. If terminal has UTF8 encoding enabled,
10856 * wrap control and symbolic keys in guillemets, else use <>.
10858 static const struct got_error *
10859 format_help_line(off_t *off, FILE *f, const struct tog_key_map *km, int width)
10861 int n, len = width;
10863 if (km->keys) {
10864 static const char *u8_glyph[] = {
10865 "\xe2\x80\xb9", /* U+2039 (utf8 <) */
10866 "\xe2\x80\xba" /* U+203A (utf8 >) */
10868 char *t0, *t, *k;
10869 int cs, s, first = 1;
10871 cs = got_locale_is_utf8();
10873 t = t0 = strdup(km->keys);
10874 if (t0 == NULL)
10875 return got_error_from_errno("strdup");
10877 len = strlen(km->keys);
10878 while ((k = strsep(&t, " ")) != NULL) {
10879 s = strlen(k) > 1; /* control or symbolic key */
10880 n = fprintf(f, "%s%s%s%s%s", first ? " " : "",
10881 cs && s ? u8_glyph[0] : s ? "<" : "", k,
10882 cs && s ? u8_glyph[1] : s ? ">" : "", t ? " " : "");
10883 if (n < 0) {
10884 free(t0);
10885 return got_error_from_errno("fprintf");
10887 first = 0;
10888 len += s ? 2 : 0;
10889 *off += n;
10891 free(t0);
10893 n = fprintf(f, "%*s%s\n", width - len, width - len ? " " : "", km->info);
10894 if (n < 0)
10895 return got_error_from_errno("fprintf");
10896 *off += n;
10898 return NULL;
10901 static const struct got_error *
10902 format_help(struct tog_help_view_state *s)
10904 const struct got_error *err = NULL;
10905 off_t off = 0;
10906 int i, max, n, show = s->all;
10907 static const struct tog_key_map km[] = {
10908 #define KEYMAP_(info, type) { NULL, (info), type }
10909 #define KEY_(keys, info) { (keys), (info), TOG_KEYMAP_KEYS }
10910 GENERATE_HELP
10911 #undef KEYMAP_
10912 #undef KEY_
10915 err = add_line_offset(&s->line_offsets, &s->nlines, 0);
10916 if (err)
10917 return err;
10919 n = nitems(km);
10920 err = max_key_str(&max, km, n);
10921 if (err)
10922 return err;
10924 for (i = 0; i < n; ++i) {
10925 if (km[i].keys == NULL) {
10926 show = s->all;
10927 if (km[i].type == TOG_KEYMAP_GLOBAL ||
10928 km[i].type == s->type || s->all)
10929 show = 1;
10931 if (show) {
10932 err = format_help_line(&off, s->f, &km[i], max);
10933 if (err)
10934 return err;
10935 err = add_line_offset(&s->line_offsets, &s->nlines, off);
10936 if (err)
10937 return err;
10940 fputc('\n', s->f);
10941 ++off;
10942 err = add_line_offset(&s->line_offsets, &s->nlines, off);
10943 return err;
10946 static const struct got_error *
10947 create_help(struct tog_help_view_state *s)
10949 FILE *f;
10950 const struct got_error *err;
10952 free(s->line_offsets);
10953 s->line_offsets = NULL;
10954 s->nlines = 0;
10956 f = got_opentemp();
10957 if (f == NULL)
10958 return got_error_from_errno("got_opentemp");
10959 s->f = f;
10961 err = format_help(s);
10962 if (err)
10963 return err;
10965 if (s->f && fflush(s->f) != 0)
10966 return got_error_from_errno("fflush");
10968 return NULL;
10971 static const struct got_error *
10972 search_start_help_view(struct tog_view *view)
10974 view->state.help.matched_line = 0;
10975 return NULL;
10978 static void
10979 search_setup_help_view(struct tog_view *view, FILE **f, off_t **line_offsets,
10980 size_t *nlines, int **first, int **last, int **match, int **selected)
10982 struct tog_help_view_state *s = &view->state.help;
10984 *f = s->f;
10985 *nlines = s->nlines;
10986 *line_offsets = s->line_offsets;
10987 *match = &s->matched_line;
10988 *first = &s->first_displayed_line;
10989 *last = &s->last_displayed_line;
10990 *selected = &s->selected_line;
10993 static const struct got_error *
10994 show_help_view(struct tog_view *view)
10996 struct tog_help_view_state *s = &view->state.help;
10997 const struct got_error *err;
10998 regmatch_t *regmatch = &view->regmatch;
10999 wchar_t *wline;
11000 char *line;
11001 ssize_t linelen;
11002 size_t linesz = 0;
11003 int width, nprinted = 0, rc = 0;
11004 int eos = view->nlines;
11006 if (view_is_hsplit_top(view))
11007 --eos; /* account for border */
11009 s->lineno = 0;
11010 rewind(s->f);
11011 werase(view->window);
11013 if (view->gline > s->nlines - 1)
11014 view->gline = s->nlines - 1;
11016 err = win_draw_center(view->window, 0, 0, view->ncols,
11017 view_needs_focus_indication(view),
11018 "tog help (press q to return to tog)");
11019 if (err)
11020 return err;
11021 if (eos <= 1)
11022 return NULL;
11023 waddstr(view->window, "\n\n");
11024 eos -= 2;
11026 s->eof = 0;
11027 view->maxx = 0;
11028 line = NULL;
11029 while (eos > 0 && nprinted < eos) {
11030 attr_t attr = 0;
11032 linelen = getline(&line, &linesz, s->f);
11033 if (linelen == -1) {
11034 if (!feof(s->f)) {
11035 free(line);
11036 return got_ferror(s->f, GOT_ERR_IO);
11038 s->eof = 1;
11039 break;
11041 if (++s->lineno < s->first_displayed_line)
11042 continue;
11043 if (view->gline && !gotoline(view, &s->lineno, &nprinted))
11044 continue;
11045 if (s->lineno == view->hiline)
11046 attr = A_STANDOUT;
11048 err = format_line(&wline, &width, NULL, line, 0, INT_MAX, 0,
11049 view->x ? 1 : 0);
11050 if (err) {
11051 free(line);
11052 return err;
11054 view->maxx = MAX(view->maxx, width);
11055 free(wline);
11056 wline = NULL;
11058 if (attr)
11059 wattron(view->window, attr);
11060 if (s->first_displayed_line + nprinted == s->matched_line &&
11061 regmatch->rm_so >= 0 && regmatch->rm_so < regmatch->rm_eo) {
11062 err = add_matched_line(&width, line, view->ncols - 1, 0,
11063 view->window, view->x, regmatch);
11064 if (err) {
11065 free(line);
11066 return err;
11068 } else {
11069 int skip;
11071 err = format_line(&wline, &width, &skip, line,
11072 view->x, view->ncols, 0, view->x ? 1 : 0);
11073 if (err) {
11074 free(line);
11075 return err;
11077 waddwstr(view->window, &wline[skip]);
11078 free(wline);
11079 wline = NULL;
11081 if (s->lineno == view->hiline) {
11082 while (width++ < view->ncols)
11083 waddch(view->window, ' ');
11084 } else {
11085 if (width < view->ncols)
11086 waddch(view->window, '\n');
11088 if (attr)
11089 wattroff(view->window, attr);
11090 if (++nprinted == 1)
11091 s->first_displayed_line = s->lineno;
11093 free(line);
11094 if (nprinted > 0)
11095 s->last_displayed_line = s->first_displayed_line + nprinted - 1;
11096 else
11097 s->last_displayed_line = s->first_displayed_line;
11099 view_border(view);
11101 if (s->eof) {
11102 rc = waddnstr(view->window,
11103 "See the tog(1) manual page for full documentation",
11104 view->ncols - 1);
11105 if (rc == ERR)
11106 return got_error_msg(GOT_ERR_RANGE, "waddnstr");
11107 } else {
11108 wmove(view->window, view->nlines - 1, 0);
11109 wclrtoeol(view->window);
11110 wstandout(view->window);
11111 rc = waddnstr(view->window, "scroll down for more...",
11112 view->ncols - 1);
11113 if (rc == ERR)
11114 return got_error_msg(GOT_ERR_RANGE, "waddnstr");
11115 if (getcurx(view->window) < view->ncols - 6) {
11116 rc = wprintw(view->window, "[%.0f%%]",
11117 100.00 * s->last_displayed_line / s->nlines);
11118 if (rc == ERR)
11119 return got_error_msg(GOT_ERR_IO, "wprintw");
11121 wstandend(view->window);
11124 return NULL;
11127 static const struct got_error *
11128 input_help_view(struct tog_view **new_view, struct tog_view *view, int ch)
11130 struct tog_help_view_state *s = &view->state.help;
11131 const struct got_error *err = NULL;
11132 char *line = NULL;
11133 ssize_t linelen;
11134 size_t linesz = 0;
11135 int eos, nscroll;
11137 eos = nscroll = view->nlines;
11138 if (view_is_hsplit_top(view))
11139 --eos; /* border */
11141 s->lineno = s->first_displayed_line - 1 + s->selected_line;
11143 switch (ch) {
11144 case '0':
11145 case '$':
11146 case KEY_RIGHT:
11147 case 'l':
11148 case KEY_LEFT:
11149 case 'h':
11150 horizontal_scroll_input(view, ch);
11151 break;
11152 case 'g':
11153 case KEY_HOME:
11154 s->first_displayed_line = 1;
11155 view->count = 0;
11156 break;
11157 case 'G':
11158 case KEY_END:
11159 view->count = 0;
11160 if (s->eof)
11161 break;
11162 s->first_displayed_line = (s->nlines - eos) + 3;
11163 s->eof = 1;
11164 break;
11165 case 'k':
11166 case KEY_UP:
11167 if (s->first_displayed_line > 1)
11168 --s->first_displayed_line;
11169 else
11170 view->count = 0;
11171 break;
11172 case CTRL('u'):
11173 case 'u':
11174 nscroll /= 2;
11175 /* FALL THROUGH */
11176 case KEY_PPAGE:
11177 case CTRL('b'):
11178 case 'b':
11179 if (s->first_displayed_line == 1) {
11180 view->count = 0;
11181 break;
11183 while (--nscroll > 0 && s->first_displayed_line > 1)
11184 s->first_displayed_line--;
11185 break;
11186 case 'j':
11187 case KEY_DOWN:
11188 case CTRL('n'):
11189 if (!s->eof)
11190 ++s->first_displayed_line;
11191 else
11192 view->count = 0;
11193 break;
11194 case CTRL('d'):
11195 case 'd':
11196 nscroll /= 2;
11197 /* FALL THROUGH */
11198 case KEY_NPAGE:
11199 case CTRL('f'):
11200 case 'f':
11201 case ' ':
11202 if (s->eof) {
11203 view->count = 0;
11204 break;
11206 while (!s->eof && --nscroll > 0) {
11207 linelen = getline(&line, &linesz, s->f);
11208 s->first_displayed_line++;
11209 if (linelen == -1) {
11210 if (feof(s->f))
11211 s->eof = 1;
11212 else
11213 err = got_ferror(s->f, GOT_ERR_IO);
11214 break;
11217 free(line);
11218 break;
11219 default:
11220 view->count = 0;
11221 break;
11224 return err;
11227 static const struct got_error *
11228 close_help_view(struct tog_view *view)
11230 struct tog_help_view_state *s = &view->state.help;
11232 free(s->line_offsets);
11233 s->line_offsets = NULL;
11234 if (fclose(s->f) == EOF)
11235 return got_error_from_errno("fclose");
11237 return NULL;
11240 static const struct got_error *
11241 reset_help_view(struct tog_view *view)
11243 struct tog_help_view_state *s = &view->state.help;
11246 if (s->f && fclose(s->f) == EOF)
11247 return got_error_from_errno("fclose");
11249 wclear(view->window);
11250 view->count = 0;
11251 view->x = 0;
11252 s->all = !s->all;
11253 s->first_displayed_line = 1;
11254 s->last_displayed_line = view->nlines;
11255 s->matched_line = 0;
11257 return create_help(s);
11260 static const struct got_error *
11261 open_help_view(struct tog_view *view, struct tog_view *parent)
11263 const struct got_error *err = NULL;
11264 struct tog_help_view_state *s = &view->state.help;
11266 s->type = (enum tog_keymap_type)parent->type;
11267 s->first_displayed_line = 1;
11268 s->last_displayed_line = view->nlines;
11269 s->selected_line = 1;
11271 view->show = show_help_view;
11272 view->input = input_help_view;
11273 view->reset = reset_help_view;
11274 view->close = close_help_view;
11275 view->search_start = search_start_help_view;
11276 view->search_setup = search_setup_help_view;
11277 view->search_next = search_next_view_match;
11279 err = create_help(s);
11280 return err;
11283 static const struct got_error *
11284 view_dispatch_request(struct tog_view **new_view, struct tog_view *view,
11285 enum tog_view_type request, int y, int x)
11287 const struct got_error *err = NULL;
11289 *new_view = NULL;
11291 switch (request) {
11292 case TOG_VIEW_DIFF:
11293 if (view->type == TOG_VIEW_LOG) {
11294 struct tog_log_view_state *s = &view->state.log;
11296 err = open_diff_view_for_commit(new_view, y, x,
11297 s->selected_entry, view, s->repo);
11298 } else
11299 return got_error_msg(GOT_ERR_NOT_IMPL,
11300 "parent/child view pair not supported");
11301 break;
11302 case TOG_VIEW_BLAME:
11303 if (view->type == TOG_VIEW_TREE) {
11304 struct tog_tree_view_state *s = &view->state.tree;
11306 err = blame_tree_entry(new_view, y, x,
11307 s->selected_entry, &s->parents, s->commit_id,
11308 s->repo);
11309 } else
11310 return got_error_msg(GOT_ERR_NOT_IMPL,
11311 "parent/child view pair not supported");
11312 break;
11313 case TOG_VIEW_LOG:
11314 tog_base_commit.idx = -1;
11315 if (view->type == TOG_VIEW_BLAME)
11316 err = log_annotated_line(new_view, y, x,
11317 view->state.blame.repo, view->state.blame.id_to_log);
11318 else if (view->type == TOG_VIEW_TREE)
11319 err = log_selected_tree_entry(new_view, y, x,
11320 &view->state.tree);
11321 else if (view->type == TOG_VIEW_REF)
11322 err = log_ref_entry(new_view, y, x,
11323 view->state.ref.selected_entry,
11324 view->state.ref.repo);
11325 else
11326 return got_error_msg(GOT_ERR_NOT_IMPL,
11327 "parent/child view pair not supported");
11328 break;
11329 case TOG_VIEW_TREE:
11330 if (view->type == TOG_VIEW_LOG)
11331 err = browse_commit_tree(new_view, y, x,
11332 view->state.log.selected_entry,
11333 view->state.log.in_repo_path,
11334 view->state.log.head_ref_name,
11335 view->state.log.repo);
11336 else if (view->type == TOG_VIEW_REF)
11337 err = browse_ref_tree(new_view, y, x,
11338 view->state.ref.selected_entry,
11339 view->state.ref.repo);
11340 else
11341 return got_error_msg(GOT_ERR_NOT_IMPL,
11342 "parent/child view pair not supported");
11343 break;
11344 case TOG_VIEW_REF:
11345 *new_view = view_open(0, 0, y, x, TOG_VIEW_REF);
11346 if (*new_view == NULL)
11347 return got_error_from_errno("view_open");
11348 if (view->type == TOG_VIEW_LOG)
11349 err = open_ref_view(*new_view, view->state.log.repo);
11350 else if (view->type == TOG_VIEW_TREE)
11351 err = open_ref_view(*new_view, view->state.tree.repo);
11352 else
11353 err = got_error_msg(GOT_ERR_NOT_IMPL,
11354 "parent/child view pair not supported");
11355 if (err)
11356 view_close(*new_view);
11357 break;
11358 case TOG_VIEW_HELP:
11359 *new_view = view_open(0, 0, 0, 0, TOG_VIEW_HELP);
11360 if (*new_view == NULL)
11361 return got_error_from_errno("view_open");
11362 err = open_help_view(*new_view, view);
11363 if (err)
11364 view_close(*new_view);
11365 break;
11366 default:
11367 return got_error_msg(GOT_ERR_NOT_IMPL, "invalid view");
11370 return err;
11374 * If view was scrolled down to move the selected line into view when opening a
11375 * horizontal split, scroll back up when closing the split/toggling fullscreen.
11377 static void
11378 offset_selection_up(struct tog_view *view)
11380 switch (view->type) {
11381 case TOG_VIEW_BLAME: {
11382 struct tog_blame_view_state *s = &view->state.blame;
11383 if (s->first_displayed_line == 1) {
11384 s->selected_line = MAX(s->selected_line - view->offset,
11386 break;
11388 if (s->first_displayed_line > view->offset)
11389 s->first_displayed_line -= view->offset;
11390 else
11391 s->first_displayed_line = 1;
11392 s->selected_line += view->offset;
11393 break;
11395 case TOG_VIEW_LOG:
11396 log_scroll_up(&view->state.log, view->offset);
11397 view->state.log.selected += view->offset;
11398 break;
11399 case TOG_VIEW_REF:
11400 ref_scroll_up(&view->state.ref, view->offset);
11401 view->state.ref.selected += view->offset;
11402 break;
11403 case TOG_VIEW_TREE:
11404 tree_scroll_up(&view->state.tree, view->offset);
11405 view->state.tree.selected += view->offset;
11406 break;
11407 default:
11408 break;
11411 view->offset = 0;
11415 * If the selected line is in the section of screen covered by the bottom split,
11416 * scroll down offset lines to move it into view and index its new position.
11418 static const struct got_error *
11419 offset_selection_down(struct tog_view *view)
11421 const struct got_error *err = NULL;
11422 const struct got_error *(*scrolld)(struct tog_view *, int);
11423 int *selected = NULL;
11424 int header, offset;
11426 switch (view->type) {
11427 case TOG_VIEW_BLAME: {
11428 struct tog_blame_view_state *s = &view->state.blame;
11429 header = 3;
11430 scrolld = NULL;
11431 if (s->selected_line > view->nlines - header) {
11432 offset = abs(view->nlines - s->selected_line - header);
11433 s->first_displayed_line += offset;
11434 s->selected_line -= offset;
11435 view->offset = offset;
11437 break;
11439 case TOG_VIEW_LOG: {
11440 struct tog_log_view_state *s = &view->state.log;
11441 scrolld = &log_scroll_down;
11442 header = view_is_parent_view(view) ? 3 : 2;
11443 selected = &s->selected;
11444 break;
11446 case TOG_VIEW_REF: {
11447 struct tog_ref_view_state *s = &view->state.ref;
11448 scrolld = &ref_scroll_down;
11449 header = 3;
11450 selected = &s->selected;
11451 break;
11453 case TOG_VIEW_TREE: {
11454 struct tog_tree_view_state *s = &view->state.tree;
11455 scrolld = &tree_scroll_down;
11456 header = 5;
11457 selected = &s->selected;
11458 break;
11460 default:
11461 selected = NULL;
11462 scrolld = NULL;
11463 header = 0;
11464 break;
11467 if (selected && *selected > view->nlines - header) {
11468 offset = abs(view->nlines - *selected - header);
11469 view->offset = offset;
11470 if (scrolld && offset) {
11471 err = scrolld(view, offset);
11472 *selected -= MIN(*selected, offset);
11476 return err;
11479 static void
11480 list_commands(FILE *fp)
11482 size_t i;
11484 fprintf(fp, "commands:");
11485 for (i = 0; i < nitems(tog_commands); i++) {
11486 const struct tog_cmd *cmd = &tog_commands[i];
11487 fprintf(fp, " %s", cmd->name);
11489 fputc('\n', fp);
11492 __dead static void
11493 usage(int hflag, int status)
11495 FILE *fp = (status == 0) ? stdout : stderr;
11497 fprintf(fp, "usage: %s [-hV] command [arg ...]\n",
11498 getprogname());
11499 if (hflag) {
11500 fprintf(fp, "lazy usage: %s path\n", getprogname());
11501 list_commands(fp);
11503 exit(status);
11506 static char **
11507 make_argv(int argc, ...)
11509 va_list ap;
11510 char **argv;
11511 int i;
11513 va_start(ap, argc);
11515 argv = calloc(argc, sizeof(char *));
11516 if (argv == NULL)
11517 err(1, "calloc");
11518 for (i = 0; i < argc; i++) {
11519 argv[i] = strdup(va_arg(ap, char *));
11520 if (argv[i] == NULL)
11521 err(1, "strdup");
11524 va_end(ap);
11525 return argv;
11529 * Try to convert 'tog path' into a 'tog log path' command.
11530 * The user could simply have mistyped the command rather than knowingly
11531 * provided a path. So check whether argv[0] can in fact be resolved
11532 * to a path in the HEAD commit and print a special error if not.
11533 * This hack is for mpi@ <3
11535 static const struct got_error *
11536 tog_log_with_path(int argc, char *argv[])
11538 const struct got_error *error = NULL, *close_err;
11539 const struct tog_cmd *cmd = NULL;
11540 struct got_repository *repo = NULL;
11541 struct got_worktree *worktree = NULL;
11542 struct got_object_id *commit_id = NULL, *id = NULL;
11543 struct got_commit_object *commit = NULL;
11544 char *cwd = NULL, *repo_path = NULL, *in_repo_path = NULL;
11545 char *commit_id_str = NULL, **cmd_argv = NULL;
11546 int *pack_fds = NULL;
11548 cwd = getcwd(NULL, 0);
11549 if (cwd == NULL)
11550 return got_error_from_errno("getcwd");
11552 error = got_repo_pack_fds_open(&pack_fds);
11553 if (error != NULL)
11554 goto done;
11556 error = got_worktree_open(&worktree, cwd, NULL);
11557 if (error && error->code != GOT_ERR_NOT_WORKTREE)
11558 goto done;
11560 if (worktree)
11561 repo_path = strdup(got_worktree_get_repo_path(worktree));
11562 else
11563 repo_path = strdup(cwd);
11564 if (repo_path == NULL) {
11565 error = got_error_from_errno("strdup");
11566 goto done;
11569 error = got_repo_open(&repo, repo_path, NULL, pack_fds);
11570 if (error != NULL)
11571 goto done;
11573 error = get_in_repo_path_from_argv0(&in_repo_path, argc, argv,
11574 repo, worktree);
11575 if (error)
11576 goto done;
11578 error = tog_load_refs(repo, 0);
11579 if (error)
11580 goto done;
11581 error = got_repo_match_object_id(&commit_id, NULL, worktree ?
11582 got_worktree_get_head_ref_name(worktree) : GOT_REF_HEAD,
11583 GOT_OBJ_TYPE_COMMIT, &tog_refs, repo);
11584 if (error)
11585 goto done;
11587 if (worktree) {
11588 got_worktree_close(worktree);
11589 worktree = NULL;
11592 error = got_object_open_as_commit(&commit, repo, commit_id);
11593 if (error)
11594 goto done;
11596 error = got_object_id_by_path(&id, repo, commit, in_repo_path);
11597 if (error) {
11598 if (error->code != GOT_ERR_NO_TREE_ENTRY)
11599 goto done;
11600 fprintf(stderr, "%s: '%s' is no known command or path\n",
11601 getprogname(), argv[0]);
11602 usage(1, 1);
11603 /* not reached */
11606 error = got_object_id_str(&commit_id_str, commit_id);
11607 if (error)
11608 goto done;
11610 cmd = &tog_commands[0]; /* log */
11611 argc = 4;
11612 cmd_argv = make_argv(argc, cmd->name, "-c", commit_id_str, argv[0]);
11613 error = cmd->cmd_main(argc, cmd_argv);
11614 done:
11615 if (repo) {
11616 close_err = got_repo_close(repo);
11617 if (error == NULL)
11618 error = close_err;
11620 if (commit)
11621 got_object_commit_close(commit);
11622 if (worktree)
11623 got_worktree_close(worktree);
11624 if (pack_fds) {
11625 const struct got_error *pack_err =
11626 got_repo_pack_fds_close(pack_fds);
11627 if (error == NULL)
11628 error = pack_err;
11630 free(id);
11631 free(commit_id_str);
11632 free(commit_id);
11633 free(cwd);
11634 free(repo_path);
11635 free(in_repo_path);
11636 if (cmd_argv) {
11637 int i;
11638 for (i = 0; i < argc; i++)
11639 free(cmd_argv[i]);
11640 free(cmd_argv);
11642 tog_free_refs();
11643 return error;
11647 main(int argc, char *argv[])
11649 const struct got_error *io_err, *error = NULL;
11650 const struct tog_cmd *cmd = NULL;
11651 int ch, hflag = 0, Vflag = 0;
11652 char **cmd_argv = NULL;
11653 static const struct option longopts[] = {
11654 { "version", no_argument, NULL, 'V' },
11655 { NULL, 0, NULL, 0}
11657 char *diff_algo_str = NULL;
11658 const char *test_script_path;
11660 setlocale(LC_CTYPE, "");
11663 * Override default signal handlers before starting ncurses.
11664 * This should prevent ncurses from installing its own
11665 * broken cleanup() signal handler.
11667 signal(SIGWINCH, tog_sigwinch);
11668 signal(SIGPIPE, tog_sigpipe);
11669 signal(SIGCONT, tog_sigcont);
11670 signal(SIGINT, tog_sigint);
11671 signal(SIGTERM, tog_sigterm);
11674 * Test mode init must happen before pledge() because "tty" will
11675 * not allow TTY-related ioctls to occur via regular files.
11677 test_script_path = getenv("TOG_TEST_SCRIPT");
11678 if (test_script_path != NULL) {
11679 error = init_mock_term(test_script_path);
11680 if (error) {
11681 fprintf(stderr, "%s: %s\n", getprogname(), error->msg);
11682 return 1;
11684 } else if (!isatty(STDIN_FILENO))
11685 errx(1, "standard input is not a tty");
11687 #if !defined(PROFILE)
11688 if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd unveil",
11689 NULL) == -1)
11690 err(1, "pledge");
11691 #endif
11693 while ((ch = getopt_long(argc, argv, "+hV", longopts, NULL)) != -1) {
11694 switch (ch) {
11695 case 'h':
11696 hflag = 1;
11697 break;
11698 case 'V':
11699 Vflag = 1;
11700 break;
11701 default:
11702 usage(hflag, 1);
11703 /* NOTREACHED */
11707 argc -= optind;
11708 argv += optind;
11709 optind = 1;
11710 optreset = 1;
11712 if (Vflag) {
11713 got_version_print_str();
11714 return 0;
11717 if (argc == 0) {
11718 if (hflag)
11719 usage(hflag, 0);
11720 /* Build an argument vector which runs a default command. */
11721 cmd = &tog_commands[0];
11722 argc = 1;
11723 cmd_argv = make_argv(argc, cmd->name);
11724 } else {
11725 size_t i;
11727 /* Did the user specify a command? */
11728 for (i = 0; i < nitems(tog_commands); i++) {
11729 if (strncmp(tog_commands[i].name, argv[0],
11730 strlen(argv[0])) == 0) {
11731 cmd = &tog_commands[i];
11732 break;
11737 diff_algo_str = getenv("TOG_DIFF_ALGORITHM");
11738 if (diff_algo_str) {
11739 if (strcasecmp(diff_algo_str, "patience") == 0)
11740 tog_diff_algo = GOT_DIFF_ALGORITHM_PATIENCE;
11741 if (strcasecmp(diff_algo_str, "myers") == 0)
11742 tog_diff_algo = GOT_DIFF_ALGORITHM_MYERS;
11745 tog_base_commit.idx = -1;
11746 tog_base_commit.marker = GOT_WORKTREE_STATE_UNKNOWN;
11748 if (cmd == NULL) {
11749 if (argc != 1)
11750 usage(0, 1);
11751 /* No command specified; try log with a path */
11752 error = tog_log_with_path(argc, argv);
11753 } else {
11754 if (hflag)
11755 cmd->cmd_usage();
11756 else
11757 error = cmd->cmd_main(argc, cmd_argv ? cmd_argv : argv);
11760 if (using_mock_io) {
11761 io_err = tog_io_close();
11762 if (error == NULL)
11763 error = io_err;
11765 endwin();
11766 if (cmd_argv) {
11767 int i;
11768 for (i = 0; i < argc; i++)
11769 free(cmd_argv[i]);
11770 free(cmd_argv);
11773 if (error && error->code != GOT_ERR_CANCELLED &&
11774 error->code != GOT_ERR_EOF &&
11775 error->code != GOT_ERR_PRIVSEP_EXIT &&
11776 error->code != GOT_ERR_PRIVSEP_PIPE &&
11777 !(error->code == GOT_ERR_ERRNO && errno == EINTR)) {
11778 fprintf(stderr, "%s: %s\n", getprogname(), error->msg);
11779 return 1;
11781 return 0;