-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathUtilities.cs
executable file
·310 lines (264 loc) · 11.1 KB
/
Utilities.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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
using System.Diagnostics;
using System.Text.RegularExpressions;
using CFI.Localization;
using CFI.Models;
namespace CFI;
public static class Utilities
{
// A pretty basic method that tries to get the name from the URL
public static string AttemptGetPackageName(string url)
{
string[] parts = url.Replace("/trunk", "").Split('/');
string name = parts[^1].Replace(".git", "").Replace(".svn", "");
return name;
}
public static Task<RepoTypes> GetRepoTypeAsync(string url) =>
Task.Run(() =>
{
ProcessStartInfo gitInfo = new("git", $"ls-remote {url}")
{
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false
};
Process gitProcess = Process.Start(gitInfo)!;
string gitOutput = gitProcess.StandardOutput.ReadToEnd();
gitProcess.WaitForExit();
// Assume git worked, will be set to a different value if it fails
RepoTypes repoType = RepoTypes.Git;
// If Git fails, try to use SVN to check the repository
if (gitProcess.ExitCode != 0)
{
ProcessStartInfo svnInfo = new("svn", $"info {url}")
{
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false
};
Process svnProcess = Process.Start(svnInfo)!;
string svnOutput = svnProcess.StandardOutput.ReadToEnd();
svnProcess.WaitForExit();
repoType = svnProcess.ExitCode == 0 ? RepoTypes.Svn : RepoTypes.Unknown;
}
return repoType;
});
private static Task<RepoTypes> CheckLocalRepoType(string directory)
{
ProcessStartInfo gitInfo = new("git", "rev-parse --is-inside-work-tree")
{
WorkingDirectory = directory,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false
};
Process gitProcess = Process.Start(gitInfo)!;
string gitOutput = gitProcess.StandardOutput.ReadToEnd();
gitProcess.WaitForExit();
if (gitProcess.ExitCode == 0)
{
return Task.FromResult(RepoTypes.Git);
}
ProcessStartInfo svnInfo = new("svn", "info")
{
WorkingDirectory = directory,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false
};
Process svnProcess = Process.Start(svnInfo)!;
string svnOutput = svnProcess.StandardOutput.ReadToEnd();
svnProcess.WaitForExit();
if (svnProcess.ExitCode == 0)
{
return Task.FromResult(RepoTypes.Svn);
}
return Task.FromResult(RepoTypes.Unknown);
}
public static async Task UpdateOrCloneAsync(ExternalRepo repo, string directory)
{
if (Directory.Exists(directory))
{
// Check if the directory is a Git repository with git rev-parse --is-inside-work-tree
ProcessStartInfo gitInfo = new("git", "rev-parse --is-inside-work-tree")
{
WorkingDirectory = directory,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false
};
Process gitProcess = Process.Start(gitInfo)!;
string gitOutput = gitProcess.StandardOutput.ReadToEnd();
gitProcess.WaitForExit();
if (gitProcess.ExitCode == 0)
{
// The directory is a Git repository, so we can use git pull
ProcessStartInfo gitPullInfo = new("git", "pull")
{
WorkingDirectory = directory,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false
};
Process gitPullProcess = Process.Start(gitPullInfo)!;
string gitPullOutput = gitPullProcess.StandardOutput.ReadToEnd();
gitPullProcess.WaitForExit();
if (gitPullProcess.ExitCode != 0)
{
Log.Error(Localizations.Current.ERROR_FAILED_REPO_CLONE, repo.ProjectName);
Console.WriteLine($"\n{gitPullProcess.StandardError.ReadToEnd()}\n");
}
else
{
Log.Information(Localizations.Current.INFO_SUCCESSFULLY_CLONED_REPO, repo.ProjectName);
}
}
else
{
// The directory is not a Git repository, so we can't update it
Log.Warning(Localizations.Current.WARNING_FOLDER_NOT_GIT_REPO_SKIPPING, repo.ProjectName);
}
}
else
{
await CloneRepoAsync(repo, directory);
}
}
public static async Task CloneRepoAsync(ExternalRepo repo, string directory)
{
repo.ProjectName = AttemptGetPackageName(repo.Url);
string? commit = repo.Commit;
string? tag = repo.Tag;
string command;
string executable;
try
{
// repo.RepoType is a task that checks if the repo is a Git
// or SVN repository using git ls-remote and svn info
RepoTypes repoType = await repo.RepoType;
if (repoType == RepoTypes.Git)
{
command = $"clone {repo.Url} {directory}/{repo.ProjectName} --depth 1";
executable = "git";
if (!string.IsNullOrEmpty(commit))
Log.Warning(Localizations.Current.WARNING_IGNORNING_UNSUPPORTED_REPO_PROPERTY, "externals->repo->commit");
if (!string.IsNullOrEmpty(tag))
command += $" --branch {tag}";
}
else if (repoType == RepoTypes.Svn)
{
command = $"checkout {repo.Url} {directory}/{repo.ProjectName} --depth=empty";
executable = "svn";
if (!string.IsNullOrEmpty(commit))
Log.Warning(Localizations.Current.WARNING_IGNORNING_UNSUPPORTED_REPO_PROPERTY, "externals->repo->commit");
if (!string.IsNullOrEmpty(tag))
Log.Warning(Localizations.Current.WARNING_IGNORNING_UNSUPPORTED_REPO_PROPERTY, "(for SVN) externals->repo->tag");
}
else
{
Log.Error(Localizations.Current.ERROR_SKIPPING_REPO, repo.Url);
return;
}
}
catch (Exception ex)
{
Log.Error(ex, Localizations.Current.ERROR_FAILED_REPO_CLONE, repo.Url);
return;
}
Log.Information(Localizations.Current.INFO_RUNNING_COMMAND, executable, command);
ProcessStartInfo info = new(executable, command)
{
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false
};
Process process = Process.Start(info)!;
process.WaitForExit();
if (process.ExitCode != 0)
{
Log.Error(Localizations.Current.ERROR_FAILED_REPO_CLONE, repo.Url);
Console.WriteLine($"\n{process.StandardError.ReadToEnd()}\n");
}
else
{
Log.Information(Localizations.Current.INFO_SUCCESSFULLY_CLONED_REPO, repo.ProjectName);
}
}
/// <summary>
/// Converts a glob-style wildcard pattern (e.g. "*.md") to an equivalent regex pattern.
/// For example:
/// "*.md" -> "^.*\\.md$"
/// "Makefile" -> "^Makefile$"
/// </summary>
private static string WildcardToRegex(string pattern)
{
// Escape all special regex chars, then replace \* with .*, etc.
// Regex.Escape("*.md") -> "\\*\\.md", then we fix the backslashes.
string escaped = Regex.Escape(pattern)
.Replace("\\*", ".*")
.Replace("\\?", ".");
return "^" + escaped + "$";
}
/// <summary>
/// Checks whether the given path matches any of the provided wildcard patterns.
/// </summary>
private static bool ShouldIgnore(string path, string[]? ignorePatterns)
{
// If there's no ignore pattern, never ignore
if (ignorePatterns == null || ignorePatterns.Length == 0)
return false;
// We will match against the full path (relative or absolute).
// If you only want to match the file name or folder name,
// you could do Path.GetFileName(path) or similar.
// Also note: to handle cross-platform directory separators,
// it can help to unify them:
string normalizedPath = path.Replace('\\', '/');
foreach (var pattern in ignorePatterns)
{
// Convert the wildcard to a regex
string regexPattern = WildcardToRegex(pattern);
// If you prefer ignoring case, add RegexOptions.IgnoreCase
if (Regex.IsMatch(normalizedPath, regexPattern, RegexOptions.IgnoreCase))
{
return true;
}
}
return false;
}
public static void CopyDirectoryWithIgnore(
string safe,
string source,
string destination,
string[]? ignorePatterns)
{
if (!Directory.Exists(source))
{
throw new ArgumentException($"Source directory does not exist: {source}", nameof(source));
}
// 1. Create all non-ignored subdirectories
// (Uses SearchOption.AllDirectories to find *every* subdir under source).
foreach (string dirPath in Directory.GetDirectories(source, "*", SearchOption.AllDirectories))
{
// Check if the directory should be ignored.
// If so, skip creating it in the destination.
if (ShouldIgnore(dirPath.Replace(safe, ""), ignorePatterns))
continue;
// Replace source path portion with destination path portion.
// This builds a parallel folder structure in 'destination'.
string destDirPath = dirPath.Replace(source, destination);
// Make sure the directory exists (Directory.CreateDirectory is idempotent).
Directory.CreateDirectory(destDirPath);
}
// 2. Copy all non-ignored files
// (Again enumerates all files in the entire directory tree).
foreach (string filePath in Directory.GetFiles(source, "*.*", SearchOption.AllDirectories))
{
if (ShouldIgnore(filePath.Replace(safe, ""), ignorePatterns))
continue;
// Build the destination file path by replacing the source folder portion.
string destFilePath = filePath.Replace(source, destination);
// Copy the file (overwrite if it already exists).
Directory.CreateDirectory(Path.GetDirectoryName(destFilePath)!);
File.Copy(filePath, destFilePath, overwrite: true);
}
}
}