"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Stefan Sperling <stsp@stsp.name>
Subject:
implement tog ref command
To:
gameoftrees@openbsd.org
Date:
Mon, 23 Nov 2020 13:44:07 +0100

Download raw body.

Thread
This adds a reference view to tog.

A ref view can be opened with 'tog ref', and from the log view
with 'r' which makes it easier to navigate between branches.

This patch requires both the tog diff improvements patch and
the tog ref list patch I have already sent.

ok?

diff refs/heads/togreflist refs/heads/togref
blob - bba1d695475b78e770ce4a96eb79d9359c360c0e
blob + a8636d6509dcc3d01f0ad75d42151d795bca5ed3
--- tog/tog.1
+++ tog/tog.1
@@ -50,6 +50,8 @@ Displays changes made in a particular commit.
 Displays the line-by-line history of a file.
 .It Tree view
 Displays the tree corresponding to a particular commit.
+.It Ref view
+Displays references in the repository.
 .El
 .Pp
 .Nm
@@ -144,6 +146,13 @@ Reload the log view and toggle display of merged commi
 The
 .Fl b
 option determines whether merged commits are displayed initially.
+.It Cm r
+Open a
+.Cm ref
+view listing all references in the repository.
+This can then be used to open a new
+.Cm log
+view for arbitrary branches and tags.
 .El
 .Pp
 The options for
@@ -373,7 +382,53 @@ If this directory is a
 .Xr got 1
 work tree, use the repository path associated with this work tree.
 .El
+.It Cm ref Oo Fl r Ar repository-path Oc
+Display references in the repository.
+.Pp
+The key bindings for
+.Cm tog ref
+are as follows:
+.Bl -tag -width Ds
+.It Cm Down-arrow, j
+Move the selection cursor down.
+.It Cm Up-arrow, k
+Move the selection cursor up.
+.It Cm Page-down, Ctrl+f
+Move the selection cursor down one page.
+.It Cm Page-up, Ctrl+b
+Move the selection cursor up one page.
+.It Cm Enter
+Open a log view which begins traversing history at the commit resolved via the
+currently selected reference.
+.It Cm i
+Show object IDs for all non-symbolic references displayed in the
+.Cm ref
+view.
+.It Cm /
+Prompt for a search pattern and start searching for matching references.
+The search pattern is an extended regular expression which is matched
+against absolute reference names.
+Regular expression syntax is documented in
+.Xr re_format 7 .
+.It Cm n
+Find the next reference which matches the current search pattern.
+.It Cm N
+Find the previous reference which matches the current search pattern.
 .El
+.Pp
+The options for
+.Cm tog ref
+are as follows:
+.Bl -tag -width Ds
+.It Fl r Ar repository-path
+Use the repository at the specified path.
+If not specified, assume the repository is located at or above the current
+working directory.
+If this directory is a
+.Xr got 1
+work tree, use the repository path associated with this work tree.
+.El
+.El
 .Sh ENVIRONMENT
 .Bl -tag -width TOG_COLORS
 .It Ev TOG_COLORS
@@ -447,6 +502,27 @@ The color used to mark up date information.
 If not set, the default value
 .Dq yellow
 is used.
+.It Ev TOG_COLOR_REFS_HEADS
+The color used to mark up references in the
+.Dq refs/heads/
+namespace.
+If not set, the default value
+.Dq green
+is used.
+.It Ev TOG_COLOR_REFS_TAGS
+The color used to mark up references in the
+.Dq refs/tags/
+namespace.
+If not set, the default value
+.Dq magenta
+is used.
+.It Ev TOG_COLOR_REFS_REMOTES
+The color used to mark up references in the
+.Dq refs/remotes/
+namespace.
+If not set, the default value
+.Dq yellow
+is used.
 .El
 .Sh EXIT STATUS
 .Ex -std tog
blob - 095716c1bd7e25ddf8b9ce47ab4a1ce69a63f0bb
blob + ff55bee61487e54861b3b26e6339c18a969e6908
--- tog/tog.c
+++ tog/tog.c
@@ -81,24 +81,28 @@ __dead static void	usage_log(void);
 __dead static void	usage_diff(void);
 __dead static void	usage_blame(void);
 __dead static void	usage_tree(void);
