-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathgit-cograph.js
executable file
·130 lines (108 loc) · 3.85 KB
/
git-cograph.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#!/usr/bin/env node
var readline = require('readline');
var path = require('path');
var createGraph = require('ngraph.graph');
var program = require('commander');
program
.version(require('./package.json').version)
.usage('[options]')
.description('Prints a graph of files that are most frequently changed together')
.parse(process.argv);
var childProcess = require('child_process')
processGitLogs();
function processGitLogs() {
var buffer = [];
var git = childProcess.spawn('git', ['log', '--name-only', '--pretty=format:""']);
var graph = createGraph();
var rl = readline.createInterface({ input: git.stdout });
rl.on('line', processLine).on('close', printResults);
git.stderr.on('data', function (data) {
console.error('stderr: ' + data.toString());
});
function printResults() {
console.warn('Pairs: ', graph.getLinkCount(), 'Nodes: ' + graph.getNodeCount());
let nodes = [];
graph.forEachNode(node => {
nodes.push(node.data.count);
});
let meanNodeCount = nodes.reduce((a, b) => a + b, 0) / nodes.length;
let stdDevNode = nodes.reduce((a, b) => a + Math.pow(b - meanNodeCount, 2), 0) / nodes.length;
let nodeFilterThreshold = meanNodeCount;// + stdDevNode;
console.warn('Mean node count: ', meanNodeCount, 'StdDev: ', Math.sqrt(stdDevNode));
let scores = [];
graph.forEachLink(link => {
let fromNode = graph.getNode(link.fromId);
let toNode = graph.getNode(link.toId);
let committedTogether = link.data.count;
// if (!(fromNode.id.match(/^src/) || toNode.id.match(/^src/))) return;
if (fromNode.data.count > nodeFilterThreshold ||
toNode.data.count > nodeFilterThreshold) {
let score = committedTogether / (fromNode.data.count + toNode.data.count - committedTogether);
scores.push({
link, score
});
}
})
scores.sort((a, b) => b.score - a.score);
if (scores.length > 0) {
let mean = scores.reduce((a, b) => a + b.score, 0) / scores.length;
let stdDev = Math.sqrt(scores.reduce((a, b) => a + Math.pow(b.score - mean, 2), 0) / scores.length);
console.warn('Jaccard similarity mean: ', mean, '; StdDev: ', stdDev);
}
console.log('graph G {')
scores.forEach(({link, score}) => {
if (score > mean + stdDev) {
console.log(` "${link.fromId}" -- "${link.toId}" [score=${Math.round(score*100)/100}];`)
}
});
console.log('}')
// var similarities = computeSimilarities(commits)
// similarities.print(fileLookup, program.count);
}
function processLine(line) {
if (line === '""') return;
if (line) {
buffer.push(line)
} else {
if (buffer.length > 0) {
buffer.forEach(fileName => {
if (!graph.hasNode(fileName)) {
graph.addNode(fileName, {count: 0});
}
graph.getNode(fileName).data.count += 1;
});
for (let i = 0; i < buffer.length - 1; i++) {
let from = buffer[i];
for (let j = i + 1; j < buffer.length; j++) {
let to = buffer[j];
let canonicalFrom = from;
let canonicalTo = to;
if (from < to) {
canonicalFrom = to;
canonicalTo = from;
}
if (!graph.hasLink(canonicalFrom, canonicalTo)) {
graph.addLink(canonicalFrom, canonicalTo, {count: 0});
}
graph.getLink(canonicalFrom, canonicalTo).data.count += 1;
}
}
buffer = [];
}
}
}
function hasFile(buffer, fileLookup) {
for (var i = 0; i < buffer.length; ++i) {
if (buffer[i] === fileLookup) return true;
}
}
}
function changeWorkingDirectoryBasedOnInput(dirName) {
try {
process.chdir(dirName);
} catch(error) {
if (error.code !== 'ENOENT') throw error;
console.error('no such directory: ' + dirName);
process.exit(1);
}
}