simple-vcs 1.0.0__py3-none-any.whl → 1.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
simple_vcs/__init__.py CHANGED
@@ -1,10 +1,10 @@
1
- """
2
- SimpleVCS - A simple version control system
3
- """
4
-
5
- __version__ = "1.0.0"
6
- __author__ = "Your Name"
7
-
8
- from .core import SimpleVCS
9
-
10
- __all__ = ["SimpleVCS"]
1
+ """
2
+ SimpleVCS - A simple version control system
3
+ """
4
+
5
+ __version__ = "1.1.0"
6
+ __author__ = "Muhammad Sufiyan Baig"
7
+
8
+ from .core import SimpleVCS
9
+
10
+ __all__ = ["SimpleVCS"]
simple_vcs/cli.py CHANGED
@@ -1,54 +1,81 @@
1
- import click
2
- from .core import SimpleVCS
3
-
4
- @click.group()
5
- @click.version_option()
6
- def main():
7
- """SimpleVCS - A simple version control system"""
8
- pass
9
-
10
- @main.command()
11
- @click.option('--path', default='.', help='Repository path')
12
- def init(path):
13
- """Initialize a new repository"""
14
- vcs = SimpleVCS(path)
15
- vcs.init_repo()
16
-
17
- @main.command()
18
- @click.argument('files', nargs=-1, required=True)
19
- def add(files):
20
- """Add files to staging area"""
21
- vcs = SimpleVCS()
22
- for file in files:
23
- vcs.add_file(file)
24
-
25
- @main.command()
26
- @click.option('-m', '--message', help='Commit message')
27
- def commit(message):
28
- """Commit staged changes"""
29
- vcs = SimpleVCS()
30
- vcs.commit(message)
31
-
32
- @main.command()
33
- @click.option('--c1', type=int, help='First commit ID')
34
- @click.option('--c2', type=int, help='Second commit ID')
35
- def diff(c1, c2):
36
- """Show differences between commits"""
37
- vcs = SimpleVCS()
38
- vcs.show_diff(c1, c2)
39
-
40
- @main.command()
41
- @click.option('--limit', type=int, help='Limit number of commits to show')
42
- def log(limit):
43
- """Show commit history"""
44
- vcs = SimpleVCS()
45
- vcs.show_log(limit)
46
-
47
- @main.command()
48
- def status():
49
- """Show repository status"""
50
- vcs = SimpleVCS()
51
- vcs.status()
52
-
53
- if __name__ == '__main__':
1
+ import click
2
+ from .core import SimpleVCS
3
+
4
+ @click.group()
5
+ @click.version_option()
6
+ def main():
7
+ """SimpleVCS - A simple version control system"""
8
+ pass
9
+
10
+ @main.command()
11
+ @click.option('--path', default='.', help='Repository path')
12
+ def init(path):
13
+ """Initialize a new repository"""
14
+ vcs = SimpleVCS(path)
15
+ vcs.init_repo()
16
+
17
+ @main.command()
18
+ @click.argument('files', nargs=-1, required=True)
19
+ def add(files):
20
+ """Add files to staging area"""
21
+ vcs = SimpleVCS()
22
+ for file in files:
23
+ vcs.add_file(file)
24
+
25
+ @main.command()
26
+ @click.option('-m', '--message', help='Commit message')
27
+ def commit(message):
28
+ """Commit staged changes"""
29
+ vcs = SimpleVCS()
30
+ vcs.commit(message)
31
+
32
+ @main.command()
33
+ @click.option('--c1', type=int, help='First commit ID')
34
+ @click.option('--c2', type=int, help='Second commit ID')
35
+ def diff(c1, c2):
36
+ """Show differences between commits"""
37
+ vcs = SimpleVCS()
38
+ vcs.show_diff(c1, c2)
39
+
40
+ @main.command()
41
+ @click.option('--limit', type=int, help='Limit number of commits to show')
42
+ def log(limit):
43
+ """Show commit history"""
44
+ vcs = SimpleVCS()
45
+ vcs.show_log(limit)
46
+
47
+ @main.command()
48
+ def status():
49
+ """Show repository status"""
50
+ vcs = SimpleVCS()
51
+ vcs.status()
52
+
53
+ @main.command()
54
+ @click.argument('commit_id', type=int)
55
+ def revert(commit_id):
56
+ """Quickly revert to a specific commit"""
57
+ vcs = SimpleVCS()
58
+ vcs.quick_revert(commit_id)
59
+
60
+ @main.command()
61
+ @click.option('--name', help='Name for the snapshot')
62
+ def snapshot(name):
63
+ """Create a compressed snapshot of the current repository state"""
64
+ vcs = SimpleVCS()
65
+ vcs.create_snapshot(name)
66
+
67
+ @main.command()
68
+ @click.argument('snapshot_path', type=click.Path(exists=True))
69
+ def restore(snapshot_path):
70
+ """Restore repository from a snapshot"""
71
+ vcs = SimpleVCS()
72
+ vcs.restore_from_snapshot(snapshot_path)
73
+
74
+ @main.command()
75
+ def compress():
76
+ """Compress stored objects to save space"""
77
+ vcs = SimpleVCS()
78
+ vcs.compress_objects()
79
+
80
+ if __name__ == '__main__':
54
81
  main()
