giddy-cli 0.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.
- giddy/__init__.py +4 -0
- giddy/cli.py +169 -0
- giddy/config.py +76 -0
- giddy/git.py +177 -0
- giddy/main.py +125 -0
- giddy/ui.py +247 -0
- giddy/utils.py +72 -0
- giddy/workflows.py +336 -0
- giddy_cli-0.1.0.dist-info/METADATA +435 -0
- giddy_cli-0.1.0.dist-info/RECORD +14 -0
- giddy_cli-0.1.0.dist-info/WHEEL +5 -0
- giddy_cli-0.1.0.dist-info/entry_points.txt +2 -0
- giddy_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- giddy_cli-0.1.0.dist-info/top_level.txt +1 -0
giddy/__init__.py
ADDED
giddy/cli.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
from InquirerPy import inquirer
|
|
2
|
+
from InquirerPy.validator import EmptyInputValidator
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from rich.panel import Panel
|
|
5
|
+
from rich.text import Text
|
|
6
|
+
|
|
7
|
+
from giddy.config import load_config
|
|
8
|
+
from giddy.git import get_current_branch, get_modified_files
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def ask_commit_details() -> str:
|
|
12
|
+
"""Interactively prompt for commit details using conventional commits format.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
A formatted commit message following conventional commits format.
|
|
16
|
+
"""
|
|
17
|
+
config = load_config()
|
|
18
|
+
defined_scopes = config.get("commits", {}).get("scopes", [])
|
|
19
|
+
|
|
20
|
+
commit_type_full = inquirer.select(
|
|
21
|
+
message="What type of change did you make?",
|
|
22
|
+
choices=[
|
|
23
|
+
"feat โจ (New feature)",
|
|
24
|
+
"fix ๐ (Bug fix)",
|
|
25
|
+
"refactor ๐จ (Code improvement)",
|
|
26
|
+
"docs ๐ (Documentation)",
|
|
27
|
+
"chore ๐งน (Dependencies, configuration)",
|
|
28
|
+
],
|
|
29
|
+
pointer="๐",
|
|
30
|
+
).execute()
|
|
31
|
+
|
|
32
|
+
commit_type = commit_type_full.split()[0]
|
|
33
|
+
|
|
34
|
+
if defined_scopes:
|
|
35
|
+
# If scopes are defined in config, show a dropdown menu
|
|
36
|
+
choices = ["(none)"] + defined_scopes
|
|
37
|
+
scope_choice = inquirer.select(
|
|
38
|
+
message="Select the scope of this change:", choices=choices, pointer="๐"
|
|
39
|
+
).execute()
|
|
40
|
+
scope = "" if scope_choice == "(none)" else scope_choice
|
|
41
|
+
else:
|
|
42
|
+
# Default behavior: free text input
|
|
43
|
+
scope = inquirer.text(
|
|
44
|
+
message="Scope of this change (e.g., api, ui - Enter to skip):"
|
|
45
|
+
).execute()
|
|
46
|
+
|
|
47
|
+
description = inquirer.text(
|
|
48
|
+
message="Describe your change (in lowercase without ending punctuation):",
|
|
49
|
+
validate=EmptyInputValidator("Description is required!"),
|
|
50
|
+
).execute()
|
|
51
|
+
|
|
52
|
+
# Format final commit message
|
|
53
|
+
scope_str = f"({scope.strip()})" if scope.strip() else ""
|
|
54
|
+
return f"{commit_type}{scope_str}: {description.strip()}"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def show_dashboard() -> None:
|
|
58
|
+
"""Display repository status dashboard.
|
|
59
|
+
|
|
60
|
+
Shows current branch, modified files, and next steps.
|
|
61
|
+
"""
|
|
62
|
+
console = Console()
|
|
63
|
+
branch = get_current_branch()
|
|
64
|
+
files = get_modified_files()
|
|
65
|
+
|
|
66
|
+
# If working tree is clean
|
|
67
|
+
if not files:
|
|
68
|
+
message = Text(
|
|
69
|
+
"\nโจ Your working directory is clean on branch '", style="green"
|
|
70
|
+
)
|
|
71
|
+
message.append(branch, style="bold cyan")
|
|
72
|
+
message.append("'.\nNothing to commit!", style="green")
|
|
73
|
+
console.print(
|
|
74
|
+
Panel(
|
|
75
|
+
message,
|
|
76
|
+
title="[bold green]๐ Giddy Status[/bold green]",
|
|
77
|
+
border_style="green",
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
# If there are modifications
|
|
83
|
+
content = Text("Current branch: ", style="white")
|
|
84
|
+
content.append(f"{branch}\n\n", style="bold cyan")
|
|
85
|
+
content.append("๐ Modified files:\n", style="bold yellow")
|
|
86
|
+
|
|
87
|
+
for file_line in files:
|
|
88
|
+
# Git --porcelain format: " M filename.py" or "?? newfile.txt"
|
|
89
|
+
state = file_line[:2]
|
|
90
|
+
file_name = file_line[3:]
|
|
91
|
+
|
|
92
|
+
if "??" in state:
|
|
93
|
+
content.append(" ๐ Untracked ", style="blue")
|
|
94
|
+
elif "M" in state:
|
|
95
|
+
content.append(" ๐ ๏ธ Modified ", style="yellow")
|
|
96
|
+
elif "D" in state:
|
|
97
|
+
content.append(" โ Deleted ", style="red")
|
|
98
|
+
elif "A" in state:
|
|
99
|
+
content.append(" โ
Added ", style="green")
|
|
100
|
+
else:
|
|
101
|
+
content.append(f" ๐ {state.strip()} ", style="white")
|
|
102
|
+
|
|
103
|
+
content.append(f"{file_name}\n", style="white")
|
|
104
|
+
|
|
105
|
+
content.append("\n๐ Run ", style="dim")
|
|
106
|
+
content.append("giddy done", style="bold green")
|
|
107
|
+
content.append(" to commit and push these changes.", style="dim")
|
|
108
|
+
|
|
109
|
+
console.print(
|
|
110
|
+
Panel(
|
|
111
|
+
content,
|
|
112
|
+
title="[bold yellow]๐ Giddy Status[/bold yellow]",
|
|
113
|
+
border_style="yellow",
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def ask_files_to_stage() -> list[str]:
|
|
119
|
+
"""
|
|
120
|
+
Prompts the user to select specific files to stage for the commit.
|
|
121
|
+
|
|
122
|
+
Retrieves the list of modified/untracked files using Git porcelain format.
|
|
123
|
+
Displays an interactive checkbox menu. The user can press 'Space' to select
|
|
124
|
+
individual files, or 'a' to select all.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
list[str]: A list of file paths selected by the user. Returns an empty
|
|
128
|
+
list if no files are modified or selected.
|
|
129
|
+
"""
|
|
130
|
+
raw_files = get_modified_files()
|
|
131
|
+
|
|
132
|
+
if not raw_files:
|
|
133
|
+
return []
|
|
134
|
+
|
|
135
|
+
# Prepare choices for InquirerPy:
|
|
136
|
+
# 'name' is what the user sees (e.g., " M src/main.py")
|
|
137
|
+
# 'value' is what the program gets (e.g., "src/main.py")
|
|
138
|
+
choices = []
|
|
139
|
+
for line in raw_files:
|
|
140
|
+
file_path = line[3:].strip()
|
|
141
|
+
choices.append({"name": line, "value": file_path})
|
|
142
|
+
|
|
143
|
+
selected_files = inquirer.checkbox(
|
|
144
|
+
message="Which files do you want to commit?",
|
|
145
|
+
choices=choices,
|
|
146
|
+
instruction="(Press <Space> to select, <Alt+a> to toggle all, <Enter> to confirm)",
|
|
147
|
+
).execute()
|
|
148
|
+
|
|
149
|
+
return selected_files
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def ask_branch_to_switch(branches: list[str], current_branch: str) -> str:
|
|
153
|
+
"""Prompt the user to fuzzy-search and select a branch."""
|
|
154
|
+
|
|
155
|
+
# Optional: Remove the current branch from the list
|
|
156
|
+
# so they don't switch to where they already are
|
|
157
|
+
available_branches = [b for b in branches if b != current_branch]
|
|
158
|
+
|
|
159
|
+
if not available_branches:
|
|
160
|
+
print("๐ No other branches found!")
|
|
161
|
+
return ""
|
|
162
|
+
|
|
163
|
+
branch = inquirer.fuzzy(
|
|
164
|
+
message="Which branch do you want to switch to?",
|
|
165
|
+
choices=available_branches,
|
|
166
|
+
instruction="(Start typing to search, Enter to select)",
|
|
167
|
+
).execute()
|
|
168
|
+
|
|
169
|
+
return branch
|
giddy/config.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import tomllib
|
|
4
|
+
|
|
5
|
+
DEFAULT_CONFIG = {
|
|
6
|
+
"core": {
|
|
7
|
+
"base_branch": "main",
|
|
8
|
+
},
|
|
9
|
+
"commits": {"scopes": []},
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def load_config() -> dict:
|
|
14
|
+
"""Load the local config or return the default."""
|
|
15
|
+
if not os.path.exists(".giddy.toml"):
|
|
16
|
+
return DEFAULT_CONFIG
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
with open(".giddy.toml", "rb") as f:
|
|
20
|
+
user_config = tomllib.load(f)
|
|
21
|
+
|
|
22
|
+
merged = DEFAULT_CONFIG.copy()
|
|
23
|
+
if "core" in user_config:
|
|
24
|
+
merged["core"].update(user_config["core"])
|
|
25
|
+
if "commits" in user_config:
|
|
26
|
+
merged["commits"].update(user_config["commits"])
|
|
27
|
+
return merged
|
|
28
|
+
|
|
29
|
+
except Exception as e:
|
|
30
|
+
print(f"โ ๏ธ Error reading .giddy.toml : {e}. Using default values.")
|
|
31
|
+
return DEFAULT_CONFIG
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
CONFIG_TEMPLATE = """# ==========================================
|
|
35
|
+
# Giddy Configuration
|
|
36
|
+
# ==========================================
|
|
37
|
+
|
|
38
|
+
[core]
|
|
39
|
+
# The main branch of your repository where PRs are merged.
|
|
40
|
+
# Default: "main"
|
|
41
|
+
base_branch = "main"
|
|
42
|
+
|
|
43
|
+
[commits]
|
|
44
|
+
# Define a list here to transform the 'scope' question into a dropdown menu.
|
|
45
|
+
# Example: scopes = ["api", "ui", "database", "docs"]
|
|
46
|
+
# Default: [] (Leaves a free text input)
|
|
47
|
+
scopes = []
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def init_config() -> None:
|
|
52
|
+
"""
|
|
53
|
+
Generates a default .giddy.toml configuration file.
|
|
54
|
+
|
|
55
|
+
Checks if the configuration file already exists in the current working
|
|
56
|
+
directory to prevent accidental overwrites. If it does not exist, it
|
|
57
|
+
creates the file with a pre-filled template containing default settings
|
|
58
|
+
and explanatory comments.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
None
|
|
62
|
+
"""
|
|
63
|
+
config_path = ".giddy.toml"
|
|
64
|
+
|
|
65
|
+
if os.path.exists(config_path):
|
|
66
|
+
print(f"\nโ ๏ธ The file \033[93m{config_path}\033[0m already exists.")
|
|
67
|
+
print(
|
|
68
|
+
" If you want to start fresh, delete it first and run 'giddy init' again."
|
|
69
|
+
)
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
with open(config_path, "w", encoding="utf-8") as f:
|
|
73
|
+
f.write(CONFIG_TEMPLATE)
|
|
74
|
+
|
|
75
|
+
print(f"\n๐ Success! A \033[92m{config_path}\033[0m file has been generated.")
|
|
76
|
+
print(" You can open it and customize Giddy's behavior for this repository.")
|
giddy/git.py
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def run_git_command(command: list[str]) -> bool:
|
|
5
|
+
"""Execute a Git command silently. Returns True if successful."""
|
|
6
|
+
try:
|
|
7
|
+
subprocess.run(command, check=True, capture_output=True)
|
|
8
|
+
return True
|
|
9
|
+
except subprocess.CalledProcessError:
|
|
10
|
+
return False
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_current_branch() -> str:
|
|
14
|
+
"""Get the current Git branch name."""
|
|
15
|
+
result = subprocess.run(
|
|
16
|
+
["git", "branch", "--show-current"], capture_output=True, text=True
|
|
17
|
+
)
|
|
18
|
+
return result.stdout.strip()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_modified_files() -> list[str]:
|
|
22
|
+
"""Get list of modified files using Git's porcelain format."""
|
|
23
|
+
result = subprocess.run(
|
|
24
|
+
["git", "status", "--porcelain", "-uall"], capture_output=True, text=True
|
|
25
|
+
)
|
|
26
|
+
return result.stdout.splitlines()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_local_branches() -> list[str]:
|
|
30
|
+
"""Get a list of local branches sorted by recent commit date."""
|
|
31
|
+
try:
|
|
32
|
+
result = subprocess.run(
|
|
33
|
+
[
|
|
34
|
+
"git",
|
|
35
|
+
"for-each-ref",
|
|
36
|
+
"--sort=-committerdate",
|
|
37
|
+
"--format=%(refname:short)",
|
|
38
|
+
"refs/heads/",
|
|
39
|
+
],
|
|
40
|
+
capture_output=True,
|
|
41
|
+
text=True,
|
|
42
|
+
check=True,
|
|
43
|
+
)
|
|
44
|
+
output = result.stdout.strip()
|
|
45
|
+
if not output:
|
|
46
|
+
return []
|
|
47
|
+
return [branch.strip() for branch in output.split("\n") if branch.strip()]
|
|
48
|
+
except subprocess.CalledProcessError:
|
|
49
|
+
return []
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def switch_to_branch(branch_name: str) -> bool:
|
|
53
|
+
"""Switch to an existing branch."""
|
|
54
|
+
return run_git_command(["git", "checkout", branch_name])
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_remote_repo_info() -> tuple[str, str]:
|
|
58
|
+
"""Extracts the repository URL and provider."""
|
|
59
|
+
result = subprocess.run(
|
|
60
|
+
["git", "config", "--get", "remote.origin.url"], capture_output=True, text=True
|
|
61
|
+
)
|
|
62
|
+
url = result.stdout.strip()
|
|
63
|
+
|
|
64
|
+
if not url:
|
|
65
|
+
return "", ""
|
|
66
|
+
|
|
67
|
+
if "github.com" in url:
|
|
68
|
+
clean_path = (
|
|
69
|
+
url.replace("git@github.com:", "")
|
|
70
|
+
.replace("https://github.com/", "")
|
|
71
|
+
.replace(".git", "")
|
|
72
|
+
)
|
|
73
|
+
return f"https://github.com/{clean_path}", "github"
|
|
74
|
+
|
|
75
|
+
if "gitlab.com" in url:
|
|
76
|
+
clean_path = (
|
|
77
|
+
url.replace("git@gitlab.com:", "")
|
|
78
|
+
.replace("https://gitlab.com/", "")
|
|
79
|
+
.replace(".git", "")
|
|
80
|
+
)
|
|
81
|
+
return f"https://gitlab.com/{clean_path}", "gitlab"
|
|
82
|
+
|
|
83
|
+
return "", ""
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# -------------------------------------------------------------------
|
|
87
|
+
# ACTIONS
|
|
88
|
+
# -------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def create_and_checkout_branch(branch_name: str) -> bool:
|
|
92
|
+
"""Create a new branch and switch to it."""
|
|
93
|
+
return run_git_command(["git", "checkout", "-b", branch_name])
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def fetch_and_prune() -> bool:
|
|
97
|
+
"""Fetch from origin and prune deleted remote branches."""
|
|
98
|
+
return run_git_command(["git", "fetch", "-p"])
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def pull_changes() -> bool:
|
|
102
|
+
"""Pull the latest changes from the remote repository."""
|
|
103
|
+
return run_git_command(["git", "pull"])
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def stage_files(files: list[str]) -> bool:
|
|
107
|
+
"""Stage specific files for commit."""
|
|
108
|
+
return run_git_command(["git", "add"] + files)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def commit_changes(message: str) -> bool:
|
|
112
|
+
"""Create a commit with the staged files."""
|
|
113
|
+
return run_git_command(["git", "commit", "-m", message])
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def push_head() -> bool:
|
|
117
|
+
"""Push the current branch to origin."""
|
|
118
|
+
return run_git_command(["git", "push", "origin", "HEAD"])
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def stash_push(message: str) -> bool:
|
|
122
|
+
"""Stash modifications and untracked files with a message."""
|
|
123
|
+
return run_git_command(["git", "stash", "push", "-u", "-m", message])
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def stash_pop() -> bool:
|
|
127
|
+
"""Pop the latest stashed changes. Returns True if no conflicts."""
|
|
128
|
+
try:
|
|
129
|
+
subprocess.run(
|
|
130
|
+
["git", "stash", "pop"], capture_output=True, text=True, check=True
|
|
131
|
+
)
|
|
132
|
+
return True
|
|
133
|
+
except subprocess.CalledProcessError:
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def get_merged_branches() -> list[str]:
|
|
138
|
+
"""Get a list of branches that have already been merged into the current branch."""
|
|
139
|
+
result = subprocess.run(
|
|
140
|
+
["git", "branch", "--merged"], capture_output=True, text=True
|
|
141
|
+
)
|
|
142
|
+
return [
|
|
143
|
+
b.strip().replace("* ", "") for b in result.stdout.splitlines() if b.strip()
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def delete_local_branch(branch_name: str) -> bool:
|
|
148
|
+
"""Delete a local branch."""
|
|
149
|
+
# Using lowercase -d is safe: Git will refuse to delete if it's not merged!
|
|
150
|
+
return run_git_command(["git", "branch", "-d", branch_name])
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def undo_last_commit() -> bool:
|
|
154
|
+
"""Undo the last commit but keep files in the working directory."""
|
|
155
|
+
return run_git_command(["git", "reset", "--soft", "HEAD~1"])
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def get_tracked_files() -> list[str]:
|
|
159
|
+
"""Get a list of all files currently tracked by Git."""
|
|
160
|
+
result = subprocess.run(["git", "ls-files"], capture_output=True, text=True)
|
|
161
|
+
return result.stdout.splitlines()
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def untrack_file(file_path: str) -> bool:
|
|
165
|
+
"""Remove a file from Git tracking without deleting it locally."""
|
|
166
|
+
# -r is for recursive (if it's a directory), --cached keeps the local file
|
|
167
|
+
return run_git_command(["git", "rm", "-r", "--cached", file_path])
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def commit_amend() -> bool:
|
|
171
|
+
"""Amend the last commit without changing its message."""
|
|
172
|
+
return run_git_command(["git", "commit", "--amend", "--no-edit"])
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def push_force() -> bool:
|
|
176
|
+
"""Force push safely using --force-with-lease."""
|
|
177
|
+
return run_git_command(["git", "push", "--force-with-lease", "origin", "HEAD"])
|
giddy/main.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
# On importe nos belles briques !
|
|
6
|
+
from giddy.git import get_current_branch, get_modified_files
|
|
7
|
+
from giddy.ui import show_dashboard, show_error
|
|
8
|
+
from giddy.utils import init_config
|
|
9
|
+
from giddy.workflows import (
|
|
10
|
+
amend_workflow,
|
|
11
|
+
commit_workflow,
|
|
12
|
+
start_workflow,
|
|
13
|
+
switch_workflow,
|
|
14
|
+
sync_workflow,
|
|
15
|
+
undo_workflow,
|
|
16
|
+
untrack_workflow,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def check_git_environment(command: str) -> None:
|
|
21
|
+
"""Check if Git is installed and if the current directory is a Git repository."""
|
|
22
|
+
if command == "init":
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
# 1. Check if Git is installed
|
|
26
|
+
try:
|
|
27
|
+
subprocess.run(["git", "--version"], capture_output=True, check=True)
|
|
28
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
29
|
+
show_error("Git is not installed or not accessible in your PATH.")
|
|
30
|
+
sys.exit(1)
|
|
31
|
+
|
|
32
|
+
# 2. Check if we are inside a Git repository
|
|
33
|
+
try:
|
|
34
|
+
subprocess.run(
|
|
35
|
+
["git", "rev-parse", "--is-inside-work-tree"],
|
|
36
|
+
capture_output=True,
|
|
37
|
+
check=True,
|
|
38
|
+
)
|
|
39
|
+
except subprocess.CalledProcessError:
|
|
40
|
+
show_error("This directory is not a Git repository. Run 'git init' first.")
|
|
41
|
+
sys.exit(1)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def app() -> None:
|
|
45
|
+
"""Main entry point for the Giddy Git assistant CLI."""
|
|
46
|
+
try:
|
|
47
|
+
if sys.platform == "win32":
|
|
48
|
+
import os
|
|
49
|
+
|
|
50
|
+
os.system("")
|
|
51
|
+
|
|
52
|
+
parser = argparse.ArgumentParser(
|
|
53
|
+
description="Interactive Git assistant to simplify your workflow."
|
|
54
|
+
)
|
|
55
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
56
|
+
|
|
57
|
+
subparsers.add_parser("start", help="Create and checkout a new feature branch.")
|
|
58
|
+
|
|
59
|
+
subparsers.add_parser(
|
|
60
|
+
"done", help="Create a commit and push changes to remote."
|
|
61
|
+
)
|
|
62
|
+
subparsers.add_parser(
|
|
63
|
+
"status", help="Display repository status and modified files."
|
|
64
|
+
)
|
|
65
|
+
subparsers.add_parser(
|
|
66
|
+
"sync",
|
|
67
|
+
help="Switch back to main, pull updates, and clean up dead branches.",
|
|
68
|
+
)
|
|
69
|
+
subparsers.add_parser(
|
|
70
|
+
"init", help="Generate a default .giddy.toml configuration file."
|
|
71
|
+
)
|
|
72
|
+
subparsers.add_parser("switch", help="Switch to a different branch.")
|
|
73
|
+
subparsers.add_parser(
|
|
74
|
+
"undo", help="Undo the last commit without losing your changes."
|
|
75
|
+
)
|
|
76
|
+
subparsers.add_parser(
|
|
77
|
+
"untrack", help="Stop tracking a file in Git without deleting it locally."
|
|
78
|
+
)
|
|
79
|
+
subparsers.add_parser(
|
|
80
|
+
"amend", help="Quickly add forgotten files to your last commit."
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
args = parser.parse_args()
|
|
84
|
+
|
|
85
|
+
# Check environment before proceeding
|
|
86
|
+
check_git_environment(args.command)
|
|
87
|
+
|
|
88
|
+
# Dispatch to the right workflow!
|
|
89
|
+
if args.command == "start":
|
|
90
|
+
start_workflow()
|
|
91
|
+
|
|
92
|
+
elif args.command == "done":
|
|
93
|
+
commit_workflow()
|
|
94
|
+
|
|
95
|
+
elif args.command == "status":
|
|
96
|
+
branch = get_current_branch()
|
|
97
|
+
files = get_modified_files()
|
|
98
|
+
show_dashboard(branch, files)
|
|
99
|
+
|
|
100
|
+
elif args.command == "sync":
|
|
101
|
+
sync_workflow()
|
|
102
|
+
|
|
103
|
+
elif args.command == "init":
|
|
104
|
+
init_config()
|
|
105
|
+
|
|
106
|
+
elif args.command == "switch":
|
|
107
|
+
switch_workflow()
|
|
108
|
+
|
|
109
|
+
elif args.command == "undo":
|
|
110
|
+
undo_workflow()
|
|
111
|
+
|
|
112
|
+
elif args.command == "untrack":
|
|
113
|
+
untrack_workflow()
|
|
114
|
+
|
|
115
|
+
elif args.command == "amend":
|
|
116
|
+
amend_workflow()
|
|
117
|
+
|
|
118
|
+
except KeyboardInterrupt:
|
|
119
|
+
# Gracefully handle Ctrl+C
|
|
120
|
+
print("\n\n๐ Operation cancelled. No changes were made.\n")
|
|
121
|
+
sys.exit(0)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
if __name__ == "__main__":
|
|
125
|
+
app()
|