diff --git a/tracer/src/Datadog.Trace/Ci/CodeOwners.cs b/tracer/src/Datadog.Trace/Ci/CodeOwners.cs index ef1d69d2bd54..ae2f821962fe 100644 --- a/tracer/src/Datadog.Trace/Ci/CodeOwners.cs +++ b/tracer/src/Datadog.Trace/Ci/CodeOwners.cs @@ -137,13 +137,18 @@ private static bool TryParseSectionHeader(string raw, [NotNullWhen(true)] out Se /// private static Regex CompileGlob(string pattern) { + var patternWithoutLeadingGlobstar = pattern.StartsWith("**/", StringComparison.Ordinal) + ? pattern.Substring(3) + : pattern; + // Escape regex metachars first. - var rx = Regex.Escape(pattern); + var rx = Regex.Escape(patternWithoutLeadingGlobstar); + + // A globstar surrounded by slashes matches zero or more directories. + rx = rx.Replace("/\\*\\*/", "/(?:[^/]+/)*"); - // Temporary sentinel for ** that we restore after dealing with single *. - rx = rx.Replace("\\*\\*", "§§DOUBLESTAR§§"); + rx = rx.Replace("\\*\\*", ".*"); // multi-level wildcard rx = rx.Replace("\\*", "[^/]*"); // single‑level wildcard - rx = rx.Replace("§§DOUBLESTAR§§", ".*"); // multi‑level wildcard rx = rx.Replace("\\?", "."); // single char if (pattern.EndsWith("/")) diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/CI/CodeOwnersTests.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/CI/CodeOwnersTests.cs index c13f2001fd95..e609eebc2bf2 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/CI/CodeOwnersTests.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/CI/CodeOwnersTests.cs @@ -51,6 +51,26 @@ public CodeOwnersTests() // New GitHub quirks [InlineData("/x/logs/error.txt", "[\"@octo-org/octocats\"]")] // **/logs pattern [InlineData("docs/getting-started.md", "[\"docs@example.com\"]")] // docs/* pattern + [InlineData("/globstar/target", "[\"@globstar-owner\"]")] + [InlineData("/globstar/one/target", "[\"@globstar-owner\"]")] + [InlineData("/globstar/one/two/target", "[\"@globstar-owner\"]")] + [InlineData("/globstar/one/not-target", "[\"@global-owner1\",\"@global-owner2\"]")] + // Leading globstar + [InlineData("foo", "[\"@foo-owner\"]")] + [InlineData("one/foo", "[\"@foo-owner\"]")] + [InlineData("one/two/foo", "[\"@foo-owner\"]")] + [InlineData("foo/bar", "[\"@foo-bar-owner\"]")] + [InlineData("one/foo/bar", "[\"@foo-bar-owner\"]")] + [InlineData("one/two/foo/bar", "[\"@foo-bar-owner\"]")] + [InlineData("foobar", "[\"@global-owner1\",\"@global-owner2\"]")] + // Trailing globstar + [InlineData("abc/file", "[\"@abc-owner\"]")] + [InlineData("abc/one/file", "[\"@abc-owner\"]")] + [InlineData("abc/one/two/file", "[\"@abc-owner\"]")] + [InlineData("abc", "[\"@global-owner1\",\"@global-owner2\"]")] + [InlineData("abcd/file", "[\"@global-owner1\",\"@global-owner2\"]")] + // An embedded **/ must not receive leading-globstar semantics + [InlineData("prefixtarget", "[\"@global-owner1\",\"@global-owner2\"]")] public void CheckGithubCodeOwners(string value, string expected) { var match = _githubCodeOwners.Match(value); diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/CI/Data/CODEOWNERS_GITHUB b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/CI/Data/CODEOWNERS_GITHUB index afbd198dcf9c..f13213c8c0f3 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/CI/Data/CODEOWNERS_GITHUB +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/CI/Data/CODEOWNERS_GITHUB @@ -51,4 +51,17 @@ apps/ @octocat # directory in the root of your repository except for the `/apps/github` # subdirectory, as its owners are left empty. /apps/ @octocat -/apps/github \ No newline at end of file +/apps/github + +# A globstar between slashes matches zero or more directories. +/globstar/**/target @globstar-owner + +# A leading globstar matches at any depth, including the repository root. +**/foo @foo-owner +**/foo/bar @foo-bar-owner + +# A trailing globstar matches everything inside the directory. +abc/** @abc-owner + +# Consecutive stars outside a globstar position are not a leading globstar. +prefix**/target @embedded-star-owner