Skip to content

Commit 9660d3e

Browse files
committed
fixed tests
1 parent f53dda9 commit 9660d3e

12 files changed

+328
-104
lines changed

code_graph/analyzers/source_analyzer.py

+29-10
Original file line numberDiff line numberDiff line change
@@ -155,28 +155,47 @@ def analyze_github_repository(self, url: str) -> None:
155155

156156
logger.info("Done processing repository")
157157

158-
def analyze_local_repository(self, path: str, ignore: Optional[List[str]]) -> None:
158+
def analyze_local_folder(self, path: str, ignore: Optional[List[str]] = []) -> Graph:
159159
"""
160-
Analyze a local Git repository.
160+
Analyze a local folder.
161161
162162
Args:
163-
path (str): Path to a local git repository
163+
path (str): Path to a local folder containing source files to process
164164
ignore (List(str)): List of paths to skip
165165
"""
166166

167-
ignore = ignore or []
168-
169-
# change working directory to repo
167+
# change working directory to path
170168
os.chdir(path)
171169

172-
repo_name = os.path.split(os.path.normpath(path))[-1]
173-
logger.debug(f'repo_name: {repo_name}')
170+
proj_name = os.path.split(os.path.normpath(path))[-1]
171+
logger.debug(f'proj_name: {proj_name}')
174172

