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.
- git_auto_pro/__init__.py +5 -0
- git_auto_pro/backup.py +87 -0
- git_auto_pro/cli.py +474 -0
- git_auto_pro/config.py +100 -0
- git_auto_pro/git_commands.py +426 -0
- git_auto_pro/github.py +318 -0
- git_auto_pro/gitignore_manager.py +400 -0
- git_auto_pro/scaffolding/__init__.py +1 -0
- git_auto_pro/scaffolding/github_templates.py +635 -0
- git_auto_pro/scaffolding/gitignore.py +149 -0
- git_auto_pro/scaffolding/hooks.py +331 -0
- git_auto_pro/scaffolding/license.py +109 -0
- git_auto_pro/scaffolding/project.py +83 -0
- git_auto_pro/scaffolding/readme.py +109 -0
- git_auto_pro/scaffolding/templates.py +336 -0
- git_auto_pro/scaffolding/workflows.py +236 -0
- git_auto_pro-1.0.0.dist-info/METADATA +477 -0
- git_auto_pro-1.0.0.dist-info/RECORD +22 -0
- git_auto_pro-1.0.0.dist-info/WHEEL +5 -0
- git_auto_pro-1.0.0.dist-info/entry_points.txt +2 -0
- git_auto_pro-1.0.0.dist-info/licenses/LICENSE +0 -0
- git_auto_pro-1.0.0.dist-info/top_level.txt +1 -0
git_auto_pro/__init__.py
ADDED
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")
|