Skip to content

Commit d374e01

Browse files
author
kwatters
committed
Frontend reorganization and polish.
- attempt to fix rendering of some blobs by removing null bytes, newlines, and limiting size - use Handlebars instead of Hogan - add code for discovering whether a directory is a git repo or not - create an index page listing all the repos in the directory you specified at the commandline
1 parent 2ce3b4c commit d374e01

12 files changed

+168
-72
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Visualize git repositories as they change, live in the browser.
88
Why?
99
----
1010

11-
gitviz might useful as a teaching tool for git.
11+
gitviz might be useful as a teaching tool for git.
1212

1313
It draws graphviz graphs on <canvas> elements, and updates them as you modify the target git repository on the fly.
1414

TODO

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
- check out http://networkx.lanl.gov/pygraphviz/ as an alternative to pydot
2+
- animate nodes as the graph changes
3+
- limit number of nodes so we can read repositories of more than "toy" size
4+
- what to show, then?
5+
- add a checkbox for showing blobs, shas
6+

app.js

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,23 @@ var fs = require('fs'),
66
path = require('path'),
77
express = require('express'),
88
http = require('http'),
9-
path = require('path');
9+
path = require('path'),
10+
async = require('async'),
11+
gitutil = require('gitutil');
1012

1113
var WATCH_INTERVAL_MS = 300;
12-
13-
if (process.argv.length !== 3)
14-
throw "usage: node app.js REPOS_ROOT # (where REPOS_ROOT is the directory above your repositories)";
15-
16-
var ROOT = process.argv[2];
14+
var ROOT;
15+
var io;
1716
var DEFAULT_REPO = 'testrepo';
1817

19-
if (!dirExistsSync(ROOT))
20-
throw 'not existing:'+ROOT;
21-
2218
var app = express();
2319

