commit 6458efa5c16a7c2047493dd0b0ba8b6c2f7cf79e from: Stefan Sperling date: Tue Nov 24 22:17:54 2020 UTC initial 'tog ref' implementation ok naddy tracey commit - 78756c87d902bd89e7c1e8ef59bf2da2439e3bc2 commit + 6458efa5c16a7c2047493dd0b0ba8b6c2f7cf79e blob - 7e5b98bdb2d1a95d74868f01b20f310c508279f7 blob + c0a85cd49ea9763190a0f4d11960ac7a8175b043 --- 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 */ @@ -385,6 +398,26 @@ struct tog_tree_view_state { struct got_object_id *commit_id; struct got_repository *repo; struct got_tree_entry *matched_entry; + 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; }; @@ -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 *); @@ -483,6 +517,15 @@ static const struct got_error *input_tree_view(struct static const struct got_error *close_tree_view(struct tog_view *); 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; @@ -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; } @@ -5572,9 +5642,610 @@ done: if (repo) got_repo_close(repo); 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, ®match, + 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;