diff --git a/tools/code-owners-parser/Azure.Sdk.Tools.CodeOwnersParser.Tests/Azure.Sdk.Tools.CodeOwnersParser.Tests.csproj b/tools/code-owners-parser/Azure.Sdk.Tools.CodeOwnersParser.Tests/Azure.Sdk.Tools.CodeOwnersParser.Tests.csproj
deleted file mode 100644
index 5054e5ad285..00000000000
--- a/tools/code-owners-parser/Azure.Sdk.Tools.CodeOwnersParser.Tests/Azure.Sdk.Tools.CodeOwnersParser.Tests.csproj
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
- net6.0
- enable
- Nullable
- false
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/tools/code-owners-parser/Azure.Sdk.Tools.CodeOwnersParser.Tests/CodeownersFileTests.cs b/tools/code-owners-parser/Azure.Sdk.Tools.CodeOwnersParser.Tests/CodeownersFileTests.cs
deleted file mode 100644
index 559e3bfde44..00000000000
--- a/tools/code-owners-parser/Azure.Sdk.Tools.CodeOwnersParser.Tests/CodeownersFileTests.cs
+++ /dev/null
@@ -1,225 +0,0 @@
-using System.Collections.Generic;
-using NUnit.Framework;
-
-namespace Azure.Sdk.Tools.CodeOwnersParser.Tests;
-
-///
-/// Please see the comment on CodeownersFileTests.testCases
-///
-[TestFixture]
-public class CodeownersFileTests
-{
- ///
- /// A battery of test cases specifying the behavior of logic matching target
- /// path to CODEOWNERS paths.
- ///
- /// For further details, please see:
- /// - Class comment for Azure.Sdk.Tools.CodeOwnersParser.MatchedCodeOwnerEntry
- /// - Azure.Sdk.Tools.RetrieveCodeOwners.Tests.RetrieveCodeOwnersProgramTests
- /// - https://github.com/Azure/azure-sdk-tools/issues/2770
- /// - https://github.com/Azure/azure-sdk-tools/issues/4859
- ///
- private static readonly TestCase[] testCases =
- {
- // @formatter:off
- // Path: Expected match:
- // Codeowners , Target , |
- new ( "/**" , "a" , true ),
- new ( "/**" , "A" , true ),
- new ( "/**" , "/a" , true ),
- new ( "/**" , "a/" , true ),
- new ( "/**" , "/a/" , true ),
- new ( "/**" , "/a/b" , true ),
- new ( "/**" , "/a/b/" , true ),
- new ( "/**" , "/a/b/c" , true ),
- new ( "/**" , "[" , true ),
- new ( "/**" , "]" , true ),
- new ( "/" , "a" , false ),
- new ( "/" , "A" , false ),
- new ( "/" , "/a" , false ),
- new ( "/" , "a/" , false ),
- new ( "/" , "/a/" , false ),
- new ( "/" , "/a/b" , false ),
- new ( "/a" , "a" , true ),
- new ( "/a" , "A" , false ),
- new ( "/a" , "/a" , true ),
- new ( "/a" , "a/" , false ),
- new ( "/a" , "/a/" , false ),
- new ( "/a" , "/a/b" , false ),
- new ( "/a" , "/a/b/" , false ),
- new ( "/a" , "/a\\ b" , false ),
- new ( "/a" , "/x/a/b" , false ),
- new ( "a" , "a" , false ),
- new ( "a" , "ab" , false ),
- new ( "a" , "ab/" , false ),
- new ( "a" , "/ab/" , false ),
- new ( "a" , "A" , false ),
- new ( "a" , "/a" , false ),
- new ( "a" , "a/" , false ),
- new ( "a" , "/a/" , false ),
- new ( "a" , "/a/b" , false ),
- new ( "a" , "/a/b/" , false ),
- new ( "a" , "/x/a/b" , false ),
- new ( "/a/" , "a" , false ),
- new ( "/a/" , "/a" , false ),
- new ( "/a/" , "a/" , true ),
- new ( "/a/" , "/a/" , true ),
- new ( "/a/" , "/a/b" , true ),
- new ( "/a/" , "/a\\ b" , false ),
- new ( "/a/" , "/a\\ b/" , false ),
- new ( "/a/" , "/a/a\\ b/" , true ),
- new ( "/a/" , "/a/b/" , true ),
- new ( "/a/" , "/A/b/" , false ),
- new ( "/a/" , "/x/a/b" , false ),
- new ( "/a/b/" , "/a" , false ),
- new ( "/a/b/" , "/a/" , false ),
- new ( "/a/b/" , "/a/b" , false ),
- new ( "/a/b/" , "/a/b/" , true ),
- new ( "/a/b/" , "/a/b/c" , true ),
- new ( "/a/b/" , "/a/b/c/" , true ),
- new ( "/a/b/" , "/a/b/c/d" , true ),
- new ( "/a/b" , "/a" , false ),
- new ( "/a/b" , "/a/" , false ),
- new ( "/a/b" , "/a/b" , true ),
- new ( "/a/b" , "/a/b/" , false ),
- new ( "/a/b" , "/a/bc" , false ),
- new ( "/a/b" , "/a/bc/" , false ),
- new ( "/a/b" , "/a/b/c" , false ),
- new ( "/a/b" , "/a/b/c/" , false ),
- new ( "/a/b" , "/a/b/c/d" , false ),
- new ( "/!a" , "!a" , false ),
- new ( "/!a" , "b" , false ),
- new ( "/a[b" , "a[b" , false ),
- new ( "/a]b" , "a]b" , false ),
- new ( "/a?b" , "a?b" , false ),
- new ( "/a?b" , "axb" , false ),
- new ( "/a" , "*" , false ),
- new ( "/*" , "*" , false ),
- new ( "/*" , "a" , true ),
- new ( "/*" , "a/" , false ),
- new ( "/*" , "/a" , true ),
- new ( "/*" , "/a/" , false ),
- new ( "/*" , "a/b" , false ),
- new ( "/*" , "/a/b" , false ),
- new ( "/*" , "[" , true ),
- new ( "/*" , "]" , true ),
- new ( "/*" , "!" , true ),
- new ( "/**" , "!" , true ),
- new ( "/a*" , "a" , true ),
- new ( "/a*" , "a/x" , true ),
- new ( "/a*" , "a/x/d" , true ),
- new ( "/a*" , "ab" , true ),
- new ( "/a*" , "ab/x" , true ),
- new ( "/a*" , "ab/x/d" , true ),
- new ( "/a/**" , "a" , false ),
- new ( "/*/**" , "a" , false ),
- new ( "/*/**" , "a/" , false ),
- new ( "/*/**" , "a/b" , false ),
- new ( "/*/" , "a" , false ),
- new ( "/*/" , "a/" , true ),
- new ( "/*/b" , "a/b" , true ),
- new ( "/**/a" , "a" , true ),
- new ( "/**/a" , "x/ba" , false ),
- new ( "/a/*" , "a" , false ),
- new ( "/a/*" , "a/" , true ),
- new ( "/a/*" , "a/b" , true ),
- new ( "/a/*" , "a/b/" , false ),
- new ( "/a/*" , "a/b/c" , false ),
- new ( "/a/*/" , "a" , false ),
- new ( "/a/*/" , "a/" , false ),
- new ( "/a/*/" , "a/b" , false ),
- new ( "/a/*/" , "a/b/" , true ),
- new ( "/a/*/" , "a/b/c" , true ),
- new ( "/a/**" , "a" , false ),
- new ( "/a/**" , "a/" , false ),
- new ( "/a/**" , "a/b" , false ),
- new ( "/a/**" , "a/b/" , false ),
- new ( "/a/**" , "a/b/c" , false ),
- new ( "/a/**/" , "a" , false ),
- new ( "/a/**/" , "a/" , false ),
- new ( "/a/**/" , "a/b" , false ),
- new ( "/a/**/" , "a/b/" , false ),
- new ( "/a/**/" , "a/b/c" , false ),
- new ( "/**/a/" , "a" , false ),
- new ( "/**/a/" , "a/" , true ),
- new ( "/**/a/" , "a/b" , true ),
- new ( "/**/b/" , "a/b" , false ),
- new ( "/**/b/" , "a/b/" , true ),
- new ( "/**/b/" , "a/c/" , false ),
- new ( "/a/*/b/" , "a/b/" , false ),
- new ( "/a/*/b/" , "a/x/b/" , true ),
- new ( "/a/*/b/" , "a/x/b/c" , true ),
- new ( "/a/*/b/" , "a/x/c" , false ),
- new ( "/a/*/b/" , "a/x/y/b" , false ),
- new ( "/a**b/" , "a/x/y/b" , false ),
- new ( "/a/**/b/" , "a/b" , false ),
- new ( "/a/**/b/" , "a/b/" , true ),
- new ( "/a/**/b/" , "a/x/b/" , true ),
- new ( "/a/**/b/" , "a/x/y/b/" , true ),
- new ( "/a/**/b/" , "a/x/y/c" , false ),
- new ( "/a/**/b/" , "a-b/" , false ),
- new ( "a/*/*" , "a/b" , false ),
- new ( "/a/*/*/d" , "a/b/c/d" , true ),
- new ( "/a/*/*/d" , "a/b/x/c/d" , false ),
- new ( "/a/**/*/d" , "a/b/x/c/d" , true ),
- new ( "*/*/b" , "a/b" , false ),
- new ( "/a*/" , "abc/" , true ),
- new ( "/a*/" , "ab/c/" , true ),
- new ( "/*b*/" , "axbyc/" , true ),
- new ( "/*c/" , "abc/" , true ),
- new ( "/*c/" , "a/abc/" , false ),
- new ( "/a*c/" , "axbyc/" , true ),
- new ( "/a*c/" , "axb/yc/" , false ),
- new ( "/**/*x*/" , "a/b/cxy/d" , true ),
- new ( "/a/*.md" , "a/x.md" , true ),
- new ( "/*/*/*.md" , "a/b/x.md" , true ),
- new ( "/**/*.md" , "a/b.md/x.md" , true ),
- new ( "**/*.md" , "a/b.md/x.md" , false ),
- new ( "/*.md" , "a/md" , false ),
- new ( "/a.*" , "a.b" , true ),
- new ( "/a.*" , "a.b/" , true ),
- new ( "/a.*" , "x/a.b/" , false ),
- new ( "/a.*/" , "a.b" , false ),
- new ( "/a.*/" , "a.b/" , true ),
- new ( "/**/*x*/AB/*/CD" , "a/b/cxy/AB/fff/CD" , true ),
- new ( "/**/*x*/AB/*/CD" , "a/b/cxy/AB/ff/ff/CD" , false ),
- new ( "/**/*x*/AB/**/CD/*" , "a/b/cxy/AB/ff/ff/CD" , false ),
- new ( "/**/*x*/AB/**/CD/*" , "a/b/cxy/AB/ff/ff/CD/" , true ),
- new ( "/**/*x*/AB/**/CD/*" , "a/b/cxy/AB/[]/!!/CD/h" , true ),
- // @formatter:on
- };
-
- ///
- /// Exercises CodeownersFileTests.testCases
- /// See comment on that member for details.
- ///
- [TestCaseSource(nameof(testCases))]
- public void TestGetMatchingCodeownersEntry(TestCase testCase)
- {
- List codeownersEntries =
- CodeownersFile.GetCodeownersEntries(testCase.CodeownersPath + "@owner");
-
- VerifyGetMatchingCodeownersEntry(testCase, codeownersEntries, testCase.ExpectedNewMatch);
- }
-
- private static void VerifyGetMatchingCodeownersEntry(
- TestCase testCase,
- List codeownersEntries,
- bool expectedMatch)
- {
- CodeownersEntry entry =
- // Act
- CodeownersFile.GetMatchingCodeownersEntry(testCase.TargetPath,
- codeownersEntries);
-
- Assert.That(entry.Owners, Has.Count.EqualTo(expectedMatch ? 1 : 0));
- }
-
- ///
- /// Please see comment on CodeownersFileTests.testCases
- ///
- public record TestCase(
- string CodeownersPath,
- string TargetPath,
- bool ExpectedNewMatch);
-}
diff --git a/tools/code-owners-parser/CodeOwnersParser.sln b/tools/code-owners-parser/CodeOwnersParser.sln
deleted file mode 100644
index a6c98e6be24..00000000000
--- a/tools/code-owners-parser/CodeOwnersParser.sln
+++ /dev/null
@@ -1,31 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.0.31903.59
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Sdk.Tools.CodeOwnersParser", "CodeOwnersParser\Azure.Sdk.Tools.CodeOwnersParser.csproj", "{55D665BF-A4B3-45EA-A2A0-B33AFB208766}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Sdk.Tools.CodeOwnersParser.Tests", "Azure.Sdk.Tools.CodeOwnersParser.Tests\Azure.Sdk.Tools.CodeOwnersParser.Tests.csproj", "{66C9FF6A-32DD-4C3C-ABE1-F1F58C1C8129}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {55D665BF-A4B3-45EA-A2A0-B33AFB208766}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {55D665BF-A4B3-45EA-A2A0-B33AFB208766}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {55D665BF-A4B3-45EA-A2A0-B33AFB208766}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {55D665BF-A4B3-45EA-A2A0-B33AFB208766}.Release|Any CPU.Build.0 = Release|Any CPU
- {66C9FF6A-32DD-4C3C-ABE1-F1F58C1C8129}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {66C9FF6A-32DD-4C3C-ABE1-F1F58C1C8129}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {66C9FF6A-32DD-4C3C-ABE1-F1F58C1C8129}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {66C9FF6A-32DD-4C3C-ABE1-F1F58C1C8129}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {E32EEF98-9184-4346-8801-C5A8A1C7FD7D}
- EndGlobalSection
-EndGlobal
diff --git a/tools/code-owners-parser/CodeOwnersParser.sln.DotSettings b/tools/code-owners-parser/CodeOwnersParser.sln.DotSettings
deleted file mode 100644
index d042e12eb56..00000000000
--- a/tools/code-owners-parser/CodeOwnersParser.sln.DotSettings
+++ /dev/null
@@ -1,6 +0,0 @@
-
- PR
- True
- True
- True
- True
\ No newline at end of file
diff --git a/tools/code-owners-parser/CodeOwnersParser/Azure.Sdk.Tools.CodeOwnersParser.csproj b/tools/code-owners-parser/CodeOwnersParser/Azure.Sdk.Tools.CodeOwnersParser.csproj
deleted file mode 100644
index dc90d68b1c6..00000000000
--- a/tools/code-owners-parser/CodeOwnersParser/Azure.Sdk.Tools.CodeOwnersParser.csproj
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
- net6.0
- enable
- Nullable
- false
-
-
-
-
-
-
-
-
diff --git a/tools/code-owners-parser/CodeOwnersParser/CodeownersEntry.cs b/tools/code-owners-parser/CodeOwnersParser/CodeownersEntry.cs
deleted file mode 100644
index 782adfd7e00..00000000000
--- a/tools/code-owners-parser/CodeOwnersParser/CodeownersEntry.cs
+++ /dev/null
@@ -1,259 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-
-namespace Azure.Sdk.Tools.CodeOwnersParser
-{
- ///
- /// The entry for CODEOWNERS has the following structure:
- ///
- ///
- /// # PRLabel: %Label
- /// # ServiceLabel: %Label
- /// path @owner @owner
- ///
- ///
- public class CodeownersEntry
- {
- const char LabelSeparator = '%';
- const char OwnerSeparator = '@';
- public const string PRLabelMoniker = "PRLabel";
- public const string ServiceLabelMoniker = "ServiceLabel";
- public const string MissingFolder = "#//";
-
- public string PathExpression { get; set; } = "";
-
- public bool ContainsWildcard => PathExpression.Contains('*');
-
- public List Owners { get; set; } = new List();
-
- public List PRLabels { get; set; } = new List();
-
- public List ServiceLabels { get; set; } = new List();
-
- public bool IsValid => !string.IsNullOrWhiteSpace(PathExpression);
-
- public CodeownersEntry()
- {
- }
-
- public CodeownersEntry(string pathExpression, List owners)
- {
- PathExpression = pathExpression;
- Owners = owners;
- }
-
- private static string[] SplitLine(string line, char splitOn)
- => line.Split(new char[] { splitOn }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
-
- public override string ToString()
- => $"HasWildcard:{ContainsWildcard} Expression:{PathExpression} " +
- $"Owners:{string.Join(",", Owners)} PRLabels:{string.Join(",", PRLabels)} " +
- $"ServiceLabels:{string.Join(",", ServiceLabels)}";
-
- public bool ProcessLabelsOnLine(string line)
- {
- if (line.Contains(PRLabelMoniker, StringComparison.OrdinalIgnoreCase))
- {
- PRLabels.AddRange(ParseLabels(line, PRLabelMoniker));
- return true;
- }
- else if (line.Contains(ServiceLabelMoniker, StringComparison.OrdinalIgnoreCase))
- {
- ServiceLabels.AddRange(ParseLabels(line, ServiceLabelMoniker));
- return true;
- }
- return false;
- }
-
- private static IEnumerable ParseLabels(string line, string moniker)
- {
- // Parse a line that looks like # PRLabel: %Label, %Label
- if (!line.Contains(moniker, StringComparison.OrdinalIgnoreCase))
- {
- yield break;
- }
-
- // If we don't have a ':', nothing to do
- int colonPosition = line.IndexOf(':');
- if (colonPosition == -1)
- {
- yield break;
- }
-
- line = line[(colonPosition + 1)..].Trim();
- foreach (string label in SplitLine(line, LabelSeparator).ToList())
- {
- yield return label;
- }
- }
-
- public void ParseOwnersAndPath(string line, TeamUserHolder teamUserHolder)
- {
- if (
- string.IsNullOrEmpty(line)
- || (IsComment(line)
- && !line.Contains(
- CodeownersEntry.MissingFolder, StringComparison.OrdinalIgnoreCase)))
- {
- return;
- }
-
- line = ParsePath(line);
- line = RemoveCommentIfAny(line);
-
- // If the line doesn't contain the OwnerSeparator AKA no owners, then the foreach loop below
- // won't work. For example, the following line would end up causing "/sdk/communication" to
- // be added as an owner when one is not listed
- // /sdk/communication/
- if (line.Contains(OwnerSeparator))
- {
- foreach (string author in SplitLine(line, OwnerSeparator).ToList())
- {
- // If the author is a team, get the user list and add that to the Owners
- if (!IsGitHubUserAlias(author))
- {
- var teamUsers = teamUserHolder.GetUsersForTeam(author);
- // If the team is found in team user data, add the list of users to
- // the owners and ensure the end result is a distinct list
- if (teamUsers.Count > 0)
- {
- // The union of the two lists will ensure the result a distinct list
- Owners = Owners.Union(teamUsers).ToList();
- }
- // Else, the team user data did not contain an entry or there were no user
- // for the team. In that case, just add the team to the list of authors
- else
- {
- Owners.Add(author);
- }
- }
- // If the entry isn't a team, then just add it
- else
- {
- Owners.Add(author);
- }
- }
- }
- else
- {
- Console.WriteLine($"Warning: CODEOWNERS line '{line}' does not have an owner entry.");
- }
- }
-
- private static bool IsComment(string line)
- => line.StartsWith("#");
-
- private static string RemoveCommentIfAny(string line)
- {
- // this is the case when we have something like @user #comment
-
- int commentIndex = line.IndexOf("#", StringComparison.OrdinalIgnoreCase);
-
- if (commentIndex >= 0)
- line = line[..commentIndex].Trim();
-
- return line;
- }
-
- private string ParsePath(string line)
- {
- // Get the start of the owner in the string
- int ownerStartPosition = line.IndexOf('@');
- if (ownerStartPosition == -1)
- {
- return line;
- }
-
- string path = line[..ownerStartPosition].Trim();
- // the first entry is the path/regex
- PathExpression = path;
-
- // remove the path from the string.
- return line[ownerStartPosition..];
- }
-
- ///
- /// Remove all code owners which are not github alias.
- ///
- public void ExcludeNonUserAliases()
- => Owners.RemoveAll(r => !IsGitHubUserAlias(r));
-
- ///
- /// Helper method to check if it is valid github alias.
- ///
- /// Alias string.
- /// True if it is a github alias, Otherwise false.
- private static bool IsGitHubUserAlias(string alias)
- {
- // We used to call the github users api but we often got 403 returned
- // due to rate limiting. So instead we are approximating the check
- // by check for a slash in the name if there is one then we will consider
- // it to be a team instead of a users.
- if (alias.Contains('/'))
- {
- return false;
- }
- return true;
- }
-
- protected bool Equals(CodeownersEntry other)
- => PathExpression == other.PathExpression
- && Owners.SequenceEqual(other.Owners)
- && PRLabels.SequenceEqual(other.PRLabels)
- && ServiceLabels.SequenceEqual(other.ServiceLabels);
-
- public override bool Equals(object? obj)
- {
- // @formatter:off
- if (ReferenceEquals(null, obj)) return false;
- if (ReferenceEquals(this, obj)) return true;
- if (obj.GetType() != this.GetType()) return false;
- return Equals((CodeownersEntry)obj);
- // @formatter:on
- }
-
- ///
- /// Implementation of GetHashCode that properly hashes collections.
- /// Implementation based on
- /// https://stackoverflow.com/a/10567544/986533
- ///
- /// This implementation is candidate to be moved to:
- /// https://github.com/Azure/azure-sdk-tools/issues/5281
- ///
- public override int GetHashCode()
- {
- int hashCode = 0;
- // ReSharper disable NonReadonlyMemberInGetHashCode
- hashCode = AddHashCodeForObject(hashCode, PathExpression);
- hashCode = AddHashCodeForEnumerable(hashCode, Owners);
- hashCode = AddHashCodeForEnumerable(hashCode, PRLabels);
- hashCode = AddHashCodeForEnumerable(hashCode, ServiceLabels);
- // ReSharper restore NonReadonlyMemberInGetHashCode
- return hashCode;
-
- // ReSharper disable once VariableHidesOuterVariable
- int AddHashCodeForEnumerable(int hashCode, IEnumerable enumerable)
- {
- foreach (var item in enumerable)
- {
- hashCode = AddHashCodeForObject(hashCode, item);
- }
- return hashCode;
- }
-
- int AddHashCodeForObject(int hc, object item)
- {
- // Based on https://stackoverflow.com/a/10567544/986533
- hc ^= item.GetHashCode();
- hc = (hc << 7) |
- (hc >> (32 - 7)); // rotate hashCode to the left to swipe over all bits
- return hc;
- }
- }
- }
-}
diff --git a/tools/code-owners-parser/CodeOwnersParser/CodeownersFile.cs b/tools/code-owners-parser/CodeOwnersParser/CodeownersFile.cs
deleted file mode 100644
index 110f001751a..00000000000
--- a/tools/code-owners-parser/CodeOwnersParser/CodeownersFile.cs
+++ /dev/null
@@ -1,127 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Text.Json;
-
-namespace Azure.Sdk.Tools.CodeOwnersParser
-{
- public static class CodeownersFile
- {
-
- public static List GetCodeownersEntriesFromFileOrUrl(
- string codeownersFilePathOrUrl,
- string? teamStorageURI = null)
- {
- string content = FileHelpers.GetFileOrUrlContents(codeownersFilePathOrUrl);
- return GetCodeownersEntries(content, teamStorageURI);
- }
-
- public static List GetCodeownersEntries(string codeownersContent, string? teamStorageURI = null)
- {
- TeamUserHolder teamUserHolder = new TeamUserHolder(teamStorageURI);
- List entries = new List();
-
- // We are going to read line by line until we find a line that is not a comment
- // OR that is using the placeholder entry inside the comment.
- // while we are trying to find the folder entry, we parse all comment lines
- // to extract the labels from it. when we find the path or placeholder,
- // we add the completed entry and create a new one.
- CodeownersEntry entry = new CodeownersEntry();
- using StringReader sr = new StringReader(codeownersContent);
- while (sr.ReadLine() is { } line)
- {
- entry = ProcessCodeownersLine(line, entry, entries, teamUserHolder);
- }
-
- return entries;
- }
-
- public static CodeownersEntry GetMatchingCodeownersEntry(
- string targetPath,
- string codeownersFilePathOrUrl,
- string? teamStorageURI = null)
- {
- var codeownersEntries = GetCodeownersEntriesFromFileOrUrl(codeownersFilePathOrUrl, teamStorageURI);
- return GetMatchingCodeownersEntry(targetPath, codeownersEntries);
- }
-
- public static Dictionary GetMatchingCodeownersEntries(
- GlobFilePath targetPath,
- string targetDir,
- string codeownersFilePathOrUrl,
- string[]? ignoredPathPrefixes = null,
- string? teamStorageURI = null)
- {
- ignoredPathPrefixes ??= Array.Empty();
-
- var codeownersEntries = GetCodeownersEntriesFromFileOrUrl(codeownersFilePathOrUrl, teamStorageURI);
-
- Dictionary codeownersEntriesByPath = targetPath
- .ResolveGlob(targetDir, ignoredPathPrefixes)
- .ToDictionary(
- path => path,
- path => GetMatchingCodeownersEntry(
- path,
- codeownersEntries));
-
- return codeownersEntriesByPath;
- }
-
- public static CodeownersEntry GetMatchingCodeownersEntry(
- string targetPath,
- List codeownersEntries)
- {
- Debug.Assert(targetPath != null);
- return new MatchedCodeownersEntry(targetPath, codeownersEntries).Value;
- }
-
- private static CodeownersEntry ProcessCodeownersLine(
- string line,
- CodeownersEntry entry,
- List entries,
- TeamUserHolder teamUserHolder)
- {
- line = NormalizeLine(line);
-
- if (string.IsNullOrWhiteSpace(line))
- {
- return entry;
- }
-
- if (!IsCommentLine(line) || (IsCommentLine(line) && IsPlaceholderEntry(line)))
- {
- entry.ParseOwnersAndPath(line, teamUserHolder);
-
- if (entry.IsValid)
- entries.Add(entry);
-
- // An entry has ended, as we got to a path: real bath or placeholder path.
- return new CodeownersEntry();
- }
-
- if (IsCommentLine(line))
- {
- // try to process the line in case there are markers that need to be extracted
- entry.ProcessLabelsOnLine(line);
- return entry;
- }
-
- throw new InvalidOperationException(
- $"This case shouldn't be possible. line: '{line}'");
- }
-
- private static bool IsPlaceholderEntry(string line)
- => line.Contains(CodeownersEntry.MissingFolder, StringComparison.OrdinalIgnoreCase);
-
- private static bool IsCommentLine(string line)
- => line.StartsWith("#");
-
- private static string NormalizeLine(string line)
- => !string.IsNullOrEmpty(line)
- // Remove tabs and trim extra whitespace
- ? line.Replace('\t', ' ').Trim()
- : line;
- }
-}
diff --git a/tools/code-owners-parser/CodeOwnersParser/DefaultStorageConstants.cs b/tools/code-owners-parser/CodeOwnersParser/DefaultStorageConstants.cs
deleted file mode 100644
index dfc25d9b957..00000000000
--- a/tools/code-owners-parser/CodeOwnersParser/DefaultStorageConstants.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Azure.Sdk.Tools.CodeOwnersParser
-{
- public class DefaultStorageConstants
- {
- public const string DefaultStorageURI = "https://azuresdkartifacts.blob.core.windows.net/azure-sdk-write-teams/azure-sdk-write-teams-blob";
- }
-}
diff --git a/tools/code-owners-parser/CodeOwnersParser/FileHelpers.cs b/tools/code-owners-parser/CodeOwnersParser/FileHelpers.cs
deleted file mode 100644
index 8cf238a9224..00000000000
--- a/tools/code-owners-parser/CodeOwnersParser/FileHelpers.cs
+++ /dev/null
@@ -1,64 +0,0 @@
-using System;
-using System.IO;
-using System.Net;
-using System.Net.Http;
-
-namespace Azure.Sdk.Tools.CodeOwnersParser
-{
- public static class FileHelpers
- {
- public static string GetFileOrUrlContents(string fileOrUrl)
- {
- if (fileOrUrl.StartsWith("https"))
- return GetUrlContents(fileOrUrl);
-
- string fullPath = Path.GetFullPath(fileOrUrl);
- if (File.Exists(fullPath))
- return File.ReadAllText(fullPath);
-
- throw new ArgumentException(
- "The path provided is neither local path nor https link. " +
- $"Please check your path: '{fileOrUrl}' resolved to '{fullPath}'.");
- }
-
- private static string GetUrlContents(string url)
- {
- int maxRetries = 3;
- int attempts = 1;
- int delayTimeInMs = 1000;
- using HttpClient client = new HttpClient();
- while (attempts <= maxRetries)
- {
- try
- {
- HttpResponseMessage response = client.GetAsync(url).ConfigureAwait(false).GetAwaiter().GetResult();
- if (response.StatusCode == HttpStatusCode.OK)
- {
- // This writeline is probably unnecessary but good to have if there are previous attempts that failed
- Console.WriteLine($"GetUrlContents for {url} attempt number {attempts} succeeded.");
- return response.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult();
- }
- else
- {
- Console.WriteLine($"GetUrlContents attempt number {attempts}. Non-{HttpStatusCode.OK} status code trying to fetch {url}. Status Code = {response.StatusCode}");
- }
- }
- catch (HttpRequestException httpReqEx)
- {
- // HttpRequestException means the request failed due to an underlying issue such as network connectivity,
- // DNS failure, server certificate validation or timeout.
- Console.WriteLine($"GetUrlContents attempt number {attempts}. HttpRequestException trying to fetch {url}. Exception message = {httpReqEx.Message}");
- if (attempts == maxRetries)
- {
- // At this point the retries have been exhausted, let this rethrow
- throw;
- }
- }
- System.Threading.Thread.Sleep(delayTimeInMs);
- attempts++;
- }
- // This will only get hit if the final retry is non-OK status code
- throw new FileLoadException($"Unable to fetch {url} after {maxRetries}. See above for status codes for each attempt.");
- }
- }
-}
diff --git a/tools/code-owners-parser/CodeOwnersParser/GlobFilePath.cs b/tools/code-owners-parser/CodeOwnersParser/GlobFilePath.cs
deleted file mode 100644
index 2529be24bde..00000000000
--- a/tools/code-owners-parser/CodeOwnersParser/GlobFilePath.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using Microsoft.Extensions.FileSystemGlobbing;
-
-namespace Azure.Sdk.Tools.CodeOwnersParser;
-
-public class GlobFilePath
-{
- private readonly string filePath;
-
- public GlobFilePath(string globFilePath)
- {
- Debug.Assert(globFilePath.IsGlobFilePath());
- this.filePath = globFilePath;
- }
-
- ///
- /// The '*' is the only character that can denote glob pattern
- /// in the used globbing library, per:
- /// - https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.filesystemglobbing.matcher?view=dotnet-plat-ext-7.0#remarks
- /// - https://learn.microsoft.com/en-us/dotnet/core/extensions/file-globbing#pattern-formats
- ///
- public static bool IsGlobFilePath(string path)
- => path.Contains('*');
-
- public List ResolveGlob(string directoryPath, string[]? ignoredPathPrefixes)
- {
- ignoredPathPrefixes ??= Array.Empty();
-
- var globMatcher = new Matcher(StringComparison.Ordinal);
- globMatcher.AddInclude(this.filePath);
-
- List matchedPaths = globMatcher.GetResultsInFullPath(directoryPath).ToList();
-
- matchedPaths = matchedPaths
- .Select(path => Path.GetRelativePath(directoryPath, path).Replace("\\", "/"))
- .Where(path => ignoredPathPrefixes.All(prefix => !path.StartsWith(prefix)))
- .ToList();
-
- return matchedPaths;
- }
-}
diff --git a/tools/code-owners-parser/CodeOwnersParser/MatchedCodeownersEntry.cs b/tools/code-owners-parser/CodeOwnersParser/MatchedCodeownersEntry.cs
deleted file mode 100644
index a719576029d..00000000000
--- a/tools/code-owners-parser/CodeOwnersParser/MatchedCodeownersEntry.cs
+++ /dev/null
@@ -1,325 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using System.Text.RegularExpressions;
-
-namespace Azure.Sdk.Tools.CodeOwnersParser
-{
- ///
- /// Represents a CODEOWNERS file entry that matched to targetPath from
- /// the list of entries, assumed to have been parsed from CODEOWNERS file.
- ///
- /// To use this class, construct it, passing as input relevant paths.
- /// Then, to obtain the value of the matched entry, reference "Value" member.
- ///
- /// This class uses a regex-based wildcard-supporting (* and **) matcher.
- ///
- /// This matcher reflects the matching behavior of the built-in GitHub CODEOWNERS interpreter,
- /// but with additional assumptions imposed about the paths present in CODEOWNERS, as guaranteed
- /// by CODEOWNERS file validation:
- /// https://github.com/Azure/azure-sdk-tools/issues/4859
- /// These assumptions are checked by IsCodeownersPathValid() method.
- /// If violated, given CODEOWNERS path will be always ignored by the matcher, never matching anything.
- /// As a result, this matcher is effectively a subset of GitHub CODEOWNERS matcher.
- ///
- /// The validation spec is given in this comment:
- /// https://github.com/Azure/azure-sdk-tools/issues/4859#issuecomment-1370360622
- /// See also RetrieveCodeOwnersProgramTests and CodeownersFileTests tests.
- ///
- /// Reference:
- /// https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#codeowners-syntax
- /// https://git-scm.com/docs/gitignore#_pattern_format
- ///
- public class MatchedCodeownersEntry
- {
- ///
- /// Token for temporarily substituting "**" in regex, to avoid it being escaped when
- /// Regex.Escape is called.
- ///
- private const string DoubleStar = "_DOUBLE_STAR_";
- ///
- /// Token for temporarily substituting "*" in regex, to avoid it being escaped when
- /// Regex.Escape is called.
- ///
- private const string SingleStar = "_SINGLE_STAR_";
-
- public readonly CodeownersEntry Value;
-
- ///
- /// See comment on IsCodeownersPathValid
- ///
- public bool IsValid => IsCodeownersPathValid(this.Value.PathExpression);
-
- ///
- /// See the class comment to understand this method purpose.
- ///
- public static bool IsCodeownersPathValid(string codeownersPathExpression)
- => !IsCommentedOutPath(codeownersPathExpression)
- && !ContainsUnsupportedCharacters(codeownersPathExpression)
- && !ContainsUnsupportedSequences(codeownersPathExpression);
-
- ///
- /// Any CODEOWNERS path with these characters will be skipped.
- /// Note these are valid parts of file paths, but we are not supporting
- /// them to simplify the matcher logic.
- ///
- private static readonly char[] unsupportedChars = { '[', ']', '!', '?' };
-
- public MatchedCodeownersEntry(string targetPath, List codeownersEntries)
- {
- this.Value = GetMatchingCodeownersEntry(targetPath, codeownersEntries);
- }
-
- ///
- /// Returns a CodeownersEntry from codeownersEntries, after normalization and validation,
- /// that match targetPath per algorithm described in the GitHub CODEOWNERS reference,
- /// as linked to in this class comment.
- ///
- /// Paths that are not valid after normalization are skipped from matching.
- ///
- /// If there is no match, this method returns "new CodeownersEntry()".
- ///
- /// For definition of "normalization", see NormalizePath().
- /// For definition of "validation", see IsCodeownersPathValid().
- /// You can also refer to the validation spec linked from this class comment.
- ///
- private CodeownersEntry GetMatchingCodeownersEntry(
- string targetPath,
- List codeownersEntries)
- {
- if (targetPath.Contains('*'))
- {
- Console.Error.WriteLine(
- $"Target path \"{targetPath}\" contains star ('*') which is not supported. "
- + "Returning no match without checking for ownership.");
- return NoMatchCodeownersEntry;
- }
-
- // targetPath is assumed to be absolute w.r.t. repository root, hence we ensure
- // it starts with "/" to denote that.
- if (!targetPath.StartsWith("/"))
- targetPath = "/" + targetPath;
-
- // Note we cannot add or trim the slash at the end of targetPath.
- // Slash at the end of target path denotes it is a directory, not a file,
- // so it can not match against a CODEOWNERS entry that is guaranteed to be a file,
- // by the virtue of not ending with "/".
-
- CodeownersEntry matchedEntry = codeownersEntries
- .Where(entry => IsCodeownersPathValid(entry.PathExpression))
- // Entries listed in CODEOWNERS file below take precedence, hence we read the file from the bottom up.
- // By convention, entries in CODEOWNERS should be sorted top-down in the order of:
- // - 'RepoPath',
- // - 'ServicePath'
- // - and then 'PackagePath'.
- // However, due to lack of validation, as of 12/29/2022 this is not always the case.
- .Reverse()
- .FirstOrDefault(
- entry => Matches(targetPath, entry),
- // assert: none of the codeownersEntries matched targetPath
- NoMatchCodeownersEntry);
-
- return matchedEntry;
- }
-
- private CodeownersEntry NoMatchCodeownersEntry { get; } = new CodeownersEntry();
-
- ///
- /// We do not output any error message in case the path is commented out,
- /// as a) such paths are expected to be processed and discarded by this logic
- /// and b) outputting error messages would possibly result in output
- /// truncation, truncating the resulting json, and thus making the output of the
- /// calling tool malformed.
- ///
- private static bool IsCommentedOutPath(string codeownersPathExpression)
- => codeownersPathExpression.Trim().StartsWith("#");
-
- ///
- /// See the comment on unsupportedChars.
- ///
- private static bool ContainsUnsupportedCharacters(string codeownersPath)
- {
- var contains = unsupportedChars.Any(codeownersPath.Contains);
- if (contains)
- {
- Console.Error.WriteLine(
- $"CODEOWNERS path \"{codeownersPath}\" contains unsupported characters: " +
- string.Join(' ', unsupportedChars) +
- " Because of that this path will never match.");
- }
- return contains;
- }
-
- private static bool ContainsUnsupportedSequences(string codeownersPath)
- {
- if (codeownersPath == "/")
- {
- // This behavior matches GitHub CODEOWNERS interpreter behavior.
- // I.e. a path of just "/" is unsupported.
- Console.Error.WriteLine(
- $"CODEOWNERS path \"{codeownersPath}\" will never match. " +
- "Use \"/**\" instead.");
- return true;
- }
-
- // See the comment below on why we support this path.
- if (codeownersPath == "/**")
- return false;
-
- if (!codeownersPath.StartsWith("/"))
- {
- Console.Error.WriteLine(
- $"CODEOWNERS path \"{codeownersPath}\" does not start with " +
- "\"/\". Prefix it with \"/\". " +
- "Until then this path will never match.");
- return true;
- }
-
- // We do not support suffix of "/**" because it is equivalent to "/".
- // For example, "/foo/**" is equivalent to "/foo/"
- // One exception to this rule is if the entire path is "/**":
- // GitHub doesn't match "/" to anything if it is the entire path,
- // and instead expects "/**".
- if (codeownersPath != "/**" && codeownersPath.EndsWith("/**"))
- {
- Console.Error.WriteLine(
- $"CODEOWNERS path \"{codeownersPath}\" ends with " +
- "unsupported sequence of \"/**\". Replace it with \"/\". " +
- "Until then this path will never match.");
- return true;
- }
-
- // We do not support suffix of "/**/" because it is equivalent to
- // suffix "/**" which is equivalent to suffix "/".
- if (codeownersPath.EndsWith("/**/"))
- {
- Console.Error.WriteLine(
- $"CODEOWNERS path \"{codeownersPath}\" ends with " +
- "unsupported sequence of \"/**/\". Replace it with \"/\". " +
- "Until then this path will never match.");
- return true;
- }
-
- // We do not support inline "**", i.e. if it is not within slashes, i.e. "/**/".
- // Any inline "**" like "/a**/" or "/**a/" or "/a**b/"
- // would be equivalent to single star, hence we forbid double star, to avoid confusion.
- if (codeownersPath.Replace("/**/", "").Contains("**"))
- {
- Console.Error.WriteLine(
- $"CODEOWNERS path \"{codeownersPath}\" contains " +
- "unsupported sequence of \"**\". Double star can be used only within slashes \"/**/\" " +
- "or as a top-level catch all path of \"/**\". " +
- "Currently this path will never match.");
- return true;
- }
-
- return false;
- }
-
- ///
- /// Returns true if the regex expression representing the PathExpression
- /// of CODEOWNERS entry matches a prefix of targetPath.
- ///
- private bool Matches(string targetPath, CodeownersEntry entry)
- {
- string codeownersPath = entry.PathExpression;
-
- Regex regex = ConvertToRegex(codeownersPath);
- // Is prefix match. I.e. it will return true if the regex matches
- // a prefix of targetPath.
- return regex.IsMatch(targetPath);
- }
-
- private Regex ConvertToRegex(string codeownersPath)
- {
- Trace.Assert(IsCodeownersPathValid(codeownersPath));
-
- // Special case: path "/**" matches everything.
- // We do not allow "/**" in any other context except when it is the entire path.
- if (codeownersPath == "/**")
- return new Regex(".*");
-
- string pattern = codeownersPath;
-
- if (codeownersPath.Contains(SingleStar) || pattern.Contains(DoubleStar))
- {
- Console.Error.WriteLine(
- $"CODEOWNERS path \"{codeownersPath}\" contains reserved phrases: " +
- $"\"{DoubleStar}\" or \"{SingleStar}\"");
- }
-
- // We replace "/**/", not "**", because we disallow "**" in any other context.
- // Specifically:
- // - because we normalize the path to start with "/", any prefix "**/" is
- // effectively "/**/";
- // - any suffix "/**", for reasons explained within ContainsUnsupportedSequences().
- // - any inline "**", for reasons explained within ContainsUnsupportedSequences().
- pattern = pattern.Replace("/**/", "/" + DoubleStar + "/");
- pattern = pattern.Replace("*", SingleStar);
-
- pattern = Regex.Escape(pattern);
-
- // Denote that all paths are absolute by pre-pending "beginning of string" symbol.
- pattern = "^" + pattern;
-
- pattern = SetPatternSuffix(pattern);
-
- pattern = pattern.Replace($"/{DoubleStar}/", "((/.*/)|/)");
- pattern = pattern.Replace(SingleStar, "([^/]*)");
-
- return new Regex(pattern);
- }
-
- ///
- /// Sets the regex pattern suffix, which can be either "$" (end of string)
- /// or nothing.
- ///
- /// GitHub's CODEOWNERS matching logic is a bit inconsistent when it comes to handling suffixes.
- ///
- /// In a nutshell:
- /// - For top level dir, `/` doesn't work. One has to use `/**`.
- /// - But for nested dirs, `/` works. I.e. one can write `/foo/` and it is equivalent to `/foo/**`.
- /// - `*` has different interpretations. If used with preceding slash, like `/*` or `/foo/*`
- /// it means "things only in this dir". But when used as a suffix, it means "anything".
- /// So `/foo*` is effectively `/foo*/** OR /foo*`. Where `*` in the OR clause
- /// should be interpreted as "any character except `/`".
- ///
- private static string SetPatternSuffix(string pattern)
- {
- // If a pattern ends with "/*" this means it should match only files
- // in the child directory, but not all descendant directories.
- // Hence we must append "$", to avoid treating the regex pattern
- // as a prefix match and instead treat it as an exact match.
- if (pattern.EndsWith($"/{SingleStar}"))
- return pattern + "$";
-
- Trace.Assert(pattern != "^/", "Path \"/\" should have been excluded by validation.");
-
- // If the pattern ends with "/" it means it is a path to a directory,
- // like "/foo/". This means "match everything in this directory,
- // at arbitrary directory nesting depth."
- //
- // If the pattern ends with "*" but not "/*" (as this case was handled above)
- // then it is a suffix *, e.g. "/foo*". This means "match everything
- // with a prefix string of "/foo". Notably, it matches not only
- // everything in "/foo/" dir, but also files like "/foobar.txt"
- if (pattern.EndsWith("/") || pattern.EndsWith(SingleStar))
- return pattern;
-
- // If the pattern doesn't end with "/" nor "*", then according to GitHub CODEOWNERS
- // matcher it is a path to a file, or to a directory with exact match.
- // However, in this matcher we assume stricter interpretation where
- // it has to be a path to a file.
- //
- // As a result we append "$", to avoid treating the regex pattern
- // as a prefix match and instead treat it as an exact match.
- //
- // If that assumption is violated, i.e. the CODEOWNERS path is actually
- // a path to a directory, due to appending "$" the result will be no match,
- // e.g. targetPath of "/foo/bar" is a no match against "/foo$".
- // This is the desired behavior, as we don't want to match against invalid paths.
- return pattern + "$";
- }
- }
-}
diff --git a/tools/code-owners-parser/CodeOwnersParser/PathExtensions.cs b/tools/code-owners-parser/CodeOwnersParser/PathExtensions.cs
deleted file mode 100644
index 4167b65d7f1..00000000000
--- a/tools/code-owners-parser/CodeOwnersParser/PathExtensions.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Azure.Sdk.Tools.CodeOwnersParser;
-
-public static class PathExtensions
-{
- public static bool IsGlobFilePath(this string path)
- => GlobFilePath.IsGlobFilePath(path);
-}
diff --git a/tools/code-owners-parser/CodeOwnersParser/TeamUserHolder.cs b/tools/code-owners-parser/CodeOwnersParser/TeamUserHolder.cs
deleted file mode 100644
index 4a3a7c4ba60..00000000000
--- a/tools/code-owners-parser/CodeOwnersParser/TeamUserHolder.cs
+++ /dev/null
@@ -1,79 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-using System.Threading.Tasks;
-
-namespace Azure.Sdk.Tools.CodeOwnersParser
-{
- public class TeamUserHolder
- {
- private string TeamUserStorageURI { get; set; } = DefaultStorageConstants.DefaultStorageURI;
- private Dictionary>? _teamUserDict = null;
-
- public Dictionary> TeamUserDict
- {
- get
- {
- if (_teamUserDict == null)
- {
- _teamUserDict = GetTeamUserData();
- }
- return _teamUserDict;
- }
- set
- {
- _teamUserDict = value;
- }
- }
-
- public TeamUserHolder(string? teamUserStorageURI)
- {
- if (!string.IsNullOrWhiteSpace(teamUserStorageURI))
- {
- TeamUserStorageURI = teamUserStorageURI;
- }
- }
-
- private Dictionary> GetTeamUserData()
- {
- if (null == _teamUserDict)
- {
- string rawJson = FileHelpers.GetFileOrUrlContents(TeamUserStorageURI);
- var list = JsonSerializer.Deserialize>>>(rawJson);
- if (null != list)
- {
- return list.ToDictionary((keyItem) => keyItem.Key, (valueItem) => valueItem.Value);
- }
- Console.WriteLine($"Error! Unable to deserialize json team/user data from {TeamUserStorageURI}. rawJson={rawJson}");
- return new Dictionary>();
- }
- return _teamUserDict;
- }
-
- public List GetUsersForTeam(string teamName)
- {
- // The teamName in the codeowners file should be in the form /.
- // The dictionary's team names do not contain the org so the org needs to
- // be stripped off. Handle the case where the teamName passed in does and
- // does not being with @org/
- string teamWithoutOrg = teamName.Trim();
- if (teamWithoutOrg.Contains('/'))
- {
- teamWithoutOrg = teamWithoutOrg.Split("/")[1];
- }
- if (TeamUserDict != null)
- {
- if (TeamUserDict.ContainsKey(teamWithoutOrg))
- {
- Console.WriteLine($"Found team entry for {teamWithoutOrg}");
- return TeamUserDict[teamWithoutOrg];
- }
- Console.WriteLine($"Warning: TeamUserDictionary did not contain a team entry for {teamWithoutOrg}");
- }
- return new List();
- }
- }
-}
diff --git a/tools/github-event-processor/ci.yml b/tools/github-event-processor/ci.yml
index dc06966fb26..f55e574bdfd 100644
--- a/tools/github-event-processor/ci.yml
+++ b/tools/github-event-processor/ci.yml
@@ -6,7 +6,7 @@ trigger:
paths:
include:
- tools/github-event-processor
- - tools/code-owners-parser/CodeOwnersParser
+ - tools/codeowners-utils/Azure.Sdk.Tools.CodeownersUtils
pr:
branches:
@@ -18,7 +18,7 @@ pr:
paths:
include:
- tools/github-event-processor
- - tools/code-owners-parser/CodeOwnersParser
+ - tools/codeowners-utils/Azure.Sdk.Tools.CodeownersUtils
extends:
template: /eng/pipelines/templates/stages/archetype-sdk-tool-dotnet.yml