From 001bb95ac51298eab0b84cd8c087898bcf2ea517 Mon Sep 17 00:00:00 2001 From: Bryce Kwon Date: Sat, 22 Aug 2026 10:09:59 -0700 Subject: Show a pinned commit in the switcher, title, crumbs --- source/ui-shared.c | 128 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 110 insertions(+), 18 deletions(-) (limited to 'source/ui-shared.c') 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) +{ + 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) { - int *count = counter; + 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("
\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(" "); html(""); html("
\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("\n"); htmlf("\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("
\n"); if (!ctx.env.authenticated || !ctx.cfg.noheader) print_header(); @@ -1320,17 +1378,51 @@ void cgit_print_pageheader(void) } if (ctx.env.authenticated) html("\n"); - if (ctx.env.authenticated && ctx.repo && ctx.qry.vpath) { - html("