From 4251f1bb695c720f968edfcadcc4cb3e2cdbe8b8 Mon Sep 17 00:00:00 2001 From: Bryce Kwon Date: Sun, 1 Nov 2026 09:45:16 -1000 Subject: Link snapshots of a slash ref by a dashed name The snapshot page refuses to work a revision out of a name holding a slash, since that name becomes the top directory of the archive, so every link for a ref like release/2.0 answered 404. The ref now goes in the link as the id, and the archive unpacks into the repository's own prefix, as any snapshot asked for by id does. --- MANUAL.txt | 5 +++-- source/ui-shared.c | 31 +++++++++++++++++++++++++++---- tests/t0110-snapshot.sh | 19 +++++++++++++++++++ 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/MANUAL.txt b/MANUAL.txt index 081faad..4efc0db 100644 --- a/MANUAL.txt +++ b/MANUAL.txt @@ -517,8 +517,9 @@ snapshots:: and "zip". The special value "all" enables all snapshot formats. All compressors use default settings. Some settings can be influenced with environment variables, for example set ZSTD_CLEVEL=10 in the web server - environment for higher (but slower) zstd compression. Default value: - none. + environment for higher (but slower) zstd compression. A ref with a slash + in its name, such as "release/2.0", has its snapshots named with a dash + in place of the slash. Default value: none. source-filter:: Specifies a command which will be invoked to format plaintext blobs in diff --git a/source/ui-shared.c b/source/ui-shared.c index 81db404..23daa2b 100644 --- a/source/ui-shared.c +++ b/source/ui-shared.c @@ -1864,11 +1864,27 @@ static void compose_snapshot_prefix(struct strbuf *filename, const char *base, c strbuf_addf(filename, "%s-%s", base, ref); } +/* + * Unlike reporevlink, this names rev whenever it is given, even where the + * page's own head is rev, since the link does not carry that head along. + */ +static void snapshot_file_link(const char *name, const char *filename, const char *rev) +{ + const char *delim = repolink(NULL, NULL, "snapshot", NULL, filename); + + if (rev) + url_arg(delim, "id", rev); + html("'>"); + html_txt(name); + html(""); +} + void cgit_print_snapshot_links(const struct cgit_repo *repo, const char *ref, const char *separator) { const struct cgit_snapshot_format *f; struct strbuf filename = STRBUF_INIT; - const char *basename; + const char *basename, *rev = NULL; + char *slash; size_t prefixlen; basename = cgit_snapshot_prefix(repo); @@ -1876,6 +1892,13 @@ void cgit_print_snapshot_links(const struct cgit_repo *repo, const char *ref, co strbuf_addstr(&filename, ref); else compose_snapshot_prefix(&filename, basename, ref); + // The snapshot page works no revision out of a name holding a slash, + // since that name becomes the archive's top directory, so a ref such + // as release/2.0 is spelled with a dash and passed outright. + for (slash = filename.buf; (slash = strchr(slash, '/')); slash++) { + *slash = '-'; + rev = ref; + } prefixlen = filename.len; for (f = cgit_snapshot_formats; f->suffix; f++) { @@ -1886,11 +1909,11 @@ void cgit_print_snapshot_links(const struct cgit_repo *repo, const char *ref, co // A file and its signature are one unit, which the stylesheet // keeps from breaking across lines. html(""); - snapshot_link(filename.buf, NULL, NULL, NULL, NULL, filename.buf); + snapshot_file_link(filename.buf, filename.buf, rev); if (cgit_snapshot_get_sig(ref, f)) { strbuf_addstr(&filename, SIG_SUFFIX); html(" ("); - snapshot_link("sig", NULL, NULL, NULL, NULL, filename.buf); + snapshot_file_link("sig", filename.buf, rev); html(")"); } else if ( starts_with(f->suffix, ".tar") && @@ -1902,7 +1925,7 @@ void cgit_print_snapshot_links(const struct cgit_repo *repo, const char *ref, co strbuf_setlen(&filename, filename.len - strlen(f->suffix)); strbuf_addstr(&filename, ".tar" SIG_SUFFIX); html(" ("); - snapshot_link("sig", NULL, NULL, NULL, NULL, filename.buf); + snapshot_file_link("sig", filename.buf, rev); html(")"); } html(""); diff --git a/tests/t0110-snapshot.sh b/tests/t0110-snapshot.sh index faebf5e..0f1ca21 100755 --- a/tests/t0110-snapshot.sh +++ b/tests/t0110-snapshot.sh @@ -227,4 +227,23 @@ test_expect_success 'a snapshot and its signature are linked as one unit' ' grep "signed-one.tar.gz (sig)" tmp ' +# A snapshot name is never worked back to a ref holding a slash, so such a +# ref is offered under a dashed name and passed along with it, even from a +# page whose own head is that ref. +test_expect_success 'a ref with a slash is linked under a dashed name' ' + git -C repos/vtags tag release/2.0 HEAD && + git -C repos/vtags branch topic/x HEAD && + cgit_rc vtagsrc "url=vtags/refs/" >tmp && + grep "vtags-release-2.0.tar.gz" tmp && + cgit_rc vtagsrc "url=vtags/commit/&h=topic/x" >tmp && + grep "vtags-topic-x.tar.gz" tmp +' + +test_expect_success GZIP 'the dashed name serves the ref' ' + cgit_rc vtagsrc "url=vtags/snapshot/vtags-release-2.0.tar.gz&id=release/2.0" >tmp && + grep "^Status: 200" tmp && + strip_headers slash.tar.gz && + test "$(gzip -dc slash.tar.gz | git -C repos/vtags get-tar-commit-id)" = "$(git -C repos/vtags rev-parse release/2.0^{commit})" +' + test_done -- cgit 2.12.1