From: Mikhail Subject: tog: limit feature To: gameoftrees@openbsd.org Date: Wed, 17 Aug 2022 16:40:09 +0300 This patch implements limit feature for tog log view. We keep two queues of commits - one for commits matching the pattern and another one for all known commits, when user wants to use the feature, we substitute those queues. Because of that displaying and movement functions has been touched only a little bit. diff refs/heads/main refs/heads/limit3 commit - 4fcc9f7404ca2e0dd2ee085f09d6246587c6c503 commit + f82712fb5e22a442d08c14774fb7506d60dab20a blob - cbd5233dd61d780f245c5bcb24d95b030a5a7676 blob + 46b73b4047ef7e83b7c97015b9138c5f2be57857 --- tog/tog.1 +++ tog/tog.1 @@ -220,6 +220,9 @@ 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 L +Limit commits to the subset of matching the pattern. Use 'all' +as a pattern to display all commits. .El .Pp The options for blob - 6ec8b5f0c155fafe55ee2aab0653d259876e6254 blob + 2e7e6254522f5ef7d8cc72da14cde10896c6d7c2 --- tog/tog.c +++ tog/tog.c @@ -356,13 +356,20 @@ 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; char *in_repo_path; char *head_ref_name; @@ -376,6 +383,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 +949,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 +2181,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,14 +2209,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)); + 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 to log_thread(), that we have + * found match, and that it should be accounted as 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) - *a->search_next_done = TOG_SEARCH_HAVE_MORE; + + 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; } errcode = pthread_mutex_unlock(&tog_mutex); @@ -2271,7 +2321,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"); @@ -2290,7 +2340,7 @@ draw_commits(struct tog_view *view) } if (asprintf(&ncommits_str, " [%d/%d] %s", - entry ? entry->idx + 1 : 0, s->commits.ncommits, + entry ? entry->idx + 1 : 0, s->commits->ncommits, search_str ? search_str : (refs_str ? refs_str : "")) == -1) { err = got_error_from_errno("asprintf"); @@ -2419,7 +2469,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 +2554,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 +2800,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,6 +2815,13 @@ log_thread(void *arg) goto done; } else if (*a->quit) done = 1; + 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); @@ -2867,7 +2929,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 +2939,128 @@ close_log_view(struct tog_view *view) return err; } +/* + * We use two queues to implement limit feature, first one consists of commits + * matching current limit_regex, second one is the real queue, of all known + * commits (real_commits). Then, when use starts the limiting we swap the + * queues, because of 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 (strcmp(pattern, "all") == 0) { + /* + * Safety measures for situation when user limit 'all' without + * previously limiting anything. + */ + if (s->limit_view == 1) { + 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->limit_view = 0; + } + return NULL; + } else { + /* + * 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->first_displayed_entry = NULL; + s->last_displayed_entry = NULL; + s->selected_entry = NULL; + s->commits = &s->limit_commits; + regcomp(&s->limit_regex, pattern, REG_EXTENDED | REG_NEWLINE); + + /* 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)); + 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 */ + while (1) { + if (s->limit_commits.ncommits < view->nlines - 1 && + !s->thread_args.log_complete) { + s->thread_args.commits_needed++; + trigger_log_thread(view, 1); + } else + break; + } + } + + 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 +3206,17 @@ 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->limit_commits.ncommits = 0; + s->repo = repo; if (head_ref_name) { s->head_ref_name = strdup(head_ref_name); @@ -3099,7 +3290,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 +3301,13 @@ 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 +3340,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; @@ -3168,14 +3366,14 @@ log_move_cursor_down(struct tog_view *view, int page) int eos = view->nlines - 2; 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 +3382,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 +3393,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 +3416,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 +3512,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 +3527,9 @@ input_log_view(struct tog_view **new_view, struct tog_ return log_goto_line(view, eos); switch (ch) { + case 'L': + err = limit_log_view(view); + break; case 'q': s->quit = 1; break; @@ -3391,7 +3592,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 +3609,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 +3685,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;