Read-only mirror — the public repositories of the Forgejo instance, refreshed every 10 minutes. The live instance (fg.dieschoenewolke.de) is reachable only over the VPN; clone here with https://git.dieschoenewolke.de/git/<owner>/<repo>.git.
summaryrefslogtreecommitdiff
path: root/src/git-source-provider.ts
diff options
context:
space:
mode:
authoreric sciple <ericsciple@users.noreply.github.com>2019-12-03 10:28:59 -0500
committerGitHub <noreply@github.com>2019-12-03 10:28:59 -0500
commite404ba5989baa550f631fb1262539d1375a856b7 (patch)
tree4cc911589e7c9a4c976bc99eb4dcb886d2b2d8a8 /src/git-source-provider.ts
parent6a11491f2eea6739d1296d604c5402cffa43a1f1 (diff)
Convert checkout to a regular action (#70)
Diffstat (limited to 'src/git-source-provider.ts')
-rw-r--r--src/git-source-provider.ts246
1 files changed, 246 insertions, 0 deletions
diff --git a/src/git-source-provider.ts b/src/git-source-provider.ts
new file mode 100644
index 0000000..0752100
--- /dev/null
+++ b/src/git-source-provider.ts
@@ -0,0 +1,246 @@
+import * as core from '@actions/core'
+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 io from '@actions/io'
+import * as path from 'path'
+import * as refHelper from './ref-helper'
+import {IGitCommandManager} from './git-command-manager'
+
+const authConfigKey = `http.https://github.com/.extraheader`
+
+export interface ISourceSettings {
+ repositoryPath: string
+ repositoryOwner: string
+ repositoryName: string
+ ref: string
+ commit: string
+ clean: boolean
+ fetchDepth: number
+ lfs: boolean
+ accessToken: string
+}
+
+export async function getSource(settings: ISourceSettings): Promise<void> {
+ core.info(
+ `Syncing repository: ${settings.repositoryOwner}/${settings.repositoryName}`
+ )
+ const repositoryUrl = `https://github.com/${encodeURIComponent(
+ settings.repositoryOwner
+ )}/${encodeURIComponent(settings.repositoryName)}`
+
+ // Remove conflicting file path
+ if (fsHelper.fileExistsSync(settings.repositoryPath)) {
+ await io.rmRF(settings.repositoryPath)
+ }
+
+ // Create directory
+ let isExisting = true
+ if (!fsHelper.directoryExistsSync(settings.repositoryPath)) {
+ isExisting = false
+ await io.mkdirP(settings.repositoryPath)
+ }
+
+ // Git command manager
+ core.info(`Working directory is '${settings.repositoryPath}'`)
+ const git = await gitCommandManager.CreateCommandManager(
+ settings.repositoryPath,
+ settings.lfs
+ )
+
+ // Try prepare existing directory, otherwise recreate
+ if (
+ isExisting &&
+ !(await tryPrepareExistingDirectory(
+ git,
+ settings.repositoryPath,
+ repositoryUrl,
+ settings.clean
+ ))
+ ) {
+ await io.rmRF(settings.repositoryPath)
+ await io.mkdirP(settings.repositoryPath)
+ }
+
+ // 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)
+
+ // 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()
+ }
+
+ // Fetch
+ const refSpec = refHelper.getRefSpec(settings.ref, settings.commit)
+ await git.fetch(settings.fetchDepth, refSpec)
+
+ // Checkout info
+ const checkoutInfo = await refHelper.getCheckoutInfo(
+ git,
+ settings.ref,
+ settings.commit
+ )
+
+ // 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
+ await git.checkout(checkoutInfo.ref, checkoutInfo.startPoint)
+
+ // Dump some info about the checked out commit
+ await git.log1()
+
+ // Set intra-task state for cleanup
+ coreCommand.issueCommand(
+ 'save-state',
+ {name: 'repositoryPath'},
+ settings.repositoryPath
+ )
+}
+
+export async function cleanup(repositoryPath: string): Promise<void> {
+ // Repo exists?
+ if (!fsHelper.fileExistsSync(path.join(repositoryPath, '.git', 'config'))) {
+ return
+ }
+ fsHelper.directoryExistsSync(repositoryPath, true)
+
+ // Remove the config key
+ const git = await gitCommandManager.CreateCommandManager(
+ repositoryPath,
+ false
+ )
+ await removeGitConfig(git, authConfigKey)
+}
+
+async function tryPrepareExistingDirectory(
+ git: IGitCommandManager,
+ repositoryPath: string,
+ repositoryUrl: string,
+ clean: boolean
+): Promise<boolean> {
+ // Fetch URL does not match
+ if (
+ !fsHelper.directoryExistsSync(path.join(repositoryPath, '.git')) ||
+ repositoryUrl !== (await git.tryGetFetchUrl())
+ ) {
+ return false
+ }
+
+ // 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}`)
+ }
+ }
+
+ 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.`
+ )
+ }
+
+ return succeeded
+ }
+
+ return true
+}
+
+async function removeGitConfig(
+ git: IGitCommandManager,
+ configKey: string
+): Promise<void> {
+ if (
+ (await git.configExists(configKey)) &&
+ !(await git.tryConfigUnset(configKey))
+ ) {
+ // Load the config contents
+ core.warning(
+ `Failed to remove '${configKey}' from the git config. Attempting to remove the config value by editing the file directly.`
+ )
+ const configPath = path.join(git.getWorkingDirectory(), '.git', 'config')
+ fsHelper.fileExistsSync(configPath)
+ let contents = fs.readFileSync(configPath).toString() || ''
+
+ // Filter - only includes lines that do not contain the config key
+ const upperConfigKey = configKey.toUpperCase()
+ const split = contents
+ .split('\n')
+ .filter(x => !x.toUpperCase().includes(upperConfigKey))
+ contents = split.join('\n')
+
+ // Rewrite the config file
+ fs.writeFileSync(configPath, contents)
+ }
+}