1+ """
2+ Core versioning functionality for ModelSync
3+ """
4+
15import os
6+ import json
7+ import shutil
28from datetime import datetime
9+ from pathlib import Path
10+ from typing import Dict , List , Optional , Any
11+ from modelsync .config import *
12+ from modelsync .utils .helpers import *
13+
14+ class ModelSyncRepo :
15+ """Main repository class for ModelSync"""
16+
17+ def __init__ (self , path : str = "." ):
18+ self .path = Path (path ).resolve ()
19+ self .modelsync_dir = self .path / MODELSYNC_DIR
20+
21+ def is_initialized (self ) -> bool :
22+ """Check if repository is initialized"""
23+ return self .modelsync_dir .exists () and (self .modelsync_dir / "config" ).exists ()
24+
25+ def init (self , user_name : str = "" , user_email : str = "" ) -> bool :
26+ """Initialize a new ModelSync repository"""
27+ if self .is_initialized ():
28+ print ("ModelSync repository already initialized." )
29+ return False
30+
31+ try :
32+ # Create directory structure
33+ for dir_path in [OBJECTS_DIR , REFS_DIR , HEADS_DIR , METADATA_DIR , LOGS_DIR ]:
34+ ensure_directory (str (self .path / dir_path ))
35+
36+ # Create initial files
37+ self ._create_initial_files (user_name , user_email )
38+
39+ # Create initial commit
40+ self ._create_initial_commit ()
41+
42+ print ("✅ ModelSync repository initialized successfully!" )
43+ return True
44+
45+ except Exception as e :
46+ print (f"❌ Error initializing repository: { e } " )
47+ return False
48+
49+ def _create_initial_files (self , user_name : str , user_email : str ) -> None :
50+ """Create initial repository files"""
51+ config = DEFAULT_CONFIG .copy ()
52+ if user_name :
53+ config ["user" ]["name" ] = user_name
54+ if user_email :
55+ config ["user" ]["email" ] = user_email
56+
57+ write_json_file (str (self .path / CONFIG_FILE ), config )
58+
59+ # Create HEAD file pointing to main branch
60+ with open (self .path / HEAD_FILE , 'w' ) as f :
61+ f .write ("ref: refs/heads/main" )
62+
63+ # Create empty index
64+ write_json_file (str (self .path / INDEX_FILE ), {"files" : {}})
65+
66+ # Create logs directory and initial log
67+ log_entry = {
68+ "timestamp" : datetime .now ().isoformat (),
69+ "action" : "init" ,
70+ "message" : "Repository initialized" ,
71+ "user" : user_name or "Unknown"
72+ }
73+ self ._write_log_entry (log_entry )
74+
75+ def _create_initial_commit (self ) -> None :
76+ """Create initial empty commit"""
77+ commit_data = {
78+ "tree" : "" ,
79+ "parent" : None ,
80+ "author" : {
81+ "name" : "ModelSync" ,
82+ "email" : "modelsync@local" ,
83+ "timestamp" : datetime .now ().isoformat ()
84+ },
85+ "committer" : {
86+ "name" : "ModelSync" ,
87+ "email" : "modelsync@local" ,
88+ "timestamp" : datetime .now ().isoformat ()
89+ },
90+ "message" : "Initial commit" ,
91+ "hash" : ""
92+ }
93+
94+ commit_hash = calculate_content_hash (json .dumps (commit_data , sort_keys = True ).encode ())
95+ commit_data ["hash" ] = commit_hash
96+
97+ # Save commit object
98+ commit_path = self .path / OBJECTS_DIR / commit_hash [:2 ] / commit_hash [2 :]
99+ ensure_directory (str (commit_path .parent ))
100+ write_json_file (str (commit_path ), commit_data )
101+
102+ # Update HEAD to point to this commit
103+ with open (self .path / HEADS_DIR / "main" , 'w' ) as f :
104+ f .write (commit_hash )
105+
106+ def add (self , file_paths : List [str ]) -> Dict [str , Any ]:
107+ """Add files to staging area"""
108+ if not self .is_initialized ():
109+ print ("❌ Not a ModelSync repository. Run 'modelsync init' first." )
110+ return {}
111+
112+ index = read_json_file (str (self .path / INDEX_FILE ))
113+ added_files = {}
114+
115+ for file_path in file_paths :
116+ full_path = self .path / file_path
117+ if not full_path .exists ():
118+ print (f"⚠️ File not found: { file_path } " )
119+ continue
120+
121+ file_info = get_file_info (str (full_path ))
122+ if file_info :
123+ index ["files" ][file_path ] = file_info
124+ added_files [file_path ] = file_info
125+ print (f"✅ Added: { file_path } " )
126+
127+ write_json_file (str (self .path / INDEX_FILE ), index )
128+ return added_files
129+
130+ def commit (self , message : str , author_name : str = "" , author_email : str = "" ) -> Optional [str ]:
131+ """Create a new commit"""
132+ if not self .is_initialized ():
133+ print ("❌ Not a ModelSync repository. Run 'modelsync init' first." )
134+ return None
135+
136+ # Get current branch
137+ current_branch = self ._get_current_branch ()
138+ if not current_branch :
139+ print ("❌ No current branch found." )
140+ return None
141+
142+ # Get staged files
143+ index = read_json_file (str (self .path / INDEX_FILE ))
144+ if not index .get ("files" ):
145+ print ("❌ No files staged for commit. Use 'modelsync add' first." )
146+ return None
147+
148+ # Create tree object
149+ tree_hash = self ._create_tree_object (index ["files" ])
150+
151+ # Get parent commit
152+ parent_hash = self ._get_branch_head (current_branch )
153+
154+ # Create commit
155+ config = get_config ()
156+ commit_data = {
157+ "tree" : tree_hash ,
158+ "parent" : parent_hash ,
159+ "author" : {
160+ "name" : author_name or config ["user" ]["name" ] or "Unknown" ,
161+ "email" : author_email or config ["user" ]["email" ] or "unknown@local" ,
162+ "timestamp" : datetime .now ().isoformat ()
163+ },
164+ "committer" : {
165+ "name" : author_name or config ["user" ]["name" ] or "Unknown" ,
166+ "email" : author_email or config ["user" ]["email" ] or "unknown@local" ,
167+ "timestamp" : datetime .now ().isoformat ()
168+ },
169+ "message" : message ,
170+ "hash" : ""
171+ }
172+
173+ commit_hash = calculate_content_hash (json .dumps (commit_data , sort_keys = True ).encode ())
174+ commit_data ["hash" ] = commit_hash
175+
176+ # Save commit object
177+ commit_path = self .path / OBJECTS_DIR / commit_hash [:2 ] / commit_hash [2 :]
178+ ensure_directory (str (commit_path .parent ))
179+ write_json_file (str (commit_path ), commit_data )
180+
181+ # Update branch head
182+ with open (self .path / HEADS_DIR / current_branch , 'w' ) as f :
183+ f .write (commit_hash )
184+
185+ # Clear staging area
186+ index ["files" ] = {}
187+ write_json_file (str (self .path / INDEX_FILE ), index )
188+
189+ # Log commit
190+ log_entry = {
191+ "timestamp" : datetime .now ().isoformat (),
192+ "action" : "commit" ,
193+ "message" : message ,
194+ "commit_hash" : commit_hash ,
195+ "files_count" : len (index ["files" ]),
196+ "user" : author_name or config ["user" ]["name" ] or "Unknown"
197+ }
198+ self ._write_log_entry (log_entry )
199+
200+ print (f"✅ Commit created: { commit_hash [:8 ]} " )
201+ print (f"📝 Message: { message } " )
202+ return commit_hash
203+
204+ def status (self ) -> Dict [str , Any ]:
205+ """Show repository status"""
206+ if not self .is_initialized ():
207+ return {"error" : "Not a ModelSync repository" }
208+
209+ # Get tracked files
210+ tracked_files = get_tracked_files (str (self .path ))
211+
212+ # Get staged files
213+ index = read_json_file (str (self .path / INDEX_FILE ))
214+ staged_files = index .get ("files" , {})
215+
216+ # Find modified files
217+ modified_files = []
218+ for file_path in tracked_files :
219+ file_info = get_file_info (file_path )
220+ if file_path in staged_files :
221+ if file_info ["hash" ] != staged_files [file_path ]["hash" ]:
222+ modified_files .append (file_path )
223+ else :
224+ modified_files .append (file_path )
225+
226+ return {
227+ "branch" : self ._get_current_branch (),
228+ "staged_files" : list (staged_files .keys ()),
229+ "modified_files" : modified_files ,
230+ "total_tracked" : len (tracked_files ),
231+ "total_staged" : len (staged_files )
232+ }
233+
234+ def log (self , oneline : bool = False ) -> List [Dict [str , Any ]]:
235+ """Show commit history"""
236+ if not self .is_initialized ():
237+ return []
238+
239+ current_branch = self ._get_current_branch ()
240+ if not current_branch :
241+ return []
242+
243+ commits = []
244+ commit_hash = self ._get_branch_head (current_branch )
245+
246+ while commit_hash :
247+ commit_path = self .path / OBJECTS_DIR / commit_hash [:2 ] / commit_hash [2 :]
248+ if commit_path .exists ():
249+ commit_data = read_json_file (str (commit_path ))
250+ commits .append (commit_data )
251+ commit_hash = commit_data .get ("parent" )
252+ else :
253+ break
254+
255+ return commits
256+
257+ def _create_tree_object (self , files : Dict [str , Any ]) -> str :
258+ """Create tree object from files"""
259+ tree_data = {"files" : files }
260+ tree_content = json .dumps (tree_data , sort_keys = True )
261+ tree_hash = calculate_content_hash (tree_content .encode ())
262+
263+ tree_path = self .path / OBJECTS_DIR / tree_hash [:2 ] / tree_hash [2 :]
264+ ensure_directory (str (tree_path .parent ))
265+ write_json_file (str (tree_path ), tree_data )
266+
267+ return tree_hash
268+
269+ def _get_current_branch (self ) -> Optional [str ]:
270+ """Get current branch name"""
271+ try :
272+ with open (self .path / HEAD_FILE , 'r' ) as f :
273+ head_content = f .read ().strip ()
274+ if head_content .startswith ("ref: refs/heads/" ):
275+ return head_content .split ("/" )[- 1 ]
276+ except FileNotFoundError :
277+ pass
278+ return None
279+
280+ def _get_branch_head (self , branch : str ) -> Optional [str ]:
281+ """Get commit hash for branch head"""
282+ try :
283+ with open (self .path / HEADS_DIR / branch , 'r' ) as f :
284+ return f .read ().strip ()
285+ except FileNotFoundError :
286+ return None
287+
288+ def _write_log_entry (self , entry : Dict [str , Any ]) -> None :
289+ """Write entry to history log"""
290+ log_path = self .path / HISTORY_FILE
291+ ensure_directory (str (log_path .parent ))
292+
293+ with open (log_path , 'a' , encoding = 'utf-8' ) as f :
294+ f .write (json .dumps (entry ) + "\n " )
3295
4- def init_repo ():
5- os . makedirs ( ".modelsync " , exist_ok = True )
6- with open ( ".modelsync/history.log" , "a" ) as f :
7- f . write ( f"[ { datetime . now () } ] Repositório ModelSync iniciado. \n " )
8- print ( "Repositório ModelSync inicializado." )
296+ # Convenience functions for CLI
297+ def init_repo ( user_name : str = " " , user_email : str = "" ):
298+ """Initialize repository - CLI wrapper"""
299+ repo = ModelSyncRepo ( )
300+ repo . init ( user_name , user_email )
9301
10- def commit_changes (message : str ):
11- with open ( ".modelsync/history.log" , "a" ) as f :
12- f . write ( f"[ { datetime . now () } ] Commit: { message } \n " )
13- print ( f"Commit salvo: { message } " )
302+ def commit_changes (message : str , author_name : str = "" , author_email : str = "" ):
303+ """Commit changes - CLI wrapper"""
304+ repo = ModelSyncRepo ( )
305+ repo . commit ( message , author_name , author_email )
0 commit comments