git-auto-pro 1.0.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.
@@ -0,0 +1,400 @@
1
+
2
+ """Interactive .gitignore file manager."""
3
+
4
+ from pathlib import Path
5
+ from typing import List, Set
6
+ import questionary
7
+ from rich.console import Console
8
+ from rich.table import Table
9
+ from rich.tree import Tree
10
+
11
+ console = Console()
12
+
13
+
14
+ def get_all_files(directory: Path = Path("."), max_depth: int = 3) -> List[Path]:
15
+ """Get all files in directory recursively."""
16
+ all_files = []
17
+
18
+ def scan_dir(path: Path, depth: int = 0):
19
+ if depth > max_depth:
20
+ return
21
+
22
+ try:
23
+ for item in path.iterdir():
24
+ # Skip .git directory
25
+ if item.name == ".git":
26
+ continue
27
+
28
+ if item.is_file():
29
+ all_files.append(item.relative_to(directory))
30
+ elif item.is_dir():
31
+ scan_dir(item, depth + 1)
32
+ except PermissionError:
33
+ pass
34
+
35
+ scan_dir(directory)
36
+ return sorted(all_files)
37
+
38
+
39
+ def load_gitignore() -> Set[str]:
40
+ """Load existing .gitignore patterns."""
41
+ gitignore_file = Path(".gitignore")
42
+ if not gitignore_file.exists():
43
+ return set()
44
+
45
+ patterns = set()
46
+ with open(gitignore_file, "r") as f:
47
+ for line in f:
48
+ line = line.strip()
49
+ if line and not line.startswith("#"):
50
+ patterns.add(line)
51
+
52
+ return patterns
53
+
54
+
55
+ def save_gitignore(patterns: Set[str]) -> None:
56
+ """Save .gitignore patterns."""
57
+ gitignore_file = Path(".gitignore")
58
+
59
+ # Read existing content to preserve comments
60
+ existing_content = ""
61
+ if gitignore_file.exists():
62
+ with open(gitignore_file, "r") as f:
63
+ existing_content = f.read()
64
+
65
+ # Write new patterns
66
+ with open(gitignore_file, "w") as f:
67
+ # Keep header comments if they exist
68
+ if existing_content and existing_content.startswith("#"):
69
+ lines = existing_content.split("\n")
70
+ for line in lines:
71
+ if line.startswith("#"):
72
+ f.write(line + "\n")
73
+ else:
74
+ break
75
+ f.write("\n")
76
+
77
+ # Write patterns
78
+ for pattern in sorted(patterns):
79
+ f.write(pattern + "\n")
80
+
81
+ console.print(f"[green]✓ Saved {len(patterns)} patterns to .gitignore[/green]")
82
+
83
+
84
+ def display_file_tree(files: List[Path], ignored: Set[str]) -> None:
85
+ """Display files in a tree structure."""
86
+ tree = Tree("📁 Project Files")
87
+
88
+ # Group by directory
89
+ dirs = {}
90
+ for file in files[:50]: # Limit to first 50 for display
91
+ parts = file.parts
92
+ if len(parts) == 1:
93
+
94
+ status = "🚫" if should_ignore(file, ignored) else "✅"
95
+ tree.add(f"{status} {file}")
96
+ else:
97
+ # File in directory
98
+ dir_name = parts[0]
99
+ if dir_name not in dirs:
100
+ dirs[dir_name] = tree.add(f"📂 {dir_name}/")
101
+
102
+ status = "🚫" if should_ignore(file, ignored) else "✅"
103
+ dirs[dir_name].add(f"{status} {'/'.join(parts[1:])}")
104
+
105
+ console.print(tree)
106
+
107
+
108
+ def should_ignore(file: Path, patterns: Set[str]) -> bool:
109
+ """Check if file matches any ignore pattern."""
110
+ file_str = str(file)
111
+
112
+ for pattern in patterns:
113
+ # Simple pattern matching
114
+ if pattern.endswith("/"):
115
+ # Directory pattern
116
+ if file_str.startswith(pattern.rstrip("/")):
117
+ return True
118
+ elif pattern.startswith("*"):
119
+ # Extension pattern
120
+ if file_str.endswith(pattern[1:]):
121
+ return True
122
+ elif pattern in file_str:
123
+
124
+ return True
125
+
126
+ return False
127
+
128
+
129
+ def interactive_gitignore_manager() -> None:
130
+ """Interactive .gitignore file manager."""
131
+ console.print("[bold cyan]📝 Interactive .gitignore Manager[/bold cyan]\n")
132
+
133
+ # Load existing patterns
134
+ ignored_patterns = load_gitignore()
135
+ console.print(f"Loaded {len(ignored_patterns)} existing patterns\n")
136
+
137
+ while True:
138
+ choice = questionary.select(
139
+ "What would you like to do?",
140
+ choices=[
141
+ "📋 View all files (with ignore status)",
142
+ "➕ Add files/patterns to ignore",
143
+ "➖ Remove patterns from ignore",
144
+ "🎯 Browse and select files to ignore",
145
+ "📊 Show current .gitignore",
146
+ "🧹 Clean: Remove ignored files from git",
147
+ "💾 Save and exit",
148
+ "❌ Exit without saving",
149
+ ]
150
+ ).ask()
151
+
152
+ if not choice:
153
+ break
154
+
155
+ if "View all files" in choice:
156
+ view_files_with_status(ignored_patterns)
157
+
158
+ elif "Add files/patterns" in choice:
159
+ add_patterns(ignored_patterns)
160
+
161
+ elif "Remove patterns" in choice:
162
+ remove_patterns(ignored_patterns)
163
+
164
+ elif "Browse and select" in choice:
165
+ browse_and_select(ignored_patterns)
166
+
167
+ elif "Show current" in choice:
168
+ show_current_gitignore(ignored_patterns)
169
+
170
+ elif "Clean: Remove" in choice:
171
+ clean_ignored_files()
172
+
173
+ elif "Save and exit" in choice:
174
+ save_gitignore(ignored_patterns)
175
+ console.print("[bold green]✓ Changes saved![/bold green]")
176
+ break
177
+
178
+ elif "Exit without saving" in choice:
179
+ confirm = questionary.confirm("Exit without saving changes?").ask()
180
+ if confirm:
181
+ console.print("[yellow]Changes discarded[/yellow]")
182
+ break
183
+
184
+
185
+ def view_files_with_status(ignored: Set[str]) -> None:
186
+ """View all files with their ignore status."""
187
+ console.print("\n[cyan]📁 Project Files:[/cyan]\n")
188
+
189
+ files = get_all_files()
190
+
191
+ if not files:
192
+ console.print("[yellow]No files found[/yellow]")
193
+ return
194
+
195
+ table = Table(show_header=True)
196
+ table.add_column("Status", style="cyan", width=8)
197
+ table.add_column("File", style="white")
198
+ table.add_column("Reason", style="dim")
199
+
200
+ for file in files[:100]: # Limit to 100 files
201
+ if should_ignore(file, ignored):
202
+ status = "🚫 Ignored"
203
+ reason = get_ignore_reason(file, ignored)
204
+ else:
205
+ status = "✅ Tracked"
206
+ reason = ""
207
+
208
+ table.add_row(status, str(file), reason)
209
+
210
+ console.print(table)
211
+
212
+ if len(files) > 100:
213
+ console.print(f"\n[dim]... and {len(files) - 100} more files[/dim]")
214
+
215
+ input("\nPress Enter to continue...")
216
+
217
+
218
+ def get_ignore_reason(file: Path, patterns: Set[str]) -> str:
219
+ """Get the pattern that causes file to be ignored."""
220
+ file_str = str(file)
221
+
222
+ for pattern in patterns:
223
+ if pattern.endswith("/") and file_str.startswith(pattern.rstrip("/")):
224
+ return f"matches: {pattern}"
225
+ elif pattern.startswith("*") and file_str.endswith(pattern[1:]):
226
+ return f"matches: {pattern}"
227
+ elif pattern in file_str:
228
+ return f"matches: {pattern}"
229
+
230
+ return ""
231
+
232
+
233
+ def add_patterns(ignored: Set[str]) -> None:
234
+ """Add new patterns to ignore."""
235
+ console.print("\n[cyan]➕ Add Patterns to .gitignore[/cyan]\n")
236
+
237
+ pattern_type = questionary.select(
238
+ "What would you like to add?",
239
+ choices=[
240
+ "📁 Folder (e.g., node_modules/)",
241
+ "📄 File extension (e.g., *.pyc)",
242
+ "📝 Specific file (e.g., config.local.py)",
243
+ "🎯 Custom pattern",
244
+ "📦 Common presets (Python, Node, etc.)",
245
+ ]
246
+ ).ask()
247
+
248
+ if "Folder" in pattern_type:
249
+ folder = questionary.text("Folder name (will add trailing /):").ask()
250
+ if folder:
251
+ pattern = folder if folder.endswith("/") else folder + "/"
252
+ ignored.add(pattern)
253
+ console.print(f"[green]✓ Added: {pattern}[/green]")
254
+
255
+ elif "File extension" in pattern_type:
256
+ ext = questionary.text("Extension (e.g., pyc, log):").ask()
257
+ if ext:
258
+ pattern = f"*.{ext}" if not ext.startswith("*.") else ext
259
+ ignored.add(pattern)
260
+ console.print(f"[green]✓ Added: {pattern}[/green]")
261
+
262
+ elif "Specific file" in pattern_type:
263
+ filename = questionary.text("File name or path:").ask()
264
+ if filename:
265
+ ignored.add(filename)
266
+ console.print(f"[green]✓ Added: {filename}[/green]")
267
+
268
+ elif "Custom pattern" in pattern_type:
269
+ pattern = questionary.text("Enter pattern:").ask()
270
+ if pattern:
271
+ ignored.add(pattern)
272
+ console.print(f"[green]✓ Added: {pattern}[/green]")
273
+
274
+ elif "Common presets" in pattern_type:
275
+ add_preset(ignored)
276
+
277
+
278
+ def add_preset(ignored: Set[str]) -> None:
279
+ """Add common preset patterns."""
280
+ presets = {
281
+ "Python": ["__pycache__/", "*.py[cod]", "*.so", ".Python", "*.egg-info/",
282
+ "dist/", "build/", ".pytest_cache/", ".coverage", "venv/", "env/"],
283
+ "Node.js": ["node_modules/", "npm-debug.log", "*.tsbuildinfo", "dist/", ".env"],
284
+ "Build artifacts": ["*.o", "*.a", "*.lib", "*.dll", "*.exe", "*.out"],
285
+ "IDEs": [".vscode/", ".idea/", "*.swp", "*.swo", ".DS_Store"],
286
+ "Logs": ["*.log", "logs/"],
287
+ }
288
+
289
+ preset = questionary.select(
290
+ "Select preset:",
291
+ choices=list(presets.keys())
292
+ ).ask()
293
+
294
+ if preset:
295
+ patterns = presets[preset]
296
+ for pattern in patterns:
297
+ ignored.add(pattern)
298
+ console.print(f"[green]✓ Added {len(patterns)} patterns from {preset} preset[/green]")
299
+
300
+
301
+ def remove_patterns(ignored: Set[str]) -> None:
302
+ """Remove patterns from ignore list."""
303
+ if not ignored:
304
+ console.print("[yellow]No patterns to remove[/yellow]")
305
+ return
306
+
307
+ patterns_to_remove = questionary.checkbox(
308
+ "Select patterns to remove:",
309
+ choices=sorted(ignored)
310
+ ).ask()
311
+
312
+ if patterns_to_remove:
313
+ for pattern in patterns_to_remove:
314
+ ignored.discard(pattern)
315
+ console.print(f"[green]✓ Removed {len(patterns_to_remove)} patterns[/green]")
316
+
317
+
318
+ def browse_and_select(ignored: Set[str]) -> None:
319
+ """Browse files and select which to ignore."""
320
+ console.print("\n[cyan]🎯 Browse and Select Files[/cyan]\n")
321
+
322
+ files = get_all_files()
323
+
324
+
325
+ tracked_files = [f for f in files if not should_ignore(f, ignored)]
326
+
327
+ if not tracked_files:
328
+ console.print("[yellow]All files are already ignored[/yellow]")
329
+ return
330
+
331
+ # Show in groups
332
+ file_choices = [str(f) for f in tracked_files[:200]] # Limit to 200
333
+
334
+ selected = questionary.checkbox(
335
+ "Select files/folders to ignore:",
336
+ choices=file_choices
337
+ ).ask()
338
+
339
+ if selected:
340
+ for item in selected:
341
+ ignored.add(item)
342
+ console.print(f"[green]✓ Added {len(selected)} items to ignore[/green]")
343
+
344
+
345
+ def show_current_gitignore(ignored: Set[str]) -> None:
346
+ """Show current .gitignore patterns."""
347
+ console.print("\n[cyan]📊 Current .gitignore Patterns:[/cyan]\n")
348
+
349
+ if not ignored:
350
+ console.print("[yellow]No patterns defined[/yellow]")
351
+ else:
352
+ table = Table(show_header=True)
353
+ table.add_column("#", style="dim", width=5)
354
+ table.add_column("Pattern", style="cyan")
355
+ table.add_column("Type", style="yellow")
356
+
357
+ for i, pattern in enumerate(sorted(ignored), 1):
358
+ if pattern.endswith("/"):
359
+ ptype = "Folder"
360
+ elif pattern.startswith("*"):
361
+ ptype = "Extension"
362
+ else:
363
+ ptype = "File/Pattern"
364
+
365
+ table.add_row(str(i), pattern, ptype)
366
+
367
+ console.print(table)
368
+
369
+ input("\nPress Enter to continue...")
370
+
371
+
372
+ def clean_ignored_files() -> None:
373
+ """Remove ignored files from git tracking."""
374
+ console.print("\n[yellow]⚠️ This will remove ignored files from git tracking[/yellow]")
375
+ console.print("[dim]Files will remain on disk but won't be tracked[/dim]\n")
376
+
377
+ confirm = questionary.confirm("Continue?").ask()
378
+
379
+ if confirm:
380
+ import subprocess
381
+ try:
382
+
383
+ result = subprocess.run(
384
+ ["git", "rm", "-r", "--cached", "."],
385
+ capture_output=True,
386
+ text=True
387
+ )
388
+
389
+ # Re-add everything (respecting .gitignore)
390
+ subprocess.run(["git", "add", "."])
391
+
392
+ console.print("[green]✓ Cleaned ignored files from git[/green]")
393
+ console.print("[dim]Run 'git-auto commit' to save changes[/dim]")
394
+ except Exception as e:
395
+ console.print(f"[red]✗ Error: {e}[/red]")
396
+
397
+
398
+
399
+
400
+
@@ -0,0 +1 @@
1
+ """Scaffolding modules for project generation."""