diff options
context:
space:
mode:
Diffstat (limited to 'source')
-rw-r--r--source/ui-shared.c128
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");
}