Files
runner/src/Runner.Plugins/Repository/v1.0/RepositoryPlugin.cs
2022-10-18 10:54:08 -04:00

173 lines
8.4 KiB
C#

using System;
using System.Threading;
using System.Threading.Tasks;
using GitHub.Runner.Sdk;
using Pipelines = GitHub.DistributedTask.Pipelines;
using System.IO;
using System.Text.RegularExpressions;
using GitHub.DistributedTask.Pipelines.Expressions;
using System.Text;
namespace GitHub.Runner.Plugins.Repository.v1_0
{
public class CheckoutTask : IRunnerActionPlugin
{
private readonly Regex _validSha1 = new(@"\b[0-9a-f]{40}\b", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled, TimeSpan.FromSeconds(2));
public async Task RunAsync(RunnerActionPluginExecutionContext executionContext, CancellationToken token)
{
string runnerWorkspace = executionContext.GetRunnerContext("workspace");
ArgUtil.Directory(runnerWorkspace, nameof(runnerWorkspace));
string tempDirectory = executionContext.GetRunnerContext("temp");
ArgUtil.Directory(tempDirectory, nameof(tempDirectory));
var repoFullName = executionContext.GetInput(Pipelines.PipelineConstants.CheckoutTaskInputs.Repository);
if (string.IsNullOrEmpty(repoFullName))
{
repoFullName = executionContext.GetGitHubContext("repository");
}
var repoFullNameSplit = repoFullName.Split("/", StringSplitOptions.RemoveEmptyEntries);
if (repoFullNameSplit.Length != 2)
{
throw new ArgumentOutOfRangeException(repoFullName);
}
string expectRepoPath;
var path = executionContext.GetInput(Pipelines.PipelineConstants.CheckoutTaskInputs.Path);
if (!string.IsNullOrEmpty(path))
{
expectRepoPath = IOUtil.ResolvePath(runnerWorkspace, path);
if (!expectRepoPath.StartsWith(runnerWorkspace.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar))
{
throw new ArgumentException($"Input path '{path}' should resolve to a directory under '{runnerWorkspace}', current resolved path '{expectRepoPath}'.");
}
}
else
{
// When repository doesn't has path set, default to sources directory 1/repoName
expectRepoPath = Path.Combine(runnerWorkspace, repoFullNameSplit[1]);
}
var workspaceRepo = executionContext.GetGitHubContext("repository");
// for self repository, we need to let the worker knows where it is after checkout.
if (string.Equals(workspaceRepo, repoFullName, StringComparison.OrdinalIgnoreCase))
{
var workspaceRepoPath = executionContext.GetGitHubContext("workspace");
executionContext.Debug($"Repository requires to be placed at '{expectRepoPath}', current location is '{workspaceRepoPath}'");
if (!string.Equals(workspaceRepoPath.Trim(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), expectRepoPath.Trim(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), IOUtil.FilePathStringComparison))
{
executionContext.Output($"Repository is current at '{workspaceRepoPath}', move to '{expectRepoPath}'.");
var count = 1;
var staging = Path.Combine(tempDirectory, $"_{count}");
while (Directory.Exists(staging))
{
count++;
staging = Path.Combine(tempDirectory, $"_{count}");
}
try
{
executionContext.Debug($"Move existing repository '{workspaceRepoPath}' to '{expectRepoPath}' via staging directory '{staging}'.");
IOUtil.MoveDirectory(workspaceRepoPath, expectRepoPath, staging, CancellationToken.None);
}
catch (Exception ex)
{
executionContext.Debug("Catch exception during repository move.");
executionContext.Debug(ex.ToString());
executionContext.Warning("Unable move and reuse existing repository to required location.");
IOUtil.DeleteDirectory(expectRepoPath, CancellationToken.None);
}
executionContext.Output($"Repository will locate at '{expectRepoPath}'.");
}
executionContext.Debug($"Update workspace repository location.");
executionContext.SetRepositoryPath(repoFullName, expectRepoPath, true);
}
string sourceBranch;
string sourceVersion;
string refInput = executionContext.GetInput(Pipelines.PipelineConstants.CheckoutTaskInputs.Ref);
if (string.IsNullOrEmpty(refInput))
{
sourceBranch = executionContext.GetGitHubContext("ref");
sourceVersion = executionContext.GetGitHubContext("sha");
}
else
{
sourceBranch = refInput;
sourceVersion = executionContext.GetInput(Pipelines.PipelineConstants.CheckoutTaskInputs.Version); // version get removed when checkout move to repo in the graph
if (string.IsNullOrEmpty(sourceVersion) && RegexUtility.IsMatch(sourceBranch, WellKnownRegularExpressions.SHA1))
{
sourceVersion = sourceBranch;
// If Ref is a SHA and the repo is self, we need to use github.ref as source branch since it might be refs/pull/*
if (string.Equals(workspaceRepo, repoFullName, StringComparison.OrdinalIgnoreCase))
{
sourceBranch = executionContext.GetGitHubContext("ref");
}
else
{
sourceBranch = "refs/heads/master";
}
}
}
bool clean = StringUtil.ConvertToBoolean(executionContext.GetInput(Pipelines.PipelineConstants.CheckoutTaskInputs.Clean), true);
string submoduleInput = executionContext.GetInput(Pipelines.PipelineConstants.CheckoutTaskInputs.Submodules);
int fetchDepth = 0;
if (!int.TryParse(executionContext.GetInput("fetch-depth"), out fetchDepth) || fetchDepth < 0)
{
fetchDepth = 0;
}
bool gitLfsSupport = StringUtil.ConvertToBoolean(executionContext.GetInput(Pipelines.PipelineConstants.CheckoutTaskInputs.Lfs));
string accessToken = executionContext.GetInput(Pipelines.PipelineConstants.CheckoutTaskInputs.Token);
if (string.IsNullOrEmpty(accessToken))
{
accessToken = executionContext.GetGitHubContext("token");
}
// register problem matcher
string problemMatcher = @"
{
""problemMatcher"": [
{
""owner"": ""checkout-git"",
""pattern"": [
{
""regexp"": ""^fatal: (.*)$"",
""message"": 1
}
]
}
]
}";
string matcherFile = Path.Combine(tempDirectory, $"git_{Guid.NewGuid()}.json");
File.WriteAllText(matcherFile, problemMatcher, new UTF8Encoding(false));
executionContext.Output($"##[add-matcher]{matcherFile}");
try
{
await new GitHubSourceProvider().GetSourceAsync(executionContext,
expectRepoPath,
repoFullName,
sourceBranch,
sourceVersion,
clean,
submoduleInput,
fetchDepth,
gitLfsSupport,
accessToken,
token);
}
finally
{
executionContext.Output("##[remove-matcher owner=checkout-git]");
}
}
}
}