git-source-provider.ts 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. import * as core from '@actions/core'
  2. import * as fsHelper from './fs-helper'
  3. import * as gitAuthHelper from './git-auth-helper'
  4. import * as gitCommandManager from './git-command-manager'
  5. import * as gitDirectoryHelper from './git-directory-helper'
  6. import * as githubApiHelper from './github-api-helper'
  7. import * as io from '@actions/io'
  8. import * as path from 'path'
  9. import * as refHelper from './ref-helper'
  10. import * as stateHelper from './state-helper'
  11. import {IGitCommandManager} from './git-command-manager'
  12. import {IGitSourceSettings} from './git-source-settings'
  13. const hostname = 'github.com'
  14. export async function getSource(settings: IGitSourceSettings): Promise<void> {
  15. core.info(
  16. `Syncing repository: ${settings.repositoryOwner}/${settings.repositoryName}`
  17. )
  18. // Remote URL
  19. const httpsUrl = `https://${hostname}/${encodeURIComponent(
  20. settings.repositoryOwner
  21. )}/${encodeURIComponent(settings.repositoryName)}`
  22. const sshUrl = `git@${hostname}:${encodeURIComponent(
  23. settings.repositoryOwner
  24. )}/${encodeURIComponent(settings.repositoryName)}.git`
  25. // Always fetch the workflow repository using the token, not the SSH key
  26. const initialRemoteUrl =
  27. !settings.sshKey || settings.isWorkflowRepository ? httpsUrl : sshUrl
  28. // Remove conflicting file path
  29. if (fsHelper.fileExistsSync(settings.repositoryPath)) {
  30. await io.rmRF(settings.repositoryPath)
  31. }
  32. // Create directory
  33. let isExisting = true
  34. if (!fsHelper.directoryExistsSync(settings.repositoryPath)) {
  35. isExisting = false
  36. await io.mkdirP(settings.repositoryPath)
  37. }
  38. // Git command manager
  39. const git = await getGitCommandManager(settings)
  40. // Prepare existing directory, otherwise recreate
  41. if (isExisting) {
  42. await gitDirectoryHelper.prepareExistingDirectory(
  43. git,
  44. settings.repositoryPath,
  45. initialRemoteUrl,
  46. [httpsUrl, sshUrl],
  47. settings.clean
  48. )
  49. }
  50. if (!git) {
  51. // Downloading using REST API
  52. core.info(`The repository will be downloaded using the GitHub REST API`)
  53. core.info(
  54. `To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH`
  55. )
  56. if (settings.submodules) {
  57. throw new Error(
  58. `Input 'submodules' not supported when falling back to download using the GitHub REST API. To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH.`
  59. )
  60. } else if (settings.sshKey) {
  61. throw new Error(
  62. `Input 'ssh-key' not supported when falling back to download using the GitHub REST API. To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH.`
  63. )
  64. }
  65. await githubApiHelper.downloadRepository(
  66. settings.authToken,
  67. settings.repositoryOwner,
  68. settings.repositoryName,
  69. settings.ref,
  70. settings.commit,
  71. settings.repositoryPath
  72. )
  73. return
  74. }
  75. // Save state for POST action
  76. stateHelper.setRepositoryPath(settings.repositoryPath)
  77. // Initialize the repository
  78. if (
  79. !fsHelper.directoryExistsSync(path.join(settings.repositoryPath, '.git'))
  80. ) {
  81. await git.init()
  82. await git.remoteAdd('origin', initialRemoteUrl)
  83. }
  84. // Disable automatic garbage collection
  85. if (!(await git.tryDisableAutomaticGarbageCollection())) {
  86. core.warning(
  87. `Unable to turn off git automatic garbage collection. The git fetch operation may trigger garbage collection and cause a delay.`
  88. )
  89. }
  90. const authHelper = gitAuthHelper.createAuthHelper(git, settings)
  91. try {
  92. // Configure auth
  93. await authHelper.configureAuth()
  94. // LFS install
  95. if (settings.lfs) {
  96. await git.lfsInstall()
  97. }
  98. // Fetch
  99. const refSpec = refHelper.getRefSpec(settings.ref, settings.commit)
  100. await git.fetch(settings.fetchDepth, refSpec)
  101. // Checkout info
  102. const checkoutInfo = await refHelper.getCheckoutInfo(
  103. git,
  104. settings.ref,
  105. settings.commit
  106. )
  107. // LFS fetch
  108. // Explicit lfs-fetch to avoid slow checkout (fetches one lfs object at a time).
  109. // Explicit lfs fetch will fetch lfs objects in parallel.
  110. if (settings.lfs) {
  111. await git.lfsFetch(checkoutInfo.startPoint || checkoutInfo.ref)
  112. }
  113. // Fix URL when using SSH
  114. if (settings.sshKey && initialRemoteUrl !== sshUrl) {
  115. await git.setRemoteUrl(sshUrl)
  116. }
  117. // Checkout
  118. await git.checkout(checkoutInfo.ref, checkoutInfo.startPoint)
  119. // Submodules
  120. if (settings.submodules) {
  121. try {
  122. // Temporarily override global config
  123. await authHelper.configureGlobalAuth()
  124. // Checkout submodules
  125. await git.submoduleSync(settings.nestedSubmodules)
  126. await git.submoduleUpdate(
  127. settings.fetchDepth,
  128. settings.nestedSubmodules
  129. )
  130. await git.submoduleForeach(
  131. 'git config --local gc.auto 0',
  132. settings.nestedSubmodules
  133. )
  134. // Persist credentials
  135. if (settings.persistCredentials) {
  136. await authHelper.configureSubmoduleAuth()
  137. }
  138. } finally {
  139. // Remove temporary global config override
  140. await authHelper.removeGlobalAuth()
  141. }
  142. }
  143. // Dump some info about the checked out commit
  144. await git.log1()
  145. } finally {
  146. // Remove auth
  147. if (!settings.persistCredentials) {
  148. await authHelper.removeAuth()
  149. }
  150. }
  151. }
  152. export async function cleanup(repositoryPath: string): Promise<void> {
  153. // Repo exists?
  154. if (
  155. !repositoryPath ||
  156. !fsHelper.fileExistsSync(path.join(repositoryPath, '.git', 'config'))
  157. ) {
  158. return
  159. }
  160. let git: IGitCommandManager
  161. try {
  162. git = await gitCommandManager.createCommandManager(repositoryPath, false)
  163. } catch {
  164. return
  165. }
  166. // Remove auth
  167. const authHelper = gitAuthHelper.createAuthHelper(git)
  168. await authHelper.removeAuth()
  169. }
  170. async function getGitCommandManager(
  171. settings: IGitSourceSettings
  172. ): Promise<IGitCommandManager | undefined> {
  173. core.info(`Working directory is '${settings.repositoryPath}'`)
  174. try {
  175. return await gitCommandManager.createCommandManager(
  176. settings.repositoryPath,
  177. settings.lfs
  178. )
  179. } catch (err) {
  180. // Git is required for LFS
  181. if (settings.lfs) {
  182. throw err
  183. }
  184. // Otherwise fallback to REST API
  185. return undefined
  186. }
  187. }