Skip to content

Commit e12d649

Browse files
authored
Merge pull request #6 from ckanz/cypher-transactions
Cypher transactions
2 parents 60b2359 + 91297d8 commit e12d649

10 files changed

+2421
-38
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@
1212
*.out
1313

1414
code2cypher
15+
*/node_modules/

cypherGenerator.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ func getLabelForFileNode(currentFile fileInfo) string {
2727

2828
// fileInfoToCypher returns a cypher statement to create a node for a given file
2929
func fileInfoToCypher(currentFile fileInfo, label string) string {
30-
properties := ("{ name: '" + currentFile.Name + "', url: '" + currentFile.Url + "'")
30+
properties := ("{ name: '" + currentFile.Name +
31+
"', path: '" + currentFile.Path +
32+
"', url: '" + currentFile.Url +
33+
"', _tempId: '" + currentFile.Id + "'")
3134

3235
if (!currentFile.IsDir) {
3336
properties += (", " + "size: " + strconv.FormatInt(currentFile.Size, 10) + ", " +
@@ -42,17 +45,19 @@ func fileInfoToCypher(currentFile fileInfo, label string) string {
4245

4346
// contributerToCypher returns a cypher statement to create node for a given contributer
4447
func contributerToCypher(contributerId, contributerName, contributerEmail string) string {
45-
return ("CREATE (" + contributerId + ":" + "person" + " { name: '" + contributerName + "', email: '" + contributerEmail + "' })")
48+
return ("MERGE (" + contributerId + ":" + "person" + " { _tempId: '" + contributerId + "', name: '" + contributerName + "', email: '" + contributerEmail + "' })")
4649
}
4750

4851
// contributerToCypherUpdate returns a cypher statement to update a given contributer's commitCount
4952
func contributerToCypherUpdate(contributerId string, commitCount int) string {
50-
return ("SET " + contributerId + ".commitCount = " + strconv.Itoa(commitCount))
53+
return ("MATCH (c:person { _tempId: '" + contributerId + "' }) " +
54+
"SET c.commitCount = " + strconv.Itoa(commitCount)) + " " +
55+
"REMOVE c._tempId"
5156
}
5257

5358
// contributionToCypher returns to cypher statement to create a relationship between a file and a contributer
5459
func contributionToCypher(fileId, contributerId string, contributionId string) string {
55-
return "CREATE (" + fileId + ")<-[" + contributionId + ":EDITED]-(" + contributerId + ")"
60+
return "CREATE (" + fileId + ")<-[" + contributionId + ":EDITED { _tempId: '" + contributionId +"' }]-(" + contributerId + ")"
5661
}
5762

5863
// contributionToCypher returns to cypher statement to create a relationship between a file and a contributer
@@ -63,10 +68,16 @@ func commitToCypher(fileId, contributerId string, contribution fileContribution)
6368

6469
// contributionToCypherUpdate returns a cypher statement to update a given contribution's commitCount
6570
func contributionToCypherUpdate(contributionId string, commitCount int) string {
66-
return "SET " + contributionId + ".commitCount = " + strconv.Itoa(commitCount)
71+
return "MATCH (c:person)-[e:EDITED { _tempId: '" + contributionId + "' }]->(f:file) " +
72+
"SET e.commitCount = " + strconv.Itoa(commitCount)
6773
}
6874

6975
// folderStructureToCypher returns to cypher statement to create a relationship between a file and its parent folder
7076
func folderStructureToCypher(currentFile fileInfo) string {
71-
return "CREATE (" + currentFile.Id + ")-[:IN_FOLDER]->(" + currentFile.ParentId + ")"
77+
return "Match (a:directory { path: '" + currentFile.ParentPath +"' }) Match (b { path: '" + currentFile.Path +"' }) CREATE (b)-[:IN_FOLDER]->(a)"
78+
}
79+
80+
// returns a cypher statement that removes a given property from all nodes
81+
func removeProperty(propertyName string) string {
82+
return "MATCH (a) REMOVE a." + propertyName
7283
}

cypherGenerator_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,17 @@ func TestFileInfoToCypher(t *testing.T) {
3030
{
3131
fileInfo { Id: "fileId", Name: "someDir", IsDir: true, },
3232
"testLabel",
33-
"CREATE (fileId:testLabel { name: 'someDir', url: '' })",
33+
"CREATE (fileId:testLabel { name: 'someDir', path: '', url: '', _tempId: 'fileId' })",
3434
},
3535
{
3636
fileInfo { Name: "someDir", IsDir: true, Url: "https://github.com/someName/someRepo/tree/master/someDir" },
3737
"testLabel",
38-
"CREATE (:testLabel { name: 'someDir', url: 'https://github.com/someName/someRepo/tree/master/someDir' })",
38+
"CREATE (:testLabel { name: 'someDir', path: '', url: 'https://github.com/someName/someRepo/tree/master/someDir', _tempId: '' })",
3939
},
4040
{
4141
fileInfo { Id: "fileId", Name: "someFile", IsDir: false, Size: 42, CommitCount: 23, ModTime: 111222333, Extension: "go" },
4242
"testLabel",
43-
"CREATE (fileId:testLabel { name: 'someFile', url: '', size: 42, commitCount: 23, lastModifiedDateTime: datetime({ epochseconds: 111222333 }), lastModifiedTimestamp: 111222333, extension: 'go' })",
43+
"CREATE (fileId:testLabel { name: 'someFile', path: '', url: '', _tempId: 'fileId', size: 42, commitCount: 23, lastModifiedDateTime: datetime({ epochseconds: 111222333 }), lastModifiedTimestamp: 111222333, extension: 'go' })",
4444
},
4545
}
4646
for _, table := range testTables {
@@ -62,7 +62,7 @@ func TestContributerToCypher(t *testing.T) {
6262
"someId",
6363
"William T. Riker",
6464
65-
"CREATE (someId:person { name: 'William T. Riker', email: '[email protected]' })",
65+
"MERGE (someId:person { _tempId: 'someId', name: 'William T. Riker', email: '[email protected]' })",
6666
},
6767
}
6868
for _, table := range testTables {
@@ -84,7 +84,7 @@ func TestContributionToCypher(t *testing.T) {
8484
"someFile_java",
8585
"William_T__Riker",
8686
"someFile_java__William_T__Riker",
87-
"CREATE (someFile_java)<-[someFile_java__William_T__Riker:EDITED]-(William_T__Riker)",
87+
"CREATE (someFile_java)<-[someFile_java__William_T__Riker:EDITED { _tempId: 'someFile_java__William_T__Riker' }]-(William_T__Riker)",
8888
},
8989
}
9090
for _, table := range testTables {
@@ -101,8 +101,8 @@ func TestFolderStructureToCypher(t *testing.T) {
101101
cypherResult string
102102
}{
103103
{
104-
fileInfo { Id: "someFileId", ParentId: "someParentId" },
105-
"CREATE (someFileId)-[:IN_FOLDER]->(someParentId)",
104+
fileInfo { Path: "someFilePath", ParentPath: "someParentPath" },
105+
"Match (a:directory { path: 'someParentPath' }) Match (b { path: 'someFilePath' }) CREATE (b)-[:IN_FOLDER]->(a)",
106106
},
107107
}
108108
for _, table := range testTables {

code2cypher.go renamed to git2cypher.go

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,27 @@ import (
77
"os"
88
"log"
99
"strings"
10-
"strconv"
1110
)
1211

1312
type fileInfo struct {
1413
Name string
14+
Path string
1515
Url string
1616
Size int64
1717
Level int
1818
IsDir bool
1919
Id string
2020
Extension string
2121
ModTime int64
22-
ParentName string
23-
ParentId string
22+
ParentPath string
2423
Contributions []fileContribution
2524
CommitCount int
2625
}
2726
var nodes []fileInfo
2827
var processedFiles = make(map[string]bool)
2928
var processedNodes = make(map[string]bool)
30-
var processedContributers = make(map[string]int)
31-
var processedContributions = make(map[string]int)
29+
var processedContributersSum = make(map[string]int)
30+
var processedContributionsSum = make(map[string]int)
3231
var verbose bool
3332
var simplified bool
3433
var repoPath string
@@ -42,12 +41,6 @@ func initFlags() {
4241
flag.Parse()
4342
}
4443

45-
// getUniqueNameString creates a unique string for a file based on its nested depth in the folder and its name
46-
// TODO: instead of depth, modified timestamp might be a better value to create unique variable names with
47-
func getUniqueNameString(index int, element string) string {
48-
return strconv.Itoa(index) + "-" + element
49-
}
50-
5144
// getFileExtension returns the extension for a given file's full name
5245
func getFileExtension (info os.FileInfo) string {
5346
if (info.IsDir() == false) {
@@ -85,9 +78,10 @@ func main() {
8578
fileDepth := len(pathSegments) - 1
8679
fileName := info.Name()
8780
verboseLog("fileName: " + fileName)
88-
uniqueNameString := getUniqueNameString(fileDepth, fileName)
8981

90-
if (processedFiles[uniqueNameString] != true) {
82+
if (processedFiles[path] != true) {
83+
parentPath := strings.Join(pathSegments[:len(pathSegments)-1], "/")
84+
verboseLog("parentPath: " + parentPath)
9185
parentDepth := fileDepth - 1
9286
if (parentDepth < 0) {
9387
parentDepth = 0
@@ -97,19 +91,19 @@ func main() {
9791

9892
nodes = append(nodes, fileInfo{
9993
Name: fileName,
94+
Path: path,
10095
Url: buildGitHubUrl(gitRepoUrl, path, info.IsDir()),
10196
Size: info.Size(),
10297
Level: fileDepth,
10398
Extension: getFileExtension(info),
104-
Id: createCypherFriendlyVarName(fileName, fileDepth),
99+
Id: createCypherFriendlyVarName(path, fileDepth),
105100
IsDir: info.IsDir(),
106101
ModTime: info.ModTime().Unix(),
107-
ParentName: pathSegments[parentDepth],
108-
ParentId : createCypherFriendlyVarName(pathSegments[parentDepth], parentDepth),
102+
ParentPath: parentPath,
109103
Contributions: contributions,
110104
CommitCount: len(contributions),
111105
})
112-
processedFiles[uniqueNameString] = true
106+
processedFiles[path] = true
113107
}
114108
}
115109

@@ -121,11 +115,14 @@ func main() {
121115
verboseLog("")
122116

123117
for _, currentFile := range nodes {
118+
var processedContributers = make(map[string]int)
119+
var processedContributions = make(map[string]int)
120+
fmt.Println(":BEGIN")
124121
label := getLabelForFileNode(currentFile)
125122

126-
if (!processedNodes[currentFile.Id]) {
123+
if (!processedNodes[currentFile.Path]) {
127124
fmt.Println(fileInfoToCypher(currentFile, label))
128-
processedNodes[currentFile.Id] = true
125+
processedNodes[currentFile.Path] = true
129126
}
130127

131128
if (label == "file") {
@@ -136,33 +133,51 @@ func main() {
136133
processedContributers[contributerId] = 0
137134
}
138135
processedContributers[contributerId] += 1
136+
processedContributersSum[contributerId] += 1
139137

140138
contributionId := currentFile.Id + "__" + contributerId
141-
contributionCypherStatement := contributionToCypher(currentFile.Id, contributerId, contributionId)
142139
if (processedContributions[contributionId] < 1) {
143-
fmt.Println(contributionCypherStatement)
140+
fmt.Println(contributionToCypher(currentFile.Id, contributerId, contributionId))
144141
processedContributions[contributionId] = 0
145142
}
146143
if (simplified != true) {
147144
fmt.Println(commitToCypher(currentFile.Id, contributerId, contribution))
148145
}
149146
processedContributions[contributionId] += 1
147+
processedContributionsSum[contributionId] += 1
150148
}
151149
}
152150

153-
if (currentFile.Id != currentFile.ParentId) {
151+
fmt.Println(";")
152+
fmt.Println(":COMMIT")
153+
154+
if (len(currentFile.ParentPath) > 0) {
155+
fmt.Println(":BEGIN")
154156
fmt.Println(folderStructureToCypher(currentFile))
157+
fmt.Println(";")
158+
fmt.Println(":COMMIT")
155159
}
156160
}
157161

158-
for contributerId, contributionCount := range processedContributers {
162+
for contributerId, contributionCount := range processedContributersSum {
163+
fmt.Println(":BEGIN")
159164
fmt.Println(contributerToCypherUpdate(contributerId, contributionCount))
165+
fmt.Println(";")
166+
fmt.Println(":COMMIT")
160167
}
161-
for contributionId, commitCount := range processedContributions {
168+
169+
for contributionId, commitCount := range processedContributionsSum {
170+
fmt.Println(":BEGIN")
162171
fmt.Println(contributionToCypherUpdate(contributionId, commitCount))
172+
fmt.Println(";")
173+
fmt.Println(":COMMIT")
163174
}
164175

176+
fmt.Println(":BEGIN")
177+
fmt.Println(removeProperty("_tempId"))
165178
fmt.Println(";")
179+
fmt.Println(":COMMIT")
180+
166181

167182
if err != nil {
168183
log.Println(err)

gitFunctions.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func getGitLog(path string, repoPath string) []fileContribution {
5757
splitLog := strings.Split(contribution, "||")
5858
if (len(splitLog) > 1) {
5959
fileContribs = append(fileContribs, fileContribution{
60-
Name: splitLog[0],
60+
Name: strings.Replace(splitLog[0], "'", " ", -1),
6161
Email: splitLog[1],
6262
Commit: splitLog[2],
6363
AbbreviatedHash: splitLog[3],

js/acorn2cypher.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
const acorn = require('acorn')
2+
const fs = require('fs')
3+
const path = require('path')
4+
5+
const SystemPath = path.join(__dirname, '/')
6+
7+
const getAllFiles = function (dirPath, arrayOfFiles) {
8+
const files = fs.readdirSync(dirPath)
9+
10+
arrayOfFiles = arrayOfFiles || []
11+
12+
files.forEach(function (file) {
13+
if (fs.statSync(dirPath + '/' + file).isDirectory()) {
14+
arrayOfFiles = getAllFiles(dirPath + '/' + file, arrayOfFiles)
15+
} else {
16+
const extension = file.split('.').pop()
17+
if (extension === 'js') arrayOfFiles.push(path.join(__dirname, dirPath, '/', file))
18+
}
19+
})
20+
21+
return arrayOfFiles
22+
}
23+
24+
const toCypher = (type, size, name, varName, index, parentIndex, rawNode) => {
25+
console.log(`CREATE (${varName}_${parentIndex !== undefined ? parentIndex : ''})-[:DECLARES]->(${parentIndex === undefined ? varName + '_' + index : ''}:${type} { size: ${size}, name: '[${name}]', ${Object.entries(rawNode).map(e => `_${e[0]}:'# ${e[1]}'`)} })`)
26+
}
27+
28+
const allFiles = getAllFiles('src/')
29+
30+
allFiles.forEach((f, i) => {
31+
try {
32+
console.log(':BEGIN')
33+
const varName = `f_${i}`
34+
console.log(`MERGE (${varName}_:file { path: '${f.split(SystemPath)[1]}' })`)
35+
const file = fs.readFileSync(f, 'utf8')
36+
37+
const r = acorn.parse(file, { ecmaVersion: 2020, sourceType: 'module' })
38+
39+
const logNode = (n, index, parentIndex) => {
40+
if (n.key) {
41+
toCypher(n.type, n.end - n.start, n.key.name, varName, index, parentIndex, n)
42+
return
43+
}
44+
const d = n.declarations ? n.declarations[0] : n.declaration
45+
if (!d) return
46+
if (d.id) {
47+
toCypher(n.type, n.end - n.start, d.id.name, varName, index, parentIndex, n)
48+
} else if (d.declarations) {
49+
toCypher(n.type, n.end - n.start, d.declarations[0].id.name, varName, index, parentIndex, n)
50+
if (d.declarations[0].body) logNodeBody(d.declarations[0].body.body, index)
51+
}
52+
if (d.body) logNodeBody(d.body.body, index)
53+
}
54+
55+
const logNodeBody = (nb, parentIndex) => {
56+
nb.forEach((n, index) => {
57+
logNode(n, index, parentIndex)
58+
// if (n.body) logNodeBody(n.body.body, index)
59+
})
60+
}
61+
62+
logNodeBody(r.body)
63+
console.log(';')
64+
console.log(':COMMIT')
65+
} catch (e) {
66+
console.log(';')
67+
console.log(':ROLLBACK')
68+
}
69+
})

js/dependencytree2cypher.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const dependencyTree = require('dependency-tree')
2+
const path = require('path')
3+
4+
const SystemPath = path.join(__dirname, '/')
5+
6+
const tree = dependencyTree({
7+
filename: 'src/index.js',
8+
directory: '.',
9+
webpackConfig: 'webpack.config.js'
10+
})
11+
12+
const createFileCypher = (fileName, varName = '') => {
13+
return `MERGE (${varName}:file { path: '${fileName.split(SystemPath)[1]}' })`
14+
}
15+
16+
const listFileImports = file => {
17+
Object.entries(file).forEach(([currentFile, fileImports]) => {
18+
console.log(':BEGIN')
19+
console.log(createFileCypher(currentFile, 'f'))
20+
Object.keys(fileImports).forEach((fileImport, index) => {
21+
const varName = `i${index}`
22+
console.log(createFileCypher(fileImport, varName))
23+
console.log(`MERGE (f)-[:IMPORTS]->(${varName})`)
24+
})
25+
console.log(';')
26+
console.log(':COMMIT')
27+
listFileImports(fileImports)
28+
})
29+
}
30+
31+
listFileImports(tree)

0 commit comments

Comments
 (0)