simple_vcs/core.py CHANGED
@@ -1,276 +1,382 @@
1
- import os
2
- import json
3
- import hashlib
4
- import shutil
5
- import time
6
- from datetime import datetime
7
- from pathlib import Path
8
- from typing import Dict, List, Optional, Tuple
9
-
10
- class SimpleVCS:
11
- """Simple Version Control System core functionality"""
12
-
13
- def __init__(self, repo_path: str = "."):
14
- self.repo_path = Path(repo_path).resolve()
15
- self.svcs_dir = self.repo_path / ".svcs"
16
- self.objects_dir = self.svcs_dir / "objects"
17
- self.commits_file = self.svcs_dir / "commits.json"
18
- self.staging_file = self.svcs_dir / "staging.json"
19
- self.head_file = self.svcs_dir / "HEAD"
20
-
21
- def init_repo(self) -> bool:
22
- """Initialize a new repository"""
23
- if self.svcs_dir.exists():
24
- print(f"Repository already exists at {self.repo_path}")
25
- return False
26
-
27
- # Create directory structure
28
- self.svcs_dir.mkdir()
29
- self.objects_dir.mkdir()
30
-
31
- # Initialize files
32
- self._write_json(self.commits_file, [])
33
- self._write_json(self.staging_file, {})
34
- self.head_file.write_text("0") # Start with commit 0
35
-
36
- print(f"Initialized empty SimpleVCS repository at {self.repo_path}")
37
- return True
38
-
39
- def add_file(self, file_path: str) -> bool:
40
- """Add a file to staging area"""
41
- if not self._check_repo():
42
- return False
43
-
44
- file_path = Path(file_path).resolve() # Convert to absolute path
45
- if not file_path.exists():
46
- print(f"File {file_path} does not exist")
47
- return False
48
-
49
- if not file_path.is_file():
50
- print(f"{file_path} is not a file")
51
- return False
52
-
53
- # Check if file is within repository
54
- try:
55
- relative_path = file_path.relative_to(self.repo_path)
56
- except ValueError:
57
- print(f"File {file_path} is not within the repository")
58
- return False
59
-
60
- # Calculate file hash
61
- file_hash = self._calculate_file_hash(file_path)
62
-
63
- # Store file content in objects
64
- self._store_object(file_hash, file_path.read_bytes())
65
-
66
- # Add to staging
67
- staging = self._read_json(self.staging_file)
68
- staging[str(relative_path)] = {
69
- "hash": file_hash,
70
- "size": file_path.stat().st_size,
71
- "modified": file_path.stat().st_mtime
72
- }
73
- self._write_json(self.staging_file, staging)
74
-
75
- print(f"Added {file_path.name} to staging area")
76
- return True
77
-
78
- def commit(self, message: Optional[str] = None) -> bool:
79
- """Commit staged changes"""
80
- if not self._check_repo():
81
- return False
82
-
83
- staging = self._read_json(self.staging_file)
84
- if not staging:
85
- print("No changes to commit")
86
- return False
87
-
88
- # Create commit object
89
- commit = {
90
- "id": len(self._read_json(self.commits_file)) + 1,
91
- "message": message or f"Commit at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
92
- "timestamp": time.time(),
93
- "files": staging.copy(),
94
- "parent": self._get_current_commit_id()
95
- }
96
-
97
- # Save commit
98
- commits = self._read_json(self.commits_file)
99
- commits.append(commit)
100
- self._write_json(self.commits_file, commits)
101
-
102
- # Update HEAD
103
- self.head_file.write_text(str(commit["id"]))
104
-
105
- # Clear staging
106
- self._write_json(self.staging_file, {})
107
-
108
- print(f"Committed changes with ID: {commit['id']}")
109
- print(f"Message: {commit['message']}")
110
- return True
111
-
112
- def show_diff(self, commit_id1: Optional[int] = None, commit_id2: Optional[int] = None) -> bool:
113
- """Show differences between commits"""
114
- if not self._check_repo():
115
- return False
116
-
117
- commits = self._read_json(self.commits_file)
118
- if not commits:
119
- print("No commits found")
120
- return False
121
-
122
- # Default to comparing last two commits
123
- if commit_id1 is None and commit_id2 is None:
124
- if len(commits) < 2:
125
- print("Need at least 2 commits to show diff")
126
- return False
127
- commit1 = commits[-2]
128
- commit2 = commits[-1]
129
- else:
130
- commit1 = self._get_commit_by_id(commit_id1 or (len(commits) - 1))
131
- commit2 = self._get_commit_by_id(commit_id2 or len(commits))
132
-
133
- if not commit1 or not commit2:
134
- print("Invalid commit IDs")
135
- return False
136
-
137
- print(f"\nDifferences between commit {commit1['id']} and {commit2['id']}:")
138
- print("-" * 50)
139
-
140
- files1 = set(commit1["files"].keys())
141
- files2 = set(commit2["files"].keys())
142
-
143
- # New files
144
- new_files = files2 - files1
145
- if new_files:
146
- print("New files:")
147
- for file in new_files:
148
- print(f" + {file}")
149
-
150
- # Deleted files
151
- deleted_files = files1 - files2
152
- if deleted_files:
153
- print("Deleted files:")
154
- for file in deleted_files:
155
- print(f" - {file}")
156
-
157
- # Modified files
158
- common_files = files1 & files2
159
- modified_files = []
160
- for file in common_files:
161
- if commit1["files"][file]["hash"] != commit2["files"][file]["hash"]:
162
- modified_files.append(file)
163
-
164
- if modified_files:
165
- print("Modified files:")
166
- for file in modified_files:
167
- print(f" M {file}")
168
-
169
- if not new_files and not deleted_files and not modified_files:
170
- print("No differences found")
171
-
172
- return True
173
-
174
- def show_log(self, limit: Optional[int] = None) -> bool:
175
- """Show commit history"""
176
- if not self._check_repo():
177
- return False
178
-
179
- commits = self._read_json(self.commits_file)
180
- if not commits:
181
- print("No commits found")
182
- return False
183
-
184
- commits_to_show = commits[-limit:] if limit else commits
185
- commits_to_show.reverse() # Show newest first
186
-
187
- print("\nCommit History:")
188
- print("=" * 50)
189
-
190
- for commit in commits_to_show:
191
- print(f"Commit ID: {commit['id']}")
192
- print(f"Date: {datetime.fromtimestamp(commit['timestamp']).strftime('%Y-%m-%d %H:%M:%S')}")
193
- print(f"Message: {commit['message']}")
194
- print(f"Files: {len(commit['files'])} file(s)")
195
- if commit.get('parent'):
196
- print(f"Parent: {commit['parent']}")
197
- print("-" * 30)
198
-
199
- return True
200
-
201
- def status(self) -> bool:
202
- """Show repository status"""
203
- if not self._check_repo():
204
- return False
205
-
206
- staging = self._read_json(self.staging_file)
207
- current_commit = self._get_current_commit()
208
-
209
- print(f"\nRepository: {self.repo_path}")
210
- print(f"Current commit: {current_commit['id'] if current_commit else 'None'}")
211
-
212
- if staging:
213
- print("\nStaged files:")
214
- for file, info in staging.items():
215
- print(f" {file}")
216
- else:
217
- print("\nNo files staged")
218
-
219
- return True
220
-
221
- # Helper methods
222
- def _check_repo(self) -> bool:
223
- """Check if repository is initialized"""
224
- if not self.svcs_dir.exists():
225
- print("Not a SimpleVCS repository. Run 'svcs init' first.")
226
- return False
227
- return True
228
-
229
- def _calculate_file_hash(self, file_path: Path) -> str:
230
- """Calculate SHA-256 hash of file"""
231
- hasher = hashlib.sha256()
232
- with open(file_path, 'rb') as f:
233
- for chunk in iter(lambda: f.read(4096), b""):
234
- hasher.update(chunk)
235
- return hasher.hexdigest()
236
-
237
- def _store_object(self, obj_hash: str, content: bytes):
238
- """Store object in objects directory"""
239
- obj_path = self.objects_dir / obj_hash
240
- if not obj_path.exists():
241
- obj_path.write_bytes(content)
242
-
243
- def _read_json(self, file_path: Path) -> Dict:
244
- """Read JSON file"""
245
- if not file_path.exists():
246
- return {}
247
- return json.loads(file_path.read_text())
248
-
249
- def _write_json(self, file_path: Path, data: Dict):
250
- """Write JSON file"""
251
- file_path.write_text(json.dumps(data, indent=2))
252
-
253
- def _get_current_commit_id(self) -> Optional[int]:
254
- """Get current commit ID"""
255
- if not self.head_file.exists():
256
- return None
257
- try:
258
- commit_id = int(self.head_file.read_text().strip())
259
- return commit_id if commit_id > 0 else None
260
- except:
261
- return None
262
-
263
- def _get_current_commit(self) -> Optional[Dict]:
264
- """Get current commit object"""
265
- commit_id = self._get_current_commit_id()
266
- if not commit_id:
267
- return None
268
- return self._get_commit_by_id(commit_id)
269
-
270
- def _get_commit_by_id(self, commit_id: int) -> Optional[Dict]:
271
- """Get commit by ID"""
272
- commits = self._read_json(self.commits_file)
273
- for commit in commits:
274
- if commit["id"] == commit_id:
275
- return commit
276
- return None
1
+ import os
2
+ import json
3
+ import hashlib
4
+ import shutil
5
+ import time
6
+ from datetime import datetime
7
+ from pathlib import Path
8
+ from typing import Dict, List, Optional, Tuple
9
+ import zipfile
10
+
11
+ class SimpleVCS:
12
+ """Simple Version Control System core functionality"""
13
+
14
+ def __init__(self, repo_path: str = "."):
15
+ self.repo_path = Path(repo_path).resolve()
16
+ self.svcs_dir = self.repo_path / ".svcs"
17
+ self.objects_dir = self.svcs_dir / "objects"
18
+ self.commits_file = self.svcs_dir / "commits.json"
19
+ self.staging_file = self.svcs_dir / "staging.json"
20
+ self.head_file = self.svcs_dir / "HEAD"
21
+
22
+ def init_repo(self) -> bool:
23
+ """Initialize a new repository"""
24
+ if self.svcs_dir.exists():
25
+ print(f"Repository already exists at {self.repo_path}")
26
+ return False
27
+
28
+ # Create directory structure
29
+ self.svcs_dir.mkdir()
30
+ self.objects_dir.mkdir()
31
+
32
+ # Initialize files
33
+ self._write_json(self.commits_file, [])
34
+ self._write_json(self.staging_file, {})
35
+ self.head_file.write_text("0") # Start with commit 0
36
+
37
+ print(f"Initialized empty SimpleVCS repository at {self.repo_path}")
38
+ return True
39
+
40
+ def add_file(self, file_path: str) -> bool:
41
+ """Add a file to staging area"""
42
+ if not self._check_repo():
43
+ return False
44
+
45
+ file_path = Path(file_path).resolve() # Convert to absolute path
46
+ if not file_path.exists():
47
+ print(f"File {file_path} does not exist")
48
+ return False
49
+
50
+ if not file_path.is_file():
51
+ print(f"{file_path} is not a file")
52
+ return False
53
+
54
+ # Check if file is within repository
55
+ try:
56
+ relative_path = file_path.relative_to(self.repo_path)
57
+ except ValueError:
58
+ print(f"File {file_path} is not within the repository")
59
+ return False
60
+
61
+ # Calculate file hash
62
+ file_hash = self._calculate_file_hash(file_path)
63
+
64
+ # Store file content in objects
65
+ self._store_object(file_hash, file_path.read_bytes())
66
+
67
+ # Add to staging
68
+ staging = self._read_json(self.staging_file)
69
+ staging[str(relative_path)] = {
70
+ "hash": file_hash,
71
+ "size": file_path.stat().st_size,
72
+ "modified": file_path.stat().st_mtime
73
+ }
74
+ self._write_json(self.staging_file, staging)
75
+
76
+ print(f"Added {file_path.name} to staging area")
77
+ return True
78
+
79
+ def commit(self, message: Optional[str] = None) -> bool:
80
+ """Commit staged changes"""
81
+ if not self._check_repo():
82
+ return False
83
+
84
+ staging = self._read_json(self.staging_file)
85
+ if not staging:
86
+ print("No changes to commit")
87
+ return False
88
+
89
+ # Create commit object
90
+ commit = {
91
+ "id": len(self._read_json(self.commits_file)) + 1,
92
+ "message": message or f"Commit at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
93
+ "timestamp": time.time(),
94
+ "files": staging.copy(),
95
+ "parent": self._get_current_commit_id()
96
+ }
97
+
98
+ # Save commit
99
+ commits = self._read_json(self.commits_file)
100
+ commits.append(commit)
101
+ self._write_json(self.commits_file, commits)
102
+
103
+ # Update HEAD
104
+ self.head_file.write_text(str(commit["id"]))
105
+
106
+ # Clear staging
107
+ self._write_json(self.staging_file, {})
108
+
109
+ print(f"Committed changes with ID: {commit['id']}")
110
+ print(f"Message: {commit['message']}")
111
+ return True
112
+
113
+ def show_diff(self, commit_id1: Optional[int] = None, commit_id2: Optional[int] = None) -> bool:
114
+ """Show differences between commits"""
115
+ if not self._check_repo():
116
+ return False
117
+
118
+ commits = self._read_json(self.commits_file)
119
+ if not commits:
120
+ print("No commits found")
121
+ return False
122
+
123
+ # Default to comparing last two commits
124
+ if commit_id1 is None and commit_id2 is None:
125
+ if len(commits) < 2:
126
+ print("Need at least 2 commits to show diff")
127
+ return False
128
+ commit1 = commits[-2]
129
+ commit2 = commits[-1]
130
+ else:
131
+ commit1 = self._get_commit_by_id(commit_id1 or (len(commits) - 1))
132
+ commit2 = self._get_commit_by_id(commit_id2 or len(commits))
133
+
134
+ if not commit1 or not commit2:
135
+ print("Invalid commit IDs")
136
+ return False
137
+
138
+ print(f"\nDifferences between commit {commit1['id']} and {commit2['id']}:")
139
+ print("-" * 50)
140
+
141
+ files1 = set(commit1["files"].keys())
142
+ files2 = set(commit2["files"].keys())
143
+
144
+ # New files
145
+ new_files = files2 - files1
146
+ if new_files:
147
+ print("New files:")
148
+ for file in new_files:
149
+ print(f" + {file}")
150
+
151
+ # Deleted files
152
+ deleted_files = files1 - files2
153
+ if deleted_files:
154
+ print("Deleted files:")
155
+ for file in deleted_files:
156
+ print(f" - {file}")
157
+
158
+ # Modified files
159
+ common_files = files1 & files2
160
+ modified_files = []
161
+ for file in common_files:
162
+ if commit1["files"][file]["hash"] != commit2["files"][file]["hash"]:
163
+ modified_files.append(file)
164
+
165
+ if modified_files:
166
+ print("Modified files:")
167
+ for file in modified_files:
168
+ print(f" M {file}")
169
+
170
+ if not new_files and not deleted_files and not modified_files:
171
+ print("No differences found")
172
+
173
+ return True
174
+
175
+ def show_log(self, limit: Optional[int] = None) -> bool:
176
+ """Show commit history"""
177
+ if not self._check_repo():
178
+ return False
179
+
180
+ commits = self._read_json(self.commits_file)
181
+ if not commits:
182
+ print("No commits found")
183
+ return False
184
+
185
+ commits_to_show = commits[-limit:] if limit else commits
186
+ commits_to_show.reverse() # Show newest first
187
+
188
+ print("\nCommit History:")
189
+ print("=" * 50)
190
+
191
+ for commit in commits_to_show:
192
+ print(f"Commit ID: {commit['id']}")
193
+ print(f"Date: {datetime.fromtimestamp(commit['timestamp']).strftime('%Y-%m-%d %H:%M:%S')}")
194
+ print(f"Message: {commit['message']}")
195
+ print(f"Files: {len(commit['files'])} file(s)")
196
+ if commit.get('parent'):
197
+ print(f"Parent: {commit['parent']}")
198
+ print("-" * 30)
199
+
200
+ return True
201
+
202
+ def status(self) -> bool:
203
+ """Show repository status"""
204
+ if not self._check_repo():
205
+ return False
206
+
207
+ staging = self._read_json(self.staging_file)
208
+ current_commit = self._get_current_commit()
209
+
210
+ print(f"\nRepository: {self.repo_path}")
211
+ print(f"Current commit: {current_commit['id'] if current_commit else 'None'}")
212
+
213
+ if staging:
214
+ print("\nStaged files:")
215
+ for file, info in staging.items():
216
+ print(f" {file}")
217
+ else:
218
+ print("\nNo files staged")
219
+
220
+ return True
221
+
222
+ # Helper methods
223
+ def _check_repo(self) -> bool:
224
+ """Check if repository is initialized"""
225
+ if not self.svcs_dir.exists():
226
+ print("Not a SimpleVCS repository. Run 'svcs init' first.")
227
+ return False
228
+ return True
229
+
230
+ def _calculate_file_hash(self, file_path: Path) -> str:
231
+ """Calculate SHA-256 hash of file"""
232
+ hasher = hashlib.sha256()
233
+ with open(file_path, 'rb') as f:
234
+ for chunk in iter(lambda: f.read(4096), b""):
235
+ hasher.update(chunk)
236
+ return hasher.hexdigest()
237
+
238
+ def _store_object(self, obj_hash: str, content: bytes):
239
+ """Store object in objects directory"""
240
+ obj_path = self.objects_dir / obj_hash
241
+ if not obj_path.exists():
242
+ obj_path.write_bytes(content)
243
+
244
+ def _read_json(self, file_path: Path) -> Dict:
245
+ """Read JSON file"""
246
+ if not file_path.exists():
247
+ return {}
248
+ return json.loads(file_path.read_text())
249
+
250
+ def _write_json(self, file_path: Path, data: Dict):
251
+ """Write JSON file"""
252
+ file_path.write_text(json.dumps(data, indent=2))
253
+
254
+ def _get_current_commit_id(self) -> Optional[int]:
255
+ """Get current commit ID"""
256
+ if not self.head_file.exists():
257
+ return None
258
+ try:
259
+ commit_id = int(self.head_file.read_text().strip())
260
+ return commit_id if commit_id > 0 else None
261
+ except:
262
+ return None
263
+
264
+ def _get_current_commit(self) -> Optional[Dict]:
265
+ """Get current commit object"""
266
+ commit_id = self._get_current_commit_id()
267
+ if not commit_id:
268
+ return None
269
+ return self._get_commit_by_id(commit_id)
270
+
271
+ def _get_commit_by_id(self, commit_id: int) -> Optional[Dict]:
272
+ """Get commit by ID"""
273
+ commits = self._read_json(self.commits_file)
274
+ for commit in commits:
275
+ if commit["id"] == commit_id:
276
+ return commit
277
+ return None
278
+
279
+ def quick_revert(self, commit_id: int) -> bool:
280
+ """Quickly revert to a specific commit"""
281
+ if not self._check_repo():
282
+ return False
283
+
284
+ commit = self._get_commit_by_id(commit_id)
285
+ if not commit:
286
+ print(f"Commit {commit_id} not found")
287
+ return False
288
+
289
+ # Restore files from the specified commit
290
+ for file_path, file_info in commit["files"].items():
291
+ target_path = self.repo_path / file_path
292
+ obj_path = self.objects_dir / file_info["hash"]
293
+
294
+ # Create parent directories if they don't exist
295
+ target_path.parent.mkdir(parents=True, exist_ok=True)
296
+
297
+ # Copy file from objects to target location
298
+ if obj_path.exists():
299
+ shutil.copy2(obj_path, target_path)
300
+
301
+ # Update HEAD to point to the reverted commit
302
+ self.head_file.write_text(str(commit_id))
303
+
304
+ print(f"Reverted to commit {commit_id}: {commit['message']}")
305
+ return True
306
+
307
+ def create_snapshot(self, name: str = None) -> bool:
308
+ """Create a compressed snapshot of the current repository state"""
309
+ if not self._check_repo():
310
+ return False
311
+
312
+ snapshot_name = name or f"snapshot_{int(time.time())}"
313
+ snapshot_path = self.repo_path / f"{snapshot_name}.zip"
314
+
315
+ # Create a zip archive of all tracked files
316
+ with zipfile.ZipFile(snapshot_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
317
+ for root, dirs, files in os.walk(self.repo_path):
318
+ # Skip .svcs directory
319
+ dirs[:] = [d for d in dirs if d != '.svcs']
320
+
321
+ for file in files:
322
+ file_path = Path(root) / file
323
+ if file_path != snapshot_path: # Don't include the snapshot itself
324
+ arc_path = file_path.relative_to(self.repo_path)
325
+ zipf.write(file_path, arc_path)
326
+
327
+ print(f"Created snapshot: {snapshot_path}")
328
+ return True
329
+
330
+ def restore_from_snapshot(self, snapshot_path: str) -> bool:
331
+ """Restore repository from a snapshot"""
332
+ snapshot_path = Path(snapshot_path)
333
+ if not snapshot_path.exists():
334
+ print(f"Snapshot {snapshot_path} does not exist")
335
+ return False
336
+
337
+ # Extract the zip archive
338
+ with zipfile.ZipFile(snapshot_path, 'r') as zipf:
339
+ # Clear current files (but preserve .svcs directory)
340
+ for item in self.repo_path.iterdir():
341
+ if item.name != '.svcs':
342
+ if item.is_dir():
343
+ shutil.rmtree(item)
344
+ else:
345
+ item.unlink()
346
+
347
+ # Extract all files
348
+ zipf.extractall(self.repo_path)
349
+
350
+ print(f"Restored from snapshot: {snapshot_path}")
351
+ return True
352
+
353
+ def compress_objects(self) -> bool:
354
+ """Compress stored objects to save space"""
355
+ if not self._check_repo():
356
+ return False
357
+
358
+ original_size = sum(f.stat().st_size for f in self.objects_dir.glob('*') if f.is_file())
359
+
360
+ # For each object file, compress it if it's large enough to benefit
361
+ for obj_file in self.objects_dir.glob('*'):
362
+ if obj_file.is_file() and obj_file.stat().st_size > 1024: # Only compress files > 1KB
363
+ # Create a compressed version with .gz extension
364
+ compressed_path = obj_file.with_suffix(obj_file.suffix + '.gz')
365
+ with open(obj_file, 'rb') as f_in:
366
+ import gzip
367
+ with gzip.open(compressed_path, 'wb') as f_out:
368
+ f_out.writelines(f_in)
369
+
370
+ # Replace original with compressed version
371
+ obj_file.unlink()
372
+ # Decompress back to original name for compatibility
373
+ with gzip.open(compressed_path, 'rb') as f_in:
374
+ with open(obj_file, 'wb') as f_out:
375
+ f_out.write(f_in.read())
376
+ compressed_path.unlink()
377
+
378
+ new_size = sum(f.stat().st_size for f in self.objects_dir.glob('*') if f.is_file())
379
+ saved_space = original_size - new_size
380
+
381
+ print(f"Compression completed. Saved approximately {saved_space} bytes.")
382
+ return True
@@ -1,35 +1,44 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simple-vcs
3
- Version: 1.0.0
4
- Summary: A simple version control system written in Python
5
- Home-page: https://github.com/yourusername/simple-vcs
6
- Author: Your Name
7
- Author-email: your.email@example.com
8
- Classifier: Development Status :: 4 - Beta
9
- Classifier: Intended Audience :: Developers
10
- Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Operating System :: OS Independent
12
- Classifier: Programming Language :: Python :: 3
13
- Classifier: Programming Language :: Python :: 3.7
14
- Classifier: Programming Language :: Python :: 3.8
15
- Classifier: Programming Language :: Python :: 3.9
16
- Classifier: Programming Language :: Python :: 3.10
3
+ Version: 1.1.0
4
+ Summary: A simple version control system with unique features for easy version management
5
+ Home-page: https://github.com/muhammadsufiyanbaig/simple_vcs
6
+ Author: Muhammad Sufiyan Baig
7
+ Author-email: Muhammad Sufiyan Baig <send.sufiyan@gmail.com>
8
+ License: MIT License
9
+
10
+ Copyright (c) 2024 SimpleVCS
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
29
+ Project-URL: Homepage, https://github.com/muhammadsufiyanbaig/simple_vcs/
30
+ Project-URL: Repository, https://github.com/muhammadsufiyanbaig/simple_vcs.git
31
+ Project-URL: Issues, https://github.com/muhammadsufiyanbaig/simple_vcs/issues
32
+ Keywords: version-control,vcs,simple-vcs,backup,snapshot
17
33
  Requires-Python: >=3.7
18
34
  Description-Content-Type: text/markdown
19
35
  License-File: LICENSE
20
36
  Requires-Dist: click>=7.0
21
37
  Dynamic: author
22
- Dynamic: author-email
23
- Dynamic: classifier
24
- Dynamic: description
25
- Dynamic: description-content-type
26
38
  Dynamic: home-page
27
39
  Dynamic: license-file
28
- Dynamic: requires-dist
29
40
  Dynamic: requires-python
30
- Dynamic: summary
31
41
 
32
- # README.md
33
42
  # SimpleVCS
34
43
 
35
44
  A simple version control system written in Python that provides basic VCS functionality similar to Git.
@@ -44,6 +53,10 @@ A simple version control system written in Python that provides basic VCS functi
44
53
  - Repository status tracking
45
54
  - Cross-platform compatibility
46
55
  - Both CLI and Python API support
56
+ - Quick revert to any previous commit
57
+ - Create and restore from snapshots
58
+ - Automatic object compression to save space
59
+ - Simplified workflow compared to Git
47
60
 
48
61
  ## Installation
49
62
 
@@ -56,7 +69,7 @@ pip install simple-vcs
56
69
  ```bash
57
70
  # Clone the repository
58
71
  git clone https://github.com/muhammadsufiyanbaig/simple_vcs.git
59
- cd simple-vcs
72
+ cd simple_vcs
60
73
 
61
74
  # Install in development mode
62
75
  pip install -e .
@@ -136,6 +149,24 @@ svcs diff --c1 1 --c2 3
136
149
  svcs diff --c1 2
137
150
  ```
138
151
 
152
+ #### Advanced Operations
153
+ ```bash
154
+ # Quickly revert to a specific commit
155
+ svcs revert 3
156
+
157
+ # Create a snapshot of current state
158
+ svcs snapshot
159
+
160
+ # Create a named snapshot
161
+ svcs snapshot --name my_backup
162
+
163
+ # Restore from a snapshot
164
+ svcs restore path/to/snapshot.zip
165
+
166
+ # Compress stored objects to save space
167
+ svcs compress
168
+ ```
169
+
139
170
  ### Python API
140
171
 
141
172
  ```python
@@ -162,6 +193,19 @@ vcs.show_diff(1, 2)
162
193
 
163
194
  # Check repository status
164
195
  vcs.status()
196
+
197
+ # Quick revert to a specific commit
198
+ vcs.quick_revert(2)
199
+
200
+ # Create a snapshot of current state
201
+ vcs.create_snapshot()
202
+ vcs.create_snapshot("my_backup")
203
+
204
+ # Restore from a snapshot
205
+ vcs.restore_from_snapshot("my_backup.zip")
206
+
207
+ # Compress stored objects to save space
208
+ vcs.compress_objects()
165
209
  ```
166
210
 
167
211
  ## Advanced Usage
@@ -202,8 +246,8 @@ When initialized, SimpleVCS creates a `.svcs` directory containing:
202
246
  ### Setting up Development Environment
203
247
  ```bash
204
248
  # Clone the repository
205
- git clone https://github.com/yourusername/simple-vcs.git
206
- cd simple-vcs
249
+ git clone https://github.com/muhammadsufiyanbaig/simple_vcs.git
250
+ cd simple_vcs
207
251
 
208
252
  # Create virtual environment
209
253
  python -m venv venv
@@ -267,10 +311,20 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
267
311
 
268
312
  ## Author
269
313
 
270
- Your Name - your.email@example.com
314
+ Muhammad Sufiyan Baig - send.sufiyan@gmail.com
315
+
316
+ Project Link: [https://github.com/muhammadsufiyanbaig/simple_vcs](https://github.com/muhammadsufiyanbaig/simple_vcs)
271
317
 
272
318
  ## Changelog
273
319
 
320
+ ### Version 1.1.0
321
+ - Added quick revert functionality to go back to any commit instantly
322
+ - Added snapshot creation and restoration features
323
+ - Added automatic object compression to save disk space
324
+ - Improved CLI with new commands (revert, snapshot, restore, compress)
325
+ - Enhanced documentation and examples
326
+ - Fixed CLI entry point issue for direct terminal usage
327
+
274
328
  ### Version 1.0.0
275
329
  - Initial release
276
330
  - Basic VCS functionality (init, add, commit, log, diff, status)
@@ -0,0 +1,10 @@
1
+ simple_vcs/__init__.py,sha256=RcYfgE1z2am5TUREGRVniDWErxGOW8dHH3hmt13bebE,166
2
+ simple_vcs/cli.py,sha256=ealBu5Q5XpeguLRnXY7OTqKuJXKFQKI5dn0dHynLGA4,2004
3
+ simple_vcs/core.py,sha256=I83hj6IxJE4q5nyXcq6kAxM8Y8MzpB_maJGEBoXOuVk,13779
4
+ simple_vcs/utils.py,sha256=CTd4gDdHqP-dawjtEeKU3csnT-Fe7_SN9a5ENQlo7wk,1202
5
+ simple_vcs-1.1.0.dist-info/licenses/LICENSE,sha256=6o_m1QgCywYf-QZnE6cuLTwu5kVVQn3vJ7JJUd0V_iY,1085
6
+ simple_vcs-1.1.0.dist-info/METADATA,sha256=QRbAb_AyBiC7LRfP3S1N1rej9_EB5dm-UJwnO6LgzAM,8231
7
+ simple_vcs-1.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
8
+ simple_vcs-1.1.0.dist-info/entry_points.txt,sha256=19JeWUvRFzwKF5p_iLQiSwCV3XTgxB7mkTLmFGrc_aY,45
9
+ simple_vcs-1.1.0.dist-info/top_level.txt,sha256=YcaiuqQjjXFL-H62tfC-hTcg-7sWFmLh65zghskauL4,11
10
+ simple_vcs-1.1.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,10 +0,0 @@
1
- simple_vcs/__init__.py,sha256=ic-WO0wttRy6ILvZL1GdR0icHUmNNSoL6fvPjKG00tE,164
2
- simple_vcs/cli.py,sha256=YZCzSozCNNZGwVdER121cPhpLhJ6miCiSOiqYcfz664,1314
3
- simple_vcs/core.py,sha256=q4FF7s6y0PRU7cMfq9E32wMsR-jrHBo_8m5qj5heRnM,9771
4
- simple_vcs/utils.py,sha256=CTd4gDdHqP-dawjtEeKU3csnT-Fe7_SN9a5ENQlo7wk,1202
5
- simple_vcs-1.0.0.dist-info/licenses/LICENSE,sha256=6o_m1QgCywYf-QZnE6cuLTwu5kVVQn3vJ7JJUd0V_iY,1085
6
- simple_vcs-1.0.0.dist-info/METADATA,sha256=5tmnxoOajcwq4kXaAbunr1nGrXfjoPRn5V_sZnCN8Ws,5908
7
- simple_vcs-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
- simple_vcs-1.0.0.dist-info/entry_points.txt,sha256=19JeWUvRFzwKF5p_iLQiSwCV3XTgxB7mkTLmFGrc_aY,45
9
- simple_vcs-1.0.0.dist-info/top_level.txt,sha256=YcaiuqQjjXFL-H62tfC-hTcg-7sWFmLh65zghskauL4,11
10
- simple_vcs-1.0.0.dist-info/RECORD,,