1 var mkdir = require("mkdirp")
2 , assert = require("assert")
3 , spawn = require("child_process").spawn
4 , exec = require("child_process").execFile
5 , once = require("once")
6 , fs = require("graceful-fs")
7 , log = require("npmlog")
8 , path = require("path")
10 , chownr = require("chownr")
11 , zlib = require("zlib")
12 , which = require("which")
13 , crypto = require("crypto")
14 , chmodr = require("chmodr")
15 , npm = require("../npm.js")
16 , rm = require("../utils/gently-rm.js")
17 , inflight = require("inflight")
18 , locker = require("../utils/locker.js")
20 , unlock = locker.unlock
21 , getCacheStat = require("./get-stat.js")
22 , addLocalTarball = require("./add-local-tarball.js")
25 // 1. cacheDir = path.join(cache,'_git-remotes',sha1(u))
26 // 2. checkGitDir(cacheDir) ? 4. : 3. (rm cacheDir if necessary)
27 // 3. git clone --mirror u cacheDir
28 // 4. cd cacheDir && git fetch -a origin
29 // 5. git archive /tmp/random.tgz
30 // 6. addLocalTarball(/tmp/random.tgz) <gitref> --format=tar --prefix=package/
31 // silent flag is used if this should error quietly
32 module.exports = function addRemoteGit (u, parsed, silent, cb_) {
33 assert(typeof u === "string", "must have git URL")
34 assert(typeof parsed === "object", "must have parsed query")
35 assert(typeof cb_ === "function", "must have callback")
37 function cb (er, data) {
38 unlock(u, function () { cb_(er, data) })
41 cb_ = inflight(u, cb_)
46 // if the path is like ssh://foo:22/some/path then it works, but
47 // it needs the ssh://
48 // If the path is like ssh://foo:some/path then it works, but
49 // only if you remove the ssh://
51 u = u.replace(/^git\+/, "")
54 // ssh paths that are scp-style urls don't need the ssh://
55 if (parsed.pathname.match(/^\/?:/)) {
56 u = u.replace(/^ssh:\/\//, "")
59 lock(u, function (er) {
62 // figure out what we should check out.
63 var co = parsed.hash && parsed.hash.substr(1) || "master"
65 var v = crypto.createHash("sha1").update(u).digest("hex").slice(0, 8)
66 v = u.replace(/[^a-zA-Z0-9]+/g, '-') + '-' + v
68 log.verbose("addRemoteGit", [u, co])
70 var p = path.join(npm.config.get("cache"), "_git-remotes", v)
72 checkGitDir(p, u, co, origUrl, silent, function(er, data) {
73 chmodr(p, npm.modes.file, function(erChmod) {
74 if (er) return cb(er, data)
75 return cb(erChmod, data)
81 function checkGitDir (p, u, co, origUrl, silent, cb) {
82 fs.stat(p, function (er, s) {
83 if (er) return cloneGitRemote(p, u, co, origUrl, silent, cb)
84 if (!s.isDirectory()) return rm(p, function (er){
86 cloneGitRemote(p, u, co, origUrl, silent, cb)
89 var git = npm.config.get("git")
90 var args = [ "config", "--get", "remote.origin.url" ]
94 which(git, function (err) {
99 exec(git, args, {cwd: p, env: env}, function (er, stdout, stderr) {
100 var stdoutTrimmed = (stdout + "\n" + stderr).trim()
101 if (er || u !== stdout.trim()) {
102 log.warn( "`git config --get remote.origin.url` returned "
103 + "wrong result ("+u+")", stdoutTrimmed )
104 return rm(p, function (er){
105 if (er) return cb(er)
106 cloneGitRemote(p, u, co, origUrl, silent, cb)
109 log.verbose("git remote.origin.url", stdoutTrimmed)
110 archiveGitRemote(p, u, co, origUrl, cb)
116 function checkGitDir (p, u, co, origUrl, silent, cb) {
117 fs.stat(p, function (er, s) {
118 if (er) return cloneGitRemote(p, u, co, origUrl, silent, cb)
119 if (!s.isDirectory()) return rm(p, function (er){
120 if (er) return cb(er)
121 cloneGitRemote(p, u, co, origUrl, silent, cb)
124 var git = npm.config.get("git")
125 var args = [ "config", "--get", "remote.origin.url" ]
129 which(git, function (err) {
134 exec(git, args, {cwd: p, env: env}, function (er, stdout, stderr) {
135 var stdoutTrimmed = (stdout + "\n" + stderr).trim()
136 if (er || u !== stdout.trim()) {
137 log.warn( "`git config --get remote.origin.url` returned "
138 + "wrong result ("+u+")", stdoutTrimmed )
139 return rm(p, function (er){
140 if (er) return cb(er)
141 cloneGitRemote(p, u, co, origUrl, silent, cb)
144 log.verbose("git remote.origin.url", stdoutTrimmed)
145 archiveGitRemote(p, u, co, origUrl, cb)
151 function cloneGitRemote (p, u, co, origUrl, silent, cb) {
152 mkdir(p, function (er) {
153 if (er) return cb(er)
155 var git = npm.config.get("git")
156 var args = [ "clone", "--mirror", u, p ]
160 which(git, function (err) {
165 exec(git, args, {cwd: p, env: env}, function (er, stdout, stderr) {
166 stdout = (stdout + "\n" + stderr).trim()
169 log.verbose("git clone " + u, stdout)
171 log.error("git clone " + u, stdout)
175 log.verbose("git clone " + u, stdout)
176 archiveGitRemote(p, u, co, origUrl, cb)
182 function archiveGitRemote (p, u, co, origUrl, cb) {
183 var git = npm.config.get("git")
184 var archive = [ "fetch", "-a", "origin" ]
185 var resolve = [ "rev-list", "-n1", co ]
191 exec(git, archive, {cwd: p, env: env}, function (er, stdout, stderr) {
192 stdout = (stdout + "\n" + stderr).trim()
194 log.error("git fetch -a origin ("+u+")", stdout)
197 log.verbose("git fetch -a origin ("+u+")", stdout)
198 tmp = path.join(npm.tmp, Date.now()+"-"+Math.random(), "tmp.tgz")
202 function verifyOwnership() {
203 if (process.platform === "win32") {
204 log.silly("verifyOwnership", "skipping for windows")
207 getCacheStat(function(er, cs) {
209 log.error("Could not get cache stat")
212 chownr(p, cs.uid, cs.gid, function(er) {
214 log.error("Failed to change folder ownership under npm cache for %s", p)
223 function resolveHead () {
224 exec(git, resolve, {cwd: p, env: env}, function (er, stdout, stderr) {
225 stdout = (stdout + "\n" + stderr).trim()
227 log.error("Failed resolving git HEAD (" + u + ")", stderr)
230 log.verbose("git rev-list -n1 " + co, stdout)
231 var parsed = url.parse(origUrl)
233 resolved = url.format(parsed)
235 // https://github.com/npm/npm/issues/3224
236 // node incorrectly sticks a / at the start of the path
237 // We know that the host won't change, so split and detect this
238 var spo = origUrl.split(parsed.host)
239 var spr = resolved.split(parsed.host)
240 if (spo[1].charAt(0) === ':' && spr[1].charAt(0) === '/')
241 spr[1] = spr[1].slice(1)
242 resolved = spr.join(parsed.host)
244 log.verbose('resolved git url', resolved)
250 mkdir(path.dirname(tmp), function (er) {
251 if (er) return cb(er)
252 var gzip = zlib.createGzip({ level: 9 })
253 var git = npm.config.get("git")
254 var args = ["archive", co, "--format=tar", "--prefix=package/"]
255 var out = fs.createWriteStream(tmp)
258 var cp = spawn(git, args, { env: env, cwd: p })
260 cp.stderr.on("data", function(chunk) {
261 log.silly(chunk.toString(), "git archive")
264 cp.stdout.pipe(gzip).pipe(out).on("close", function() {
265 addLocalTarball(tmp, null, null, function(er, data) {
266 if (data) data._resolved = resolved
276 // git responds to env vars in some weird ways in post-receive hooks
277 // so don't carry those along.
278 if (gitEnv_) return gitEnv_
280 for (var k in process.env) {
281 if (!~['GIT_PROXY_COMMAND','GIT_SSH','GIT_SSL_NO_VERIFY'].indexOf(k) && k.match(/^GIT/)) continue
282 gitEnv_[k] = process.env[k]