bump version number
[got-portable.git] / tog / tog.c
blob35e506fa81a4f1d159e47643c3af493dc71e737c
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 #if defined(__FreeBSD__) || defined(__APPLE__)
26 #define _XOPEN_SOURCE_EXTENDED /* for ncurses wide-character functions */
27 #endif
28 #include <curses.h>
29 #include <panel.h>
30 #include <locale.h>
31 #include <signal.h>
32 #include <stdlib.h>
33 #include <stdarg.h>
34 #include <stdio.h>
35 #include <getopt.h>
36 #include <string.h>
37 #include <err.h>
38 #include <unistd.h>
39 #include <limits.h>
40 #include <wchar.h>
41 #include <time.h>
42 #include <pthread.h>
43 #include <libgen.h>
44 #include <regex.h>
45 #include <sched.h>
47 #include "got_version.h"
48 #include "got_error.h"
49 #include "got_object.h"
50 #include "got_reference.h"
51 #include "got_repository.h"
52 #include "got_diff.h"
53 #include "got_opentemp.h"
54 #include "got_utf8.h"
55 #include "got_cancel.h"
56 #include "got_commit_graph.h"
57 #include "got_blame.h"
58 #include "got_privsep.h"
59 #include "got_path.h"
60 #include "got_worktree.h"
61 #include "got_keyword.h"
63 #ifndef MIN
64 #define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b))
65 #endif
67 #ifndef MAX
68 #define MAX(_a,_b) ((_a) > (_b) ? (_a) : (_b))
69 #endif
71 #ifndef CTRL
72 #define CTRL(x) ((x) & 0x1f)
73 #endif
75 #ifndef nitems
76 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
77 #endif
79 struct tog_cmd {
80 const char *name;
81 const struct got_error *(*cmd_main)(int, char *[]);
82 void (*cmd_usage)(void);
85 __dead static void usage(int, int);
86 __dead static void usage_log(void);
87 __dead static void usage_diff(void);
88 __dead static void usage_blame(void);
89 __dead static void usage_tree(void);
90 __dead static void usage_ref(void);
92 static const struct got_error* cmd_log(int, char *[]);
93 static const struct got_error* cmd_diff(int, char *[]);
94 static const struct got_error* cmd_blame(int, char *[]);
95 static const struct got_error* cmd_tree(int, char *[]);
96 static const struct got_error* cmd_ref(int, char *[]);
98 static const struct tog_cmd tog_commands[] = {
99 { "log", cmd_log, usage_log },
100 { "diff", cmd_diff, usage_diff },
101 { "blame", cmd_blame, usage_blame },
102 { "tree", cmd_tree, usage_tree },
103 { "ref", cmd_ref, usage_ref },
106 enum tog_view_type {
107 TOG_VIEW_DIFF,
108 TOG_VIEW_LOG,
109 TOG_VIEW_BLAME,
110 TOG_VIEW_TREE,
111 TOG_VIEW_REF,
112 TOG_VIEW_HELP
115 /* Match _DIFF to _HELP with enum tog_view_type TOG_VIEW_* counterparts. */
116 enum tog_keymap_type {
117 TOG_KEYMAP_KEYS = -2,
118 TOG_KEYMAP_GLOBAL,
119 TOG_KEYMAP_DIFF,
120 TOG_KEYMAP_LOG,
121 TOG_KEYMAP_BLAME,
122 TOG_KEYMAP_TREE,
123 TOG_KEYMAP_REF,
124 TOG_KEYMAP_HELP
127 enum tog_view_mode {
128 TOG_VIEW_SPLIT_NONE,
129 TOG_VIEW_SPLIT_VERT,
130 TOG_VIEW_SPLIT_HRZN
133 #define HSPLIT_SCALE 0.3f /* default horizontal split scale */
135 #define TOG_EOF_STRING "(END)"
137 struct commit_queue_entry {
138 TAILQ_ENTRY(commit_queue_entry) entry;
139 struct got_object_id *id;
140 struct got_commit_object *commit;
141 int idx;
143 TAILQ_HEAD(commit_queue_head, commit_queue_entry);
144 struct commit_queue {
145 int ncommits;
146 struct commit_queue_head head;
149 struct tog_color {
150 STAILQ_ENTRY(tog_color) entry;
151 regex_t regex;
152 short colorpair;
154 STAILQ_HEAD(tog_colors, tog_color);
156 static struct got_reflist_head tog_refs = TAILQ_HEAD_INITIALIZER(tog_refs);
157 static struct got_reflist_object_id_map *tog_refs_idmap;
158 static struct {
159 struct got_object_id *id;
160 int idx;
161 char marker;
162 } tog_base_commit;
163 static enum got_diff_algorithm tog_diff_algo = GOT_DIFF_ALGORITHM_PATIENCE;
165 static const struct got_error *
166 tog_ref_cmp_by_name(void *arg, int *cmp, struct got_reference *re1,
167 struct got_reference* re2)
169 const char *name1 = got_ref_get_name(re1);
170 const char *name2 = got_ref_get_name(re2);
171 int isbackup1, isbackup2;
173 /* Sort backup refs towards the bottom of the list. */
174 isbackup1 = strncmp(name1, "refs/got/backup/", 16) == 0;
175 isbackup2 = strncmp(name2, "refs/got/backup/", 16) == 0;
176 if (!isbackup1 && isbackup2) {
177 *cmp = -1;
178 return NULL;
179 } else if (isbackup1 && !isbackup2) {
180 *cmp = 1;
181 return NULL;
184 *cmp = got_path_cmp(name1, name2, strlen(name1), strlen(name2));
185 return NULL;
188 static const struct got_error *
189 tog_load_refs(struct got_repository *repo, int sort_by_date)
191 const struct got_error *err;
193 err = got_ref_list(&tog_refs, repo, NULL, sort_by_date ?
194 got_ref_cmp_by_commit_timestamp_descending : tog_ref_cmp_by_name,
195 repo);
196 if (err)
197 return err;
199 return got_reflist_object_id_map_create(&tog_refs_idmap, &tog_refs,
200 repo);
203 static void
204 tog_free_refs(void)
206 if (tog_refs_idmap) {
207 got_reflist_object_id_map_free(tog_refs_idmap);
208 tog_refs_idmap = NULL;
210 got_ref_list_free(&tog_refs);
213 static const struct got_error *
214 add_color(struct tog_colors *colors, const char *pattern,
215 int idx, short color)
217 const struct got_error *err = NULL;
218 struct tog_color *tc;
219 int regerr = 0;
221 if (idx < 1 || idx > COLOR_PAIRS - 1)
222 return NULL;
224 init_pair(idx, color, -1);
226 tc = calloc(1, sizeof(*tc));
227 if (tc == NULL)
228 return got_error_from_errno("calloc");
229 regerr = regcomp(&tc->regex, pattern,
230 REG_EXTENDED | REG_NOSUB | REG_NEWLINE);
231 if (regerr) {
232 static char regerr_msg[512];
233 static char err_msg[512];
234 regerror(regerr, &tc->regex, regerr_msg,
235 sizeof(regerr_msg));
236 snprintf(err_msg, sizeof(err_msg), "regcomp: %s",
237 regerr_msg);
238 err = got_error_msg(GOT_ERR_REGEX, err_msg);
239 free(tc);
240 return err;
242 tc->colorpair = idx;
243 STAILQ_INSERT_HEAD(colors, tc, entry);
244 return NULL;
247 static void
248 free_colors(struct tog_colors *colors)
250 struct tog_color *tc;
252 while (!STAILQ_EMPTY(colors)) {
253 tc = STAILQ_FIRST(colors);
254 STAILQ_REMOVE_HEAD(colors, entry);
255 regfree(&tc->regex);
256 free(tc);
260 static struct tog_color *
261 get_color(struct tog_colors *colors, int colorpair)
263 struct tog_color *tc = NULL;
265 STAILQ_FOREACH(tc, colors, entry) {
266 if (tc->colorpair == colorpair)
267 return tc;
270 return NULL;
273 static int
274 default_color_value(const char *envvar)
276 if (strcmp(envvar, "TOG_COLOR_DIFF_MINUS") == 0)
277 return COLOR_MAGENTA;
278 if (strcmp(envvar, "TOG_COLOR_DIFF_PLUS") == 0)
279 return COLOR_CYAN;
280 if (strcmp(envvar, "TOG_COLOR_DIFF_CHUNK_HEADER") == 0)
281 return COLOR_YELLOW;
282 if (strcmp(envvar, "TOG_COLOR_DIFF_META") == 0)
283 return COLOR_GREEN;
284 if (strcmp(envvar, "TOG_COLOR_TREE_SUBMODULE") == 0)
285 return COLOR_MAGENTA;
286 if (strcmp(envvar, "TOG_COLOR_TREE_SYMLINK") == 0)
287 return COLOR_MAGENTA;
288 if (strcmp(envvar, "TOG_COLOR_TREE_DIRECTORY") == 0)
289 return COLOR_CYAN;
290 if (strcmp(envvar, "TOG_COLOR_TREE_EXECUTABLE") == 0)
291 return COLOR_GREEN;
292 if (strcmp(envvar, "TOG_COLOR_COMMIT") == 0)
293 return COLOR_GREEN;
294 if (strcmp(envvar, "TOG_COLOR_AUTHOR") == 0)
295 return COLOR_CYAN;
296 if (strcmp(envvar, "TOG_COLOR_DATE") == 0)
297 return COLOR_YELLOW;
298 if (strcmp(envvar, "TOG_COLOR_REFS_HEADS") == 0)
299 return COLOR_GREEN;
300 if (strcmp(envvar, "TOG_COLOR_REFS_TAGS") == 0)
301 return COLOR_MAGENTA;
302 if (strcmp(envvar, "TOG_COLOR_REFS_REMOTES") == 0)
303 return COLOR_YELLOW;
304 if (strcmp(envvar, "TOG_COLOR_REFS_BACKUP") == 0)
305 return COLOR_CYAN;
307 return -1;
310 static int
311 get_color_value(const char *envvar)
313 const char *val = getenv(envvar);
315 if (val == NULL)
316 return default_color_value(envvar);
318 if (strcasecmp(val, "black") == 0)
319 return COLOR_BLACK;
320 if (strcasecmp(val, "red") == 0)
321 return COLOR_RED;
322 if (strcasecmp(val, "green") == 0)
323 return COLOR_GREEN;
324 if (strcasecmp(val, "yellow") == 0)
325 return COLOR_YELLOW;
326 if (strcasecmp(val, "blue") == 0)
327 return COLOR_BLUE;
328 if (strcasecmp(val, "magenta") == 0)
329 return COLOR_MAGENTA;
330 if (strcasecmp(val, "cyan") == 0)
331 return COLOR_CYAN;
332 if (strcasecmp(val, "white") == 0)
333 return COLOR_WHITE;
334 if (strcasecmp(val, "default") == 0)
335 return -1;
337 return default_color_value(envvar);
340 struct tog_diff_view_state {
341 struct got_object_id *id1, *id2;
342 const char *label1, *label2;
343 char *action;
344 FILE *f, *f1, *f2;
345 int fd1, fd2;
346 int lineno;
347 int first_displayed_line;
348 int last_displayed_line;
349 int eof;
350 int diff_context;
351 int ignore_whitespace;
352 int force_text_diff;
353 struct got_repository *repo;
354 struct got_diff_line *lines;
355 size_t nlines;
356 int matched_line;
357 int selected_line;
359 /* passed from log or blame view; may be NULL */
360 struct tog_view *parent_view;
363 pthread_mutex_t tog_mutex = PTHREAD_MUTEX_INITIALIZER;
364 static volatile sig_atomic_t tog_thread_error;
366 struct tog_log_thread_args {
367 pthread_cond_t need_commits;
368 pthread_cond_t commit_loaded;
369 int commits_needed;
370 int load_all;
371 struct got_commit_graph *graph;
372 struct commit_queue *real_commits;
373 const char *in_repo_path;
374 struct got_object_id *start_id;
375 struct got_repository *repo;
376 int *pack_fds;
377 int log_complete;
378 pthread_cond_t log_loaded;
379 sig_atomic_t *quit;
380 struct commit_queue_entry **first_displayed_entry;
381 struct commit_queue_entry **selected_entry;
382 int *searching;
383 int *search_next_done;
384 regex_t *regex;
385 int *limiting;
386 int limit_match;
387 regex_t *limit_regex;
388 struct commit_queue *limit_commits;
389 struct got_worktree *worktree;
390 int need_commit_marker;
393 struct tog_log_view_state {
394 struct commit_queue *commits;
395 struct commit_queue_entry *first_displayed_entry;
396 struct commit_queue_entry *last_displayed_entry;
397 struct commit_queue_entry *selected_entry;
398 struct commit_queue_entry *marked_entry;
399 struct commit_queue real_commits;
400 int selected;
401 char *in_repo_path;
402 char *head_ref_name;
403 int log_branches;
404 struct got_repository *repo;
405 struct got_object_id *start_id;
406 sig_atomic_t quit;
407 pthread_t thread;
408 struct tog_log_thread_args thread_args;
409 struct commit_queue_entry *matched_entry;
410 struct commit_queue_entry *search_entry;
411 struct tog_colors colors;
412 int use_committer;
413 int limit_view;
414 regex_t limit_regex;
415 struct commit_queue limit_commits;
418 #define TOG_COLOR_DIFF_MINUS 1
419 #define TOG_COLOR_DIFF_PLUS 2
420 #define TOG_COLOR_DIFF_CHUNK_HEADER 3
421 #define TOG_COLOR_DIFF_META 4
422 #define TOG_COLOR_TREE_SUBMODULE 5
423 #define TOG_COLOR_TREE_SYMLINK 6
424 #define TOG_COLOR_TREE_DIRECTORY 7
425 #define TOG_COLOR_TREE_EXECUTABLE 8
426 #define TOG_COLOR_COMMIT 9
427 #define TOG_COLOR_AUTHOR 10
428 #define TOG_COLOR_DATE 11
429 #define TOG_COLOR_REFS_HEADS 12
430 #define TOG_COLOR_REFS_TAGS 13
431 #define TOG_COLOR_REFS_REMOTES 14
432 #define TOG_COLOR_REFS_BACKUP 15
434 struct tog_blame_cb_args {
435 struct tog_blame_line *lines; /* one per line */
436 int nlines;
438 struct tog_view *view;
439 struct got_object_id *commit_id;
440 int *quit;
443 struct tog_blame_thread_args {
444 const char *path;
445 struct got_repository *repo;
446 struct tog_blame_cb_args *cb_args;
447 int *complete;
448 got_cancel_cb cancel_cb;
449 void *cancel_arg;
450 pthread_cond_t blame_complete;
453 struct tog_blame {
454 FILE *f;
455 off_t filesize;
456 struct tog_blame_line *lines;
457 int nlines;
458 off_t *line_offsets;
459 pthread_t thread;
460 struct tog_blame_thread_args thread_args;
461 struct tog_blame_cb_args cb_args;
462 const char *path;
463 int *pack_fds;
466 struct tog_blame_view_state {
467 int first_displayed_line;
468 int last_displayed_line;
469 int selected_line;
470 int last_diffed_line;
471 int blame_complete;
472 int eof;
473 int done;
474 struct got_object_id_queue blamed_commits;
475 struct got_object_qid *blamed_commit;
476 char *path;
477 struct got_repository *repo;
478 struct got_object_id *commit_id;
479 struct got_object_id *id_to_log;
480 struct tog_blame blame;
481 int matched_line;
482 struct tog_colors colors;
485 struct tog_parent_tree {
486 TAILQ_ENTRY(tog_parent_tree) entry;
487 struct got_tree_object *tree;
488 struct got_tree_entry *first_displayed_entry;
489 struct got_tree_entry *selected_entry;
490 int selected;
493 TAILQ_HEAD(tog_parent_trees, tog_parent_tree);
495 struct tog_tree_view_state {
496 char *tree_label;
497 struct got_object_id *commit_id;/* commit which this tree belongs to */
498 struct got_tree_object *root; /* the commit's root tree entry */
499 struct got_tree_object *tree; /* currently displayed (sub-)tree */
500 struct got_tree_entry *first_displayed_entry;
501 struct got_tree_entry *last_displayed_entry;
502 struct got_tree_entry *selected_entry;
503 int ndisplayed, selected, show_ids;
504 struct tog_parent_trees parents; /* parent trees of current sub-tree */
505 char *head_ref_name;
506 struct got_repository *repo;
507 struct got_tree_entry *matched_entry;
508 struct tog_colors colors;
511 struct tog_reflist_entry {
512 TAILQ_ENTRY(tog_reflist_entry) entry;
513 struct got_reference *ref;
514 int idx;
517 TAILQ_HEAD(tog_reflist_head, tog_reflist_entry);
519 struct tog_ref_view_state {
520 struct tog_reflist_head refs;
521 struct tog_reflist_entry *first_displayed_entry;
522 struct tog_reflist_entry *last_displayed_entry;
523 struct tog_reflist_entry *selected_entry;
524 int nrefs, ndisplayed, selected, show_date, show_ids, sort_by_date;
525 struct got_repository *repo;
526 struct tog_reflist_entry *matched_entry;
527 struct tog_colors colors;
530 struct tog_help_view_state {
531 FILE *f;
532 off_t *line_offsets;
533 size_t nlines;
534 int lineno;
535 int first_displayed_line;
536 int last_displayed_line;
537 int eof;
538 int matched_line;
539 int selected_line;
540 int all;
541 enum tog_keymap_type type;
544 #define GENERATE_HELP \
545 KEYMAP_("Global", TOG_KEYMAP_GLOBAL), \
546 KEY_("H F1", "Open view-specific help (double tap for all help)"), \
547 KEY_("k C-p Up", "Move cursor or page up one line"), \
548 KEY_("j C-n Down", "Move cursor or page down one line"), \
549 KEY_("C-b b PgUp", "Scroll the view up one page"), \
550 KEY_("C-f f PgDn Space", "Scroll the view down one page"), \
551 KEY_("C-u u", "Scroll the view up one half page"), \
552 KEY_("C-d d", "Scroll the view down one half page"), \
553 KEY_("g", "Go to line N (default: first line)"), \
554 KEY_("Home =", "Go to the first line"), \
555 KEY_("G", "Go to line N (default: last line)"), \
556 KEY_("End *", "Go to the last line"), \
557 KEY_("l Right", "Scroll the view right"), \
558 KEY_("h Left", "Scroll the view left"), \
559 KEY_("$", "Scroll view to the rightmost position"), \
560 KEY_("0", "Scroll view to the leftmost position"), \
561 KEY_("-", "Decrease size of the focussed split"), \
562 KEY_("+", "Increase size of the focussed split"), \
563 KEY_("Tab", "Switch focus between views"), \
564 KEY_("F", "Toggle fullscreen mode"), \
565 KEY_("S", "Switch split-screen layout"), \
566 KEY_("/", "Open prompt to enter search term"), \
567 KEY_("n", "Find next line/token matching the current search term"), \
568 KEY_("N", "Find previous line/token matching the current search term"),\
569 KEY_("q", "Quit the focussed view; Quit help screen"), \
570 KEY_("Q", "Quit tog"), \
572 KEYMAP_("Log view", TOG_KEYMAP_LOG), \
573 KEY_("< ,", "Move cursor up one commit"), \
574 KEY_("> .", "Move cursor down one commit"), \
575 KEY_("Enter", "Open diff view of the selected commit"), \
576 KEY_("B", "Reload the log view and toggle display of merged commits"), \
577 KEY_("R", "Open ref view of all repository references"), \
578 KEY_("T", "Display tree view of the repository from the selected" \
579 " commit"), \
580 KEY_("m", "Mark or unmark the selected entry for diffing with the " \
581 "next selected commit"), \
582 KEY_("@", "Toggle between displaying author and committer name"), \
583 KEY_("&", "Open prompt to enter term to limit commits displayed"), \
584 KEY_("C-g Backspace", "Cancel current search or log operation"), \
585 KEY_("C-l", "Reload the log view with new commits in the repository"), \
587 KEYMAP_("Diff view", TOG_KEYMAP_DIFF), \
588 KEY_("K < ,", "Display diff of next line in the file/log entry"), \
589 KEY_("J > .", "Display diff of previous line in the file/log entry"), \
590 KEY_("A", "Toggle between Myers and Patience diff algorithm"), \
591 KEY_("a", "Toggle treatment of file as ASCII irrespective of binary" \
592 " data"), \
593 KEY_("p", "Write diff to a patch file in /tmp"), \
594 KEY_("(", "Go to the previous file in the diff"), \
595 KEY_(")", "Go to the next file in the diff"), \
596 KEY_("{", "Go to the previous hunk in the diff"), \
597 KEY_("}", "Go to the next hunk in the diff"), \
598 KEY_("[", "Decrease the number of context lines"), \
599 KEY_("]", "Increase the number of context lines"), \
600 KEY_("w", "Toggle ignore whitespace-only changes in the diff"), \
602 KEYMAP_("Blame view", TOG_KEYMAP_BLAME), \
603 KEY_("Enter", "Display diff view of the selected line's commit"), \
604 KEY_("A", "Toggle diff algorithm between Myers and Patience"), \
605 KEY_("L", "Open log view for the currently selected annotated line"), \
606 KEY_("C", "Reload view with the previously blamed commit"), \
607 KEY_("c", "Reload view with the version of the file found in the" \
608 " selected line's commit"), \
609 KEY_("p", "Reload view with the version of the file found in the" \
610 " selected line's parent commit"), \
612 KEYMAP_("Tree view", TOG_KEYMAP_TREE), \
613 KEY_("Enter", "Enter selected directory or open blame view of the" \
614 " selected file"), \
615 KEY_("L", "Open log view for the selected entry"), \
616 KEY_("R", "Open ref view of all repository references"), \
617 KEY_("i", "Show object IDs for all tree entries"), \
618 KEY_("Backspace", "Return to the parent directory"), \
620 KEYMAP_("Ref view", TOG_KEYMAP_REF), \
621 KEY_("Enter", "Display log view of the selected reference"), \
622 KEY_("T", "Display tree view of the selected reference"), \
623 KEY_("i", "Toggle display of IDs for all non-symbolic references"), \
624 KEY_("m", "Toggle display of last modified date for each reference"), \
625 KEY_("o", "Toggle reference sort order (name -> timestamp)"), \
626 KEY_("C-l", "Reload view with all repository references")
628 struct tog_key_map {
629 const char *keys;
630 const char *info;
631 enum tog_keymap_type type;
634 /* curses io for tog regress */
635 struct tog_io {
636 FILE *cin;
637 FILE *cout;
638 FILE *f;
639 FILE *sdump;
640 char *input_str;
641 int wait_for_ui;
642 } tog_io;
643 static int using_mock_io;
645 #define TOG_KEY_SCRDUMP SHRT_MIN
648 * We implement two types of views: parent views and child views.
650 * The 'Tab' key switches focus between a parent view and its child view.
651 * Child views are shown side-by-side to their parent view, provided
652 * there is enough screen estate.
654 * When a new view is opened from within a parent view, this new view
655 * becomes a child view of the parent view, replacing any existing child.
657 * When a new view is opened from within a child view, this new view
658 * becomes a parent view which will obscure the views below until the
659 * user quits the new parent view by typing 'q'.
661 * This list of views contains parent views only.
662 * Child views are only pointed to by their parent view.
664 TAILQ_HEAD(tog_view_list_head, tog_view);
666 struct tog_view {
667 TAILQ_ENTRY(tog_view) entry;
668 WINDOW *window;
669 PANEL *panel;
670 int nlines, ncols, begin_y, begin_x; /* based on split height/width */
671 int resized_y, resized_x; /* begin_y/x based on user resizing */
672 int maxx, x; /* max column and current start column */
673 int lines, cols; /* copies of LINES and COLS */
674 int nscrolled, offset; /* lines scrolled and hsplit line offset */
675 int gline, hiline; /* navigate to and highlight this nG line */
676 int ch, count; /* current keymap and count prefix */
677 int resized; /* set when in a resize event */
678 int focussed; /* Only set on one parent or child view at a time. */
679 int dying;
680 struct tog_view *parent;
681 struct tog_view *child;
684 * This flag is initially set on parent views when a new child view
685 * is created. It gets toggled when the 'Tab' key switches focus
686 * between parent and child.
687 * The flag indicates whether focus should be passed on to our child
688 * view if this parent view gets picked for focus after another parent
689 * view was closed. This prevents child views from losing focus in such
690 * situations.
692 int focus_child;
694 enum tog_view_mode mode;
695 /* type-specific state */
696 enum tog_view_type type;
697 union {
698 struct tog_diff_view_state diff;
699 struct tog_log_view_state log;
700 struct tog_blame_view_state blame;
701 struct tog_tree_view_state tree;
702 struct tog_ref_view_state ref;
703 struct tog_help_view_state help;
704 } state;
706 const struct got_error *(*show)(struct tog_view *);
707 const struct got_error *(*input)(struct tog_view **,
708 struct tog_view *, int);
709 const struct got_error *(*reset)(struct tog_view *);
710 const struct got_error *(*resize)(struct tog_view *, int);
711 const struct got_error *(*close)(struct tog_view *);
713 const struct got_error *(*search_start)(struct tog_view *);
714 const struct got_error *(*search_next)(struct tog_view *);
715 void (*search_setup)(struct tog_view *, FILE **, off_t **, size_t *,
716 int **, int **, int **, int **);
717 int search_started;
718 int searching;
719 #define TOG_SEARCH_FORWARD 1
720 #define TOG_SEARCH_BACKWARD 2
721 int search_next_done;
722 #define TOG_SEARCH_HAVE_MORE 1
723 #define TOG_SEARCH_NO_MORE 2
724 #define TOG_SEARCH_HAVE_NONE 3
725 regex_t regex;
726 regmatch_t regmatch;
727 const char *action;
730 static const struct got_error *open_diff_view(struct tog_view *,
731 struct got_object_id *, struct got_object_id *,
732 const char *, const char *, int, int, int, struct tog_view *,
733 struct got_repository *);
734 static const struct got_error *show_diff_view(struct tog_view *);
735 static const struct got_error *input_diff_view(struct tog_view **,
736 struct tog_view *, int);
737 static const struct got_error *reset_diff_view(struct tog_view *);
738 static const struct got_error* close_diff_view(struct tog_view *);
739 static const struct got_error *search_start_diff_view(struct tog_view *);
740 static void search_setup_diff_view(struct tog_view *, FILE **, off_t **,
741 size_t *, int **, int **, int **, int **);
742 static const struct got_error *search_next_view_match(struct tog_view *);
744 static const struct got_error *open_log_view(struct tog_view *,
745 struct got_object_id *, struct got_repository *,
746 const char *, const char *, int, struct got_worktree *);
747 static const struct got_error * show_log_view(struct tog_view *);
748 static const struct got_error *input_log_view(struct tog_view **,
749 struct tog_view *, int);
750 static const struct got_error *resize_log_view(struct tog_view *, int);
751 static const struct got_error *close_log_view(struct tog_view *);
752 static const struct got_error *search_start_log_view(struct tog_view *);
753 static const struct got_error *search_next_log_view(struct tog_view *);
755 static const struct got_error *open_blame_view(struct tog_view *, char *,
756 struct got_object_id *, struct got_repository *);
757 static const struct got_error *show_blame_view(struct tog_view *);
758 static const struct got_error *input_blame_view(struct tog_view **,
759 struct tog_view *, int);
760 static const struct got_error *reset_blame_view(struct tog_view *);
761 static const struct got_error *close_blame_view(struct tog_view *);
762 static const struct got_error *search_start_blame_view(struct tog_view *);
763 static void search_setup_blame_view(struct tog_view *, FILE **, off_t **,
764 size_t *, int **, int **, int **, int **);
766 static const struct got_error *open_tree_view(struct tog_view *,
767 struct got_object_id *, const char *, struct got_repository *);
768 static const struct got_error *show_tree_view(struct tog_view *);
769 static const struct got_error *input_tree_view(struct tog_view **,
770 struct tog_view *, int);
771 static const struct got_error *close_tree_view(struct tog_view *);
772 static const struct got_error *search_start_tree_view(struct tog_view *);
773 static const struct got_error *search_next_tree_view(struct tog_view *);
775 static const struct got_error *open_ref_view(struct tog_view *,
776 struct got_repository *);
777 static const struct got_error *show_ref_view(struct tog_view *);
778 static const struct got_error *input_ref_view(struct tog_view **,
779 struct tog_view *, int);
780 static const struct got_error *close_ref_view(struct tog_view *);
781 static const struct got_error *search_start_ref_view(struct tog_view *);
782 static const struct got_error *search_next_ref_view(struct tog_view *);
784 static const struct got_error *open_help_view(struct tog_view *,
785 struct tog_view *);
786 static const struct got_error *show_help_view(struct tog_view *);
787 static const struct got_error *input_help_view(struct tog_view **,
788 struct tog_view *, int);
789 static const struct got_error *reset_help_view(struct tog_view *);
790 static const struct got_error* close_help_view(struct tog_view *);
791 static const struct got_error *search_start_help_view(struct tog_view *);
792 static void search_setup_help_view(struct tog_view *, FILE **, off_t **,
793 size_t *, int **, int **, int **, int **);
795 static volatile sig_atomic_t tog_sigwinch_received;
796 static volatile sig_atomic_t tog_sigpipe_received;
797 static volatile sig_atomic_t tog_sigcont_received;
798 static volatile sig_atomic_t tog_sigint_received;
799 static volatile sig_atomic_t tog_sigterm_received;
801 static void
802 tog_sigwinch(int signo)
804 tog_sigwinch_received = 1;
807 static void
808 tog_sigpipe(int signo)
810 tog_sigpipe_received = 1;
813 static void
814 tog_sigcont(int signo)
816 tog_sigcont_received = 1;
819 static void
820 tog_sigint(int signo)
822 tog_sigint_received = 1;
825 static void
826 tog_sigterm(int signo)
828 tog_sigterm_received = 1;
831 static int
832 tog_fatal_signal_received(void)
834 return (tog_sigpipe_received ||
835 tog_sigint_received || tog_sigterm_received);
838 static const struct got_error *
839 view_close(struct tog_view *view)
841 const struct got_error *err = NULL, *child_err = NULL;
843 if (view->child) {
844 child_err = view_close(view->child);
845 view->child = NULL;
847 if (view->close)
848 err = view->close(view);
849 if (view->panel) {
850 del_panel(view->panel);
851 view->panel = NULL;
853 if (view->window) {
854 delwin(view->window);
855 view->window = NULL;
857 free(view);
858 return err ? err : child_err;
861 static struct tog_view *
862 view_open(int nlines, int ncols, int begin_y, int begin_x,
863 enum tog_view_type type)
865 struct tog_view *view = calloc(1, sizeof(*view));
867 if (view == NULL)
868 return NULL;
870 view->type = type;
871 view->lines = LINES;
872 view->cols = COLS;
873 view->nlines = nlines ? nlines : LINES - begin_y;
874 view->ncols = ncols ? ncols : COLS - begin_x;
875 view->begin_y = begin_y;
876 view->begin_x = begin_x;
877 view->window = newwin(nlines, ncols, begin_y, begin_x);
878 if (view->window == NULL) {
879 view_close(view);
880 return NULL;
882 view->panel = new_panel(view->window);
883 if (view->panel == NULL ||
884 set_panel_userptr(view->panel, view) != OK) {
885 view_close(view);
886 return NULL;
889 keypad(view->window, TRUE);
890 return view;
893 static int
894 view_split_begin_x(int begin_x)
896 if (begin_x > 0 || COLS < 120)
897 return 0;
898 return (COLS - MAX(COLS / 2, 80));
901 /* XXX Stub till we decide what to do. */
902 static int
903 view_split_begin_y(int lines)
905 return lines * HSPLIT_SCALE;
908 static const struct got_error *view_resize(struct tog_view *);
910 static const struct got_error *
911 view_splitscreen(struct tog_view *view)
913 const struct got_error *err = NULL;
915 if (!view->resized && view->mode == TOG_VIEW_SPLIT_HRZN) {
916 if (view->resized_y && view->resized_y < view->lines)
917 view->begin_y = view->resized_y;
918 else
919 view->begin_y = view_split_begin_y(view->nlines);
920 view->begin_x = 0;
921 } else if (!view->resized) {
922 if (view->resized_x && view->resized_x < view->cols - 1 &&
923 view->cols > 119)
924 view->begin_x = view->resized_x;
925 else
926 view->begin_x = view_split_begin_x(0);
927 view->begin_y = 0;
929 view->nlines = LINES - view->begin_y;
930 view->ncols = COLS - view->begin_x;
931 view->lines = LINES;
932 view->cols = COLS;
933 err = view_resize(view);
934 if (err)
935 return err;
937 if (view->parent && view->mode == TOG_VIEW_SPLIT_HRZN)
938 view->parent->nlines = view->begin_y;
940 if (mvwin(view->window, view->begin_y, view->begin_x) == ERR)
941 return got_error_from_errno("mvwin");
943 return NULL;
946 static const struct got_error *
947 view_fullscreen(struct tog_view *view)
949 const struct got_error *err = NULL;
951 view->begin_x = 0;
952 view->begin_y = view->resized ? view->begin_y : 0;
953 view->nlines = view->resized ? view->nlines : LINES;
954 view->ncols = COLS;
955 view->lines = LINES;
956 view->cols = COLS;
957 err = view_resize(view);
958 if (err)
959 return err;
961 if (mvwin(view->window, view->begin_y, view->begin_x) == ERR)
962 return got_error_from_errno("mvwin");
964 return NULL;
967 static int
968 view_is_parent_view(struct tog_view *view)
970 return view->parent == NULL;
973 static int
974 view_is_splitscreen(struct tog_view *view)
976 return view->begin_x > 0 || view->begin_y > 0;
979 static int
980 view_is_fullscreen(struct tog_view *view)
982 return view->nlines == LINES && view->ncols == COLS;
985 static int
986 view_is_hsplit_top(struct tog_view *view)
988 return view->mode == TOG_VIEW_SPLIT_HRZN && view->child &&
989 view_is_splitscreen(view->child);
992 static void
993 view_border(struct tog_view *view)
995 PANEL *panel;
996 const struct tog_view *view_above;
998 if (view->parent)
999 return view_border(view->parent);
1001 panel = panel_above(view->panel);
1002 if (panel == NULL)
1003 return;
1005 view_above = panel_userptr(panel);
1006 if (view->mode == TOG_VIEW_SPLIT_HRZN)
1007 mvwhline(view->window, view_above->begin_y - 1,
1008 view->begin_x, ACS_HLINE, view->ncols);
1009 else
1010 mvwvline(view->window, view->begin_y, view_above->begin_x - 1,
1011 ACS_VLINE, view->nlines);
1014 static const struct got_error *view_init_hsplit(struct tog_view *, int);
1015 static const struct got_error *request_log_commits(struct tog_view *);
1016 static const struct got_error *offset_selection_down(struct tog_view *);
1017 static void offset_selection_up(struct tog_view *);
1018 static void view_get_split(struct tog_view *, int *, int *);
1020 static const struct got_error *
1021 view_resize(struct tog_view *view)
1023 const struct got_error *err = NULL;
1024 int dif, nlines, ncols;
1026 dif = LINES - view->lines; /* line difference */
1028 if (view->lines > LINES)
1029 nlines = view->nlines - (view->lines - LINES);
1030 else
1031 nlines = view->nlines + (LINES - view->lines);
1032 if (view->cols > COLS)
1033 ncols = view->ncols - (view->cols - COLS);
1034 else
1035 ncols = view->ncols + (COLS - view->cols);
1037 if (view->child) {
1038 int hs = view->child->begin_y;
1040 if (!view_is_fullscreen(view))
1041 view->child->begin_x = view_split_begin_x(view->begin_x);
1042 if (view->mode == TOG_VIEW_SPLIT_HRZN ||
1043 view->child->begin_x == 0) {
1044 ncols = COLS;
1046 view_fullscreen(view->child);
1047 if (view->child->focussed)
1048 show_panel(view->child->panel);
1049 else
1050 show_panel(view->panel);
1051 } else {
1052 ncols = view->child->begin_x;
1054 view_splitscreen(view->child);
1055 show_panel(view->child->panel);
1058 * XXX This is ugly and needs to be moved into the above
1059 * logic but "works" for now and my attempts at moving it
1060 * break either 'tab' or 'F' key maps in horizontal splits.
1062 if (hs) {
1063 err = view_splitscreen(view->child);
1064 if (err)
1065 return err;
1066 if (dif < 0) { /* top split decreased */
1067 err = offset_selection_down(view);
1068 if (err)
1069 return err;
1071 view_border(view);
1072 update_panels();
1073 doupdate();
1074 show_panel(view->child->panel);
1075 nlines = view->nlines;
1077 } else if (view->parent == NULL)
1078 ncols = COLS;
1080 if (view->resize && dif > 0) {
1081 err = view->resize(view, dif);
1082 if (err)
1083 return err;
1086 if (wresize(view->window, nlines, ncols) == ERR)
1087 return got_error_from_errno("wresize");
1088 if (replace_panel(view->panel, view->window) == ERR)
1089 return got_error_from_errno("replace_panel");
1090 wclear(view->window);
1092 view->nlines = nlines;
1093 view->ncols = ncols;
1094 view->lines = LINES;
1095 view->cols = COLS;
1097 return NULL;
1100 static const struct got_error *
1101 resize_log_view(struct tog_view *view, int increase)
1103 struct tog_log_view_state *s = &view->state.log;
1104 const struct got_error *err = NULL;
1105 int n = 0;
1107 if (s->selected_entry)
1108 n = s->selected_entry->idx + view->lines - s->selected;
1111 * Request commits to account for the increased
1112 * height so we have enough to populate the view.
1114 if (s->commits->ncommits < n) {
1115 view->nscrolled = n - s->commits->ncommits + increase + 1;
1116 err = request_log_commits(view);
1119 return err;
1122 static void
1123 view_adjust_offset(struct tog_view *view, int n)
1125 if (n == 0)
1126 return;
1128 if (view->parent && view->parent->offset) {
1129 if (view->parent->offset + n >= 0)
1130 view->parent->offset += n;
1131 else
1132 view->parent->offset = 0;
1133 } else if (view->offset) {
1134 if (view->offset - n >= 0)
1135 view->offset -= n;
1136 else
1137 view->offset = 0;
1141 static const struct got_error *
1142 view_resize_split(struct tog_view *view, int resize)
1144 const struct got_error *err = NULL;
1145 struct tog_view *v = NULL;
1147 if (view->parent)
1148 v = view->parent;
1149 else
1150 v = view;
1152 if (!v->child || !view_is_splitscreen(v->child))
1153 return NULL;
1155 v->resized = v->child->resized = resize; /* lock for resize event */
1157 if (view->mode == TOG_VIEW_SPLIT_HRZN) {
1158 if (v->child->resized_y)
1159 v->child->begin_y = v->child->resized_y;
1160 if (view->parent)
1161 v->child->begin_y -= resize;
1162 else
1163 v->child->begin_y += resize;
1164 if (v->child->begin_y < 3) {
1165 view->count = 0;
1166 v->child->begin_y = 3;
1167 } else if (v->child->begin_y > LINES - 1) {
1168 view->count = 0;
1169 v->child->begin_y = LINES - 1;
1171 v->ncols = COLS;
1172 v->child->ncols = COLS;
1173 view_adjust_offset(view, resize);
1174 err = view_init_hsplit(v, v->child->begin_y);
1175 if (err)
1176 return err;
1177 v->child->resized_y = v->child->begin_y;
1178 } else {
1179 if (v->child->resized_x)
1180 v->child->begin_x = v->child->resized_x;
1181 if (view->parent)
1182 v->child->begin_x -= resize;
1183 else
1184 v->child->begin_x += resize;
1185 if (v->child->begin_x < 11) {
1186 view->count = 0;
1187 v->child->begin_x = 11;
1188 } else if (v->child->begin_x > COLS - 1) {
1189 view->count = 0;
1190 v->child->begin_x = COLS - 1;
1192 v->child->resized_x = v->child->begin_x;
1195 v->child->mode = v->mode;
1196 v->child->nlines = v->lines - v->child->begin_y;
1197 v->child->ncols = v->cols - v->child->begin_x;
1198 v->focus_child = 1;
1200 err = view_fullscreen(v);
1201 if (err)
1202 return err;
1203 err = view_splitscreen(v->child);
1204 if (err)
1205 return err;
1207 if (v->mode == TOG_VIEW_SPLIT_HRZN) {
1208 err = offset_selection_down(v->child);
1209 if (err)
1210 return err;
1213 if (v->resize)
1214 err = v->resize(v, 0);
1215 else if (v->child->resize)
1216 err = v->child->resize(v->child, 0);
1218 v->resized = v->child->resized = 0;
1220 return err;
1223 static void
1224 view_transfer_size(struct tog_view *dst, struct tog_view *src)
1226 struct tog_view *v = src->child ? src->child : src;
1228 dst->resized_x = v->resized_x;
1229 dst->resized_y = v->resized_y;
1232 static const struct got_error *
1233 view_close_child(struct tog_view *view)
1235 const struct got_error *err = NULL;
1237 if (view->child == NULL)
1238 return NULL;
1240 err = view_close(view->child);
1241 view->child = NULL;
1242 return err;
1245 static const struct got_error *
1246 view_set_child(struct tog_view *view, struct tog_view *child)
1248 const struct got_error *err = NULL;
1250 view->child = child;
1251 child->parent = view;
1253 err = view_resize(view);
1254 if (err)
1255 return err;
1257 if (view->child->resized_x || view->child->resized_y)
1258 err = view_resize_split(view, 0);
1260 return err;
1263 static const struct got_error *view_dispatch_request(struct tog_view **,
1264 struct tog_view *, enum tog_view_type, int, int);
1266 static const struct got_error *
1267 view_request_new(struct tog_view **requested, struct tog_view *view,
1268 enum tog_view_type request)
1270 struct tog_view *new_view = NULL;
1271 const struct got_error *err;
1272 int y = 0, x = 0;
1274 *requested = NULL;
1276 if (view_is_parent_view(view) && request != TOG_VIEW_HELP)
1277 view_get_split(view, &y, &x);
1279 err = view_dispatch_request(&new_view, view, request, y, x);
1280 if (err)
1281 return err;
1283 if (view_is_parent_view(view) && view->mode == TOG_VIEW_SPLIT_HRZN &&
1284 request != TOG_VIEW_HELP) {
1285 err = view_init_hsplit(view, y);
1286 if (err)
1287 return err;
1290 view->focussed = 0;
1291 new_view->focussed = 1;
1292 new_view->mode = view->mode;
1293 new_view->nlines = request == TOG_VIEW_HELP ?
1294 view->lines : view->lines - y;
1296 if (view_is_parent_view(view) && request != TOG_VIEW_HELP) {
1297 view_transfer_size(new_view, view);
1298 err = view_close_child(view);
1299 if (err)
1300 return err;
1301 err = view_set_child(view, new_view);
1302 if (err)
1303 return err;
1304 view->focus_child = 1;
1305 } else
1306 *requested = new_view;
1308 return NULL;
1311 static void
1312 tog_resizeterm(void)
1314 int cols, lines;
1315 struct winsize size;
1317 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) < 0) {
1318 cols = 80; /* Default */
1319 lines = 24;
1320 } else {
1321 cols = size.ws_col;
1322 lines = size.ws_row;
1324 resize_term(lines, cols);
1327 static const struct got_error *
1328 view_search_start(struct tog_view *view, int fast_refresh)
1330 const struct got_error *err = NULL;
1331 struct tog_view *v = view;
1332 char pattern[1024];
1333 int ret;
1335 if (view->search_started) {
1336 regfree(&view->regex);
1337 view->searching = 0;
1338 memset(&view->regmatch, 0, sizeof(view->regmatch));
1340 view->search_started = 0;
1342 if (view->nlines < 1)
1343 return NULL;
1345 if (view_is_hsplit_top(view))
1346 v = view->child;
1347 else if (view->mode == TOG_VIEW_SPLIT_VERT && view->parent)
1348 v = view->parent;
1350 if (tog_io.input_str != NULL) {
1351 if (strlcpy(pattern, tog_io.input_str, sizeof(pattern)) >=
1352 sizeof(pattern))
1353 return got_error(GOT_ERR_NO_SPACE);
1354 } else {
1355 mvwaddstr(v->window, v->nlines - 1, 0, "/");
1356 wclrtoeol(v->window);
1357 nodelay(v->window, FALSE); /* block for search term input */
1358 nocbreak();
1359 echo();
1360 ret = wgetnstr(v->window, pattern, sizeof(pattern));
1361 wrefresh(v->window);
1362 cbreak();
1363 noecho();
1364 nodelay(v->window, TRUE);
1365 if (!fast_refresh && !using_mock_io)
1366 halfdelay(10);
1367 if (ret == ERR)
1368 return NULL;
1371 if (regcomp(&view->regex, pattern, REG_EXTENDED | REG_NEWLINE) == 0) {
1372 err = view->search_start(view);
1373 if (err) {
1374 regfree(&view->regex);
1375 return err;
1377 view->search_started = 1;
1378 view->searching = TOG_SEARCH_FORWARD;
1379 view->search_next_done = 0;
1380 view->search_next(view);
1383 return NULL;
1386 /* Switch split mode. If view is a parent or child, draw the new splitscreen. */
1387 static const struct got_error *
1388 switch_split(struct tog_view *view)
1390 const struct got_error *err = NULL;
1391 struct tog_view *v = NULL;
1393 if (view->parent)
1394 v = view->parent;
1395 else
1396 v = view;
1398 if (v->mode == TOG_VIEW_SPLIT_HRZN)
1399 v->mode = TOG_VIEW_SPLIT_VERT;
1400 else
1401 v->mode = TOG_VIEW_SPLIT_HRZN;
1403 if (!v->child)
1404 return NULL;
1405 else if (v->mode == TOG_VIEW_SPLIT_VERT && v->cols < 120)
1406 v->mode = TOG_VIEW_SPLIT_NONE;
1408 view_get_split(v, &v->child->begin_y, &v->child->begin_x);
1409 if (v->mode == TOG_VIEW_SPLIT_HRZN && v->child->resized_y)
1410 v->child->begin_y = v->child->resized_y;
1411 else if (v->mode == TOG_VIEW_SPLIT_VERT && v->child->resized_x)
1412 v->child->begin_x = v->child->resized_x;
1415 if (v->mode == TOG_VIEW_SPLIT_HRZN) {
1416 v->ncols = COLS;
1417 v->child->ncols = COLS;
1418 v->child->nscrolled = LINES - v->child->nlines;
1420 err = view_init_hsplit(v, v->child->begin_y);
1421 if (err)
1422 return err;
1424 v->child->mode = v->mode;
1425 v->child->nlines = v->lines - v->child->begin_y;
1426 v->focus_child = 1;
1428 err = view_fullscreen(v);
1429 if (err)
1430 return err;
1431 err = view_splitscreen(v->child);
1432 if (err)
1433 return err;
1435 if (v->mode == TOG_VIEW_SPLIT_NONE)
1436 v->mode = TOG_VIEW_SPLIT_VERT;
1437 if (v->mode == TOG_VIEW_SPLIT_HRZN) {
1438 err = offset_selection_down(v);
1439 if (err)
1440 return err;
1441 err = offset_selection_down(v->child);
1442 if (err)
1443 return err;
1444 } else {
1445 offset_selection_up(v);
1446 offset_selection_up(v->child);
1448 if (v->resize)
1449 err = v->resize(v, 0);
1450 else if (v->child->resize)
1451 err = v->child->resize(v->child, 0);
1453 return err;
1457 * Strip trailing whitespace from str starting at byte *n;
1458 * if *n < 0, use strlen(str). Return new str length in *n.
1460 static void
1461 strip_trailing_ws(char *str, int *n)
1463 size_t x = *n;
1465 if (str == NULL || *str == '\0')
1466 return;
1468 if (x < 0)
1469 x = strlen(str);
1471 while (x-- > 0 && isspace((unsigned char)str[x]))
1472 str[x] = '\0';
1474 *n = x + 1;
1478 * Extract visible substring of line y from the curses screen
1479 * and strip trailing whitespace. If vline is set, overwrite
1480 * line[vline] with '|' because the ACS_VLINE character is
1481 * written out as 'x'. Write the line to file f.
1483 static const struct got_error *
1484 view_write_line(FILE *f, int y, int vline)
1486 char line[COLS * MB_LEN_MAX]; /* allow for multibyte chars */
1487 int r, w;
1489 r = mvwinnstr(curscr, y, 0, line, sizeof(line));
1490 if (r == ERR)
1491 return got_error_fmt(GOT_ERR_RANGE,
1492 "failed to extract line %d", y);
1495 * In some views, lines are padded with blanks to COLS width.
1496 * Strip them so we can diff without the -b flag when testing.
1498 strip_trailing_ws(line, &r);
1500 if (vline > 0)
1501 line[vline] = '|';
1503 w = fprintf(f, "%s\n", line);
1504 if (w != r + 1) /* \n */
1505 return got_ferror(f, GOT_ERR_IO);
1507 return NULL;
1511 * Capture the visible curses screen by writing each line to the
1512 * file at the path set via the TOG_SCR_DUMP environment variable.
1514 static const struct got_error *
1515 screendump(struct tog_view *view)
1517 const struct got_error *err;
1518 int i;
1520 err = got_opentemp_truncate(tog_io.sdump);
1521 if (err)
1522 return err;
1524 if ((view->child && view->child->begin_x) ||
1525 (view->parent && view->begin_x)) {
1526 int ncols = view->child ? view->ncols : view->parent->ncols;
1528 /* vertical splitscreen */
1529 for (i = 0; i < view->nlines; ++i) {
1530 err = view_write_line(tog_io.sdump, i, ncols - 1);
1531 if (err)
1532 goto done;
1534 } else {
1535 int hline = 0;
1537 /* fullscreen or horizontal splitscreen */
1538 if ((view->child && view->child->begin_y) ||
1539 (view->parent && view->begin_y)) /* hsplit */
1540 hline = view->child ?
1541 view->child->begin_y : view->begin_y;
1543 for (i = 0; i < view->lines; i++) {
1544 if (hline && i == hline - 1) {
1545 int c;
1547 /* ACS_HLINE writes out as 'q', overwrite it */
1548 for (c = 0; c < view->cols; ++c)
1549 fputc('-', tog_io.sdump);
1550 fputc('\n', tog_io.sdump);
1551 continue;
1554 err = view_write_line(tog_io.sdump, i, 0);
1555 if (err)
1556 goto done;
1560 done:
1561 return err;
1565 * Compute view->count from numeric input. Assign total to view->count and
1566 * return first non-numeric key entered.
1568 static int
1569 get_compound_key(struct tog_view *view, int c)
1571 struct tog_view *v = view;
1572 int x, n = 0;
1574 if (view_is_hsplit_top(view))
1575 v = view->child;
1576 else if (view->mode == TOG_VIEW_SPLIT_VERT && view->parent)
1577 v = view->parent;
1579 view->count = 0;
1580 cbreak(); /* block for input */
1581 nodelay(view->window, FALSE);
1582 wmove(v->window, v->nlines - 1, 0);
1583 wclrtoeol(v->window);
1584 waddch(v->window, ':');
1586 do {
1587 x = getcurx(v->window);
1588 if (x != ERR && x < view->ncols) {
1589 waddch(v->window, c);
1590 wrefresh(v->window);
1594 * Don't overflow. Max valid request should be the greatest
1595 * between the longest and total lines; cap at 10 million.
1597 if (n >= 9999999)
1598 n = 9999999;
1599 else
1600 n = n * 10 + (c - '0');
1601 } while (((c = wgetch(view->window))) >= '0' && c <= '9' && c != ERR);
1603 if (c == 'G' || c == 'g') { /* nG key map */
1604 view->gline = view->hiline = n;
1605 n = 0;
1606 c = 0;
1609 /* Massage excessive or inapplicable values at the input handler. */
1610 view->count = n;
1612 return c;
1615 static void
1616 action_report(struct tog_view *view)
1618 struct tog_view *v = view;
1620 if (view_is_hsplit_top(view))
1621 v = view->child;
1622 else if (view->mode == TOG_VIEW_SPLIT_VERT && view->parent)
1623 v = view->parent;
1625 wmove(v->window, v->nlines - 1, 0);
1626 wclrtoeol(v->window);
1627 wprintw(v->window, ":%s", view->action);
1628 wrefresh(v->window);
1631 * Clear action status report. Only clear in blame view
1632 * once annotating is complete, otherwise it's too fast.
1633 * In diff view, let its state control view->action lifetime.
1635 if (view->type == TOG_VIEW_BLAME) {
1636 if (view->state.blame.blame_complete)
1637 view->action = NULL;
1638 } else if (view->type == TOG_VIEW_DIFF) {
1639 view->action = view->state.diff.action;
1640 } else
1641 view->action = NULL;
1645 * Read the next line from the test script and assign
1646 * key instruction to *ch. If at EOF, set the *done flag.
1648 static const struct got_error *
1649 tog_read_script_key(FILE *script, struct tog_view *view, int *ch, int *done)
1651 const struct got_error *err = NULL;
1652 char *line = NULL;
1653 size_t linesz = 0;
1654 ssize_t n;
1657 if (view->count && --view->count) {
1658 *ch = view->ch;
1659 return NULL;
1660 } else
1661 *ch = -1;
1663 if ((n = getline(&line, &linesz, script)) == -1) {
1664 if (feof(script)) {
1665 *done = 1;
1666 goto done;
1667 } else {
1668 err = got_ferror(script, GOT_ERR_IO);
1669 goto done;
1673 if (strncasecmp(line, "WAIT_FOR_UI", 11) == 0)
1674 tog_io.wait_for_ui = 1;
1675 else if (strncasecmp(line, "KEY_ENTER", 9) == 0)
1676 *ch = KEY_ENTER;
1677 else if (strncasecmp(line, "KEY_RIGHT", 9) == 0)
1678 *ch = KEY_RIGHT;
1679 else if (strncasecmp(line, "KEY_LEFT", 8) == 0)
1680 *ch = KEY_LEFT;
1681 else if (strncasecmp(line, "KEY_DOWN", 8) == 0)
1682 *ch = KEY_DOWN;
1683 else if (strncasecmp(line, "KEY_UP", 6) == 0)
1684 *ch = KEY_UP;
1685 else if (strncasecmp(line, "TAB", 3) == 0)
1686 *ch = '\t';
1687 else if (strncasecmp(line, "SCREENDUMP", 10) == 0)
1688 *ch = TOG_KEY_SCRDUMP;
1689 else if (isdigit((unsigned char)*line)) {
1690 char *t = line;
1692 while (isdigit((unsigned char)*t))
1693 ++t;
1694 view->ch = *ch = *t;
1695 *t = '\0';
1696 /* ignore error, view->count is 0 if instruction is invalid */
1697 view->count = strtonum(line, 0, INT_MAX, NULL);
1698 } else {
1699 *ch = *line;
1700 if (n > 2 && (*ch == '/' || *ch == '&')) {
1701 /* skip leading keymap and trim trailing newline */
1702 tog_io.input_str = strndup(line + 1, n - 2);
1703 if (tog_io.input_str == NULL) {
1704 err = got_error_from_errno("strndup");
1705 goto done;
1710 done:
1711 free(line);
1712 return err;
1715 static void
1716 log_mark_clear(struct tog_log_view_state *s)
1718 s->marked_entry = NULL;
1721 static const struct got_error *
1722 view_input(struct tog_view **new, int *done, struct tog_view *view,
1723 struct tog_view_list_head *views, int fast_refresh)
1725 const struct got_error *err = NULL;
1726 struct tog_view *v;
1727 int ch, errcode;
1729 *new = NULL;
1731 if (view->action)
1732 action_report(view);
1734 /* Clear "no matches" indicator. */
1735 if (view->search_next_done == TOG_SEARCH_NO_MORE ||
1736 view->search_next_done == TOG_SEARCH_HAVE_NONE) {
1737 view->search_next_done = TOG_SEARCH_HAVE_MORE;
1738 view->count = 0;
1741 if (view->searching && !view->search_next_done) {
1742 errcode = pthread_mutex_unlock(&tog_mutex);
1743 if (errcode)
1744 return got_error_set_errno(errcode,
1745 "pthread_mutex_unlock");
1746 sched_yield();
1747 errcode = pthread_mutex_lock(&tog_mutex);
1748 if (errcode)
1749 return got_error_set_errno(errcode,
1750 "pthread_mutex_lock");
1751 view->search_next(view);
1752 return NULL;
1755 /* Allow threads to make progress while we are waiting for input. */
1756 errcode = pthread_mutex_unlock(&tog_mutex);
1757 if (errcode)
1758 return got_error_set_errno(errcode, "pthread_mutex_unlock");
1760 if (using_mock_io) {
1761 err = tog_read_script_key(tog_io.f, view, &ch, done);
1762 if (err) {
1763 errcode = pthread_mutex_lock(&tog_mutex);
1764 return err;
1766 } else if (view->count && --view->count) {
1767 cbreak();
1768 nodelay(view->window, TRUE);
1769 ch = wgetch(view->window);
1770 /* let C-g or backspace abort unfinished count */
1771 if (ch == CTRL('g') || ch == KEY_BACKSPACE)
1772 view->count = 0;
1773 else
1774 ch = view->ch;
1775 } else {
1776 ch = wgetch(view->window);
1777 if (ch >= '1' && ch <= '9')
1778 view->ch = ch = get_compound_key(view, ch);
1780 if (view->hiline && ch != ERR && ch != 0)
1781 view->hiline = 0; /* key pressed, clear line highlight */
1782 nodelay(view->window, TRUE);
1783 errcode = pthread_mutex_lock(&tog_mutex);
1784 if (errcode)
1785 return got_error_set_errno(errcode, "pthread_mutex_lock");
1787 if (tog_sigwinch_received || tog_sigcont_received) {
1788 tog_resizeterm();
1789 tog_sigwinch_received = 0;
1790 tog_sigcont_received = 0;
1791 TAILQ_FOREACH(v, views, entry) {
1792 err = view_resize(v);
1793 if (err)
1794 return err;
1795 err = v->input(new, v, KEY_RESIZE);
1796 if (err)
1797 return err;
1798 if (v->child) {
1799 err = view_resize(v->child);
1800 if (err)
1801 return err;
1802 err = v->child->input(new, v->child,
1803 KEY_RESIZE);
1804 if (err)
1805 return err;
1806 if (v->child->resized_x || v->child->resized_y) {
1807 err = view_resize_split(v, 0);
1808 if (err)
1809 return err;
1815 switch (ch) {
1816 case '?':
1817 case 'H':
1818 case KEY_F(1):
1819 if (view->type == TOG_VIEW_HELP)
1820 err = view->reset(view);
1821 else
1822 err = view_request_new(new, view, TOG_VIEW_HELP);
1823 break;
1824 case '\t':
1825 view->count = 0;
1826 if (view->child) {
1827 view->focussed = 0;
1828 view->child->focussed = 1;
1829 view->focus_child = 1;
1830 } else if (view->parent) {
1831 view->focussed = 0;
1832 view->parent->focussed = 1;
1833 view->parent->focus_child = 0;
1834 if (!view_is_splitscreen(view)) {
1835 if (view->parent->resize) {
1836 err = view->parent->resize(view->parent,
1838 if (err)
1839 return err;
1841 offset_selection_up(view->parent);
1842 err = view_fullscreen(view->parent);
1843 if (err)
1844 return err;
1847 break;
1848 case 'q':
1849 if (view->parent != NULL) {
1850 if (view->parent->type == TOG_VIEW_LOG)
1851 log_mark_clear(&view->parent->state.log);
1853 if (view->mode == TOG_VIEW_SPLIT_HRZN) {
1854 if (view->parent->resize) {
1856 * Might need more commits
1857 * to fill fullscreen.
1859 err = view->parent->resize(
1860 view->parent, 0);
1861 if (err)
1862 break;
1864 offset_selection_up(view->parent);
1867 err = view->input(new, view, ch);
1868 view->dying = 1;
1869 break;
1870 case 'Q':
1871 *done = 1;
1872 break;
1873 case 'F':
1874 view->count = 0;
1875 if (view_is_parent_view(view)) {
1876 if (view->child == NULL)
1877 break;
1878 if (view_is_splitscreen(view->child)) {
1879 view->focussed = 0;
1880 view->child->focussed = 1;
1881 err = view_fullscreen(view->child);
1882 } else {
1883 err = view_splitscreen(view->child);
1884 if (!err)
1885 err = view_resize_split(view, 0);
1887 if (err)
1888 break;
1889 err = view->child->input(new, view->child,
1890 KEY_RESIZE);
1891 } else {
1892 if (view_is_splitscreen(view)) {
1893 view->parent->focussed = 0;
1894 view->focussed = 1;
1895 err = view_fullscreen(view);
1896 } else {
1897 err = view_splitscreen(view);
1898 if (!err && view->mode != TOG_VIEW_SPLIT_HRZN)
1899 err = view_resize(view->parent);
1900 if (!err)
1901 err = view_resize_split(view, 0);
1903 if (err)
1904 break;
1905 err = view->input(new, view, KEY_RESIZE);
1907 if (err)
1908 break;
1909 if (view->resize) {
1910 err = view->resize(view, 0);
1911 if (err)
1912 break;
1914 if (view->parent) {
1915 if (view->parent->resize) {
1916 err = view->parent->resize(view->parent, 0);
1917 if (err != NULL)
1918 break;
1920 err = offset_selection_down(view->parent);
1921 if (err != NULL)
1922 break;
1924 err = offset_selection_down(view);
1925 break;
1926 case 'S':
1927 view->count = 0;
1928 err = switch_split(view);
1929 break;
1930 case '-':
1931 err = view_resize_split(view, -1);
1932 break;
1933 case '+':
1934 err = view_resize_split(view, 1);
1935 break;
1936 case KEY_RESIZE:
1937 break;
1938 case '/':
1939 view->count = 0;
1940 if (view->search_start)
1941 view_search_start(view, fast_refresh);
1942 else
1943 err = view->input(new, view, ch);
1944 break;
1945 case 'N':
1946 case 'n':
1947 if (view->search_started && view->search_next) {
1948 view->searching = (ch == 'n' ?
1949 TOG_SEARCH_FORWARD : TOG_SEARCH_BACKWARD);
1950 view->search_next_done = 0;
1951 view->search_next(view);
1952 } else
1953 err = view->input(new, view, ch);
1954 break;
1955 case 'A':
1956 if (tog_diff_algo == GOT_DIFF_ALGORITHM_MYERS) {
1957 tog_diff_algo = GOT_DIFF_ALGORITHM_PATIENCE;
1958 view->action = "Patience diff algorithm";
1959 } else {
1960 tog_diff_algo = GOT_DIFF_ALGORITHM_MYERS;
1961 view->action = "Myers diff algorithm";
1963 TAILQ_FOREACH(v, views, entry) {
1964 if (v->reset) {
1965 err = v->reset(v);
1966 if (err)
1967 return err;
1969 if (v->child && v->child->reset) {
1970 err = v->child->reset(v->child);
1971 if (err)
1972 return err;
1975 break;
1976 case TOG_KEY_SCRDUMP:
1977 err = screendump(view);
1978 break;
1979 default:
1980 err = view->input(new, view, ch);
1981 break;
1984 return err;
1987 static int
1988 view_needs_focus_indication(struct tog_view *view)
1990 if (view_is_parent_view(view)) {
1991 if (view->child == NULL || view->child->focussed)
1992 return 0;
1993 if (!view_is_splitscreen(view->child))
1994 return 0;
1995 } else if (!view_is_splitscreen(view))
1996 return 0;
1998 return view->focussed;
2001 static const struct got_error *
2002 tog_io_close(void)
2004 const struct got_error *err = NULL;
2006 if (tog_io.cin && fclose(tog_io.cin) == EOF)
2007 err = got_ferror(tog_io.cin, GOT_ERR_IO);
2008 if (tog_io.cout && fclose(tog_io.cout) == EOF && err == NULL)
2009 err = got_ferror(tog_io.cout, GOT_ERR_IO);
2010 if (tog_io.f && fclose(tog_io.f) == EOF && err == NULL)
2011 err = got_ferror(tog_io.f, GOT_ERR_IO);
2012 if (tog_io.sdump && fclose(tog_io.sdump) == EOF && err == NULL)
2013 err = got_ferror(tog_io.sdump, GOT_ERR_IO);
2014 if (tog_io.input_str != NULL)
2015 free(tog_io.input_str);
2017 return err;
2020 static const struct got_error *
2021 view_loop(struct tog_view *view)
2023 const struct got_error *err = NULL;
2024 struct tog_view_list_head views;
2025 struct tog_view *new_view;
2026 char *mode;
2027 int fast_refresh = 10;
2028 int done = 0, errcode;
2030 mode = getenv("TOG_VIEW_SPLIT_MODE");
2031 if (!mode || !(*mode == 'h' || *mode == 'H'))
2032 view->mode = TOG_VIEW_SPLIT_VERT;
2033 else
2034 view->mode = TOG_VIEW_SPLIT_HRZN;
2036 errcode = pthread_mutex_lock(&tog_mutex);
2037 if (errcode)
2038 return got_error_set_errno(errcode, "pthread_mutex_lock");
2040 TAILQ_INIT(&views);
2041 TAILQ_INSERT_HEAD(&views, view, entry);
2043 view->focussed = 1;
2044 err = view->show(view);
2045 if (err)
2046 return err;
2047 update_panels();
2048 doupdate();
2049 while (!TAILQ_EMPTY(&views) && !done && !tog_thread_error &&
2050 !tog_fatal_signal_received()) {
2051 /* Refresh fast during initialization, then become slower. */
2052 if (fast_refresh && --fast_refresh == 0 && !using_mock_io)
2053 halfdelay(10); /* switch to once per second */
2055 err = view_input(&new_view, &done, view, &views, fast_refresh);
2056 if (err)
2057 break;
2059 if (view->dying && view == TAILQ_FIRST(&views) &&
2060 TAILQ_NEXT(view, entry) == NULL)
2061 done = 1;
2062 if (done) {
2063 struct tog_view *v;
2066 * When we quit, scroll the screen up a single line
2067 * so we don't lose any information.
2069 TAILQ_FOREACH(v, &views, entry) {
2070 wmove(v->window, 0, 0);
2071 wdeleteln(v->window);
2072 wnoutrefresh(v->window);
2073 if (v->child && !view_is_fullscreen(v)) {
2074 wmove(v->child->window, 0, 0);
2075 wdeleteln(v->child->window);
2076 wnoutrefresh(v->child->window);
2079 doupdate();
2082 if (view->dying) {
2083 struct tog_view *v, *prev = NULL;
2085 if (view_is_parent_view(view))
2086 prev = TAILQ_PREV(view, tog_view_list_head,
2087 entry);
2088 else if (view->parent)
2089 prev = view->parent;
2091 if (view->parent) {
2092 view->parent->child = NULL;
2093 view->parent->focus_child = 0;
2094 /* Restore fullscreen line height. */
2095 view->parent->nlines = view->parent->lines;
2096 err = view_resize(view->parent);
2097 if (err)
2098 break;
2099 /* Make resized splits persist. */
2100 view_transfer_size(view->parent, view);
2101 } else
2102 TAILQ_REMOVE(&views, view, entry);
2104 err = view_close(view);
2105 if (err)
2106 goto done;
2108 view = NULL;
2109 TAILQ_FOREACH(v, &views, entry) {
2110 if (v->focussed)
2111 break;
2113 if (view == NULL && new_view == NULL) {
2114 /* No view has focus. Try to pick one. */
2115 if (prev)
2116 view = prev;
2117 else if (!TAILQ_EMPTY(&views)) {
2118 view = TAILQ_LAST(&views,
2119 tog_view_list_head);
2121 if (view) {
2122 if (view->focus_child) {
2123 view->child->focussed = 1;
2124 view = view->child;
2125 } else
2126 view->focussed = 1;
2130 if (new_view) {
2131 struct tog_view *v, *t;
2132 /* Only allow one parent view per type. */
2133 TAILQ_FOREACH_SAFE(v, &views, entry, t) {
2134 if (v->type != new_view->type)
2135 continue;
2136 TAILQ_REMOVE(&views, v, entry);
2137 err = view_close(v);
2138 if (err)
2139 goto done;
2140 break;
2142 TAILQ_INSERT_TAIL(&views, new_view, entry);
2143 view = new_view;
2145 if (view && !done) {
2146 if (view_is_parent_view(view)) {
2147 if (view->child && view->child->focussed)
2148 view = view->child;
2149 } else {
2150 if (view->parent && view->parent->focussed)
2151 view = view->parent;
2153 show_panel(view->panel);
2154 if (view->child && view_is_splitscreen(view->child))
2155 show_panel(view->child->panel);
2156 if (view->parent && view_is_splitscreen(view)) {
2157 err = view->parent->show(view->parent);
2158 if (err)
2159 goto done;
2161 err = view->show(view);
2162 if (err)
2163 goto done;
2164 if (view->child) {
2165 err = view->child->show(view->child);
2166 if (err)
2167 goto done;
2169 update_panels();
2170 doupdate();
2173 done:
2174 while (!TAILQ_EMPTY(&views)) {
2175 const struct got_error *close_err;
2176 view = TAILQ_FIRST(&views);
2177 TAILQ_REMOVE(&views, view, entry);
2178 close_err = view_close(view);
2179 if (close_err && err == NULL)
2180 err = close_err;
2183 errcode = pthread_mutex_unlock(&tog_mutex);
2184 if (errcode && err == NULL)
2185 err = got_error_set_errno(errcode, "pthread_mutex_unlock");
2187 return err;
2190 __dead static void
2191 usage_log(void)
2193 endwin();
2194 fprintf(stderr,
2195 "usage: %s log [-b] [-c commit] [-r repository-path] [path]\n",
2196 getprogname());
2197 exit(1);
2200 /* Create newly allocated wide-character string equivalent to a byte string. */
2201 static const struct got_error *
2202 mbs2ws(wchar_t **ws, size_t *wlen, const char *s)
2204 char *vis = NULL;
2205 const struct got_error *err = NULL;
2207 *ws = NULL;
2208 *wlen = mbstowcs(NULL, s, 0);
2209 if (*wlen == (size_t)-1) {
2210 int vislen;
2211 if (errno != EILSEQ)
2212 return got_error_from_errno("mbstowcs");
2214 /* byte string invalid in current encoding; try to "fix" it */
2215 err = got_mbsavis(&vis, &vislen, s);
2216 if (err)
2217 return err;
2218 *wlen = mbstowcs(NULL, vis, 0);
2219 if (*wlen == (size_t)-1) {
2220 err = got_error_from_errno("mbstowcs"); /* give up */
2221 goto done;
2225 *ws = calloc(*wlen + 1, sizeof(**ws));
2226 if (*ws == NULL) {
2227 err = got_error_from_errno("calloc");
2228 goto done;
2231 if (mbstowcs(*ws, vis ? vis : s, *wlen) != *wlen)
2232 err = got_error_from_errno("mbstowcs");
2233 done:
2234 free(vis);
2235 if (err) {
2236 free(*ws);
2237 *ws = NULL;
2238 *wlen = 0;
2240 return err;
2243 static const struct got_error *
2244 expand_tab(char **ptr, const char *src)
2246 char *dst;
2247 size_t len, n, idx = 0, sz = 0;
2249 *ptr = NULL;
2250 n = len = strlen(src);
2251 dst = malloc(n + 1);
2252 if (dst == NULL)
2253 return got_error_from_errno("malloc");
2255 while (idx < len && src[idx]) {
2256 const char c = src[idx];
2258 if (c == '\t') {
2259 size_t nb = TABSIZE - sz % TABSIZE;
2260 char *p;
2262 p = realloc(dst, n + nb);
2263 if (p == NULL) {
2264 free(dst);
2265 return got_error_from_errno("realloc");
2268 dst = p;
2269 n += nb;
2270 memset(dst + sz, ' ', nb);
2271 sz += nb;
2272 } else
2273 dst[sz++] = src[idx];
2274 ++idx;
2277 dst[sz] = '\0';
2278 *ptr = dst;
2279 return NULL;
2283 * Advance at most n columns from wline starting at offset off.
2284 * Return the index to the first character after the span operation.
2285 * Return the combined column width of all spanned wide characters in
2286 * *rcol.
2288 static int
2289 span_wline(int *rcol, int off, wchar_t *wline, int n, int col_tab_align)
2291 int width, i, cols = 0;
2293 if (n == 0) {
2294 *rcol = cols;
2295 return off;
2298 for (i = off; wline[i] != L'\0'; ++i) {
2299 if (wline[i] == L'\t')
2300 width = TABSIZE - ((cols + col_tab_align) % TABSIZE);
2301 else
2302 width = wcwidth(wline[i]);
2304 if (width == -1) {
2305 width = 1;
2306 wline[i] = L'.';
2309 if (cols + width > n)
2310 break;
2311 cols += width;
2314 *rcol = cols;
2315 return i;
2319 * Format a line for display, ensuring that it won't overflow a width limit.
2320 * With scrolling, the width returned refers to the scrolled version of the
2321 * line, which starts at (*wlinep)[*scrollxp]. The caller must free *wlinep.
2323 static const struct got_error *
2324 format_line(wchar_t **wlinep, int *widthp, int *scrollxp,
2325 const char *line, int nscroll, int wlimit, int col_tab_align, int expand)
2327 const struct got_error *err = NULL;
2328 int cols;
2329 wchar_t *wline = NULL;
2330 char *exstr = NULL;
2331 size_t wlen;
2332 int i, scrollx;
2334 *wlinep = NULL;
2335 *widthp = 0;
2337 if (expand) {
2338 err = expand_tab(&exstr, line);
2339 if (err)
2340 return err;
2343 err = mbs2ws(&wline, &wlen, expand ? exstr : line);
2344 free(exstr);
2345 if (err)
2346 return err;
2348 if (wlen > 0 && wline[wlen - 1] == L'\n') {
2349 wline[wlen - 1] = L'\0';
2350 wlen--;
2352 if (wlen > 0 && wline[wlen - 1] == L'\r') {
2353 wline[wlen - 1] = L'\0';
2354 wlen--;
2357 scrollx = span_wline(&cols, 0, wline, nscroll, col_tab_align);
2359 i = span_wline(&cols, scrollx, wline, wlimit, col_tab_align);
2360 wline[i] = L'\0';
2362 if (widthp)
2363 *widthp = cols;
2364 if (scrollxp)
2365 *scrollxp = scrollx;
2366 if (err)
2367 free(wline);
2368 else
2369 *wlinep = wline;
2370 return err;
2373 static const struct got_error*
2374 build_refs_str(char **refs_str, struct got_reflist_head *refs,
2375 struct got_object_id *id, struct got_repository *repo)
2377 static const struct got_error *err = NULL;
2378 struct got_reflist_entry *re;
2379 char *s;
2380 const char *name;
2382 *refs_str = NULL;
2384 if (refs == NULL)
2385 return NULL;
2387 TAILQ_FOREACH(re, refs, entry) {
2388 struct got_tag_object *tag = NULL;
2389 struct got_object_id *ref_id;
2390 int cmp;
2392 name = got_ref_get_name(re->ref);
2393 if (strcmp(name, GOT_REF_HEAD) == 0)
2394 continue;
2395 if (strncmp(name, "refs/", 5) == 0)
2396 name += 5;
2397 if (strncmp(name, "got/", 4) == 0)
2398 continue;
2399 if (strncmp(name, "heads/", 6) == 0)
2400 name += 6;
2401 if (strncmp(name, "remotes/", 8) == 0) {
2402 name += 8;
2403 s = strstr(name, "/" GOT_REF_HEAD);
2404 if (s != NULL && strcmp(s, "/" GOT_REF_HEAD) == 0)
2405 continue;
2407 err = got_ref_resolve(&ref_id, repo, re->ref);
2408 if (err)
2409 break;
2410 if (strncmp(name, "tags/", 5) == 0) {
2411 err = got_object_open_as_tag(&tag, repo, ref_id);
2412 if (err) {
2413 if (err->code != GOT_ERR_OBJ_TYPE) {
2414 free(ref_id);
2415 break;
2417 /* Ref points at something other than a tag. */
2418 err = NULL;
2419 tag = NULL;
2422 cmp = got_object_id_cmp(tag ?
2423 got_object_tag_get_object_id(tag) : ref_id, id);
2424 free(ref_id);
2425 if (tag)
2426 got_object_tag_close(tag);
2427 if (cmp != 0)
2428 continue;
2429 s = *refs_str;
2430 if (asprintf(refs_str, "%s%s%s", s ? s : "",
2431 s ? ", " : "", name) == -1) {
2432 err = got_error_from_errno("asprintf");
2433 free(s);
2434 *refs_str = NULL;
2435 break;
2437 free(s);
2440 return err;
2443 static const struct got_error *
2444 format_author(wchar_t **wauthor, int *author_width, char *author, int limit,
2445 int col_tab_align)
2447 char *smallerthan;
2449 smallerthan = strchr(author, '<');
2450 if (smallerthan && smallerthan[1] != '\0')
2451 author = smallerthan + 1;
2452 author[strcspn(author, "@>")] = '\0';
2453 return format_line(wauthor, author_width, NULL, author, 0, limit,
2454 col_tab_align, 0);
2457 static const struct got_error *
2458 draw_commit_marker(struct tog_view *view, char c)
2460 struct tog_color *tc;
2462 if (view->type != TOG_VIEW_LOG)
2463 return got_error_msg(GOT_ERR_NOT_IMPL, "view not supported");
2465 tc = get_color(&view->state.log.colors, TOG_COLOR_COMMIT);
2466 if (tc != NULL)
2467 wattr_on(view->window, COLOR_PAIR(tc->colorpair), NULL);
2468 if (waddch(view->window, c) == ERR)
2469 return got_error_msg(GOT_ERR_IO, "waddch");
2470 if (tc != NULL)
2471 wattr_off(view->window, COLOR_PAIR(tc->colorpair), NULL);
2473 return NULL;
2476 static const struct got_error *
2477 draw_commit(struct tog_view *view, struct commit_queue_entry *entry,
2478 const size_t date_display_cols, int author_display_cols)
2480 struct tog_log_view_state *s = &view->state.log;
2481 const struct got_error *err = NULL;
2482 struct got_commit_object *commit = entry->commit;
2483 struct got_object_id *id = entry->id;
2484 char datebuf[12]; /* YYYY-MM-DD + SPACE + NUL */
2485 char *refs_str = NULL;
2486 char *logmsg0 = NULL, *logmsg = NULL;
2487 char *author = NULL;
2488 wchar_t *wrefstr = NULL, *wlogmsg = NULL, *wauthor = NULL;
2489 int author_width, refstr_width, logmsg_width;
2490 char *newline, *line = NULL;
2491 int col, limit, scrollx, logmsg_x;
2492 const int avail = view->ncols, marker_column = author_display_cols + 1;
2493 struct tm tm;
2494 time_t committer_time;
2495 struct tog_color *tc;
2496 struct got_reflist_head *refs;
2498 if (tog_base_commit.id != NULL && tog_base_commit.idx == -1 &&
2499 got_object_id_cmp(id, tog_base_commit.id) == 0)
2500 tog_base_commit.idx = entry->idx;
2501 if (tog_io.wait_for_ui && s->thread_args.need_commit_marker) {
2502 int rc;
2504 rc = pthread_cond_wait(&s->thread_args.log_loaded, &tog_mutex);
2505 if (rc)
2506 return got_error_set_errno(rc, "pthread_cond_wait");
2509 committer_time = got_object_commit_get_committer_time(commit);
2510 if (gmtime_r(&committer_time, &tm) == NULL)
2511 return got_error_from_errno("gmtime_r");
2512 if (strftime(datebuf, sizeof(datebuf), "%F ", &tm) == 0)
2513 return got_error(GOT_ERR_NO_SPACE);
2515 if (avail <= date_display_cols)
2516 limit = MIN(sizeof(datebuf) - 1, avail);
2517 else
2518 limit = MIN(date_display_cols, sizeof(datebuf) - 1);
2519 tc = get_color(&s->colors, TOG_COLOR_DATE);
2520 if (tc)
2521 wattr_on(view->window,
2522 COLOR_PAIR(tc->colorpair), NULL);
2523 waddnstr(view->window, datebuf, limit);
2524 if (tc)
2525 wattr_off(view->window,
2526 COLOR_PAIR(tc->colorpair), NULL);
2527 col = limit;
2528 if (col > avail)
2529 goto done;
2531 if (avail >= 120) {
2532 char *id_str;
2533 err = got_object_id_str(&id_str, id);
2534 if (err)
2535 goto done;
2536 tc = get_color(&s->colors, TOG_COLOR_COMMIT);
2537 if (tc)
2538 wattr_on(view->window,
2539 COLOR_PAIR(tc->colorpair), NULL);
2540 wprintw(view->window, "%.8s ", id_str);
2541 if (tc)
2542 wattr_off(view->window,
2543 COLOR_PAIR(tc->colorpair), NULL);
2544 free(id_str);
2545 col += 9;
2546 if (col > avail)
2547 goto done;
2550 if (s->use_committer)
2551 author = strdup(got_object_commit_get_committer(commit));
2552 else
2553 author = strdup(got_object_commit_get_author(commit));
2554 if (author == NULL) {
2555 err = got_error_from_errno("strdup");
2556 goto done;
2558 err = format_author(&wauthor, &author_width, author, avail - col, col);
2559 if (err)
2560 goto done;
2561 tc = get_color(&s->colors, TOG_COLOR_AUTHOR);
2562 if (tc)
2563 wattr_on(view->window,
2564 COLOR_PAIR(tc->colorpair), NULL);
2565 waddwstr(view->window, wauthor);
2566 col += author_width;
2567 while (col < avail && author_width < author_display_cols + 2) {
2568 if (s->marked_entry == entry && author_width == marker_column) {
2569 err = draw_commit_marker(view, '>');
2570 if (err != NULL)
2571 goto done;
2572 } else if (tog_base_commit.marker != GOT_WORKTREE_STATE_UNKNOWN
2573 && author_width == marker_column &&
2574 entry->idx == tog_base_commit.idx && !s->limit_view) {
2575 err = draw_commit_marker(view, tog_base_commit.marker);
2576 if (err != NULL)
2577 goto done;
2578 } else
2579 waddch(view->window, ' ');
2580 col++;
2581 author_width++;
2583 if (tc)
2584 wattr_off(view->window,
2585 COLOR_PAIR(tc->colorpair), NULL);
2586 if (col > avail)
2587 goto done;
2589 err = got_object_commit_get_logmsg(&logmsg0, commit);
2590 if (err)
2591 goto done;
2592 logmsg = logmsg0;
2593 while (*logmsg == '\n')
2594 logmsg++;
2595 newline = strchr(logmsg, '\n');
2596 if (newline)
2597 *newline = '\0';
2599 limit = avail - col;
2600 if (view->child && !view_is_hsplit_top(view) && limit > 0)
2601 limit--; /* for the border */
2603 /* Prepend reference labels to log message if possible .*/
2604 refs = got_reflist_object_id_map_lookup(tog_refs_idmap, id);
2605 err = build_refs_str(&refs_str, refs, id, s->repo);
2606 if (err)
2607 goto done;
2608 if (refs_str) {
2609 char *rs;
2611 if (asprintf(&rs, "[%s]", refs_str) == -1) {
2612 err = got_error_from_errno("asprintf");
2613 goto done;
2615 err = format_line(&wrefstr, &refstr_width,
2616 &scrollx, rs, view->x, limit, col, 1);
2617 free(rs);
2618 if (err)
2619 goto done;
2620 tc = get_color(&s->colors, TOG_COLOR_COMMIT);
2621 if (tc)
2622 wattr_on(view->window,
2623 COLOR_PAIR(tc->colorpair), NULL);
2624 waddwstr(view->window, &wrefstr[scrollx]);
2625 if (tc)
2626 wattr_off(view->window,
2627 COLOR_PAIR(tc->colorpair), NULL);
2628 col += MAX(refstr_width, 0);
2629 if (col > avail)
2630 goto done;
2632 if (col < avail) {
2633 waddch(view->window, ' ');
2634 col++;
2637 if (refstr_width > 0)
2638 logmsg_x = 0;
2639 else {
2640 int unscrolled_refstr_width;
2641 size_t len = wcslen(wrefstr);
2644 * No need to check for -1 return value here since
2645 * unprintables have been replaced by span_wline().
2647 unscrolled_refstr_width = wcswidth(wrefstr, len);
2648 unscrolled_refstr_width += 1; /* trailing space */
2649 logmsg_x = view->x - unscrolled_refstr_width;
2652 limit = avail - col;
2653 if (view->child && !view_is_hsplit_top(view) && limit > 0)
2654 limit--; /* for the border */
2655 } else
2656 logmsg_x = view->x;
2658 err = format_line(&wlogmsg, &logmsg_width, &scrollx, logmsg, logmsg_x,
2659 limit, col, 1);
2660 if (err)
2661 goto done;
2662 waddwstr(view->window, &wlogmsg[scrollx]);
2663 col += MAX(logmsg_width, 0);
2664 while (col < avail) {
2665 waddch(view->window, ' ');
2666 col++;
2668 done:
2669 free(logmsg0);
2670 free(wlogmsg);
2671 free(wrefstr);
2672 free(refs_str);
2673 free(author);
2674 free(wauthor);
2675 free(line);
2676 return err;
2679 static struct commit_queue_entry *
2680 alloc_commit_queue_entry(struct got_commit_object *commit,
2681 struct got_object_id *id)
2683 struct commit_queue_entry *entry;
2684 struct got_object_id *dup;
2686 entry = calloc(1, sizeof(*entry));
2687 if (entry == NULL)
2688 return NULL;
2690 dup = got_object_id_dup(id);
2691 if (dup == NULL) {
2692 free(entry);
2693 return NULL;
2696 entry->id = dup;
2697 entry->commit = commit;
2698 return entry;
2701 static void
2702 pop_commit(struct commit_queue *commits)
2704 struct commit_queue_entry *entry;
2706 entry = TAILQ_FIRST(&commits->head);
2707 TAILQ_REMOVE(&commits->head, entry, entry);
2708 got_object_commit_close(entry->commit);
2709 commits->ncommits--;
2710 free(entry->id);
2711 free(entry);
2714 static void
2715 free_commits(struct commit_queue *commits)
2717 while (!TAILQ_EMPTY(&commits->head))
2718 pop_commit(commits);
2721 static const struct got_error *
2722 match_commit(int *have_match, struct got_object_id *id,
2723 struct got_commit_object *commit, regex_t *regex)
2725 const struct got_error *err = NULL;
2726 regmatch_t regmatch;
2727 char *id_str = NULL, *logmsg = NULL;
2729 *have_match = 0;
2731 err = got_object_id_str(&id_str, id);
2732 if (err)
2733 return err;
2735 err = got_object_commit_get_logmsg(&logmsg, commit);
2736 if (err)
2737 goto done;
2739 if (regexec(regex, got_object_commit_get_author(commit), 1,
2740 &regmatch, 0) == 0 ||
2741 regexec(regex, got_object_commit_get_committer(commit), 1,
2742 &regmatch, 0) == 0 ||
2743 regexec(regex, id_str, 1, &regmatch, 0) == 0 ||
2744 regexec(regex, logmsg, 1, &regmatch, 0) == 0)
2745 *have_match = 1;
2746 done:
2747 free(id_str);
2748 free(logmsg);
2749 return err;
2752 static const struct got_error *
2753 queue_commits(struct tog_log_thread_args *a)
2755 const struct got_error *err = NULL;
2758 * We keep all commits open throughout the lifetime of the log
2759 * view in order to avoid having to re-fetch commits from disk
2760 * while updating the display.
2762 do {
2763 struct got_object_id id;
2764 struct got_commit_object *commit;
2765 struct commit_queue_entry *entry;
2766 int limit_match = 0;
2767 int errcode;
2769 err = got_commit_graph_iter_next(&id, a->graph, a->repo,
2770 NULL, NULL);
2771 if (err)
2772 break;
2774 err = got_object_open_as_commit(&commit, a->repo, &id);
2775 if (err)
2776 break;
2777 entry = alloc_commit_queue_entry(commit, &id);
2778 if (entry == NULL) {
2779 err = got_error_from_errno("alloc_commit_queue_entry");
2780 break;
2783 errcode = pthread_mutex_lock(&tog_mutex);
2784 if (errcode) {
2785 err = got_error_set_errno(errcode,
2786 "pthread_mutex_lock");
2787 break;
2790 entry->idx = a->real_commits->ncommits;
2791 TAILQ_INSERT_TAIL(&a->real_commits->head, entry, entry);
2792 a->real_commits->ncommits++;
2794 if (*a->limiting) {
2795 err = match_commit(&limit_match, &id, commit,
2796 a->limit_regex);
2797 if (err)
2798 break;
2800 if (limit_match) {
2801 struct commit_queue_entry *matched;
2803 matched = alloc_commit_queue_entry(
2804 entry->commit, entry->id);
2805 if (matched == NULL) {
2806 err = got_error_from_errno(
2807 "alloc_commit_queue_entry");
2808 break;
2810 matched->commit = entry->commit;
2811 got_object_commit_retain(entry->commit);
2813 matched->idx = a->limit_commits->ncommits;
2814 TAILQ_INSERT_TAIL(&a->limit_commits->head,
2815 matched, entry);
2816 a->limit_commits->ncommits++;
2820 * This is how we signal log_thread() that we
2821 * have found a match, and that it should be
2822 * counted as a new entry for the view.
2824 a->limit_match = limit_match;
2827 if (*a->searching == TOG_SEARCH_FORWARD &&
2828 !*a->search_next_done) {
2829 int have_match;
2830 err = match_commit(&have_match, &id, commit, a->regex);
2831 if (err)
2832 break;
2834 if (*a->limiting) {
2835 if (limit_match && have_match)
2836 *a->search_next_done =
2837 TOG_SEARCH_HAVE_MORE;
2838 } else if (have_match)
2839 *a->search_next_done = TOG_SEARCH_HAVE_MORE;
2842 errcode = pthread_mutex_unlock(&tog_mutex);
2843 if (errcode && err == NULL)
2844 err = got_error_set_errno(errcode,
2845 "pthread_mutex_unlock");
2846 if (err)
2847 break;
2848 } while (*a->searching == TOG_SEARCH_FORWARD && !*a->search_next_done);
2850 return err;
2853 static void
2854 select_commit(struct tog_log_view_state *s)
2856 struct commit_queue_entry *entry;
2857 int ncommits = 0;
2859 entry = s->first_displayed_entry;
2860 while (entry) {
2861 if (ncommits == s->selected) {
2862 s->selected_entry = entry;
2863 break;
2865 entry = TAILQ_NEXT(entry, entry);
2866 ncommits++;
2870 static const struct got_error *
2871 draw_commits(struct tog_view *view)
2873 const struct got_error *err = NULL;
2874 struct tog_log_view_state *s = &view->state.log;
2875 struct commit_queue_entry *entry = s->selected_entry;
2876 int limit = view->nlines;
2877 int width;
2878 int ncommits, author_cols = 4, refstr_cols;
2879 char *id_str = NULL, *header = NULL, *ncommits_str = NULL;
2880 char *refs_str = NULL;
2881 wchar_t *wline;
2882 struct tog_color *tc;
2883 static const size_t date_display_cols = 12;
2884 struct got_reflist_head *refs;
2886 if (view_is_hsplit_top(view))
2887 --limit; /* account for border */
2889 if (s->selected_entry &&
2890 !(view->searching && view->search_next_done == 0)) {
2891 err = got_object_id_str(&id_str, s->selected_entry->id);
2892 if (err)
2893 return err;
2894 refs = got_reflist_object_id_map_lookup(tog_refs_idmap,
2895 s->selected_entry->id);
2896 err = build_refs_str(&refs_str, refs, s->selected_entry->id,
2897 s->repo);
2898 if (err)
2899 goto done;
2902 if (s->thread_args.commits_needed == 0 && !using_mock_io)
2903 halfdelay(10); /* disable fast refresh */
2905 if (s->thread_args.commits_needed > 0 || s->thread_args.load_all) {
2906 if (asprintf(&ncommits_str, " [%d/%d] %s",
2907 entry ? entry->idx + 1 : 0, s->commits->ncommits,
2908 (view->searching && !view->search_next_done) ?
2909 "searching..." : "loading...") == -1) {
2910 err = got_error_from_errno("asprintf");
2911 goto done;
2913 } else {
2914 const char *search_str = NULL;
2915 const char *limit_str = NULL;
2917 if (view->searching) {
2918 if (view->search_next_done == TOG_SEARCH_NO_MORE)
2919 search_str = "no more matches";
2920 else if (view->search_next_done == TOG_SEARCH_HAVE_NONE)
2921 search_str = "no matches found";
2922 else if (!view->search_next_done)
2923 search_str = "searching...";
2926 if (s->limit_view && s->commits->ncommits == 0)
2927 limit_str = "no matches found";
2929 if (asprintf(&ncommits_str, " [%d/%d] %s %s",
2930 entry ? entry->idx + 1 : 0, s->commits->ncommits,
2931 search_str ? search_str : (refs_str ? refs_str : ""),
2932 limit_str ? limit_str : "") == -1) {
2933 err = got_error_from_errno("asprintf");
2934 goto done;
2938 free(refs_str);
2939 refs_str = NULL;
2941 if (s->in_repo_path && strcmp(s->in_repo_path, "/") != 0) {
2942 if (asprintf(&header, "commit %s %s%s", id_str ? id_str :
2943 "........................................",
2944 s->in_repo_path, ncommits_str) == -1) {
2945 err = got_error_from_errno("asprintf");
2946 header = NULL;
2947 goto done;
2949 } else if (asprintf(&header, "commit %s%s",
2950 id_str ? id_str : "........................................",
2951 ncommits_str) == -1) {
2952 err = got_error_from_errno("asprintf");
2953 header = NULL;
2954 goto done;
2956 err = format_line(&wline, &width, NULL, header, 0, view->ncols, 0, 0);
2957 if (err)
2958 goto done;
2960 werase(view->window);
2962 if (view_needs_focus_indication(view))
2963 wstandout(view->window);
2964 tc = get_color(&s->colors, TOG_COLOR_COMMIT);
2965 if (tc)
2966 wattr_on(view->window, COLOR_PAIR(tc->colorpair), NULL);
2967 waddwstr(view->window, wline);
2968 while (width < view->ncols) {
2969 waddch(view->window, ' ');
2970 width++;
2972 if (tc)
2973 wattr_off(view->window, COLOR_PAIR(tc->colorpair), NULL);
2974 if (view_needs_focus_indication(view))
2975 wstandend(view->window);
2976 free(wline);
2977 if (limit <= 1)
2978 goto done;
2980 /* Grow author column size if necessary, and set view->maxx. */
2981 entry = s->first_displayed_entry;
2982 ncommits = 0;
2983 view->maxx = 0;
2984 while (entry) {
2985 struct got_commit_object *c = entry->commit;
2986 char *author, *eol, *msg, *msg0;
2987 wchar_t *wauthor, *wmsg;
2988 int width;
2989 if (ncommits >= limit - 1)
2990 break;
2991 if (s->use_committer)
2992 author = strdup(got_object_commit_get_committer(c));
2993 else
2994 author = strdup(got_object_commit_get_author(c));
2995 if (author == NULL) {
2996 err = got_error_from_errno("strdup");
2997 goto done;
2999 err = format_author(&wauthor, &width, author, COLS,
3000 date_display_cols);
3001 if (author_cols < width)
3002 author_cols = width;
3003 free(wauthor);
3004 free(author);
3005 if (err)
3006 goto done;
3007 refs = got_reflist_object_id_map_lookup(tog_refs_idmap,
3008 entry->id);
3009 err = build_refs_str(&refs_str, refs, entry->id, s->repo);
3010 if (err)
3011 goto done;
3012 if (refs_str) {
3013 wchar_t *ws;
3014 err = format_line(&ws, &width, NULL, refs_str,
3015 0, INT_MAX, date_display_cols + author_cols, 0);
3016 free(ws);
3017 free(refs_str);
3018 refs_str = NULL;
3019 if (err)
3020 goto done;
3021 refstr_cols = width + 3; /* account for [ ] + space */
3022 } else
3023 refstr_cols = 0;
3024 err = got_object_commit_get_logmsg(&msg0, c);
3025 if (err)
3026 goto done;
3027 msg = msg0;
3028 while (*msg == '\n')
3029 ++msg;
3030 if ((eol = strchr(msg, '\n')))
3031 *eol = '\0';
3032 err = format_line(&wmsg, &width, NULL, msg, 0, INT_MAX,
3033 date_display_cols + author_cols + refstr_cols, 0);
3034 if (err)
3035 goto done;
3036 view->maxx = MAX(view->maxx, width + refstr_cols);
3037 free(msg0);
3038 free(wmsg);
3039 ncommits++;
3040 entry = TAILQ_NEXT(entry, entry);
3043 entry = s->first_displayed_entry;
3044 s->last_displayed_entry = s->first_displayed_entry;
3045 ncommits = 0;
3046 while (entry) {
3047 if (ncommits >= limit - 1)
3048 break;
3049 if (ncommits == s->selected)
3050 wstandout(view->window);
3051 err = draw_commit(view, entry, date_display_cols, author_cols);
3052 if (ncommits == s->selected)
3053 wstandend(view->window);
3054 if (err)
3055 goto done;
3056 ncommits++;
3057 s->last_displayed_entry = entry;
3058 entry = TAILQ_NEXT(entry, entry);
3061 view_border(view);
3062 done:
3063 free(id_str);
3064 free(refs_str);
3065 free(ncommits_str);
3066 free(header);
3067 return err;
3070 static void
3071 log_scroll_up(struct tog_log_view_state *s, int maxscroll)
3073 struct commit_queue_entry *entry;
3074 int nscrolled = 0;
3076 entry = TAILQ_FIRST(&s->commits->head);
3077 if (s->first_displayed_entry == entry)
3078 return;
3080 entry = s->first_displayed_entry;
3081 while (entry && nscrolled < maxscroll) {
3082 entry = TAILQ_PREV(entry, commit_queue_head, entry);
3083 if (entry) {
3084 s->first_displayed_entry = entry;
3085 nscrolled++;
3090 static const struct got_error *
3091 trigger_log_thread(struct tog_view *view, int wait)
3093 struct tog_log_thread_args *ta = &view->state.log.thread_args;
3094 int errcode;
3096 if (!using_mock_io)
3097 halfdelay(1); /* fast refresh while loading commits */
3099 while (!ta->log_complete && !tog_thread_error &&
3100 (ta->commits_needed > 0 || ta->load_all)) {
3101 /* Wake the log thread. */
3102 errcode = pthread_cond_signal(&ta->need_commits);
3103 if (errcode)
3104 return got_error_set_errno(errcode,
3105 "pthread_cond_signal");
3108 * The mutex will be released while the view loop waits
3109 * in wgetch(), at which time the log thread will run.
3111 if (!wait)
3112 break;
3114 /* Display progress update in log view. */
3115 show_log_view(view);
3116 update_panels();
3117 doupdate();
3119 /* Wait right here while next commit is being loaded. */
3120 errcode = pthread_cond_wait(&ta->commit_loaded, &tog_mutex);
3121 if (errcode)
3122 return got_error_set_errno(errcode,
3123 "pthread_cond_wait");
3125 /* Display progress update in log view. */
3126 show_log_view(view);
3127 update_panels();
3128 doupdate();
3131 return NULL;
3134 static const struct got_error *
3135 request_log_commits(struct tog_view *view)
3137 struct tog_log_view_state *state = &view->state.log;
3138 const struct got_error *err = NULL;
3140 if (state->thread_args.log_complete)
3141 return NULL;
3143 state->thread_args.commits_needed += view->nscrolled;
3144 err = trigger_log_thread(view, 1);
3145 view->nscrolled = 0;
3147 return err;
3150 static const struct got_error *
3151 log_scroll_down(struct tog_view *view, int maxscroll)
3153 struct tog_log_view_state *s = &view->state.log;
3154 const struct got_error *err = NULL;
3155 struct commit_queue_entry *pentry;
3156 int nscrolled = 0, ncommits_needed;
3158 if (s->last_displayed_entry == NULL)
3159 return NULL;
3161 ncommits_needed = s->last_displayed_entry->idx + 2 + maxscroll;
3162 if (s->commits->ncommits < ncommits_needed &&
3163 !s->thread_args.log_complete) {
3165 * Ask the log thread for required amount of commits.
3167 s->thread_args.commits_needed +=
3168 ncommits_needed - s->commits->ncommits;
3169 err = trigger_log_thread(view, 1);
3170 if (err)
3171 return err;
3174 do {
3175 pentry = TAILQ_NEXT(s->last_displayed_entry, entry);
3176 if (pentry == NULL && view->mode != TOG_VIEW_SPLIT_HRZN)
3177 break;
3179 s->last_displayed_entry = pentry ?
3180 pentry : s->last_displayed_entry;
3182 pentry = TAILQ_NEXT(s->first_displayed_entry, entry);
3183 if (pentry == NULL)
3184 break;
3185 s->first_displayed_entry = pentry;
3186 } while (++nscrolled < maxscroll);
3188 if (view->mode == TOG_VIEW_SPLIT_HRZN && !s->thread_args.log_complete)
3189 view->nscrolled += nscrolled;
3190 else
3191 view->nscrolled = 0;
3193 return err;
3196 static const struct got_error *
3197 open_diff_view_for_commit(struct tog_view **new_view, int begin_y, int begin_x,
3198 struct got_commit_object *commit, struct got_object_id *commit_id,
3199 struct tog_view *log_view, struct got_repository *repo)
3201 const struct got_error *err;
3202 struct got_object_qid *p;
3203 struct got_object_id *parent_id;
3204 struct tog_view *diff_view;
3205 struct tog_log_view_state *ls = NULL;
3207 diff_view = view_open(0, 0, begin_y, begin_x, TOG_VIEW_DIFF);
3208 if (diff_view == NULL)
3209 return got_error_from_errno("view_open");
3211 if (log_view != NULL)
3212 ls = &log_view->state.log;
3214 if (ls != NULL && ls->marked_entry != NULL &&
3215 ls->marked_entry != ls->selected_entry)
3216 parent_id = ls->marked_entry->id;
3217 else if ((p = STAILQ_FIRST(got_object_commit_get_parent_ids(commit))))
3218 parent_id = &p->id;
3219 else
3220 parent_id = NULL;
3222 err = open_diff_view(diff_view, parent_id, commit_id,
3223 NULL, NULL, 3, 0, 0, log_view, repo);
3224 if (err == NULL)
3225 *new_view = diff_view;
3226 return err;
3229 static const struct got_error *
3230 tree_view_visit_subtree(struct tog_tree_view_state *s,
3231 struct got_tree_object *subtree)
3233 struct tog_parent_tree *parent;
3235 parent = calloc(1, sizeof(*parent));
3236 if (parent == NULL)
3237 return got_error_from_errno("calloc");
3239 parent->tree = s->tree;
3240 parent->first_displayed_entry = s->first_displayed_entry;
3241 parent->selected_entry = s->selected_entry;
3242 parent->selected = s->selected;
3243 TAILQ_INSERT_HEAD(&s->parents, parent, entry);
3244 s->tree = subtree;
3245 s->selected = 0;
3246 s->first_displayed_entry = NULL;
3247 return NULL;
3250 static const struct got_error *
3251 tree_view_walk_path(struct tog_tree_view_state *s,
3252 struct got_commit_object *commit, const char *path)
3254 const struct got_error *err = NULL;
3255 struct got_tree_object *tree = NULL;
3256 const char *p;
3257 char *slash, *subpath = NULL;
3259 /* Walk the path and open corresponding tree objects. */
3260 p = path;
3261 while (*p) {
3262 struct got_tree_entry *te;
3263 struct got_object_id *tree_id;
3264 char *te_name;
3266 while (p[0] == '/')
3267 p++;
3269 /* Ensure the correct subtree entry is selected. */
3270 slash = strchr(p, '/');
3271 if (slash == NULL)
3272 te_name = strdup(p);
3273 else
3274 te_name = strndup(p, slash - p);
3275 if (te_name == NULL) {
3276 err = got_error_from_errno("strndup");
3277 break;
3279 te = got_object_tree_find_entry(s->tree, te_name);
3280 if (te == NULL) {
3281 err = got_error_path(te_name, GOT_ERR_NO_TREE_ENTRY);
3282 free(te_name);
3283 break;
3285 free(te_name);
3286 s->first_displayed_entry = s->selected_entry = te;
3288 if (!S_ISDIR(got_tree_entry_get_mode(s->selected_entry)))
3289 break; /* jump to this file's entry */
3291 slash = strchr(p, '/');
3292 if (slash)
3293 subpath = strndup(path, slash - path);
3294 else
3295 subpath = strdup(path);
3296 if (subpath == NULL) {
3297 err = got_error_from_errno("strdup");
3298 break;
3301 err = got_object_id_by_path(&tree_id, s->repo, commit,
3302 subpath);
3303 if (err)
3304 break;
3306 err = got_object_open_as_tree(&tree, s->repo, tree_id);
3307 free(tree_id);
3308 if (err)
3309 break;
3311 err = tree_view_visit_subtree(s, tree);
3312 if (err) {
3313 got_object_tree_close(tree);
3314 break;
3316 if (slash == NULL)
3317 break;
3318 free(subpath);
3319 subpath = NULL;
3320 p = slash;
3323 free(subpath);
3324 return err;
3327 static const struct got_error *
3328 browse_commit_tree(struct tog_view **new_view, int begin_y, int begin_x,
3329 struct commit_queue_entry *entry, const char *path,
3330 const char *head_ref_name, struct got_repository *repo)
3332 const struct got_error *err = NULL;
3333 struct tog_tree_view_state *s;
3334 struct tog_view *tree_view;
3336 tree_view = view_open(0, 0, begin_y, begin_x, TOG_VIEW_TREE);
3337 if (tree_view == NULL)
3338 return got_error_from_errno("view_open");
3340 err = open_tree_view(tree_view, entry->id, head_ref_name, repo);
3341 if (err)
3342 return err;
3343 s = &tree_view->state.tree;
3345 *new_view = tree_view;
3347 if (got_path_is_root_dir(path))
3348 return NULL;
3350 return tree_view_walk_path(s, entry->commit, path);
3353 static const struct got_error *
3354 block_signals_used_by_main_thread(void)
3356 sigset_t sigset;
3357 int errcode;
3359 if (sigemptyset(&sigset) == -1)
3360 return got_error_from_errno("sigemptyset");
3362 /* tog handles SIGWINCH, SIGCONT, SIGINT, SIGTERM */
3363 if (sigaddset(&sigset, SIGWINCH) == -1)
3364 return got_error_from_errno("sigaddset");
3365 if (sigaddset(&sigset, SIGCONT) == -1)
3366 return got_error_from_errno("sigaddset");
3367 if (sigaddset(&sigset, SIGINT) == -1)
3368 return got_error_from_errno("sigaddset");
3369 if (sigaddset(&sigset, SIGTERM) == -1)
3370 return got_error_from_errno("sigaddset");
3372 /* ncurses handles SIGTSTP */
3373 if (sigaddset(&sigset, SIGTSTP) == -1)
3374 return got_error_from_errno("sigaddset");
3376 errcode = pthread_sigmask(SIG_BLOCK, &sigset, NULL);
3377 if (errcode)
3378 return got_error_set_errno(errcode, "pthread_sigmask");
3380 return NULL;
3383 static void *
3384 log_thread(void *arg)
3386 const struct got_error *err = NULL;
3387 int errcode = 0;
3388 struct tog_log_thread_args *a = arg;
3389 int done = 0;
3392 * Sync startup with main thread such that we begin our
3393 * work once view_input() has released the mutex.
3395 errcode = pthread_mutex_lock(&tog_mutex);
3396 if (errcode) {
3397 err = got_error_set_errno(errcode, "pthread_mutex_lock");
3398 return (void *)err;
3401 err = block_signals_used_by_main_thread();
3402 if (err) {
3403 pthread_mutex_unlock(&tog_mutex);
3404 goto done;
3407 while (!done && !err && !tog_fatal_signal_received()) {
3408 errcode = pthread_mutex_unlock(&tog_mutex);
3409 if (errcode) {
3410 err = got_error_set_errno(errcode,
3411 "pthread_mutex_unlock");
3412 goto done;
3414 err = queue_commits(a);
3415 if (err) {
3416 if (err->code != GOT_ERR_ITER_COMPLETED)
3417 goto done;
3418 err = NULL;
3419 done = 1;
3420 a->commits_needed = 0;
3421 } else if (a->commits_needed > 0 && !a->load_all) {
3422 if (*a->limiting) {
3423 if (a->limit_match)
3424 a->commits_needed--;
3425 } else
3426 a->commits_needed--;
3429 errcode = pthread_mutex_lock(&tog_mutex);
3430 if (errcode) {
3431 err = got_error_set_errno(errcode,
3432 "pthread_mutex_lock");
3433 goto done;
3434 } else if (*a->quit)
3435 done = 1;
3436 else if (*a->limiting && *a->first_displayed_entry == NULL) {
3437 *a->first_displayed_entry =
3438 TAILQ_FIRST(&a->limit_commits->head);
3439 *a->selected_entry = *a->first_displayed_entry;
3440 } else if (*a->first_displayed_entry == NULL) {
3441 *a->first_displayed_entry =
3442 TAILQ_FIRST(&a->real_commits->head);
3443 *a->selected_entry = *a->first_displayed_entry;
3446 errcode = pthread_cond_signal(&a->commit_loaded);
3447 if (errcode) {
3448 err = got_error_set_errno(errcode,
3449 "pthread_cond_signal");
3450 pthread_mutex_unlock(&tog_mutex);
3451 goto done;
3454 if (a->commits_needed == 0 &&
3455 a->need_commit_marker && a->worktree) {
3456 errcode = pthread_mutex_unlock(&tog_mutex);
3457 if (errcode) {
3458 err = got_error_set_errno(errcode,
3459 "pthread_mutex_unlock");
3460 goto done;
3462 err = got_worktree_get_state(&tog_base_commit.marker,
3463 a->repo, a->worktree, NULL, NULL);
3464 if (err)
3465 goto done;
3466 errcode = pthread_mutex_lock(&tog_mutex);
3467 if (errcode) {
3468 err = got_error_set_errno(errcode,
3469 "pthread_mutex_lock");
3470 goto done;
3472 a->need_commit_marker = 0;
3474 * The main thread did not close this
3475 * work tree yet. Close it now.
3477 got_worktree_close(a->worktree);
3478 a->worktree = NULL;
3480 if (*a->quit)
3481 done = 1;
3484 if (done)
3485 a->commits_needed = 0;
3486 else {
3487 if (a->commits_needed == 0 && !a->load_all) {
3488 if (tog_io.wait_for_ui) {
3489 errcode = pthread_cond_signal(
3490 &a->log_loaded);
3491 if (errcode && err == NULL)
3492 err = got_error_set_errno(
3493 errcode,
3494 "pthread_cond_signal");
3497 errcode = pthread_cond_wait(&a->need_commits,
3498 &tog_mutex);
3499 if (errcode) {
3500 err = got_error_set_errno(errcode,
3501 "pthread_cond_wait");
3502 pthread_mutex_unlock(&tog_mutex);
3503 goto done;
3505 if (*a->quit)
3506 done = 1;
3510 a->log_complete = 1;
3511 if (tog_io.wait_for_ui) {
3512 errcode = pthread_cond_signal(&a->log_loaded);
3513 if (errcode && err == NULL)
3514 err = got_error_set_errno(errcode,
3515 "pthread_cond_signal");
3518 errcode = pthread_mutex_unlock(&tog_mutex);
3519 if (errcode)
3520 err = got_error_set_errno(errcode, "pthread_mutex_unlock");
3521 done:
3522 if (err) {
3523 tog_thread_error = 1;
3524 pthread_cond_signal(&a->commit_loaded);
3525 if (a->worktree) {
3526 got_worktree_close(a->worktree);
3527 a->worktree = NULL;
3530 return (void *)err;
3533 static const struct got_error *
3534 stop_log_thread(struct tog_log_view_state *s)
3536 const struct got_error *err = NULL, *thread_err = NULL;
3537 int errcode;
3539 if (s->thread) {
3540 s->quit = 1;
3541 errcode = pthread_cond_signal(&s->thread_args.need_commits);
3542 if (errcode)
3543 return got_error_set_errno(errcode,
3544 "pthread_cond_signal");
3545 errcode = pthread_mutex_unlock(&tog_mutex);
3546 if (errcode)
3547 return got_error_set_errno(errcode,
3548 "pthread_mutex_unlock");
3549 errcode = pthread_join(s->thread, (void **)&thread_err);
3550 if (errcode)
3551 return got_error_set_errno(errcode, "pthread_join");
3552 errcode = pthread_mutex_lock(&tog_mutex);
3553 if (errcode)
3554 return got_error_set_errno(errcode,
3555 "pthread_mutex_lock");
3556 s->thread = 0; //NULL;
3559 if (s->thread_args.repo) {
3560 err = got_repo_close(s->thread_args.repo);
3561 s->thread_args.repo = NULL;
3564 if (s->thread_args.pack_fds) {
3565 const struct got_error *pack_err =
3566 got_repo_pack_fds_close(s->thread_args.pack_fds);
3567 if (err == NULL)
3568 err = pack_err;
3569 s->thread_args.pack_fds = NULL;
3572 if (s->thread_args.graph) {
3573 got_commit_graph_close(s->thread_args.graph);
3574 s->thread_args.graph = NULL;
3577 return err ? err : thread_err;
3580 static const struct got_error *
3581 close_log_view(struct tog_view *view)
3583 const struct got_error *err = NULL;
3584 struct tog_log_view_state *s = &view->state.log;
3585 int errcode;
3587 log_mark_clear(s);
3589 err = stop_log_thread(s);
3591 errcode = pthread_cond_destroy(&s->thread_args.need_commits);
3592 if (errcode && err == NULL)
3593 err = got_error_set_errno(errcode, "pthread_cond_destroy");
3595 errcode = pthread_cond_destroy(&s->thread_args.commit_loaded);
3596 if (errcode && err == NULL)
3597 err = got_error_set_errno(errcode, "pthread_cond_destroy");
3599 free_commits(&s->limit_commits);
3600 free_commits(&s->real_commits);
3601 free_colors(&s->colors);
3602 free(s->in_repo_path);
3603 s->in_repo_path = NULL;
3604 free(s->start_id);
3605 s->start_id = NULL;
3606 free(s->head_ref_name);
3607 s->head_ref_name = NULL;
3608 return err;
3612 * We use two queues to implement the limit feature: first consists of
3613 * commits matching the current limit_regex; second is the real queue
3614 * of all known commits (real_commits). When the user starts limiting,
3615 * we swap queues such that all movement and displaying functionality
3616 * works with very slight change.
3618 static const struct got_error *
3619 limit_log_view(struct tog_view *view)
3621 struct tog_log_view_state *s = &view->state.log;
3622 struct commit_queue_entry *entry;
3623 struct tog_view *v = view;
3624 const struct got_error *err = NULL;
3625 char pattern[1024];
3626 int ret;
3628 if (view_is_hsplit_top(view))
3629 v = view->child;
3630 else if (view->mode == TOG_VIEW_SPLIT_VERT && view->parent)
3631 v = view->parent;
3633 if (tog_io.input_str != NULL) {
3634 if (strlcpy(pattern, tog_io.input_str, sizeof(pattern)) >=
3635 sizeof(pattern))
3636 return got_error(GOT_ERR_NO_SPACE);
3637 } else {
3638 wmove(v->window, v->nlines - 1, 0);
3639 wclrtoeol(v->window);
3640 mvwaddstr(v->window, v->nlines - 1, 0, "&/");
3641 nodelay(v->window, FALSE);
3642 nocbreak();
3643 echo();
3644 ret = wgetnstr(v->window, pattern, sizeof(pattern));
3645 cbreak();
3646 noecho();
3647 nodelay(v->window, TRUE);
3648 if (ret == ERR)
3649 return NULL;
3652 if (*pattern == '\0') {
3654 * Safety measure for the situation where the user
3655 * resets limit without previously limiting anything.
3657 if (!s->limit_view)
3658 return NULL;
3661 * User could have pressed Ctrl+L, which refreshed the
3662 * commit queues, it means we can't save previously
3663 * (before limit took place) displayed entries,
3664 * because they would point to already free'ed memory,
3665 * so we are forced to always select first entry of
3666 * the queue.
3668 s->commits = &s->real_commits;
3669 s->first_displayed_entry = TAILQ_FIRST(&s->real_commits.head);
3670 s->selected_entry = s->first_displayed_entry;
3671 s->selected = 0;
3672 s->limit_view = 0;
3674 return NULL;
3677 if (regcomp(&s->limit_regex, pattern, REG_EXTENDED | REG_NEWLINE))
3678 return NULL;
3680 s->limit_view = 1;
3682 /* Clear the screen while loading limit view */
3683 s->first_displayed_entry = NULL;
3684 s->last_displayed_entry = NULL;
3685 s->selected_entry = NULL;
3686 s->commits = &s->limit_commits;
3688 /* Prepare limit queue for new search */
3689 free_commits(&s->limit_commits);
3690 s->limit_commits.ncommits = 0;
3692 /* First process commits, which are in queue already */
3693 TAILQ_FOREACH(entry, &s->real_commits.head, entry) {
3694 int have_match = 0;
3696 err = match_commit(&have_match, entry->id,
3697 entry->commit, &s->limit_regex);
3698 if (err)
3699 return err;
3701 if (have_match) {
3702 struct commit_queue_entry *matched;
3704 matched = alloc_commit_queue_entry(entry->commit,
3705 entry->id);
3706 if (matched == NULL) {
3707 err = got_error_from_errno(
3708 "alloc_commit_queue_entry");
3709 break;
3711 matched->commit = entry->commit;
3712 got_object_commit_retain(entry->commit);
3714 matched->idx = s->limit_commits.ncommits;
3715 TAILQ_INSERT_TAIL(&s->limit_commits.head,
3716 matched, entry);
3717 s->limit_commits.ncommits++;
3721 /* Second process all the commits, until we fill the screen */
3722 if (s->limit_commits.ncommits < view->nlines - 1 &&
3723 !s->thread_args.log_complete) {
3724 s->thread_args.commits_needed +=
3725 view->nlines - s->limit_commits.ncommits - 1;
3726 err = trigger_log_thread(view, 1);
3727 if (err)
3728 return err;
3731 s->first_displayed_entry = TAILQ_FIRST(&s->commits->head);
3732 s->selected_entry = TAILQ_FIRST(&s->commits->head);
3733 s->selected = 0;
3735 return NULL;
3738 static const struct got_error *
3739 search_start_log_view(struct tog_view *view)
3741 struct tog_log_view_state *s = &view->state.log;
3743 s->matched_entry = NULL;
3744 s->search_entry = NULL;
3745 return NULL;
3748 static const struct got_error *
3749 search_next_log_view(struct tog_view *view)
3751 const struct got_error *err = NULL;
3752 struct tog_log_view_state *s = &view->state.log;
3753 struct commit_queue_entry *entry;
3755 /* Display progress update in log view. */
3756 show_log_view(view);
3757 update_panels();
3758 doupdate();
3760 if (s->search_entry) {
3761 if (!using_mock_io) {
3762 int errcode, ch;
3764 errcode = pthread_mutex_unlock(&tog_mutex);
3765 if (errcode)
3766 return got_error_set_errno(errcode,
3767 "pthread_mutex_unlock");
3768 ch = wgetch(view->window);
3769 errcode = pthread_mutex_lock(&tog_mutex);
3770 if (errcode)
3771 return got_error_set_errno(errcode,
3772 "pthread_mutex_lock");
3773 if (ch == CTRL('g') || ch == KEY_BACKSPACE) {
3774 view->search_next_done = TOG_SEARCH_HAVE_MORE;
3775 return NULL;
3778 if (view->searching == TOG_SEARCH_FORWARD)
3779 entry = TAILQ_NEXT(s->search_entry, entry);
3780 else
3781 entry = TAILQ_PREV(s->search_entry,
3782 commit_queue_head, entry);
3783 } else if (s->matched_entry) {
3785 * If the user has moved the cursor after we hit a match,
3786 * the position from where we should continue searching
3787 * might have changed.
3789 if (view->searching == TOG_SEARCH_FORWARD)
3790 entry = TAILQ_NEXT(s->selected_entry, entry);
3791 else
3792 entry = TAILQ_PREV(s->selected_entry, commit_queue_head,
3793 entry);
3794 } else {
3795 entry = s->selected_entry;
3798 while (1) {
3799 int have_match = 0;
3801 if (entry == NULL) {
3802 if (s->thread_args.log_complete ||
3803 view->searching == TOG_SEARCH_BACKWARD) {
3804 view->search_next_done =
3805 (s->matched_entry == NULL ?
3806 TOG_SEARCH_HAVE_NONE : TOG_SEARCH_NO_MORE);
3807 s->search_entry = NULL;
3808 return NULL;
3811 * Poke the log thread for more commits and return,
3812 * allowing the main loop to make progress. Search
3813 * will resume at s->search_entry once we come back.
3815 s->search_entry = s->selected_entry;
3816 s->thread_args.commits_needed++;
3817 return trigger_log_thread(view, 0);
3820 err = match_commit(&have_match, entry->id, entry->commit,
3821 &view->regex);
3822 if (err)
3823 break;
3824 if (have_match) {
3825 view->search_next_done = TOG_SEARCH_HAVE_MORE;
3826 s->matched_entry = entry;
3827 break;
3830 s->search_entry = entry;
3831 if (view->searching == TOG_SEARCH_FORWARD)
3832 entry = TAILQ_NEXT(entry, entry);
3833 else
3834 entry = TAILQ_PREV(entry, commit_queue_head, entry);
3837 if (s->matched_entry) {
3838 int cur = s->selected_entry->idx;
3839 while (cur < s->matched_entry->idx) {
3840 err = input_log_view(NULL, view, KEY_DOWN);
3841 if (err)
3842 return err;
3843 cur++;
3845 while (cur > s->matched_entry->idx) {
3846 err = input_log_view(NULL, view, KEY_UP);
3847 if (err)
3848 return err;
3849 cur--;
3853 s->search_entry = NULL;
3855 return NULL;
3858 static const struct got_error *
3859 open_log_view(struct tog_view *view, struct got_object_id *start_id,
3860 struct got_repository *repo, const char *head_ref_name,
3861 const char *in_repo_path, int log_branches,
3862 struct got_worktree *worktree)
3864 const struct got_error *err = NULL;
3865 struct tog_log_view_state *s = &view->state.log;
3866 struct got_repository *thread_repo = NULL;
3867 struct got_commit_graph *thread_graph = NULL;
3868 int errcode;
3870 if (in_repo_path != s->in_repo_path) {
3871 free(s->in_repo_path);
3872 s->in_repo_path = strdup(in_repo_path);
3873 if (s->in_repo_path == NULL) {
3874 err = got_error_from_errno("strdup");
3875 goto done;
3879 /* The commit queue only contains commits being displayed. */
3880 TAILQ_INIT(&s->real_commits.head);
3881 s->real_commits.ncommits = 0;
3882 s->commits = &s->real_commits;
3884 TAILQ_INIT(&s->limit_commits.head);
3885 s->limit_view = 0;
3886 s->limit_commits.ncommits = 0;
3888 s->repo = repo;
3889 if (head_ref_name) {
3890 s->head_ref_name = strdup(head_ref_name);
3891 if (s->head_ref_name == NULL) {
3892 err = got_error_from_errno("strdup");
3893 goto done;
3896 s->start_id = got_object_id_dup(start_id);
3897 if (s->start_id == NULL) {
3898 err = got_error_from_errno("got_object_id_dup");
3899 goto done;
3901 s->log_branches = log_branches;
3902 s->use_committer = 1;
3904 STAILQ_INIT(&s->colors);
3905 if (has_colors() && getenv("TOG_COLORS") != NULL) {
3906 err = add_color(&s->colors, "^$", TOG_COLOR_COMMIT,
3907 get_color_value("TOG_COLOR_COMMIT"));
3908 if (err)
3909 goto done;
3910 err = add_color(&s->colors, "^$", TOG_COLOR_AUTHOR,
3911 get_color_value("TOG_COLOR_AUTHOR"));
3912 if (err)
3913 goto done;
3914 err = add_color(&s->colors, "^$", TOG_COLOR_DATE,
3915 get_color_value("TOG_COLOR_DATE"));
3916 if (err)
3917 goto done;
3920 view->show = show_log_view;
3921 view->input = input_log_view;
3922 view->resize = resize_log_view;
3923 view->close = close_log_view;
3924 view->search_start = search_start_log_view;
3925 view->search_next = search_next_log_view;
3927 if (s->thread_args.pack_fds == NULL) {
3928 err = got_repo_pack_fds_open(&s->thread_args.pack_fds);
3929 if (err)
3930 goto done;
3932 err = got_repo_open(&thread_repo, got_repo_get_path(repo), NULL,
3933 s->thread_args.pack_fds);
3934 if (err)
3935 goto done;
3936 err = got_commit_graph_open(&thread_graph, s->in_repo_path,
3937 !s->log_branches);
3938 if (err)
3939 goto done;
3940 err = got_commit_graph_bfsort(thread_graph, s->start_id,
3941 s->repo, NULL, NULL);
3942 if (err)
3943 goto done;
3945 errcode = pthread_cond_init(&s->thread_args.need_commits, NULL);
3946 if (errcode) {
3947 err = got_error_set_errno(errcode, "pthread_cond_init");
3948 goto done;
3950 errcode = pthread_cond_init(&s->thread_args.commit_loaded, NULL);
3951 if (errcode) {
3952 err = got_error_set_errno(errcode, "pthread_cond_init");
3953 goto done;
3956 if (using_mock_io) {
3957 int rc;
3959 rc = pthread_cond_init(&s->thread_args.log_loaded, NULL);
3960 if (rc)
3961 return got_error_set_errno(rc, "pthread_cond_init");
3964 s->thread_args.commits_needed = view->nlines;
3965 s->thread_args.graph = thread_graph;
3966 s->thread_args.real_commits = &s->real_commits;
3967 s->thread_args.limit_commits = &s->limit_commits;
3968 s->thread_args.in_repo_path = s->in_repo_path;
3969 s->thread_args.start_id = s->start_id;
3970 s->thread_args.repo = thread_repo;
3971 s->thread_args.log_complete = 0;
3972 s->thread_args.quit = &s->quit;
3973 s->thread_args.first_displayed_entry = &s->first_displayed_entry;
3974 s->thread_args.selected_entry = &s->selected_entry;
3975 s->thread_args.searching = &view->searching;
3976 s->thread_args.search_next_done = &view->search_next_done;
3977 s->thread_args.regex = &view->regex;
3978 s->thread_args.limiting = &s->limit_view;
3979 s->thread_args.limit_regex = &s->limit_regex;
3980 s->thread_args.limit_commits = &s->limit_commits;
3981 s->thread_args.worktree = worktree;
3982 if (worktree)
3983 s->thread_args.need_commit_marker = 1;
3984 done:
3985 if (err) {
3986 if (view->close == NULL)
3987 close_log_view(view);
3988 view_close(view);
3990 return err;
3993 static const struct got_error *
3994 show_log_view(struct tog_view *view)
3996 const struct got_error *err;
3997 struct tog_log_view_state *s = &view->state.log;
3999 if (s->thread == 0) { //NULL) {
4000 int errcode = pthread_create(&s->thread, NULL, log_thread,
4001 &s->thread_args);
4002 if (errcode)
4003 return got_error_set_errno(errcode, "pthread_create");
4004 if (s->thread_args.commits_needed > 0) {
4005 err = trigger_log_thread(view, 1);
4006 if (err)
4007 return err;
4011 return draw_commits(view);
4014 static void
4015 log_move_cursor_up(struct tog_view *view, int page, int home)
4017 struct tog_log_view_state *s = &view->state.log;
4019 if (s->first_displayed_entry == NULL)
4020 return;
4021 if (s->selected_entry->idx == 0)
4022 view->count = 0;
4024 if ((page && TAILQ_FIRST(&s->commits->head) == s->first_displayed_entry)
4025 || home)
4026 s->selected = home ? 0 : MAX(0, s->selected - page - 1);
4028 if (!page && !home && s->selected > 0)
4029 --s->selected;
4030 else
4031 log_scroll_up(s, home ? s->commits->ncommits : MAX(page, 1));
4033 select_commit(s);
4034 return;
4037 static const struct got_error *
4038 log_move_cursor_down(struct tog_view *view, int page)
4040 struct tog_log_view_state *s = &view->state.log;
4041 const struct got_error *err = NULL;
4042 int eos = view->nlines - 2;
4044 if (s->first_displayed_entry == NULL)
4045 return NULL;
4047 if (s->thread_args.log_complete &&
4048 s->selected_entry->idx >= s->commits->ncommits - 1)
4049 return NULL;
4051 if (view_is_hsplit_top(view))
4052 --eos; /* border consumes the last line */
4054 if (!page) {
4055 if (s->selected < MIN(eos, s->commits->ncommits - 1))
4056 ++s->selected;
4057 else
4058 err = log_scroll_down(view, 1);
4059 } else if (s->thread_args.load_all && s->thread_args.log_complete) {
4060 struct commit_queue_entry *entry;
4061 int n;
4063 s->selected = 0;
4064 entry = TAILQ_LAST(&s->commits->head, commit_queue_head);
4065 s->last_displayed_entry = entry;
4066 for (n = 0; n <= eos; n++) {
4067 if (entry == NULL)
4068 break;
4069 s->first_displayed_entry = entry;
4070 entry = TAILQ_PREV(entry, commit_queue_head, entry);
4072 if (n > 0)
4073 s->selected = n - 1;
4074 } else {
4075 if (s->last_displayed_entry->idx == s->commits->ncommits - 1 &&
4076 s->thread_args.log_complete)
4077 s->selected += MIN(page,
4078 s->commits->ncommits - s->selected_entry->idx - 1);
4079 else
4080 err = log_scroll_down(view, page);
4082 if (err)
4083 return err;
4086 * We might necessarily overshoot in horizontal
4087 * splits; if so, select the last displayed commit.
4089 if (view_is_hsplit_top(view) && s->first_displayed_entry &&
4090 s->last_displayed_entry) {
4091 s->selected = MIN(s->selected,
4092 s->last_displayed_entry->idx -
4093 s->first_displayed_entry->idx);
4096 select_commit(s);
4098 if (s->thread_args.log_complete &&
4099 s->selected_entry->idx == s->commits->ncommits - 1)
4100 view->count = 0;
4102 return NULL;
4105 static void
4106 view_get_split(struct tog_view *view, int *y, int *x)
4108 *x = 0;
4109 *y = 0;
4111 if (view->mode == TOG_VIEW_SPLIT_HRZN) {
4112 if (view->child && view->child->resized_y)
4113 *y = view->child->resized_y;
4114 else if (view->resized_y)
4115 *y = view->resized_y;
4116 else
4117 *y = view_split_begin_y(view->lines);
4118 } else if (view->mode == TOG_VIEW_SPLIT_VERT) {
4119 if (view->child && view->child->resized_x)
4120 *x = view->child->resized_x;
4121 else if (view->resized_x)
4122 *x = view->resized_x;
4123 else
4124 *x = view_split_begin_x(view->begin_x);
4128 /* Split view horizontally at y and offset view->state->selected line. */
4129 static const struct got_error *
4130 view_init_hsplit(struct tog_view *view, int y)
4132 const struct got_error *err = NULL;
4134 view->nlines = y;
4135 view->ncols = COLS;
4136 err = view_resize(view);
4137 if (err)
4138 return err;
4140 err = offset_selection_down(view);
4142 return err;
4145 static const struct got_error *
4146 log_goto_line(struct tog_view *view, int nlines)
4148 const struct got_error *err = NULL;
4149 struct tog_log_view_state *s = &view->state.log;
4150 int g, idx = s->selected_entry->idx;
4152 if (s->first_displayed_entry == NULL || s->last_displayed_entry == NULL)
4153 return NULL;
4155 g = view->gline;
4156 view->gline = 0;
4158 if (g >= s->first_displayed_entry->idx + 1 &&
4159 g <= s->last_displayed_entry->idx + 1 &&
4160 g - s->first_displayed_entry->idx - 1 < nlines) {
4161 s->selected = g - s->first_displayed_entry->idx - 1;
4162 select_commit(s);
4163 return NULL;
4166 if (idx + 1 < g) {
4167 err = log_move_cursor_down(view, g - idx - 1);
4168 if (!err && g > s->selected_entry->idx + 1)
4169 err = log_move_cursor_down(view,
4170 g - s->first_displayed_entry->idx - 1);
4171 if (err)
4172 return err;
4173 } else if (idx + 1 > g)
4174 log_move_cursor_up(view, idx - g + 1, 0);
4176 if (g < nlines && s->first_displayed_entry->idx == 0)
4177 s->selected = g - 1;
4179 select_commit(s);
4180 return NULL;
4184 static void
4185 horizontal_scroll_input(struct tog_view *view, int ch)
4188 switch (ch) {
4189 case KEY_LEFT:
4190 case 'h':
4191 view->x -= MIN(view->x, 2);
4192 if (view->x <= 0)
4193 view->count = 0;
4194 break;
4195 case KEY_RIGHT:
4196 case 'l':
4197 if (view->x + view->ncols / 2 < view->maxx)
4198 view->x += 2;
4199 else
4200 view->count = 0;
4201 break;
4202 case '0':
4203 view->x = 0;
4204 break;
4205 case '$':
4206 view->x = MAX(view->maxx - view->ncols / 2, 0);
4207 view->count = 0;
4208 break;
4209 default:
4210 break;
4214 static void
4215 log_mark_commit(struct tog_log_view_state *s)
4217 if (s->selected_entry == s->marked_entry)
4218 s->marked_entry = NULL;
4219 else
4220 s->marked_entry = s->selected_entry;
4223 static const struct got_error *
4224 input_log_view(struct tog_view **new_view, struct tog_view *view, int ch)
4226 const struct got_error *err = NULL;
4227 struct tog_log_view_state *s = &view->state.log;
4228 int eos, nscroll;
4230 if (s->thread_args.load_all) {
4231 if (ch == CTRL('g') || ch == KEY_BACKSPACE)
4232 s->thread_args.load_all = 0;
4233 else if (s->thread_args.log_complete) {
4234 err = log_move_cursor_down(view, s->commits->ncommits);
4235 s->thread_args.load_all = 0;
4237 if (err)
4238 return err;
4241 eos = nscroll = view->nlines - 1;
4242 if (view_is_hsplit_top(view))
4243 --eos; /* border */
4245 if (view->gline)
4246 return log_goto_line(view, eos);
4248 switch (ch) {
4249 case '&':
4250 err = limit_log_view(view);
4251 break;
4252 case 'q':
4253 s->quit = 1;
4254 break;
4255 case '0':
4256 case '$':
4257 case KEY_RIGHT:
4258 case 'l':
4259 case KEY_LEFT:
4260 case 'h':
4261 horizontal_scroll_input(view, ch);
4262 break;
4263 case 'k':
4264 case KEY_UP:
4265 case '<':
4266 case ',':
4267 case CTRL('p'):
4268 log_move_cursor_up(view, 0, 0);
4269 break;
4270 case 'g':
4271 case '=':
4272 case KEY_HOME:
4273 log_move_cursor_up(view, 0, 1);
4274 view->count = 0;
4275 break;
4276 case CTRL('u'):
4277 case 'u':
4278 nscroll /= 2;
4279 /* FALL THROUGH */
4280 case KEY_PPAGE:
4281 case CTRL('b'):
4282 case 'b':
4283 log_move_cursor_up(view, nscroll, 0);
4284 break;
4285 case 'j':
4286 case KEY_DOWN:
4287 case '>':
4288 case '.':
4289 case CTRL('n'):
4290 err = log_move_cursor_down(view, 0);
4291 break;
4292 case '@':
4293 s->use_committer = !s->use_committer;
4294 view->action = s->use_committer ?
4295 "show committer" : "show commit author";
4296 break;
4297 case 'G':
4298 case '*':
4299 case KEY_END: {
4300 /* We don't know yet how many commits, so we're forced to
4301 * traverse them all. */
4302 view->count = 0;
4303 s->thread_args.load_all = 1;
4304 if (!s->thread_args.log_complete)
4305 return trigger_log_thread(view, 0);
4306 err = log_move_cursor_down(view, s->commits->ncommits);
4307 s->thread_args.load_all = 0;
4308 break;
4310 case CTRL('d'):
4311 case 'd':
4312 nscroll /= 2;
4313 /* FALL THROUGH */
4314 case KEY_NPAGE:
4315 case CTRL('f'):
4316 case 'f':
4317 case ' ':
4318 err = log_move_cursor_down(view, nscroll);
4319 break;
4320 case KEY_RESIZE:
4321 if (s->selected > view->nlines - 2)
4322 s->selected = view->nlines - 2;
4323 if (s->selected > s->commits->ncommits - 1)
4324 s->selected = s->commits->ncommits - 1;
4325 select_commit(s);
4326 if (s->commits->ncommits < view->nlines - 1 &&
4327 !s->thread_args.log_complete) {
4328 s->thread_args.commits_needed += (view->nlines - 1) -
4329 s->commits->ncommits;
4330 err = trigger_log_thread(view, 1);
4332 break;
4333 case KEY_ENTER:
4334 case '\r':
4335 view->count = 0;
4336 if (s->selected_entry == NULL)
4337 break;
4338 err = view_request_new(new_view, view, TOG_VIEW_DIFF);
4339 break;
4340 case 'T':
4341 view->count = 0;
4342 if (s->selected_entry == NULL)
4343 break;
4344 err = view_request_new(new_view, view, TOG_VIEW_TREE);
4345 break;
4346 case KEY_BACKSPACE:
4347 case CTRL('l'):
4348 case 'B':
4349 view->count = 0;
4350 if (ch == KEY_BACKSPACE &&
4351 got_path_is_root_dir(s->in_repo_path))
4352 break;
4353 err = stop_log_thread(s);
4354 if (err)
4355 return err;
4356 if (ch == KEY_BACKSPACE) {
4357 char *parent_path;
4358 err = got_path_dirname(&parent_path, s->in_repo_path);
4359 if (err)
4360 return err;
4361 free(s->in_repo_path);
4362 s->in_repo_path = parent_path;
4363 s->thread_args.in_repo_path = s->in_repo_path;
4364 } else if (ch == CTRL('l')) {
4365 struct got_object_id *start_id;
4366 err = got_repo_match_object_id(&start_id, NULL,
4367 s->head_ref_name ? s->head_ref_name : GOT_REF_HEAD,
4368 GOT_OBJ_TYPE_COMMIT, &tog_refs, s->repo);
4369 if (err) {
4370 if (s->head_ref_name == NULL ||
4371 err->code != GOT_ERR_NOT_REF)
4372 return err;
4373 /* Try to cope with deleted references. */
4374 free(s->head_ref_name);
4375 s->head_ref_name = NULL;
4376 err = got_repo_match_object_id(&start_id,
4377 NULL, GOT_REF_HEAD, GOT_OBJ_TYPE_COMMIT,
4378 &tog_refs, s->repo);
4379 if (err)
4380 return err;
4382 free(s->start_id);
4383 s->start_id = start_id;
4384 s->thread_args.start_id = s->start_id;
4385 } else /* 'B' */
4386 s->log_branches = !s->log_branches;
4388 if (s->thread_args.pack_fds == NULL) {
4389 err = got_repo_pack_fds_open(&s->thread_args.pack_fds);
4390 if (err)
4391 return err;
4393 err = got_repo_open(&s->thread_args.repo,
4394 got_repo_get_path(s->repo), NULL,
4395 s->thread_args.pack_fds);
4396 if (err)
4397 return err;
4398 tog_free_refs();
4399 err = tog_load_refs(s->repo, 0);
4400 if (err)
4401 return err;
4402 err = got_commit_graph_open(&s->thread_args.graph,
4403 s->in_repo_path, !s->log_branches);
4404 if (err)
4405 return err;
4406 err = got_commit_graph_bfsort(s->thread_args.graph,
4407 s->start_id, s->repo, NULL, NULL);
4408 if (err)
4409 return err;
4410 free_commits(&s->real_commits);
4411 free_commits(&s->limit_commits);
4412 s->first_displayed_entry = NULL;
4413 s->last_displayed_entry = NULL;
4414 s->selected_entry = NULL;
4415 s->selected = 0;
4416 s->thread_args.log_complete = 0;
4417 s->quit = 0;
4418 s->thread_args.commits_needed = view->lines;
4419 s->matched_entry = NULL;
4420 s->search_entry = NULL;
4421 view->offset = 0;
4422 break;
4423 case 'm':
4424 log_mark_commit(s);
4425 break;
4426 case 'R':
4427 view->count = 0;
4428 err = view_request_new(new_view, view, TOG_VIEW_REF);
4429 break;
4430 default:
4431 view->count = 0;
4432 break;
4435 return err;
4438 static const struct got_error *
4439 apply_unveil(const char *repo_path, const char *worktree_path)
4441 const struct got_error *error;
4443 #ifdef PROFILE
4444 if (unveil("gmon.out", "rwc") != 0)
4445 return got_error_from_errno2("unveil", "gmon.out");
4446 #endif
4447 if (repo_path && unveil(repo_path, "r") != 0)
4448 return got_error_from_errno2("unveil", repo_path);
4450 if (worktree_path && unveil(worktree_path, "rwc") != 0)
4451 return got_error_from_errno2("unveil", worktree_path);
4453 if (unveil(GOT_TMPDIR_STR, "rwc") != 0)
4454 return got_error_from_errno2("unveil", GOT_TMPDIR_STR);
4456 error = got_privsep_unveil_exec_helpers();
4457 if (error != NULL)
4458 return error;
4460 if (unveil(NULL, NULL) != 0)
4461 return got_error_from_errno("unveil");
4463 return NULL;
4466 static const struct got_error *
4467 init_mock_term(const char *test_script_path)
4469 const struct got_error *err = NULL;
4470 const char *screen_dump_path;
4471 int in;
4473 if (test_script_path == NULL || *test_script_path == '\0')
4474 return got_error_msg(GOT_ERR_IO, "TOG_TEST_SCRIPT not defined");
4476 tog_io.f = fopen(test_script_path, "re");
4477 if (tog_io.f == NULL) {
4478 err = got_error_from_errno_fmt("fopen: %s",
4479 test_script_path);
4480 goto done;
4483 /* test mode, we don't want any output */
4484 tog_io.cout = fopen("/dev/null", "w+");
4485 if (tog_io.cout == NULL) {
4486 err = got_error_from_errno2("fopen", "/dev/null");
4487 goto done;
4490 in = dup(fileno(tog_io.cout));
4491 if (in == -1) {
4492 err = got_error_from_errno("dup");
4493 goto done;
4495 tog_io.cin = fdopen(in, "r");
4496 if (tog_io.cin == NULL) {
4497 err = got_error_from_errno("fdopen");
4498 close(in);
4499 goto done;
4502 screen_dump_path = getenv("TOG_SCR_DUMP");
4503 if (screen_dump_path == NULL || *screen_dump_path == '\0')
4504 return got_error_msg(GOT_ERR_IO, "TOG_SCR_DUMP not defined");
4505 tog_io.sdump = fopen(screen_dump_path, "we");
4506 if (tog_io.sdump == NULL) {
4507 err = got_error_from_errno2("fopen", screen_dump_path);
4508 goto done;
4511 if (fseeko(tog_io.f, 0L, SEEK_SET) == -1) {
4512 err = got_error_from_errno("fseeko");
4513 goto done;
4516 if (newterm(NULL, tog_io.cout, tog_io.cin) == NULL)
4517 err = got_error_msg(GOT_ERR_IO,
4518 "newterm: failed to initialise curses");
4520 using_mock_io = 1;
4522 done:
4523 if (err)
4524 tog_io_close();
4525 return err;
4528 static void
4529 init_curses(void)
4531 if (using_mock_io) /* In test mode we use a fake terminal */
4532 return;
4534 initscr();
4536 cbreak();
4537 halfdelay(1); /* Fast refresh while initial view is loading. */
4538 noecho();
4539 nonl();
4540 intrflush(stdscr, FALSE);
4541 keypad(stdscr, TRUE);
4542 curs_set(0);
4543 if (getenv("TOG_COLORS") != NULL) {
4544 start_color();
4545 use_default_colors();
4548 return;
4551 static const struct got_error *
4552 set_tog_base_commit(struct got_repository *repo, struct got_worktree *worktree)
4554 tog_base_commit.id = got_object_id_dup(
4555 got_worktree_get_base_commit_id(worktree));
4556 if (tog_base_commit.id == NULL)
4557 return got_error_from_errno( "got_object_id_dup");
4559 return NULL;
4562 static const struct got_error *
4563 get_in_repo_path_from_argv0(char **in_repo_path, int argc, char *argv[],
4564 struct got_repository *repo, struct got_worktree *worktree)
4566 const struct got_error *err = NULL;
4568 if (argc == 0) {
4569 *in_repo_path = strdup("/");
4570 if (*in_repo_path == NULL)
4571 return got_error_from_errno("strdup");
4572 return NULL;
4575 if (worktree) {
4576 const char *prefix = got_worktree_get_path_prefix(worktree);
4577 char *p;
4579 err = got_worktree_resolve_path(&p, worktree, argv[0]);
4580 if (err)
4581 return err;
4582 if (asprintf(in_repo_path, "%s%s%s", prefix,
4583 (p[0] != '\0' && !got_path_is_root_dir(prefix)) ? "/" : "",
4584 p) == -1) {
4585 err = got_error_from_errno("asprintf");
4586 *in_repo_path = NULL;
4588 free(p);
4589 } else
4590 err = got_repo_map_path(in_repo_path, repo, argv[0]);
4592 return err;
4595 static const struct got_error *
4596 cmd_log(int argc, char *argv[])
4598 const struct got_error *error;
4599 struct got_repository *repo = NULL;
4600 struct got_worktree *worktree = NULL;
4601 struct got_object_id *start_id = NULL;
4602 char *in_repo_path = NULL, *repo_path = NULL, *cwd = NULL;
4603 char *keyword_idstr = NULL, *start_commit = NULL, *label = NULL;
4604 struct got_reference *ref = NULL;
4605 const char *head_ref_name = NULL;
4606 int ch, log_branches = 0;
4607 struct tog_view *view;
4608 int *pack_fds = NULL;
4610 while ((ch = getopt(argc, argv, "bc:r:")) != -1) {
4611 switch (ch) {
4612 case 'b':
4613 log_branches = 1;
4614 break;
4615 case 'c':
4616 start_commit = optarg;
4617 break;
4618 case 'r':
4619 repo_path = realpath(optarg, NULL);
4620 if (repo_path == NULL)
4621 return got_error_from_errno2("realpath",
4622 optarg);
4623 break;
4624 default:
4625 usage_log();
4626 /* NOTREACHED */
4630 argc -= optind;
4631 argv += optind;
4633 if (argc > 1)
4634 usage_log();
4636 error = got_repo_pack_fds_open(&pack_fds);
4637 if (error != NULL)
4638 goto done;
4640 if (repo_path == NULL) {
4641 cwd = getcwd(NULL, 0);
4642 if (cwd == NULL) {
4643 error = got_error_from_errno("getcwd");
4644 goto done;
4646 error = got_worktree_open(&worktree, cwd, NULL);
4647 if (error && error->code != GOT_ERR_NOT_WORKTREE)
4648 goto done;
4649 if (worktree)
4650 repo_path =
4651 strdup(got_worktree_get_repo_path(worktree));
4652 else
4653 repo_path = strdup(cwd);
4654 if (repo_path == NULL) {
4655 error = got_error_from_errno("strdup");
4656 goto done;
4660 error = got_repo_open(&repo, repo_path, NULL, pack_fds);
4661 if (error != NULL)
4662 goto done;
4664 error = get_in_repo_path_from_argv0(&in_repo_path, argc, argv,
4665 repo, worktree);
4666 if (error)
4667 goto done;
4669 init_curses();
4671 error = apply_unveil(got_repo_get_path(repo),
4672 worktree ? got_worktree_get_root_path(worktree) : NULL);
4673 if (error)
4674 goto done;
4676 /* already loaded by tog_log_with_path()? */
4677 if (TAILQ_EMPTY(&tog_refs)) {
4678 error = tog_load_refs(repo, 0);
4679 if (error)
4680 goto done;
4683 if (start_commit == NULL) {
4684 error = got_repo_match_object_id(&start_id, &label,
4685 worktree ? got_worktree_get_head_ref_name(worktree) :
4686 GOT_REF_HEAD, GOT_OBJ_TYPE_COMMIT, &tog_refs, repo);
4687 if (error)
4688 goto done;
4689 head_ref_name = label;
4690 } else {
4691 error = got_keyword_to_idstr(&keyword_idstr, start_commit,
4692 repo, worktree);
4693 if (error != NULL)
4694 goto done;
4695 if (keyword_idstr != NULL)
4696 start_commit = keyword_idstr;
4698 error = got_ref_open(&ref, repo, start_commit, 0);
4699 if (error == NULL)
4700 head_ref_name = got_ref_get_name(ref);
4701 else if (error->code != GOT_ERR_NOT_REF)
4702 goto done;
4703 error = got_repo_match_object_id(&start_id, NULL,
4704 start_commit, GOT_OBJ_TYPE_COMMIT, &tog_refs, repo);
4705 if (error)
4706 goto done;
4709 view = view_open(0, 0, 0, 0, TOG_VIEW_LOG);
4710 if (view == NULL) {
4711 error = got_error_from_errno("view_open");
4712 goto done;
4715 if (worktree) {
4716 error = set_tog_base_commit(repo, worktree);
4717 if (error != NULL)
4718 goto done;
4721 error = open_log_view(view, start_id, repo, head_ref_name,
4722 in_repo_path, log_branches, worktree);
4723 if (error)
4724 goto done;
4726 if (worktree) {
4727 /* The work tree will be closed by the log thread. */
4728 worktree = NULL;
4731 error = view_loop(view);
4733 done:
4734 free(tog_base_commit.id);
4735 free(keyword_idstr);
4736 free(in_repo_path);
4737 free(repo_path);
4738 free(cwd);
4739 free(start_id);
4740 free(label);
4741 if (ref)
4742 got_ref_close(ref);
4743 if (repo) {
4744 const struct got_error *close_err = got_repo_close(repo);
4745 if (error == NULL)
4746 error = close_err;
4748 if (worktree)
4749 got_worktree_close(worktree);
4750 if (pack_fds) {
4751 const struct got_error *pack_err =
4752 got_repo_pack_fds_close(pack_fds);
4753 if (error == NULL)
4754 error = pack_err;
4756 tog_free_refs();
4757 return error;
4760 __dead static void
4761 usage_diff(void)
4763 endwin();
4764 fprintf(stderr, "usage: %s diff [-aw] [-C number] [-r repository-path] "
4765 "object1 object2\n", getprogname());
4766 exit(1);
4769 static int
4770 match_line(const char *line, regex_t *regex, size_t nmatch,
4771 regmatch_t *regmatch)
4773 return regexec(regex, line, nmatch, regmatch, 0) == 0;
4776 static struct tog_color *
4777 match_color(struct tog_colors *colors, const char *line)
4779 struct tog_color *tc = NULL;
4781 STAILQ_FOREACH(tc, colors, entry) {
4782 if (match_line(line, &tc->regex, 0, NULL))
4783 return tc;
4786 return NULL;
4789 static const struct got_error *
4790 add_matched_line(int *wtotal, const char *line, int wlimit, int col_tab_align,
4791 WINDOW *window, int skipcol, regmatch_t *regmatch)
4793 const struct got_error *err = NULL;
4794 char *exstr = NULL;
4795 wchar_t *wline = NULL;
4796 int rme, rms, n, width, scrollx;
4797 int width0 = 0, width1 = 0, width2 = 0;
4798 char *seg0 = NULL, *seg1 = NULL, *seg2 = NULL;
4800 *wtotal = 0;
4802 rms = regmatch->rm_so;
4803 rme = regmatch->rm_eo;
4805 err = expand_tab(&exstr, line);
4806 if (err)
4807 return err;
4809 /* Split the line into 3 segments, according to match offsets. */
4810 seg0 = strndup(exstr, rms);
4811 if (seg0 == NULL) {
4812 err = got_error_from_errno("strndup");
4813 goto done;
4815 seg1 = strndup(exstr + rms, rme - rms);
4816 if (seg1 == NULL) {
4817 err = got_error_from_errno("strndup");
4818 goto done;
4820 seg2 = strdup(exstr + rme);
4821 if (seg2 == NULL) {
4822 err = got_error_from_errno("strndup");
4823 goto done;
4826 /* draw up to matched token if we haven't scrolled past it */
4827 err = format_line(&wline, &width0, NULL, seg0, 0, wlimit,
4828 col_tab_align, 1);
4829 if (err)
4830 goto done;
4831 n = MAX(width0 - skipcol, 0);
4832 if (n) {
4833 free(wline);
4834 err = format_line(&wline, &width, &scrollx, seg0, skipcol,
4835 wlimit, col_tab_align, 1);
4836 if (err)
4837 goto done;
4838 waddwstr(window, &wline[scrollx]);
4839 wlimit -= width;
4840 *wtotal += width;
4843 if (wlimit > 0) {
4844 int i = 0, w = 0;
4845 size_t wlen;
4847 free(wline);
4848 err = format_line(&wline, &width1, NULL, seg1, 0, wlimit,
4849 col_tab_align, 1);
4850 if (err)
4851 goto done;
4852 wlen = wcslen(wline);
4853 while (i < wlen) {
4854 width = wcwidth(wline[i]);
4855 if (width == -1) {
4856 /* should not happen, tabs are expanded */
4857 err = got_error(GOT_ERR_RANGE);
4858 goto done;
4860 if (width0 + w + width > skipcol)
4861 break;
4862 w += width;
4863 i++;
4865 /* draw (visible part of) matched token (if scrolled into it) */
4866 if (width1 - w > 0) {
4867 wattron(window, A_STANDOUT);
4868 waddwstr(window, &wline[i]);
4869 wattroff(window, A_STANDOUT);
4870 wlimit -= (width1 - w);
4871 *wtotal += (width1 - w);
4875 if (wlimit > 0) { /* draw rest of line */
4876 free(wline);
4877 if (skipcol > width0 + width1) {
4878 err = format_line(&wline, &width2, &scrollx, seg2,
4879 skipcol - (width0 + width1), wlimit,
4880 col_tab_align, 1);
4881 if (err)
4882 goto done;
4883 waddwstr(window, &wline[scrollx]);
4884 } else {
4885 err = format_line(&wline, &width2, NULL, seg2, 0,
4886 wlimit, col_tab_align, 1);
4887 if (err)
4888 goto done;
4889 waddwstr(window, wline);
4891 *wtotal += width2;
4893 done:
4894 free(wline);
4895 free(exstr);
4896 free(seg0);
4897 free(seg1);
4898 free(seg2);
4899 return err;
4902 static int
4903 gotoline(struct tog_view *view, int *lineno, int *nprinted)
4905 FILE *f = NULL;
4906 int *eof, *first, *selected;
4908 if (view->type == TOG_VIEW_DIFF) {
4909 struct tog_diff_view_state *s = &view->state.diff;
4911 first = &s->first_displayed_line;
4912 selected = first;
4913 eof = &s->eof;
4914 f = s->f;
4915 } else if (view->type == TOG_VIEW_HELP) {
4916 struct tog_help_view_state *s = &view->state.help;
4918 first = &s->first_displayed_line;
4919 selected = first;
4920 eof = &s->eof;
4921 f = s->f;
4922 } else if (view->type == TOG_VIEW_BLAME) {
4923 struct tog_blame_view_state *s = &view->state.blame;
4925 first = &s->first_displayed_line;
4926 selected = &s->selected_line;
4927 eof = &s->eof;
4928 f = s->blame.f;
4929 } else
4930 return 0;
4932 /* Center gline in the middle of the page like vi(1). */
4933 if (*lineno < view->gline - (view->nlines - 3) / 2)
4934 return 0;
4935 if (*first != 1 && (*lineno > view->gline - (view->nlines - 3) / 2)) {
4936 rewind(f);
4937 *eof = 0;
4938 *first = 1;
4939 *lineno = 0;
4940 *nprinted = 0;
4941 return 0;
4944 *selected = view->gline <= (view->nlines - 3) / 2 ?
4945 view->gline : (view->nlines - 3) / 2 + 1;
4946 view->gline = 0;
4948 return 1;
4951 static const struct got_error *
4952 draw_file(struct tog_view *view, const char *header)
4954 struct tog_diff_view_state *s = &view->state.diff;
4955 regmatch_t *regmatch = &view->regmatch;
4956 const struct got_error *err;
4957 int nprinted = 0;
4958 char *line;
4959 size_t linesize = 0;
4960 ssize_t linelen;
4961 wchar_t *wline;
4962 int width;
4963 int max_lines = view->nlines;
4964 int nlines = s->nlines;
4965 off_t line_offset;
4967 s->lineno = s->first_displayed_line - 1;
4968 line_offset = s->lines[s->first_displayed_line - 1].offset;
4969 if (fseeko(s->f, line_offset, SEEK_SET) == -1)
4970 return got_error_from_errno("fseek");
4972 werase(view->window);
4974 if (view->gline > s->nlines - 1)
4975 view->gline = s->nlines - 1;
4977 if (header) {
4978 int ln = view->gline ? view->gline <= (view->nlines - 3) / 2 ?
4979 1 : view->gline - (view->nlines - 3) / 2 :
4980 s->lineno + s->selected_line;
4982 if (asprintf(&line, "[%d/%d] %s", ln, nlines, header) == -1)
4983 return got_error_from_errno("asprintf");
4984 err = format_line(&wline, &width, NULL, line, 0, view->ncols,
4985 0, 0);
4986 free(line);
4987 if (err)
4988 return err;
4990 if (view_needs_focus_indication(view))
4991 wstandout(view->window);
4992 waddwstr(view->window, wline);
4993 free(wline);
4994 wline = NULL;
4995 while (width++ < view->ncols)
4996 waddch(view->window, ' ');
4997 if (view_needs_focus_indication(view))
4998 wstandend(view->window);
5000 if (max_lines <= 1)
5001 return NULL;
5002 max_lines--;
5005 s->eof = 0;
5006 view->maxx = 0;
5007 line = NULL;
5008 while (max_lines > 0 && nprinted < max_lines) {
5009 enum got_diff_line_type linetype;
5010 attr_t attr = 0;
5012 linelen = getline(&line, &linesize, s->f);
5013 if (linelen == -1) {
5014 if (feof(s->f)) {
5015 s->eof = 1;
5016 break;
5018 free(line);
5019 return got_ferror(s->f, GOT_ERR_IO);
5022 if (++s->lineno < s->first_displayed_line)
5023 continue;
5024 if (view->gline && !gotoline(view, &s->lineno, &nprinted))
5025 continue;
5026 if (s->lineno == view->hiline)
5027 attr = A_STANDOUT;
5029 /* Set view->maxx based on full line length. */
5030 err = format_line(&wline, &width, NULL, line, 0, INT_MAX, 0,
5031 view->x ? 1 : 0);
5032 if (err) {
5033 free(line);
5034 return err;
5036 view->maxx = MAX(view->maxx, width);
5037 free(wline);
5038 wline = NULL;
5040 linetype = s->lines[s->lineno].type;
5041 if (linetype > GOT_DIFF_LINE_LOGMSG &&
5042 linetype < GOT_DIFF_LINE_CONTEXT)
5043 attr |= COLOR_PAIR(linetype);
5044 if (attr)
5045 wattron(view->window, attr);
5046 if (s->first_displayed_line + nprinted == s->matched_line &&
5047 regmatch->rm_so >= 0 && regmatch->rm_so < regmatch->rm_eo) {
5048 err = add_matched_line(&width, line, view->ncols, 0,
5049 view->window, view->x, regmatch);
5050 if (err) {
5051 free(line);
5052 return err;
5054 } else {
5055 int skip;
5056 err = format_line(&wline, &width, &skip, line,
5057 view->x, view->ncols, 0, view->x ? 1 : 0);
5058 if (err) {
5059 free(line);
5060 return err;
5062 waddwstr(view->window, &wline[skip]);
5063 free(wline);
5064 wline = NULL;
5066 if (s->lineno == view->hiline) {
5067 /* highlight full gline length */
5068 while (width++ < view->ncols)
5069 waddch(view->window, ' ');
5070 } else {
5071 if (width <= view->ncols - 1)
5072 waddch(view->window, '\n');
5074 if (attr)
5075 wattroff(view->window, attr);
5076 if (++nprinted == 1)
5077 s->first_displayed_line = s->lineno;
5079 free(line);
5080 if (nprinted >= 1)
5081 s->last_displayed_line = s->first_displayed_line +
5082 (nprinted - 1);
5083 else
5084 s->last_displayed_line = s->first_displayed_line;
5086 view_border(view);
5088 if (s->eof) {
5089 while (nprinted < view->nlines) {
5090 waddch(view->window, '\n');
5091 nprinted++;
5094 err = format_line(&wline, &width, NULL, TOG_EOF_STRING, 0,
5095 view->ncols, 0, 0);
5096 if (err) {
5097 return err;
5100 wstandout(view->window);
5101 waddwstr(view->window, wline);
5102 free(wline);
5103 wline = NULL;
5104 wstandend(view->window);
5107 return NULL;
5110 static char *
5111 get_datestr(time_t *time, char *datebuf)
5113 struct tm mytm, *tm;
5114 char *p, *s;
5116 tm = gmtime_r(time, &mytm);
5117 if (tm == NULL)
5118 return NULL;
5119 s = asctime_r(tm, datebuf);
5120 if (s == NULL)
5121 return NULL;
5122 p = strchr(s, '\n');
5123 if (p)
5124 *p = '\0';
5125 return s;
5128 static const struct got_error *
5129 add_line_metadata(struct got_diff_line **lines, size_t *nlines,
5130 off_t off, uint8_t type)
5132 struct got_diff_line *p;
5134 p = reallocarray(*lines, *nlines + 1, sizeof(**lines));
5135 if (p == NULL)
5136 return got_error_from_errno("reallocarray");
5137 *lines = p;
5138 (*lines)[*nlines].offset = off;
5139 (*lines)[*nlines].type = type;
5140 (*nlines)++;
5142 return NULL;
5145 static const struct got_error *
5146 cat_diff(FILE *dst, FILE *src, struct got_diff_line **d_lines, size_t *d_nlines,
5147 struct got_diff_line *s_lines, size_t s_nlines)
5149 struct got_diff_line *p;
5150 char buf[BUFSIZ];
5151 size_t i, r;
5153 if (fseeko(src, 0L, SEEK_SET) == -1)
5154 return got_error_from_errno("fseeko");
5156 for (;;) {
5157 r = fread(buf, 1, sizeof(buf), src);
5158 if (r == 0) {
5159 if (ferror(src))
5160 return got_error_from_errno("fread");
5161 if (feof(src))
5162 break;
5164 if (fwrite(buf, 1, r, dst) != r)
5165 return got_ferror(dst, GOT_ERR_IO);
5168 if (s_nlines == 0 && *d_nlines == 0)
5169 return NULL;
5172 * If commit info was in dst, increment line offsets
5173 * of the appended diff content, but skip s_lines[0]
5174 * because offset zero is already in *d_lines.
5176 if (*d_nlines > 0) {
5177 for (i = 1; i < s_nlines; ++i)
5178 s_lines[i].offset += (*d_lines)[*d_nlines - 1].offset;
5180 if (s_nlines > 0) {
5181 --s_nlines;
5182 ++s_lines;
5186 p = reallocarray(*d_lines, *d_nlines + s_nlines, sizeof(*p));
5187 if (p == NULL) {
5188 /* d_lines is freed in close_diff_view() */
5189 return got_error_from_errno("reallocarray");
5192 *d_lines = p;
5194 memcpy(*d_lines + *d_nlines, s_lines, s_nlines * sizeof(*s_lines));
5195 *d_nlines += s_nlines;
5197 return NULL;
5200 static const struct got_error *
5201 write_diffstat(FILE *outfile, struct got_diff_line **lines, size_t *nlines,
5202 struct got_diffstat_cb_arg *dsa)
5204 const struct got_error *err;
5205 struct got_pathlist_entry *pe;
5206 off_t offset;
5207 int n;
5209 if (*nlines == 0) {
5210 err = add_line_metadata(lines, nlines, 0, GOT_DIFF_LINE_NONE);
5211 if (err != NULL)
5212 return err;
5213 offset = 0;
5214 } else
5215 offset = (*lines)[*nlines - 1].offset;
5217 TAILQ_FOREACH(pe, dsa->paths, entry) {
5218 struct got_diff_changed_path *cp = pe->data;
5219 int pad = dsa->max_path_len - pe->path_len + 1;
5221 n = fprintf(outfile, "%c %s%*c | %*d+ %*d-\n", cp->status,
5222 pe->path, pad, ' ', dsa->add_cols + 1, cp->add,
5223 dsa->rm_cols + 1, cp->rm);
5224 if (n < 0)
5225 return got_error_from_errno("fprintf");
5227 offset += n;
5228 err = add_line_metadata(lines, nlines, offset,
5229 GOT_DIFF_LINE_CHANGES);
5230 if (err != NULL)
5231 return err;
5234 if (fputc('\n', outfile) == EOF)
5235 return got_error_from_errno("fputc");
5237 offset++;
5238 err = add_line_metadata(lines, nlines, offset, GOT_DIFF_LINE_NONE);
5239 if (err != NULL)
5240 return err;
5242 n = fprintf(outfile,
5243 "%d file%s changed, %d insertion%s(+), %d deletion%s(-)\n",
5244 dsa->nfiles, dsa->nfiles > 1 ? "s" : "", dsa->ins,
5245 dsa->ins != 1 ? "s" : "", dsa->del, dsa->del != 1 ? "s" : "");
5246 if (n < 0)
5247 return got_error_from_errno("fprintf");
5249 offset += n;
5250 err = add_line_metadata(lines, nlines, offset, GOT_DIFF_LINE_NONE);
5251 if (err != NULL)
5252 return err;
5254 if (fputc('\n', outfile) == EOF)
5255 return got_error_from_errno("fputc");
5257 offset++;
5258 return add_line_metadata(lines, nlines, offset, GOT_DIFF_LINE_NONE);
5261 static const struct got_error *
5262 write_commit_info(struct got_diff_line **lines, size_t *nlines,
5263 struct got_object_id *commit_id, struct got_reflist_head *refs,
5264 struct got_repository *repo, int ignore_ws, int force_text_diff,
5265 struct got_diffstat_cb_arg *dsa, FILE *outfile)
5267 const struct got_error *err = NULL;
5268 char datebuf[26], *datestr;
5269 struct got_commit_object *commit;
5270 char *id_str = NULL, *logmsg = NULL, *s = NULL, *line;
5271 time_t committer_time;
5272 const char *author, *committer;
5273 char *refs_str = NULL;
5274 off_t outoff = 0;
5275 int n;
5277 err = build_refs_str(&refs_str, refs, commit_id, repo);
5278 if (err)
5279 return err;
5281 err = got_object_open_as_commit(&commit, repo, commit_id);
5282 if (err)
5283 return err;
5285 err = got_object_id_str(&id_str, commit_id);
5286 if (err) {
5287 err = got_error_from_errno("got_object_id_str");
5288 goto done;
5291 err = add_line_metadata(lines, nlines, 0, GOT_DIFF_LINE_NONE);
5292 if (err)
5293 goto done;
5295 n = fprintf(outfile, "commit %s%s%s%s\n", id_str, refs_str ? " (" : "",
5296 refs_str ? refs_str : "", refs_str ? ")" : "");
5297 if (n < 0) {
5298 err = got_error_from_errno("fprintf");
5299 goto done;
5301 outoff += n;
5302 err = add_line_metadata(lines, nlines, outoff, GOT_DIFF_LINE_META);
5303 if (err)
5304 goto done;
5306 n = fprintf(outfile, "from: %s\n",
5307 got_object_commit_get_author(commit));
5308 if (n < 0) {
5309 err = got_error_from_errno("fprintf");
5310 goto done;
5312 outoff += n;
5313 err = add_line_metadata(lines, nlines, outoff, GOT_DIFF_LINE_AUTHOR);
5314 if (err)
5315 goto done;
5317 author = got_object_commit_get_author(commit);
5318 committer = got_object_commit_get_committer(commit);
5319 if (strcmp(author, committer) != 0) {
5320 n = fprintf(outfile, "via: %s\n", committer);
5321 if (n < 0) {
5322 err = got_error_from_errno("fprintf");
5323 goto done;
5325 outoff += n;
5326 err = add_line_metadata(lines, nlines, outoff,
5327 GOT_DIFF_LINE_AUTHOR);
5328 if (err)
5329 goto done;
5331 committer_time = got_object_commit_get_committer_time(commit);
5332 datestr = get_datestr(&committer_time, datebuf);
5333 if (datestr) {
5334 n = fprintf(outfile, "date: %s UTC\n", datestr);
5335 if (n < 0) {
5336 err = got_error_from_errno("fprintf");
5337 goto done;
5339 outoff += n;
5340 err = add_line_metadata(lines, nlines, outoff,
5341 GOT_DIFF_LINE_DATE);
5342 if (err)
5343 goto done;
5345 if (got_object_commit_get_nparents(commit) > 1) {
5346 const struct got_object_id_queue *parent_ids;
5347 struct got_object_qid *qid;
5348 int pn = 1;
5349 parent_ids = got_object_commit_get_parent_ids(commit);
5350 STAILQ_FOREACH(qid, parent_ids, entry) {
5351 err = got_object_id_str(&id_str, &qid->id);
5352 if (err)
5353 goto done;
5354 n = fprintf(outfile, "parent %d: %s\n", pn++, id_str);
5355 if (n < 0) {
5356 err = got_error_from_errno("fprintf");
5357 goto done;
5359 outoff += n;
5360 err = add_line_metadata(lines, nlines, outoff,
5361 GOT_DIFF_LINE_META);
5362 if (err)
5363 goto done;
5364 free(id_str);
5365 id_str = NULL;
5369 err = got_object_commit_get_logmsg(&logmsg, commit);
5370 if (err)
5371 goto done;
5372 s = logmsg;
5373 while ((line = strsep(&s, "\n")) != NULL) {
5374 n = fprintf(outfile, "%s\n", line);
5375 if (n < 0) {
5376 err = got_error_from_errno("fprintf");
5377 goto done;
5379 outoff += n;
5380 err = add_line_metadata(lines, nlines, outoff,
5381 GOT_DIFF_LINE_LOGMSG);
5382 if (err)
5383 goto done;
5386 done:
5387 free(id_str);
5388 free(logmsg);
5389 free(refs_str);
5390 got_object_commit_close(commit);
5391 return err;
5394 static const struct got_error *
5395 create_diff(struct tog_diff_view_state *s)
5397 const struct got_error *err = NULL;
5398 FILE *tmp_diff_file = NULL;
5399 int obj_type;
5400 struct got_diff_line *lines = NULL;
5401 struct got_pathlist_head changed_paths;
5402 struct got_commit_object *commit2 = NULL;
5403 struct got_diffstat_cb_arg dsa;
5404 size_t nlines = 0;
5406 TAILQ_INIT(&changed_paths);
5407 memset(&dsa, 0, sizeof(dsa));
5408 dsa.paths = &changed_paths;
5409 dsa.diff_algo = tog_diff_algo;
5410 dsa.force_text = s->force_text_diff;
5411 dsa.ignore_ws = s->ignore_whitespace;
5413 free(s->lines);
5414 s->lines = malloc(sizeof(*s->lines));
5415 if (s->lines == NULL)
5416 return got_error_from_errno("malloc");
5417 s->nlines = 0;
5419 if (s->f && fclose(s->f) == EOF) {
5420 s->f = NULL;
5421 return got_error_from_errno("fclose");
5424 s->f = got_opentemp();
5425 if (s->f == NULL)
5426 return got_error_from_errno("got_opentemp");
5429 * The diffstat requires the diff to be built first, but we want the
5430 * diffstat to precede the diff when displayed. Build the diff first
5431 * in the temporary file and write the diffstat and/or commit info to
5432 * the persistent file (s->f) from which views are drawn, then append
5433 * the diff from the temp file to the diffstat/commit info in s->f.
5435 tmp_diff_file = got_opentemp();
5436 if (tmp_diff_file == NULL)
5437 return got_error_from_errno("got_opentemp");
5439 lines = malloc(sizeof(*lines));
5440 if (lines == NULL) {
5441 err = got_error_from_errno("malloc");
5442 goto done;
5445 if (s->id1)
5446 err = got_object_get_type(&obj_type, s->repo, s->id1);
5447 else
5448 err = got_object_get_type(&obj_type, s->repo, s->id2);
5449 if (err)
5450 goto done;
5452 switch (obj_type) {
5453 case GOT_OBJ_TYPE_BLOB:
5454 err = got_diff_objects_as_blobs(&lines, &nlines,
5455 s->f1, s->f2, s->fd1, s->fd2, s->id1, s->id2,
5456 NULL, NULL, tog_diff_algo, s->diff_context,
5457 s->ignore_whitespace, s->force_text_diff, &dsa, s->repo,
5458 tmp_diff_file);
5459 if (err != NULL)
5460 goto done;
5461 break;
5462 case GOT_OBJ_TYPE_TREE:
5463 err = got_diff_objects_as_trees(&lines, &nlines,
5464 s->f1, s->f2, s->fd1, s->fd2, s->id1, s->id2, NULL, "", "",
5465 tog_diff_algo, s->diff_context, s->ignore_whitespace,
5466 s->force_text_diff, &dsa, s->repo, tmp_diff_file);
5467 if (err != NULL)
5468 goto done;
5469 break;
5470 case GOT_OBJ_TYPE_COMMIT: {
5471 const struct got_object_id_queue *parent_ids;
5472 struct got_object_qid *pid;
5473 struct got_reflist_head *refs;
5475 err = got_diff_objects_as_commits(&lines, &nlines,
5476 s->f1, s->f2, s->fd1, s->fd2, s->id1, s->id2, NULL,
5477 tog_diff_algo, s->diff_context, s->ignore_whitespace,
5478 s->force_text_diff, &dsa, s->repo, tmp_diff_file);
5479 if (err)
5480 goto done;
5482 refs = got_reflist_object_id_map_lookup(tog_refs_idmap, s->id2);
5483 /* Show commit info if we're diffing to a parent/root commit. */
5484 if (s->id1 == NULL) {
5485 err = write_commit_info(&s->lines, &s->nlines, s->id2,
5486 refs, s->repo, s->ignore_whitespace,
5487 s->force_text_diff, &dsa, s->f);
5488 if (err)
5489 goto done;
5490 } else {
5491 err = got_object_open_as_commit(&commit2, s->repo,
5492 s->id2);
5493 if (err)
5494 goto done;
5496 parent_ids = got_object_commit_get_parent_ids(commit2);
5497 STAILQ_FOREACH(pid, parent_ids, entry) {
5498 if (got_object_id_cmp(s->id1, &pid->id) == 0) {
5499 err = write_commit_info(&s->lines,
5500 &s->nlines, s->id2, refs, s->repo,
5501 s->ignore_whitespace,
5502 s->force_text_diff, &dsa, s->f);
5503 if (err)
5504 goto done;
5505 break;
5509 break;
5511 default:
5512 err = got_error(GOT_ERR_OBJ_TYPE);
5513 goto done;
5516 err = write_diffstat(s->f, &s->lines, &s->nlines, &dsa);
5517 if (err != NULL)
5518 goto done;
5520 err = cat_diff(s->f, tmp_diff_file, &s->lines, &s->nlines,
5521 lines, nlines);
5523 done:
5524 free(lines);
5525 if (commit2 != NULL)
5526 got_object_commit_close(commit2);
5527 got_pathlist_free(&changed_paths, GOT_PATHLIST_FREE_ALL);
5528 if (s->f && fflush(s->f) != 0 && err == NULL)
5529 err = got_error_from_errno("fflush");
5530 if (tmp_diff_file && fclose(tmp_diff_file) == EOF && err == NULL)
5531 err = got_error_from_errno("fclose");
5532 return err;
5535 static void
5536 diff_view_indicate_progress(struct tog_view *view)
5538 mvwaddstr(view->window, 0, 0, "diffing...");
5539 update_panels();
5540 doupdate();
5543 static const struct got_error *
5544 search_start_diff_view(struct tog_view *view)
5546 struct tog_diff_view_state *s = &view->state.diff;
5548 s->matched_line = 0;
5549 return NULL;
5552 static void
5553 search_setup_diff_view(struct tog_view *view, FILE **f, off_t **line_offsets,
5554 size_t *nlines, int **first, int **last, int **match, int **selected)
5556 struct tog_diff_view_state *s = &view->state.diff;
5558 *f = s->f;
5559 *nlines = s->nlines;
5560 *line_offsets = NULL;
5561 *match = &s->matched_line;
5562 *first = &s->first_displayed_line;
5563 *last = &s->last_displayed_line;
5564 *selected = &s->selected_line;
5567 static const struct got_error *
5568 search_next_view_match(struct tog_view *view)
5570 const struct got_error *err = NULL;
5571 FILE *f;
5572 int lineno;
5573 char *line = NULL;
5574 size_t linesize = 0;
5575 ssize_t linelen;
5576 off_t *line_offsets;
5577 size_t nlines = 0;
5578 int *first, *last, *match, *selected;
5580 if (!view->search_setup)
5581 return got_error_msg(GOT_ERR_NOT_IMPL,
5582 "view search not supported");
5583 view->search_setup(view, &f, &line_offsets, &nlines, &first, &last,
5584 &match, &selected);
5586 if (!view->searching) {
5587 view->search_next_done = TOG_SEARCH_HAVE_MORE;
5588 return NULL;
5591 if (*match) {
5592 if (view->searching == TOG_SEARCH_FORWARD)
5593 lineno = *first + 1;
5594 else
5595 lineno = *first - 1;
5596 } else
5597 lineno = *first - 1 + *selected;
5599 while (1) {
5600 off_t offset;
5602 if (lineno <= 0 || lineno > nlines) {
5603 if (*match == 0) {
5604 view->search_next_done = TOG_SEARCH_HAVE_MORE;
5605 break;
5608 if (view->searching == TOG_SEARCH_FORWARD)
5609 lineno = 1;
5610 else
5611 lineno = nlines;
5614 offset = view->type == TOG_VIEW_DIFF ?
5615 view->state.diff.lines[lineno - 1].offset :
5616 line_offsets[lineno - 1];
5617 if (fseeko(f, offset, SEEK_SET) != 0) {
5618 free(line);
5619 return got_error_from_errno("fseeko");
5621 linelen = getline(&line, &linesize, f);
5622 if (linelen != -1) {
5623 char *exstr;
5624 err = expand_tab(&exstr, line);
5625 if (err)
5626 break;
5627 if (match_line(exstr, &view->regex, 1,
5628 &view->regmatch)) {
5629 view->search_next_done = TOG_SEARCH_HAVE_MORE;
5630 *match = lineno;
5631 free(exstr);
5632 break;
5634 free(exstr);
5636 if (view->searching == TOG_SEARCH_FORWARD)
5637 lineno++;
5638 else
5639 lineno--;
5641 free(line);
5643 if (*match) {
5644 *first = *match;
5645 *selected = 1;
5648 return err;
5651 static const struct got_error *
5652 close_diff_view(struct tog_view *view)
5654 const struct got_error *err = NULL;
5655 struct tog_diff_view_state *s = &view->state.diff;
5657 free(s->id1);
5658 s->id1 = NULL;
5659 free(s->id2);
5660 s->id2 = NULL;
5661 free(s->action);
5662 s->action = NULL;
5663 if (s->f && fclose(s->f) == EOF)
5664 err = got_error_from_errno("fclose");
5665 s->f = NULL;
5666 if (s->f1 && fclose(s->f1) == EOF && err == NULL)
5667 err = got_error_from_errno("fclose");
5668 s->f1 = NULL;
5669 if (s->f2 && fclose(s->f2) == EOF && err == NULL)
5670 err = got_error_from_errno("fclose");
5671 s->f2 = NULL;
5672 if (s->fd1 != -1 && close(s->fd1) == -1 && err == NULL)
5673 err = got_error_from_errno("close");
5674 s->fd1 = -1;
5675 if (s->fd2 != -1 && close(s->fd2) == -1 && err == NULL)
5676 err = got_error_from_errno("close");
5677 s->fd2 = -1;
5678 free(s->lines);
5679 s->lines = NULL;
5680 s->nlines = 0;
5681 return err;
5684 static const struct got_error *
5685 open_diff_view(struct tog_view *view, struct got_object_id *id1,
5686 struct got_object_id *id2, const char *label1, const char *label2,
5687 int diff_context, int ignore_whitespace, int force_text_diff,
5688 struct tog_view *parent_view, struct got_repository *repo)
5690 const struct got_error *err;
5691 struct tog_diff_view_state *s = &view->state.diff;
5693 memset(s, 0, sizeof(*s));
5694 s->fd1 = -1;
5695 s->fd2 = -1;
5697 if (id1 != NULL && id2 != NULL) {
5698 int type1, type2;
5700 err = got_object_get_type(&type1, repo, id1);
5701 if (err)
5702 goto done;
5703 err = got_object_get_type(&type2, repo, id2);
5704 if (err)
5705 goto done;
5707 if (type1 != type2) {
5708 err = got_error(GOT_ERR_OBJ_TYPE);
5709 goto done;
5712 s->first_displayed_line = 1;
5713 s->last_displayed_line = view->nlines;
5714 s->selected_line = 1;
5715 s->repo = repo;
5716 s->label1 = label1;
5717 s->label2 = label2;
5719 if (id1) {
5720 s->id1 = got_object_id_dup(id1);
5721 if (s->id1 == NULL) {
5722 err = got_error_from_errno("got_object_id_dup");
5723 goto done;
5725 } else
5726 s->id1 = NULL;
5728 s->id2 = got_object_id_dup(id2);
5729 if (s->id2 == NULL) {
5730 err = got_error_from_errno("got_object_id_dup");
5731 goto done;
5734 s->f1 = got_opentemp();
5735 if (s->f1 == NULL) {
5736 err = got_error_from_errno("got_opentemp");
5737 goto done;
5740 s->f2 = got_opentemp();
5741 if (s->f2 == NULL) {
5742 err = got_error_from_errno("got_opentemp");
5743 goto done;
5746 s->fd1 = got_opentempfd();
5747 if (s->fd1 == -1) {
5748 err = got_error_from_errno("got_opentempfd");
5749 goto done;
5752 s->fd2 = got_opentempfd();
5753 if (s->fd2 == -1) {
5754 err = got_error_from_errno("got_opentempfd");
5755 goto done;
5758 s->diff_context = diff_context;
5759 s->ignore_whitespace = ignore_whitespace;
5760 s->force_text_diff = force_text_diff;
5761 s->parent_view = parent_view;
5762 s->repo = repo;
5764 if (has_colors() && getenv("TOG_COLORS") != NULL && !using_mock_io) {
5765 int rc;
5767 rc = init_pair(GOT_DIFF_LINE_MINUS,
5768 get_color_value("TOG_COLOR_DIFF_MINUS"), -1);
5769 if (rc != ERR)
5770 rc = init_pair(GOT_DIFF_LINE_PLUS,
5771 get_color_value("TOG_COLOR_DIFF_PLUS"), -1);
5772 if (rc != ERR)
5773 rc = init_pair(GOT_DIFF_LINE_HUNK,
5774 get_color_value("TOG_COLOR_DIFF_CHUNK_HEADER"), -1);
5775 if (rc != ERR)
5776 rc = init_pair(GOT_DIFF_LINE_META,
5777 get_color_value("TOG_COLOR_DIFF_META"), -1);
5778 if (rc != ERR)
5779 rc = init_pair(GOT_DIFF_LINE_CHANGES,
5780 get_color_value("TOG_COLOR_DIFF_META"), -1);
5781 if (rc != ERR)
5782 rc = init_pair(GOT_DIFF_LINE_BLOB_MIN,
5783 get_color_value("TOG_COLOR_DIFF_META"), -1);
5784 if (rc != ERR)
5785 rc = init_pair(GOT_DIFF_LINE_BLOB_PLUS,
5786 get_color_value("TOG_COLOR_DIFF_META"), -1);
5787 if (rc != ERR)
5788 rc = init_pair(GOT_DIFF_LINE_AUTHOR,
5789 get_color_value("TOG_COLOR_AUTHOR"), -1);
5790 if (rc != ERR)
5791 rc = init_pair(GOT_DIFF_LINE_DATE,
5792 get_color_value("TOG_COLOR_DATE"), -1);
5793 if (rc == ERR) {
5794 err = got_error(GOT_ERR_RANGE);
5795 goto done;
5799 if (parent_view && parent_view->type == TOG_VIEW_LOG &&
5800 view_is_splitscreen(view))
5801 show_log_view(parent_view); /* draw border */
5802 diff_view_indicate_progress(view);
5804 err = create_diff(s);
5806 view->show = show_diff_view;
5807 view->input = input_diff_view;
5808 view->reset = reset_diff_view;
5809 view->close = close_diff_view;
5810 view->search_start = search_start_diff_view;
5811 view->search_setup = search_setup_diff_view;
5812 view->search_next = search_next_view_match;
5813 done:
5814 if (err) {
5815 if (view->close == NULL)
5816 close_diff_view(view);
5817 view_close(view);
5819 return err;
5822 static const struct got_error *
5823 show_diff_view(struct tog_view *view)
5825 const struct got_error *err;
5826 struct tog_diff_view_state *s = &view->state.diff;
5827 char *id_str1 = NULL, *id_str2, *header;
5828 const char *label1, *label2;
5830 if (s->id1) {
5831 err = got_object_id_str(&id_str1, s->id1);
5832 if (err)
5833 return err;
5834 label1 = s->label1 ? s->label1 : id_str1;
5835 } else
5836 label1 = "/dev/null";
5838 err = got_object_id_str(&id_str2, s->id2);
5839 if (err)
5840 return err;
5841 label2 = s->label2 ? s->label2 : id_str2;
5843 if (asprintf(&header, "diff %s %s", label1, label2) == -1) {
5844 err = got_error_from_errno("asprintf");
5845 free(id_str1);
5846 free(id_str2);
5847 return err;
5849 free(id_str1);
5850 free(id_str2);
5852 err = draw_file(view, header);
5853 free(header);
5854 return err;
5857 static const struct got_error *
5858 diff_write_patch(struct tog_view *view)
5860 const struct got_error *err;
5861 struct tog_diff_view_state *s = &view->state.diff;
5862 FILE *f = NULL;
5863 char buf[BUFSIZ], pathbase[PATH_MAX];
5864 char *idstr2, *idstr1 = NULL, *path = NULL;
5865 size_t r;
5866 off_t pos;
5867 int rc;
5869 if (s->action != NULL) {
5870 free(s->action);
5871 s->action = NULL;
5874 pos = ftello(s->f);
5875 if (pos == -1)
5876 return got_error_from_errno("ftello");
5877 if (fseeko(s->f, 0L, SEEK_SET) == -1)
5878 return got_error_from_errno("fseeko");
5880 if (s->id1 != NULL) {
5881 err = got_object_id_str(&idstr1, s->id1);
5882 if (err != NULL)
5883 return err;
5885 err = got_object_id_str(&idstr2, s->id2);
5886 if (err != NULL)
5887 goto done;
5889 rc = snprintf(pathbase, sizeof(pathbase), "%s/tog-%.8s-%.8s",
5890 GOT_TMPDIR_STR, idstr1 != NULL ? idstr1 : "empty", idstr2);
5891 if (rc < 0 || (size_t)rc >= sizeof(pathbase)) {
5892 err = got_error(rc < 0 ? GOT_ERR_IO : GOT_ERR_NO_SPACE);
5893 goto done;
5896 err = got_opentemp_named(&path, &f, pathbase, ".diff");
5897 if (err != NULL)
5898 goto done;
5900 while ((r = fread(buf, 1, sizeof(buf), s->f)) > 0) {
5901 if (fwrite(buf, 1, r, f) != r) {
5902 err = got_ferror(f, GOT_ERR_IO);
5903 goto done;
5907 if (ferror(s->f)) {
5908 err = got_error_from_errno("fread");
5909 goto done;
5911 if (fseeko(s->f, pos, SEEK_SET) == -1) {
5912 err = got_error_from_errno("fseeko");
5913 goto done;
5916 if (fflush(f) == EOF) {
5917 err = got_error_from_errno2("fflush", path);
5918 goto done;
5921 if (asprintf(&s->action, "patch file written to %s", path) == -1) {
5922 err = got_error_from_errno("asprintf");
5923 goto done;
5926 view->action = s->action;
5928 done:
5929 if (f != NULL && fclose(f) == EOF && err == NULL)
5930 err = got_error_from_errno2("fclose", path);
5931 free(path);
5932 free(idstr1);
5933 free(idstr2);
5934 return err;
5937 static const struct got_error *
5938 set_selected_commit(struct tog_diff_view_state *s,
5939 struct commit_queue_entry *entry)
5941 const struct got_error *err;
5942 const struct got_object_id_queue *parent_ids;
5943 struct got_commit_object *selected_commit;
5944 struct got_object_qid *pid;
5946 free(s->id2);
5947 s->id2 = got_object_id_dup(entry->id);
5948 if (s->id2 == NULL)
5949 return got_error_from_errno("got_object_id_dup");
5951 err = got_object_open_as_commit(&selected_commit, s->repo, entry->id);
5952 if (err)
5953 return err;
5954 parent_ids = got_object_commit_get_parent_ids(selected_commit);
5955 free(s->id1);
5956 pid = STAILQ_FIRST(parent_ids);
5957 s->id1 = pid ? got_object_id_dup(&pid->id) : NULL;
5958 got_object_commit_close(selected_commit);
5959 return NULL;
5962 static const struct got_error *
5963 reset_diff_view(struct tog_view *view)
5965 struct tog_diff_view_state *s = &view->state.diff;
5967 view->count = 0;
5968 wclear(view->window);
5969 s->first_displayed_line = 1;
5970 s->last_displayed_line = view->nlines;
5971 s->matched_line = 0;
5972 if (s->action != NULL) {
5973 free(s->action);
5974 s->action = NULL;
5976 diff_view_indicate_progress(view);
5977 return create_diff(s);
5980 static void
5981 diff_prev_index(struct tog_diff_view_state *s, enum got_diff_line_type type)
5983 int start, i;
5985 i = start = s->first_displayed_line - 1;
5987 while (s->lines[i].type != type) {
5988 if (i == 0)
5989 i = s->nlines - 1;
5990 if (--i == start)
5991 return; /* do nothing, requested type not in file */
5994 s->selected_line = 1;
5995 s->first_displayed_line = i;
5998 static void
5999 diff_next_index(struct tog_diff_view_state *s, enum got_diff_line_type type)
6001 int start, i;
6003 i = start = s->first_displayed_line + 1;
6005 while (s->lines[i].type != type) {
6006 if (i == s->nlines - 1)
6007 i = 0;
6008 if (++i == start)
6009 return; /* do nothing, requested type not in file */
6012 s->selected_line = 1;
6013 s->first_displayed_line = i;
6016 static struct got_object_id *get_selected_commit_id(struct tog_blame_line *,
6017 int, int, int);
6018 static struct got_object_id *get_annotation_for_line(struct tog_blame_line *,
6019 int, int);
6021 static const struct got_error *
6022 input_diff_view(struct tog_view **new_view, struct tog_view *view, int ch)
6024 const struct got_error *err = NULL;
6025 struct tog_diff_view_state *s = &view->state.diff;
6026 struct tog_log_view_state *ls;
6027 struct commit_queue_entry *old_selected_entry;
6028 char *line = NULL;
6029 size_t linesize = 0;
6030 ssize_t linelen;
6031 int i, nscroll = view->nlines - 1, up = 0;
6033 s->lineno = s->first_displayed_line - 1 + s->selected_line;
6035 if (s->action != NULL && ch != ERR) {
6036 free(s->action);
6037 s->action = NULL;
6038 view->action = NULL;
6041 switch (ch) {
6042 case '0':
6043 case '$':
6044 case KEY_RIGHT:
6045 case 'l':
6046 case KEY_LEFT:
6047 case 'h':
6048 horizontal_scroll_input(view, ch);
6049 break;
6050 case 'a':
6051 case 'w':
6052 if (ch == 'a') {
6053 s->force_text_diff = !s->force_text_diff;
6054 view->action = s->force_text_diff ?
6055 "force ASCII text enabled" :
6056 "force ASCII text disabled";
6058 else if (ch == 'w') {
6059 s->ignore_whitespace = !s->ignore_whitespace;
6060 view->action = s->ignore_whitespace ?
6061 "ignore whitespace enabled" :
6062 "ignore whitespace disabled";
6064 err = reset_diff_view(view);
6065 break;
6066 case 'g':
6067 case KEY_HOME:
6068 s->first_displayed_line = 1;
6069 view->count = 0;
6070 break;
6071 case 'G':
6072 case KEY_END:
6073 view->count = 0;
6074 if (s->eof)
6075 break;
6077 s->first_displayed_line = (s->nlines - view->nlines) + 2;
6078 s->eof = 1;
6079 break;
6080 case 'k':
6081 case KEY_UP:
6082 case CTRL('p'):
6083 if (s->first_displayed_line > 1)
6084 s->first_displayed_line--;
6085 else
6086 view->count = 0;
6087 break;
6088 case CTRL('u'):
6089 case 'u':
6090 nscroll /= 2;
6091 /* FALL THROUGH */
6092 case KEY_PPAGE:
6093 case CTRL('b'):
6094 case 'b':
6095 if (s->first_displayed_line == 1) {
6096 view->count = 0;
6097 break;
6099 i = 0;
6100 while (i++ < nscroll && s->first_displayed_line > 1)
6101 s->first_displayed_line--;
6102 break;
6103 case 'j':
6104 case KEY_DOWN:
6105 case CTRL('n'):
6106 if (!s->eof)
6107 s->first_displayed_line++;
6108 else
6109 view->count = 0;
6110 break;
6111 case CTRL('d'):
6112 case 'd':
6113 nscroll /= 2;
6114 /* FALL THROUGH */
6115 case KEY_NPAGE:
6116 case CTRL('f'):
6117 case 'f':
6118 case ' ':
6119 if (s->eof) {
6120 view->count = 0;
6121 break;
6123 i = 0;
6124 while (!s->eof && i++ < nscroll) {
6125 linelen = getline(&line, &linesize, s->f);
6126 s->first_displayed_line++;
6127 if (linelen == -1) {
6128 if (feof(s->f)) {
6129 s->eof = 1;
6130 } else
6131 err = got_ferror(s->f, GOT_ERR_IO);
6132 break;
6135 free(line);
6136 break;
6137 case '(':
6138 diff_prev_index(s, GOT_DIFF_LINE_BLOB_MIN);
6139 break;
6140 case ')':
6141 diff_next_index(s, GOT_DIFF_LINE_BLOB_MIN);
6142 break;
6143 case '{':
6144 diff_prev_index(s, GOT_DIFF_LINE_HUNK);
6145 break;
6146 case '}':
6147 diff_next_index(s, GOT_DIFF_LINE_HUNK);
6148 break;
6149 case '[':
6150 if (s->diff_context > 0) {
6151 s->diff_context--;
6152 s->matched_line = 0;
6153 diff_view_indicate_progress(view);
6154 err = create_diff(s);
6155 if (s->first_displayed_line + view->nlines - 1 >
6156 s->nlines) {
6157 s->first_displayed_line = 1;
6158 s->last_displayed_line = view->nlines;
6160 } else
6161 view->count = 0;
6162 break;
6163 case ']':
6164 if (s->diff_context < GOT_DIFF_MAX_CONTEXT) {
6165 s->diff_context++;
6166 s->matched_line = 0;
6167 diff_view_indicate_progress(view);
6168 err = create_diff(s);
6169 } else
6170 view->count = 0;
6171 break;
6172 case '<':
6173 case ',':
6174 case 'K':
6175 up = 1;
6176 /* FALL THROUGH */
6177 case '>':
6178 case '.':
6179 case 'J':
6180 if (s->parent_view == NULL) {
6181 view->count = 0;
6182 break;
6184 s->parent_view->count = view->count;
6186 if (s->parent_view->type == TOG_VIEW_LOG) {
6187 ls = &s->parent_view->state.log;
6188 old_selected_entry = ls->selected_entry;
6190 err = input_log_view(NULL, s->parent_view,
6191 up ? KEY_UP : KEY_DOWN);
6192 if (err)
6193 break;
6194 view->count = s->parent_view->count;
6196 if (old_selected_entry == ls->selected_entry)
6197 break;
6199 log_mark_clear(ls);
6201 err = set_selected_commit(s, ls->selected_entry);
6202 if (err)
6203 break;
6204 } else if (s->parent_view->type == TOG_VIEW_BLAME) {
6205 struct tog_blame_view_state *bs;
6206 struct got_object_id *id, *prev_id;
6208 bs = &s->parent_view->state.blame;
6209 prev_id = get_annotation_for_line(bs->blame.lines,
6210 bs->blame.nlines, bs->last_diffed_line);
6212 err = input_blame_view(&view, s->parent_view,
6213 up ? KEY_UP : KEY_DOWN);
6214 if (err)
6215 break;
6216 view->count = s->parent_view->count;
6218 if (prev_id == NULL)
6219 break;
6220 id = get_selected_commit_id(bs->blame.lines,
6221 bs->blame.nlines, bs->first_displayed_line,
6222 bs->selected_line);
6223 if (id == NULL)
6224 break;
6226 if (!got_object_id_cmp(prev_id, id))
6227 break;
6229 err = input_blame_view(&view, s->parent_view, KEY_ENTER);
6230 if (err)
6231 break;
6233 s->first_displayed_line = 1;
6234 s->last_displayed_line = view->nlines;
6235 s->matched_line = 0;
6236 view->x = 0;
6238 diff_view_indicate_progress(view);
6239 err = create_diff(s);
6240 break;
6241 case 'p':
6242 err = diff_write_patch(view);
6243 break;
6244 default:
6245 view->count = 0;
6246 break;
6249 return err;
6252 static const struct got_error *
6253 cmd_diff(int argc, char *argv[])
6255 const struct got_error *error;
6256 struct got_repository *repo = NULL;
6257 struct got_worktree *worktree = NULL;
6258 struct got_object_id *id1 = NULL, *id2 = NULL;
6259 char *repo_path = NULL, *cwd = NULL;
6260 char *id_str1 = NULL, *id_str2 = NULL;
6261 char *keyword_idstr1 = NULL, *keyword_idstr2 = NULL;
6262 char *label1 = NULL, *label2 = NULL;
6263 int diff_context = 3, ignore_whitespace = 0;
6264 int ch, force_text_diff = 0;
6265 const char *errstr;
6266 struct tog_view *view;
6267 int *pack_fds = NULL;
6269 while ((ch = getopt(argc, argv, "aC:r:w")) != -1) {
6270 switch (ch) {
6271 case 'a':
6272 force_text_diff = 1;
6273 break;
6274 case 'C':
6275 diff_context = strtonum(optarg, 0, GOT_DIFF_MAX_CONTEXT,
6276 &errstr);
6277 if (errstr != NULL)
6278 errx(1, "number of context lines is %s: %s",
6279 errstr, errstr);
6280 break;
6281 case 'r':
6282 repo_path = realpath(optarg, NULL);
6283 if (repo_path == NULL)
6284 return got_error_from_errno2("realpath",
6285 optarg);
6286 got_path_strip_trailing_slashes(repo_path);
6287 break;
6288 case 'w':
6289 ignore_whitespace = 1;
6290 break;
6291 default:
6292 usage_diff();
6293 /* NOTREACHED */
6297 argc -= optind;
6298 argv += optind;
6300 if (argc == 0) {
6301 usage_diff(); /* TODO show local worktree changes */
6302 } else if (argc == 2) {
6303 id_str1 = argv[0];
6304 id_str2 = argv[1];
6305 } else
6306 usage_diff();
6308 error = got_repo_pack_fds_open(&pack_fds);
6309 if (error)
6310 goto done;
6312 if (repo_path == NULL) {
6313 cwd = getcwd(NULL, 0);
6314 if (cwd == NULL)
6315 return got_error_from_errno("getcwd");
6316 error = got_worktree_open(&worktree, cwd, NULL);
6317 if (error && error->code != GOT_ERR_NOT_WORKTREE)
6318 goto done;
6319 if (worktree)
6320 repo_path =
6321 strdup(got_worktree_get_repo_path(worktree));
6322 else
6323 repo_path = strdup(cwd);
6324 if (repo_path == NULL) {
6325 error = got_error_from_errno("strdup");
6326 goto done;
6330 error = got_repo_open(&repo, repo_path, NULL, pack_fds);
6331 if (error)
6332 goto done;
6334 init_curses();
6336 error = apply_unveil(got_repo_get_path(repo), NULL);
6337 if (error)
6338 goto done;
6340 error = tog_load_refs(repo, 0);
6341 if (error)
6342 goto done;
6344 if (id_str1 != NULL) {
6345 error = got_keyword_to_idstr(&keyword_idstr1, id_str1,
6346 repo, worktree);
6347 if (error != NULL)
6348 goto done;
6349 if (keyword_idstr1 != NULL)
6350 id_str1 = keyword_idstr1;
6352 if (id_str2 != NULL) {
6353 error = got_keyword_to_idstr(&keyword_idstr2, id_str2,
6354 repo, worktree);
6355 if (error != NULL)
6356 goto done;
6357 if (keyword_idstr2 != NULL)
6358 id_str2 = keyword_idstr2;
6361 error = got_repo_match_object_id(&id1, &label1, id_str1,
6362 GOT_OBJ_TYPE_ANY, &tog_refs, repo);
6363 if (error)
6364 goto done;
6366 error = got_repo_match_object_id(&id2, &label2, id_str2,
6367 GOT_OBJ_TYPE_ANY, &tog_refs, repo);
6368 if (error)
6369 goto done;
6371 view = view_open(0, 0, 0, 0, TOG_VIEW_DIFF);
6372 if (view == NULL) {
6373 error = got_error_from_errno("view_open");
6374 goto done;
6376 error = open_diff_view(view, id1, id2, label1, label2, diff_context,
6377 ignore_whitespace, force_text_diff, NULL, repo);
6378 if (error)
6379 goto done;
6381 if (worktree) {
6382 error = set_tog_base_commit(repo, worktree);
6383 if (error != NULL)
6384 goto done;
6386 /* Release work tree lock. */
6387 got_worktree_close(worktree);
6388 worktree = NULL;
6391 error = view_loop(view);
6393 done:
6394 free(tog_base_commit.id);
6395 free(keyword_idstr1);
6396 free(keyword_idstr2);
6397 free(label1);
6398 free(label2);
6399 free(id1);
6400 free(id2);
6401 free(repo_path);
6402 free(cwd);
6403 if (repo) {
6404 const struct got_error *close_err = got_repo_close(repo);
6405 if (error == NULL)
6406 error = close_err;
6408 if (worktree)
6409 got_worktree_close(worktree);
6410 if (pack_fds) {
6411 const struct got_error *pack_err =
6412 got_repo_pack_fds_close(pack_fds);
6413 if (error == NULL)
6414 error = pack_err;
6416 tog_free_refs();
6417 return error;
6420 __dead static void
6421 usage_blame(void)
6423 endwin();
6424 fprintf(stderr,
6425 "usage: %s blame [-c commit] [-r repository-path] path\n",
6426 getprogname());
6427 exit(1);
6430 struct tog_blame_line {
6431 int annotated;
6432 struct got_object_id *id;
6435 static const struct got_error *
6436 draw_blame(struct tog_view *view)
6438 struct tog_blame_view_state *s = &view->state.blame;
6439 struct tog_blame *blame = &s->blame;
6440 regmatch_t *regmatch = &view->regmatch;
6441 const struct got_error *err;
6442 int lineno = 0, nprinted = 0;
6443 char *line = NULL;
6444 size_t linesize = 0;
6445 ssize_t linelen;
6446 wchar_t *wline;
6447 int width;
6448 struct tog_blame_line *blame_line;
6449 struct got_object_id *prev_id = NULL;
6450 char *id_str;
6451 struct tog_color *tc;
6453 err = got_object_id_str(&id_str, &s->blamed_commit->id);
6454 if (err)
6455 return err;
6457 rewind(blame->f);
6458 werase(view->window);
6460 if (asprintf(&line, "commit %s", id_str) == -1) {
6461 err = got_error_from_errno("asprintf");
6462 free(id_str);
6463 return err;
6466 err = format_line(&wline, &width, NULL, line, 0, view->ncols, 0, 0);
6467 free(line);
6468 line = NULL;
6469 if (err)
6470 return err;
6471 if (view_needs_focus_indication(view))
6472 wstandout(view->window);
6473 tc = get_color(&s->colors, TOG_COLOR_COMMIT);
6474 if (tc)
6475 wattr_on(view->window, COLOR_PAIR(tc->colorpair), NULL);
6476 waddwstr(view->window, wline);
6477 while (width++ < view->ncols)
6478 waddch(view->window, ' ');
6479 if (tc)
6480 wattr_off(view->window, COLOR_PAIR(tc->colorpair), NULL);
6481 if (view_needs_focus_indication(view))
6482 wstandend(view->window);
6483 free(wline);
6484 wline = NULL;
6486 if (view->gline > blame->nlines)
6487 view->gline = blame->nlines;
6489 if (tog_io.wait_for_ui) {
6490 struct tog_blame_thread_args *bta = &s->blame.thread_args;
6491 int rc;
6493 rc = pthread_cond_wait(&bta->blame_complete, &tog_mutex);
6494 if (rc)
6495 return got_error_set_errno(rc, "pthread_cond_wait");
6496 tog_io.wait_for_ui = 0;
6499 if (asprintf(&line, "[%d/%d] %s%s", view->gline ? view->gline :
6500 s->first_displayed_line - 1 + s->selected_line, blame->nlines,
6501 s->blame_complete ? "" : "annotating... ", s->path) == -1) {
6502 free(id_str);
6503 return got_error_from_errno("asprintf");
6505 free(id_str);
6506 err = format_line(&wline, &width, NULL, line, 0, view->ncols, 0, 0);
6507 free(line);
6508 line = NULL;
6509 if (err)
6510 return err;
6511 waddwstr(view->window, wline);
6512 free(wline);
6513 wline = NULL;
6514 if (width < view->ncols - 1)
6515 waddch(view->window, '\n');
6517 s->eof = 0;
6518 view->maxx = 0;
6519 while (nprinted < view->nlines - 2) {
6520 linelen = getline(&line, &linesize, blame->f);
6521 if (linelen == -1) {
6522 if (feof(blame->f)) {
6523 s->eof = 1;
6524 break;
6526 free(line);
6527 return got_ferror(blame->f, GOT_ERR_IO);
6529 if (++lineno < s->first_displayed_line)
6530 continue;
6531 if (view->gline && !gotoline(view, &lineno, &nprinted))
6532 continue;
6534 /* Set view->maxx based on full line length. */
6535 err = format_line(&wline, &width, NULL, line, 0, INT_MAX, 9, 1);
6536 if (err) {
6537 free(line);
6538 return err;
6540 free(wline);
6541 wline = NULL;
6542 view->maxx = MAX(view->maxx, width);
6544 if (nprinted == s->selected_line - 1)
6545 wstandout(view->window);
6547 if (blame->nlines > 0) {
6548 blame_line = &blame->lines[lineno - 1];
6549 if (blame_line->annotated && prev_id &&
6550 got_object_id_cmp(prev_id, blame_line->id) == 0 &&
6551 !(nprinted == s->selected_line - 1)) {
6552 waddstr(view->window, " ");
6553 } else if (blame_line->annotated) {
6554 char *id_str;
6555 err = got_object_id_str(&id_str,
6556 blame_line->id);
6557 if (err) {
6558 free(line);
6559 return err;
6561 tc = get_color(&s->colors, TOG_COLOR_COMMIT);
6562 if (tc)
6563 wattr_on(view->window,
6564 COLOR_PAIR(tc->colorpair), NULL);
6565 wprintw(view->window, "%.8s", id_str);
6566 if (tc)
6567 wattr_off(view->window,
6568 COLOR_PAIR(tc->colorpair), NULL);
6569 free(id_str);
6570 prev_id = blame_line->id;
6571 } else {
6572 waddstr(view->window, "........");
6573 prev_id = NULL;
6575 } else {
6576 waddstr(view->window, "........");
6577 prev_id = NULL;
6580 if (nprinted == s->selected_line - 1)
6581 wstandend(view->window);
6582 waddstr(view->window, " ");
6584 if (view->ncols <= 9) {
6585 width = 9;
6586 } else if (s->first_displayed_line + nprinted ==
6587 s->matched_line &&
6588 regmatch->rm_so >= 0 && regmatch->rm_so < regmatch->rm_eo) {
6589 err = add_matched_line(&width, line, view->ncols - 9, 9,
6590 view->window, view->x, regmatch);
6591 if (err) {
6592 free(line);
6593 return err;
6595 width += 9;
6596 } else {
6597 int skip;
6598 err = format_line(&wline, &width, &skip, line,
6599 view->x, view->ncols - 9, 9, 1);
6600 if (err) {
6601 free(line);
6602 return err;
6604 waddwstr(view->window, &wline[skip]);
6605 width += 9;
6606 free(wline);
6607 wline = NULL;
6610 if (width <= view->ncols - 1)
6611 waddch(view->window, '\n');
6612 if (++nprinted == 1)
6613 s->first_displayed_line = lineno;
6615 free(line);
6616 s->last_displayed_line = lineno;
6618 view_border(view);
6620 return NULL;
6623 static const struct got_error *
6624 blame_cb(void *arg, int nlines, int lineno,
6625 struct got_commit_object *commit, struct got_object_id *id)
6627 const struct got_error *err = NULL;
6628 struct tog_blame_cb_args *a = arg;
6629 struct tog_blame_line *line;
6630 int errcode;
6632 if (nlines != a->nlines ||
6633 (lineno != -1 && lineno < 1) || lineno > a->nlines)
6634 return got_error(GOT_ERR_RANGE);
6636 errcode = pthread_mutex_lock(&tog_mutex);
6637 if (errcode)
6638 return got_error_set_errno(errcode, "pthread_mutex_lock");
6640 if (*a->quit) { /* user has quit the blame view */
6641 err = got_error(GOT_ERR_ITER_COMPLETED);
6642 goto done;
6645 if (lineno == -1)
6646 goto done; /* no change in this commit */
6648 line = &a->lines[lineno - 1];
6649 if (line->annotated)
6650 goto done;
6652 line->id = got_object_id_dup(id);
6653 if (line->id == NULL) {
6654 err = got_error_from_errno("got_object_id_dup");
6655 goto done;
6657 line->annotated = 1;
6658 done:
6659 errcode = pthread_mutex_unlock(&tog_mutex);
6660 if (errcode)
6661 err = got_error_set_errno(errcode, "pthread_mutex_unlock");
6662 return err;
6665 static void *
6666 blame_thread(void *arg)
6668 const struct got_error *err, *close_err;
6669 struct tog_blame_thread_args *ta = arg;
6670 struct tog_blame_cb_args *a = ta->cb_args;
6671 int errcode, fd1 = -1, fd2 = -1;
6672 FILE *f1 = NULL, *f2 = NULL;
6674 fd1 = got_opentempfd();
6675 if (fd1 == -1)
6676 return (void *)got_error_from_errno("got_opentempfd");
6678 fd2 = got_opentempfd();
6679 if (fd2 == -1) {
6680 err = got_error_from_errno("got_opentempfd");
6681 goto done;
6684 f1 = got_opentemp();
6685 if (f1 == NULL) {
6686 err = (void *)got_error_from_errno("got_opentemp");
6687 goto done;
6689 f2 = got_opentemp();
6690 if (f2 == NULL) {
6691 err = (void *)got_error_from_errno("got_opentemp");
6692 goto done;
6695 err = block_signals_used_by_main_thread();
6696 if (err)
6697 goto done;
6699 err = got_blame(ta->path, a->commit_id, ta->repo,
6700 tog_diff_algo, blame_cb, ta->cb_args,
6701 ta->cancel_cb, ta->cancel_arg, fd1, fd2, f1, f2);
6702 if (err && err->code == GOT_ERR_CANCELLED)
6703 err = NULL;
6705 errcode = pthread_mutex_lock(&tog_mutex);
6706 if (errcode) {
6707 err = got_error_set_errno(errcode, "pthread_mutex_lock");
6708 goto done;
6711 close_err = got_repo_close(ta->repo);
6712 if (err == NULL)
6713 err = close_err;
6714 ta->repo = NULL;
6715 *ta->complete = 1;
6717 if (tog_io.wait_for_ui) {
6718 errcode = pthread_cond_signal(&ta->blame_complete);
6719 if (errcode && err == NULL)
6720 err = got_error_set_errno(errcode,
6721 "pthread_cond_signal");
6724 errcode = pthread_mutex_unlock(&tog_mutex);
6725 if (errcode && err == NULL)
6726 err = got_error_set_errno(errcode, "pthread_mutex_unlock");
6728 done:
6729 if (fd1 != -1 && close(fd1) == -1 && err == NULL)
6730 err = got_error_from_errno("close");
6731 if (fd2 != -1 && close(fd2) == -1 && err == NULL)
6732 err = got_error_from_errno("close");
6733 if (f1 && fclose(f1) == EOF && err == NULL)
6734 err = got_error_from_errno("fclose");
6735 if (f2 && fclose(f2) == EOF && err == NULL)
6736 err = got_error_from_errno("fclose");
6738 return (void *)err;
6741 static struct got_object_id *
6742 get_selected_commit_id(struct tog_blame_line *lines, int nlines,
6743 int first_displayed_line, int selected_line)
6745 struct tog_blame_line *line;
6747 if (nlines <= 0)
6748 return NULL;
6750 line = &lines[first_displayed_line - 1 + selected_line - 1];
6751 if (!line->annotated)
6752 return NULL;
6754 return line->id;
6757 static struct got_object_id *
6758 get_annotation_for_line(struct tog_blame_line *lines, int nlines,
6759 int lineno)
6761 struct tog_blame_line *line;
6763 if (nlines <= 0 || lineno >= nlines)
6764 return NULL;
6766 line = &lines[lineno - 1];
6767 if (!line->annotated)
6768 return NULL;
6770 return line->id;
6773 static const struct got_error *
6774 stop_blame(struct tog_blame *blame)
6776 const struct got_error *err = NULL;
6777 int i;
6779 if (blame->thread) {
6780 int errcode;
6781 errcode = pthread_mutex_unlock(&tog_mutex);
6782 if (errcode)
6783 return got_error_set_errno(errcode,
6784 "pthread_mutex_unlock");
6785 errcode = pthread_join(blame->thread, (void **)&err);
6786 if (errcode)
6787 return got_error_set_errno(errcode, "pthread_join");
6788 errcode = pthread_mutex_lock(&tog_mutex);
6789 if (errcode)
6790 return got_error_set_errno(errcode,
6791 "pthread_mutex_lock");
6792 if (err && err->code == GOT_ERR_ITER_COMPLETED)
6793 err = NULL;
6794 blame->thread = 0; //NULL;
6796 if (blame->thread_args.repo) {
6797 const struct got_error *close_err;
6798 close_err = got_repo_close(blame->thread_args.repo);
6799 if (err == NULL)
6800 err = close_err;
6801 blame->thread_args.repo = NULL;
6803 if (blame->f) {
6804 if (fclose(blame->f) == EOF && err == NULL)
6805 err = got_error_from_errno("fclose");
6806 blame->f = NULL;
6808 if (blame->lines) {
6809 for (i = 0; i < blame->nlines; i++)
6810 free(blame->lines[i].id);
6811 free(blame->lines);
6812 blame->lines = NULL;
6814 free(blame->cb_args.commit_id);
6815 blame->cb_args.commit_id = NULL;
6816 if (blame->pack_fds) {
6817 const struct got_error *pack_err =
6818 got_repo_pack_fds_close(blame->pack_fds);
6819 if (err == NULL)
6820 err = pack_err;
6821 blame->pack_fds = NULL;
6823 free(blame->line_offsets);
6824 blame->line_offsets = NULL;
6825 return err;
6828 static const struct got_error *
6829 cancel_blame_view(void *arg)
6831 const struct got_error *err = NULL;
6832 int *done = arg;
6833 int errcode;
6835 errcode = pthread_mutex_lock(&tog_mutex);
6836 if (errcode)
6837 return got_error_set_errno(errcode,
6838 "pthread_mutex_unlock");
6840 if (*done)
6841 err = got_error(GOT_ERR_CANCELLED);
6843 errcode = pthread_mutex_unlock(&tog_mutex);
6844 if (errcode)
6845 return got_error_set_errno(errcode,
6846 "pthread_mutex_lock");
6848 return err;
6851 static const struct got_error *
6852 run_blame(struct tog_view *view)
6854 struct tog_blame_view_state *s = &view->state.blame;
6855 struct tog_blame *blame = &s->blame;
6856 const struct got_error *err = NULL;
6857 struct got_commit_object *commit = NULL;
6858 struct got_blob_object *blob = NULL;
6859 struct got_repository *thread_repo = NULL;
6860 struct got_object_id *obj_id = NULL;
6861 int obj_type, fd = -1;
6862 int *pack_fds = NULL;
6864 err = got_object_open_as_commit(&commit, s->repo,
6865 &s->blamed_commit->id);
6866 if (err)
6867 return err;
6869 fd = got_opentempfd();
6870 if (fd == -1) {
6871 err = got_error_from_errno("got_opentempfd");
6872 goto done;
6875 err = got_object_id_by_path(&obj_id, s->repo, commit, s->path);
6876 if (err)
6877 goto done;
6879 err = got_object_get_type(&obj_type, s->repo, obj_id);
6880 if (err)
6881 goto done;
6883 if (obj_type != GOT_OBJ_TYPE_BLOB) {
6884 err = got_error(GOT_ERR_OBJ_TYPE);
6885 goto done;
6888 err = got_object_open_as_blob(&blob, s->repo, obj_id, 8192, fd);
6889 if (err)
6890 goto done;
6891 blame->f = got_opentemp();
6892 if (blame->f == NULL) {
6893 err = got_error_from_errno("got_opentemp");
6894 goto done;
6896 err = got_object_blob_dump_to_file(&blame->filesize, &blame->nlines,
6897 &blame->line_offsets, blame->f, blob);
6898 if (err)
6899 goto done;
6900 if (blame->nlines == 0) {
6901 s->blame_complete = 1;
6902 goto done;
6905 /* Don't include \n at EOF in the blame line count. */
6906 if (blame->line_offsets[blame->nlines - 1] == blame->filesize)
6907 blame->nlines--;
6909 blame->lines = calloc(blame->nlines, sizeof(*blame->lines));
6910 if (blame->lines == NULL) {
6911 err = got_error_from_errno("calloc");
6912 goto done;
6915 err = got_repo_pack_fds_open(&pack_fds);
6916 if (err)
6917 goto done;
6918 err = got_repo_open(&thread_repo, got_repo_get_path(s->repo), NULL,
6919 pack_fds);
6920 if (err)
6921 goto done;
6923 blame->pack_fds = pack_fds;
6924 blame->cb_args.view = view;
6925 blame->cb_args.lines = blame->lines;
6926 blame->cb_args.nlines = blame->nlines;
6927 blame->cb_args.commit_id = got_object_id_dup(&s->blamed_commit->id);
6928 if (blame->cb_args.commit_id == NULL) {
6929 err = got_error_from_errno("got_object_id_dup");
6930 goto done;
6932 blame->cb_args.quit = &s->done;
6934 blame->thread_args.path = s->path;
6935 blame->thread_args.repo = thread_repo;
6936 blame->thread_args.cb_args = &blame->cb_args;
6937 blame->thread_args.complete = &s->blame_complete;
6938 blame->thread_args.cancel_cb = cancel_blame_view;
6939 blame->thread_args.cancel_arg = &s->done;
6940 s->blame_complete = 0;
6942 if (s->first_displayed_line + view->nlines - 1 > blame->nlines) {
6943 s->first_displayed_line = 1;
6944 s->last_displayed_line = view->nlines;
6945 s->selected_line = 1;
6947 s->matched_line = 0;
6949 done:
6950 if (commit)
6951 got_object_commit_close(commit);
6952 if (fd != -1 && close(fd) == -1 && err == NULL)
6953 err = got_error_from_errno("close");
6954 if (blob)
6955 got_object_blob_close(blob);
6956 free(obj_id);
6957 if (err)
6958 stop_blame(blame);
6959 return err;
6962 static const struct got_error *
6963 open_blame_view(struct tog_view *view, char *path,
6964 struct got_object_id *commit_id, struct got_repository *repo)
6966 const struct got_error *err = NULL;
6967 struct tog_blame_view_state *s = &view->state.blame;
6969 STAILQ_INIT(&s->blamed_commits);
6971 s->path = strdup(path);
6972 if (s->path == NULL)
6973 return got_error_from_errno("strdup");
6975 err = got_object_qid_alloc(&s->blamed_commit, commit_id);
6976 if (err) {
6977 free(s->path);
6978 return err;
6981 STAILQ_INSERT_HEAD(&s->blamed_commits, s->blamed_commit, entry);
6982 s->first_displayed_line = 1;
6983 s->last_displayed_line = view->nlines;
6984 s->selected_line = 1;
6985 s->blame_complete = 0;
6986 s->repo = repo;
6987 s->commit_id = commit_id;
6988 memset(&s->blame, 0, sizeof(s->blame));
6990 STAILQ_INIT(&s->colors);
6991 if (has_colors() && getenv("TOG_COLORS") != NULL) {
6992 err = add_color(&s->colors, "^", TOG_COLOR_COMMIT,
6993 get_color_value("TOG_COLOR_COMMIT"));
6994 if (err)
6995 return err;
6998 view->show = show_blame_view;
6999 view->input = input_blame_view;
7000 view->reset = reset_blame_view;
7001 view->close = close_blame_view;
7002 view->search_start = search_start_blame_view;
7003 view->search_setup = search_setup_blame_view;
7004 view->search_next = search_next_view_match;
7006 if (using_mock_io) {
7007 struct tog_blame_thread_args *bta = &s->blame.thread_args;
7008 int rc;
7010 rc = pthread_cond_init(&bta->blame_complete, NULL);
7011 if (rc)
7012 return got_error_set_errno(rc, "pthread_cond_init");
7015 return run_blame(view);
7018 static const struct got_error *
7019 close_blame_view(struct tog_view *view)
7021 const struct got_error *err = NULL;
7022 struct tog_blame_view_state *s = &view->state.blame;
7024 if (s->blame.thread)
7025 err = stop_blame(&s->blame);
7027 while (!STAILQ_EMPTY(&s->blamed_commits)) {
7028 struct got_object_qid *blamed_commit;
7029 blamed_commit = STAILQ_FIRST(&s->blamed_commits);
7030 STAILQ_REMOVE_HEAD(&s->blamed_commits, entry);
7031 got_object_qid_free(blamed_commit);
7034 if (using_mock_io) {
7035 struct tog_blame_thread_args *bta = &s->blame.thread_args;
7036 int rc;
7038 rc = pthread_cond_destroy(&bta->blame_complete);
7039 if (rc && err == NULL)
7040 err = got_error_set_errno(rc, "pthread_cond_destroy");
7043 free(s->path);
7044 free_colors(&s->colors);
7045 return err;
7048 static const struct got_error *
7049 search_start_blame_view(struct tog_view *view)
7051 struct tog_blame_view_state *s = &view->state.blame;
7053 s->matched_line = 0;
7054 return NULL;
7057 static void
7058 search_setup_blame_view(struct tog_view *view, FILE **f, off_t **line_offsets,
7059 size_t *nlines, int **first, int **last, int **match, int **selected)
7061 struct tog_blame_view_state *s = &view->state.blame;
7063 *f = s->blame.f;
7064 *nlines = s->blame.nlines;
7065 *line_offsets = s->blame.line_offsets;
7066 *match = &s->matched_line;
7067 *first = &s->first_displayed_line;
7068 *last = &s->last_displayed_line;
7069 *selected = &s->selected_line;
7072 static const struct got_error *
7073 show_blame_view(struct tog_view *view)
7075 const struct got_error *err = NULL;
7076 struct tog_blame_view_state *s = &view->state.blame;
7077 int errcode;
7079 if (s->blame.thread == 0 && !s->blame_complete) {
7080 errcode = pthread_create(&s->blame.thread, NULL, blame_thread,
7081 &s->blame.thread_args);
7082 if (errcode)
7083 return got_error_set_errno(errcode, "pthread_create");
7085 if (!using_mock_io)
7086 halfdelay(1); /* fast refresh while annotating */
7089 if (s->blame_complete && !using_mock_io)
7090 halfdelay(10); /* disable fast refresh */
7092 err = draw_blame(view);
7094 view_border(view);
7095 return err;
7098 static const struct got_error *
7099 log_annotated_line(struct tog_view **new_view, int begin_y, int begin_x,
7100 struct got_repository *repo, struct got_object_id *id)
7102 struct tog_view *log_view;
7103 const struct got_error *err = NULL;
7105 *new_view = NULL;
7107 log_view = view_open(0, 0, begin_y, begin_x, TOG_VIEW_LOG);
7108 if (log_view == NULL)
7109 return got_error_from_errno("view_open");
7111 err = open_log_view(log_view, id, repo, GOT_REF_HEAD, "", 0, NULL);
7112 if (err)
7113 view_close(log_view);
7114 else
7115 *new_view = log_view;
7117 return err;
7120 static const struct got_error *
7121 input_blame_view(struct tog_view **new_view, struct tog_view *view, int ch)
7123 const struct got_error *err = NULL, *thread_err = NULL;
7124 struct tog_view *diff_view;
7125 struct tog_blame_view_state *s = &view->state.blame;
7126 int eos, nscroll, begin_y = 0, begin_x = 0;
7128 eos = nscroll = view->nlines - 2;
7129 if (view_is_hsplit_top(view))
7130 --eos; /* border */
7132 switch (ch) {
7133 case '0':
7134 case '$':
7135 case KEY_RIGHT:
7136 case 'l':
7137 case KEY_LEFT:
7138 case 'h':
7139 horizontal_scroll_input(view, ch);
7140 break;
7141 case 'q':
7142 s->done = 1;
7143 break;
7144 case 'g':
7145 case KEY_HOME:
7146 s->selected_line = 1;
7147 s->first_displayed_line = 1;
7148 view->count = 0;
7149 break;
7150 case 'G':
7151 case KEY_END:
7152 if (s->blame.nlines < eos) {
7153 s->selected_line = s->blame.nlines;
7154 s->first_displayed_line = 1;
7155 } else {
7156 s->selected_line = eos;
7157 s->first_displayed_line = s->blame.nlines - (eos - 1);
7159 view->count = 0;
7160 break;
7161 case 'k':
7162 case KEY_UP:
7163 case CTRL('p'):
7164 if (s->selected_line > 1)
7165 s->selected_line--;
7166 else if (s->selected_line == 1 &&
7167 s->first_displayed_line > 1)
7168 s->first_displayed_line--;
7169 else
7170 view->count = 0;
7171 break;
7172 case CTRL('u'):
7173 case 'u':
7174 nscroll /= 2;
7175 /* FALL THROUGH */
7176 case KEY_PPAGE:
7177 case CTRL('b'):
7178 case 'b':
7179 if (s->first_displayed_line == 1) {
7180 if (view->count > 1)
7181 nscroll += nscroll;
7182 s->selected_line = MAX(1, s->selected_line - nscroll);
7183 view->count = 0;
7184 break;
7186 if (s->first_displayed_line > nscroll)
7187 s->first_displayed_line -= nscroll;
7188 else
7189 s->first_displayed_line = 1;
7190 break;
7191 case 'j':
7192 case KEY_DOWN:
7193 case CTRL('n'):
7194 if (s->selected_line < eos && s->first_displayed_line +
7195 s->selected_line <= s->blame.nlines)
7196 s->selected_line++;
7197 else if (s->first_displayed_line < s->blame.nlines - (eos - 1))
7198 s->first_displayed_line++;
7199 else
7200 view->count = 0;
7201 break;
7202 case 'c':
7203 case 'p': {
7204 struct got_object_id *id = NULL;
7206 view->count = 0;
7207 id = get_selected_commit_id(s->blame.lines, s->blame.nlines,
7208 s->first_displayed_line, s->selected_line);
7209 if (id == NULL)
7210 break;
7211 if (ch == 'p') {
7212 struct got_commit_object *commit, *pcommit;
7213 struct got_object_qid *pid;
7214 struct got_object_id *blob_id = NULL;
7215 int obj_type;
7216 err = got_object_open_as_commit(&commit,
7217 s->repo, id);
7218 if (err)
7219 break;
7220 pid = STAILQ_FIRST(
7221 got_object_commit_get_parent_ids(commit));
7222 if (pid == NULL) {
7223 got_object_commit_close(commit);
7224 break;
7226 /* Check if path history ends here. */
7227 err = got_object_open_as_commit(&pcommit,
7228 s->repo, &pid->id);
7229 if (err)
7230 break;
7231 err = got_object_id_by_path(&blob_id, s->repo,
7232 pcommit, s->path);
7233 got_object_commit_close(pcommit);
7234 if (err) {
7235 if (err->code == GOT_ERR_NO_TREE_ENTRY)
7236 err = NULL;
7237 got_object_commit_close(commit);
7238 break;
7240 err = got_object_get_type(&obj_type, s->repo,
7241 blob_id);
7242 free(blob_id);
7243 /* Can't blame non-blob type objects. */
7244 if (obj_type != GOT_OBJ_TYPE_BLOB) {
7245 got_object_commit_close(commit);
7246 break;
7248 err = got_object_qid_alloc(&s->blamed_commit,
7249 &pid->id);
7250 got_object_commit_close(commit);
7251 } else {
7252 if (got_object_id_cmp(id,
7253 &s->blamed_commit->id) == 0)
7254 break;
7255 err = got_object_qid_alloc(&s->blamed_commit,
7256 id);
7258 if (err)
7259 break;
7260 s->done = 1;
7261 thread_err = stop_blame(&s->blame);
7262 s->done = 0;
7263 if (thread_err)
7264 break;
7265 STAILQ_INSERT_HEAD(&s->blamed_commits,
7266 s->blamed_commit, entry);
7267 err = run_blame(view);
7268 if (err)
7269 break;
7270 break;
7272 case 'C': {
7273 struct got_object_qid *first;
7275 view->count = 0;
7276 first = STAILQ_FIRST(&s->blamed_commits);
7277 if (!got_object_id_cmp(&first->id, s->commit_id))
7278 break;
7279 s->done = 1;
7280 thread_err = stop_blame(&s->blame);
7281 s->done = 0;
7282 if (thread_err)
7283 break;
7284 STAILQ_REMOVE_HEAD(&s->blamed_commits, entry);
7285 got_object_qid_free(s->blamed_commit);
7286 s->blamed_commit =
7287 STAILQ_FIRST(&s->blamed_commits);
7288 err = run_blame(view);
7289 if (err)
7290 break;
7291 break;
7293 case 'L':
7294 view->count = 0;
7295 s->id_to_log = get_selected_commit_id(s->blame.lines,
7296 s->blame.nlines, s->first_displayed_line, s->selected_line);
7297 if (s->id_to_log)
7298 err = view_request_new(new_view, view, TOG_VIEW_LOG);
7299 break;
7300 case KEY_ENTER:
7301 case '\r': {
7302 struct got_object_id *id = NULL;
7303 struct got_object_qid *pid;
7304 struct got_commit_object *commit = NULL;
7306 view->count = 0;
7307 id = get_selected_commit_id(s->blame.lines, s->blame.nlines,
7308 s->first_displayed_line, s->selected_line);
7309 if (id == NULL)
7310 break;
7311 err = got_object_open_as_commit(&commit, s->repo, id);
7312 if (err)
7313 break;
7314 pid = STAILQ_FIRST(got_object_commit_get_parent_ids(commit));
7315 if (*new_view) {
7316 /* traversed from diff view, release diff resources */
7317 err = close_diff_view(*new_view);
7318 if (err)
7319 break;
7320 diff_view = *new_view;
7321 } else {
7322 if (view_is_parent_view(view))
7323 view_get_split(view, &begin_y, &begin_x);
7325 diff_view = view_open(0, 0, begin_y, begin_x,
7326 TOG_VIEW_DIFF);
7327 if (diff_view == NULL) {
7328 got_object_commit_close(commit);
7329 err = got_error_from_errno("view_open");
7330 break;
7333 err = open_diff_view(diff_view, pid ? &pid->id : NULL,
7334 id, NULL, NULL, 3, 0, 0, view, s->repo);
7335 got_object_commit_close(commit);
7336 if (err)
7337 break;
7338 s->last_diffed_line = s->first_displayed_line - 1 +
7339 s->selected_line;
7340 if (*new_view)
7341 break; /* still open from active diff view */
7342 if (view_is_parent_view(view) &&
7343 view->mode == TOG_VIEW_SPLIT_HRZN) {
7344 err = view_init_hsplit(view, begin_y);
7345 if (err)
7346 break;
7349 view->focussed = 0;
7350 diff_view->focussed = 1;
7351 diff_view->mode = view->mode;
7352 diff_view->nlines = view->lines - begin_y;
7353 if (view_is_parent_view(view)) {
7354 view_transfer_size(diff_view, view);
7355 err = view_close_child(view);
7356 if (err)
7357 break;
7358 err = view_set_child(view, diff_view);
7359 if (err)
7360 break;
7361 view->focus_child = 1;
7362 } else
7363 *new_view = diff_view;
7364 if (err)
7365 break;
7366 break;
7368 case CTRL('d'):
7369 case 'd':
7370 nscroll /= 2;
7371 /* FALL THROUGH */
7372 case KEY_NPAGE:
7373 case CTRL('f'):
7374 case 'f':
7375 case ' ':
7376 if (s->last_displayed_line >= s->blame.nlines &&
7377 s->selected_line >= MIN(s->blame.nlines,
7378 view->nlines - 2)) {
7379 view->count = 0;
7380 break;
7382 if (s->last_displayed_line >= s->blame.nlines &&
7383 s->selected_line < view->nlines - 2) {
7384 s->selected_line +=
7385 MIN(nscroll, s->last_displayed_line -
7386 s->first_displayed_line - s->selected_line + 1);
7388 if (s->last_displayed_line + nscroll <= s->blame.nlines)
7389 s->first_displayed_line += nscroll;
7390 else
7391 s->first_displayed_line =
7392 s->blame.nlines - (view->nlines - 3);
7393 break;
7394 case KEY_RESIZE:
7395 if (s->selected_line > view->nlines - 2) {
7396 s->selected_line = MIN(s->blame.nlines,
7397 view->nlines - 2);
7399 break;
7400 default:
7401 view->count = 0;
7402 break;
7404 return thread_err ? thread_err : err;
7407 static const struct got_error *
7408 reset_blame_view(struct tog_view *view)
7410 const struct got_error *err;
7411 struct tog_blame_view_state *s = &view->state.blame;
7413 view->count = 0;
7414 s->done = 1;
7415 err = stop_blame(&s->blame);
7416 s->done = 0;
7417 if (err)
7418 return err;
7419 return run_blame(view);
7422 static const struct got_error *
7423 cmd_blame(int argc, char *argv[])
7425 const struct got_error *error;
7426 struct got_repository *repo = NULL;
7427 struct got_worktree *worktree = NULL;
7428 char *cwd = NULL, *repo_path = NULL, *in_repo_path = NULL;
7429 char *link_target = NULL;
7430 struct got_object_id *commit_id = NULL;
7431 struct got_commit_object *commit = NULL;
7432 char *keyword_idstr = NULL, *commit_id_str = NULL;
7433 int ch;
7434 struct tog_view *view = NULL;
7435 int *pack_fds = NULL;
7437 while ((ch = getopt(argc, argv, "c:r:")) != -1) {
7438 switch (ch) {
7439 case 'c':
7440 commit_id_str = optarg;
7441 break;
7442 case 'r':
7443 repo_path = realpath(optarg, NULL);
7444 if (repo_path == NULL)
7445 return got_error_from_errno2("realpath",
7446 optarg);
7447 break;
7448 default:
7449 usage_blame();
7450 /* NOTREACHED */
7454 argc -= optind;
7455 argv += optind;
7457 if (argc != 1)
7458 usage_blame();
7460 error = got_repo_pack_fds_open(&pack_fds);
7461 if (error != NULL)
7462 goto done;
7464 if (repo_path == NULL) {
7465 cwd = getcwd(NULL, 0);
7466 if (cwd == NULL)
7467 return got_error_from_errno("getcwd");
7468 error = got_worktree_open(&worktree, cwd, NULL);
7469 if (error && error->code != GOT_ERR_NOT_WORKTREE)
7470 goto done;
7471 if (worktree)
7472 repo_path =
7473 strdup(got_worktree_get_repo_path(worktree));
7474 else
7475 repo_path = strdup(cwd);
7476 if (repo_path == NULL) {
7477 error = got_error_from_errno("strdup");
7478 goto done;
7482 error = got_repo_open(&repo, repo_path, NULL, pack_fds);
7483 if (error != NULL)
7484 goto done;
7486 error = get_in_repo_path_from_argv0(&in_repo_path, argc, argv, repo,
7487 worktree);
7488 if (error)
7489 goto done;
7491 init_curses();
7493 error = apply_unveil(got_repo_get_path(repo), NULL);
7494 if (error)
7495 goto done;
7497 error = tog_load_refs(repo, 0);
7498 if (error)
7499 goto done;
7501 if (commit_id_str == NULL) {
7502 struct got_reference *head_ref;
7503 error = got_ref_open(&head_ref, repo, worktree ?
7504 got_worktree_get_head_ref_name(worktree) : GOT_REF_HEAD, 0);
7505 if (error != NULL)
7506 goto done;
7507 error = got_ref_resolve(&commit_id, repo, head_ref);
7508 got_ref_close(head_ref);
7509 } else {
7510 error = got_keyword_to_idstr(&keyword_idstr, commit_id_str,
7511 repo, worktree);
7512 if (error != NULL)
7513 goto done;
7514 if (keyword_idstr != NULL)
7515 commit_id_str = keyword_idstr;
7517 error = got_repo_match_object_id(&commit_id, NULL,
7518 commit_id_str, GOT_OBJ_TYPE_COMMIT, &tog_refs, repo);
7520 if (error != NULL)
7521 goto done;
7523 error = got_object_open_as_commit(&commit, repo, commit_id);
7524 if (error)
7525 goto done;
7527 error = got_object_resolve_symlinks(&link_target, in_repo_path,
7528 commit, repo);
7529 if (error)
7530 goto done;
7532 view = view_open(0, 0, 0, 0, TOG_VIEW_BLAME);
7533 if (view == NULL) {
7534 error = got_error_from_errno("view_open");
7535 goto done;
7537 error = open_blame_view(view, link_target ? link_target : in_repo_path,
7538 commit_id, repo);
7539 if (error != NULL) {
7540 if (view->close == NULL)
7541 close_blame_view(view);
7542 view_close(view);
7543 goto done;
7546 if (worktree) {
7547 error = set_tog_base_commit(repo, worktree);
7548 if (error != NULL)
7549 goto done;
7551 /* Release work tree lock. */
7552 got_worktree_close(worktree);
7553 worktree = NULL;
7556 error = view_loop(view);
7558 done:
7559 free(tog_base_commit.id);
7560 free(repo_path);
7561 free(in_repo_path);
7562 free(link_target);
7563 free(cwd);
7564 free(commit_id);
7565 free(keyword_idstr);
7566 if (commit)
7567 got_object_commit_close(commit);
7568 if (worktree)
7569 got_worktree_close(worktree);
7570 if (repo) {
7571 const struct got_error *close_err = got_repo_close(repo);
7572 if (error == NULL)
7573 error = close_err;
7575 if (pack_fds) {
7576 const struct got_error *pack_err =
7577 got_repo_pack_fds_close(pack_fds);
7578 if (error == NULL)
7579 error = pack_err;
7581 tog_free_refs();
7582 return error;
7585 static const struct got_error *
7586 draw_tree_entries(struct tog_view *view, const char *parent_path)
7588 struct tog_tree_view_state *s = &view->state.tree;
7589 const struct got_error *err = NULL;
7590 struct got_tree_entry *te;
7591 wchar_t *wline;
7592 char *index = NULL;
7593 struct tog_color *tc;
7594 int width, n, nentries, scrollx, i = 1;
7595 int limit = view->nlines;
7597 s->ndisplayed = 0;
7598 if (view_is_hsplit_top(view))
7599 --limit; /* border */
7601 werase(view->window);
7603 if (limit == 0)
7604 return NULL;
7606 err = format_line(&wline, &width, NULL, s->tree_label, 0, view->ncols,
7607 0, 0);
7608 if (err)
7609 return err;
7610 if (view_needs_focus_indication(view))
7611 wstandout(view->window);
7612 tc = get_color(&s->colors, TOG_COLOR_COMMIT);
7613 if (tc)
7614 wattr_on(view->window, COLOR_PAIR(tc->colorpair), NULL);
7615 waddwstr(view->window, wline);
7616 free(wline);
7617 wline = NULL;
7618 while (width++ < view->ncols)
7619 waddch(view->window, ' ');
7620 if (tc)
7621 wattr_off(view->window, COLOR_PAIR(tc->colorpair), NULL);
7622 if (view_needs_focus_indication(view))
7623 wstandend(view->window);
7624 if (--limit <= 0)
7625 return NULL;
7627 i += s->selected;
7628 if (s->first_displayed_entry) {
7629 i += got_tree_entry_get_index(s->first_displayed_entry);
7630 if (s->tree != s->root)
7631 ++i; /* account for ".." entry */
7633 nentries = got_object_tree_get_nentries(s->tree);
7634 if (asprintf(&index, "[%d/%d] %s",
7635 i, nentries + (s->tree == s->root ? 0 : 1), parent_path) == -1)
7636 return got_error_from_errno("asprintf");
7637 err = format_line(&wline, &width, NULL, index, 0, view->ncols, 0, 0);
7638 free(index);
7639 if (err)
7640 return err;
7641 waddwstr(view->window, wline);
7642 free(wline);
7643 wline = NULL;
7644 if (width < view->ncols - 1)
7645 waddch(view->window, '\n');
7646 if (--limit <= 0)
7647 return NULL;
7648 waddch(view->window, '\n');
7649 if (--limit <= 0)
7650 return NULL;
7652 if (s->first_displayed_entry == NULL) {
7653 te = got_object_tree_get_first_entry(s->tree);
7654 if (s->selected == 0) {
7655 if (view->focussed)
7656 wstandout(view->window);
7657 s->selected_entry = NULL;
7659 waddstr(view->window, " ..\n"); /* parent directory */
7660 if (s->selected == 0 && view->focussed)
7661 wstandend(view->window);
7662 s->ndisplayed++;
7663 if (--limit <= 0)
7664 return NULL;
7665 n = 1;
7666 } else {
7667 n = 0;
7668 te = s->first_displayed_entry;
7671 view->maxx = 0;
7672 for (i = got_tree_entry_get_index(te); i < nentries; i++) {
7673 char *line = NULL, *id_str = NULL, *link_target = NULL;
7674 const char *modestr = "";
7675 mode_t mode;
7677 te = got_object_tree_get_entry(s->tree, i);
7678 mode = got_tree_entry_get_mode(te);
7680 if (s->show_ids) {
7681 err = got_object_id_str(&id_str,
7682 got_tree_entry_get_id(te));
7683 if (err)
7684 return got_error_from_errno(
7685 "got_object_id_str");
7687 if (got_object_tree_entry_is_submodule(te))
7688 modestr = "$";
7689 else if (S_ISLNK(mode)) {
7690 int i;
7692 err = got_tree_entry_get_symlink_target(&link_target,
7693 te, s->repo);
7694 if (err) {
7695 free(id_str);
7696 return err;
7698 for (i = 0; link_target[i] != '\0'; i++) {
7699 if (!isprint((unsigned char)link_target[i]))
7700 link_target[i] = '?';
7702 modestr = "@";
7704 else if (S_ISDIR(mode))
7705 modestr = "/";
7706 else if (mode & S_IXUSR)
7707 modestr = "*";
7708 if (asprintf(&line, "%s %s%s%s%s", id_str ? id_str : "",
7709 got_tree_entry_get_name(te), modestr,
7710 link_target ? " -> ": "",
7711 link_target ? link_target : "") == -1) {
7712 free(id_str);
7713 free(link_target);
7714 return got_error_from_errno("asprintf");
7716 free(id_str);
7717 free(link_target);
7719 /* use full line width to determine view->maxx */
7720 err = format_line(&wline, &width, NULL, line, 0, INT_MAX, 0, 0);
7721 if (err) {
7722 free(line);
7723 break;
7725 view->maxx = MAX(view->maxx, width);
7726 free(wline);
7727 wline = NULL;
7729 err = format_line(&wline, &width, &scrollx, line, view->x,
7730 view->ncols, 0, 0);
7731 if (err) {
7732 free(line);
7733 break;
7735 if (n == s->selected) {
7736 if (view->focussed)
7737 wstandout(view->window);
7738 s->selected_entry = te;
7740 tc = match_color(&s->colors, line);
7741 if (tc)
7742 wattr_on(view->window,
7743 COLOR_PAIR(tc->colorpair), NULL);
7744 waddwstr(view->window, &wline[scrollx]);
7745 if (tc)
7746 wattr_off(view->window,
7747 COLOR_PAIR(tc->colorpair), NULL);
7748 if (width < view->ncols)
7749 waddch(view->window, '\n');
7750 if (n == s->selected && view->focussed)
7751 wstandend(view->window);
7752 free(line);
7753 free(wline);
7754 wline = NULL;
7755 n++;
7756 s->ndisplayed++;
7757 s->last_displayed_entry = te;
7758 if (--limit <= 0)
7759 break;
7762 return err;
7765 static void
7766 tree_scroll_up(struct tog_tree_view_state *s, int maxscroll)
7768 struct got_tree_entry *te;
7769 int isroot = s->tree == s->root;
7770 int i = 0;
7772 if (s->first_displayed_entry == NULL)
7773 return;
7775 te = got_tree_entry_get_prev(s->tree, s->first_displayed_entry);
7776 while (i++ < maxscroll) {
7777 if (te == NULL) {
7778 if (!isroot)
7779 s->first_displayed_entry = NULL;
7780 break;
7782 s->first_displayed_entry = te;
7783 te = got_tree_entry_get_prev(s->tree, te);
7787 static const struct got_error *
7788 tree_scroll_down(struct tog_view *view, int maxscroll)
7790 struct tog_tree_view_state *s = &view->state.tree;
7791 struct got_tree_entry *next, *last;
7792 int n = 0;
7794 if (s->first_displayed_entry)
7795 next = got_tree_entry_get_next(s->tree,
7796 s->first_displayed_entry);
7797 else
7798 next = got_object_tree_get_first_entry(s->tree);
7800 last = s->last_displayed_entry;
7801 while (next && n++ < maxscroll) {
7802 if (last) {
7803 s->last_displayed_entry = last;
7804 last = got_tree_entry_get_next(s->tree, last);
7806 if (last || (view->mode == TOG_VIEW_SPLIT_HRZN && next)) {
7807 s->first_displayed_entry = next;
7808 next = got_tree_entry_get_next(s->tree, next);
7812 return NULL;
7815 static const struct got_error *
7816 tree_entry_path(char **path, struct tog_parent_trees *parents,
7817 struct got_tree_entry *te)
7819 const struct got_error *err = NULL;
7820 struct tog_parent_tree *pt;
7821 size_t len = 2; /* for leading slash and NUL */
7823 TAILQ_FOREACH(pt, parents, entry)
7824 len += strlen(got_tree_entry_get_name(pt->selected_entry))
7825 + 1 /* slash */;
7826 if (te)
7827 len += strlen(got_tree_entry_get_name(te));
7829 *path = calloc(1, len);
7830 if (path == NULL)
7831 return got_error_from_errno("calloc");
7833 (*path)[0] = '/';
7834 pt = TAILQ_LAST(parents, tog_parent_trees);
7835 while (pt) {
7836 const char *name = got_tree_entry_get_name(pt->selected_entry);
7837 if (strlcat(*path, name, len) >= len) {
7838 err = got_error(GOT_ERR_NO_SPACE);
7839 goto done;
7841 if (strlcat(*path, "/", len) >= len) {
7842 err = got_error(GOT_ERR_NO_SPACE);
7843 goto done;
7845 pt = TAILQ_PREV(pt, tog_parent_trees, entry);
7847 if (te) {
7848 if (strlcat(*path, got_tree_entry_get_name(te), len) >= len) {
7849 err = got_error(GOT_ERR_NO_SPACE);
7850 goto done;
7853 done:
7854 if (err) {
7855 free(*path);
7856 *path = NULL;
7858 return err;
7861 static const struct got_error *
7862 blame_tree_entry(struct tog_view **new_view, int begin_y, int begin_x,
7863 struct got_tree_entry *te, struct tog_parent_trees *parents,
7864 struct got_object_id *commit_id, struct got_repository *repo)
7866 const struct got_error *err = NULL;
7867 char *path;
7868 struct tog_view *blame_view;
7870 *new_view = NULL;
7872 err = tree_entry_path(&path, parents, te);
7873 if (err)
7874 return err;
7876 blame_view = view_open(0, 0, begin_y, begin_x, TOG_VIEW_BLAME);
7877 if (blame_view == NULL) {
7878 err = got_error_from_errno("view_open");
7879 goto done;
7882 err = open_blame_view(blame_view, path, commit_id, repo);
7883 if (err) {
7884 if (err->code == GOT_ERR_CANCELLED)
7885 err = NULL;
7886 view_close(blame_view);
7887 } else
7888 *new_view = blame_view;
7889 done:
7890 free(path);
7891 return err;
7894 static const struct got_error *
7895 log_selected_tree_entry(struct tog_view **new_view, int begin_y, int begin_x,
7896 struct tog_tree_view_state *s)
7898 struct tog_view *log_view;
7899 const struct got_error *err = NULL;
7900 char *path;
7902 *new_view = NULL;
7904 log_view = view_open(0, 0, begin_y, begin_x, TOG_VIEW_LOG);
7905 if (log_view == NULL)
7906 return got_error_from_errno("view_open");
7908 err = tree_entry_path(&path, &s->parents, s->selected_entry);
7909 if (err)
7910 return err;
7912 err = open_log_view(log_view, s->commit_id, s->repo, s->head_ref_name,
7913 path, 0, NULL);
7914 if (err)
7915 view_close(log_view);
7916 else
7917 *new_view = log_view;
7918 free(path);
7919 return err;
7922 static const struct got_error *
7923 open_tree_view(struct tog_view *view, struct got_object_id *commit_id,
7924 const char *head_ref_name, struct got_repository *repo)
7926 const struct got_error *err = NULL;
7927 char *commit_id_str = NULL;
7928 struct tog_tree_view_state *s = &view->state.tree;
7929 struct got_commit_object *commit = NULL;
7931 TAILQ_INIT(&s->parents);
7932 STAILQ_INIT(&s->colors);
7934 s->commit_id = got_object_id_dup(commit_id);
7935 if (s->commit_id == NULL) {
7936 err = got_error_from_errno("got_object_id_dup");
7937 goto done;
7940 err = got_object_open_as_commit(&commit, repo, commit_id);
7941 if (err)
7942 goto done;
7945 * The root is opened here and will be closed when the view is closed.
7946 * Any visited subtrees and their path-wise parents are opened and
7947 * closed on demand.
7949 err = got_object_open_as_tree(&s->root, repo,
7950 got_object_commit_get_tree_id(commit));
7951 if (err)
7952 goto done;
7953 s->tree = s->root;
7955 err = got_object_id_str(&commit_id_str, commit_id);
7956 if (err != NULL)
7957 goto done;
7959 if (asprintf(&s->tree_label, "commit %s", commit_id_str) == -1) {
7960 err = got_error_from_errno("asprintf");
7961 goto done;
7964 s->first_displayed_entry = got_object_tree_get_entry(s->tree, 0);
7965 s->selected_entry = got_object_tree_get_entry(s->tree, 0);
7966 if (head_ref_name) {
7967 s->head_ref_name = strdup(head_ref_name);
7968 if (s->head_ref_name == NULL) {
7969 err = got_error_from_errno("strdup");
7970 goto done;
7973 s->repo = repo;
7975 if (has_colors() && getenv("TOG_COLORS") != NULL) {
7976 err = add_color(&s->colors, "\\$$",
7977 TOG_COLOR_TREE_SUBMODULE,
7978 get_color_value("TOG_COLOR_TREE_SUBMODULE"));
7979 if (err)
7980 goto done;
7981 err = add_color(&s->colors, "@$", TOG_COLOR_TREE_SYMLINK,
7982 get_color_value("TOG_COLOR_TREE_SYMLINK"));
7983 if (err)
7984 goto done;
7985 err = add_color(&s->colors, "/$",
7986 TOG_COLOR_TREE_DIRECTORY,
7987 get_color_value("TOG_COLOR_TREE_DIRECTORY"));
7988 if (err)
7989 goto done;
7991 err = add_color(&s->colors, "\\*$",
7992 TOG_COLOR_TREE_EXECUTABLE,
7993 get_color_value("TOG_COLOR_TREE_EXECUTABLE"));
7994 if (err)
7995 goto done;
7997 err = add_color(&s->colors, "^$", TOG_COLOR_COMMIT,
7998 get_color_value("TOG_COLOR_COMMIT"));
7999 if (err)
8000 goto done;
8003 view->show = show_tree_view;
8004 view->input = input_tree_view;
8005 view->close = close_tree_view;
8006 view->search_start = search_start_tree_view;
8007 view->search_next = search_next_tree_view;
8008 done:
8009 free(commit_id_str);
8010 if (commit)
8011 got_object_commit_close(commit);
8012 if (err) {
8013 if (view->close == NULL)
8014 close_tree_view(view);
8015 view_close(view);
8017 return err;
8020 static const struct got_error *
8021 close_tree_view(struct tog_view *view)
8023 struct tog_tree_view_state *s = &view->state.tree;
8025 free_colors(&s->colors);
8026 free(s->tree_label);
8027 s->tree_label = NULL;
8028 free(s->commit_id);
8029 s->commit_id = NULL;
8030 free(s->head_ref_name);
8031 s->head_ref_name = NULL;
8032 while (!TAILQ_EMPTY(&s->parents)) {
8033 struct tog_parent_tree *parent;
8034 parent = TAILQ_FIRST(&s->parents);
8035 TAILQ_REMOVE(&s->parents, parent, entry);
8036 if (parent->tree != s->root)
8037 got_object_tree_close(parent->tree);
8038 free(parent);
8041 if (s->tree != NULL && s->tree != s->root)
8042 got_object_tree_close(s->tree);
8043 if (s->root)
8044 got_object_tree_close(s->root);
8045 return NULL;
8048 static const struct got_error *
8049 search_start_tree_view(struct tog_view *view)
8051 struct tog_tree_view_state *s = &view->state.tree;
8053 s->matched_entry = NULL;
8054 return NULL;
8057 static int
8058 match_tree_entry(struct got_tree_entry *te, regex_t *regex)
8060 regmatch_t regmatch;
8062 return regexec(regex, got_tree_entry_get_name(te), 1, &regmatch,
8063 0) == 0;
8066 static const struct got_error *
8067 search_next_tree_view(struct tog_view *view)
8069 struct tog_tree_view_state *s = &view->state.tree;
8070 struct got_tree_entry *te = NULL;
8072 if (!view->searching) {
8073 view->search_next_done = TOG_SEARCH_HAVE_MORE;
8074 return NULL;
8077 if (s->matched_entry) {
8078 if (view->searching == TOG_SEARCH_FORWARD) {
8079 if (s->selected_entry)
8080 te = got_tree_entry_get_next(s->tree,
8081 s->selected_entry);
8082 else
8083 te = got_object_tree_get_first_entry(s->tree);
8084 } else {
8085 if (s->selected_entry == NULL)
8086 te = got_object_tree_get_last_entry(s->tree);
8087 else
8088 te = got_tree_entry_get_prev(s->tree,
8089 s->selected_entry);
8091 } else {
8092 if (s->selected_entry)
8093 te = s->selected_entry;
8094 else if (view->searching == TOG_SEARCH_FORWARD)
8095 te = got_object_tree_get_first_entry(s->tree);
8096 else
8097 te = got_object_tree_get_last_entry(s->tree);
8100 while (1) {
8101 if (te == NULL) {
8102 if (s->matched_entry == NULL) {
8103 view->search_next_done = TOG_SEARCH_HAVE_MORE;
8104 return NULL;
8106 if (view->searching == TOG_SEARCH_FORWARD)
8107 te = got_object_tree_get_first_entry(s->tree);
8108 else
8109 te = got_object_tree_get_last_entry(s->tree);
8112 if (match_tree_entry(te, &view->regex)) {
8113 view->search_next_done = TOG_SEARCH_HAVE_MORE;
8114 s->matched_entry = te;
8115 break;
8118 if (view->searching == TOG_SEARCH_FORWARD)
8119 te = got_tree_entry_get_next(s->tree, te);
8120 else
8121 te = got_tree_entry_get_prev(s->tree, te);
8124 if (s->matched_entry) {
8125 s->first_displayed_entry = s->matched_entry;
8126 s->selected = 0;
8129 return NULL;
8132 static const struct got_error *
8133 show_tree_view(struct tog_view *view)
8135 const struct got_error *err = NULL;
8136 struct tog_tree_view_state *s = &view->state.tree;
8137 char *parent_path;
8139 err = tree_entry_path(&parent_path, &s->parents, NULL);
8140 if (err)
8141 return err;
8143 err = draw_tree_entries(view, parent_path);
8144 free(parent_path);
8146 view_border(view);
8147 return err;
8150 static const struct got_error *
8151 tree_goto_line(struct tog_view *view, int nlines)
8153 const struct got_error *err = NULL;
8154 struct tog_tree_view_state *s = &view->state.tree;
8155 struct got_tree_entry **fte, **lte, **ste;
8156 int g, last, first = 1, i = 1;
8157 int root = s->tree == s->root;
8158 int off = root ? 1 : 2;
8160 g = view->gline;
8161 view->gline = 0;
8163 if (g == 0)
8164 g = 1;
8165 else if (g > got_object_tree_get_nentries(s->tree))
8166 g = got_object_tree_get_nentries(s->tree) + (root ? 0 : 1);
8168 fte = &s->first_displayed_entry;
8169 lte = &s->last_displayed_entry;
8170 ste = &s->selected_entry;
8172 if (*fte != NULL) {
8173 first = got_tree_entry_get_index(*fte);
8174 first += off; /* account for ".." */
8176 last = got_tree_entry_get_index(*lte);
8177 last += off;
8179 if (g >= first && g <= last && g - first < nlines) {
8180 s->selected = g - first;
8181 return NULL; /* gline is on the current page */
8184 if (*ste != NULL) {
8185 i = got_tree_entry_get_index(*ste);
8186 i += off;
8189 if (i < g) {
8190 err = tree_scroll_down(view, g - i);
8191 if (err)
8192 return err;
8193 if (got_tree_entry_get_index(*lte) >=
8194 got_object_tree_get_nentries(s->tree) - 1 &&
8195 first + s->selected < g &&
8196 s->selected < s->ndisplayed - 1) {
8197 first = got_tree_entry_get_index(*fte);
8198 first += off;
8199 s->selected = g - first;
8201 } else if (i > g)
8202 tree_scroll_up(s, i - g);
8204 if (g < nlines &&
8205 (*fte == NULL || (root && !got_tree_entry_get_index(*fte))))
8206 s->selected = g - 1;
8208 return NULL;
8211 static const struct got_error *
8212 input_tree_view(struct tog_view **new_view, struct tog_view *view, int ch)
8214 const struct got_error *err = NULL;
8215 struct tog_tree_view_state *s = &view->state.tree;
8216 struct got_tree_entry *te;
8217 int n, nscroll = view->nlines - 3;
8219 if (view->gline)
8220 return tree_goto_line(view, nscroll);
8222 switch (ch) {
8223 case '0':
8224 case '$':
8225 case KEY_RIGHT:
8226 case 'l':
8227 case KEY_LEFT:
8228 case 'h':
8229 horizontal_scroll_input(view, ch);
8230 break;
8231 case 'i':
8232 s->show_ids = !s->show_ids;
8233 view->count = 0;
8234 break;
8235 case 'L':
8236 view->count = 0;
8237 if (!s->selected_entry)
8238 break;
8239 err = view_request_new(new_view, view, TOG_VIEW_LOG);
8240 break;
8241 case 'R':
8242 view->count = 0;
8243 err = view_request_new(new_view, view, TOG_VIEW_REF);
8244 break;
8245 case 'g':
8246 case '=':
8247 case KEY_HOME:
8248 s->selected = 0;
8249 view->count = 0;
8250 if (s->tree == s->root)
8251 s->first_displayed_entry =
8252 got_object_tree_get_first_entry(s->tree);
8253 else
8254 s->first_displayed_entry = NULL;
8255 break;
8256 case 'G':
8257 case '*':
8258 case KEY_END: {
8259 int eos = view->nlines - 3;
8261 if (view->mode == TOG_VIEW_SPLIT_HRZN)
8262 --eos; /* border */
8263 s->selected = 0;
8264 view->count = 0;
8265 te = got_object_tree_get_last_entry(s->tree);
8266 for (n = 0; n < eos; n++) {
8267 if (te == NULL) {
8268 if (s->tree != s->root) {
8269 s->first_displayed_entry = NULL;
8270 n++;
8272 break;
8274 s->first_displayed_entry = te;
8275 te = got_tree_entry_get_prev(s->tree, te);
8277 if (n > 0)
8278 s->selected = n - 1;
8279 break;
8281 case 'k':
8282 case KEY_UP:
8283 case CTRL('p'):
8284 if (s->selected > 0) {
8285 s->selected--;
8286 break;
8288 tree_scroll_up(s, 1);
8289 if (s->selected_entry == NULL ||
8290 (s->tree == s->root && s->selected_entry ==
8291 got_object_tree_get_first_entry(s->tree)))
8292 view->count = 0;
8293 break;
8294 case CTRL('u'):
8295 case 'u':
8296 nscroll /= 2;
8297 /* FALL THROUGH */
8298 case KEY_PPAGE:
8299 case CTRL('b'):
8300 case 'b':
8301 if (s->tree == s->root) {
8302 if (got_object_tree_get_first_entry(s->tree) ==
8303 s->first_displayed_entry)
8304 s->selected -= MIN(s->selected, nscroll);
8305 } else {
8306 if (s->first_displayed_entry == NULL)
8307 s->selected -= MIN(s->selected, nscroll);
8309 tree_scroll_up(s, MAX(0, nscroll));
8310 if (s->selected_entry == NULL ||
8311 (s->tree == s->root && s->selected_entry ==
8312 got_object_tree_get_first_entry(s->tree)))
8313 view->count = 0;
8314 break;
8315 case 'j':
8316 case KEY_DOWN:
8317 case CTRL('n'):
8318 if (s->selected < s->ndisplayed - 1) {
8319 s->selected++;
8320 break;
8322 if (got_tree_entry_get_next(s->tree, s->last_displayed_entry)
8323 == NULL) {
8324 /* can't scroll any further */
8325 view->count = 0;
8326 break;
8328 tree_scroll_down(view, 1);
8329 break;
8330 case CTRL('d'):
8331 case 'd':
8332 nscroll /= 2;
8333 /* FALL THROUGH */
8334 case KEY_NPAGE:
8335 case CTRL('f'):
8336 case 'f':
8337 case ' ':
8338 if (got_tree_entry_get_next(s->tree, s->last_displayed_entry)
8339 == NULL) {
8340 /* can't scroll any further; move cursor down */
8341 if (s->selected < s->ndisplayed - 1)
8342 s->selected += MIN(nscroll,
8343 s->ndisplayed - s->selected - 1);
8344 else
8345 view->count = 0;
8346 break;
8348 tree_scroll_down(view, nscroll);
8349 break;
8350 case KEY_ENTER:
8351 case '\r':
8352 case KEY_BACKSPACE:
8353 if (s->selected_entry == NULL || ch == KEY_BACKSPACE) {
8354 struct tog_parent_tree *parent;
8355 /* user selected '..' */
8356 if (s->tree == s->root) {
8357 view->count = 0;
8358 break;
8360 parent = TAILQ_FIRST(&s->parents);
8361 TAILQ_REMOVE(&s->parents, parent,
8362 entry);
8363 got_object_tree_close(s->tree);
8364 s->tree = parent->tree;
8365 s->first_displayed_entry =
8366 parent->first_displayed_entry;
8367 s->selected_entry =
8368 parent->selected_entry;
8369 s->selected = parent->selected;
8370 if (s->selected > view->nlines - 3) {
8371 err = offset_selection_down(view);
8372 if (err)
8373 break;
8375 free(parent);
8376 } else if (S_ISDIR(got_tree_entry_get_mode(
8377 s->selected_entry))) {
8378 struct got_tree_object *subtree;
8379 view->count = 0;
8380 err = got_object_open_as_tree(&subtree, s->repo,
8381 got_tree_entry_get_id(s->selected_entry));
8382 if (err)
8383 break;
8384 err = tree_view_visit_subtree(s, subtree);
8385 if (err) {
8386 got_object_tree_close(subtree);
8387 break;
8389 } else if (S_ISREG(got_tree_entry_get_mode(s->selected_entry)))
8390 err = view_request_new(new_view, view, TOG_VIEW_BLAME);
8391 break;
8392 case KEY_RESIZE:
8393 if (view->nlines >= 4 && s->selected >= view->nlines - 3)
8394 s->selected = view->nlines - 4;
8395 view->count = 0;
8396 break;
8397 default:
8398 view->count = 0;
8399 break;
8402 return err;
8405 __dead static void
8406 usage_tree(void)
8408 endwin();
8409 fprintf(stderr,
8410 "usage: %s tree [-c commit] [-r repository-path] [path]\n",
8411 getprogname());
8412 exit(1);
8415 static const struct got_error *
8416 cmd_tree(int argc, char *argv[])
8418 const struct got_error *error;
8419 struct got_repository *repo = NULL;
8420 struct got_worktree *worktree = NULL;
8421 char *cwd = NULL, *repo_path = NULL, *in_repo_path = NULL;
8422 struct got_object_id *commit_id = NULL;
8423 struct got_commit_object *commit = NULL;
8424 const char *commit_id_arg = NULL;
8425 char *keyword_idstr = NULL, *label = NULL;
8426 struct got_reference *ref = NULL;
8427 const char *head_ref_name = NULL;
8428 int ch;
8429 struct tog_view *view;
8430 int *pack_fds = NULL;
8432 while ((ch = getopt(argc, argv, "c:r:")) != -1) {
8433 switch (ch) {
8434 case 'c':
8435 commit_id_arg = optarg;
8436 break;
8437 case 'r':
8438 repo_path = realpath(optarg, NULL);
8439 if (repo_path == NULL)
8440 return got_error_from_errno2("realpath",
8441 optarg);
8442 break;
8443 default:
8444 usage_tree();
8445 /* NOTREACHED */
8449 argc -= optind;
8450 argv += optind;
8452 if (argc > 1)
8453 usage_tree();
8455 error = got_repo_pack_fds_open(&pack_fds);
8456 if (error != NULL)
8457 goto done;
8459 if (repo_path == NULL) {
8460 cwd = getcwd(NULL, 0);
8461 if (cwd == NULL)
8462 return got_error_from_errno("getcwd");
8463 error = got_worktree_open(&worktree, cwd, NULL);
8464 if (error && error->code != GOT_ERR_NOT_WORKTREE)
8465 goto done;
8466 if (worktree)
8467 repo_path =
8468 strdup(got_worktree_get_repo_path(worktree));
8469 else
8470 repo_path = strdup(cwd);
8471 if (repo_path == NULL) {
8472 error = got_error_from_errno("strdup");
8473 goto done;
8477 error = got_repo_open(&repo, repo_path, NULL, pack_fds);
8478 if (error != NULL)
8479 goto done;
8481 error = get_in_repo_path_from_argv0(&in_repo_path, argc, argv,
8482 repo, worktree);
8483 if (error)
8484 goto done;
8486 init_curses();
8488 error = apply_unveil(got_repo_get_path(repo), NULL);
8489 if (error)
8490 goto done;
8492 error = tog_load_refs(repo, 0);
8493 if (error)
8494 goto done;
8496 if (commit_id_arg == NULL) {
8497 error = got_repo_match_object_id(&commit_id, &label,
8498 worktree ? got_worktree_get_head_ref_name(worktree) :
8499 GOT_REF_HEAD, GOT_OBJ_TYPE_COMMIT, &tog_refs, repo);
8500 if (error)
8501 goto done;
8502 head_ref_name = label;
8503 } else {
8504 error = got_keyword_to_idstr(&keyword_idstr, commit_id_arg,
8505 repo, worktree);
8506 if (error != NULL)
8507 goto done;
8508 if (keyword_idstr != NULL)
8509 commit_id_arg = keyword_idstr;
8511 error = got_ref_open(&ref, repo, commit_id_arg, 0);
8512 if (error == NULL)
8513 head_ref_name = got_ref_get_name(ref);
8514 else if (error->code != GOT_ERR_NOT_REF)
8515 goto done;
8516 error = got_repo_match_object_id(&commit_id, NULL,
8517 commit_id_arg, GOT_OBJ_TYPE_COMMIT, &tog_refs, repo);
8518 if (error)
8519 goto done;
8522 error = got_object_open_as_commit(&commit, repo, commit_id);
8523 if (error)
8524 goto done;
8526 view = view_open(0, 0, 0, 0, TOG_VIEW_TREE);
8527 if (view == NULL) {
8528 error = got_error_from_errno("view_open");
8529 goto done;
8531 error = open_tree_view(view, commit_id, head_ref_name, repo);
8532 if (error)
8533 goto done;
8534 if (!got_path_is_root_dir(in_repo_path)) {
8535 error = tree_view_walk_path(&view->state.tree, commit,
8536 in_repo_path);
8537 if (error)
8538 goto done;
8541 if (worktree) {
8542 error = set_tog_base_commit(repo, worktree);
8543 if (error != NULL)
8544 goto done;
8546 /* Release work tree lock. */
8547 got_worktree_close(worktree);
8548 worktree = NULL;
8551 error = view_loop(view);
8553 done:
8554 free(tog_base_commit.id);
8555 free(keyword_idstr);
8556 free(repo_path);
8557 free(cwd);
8558 free(commit_id);
8559 free(label);
8560 if (commit != NULL)
8561 got_object_commit_close(commit);
8562 if (ref)
8563 got_ref_close(ref);
8564 if (worktree != NULL)
8565 got_worktree_close(worktree);
8566 if (repo) {
8567 const struct got_error *close_err = got_repo_close(repo);
8568 if (error == NULL)
8569 error = close_err;
8571 if (pack_fds) {
8572 const struct got_error *pack_err =
8573 got_repo_pack_fds_close(pack_fds);
8574 if (error == NULL)
8575 error = pack_err;
8577 tog_free_refs();
8578 return error;
8581 static const struct got_error *
8582 ref_view_load_refs(struct tog_ref_view_state *s)
8584 struct got_reflist_entry *sre;
8585 struct tog_reflist_entry *re;
8587 s->nrefs = 0;
8588 TAILQ_FOREACH(sre, &tog_refs, entry) {
8589 if (strncmp(got_ref_get_name(sre->ref),
8590 "refs/got/", 9) == 0 &&
8591 strncmp(got_ref_get_name(sre->ref),
8592 "refs/got/backup/", 16) != 0)
8593 continue;
8595 re = malloc(sizeof(*re));
8596 if (re == NULL)
8597 return got_error_from_errno("malloc");
8599 re->ref = got_ref_dup(sre->ref);
8600 if (re->ref == NULL)
8601 return got_error_from_errno("got_ref_dup");
8602 re->idx = s->nrefs++;
8603 TAILQ_INSERT_TAIL(&s->refs, re, entry);
8606 s->first_displayed_entry = TAILQ_FIRST(&s->refs);
8607 return NULL;
8610 static void
8611 ref_view_free_refs(struct tog_ref_view_state *s)
8613 struct tog_reflist_entry *re;
8615 while (!TAILQ_EMPTY(&s->refs)) {
8616 re = TAILQ_FIRST(&s->refs);
8617 TAILQ_REMOVE(&s->refs, re, entry);
8618 got_ref_close(re->ref);
8619 free(re);
8623 static const struct got_error *
8624 open_ref_view(struct tog_view *view, struct got_repository *repo)
8626 const struct got_error *err = NULL;
8627 struct tog_ref_view_state *s = &view->state.ref;
8629 s->selected_entry = 0;
8630 s->repo = repo;
8632 TAILQ_INIT(&s->refs);
8633 STAILQ_INIT(&s->colors);
8635 err = ref_view_load_refs(s);
8636 if (err)
8637 goto done;
8639 if (has_colors() && getenv("TOG_COLORS") != NULL) {
8640 err = add_color(&s->colors, "^refs/heads/",
8641 TOG_COLOR_REFS_HEADS,
8642 get_color_value("TOG_COLOR_REFS_HEADS"));
8643 if (err)
8644 goto done;
8646 err = add_color(&s->colors, "^refs/tags/",
8647 TOG_COLOR_REFS_TAGS,
8648 get_color_value("TOG_COLOR_REFS_TAGS"));
8649 if (err)
8650 goto done;
8652 err = add_color(&s->colors, "^refs/remotes/",
8653 TOG_COLOR_REFS_REMOTES,
8654 get_color_value("TOG_COLOR_REFS_REMOTES"));
8655 if (err)
8656 goto done;
8658 err = add_color(&s->colors, "^refs/got/backup/",
8659 TOG_COLOR_REFS_BACKUP,
8660 get_color_value("TOG_COLOR_REFS_BACKUP"));
8661 if (err)
8662 goto done;
8665 view->show = show_ref_view;
8666 view->input = input_ref_view;
8667 view->close = close_ref_view;
8668 view->search_start = search_start_ref_view;
8669 view->search_next = search_next_ref_view;
8670 done:
8671 if (err) {
8672 if (view->close == NULL)
8673 close_ref_view(view);
8674 view_close(view);
8676 return err;
8679 static const struct got_error *
8680 close_ref_view(struct tog_view *view)
8682 struct tog_ref_view_state *s = &view->state.ref;
8684 ref_view_free_refs(s);
8685 free_colors(&s->colors);
8687 return NULL;
8690 static const struct got_error *
8691 resolve_reflist_entry(struct got_object_id **commit_id,
8692 struct tog_reflist_entry *re, struct got_repository *repo)
8694 const struct got_error *err = NULL;
8695 struct got_object_id *obj_id;
8696 struct got_tag_object *tag = NULL;
8697 int obj_type;
8699 *commit_id = NULL;
8701 err = got_ref_resolve(&obj_id, repo, re->ref);
8702 if (err)
8703 return err;
8705 err = got_object_get_type(&obj_type, repo, obj_id);
8706 if (err)
8707 goto done;
8709 switch (obj_type) {
8710 case GOT_OBJ_TYPE_COMMIT:
8711 *commit_id = obj_id;
8712 break;
8713 case GOT_OBJ_TYPE_TAG:
8714 err = got_object_open_as_tag(&tag, repo, obj_id);
8715 if (err)
8716 goto done;
8717 free(obj_id);
8718 err = got_object_get_type(&obj_type, repo,
8719 got_object_tag_get_object_id(tag));
8720 if (err)
8721 goto done;
8722 if (obj_type != GOT_OBJ_TYPE_COMMIT) {
8723 err = got_error(GOT_ERR_OBJ_TYPE);
8724 goto done;
8726 *commit_id = got_object_id_dup(
8727 got_object_tag_get_object_id(tag));
8728 if (*commit_id == NULL) {
8729 err = got_error_from_errno("got_object_id_dup");
8730 goto done;
8732 break;
8733 default:
8734 err = got_error(GOT_ERR_OBJ_TYPE);
8735 break;
8738 done:
8739 if (tag)
8740 got_object_tag_close(tag);
8741 if (err) {
8742 free(*commit_id);
8743 *commit_id = NULL;
8745 return err;
8748 static const struct got_error *
8749 log_ref_entry(struct tog_view **new_view, int begin_y, int begin_x,
8750 struct tog_reflist_entry *re, struct got_repository *repo)
8752 struct tog_view *log_view;
8753 const struct got_error *err = NULL;
8754 struct got_object_id *commit_id = NULL;
8756 *new_view = NULL;
8758 err = resolve_reflist_entry(&commit_id, re, repo);
8759 if (err)
8760 return err;
8762 log_view = view_open(0, 0, begin_y, begin_x, TOG_VIEW_LOG);
8763 if (log_view == NULL) {
8764 err = got_error_from_errno("view_open");
8765 goto done;
8768 err = open_log_view(log_view, commit_id, repo,
8769 got_ref_get_name(re->ref), "", 0, NULL);
8770 done:
8771 if (err)
8772 view_close(log_view);
8773 else
8774 *new_view = log_view;
8775 free(commit_id);
8776 return err;
8779 static void
8780 ref_scroll_up(struct tog_ref_view_state *s, int maxscroll)
8782 struct tog_reflist_entry *re;
8783 int i = 0;
8785 if (s->first_displayed_entry == TAILQ_FIRST(&s->refs))
8786 return;
8788 re = TAILQ_PREV(s->first_displayed_entry, tog_reflist_head, entry);
8789 while (i++ < maxscroll) {
8790 if (re == NULL)
8791 break;
8792 s->first_displayed_entry = re;
8793 re = TAILQ_PREV(re, tog_reflist_head, entry);
8797 static const struct got_error *
8798 ref_scroll_down(struct tog_view *view, int maxscroll)
8800 struct tog_ref_view_state *s = &view->state.ref;
8801 struct tog_reflist_entry *next, *last;
8802 int n = 0;
8804 if (s->first_displayed_entry)
8805 next = TAILQ_NEXT(s->first_displayed_entry, entry);
8806 else
8807 next = TAILQ_FIRST(&s->refs);
8809 last = s->last_displayed_entry;
8810 while (next && n++ < maxscroll) {
8811 if (last) {
8812 s->last_displayed_entry = last;
8813 last = TAILQ_NEXT(last, entry);
8815 if (last || (view->mode == TOG_VIEW_SPLIT_HRZN)) {
8816 s->first_displayed_entry = next;
8817 next = TAILQ_NEXT(next, entry);
8821 return NULL;
8824 static const struct got_error *
8825 search_start_ref_view(struct tog_view *view)
8827 struct tog_ref_view_state *s = &view->state.ref;
8829 s->matched_entry = NULL;
8830 return NULL;
8833 static int
8834 match_reflist_entry(struct tog_reflist_entry *re, regex_t *regex)
8836 regmatch_t regmatch;
8838 return regexec(regex, got_ref_get_name(re->ref), 1, &regmatch,
8839 0) == 0;
8842 static const struct got_error *
8843 search_next_ref_view(struct tog_view *view)
8845 struct tog_ref_view_state *s = &view->state.ref;
8846 struct tog_reflist_entry *re = NULL;
8848 if (!view->searching) {
8849 view->search_next_done = TOG_SEARCH_HAVE_MORE;
8850 return NULL;
8853 if (s->matched_entry) {
8854 if (view->searching == TOG_SEARCH_FORWARD) {
8855 if (s->selected_entry)
8856 re = TAILQ_NEXT(s->selected_entry, entry);
8857 else
8858 re = TAILQ_PREV(s->selected_entry,
8859 tog_reflist_head, entry);
8860 } else {
8861 if (s->selected_entry == NULL)
8862 re = TAILQ_LAST(&s->refs, tog_reflist_head);
8863 else
8864 re = TAILQ_PREV(s->selected_entry,
8865 tog_reflist_head, entry);
8867 } else {
8868 if (s->selected_entry)
8869 re = s->selected_entry;
8870 else if (view->searching == TOG_SEARCH_FORWARD)
8871 re = TAILQ_FIRST(&s->refs);
8872 else
8873 re = TAILQ_LAST(&s->refs, tog_reflist_head);
8876 while (1) {
8877 if (re == NULL) {
8878 if (s->matched_entry == NULL) {
8879 view->search_next_done = TOG_SEARCH_HAVE_MORE;
8880 return NULL;
8882 if (view->searching == TOG_SEARCH_FORWARD)
8883 re = TAILQ_FIRST(&s->refs);
8884 else
8885 re = TAILQ_LAST(&s->refs, tog_reflist_head);
8888 if (match_reflist_entry(re, &view->regex)) {
8889 view->search_next_done = TOG_SEARCH_HAVE_MORE;
8890 s->matched_entry = re;
8891 break;
8894 if (view->searching == TOG_SEARCH_FORWARD)
8895 re = TAILQ_NEXT(re, entry);
8896 else
8897 re = TAILQ_PREV(re, tog_reflist_head, entry);
8900 if (s->matched_entry) {
8901 s->first_displayed_entry = s->matched_entry;
8902 s->selected = 0;
8905 return NULL;
8908 static const struct got_error *
8909 show_ref_view(struct tog_view *view)
8911 const struct got_error *err = NULL;
8912 struct tog_ref_view_state *s = &view->state.ref;
8913 struct tog_reflist_entry *re;
8914 char *line = NULL;
8915 wchar_t *wline;
8916 struct tog_color *tc;
8917 int width, n, scrollx;
8918 int limit = view->nlines;
8920 werase(view->window);
8922 s->ndisplayed = 0;
8923 if (view_is_hsplit_top(view))
8924 --limit; /* border */
8926 if (limit == 0)
8927 return NULL;
8929 re = s->first_displayed_entry;
8931 if (asprintf(&line, "references [%d/%d]", re->idx + s->selected + 1,
8932 s->nrefs) == -1)
8933 return got_error_from_errno("asprintf");
8935 err = format_line(&wline, &width, NULL, line, 0, view->ncols, 0, 0);
8936 if (err) {
8937 free(line);
8938 return err;
8940 if (view_needs_focus_indication(view))
8941 wstandout(view->window);
8942 waddwstr(view->window, wline);
8943 while (width++ < view->ncols)
8944 waddch(view->window, ' ');
8945 if (view_needs_focus_indication(view))
8946 wstandend(view->window);
8947 free(wline);
8948 wline = NULL;
8949 free(line);
8950 line = NULL;
8951 if (--limit <= 0)
8952 return NULL;
8954 n = 0;
8955 view->maxx = 0;
8956 while (re && limit > 0) {
8957 char *line = NULL;
8958 char ymd[13]; /* YYYY-MM-DD + " " + NUL */
8960 if (s->show_date) {
8961 struct got_commit_object *ci;
8962 struct got_tag_object *tag;
8963 struct got_object_id *id;
8964 struct tm tm;
8965 time_t t;
8967 err = got_ref_resolve(&id, s->repo, re->ref);
8968 if (err)
8969 return err;
8970 err = got_object_open_as_tag(&tag, s->repo, id);
8971 if (err) {
8972 if (err->code != GOT_ERR_OBJ_TYPE) {
8973 free(id);
8974 return err;
8976 err = got_object_open_as_commit(&ci, s->repo,
8977 id);
8978 if (err) {
8979 free(id);
8980 return err;
8982 t = got_object_commit_get_committer_time(ci);
8983 got_object_commit_close(ci);
8984 } else {
8985 t = got_object_tag_get_tagger_time(tag);
8986 got_object_tag_close(tag);
8988 free(id);
8989 if (gmtime_r(&t, &tm) == NULL)
8990 return got_error_from_errno("gmtime_r");
8991 if (strftime(ymd, sizeof(ymd), "%F ", &tm) == 0)
8992 return got_error(GOT_ERR_NO_SPACE);
8994 if (got_ref_is_symbolic(re->ref)) {
8995 if (asprintf(&line, "%s%s -> %s", s->show_date ?
8996 ymd : "", got_ref_get_name(re->ref),
8997 got_ref_get_symref_target(re->ref)) == -1)
8998 return got_error_from_errno("asprintf");
8999 } else if (s->show_ids) {
9000 struct got_object_id *id;
9001 char *id_str;
9002 err = got_ref_resolve(&id, s->repo, re->ref);
9003 if (err)
9004 return err;
9005 err = got_object_id_str(&id_str, id);
9006 if (err) {
9007 free(id);
9008 return err;
9010 if (asprintf(&line, "%s%s: %s", s->show_date ? ymd : "",
9011 got_ref_get_name(re->ref), id_str) == -1) {
9012 err = got_error_from_errno("asprintf");
9013 free(id);
9014 free(id_str);
9015 return err;
9017 free(id);
9018 free(id_str);
9019 } else if (asprintf(&line, "%s%s", s->show_date ? ymd : "",
9020 got_ref_get_name(re->ref)) == -1)
9021 return got_error_from_errno("asprintf");
9023 /* use full line width to determine view->maxx */
9024 err = format_line(&wline, &width, NULL, line, 0, INT_MAX, 0, 0);
9025 if (err) {
9026 free(line);
9027 return err;
9029 view->maxx = MAX(view->maxx, width);
9030 free(wline);
9031 wline = NULL;
9033 err = format_line(&wline, &width, &scrollx, line, view->x,
9034 view->ncols, 0, 0);
9035 if (err) {
9036 free(line);
9037 return err;
9039 if (n == s->selected) {
9040 if (view->focussed)
9041 wstandout(view->window);
9042 s->selected_entry = re;
9044 tc = match_color(&s->colors, got_ref_get_name(re->ref));
9045 if (tc)
9046 wattr_on(view->window,
9047 COLOR_PAIR(tc->colorpair), NULL);
9048 waddwstr(view->window, &wline[scrollx]);
9049 if (tc)
9050 wattr_off(view->window,
9051 COLOR_PAIR(tc->colorpair), NULL);
9052 if (width < view->ncols)
9053 waddch(view->window, '\n');
9054 if (n == s->selected && view->focussed)
9055 wstandend(view->window);
9056 free(line);
9057 free(wline);
9058 wline = NULL;
9059 n++;
9060 s->ndisplayed++;
9061 s->last_displayed_entry = re;
9063 limit--;
9064 re = TAILQ_NEXT(re, entry);
9067 view_border(view);
9068 return err;
9071 static const struct got_error *
9072 browse_ref_tree(struct tog_view **new_view, int begin_y, int begin_x,
9073 struct tog_reflist_entry *re, struct got_repository *repo)
9075 const struct got_error *err = NULL;
9076 struct got_object_id *commit_id = NULL;
9077 struct tog_view *tree_view;
9079 *new_view = NULL;
9081 err = resolve_reflist_entry(&commit_id, re, repo);
9082 if (err)
9083 return err;
9085 tree_view = view_open(0, 0, begin_y, begin_x, TOG_VIEW_TREE);
9086 if (tree_view == NULL) {
9087 err = got_error_from_errno("view_open");
9088 goto done;
9091 err = open_tree_view(tree_view, commit_id,
9092 got_ref_get_name(re->ref), repo);
9093 if (err)
9094 goto done;
9096 *new_view = tree_view;
9097 done:
9098 free(commit_id);
9099 return err;
9102 static const struct got_error *
9103 ref_goto_line(struct tog_view *view, int nlines)
9105 const struct got_error *err = NULL;
9106 struct tog_ref_view_state *s = &view->state.ref;
9107 int g, idx = s->selected_entry->idx;
9109 g = view->gline;
9110 view->gline = 0;
9112 if (g == 0)
9113 g = 1;
9114 else if (g > s->nrefs)
9115 g = s->nrefs;
9117 if (g >= s->first_displayed_entry->idx + 1 &&
9118 g <= s->last_displayed_entry->idx + 1 &&
9119 g - s->first_displayed_entry->idx - 1 < nlines) {
9120 s->selected = g - s->first_displayed_entry->idx - 1;
9121 return NULL;
9124 if (idx + 1 < g) {
9125 err = ref_scroll_down(view, g - idx - 1);
9126 if (err)
9127 return err;
9128 if (TAILQ_NEXT(s->last_displayed_entry, entry) == NULL &&
9129 s->first_displayed_entry->idx + s->selected < g &&
9130 s->selected < s->ndisplayed - 1)
9131 s->selected = g - s->first_displayed_entry->idx - 1;
9132 } else if (idx + 1 > g)
9133 ref_scroll_up(s, idx - g + 1);
9135 if (g < nlines && s->first_displayed_entry->idx == 0)
9136 s->selected = g - 1;
9138 return NULL;
9142 static const struct got_error *
9143 input_ref_view(struct tog_view **new_view, struct tog_view *view, int ch)
9145 const struct got_error *err = NULL;
9146 struct tog_ref_view_state *s = &view->state.ref;
9147 struct tog_reflist_entry *re;
9148 int n, nscroll = view->nlines - 1;
9150 if (view->gline)
9151 return ref_goto_line(view, nscroll);
9153 switch (ch) {
9154 case '0':
9155 case '$':
9156 case KEY_RIGHT:
9157 case 'l':
9158 case KEY_LEFT:
9159 case 'h':
9160 horizontal_scroll_input(view, ch);
9161 break;
9162 case 'i':
9163 s->show_ids = !s->show_ids;
9164 view->count = 0;
9165 break;
9166 case 'm':
9167 s->show_date = !s->show_date;
9168 view->count = 0;
9169 break;
9170 case 'o':
9171 s->sort_by_date = !s->sort_by_date;
9172 view->action = s->sort_by_date ? "sort by date" : "sort by name";
9173 view->count = 0;
9174 err = got_reflist_sort(&tog_refs, s->sort_by_date ?
9175 got_ref_cmp_by_commit_timestamp_descending :
9176 tog_ref_cmp_by_name, s->repo);
9177 if (err)
9178 break;
9179 got_reflist_object_id_map_free(tog_refs_idmap);
9180 err = got_reflist_object_id_map_create(&tog_refs_idmap,
9181 &tog_refs, s->repo);
9182 if (err)
9183 break;
9184 ref_view_free_refs(s);
9185 err = ref_view_load_refs(s);
9186 break;
9187 case KEY_ENTER:
9188 case '\r':
9189 view->count = 0;
9190 if (!s->selected_entry)
9191 break;
9192 err = view_request_new(new_view, view, TOG_VIEW_LOG);
9193 break;
9194 case 'T':
9195 view->count = 0;
9196 if (!s->selected_entry)
9197 break;
9198 err = view_request_new(new_view, view, TOG_VIEW_TREE);
9199 break;
9200 case 'g':
9201 case '=':
9202 case KEY_HOME:
9203 s->selected = 0;
9204 view->count = 0;
9205 s->first_displayed_entry = TAILQ_FIRST(&s->refs);
9206 break;
9207 case 'G':
9208 case '*':
9209 case KEY_END: {
9210 int eos = view->nlines - 1;
9212 if (view->mode == TOG_VIEW_SPLIT_HRZN)
9213 --eos; /* border */
9214 s->selected = 0;
9215 view->count = 0;
9216 re = TAILQ_LAST(&s->refs, tog_reflist_head);
9217 for (n = 0; n < eos; n++) {
9218 if (re == NULL)
9219 break;
9220 s->first_displayed_entry = re;
9221 re = TAILQ_PREV(re, tog_reflist_head, entry);
9223 if (n > 0)
9224 s->selected = n - 1;
9225 break;
9227 case 'k':
9228 case KEY_UP:
9229 case CTRL('p'):
9230 if (s->selected > 0) {
9231 s->selected--;
9232 break;
9234 ref_scroll_up(s, 1);
9235 if (s->selected_entry == TAILQ_FIRST(&s->refs))
9236 view->count = 0;
9237 break;
9238 case CTRL('u'):
9239 case 'u':
9240 nscroll /= 2;
9241 /* FALL THROUGH */
9242 case KEY_PPAGE:
9243 case CTRL('b'):
9244 case 'b':
9245 if (s->first_displayed_entry == TAILQ_FIRST(&s->refs))
9246 s->selected -= MIN(nscroll, s->selected);
9247 ref_scroll_up(s, MAX(0, nscroll));
9248 if (s->selected_entry == TAILQ_FIRST(&s->refs))
9249 view->count = 0;
9250 break;
9251 case 'j':
9252 case KEY_DOWN:
9253 case CTRL('n'):
9254 if (s->selected < s->ndisplayed - 1) {
9255 s->selected++;
9256 break;
9258 if (TAILQ_NEXT(s->last_displayed_entry, entry) == NULL) {
9259 /* can't scroll any further */
9260 view->count = 0;
9261 break;
9263 ref_scroll_down(view, 1);
9264 break;
9265 case CTRL('d'):
9266 case 'd':
9267 nscroll /= 2;
9268 /* FALL THROUGH */
9269 case KEY_NPAGE:
9270 case CTRL('f'):
9271 case 'f':
9272 case ' ':
9273 if (TAILQ_NEXT(s->last_displayed_entry, entry) == NULL) {
9274 /* can't scroll any further; move cursor down */
9275 if (s->selected < s->ndisplayed - 1)
9276 s->selected += MIN(nscroll,
9277 s->ndisplayed - s->selected - 1);
9278 if (view->count > 1 && s->selected < s->ndisplayed - 1)
9279 s->selected += s->ndisplayed - s->selected - 1;
9280 view->count = 0;
9281 break;
9283 ref_scroll_down(view, nscroll);
9284 break;
9285 case CTRL('l'):
9286 view->count = 0;
9287 tog_free_refs();
9288 err = tog_load_refs(s->repo, s->sort_by_date);
9289 if (err)
9290 break;
9291 ref_view_free_refs(s);
9292 err = ref_view_load_refs(s);
9293 break;
9294 case KEY_RESIZE:
9295 if (view->nlines >= 2 && s->selected >= view->nlines - 1)
9296 s->selected = view->nlines - 2;
9297 break;
9298 default:
9299 view->count = 0;
9300 break;
9303 return err;
9306 __dead static void
9307 usage_ref(void)
9309 endwin();
9310 fprintf(stderr, "usage: %s ref [-r repository-path]\n",
9311 getprogname());
9312 exit(1);
9315 static const struct got_error *
9316 cmd_ref(int argc, char *argv[])
9318 const struct got_error *error;
9319 struct got_repository *repo = NULL;
9320 struct got_worktree *worktree = NULL;
9321 char *cwd = NULL, *repo_path = NULL;
9322 int ch;
9323 struct tog_view *view;
9324 int *pack_fds = NULL;
9326 while ((ch = getopt(argc, argv, "r:")) != -1) {
9327 switch (ch) {
9328 case 'r':
9329 repo_path = realpath(optarg, NULL);
9330 if (repo_path == NULL)
9331 return got_error_from_errno2("realpath",
9332 optarg);
9333 break;
9334 default:
9335 usage_ref();
9336 /* NOTREACHED */
9340 argc -= optind;
9341 argv += optind;
9343 if (argc > 1)
9344 usage_ref();
9346 error = got_repo_pack_fds_open(&pack_fds);
9347 if (error != NULL)
9348 goto done;
9350 if (repo_path == NULL) {
9351 cwd = getcwd(NULL, 0);
9352 if (cwd == NULL)
9353 return got_error_from_errno("getcwd");
9354 error = got_worktree_open(&worktree, cwd, NULL);
9355 if (error && error->code != GOT_ERR_NOT_WORKTREE)
9356 goto done;
9357 if (worktree)
9358 repo_path =
9359 strdup(got_worktree_get_repo_path(worktree));
9360 else
9361 repo_path = strdup(cwd);
9362 if (repo_path == NULL) {
9363 error = got_error_from_errno("strdup");
9364 goto done;
9368 error = got_repo_open(&repo, repo_path, NULL, pack_fds);
9369 if (error != NULL)
9370 goto done;
9372 init_curses();
9374 error = apply_unveil(got_repo_get_path(repo), NULL);
9375 if (error)
9376 goto done;
9378 error = tog_load_refs(repo, 0);
9379 if (error)
9380 goto done;
9382 view = view_open(0, 0, 0, 0, TOG_VIEW_REF);
9383 if (view == NULL) {
9384 error = got_error_from_errno("view_open");
9385 goto done;
9388 error = open_ref_view(view, repo);
9389 if (error)
9390 goto done;
9392 if (worktree) {
9393 error = set_tog_base_commit(repo, worktree);
9394 if (error != NULL)
9395 goto done;
9397 /* Release work tree lock. */
9398 got_worktree_close(worktree);
9399 worktree = NULL;
9402 error = view_loop(view);
9404 done:
9405 free(tog_base_commit.id);
9406 free(repo_path);
9407 free(cwd);
9408 if (worktree != NULL)
9409 got_worktree_close(worktree);
9410 if (repo) {
9411 const struct got_error *close_err = got_repo_close(repo);
9412 if (close_err)
9413 error = close_err;
9415 if (pack_fds) {
9416 const struct got_error *pack_err =
9417 got_repo_pack_fds_close(pack_fds);
9418 if (error == NULL)
9419 error = pack_err;
9421 tog_free_refs();
9422 return error;
9425 static const struct got_error*
9426 win_draw_center(WINDOW *win, size_t y, size_t x, size_t maxx, int focus,
9427 const char *str)
9429 size_t len;
9431 if (win == NULL)
9432 win = stdscr;
9434 len = strlen(str);
9435 x = x ? x : maxx > len ? (maxx - len) / 2 : 0;
9437 if (focus)
9438 wstandout(win);
9439 if (mvwprintw(win, y, x, "%s", str) == ERR)
9440 return got_error_msg(GOT_ERR_RANGE, "mvwprintw");
9441 if (focus)
9442 wstandend(win);
9444 return NULL;
9447 static const struct got_error *
9448 add_line_offset(off_t **line_offsets, size_t *nlines, off_t off)
9450 off_t *p;
9452 p = reallocarray(*line_offsets, *nlines + 1, sizeof(off_t));
9453 if (p == NULL) {
9454 free(*line_offsets);
9455 *line_offsets = NULL;
9456 return got_error_from_errno("reallocarray");
9459 *line_offsets = p;
9460 (*line_offsets)[*nlines] = off;
9461 ++(*nlines);
9462 return NULL;
9465 static const struct got_error *
9466 max_key_str(int *ret, const struct tog_key_map *km, size_t n)
9468 *ret = 0;
9470 for (;n > 0; --n, ++km) {
9471 char *t0, *t, *k;
9472 size_t len = 1;
9474 if (km->keys == NULL)
9475 continue;
9477 t = t0 = strdup(km->keys);
9478 if (t0 == NULL)
9479 return got_error_from_errno("strdup");
9481 len += strlen(t);
9482 while ((k = strsep(&t, " ")) != NULL)
9483 len += strlen(k) > 1 ? 2 : 0;
9484 free(t0);
9485 *ret = MAX(*ret, len);
9488 return NULL;
9492 * Write keymap section headers, keys, and key info in km to f.
9493 * Save line offset to *off. If terminal has UTF8 encoding enabled,
9494 * wrap control and symbolic keys in guillemets, else use <>.
9496 static const struct got_error *
9497 format_help_line(off_t *off, FILE *f, const struct tog_key_map *km, int width)
9499 int n, len = width;
9501 if (km->keys) {
9502 static const char *u8_glyph[] = {
9503 "\xe2\x80\xb9", /* U+2039 (utf8 <) */
9504 "\xe2\x80\xba" /* U+203A (utf8 >) */
9506 char *t0, *t, *k;
9507 int cs, s, first = 1;
9509 cs = got_locale_is_utf8();
9511 t = t0 = strdup(km->keys);
9512 if (t0 == NULL)
9513 return got_error_from_errno("strdup");
9515 len = strlen(km->keys);
9516 while ((k = strsep(&t, " ")) != NULL) {
9517 s = strlen(k) > 1; /* control or symbolic key */
9518 n = fprintf(f, "%s%s%s%s%s", first ? " " : "",
9519 cs && s ? u8_glyph[0] : s ? "<" : "", k,
9520 cs && s ? u8_glyph[1] : s ? ">" : "", t ? " " : "");
9521 if (n < 0) {
9522 free(t0);
9523 return got_error_from_errno("fprintf");
9525 first = 0;
9526 len += s ? 2 : 0;
9527 *off += n;
9529 free(t0);
9531 n = fprintf(f, "%*s%s\n", width - len, width - len ? " " : "", km->info);
9532 if (n < 0)
9533 return got_error_from_errno("fprintf");
9534 *off += n;
9536 return NULL;
9539 static const struct got_error *
9540 format_help(struct tog_help_view_state *s)
9542 const struct got_error *err = NULL;
9543 off_t off = 0;
9544 int i, max, n, show = s->all;
9545 static const struct tog_key_map km[] = {
9546 #define KEYMAP_(info, type) { NULL, (info), type }
9547 #define KEY_(keys, info) { (keys), (info), TOG_KEYMAP_KEYS }
9548 GENERATE_HELP
9549 #undef KEYMAP_
9550 #undef KEY_
9553 err = add_line_offset(&s->line_offsets, &s->nlines, 0);
9554 if (err)
9555 return err;
9557 n = nitems(km);
9558 err = max_key_str(&max, km, n);
9559 if (err)
9560 return err;
9562 for (i = 0; i < n; ++i) {
9563 if (km[i].keys == NULL) {
9564 show = s->all;
9565 if (km[i].type == TOG_KEYMAP_GLOBAL ||
9566 km[i].type == s->type || s->all)
9567 show = 1;
9569 if (show) {
9570 err = format_help_line(&off, s->f, &km[i], max);
9571 if (err)
9572 return err;
9573 err = add_line_offset(&s->line_offsets, &s->nlines, off);
9574 if (err)
9575 return err;
9578 fputc('\n', s->f);
9579 ++off;
9580 err = add_line_offset(&s->line_offsets, &s->nlines, off);
9581 return err;
9584 static const struct got_error *
9585 create_help(struct tog_help_view_state *s)
9587 FILE *f;
9588 const struct got_error *err;
9590 free(s->line_offsets);
9591 s->line_offsets = NULL;
9592 s->nlines = 0;
9594 f = got_opentemp();
9595 if (f == NULL)
9596 return got_error_from_errno("got_opentemp");
9597 s->f = f;
9599 err = format_help(s);
9600 if (err)
9601 return err;
9603 if (s->f && fflush(s->f) != 0)
9604 return got_error_from_errno("fflush");
9606 return NULL;
9609 static const struct got_error *
9610 search_start_help_view(struct tog_view *view)
9612 view->state.help.matched_line = 0;
9613 return NULL;
9616 static void
9617 search_setup_help_view(struct tog_view *view, FILE **f, off_t **line_offsets,
9618 size_t *nlines, int **first, int **last, int **match, int **selected)
9620 struct tog_help_view_state *s = &view->state.help;
9622 *f = s->f;
9623 *nlines = s->nlines;
9624 *line_offsets = s->line_offsets;
9625 *match = &s->matched_line;
9626 *first = &s->first_displayed_line;
9627 *last = &s->last_displayed_line;
9628 *selected = &s->selected_line;
9631 static const struct got_error *
9632 show_help_view(struct tog_view *view)
9634 struct tog_help_view_state *s = &view->state.help;
9635 const struct got_error *err;
9636 regmatch_t *regmatch = &view->regmatch;
9637 wchar_t *wline;
9638 char *line;
9639 ssize_t linelen;
9640 size_t linesz = 0;
9641 int width, nprinted = 0, rc = 0;
9642 int eos = view->nlines;
9644 if (view_is_hsplit_top(view))
9645 --eos; /* account for border */
9647 s->lineno = 0;
9648 rewind(s->f);
9649 werase(view->window);
9651 if (view->gline > s->nlines - 1)
9652 view->gline = s->nlines - 1;
9654 err = win_draw_center(view->window, 0, 0, view->ncols,
9655 view_needs_focus_indication(view),
9656 "tog help (press q to return to tog)");
9657 if (err)
9658 return err;
9659 if (eos <= 1)
9660 return NULL;
9661 waddstr(view->window, "\n\n");
9662 eos -= 2;
9664 s->eof = 0;
9665 view->maxx = 0;
9666 line = NULL;
9667 while (eos > 0 && nprinted < eos) {
9668 attr_t attr = 0;
9670 linelen = getline(&line, &linesz, s->f);
9671 if (linelen == -1) {
9672 if (!feof(s->f)) {
9673 free(line);
9674 return got_ferror(s->f, GOT_ERR_IO);
9676 s->eof = 1;
9677 break;
9679 if (++s->lineno < s->first_displayed_line)
9680 continue;
9681 if (view->gline && !gotoline(view, &s->lineno, &nprinted))
9682 continue;
9683 if (s->lineno == view->hiline)
9684 attr = A_STANDOUT;
9686 err = format_line(&wline, &width, NULL, line, 0, INT_MAX, 0,
9687 view->x ? 1 : 0);
9688 if (err) {
9689 free(line);
9690 return err;
9692 view->maxx = MAX(view->maxx, width);
9693 free(wline);
9694 wline = NULL;
9696 if (attr)
9697 wattron(view->window, attr);
9698 if (s->first_displayed_line + nprinted == s->matched_line &&
9699 regmatch->rm_so >= 0 && regmatch->rm_so < regmatch->rm_eo) {
9700 err = add_matched_line(&width, line, view->ncols - 1, 0,
9701 view->window, view->x, regmatch);
9702 if (err) {
9703 free(line);
9704 return err;
9706 } else {
9707 int skip;
9709 err = format_line(&wline, &width, &skip, line,
9710 view->x, view->ncols, 0, view->x ? 1 : 0);
9711 if (err) {
9712 free(line);
9713 return err;
9715 waddwstr(view->window, &wline[skip]);
9716 free(wline);
9717 wline = NULL;
9719 if (s->lineno == view->hiline) {
9720 while (width++ < view->ncols)
9721 waddch(view->window, ' ');
9722 } else {
9723 if (width < view->ncols)
9724 waddch(view->window, '\n');
9726 if (attr)
9727 wattroff(view->window, attr);
9728 if (++nprinted == 1)
9729 s->first_displayed_line = s->lineno;
9731 free(line);
9732 if (nprinted > 0)
9733 s->last_displayed_line = s->first_displayed_line + nprinted - 1;
9734 else
9735 s->last_displayed_line = s->first_displayed_line;
9737 view_border(view);
9739 if (s->eof) {
9740 rc = waddnstr(view->window,
9741 "See the tog(1) manual page for full documentation",
9742 view->ncols - 1);
9743 if (rc == ERR)
9744 return got_error_msg(GOT_ERR_RANGE, "waddnstr");
9745 } else {
9746 wmove(view->window, view->nlines - 1, 0);
9747 wclrtoeol(view->window);
9748 wstandout(view->window);
9749 rc = waddnstr(view->window, "scroll down for more...",
9750 view->ncols - 1);
9751 if (rc == ERR)
9752 return got_error_msg(GOT_ERR_RANGE, "waddnstr");
9753 if (getcurx(view->window) < view->ncols - 6) {
9754 rc = wprintw(view->window, "[%.0f%%]",
9755 100.00 * s->last_displayed_line / s->nlines);
9756 if (rc == ERR)
9757 return got_error_msg(GOT_ERR_IO, "wprintw");
9759 wstandend(view->window);
9762 return NULL;
9765 static const struct got_error *
9766 input_help_view(struct tog_view **new_view, struct tog_view *view, int ch)
9768 struct tog_help_view_state *s = &view->state.help;
9769 const struct got_error *err = NULL;
9770 char *line = NULL;
9771 ssize_t linelen;
9772 size_t linesz = 0;
9773 int eos, nscroll;
9775 eos = nscroll = view->nlines;
9776 if (view_is_hsplit_top(view))
9777 --eos; /* border */
9779 s->lineno = s->first_displayed_line - 1 + s->selected_line;
9781 switch (ch) {
9782 case '0':
9783 case '$':
9784 case KEY_RIGHT:
9785 case 'l':
9786 case KEY_LEFT:
9787 case 'h':
9788 horizontal_scroll_input(view, ch);
9789 break;
9790 case 'g':
9791 case KEY_HOME:
9792 s->first_displayed_line = 1;
9793 view->count = 0;
9794 break;
9795 case 'G':
9796 case KEY_END:
9797 view->count = 0;
9798 if (s->eof)
9799 break;
9800 s->first_displayed_line = (s->nlines - eos) + 3;
9801 s->eof = 1;
9802 break;
9803 case 'k':
9804 case KEY_UP:
9805 if (s->first_displayed_line > 1)
9806 --s->first_displayed_line;
9807 else
9808 view->count = 0;
9809 break;
9810 case CTRL('u'):
9811 case 'u':
9812 nscroll /= 2;
9813 /* FALL THROUGH */
9814 case KEY_PPAGE:
9815 case CTRL('b'):
9816 case 'b':
9817 if (s->first_displayed_line == 1) {
9818 view->count = 0;
9819 break;
9821 while (--nscroll > 0 && s->first_displayed_line > 1)
9822 s->first_displayed_line--;
9823 break;
9824 case 'j':
9825 case KEY_DOWN:
9826 case CTRL('n'):
9827 if (!s->eof)
9828 ++s->first_displayed_line;
9829 else
9830 view->count = 0;
9831 break;
9832 case CTRL('d'):
9833 case 'd':
9834 nscroll /= 2;
9835 /* FALL THROUGH */
9836 case KEY_NPAGE:
9837 case CTRL('f'):
9838 case 'f':
9839 case ' ':
9840 if (s->eof) {
9841 view->count = 0;
9842 break;
9844 while (!s->eof && --nscroll > 0) {
9845 linelen = getline(&line, &linesz, s->f);
9846 s->first_displayed_line++;
9847 if (linelen == -1) {
9848 if (feof(s->f))
9849 s->eof = 1;
9850 else
9851 err = got_ferror(s->f, GOT_ERR_IO);
9852 break;
9855 free(line);
9856 break;
9857 default:
9858 view->count = 0;
9859 break;
9862 return err;
9865 static const struct got_error *
9866 close_help_view(struct tog_view *view)
9868 struct tog_help_view_state *s = &view->state.help;
9870 free(s->line_offsets);
9871 s->line_offsets = NULL;
9872 if (fclose(s->f) == EOF)
9873 return got_error_from_errno("fclose");
9875 return NULL;
9878 static const struct got_error *
9879 reset_help_view(struct tog_view *view)
9881 struct tog_help_view_state *s = &view->state.help;
9884 if (s->f && fclose(s->f) == EOF)
9885 return got_error_from_errno("fclose");
9887 wclear(view->window);
9888 view->count = 0;
9889 view->x = 0;
9890 s->all = !s->all;
9891 s->first_displayed_line = 1;
9892 s->last_displayed_line = view->nlines;
9893 s->matched_line = 0;
9895 return create_help(s);
9898 static const struct got_error *
9899 open_help_view(struct tog_view *view, struct tog_view *parent)
9901 const struct got_error *err = NULL;
9902 struct tog_help_view_state *s = &view->state.help;
9904 s->type = (enum tog_keymap_type)parent->type;
9905 s->first_displayed_line = 1;
9906 s->last_displayed_line = view->nlines;
9907 s->selected_line = 1;
9909 view->show = show_help_view;
9910 view->input = input_help_view;
9911 view->reset = reset_help_view;
9912 view->close = close_help_view;
9913 view->search_start = search_start_help_view;
9914 view->search_setup = search_setup_help_view;
9915 view->search_next = search_next_view_match;
9917 err = create_help(s);
9918 return err;
9921 static const struct got_error *
9922 view_dispatch_request(struct tog_view **new_view, struct tog_view *view,
9923 enum tog_view_type request, int y, int x)
9925 const struct got_error *err = NULL;
9927 *new_view = NULL;
9929 switch (request) {
9930 case TOG_VIEW_DIFF:
9931 if (view->type == TOG_VIEW_LOG) {
9932 struct tog_log_view_state *s = &view->state.log;
9934 err = open_diff_view_for_commit(new_view, y, x,
9935 s->selected_entry->commit, s->selected_entry->id,
9936 view, s->repo);
9937 } else
9938 return got_error_msg(GOT_ERR_NOT_IMPL,
9939 "parent/child view pair not supported");
9940 break;
9941 case TOG_VIEW_BLAME:
9942 if (view->type == TOG_VIEW_TREE) {
9943 struct tog_tree_view_state *s = &view->state.tree;
9945 err = blame_tree_entry(new_view, y, x,
9946 s->selected_entry, &s->parents, s->commit_id,
9947 s->repo);
9948 } else
9949 return got_error_msg(GOT_ERR_NOT_IMPL,
9950 "parent/child view pair not supported");
9951 break;
9952 case TOG_VIEW_LOG:
9953 if (view->type == TOG_VIEW_BLAME)
9954 err = log_annotated_line(new_view, y, x,
9955 view->state.blame.repo, view->state.blame.id_to_log);
9956 else if (view->type == TOG_VIEW_TREE)
9957 err = log_selected_tree_entry(new_view, y, x,
9958 &view->state.tree);
9959 else if (view->type == TOG_VIEW_REF)
9960 err = log_ref_entry(new_view, y, x,
9961 view->state.ref.selected_entry,
9962 view->state.ref.repo);
9963 else
9964 return got_error_msg(GOT_ERR_NOT_IMPL,
9965 "parent/child view pair not supported");
9966 break;
9967 case TOG_VIEW_TREE:
9968 if (view->type == TOG_VIEW_LOG)
9969 err = browse_commit_tree(new_view, y, x,
9970 view->state.log.selected_entry,
9971 view->state.log.in_repo_path,
9972 view->state.log.head_ref_name,
9973 view->state.log.repo);
9974 else if (view->type == TOG_VIEW_REF)
9975 err = browse_ref_tree(new_view, y, x,
9976 view->state.ref.selected_entry,
9977 view->state.ref.repo);
9978 else
9979 return got_error_msg(GOT_ERR_NOT_IMPL,
9980 "parent/child view pair not supported");
9981 break;
9982 case TOG_VIEW_REF:
9983 *new_view = view_open(0, 0, y, x, TOG_VIEW_REF);
9984 if (*new_view == NULL)
9985 return got_error_from_errno("view_open");
9986 if (view->type == TOG_VIEW_LOG)
9987 err = open_ref_view(*new_view, view->state.log.repo);
9988 else if (view->type == TOG_VIEW_TREE)
9989 err = open_ref_view(*new_view, view->state.tree.repo);
9990 else
9991 err = got_error_msg(GOT_ERR_NOT_IMPL,
9992 "parent/child view pair not supported");
9993 if (err)
9994 view_close(*new_view);
9995 break;
9996 case TOG_VIEW_HELP:
9997 *new_view = view_open(0, 0, 0, 0, TOG_VIEW_HELP);
9998 if (*new_view == NULL)
9999 return got_error_from_errno("view_open");
10000 err = open_help_view(*new_view, view);
10001 if (err)
10002 view_close(*new_view);
10003 break;
10004 default:
10005 return got_error_msg(GOT_ERR_NOT_IMPL, "invalid view");
10008 return err;
10012 * If view was scrolled down to move the selected line into view when opening a
10013 * horizontal split, scroll back up when closing the split/toggling fullscreen.
10015 static void
10016 offset_selection_up(struct tog_view *view)
10018 switch (view->type) {
10019 case TOG_VIEW_BLAME: {
10020 struct tog_blame_view_state *s = &view->state.blame;
10021 if (s->first_displayed_line == 1) {
10022 s->selected_line = MAX(s->selected_line - view->offset,
10024 break;
10026 if (s->first_displayed_line > view->offset)
10027 s->first_displayed_line -= view->offset;
10028 else
10029 s->first_displayed_line = 1;
10030 s->selected_line += view->offset;
10031 break;
10033 case TOG_VIEW_LOG:
10034 log_scroll_up(&view->state.log, view->offset);
10035 view->state.log.selected += view->offset;
10036 break;
10037 case TOG_VIEW_REF:
10038 ref_scroll_up(&view->state.ref, view->offset);
10039 view->state.ref.selected += view->offset;
10040 break;
10041 case TOG_VIEW_TREE:
10042 tree_scroll_up(&view->state.tree, view->offset);
10043 view->state.tree.selected += view->offset;
10044 break;
10045 default:
10046 break;
10049 view->offset = 0;
10053 * If the selected line is in the section of screen covered by the bottom split,
10054 * scroll down offset lines to move it into view and index its new position.
10056 static const struct got_error *
10057 offset_selection_down(struct tog_view *view)
10059 const struct got_error *err = NULL;
10060 const struct got_error *(*scrolld)(struct tog_view *, int);
10061 int *selected = NULL;
10062 int header, offset;
10064 switch (view->type) {
10065 case TOG_VIEW_BLAME: {
10066 struct tog_blame_view_state *s = &view->state.blame;
10067 header = 3;
10068 scrolld = NULL;
10069 if (s->selected_line > view->nlines - header) {
10070 offset = abs(view->nlines - s->selected_line - header);
10071 s->first_displayed_line += offset;
10072 s->selected_line -= offset;
10073 view->offset = offset;
10075 break;
10077 case TOG_VIEW_LOG: {
10078 struct tog_log_view_state *s = &view->state.log;
10079 scrolld = &log_scroll_down;
10080 header = view_is_parent_view(view) ? 3 : 2;
10081 selected = &s->selected;
10082 break;
10084 case TOG_VIEW_REF: {
10085 struct tog_ref_view_state *s = &view->state.ref;
10086 scrolld = &ref_scroll_down;
10087 header = 3;
10088 selected = &s->selected;
10089 break;
10091 case TOG_VIEW_TREE: {
10092 struct tog_tree_view_state *s = &view->state.tree;
10093 scrolld = &tree_scroll_down;
10094 header = 5;
10095 selected = &s->selected;
10096 break;
10098 default:
10099 selected = NULL;
10100 scrolld = NULL;
10101 header = 0;
10102 break;
10105 if (selected && *selected > view->nlines - header) {
10106 offset = abs(view->nlines - *selected - header);
10107 view->offset = offset;
10108 if (scrolld && offset) {
10109 err = scrolld(view, offset);
10110 *selected -= offset;
10114 return err;
10117 static void
10118 list_commands(FILE *fp)
10120 size_t i;
10122 fprintf(fp, "commands:");
10123 for (i = 0; i < nitems(tog_commands); i++) {
10124 const struct tog_cmd *cmd = &tog_commands[i];
10125 fprintf(fp, " %s", cmd->name);
10127 fputc('\n', fp);
10130 __dead static void
10131 usage(int hflag, int status)
10133 FILE *fp = (status == 0) ? stdout : stderr;
10135 fprintf(fp, "usage: %s [-hV] command [arg ...]\n",
10136 getprogname());
10137 if (hflag) {
10138 fprintf(fp, "lazy usage: %s path\n", getprogname());
10139 list_commands(fp);
10141 exit(status);
10144 static char **
10145 make_argv(int argc, ...)
10147 va_list ap;
10148 char **argv;
10149 int i;
10151 va_start(ap, argc);
10153 argv = calloc(argc, sizeof(char *));
10154 if (argv == NULL)
10155 err(1, "calloc");
10156 for (i = 0; i < argc; i++) {
10157 argv[i] = strdup(va_arg(ap, char *));
10158 if (argv[i] == NULL)
10159 err(1, "strdup");
10162 va_end(ap);
10163 return argv;
10167 * Try to convert 'tog path' into a 'tog log path' command.
10168 * The user could simply have mistyped the command rather than knowingly
10169 * provided a path. So check whether argv[0] can in fact be resolved
10170 * to a path in the HEAD commit and print a special error if not.
10171 * This hack is for mpi@ <3
10173 static const struct got_error *
10174 tog_log_with_path(int argc, char *argv[])
10176 const struct got_error *error = NULL, *close_err;
10177 const struct tog_cmd *cmd = NULL;
10178 struct got_repository *repo = NULL;
10179 struct got_worktree *worktree = NULL;
10180 struct got_object_id *commit_id = NULL, *id = NULL;
10181 struct got_commit_object *commit = NULL;
10182 char *cwd = NULL, *repo_path = NULL, *in_repo_path = NULL;
10183 char *commit_id_str = NULL, **cmd_argv = NULL;
10184 int *pack_fds = NULL;
10186 cwd = getcwd(NULL, 0);
10187 if (cwd == NULL)
10188 return got_error_from_errno("getcwd");
10190 error = got_repo_pack_fds_open(&pack_fds);
10191 if (error != NULL)
10192 goto done;
10194 error = got_worktree_open(&worktree, cwd, NULL);
10195 if (error && error->code != GOT_ERR_NOT_WORKTREE)
10196 goto done;
10198 if (worktree)
10199 repo_path = strdup(got_worktree_get_repo_path(worktree));
10200 else
10201 repo_path = strdup(cwd);
10202 if (repo_path == NULL) {
10203 error = got_error_from_errno("strdup");
10204 goto done;
10207 error = got_repo_open(&repo, repo_path, NULL, pack_fds);
10208 if (error != NULL)
10209 goto done;
10211 error = get_in_repo_path_from_argv0(&in_repo_path, argc, argv,
10212 repo, worktree);
10213 if (error)
10214 goto done;
10216 error = tog_load_refs(repo, 0);
10217 if (error)
10218 goto done;
10219 error = got_repo_match_object_id(&commit_id, NULL, worktree ?
10220 got_worktree_get_head_ref_name(worktree) : GOT_REF_HEAD,
10221 GOT_OBJ_TYPE_COMMIT, &tog_refs, repo);
10222 if (error)
10223 goto done;
10225 if (worktree) {
10226 got_worktree_close(worktree);
10227 worktree = NULL;
10230 error = got_object_open_as_commit(&commit, repo, commit_id);
10231 if (error)
10232 goto done;
10234 error = got_object_id_by_path(&id, repo, commit, in_repo_path);
10235 if (error) {
10236 if (error->code != GOT_ERR_NO_TREE_ENTRY)
10237 goto done;
10238 fprintf(stderr, "%s: '%s' is no known command or path\n",
10239 getprogname(), argv[0]);
10240 usage(1, 1);
10241 /* not reached */
10244 error = got_object_id_str(&commit_id_str, commit_id);
10245 if (error)
10246 goto done;
10248 cmd = &tog_commands[0]; /* log */
10249 argc = 4;
10250 cmd_argv = make_argv(argc, cmd->name, "-c", commit_id_str, argv[0]);
10251 error = cmd->cmd_main(argc, cmd_argv);
10252 done:
10253 if (repo) {
10254 close_err = got_repo_close(repo);
10255 if (error == NULL)
10256 error = close_err;
10258 if (commit)
10259 got_object_commit_close(commit);
10260 if (worktree)
10261 got_worktree_close(worktree);
10262 if (pack_fds) {
10263 const struct got_error *pack_err =
10264 got_repo_pack_fds_close(pack_fds);
10265 if (error == NULL)
10266 error = pack_err;
10268 free(id);
10269 free(commit_id_str);
10270 free(commit_id);
10271 free(cwd);
10272 free(repo_path);
10273 free(in_repo_path);
10274 if (cmd_argv) {
10275 int i;
10276 for (i = 0; i < argc; i++)
10277 free(cmd_argv[i]);
10278 free(cmd_argv);
10280 tog_free_refs();
10281 return error;
10285 main(int argc, char *argv[])
10287 const struct got_error *io_err, *error = NULL;
10288 const struct tog_cmd *cmd = NULL;
10289 int ch, hflag = 0, Vflag = 0;
10290 char **cmd_argv = NULL;
10291 static const struct option longopts[] = {
10292 { "version", no_argument, NULL, 'V' },
10293 { NULL, 0, NULL, 0}
10295 char *diff_algo_str = NULL;
10296 const char *test_script_path;
10298 setlocale(LC_CTYPE, "");
10301 * Override default signal handlers before starting ncurses.
10302 * This should prevent ncurses from installing its own
10303 * broken cleanup() signal handler.
10305 signal(SIGWINCH, tog_sigwinch);
10306 signal(SIGPIPE, tog_sigpipe);
10307 signal(SIGCONT, tog_sigcont);
10308 signal(SIGINT, tog_sigint);
10309 signal(SIGTERM, tog_sigterm);
10312 * Test mode init must happen before pledge() because "tty" will
10313 * not allow TTY-related ioctls to occur via regular files.
10315 test_script_path = getenv("TOG_TEST_SCRIPT");
10316 if (test_script_path != NULL) {
10317 error = init_mock_term(test_script_path);
10318 if (error) {
10319 fprintf(stderr, "%s: %s\n", getprogname(), error->msg);
10320 return 1;
10322 } else if (!isatty(STDIN_FILENO))
10323 errx(1, "standard input is not a tty");
10325 #if !defined(PROFILE)
10326 if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd unveil",
10327 NULL) == -1)
10328 err(1, "pledge");
10329 #endif
10331 while ((ch = getopt_long(argc, argv, "+hV", longopts, NULL)) != -1) {
10332 switch (ch) {
10333 case 'h':
10334 hflag = 1;
10335 break;
10336 case 'V':
10337 Vflag = 1;
10338 break;
10339 default:
10340 usage(hflag, 1);
10341 /* NOTREACHED */
10345 argc -= optind;
10346 argv += optind;
10347 optind = 1;
10348 optreset = 1;
10350 if (Vflag) {
10351 got_version_print_str();
10352 return 0;
10355 if (argc == 0) {
10356 if (hflag)
10357 usage(hflag, 0);
10358 /* Build an argument vector which runs a default command. */
10359 cmd = &tog_commands[0];
10360 argc = 1;
10361 cmd_argv = make_argv(argc, cmd->name);
10362 } else {
10363 size_t i;
10365 /* Did the user specify a command? */
10366 for (i = 0; i < nitems(tog_commands); i++) {
10367 if (strncmp(tog_commands[i].name, argv[0],
10368 strlen(argv[0])) == 0) {
10369 cmd = &tog_commands[i];
10370 break;
10375 diff_algo_str = getenv("TOG_DIFF_ALGORITHM");
10376 if (diff_algo_str) {
10377 if (strcasecmp(diff_algo_str, "patience") == 0)
10378 tog_diff_algo = GOT_DIFF_ALGORITHM_PATIENCE;
10379 if (strcasecmp(diff_algo_str, "myers") == 0)
10380 tog_diff_algo = GOT_DIFF_ALGORITHM_MYERS;
10383 tog_base_commit.idx = -1;
10384 tog_base_commit.marker = GOT_WORKTREE_STATE_UNKNOWN;
10386 if (cmd == NULL) {
10387 if (argc != 1)
10388 usage(0, 1);
10389 /* No command specified; try log with a path */
10390 error = tog_log_with_path(argc, argv);
10391 } else {
10392 if (hflag)
10393 cmd->cmd_usage();
10394 else
10395 error = cmd->cmd_main(argc, cmd_argv ? cmd_argv : argv);
10398 if (using_mock_io) {
10399 io_err = tog_io_close();
10400 if (error == NULL)
10401 error = io_err;
10403 endwin();
10404 if (cmd_argv) {
10405 int i;
10406 for (i = 0; i < argc; i++)
10407 free(cmd_argv[i]);
10408 free(cmd_argv);
10411 if (error && error->code != GOT_ERR_CANCELLED &&
10412 error->code != GOT_ERR_EOF &&
10413 error->code != GOT_ERR_PRIVSEP_EXIT &&
10414 error->code != GOT_ERR_PRIVSEP_PIPE &&
10415 !(error->code == GOT_ERR_ERRNO && errno == EINTR)) {
10416 fprintf(stderr, "%s: %s\n", getprogname(), error->msg);
10417 return 1;
10419 return 0;