git-source-provider.ts 5.4 KB

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