mirror of https://github.com/nodejs/node.git
286 lines
8.5 KiB
JavaScript
286 lines
8.5 KiB
JavaScript
var mkdir = require("mkdirp")
|
|
, assert = require("assert")
|
|
, spawn = require("child_process").spawn
|
|
, exec = require("child_process").execFile
|
|
, once = require("once")
|
|
, fs = require("graceful-fs")
|
|
, log = require("npmlog")
|
|
, path = require("path")
|
|
, url = require("url")
|
|
, chownr = require("chownr")
|
|
, zlib = require("zlib")
|
|
, which = require("which")
|
|
, crypto = require("crypto")
|
|
, chmodr = require("chmodr")
|
|
, npm = require("../npm.js")
|
|
, rm = require("../utils/gently-rm.js")
|
|
, inflight = require("inflight")
|
|
, locker = require("../utils/locker.js")
|
|
, lock = locker.lock
|
|
, unlock = locker.unlock
|
|
, getCacheStat = require("./get-stat.js")
|
|
, addLocalTarball = require("./add-local-tarball.js")
|
|
|
|
|
|
// 1. cacheDir = path.join(cache,'_git-remotes',sha1(u))
|
|
// 2. checkGitDir(cacheDir) ? 4. : 3. (rm cacheDir if necessary)
|
|
// 3. git clone --mirror u cacheDir
|
|
// 4. cd cacheDir && git fetch -a origin
|
|
// 5. git archive /tmp/random.tgz
|
|
// 6. addLocalTarball(/tmp/random.tgz) <gitref> --format=tar --prefix=package/
|
|
// silent flag is used if this should error quietly
|
|
module.exports = function addRemoteGit (u, parsed, silent, cb_) {
|
|
assert(typeof u === "string", "must have git URL")
|
|
assert(typeof parsed === "object", "must have parsed query")
|
|
assert(typeof cb_ === "function", "must have callback")
|
|
|
|
function cb (er, data) {
|
|
unlock(u, function () { cb_(er, data) })
|
|
}
|
|
|
|
cb_ = inflight(u, cb_)
|
|
|
|
if (!cb_) return
|
|
|
|
// git is so tricky!
|
|
// if the path is like ssh://foo:22/some/path then it works, but
|
|
// it needs the ssh://
|
|
// If the path is like ssh://foo:some/path then it works, but
|
|
// only if you remove the ssh://
|
|
var origUrl = u
|
|
u = u.replace(/^git\+/, "")
|
|
.replace(/#.*$/, "")
|
|
|
|
// ssh paths that are scp-style urls don't need the ssh://
|
|
if (parsed.pathname.match(/^\/?:/)) {
|
|
u = u.replace(/^ssh:\/\//, "")
|
|
}
|
|
|
|
lock(u, function (er) {
|
|
if (er) return cb(er)
|
|
|
|
// figure out what we should check out.
|
|
var co = parsed.hash && parsed.hash.substr(1) || "master"
|
|
|
|
var v = crypto.createHash("sha1").update(u).digest("hex").slice(0, 8)
|
|
v = u.replace(/[^a-zA-Z0-9]+/g, '-') + '-' + v
|
|
|
|
log.verbose("addRemoteGit", [u, co])
|
|
|
|
var p = path.join(npm.config.get("cache"), "_git-remotes", v)
|
|
|
|
checkGitDir(p, u, co, origUrl, silent, function(er, data) {
|
|
chmodr(p, npm.modes.file, function(erChmod) {
|
|
if (er) return cb(er, data)
|
|
return cb(erChmod, data)
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
function checkGitDir (p, u, co, origUrl, silent, cb) {
|
|
fs.stat(p, function (er, s) {
|
|
if (er) return cloneGitRemote(p, u, co, origUrl, silent, cb)
|
|
if (!s.isDirectory()) return rm(p, function (er){
|
|
if (er) return cb(er)
|
|
cloneGitRemote(p, u, co, origUrl, silent, cb)
|
|
})
|
|
|
|
var git = npm.config.get("git")
|
|
var args = [ "config", "--get", "remote.origin.url" ]
|
|
var env = gitEnv()
|
|
|
|
// check for git
|
|
which(git, function (err) {
|
|
if (err) {
|
|
err.code = "ENOGIT"
|
|
return cb(err)
|
|
}
|
|
exec(git, args, {cwd: p, env: env}, function (er, stdout, stderr) {
|
|
var stdoutTrimmed = (stdout + "\n" + stderr).trim()
|
|
if (er || u !== stdout.trim()) {
|
|
log.warn( "`git config --get remote.origin.url` returned "
|
|
+ "wrong result ("+u+")", stdoutTrimmed )
|
|
return rm(p, function (er){
|
|
if (er) return cb(er)
|
|
cloneGitRemote(p, u, co, origUrl, silent, cb)
|
|
})
|
|
}
|
|
log.verbose("git remote.origin.url", stdoutTrimmed)
|
|
archiveGitRemote(p, u, co, origUrl, cb)
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
function checkGitDir (p, u, co, origUrl, silent, cb) {
|
|
fs.stat(p, function (er, s) {
|
|
if (er) return cloneGitRemote(p, u, co, origUrl, silent, cb)
|
|
if (!s.isDirectory()) return rm(p, function (er){
|
|
if (er) return cb(er)
|
|
cloneGitRemote(p, u, co, origUrl, silent, cb)
|
|
})
|
|
|
|
var git = npm.config.get("git")
|
|
var args = [ "config", "--get", "remote.origin.url" ]
|
|
var env = gitEnv()
|
|
|
|
// check for git
|
|
which(git, function (err) {
|
|
if (err) {
|
|
err.code = "ENOGIT"
|
|
return cb(err)
|
|
}
|
|
exec(git, args, {cwd: p, env: env}, function (er, stdout, stderr) {
|
|
var stdoutTrimmed = (stdout + "\n" + stderr).trim()
|
|
if (er || u !== stdout.trim()) {
|
|
log.warn( "`git config --get remote.origin.url` returned "
|
|
+ "wrong result ("+u+")", stdoutTrimmed )
|
|
return rm(p, function (er){
|
|
if (er) return cb(er)
|
|
cloneGitRemote(p, u, co, origUrl, silent, cb)
|
|
})
|
|
}
|
|
log.verbose("git remote.origin.url", stdoutTrimmed)
|
|
archiveGitRemote(p, u, co, origUrl, cb)
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
function cloneGitRemote (p, u, co, origUrl, silent, cb) {
|
|
mkdir(p, function (er) {
|
|
if (er) return cb(er)
|
|
|
|
var git = npm.config.get("git")
|
|
var args = [ "clone", "--mirror", u, p ]
|
|
var env = gitEnv()
|
|
|
|
// check for git
|
|
which(git, function (err) {
|
|
if (err) {
|
|
err.code = "ENOGIT"
|
|
return cb(err)
|
|
}
|
|
exec(git, args, {cwd: p, env: env}, function (er, stdout, stderr) {
|
|
stdout = (stdout + "\n" + stderr).trim()
|
|
if (er) {
|
|
if (silent) {
|
|
log.verbose("git clone " + u, stdout)
|
|
} else {
|
|
log.error("git clone " + u, stdout)
|
|
}
|
|
return cb(er)
|
|
}
|
|
log.verbose("git clone " + u, stdout)
|
|
archiveGitRemote(p, u, co, origUrl, cb)
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
function archiveGitRemote (p, u, co, origUrl, cb) {
|
|
var git = npm.config.get("git")
|
|
var archive = [ "fetch", "-a", "origin" ]
|
|
var resolve = [ "rev-list", "-n1", co ]
|
|
var env = gitEnv()
|
|
|
|
var resolved = null
|
|
var tmp
|
|
|
|
exec(git, archive, {cwd: p, env: env}, function (er, stdout, stderr) {
|
|
stdout = (stdout + "\n" + stderr).trim()
|
|
if (er) {
|
|
log.error("git fetch -a origin ("+u+")", stdout)
|
|
return cb(er)
|
|
}
|
|
log.verbose("git fetch -a origin ("+u+")", stdout)
|
|
tmp = path.join(npm.tmp, Date.now()+"-"+Math.random(), "tmp.tgz")
|
|
verifyOwnership()
|
|
})
|
|
|
|
function verifyOwnership() {
|
|
if (process.platform === "win32") {
|
|
log.silly("verifyOwnership", "skipping for windows")
|
|
resolveHead()
|
|
} else {
|
|
getCacheStat(function(er, cs) {
|
|
if (er) {
|
|
log.error("Could not get cache stat")
|
|
return cb(er)
|
|
}
|
|
chownr(p, cs.uid, cs.gid, function(er) {
|
|
if (er) {
|
|
log.error("Failed to change folder ownership under npm cache for %s", p)
|
|
return cb(er)
|
|
}
|
|
resolveHead()
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
function resolveHead () {
|
|
exec(git, resolve, {cwd: p, env: env}, function (er, stdout, stderr) {
|
|
stdout = (stdout + "\n" + stderr).trim()
|
|
if (er) {
|
|
log.error("Failed resolving git HEAD (" + u + ")", stderr)
|
|
return cb(er)
|
|
}
|
|
log.verbose("git rev-list -n1 " + co, stdout)
|
|
var parsed = url.parse(origUrl)
|
|
parsed.hash = stdout
|
|
resolved = url.format(parsed)
|
|
|
|
// https://github.com/npm/npm/issues/3224
|
|
// node incorrectly sticks a / at the start of the path
|
|
// We know that the host won't change, so split and detect this
|
|
var spo = origUrl.split(parsed.host)
|
|
var spr = resolved.split(parsed.host)
|
|
if (spo[1].charAt(0) === ':' && spr[1].charAt(0) === '/')
|
|
spr[1] = spr[1].slice(1)
|
|
resolved = spr.join(parsed.host)
|
|
|
|
log.verbose('resolved git url', resolved)
|
|
next()
|
|
})
|
|
}
|
|
|
|
function next () {
|
|
mkdir(path.dirname(tmp), function (er) {
|
|
if (er) return cb(er)
|
|
var gzip = zlib.createGzip({ level: 9 })
|
|
var git = npm.config.get("git")
|
|
var args = ["archive", co, "--format=tar", "--prefix=package/"]
|
|
var out = fs.createWriteStream(tmp)
|
|
var env = gitEnv()
|
|
cb = once(cb)
|
|
var cp = spawn(git, args, { env: env, cwd: p })
|
|
cp.on("error", cb)
|
|
cp.stderr.on("data", function(chunk) {
|
|
log.silly(chunk.toString(), "git archive")
|
|
})
|
|
|
|
cp.stdout.pipe(gzip).pipe(out).on("close", function() {
|
|
addLocalTarball(tmp, null, null, function(er, data) {
|
|
if (data) data._resolved = resolved
|
|
cb(er, data)
|
|
})
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
var gitEnv_
|
|
function gitEnv () {
|
|
// git responds to env vars in some weird ways in post-receive hooks
|
|
// so don't carry those along.
|
|
if (gitEnv_) return gitEnv_
|
|
gitEnv_ = {}
|
|
for (var k in process.env) {
|
|
if (!~['GIT_PROXY_COMMAND','GIT_SSH','GIT_SSL_NO_VERIFY'].indexOf(k) && k.match(/^GIT/)) continue
|
|
gitEnv_[k] = process.env[k]
|
|
}
|
|
return gitEnv_
|
|
}
|