2022-09-15 15:55:45 +02:00
using System ;
2019-10-10 00:52:42 -04:00
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 Regex ( @"\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]" ) ;
}
}
}
}