diff options
| author | eric sciple <ericsciple@users.noreply.github.com> | 2019-12-12 13:16:16 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-12-12 13:16:16 -0500 |
| commit | 25ac60e43ded675f082e10f444b0ad95ea43622e (patch) | |
| tree | 4f254bf120e072e899f70c2646541eeec6fbbd63 /src/git-source-provider.ts | |
| parent | f60b14d6faba41537d0983497a96349d75b1b481 (diff) | |
fallback to REST API to download repo (#104)
Diffstat (limited to 'src/git-source-provider.ts')
| -rw-r--r-- | src/git-source-provider.ts | 290 |
1 files changed, 162 insertions, 128 deletions
diff --git a/src/git-source-provider.ts b/src/git-source-provider.ts index fc41fd2..bf086ff 100644 --- a/src/git-source-provider.ts +++ b/src/git-source-provider.ts @@ -3,9 +3,11 @@ import * as coreCommand from '@actions/core/lib/command' import * as fs from 'fs' import * as fsHelper from './fs-helper' import * as gitCommandManager from './git-command-manager' +import * as githubApiHelper from './github-api-helper' import * as io from '@actions/io' import * as path from 'path' import * as refHelper from './ref-helper' +import * as stateHelper from './state-helper' import {IGitCommandManager} from './git-command-manager' const authConfigKey = `http.https://github.com/.extraheader` @@ -23,6 +25,7 @@ export interface ISourceSettings { } export async function getSource(settings: ISourceSettings): Promise<void> { + // Repository URL core.info( `Syncing repository: ${settings.repositoryOwner}/${settings.repositoryName}` ) @@ -43,92 +46,92 @@ export async function getSource(settings: ISourceSettings): Promise<void> { } // Git command manager - core.info(`Working directory is '${settings.repositoryPath}'`) - const git = await gitCommandManager.CreateCommandManager( - settings.repositoryPath, - settings.lfs - ) + const git = await getGitCommandManager(settings) - // Try prepare existing directory, otherwise recreate - if ( - isExisting && - !(await tryPrepareExistingDirectory( + // Prepare existing directory, otherwise recreate + if (isExisting) { + await prepareExistingDirectory( git, settings.repositoryPath, repositoryUrl, settings.clean - )) - ) { - // Delete the contents of the directory. Don't delete the directory itself - // since it may be the current working directory. - core.info(`Deleting the contents of '${settings.repositoryPath}'`) - for (const file of await fs.promises.readdir(settings.repositoryPath)) { - await io.rmRF(path.join(settings.repositoryPath, file)) - } - } - - // Initialize the repository - if ( - !fsHelper.directoryExistsSync(path.join(settings.repositoryPath, '.git')) - ) { - await git.init() - await git.remoteAdd('origin', repositoryUrl) - } - - // Disable automatic garbage collection - if (!(await git.tryDisableAutomaticGarbageCollection())) { - core.warning( - `Unable to turn off git automatic garbage collection. The git fetch operation may trigger garbage collection and cause a delay.` ) } - // Remove possible previous extraheader - await removeGitConfig(git, authConfigKey) + if (!git) { + // Downloading using REST API + core.info(`The repository will be downloaded using the GitHub REST API`) + core.info( + `To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH` + ) + await githubApiHelper.downloadRepository( + settings.accessToken, + settings.repositoryOwner, + settings.repositoryName, + settings.ref, + settings.commit, + settings.repositoryPath + ) + } else { + // Save state for POST action + stateHelper.setRepositoryPath(settings.repositoryPath) + + // Initialize the repository + if ( + !fsHelper.directoryExistsSync(path.join(settings.repositoryPath, '.git')) + ) { + await git.init() + await git.remoteAdd('origin', repositoryUrl) + } - // Add extraheader (auth) - const base64Credentials = Buffer.from( - `x-access-token:${settings.accessToken}`, - 'utf8' - ).toString('base64') - core.setSecret(base64Credentials) - const authConfigValue = `AUTHORIZATION: basic ${base64Credentials}` - await git.config(authConfigKey, authConfigValue) - - // LFS install - if (settings.lfs) { - await git.lfsInstall() - } + // Disable automatic garbage collection + if (!(await git.tryDisableAutomaticGarbageCollection())) { + core.warning( + `Unable to turn off git automatic garbage collection. The git fetch operation may trigger garbage collection and cause a delay.` + ) + } - // Fetch - const refSpec = refHelper.getRefSpec(settings.ref, settings.commit) - await git.fetch(settings.fetchDepth, refSpec) + // Remove possible previous extraheader + await removeGitConfig(git, authConfigKey) + + // Add extraheader (auth) + const base64Credentials = Buffer.from( + `x-access-token:${settings.accessToken}`, + 'utf8' + ).toString('base64') + core.setSecret(base64Credentials) + const authConfigValue = `AUTHORIZATION: basic ${base64Credentials}` + await git.config(authConfigKey, authConfigValue) + + // LFS install + if (settings.lfs) { + await git.lfsInstall() + } - // Checkout info - const checkoutInfo = await refHelper.getCheckoutInfo( - git, - settings.ref, - settings.commit - ) + // Fetch + const refSpec = refHelper.getRefSpec(settings.ref, settings.commit) + await git.fetch(settings.fetchDepth, refSpec) - // LFS fetch - // Explicit lfs-fetch to avoid slow checkout (fetches one lfs object at a time). - // Explicit lfs fetch will fetch lfs objects in parallel. - if (settings.lfs) { - await git.lfsFetch(checkoutInfo.startPoint || checkoutInfo.ref) - } + // Checkout info + const checkoutInfo = await refHelper.getCheckoutInfo( + git, + settings.ref, + settings.commit + ) - // Checkout - await git.checkout(checkoutInfo.ref, checkoutInfo.startPoint) + // LFS fetch + // Explicit lfs-fetch to avoid slow checkout (fetches one lfs object at a time). + // Explicit lfs fetch will fetch lfs objects in parallel. + if (settings.lfs) { + await git.lfsFetch(checkoutInfo.startPoint || checkoutInfo.ref) + } - // Dump some info about the checked out commit - await git.log1() + // Checkout + await git.checkout(checkoutInfo.ref, checkoutInfo.startPoint) - // Set intra-task state for cleanup - coreCommand.issueCommand( - 'save-state', - {name: 'repositoryPath'}, - settings.repositoryPath - ) + // Dump some info about the checked out commit + await git.log1() + } } export async function cleanup(repositoryPath: string): Promise<void> { @@ -146,79 +149,110 @@ export async function cleanup(repositoryPath: string): Promise<void> { await removeGitConfig(git, authConfigKey) } -async function tryPrepareExistingDirectory( +async function getGitCommandManager( + settings: ISourceSettings +): Promise<IGitCommandManager> { + core.info(`Working directory is '${settings.repositoryPath}'`) + let git = (null as unknown) as IGitCommandManager + try { + return await gitCommandManager.CreateCommandManager( + settings.repositoryPath, + settings.lfs + ) + } catch (err) { + // Git is required for LFS + if (settings.lfs) { + throw err + } + + // Otherwise fallback to REST API + return (null as unknown) as IGitCommandManager + } +} + +async function prepareExistingDirectory( git: IGitCommandManager, repositoryPath: string, repositoryUrl: string, clean: boolean -): Promise<boolean> { +): Promise<void> { + let remove = false + + // Check whether using git or REST API + if (!git) { + remove = true + } // Fetch URL does not match - if ( + else if ( !fsHelper.directoryExistsSync(path.join(repositoryPath, '.git')) || repositoryUrl !== (await git.tryGetFetchUrl()) ) { - return false - } + remove = true + } else { + // Delete any index.lock and shallow.lock left by a previously canceled run or crashed git process + const lockPaths = [ + path.join(repositoryPath, '.git', 'index.lock'), + path.join(repositoryPath, '.git', 'shallow.lock') + ] + for (const lockPath of lockPaths) { + try { + await io.rmRF(lockPath) + } catch (error) { + core.debug(`Unable to delete '${lockPath}'. ${error.message}`) + } + } - // Delete any index.lock and shallow.lock left by a previously canceled run or crashed git process - const lockPaths = [ - path.join(repositoryPath, '.git', 'index.lock'), - path.join(repositoryPath, '.git', 'shallow.lock') - ] - for (const lockPath of lockPaths) { try { - await io.rmRF(lockPath) + // Checkout detached HEAD + if (!(await git.isDetached())) { + await git.checkoutDetach() + } + + // Remove all refs/heads/* + let branches = await git.branchList(false) + for (const branch of branches) { + await git.branchDelete(false, branch) + } + + // Remove all refs/remotes/origin/* to avoid conflicts + branches = await git.branchList(true) + for (const branch of branches) { + await git.branchDelete(true, branch) + } + + // Clean + if (clean) { + if (!(await git.tryClean())) { + core.debug( + `The clean command failed. This might be caused by: 1) path too long, 2) permission issue, or 3) file in use. For futher investigation, manually run 'git clean -ffdx' on the directory '${repositoryPath}'.` + ) + remove = true + } else if (!(await git.tryReset())) { + remove = true + } + + if (remove) { + core.warning( + `Unable to clean or reset the repository. The repository will be recreated instead.` + ) + } + } } catch (error) { - core.debug(`Unable to delete '${lockPath}'. ${error.message}`) - } - } - - try { - // Checkout detached HEAD - if (!(await git.isDetached())) { - await git.checkoutDetach() - } - - // Remove all refs/heads/* - let branches = await git.branchList(false) - for (const branch of branches) { - await git.branchDelete(false, branch) - } - - // Remove all refs/remotes/origin/* to avoid conflicts - branches = await git.branchList(true) - for (const branch of branches) { - await git.branchDelete(true, branch) - } - } catch (error) { - core.warning( - `Unable to prepare the existing repository. The repository will be recreated instead.` - ) - return false - } - - // Clean - if (clean) { - let succeeded = true - if (!(await git.tryClean())) { - core.debug( - `The clean command failed. This might be caused by: 1) path too long, 2) permission issue, or 3) file in use. For futher investigation, manually run 'git clean -ffdx' on the directory '${repositoryPath}'.` - ) - succeeded = false - } else if (!(await git.tryReset())) { - succeeded = false - } - - if (!succeeded) { core.warning( - `Unable to clean or reset the repository. The repository will be recreated instead.` + `Unable to prepare the existing repository. The repository will be recreated instead.` ) + remove = true } - - return succeeded } - return true + if (remove) { + // Delete the contents of the directory. Don't delete the directory itself + // since it might be the current working directory. + core.info(`Deleting the contents of '${repositoryPath}'`) + for (const file of await fs.promises.readdir(repositoryPath)) { + await io.rmRF(path.join(repositoryPath, file)) + } + } } async function removeGitConfig( |
