Download raw body.
tog: limit feature
On 22-08-19 07:51PM, Mikhail wrote:
> On Sat, Aug 20, 2022 at 02:27:11AM +1000, Mark Jamsek wrote:
> > On 22-08-19 07:09PM, Mikhail wrote:
> > > On Sat, Aug 20, 2022 at 01:55:22AM +1000, Mark Jamsek wrote:
> > > > There're a few minor style issues that crept in with the indentation
> > > > changes that I've annotated inline, but otherwise this is looking good.
> > > > Thanks, Mikhail! This will be a useful addition in my togging.
> > >
> > > Fixed.
> >
> > You missed the 80 column wrap on tog.c:2228
> >
> > 2228 TAILQ_INSERT_TAIL(&a->limit_commits->head, matched,
> > 2229 entry);
> >
> > should be:
> >
> > 2228 TAILQ_INSERT_TAIL(&a->limit_commits->head,
> > 2229 matched, entry);
> >
> > And op caught the missed malloc() checks in queue_commits() and
> > limit_log_view() annotated below.
>
> Thanks, done.
This looks good to me! ok
> :set colorcolumn=80 will be in my .vimrc now
I have this and it's great. If you work in a few different codebases
with different styles, I recommend editorconfig^. It's simple and
minimal, works with version control, and helps maintain consistent code
styles between projects without much thought. It even helps within the
same project when using different languages.
^https://editorconfig.org/
> diff refs/heads/main refs/heads/limit4
> commit - 01498c42e0fc9fb6355312a236656cf2f36cebc0
> commit + bdd73014325993c623059e4bae93180f3028dd57
> blob - cbd5233dd61d780f245c5bcb24d95b030a5a7676
> blob + 5ca270df408b1c24da4ce5567e686412705ded4f
> --- tog/tog.1
> +++ tog/tog.1
> @@ -220,6 +220,12 @@ This can then be used to open a new
> view for arbitrary branches and tags.
> .It Cm @
> Toggle between showing the author and the committer name.
> +.It Cm &
> +Prompt for a pattern to display a log view limited to the subset of
> +commits matching the provided pattern, which is an extended regular
> +expression matched against a commit's author name, committer name,
> +log message, and commit ID SHA1 hash.
> +Use empty string as a pattern to display all commits.
> .El
> .Pp
> The options for
> blob - 6ec8b5f0c155fafe55ee2aab0653d259876e6254
> blob + f3bb774ee3b1ebc439996598c26453855abb0147
> --- tog/tog.c
> +++ tog/tog.c
> @@ -356,14 +356,22 @@ struct tog_log_thread_args {
> int *searching;
> int *search_next_done;
> regex_t *regex;
> + int *limiting;
> + int limit_match;
> + regex_t *limit_regex;
> + struct commit_queue *limit_commits;
> + struct commit_queue_entry **saved_first_displayed_entry;
> + struct commit_queue_entry **saved_selected_entry;
> };
>
> struct tog_log_view_state {
> - struct commit_queue commits;
> + struct commit_queue *commits;
> struct commit_queue_entry *first_displayed_entry;
> struct commit_queue_entry *last_displayed_entry;
> struct commit_queue_entry *selected_entry;
> + struct commit_queue real_commits;
> int selected;
> + int saved_selected;
> char *in_repo_path;
> char *head_ref_name;
> int log_branches;
> @@ -376,6 +384,13 @@ struct tog_log_view_state {
> struct commit_queue_entry *search_entry;
> struct tog_colors colors;
> int use_committer;
> + int limit_view;
> + regex_t limit_regex;
> + struct commit_queue limit_commits;
> + struct commit_queue *saved_commits;
> + struct commit_queue_entry *saved_first_displayed_entry;
> + struct commit_queue_entry *saved_last_displayed_entry;
> + struct commit_queue_entry *saved_selected_entry;
> };
>
> #define TOG_COLOR_DIFF_MINUS 1
> @@ -935,8 +950,8 @@ resize_log_view(struct tog_view *view, int increase)
> * Request commits to account for the increased
> * height so we have enough to populate the view.
> */
> - if (s->commits.ncommits < n) {
> - view->nscrolled = n - s->commits.ncommits + increase + 1;
> + if (s->commits->ncommits < n) {
> + view->nscrolled = n - s->commits->ncommits + increase + 1;
> err = request_log_commits(view);
> }
>
> @@ -2167,6 +2182,7 @@ queue_commits(struct tog_log_thread_args *a)
> struct got_object_id *id;
> struct got_commit_object *commit;
> struct commit_queue_entry *entry;
> + int limit_match = 0;
> int errcode;
>
> err = got_commit_graph_iter_next(&id, a->graph, a->repo,
> @@ -2194,13 +2210,49 @@ queue_commits(struct tog_log_thread_args *a)
> TAILQ_INSERT_TAIL(&a->commits->head, entry, entry);
> a->commits->ncommits++;
>
> + if (*a->limiting) {
> + err = match_commit(&limit_match, id, commit,
> + a->limit_regex);
> + if (err)
> + break;
> +
> + if (limit_match) {
> + struct commit_queue_entry *matched;
> +
> + matched =
> + malloc(sizeof(struct commit_queue_entry));
> + if (matched == NULL)
> + return got_error_from_errno("malloc");
> +
> + memcpy(matched, entry,
> + sizeof(struct commit_queue_entry));
> +
> + matched->idx = a->limit_commits->ncommits;
> + TAILQ_INSERT_TAIL(&a->limit_commits->head,
> + matched, entry);
> + a->limit_commits->ncommits++;
> + }
> +
> + /*
> + * This is how we signal log_thread() that we have found
> + * a match, and that it should be counted as a new entry
> + * for the view.
> + */
> + a->limit_match = limit_match;
> + }
> +
> if (*a->searching == TOG_SEARCH_FORWARD &&
> !*a->search_next_done) {
> int have_match;
> err = match_commit(&have_match, id, commit, a->regex);
> if (err)
> break;
> - if (have_match)
> +
> + if (*a->limiting) {
> + if (limit_match && have_match)
> + *a->search_next_done =
> + TOG_SEARCH_HAVE_MORE;
> + } else if (have_match)
> *a->search_next_done = TOG_SEARCH_HAVE_MORE;
> }
>
> @@ -2271,7 +2323,7 @@ draw_commits(struct tog_view *view)
>
> if (s->thread_args.commits_needed > 0 || s->thread_args.load_all) {
> if (asprintf(&ncommits_str, " [%d/%d] %s",
> - entry ? entry->idx + 1 : 0, s->commits.ncommits,
> + entry ? entry->idx + 1 : 0, s->commits->ncommits,
> (view->searching && !view->search_next_done) ?
> "searching..." : "loading...") == -1) {
> err = got_error_from_errno("asprintf");
> @@ -2279,6 +2331,7 @@ draw_commits(struct tog_view *view)
> }
> } else {
> const char *search_str = NULL;
> + const char *limit_str = NULL;
>
> if (view->searching) {
> if (view->search_next_done == TOG_SEARCH_NO_MORE)
> @@ -2289,10 +2342,14 @@ draw_commits(struct tog_view *view)
> search_str = "searching...";
> }
>
> - if (asprintf(&ncommits_str, " [%d/%d] %s",
> - entry ? entry->idx + 1 : 0, s->commits.ncommits,
> + if (s->limit_view && s->commits->ncommits == 0)
> + limit_str = "no limit matches found";
> +
> + if (asprintf(&ncommits_str, " [%d/%d] %s %s",
> + entry ? entry->idx + 1 : 0, s->commits->ncommits,
> search_str ? search_str :
> - (refs_str ? refs_str : "")) == -1) {
> + (refs_str ? refs_str : ""),
> + limit_str ? limit_str : "") == -1) {
> err = got_error_from_errno("asprintf");
> goto done;
> }
> @@ -2419,7 +2476,7 @@ log_scroll_up(struct tog_log_view_state *s, int maxscr
> struct commit_queue_entry *entry;
> int nscrolled = 0;
>
> - entry = TAILQ_FIRST(&s->commits.head);
> + entry = TAILQ_FIRST(&s->commits->head);
> if (s->first_displayed_entry == entry)
> return;
>
> @@ -2504,13 +2561,13 @@ log_scroll_down(struct tog_view *view, int maxscroll)
> return NULL;
>
> ncommits_needed = s->last_displayed_entry->idx + 1 + maxscroll;
> - if (s->commits.ncommits < ncommits_needed &&
> + if (s->commits->ncommits < ncommits_needed &&
> !s->thread_args.log_complete) {
> /*
> * Ask the log thread for required amount of commits.
> */
> s->thread_args.commits_needed +=
> - ncommits_needed - s->commits.ncommits;
> + ncommits_needed - s->commits->ncommits;
> err = trigger_log_thread(view, 1);
> if (err)
> return err;
> @@ -2750,8 +2807,13 @@ log_thread(void *arg)
> goto done;
> err = NULL;
> done = 1;
> - } else if (a->commits_needed > 0 && !a->load_all)
> - a->commits_needed--;
> + } else if (a->commits_needed > 0 && !a->load_all) {
> + if (*a->limiting) {
> + if (a->limit_match)
> + a->commits_needed--;
> + } else
> + a->commits_needed--;
> + }
>
> errcode = pthread_mutex_lock(&tog_mutex);
> if (errcode) {
> @@ -2760,8 +2822,12 @@ log_thread(void *arg)
> goto done;
> } else if (*a->quit)
> done = 1;
> - else if (*a->first_displayed_entry == NULL) {
> + else if (*a->limiting && *a->first_displayed_entry == NULL) {
> *a->first_displayed_entry =
> + TAILQ_FIRST(&a->limit_commits->head);
> + *a->selected_entry = *a->first_displayed_entry;
> + } else if (*a->first_displayed_entry == NULL) {
> + *a->first_displayed_entry =
> TAILQ_FIRST(&a->commits->head);
> *a->selected_entry = *a->first_displayed_entry;
> }
> @@ -2867,7 +2933,7 @@ close_log_view(struct tog_view *view)
> if (errcode && err == NULL)
> err = got_error_set_errno(errcode, "pthread_cond_destroy");
>
> - free_commits(&s->commits);
> + free_commits(s->commits);
> free(s->in_repo_path);
> s->in_repo_path = NULL;
> free(s->start_id);
> @@ -2877,7 +2943,131 @@ close_log_view(struct tog_view *view)
> return err;
> }
>
> +/*
> + * We use two queues to implement the limit feature: first consists of commits
> + * matching the current limit_regex; second is the real queue of all known
> + * commits (real_commits). When the user starts limiting, we swap queues such
> + * that all movement and displaying functionality works with very slight change.
> + */
> static const struct got_error *
> +limit_log_view(struct tog_view *view)
> +{
> + struct tog_log_view_state *s = &view->state.log;
> + struct commit_queue_entry *entry;
> + struct tog_view *v = view;
> + const struct got_error *err = NULL;
> + char pattern[1024];
> + int ret;
> +
> + if (view_is_hsplit_top(view))
> + v = view->child;
> + else if (view->mode == TOG_VIEW_SPLIT_VERT && view->parent)
> + v = view->parent;
> +
> + /* Get the pattern */
> + wmove(v->window, v->nlines - 1, 0);
> + wclrtoeol(v->window);
> + mvwaddstr(v->window, v->nlines - 1, 0, "limit: ");
> + nodelay(v->window, FALSE);
> + nocbreak();
> + echo();
> + ret = wgetnstr(v->window, pattern, sizeof(pattern));
> + cbreak();
> + noecho();
> + nodelay(v->window, TRUE);
> + if (ret == ERR)
> + return NULL;
> +
> + if (*pattern == '\0') {
> + /*
> + * Safety measure for the situation where the user resets limit
> + * without previously limiting anything.
> + */
> + if (!s->limit_view)
> + return NULL;
> +
> + s->first_displayed_entry = s->saved_first_displayed_entry;
> + s->last_displayed_entry = s->saved_last_displayed_entry;
> + s->selected_entry = s->saved_selected_entry;
> + s->commits = &s->real_commits;
> + s->selected = s->saved_selected;
> + s->limit_view = 0;
> +
> + return NULL;
> + }
> +
> + if (regcomp(&s->limit_regex, pattern, REG_EXTENDED | REG_NEWLINE))
> + return NULL;
> +
> + /*
> + * This check is needed because user can issue limit "inside"
> + * limit, in this case don't overwrite saved entries, and start
> + * the procedure again.
> + */
> + if (s->limit_view == 0) {
> + s->limit_view = 1;
> + s->saved_first_displayed_entry = s->first_displayed_entry;
> + s->saved_last_displayed_entry = s->last_displayed_entry;
> + s->saved_selected_entry = s->selected_entry;
> + s->saved_selected = s->selected;
> + }
> +
> + s->first_displayed_entry = NULL;
> + s->last_displayed_entry = NULL;
> + s->selected_entry = NULL;
> + s->commits = &s->limit_commits;
> +
> + /* Prepare limit queue for new search */
> + while ((entry = TAILQ_FIRST(&s->limit_commits.head))) {
> + TAILQ_REMOVE(&s->limit_commits.head, entry, entry);
> + free(entry);
> + }
> + s->limit_commits.ncommits = 0;
> +
> + /* First process commits, which are in queue already */
> + TAILQ_FOREACH(entry, &s->real_commits.head, entry) {
> + struct commit_queue_entry *matched;
> + int have_match = 0;
> +
> + err = match_commit(&have_match, entry->id,
> + entry->commit, &s->limit_regex);
> + if (err)
> + return err;
> +
> + if (have_match) {
> + matched = malloc(sizeof(struct commit_queue_entry));
> + if (matched == NULL)
> + return got_error_from_errno("malloc");
> +
> + memcpy(matched, entry,
> + sizeof(struct commit_queue_entry));
> +
> + matched->idx = s->limit_commits.ncommits;
> + TAILQ_INSERT_TAIL(&s->limit_commits.head,
> + matched, entry);
> + s->limit_commits.ncommits++;
> + }
> +
> + }
> +
> + /* Second process all the commits, until we fill the screen */
> + if (s->limit_commits.ncommits < view->nlines - 1 &&
> + !s->thread_args.log_complete) {
> + s->thread_args.commits_needed +=
> + view->nlines - s->limit_commits.ncommits - 1;
> + err = trigger_log_thread(view, 1);
> + if (err)
> + return err;
> + }
> +
> + s->first_displayed_entry = TAILQ_FIRST(&s->commits->head);
> + s->selected_entry = TAILQ_FIRST(&s->commits->head);
> + s->selected = 0;
> +
> + return NULL;
> +}
> +
> +static const struct got_error *
> search_start_log_view(struct tog_view *view)
> {
> struct tog_log_view_state *s = &view->state.log;
> @@ -3023,9 +3213,18 @@ open_log_view(struct tog_view *view, struct got_object
> }
>
> /* The commit queue only contains commits being displayed. */
> - TAILQ_INIT(&s->commits.head);
> - s->commits.ncommits = 0;
> + TAILQ_INIT(&s->real_commits.head);
> + s->real_commits.ncommits = 0;
> + s->commits = &s->real_commits;
>
> + TAILQ_INIT(&s->limit_commits.head);
> + s->limit_view = 0;
> + s->saved_first_displayed_entry = NULL;
> + s->saved_last_displayed_entry = NULL;
> + s->saved_selected_entry = NULL;
> + s->saved_selected = 0;
> + s->limit_commits.ncommits = 0;
> +
> s->repo = repo;
> if (head_ref_name) {
> s->head_ref_name = strdup(head_ref_name);
> @@ -3099,7 +3298,7 @@ open_log_view(struct tog_view *view, struct got_object
>
> s->thread_args.commits_needed = view->nlines;
> s->thread_args.graph = thread_graph;
> - s->thread_args.commits = &s->commits;
> + s->thread_args.commits = s->commits;
> s->thread_args.in_repo_path = s->in_repo_path;
> s->thread_args.start_id = s->start_id;
> s->thread_args.repo = thread_repo;
> @@ -3110,6 +3309,12 @@ open_log_view(struct tog_view *view, struct got_object
> s->thread_args.searching = &view->searching;
> s->thread_args.search_next_done = &view->search_next_done;
> s->thread_args.regex = &view->regex;
> + s->thread_args.limiting = &s->limit_view;
> + s->thread_args.limit_regex = &s->limit_regex;
> + s->thread_args.limit_commits = &s->limit_commits;
> + s->thread_args.saved_first_displayed_entry =
> + &s->saved_first_displayed_entry;
> + s->thread_args.saved_selected_entry = &s->saved_selected_entry;
> done:
> if (err)
> close_log_view(view);
> @@ -3142,19 +3347,19 @@ log_move_cursor_up(struct tog_view *view, int page, in
> {
> struct tog_log_view_state *s = &view->state.log;
>
> + if (s->first_displayed_entry == NULL)
> + return;
> if (s->selected_entry->idx == 0)
> view->count = 0;
> - if (s->first_displayed_entry == NULL)
> - return;
>
> - if ((page && TAILQ_FIRST(&s->commits.head) == s->first_displayed_entry)
> + if ((page && TAILQ_FIRST(&s->commits->head) == s->first_displayed_entry)
> || home)
> s->selected = home ? 0 : MAX(0, s->selected - page - 1);
>
> if (!page && !home && s->selected > 0)
> --s->selected;
> else
> - log_scroll_up(s, home ? s->commits.ncommits : MAX(page, 1));
> + log_scroll_up(s, home ? s->commits->ncommits : MAX(page, 1));
>
> select_commit(s);
> return;
> @@ -3167,15 +3372,18 @@ log_move_cursor_down(struct tog_view *view, int page)
> const struct got_error *err = NULL;
> int eos = view->nlines - 2;
>
> + if (s->first_displayed_entry == NULL)
> + return NULL;
> +
> if (s->thread_args.log_complete &&
> - s->selected_entry->idx >= s->commits.ncommits - 1)
> + s->selected_entry->idx >= s->commits->ncommits - 1)
> return NULL;
>
> if (view_is_hsplit_top(view))
> --eos; /* border consumes the last line */
>
> if (!page) {
> - if (s->selected < MIN(eos, s->commits.ncommits - 1))
> + if (s->selected < MIN(eos, s->commits->ncommits - 1))
> ++s->selected;
> else
> err = log_scroll_down(view, 1);
> @@ -3184,7 +3392,7 @@ log_move_cursor_down(struct tog_view *view, int page)
> int n;
>
> s->selected = 0;
> - entry = TAILQ_LAST(&s->commits.head, commit_queue_head);
> + entry = TAILQ_LAST(&s->commits->head, commit_queue_head);
> s->last_displayed_entry = entry;
> for (n = 0; n <= eos; n++) {
> if (entry == NULL)
> @@ -3195,10 +3403,10 @@ log_move_cursor_down(struct tog_view *view, int page)
> if (n > 0)
> s->selected = n - 1;
> } else {
> - if (s->last_displayed_entry->idx == s->commits.ncommits - 1 &&
> + if (s->last_displayed_entry->idx == s->commits->ncommits - 1 &&
> s->thread_args.log_complete)
> s->selected += MIN(page,
> - s->commits.ncommits - s->selected_entry->idx - 1);
> + s->commits->ncommits - s->selected_entry->idx - 1);
> else
> err = log_scroll_down(view, page);
> }
> @@ -3218,7 +3426,7 @@ log_move_cursor_down(struct tog_view *view, int page)
> select_commit(s);
>
> if (s->thread_args.log_complete &&
> - s->selected_entry->idx == s->commits.ncommits - 1)
> + s->selected_entry->idx == s->commits->ncommits - 1)
> view->count = 0;
>
> return NULL;
> @@ -3314,7 +3522,7 @@ input_log_view(struct tog_view **new_view, struct tog_
> if (ch == CTRL('g') || ch == KEY_BACKSPACE)
> s->thread_args.load_all = 0;
> else if (s->thread_args.log_complete) {
> - err = log_move_cursor_down(view, s->commits.ncommits);
> + err = log_move_cursor_down(view, s->commits->ncommits);
> s->thread_args.load_all = 0;
> }
> if (err)
> @@ -3329,6 +3537,9 @@ input_log_view(struct tog_view **new_view, struct tog_
> return log_goto_line(view, eos);
>
> switch (ch) {
> + case '&':
> + err = limit_log_view(view);
> + break;
> case 'q':
> s->quit = 1;
> break;
> @@ -3391,7 +3602,7 @@ input_log_view(struct tog_view **new_view, struct tog_
> s->thread_args.load_all = 1;
> if (!s->thread_args.log_complete)
> return trigger_log_thread(view, 0);
> - err = log_move_cursor_down(view, s->commits.ncommits);
> + err = log_move_cursor_down(view, s->commits->ncommits);
> s->thread_args.load_all = 0;
> break;
> }
> @@ -3408,13 +3619,13 @@ input_log_view(struct tog_view **new_view, struct tog_
> case KEY_RESIZE:
> if (s->selected > view->nlines - 2)
> s->selected = view->nlines - 2;
> - if (s->selected > s->commits.ncommits - 1)
> - s->selected = s->commits.ncommits - 1;
> + if (s->selected > s->commits->ncommits - 1)
> + s->selected = s->commits->ncommits - 1;
> select_commit(s);
> - if (s->commits.ncommits < view->nlines - 1 &&
> + if (s->commits->ncommits < view->nlines - 1 &&
> !s->thread_args.log_complete) {
> s->thread_args.commits_needed += (view->nlines - 1) -
> - s->commits.ncommits;
> + s->commits->ncommits;
> err = trigger_log_thread(view, 1);
> }
> break;
> @@ -3484,7 +3695,7 @@ input_log_view(struct tog_view **new_view, struct tog_
> s->start_id, s->repo, NULL, NULL);
> if (err)
> return err;
> - free_commits(&s->commits);
> + free_commits(s->commits);
> s->first_displayed_entry = NULL;
> s->last_displayed_entry = NULL;
> s->selected_entry = NULL;
>
--
Mark Jamsek <fnc.bsdbox.org>
GPG: F2FF 13DE 6A06 C471 CA80 E6E2 2930 DC66 86EE CF68
tog: limit feature