+__dead static void	usage_ref(void);
 
 static const struct got_error*	cmd_log(int, char *[]);
 static const struct got_error*	cmd_diff(int, char *[]);
 static const struct got_error*	cmd_blame(int, char *[]);
 static const struct got_error*	cmd_tree(int, char *[]);
+static const struct got_error*	cmd_ref(int, char *[]);
 
 static struct tog_cmd tog_commands[] = {
 	{ "log",	cmd_log,	usage_log },
 	{ "diff",	cmd_diff,	usage_diff },
 	{ "blame",	cmd_blame,	usage_blame },
 	{ "tree",	cmd_tree,	usage_tree },
+	{ "ref",	cmd_ref,	usage_ref },
 };
 
 enum tog_view_type {
 	TOG_VIEW_DIFF,
 	TOG_VIEW_LOG,
 	TOG_VIEW_BLAME,
-	TOG_VIEW_TREE
+	TOG_VIEW_TREE,
+	TOG_VIEW_REF,
 };
 
 #define TOG_EOF_STRING	"(END)"
@@ -207,6 +211,12 @@ default_color_value(const char *envvar)
 		return COLOR_CYAN;
 	if (strcmp(envvar, "TOG_COLOR_DATE") == 0)
 		return COLOR_YELLOW;
+	if (strcmp(envvar, "TOG_COLOR_REFS_HEADS") == 0)
+		return COLOR_GREEN;
+	if (strcmp(envvar, "TOG_COLOR_REFS_TAGS") == 0)
+		return COLOR_MAGENTA;
+	if (strcmp(envvar, "TOG_COLOR_REFS_REMOTES") == 0)
+		return COLOR_YELLOW;
 
 	return -1;
 }
