Added submodule support

using an ini parser for .gitmodules
This commit is contained in:
Jerry Wang 2014-05-18 15:11:01 -04:00
parent d158f10911
commit f64281175e
6 changed files with 135 additions and 50 deletions

View File

@ -31,6 +31,7 @@
<string>lib/js/underscore.js</string> <string>lib/js/underscore.js</string>
<string>lib/js/base64.js</string> <string>lib/js/base64.js</string>
<string>lib/js/github.js</string> <string>lib/js/github.js</string>
<string>lib/js/iniparser.js</string>
<string>inject.js</string> <string>inject.js</string>
</array> </array>
</dict> </dict>

View File

@ -10,6 +10,7 @@ pageMod.PageMod({
data.url('lib/js/underscore.js'), data.url('lib/js/underscore.js'),
data.url('lib/js/base64.js'), data.url('lib/js/base64.js'),
data.url('lib/js/github.js'), data.url('lib/js/github.js'),
data.url('lib/js/iniparser.js'),
data.url('inject.js')], data.url('inject.js')],
contentStyleFile: [data.url('lib/css/jstree.css'), contentStyleFile: [data.url('lib/css/jstree.css'),
data.url('inject.css')], data.url('inject.css')],

View File

@ -90,7 +90,8 @@ html.octotree {
} }
.octotree_treeview .jstree-icon.tree, .octotree_treeview .jstree-icon.tree,
.octotree_treeview .jstree-icon.blob { .octotree_treeview .jstree-icon.blob,
.octotree_treeview .jstree-icon.commit {
font: normal normal 16px octicons; font: normal normal 16px octicons;
display: inline-block; display: inline-block;
margin-top: 4px; margin-top: 4px;
@ -106,6 +107,10 @@ html.octotree {
content: '\f011'; content: '\f011';
color: #777; color: #777;
} }
.octotree_treeview .jstree-icon.commit:before {
content: '\f017';
color: #777;
}
.octotree_treeview .jstree-anchor { .octotree_treeview .jstree-anchor {
color: #4183c4 !important; color: #4183c4 !important;
@ -142,17 +147,17 @@ html.octotree {
} }
/* Options form */ /* Options form */
.octotree_options div { .octotree_options div {
margin: 6px; margin: 6px;
} }
.octotree_options input { .octotree_options input {
width: 237px; width: 237px;
} }
.octotree_options .error { .octotree_options .error {
color: #900; color: #900;
} }
.octotree_options button { .octotree_options button {
margin-right: 5px; margin-right: 5px;
} }
/* Toggle button */ /* Toggle button */

View File

@ -3,8 +3,8 @@
, TOKEN = 'octotree.github_access_token' , TOKEN = 'octotree.github_access_token'
, SHOWN = 'octotree.shown' , SHOWN = 'octotree.shown'
, RESERVED_USER_NAMES = [ , RESERVED_USER_NAMES = [
'settings', 'orgs', 'organizations', 'settings', 'orgs', 'organizations',
'site', 'blog', 'about', 'site', 'blog', 'about',
'styleguide', 'showcases', 'trending', 'styleguide', 'showcases', 'trending',
'stars', 'dashboard', 'notifications' 'stars', 'dashboard', 'notifications'
] ]
@ -96,7 +96,7 @@
// (username)/(reponame)[/(subpart)] // (username)/(reponame)[/(subpart)]
var match = location.pathname.match(/([^\/]+)\/([^\/]+)(?:\/([^\/]+))?/) var match = location.pathname.match(/([^\/]+)\/([^\/]+)(?:\/([^\/]+))?/)
if (!match) return false if (!match) return false
// Not a repository, skip // Not a repository, skip
if (~RESERVED_USER_NAMES.indexOf(match[1])) return false if (~RESERVED_USER_NAMES.indexOf(match[1])) return false
if (~RESERVED_REPO_NAMES.indexOf(match[2])) return false if (~RESERVED_REPO_NAMES.indexOf(match[2])) return false
@ -104,11 +104,11 @@
// Not a code page, skip // Not a code page, skip
if (match[3] && !~['tree', 'blob'].indexOf(match[3])) return false if (match[3] && !~['tree', 'blob'].indexOf(match[3])) return false
var branch = $('*[data-master-branch]').data('ref') || var branch = $('*[data-master-branch]').data('ref') ||
$('*[data-master-branch] > .js-select-button').text() || $('*[data-master-branch] > .js-select-button').text() ||
'master' 'master'
return { return {
username : match[1], username : match[1],
reponame : match[2], reponame : match[2],
branch : branch branch : branch
} }
@ -122,32 +122,45 @@
api.getTree(encodeURIComponent(repo.branch) + '?recursive=true', function(err, tree) { api.getTree(encodeURIComponent(repo.branch) + '?recursive=true', function(err, tree) {
if (err) return done(err) if (err) return done(err)
tree.forEach(function(item) { fetchSubmoduleData(api, repo, tree, function(err, submods) {
var path = item.path if (err) return done(err)
, type = item.type tree.forEach(function(item) {
, index = path.lastIndexOf('/') var path = item.path
, name = path.substring(index + 1) , type = item.type
, folder = folders[path.substring(0, index)] , index = path.lastIndexOf('/')
, url = '/' + repo.username + '/' + repo.reponame + '/' + type + '/' + repo.branch + '/' + path , name = path.substring(index + 1)
, folder = folders[path.substring(0, index)]
, url = '/' + repo.username + '/' + repo.reponame + '/' + type + '/' + repo.branch + '/' + path
, modkey = ''
folder.push(item) folder.push(item)
item.id = PREFIX + path item.id = PREFIX + path
item.text = $dummyDiv.text(name).html() // sanitizes, closes #9 item.text = $dummyDiv.text(name).html() // sanitizes, closes #9
item.icon = type // use `type` as class name for tree node item.icon = type // use `type` as class name for tree node
if (type === 'tree') { if (type === 'tree') {
folders[item.path] = item.children = [] folders[item.path] = item.children = []
item.a_attr = { href: '#' } item.a_attr = { href: '#' }
} }
else if (type === 'blob') { else if (type === 'blob') {
item.a_attr = { href: url } item.a_attr = { href: url }
} }
else if (type === 'commit') {
modkey = 'submodule "' + item.path + '"' //key from parsing gitmodules file
if(submods && submods[modkey]) {
submod = submods[modkey]
item.a_attr = { href: submod.url.replace("git@github.com:", "/") }
}
}
})
done(null, sort(root))
}) })
done(null, sort(root))
function sort(folder) { function sort(folder) {
folder.sort(function(a, b) { folder.sort(function(a, b) {
if (a.type === b.type) return a.text.localeCompare(b.text) //github treats submodules like folders
var compare = ((a.type === 'tree' || a.type === 'commit') &&
(b.type === 'tree' || b.type === 'commit'))
if (a.type === b.type || compare) return a.text.localeCompare(b.text)
return a.type === 'tree' ? -1 : 1 return a.type === 'tree' ? -1 : 1
}) })
folder.forEach(function(item) { folder.forEach(function(item) {
@ -158,6 +171,26 @@
}) })
} }
function fetchSubmoduleData(api, repo, tree, cb) {
var submodules = {}
, item = null
//use tree to find sha for .gitmodules
item = _.find(tree, function(file) { return /\.gitmodules/i.test(file.path) })
if(item) {
api.getBlob(item.sha, function (err, content, sha) {
if(err) cb(err, null)
if(content) {
submodules = iniParser.parse(content)
}
cb(null, submodules)
})
}
else
{
cb(null, null)
}
}
function onFetchError(err) { function onFetchError(err) {
var header = 'Error: ' + err.error var header = 'Error: ' + err.error
, hasToken = !!store.get(TOKEN) , hasToken = !!store.get(TOKEN)
@ -176,20 +209,20 @@
break break
case 404: case 404:
header = 'Private repository!' header = 'Private repository!'
message = hasToken message = hasToken
? 'You are not allowed to access this repository.' ? 'You are not allowed to access this repository.'
: 'Accessing private repositories requires a GitHub access token. Follow <a href="https://github.com/settings/tokens/new" target="_blank">this link</a> to create one and paste it below.' : 'Accessing private repositories requires a GitHub access token. Follow <a href="https://github.com/settings/tokens/new" target="_blank">this link</a> to create one and paste it below.'
break break
case 403: case 403:
if (~err.request.getAllResponseHeaders().indexOf('X-RateLimit-Remaining: 0')) { if (~err.request.getAllResponseHeaders().indexOf('X-RateLimit-Remaining: 0')) {
header = 'API limit exceeded!' header = 'API limit exceeded!'
message = hasToken message = hasToken
? 'You have exceeded the API hourly limit.' ? 'You have exceeded the API hourly limit.'
: 'You have exceeded the GitHub API hourly limit and need GitHub access token to make extra requests. Follow <a href="https://github.com/settings/tokens/new" target="_blank">this link</a> to create one and paste it below.' : 'You have exceeded the GitHub API hourly limit and need GitHub access token to make extra requests. Follow <a href="https://github.com/settings/tokens/new" target="_blank">this link</a> to create one and paste it below.'
} }
break break
} }
renderSidebar('<div class="octotree_header_error">' + header + '</div>', message) renderSidebar('<div class="octotree_header_error">' + header + '</div>', message)
} }
@ -209,19 +242,23 @@
.on('click', function(e) { .on('click', function(e) {
var $target = $(e.target) var $target = $(e.target)
if ($target.is('a.jstree-anchor') && $target.children(':first').hasClass('blob')) { if ($target.is('a.jstree-anchor') && $target.children(':first').hasClass('blob')) {
$.pjax({ $.pjax({
url : $target.attr('href'), url : $target.attr('href'),
timeout : 5000, //gives it more time, should really have a progress indicator... timeout : 5000, //gives it more time, should really have a progress indicator...
container : $('#js-repo-pjax-container') container : $('#js-repo-pjax-container')
}) })
} }
else if ($target.is('a.jstree-anchor') && $target.children(':first').hasClass('commit')) {
//link to submodule new page
window.location.href = $target.attr('href')
}
}) })
.on('ready.jstree', function() { .on('ready.jstree', function() {
var headerText = '<div class="octotree_header_repo">' + var headerText = '<div class="octotree_header_repo">' +
repo.username + ' / ' + repo.reponame + repo.username + ' / ' + repo.reponame +
'</div>' + '</div>' +
'<div class="octotree_header_branch">' + '<div class="octotree_header_branch">' +
repo.branch + repo.branch +
'</div>' '</div>'
renderSidebar(headerText) renderSidebar(headerText)
cb() cb()
@ -256,7 +293,7 @@
if (shown) $html.removeClass(PREFIX) if (shown) $html.removeClass(PREFIX)
else $html.addClass(PREFIX) else $html.addClass(PREFIX)
store.set(SHOWN, !shown) store.set(SHOWN, !shown)
} }
function saveToken(event) { function saveToken(event) {
event.preventDefault() event.preventDefault()
@ -283,4 +320,4 @@
return localStorage.setItem(key, JSON.stringify(val)) return localStorage.setItem(key, JSON.stringify(val))
} }
} }
})() })()

