summaryrefslogtreecommitdiff
path: root/deps/npm/node_modules/pacote/lib/fetchers/git.js
blob: 7db4a79de3dc74e5c085dfaf0125f35d91d5e3d6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
'use strict'

const BB = require('bluebird')

const cacache = require('cacache')
const cacheKey = require('../util/cache-key')
const Fetcher = require('../fetch')
const git = require('../util/git')
const mkdirp = BB.promisify(require('mkdirp'))
const pickManifest = require('npm-pick-manifest')
const optCheck = require('../util/opt-check')
const osenv = require('osenv')
const packDir = require('../util/pack-dir')
const PassThrough = require('stream').PassThrough
const path = require('path')
const pipe = BB.promisify(require('mississippi').pipe)
const rimraf = BB.promisify(require('rimraf'))
const uniqueFilename = require('unique-filename')

// `git` dependencies are fetched from git repositories and packed up.
const fetchGit = module.exports = Object.create(null)

Fetcher.impl(fetchGit, {
  packument (spec, opts) {
    return BB.reject(new Error('Not implemented yet.'))
  },

  manifest (spec, opts) {
    opts = optCheck(opts)
    if (spec.hosted && spec.hosted.getDefaultRepresentation() === 'shortcut') {
      return hostedManifest(spec, opts)
    } else {
      // If it's not a shortcut, don't do fallbacks.
      return plainManifest(spec.fetchSpec, spec, opts)
    }
  },

  tarball (spec, opts) {
    opts = optCheck(opts)
    const stream = new PassThrough()
    this.manifest(spec, opts).then(manifest => {
      stream.emit('manifest', manifest)
      return pipe(
        this.fromManifest(
          manifest, spec, opts
        ).on('integrity', i => stream.emit('integrity', i)), stream
      )
    }).catch(err => stream.emit('error', err))
    return stream
  },

  fromManifest (manifest, spec, opts) {
    opts = optCheck(opts)
    let streamError
    const stream = new PassThrough().on('error', e => { streamError = e })
    const cacheName = manifest._uniqueResolved || manifest._resolved || ''
    const cacheStream = (
      opts.cache &&
      cacache.get.stream(
        opts.cache, cacheKey('packed-dir', cacheName), opts
      ).on('integrity', i => stream.emit('integrity', i))
    )
    cacheStream.pipe(stream)
    cacheStream.on('error', err => {
      if (err.code !== 'ENOENT') {
        return stream.emit('error', err)
      } else {
        stream.emit('reset')
        return withTmp(opts, tmp => {
          if (streamError) { throw streamError }
          return cloneRepo(
            spec, manifest._repo, manifest._ref, manifest._rawRef, tmp, opts
          ).then(HEAD => {
            if (streamError) { throw streamError }
            manifest._resolved = spec.saveSpec.replace(/(:?#.*)?$/, `#${HEAD}`)
            manifest._uniqueResolved = manifest._resolved
            return packDir(manifest, manifest._uniqueResolved, tmp, stream, opts)
          })
        }).catch(err => stream.emit('error', err))
      }
    })
    return stream
  }
})

function hostedManifest (spec, opts) {
  return BB.resolve(null).then(() => {
    if (!spec.hosted.git()) {
      throw new Error(`No git url for ${spec}`)
    }
    return plainManifest(spec.hosted.git(), spec, opts)
  }).catch(err => {
    if (!spec.hosted.https()) {
      throw err
    }
    return plainManifest(spec.hosted.https(), spec, opts)
  }).catch(err => {
    if (!spec.hosted.sshurl()) {
      throw err
    }
    return plainManifest(spec.hosted.sshurl(), spec, opts)
  })
}

function plainManifest (repo, spec, opts) {
  const rawRef = spec.gitCommittish || spec.gitRange
  return resolve(
    repo, spec, spec.name, opts
  ).then(ref => {
    if (ref) {
      const resolved = spec.saveSpec.replace(/(?:#.*)?$/, `#${ref.sha}`)
      return {
        _repo: repo,
        _resolved: resolved,
        _spec: spec,
        _ref: ref,
        _rawRef: spec.gitCommittish || spec.gitRange,
        _uniqueResolved: resolved,
        _integrity: false,
        _shasum: false
      }
    } else {
      // We're SOL and need a full clone :(
      //
      // If we're confident enough that `rawRef` is a commit SHA,
      // then we can at least get `finalize-manifest` to cache its result.
      const resolved = spec.saveSpec.replace(/(?:#.*)?$/, rawRef ? `#${rawRef}` : '')
      return {
        _repo: repo,
        _rawRef: rawRef,
        _resolved: rawRef && rawRef.match(/^[a-f0-9]{40}$/) && resolved,
        _uniqueResolved: rawRef && rawRef.match(/^[a-f0-9]{40}$/) && resolved,
        _integrity: false,
        _shasum: false
      }
    }
  })
}

function resolve (url, spec, name, opts) {
  const isSemver = !!spec.gitRange
  return git.revs(url, opts).then(remoteRefs => {
    return isSemver
      ? pickManifest({
        versions: remoteRefs.versions,
        'dist-tags': remoteRefs['dist-tags'],
        name: name
      }, spec.gitRange, opts)
      : remoteRefs
        ? BB.resolve(
          remoteRefs.refs[spec.gitCommittish] || remoteRefs.refs[remoteRefs.shas[spec.gitCommittish]]
        )
        : null
  })
}

function withTmp (opts, cb) {
  if (opts.cache) {
    // cacache has a special facility for working in a tmp dir
    return cacache.tmp.withTmp(opts.cache, { tmpPrefix: 'git-clone', uid: opts.uid, gid: opts.gid }, cb)
  } else {
    const tmpDir = path.join(osenv.tmpdir(), 'pacote-git-tmp')
    const tmpName = uniqueFilename(tmpDir, 'git-clone')
    const tmp = mkdirp(tmpName).then(() => tmpName).disposer(rimraf)
    return BB.using(tmp, cb)
  }
}

// Only certain whitelisted hosted gits support shadow cloning
const SHALLOW_HOSTS = new Set(['github', 'gist', 'gitlab', 'bitbucket'])
function cloneRepo (spec, repo, resolvedRef, rawRef, tmp, opts) {
  const ref = resolvedRef ? resolvedRef.ref : rawRef
  if (resolvedRef && spec.hosted && SHALLOW_HOSTS.has(spec.hosted.type)) {
    return git.shallow(repo, ref, tmp, opts)
  } else {
    return git.clone(repo, ref, tmp, opts)
  }
}