-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathgit-cograph.js
executable file
·128 lines (106 loc) · 3.79 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
#!/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);
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('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);
}
}