| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 |
- 'use strict'
- const BB = require('bluebird')
- const cp = require('child_process')
- const execFileAsync = BB.promisify(cp.execFile, {
- multiArgs: true
- })
- const finished = require('./finished')
- const LRU = require('lru-cache')
- const optCheck = require('./opt-check')
- const osenv = require('osenv')
- const path = require('path')
- const pinflight = require('promise-inflight')
- const promiseRetry = require('promise-retry')
- const uniqueFilename = require('unique-filename')
- const which = BB.promisify(require('which'))
- const semver = require('semver')
- const GOOD_ENV_VARS = new Set([
- 'GIT_ASKPASS',
- 'GIT_EXEC_PATH',
- 'GIT_PROXY_COMMAND',
- 'GIT_SSH',
- 'GIT_SSH_COMMAND',
- 'GIT_SSL_CAINFO',
- 'GIT_SSL_NO_VERIFY'
- ])
- const GIT_TRANSIENT_ERRORS = [
- 'remote error: Internal Server Error',
- 'The remote end hung up unexpectedly',
- 'Connection timed out',
- 'Operation timed out',
- 'Failed to connect to .* Timed out',
- 'Connection reset by peer',
- 'SSL_ERROR_SYSCALL',
- 'The requested URL returned error: 503'
- ].join('|')
- const GIT_TRANSIENT_ERROR_RE = new RegExp(GIT_TRANSIENT_ERRORS)
- const GIT_TRANSIENT_ERROR_MAX_RETRY_NUMBER = 3
- function shouldRetry (error, number) {
- return GIT_TRANSIENT_ERROR_RE.test(error) && (number < GIT_TRANSIENT_ERROR_MAX_RETRY_NUMBER)
- }
- const GIT_ = 'GIT_'
- let GITENV
- function gitEnv () {
- if (GITENV) { return GITENV }
- const tmpDir = path.join(osenv.tmpdir(), 'pacote-git-template-tmp')
- const tmpName = uniqueFilename(tmpDir, 'git-clone')
- GITENV = {
- GIT_ASKPASS: 'echo',
- GIT_TEMPLATE_DIR: tmpName
- }
- Object.keys(process.env).forEach(k => {
- if (GOOD_ENV_VARS.has(k) || !k.startsWith(GIT_)) {
- GITENV[k] = process.env[k]
- }
- })
- return GITENV
- }
- let GITPATH
- try {
- GITPATH = which.sync('git')
- } catch (e) {}
- module.exports.clone = fullClone
- function fullClone (repo, committish, target, opts) {
- opts = optCheck(opts)
- const gitArgs = ['clone', '--mirror', '-q', repo, path.join(target, '.git')]
- if (process.platform === 'win32') {
- gitArgs.push('--config', 'core.longpaths=true')
- }
- return execGit(gitArgs, { cwd: target }, opts).then(() => {
- return execGit(['init'], { cwd: target }, opts)
- }).then(() => {
- return execGit(['checkout', committish || 'HEAD'], { cwd: target }, opts)
- }).then(() => {
- return updateSubmodules(target, opts)
- }).then(() => headSha(target, opts))
- }
- module.exports.shallow = shallowClone
- function shallowClone (repo, branch, target, opts) {
- opts = optCheck(opts)
- const gitArgs = ['clone', '--depth=1', '-q']
- if (branch) {
- gitArgs.push('-b', branch)
- }
- gitArgs.push(repo, target)
- if (process.platform === 'win32') {
- gitArgs.push('--config', 'core.longpaths=true')
- }
- return execGit(gitArgs, {
- cwd: target
- }, opts).then(() => {
- return updateSubmodules(target, opts)
- }).then(() => headSha(target, opts))
- }
- function updateSubmodules (localRepo, opts) {
- const gitArgs = ['submodule', 'update', '-q', '--init', '--recursive']
- return execGit(gitArgs, {
- cwd: localRepo
- }, opts)
- }
- function headSha (repo, opts) {
- opts = optCheck(opts)
- return execGit(['rev-parse', '--revs-only', 'HEAD'], { cwd: repo }, opts).spread(stdout => {
- return stdout.trim()
- })
- }
- const CARET_BRACES = '^{}'
- const REVS = new LRU({
- max: 100,
- maxAge: 5 * 60 * 1000
- })
- module.exports.revs = revs
- function revs (repo, opts) {
- opts = optCheck(opts)
- const cached = REVS.get(repo)
- if (cached) {
- return BB.resolve(cached)
- }
- return pinflight(`ls-remote:${repo}`, () => {
- return spawnGit(['ls-remote', '-h', '-t', repo], {
- env: gitEnv()
- }, opts).then((stdout) => {
- return stdout.split('\n').reduce((revs, line) => {
- const split = line.split(/\s+/, 2)
- if (split.length < 2) { return revs }
- const sha = split[0].trim()
- const ref = split[1].trim().match(/(?:refs\/[^/]+\/)?(.*)/)[1]
- if (!ref) { return revs } // ???
- if (ref.endsWith(CARET_BRACES)) { return revs } // refs/tags/x^{} crap
- const type = refType(line)
- const doc = { sha, ref, type }
- revs.refs[ref] = doc
- // We can check out shallow clones on specific SHAs if we have a ref
- if (revs.shas[sha]) {
- revs.shas[sha].push(ref)
- } else {
- revs.shas[sha] = [ref]
- }
- if (type === 'tag') {
- const match = ref.match(/v?(\d+\.\d+\.\d+(?:[-+].+)?)$/)
- if (match && semver.valid(match[1], true)) {
- revs.versions[semver.clean(match[1], true)] = doc
- }
- }
- return revs
- }, { versions: {}, 'dist-tags': {}, refs: {}, shas: {} })
- }, err => {
- err.message = `Error while executing:\n${GITPATH} ls-remote -h -t ${repo}\n\n${err.stderr}\n${err.message}`
- throw err
- }).then(revs => {
- if (revs.refs.HEAD) {
- const HEAD = revs.refs.HEAD
- Object.keys(revs.versions).forEach(v => {
- if (v.sha === HEAD.sha) {
- revs['dist-tags'].HEAD = v
- if (!revs.refs.latest) {
- revs['dist-tags'].latest = revs.refs.HEAD
- }
- }
- })
- }
- REVS.set(repo, revs)
- return revs
- })
- })
- }
- module.exports._exec = execGit
- function execGit (gitArgs, gitOpts, opts) {
- opts = optCheck(opts)
- return checkGit(opts).then(gitPath => {
- return promiseRetry((retry, number) => {
- if (number !== 1) {
- opts.log.silly('pacote', 'Retrying git command: ' + gitArgs.join(' ') + ' attempt # ' + number)
- }
- return execFileAsync(gitPath, gitArgs, mkOpts(gitOpts, opts)).catch((err) => {
- if (shouldRetry(err, number)) {
- retry(err)
- } else {
- throw err
- }
- })
- }, opts.retry != null ? opts.retry : {
- retries: opts['fetch-retries'],
- factor: opts['fetch-retry-factor'],
- maxTimeout: opts['fetch-retry-maxtimeout'],
- minTimeout: opts['fetch-retry-mintimeout']
- })
- })
- }
- module.exports._spawn = spawnGit
- function spawnGit (gitArgs, gitOpts, opts) {
- opts = optCheck(opts)
- return checkGit(opts).then(gitPath => {
- return promiseRetry((retry, number) => {
- if (number !== 1) {
- opts.log.silly('pacote', 'Retrying git command: ' + gitArgs.join(' ') + ' attempt # ' + number)
- }
- const child = cp.spawn(gitPath, gitArgs, mkOpts(gitOpts, opts))
- let stdout = ''
- let stderr = ''
- child.stdout.on('data', d => { stdout += d })
- child.stderr.on('data', d => { stderr += d })
- return finished(child, true).catch(err => {
- if (shouldRetry(stderr, number)) {
- retry(err)
- } else {
- err.stderr = stderr
- throw err
- }
- }).then(() => {
- return stdout
- })
- }, opts.retry)
- })
- }
- function mkOpts (_gitOpts, opts) {
- const gitOpts = {
- env: gitEnv()
- }
- if (+opts.uid && !isNaN(opts.uid)) {
- gitOpts.uid = +opts.uid
- }
- if (+opts.gid && !isNaN(opts.gid)) {
- gitOpts.gid = +opts.gid
- }
- Object.assign(gitOpts, _gitOpts)
- return gitOpts
- }
- function checkGit (opts) {
- if (opts.git) {
- return BB.resolve(opts.git)
- } else if (!GITPATH) {
- const err = new Error('No git binary found in $PATH')
- err.code = 'ENOGIT'
- return BB.reject(err)
- } else {
- return BB.resolve(GITPATH)
- }
- }
- const REFS_TAGS = 'refs/tags/'
- const REFS_HEADS = 'refs/heads/'
- const HEAD = 'HEAD'
- function refType (ref) {
- return ref.indexOf(REFS_TAGS) !== -1
- ? 'tag'
- : ref.indexOf(REFS_HEADS) !== -1
- ? 'branch'
- : ref.endsWith(HEAD)
- ? 'head'
- : 'other'
- }
|