20+
require('./handlebars-helpers.js')(require('hbs'));
21+
2422
app.configure(function(){
2523
app.set('port', process.env.PORT || 3000);
2624
app.set('views', __dirname + '/views');
27-
app.set('view engine', 'hjs');
25+
app.set('view engine', 'hbs');
2826
app.use(express.favicon());
2927
app.use(express.logger('dev'));
3028
app.use(express.bodyParser());
@@ -39,11 +37,14 @@ app.configure('development', function(){
3937

4038
function graphPage(req, res) {
4139
var repo = req.params['repo'] || 'testrepo';
42-
res.render('index', {repo: repo});
40+
res.render('repo', {repo: repo});
4341
};
4442

45-
app.get('/', function(req, res) {
46-
res.redirect(DEFAULT_REPO);
43+
app.get('/', function(req, res, next) {
44+
gitutil.listRepos(ROOT, function(err, repos) {
45+
if (err) return next(err);
46+
res.render('index', {repos: repos});
47+
});
4748
});
4849

4950
app.get('/:repo/graph', function(req, res, next) {
@@ -73,22 +74,14 @@ app.get('/:repo/graph', function(req, res, next) {
7374

7475
app.get('/:repo', graphPage);
7576

76-
var server = http.createServer(app);
77-
78-
var io = require('socket.io').listen(server);
79-
80-
server.listen(app.get('port'), function(){
81-
console.log("Express server listening on port " + app.get('port'));
82-
});
83-
8477
var _watched ={};
8578
function watchRepo(repo) {
8679
if (_watched[repo]) return;
8780
_watched[repo] = true;
8881

8982
var repodir = path.join(ROOT, repo);
9083
if (!dirExistsSync(repodir))
91-
throw 'not existing: ' + repodir;
84+
throw 'The repository root you provided does not not exist: ' + repodir;
9285

9386
require('watch').watchTree(repodir, {interval: WATCH_INTERVAL_MS}, function() {
9487
onChange(repo);
@@ -100,16 +93,28 @@ function onChange(repo) {
10093
if (timeouts[repo]) return;
10194
timeouts[repo] = setTimeout(function() {
10295
timeouts[repo] = null;
103-
console.log("REPO CHANGED", repo);
10496
io.sockets.emit('change:' + repo);
10597
}, 100);
10698
}
10799

108-
io.sockets.on('connection', function(socket) {
109-
});
110-
111100
function dirExistsSync (d) {
112101
try { return fs.statSync(d).isDirectory(); }
113102
catch (er) { return false; }
114103
}
115104

105+
if (require.main === module) {
106+
if (process.argv.length !== 3)
107+
throw "usage: node app.js REPOS_ROOT # (where REPOS_ROOT is the directory above your repositories)";
108+
109+
ROOT = process.argv[2];
110+
if (!dirExistsSync(ROOT))
111+
throw 'not existing:'+ROOT;
112+
113+
var server = http.createServer(app);
114+
115+
io = require('socket.io').listen(server);
116+
117+
server.listen(app.get('port'), function(){
118+
console.log("Express server listening on port " + app.get('port'));
119+
});
120+
}

gitutil.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
var fs = require('fs'),
2+
path = require('path');
3+
4+
var isGitDir = exports.isGitDir = function(fullPath) {
5+
var gitDir = path.join(fullPath, '.git');
6+
7+
console.log('TRUE1', gitDir);
8+
// check for a normal repository
9+
if (fs.existsSync(gitDir)) {
10+
return true;
11+
}
12+
13+
// check for a bare repository
14+
var bareRepoDirs = ['hooks', 'info', 'objects', 'refs'].map(function(f) {
15+
return fs.existsSync(path.join(fullPath, f));
16+
});
17+
18+
return bareRepoDirs.every(function(f) { return f; });
19+
};
20+
21+
exports.listRepos = function(root, callback) {
22+
fs.readdir(root, function(err, files) {
23+
if (err) return callback(err);
24+
25+
var repos = files
26+
.map(function(f) { return {name: f, path: path.join(root, f)}; })
27+
.filter(function(info) { return isGitDir(info.path); });
28+
29+
callback(null, repos);
30+
});
31+
};
32+

gitviz.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
import subprocess
99
import tempfile
1010

11+
MAX_NODES = 50
12+
MAX_BLOB = 200
13+
1114
vertices = {}
1215

1316
def node_opts(**opts):
@@ -53,9 +56,10 @@ def vertex_opts_for_obj(obj, **opts):
5356
#shape='cube'
5457
)
5558
elif obj.type_name == 'blob':
59+
label = q(str(obj).decode('ascii', 'ignore').replace('\0', '').replace('\n', '\\n')[:MAX_BLOB])
5660
opts.update(
5761
shape='egg',
58-
label=q(str(obj))
62+
label=label
5963
)
6064
else:
6165
opts.update(
@@ -119,11 +123,13 @@ def walk_node(objstore, seen, sha):
119123
elif obj.type_name == 'commit':
120124
tree = obj.tree
121125
tree_vert = vert_for_sha(objstore, obj.tree)
126+
seen.add(tree_vert)
122127
walk_node(objstore, seen, tree)
123128
add_edge(vert, tree_vert)
124129

125130
for parent_sha in obj.parents:
126131
parent_vert = vert_for_sha(objstore, parent_sha)
132+
seen.add(parent_vert)
127133
add_edge(vert, parent_vert)
128134
walk_node(objstore, seen, parent_sha)
129135

handlebars-helpers.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
var fs = require('fs'),
2+
hbs = require('hbs');
3+
4+
module.exports = function(hbs) {
5+
var blocks = {};
6+
7+
hbs.registerHelper('extend', function(name, context) {
8+
var block = blocks[name];
9+
if (!block) {
10+
block = blocks[name] = [];
11+
}
12+
13+
block.push(context.fn(this));
14+
});
15+
16+
hbs.registerHelper('block', function(name) {
17+
var val = (blocks[name] || []).join('\n');
18+
19+
// clear the block
20+
blocks[name] = [];
21+
return val;
22+
});
23+
24+
}
25+
26+
// Register all the partials
27+
fs.readdirSync(__dirname + '/views/partials').forEach(function(filename) {
28+
var partial = filename.slice(0, -4);
29+
hbs.registerPartial(partial, fs.readFileSync(__dirname + '/views/partials/' + filename).toString());
30+
});;

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
"version": "0.0.1",
44
"dependencies": {
55
"supervisor": "0.4.1",
6-
"hjs": "0.0.4",
76
"express": "3.0.0rc3",
87
"watch": "0.5.1",
98
"watch-tree": "0.1.1",
109
"socket.io": "0.9.10",
1110
"node-watch": "0.2.4",
12-
"ejs": "0.8.2"
11+
"hbs": ">= 1.0.4",
12+
"async": ">= 0.1.22"
1313
}
1414
}

tests/TODO

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
repositories with
2+
- missing objects
3+
- large binary files
4+
- images

views/index.hbs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<h1>gitviz</h1>
2+
<h3>repos:</h3>
3+
<ul>
4+
{{#each repos}}
5+
<li><a href="/{{name}}">{{name}}</a></li>
6+
{{/each}}
7+
</ul>

views/index.hjs

Lines changed: 0 additions & 42 deletions
This file was deleted.

views/layout.hbs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>{{title}}</title>
5+
<link rel='stylesheet' href='/stylesheets/style.css' />
6+
7+
{{{block "stylesheets"}}}
8+
{{{block "javascript"}}}
9+
</head>
10+
<body>
11+
{{{body}}}
12+
</body>
13+
</html>

views/repo.hbs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{{#extend "stylesheets"}}
2+
<link rel="stylesheet" type="text/css" href="/stylesheets/canviz.css" />
3+
<script type="text/javascript" src="/javascripts/prototype.js"></script>
4+
<script type="text/javascript" src="/javascripts/canviz.js"></script>
5+
<script type="text/javascript" src="/javascripts/x11colors.js"></script>
6+
{{/extend}}
7+
8+
{{#extend "javascript"}}
9+
<script src="/socket.io/socket.io.js"></script>
10+
<script>
11+
document.observe('dom:loaded', function() {
12+
var socket = io.connect('http://localhost');
13+
socket.on('connect', function() {
14+
refreshGraph();
15+
});
16+
socket.on('change:{{ repo }}', function(data) {
17+
console.log('client received change');
18+
refreshGraph();
19+
});
20+
});
21+
22+
var canviz;
23+
function refreshGraph() {
24+
if (!canviz)
25+
canviz = new Canviz('graph_container');
26+
canviz.load('/{{ repo }}/graph');
27+
}
28+
29+
</script>
30+
{{/extend}}
31+
32+
<div id="page_container">
33+
<div id="graph_container"></div>
34+
<div id="debug_output"></div>
35+
</div>

0 commit comments

Comments
 (0)