git.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. 'use strict'
  2. const BB = require('bluebird')
  3. const cacache = require('cacache')
  4. const cacheKey = require('../util/cache-key')
  5. const Fetcher = require('../fetch')
  6. const git = require('../util/git')
  7. const mkdirp = BB.promisify(require('mkdirp'))
  8. const pickManifest = require('npm-pick-manifest')
  9. const optCheck = require('../util/opt-check')
  10. const osenv = require('osenv')
  11. const packDir = require('../util/pack-dir')
  12. const PassThrough = require('stream').PassThrough
  13. const path = require('path')
  14. const pipe = BB.promisify(require('mississippi').pipe)
  15. const rimraf = BB.promisify(require('rimraf'))
  16. const uniqueFilename = require('unique-filename')
  17. // `git` dependencies are fetched from git repositories and packed up.
  18. const fetchGit = module.exports = Object.create(null)
  19. Fetcher.impl(fetchGit, {
  20. packument (spec, opts) {
  21. return BB.reject(new Error('Not implemented yet.'))
  22. },
  23. manifest (spec, opts) {
  24. opts = optCheck(opts)
  25. if (spec.hosted && spec.hosted.getDefaultRepresentation() === 'shortcut') {
  26. return hostedManifest(spec, opts)
  27. } else {
  28. // If it's not a shortcut, don't do fallbacks.
  29. return plainManifest(spec.fetchSpec, spec, opts)
  30. }
  31. },
  32. tarball (spec, opts) {
  33. opts = optCheck(opts)
  34. const stream = new PassThrough()
  35. this.manifest(spec, opts).then(manifest => {
  36. stream.emit('manifest', manifest)
  37. return pipe(
  38. this.fromManifest(
  39. manifest, spec, opts
  40. ).on('integrity', i => stream.emit('integrity', i)), stream
  41. )
  42. }).catch(err => stream.emit('error', err))
  43. return stream
  44. },
  45. fromManifest (manifest, spec, opts) {
  46. opts = optCheck(opts)
  47. let streamError
  48. const stream = new PassThrough().on('error', e => { streamError = e })
  49. const cacheName = manifest._uniqueResolved || manifest._resolved || ''
  50. const cacheStream = (
  51. opts.cache &&
  52. cacache.get.stream(
  53. opts.cache, cacheKey('packed-dir', cacheName), opts
  54. ).on('integrity', i => stream.emit('integrity', i))
  55. )
  56. cacheStream.pipe(stream)
  57. cacheStream.on('error', err => {
  58. if (err.code !== 'ENOENT') {
  59. return stream.emit('error', err)
  60. } else {
  61. stream.emit('reset')
  62. return withTmp(opts, tmp => {
  63. if (streamError) { throw streamError }
  64. return cloneRepo(
  65. spec, manifest._repo, manifest._ref, manifest._rawRef, tmp, opts
  66. ).then(HEAD => {
  67. if (streamError) { throw streamError }
  68. manifest._resolved = spec.saveSpec.replace(/(:?#.*)?$/, `#${HEAD}`)
  69. manifest._uniqueResolved = manifest._resolved
  70. return packDir(manifest, manifest._uniqueResolved, tmp, stream, opts)
  71. })
  72. }).catch(err => stream.emit('error', err))
  73. }
  74. })
  75. return stream
  76. }
  77. })
  78. function hostedManifest (spec, opts) {
  79. return BB.resolve(null).then(() => {
  80. if (!spec.hosted.git()) {
  81. throw new Error(`No git url for ${spec}`)
  82. }
  83. return plainManifest(spec.hosted.git(), spec, opts)
  84. }).catch(err => {
  85. if (!spec.hosted.https()) {
  86. throw err
  87. }
  88. return plainManifest(spec.hosted.https(), spec, opts)
  89. }).catch(err => {
  90. if (!spec.hosted.sshurl()) {
  91. throw err
  92. }
  93. return plainManifest(spec.hosted.sshurl(), spec, opts)
  94. })
  95. }
  96. function plainManifest (repo, spec, opts) {
  97. const rawRef = spec.gitCommittish || spec.gitRange
  98. return resolve(
  99. repo, spec, spec.name, opts
  100. ).then(ref => {
  101. if (ref) {
  102. const resolved = spec.saveSpec.replace(/(?:#.*)?$/, `#${ref.sha}`)
  103. return {
  104. _repo: repo,
  105. _resolved: resolved,
  106. _spec: spec,
  107. _ref: ref,
  108. _rawRef: spec.gitCommittish || spec.gitRange,
  109. _uniqueResolved: resolved,
  110. _integrity: false,
  111. _shasum: false
  112. }
  113. } else {
  114. // We're SOL and need a full clone :(
  115. //
  116. // If we're confident enough that `rawRef` is a commit SHA,
  117. // then we can at least get `finalize-manifest` to cache its result.
  118. const resolved = spec.saveSpec.replace(/(?:#.*)?$/, rawRef ? `#${rawRef}` : '')
  119. return {
  120. _repo: repo,
  121. _rawRef: rawRef,
  122. _resolved: rawRef && rawRef.match(/^[a-f0-9]{40}$/) && resolved,
  123. _uniqueResolved: rawRef && rawRef.match(/^[a-f0-9]{40}$/) && resolved,
  124. _integrity: false,
  125. _shasum: false
  126. }
  127. }
  128. })
  129. }
  130. function resolve (url, spec, name, opts) {
  131. const isSemver = !!spec.gitRange
  132. return git.revs(url, opts).then(remoteRefs => {
  133. return isSemver
  134. ? pickManifest({
  135. versions: remoteRefs.versions,
  136. 'dist-tags': remoteRefs['dist-tags'],
  137. name: name
  138. }, spec.gitRange, opts)
  139. : remoteRefs
  140. ? BB.resolve(
  141. remoteRefs.refs[spec.gitCommittish] || remoteRefs.refs[remoteRefs.shas[spec.gitCommittish]]
  142. )
  143. : null
  144. })
  145. }
  146. function withTmp (opts, cb) {
  147. if (opts.cache) {
  148. // cacache has a special facility for working in a tmp dir
  149. return cacache.tmp.withTmp(opts.cache, { tmpPrefix: 'git-clone' }, cb)
  150. } else {
  151. const tmpDir = path.join(osenv.tmpdir(), 'pacote-git-tmp')
  152. const tmpName = uniqueFilename(tmpDir, 'git-clone')
  153. const tmp = mkdirp(tmpName).then(() => tmpName).disposer(rimraf)
  154. return BB.using(tmp, cb)
  155. }
  156. }
  157. // Only certain whitelisted hosted gits support shadow cloning
  158. const SHALLOW_HOSTS = new Set(['github', 'gist', 'gitlab', 'bitbucket'])
  159. function cloneRepo (spec, repo, resolvedRef, rawRef, tmp, opts) {
  160. const ref = resolvedRef ? resolvedRef.ref : rawRef
  161. if (resolvedRef && spec.hosted && SHALLOW_HOSTS.has(spec.hosted.type)) {
  162. return git.shallow(repo, ref, tmp, opts)
  163. } else {
  164. return git.clone(repo, ref, tmp, opts)
  165. }
  166. }