175173
# Initialize the graph and analyzer
176-
self.graph = Graph(repo_name, self.host, self.port, self.username,
174+
self.graph = Graph(proj_name, self.host, self.port, self.username,
177175
self.password)
178176

179177
# Analyze source files
180178
self.analyze_sources(ignore)
181179

182-
logger.info("Done processing repository")
180+
logger.info("Done processing folder")
181+
182+
return self.graph
183+
184+
def analyze_local_repository(self, path: str, ignore: Optional[List[str]] = []) -> Graph:
185+
"""
186+
Analyze a local Git repository.
187+
188+
Args:
189+
path (str): Path to a local git repository
190+
ignore (List(str)): List of paths to skip
191+
"""
192+
193+
self.analyze_local_folder(path, ignore)
194+
195+
# Save processed commit hash to the DB
196+
repo = Repo(path)
197+
head = repo.commit("HEAD")
198+
self.graph.set_graph_commit(head.hexsha)
199+
200+
return self.graph
201+

code_graph/git_utils/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
from .git_utils import build_commit_graph
1+
from .git_utils import build_commit_graph, switch_commit, GitGraph

code_graph/git_utils/git_graph.py

+70-9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
from falkordb import FalkorDB
2+
from typing import List, Optional
3+
14
class GitGraph():
25
"""
36
Represents a git commit graph
@@ -21,16 +24,35 @@ def __init__(self, name: str, host: str = 'localhost', port: int = 6379,
2124
pass
2225

2326

24-
def add_commit(self, commit_hash: str, author: str, message: str, date, int) -> None:
27+
def add_commit(self, commit_hash: str, author: str, message: str, date: int) -> None:
2528
"""
2629
Add a new commit to the graph
2730
"""
2831
q = "MERGE (c:Commit {hash: $hash, author: $author, message: $message, date: $date})"
29-
params = {'hash': commit_hash, 'author': author, 'message': message, 'date': $date}
32+
params = {'hash': commit_hash, 'author': author, 'message': message, 'date': date}
3033
self.g.query(q, params)
3134

35+
def get_commits(self, hashes: List[str]) -> List[dict]:
36+
q = """MATCH (c:Commit)
37+
WHERE c.hash IN $hashes
38+
RETURN c"""
39+
40+
params = {'hashes': hashes}
41+
res = self.g.query(q, params).result_set
42+
43+
commits = []
44+
for row in res:
45+
commit = row[0]
46+
commit = {'hash': commit.properties['hash'],
47+
'date': commit.properties['date'],
48+
'author': commit.properties['author'],
49+
'message': commit.properties['message']}
3250

33-
def connect_commits(child: str, parent: str) -> None:
51+
commits.append(commit)
52+
53+
return commits
54+
55+
def connect_commits(self, child: str, parent: str) -> None:
3456
"""
3557
connect commits via both PARENT and CHILD edges
3658
"""
@@ -41,30 +63,69 @@ def connect_commits(child: str, parent: str) -> None:
4163

4264
params = {'child_hash': child, 'parent_hash': parent}
4365

44-
self.g.query(q, parent)
66+
self.g.query(q, params)
67+
4568

46-
def set_parent_transition(child: str, parent: str, queries: [tuple[str: dict]]) -> None:
69+
def set_parent_transition(self, child: str, parent: str, queries: [str], params: [dict]) -> None:
4770
"""
4871
Sets the queries and parameters needed to transition the code-graph
4972
from the child commit to the parent commit
5073
"""
5174

52-
q = """MATCH (child: Commit {hash: $child})-[e:PARENT]->(parent {hash: $parent})
75+
q = """MATCH (child :Commit {hash: $child})-[e:PARENT]->(parent :Commit {hash: $parent})
5376
SET e.queries = $queries, e.params = $params"""
5477

55-
params = {'child': child, 'parent': parent, 'queries': queries}
78+
params = {'child': child, 'parent': parent, 'queries': queries, 'params': params}
5679

5780
self.g.query(q, params)
5881

59-
def set_child_transition(child: str, parent: str, queries: [tuple[str: dict]]) -> None:
82+
83+
def set_child_transition(self, child: str, parent: str, queries: List[tuple[str: dict]]) -> None:
6084
"""
6185
Sets the queries and parameters needed to transition the code-graph
6286
from the parent commit to the child commit
6387
"""
6488

65-
q = """MATCH (parent {hash: $parent})-[e:CHILD]->(child: Commit {hash: $child})
89+
q = """MATCH (parent :Commit {hash: $parent})-[e:CHILD]->(child :Commit {hash: $child})
6690
SET e.queries = $queries, e.params = $params"""
6791

6892
params = {'child': child, 'parent': parent, 'queries': queries}
6993

7094
self.g.query(q, params)
95+
96+
97+
def get_parent_transition(self, child: str, parent: str) -> List[tuple[str: dict]]:
98+
"""
99+
Get queries and parameters transitioning from child commit to parent commit
100+
"""
101+
q = """MATCH path = (:Commit {hash: $child_hash})-[:PARENT*]->(:Commit {hash: $parent_hash})
102+
WITH path
103+
LIMIT 1
104+
UNWIND relationships(path) AS e
105+
WITH e
106+
WHERE e.queries is not NULL
107+
RETURN collect(e.queries), collect(e.params)
108+
"""
109+
110+
res = self.g.query(q, {'child_hash': child, 'parent_hash': parent}).result_set
111+
112+
return (res[0][0], res[0][1])
113+
114+
115+
def get_child_transition(self, child: str, parent: str) -> List[tuple[str: dict]]:
116+
"""
117+
Get queries transitioning from parent to child
118+
"""
119+
q = """MATCH path = (:Commit {hash: $parent_hash})-[:CHILD*]->(:Commit {hash: $child_hash})
120+
WITH path
121+
LIMIT 1
122+
UNWIND relationships(path) AS e
123+
WITH e
124+
WHERE e.queries is not NULL
125+
RETURN collect(e.queries), collect(e.params)
126+
"""
127+
128+
res = self.g.query(q, {'child_hash': child, 'parent_hash': parent}).result_set
129+
130+
return (res[0][0], res[0][1])
131+

code_graph/git_utils/git_utils.py

+80-14
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import os
22
import time
3+
import json
34
import redis
45
import threading
56
import subprocess
67
from git import Repo
78
from ..graph import Graph
9+
from .git_graph import GitGraph
10+
from typing import List, Optional
811

912
monitor_thread = None
1013
replica_process = None
@@ -98,23 +101,39 @@ def stop_monitor_effects():
98101
print("monitor thread exited")
99102

100103
# build a graph capturing the git commit history
101-
def build_commit_graph(path: str):
104+
def build_commit_graph(path: str, ignore_list: Optional[List[str]] = []) -> GitGraph:
105+
print(f"Processing git history at: {path}")
106+
print(f"ignoring the following paths: {ignore_list}")
107+
102108
repo = Repo(path)
103109

104110
repo_name = os.path.split(os.path.normpath(path))[-1]
105-
g = Graph(repo_name)
111+
g = Graph(repo_name)
112+
git_graph = GitGraph('{' + repo_name + '}' + '_git')
106113

107114
#setup_replication()
108115

109116
# start monitoring graph effects
110117
# these capture the changes a graph goes through when moving from one
111118
# git commit to another
112-
start_monitor_effects(g.g.name)
119+
#start_monitor_effects(g.g.name)
113120

114121
head_commit = repo.commit("HEAD")
122+
123+
# add commit to the git graph
124+
git_graph.add_commit(head_commit.hexsha, head_commit.author.name,
125+
head_commit.message, head_commit.committed_date)
126+
115127
while len(head_commit.parents) > 0:
116128
prev_commit = head_commit.parents[0]
117129

130+
# add commit to the git graph
131+
git_graph.add_commit(prev_commit.hexsha, prev_commit.author.name,
132+
prev_commit.message, prev_commit.committed_date)
133+
134+
# connect child parent commits relation
135+
git_graph.connect_commits(head_commit.hexsha, prev_commit.hexsha)
136+
118137
# represents the changes going backward!
119138
# e.g. which files need to be deleted when moving back one commit
120139
#
@@ -134,16 +153,17 @@ def build_commit_graph(path: str):
134153

135154
for change in diff:
136155
if change.new_file:
137-
#print(f"new_file: {change.b_path}")
138-
added.append(change.b_path)
156+
if all(not change.b_path.startswith(ignore) for ignore in ignore_list):
157+
#print(f"new_file: {change.b_path}")
158+
added.append(change.b_path)
139159
elif change.deleted_file:
140-
print(f"deleted_file: {change.a_path}")
141-
deleted.append(change.a_path)
160+
if all(not change.a_path.startswith(ignore) for ignore in ignore_list):
161+
print(f"deleted_file: {change.a_path}")
162+
deleted.append(change.a_path)
142163
elif change.change_type == 'M':
143-
#print(f"modified_file: {change.a_path}")
144-
modified.append(change.a_path)
145-
146-
head_commit = prev_commit
164+
if all(not change.a_path.startswith(ignore) for ignore in ignore_list):
165+
#print(f"modified_file: {change.a_path}")
166+
modified.append(change.a_path)
147167

148168
#-----------------------------------------------------------------------
149169
# apply changes
@@ -159,16 +179,62 @@ def build_commit_graph(path: str):
159179
'ext' : os.path.splitext(path)[1]} for path in deleted]
160180

161181
# remove deleted files from the graph
162-
q, params = g.delete_files(deleted_files, True)
163-
if(q is not None):
182+
transition = g.delete_files(deleted_files, True)
183+
if(transition is not None):
184+
queries, params = transition
164185
# log transition action
186+
git_graph.set_parent_transition(head_commit.hexsha,
187+
prev_commit.hexsha, queries,
188+
json.dumps(params))
165189

166-
input("Press Enter to continue...")
190+
# advance to the next commit
191+
head_commit = prev_commit
167192

168193
# clean up
169194
#stop_monitor_effects()
170195
#teardown_replica()
171196

197+
return git_graph
198+
199+
def switch_commit(repo: str, to: str):
200+
"""switch graph state from its current commit to given commit"""
201+
202+
g = Graph(repo)
203+
git_graph = GitGraph('{' + repo + '}' + 'git')
204+
205+
# Get the graph's current commit
206+
current_hash = g.get_graph_commit()
207+
208+
# Find path from current commit to desired commit
209+
commits = git_graph.get_commits([current_hash, to])
210+
211+
if len(commits) != 2:
212+
print("missing commits")
213+
return
214+
215+
# determine relation between commits
216+
current_commit = commits[0] if commits[0]['hash'] == current_hash else commits[1]
217+
new_commit = commits[0] if commits[0]['hash'] == to else commits[1]
218+
219+
if current_commit['date'] > new_commit['date']:
220+
print("moving backwared")
221+
queries, params = git_graph.get_parent_transition(current_commit['hash'], new_commit['hash'])
222+
else:
223+
print("moving forwards")
224+
queries, params = git_graph.get_child_transition(current_commit['hash'], new_commit['hash'])
225+
226+
# Apply transitions
227+
for i in range(0, len(queries)):
228+
q = queries[i]
229+
p = json.loads(params[i])
230+
print(f"query: {q}")
231+
print(f"params: {p}")
232+
233+
g.rerun_query(q, p)
234+
235+
# update graph's commit
236+
g.set_graph_commit(to)
237+
172238
if __name__ == "__main__":
173239
build_commit_graph("/Users/roilipman/Dev/FalkorDB")
174240

code_graph/graph.py

+20
Original file line numberDiff line numberDiff line change
@@ -426,3 +426,23 @@ def get_struct(self, struct_id: int) -> Optional[Struct]:
426426
s = res.result_set[0][0]
427427
return self._struct_from_node(s)
428428

429+
def set_graph_commit(self, commit_hash: str) -> None:
430+
"""Save processed commit hash to the DB"""
431+
432+
# connect to DB
433+
434+
# Set STRING key {FalkorDB}_commit = commit_hash
435+
self.db.connection.set('{' + self.g.name + '}' + '_commit', commit_hash)
436+
437+
def get_graph_commit(self) -> str:
438+
"""Get the current commit the graph is at"""
439+
440+
return self.db.connection.get('{' + self.g.name + '}' + '_commit')
441+
442+
443+
def rerun_query(self, q: str, params: dict) -> None:
444+
"""
445+
Re-run a query to transition the graph from one state to another
446+
"""
447+
448+
res = self.g.query(q, params)

main.py

+26-1
Original file line numberDiff line numberDiff line change
@@ -258,11 +258,36 @@ def process_git_history():
258258

259259
# path to local repository
260260
repo = data.get('repo')
261+
if repo is None:
262+
return jsonify({'status': f'Missing mandatory parameter "repo"'}), 400
263+
264+
ignore_list = data.get('ignore') or []
265+
266+
build_commit_graph(repo, ignore_list)
267+
268+
# Create a response
269+
response = {
270+
'status': 'success',
271+
}
261272

273+
return jsonify(response), 200
274+
275+
276+
@app.route('/switch_commit', methods=['POST'])
277+
def process_switch_commit():
278+
# Get JSON data from the request
279+
data = request.get_json()
280+
281+
# path to local repository
282+
repo = data.get('repo')
262283
if repo is None:
263284
return jsonify({'status': f'Missing mandatory parameter "repo"'}), 400
264285

265-
build_commit_graph(repo)
286+
commit = data.get('commit')
287+
if commit is None:
288+
return jsonify({'status': f'Missing mandatory parameter "commit"'}), 400
289+
290+
switch_commit(repo, commit)
266291

267292
# Create a response
268293
response = {

tests/git_repo

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Subproject commit df8d021dbae077a39693c1e76e8438006d62603e

0 commit comments

Comments
 (0)