-
Notifications
You must be signed in to change notification settings - Fork 185
/
Copy pathCodeOwnersFile.cs
106 lines (87 loc) · 3.97 KB
/
CodeOwnersFile.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
using OutputColorizer;
using System;
using System.Collections.Generic;
using System.IO;
namespace Azure.Sdk.Tools.CodeOwnersParser
{
public static class CodeOwnersFile
{
public static List<CodeOwnerEntry> ParseFile(string filePathOrUrl)
{
string content;
content = FileHelpers.GetFileContents(filePathOrUrl);
return ParseContent(content);
}
public static List<CodeOwnerEntry> ParseContent(string fileContent)
{
List<CodeOwnerEntry> entries = new List<CodeOwnerEntry>();
string line;
// An entry ends when we get to a path (a real path or a commented dummy path)
using (StringReader sr = new StringReader(fileContent))
{
CodeOwnerEntry entry = new CodeOwnerEntry();
// 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.
while ((line = sr.ReadLine()) != null)
{
line = NormalizeLine(line);
if (string.IsNullOrWhiteSpace(line))
{
continue;
}
if (!line.StartsWith("#") || line.IndexOf(CodeOwnerEntry.MissingFolder, System.StringComparison.OrdinalIgnoreCase) >= 0)
{
// If this is not a comment line OR this is a placeholder entry
entry.ParseOwnersAndPath(line);
// only add it if it is a valid entry
if (entry.IsValid)
{
entries.Add(entry);
}
// create a new entry.
entry = new CodeOwnerEntry();
}
else if (line.StartsWith("#"))
{
// try to process the line in case there are markers that need to be extracted
entry.ProcessLabelsOnLine(line);
}
}
}
return entries;
}
public static CodeOwnerEntry ParseAndFindOwnersForClosestMatch(string codeOwnersFilePathOrUrl, string targetPath)
{
var codeOwnerEntries = ParseFile(codeOwnersFilePathOrUrl);
return FindOwnersForClosestMatch(codeOwnerEntries, targetPath);
}
public static CodeOwnerEntry FindOwnersForClosestMatch(List<CodeOwnerEntry> codeOwnerEntries, string targetPath)
{
// Normalize the start and end of the paths by trimming slash
targetPath = targetPath.Trim('/');
// We want to find the match closest to the bottom of the codeowners file.
// CODEOWNERS sorts the paths in order of 'RepoPath', 'ServicePath' and then 'PackagePath'.
for (int i = codeOwnerEntries.Count - 1; i >= 0; i--)
{
string codeOwnerPath = codeOwnerEntries[i].PathExpression.Trim('/');
// Note that this only matches on paths without glob patterns which is good enough
// for our current scenarios but in the future might need to support globs
if (targetPath.StartsWith(codeOwnerPath, StringComparison.OrdinalIgnoreCase))
{
return codeOwnerEntries[i];
}
}
return new CodeOwnerEntry();
}
private static string NormalizeLine(string line)
{
if (string.IsNullOrEmpty(line))
{
return line;
}
// Remove tabs and trim extra whitespace
return line.Replace('\t', ' ').Trim();
}
}
}