Repository: dart-lang/pub-dev (dart)

Issue:
Allow / in tagPattern
`/` is a valid character in git tags, and `package-name/{{version}}` is a reasonably common pattern for subpackage tags. However, pub.dev currently forbids this as an expected tag pattern. It should allow it instead.

Pull request description:
Fixes #8690.

Patch:
diff --git a/app/lib/package/backend.dart b/app/lib/package/backend.dart
index 4818d11b13..e1d81cf123 100644
--- a/app/lib/package/backend.dart
+++ b/app/lib/package/backend.dart
@@ -59,8 +59,11 @@ final _defaultMaxVersionsPerPackage = 1000;
 final Logger _logger = Logger('pub.cloud_repository');
 final _validGitHubUserOrRepoRegExp =
     RegExp(r'^[a-z0-9\-\._]+$', caseSensitive: false);
+// NOTE: The `/` character is allowed inside the tag pattern because we are
+//       not splitting the `refs/tags/` prefix. A change of that parsing
+//       should specifically test the presence of `/`.
 final _validGitHubVersionPattern =
-    RegExp(r'^[a-z0-9\-._]+$', caseSensitive: false);
+    RegExp(r'^[a-z0-9\-._/]+$', caseSensitive: false);
 final _validGitHubEnvironment =
     RegExp(r'^[a-z0-9\-\._]+$', caseSensitive: false);
 
@@ -1759,6 +1762,9 @@ void verifyTagPatternWithRef({
     throw AssertionError(
         'Configured tag pattern does not include `{{version}}`');
   }
+// NOTE: The `/` character is allowed inside the tag pattern because we are
+//       not splitting the `refs/tags/` prefix. A change of this parsing
+//       should specifically test the presence of `/`.
   final expectedRefStart = 'refs/tags/';
   if (!ref.startsWith(expectedRefStart)) {
     throw AuthorizationException.githubActionIssue(


Test patch:
diff --git a/app/test/package/backend_test.dart b/app/test/package/backend_test.dart
index 96f1373625..7b76bcdee5 100644
--- a/app/test/package/backend_test.dart
+++ b/app/test/package/backend_test.dart
@@ -480,6 +480,8 @@ void main() {
         'package-{{version}}',
         'package-v{{version}}',
         'package-v{{version}}-postfix',
+        'abc/def-{{version}}',
+        '{{version}}-abc/def',
       ];
       for (final value in values) {
         verifyTagPattern(tagPattern: value);
@@ -491,8 +493,6 @@ void main() {
         '', // empty pattern is not allowed
         '{{version}}{{version}}', // two {{version}} is not allowed
         '%-{{version}}', // % is not allowed
-        'abc/def-{{version}}', // / is not allowed
-        '{{version}}-abc/def', // / is not allowed
       ];
       for (final value in values) {
         expect(
@@ -506,6 +506,7 @@ void main() {
       final values = [
         ('{{version}}', 'refs/tags/1.0.0'),
         ('pkg-v{{version}}', 'refs/tags/pkg-v1.0.0'),
+        ('dir/pkg-v{{version}}', 'refs/tags/dir/pkg-v1.0.0'),
       ];
       for (final value in values) {
         verifyTagPatternWithRef(
@@ -520,6 +521,7 @@ void main() {
       final values = [
         ('v{{version}}', 'refs/tags/1.0.0'), // does not match `v` prefix
         ('v{{version}}', 'refs/x/v1.0.0'), // missing refs/tags
+        ('dir/{{version}}', 'refs/tags/v1.0.0'), // missing dir/ prefix
       ];
       for (final value in values) {
         expect(