This repository was archived by the owner on Feb 18, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .travis.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
language: node_js
sudo: false
node_js:
- "4"
- "5"
- "6"
66 changes: 0 additions & 66 deletions exec-git.js

This file was deleted.

210 changes: 127 additions & 83 deletions github.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,11 +13,10 @@ var zlib = require('zlib');

var semver = require('semver');

var which = require('which');

function extend(dest, src) {
for (var key in src) {
dest[key] = src[key]
if (typeof dest[key] === 'object') extend(dest[key], src[key]);
else dest[key] = src[key];
}

return dest;
Expand All@@ -28,7 +27,7 @@ try {
}
catch(e) {}

var execGit = require('./exec-git');
var lsRemote = require('./ls-remote');

function createRemoteStrings(auth, hostname) {
var authString = auth.username ? (encodeURIComponent(auth.username) + ':' + encodeURIComponent(auth.password) + '@') : '';
Expand DownExpand Up@@ -82,15 +81,6 @@ function isGithubToken(token) {
}

var GithubLocation = function(options, ui) {

// ensure git is installed
try {
which.sync('git');
}
catch(ex) {
throw 'Git not installed. You can install git from `http://git-scm.com/downloads`.';
}

this.name = options.name;

this.max_repo_size = (options.maxRepoSize || 0) * 1024 * 1024;
Expand All@@ -112,22 +102,13 @@ var GithubLocation = function(options, ui) {

this.ui = ui;

this.execOpt = {
cwd: options.tmpDir,
timeout: options.timeout * 1000,
killSignal: 'SIGKILL',
maxBuffer: this.max_repo_size || 2 * 1024 * 1024,
env: extend({}, process.env)
};

this.defaultRequestOptions = {
headers: {
'User-Agent': 'jspm'
},
strictSSL: 'strictSSL' in options ? options.strictSSL : true
};

if (!this.defaultRequestOptions.strictSSL) {
this.execOpt.env.GIT_SSL_NO_VERIFY = '1'
}

var self = this, envMap = {
ca: 'GIT_SSL_CAINFO',
cert: 'GIT_SSL_CERT',
Expand All@@ -137,7 +118,6 @@ var GithubLocation = function(options, ui) {
['ca', 'cert', 'key'].forEach(function(key) {
if (key in options) {
var path = expandTilde(options[key]);
self.execOpt.env[envMap[key]] = path;
self.defaultRequestOptions[key] = fs.readFileSync(path, 'ascii');
}
});
Expand DownExpand Up@@ -302,16 +282,15 @@ GithubLocation.prototype = {
return new Promise(function(resolve, reject) {
request(extend({
uri: remoteString + repo + authSuffix,
headers: {
'User-Agent': 'jspm'
},
followRedirect: false
}, self.defaultRequestOptions
))
}, self.defaultRequestOptions))
.on('response', function(res) {
// redirect
if (res.statusCode == 301)
resolve({ redirect: self.name + ':' + res.headers.location.split('/').splice(3).join('/') });
if (res.statusCode == 301) {
// strip access token
var path = require('url').parse(res.headers.location).pathname;
resolve({ redirect: self.name + ':' + path.substr(1) });
}

if (res.statusCode == 401)
reject('Invalid authentication details.\n' +
Expand All@@ -338,56 +317,124 @@ GithubLocation.prototype = {
// { versions: { versionhash } }
// { notfound: true }
lookup: function(repo) {
var execOpt = this.execOpt;
var self = this;
var remoteString = this.remoteString;
return new Promise(function(resolve, reject) {
execGit('ls-remote ' + remoteString.replace(/(['"()])/g, '\\\$1') + repo + '.git refs/tags/* refs/heads/*', execOpt, function(err, stdout, stderr) {
if (err) {
if (err.toString().indexOf('not found') == -1) {
var error = new Error(stderr);
error.hideStack = true;
error.retriable = true;
reject(error);
}

return Promise.resolve()
.then(function() {
if (self.auth && self.auth.token) {
// use API to get branches/tags
return Promise.all(['tags', 'heads'].map(function(type) {
return asp(request)(extend({
uri: self.apiRemoteString + 'repos/' + repo + '/git/refs/' + type + self.authSuffix,
headers: {
'Accept': 'application/vnd.github.v3.raw'
}
}, self.defaultRequestOptions));
})).then(function(responses) {
var tagRes = responses[0];
var headRes = responses[1];

var refs = [];

// there should always be heads
if (headRes.statusCode != 200)
throw { statusCode: headRes.statusCode, headers: headRes.headers, api: true };
else
resolve({ notfound: true });
}
refs = refs.concat(JSON.parse(headRes.body));

// tag response can be 404, i.e. no tags
if (tagRes.statusCode == 200)
refs = refs.concat(JSON.parse(tagRes.body));
else if (tagRes.statusCode != 404)
throw { statusCode: tagRes.statusCode, headers: tagRes.headers, api: true };

versions = {};
var refs = stdout.split('\n');
for (var i = 0; i < refs.length; i++) {
if (!refs[i])
continue;
return refs.map(function(obj) {
return { sha: obj.object.sha, name: obj.ref };
});
});
} else {
// fallback to git-based approach
return false;
}
})
.catch(function(e) {
if (e.headers && e.headers['x-ratelimit-remaining'] == '0') {
if (!apiWarned) {
ui.log('API ratelimit reached, falling back to slower git protocol');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original numbers you posted showed the git protocol as faster. Did it turn out the API is faster then?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or is this a consequence of this implementation of the git protocol over the native git ls-remote?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was initially doing one request to get all references - tags/heads and pull requests. This proved very slow on large repos with lots of PRs.

at @tamird's suggestion I then did two requests, one to fetch tags, one for heads. This is now the same speed or faster than git, and can be cached (but isn't cached currently in my implementation)

apiWarned = true;
}
// fallback to git-based approach
return false;
} else {
throw e;
}
})
.then(function(refs) {
// API response
if (refs) return refs;

// no API auth or API auth is rate-limited, use git
return lsRemote(extend({
url: remoteString + repo + '.git'
}, self.defaultRequestOptions));
})
.then(function(refs) {
var versions = {};
refs.forEach(function(ref) {
var version;
var versionObj = { hash: ref.sha, meta: {} };
if (ref.name.substr(0, 11) == 'refs/heads/') {
version = ref.name.substr(11);
versionObj.stable = false;
}

var hash = refs[i].substr(0, refs[i].indexOf('\t'));
var refName = refs[i].substr(hash.length + 1);
var version;
var versionObj = { hash: hash, meta: {} };
else if (ref.name.substr(0, 10) == 'refs/tags/') {
if (ref.name.substr(ref.name.length - 3, 3) == '^{}')
version = ref.name.substr(10, ref.name.length - 13);
else
version = ref.name.substr(10);

if (refName.substr(0, 11) == 'refs/heads/') {
version = refName.substr(11);
versionObj.stable = false;
if (version.substr(0, 1) == 'v' && semver.valid(version.substr(1))) {
version = version.substr(1);
// note when we remove a "v" which versions we need to add it back to
// to work out the tag version again
versionObj.meta.vPrefix = true;
}
}

else if (refName.substr(0, 10) == 'refs/tags/') {
if (refName.substr(refName.length - 3, 3) == '^{}')
version = refName.substr(10, refName.length - 13);
else
version = refName.substr(10);

if (version.substr(0, 1) == 'v' && semver.valid(version.substr(1))) {
version = version.substr(1);
// note when we remove a "v" which versions we need to add it back to
// to work out the tag version again
versionObj.meta.vPrefix = true;
}
}
versions[version] = versionObj;
});

versions[version] = versionObj;
return { versions: versions };
})
.catch(function(error) {
if (error.statusCode) {
var headerSuffix = '\n' + JSON.stringify(error.headers, null, 2);

if (error.statusCode == 406 || error.statusCode == 401) {
if (error.api) {
// TODO: replace this with the api failure response code from below
error = new Error('api says invalid auth: ' + error.statusCode + headerSuffix);
}
else {
error = new Error('Invalid authentication details.\n' +
'Run %jspm registry config ' + self.name + '% to reconfigure the credentials, or update them in your ~/.netrc file.');
}
}
else if (error.statusCode == 404)
return { notfound: true };
else
error = new Error('invalid status code: ' + error.statusCode + headerSuffix);
}

resolve({ versions: versions });
});
if (typeof error == 'string') {
error = new Error(error);
}

error.retriable = true;
error.hideStack = true;
throw error;
});
},

Expand All@@ -400,17 +447,16 @@ GithubLocation.prototype = {
var self = this;
var ui = this.ui;

return asp(request)({
return asp(request)(extend({
uri: this.apiRemoteString + 'repos/' + repo + '/contents/package.json' + this.authSuffix,
headers: {
'User-Agent': 'jspm',
'Accept': 'application/vnd.github.v3.raw'
},
qs: {
ref: version
},
strictSSL: this.defaultRequestOptions.strictSSL
}).then(function(res) {
}
}, self.defaultRequestOptions))
.then(function(res) {
// API auth failure warnings
function apiFailWarn(reason, showAuthCommand) {
if (apiWarned)
Expand DownExpand Up@@ -534,7 +580,6 @@ GithubLocation.prototype = {
if (meta.vPrefix)
version = 'v' + version;

var execOpt = this.execOpt;
var max_repo_size = this.max_repo_size;
var remoteString = this.remoteString;
var authSuffix = this.authSuffix;
Expand All@@ -543,11 +588,10 @@ GithubLocation.prototype = {

// Download from the git archive
return new Promise(function(resolve, reject) {
request({
request(extend({
uri: remoteString + repo + '/archive/' + version + '.tar.gz' + authSuffix,
headers: { 'accept': 'application/octet-stream' },
strictSSL: self.defaultRequestOptions.strictSSL
})
headers: { 'accept': 'application/octet-stream' }
}, self.defaultRequestOptions))
.on('response', function(pkgRes) {
if (pkgRes.statusCode != 200)
return reject('Bad response code ' + pkgRes.statusCode);
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
This repository was archived by the owner on Feb 18, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .travis.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
language: node_js
sudo: false
node_js:
- "4"
- "5"
- "6"
66 changes: 0 additions & 66 deletions exec-git.js

This file was deleted.

210 changes: 127 additions & 83 deletions github.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,11 +13,10 @@ var zlib = require('zlib');

var semver = require('semver');

var which = require('which');

function extend(dest, src) {
for (var key in src) {
dest[key] = src[key]
if (typeof dest[key] === 'object') extend(dest[key], src[key]);
else dest[key] = src[key];
}

return dest;
Expand All@@ -28,7 +27,7 @@ try {
}
catch(e) {}

var execGit = require('./exec-git');
var lsRemote = require('./ls-remote');

function createRemoteStrings(auth, hostname) {
var authString = auth.username ? (encodeURIComponent(auth.username) + ':' + encodeURIComponent(auth.password) + '@') : '';
Expand DownExpand Up@@ -82,15 +81,6 @@ function isGithubToken(token) {
}

var GithubLocation = function(options, ui) {

// ensure git is installed
try {
which.sync('git');
}
catch(ex) {
throw 'Git not installed. You can install git from `http://git-scm.com/downloads`.';
}

this.name = options.name;

this.max_repo_size = (options.maxRepoSize || 0) * 1024 * 1024;
Expand All@@ -112,22 +102,13 @@ var GithubLocation = function(options, ui) {

this.ui = ui;

this.execOpt = {
cwd: options.tmpDir,
timeout: options.timeout * 1000,
killSignal: 'SIGKILL',
maxBuffer: this.max_repo_size || 2 * 1024 * 1024,
env: extend({}, process.env)
};

this.defaultRequestOptions = {
headers: {
'User-Agent': 'jspm'
},
strictSSL: 'strictSSL' in options ? options.strictSSL : true
};

if (!this.defaultRequestOptions.strictSSL) {
this.execOpt.env.GIT_SSL_NO_VERIFY = '1'
}

var self = this, envMap = {
ca: 'GIT_SSL_CAINFO',
cert: 'GIT_SSL_CERT',
Expand All@@ -137,7 +118,6 @@ var GithubLocation = function(options, ui) {
['ca', 'cert', 'key'].forEach(function(key) {
if (key in options) {
var path = expandTilde(options[key]);
self.execOpt.env[envMap[key]] = path;
self.defaultRequestOptions[key] = fs.readFileSync(path, 'ascii');
}
});
Expand DownExpand Up@@ -302,16 +282,15 @@ GithubLocation.prototype = {
return new Promise(function(resolve, reject) {
request(extend({
uri: remoteString + repo + authSuffix,
headers: {
'User-Agent': 'jspm'
},
followRedirect: false
}, self.defaultRequestOptions
))
}, self.defaultRequestOptions))
.on('response', function(res) {
// redirect
if (res.statusCode == 301)
resolve({ redirect: self.name + ':' + res.headers.location.split('/').splice(3).join('/') });
if (res.statusCode == 301) {
// strip access token
var path = require('url').parse(res.headers.location).pathname;
resolve({ redirect: self.name + ':' + path.substr(1) });
}

if (res.statusCode == 401)
reject('Invalid authentication details.\n' +
Expand All@@ -338,56 +317,124 @@ GithubLocation.prototype = {
// { versions: { versionhash } }
// { notfound: true }
lookup: function(repo) {
var execOpt = this.execOpt;
var self = this;
var remoteString = this.remoteString;
return new Promise(function(resolve, reject) {
execGit('ls-remote ' + remoteString.replace(/(['"()])/g, '\\\$1') + repo + '.git refs/tags/* refs/heads/*', execOpt, function(err, stdout, stderr) {
if (err) {
if (err.toString().indexOf('not found') == -1) {
var error = new Error(stderr);
error.hideStack = true;
error.retriable = true;
reject(error);
}

return Promise.resolve()
.then(function() {
if (self.auth && self.auth.token) {
// use API to get branches/tags
return Promise.all(['tags', 'heads'].map(function(type) {
return asp(request)(extend({
uri: self.apiRemoteString + 'repos/' + repo + '/git/refs/' + type + self.authSuffix,
headers: {
'Accept': 'application/vnd.github.v3.raw'
}
}, self.defaultRequestOptions));
})).then(function(responses) {
var tagRes = responses[0];
var headRes = responses[1];

var refs = [];

// there should always be heads
if (headRes.statusCode != 200)
throw { statusCode: headRes.statusCode, headers: headRes.headers, api: true };
else
resolve({ notfound: true });
}
refs = refs.concat(JSON.parse(headRes.body));

// tag response can be 404, i.e. no tags
if (tagRes.statusCode == 200)
refs = refs.concat(JSON.parse(tagRes.body));
else if (tagRes.statusCode != 404)
throw { statusCode: tagRes.statusCode, headers: tagRes.headers, api: true };

versions = {};
var refs = stdout.split('\n');
for (var i = 0; i < refs.length; i++) {
if (!refs[i])
continue;
return refs.map(function(obj) {
return { sha: obj.object.sha, name: obj.ref };
});
});
} else {
// fallback to git-based approach
return false;
}
})
.catch(function(e) {
if (e.headers && e.headers['x-ratelimit-remaining'] == '0') {
if (!apiWarned) {
ui.log('API ratelimit reached, falling back to slower git protocol');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original numbers you posted showed the git protocol as faster. Did it turn out the API is faster then?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or is this a consequence of this implementation of the git protocol over the native git ls-remote?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was initially doing one request to get all references - tags/heads and pull requests. This proved very slow on large repos with lots of PRs.

at @tamird's suggestion I then did two requests, one to fetch tags, one for heads. This is now the same speed or faster than git, and can be cached (but isn't cached currently in my implementation)

apiWarned = true;
}
// fallback to git-based approach
return false;
} else {
throw e;
}
})
.then(function(refs) {
// API response
if (refs) return refs;

// no API auth or API auth is rate-limited, use git
return lsRemote(extend({
url: remoteString + repo + '.git'
}, self.defaultRequestOptions));
})
.then(function(refs) {
var versions = {};
refs.forEach(function(ref) {
var version;
var versionObj = { hash: ref.sha, meta: {} };
if (ref.name.substr(0, 11) == 'refs/heads/') {
version = ref.name.substr(11);
versionObj.stable = false;
}

var hash = refs[i].substr(0, refs[i].indexOf('\t'));
var refName = refs[i].substr(hash.length + 1);
var version;
var versionObj = { hash: hash, meta: {} };
else if (ref.name.substr(0, 10) == 'refs/tags/') {
if (ref.name.substr(ref.name.length - 3, 3) == '^{}')
version = ref.name.substr(10, ref.name.length - 13);
else
version = ref.name.substr(10);

if (refName.substr(0, 11) == 'refs/heads/') {
version = refName.substr(11);
versionObj.stable = false;
if (version.substr(0, 1) == 'v' && semver.valid(version.substr(1))) {
version = version.substr(1);
// note when we remove a "v" which versions we need to add it back to
// to work out the tag version again
versionObj.meta.vPrefix = true;
}
}

else if (refName.substr(0, 10) == 'refs/tags/') {
if (refName.substr(refName.length - 3, 3) == '^{}')
version = refName.substr(10, refName.length - 13);
else
version = refName.substr(10);

if (version.substr(0, 1) == 'v' && semver.valid(version.substr(1))) {
version = version.substr(1);
// note when we remove a "v" which versions we need to add it back to
// to work out the tag version again
versionObj.meta.vPrefix = true;
}
}
versions[version] = versionObj;
});

versions[version] = versionObj;
return { versions: versions };
})
.catch(function(error) {
if (error.statusCode) {
var headerSuffix = '\n' + JSON.stringify(error.headers, null, 2);

if (error.statusCode == 406 || error.statusCode == 401) {
if (error.api) {
// TODO: replace this with the api failure response code from below
error = new Error('api says invalid auth: ' + error.statusCode + headerSuffix);
}
else {
error = new Error('Invalid authentication details.\n' +
'Run %jspm registry config ' + self.name + '% to reconfigure the credentials, or update them in your ~/.netrc file.');
}
}
else if (error.statusCode == 404)
return { notfound: true };
else
error = new Error('invalid status code: ' + error.statusCode + headerSuffix);
}

resolve({ versions: versions });
});
if (typeof error == 'string') {
error = new Error(error);
}

error.retriable = true;
error.hideStack = true;
throw error;
});
},

Expand All@@ -400,17 +447,16 @@ GithubLocation.prototype = {
var self = this;
var ui = this.ui;

return asp(request)({
return asp(request)(extend({
uri: this.apiRemoteString + 'repos/' + repo + '/contents/package.json' + this.authSuffix,
headers: {
'User-Agent': 'jspm',
'Accept': 'application/vnd.github.v3.raw'
},
qs: {
ref: version
},
strictSSL: this.defaultRequestOptions.strictSSL
}).then(function(res) {
}
}, self.defaultRequestOptions))
.then(function(res) {
// API auth failure warnings
function apiFailWarn(reason, showAuthCommand) {
if (apiWarned)
Expand DownExpand Up@@ -534,7 +580,6 @@ GithubLocation.prototype = {
if (meta.vPrefix)
version = 'v' + version;

var execOpt = this.execOpt;
var max_repo_size = this.max_repo_size;
var remoteString = this.remoteString;
var authSuffix = this.authSuffix;
Expand All@@ -543,11 +588,10 @@ GithubLocation.prototype = {

// Download from the git archive
return new Promise(function(resolve, reject) {
request({
request(extend({
uri: remoteString + repo + '/archive/' + version + '.tar.gz' + authSuffix,
headers: { 'accept': 'application/octet-stream' },
strictSSL: self.defaultRequestOptions.strictSSL
})
headers: { 'accept': 'application/octet-stream' }
}, self.defaultRequestOptions))
.on('response', function(pkgRes) {
if (pkgRes.statusCode != 200)
return reject('Bad response code ' + pkgRes.statusCode);
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Feb 18, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .travis.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
language: node_js
sudo: false
node_js:
- "4"
- "5"
- "6"
66 changes: 0 additions & 66 deletions exec-git.js

This file was deleted.

210 changes: 127 additions & 83 deletions github.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,11 +13,10 @@ var zlib = require('zlib');

var semver = require('semver');

var which = require('which');

function extend(dest, src) {
for (var key in src) {
dest[key] = src[key]
if (typeof dest[key] === 'object') extend(dest[key], src[key]);
else dest[key] = src[key];
}

return dest;
Expand All@@ -28,7 +27,7 @@ try {
}
catch(e) {}

var execGit = require('./exec-git');
var lsRemote = require('./ls-remote');

function createRemoteStrings(auth, hostname) {
var authString = auth.username ? (encodeURIComponent(auth.username) + ':' + encodeURIComponent(auth.password) + '@') : '';
Expand DownExpand Up@@ -82,15 +81,6 @@ function isGithubToken(token) {
}

var GithubLocation = function(options, ui) {

// ensure git is installed
try {
which.sync('git');
}
catch(ex) {
throw 'Git not installed. You can install git from `http://git-scm.com/downloads`.';
}

this.name = options.name;

this.max_repo_size = (options.maxRepoSize || 0) * 1024 * 1024;
Expand All@@ -112,22 +102,13 @@ var GithubLocation = function(options, ui) {

this.ui = ui;

this.execOpt = {
cwd: options.tmpDir,
timeout: options.timeout * 1000,
killSignal: 'SIGKILL',
maxBuffer: this.max_repo_size || 2 * 1024 * 1024,
env: extend({}, process.env)
};

this.defaultRequestOptions = {
headers: {
'User-Agent': 'jspm'
},
strictSSL: 'strictSSL' in options ? options.strictSSL : true
};

if (!this.defaultRequestOptions.strictSSL) {
this.execOpt.env.GIT_SSL_NO_VERIFY = '1'
}

var self = this, envMap = {
ca: 'GIT_SSL_CAINFO',
cert: 'GIT_SSL_CERT',
Expand All@@ -137,7 +118,6 @@ var GithubLocation = function(options, ui) {
['ca', 'cert', 'key'].forEach(function(key) {
if (key in options) {
var path = expandTilde(options[key]);
self.execOpt.env[envMap[key]] = path;
self.defaultRequestOptions[key] = fs.readFileSync(path, 'ascii');
}
});
Expand DownExpand Up@@ -302,16 +282,15 @@ GithubLocation.prototype = {
return new Promise(function(resolve, reject) {
request(extend({
uri: remoteString + repo + authSuffix,
headers: {
'User-Agent': 'jspm'
},
followRedirect: false
}, self.defaultRequestOptions
))
}, self.defaultRequestOptions))
.on('response', function(res) {
// redirect
if (res.statusCode == 301)
resolve({ redirect: self.name + ':' + res.headers.location.split('/').splice(3).join('/') });
if (res.statusCode == 301) {
// strip access token
var path = require('url').parse(res.headers.location).pathname;
resolve({ redirect: self.name + ':' + path.substr(1) });
}

if (res.statusCode == 401)
reject('Invalid authentication details.\n' +
Expand All@@ -338,56 +317,124 @@ GithubLocation.prototype = {
// { versions: { versionhash } }
// { notfound: true }
lookup: function(repo) {
var execOpt = this.execOpt;
var self = this;
var remoteString = this.remoteString;
return new Promise(function(resolve, reject) {
execGit('ls-remote ' + remoteString.replace(/(['"()])/g, '\\\$1') + repo + '.git refs/tags/* refs/heads/*', execOpt, function(err, stdout, stderr) {
if (err) {
if (err.toString().indexOf('not found') == -1) {
var error = new Error(stderr);
error.hideStack = true;
error.retriable = true;
reject(error);
}

return Promise.resolve()
.then(function() {
if (self.auth && self.auth.token) {
// use API to get branches/tags
return Promise.all(['tags', 'heads'].map(function(type) {
return asp(request)(extend({
uri: self.apiRemoteString + 'repos/' + repo + '/git/refs/' + type + self.authSuffix,
headers: {
'Accept': 'application/vnd.github.v3.raw'
}
}, self.defaultRequestOptions));
})).then(function(responses) {
var tagRes = responses[0];
var headRes = responses[1];

var refs = [];

// there should always be heads
if (headRes.statusCode != 200)
throw { statusCode: headRes.statusCode, headers: headRes.headers, api: true };
else
resolve({ notfound: true });
}
refs = refs.concat(JSON.parse(headRes.body));

// tag response can be 404, i.e. no tags
if (tagRes.statusCode == 200)
refs = refs.concat(JSON.parse(tagRes.body));
else if (tagRes.statusCode != 404)
throw { statusCode: tagRes.statusCode, headers: tagRes.headers, api: true };

versions = {};
var refs = stdout.split('\n');
for (var i = 0; i < refs.length; i++) {
if (!refs[i])
continue;
return refs.map(function(obj) {
return { sha: obj.object.sha, name: obj.ref };
});
});
} else {
// fallback to git-based approach
return false;
}
})
.catch(function(e) {
if (e.headers && e.headers['x-ratelimit-remaining'] == '0') {
if (!apiWarned) {
ui.log('API ratelimit reached, falling back to slower git protocol');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original numbers you posted showed the git protocol as faster. Did it turn out the API is faster then?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or is this a consequence of this implementation of the git protocol over the native git ls-remote?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was initially doing one request to get all references - tags/heads and pull requests. This proved very slow on large repos with lots of PRs.

at @tamird's suggestion I then did two requests, one to fetch tags, one for heads. This is now the same speed or faster than git, and can be cached (but isn't cached currently in my implementation)

apiWarned = true;
}
// fallback to git-based approach
return false;
} else {
throw e;
}
})
.then(function(refs) {
// API response
if (refs) return refs;

// no API auth or API auth is rate-limited, use git
return lsRemote(extend({
url: remoteString + repo + '.git'
}, self.defaultRequestOptions));
})
.then(function(refs) {
var versions = {};
refs.forEach(function(ref) {
var version;
var versionObj = { hash: ref.sha, meta: {} };
if (ref.name.substr(0, 11) == 'refs/heads/') {
version = ref.name.substr(11);
versionObj.stable = false;
}

var hash = refs[i].substr(0, refs[i].indexOf('\t'));
var refName = refs[i].substr(hash.length + 1);
var version;
var versionObj = { hash: hash, meta: {} };
else if (ref.name.substr(0, 10) == 'refs/tags/') {
if (ref.name.substr(ref.name.length - 3, 3) == '^{}')
version = ref.name.substr(10, ref.name.length - 13);
else
version = ref.name.substr(10);

if (refName.substr(0, 11) == 'refs/heads/') {
version = refName.substr(11);
versionObj.stable = false;
if (version.substr(0, 1) == 'v' && semver.valid(version.substr(1))) {
version = version.substr(1);
// note when we remove a "v" which versions we need to add it back to
// to work out the tag version again
versionObj.meta.vPrefix = true;
}
}

else if (refName.substr(0, 10) == 'refs/tags/') {
if (refName.substr(refName.length - 3, 3) == '^{}')
version = refName.substr(10, refName.length - 13);
else
version = refName.substr(10);

if (version.substr(0, 1) == 'v' && semver.valid(version.substr(1))) {
version = version.substr(1);
// note when we remove a "v" which versions we need to add it back to
// to work out the tag version again
versionObj.meta.vPrefix = true;
}
}
versions[version] = versionObj;
});

versions[version] = versionObj;
return { versions: versions };
})
.catch(function(error) {
if (error.statusCode) {
var headerSuffix = '\n' + JSON.stringify(error.headers, null, 2);

if (error.statusCode == 406 || error.statusCode == 401) {
if (error.api) {
// TODO: replace this with the api failure response code from below
error = new Error('api says invalid auth: ' + error.statusCode + headerSuffix);
}
else {
error = new Error('Invalid authentication details.\n' +
'Run %jspm registry config ' + self.name + '% to reconfigure the credentials, or update them in your ~/.netrc file.');
}
}
else if (error.statusCode == 404)
return { notfound: true };
else
error = new Error('invalid status code: ' + error.statusCode + headerSuffix);
}

resolve({ versions: versions });
});
if (typeof error == 'string') {
error = new Error(error);
}

error.retriable = true;
error.hideStack = true;
throw error;
});
},

Expand All@@ -400,17 +447,16 @@ GithubLocation.prototype = {
var self = this;
var ui = this.ui;

return asp(request)({
return asp(request)(extend({
uri: this.apiRemoteString + 'repos/' + repo + '/contents/package.json' + this.authSuffix,
headers: {
'User-Agent': 'jspm',
'Accept': 'application/vnd.github.v3.raw'
},
qs: {
ref: version
},
strictSSL: this.defaultRequestOptions.strictSSL
}).then(function(res) {
}
}, self.defaultRequestOptions))
.then(function(res) {
// API auth failure warnings
function apiFailWarn(reason, showAuthCommand) {
if (apiWarned)
Expand DownExpand Up@@ -534,7 +580,6 @@ GithubLocation.prototype = {
if (meta.vPrefix)
version = 'v' + version;

var execOpt = this.execOpt;
var max_repo_size = this.max_repo_size;
var remoteString = this.remoteString;
var authSuffix = this.authSuffix;
Expand All@@ -543,11 +588,10 @@ GithubLocation.prototype = {

// Download from the git archive
return new Promise(function(resolve, reject) {
request({
request(extend({
uri: remoteString + repo + '/archive/' + version + '.tar.gz' + authSuffix,
headers: { 'accept': 'application/octet-stream' },
strictSSL: self.defaultRequestOptions.strictSSL
})
headers: { 'accept': 'application/octet-stream' }
}, self.defaultRequestOptions))
.on('response', function(pkgRes) {
if (pkgRes.statusCode != 200)
return reject('Bad response code ' + pkgRes.statusCode);
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Feb 18, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .travis.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
language: node_js
sudo: false
node_js:
- "4"
- "5"
- "6"
66 changes: 0 additions & 66 deletions exec-git.js

This file was deleted.

210 changes: 127 additions & 83 deletions github.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,11 +13,10 @@ var zlib = require('zlib');

var semver = require('semver');

var which = require('which');

function extend(dest, src) {
for (var key in src) {
dest[key] = src[key]
if (typeof dest[key] === 'object') extend(dest[key], src[key]);
else dest[key] = src[key];
}

return dest;
Expand All@@ -28,7 +27,7 @@ try {
}
catch(e) {}

var execGit = require('./exec-git');
var lsRemote = require('./ls-remote');

function createRemoteStrings(auth, hostname) {
var authString = auth.username ? (encodeURIComponent(auth.username) + ':' + encodeURIComponent(auth.password) + '@') : '';
Expand DownExpand Up@@ -82,15 +81,6 @@ function isGithubToken(token) {
}

var GithubLocation = function(options, ui) {

// ensure git is installed
try {
which.sync('git');
}
catch(ex) {
throw 'Git not installed. You can install git from `http://git-scm.com/downloads`.';
}

this.name = options.name;

this.max_repo_size = (options.maxRepoSize || 0) * 1024 * 1024;
Expand All@@ -112,22 +102,13 @@ var GithubLocation = function(options, ui) {

this.ui = ui;

this.execOpt = {
cwd: options.tmpDir,
timeout: options.timeout * 1000,
killSignal: 'SIGKILL',
maxBuffer: this.max_repo_size || 2 * 1024 * 1024,
env: extend({}, process.env)
};

this.defaultRequestOptions = {
headers: {
'User-Agent': 'jspm'
},
strictSSL: 'strictSSL' in options ? options.strictSSL : true
};

if (!this.defaultRequestOptions.strictSSL) {
this.execOpt.env.GIT_SSL_NO_VERIFY = '1'
}

var self = this, envMap = {
ca: 'GIT_SSL_CAINFO',
cert: 'GIT_SSL_CERT',
Expand All@@ -137,7 +118,6 @@ var GithubLocation = function(options, ui) {
['ca', 'cert', 'key'].forEach(function(key) {
if (key in options) {
var path = expandTilde(options[key]);
self.execOpt.env[envMap[key]] = path;
self.defaultRequestOptions[key] = fs.readFileSync(path, 'ascii');
}
});
Expand DownExpand Up@@ -302,16 +282,15 @@ GithubLocation.prototype = {
return new Promise(function(resolve, reject) {
request(extend({
uri: remoteString + repo + authSuffix,
headers: {
'User-Agent': 'jspm'
},
followRedirect: false
}, self.defaultRequestOptions
))
}, self.defaultRequestOptions))
.on('response', function(res) {
// redirect
if (res.statusCode == 301)
resolve({ redirect: self.name + ':' + res.headers.location.split('/').splice(3).join('/') });
if (res.statusCode == 301) {
// strip access token
var path = require('url').parse(res.headers.location).pathname;
resolve({ redirect: self.name + ':' + path.substr(1) });
}

if (res.statusCode == 401)
reject('Invalid authentication details.\n' +
Expand All@@ -338,56 +317,124 @@ GithubLocation.prototype = {
// { versions: { versionhash } }
// { notfound: true }
lookup: function(repo) {
var execOpt = this.execOpt;
var self = this;
var remoteString = this.remoteString;
return new Promise(function(resolve, reject) {
execGit('ls-remote ' + remoteString.replace(/(['"()])/g, '\\\$1') + repo + '.git refs/tags/* refs/heads/*', execOpt, function(err, stdout, stderr) {
if (err) {
if (err.toString().indexOf('not found') == -1) {
var error = new Error(stderr);
error.hideStack = true;
error.retriable = true;
reject(error);
}

return Promise.resolve()
.then(function() {
if (self.auth && self.auth.token) {
// use API to get branches/tags
return Promise.all(['tags', 'heads'].map(function(type) {
return asp(request)(extend({
uri: self.apiRemoteString + 'repos/' + repo + '/git/refs/' + type + self.authSuffix,
headers: {
'Accept': 'application/vnd.github.v3.raw'
}
}, self.defaultRequestOptions));
})).then(function(responses) {
var tagRes = responses[0];
var headRes = responses[1];

var refs = [];

// there should always be heads
if (headRes.statusCode != 200)
throw { statusCode: headRes.statusCode, headers: headRes.headers, api: true };
else
resolve({ notfound: true });
}
refs = refs.concat(JSON.parse(headRes.body));

// tag response can be 404, i.e. no tags
if (tagRes.statusCode == 200)
refs = refs.concat(JSON.parse(tagRes.body));
else if (tagRes.statusCode != 404)
throw { statusCode: tagRes.statusCode, headers: tagRes.headers, api: true };

versions = {};
var refs = stdout.split('\n');
for (var i = 0; i < refs.length; i++) {
if (!refs[i])
continue;
return refs.map(function(obj) {
return { sha: obj.object.sha, name: obj.ref };
});
});
} else {
// fallback to git-based approach
return false;
}
})
.catch(function(e) {
if (e.headers && e.headers['x-ratelimit-remaining'] == '0') {
if (!apiWarned) {
ui.log('API ratelimit reached, falling back to slower git protocol');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original numbers you posted showed the git protocol as faster. Did it turn out the API is faster then?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or is this a consequence of this implementation of the git protocol over the native git ls-remote?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was initially doing one request to get all references - tags/heads and pull requests. This proved very slow on large repos with lots of PRs.

at @tamird's suggestion I then did two requests, one to fetch tags, one for heads. This is now the same speed or faster than git, and can be cached (but isn't cached currently in my implementation)

apiWarned = true;
}
// fallback to git-based approach
return false;
} else {
throw e;
}
})
.then(function(refs) {
// API response
if (refs) return refs;

// no API auth or API auth is rate-limited, use git
return lsRemote(extend({
url: remoteString + repo + '.git'
}, self.defaultRequestOptions));
})
.then(function(refs) {
var versions = {};
refs.forEach(function(ref) {
var version;
var versionObj = { hash: ref.sha, meta: {} };
if (ref.name.substr(0, 11) == 'refs/heads/') {
version = ref.name.substr(11);
versionObj.stable = false;
}

var hash = refs[i].substr(0, refs[i].indexOf('\t'));
var refName = refs[i].substr(hash.length + 1);
var version;
var versionObj = { hash: hash, meta: {} };
else if (ref.name.substr(0, 10) == 'refs/tags/') {
if (ref.name.substr(ref.name.length - 3, 3) == '^{}')
version = ref.name.substr(10, ref.name.length - 13);
else
version = ref.name.substr(10);

if (refName.substr(0, 11) == 'refs/heads/') {
version = refName.substr(11);
versionObj.stable = false;
if (version.substr(0, 1) == 'v' && semver.valid(version.substr(1))) {
version = version.substr(1);
// note when we remove a "v" which versions we need to add it back to
// to work out the tag version again
versionObj.meta.vPrefix = true;
}
}

else if (refName.substr(0, 10) == 'refs/tags/') {
if (refName.substr(refName.length - 3, 3) == '^{}')
version = refName.substr(10, refName.length - 13);
else
version = refName.substr(10);

if (version.substr(0, 1) == 'v' && semver.valid(version.substr(1))) {
version = version.substr(1);
// note when we remove a "v" which versions we need to add it back to
// to work out the tag version again
versionObj.meta.vPrefix = true;
}
}
versions[version] = versionObj;
});

versions[version] = versionObj;
return { versions: versions };
})
.catch(function(error) {
if (error.statusCode) {
var headerSuffix = '\n' + JSON.stringify(error.headers, null, 2);

if (error.statusCode == 406 || error.statusCode == 401) {
if (error.api) {
// TODO: replace this with the api failure response code from below
error = new Error('api says invalid auth: ' + error.statusCode + headerSuffix);
}
else {
error = new Error('Invalid authentication details.\n' +
'Run %jspm registry config ' + self.name + '% to reconfigure the credentials, or update them in your ~/.netrc file.');
}
}
else if (error.statusCode == 404)
return { notfound: true };
else
error = new Error('invalid status code: ' + error.statusCode + headerSuffix);
}

resolve({ versions: versions });
});
if (typeof error == 'string') {
error = new Error(error);
}

error.retriable = true;
error.hideStack = true;
throw error;
});
},

Expand All@@ -400,17 +447,16 @@ GithubLocation.prototype = {
var self = this;
var ui = this.ui;

return asp(request)({
return asp(request)(extend({
uri: this.apiRemoteString + 'repos/' + repo + '/contents/package.json' + this.authSuffix,
headers: {
'User-Agent': 'jspm',
'Accept': 'application/vnd.github.v3.raw'
},
qs: {
ref: version
},
strictSSL: this.defaultRequestOptions.strictSSL
}).then(function(res) {
}
}, self.defaultRequestOptions))
.then(function(res) {
// API auth failure warnings
function apiFailWarn(reason, showAuthCommand) {
if (apiWarned)
Expand DownExpand Up@@ -534,7 +580,6 @@ GithubLocation.prototype = {
if (meta.vPrefix)
version = 'v' + version;

var execOpt = this.execOpt;
var max_repo_size = this.max_repo_size;
var remoteString = this.remoteString;
var authSuffix = this.authSuffix;
Expand All@@ -543,11 +588,10 @@ GithubLocation.prototype = {

// Download from the git archive
return new Promise(function(resolve, reject) {
request({
request(extend({
uri: remoteString + repo + '/archive/' + version + '.tar.gz' + authSuffix,
headers: { 'accept': 'application/octet-stream' },
strictSSL: self.defaultRequestOptions.strictSSL
})
headers: { 'accept': 'application/octet-stream' }
}, self.defaultRequestOptions))
.on('response', function(pkgRes) {
if (pkgRes.statusCode != 200)
return reject('Bad response code ' + pkgRes.statusCode);
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
This repository was archived by the owner on Feb 18, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .travis.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
language: node_js
sudo: false
node_js:
- "4"
- "5"
- "6"
66 changes: 0 additions & 66 deletions exec-git.js

This file was deleted.

210 changes: 127 additions & 83 deletions github.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,11 +13,10 @@ var zlib = require('zlib');

var semver = require('semver');

var which = require('which');

function extend(dest, src) {
for (var key in src) {
dest[key] = src[key]
if (typeof dest[key] === 'object') extend(dest[key], src[key]);
else dest[key] = src[key];
}

return dest;
Expand All@@ -28,7 +27,7 @@ try {
}
catch(e) {}

var execGit = require('./exec-git');
var lsRemote = require('./ls-remote');

function createRemoteStrings(auth, hostname) {
var authString = auth.username ? (encodeURIComponent(auth.username) + ':' + encodeURIComponent(auth.password) + '@') : '';
Expand DownExpand Up@@ -82,15 +81,6 @@ function isGithubToken(token) {
}

var GithubLocation = function(options, ui) {

// ensure git is installed
try {
which.sync('git');
}
catch(ex) {
throw 'Git not installed. You can install git from `http://git-scm.com/downloads`.';
}

this.name = options.name;

this.max_repo_size = (options.maxRepoSize || 0) * 1024 * 1024;
Expand All@@ -112,22 +102,13 @@ var GithubLocation = function(options, ui) {

this.ui = ui;

this.execOpt = {
cwd: options.tmpDir,
timeout: options.timeout * 1000,
killSignal: 'SIGKILL',
maxBuffer: this.max_repo_size || 2 * 1024 * 1024,
env: extend({}, process.env)
};

this.defaultRequestOptions = {
headers: {
'User-Agent': 'jspm'
},
strictSSL: 'strictSSL' in options ? options.strictSSL : true
};

if (!this.defaultRequestOptions.strictSSL) {
this.execOpt.env.GIT_SSL_NO_VERIFY = '1'
}

var self = this, envMap = {
ca: 'GIT_SSL_CAINFO',
cert: 'GIT_SSL_CERT',
Expand All@@ -137,7 +118,6 @@ var GithubLocation = function(options, ui) {
['ca', 'cert', 'key'].forEach(function(key) {
if (key in options) {
var path = expandTilde(options[key]);
self.execOpt.env[envMap[key]] = path;
self.defaultRequestOptions[key] = fs.readFileSync(path, 'ascii');
}
});
Expand DownExpand Up@@ -302,16 +282,15 @@ GithubLocation.prototype = {
return new Promise(function(resolve, reject) {
request(extend({
uri: remoteString + repo + authSuffix,
headers: {
'User-Agent': 'jspm'
},
followRedirect: false
}, self.defaultRequestOptions
))
}, self.defaultRequestOptions))
.on('response', function(res) {
// redirect
if (res.statusCode == 301)
resolve({ redirect: self.name + ':' + res.headers.location.split('/').splice(3).join('/') });
if (res.statusCode == 301) {
// strip access token
var path = require('url').parse(res.headers.location).pathname;
resolve({ redirect: self.name + ':' + path.substr(1) });
}

if (res.statusCode == 401)
reject('Invalid authentication details.\n' +
Expand All@@ -338,56 +317,124 @@ GithubLocation.prototype = {
// { versions: { versionhash } }
// { notfound: true }
lookup: function(repo) {
var execOpt = this.execOpt;
var self = this;
var remoteString = this.remoteString;
return new Promise(function(resolve, reject) {
execGit('ls-remote ' + remoteString.replace(/(['"()])/g, '\\\$1') + repo + '.git refs/tags/* refs/heads/*', execOpt, function(err, stdout, stderr) {
if (err) {
if (err.toString().indexOf('not found') == -1) {
var error = new Error(stderr);
error.hideStack = true;
error.retriable = true;
reject(error);
}

return Promise.resolve()
.then(function() {
if (self.auth && self.auth.token) {
// use API to get branches/tags
return Promise.all(['tags', 'heads'].map(function(type) {
return asp(request)(extend({
uri: self.apiRemoteString + 'repos/' + repo + '/git/refs/' + type + self.authSuffix,
headers: {
'Accept': 'application/vnd.github.v3.raw'
}
}, self.defaultRequestOptions));
})).then(function(responses) {
var tagRes = responses[0];
var headRes = responses[1];

var refs = [];

// there should always be heads
if (headRes.statusCode != 200)
throw { statusCode: headRes.statusCode, headers: headRes.headers, api: true };
else
resolve({ notfound: true });
}
refs = refs.concat(JSON.parse(headRes.body));

// tag response can be 404, i.e. no tags
if (tagRes.statusCode == 200)
refs = refs.concat(JSON.parse(tagRes.body));
else if (tagRes.statusCode != 404)
throw { statusCode: tagRes.statusCode, headers: tagRes.headers, api: true };

versions = {};
var refs = stdout.split('\n');
for (var i = 0; i < refs.length; i++) {
if (!refs[i])
continue;
return refs.map(function(obj) {
return { sha: obj.object.sha, name: obj.ref };
});
});
} else {
// fallback to git-based approach
return false;
}
})
.catch(function(e) {
if (e.headers && e.headers['x-ratelimit-remaining'] == '0') {
if (!apiWarned) {
ui.log('API ratelimit reached, falling back to slower git protocol');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original numbers you posted showed the git protocol as faster. Did it turn out the API is faster then?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or is this a consequence of this implementation of the git protocol over the native git ls-remote?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was initially doing one request to get all references - tags/heads and pull requests. This proved very slow on large repos with lots of PRs.

at @tamird's suggestion I then did two requests, one to fetch tags, one for heads. This is now the same speed or faster than git, and can be cached (but isn't cached currently in my implementation)

apiWarned = true;
}
// fallback to git-based approach
return false;
} else {
throw e;
}
})
.then(function(refs) {
// API response
if (refs) return refs;

// no API auth or API auth is rate-limited, use git
return lsRemote(extend({
url: remoteString + repo + '.git'
}, self.defaultRequestOptions));
})
.then(function(refs) {
var versions = {};
refs.forEach(function(ref) {
var version;
var versionObj = { hash: ref.sha, meta: {} };
if (ref.name.substr(0, 11) == 'refs/heads/') {
version = ref.name.substr(11);
versionObj.stable = false;
}

var hash = refs[i].substr(0, refs[i].indexOf('\t'));
var refName = refs[i].substr(hash.length + 1);
var version;
var versionObj = { hash: hash, meta: {} };
else if (ref.name.substr(0, 10) == 'refs/tags/') {
if (ref.name.substr(ref.name.length - 3, 3) == '^{}')
version = ref.name.substr(10, ref.name.length - 13);
else
version = ref.name.substr(10);

if (refName.substr(0, 11) == 'refs/heads/') {
version = refName.substr(11);
versionObj.stable = false;
if (version.substr(0, 1) == 'v' && semver.valid(version.substr(1))) {
version = version.substr(1);
// note when we remove a "v" which versions we need to add it back to
// to work out the tag version again
versionObj.meta.vPrefix = true;
}
}

else if (refName.substr(0, 10) == 'refs/tags/') {
if (refName.substr(refName.length - 3, 3) == '^{}')
version = refName.substr(10, refName.length - 13);
else
version = refName.substr(10);

if (version.substr(0, 1) == 'v' && semver.valid(version.substr(1))) {
version = version.substr(1);
// note when we remove a "v" which versions we need to add it back to
// to work out the tag version again
versionObj.meta.vPrefix = true;
}
}
versions[version] = versionObj;
});

versions[version] = versionObj;
return { versions: versions };
})
.catch(function(error) {
if (error.statusCode) {
var headerSuffix = '\n' + JSON.stringify(error.headers, null, 2);

if (error.statusCode == 406 || error.statusCode == 401) {
if (error.api) {
// TODO: replace this with the api failure response code from below
error = new Error('api says invalid auth: ' + error.statusCode + headerSuffix);
}
else {
error = new Error('Invalid authentication details.\n' +
'Run %jspm registry config ' + self.name + '% to reconfigure the credentials, or update them in your ~/.netrc file.');
}
}
else if (error.statusCode == 404)
return { notfound: true };
else
error = new Error('invalid status code: ' + error.statusCode + headerSuffix);
}

resolve({ versions: versions });
});
if (typeof error == 'string') {
error = new Error(error);
}

error.retriable = true;
error.hideStack = true;
throw error;
});
},

Expand All@@ -400,17 +447,16 @@ GithubLocation.prototype = {
var self = this;
var ui = this.ui;

return asp(request)({
return asp(request)(extend({
uri: this.apiRemoteString + 'repos/' + repo + '/contents/package.json' + this.authSuffix,
headers: {
'User-Agent': 'jspm',
'Accept': 'application/vnd.github.v3.raw'
},
qs: {
ref: version
},
strictSSL: this.defaultRequestOptions.strictSSL
}).then(function(res) {
}
}, self.defaultRequestOptions))
.then(function(res) {
// API auth failure warnings
function apiFailWarn(reason, showAuthCommand) {
if (apiWarned)
Expand DownExpand Up@@ -534,7 +580,6 @@ GithubLocation.prototype = {
if (meta.vPrefix)
version = 'v' + version;

var execOpt = this.execOpt;
var max_repo_size = this.max_repo_size;
var remoteString = this.remoteString;
var authSuffix = this.authSuffix;
Expand All@@ -543,11 +588,10 @@ GithubLocation.prototype = {

// Download from the git archive
return new Promise(function(resolve, reject) {
request({
request(extend({
uri: remoteString + repo + '/archive/' + version + '.tar.gz' + authSuffix,
headers: { 'accept': 'application/octet-stream' },
strictSSL: self.defaultRequestOptions.strictSSL
})
headers: { 'accept': 'application/octet-stream' }
}, self.defaultRequestOptions))
.on('response', function(pkgRes) {
if (pkgRes.statusCode != 200)
return reject('Bad response code ' + pkgRes.statusCode);
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Feb 18, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .travis.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
language: node_js
sudo: false
node_js:
- "4"
- "5"
- "6"
66 changes: 0 additions & 66 deletions exec-git.js

This file was deleted.

210 changes: 127 additions & 83 deletions github.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,11 +13,10 @@ var zlib = require('zlib');

var semver = require('semver');

var which = require('which');

function extend(dest, src) {
for (var key in src) {
dest[key] = src[key]
if (typeof dest[key] === 'object') extend(dest[key], src[key]);
else dest[key] = src[key];
}

return dest;
Expand All@@ -28,7 +27,7 @@ try {
}
catch(e) {}

var execGit = require('./exec-git');
var lsRemote = require('./ls-remote');

function createRemoteStrings(auth, hostname) {
var authString = auth.username ? (encodeURIComponent(auth.username) + ':' + encodeURIComponent(auth.password) + '@') : '';
Expand DownExpand Up@@ -82,15 +81,6 @@ function isGithubToken(token) {
}

var GithubLocation = function(options, ui) {

// ensure git is installed
try {
which.sync('git');
}
catch(ex) {
throw 'Git not installed. You can install git from `http://git-scm.com/downloads`.';
}

this.name = options.name;

this.max_repo_size = (options.maxRepoSize || 0) * 1024 * 1024;
Expand All@@ -112,22 +102,13 @@ var GithubLocation = function(options, ui) {

this.ui = ui;

this.execOpt = {
cwd: options.tmpDir,
timeout: options.timeout * 1000,
killSignal: 'SIGKILL',
maxBuffer: this.max_repo_size || 2 * 1024 * 1024,
env: extend({}, process.env)
};

this.defaultRequestOptions = {
headers: {
'User-Agent': 'jspm'
},
strictSSL: 'strictSSL' in options ? options.strictSSL : true
};

if (!this.defaultRequestOptions.strictSSL) {
this.execOpt.env.GIT_SSL_NO_VERIFY = '1'
}

var self = this, envMap = {
ca: 'GIT_SSL_CAINFO',
cert: 'GIT_SSL_CERT',
Expand All@@ -137,7 +118,6 @@ var GithubLocation = function(options, ui) {
['ca', 'cert', 'key'].forEach(function(key) {
if (key in options) {
var path = expandTilde(options[key]);
self.execOpt.env[envMap[key]] = path;
self.defaultRequestOptions[key] = fs.readFileSync(path, 'ascii');
}
});
Expand DownExpand Up@@ -302,16 +282,15 @@ GithubLocation.prototype = {
return new Promise(function(resolve, reject) {
request(extend({
uri: remoteString + repo + authSuffix,
headers: {
'User-Agent': 'jspm'
},
followRedirect: false
}, self.defaultRequestOptions
))
}, self.defaultRequestOptions))
.on('response', function(res) {
// redirect
if (res.statusCode == 301)
resolve({ redirect: self.name + ':' + res.headers.location.split('/').splice(3).join('/') });
if (res.statusCode == 301) {
// strip access token
var path = require('url').parse(res.headers.location).pathname;
resolve({ redirect: self.name + ':' + path.substr(1) });
}

if (res.statusCode == 401)
reject('Invalid authentication details.\n' +
Expand All@@ -338,56 +317,124 @@ GithubLocation.prototype = {
// { versions: { versionhash } }
// { notfound: true }
lookup: function(repo) {
var execOpt = this.execOpt;
var self = this;
var remoteString = this.remoteString;
return new Promise(function(resolve, reject) {
execGit('ls-remote ' + remoteString.replace(/(['"()])/g, '\\\$1') + repo + '.git refs/tags/* refs/heads/*', execOpt, function(err, stdout, stderr) {
if (err) {
if (err.toString().indexOf('not found') == -1) {
var error = new Error(stderr);
error.hideStack = true;
error.retriable = true;
reject(error);
}

return Promise.resolve()
.then(function() {
if (self.auth && self.auth.token) {
// use API to get branches/tags
return Promise.all(['tags', 'heads'].map(function(type) {
return asp(request)(extend({
uri: self.apiRemoteString + 'repos/' + repo + '/git/refs/' + type + self.authSuffix,
headers: {
'Accept': 'application/vnd.github.v3.raw'
}
}, self.defaultRequestOptions));
})).then(function(responses) {
var tagRes = responses[0];
var headRes = responses[1];

var refs = [];

// there should always be heads
if (headRes.statusCode != 200)
throw { statusCode: headRes.statusCode, headers: headRes.headers, api: true };
else
resolve({ notfound: true });
}
refs = refs.concat(JSON.parse(headRes.body));

// tag response can be 404, i.e. no tags
if (tagRes.statusCode == 200)
refs = refs.concat(JSON.parse(tagRes.body));
else if (tagRes.statusCode != 404)
throw { statusCode: tagRes.statusCode, headers: tagRes.headers, api: true };

versions = {};
var refs = stdout.split('\n');
for (var i = 0; i < refs.length; i++) {
if (!refs[i])
continue;
return refs.map(function(obj) {
return { sha: obj.object.sha, name: obj.ref };
});
});
} else {
// fallback to git-based approach
return false;
}
})
.catch(function(e) {
if (e.headers && e.headers['x-ratelimit-remaining'] == '0') {
if (!apiWarned) {
ui.log('API ratelimit reached, falling back to slower git protocol');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original numbers you posted showed the git protocol as faster. Did it turn out the API is faster then?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or is this a consequence of this implementation of the git protocol over the native git ls-remote?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was initially doing one request to get all references - tags/heads and pull requests. This proved very slow on large repos with lots of PRs.

at @tamird's suggestion I then did two requests, one to fetch tags, one for heads. This is now the same speed or faster than git, and can be cached (but isn't cached currently in my implementation)

apiWarned = true;
}
// fallback to git-based approach
return false;
} else {
throw e;
}
})
.then(function(refs) {
// API response
if (refs) return refs;

// no API auth or API auth is rate-limited, use git
return lsRemote(extend({
url: remoteString + repo + '.git'
}, self.defaultRequestOptions));
})
.then(function(refs) {
var versions = {};
refs.forEach(function(ref) {
var version;
var versionObj = { hash: ref.sha, meta: {} };
if (ref.name.substr(0, 11) == 'refs/heads/') {
version = ref.name.substr(11);
versionObj.stable = false;
}

var hash = refs[i].substr(0, refs[i].indexOf('\t'));
var refName = refs[i].substr(hash.length + 1);
var version;
var versionObj = { hash: hash, meta: {} };
else if (ref.name.substr(0, 10) == 'refs/tags/') {
if (ref.name.substr(ref.name.length - 3, 3) == '^{}')
version = ref.name.substr(10, ref.name.length - 13);
else
version = ref.name.substr(10);

if (refName.substr(0, 11) == 'refs/heads/') {
version = refName.substr(11);
versionObj.stable = false;
if (version.substr(0, 1) == 'v' && semver.valid(version.substr(1))) {
version = version.substr(1);
// note when we remove a "v" which versions we need to add it back to
// to work out the tag version again
versionObj.meta.vPrefix = true;
}
}

else if (refName.substr(0, 10) == 'refs/tags/') {
if (refName.substr(refName.length - 3, 3) == '^{}')
version = refName.substr(10, refName.length - 13);
else
version = refName.substr(10);

if (version.substr(0, 1) == 'v' && semver.valid(version.substr(1))) {
version = version.substr(1);
// note when we remove a "v" which versions we need to add it back to
// to work out the tag version again
versionObj.meta.vPrefix = true;
}
}
versions[version] = versionObj;
});

versions[version] = versionObj;
return { versions: versions };
})
.catch(function(error) {
if (error.statusCode) {
var headerSuffix = '\n' + JSON.stringify(error.headers, null, 2);

if (error.statusCode == 406 || error.statusCode == 401) {
if (error.api) {
// TODO: replace this with the api failure response code from below
error = new Error('api says invalid auth: ' + error.statusCode + headerSuffix);
}
else {
error = new Error('Invalid authentication details.\n' +
'Run %jspm registry config ' + self.name + '% to reconfigure the credentials, or update them in your ~/.netrc file.');
}
}
else if (error.statusCode == 404)
return { notfound: true };
else
error = new Error('invalid status code: ' + error.statusCode + headerSuffix);
}

resolve({ versions: versions });
});
if (typeof error == 'string') {
error = new Error(error);
}

error.retriable = true;
error.hideStack = true;
throw error;
});
},

Expand All@@ -400,17 +447,16 @@ GithubLocation.prototype = {
var self = this;
var ui = this.ui;

return asp(request)({
return asp(request)(extend({
uri: this.apiRemoteString + 'repos/' + repo + '/contents/package.json' + this.authSuffix,
headers: {
'User-Agent': 'jspm',
'Accept': 'application/vnd.github.v3.raw'
},
qs: {
ref: version
},
strictSSL: this.defaultRequestOptions.strictSSL
}).then(function(res) {
}
}, self.defaultRequestOptions))
.then(function(res) {
// API auth failure warnings
function apiFailWarn(reason, showAuthCommand) {
if (apiWarned)
Expand DownExpand Up@@ -534,7 +580,6 @@ GithubLocation.prototype = {
if (meta.vPrefix)
version = 'v' + version;

var execOpt = this.execOpt;
var max_repo_size = this.max_repo_size;
var remoteString = this.remoteString;
var authSuffix = this.authSuffix;
Expand All@@ -543,11 +588,10 @@ GithubLocation.prototype = {

// Download from the git archive
return new Promise(function(resolve, reject) {
request({
request(extend({
uri: remoteString + repo + '/archive/' + version + '.tar.gz' + authSuffix,
headers: { 'accept': 'application/octet-stream' },
strictSSL: self.defaultRequestOptions.strictSSL
})
headers: { 'accept': 'application/octet-stream' }
}, self.defaultRequestOptions))
.on('response', function(pkgRes) {
if (pkgRes.statusCode != 200)
return reject('Bad response code ' + pkgRes.statusCode);
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Feb 18, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .travis.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
language: node_js
sudo: false
node_js:
- "4"
- "5"
- "6"
66 changes: 0 additions & 66 deletions exec-git.js

This file was deleted.

210 changes: 127 additions & 83 deletions github.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,11 +13,10 @@ var zlib = require('zlib');

var semver = require('semver');

var which = require('which');

function extend(dest, src) {
for (var key in src) {
dest[key] = src[key]
if (typeof dest[key] === 'object') extend(dest[key], src[key]);
else dest[key] = src[key];
}

return dest;
Expand All@@ -28,7 +27,7 @@ try {
}
catch(e) {}

var execGit = require('./exec-git');
var lsRemote = require('./ls-remote');

function createRemoteStrings(auth, hostname) {
var authString = auth.username ? (encodeURIComponent(auth.username) + ':' + encodeURIComponent(auth.password) + '@') : '';
Expand DownExpand Up@@ -82,15 +81,6 @@ function isGithubToken(token) {
}

var GithubLocation = function(options, ui) {

// ensure git is installed
try {
which.sync('git');
}
catch(ex) {
throw 'Git not installed. You can install git from `http://git-scm.com/downloads`.';
}

this.name = options.name;

this.max_repo_size = (options.maxRepoSize || 0) * 1024 * 1024;
Expand All@@ -112,22 +102,13 @@ var GithubLocation = function(options, ui) {

this.ui = ui;

this.execOpt = {
cwd: options.tmpDir,
timeout: options.timeout * 1000,
killSignal: 'SIGKILL',
maxBuffer: this.max_repo_size || 2 * 1024 * 1024,
env: extend({}, process.env)
};

this.defaultRequestOptions = {
headers: {
'User-Agent': 'jspm'
},
strictSSL: 'strictSSL' in options ? options.strictSSL : true
};

if (!this.defaultRequestOptions.strictSSL) {
this.execOpt.env.GIT_SSL_NO_VERIFY = '1'
}

var self = this, envMap = {
ca: 'GIT_SSL_CAINFO',
cert: 'GIT_SSL_CERT',
Expand All@@ -137,7 +118,6 @@ var GithubLocation = function(options, ui) {
['ca', 'cert', 'key'].forEach(function(key) {
if (key in options) {
var path = expandTilde(options[key]);
self.execOpt.env[envMap[key]] = path;
self.defaultRequestOptions[key] = fs.readFileSync(path, 'ascii');
}
});
Expand DownExpand Up@@ -302,16 +282,15 @@ GithubLocation.prototype = {
return new Promise(function(resolve, reject) {
request(extend({
uri: remoteString + repo + authSuffix,
headers: {
'User-Agent': 'jspm'
},
followRedirect: false
}, self.defaultRequestOptions
))
}, self.defaultRequestOptions))
.on('response', function(res) {
// redirect
if (res.statusCode == 301)
resolve({ redirect: self.name + ':' + res.headers.location.split('/').splice(3).join('/') });
if (res.statusCode == 301) {
// strip access token
var path = require('url').parse(res.headers.location).pathname;
resolve({ redirect: self.name + ':' + path.substr(1) });
}

if (res.statusCode == 401)
reject('Invalid authentication details.\n' +
Expand All@@ -338,56 +317,124 @@ GithubLocation.prototype = {
// { versions: { versionhash } }
// { notfound: true }
lookup: function(repo) {
var execOpt = this.execOpt;
var self = this;
var remoteString = this.remoteString;
return new Promise(function(resolve, reject) {
execGit('ls-remote ' + remoteString.replace(/(['"()])/g, '\\\$1') + repo + '.git refs/tags/* refs/heads/*', execOpt, function(err, stdout, stderr) {
if (err) {
if (err.toString().indexOf('not found') == -1) {
var error = new Error(stderr);
error.hideStack = true;
error.retriable = true;
reject(error);
}

return Promise.resolve()
.then(function() {
if (self.auth && self.auth.token) {
// use API to get branches/tags
return Promise.all(['tags', 'heads'].map(function(type) {
return asp(request)(extend({
uri: self.apiRemoteString + 'repos/' + repo + '/git/refs/' + type + self.authSuffix,
headers: {
'Accept': 'application/vnd.github.v3.raw'
}
}, self.defaultRequestOptions));
})).then(function(responses) {
var tagRes = responses[0];
var headRes = responses[1];

var refs = [];

// there should always be heads
if (headRes.statusCode != 200)
throw { statusCode: headRes.statusCode, headers: headRes.headers, api: true };
else
resolve({ notfound: true });
}
refs = refs.concat(JSON.parse(headRes.body));

// tag response can be 404, i.e. no tags
if (tagRes.statusCode == 200)
refs = refs.concat(JSON.parse(tagRes.body));
else if (tagRes.statusCode != 404)
throw { statusCode: tagRes.statusCode, headers: tagRes.headers, api: true };

versions = {};
var refs = stdout.split('\n');
for (var i = 0; i < refs.length; i++) {
if (!refs[i])
continue;
return refs.map(function(obj) {
return { sha: obj.object.sha, name: obj.ref };
});
});
} else {
// fallback to git-based approach
return false;
}
})
.catch(function(e) {
if (e.headers && e.headers['x-ratelimit-remaining'] == '0') {
if (!apiWarned) {
ui.log('API ratelimit reached, falling back to slower git protocol');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original numbers you posted showed the git protocol as faster. Did it turn out the API is faster then?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or is this a consequence of this implementation of the git protocol over the native git ls-remote?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was initially doing one request to get all references - tags/heads and pull requests. This proved very slow on large repos with lots of PRs.

at @tamird's suggestion I then did two requests, one to fetch tags, one for heads. This is now the same speed or faster than git, and can be cached (but isn't cached currently in my implementation)

apiWarned = true;
}
// fallback to git-based approach
return false;
} else {
throw e;
}
})
.then(function(refs) {
// API response
if (refs) return refs;

// no API auth or API auth is rate-limited, use git
return lsRemote(extend({
url: remoteString + repo + '.git'
}, self.defaultRequestOptions));
})
.then(function(refs) {
var versions = {};
refs.forEach(function(ref) {
var version;
var versionObj = { hash: ref.sha, meta: {} };
if (ref.name.substr(0, 11) == 'refs/heads/') {
version = ref.name.substr(11);
versionObj.stable = false;
}

var hash = refs[i].substr(0, refs[i].indexOf('\t'));
var refName = refs[i].substr(hash.length + 1);
var version;
var versionObj = { hash: hash, meta: {} };
else if (ref.name.substr(0, 10) == 'refs/tags/') {
if (ref.name.substr(ref.name.length - 3, 3) == '^{}')
version = ref.name.substr(10, ref.name.length - 13);
else
version = ref.name.substr(10);

if (refName.substr(0, 11) == 'refs/heads/') {
version = refName.substr(11);
versionObj.stable = false;
if (version.substr(0, 1) == 'v' && semver.valid(version.substr(1))) {
version = version.substr(1);
// note when we remove a "v" which versions we need to add it back to
// to work out the tag version again
versionObj.meta.vPrefix = true;
}
}

else if (refName.substr(0, 10) == 'refs/tags/') {
if (refName.substr(refName.length - 3, 3) == '^{}')
version = refName.substr(10, refName.length - 13);
else
version = refName.substr(10);

if (version.substr(0, 1) == 'v' && semver.valid(version.substr(1))) {
version = version.substr(1);
// note when we remove a "v" which versions we need to add it back to
// to work out the tag version again
versionObj.meta.vPrefix = true;
}
}
versions[version] = versionObj;
});

versions[version] = versionObj;
return { versions: versions };
})
.catch(function(error) {
if (error.statusCode) {
var headerSuffix = '\n' + JSON.stringify(error.headers, null, 2);

if (error.statusCode == 406 || error.statusCode == 401) {
if (error.api) {
// TODO: replace this with the api failure response code from below
error = new Error('api says invalid auth: ' + error.statusCode + headerSuffix);
}
else {
error = new Error('Invalid authentication details.\n' +
'Run %jspm registry config ' + self.name + '% to reconfigure the credentials, or update them in your ~/.netrc file.');
}
}
else if (error.statusCode == 404)
return { notfound: true };
else
error = new Error('invalid status code: ' + error.statusCode + headerSuffix);
}

resolve({ versions: versions });
});
if (typeof error == 'string') {
error = new Error(error);
}

error.retriable = true;
error.hideStack = true;
throw error;
});
},

Expand All@@ -400,17 +447,16 @@ GithubLocation.prototype = {
var self = this;
var ui = this.ui;

return asp(request)({
return asp(request)(extend({
uri: this.apiRemoteString + 'repos/' + repo + '/contents/package.json' + this.authSuffix,
headers: {
'User-Agent': 'jspm',
'Accept': 'application/vnd.github.v3.raw'
},
qs: {
ref: version
},
strictSSL: this.defaultRequestOptions.strictSSL
}).then(function(res) {
}
}, self.defaultRequestOptions))
.then(function(res) {
// API auth failure warnings
function apiFailWarn(reason, showAuthCommand) {
if (apiWarned)
Expand DownExpand Up@@ -534,7 +580,6 @@ GithubLocation.prototype = {
if (meta.vPrefix)
version = 'v' + version;

var execOpt = this.execOpt;
var max_repo_size = this.max_repo_size;
var remoteString = this.remoteString;
var authSuffix = this.authSuffix;
Expand All@@ -543,11 +588,10 @@ GithubLocation.prototype = {

// Download from the git archive
return new Promise(function(resolve, reject) {
request({
request(extend({
uri: remoteString + repo + '/archive/' + version + '.tar.gz' + authSuffix,
headers: { 'accept': 'application/octet-stream' },
strictSSL: self.defaultRequestOptions.strictSSL
})
headers: { 'accept': 'application/octet-stream' }
}, self.defaultRequestOptions))
.on('response', function(pkgRes) {
if (pkgRes.statusCode != 200)
return reject('Bad response code ' + pkgRes.statusCode);
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
This repository was archived by the owner on Feb 18, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .travis.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
language: node_js
sudo: false
node_js:
- "4"
- "5"
- "6"
66 changes: 0 additions & 66 deletions exec-git.js

This file was deleted.

210 changes: 127 additions & 83 deletions github.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,11 +13,10 @@ var zlib = require('zlib');

var semver = require('semver');

var which = require('which');

function extend(dest, src) {
for (var key in src) {
dest[key] = src[key]
if (typeof dest[key] === 'object') extend(dest[key], src[key]);
else dest[key] = src[key];
}

return dest;
Expand All@@ -28,7 +27,7 @@ try {
}
catch(e) {}

var execGit = require('./exec-git');
var lsRemote = require('./ls-remote');

function createRemoteStrings(auth, hostname) {
var authString = auth.username ? (encodeURIComponent(auth.username) + ':' + encodeURIComponent(auth.password) + '@') : '';
Expand DownExpand Up@@ -82,15 +81,6 @@ function isGithubToken(token) {
}

var GithubLocation = function(options, ui) {

// ensure git is installed
try {
which.sync('git');
}
catch(ex) {
throw 'Git not installed. You can install git from `http://git-scm.com/downloads`.';
}

this.name = options.name;

this.max_repo_size = (options.maxRepoSize || 0) * 1024 * 1024;
Expand All@@ -112,22 +102,13 @@ var GithubLocation = function(options, ui) {

this.ui = ui;

this.execOpt = {
cwd: options.tmpDir,
timeout: options.timeout * 1000,
killSignal: 'SIGKILL',
maxBuffer: this.max_repo_size || 2 * 1024 * 1024,
env: extend({}, process.env)
};

this.defaultRequestOptions = {
headers: {
'User-Agent': 'jspm'
},
strictSSL: 'strictSSL' in options ? options.strictSSL : true
};

if (!this.defaultRequestOptions.strictSSL) {
this.execOpt.env.GIT_SSL_NO_VERIFY = '1'
}

var self = this, envMap = {
ca: 'GIT_SSL_CAINFO',
cert: 'GIT_SSL_CERT',
Expand All@@ -137,7 +118,6 @@ var GithubLocation = function(options, ui) {
['ca', 'cert', 'key'].forEach(function(key) {
if (key in options) {
var path = expandTilde(options[key]);
self.execOpt.env[envMap[key]] = path;
self.defaultRequestOptions[key] = fs.readFileSync(path, 'ascii');
}
});
Expand DownExpand Up@@ -302,16 +282,15 @@ GithubLocation.prototype = {
return new Promise(function(resolve, reject) {
request(extend({
uri: remoteString + repo + authSuffix,
headers: {
'User-Agent': 'jspm'
},
followRedirect: false
}, self.defaultRequestOptions
))
}, self.defaultRequestOptions))
.on('response', function(res) {
// redirect
if (res.statusCode == 301)
resolve({ redirect: self.name + ':' + res.headers.location.split('/').splice(3).join('/') });
if (res.statusCode == 301) {
// strip access token
var path = require('url').parse(res.headers.location).pathname;
resolve({ redirect: self.name + ':' + path.substr(1) });
}

if (res.statusCode == 401)
reject('Invalid authentication details.\n' +
Expand All@@ -338,56 +317,124 @@ GithubLocation.prototype = {
// { versions: { versionhash } }
// { notfound: true }
lookup: function(repo) {
var execOpt = this.execOpt;
var self = this;
var remoteString = this.remoteString;
return new Promise(function(resolve, reject) {
execGit('ls-remote ' + remoteString.replace(/(['"()])/g, '\\\$1') + repo + '.git refs/tags/* refs/heads/*', execOpt, function(err, stdout, stderr) {
if (err) {
if (err.toString().indexOf('not found') == -1) {
var error = new Error(stderr);
error.hideStack = true;
error.retriable = true;
reject(error);
}

return Promise.resolve()
.then(function() {
if (self.auth && self.auth.token) {
// use API to get branches/tags
return Promise.all(['tags', 'heads'].map(function(type) {
return asp(request)(extend({
uri: self.apiRemoteString + 'repos/' + repo + '/git/refs/' + type + self.authSuffix,
headers: {
'Accept': 'application/vnd.github.v3.raw'
}
}, self.defaultRequestOptions));
})).then(function(responses) {
var tagRes = responses[0];
var headRes = responses[1];

var refs = [];

// there should always be heads
if (headRes.statusCode != 200)
throw { statusCode: headRes.statusCode, headers: headRes.headers, api: true };
else
resolve({ notfound: true });
}
refs = refs.concat(JSON.parse(headRes.body));

// tag response can be 404, i.e. no tags
if (tagRes.statusCode == 200)
refs = refs.concat(JSON.parse(tagRes.body));
else if (tagRes.statusCode != 404)
throw { statusCode: tagRes.statusCode, headers: tagRes.headers, api: true };

versions = {};
var refs = stdout.split('\n');
for (var i = 0; i < refs.length; i++) {
if (!refs[i])
continue;
return refs.map(function(obj) {
return { sha: obj.object.sha, name: obj.ref };
});
});
} else {
// fallback to git-based approach
return false;
}
})
.catch(function(e) {
if (e.headers && e.headers['x-ratelimit-remaining'] == '0') {
if (!apiWarned) {
ui.log('API ratelimit reached, falling back to slower git protocol');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original numbers you posted showed the git protocol as faster. Did it turn out the API is faster then?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or is this a consequence of this implementation of the git protocol over the native git ls-remote?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was initially doing one request to get all references - tags/heads and pull requests. This proved very slow on large repos with lots of PRs.

at @tamird's suggestion I then did two requests, one to fetch tags, one for heads. This is now the same speed or faster than git, and can be cached (but isn't cached currently in my implementation)

apiWarned = true;
}
// fallback to git-based approach
return false;
} else {
throw e;
}
})
.then(function(refs) {
// API response
if (refs) return refs;

// no API auth or API auth is rate-limited, use git
return lsRemote(extend({
url: remoteString + repo + '.git'
}, self.defaultRequestOptions));
})
.then(function(refs) {
var versions = {};
refs.forEach(function(ref) {
var version;
var versionObj = { hash: ref.sha, meta: {} };
if (ref.name.substr(0, 11) == 'refs/heads/') {
version = ref.name.substr(11);
versionObj.stable = false;
}

var hash = refs[i].substr(0, refs[i].indexOf('\t'));
var refName = refs[i].substr(hash.length + 1);
var version;
var versionObj = { hash: hash, meta: {} };
else if (ref.name.substr(0, 10) == 'refs/tags/') {
if (ref.name.substr(ref.name.length - 3, 3) == '^{}')
version = ref.name.substr(10, ref.name.length - 13);
else
version = ref.name.substr(10);

if (refName.substr(0, 11) == 'refs/heads/') {
version = refName.substr(11);
versionObj.stable = false;
if (version.substr(0, 1) == 'v' && semver.valid(version.substr(1))) {
version = version.substr(1);
// note when we remove a "v" which versions we need to add it back to
// to work out the tag version again
versionObj.meta.vPrefix = true;
}
}

else if (refName.substr(0, 10) == 'refs/tags/') {
if (refName.substr(refName.length - 3, 3) == '^{}')
version = refName.substr(10, refName.length - 13);
else
version = refName.substr(10);

if (version.substr(0, 1) == 'v' && semver.valid(version.substr(1))) {
version = version.substr(1);
// note when we remove a "v" which versions we need to add it back to
// to work out the tag version again
versionObj.meta.vPrefix = true;
}
}
versions[version] = versionObj;
});

versions[version] = versionObj;
return { versions: versions };
})
.catch(function(error) {
if (error.statusCode) {
var headerSuffix = '\n' + JSON.stringify(error.headers, null, 2);

if (error.statusCode == 406 || error.statusCode == 401) {
if (error.api) {
// TODO: replace this with the api failure response code from below
error = new Error('api says invalid auth: ' + error.statusCode + headerSuffix);
}
else {
error = new Error('Invalid authentication details.\n' +
'Run %jspm registry config ' + self.name + '% to reconfigure the credentials, or update them in your ~/.netrc file.');
}
}
else if (error.statusCode == 404)
return { notfound: true };
else
error = new Error('invalid status code: ' + error.statusCode + headerSuffix);
}

resolve({ versions: versions });
});
if (typeof error == 'string') {
error = new Error(error);
}

error.retriable = true;
error.hideStack = true;
throw error;
});
},

Expand All@@ -400,17 +447,16 @@ GithubLocation.prototype = {
var self = this;
var ui = this.ui;

return asp(request)({
return asp(request)(extend({
uri: this.apiRemoteString + 'repos/' + repo + '/contents/package.json' + this.authSuffix,
headers: {
'User-Agent': 'jspm',
'Accept': 'application/vnd.github.v3.raw'
},
qs: {
ref: version
},
strictSSL: this.defaultRequestOptions.strictSSL
}).then(function(res) {
}
}, self.defaultRequestOptions))
.then(function(res) {
// API auth failure warnings
function apiFailWarn(reason, showAuthCommand) {
if (apiWarned)
Expand DownExpand Up@@ -534,7 +580,6 @@ GithubLocation.prototype = {
if (meta.vPrefix)
version = 'v' + version;

var execOpt = this.execOpt;
var max_repo_size = this.max_repo_size;
var remoteString = this.remoteString;
var authSuffix = this.authSuffix;
Expand All@@ -543,11 +588,10 @@ GithubLocation.prototype = {

// Download from the git archive
return new Promise(function(resolve, reject) {
request({
request(extend({
uri: remoteString + repo + '/archive/' + version + '.tar.gz' + authSuffix,
headers: { 'accept': 'application/octet-stream' },
strictSSL: self.defaultRequestOptions.strictSSL
})
headers: { 'accept': 'application/octet-stream' }
}, self.defaultRequestOptions))
.on('response', function(pkgRes) {
if (pkgRes.statusCode != 200)
return reject('Bad response code ' + pkgRes.statusCode);
Expand Down
Loading