Skip to content

Commit 589fdcf

Browse files
committed
improve docs and delete file query
1 parent 9660d3e commit 589fdcf

File tree

4 files changed

+156
-43
lines changed

4 files changed

+156
-43
lines changed

code_graph/git_utils/git_graph.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def set_child_transition(self, child: str, parent: str, queries: List[tuple[str:
9494
self.g.query(q, params)
9595

9696

97-
def get_parent_transition(self, child: str, parent: str) -> List[tuple[str: dict]]:
97+
def get_parent_transitions(self, child: str, parent: str) -> List[tuple[str: dict]]:
9898
"""
9999
Get queries and parameters transitioning from child commit to parent commit
100100
"""
@@ -112,9 +112,9 @@ def get_parent_transition(self, child: str, parent: str) -> List[tuple[str: dict
112112
return (res[0][0], res[0][1])
113113

114114

115-
def get_child_transition(self, child: str, parent: str) -> List[tuple[str: dict]]:
115+
def get_child_transitions(self, child: str, parent: str) -> List[tuple[str: dict]]:
116116
"""
117-
Get queries transitioning from parent to child
117+
Get queries and parameters transitioning from parent commit to child commit
118118
"""
119119
q = """MATCH path = (:Commit {hash: $parent_hash})-[:CHILD*]->(:Commit {hash: $child_hash})
120120
WITH path

code_graph/git_utils/git_utils.py

Lines changed: 102 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,24 @@
22
import time
33
import json
44
import redis
5+
import logging
56
import threading
67
import subprocess
78
from git import Repo
89
from ..graph import Graph
910
from .git_graph import GitGraph
1011
from typing import List, Optional
1112

13+
# Configure logging
14+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
15+
1216
monitor_thread = None
1317
replica_process = None
1418
monitor_exit_event = threading.Event()
1519

20+
def GitRepoName(repo_name):
21+
return "{" + repo_name + "}_git"
22+
1623
# setup replication to the master
1724
def setup_replication():
1825
global replica_process
@@ -108,8 +115,10 @@ def build_commit_graph(path: str, ignore_list: Optional[List[str]] = []) -> GitG
108115
repo = Repo(path)
109116

110117
repo_name = os.path.split(os.path.normpath(path))[-1]
111-
g = Graph(repo_name)
112-
git_graph = GitGraph('{' + repo_name + '}' + '_git')
118+
119+
# Clone graph into a temporary graph
120+
g = Graph(repo_name).clone(repo_name + "_tmp")
121+
git_graph = GitGraph(GitRepoName(repo_name))
113122

114123
#setup_replication()
115124

@@ -194,46 +203,111 @@ def build_commit_graph(path: str, ignore_list: Optional[List[str]] = []) -> GitG
194203
#stop_monitor_effects()
195204
#teardown_replica()
196205

197-
return git_graph
206+
# Delete temporaty graph
207+
g.delete()
198208

199-
def switch_commit(repo: str, to: str):
200-
"""switch graph state from its current commit to given commit"""
209+
return git_graph
201210

211+
def switch_commit(repo: str, to: str) -> dict[str, dict[str, list]]:
212+
"""
213+
Switches the state of a graph repository from its current commit to the given commit.
214+
215+
This function handles switching between two git commits for a graph-based repository.
216+
It identifies the changes (additions, deletions, modifications) in nodes and edges between
217+
the current commit and the target commit and then applies the necessary transitions.
218+
219+
Args:
220+
repo (str): The name of the graph repository to switch commits.
221+
to (str): The target commit hash to switch the graph to.
222+
223+
Returns:
224+
dict: A dictionary containing the changes made during the commit switch, organized by:
225+
- 'deletions': {
226+
'nodes': List of node IDs deleted,
227+
'edges': List of edge IDs deleted
228+
},
229+
- 'additions': {
230+
'nodes': List of new Node objects added,
231+
'edges': List of new Edge objects added
232+
},
233+
- 'modifications': {
234+
'nodes': List of modified Node objects,
235+
'edges': List of modified Edge objects
236+
}
237+
"""
238+
239+
# Validate input arguments
240+
if not repo or not isinstance(repo, str):
241+
raise ValueError("Invalid repository name")
242+
243+
if not to or not isinstance(to, str):
244+
raise ValueError("Invalid desired commit value")
245+
246+
# Initialize return value to an empty change set
247+
change_set = {
248+
'deletions': {
249+
'nodes': [],
250+
'edges': []
251+
},
252+
'additions': {
253+
'nodes': [],
254+
'edges': [],
255+
},
256+
'modifications': {
257+
'nodes': [],
258+
'edges': []
259+
}
260+
}
261+
262+
# Initialize the graph and GitGraph objects
202263
g = Graph(repo)
203-
git_graph = GitGraph('{' + repo + '}' + 'git')
264+
git_graph = GitGraph(GitRepoName(repo))
204265

205-
# Get the graph's current commit
266+
# Get the current commit hash of the graph
206267
current_hash = g.get_graph_commit()
268+
logging.info(f"Current graph commit: {current_hash}")
207269

208-
# Find path from current commit to desired commit
270+
if current_hash == to:
271+
# No change remain at the current commit
272+
return change_set
273+
274+
# Find the path between the current commit and the desired commit
209275
commits = git_graph.get_commits([current_hash, to])
210276

277+
# Ensure both current and target commits are present
211278
if len(commits) != 2:
212-
print("missing commits")
213-
return
279+
logging.error("Missing commits. Unable to proceed.")
280+
raise ValueError("Commits not found")
214281

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]
282+
# Identify the current and new commits based on their hashes
283+
current_commit, new_commit = (commits if commits[0]['hash'] == current_hash else reversed(commits))
218284

285+
# Determine the direction of the switch (forward or backward in commit history)
219286
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'])
287+
logging.info(f"Moving backward from {current_commit['hash']} to {new_commit['hash']}")
288+
# Get the transitions (queries and parameters) for moving backward
289+
queries, params = git_graph.get_parent_transitions(current_commit['hash'], new_commit['hash'])
222290
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
291+
logging.info(f"Moving forward from {current_commit['hash']} to {new_commit['hash']}")
292+
# Get the transitions (queries and parameters) for moving forward
293+
queries, params = git_graph.get_child_transitions(current_commit['hash'], new_commit['hash'])
294+
295+
# Apply each transition query with its respective parameters
296+
for q, p in zip(queries, params):
297+
p = json.loads(p)
298+
logging.debug(f"Executing query: {q} with params: {p}")
299+
300+
# Rerun the query with parameters on the graph
301+
res = g.rerun_query(q, p)
302+
if "DELETE" in q:
303+
deleted_nodes = res.result_set[0][0]
304+
change_set['deletions']['nodes'] += deleted_nodes
305+
306+
# Update the graph's commit to the new target commit
236307
g.set_graph_commit(to)
308+
logging.info(f"Graph commit updated to {to}")
309+
310+
return change_set
237311

238312
if __name__ == "__main__":
239313
build_commit_graph("/Users/roilipman/Dev/FalkorDB")

code_graph/graph.py

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import time
12
from .entities import *
23
from typing import List, Optional
3-
from falkordb import FalkorDB, Node
4+
from falkordb import FalkorDB, Node, QueryResult
45

56
class Graph():
67
"""
@@ -11,6 +12,12 @@ def __init__(self, name: str, host: str = 'localhost', port: int = 6379,
1112
username: Optional[str] = None, password: Optional[str] = None) -> None:
1213
self.db = FalkorDB(host=host, port=port, username=username,
1314
password=password)
15+
16+
self.host = host
17+
self.port = port
18+
self.username = username
19+
self.password = password
20+
1421
self.g = self.db.select_graph(name)
1522

1623
# create indicies
@@ -27,6 +34,35 @@ def __init__(self, name: str, host: str = 'localhost', port: int = 6379,
2734
except Exception:
2835
pass
2936

37+
def clone(self, clone: str) -> "Graph":
38+
"""
39+
Create a copy of the graph under the name clone
40+
41+
Returns:
42+
a new instance of Graph
43+
"""
44+
45+
# Make sure key clone isn't already exists
46+
if self.db.connection.exists(clone):
47+
raise Exception(f"Can not create clone, key: {clone} already exists.")
48+
49+
self.g.copy(clone)
50+
51+
# Wait for the clone to become available
52+
while not self.db.connection.exists(clone):
53+
# TODO: add a waiting limit
54+
time.sleep(1)
55+
56+
return Graph(clone, self.host, self.port, self.username, self.password)
57+
58+
59+
def delete(self) -> None:
60+
"""
61+
Delete graph
62+
"""
63+
self.g.delete()
64+
65+
3066
def add_class(self, c: Class) -> None:
3167
"""
3268
Adds a class node to the graph database.
@@ -240,7 +276,7 @@ def add_file(self, file: File) -> None:
240276
res = self.g.query(q, params)
241277
file.id = res.result_set[0][0]
242278

243-
def delete_files(self, files: List[dict], log: bool = False) -> tuple[str, dict]:
279+
def delete_files(self, files: List[dict], log: bool = False) -> tuple[str, dict, List[int]]:
244280
"""
245281
Deletes file(s) from the graph in addition to any other entity
246282
defined in the file
@@ -249,14 +285,16 @@ def delete_files(self, files: List[dict], log: bool = False) -> tuple[str, dict]
249285
files = [{'path':_, 'name': _, 'ext': _}, ...]
250286
"""
251287

252-
q = """UNWIND $files as file
288+
q = """UNWIND $files AS file
253289
MATCH (f:File {path: file['path'], name: file['name'], ext: file['ext']})
254-
CALL {
255-
WITH f
256-
MATCH (f)-[:DEFINES]->(e)
257-
DELETE e
258-
}
259-
DELETE f
290+
WITH collect(f) AS Fs
291+
UNWIND Fs AS f
292+
MATCH (f)-[:DEFINES]->(e)
293+
WITH Fs, collect(e) AS Es
294+
WITH Fs + Es AS entities
295+
UNWIND entities AS e
296+
DELETE e
297+
RETURN collect(ID(e))
260298
"""
261299

262300
params = {'files': files}
@@ -440,9 +478,9 @@ def get_graph_commit(self) -> str:
440478
return self.db.connection.get('{' + self.g.name + '}' + '_commit')
441479

442480

443-
def rerun_query(self, q: str, params: dict) -> None:
481+
def rerun_query(self, q: str, params: dict) -> QueryResult:
444482
"""
445483
Re-run a query to transition the graph from one state to another
446484
"""
447485

448-
res = self.g.query(q, params)
486+
return self.g.query(q, params)

main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,11 +287,12 @@ def process_switch_commit():
287287
if commit is None:
288288
return jsonify({'status': f'Missing mandatory parameter "commit"'}), 400
289289

290-
switch_commit(repo, commit)
290+
change_set = switch_commit(repo, commit)
291291

292292
# Create a response
293293
response = {
294294
'status': 'success',
295+
'change_set': change_set
295296
}
296297

297298
return jsonify(response), 200

0 commit comments

Comments
 (0)