diff options
context:
space:
mode:
-rw-r--r--source/ui-shared.c43
-rwxr-xr-xtests/t0110-snapshot.sh34
2 files changed, 69 insertions, 8 deletions
diff --git a/source/ui-shared.c b/source/ui-shared.c
index 58e7f03..2f0843c 100644
--- a/source/ui-shared.c
+++ b/source/ui-shared.c
@@ -1741,20 +1741,47 @@ void cgit_print_filemode(unsigned short mode)
html_fileperm(mode);
}
-static void compose_snapshot_prefix(struct strbuf *filename, const char *base, const char *ref)
+/*
+ * One tag's claim on a snapshot stem, the stem being the tag name with any
+ * leading v or V removed. Claims are counted over the tag list itself rather
+ * than by looking refs up by name, because a loose ref lookup on a
+ * case-insensitive filesystem finds v1.2 when asked for V1.2 and would call
+ * every tag still stored loose ambiguous.
+ */
+struct snapshot_stem {
+ const char *tag;
+ const char *stem;
+ int claimants;
+ int is_tag;
+};
+
+static int count_stem_claimants(const struct reference *ref, void *data)
{
- struct object_id oid;
+ struct snapshot_stem *probe = data;
+ const char *name = ref->name;
+ if (!strcmp(name, probe->tag))
+ probe->is_tag = 1;
+ if (name[0] == 'v' || name[0] == 'V')
+ name++;
+ if (!strcmp(name, probe->stem))
+ probe->claimants++;
+ return 0;
+}
+
+static void compose_snapshot_prefix(struct strbuf *filename, const char *base, const char *ref)
+{
// A tag named v1.2 or V1.2 gives its snapshot the prettier name 1.2,
// but only where dropping the letter cannot land two different tags on
// one name, so that a snapshot can still be traced back to the tag it
// was made from.
- if (repo_get_oid(the_repository, cgit_fmt("refs/tags/%s", ref), &oid) == 0 &&
- (ref[0] == 'v' || ref[0] == 'V') && isdigit((unsigned char)ref[1]) &&
- ((repo_get_oid(the_repository, cgit_fmt("refs/tags/%s", ref + 1), &oid) == 0) +
- (repo_get_oid(the_repository, cgit_fmt("refs/tags/v%s", ref + 1), &oid) == 0) +
- (repo_get_oid(the_repository, cgit_fmt("refs/tags/V%s", ref + 1), &oid) == 0) == 1))
- ref++;
+ if ((ref[0] == 'v' || ref[0] == 'V') && isdigit((unsigned char)ref[1])) {
+ struct snapshot_stem probe = { .tag = ref, .stem = ref + 1 };
+
+ refs_for_each_tag_ref(get_main_ref_store(the_repository), count_stem_claimants, &probe);
+ if (probe.is_tag && probe.claimants == 1)
+ ref++;
+ }
strbuf_addf(filename, "%s-%s", base, ref);
}
diff --git a/tests/t0110-snapshot.sh b/tests/t0110-snapshot.sh
index 9c8ce35..79fecef 100755
--- a/tests/t0110-snapshot.sh
+++ b/tests/t0110-snapshot.sh
@@ -112,4 +112,38 @@ test_expect_success UNZIP 'unzip and verify the content' '
test_line_count = 1 master/file-5
'
+# A tag named v1.0 sheds its v in the snapshot name unless dropping it would
+# merge two tags. The first tag below stays a loose ref on purpose, because on
+# a case-insensitive filesystem a ref lookup for V1.0 finds the loose v1.0
+# file, and deciding by lookups called every fresh tag ambiguous and kept its
+# v until git happened to pack the refs.
+test_expect_success 'set up a repo with a versioned tag' '
+ mkrepo repos/vtags 2 &&
+ git -C repos/vtags update-ref refs/tags/v1.0 HEAD &&
+ {
+ echo "virtual-root=/" &&
+ echo "cache-size=0" &&
+ echo "snapshots=tar.gz" &&
+ echo "repo.url=vtags" &&
+ echo "repo.path=$PWD/repos/vtags/.git"
+ } >vtagsrc
+'
+
+test_expect_success 'a loose v tag still sheds its v in the snapshot name' '
+ CGIT_CONFIG="$PWD/vtagsrc" QUERY_STRING="url=vtags/refs/" cgit >tmp &&
+ grep "vtags-1.0.tar.gz" tmp &&
+ ! grep "vtags-v1.0.tar.gz" tmp
+'
+
+# The second tag goes in only after the first is packed, since two loose files
+# differing in case alone cannot coexist on a case-insensitive filesystem.
+test_expect_success 'a real case collision keeps the v on both tags' '
+ git -C repos/vtags pack-refs --all &&
+ git -C repos/vtags update-ref refs/tags/V1.0 HEAD~1 &&
+ CGIT_CONFIG="$PWD/vtagsrc" QUERY_STRING="url=vtags/refs/" cgit >tmp &&
+ grep "vtags-v1.0.tar.gz" tmp &&
+ grep "vtags-V1.0.tar.gz" tmp &&
+ ! grep "vtags-1.0.tar.gz" tmp
+'
+
test_done