@@ -315,6 +325,9 @@ struct tog_log_view_state {
 #define TOG_COLOR_COMMIT		9
 #define TOG_COLOR_AUTHOR		10
 #define TOG_COLOR_DATE		11
+#define TOG_COLOR_REFS_HEADS		12
+#define TOG_COLOR_REFS_TAGS		13
+#define TOG_COLOR_REFS_REMOTES		14
 
 struct tog_blame_cb_args {
 	struct tog_blame_line *lines; /* one per line */
@@ -388,6 +401,26 @@ struct tog_tree_view_state {
 	struct tog_colors colors;
 };
 
+struct tog_reflist_entry {
+	TAILQ_ENTRY(tog_reflist_entry) entry;
+	struct got_reference *ref;
+	int idx;
+};
+
+TAILQ_HEAD(tog_reflist_head, tog_reflist_entry);
+
+struct tog_ref_view_state {
+	struct got_reflist_head simplerefs; /* SIMPLEQ */
+	struct tog_reflist_head refs;	/* TAILQ */
+	struct tog_reflist_entry *first_displayed_entry;
+	struct tog_reflist_entry *last_displayed_entry;
+	struct tog_reflist_entry *selected_entry;
+	int nrefs, ndisplayed, selected, show_ids;
+	struct got_repository *repo;
+	struct tog_reflist_entry *matched_entry;
+	struct tog_colors colors;
+};
+
 /*
  * We implement two types of views: parent views and child views.
  *
@@ -425,6 +458,7 @@ struct tog_view {
 		struct tog_log_view_state log;
 		struct tog_blame_view_state blame;
 		struct tog_tree_view_state tree;
+		struct tog_ref_view_state ref;
 	} state;
 
 	const struct got_error *(*show)(struct tog_view *);
@@ -484,6 +518,15 @@ static const struct got_error *close_tree_view(struct 
 static const struct got_error *search_start_tree_view(struct tog_view *);
 static const struct got_error *search_next_tree_view(struct tog_view *);
 
+static const struct got_error *open_ref_view(struct tog_view *,
+    struct got_repository *);
+static const struct got_error *show_ref_view(struct tog_view *);
+static const struct got_error *input_ref_view(struct tog_view **,
+    struct tog_view **, struct tog_view **, struct tog_view *, int);
+static const struct got_error *close_ref_view(struct tog_view *);
+static const struct got_error *search_start_ref_view(struct tog_view *);
+static const struct got_error *search_next_ref_view(struct tog_view *);
+
 static volatile sig_atomic_t tog_sigwinch_received;
 static volatile sig_atomic_t tog_sigpipe_received;
 static volatile sig_atomic_t tog_sigcont_received;
@@ -2320,6 +2363,7 @@ input_log_view(struct tog_view **new_view, struct tog_
 	struct tog_log_view_state *s = &view->state.log;
 	char *parent_path, *in_repo_path = NULL;
 	struct tog_view *diff_view = NULL, *tree_view = NULL, *lv = NULL;
+	struct tog_view *ref_view = NULL;
 	int begin_x = 0;
 	struct got_object_id *start_id;
 
@@ -2527,6 +2571,32 @@ input_log_view(struct tog_view **new_view, struct tog_
 		*dead_view = view;
 		*new_view = lv;
 		break;
+	case 'r':
+		if (view_is_parent_view(view))
+			begin_x = view_split_begin_x(view->begin_x);
+		ref_view = view_open(view->nlines, view->ncols,
+		    view->begin_y, begin_x, TOG_VIEW_REF);
+		if (ref_view == NULL)
+			return got_error_from_errno("view_open");
+		err = open_ref_view(ref_view, s->repo);
+		if (err) {
+			view_close(ref_view);
+			return err;
+		}
+		if (view_is_parent_view(view)) {
+			err = view_close_child(view);
+			if (err)
+				return err;
+			err = view_set_child(view, ref_view);
+			if (err) {
+				view_close(ref_view);
+				break;
+			}
+			*focus_view = ref_view;
+			view->child_focussed = 1;
+		} else
+			*new_view = ref_view;
+		break;
 	default:
 		break;
 	}
@@ -5574,7 +5644,608 @@ done:
 	return error;
 }
 
+static const struct got_error *
+ref_view_load_refs(struct tog_ref_view_state *s)
+{
+	const struct got_error *err;
+	struct got_reflist_entry *sre;
+	struct tog_reflist_entry *re;
+
+	err = got_ref_list(&s->simplerefs, s->repo, NULL,
+	    got_ref_cmp_by_name, NULL);
+	if (err)
+		return err;
+
+	s->nrefs = 0;
+	SIMPLEQ_FOREACH(sre, &s->simplerefs, entry) {
+		if (strncmp(got_ref_get_name(sre->ref), "refs/got/", 9) == 0)
+			continue;
+
+		re = malloc(sizeof(*re));
+		if (re == NULL)
+			return got_error_from_errno("malloc");
+
+		re->ref = sre->ref;
+		re->idx = s->nrefs++;
+		TAILQ_INSERT_TAIL(&s->refs, re, entry);
+	}
+
+	return NULL;
+}
+
+void
+ref_view_free_refs(struct tog_ref_view_state *s)
+{
+	struct tog_reflist_entry *re;
+
+	while (!TAILQ_EMPTY(&s->refs)) {
+		re = TAILQ_FIRST(&s->refs);
+		TAILQ_REMOVE(&s->refs, re, entry);
+		free(re);
+	}
+	got_ref_list_free(&s->simplerefs);
+}
+
+static const struct got_error *
+open_ref_view(struct tog_view *view, struct got_repository *repo)
+{
+	const struct got_error *err = NULL;
+	struct tog_ref_view_state *s = &view->state.ref;
+
+	s->first_displayed_entry = 0;
+	s->selected_entry = 0;
+	s->repo = repo;
+
+	SIMPLEQ_INIT(&s->simplerefs);
+	TAILQ_INIT(&s->refs);
+	SIMPLEQ_INIT(&s->colors);
+
+	err = ref_view_load_refs(s);
+	if (err)
+		return err;
+
+	if (has_colors() && getenv("TOG_COLORS") != NULL) {
+		err = add_color(&s->colors, "^refs/heads/",
+		    TOG_COLOR_REFS_HEADS,
+		    get_color_value("TOG_COLOR_REFS_HEADS"));
+		if (err)
+			goto done;
+
+		err = add_color(&s->colors, "^refs/tags/",
+		    TOG_COLOR_REFS_TAGS,
+		    get_color_value("TOG_COLOR_REFS_TAGS"));
+		if (err)
+			goto done;
+
+		err = add_color(&s->colors, "^refs/remotes/",
+		    TOG_COLOR_REFS_REMOTES,
+		    get_color_value("TOG_COLOR_REFS_REMOTES"));
+		if (err)
+			goto done;
+	}
+
+	view->show = show_ref_view;
+	view->input = input_ref_view;
+	view->close = close_ref_view;
+	view->search_start = search_start_ref_view;
+	view->search_next = search_next_ref_view;
+done:
+	if (err)
+		free_colors(&s->colors);
+	return err;
+}
+
+static const struct got_error *
+close_ref_view(struct tog_view *view)
+{
+	struct tog_ref_view_state *s = &view->state.ref;
+
+	ref_view_free_refs(s);
+	free_colors(&s->colors);
+
+	return NULL;
+}
+
+static const struct got_error *
+log_ref_entry(struct tog_view **new_view, int begin_x,
+    struct tog_reflist_entry *re, struct got_repository *repo)
+{
+	struct tog_view *log_view;
+	const struct got_error *err = NULL;
+	struct got_object_id *obj_id = NULL;
+	struct got_object_id *commit_id = NULL;
+	struct got_tag_object *tag = NULL;
+	int obj_type;
+
+	*new_view = NULL;
+
+	err = got_ref_resolve(&obj_id, repo, re->ref);
+	if (err)
+		return err;
+
+	err = got_object_get_type(&obj_type, repo, obj_id);
+	if (err)
+		goto done;
+
+	switch (obj_type) {
+	case GOT_OBJ_TYPE_COMMIT:
+		commit_id = obj_id;
+		break;
+	case GOT_OBJ_TYPE_TAG:
+		err = got_object_open_as_tag(&tag, repo, obj_id);
+		if (err)
+			goto done;
+		commit_id = got_object_tag_get_object_id(tag);
+		err = got_object_get_type(&obj_type, repo, commit_id);
+		if (err || obj_type != GOT_OBJ_TYPE_COMMIT)
+			goto done;
+		break;
+	default:
+		free(obj_id);
+		return NULL;
+	}
+
+	log_view = view_open(0, 0, 0, begin_x, TOG_VIEW_LOG);
+	if (log_view == NULL) {
+		err = got_error_from_errno("view_open");
+		goto done;
+	}
+
+	err = open_log_view(log_view, commit_id, repo, NULL, "", 0);
+done:
+	if (err)
+		view_close(log_view);
+	else
+		*new_view = log_view;
+	if (tag)
+		got_object_tag_close(tag);
+	free(obj_id);
+	return err;
+}
+
 static void
+ref_scroll_up(struct tog_view *view,
+    struct tog_reflist_entry **first_displayed_entry, int maxscroll,
+    struct tog_reflist_head *refs)
+{
+	int i;
+
+	if (*first_displayed_entry == TAILQ_FIRST(refs))
+		return;
+
+	i = 0;
+	while (*first_displayed_entry && i < maxscroll) {
+		*first_displayed_entry = TAILQ_PREV(*first_displayed_entry,
+		    tog_reflist_head, entry);
+		i++;
+	}
+}
+
+static int
+ref_scroll_down(struct tog_reflist_entry **first_displayed_entry, int maxscroll,
+	struct tog_reflist_entry *last_displayed_entry,
+	struct tog_reflist_head *refs)
+{
+	struct tog_reflist_entry *next, *last;
+	int n = 0;
+
+	if (*first_displayed_entry)
+		next = TAILQ_NEXT(*first_displayed_entry, entry);
+	else
+		next = TAILQ_FIRST(refs);
+
+	last = last_displayed_entry;
+	while (next && last && n++ < maxscroll) {
+		last = TAILQ_NEXT(last, entry);
+		if (last) {
+			*first_displayed_entry = next;
+			next = TAILQ_NEXT(next, entry);
+		}
+	}
+	return n;
+}
+
+static const struct got_error *
+search_start_ref_view(struct tog_view *view)
+{
+	struct tog_ref_view_state *s = &view->state.ref;
+
+	s->matched_entry = NULL;
+	return NULL;
+}
+
+static int
+match_reflist_entry(struct tog_reflist_entry *re, regex_t *regex)
+{
+	regmatch_t regmatch;
+
+	return regexec(regex, got_ref_get_name(re->ref), 1, &regmatch,
+	    0) == 0;
+}
+
+static const struct got_error *
+search_next_ref_view(struct tog_view *view)
+{
+	struct tog_ref_view_state *s = &view->state.ref;
+	struct tog_reflist_entry *re = NULL;
+
+	if (!view->searching) {
+		view->search_next_done = TOG_SEARCH_HAVE_MORE;
+		return NULL;
+	}
+
+	if (s->matched_entry) {
+		if (view->searching == TOG_SEARCH_FORWARD) {
+			if (s->selected_entry)
+				re = TAILQ_NEXT(s->selected_entry, entry);
+			else
+				re = TAILQ_PREV(s->selected_entry,
+				    tog_reflist_head, entry);
+		} else {
+			if (s->selected_entry == NULL)
+				re = TAILQ_LAST(&s->refs, tog_reflist_head);
+			else
+				re = TAILQ_PREV(s->selected_entry,
+				    tog_reflist_head, entry);
+		}
+	} else {
+		if (view->searching == TOG_SEARCH_FORWARD)
+			re = TAILQ_FIRST(&s->refs);
+		else
+			re = TAILQ_LAST(&s->refs, tog_reflist_head);
+	}
+
+	while (1) {
+		if (re == NULL) {
+			if (s->matched_entry == NULL) {
+				view->search_next_done = TOG_SEARCH_HAVE_MORE;
+				return NULL;
+			}
+			if (view->searching == TOG_SEARCH_FORWARD)
+				re = TAILQ_FIRST(&s->refs);
+			else
+				re = TAILQ_LAST(&s->refs, tog_reflist_head);
+		}
+
+		if (match_reflist_entry(re, &view->regex)) {
+			view->search_next_done = TOG_SEARCH_HAVE_MORE;
+			s->matched_entry = re;
+			break;
+		}
+
+		if (view->searching == TOG_SEARCH_FORWARD)
+			re = TAILQ_NEXT(re, entry);
+		else
+			re = TAILQ_PREV(re, tog_reflist_head, entry);
+	}
+
+	if (s->matched_entry) {
+		s->first_displayed_entry = s->matched_entry;
+		s->selected = 0;
+	}
+
+	return NULL;
+}
+
+static const struct got_error *
+show_ref_view(struct tog_view *view)
+{
+	const struct got_error *err = NULL;
+	struct tog_ref_view_state *s = &view->state.ref;
+	struct tog_reflist_entry *re;
+	char *line = NULL;
+	wchar_t *wline;
+	struct tog_color *tc;
+	int width, n;
+	int limit = view->nlines;
+
+	werase(view->window);
+
+	s->ndisplayed = 0;
+
+	if (limit == 0)
+		return NULL;
+
+	if (s->first_displayed_entry)
+		re = s->first_displayed_entry;
+	else
+		re = TAILQ_FIRST(&s->refs);
+
+	if (asprintf(&line, "references [%d/%d]", re->idx + s->selected + 1,
+	    s->nrefs) == -1)
+		return got_error_from_errno("asprintf");
+
+	err = format_line(&wline, &width, line, view->ncols, 0);
+	if (err) {
+		free(line);
+		return err;
+	}
+	if (view_needs_focus_indication(view))
+		wstandout(view->window);
+	waddwstr(view->window, wline);
+	if (view_needs_focus_indication(view))
+		wstandend(view->window);
+	free(wline);
+	wline = NULL;
+	free(line);
+	line = NULL;
+	if (width < view->ncols - 1)
+		waddch(view->window, '\n');
+	if (--limit <= 0)
+		return NULL;
+
+	n = 0;
+	while (re && limit > 0) {
+		char *line = NULL;
+
+		if (got_ref_is_symbolic(re->ref)) {
+			if (asprintf(&line, "%s -> %s",
+			    got_ref_get_name(re->ref),
+			    got_ref_get_symref_target(re->ref)) == -1)
+				return got_error_from_errno("asprintf");
+		} else if (s->show_ids) {
+			struct got_object_id *id;
+			char *id_str;
+			err = got_ref_resolve(&id, s->repo, re->ref);
+			if (err)
+				return err;
+			err = got_object_id_str(&id_str, id);
+			if (err) {
+				free(id);
+				return err;
+			}
+			if (asprintf(&line, "%s: %s",
+			    got_ref_get_name(re->ref), id_str) == -1) {
+				err = got_error_from_errno("asprintf");
+				free(id);
+				free(id_str);
+				return err;
+			}
+			free(id);
+			free(id_str);
+		} else {
+			line = strdup(got_ref_get_name(re->ref));
+			if (line == NULL)
+				return got_error_from_errno("strdup");
+		}
+
+		err = format_line(&wline, &width, line, view->ncols, 0);
+		if (err) {
+			free(line);
+			return err;
+		}
+		if (n == s->selected) {
+			if (view->focussed)
+				wstandout(view->window);
+			s->selected_entry = re;
+		}
+		tc = match_color(&s->colors, got_ref_get_name(re->ref));
+		if (tc)
+			wattr_on(view->window,
+			    COLOR_PAIR(tc->colorpair), NULL);
+		waddwstr(view->window, wline);
+		if (tc)
+			wattr_off(view->window,
+			    COLOR_PAIR(tc->colorpair), NULL);
+		if (width < view->ncols - 1)
+			waddch(view->window, '\n');
+		if (n == s->selected && view->focussed)
+			wstandend(view->window);
+		free(line);
+		free(wline);
+		wline = NULL;
+		n++;
+		s->ndisplayed++;
+		s->last_displayed_entry = re;
+
+		limit--;
+		re = TAILQ_NEXT(re, entry);
+	}
+
+	view_vborder(view);
+	return err;
+}
+
+static const struct got_error *
+input_ref_view(struct tog_view **new_view, struct tog_view **dead_view,
+    struct tog_view **focus_view, struct tog_view *view, int ch)
+{
+	const struct got_error *err = NULL;
+	struct tog_ref_view_state *s = &view->state.ref;
+	struct tog_view *log_view;
+	int begin_x = 0, nscrolled;
+
+	switch (ch) {
+	case 'i':
+		s->show_ids = !s->show_ids;
+		break;
+	case KEY_ENTER:
+	case '\r':
+		if (!s->selected_entry)
+			break;
+		if (view_is_parent_view(view))
+			begin_x = view_split_begin_x(view->begin_x);
+		err = log_ref_entry(&log_view, begin_x, s->selected_entry,
+		    s->repo);
+		if (view_is_parent_view(view)) {
+			err = view_close_child(view);
+			if (err)
+				return err;
+			err = view_set_child(view, log_view);
+			if (err) {
+				view_close(log_view);
+				break;
+			}
+			*focus_view = log_view;
+			view->child_focussed = 1;
+		} else
+			*new_view = log_view;
+		break;
+	case 'k':
+	case KEY_UP:
+		if (s->selected > 0) {
+			s->selected--;
+			if (s->selected == 0)
+				break;
+		}
+		if (s->selected > 0)
+			break;
+		ref_scroll_up(view, &s->first_displayed_entry, 1, &s->refs);
+		break;
+	case KEY_PPAGE:
+	case CTRL('b'):
+		ref_scroll_up(view, &s->first_displayed_entry,
+		    MAX(0, view->nlines - 4 - s->selected), &s->refs);
+		s->selected = 0;
+		break;
+	case 'j':
+	case KEY_DOWN:
+		if (s->selected < s->ndisplayed - 1) {
+			s->selected++;
+			break;
+		}
+		if (TAILQ_NEXT(s->last_displayed_entry, entry) == NULL)
+			/* can't scroll any further */
+			break;
+		ref_scroll_down(&s->first_displayed_entry, 1,
+		    s->last_displayed_entry, &s->refs);
+		break;
+	case KEY_NPAGE:
+	case CTRL('f'):
+		if (TAILQ_NEXT(s->last_displayed_entry, entry) == NULL) {
+			/* can't scroll any further; move cursor down */
+			if (s->selected < s->ndisplayed - 1)
+				s->selected = s->ndisplayed - 1;
+			break;
+		}
+		nscrolled = ref_scroll_down(&s->first_displayed_entry,
+		    view->nlines, s->last_displayed_entry, &s->refs);
+		if (nscrolled < view->nlines) {
+			int ndisplayed = 0;
+			struct tog_reflist_entry *re;
+			re = s->first_displayed_entry;
+			do {
+				ndisplayed++;
+				re = TAILQ_NEXT(re, entry);
+			} while (re);
+			s->selected = ndisplayed - 1;
+		}
+		break;
+	case CTRL('l'):
+		ref_view_free_refs(s);
+		err = ref_view_load_refs(s);
+		break;
+	case KEY_RESIZE:
+		if (s->selected > view->nlines)
+			s->selected = s->ndisplayed - 1;
+		break;
+	default:
+		break;
+	}
+
+	return err;
+}
+
+__dead static void
+usage_ref(void)
+{
+	endwin();
+	fprintf(stderr, "usage: %s ref [-r repository-path]\n",
+	    getprogname());
+	exit(1);
+}
+
+static const struct got_error *
+cmd_ref(int argc, char *argv[])
+{
+	const struct got_error *error;
+	struct got_repository *repo = NULL;
+	struct got_worktree *worktree = NULL;
+	char *cwd = NULL, *repo_path = NULL;
+	int ch;
+	struct tog_view *view;
+
+#ifndef PROFILE
+	if (pledge("stdio rpath wpath cpath flock proc tty exec sendfd unveil",
+	    NULL) == -1)
+		err(1, "pledge");
+#endif
+
+	while ((ch = getopt(argc, argv, "r:")) != -1) {
+		switch (ch) {
+		case 'r':
+			repo_path = realpath(optarg, NULL);
+			if (repo_path == NULL)
+				return got_error_from_errno2("realpath",
+				    optarg);
+			break;
+		default:
+			usage_tree();
+			/* NOTREACHED */
+		}
+	}
+
+	argc -= optind;
+	argv += optind;
+
+	if (argc > 1)
+		usage_tree();
+
+	cwd = getcwd(NULL, 0);
+	if (cwd == NULL)
+		return got_error_from_errno("getcwd");
+
+	error = got_worktree_open(&worktree, cwd);
+	if (error && error->code != GOT_ERR_NOT_WORKTREE)
+		goto done;
+
+	if (repo_path == NULL) {
+		if (worktree)
+			repo_path =
+			    strdup(got_worktree_get_repo_path(worktree));
+		else
+			repo_path = strdup(cwd);
+	}
+	if (repo_path == NULL) {
+		error = got_error_from_errno("strdup");
+		goto done;
+	}
+
+	error = got_repo_open(&repo, repo_path, NULL);
+	if (error != NULL)
+		goto done;
+
+	init_curses();
+
+	error = apply_unveil(got_repo_get_path(repo), NULL);
+	if (error)
+		goto done;
+
+	view = view_open(0, 0, 0, 0, TOG_VIEW_REF);
+	if (view == NULL) {
+		error = got_error_from_errno("view_open");
+		goto done;
+	}
+
+	error = open_ref_view(view, repo);
+	if (error)
+		goto done;
+
+	if (worktree) {
+		/* Release work tree lock. */
+		got_worktree_close(worktree);
+		worktree = NULL;
+	}
+	error = view_loop(view);
+done:
+	free(repo_path);
+	free(cwd);
+	if (repo)
+		got_repo_close(repo);
+	return error;
+}
+
+static void
 list_commands(FILE *fp)
 {
 	int i;