diff options
| author | Bryce Kwon <bryce@brycekwon.com> | |
|---|---|---|
| committer | Bryce Kwon <bryce@brycekwon.com> | |
| commit | ||
| parent | ||
| tree | ||
| download | ||
Show a pinned commit in the switcher, title, crumbs
Diffstat (limited to 'source/ui-shared.c')
| -rw-r--r-- | source/ui-shared.c | 128 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 file changed, 110 insertions, 18 deletions
diff --git a/source/ui-shared.c b/source/ui-shared.c index db441e0..066c25d 100644 --- a/source/ui-shared.c +++ b/source/ui-shared.c @@ -373,15 +373,48 @@ static void add_clone_urls(void (*fn)(const char *), char *urls, char *suffix) strbuf_list_free(url_list); } -static int print_branch_option(const struct reference *ref, void *counter) +/* + * The id parameter pins a page to one commit while the rest of the chrome + * keeps naming the branch. Answer that commit, or null when the request + * follows the branch or the pin lands on its tip anyway. + */ +static const struct object_id *pinned_oid(void) { - int *count = counter; + static struct object_id oid; + struct object_id head_oid; + struct commit *commit; + + if (!ctx.repo || !ctx.qry.has_oid || !ctx.qry.oid || !ctx.qry.head) + return NULL; + if (repo_get_oid(the_repository, ctx.qry.oid, &oid) || + repo_get_oid(the_repository, ctx.qry.head, &head_oid)) + return NULL; + // A full hex id resolves without being looked up, so only peeling to a + // commit proves the pin names history, and a tag pinned to the tip + // compares equal only by its peeled commit. + commit = lookup_commit_reference_gently(the_repository, &oid, 1); + if (!commit) + return NULL; + oidcpy(&oid, &commit->object.oid); + if (oideq(&oid, &head_oid)) + return NULL; + return &oid; +} + +struct branch_option_data { + int count; + const char *selected; +}; + +static int print_branch_option(const struct reference *ref, void *data) +{ + struct branch_option_data *opt = data; const char *name = ref->name; // The switcher runs on every page, so it is bounded like the refs list. - if (ctx.cfg.max_ref_count && *count >= ctx.cfg.max_ref_count) + if (ctx.cfg.max_ref_count && opt->count >= ctx.cfg.max_ref_count) return -1; - (*count)++; - html_option(name, name, ctx.qry.head); + opt->count++; + html_option(name, name, opt->selected); return 0; } @@ -422,15 +455,33 @@ static void print_header(void) // A repository with no commits has no branches to list, so the // switcher would be an empty select next to a switch button. if (ctx.repo && ctx.env.authenticated && !ctx.empty_repo) { + const struct object_id *pinned = pinned_oid(); + // Only one option may carry selected, and a pinned commit + // outranks the branch it was reached from. + struct branch_option_data opt = { + .selected = pinned ? NULL : ctx.qry.head, + }; + char *oid = ctx.qry.oid, *oid2 = ctx.qry.oid2; + html("<form class='branch-switch' method='get'>\n"); + // Switching branch means leaving the pinned commit behind, so + // the id fields stay out of the form. + ctx.qry.oid = ctx.qry.oid2 = NULL; cgit_add_hidden_formfields(0, 1, ctx.qry.page); + ctx.qry.oid = oid; + ctx.qry.oid2 = oid2; html("<select name='h' aria-label='Branch' data-autosubmit='1'>\n"); - int branch_count = 0; + if (pinned) { + const char *hex = oid_to_hex(pinned); + + html_option(hex, cgit_fmt("%.*s (detached)", + SHORT_HASH_LEN, hex), hex); + } refs_for_each_branch_ref(get_main_ref_store(the_repository), - print_branch_option, &branch_count); + print_branch_option, &opt); if (ctx.repo->enable_remote_branches) refs_for_each_remote_ref(get_main_ref_store(the_repository), - print_branch_option, &branch_count); + print_branch_option, &opt); html("</select> "); html("<input type='submit' value='switch'>"); html("</form>\n"); @@ -1134,6 +1185,7 @@ void cgit_redirect(const char *url, bool permanent) void cgit_print_docstart(void) { + const struct object_id *pinned; char *host = cgit_hosturl(); if (ctx.cfg.embedded) { @@ -1154,6 +1206,10 @@ void cgit_print_docstart(void) // An error page reached before a title was chosen still has to name // itself, since an empty title element is not valid. html_txt(ctx.page.title ? ctx.page.title : ctx.cfg.root_title); + // The auth page goes out before the repository is opened, when + // pinned_oid cannot resolve anything yet. + if (ctx.env.authenticated && (pinned = pinned_oid())) + html_txtf(" @ %.*s", SHORT_HASH_LEN, oid_to_hex(pinned)); html("</title>\n"); htmlf("<meta name='generator' content='cgit %s'>\n", cgit_version); if (ctx.cfg.robots && *ctx.cfg.robots) { @@ -1298,6 +1354,8 @@ void cgit_add_hidden_formfields(int incl_head, int incl_search, void cgit_print_pageheader(void) { + const struct object_id *pinned; + html("<div id='cgit'>\n"); if (!ctx.env.authenticated || !ctx.cfg.noheader) print_header(); @@ -1320,17 +1378,51 @@ void cgit_print_pageheader(void) } if (ctx.env.authenticated) html("</nav>\n"); - if (ctx.env.authenticated && ctx.repo && ctx.qry.vpath) { - html("<nav class='path' aria-label='breadcrumb'>"); - html("path: "); - print_path_crumbs(ctx.qry.vpath); - if (ctx.repo->enable_follow_links && !strcmp(ctx.qry.page, "log")) { + pinned = ctx.env.authenticated && ctx.repo ? pinned_oid() : NULL; + // The commit and diff pages name the commit they show as their own + // content, so the rev crumb would only repeat them. + if (pinned && ctx.qry.page && (!strcmp(ctx.qry.page, "commit") || + !strcmp(ctx.qry.page, "diff"))) + pinned = NULL; + if ((ctx.env.authenticated && ctx.repo && ctx.qry.vpath) || pinned) { + html("<nav class='path' aria-label='"); + html(ctx.qry.vpath ? "breadcrumb" : "revision"); + html("'>"); + if (ctx.qry.vpath) { + // The nav lays out as a flex row, which would make + // every crumb its own item and swallow the spaces + // between them, so the run stays inline in a span. + html("<span>path: "); + print_path_crumbs(ctx.qry.vpath); + if (ctx.repo->enable_follow_links && + !strcmp(ctx.qry.page, "log")) { + html(" ("); + ctx.qry.follow = !ctx.qry.follow; + self_link(ctx.qry.follow ? "follow" : "unfollow", + NULL, NULL); + ctx.qry.follow = !ctx.qry.follow; + html(")"); + } + html("</span>"); + } + if (pinned) { + const char *hex = oid_to_hex(pinned); + char *oid = ctx.qry.oid; + + html("<span class='rev'>rev: "); + cgit_commit_link(cgit_fmt("%.*s", SHORT_HASH_LEN, hex), + NULL, NULL, ctx.qry.head, hex, + ctx.qry.vpath); html(" ("); - ctx.qry.follow = !ctx.qry.follow; - self_link(ctx.qry.follow ? "follow" : "unfollow", - NULL, NULL); - ctx.qry.follow = !ctx.qry.follow; - html(")"); + // self_link reads the pin out of ctx.qry, so lifting + // it is what makes the link land back on the tip of + // the branch. + ctx.qry.oid = NULL; + ctx.qry.has_oid = 0; + self_link("latest", NULL, NULL); + ctx.qry.oid = oid; + ctx.qry.has_oid = 1; + html(")</span>"); } html("</nav>\n"); } |
