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

From:
Tracey Emery <tracey@traceyemery.net>
Subject:
Re: implement tog ref command
To:
gameoftrees@openbsd.org
Date:
Mon, 23 Nov 2020 09:57:15 -0700

Download raw body.

Thread
On Mon, Nov 23, 2020 at 01:44:07PM +0100, Stefan Sperling wrote:
> 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?

That's awesome! ok to all three tracey

Switching branches is super smooth!

> 
> 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;

-- 

Tracey Emery