40
src/lib/js/iniparser.js Normal file
View File

@ -0,0 +1,40 @@
// modified from https://github.com/shockie/node-iniparser/blob/master/lib/node-iniparser.js
// https://github.com/shockie/node-iniparser
(function() {
var iniParser = {}
iniParser.parse = function(data) {
var regex = {
section: /^\s*\[\s*([^\]]*)\s*\]\s*$/,
param: /^\s*([\w\.\-\_]+)\s*=\s*(.*?)\s*$/,
comment: /^\s*;.*$/
}
var value = {}
var lines = data.split(/\r\n|\r|\n/)
var section = null
lines.forEach(function(line) {
if(regex.comment.test(line)) {
return
}else if(regex.param.test(line)) {
var match = line.match(regex.param)
if(section) {
value[section][match[1]] = match[2]
}else{
value[match[1]] = match[2]
}
}else if(regex.section.test(line)) {
var match = line.match(regex.section)
value[match[1]] = {}
section = match[1]
}else if(line.length == 0 && section) {
section = null
}
})
return value
}
if (typeof exports !== 'undefined') {
module.exports = iniParser
} else {
window.iniParser = iniParser
}
}).call(this)

View File

@ -30,6 +30,7 @@
"lib/js/underscore.js", "lib/js/underscore.js",
"lib/js/base64.js", "lib/js/base64.js",
"lib/js/github.js", "lib/js/github.js",
"lib/js/iniparser.js",
"inject.js" "inject.js"
] ]
} }