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,5 @@
1
+ """Git-Auto Pro - Complete Git + GitHub automation CLI tool."""
2
+
3
+ __version__ = "1.1.0"
4
+ __author__ = "Himanshu Singh"
5
+ __email__ = "choudharyhimanshusingh966@gmail.com"
git_auto_pro/backup.py ADDED
@@ -0,0 +1,87 @@
1
+ """Repository backup and restore module."""
2
+
3
+ import shutil
4
+ import tarfile
5
+ from pathlib import Path
6
+ from datetime import datetime
7
+ from typing import Optional
8
+ from rich.console import Console
9
+ from rich.progress import Progress, SpinnerColumn, TextColumn
10
+
11
+ console = Console()
12
+
13
+
14
+ def create_backup(output: Optional[str] = None) -> None:
15
+ """Create repository backup."""
16
+ console.print("[bold cyan]💾 Creating repository backup[/bold cyan]\n")
17
+
18
+ try:
19
+ # Get repository name
20
+ repo_name = Path.cwd().name
21
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
22
+
23
+ if not output:
24
+ output = f"{repo_name}_backup_{timestamp}.tar.gz"
25
+
26
+ with Progress(
27
+ SpinnerColumn(),
28
+ TextColumn("[progress.description]{task.description}"),
29
+ console=console,
30
+ ) as progress:
31
+ task = progress.add_task("Creating backup...", total=None)
32
+
33
+ # Create tar archive
34
+ with tarfile.open(output, "w:gz") as tar:
35
+
36
+ for item in Path.cwd().iterdir():
37
+ if item.name != ".git":
38
+ tar.add(item, arcname=item.name)
39
+
40
+
41
+ git_dir = Path(".git")
42
+ if git_dir.exists():
43
+ for item in ["config", "HEAD", "refs", "hooks"]:
44
+ git_item = git_dir / item
45
+ if git_item.exists():
46
+ tar.add(git_item, arcname=f".git/{item}")
47
+
48
+ progress.update(task, description="Backup complete!")
49
+
50
+ backup_size = Path(output).stat().st_size / (1024 * 1024) # MB
51
+ console.print(f"[green]✓ Backup created: {output}[/green]")
52
+ console.print(f"[cyan]Size: {backup_size:.2f} MB[/cyan]")
53
+
54
+ except Exception as e:
55
+ console.print(f"[red]✗ Backup failed: {e}[/red]")
56
+
57
+
58
+ def restore_backup(backup_path: str) -> None:
59
+ """Restore from backup."""
60
+ console.print(f"[bold cyan]♻️ Restoring from backup: {backup_path}[/bold cyan]\n")
61
+
62
+ if not Path(backup_path).exists():
63
+ console.print(f"[red]✗ Backup file not found: {backup_path}[/red]")
64
+ return
65
+
66
+ try:
67
+ # Create restore directory
68
+ restore_dir = Path.cwd() / "restored"
69
+ restore_dir.mkdir(exist_ok=True)
70
+
71
+ with Progress(
72
+ SpinnerColumn(),
73
+ TextColumn("[progress.description]{task.description}"),
74
+ console=console,
75
+ ) as progress:
76
+ task = progress.add_task("Extracting backup...", total=None)
77
+
78
+ # Extract tar archive
79
+ with tarfile.open(backup_path, "r:gz") as tar:
80
+ tar.extractall(restore_dir)
81
+
82
+ progress.update(task, description="Restore complete!")
83
+
84
+ console.print(f"[green]✓ Backup restored to: {restore_dir}[/green]")
85
+
86
+ except Exception as e:
87
+ console.print(f"[red]✗ Restore failed: {e}[/red]")
git_auto_pro/cli.py ADDED
@@ -0,0 +1,474 @@
1
+ """Main CLI interface using Typer."""
2
+
3
+ import typer
4
+ from typing import Optional, List
5
+ from pathlib import Path
6
+ from rich.console import Console
7
+ from rich.panel import Panel
8
+ from rich.table import Table
9
+
10
+ # Import submodules
11
+ from .github import (
12
+ login_github,
13
+ create_github_repo,
14
+ add_collaborator,
15
+ protect_branch,
16
+ )
17
+ from .git_commands import (
18
+ git_init,
19
+ git_add,
20
+ git_commit,
21
+ git_push,
22
+ git_pull,
23
+ git_status,
24
+ git_log,
25
+ git_branch,
26
+ git_switch,
27
+ git_delete_branch,
28
+ git_stash,
29
+ git_stash_apply,
30
+ git_merge,
31
+ git_clone,
32
+ git_stats,
33
+ )
34
+ from .config import (
35
+ get_config,
36
+ set_config,
37
+ list_config,
38
+ reset_config,
39
+ )
40
+ from .scaffolding.project import create_new_project
41
+ from .scaffolding.readme import generate_readme
42
+ from .scaffolding.license import generate_license
43
+ from .scaffolding.gitignore import generate_gitignore
44
+ from .scaffolding.templates import generate_template
45
+ from .scaffolding.workflows import generate_workflow
46
+ from .scaffolding.hooks import setup_hook
47
+ from .scaffolding.github_templates import generate_github_templates
48
+ from .backup import create_backup, restore_backup
49
+
50
+ console = Console()
51
+ app = typer.Typer(
52
+ name="git-auto",
53
+ help="🚀 Complete Git + GitHub automation CLI tool",
54
+ add_completion=False,
55
+ )
56
+
57
+ # ============================================================================
58
+ # AUTHENTICATION COMMANDS
59
+ # ============================================================================
60
+
61
+ @app.command()
62
+ def login(
63
+ token: Optional[str] = typer.Option(None, "--token", "-t", help="GitHub Personal Access Token")
64
+ ):
65
+ """🔐 Login to GitHub using Personal Access Token."""
66
+ login_github(token)
67
+
68
+ @app.command()
69
+ def logout(
70
+ confirm: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation")
71
+ ):
72
+ """🚪 Logout and clear stored GitHub token."""
73
+ from .github import clear_stored_token
74
+
75
+ if not confirm:
76
+ confirm = typer.confirm("Are you sure you want to logout?")
77
+
78
+ if confirm:
79
+ clear_stored_token()
80
+ console.print("[bold green]✓ Logged out successfully[/bold green]")
81
+ else:
82
+ console.print("[yellow]Logout cancelled[/yellow]")
83
+
84
+ # ============================================================================
85
+ # REPOSITORY COMMANDS
86
+ # ============================================================================
87
+
88
+ @app.command()
89
+ def create_repo(
90
+ name: str = typer.Argument(..., help="Repository name"),
91
+ private: bool = typer.Option(False, "--private", "-p", help="Create private repository"),
92
+ description: Optional[str] = typer.Option(None, "--description", "-d"),
93
+ homepage: Optional[str] = typer.Option(None, "--homepage", "-h"),
94
+ topics: Optional[str] = typer.Option(None, "--topics", "-t", help="Comma-separated topics"),
95
+ auto_init: bool = typer.Option(False, "--auto-init", help="Initialize with README"),
96
+ ):
97
+ """📦 Create a new GitHub repository."""
98
+ topics_list = [t.strip() for t in topics.split(",")] if topics else None
99
+ create_github_repo(name, private, description, homepage, topics_list, auto_init)
100
+
101
+
102
+ @app.command()
103
+ def init(
104
+ connect: Optional[str] = typer.Option(None, "--connect", "-c", help="Connect to remote URL")
105
+ ):
106
+ """🎬 Initialize Git repository in current directory."""
107
+ git_init(connect)
108
+
109
+
110
+ # ============================================================================
111
+ # PROJECT CREATION
112
+ # ============================================================================
113
+
114
+ @app.command()
115
+ def new(
116
+ project_name: str = typer.Argument(..., help="Project name"),
117
+ template: Optional[str] = typer.Option(None, "--template", "-t", help="Project template"),
118
+ private: bool = typer.Option(False, "--private", "-p", help="Create private repository"),
119
+ no_github: bool = typer.Option(False, "--no-github", help="Skip GitHub repository creation"),
120
+ ):
121
+ """✨ Create a complete new project with Git + GitHub setup."""
122
+ create_new_project(project_name, template, private, no_github)
123
+
124
+
125
+ # ============================================================================
126
+ # BASIC GIT COMMANDS
127
+ # ============================================================================
128
+
129
+ @app.command()
130
+ def add(
131
+ files: Optional[List[str]] = typer.Argument(None, help="Files to add (default: all)"),
132
+ all: bool = typer.Option(False, "--all", "-A", help="Add all files"),
133
+ ):
134
+ """➕ Stage files for commit."""
135
+ git_add(files, all)
136
+
137
+
138
+ @app.command()
139
+ def commit(
140
+ message: str = typer.Argument(..., help="Commit message"),
141
+ conventional: bool = typer.Option(False, "--conventional", "-c", help="Use conventional commit format"),
142
+ amend: bool = typer.Option(False, "--amend", help="Amend previous commit"),
143
+ ):
144
+ """💾 Commit staged changes."""
145
+ git_commit(message, conventional, amend)
146
+
147
+
148
+ @app.command()
149
+ def push(
150
+ message: Optional[str] = typer.Argument(None, help="Commit message (auto add + commit + push)"),
151
+ branch: str = typer.Option("main", "--branch", "-b", help="Branch to push"),
152
+ force: bool = typer.Option(False, "--force", "-f", help="Force push"),
153
+ ):
154
+ """⬆️ Push commits to remote."""
155
+ git_push(message, branch, force)
156
+
157
+
158
+ @app.command()
159
+ def pull(
160
+ branch: Optional[str] = typer.Option(None, "--branch", "-b", help="Branch to pull"),
161
+ rebase: bool = typer.Option(False, "--rebase", "-r", help="Rebase instead of merge"),
162
+ ):
163
+ """⬇️ Pull changes from remote."""
164
+ git_pull(branch, rebase)
165
+
166
+
167
+ @app.command()
168
+ def status(
169
+ short: bool = typer.Option(False, "--short", "-s", help="Short format"),
170
+ ):
171
+ """📊 Show working tree status."""
172
+ git_status(short)
173
+
174
+
175
+ @app.command()
176
+ def log(
177
+ limit: int = typer.Option(10, "--limit", "-n", help="Number of commits to show"),
178
+ oneline: bool = typer.Option(False, "--oneline", help="One line per commit"),
179
+ graph: bool = typer.Option(False, "--graph", "-g", help="Show commit graph"),
180
+ ):
181
+ """📜 Show commit history."""
182
+ git_log(limit, oneline, graph)
183
+
184
+
185
+ # ============================================================================
186
+ # BRANCH COMMANDS
187
+ # ============================================================================
188
+
189
+ @app.command()
190
+ def branch(
191
+ name: Optional[str] = typer.Argument(None, help="Branch name to create"),
192
+ list: bool = typer.Option(False, "--list", "-l", help="List all branches"),
193
+ remote: bool = typer.Option(False, "--remote", "-r", help="List remote branches"),
194
+ ):
195
+ """🌿 Create or list branches."""
196
+ git_branch(name, list, remote)
197
+
198
+
199
+ @app.command()
200
+ def switch(
201
+ name: str = typer.Argument(..., help="Branch name to switch to"),
202
+ create: bool = typer.Option(False, "--create", "-c", help="Create branch if it doesn't exist"),
203
+ ):
204
+ """🔄 Switch to a different branch."""
205
+ git_switch(name, create)
206
+
207
+
208
+ @app.command()
209
+ def delete_branch(
210
+ name: str = typer.Argument(..., help="Branch name to delete"),
211
+ force: bool = typer.Option(False, "--force", "-f", help="Force delete"),
212
+ ):
213
+ """🗑️ Delete a branch."""
214
+ git_delete_branch(name, force)
215
+
216
+
217
+ # ============================================================================
218
+ # STASH COMMANDS
219
+ # ============================================================================
220
+
221
+ @app.command()
222
+ def stash(
223
+ message: Optional[str] = typer.Option(None, "--message", "-m", help="Stash message"),
224
+ list: bool = typer.Option(False, "--list", "-l", help="List all stashes"),
225
+ ):
226
+ """💼 Stash uncommitted changes."""
227
+ git_stash(message, list)
228
+
229
+
230
+ @app.command()
231
+ def stash_apply(
232
+ index: int = typer.Option(0, "--index", "-i", help="Stash index to apply"),
233
+ pop: bool = typer.Option(False, "--pop", "-p", help="Apply and remove stash"),
234
+ ):
235
+ """📤 Apply stashed changes."""
236
+ git_stash_apply(index, pop)
237
+
238
+
239
+ # ============================================================================
240
+ # MERGE COMMANDS
241
+ # ============================================================================
242
+
243
+ @app.command()
244
+ def merge(
245
+ branch: str = typer.Argument(..., help="Branch to merge"),
246
+ no_ff: bool = typer.Option(False, "--no-ff", help="No fast-forward merge"),
247
+ squash: bool = typer.Option(False, "--squash", help="Squash commits"),
248
+ ):
249
+ """🔗 Merge branches."""
250
+ git_merge(branch, no_ff, squash)
251
+
252
+
253
+ # ============================================================================
254
+ # CLONE COMMAND
255
+ # ============================================================================
256
+
257
+ @app.command()
258
+ def clone(
259
+ url: str = typer.Argument(..., help="Repository URL to clone"),
260
+ directory: Optional[str] = typer.Option(None, "--dir", "-d", help="Target directory"),
261
+ depth: Optional[int] = typer.Option(None, "--depth", help="Shallow clone depth"),
262
+ ):
263
+ """📥 Clone a repository."""
264
+ git_clone(url, directory, depth)
265
+
266
+
267
+ # ============================================================================
268
+ # STATISTICS
269
+ # ============================================================================
270
+
271
+ @app.command()
272
+ def stats(
273
+ detailed: bool = typer.Option(False, "--detailed", "-d", help="Show detailed statistics"),
274
+ ):
275
+ """📈 Show repository statistics."""
276
+ git_stats(detailed)
277
+
278
+
279
+ # ============================================================================
280
+ # GENERATORS
281
+ # ============================================================================
282
+
283
+ @app.command()
284
+ def readme(
285
+ interactive: bool = typer.Option(True, "--interactive", "-i", help="Interactive mode"),
286
+ output: str = typer.Option("README.md", "--output", "-o", help="Output file"),
287
+ ):
288
+ """📝 Generate professional README.md."""
289
+ generate_readme(interactive, output)
290
+
291
+
292
+ @app.command()
293
+ def license(
294
+ type: Optional[str] = typer.Option(None, "--type", "-t", help="License type"),
295
+ author: Optional[str] = typer.Option(None, "--author", "-a", help="Author name"),
296
+ year: Optional[int] = typer.Option(None, "--year", "-y", help="Copyright year"),
297
+ ):
298
+ """⚖️ Generate LICENSE file."""
299
+ generate_license(type, author, year)
300
+
301
+
302
+ @app.command()
303
+ def ignore(
304
+ template: Optional[str] = typer.Option(None, "--template", "-t", help="Template type"),
305
+ ):
306
+ """🚫 Generate .gitignore file."""
307
+ generate_gitignore(template)
308
+
309
+
310
+ @app.command()
311
+ def template(
312
+ type: str = typer.Argument(..., help="Template type (python, node, cpp, web, etc.)"),
313
+ output: Optional[str] = typer.Option(None, "--output", "-o", help="Output directory"),
314
+ ):
315
+ """📋 Generate project template structure."""
316
+ generate_template(type, output)
317
+
318
+
319
+ # ============================================================================
320
+ # WORKFLOWS & HOOKS
321
+ # ============================================================================
322
+
323
+ @app.command()
324
+ def workflow(
325
+ type: str = typer.Argument(..., help="Workflow type (ci, cd, test, etc.)"),
326
+ platform: str = typer.Option("github", "--platform", "-p", help="CI platform"),
327
+ ):
328
+ """⚙️ Generate CI/CD workflow files."""
329
+ generate_workflow(type, platform)
330
+
331
+
332
+ @app.command()
333
+ def hook(
334
+ type: str = typer.Argument(..., help="Hook type (pre-commit, pre-push, etc.)"),
335
+ script: Optional[str] = typer.Option(None, "--script", "-s", help="Custom script path"),
336
+ ):
337
+ """🪝 Setup Git hooks."""
338
+ setup_hook(type, script)
339
+
340
+
341
+ # ============================================================================
342
+ # GITHUB TEMPLATES
343
+ # ============================================================================
344
+
345
+ @app.command()
346
+ def templates(
347
+ type: str = typer.Argument(..., help="Template type (issue, pr, contributing)"),
348
+ ):
349
+ """📋 Generate GitHub issue/PR templates."""
350
+ generate_github_templates(type)
351
+
352
+
353
+ # ============================================================================
354
+ # COLLABORATION
355
+ # ============================================================================
356
+
357
+ @app.command()
358
+ def collab(
359
+ username: str = typer.Argument(..., help="GitHub username to add"),
360
+ repo: Optional[str] = typer.Option(None, "--repo", "-r", help="Repository name"),
361
+ permission: str = typer.Option("push", "--permission", "-p", help="Permission level"),
362
+ ):
363
+ """👥 Add collaborator to repository."""
364
+ add_collaborator(username, repo, permission)
365
+
366
+
367
+ @app.command()
368
+ def protect(
369
+ branch: str = typer.Argument("main", help="Branch to protect"),
370
+ repo: Optional[str] = typer.Option(None, "--repo", "-r", help="Repository name"),
371
+ ):
372
+ """🛡️ Setup branch protection rules."""
373
+ protect_branch(branch, repo)
374
+
375
+
376
+ # ============================================================================
377
+ # BACKUP & RESTORE
378
+ # ============================================================================
379
+
380
+ @app.command()
381
+ def backup(
382
+ output: Optional[str] = typer.Option(None, "--output", "-o", help="Backup location"),
383
+ ):
384
+ """💾 Create repository backup."""
385
+ create_backup(output)
386
+
387
+
388
+ @app.command()
389
+ def restore(
390
+ backup_path: str = typer.Argument(..., help="Backup file path"),
391
+ ):
392
+ """♻️ Restore from backup."""
393
+ restore_backup(backup_path)
394
+
395
+
396
+ # ============================================================================
397
+ # CONFIGURATION
398
+ # ============================================================================
399
+
400
+ config_app = typer.Typer(help="⚙️ Configuration management")
401
+ app.add_typer(config_app, name="config")
402
+
403
+
404
+ @config_app.command("set")
405
+ def config_set(
406
+ key: str = typer.Argument(..., help="Configuration key"),
407
+ value: str = typer.Argument(..., help="Configuration value"),
408
+ ):
409
+ """Set configuration value."""
410
+ set_config(key, value)
411
+
412
+
413
+ @config_app.command("get")
414
+ def config_get(
415
+ key: str = typer.Argument(..., help="Configuration key"),
416
+ ):
417
+ """Get configuration value."""
418
+ value = get_config(key)
419
+ if value:
420
+ console.print(f"[bold cyan]{key}:[/bold cyan] {value}")
421
+ else:
422
+ console.print(f"[yellow]No value set for '{key}'[/yellow]")
423
+
424
+
425
+ @config_app.command("list")
426
+ def config_list():
427
+ """List all configuration values."""
428
+ list_config()
429
+
430
+
431
+ @config_app.command("reset")
432
+ def config_reset(
433
+ confirm: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
434
+ ):
435
+ """Reset configuration to defaults."""
436
+ if not confirm:
437
+ confirm = typer.confirm("Are you sure you want to reset all configuration?")
438
+ if confirm:
439
+ reset_config()
440
+
441
+
442
+ # ============================================================================
443
+ # VERSION & HELP
444
+ # ============================================================================
445
+
446
+ @app.command()
447
+ def version():
448
+ """📌 Show version information."""
449
+ from . import __version__
450
+
451
+ version_text = (
452
+ f"[bold cyan]Git-Auto Pro[/bold cyan]\n"
453
+ f"Version: [yellow]{__version__}[/yellow]\n"
454
+ f"Complete Git + GitHub Automation Tool"
455
+ )
456
+
457
+ console.print(Panel(
458
+ version_text,
459
+ border_style="cyan"
460
+ ))
461
+
462
+ # ============================================================================
463
+ # GITIGNORE MANAGER
464
+ # ============================================================================
465
+
466
+ @app.command()
467
+ def ignore_manager():
468
+ """🎯 Interactive .gitignore file manager - browse and select files."""
469
+ from .gitignore_manager import interactive_gitignore_manager
470
+
471
+ interactive_gitignore_manager()
472
+
473
+ if __name__ == "__main__":
474
+ app()
git_auto_pro/config.py ADDED
@@ -0,0 +1,100 @@
1
+ """Configuration management module."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Any, Optional, Dict, Union
6
+ from rich.console import Console
7
+ from rich.table import Table
8
+
9
+ console = Console()
10
+
11
+ CONFIG_FILE = Path.home() / ".git-auto-config.json"
12
+
13
+ DEFAULT_CONFIG = {
14
+ "default_branch": "main",
15
+ "default_commit_message": "Update files",
16
+ "default_license": "MIT",
17
+ "default_project_type": "python",
18
+ "auto_push": False,
19
+ "conventional_commits": False,
20
+ "editor": "nano",
21
+ "git_user_name": "",
22
+ "git_user_email": "",
23
+ }
24
+
25
+
26
+ def load_config() -> Dict[str, Any]:
27
+ """Load configuration from file."""
28
+ if not CONFIG_FILE.exists():
29
+ return DEFAULT_CONFIG.copy()
30
+
31
+ try:
32
+ with open(CONFIG_FILE, "r") as f:
33
+ config = json.load(f)
34
+ return {**DEFAULT_CONFIG, **config}
35
+ except Exception as e:
36
+ console.print(f"[yellow]Warning: Could not load config: {e}[/yellow]")
37
+ return DEFAULT_CONFIG.copy()
38
+
39
+
40
+ def save_config(config: Dict[str, Any]) -> None:
41
+ """Save configuration to file."""
42
+ try:
43
+ with open(CONFIG_FILE, "w") as f:
44
+ json.dump(config, f, indent=2)
45
+ except Exception as e:
46
+ console.print(f"[red]✗ Failed to save config: {e}[/red]")
47
+ raise
48
+
49
+
50
+ def get_config(key: str) -> Optional[Any]:
51
+ """Get a configuration value."""
52
+ config = load_config()
53
+ return config.get(key)
54
+
55
+
56
+ def set_config(key: str, value: str) -> None:
57
+ """Set a configuration value."""
58
+ config = load_config()
59
+
60
+ # Type conversion - store as proper type
61
+ converted_value: Union[str, bool, int] = value
62
+ if value.lower() in ("true", "false"):
63
+ converted_value = value.lower() == "true"
64
+ elif value.isdigit():
65
+ converted_value = int(value)
66
+
67
+ config[key] = converted_value
68
+ save_config(config)
69
+ console.print(f"[green]✓ Set {key} = {converted_value}[/green]")
70
+
71
+
72
+ def list_config() -> None:
73
+ """List all configuration values."""
74
+ config = load_config()
75
+
76
+ table = Table(title="Configuration", show_header=True)
77
+ table.add_column("Key", style="cyan", width=30)
78
+ table.add_column("Value", style="yellow")
79
+
80
+ for key, value in sorted(config.items()):
81
+ table.add_row(key, str(value))
82
+
83
+ console.print(table)
84
+ console.print(f"\n[dim]Config file: {CONFIG_FILE}[/dim]")
85
+
86
+
87
+ def reset_config() -> None:
88
+ """Reset configuration to defaults."""
89
+ save_config(DEFAULT_CONFIG)
90
+ console.print("[green]✓ Configuration reset to defaults[/green]")
91
+
92
+
93
+ def get_default_branch() -> str:
94
+ """Get default branch name."""
95
+ return str(get_config("default_branch") or "main")
96
+
97
+
98
+ def get_default_license() -> str:
99
+ """Get default license type."""
100
+ return str(get_config("default_license") or "MIT")