pygitgo 1.8.0__tar.gz → 1.8.1__tar.gz

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.
Files changed (71) hide show
  1. {pygitgo-1.8.0/src/pygitgo.egg-info → pygitgo-1.8.1}/PKG-INFO +1 -1
  2. {pygitgo-1.8.0 → pygitgo-1.8.1}/pyproject.toml +1 -1
  3. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/auth/account.py +26 -0
  4. pygitgo-1.8.1/src/pygitgo/auth/manager.py +134 -0
  5. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/auth/ssh_utils.py +87 -8
  6. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/git_branch.py +4 -4
  7. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/git_core.py +14 -11
  8. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/git_remote.py +9 -11
  9. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/init.py +18 -4
  10. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/jump.py +26 -26
  11. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/link.py +5 -7
  12. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/pull.py +3 -5
  13. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/repo.py +24 -9
  14. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/stash.py +14 -12
  15. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/undo.py +4 -6
  16. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/exceptions.py +0 -1
  17. pygitgo-1.8.1/src/pygitgo/utils/bootstrap.py +15 -0
  18. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/utils/executor.py +10 -2
  19. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/utils/validators.py +1 -1
  20. {pygitgo-1.8.0 → pygitgo-1.8.1/src/pygitgo.egg-info}/PKG-INFO +1 -1
  21. {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_account.py +49 -1
  22. pygitgo-1.8.1/tests/test_bootstrap.py +29 -0
  23. {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_config_command.py +1 -1
  24. {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_config_utils.py +0 -5
  25. {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_git_branch.py +6 -5
  26. {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_git_core.py +22 -17
  27. {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_git_remote.py +13 -18
  28. {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_jump.py +17 -17
  29. {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_link.py +6 -5
  30. pygitgo-1.8.1/tests/test_manager.py +146 -0
  31. {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_pull.py +8 -7
  32. {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_push.py +1 -2
  33. {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_repo.py +0 -2
  34. {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_ssh_utils.py +69 -2
  35. {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_stash.py +16 -8
  36. {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_undo.py +4 -9
  37. pygitgo-1.8.0/src/pygitgo/auth/manager.py +0 -101
  38. pygitgo-1.8.0/src/pygitgo/utils/bootstrap.py +0 -26
  39. pygitgo-1.8.0/tests/test_bootstrap.py +0 -54
  40. pygitgo-1.8.0/tests/test_manager.py +0 -79
  41. {pygitgo-1.8.0 → pygitgo-1.8.1}/LICENSE +0 -0
  42. {pygitgo-1.8.0 → pygitgo-1.8.1}/README.md +0 -0
  43. {pygitgo-1.8.0 → pygitgo-1.8.1}/setup.cfg +0 -0
  44. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/__init__.py +0 -0
  45. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/auth/__init__.py +0 -0
  46. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/__init__.py +0 -0
  47. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/config.py +0 -0
  48. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/new.py +0 -0
  49. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/push.py +0 -0
  50. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/staging.py +0 -0
  51. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/state.py +0 -0
  52. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/user.py +0 -0
  53. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/main.py +0 -0
  54. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/utils/__init__.py +0 -0
  55. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/utils/colors.py +0 -0
  56. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/utils/config.py +0 -0
  57. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/utils/platform.py +0 -0
  58. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/utils/update_checker.py +0 -0
  59. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo.egg-info/SOURCES.txt +0 -0
  60. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo.egg-info/dependency_links.txt +0 -0
  61. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo.egg-info/entry_points.txt +0 -0
  62. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo.egg-info/requires.txt +0 -0
  63. {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo.egg-info/top_level.txt +0 -0
  64. {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_init.py +0 -0
  65. {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_new.py +0 -0
  66. {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_platform.py +0 -0
  67. {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_staging.py +0 -0
  68. {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_state.py +0 -0
  69. {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_update_checker.py +0 -0
  70. {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_user.py +0 -0
  71. {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_validators.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pygitgo
3
- Version: 1.8.0
3
+ Version: 1.8.1
4
4
  Summary: GitGo CLI - Your Fast Git Companion. Simplifies git push, link, stash, and user management.
5
5
  Author: Huerte
6
6
  License: GPL-3.0-or-later
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pygitgo"
7
- version = "1.8.0"
7
+ version = "1.8.1"
8
8
  description = "GitGo CLI - Your Fast Git Companion. Simplifies git push, link, stash, and user management."
9
9
  readme = "README.md"
10
10
  license = {text = "GPL-3.0-or-later"}
@@ -65,3 +65,29 @@ def ensure_user_configure(default_email=None, default_username=None):
65
65
  error("Invalid configuration. Name and Email are required.")
66
66
  return False
67
67
 
68
+ def sanitize_signing_config():
69
+ try:
70
+ gpgsign = run_command(["git", "config", "--global", "commit.gpgsign"]).strip().lower()
71
+ except GitCommandError:
72
+ return
73
+
74
+ if gpgsign != "true":
75
+ return
76
+
77
+ try:
78
+ fmt = run_command(["git", "config", "--global", "gpg.format"]).strip().lower()
79
+ except GitCommandError:
80
+ fmt = ""
81
+
82
+ if fmt == "ssh":
83
+ return
84
+
85
+ warning("Detected 'commit.gpgsign=true' with no GPG key configured.")
86
+ warning("Disabling global GPG signing to prevent commit failures.")
87
+
88
+ try:
89
+ run_command(["git", "config", "--global", "--unset", "gpg.program"])
90
+ except GitCommandError:
91
+ pass
92
+
93
+
@@ -0,0 +1,134 @@
1
+ from pygitgo.utils.colors import info, success, warning, error
2
+ from pygitgo.utils.platform import get_platform
3
+ from pygitgo.utils.executor import run_command
4
+ from pygitgo.exceptions import GitCommandError
5
+ from pygitgo.utils.platform import open_url
6
+ from . import ssh_utils
7
+ import os
8
+
9
+
10
+ def _configure_ssh_signing(key_path):
11
+ try:
12
+ run_command(["git", "config", "--global", "gpg.format", "ssh"])
13
+ run_command(["git", "config", "--global", "user.signingkey", str(key_path)])
14
+ except Exception:
15
+ pass
16
+
17
+
18
+ def login():
19
+ from .account import sanitize_signing_config, ensure_user_configure
20
+
21
+ sanitize_signing_config()
22
+
23
+ key_path = ssh_utils.get_ssh_key_path()
24
+ already_logged_in = key_path.exists()
25
+ ok_text = "Already logged in via GitGo." if already_logged_in else "GitHub connection verified."
26
+
27
+ if ssh_utils.check_connection(ok_text=ok_text):
28
+ if already_logged_in:
29
+ _configure_ssh_signing(key_path)
30
+ ensure_user_configure(default_username=ssh_utils.get_github_username())
31
+ else:
32
+ warning("GitHub SSH connection is active, but NOT via a GitGo-managed key.")
33
+ warning("To use GitGo's full login (SSH + verified commits), you must log out first.")
34
+ info("Run: gitgo user logout")
35
+ info("Then run: gitgo user login")
36
+
37
+ return True
38
+
39
+ info("Initiating login sequence...")
40
+ while True:
41
+ email = input("Enter your email for GitHub: ").strip()
42
+ if "@" in email and "." in email:
43
+ break
44
+ else:
45
+ error("Please enter a valid email address.")
46
+
47
+ key_path = ssh_utils.generate_ssh_key(email=email)
48
+ pub_key_path = str(key_path) + ".pub"
49
+
50
+ with open(pub_key_path, "r") as f:
51
+ pub_key = f.read()
52
+
53
+ if not pub_key:
54
+ error("Failed to read the generated public key.")
55
+ return False
56
+
57
+ success("SSH Key generated successfully!")
58
+
59
+ print()
60
+ print(" Your SSH public key:")
61
+ print()
62
+ print(f" {pub_key.strip()}")
63
+ print()
64
+
65
+ info("Copy the key above, then add it TWICE on GitHub:")
66
+ info(" 1. Authentication Key — for pushing and pulling")
67
+ info(" 2. Signing Key — for Verified commits")
68
+ info("Same key text for both.")
69
+
70
+ input("\nOnce you've copied the key, press Enter to open GitHub...")
71
+
72
+ open_url("https://github.com/settings/ssh/new")
73
+
74
+ input(
75
+ "\nAfter adding both keys on GitHub,\n"
76
+ "come back here and press Enter to verify the connection..."
77
+ )
78
+
79
+ ssh_utils.ensure_ssh_agent(ssh_utils.get_ssh_key_path())
80
+
81
+ ssh_utils.clear_ssh_cache()
82
+
83
+ if ssh_utils.check_connection(ok_text="Login successful. You are connected.", fail_text="SSH key not recognised by GitHub."):
84
+ github_username = ssh_utils.get_github_username()
85
+ ensure_user_configure(default_email=email, default_username=github_username)
86
+ return True
87
+
88
+ error("Login Failed. The SSH key may not have been added to GitHub correctly.")
89
+ info("Possible causes:")
90
+ info(" 1. The key was not pasted on GitHub")
91
+
92
+ if get_platform() == "windows":
93
+ info(" 2. SSH agent is not running — run in PowerShell (as Administrator):")
94
+ info(" Set-Service ssh-agent -StartupType Automatic")
95
+ info(" Start-Service ssh-agent")
96
+ info(" Then run 'gitgo user login' again.")
97
+ else:
98
+ info(" 2. SSH agent is not running (try: eval $(ssh-agent) && ssh-add)")
99
+
100
+ info(" 3. Network or firewall is blocking SSH connections")
101
+ info("Need help? Full guide: https://github.com/Huerte/GitGo/blob/main/docs/login-guide.md")
102
+ return False
103
+
104
+
105
+ def logout():
106
+ key_path = ssh_utils.get_ssh_key_path()
107
+ if not key_path.exists():
108
+ warning("You are already logged out (no keys found).")
109
+ return False
110
+
111
+ try:
112
+
113
+ os.remove(key_path)
114
+
115
+ pub_key_path = str(key_path) + ".pub"
116
+ if os.path.exists(pub_key_path):
117
+ os.remove(pub_key_path)
118
+
119
+ try:
120
+ run_command(["git", "config", "--global", "--unset-all", "user.name"])
121
+ except GitCommandError:
122
+ pass
123
+
124
+ try:
125
+ run_command(["git", "config", "--global", "--unset-all", "user.email"])
126
+ except GitCommandError:
127
+ pass
128
+
129
+ success("User successfully logout")
130
+ return True
131
+
132
+ except Exception as e:
133
+ error(f"Failed to remove SSH keys\nCAUSE OF ERROR: {e}")
134
+ return False
@@ -1,14 +1,19 @@
1
1
  from pygitgo.exceptions import GitCommandError, GitGoError
2
2
  from pygitgo.utils.colors import info, success, warning
3
+ from pygitgo.utils.platform import get_platform
3
4
  from pygitgo.utils.executor import run_command
4
5
  from pathlib import Path
5
6
  import subprocess
7
+ import time
6
8
  import os
7
9
  import re
8
10
 
9
11
 
10
12
  SSH_TIMEOUT_SECONDS = 10
11
13
 
14
+ _cached_ssh_response = None
15
+ _cache_populated = False
16
+
12
17
 
13
18
  def ensure_github_known_host():
14
19
  known_hosts = Path.home() / ".ssh" / "known_hosts"
@@ -47,13 +52,50 @@ def _get_github_ssh_response():
47
52
  except (subprocess.TimeoutExpired, OSError):
48
53
  return None
49
54
 
50
- def check_connection():
55
+
56
+ def _get_cached_ssh_response():
57
+ global _cached_ssh_response, _cache_populated
58
+ if not _cache_populated:
59
+ _cached_ssh_response = _get_github_ssh_response()
60
+ _cache_populated = True
61
+ return _cached_ssh_response
62
+
63
+
64
+ def clear_ssh_cache():
65
+ global _cached_ssh_response, _cache_populated
66
+ _cached_ssh_response = None
67
+ _cache_populated = False
68
+
69
+
70
+ def check_connection(ok_text=None, fail_text=None):
71
+ from yaspin import yaspin
72
+ import sys
73
+
51
74
  ensure_github_known_host()
52
- output = _get_github_ssh_response()
53
- return output is not None and "successfully authenticated" in output
75
+
76
+ kwargs = {"text": ok_text or "Verifying GitHub connection..."}
77
+ if sys.stdout.isatty():
78
+ kwargs["color"] = "cyan"
79
+ spinner = yaspin(**kwargs)
80
+ spinner.start()
81
+
82
+ output = _get_cached_ssh_response()
83
+ connected = output is not None and "successfully authenticated" in output
84
+
85
+ if connected:
86
+ if ok_text:
87
+ spinner.text = ok_text
88
+ spinner.ok("✔")
89
+ else:
90
+ if fail_text:
91
+ spinner.text = fail_text
92
+ spinner.fail("✖")
93
+
94
+ return connected
95
+
54
96
 
55
97
  def get_github_username():
56
- output = _get_github_ssh_response()
98
+ output = _get_cached_ssh_response()
57
99
  if output and "Hi " in output and "!" in output:
58
100
  try:
59
101
  return output.split("Hi ")[1].split("!")[0]
@@ -92,10 +134,8 @@ def generate_ssh_key(email):
92
134
  "\nFailed to generate SSH key. Is 'ssh-keygen' installed on your system?\n"
93
135
  f"Details: {e}"
94
136
  )
95
- try:
96
- run_command(["ssh-add", str(key_path)])
97
- except (GitCommandError, OSError):
98
- pass
137
+
138
+ ensure_ssh_agent(key_path)
99
139
 
100
140
  return key_path
101
141
 
@@ -114,3 +154,42 @@ def convert_https_to_ssh(url):
114
154
 
115
155
  def is_ssh_url(url):
116
156
  return url.strip().startswith("git@")
157
+
158
+ def _try_ssh_add(key_path):
159
+ try:
160
+ run_command(["ssh-add", str(key_path)])
161
+ return True
162
+ except (GitCommandError, OSError):
163
+ return False
164
+
165
+ def ensure_ssh_agent(key_path):
166
+ if _try_ssh_add(key_path):
167
+ return True
168
+
169
+ if get_platform() == "windows":
170
+ try:
171
+ subprocess.run(
172
+ ["sc", "start", "ssh-agent"],
173
+ capture_output=True, timeout=5
174
+ )
175
+ except Exception:
176
+ pass
177
+
178
+ time.sleep(1)
179
+
180
+ if _try_ssh_add(key_path):
181
+ return True
182
+
183
+ warning("SSH agent is not running on this machine.")
184
+ info("To fix this permanently, run in PowerShell (as Administrator):")
185
+ info(" Set-Service ssh-agent -StartupType Automatic")
186
+ info(" Start-Service ssh-agent")
187
+ info("Then run 'gitgo user login' again.")
188
+
189
+ else:
190
+ warning("SSH agent is not running.")
191
+ info("Run: eval $(ssh-agent) && ssh-add")
192
+ info("Then run 'gitgo user login' again.")
193
+
194
+ return False
195
+
@@ -30,11 +30,11 @@ def is_branch_exist(branch):
30
30
  return bool(run_command(["git", "branch", "-r", "--list", f"*/{branch}"])) or bool(run_command(["git", "branch", "--list", branch]))
31
31
 
32
32
 
33
- def git_new_branch(branch):
33
+ def git_new_branch(branch, ok_text=None):
34
34
  try:
35
- run_command(["git", "checkout", "-b", branch], loading_msg=f"Creating branch '{branch}'...")
36
- print()
37
- success(f"Branch '{branch}' created.")
35
+ if not ok_text:
36
+ ok_text = f"Branch '{branch}' created."
37
+ run_command(["git", "checkout", "-b", branch], loading_msg=f"Creating branch '{branch}'...", ok_text=ok_text)
38
38
  except GitCommandError:
39
39
  try:
40
40
  current = get_current_branch()
@@ -1,6 +1,7 @@
1
1
  from pygitgo.auth.ssh_utils import convert_https_to_ssh, get_ssh_key_path, is_ssh_url, check_connection
2
2
  from pygitgo.utils.colors import info, success, warning, error
3
3
  from pygitgo.exceptions import GitGoError, GitCommandError
4
+ from pygitgo.auth.account import sanitize_signing_config
4
5
  from pygitgo.commands.git_remote import handle_rebase
5
6
  from pygitgo.utils.config import get_default_branch
6
7
  from pygitgo.utils.executor import run_command
@@ -18,13 +19,15 @@ def _get_signing_flags():
18
19
  ]
19
20
 
20
21
 
21
- def git_commit(commit_message, loading_msg="Commiting changes...", skip_staging=False):
22
+ def git_commit(commit_message, loading_msg="Commiting changes...", skip_staging=False, ok_text=None):
22
23
  try:
23
24
  status_result = run_command(["git", "status", "--porcelain"])
24
25
  if not status_result.strip():
25
26
  return False
26
27
  except GitCommandError:
27
28
  return False
29
+
30
+ sanitize_signing_config()
28
31
 
29
32
  if not skip_staging:
30
33
  run_command(["git", "add", "."], loading_msg="Staging files...")
@@ -33,29 +36,31 @@ def git_commit(commit_message, loading_msg="Commiting changes...", skip_staging=
33
36
 
34
37
  signing_flags = _get_signing_flags()
35
38
  commit_command = ["git"] + signing_flags + ["commit", "-S", "-m", clean_message]
36
- run_command(commit_command, loading_msg=loading_msg)
39
+ run_command(commit_command, loading_msg=loading_msg, ok_text=ok_text)
37
40
 
38
41
  return True
39
42
 
40
43
 
41
- def git_init():
44
+ def git_init(ok_text=None):
42
45
  if os.path.isdir(".git"):
43
46
  warning("Already a git repository! Skipping init...")
44
47
  return False
45
48
 
46
49
  default_main_branch = get_default_branch()
47
50
 
51
+ if not ok_text:
52
+ ok_text = "Git repository initialized."
53
+
48
54
  try:
49
- run_command(["git", "init", "-b", default_main_branch], loading_msg="Initializing git repository...")
55
+ run_command(["git", "init", "-b", default_main_branch], loading_msg="Initializing git repository...", ok_text=ok_text)
50
56
  except GitCommandError:
51
57
  run_command(["git", "init"], loading_msg="Initializing git repository...")
52
- run_command(["git", "checkout", "-b", default_main_branch])
58
+ run_command(["git", "checkout", "-b", default_main_branch], ok_text=ok_text)
53
59
 
54
- success("Git repository initialized.")
55
60
  return True
56
61
 
57
62
 
58
- def git_push(branch):
63
+ def git_push(branch, ok_text=None):
59
64
  try:
60
65
  remote_url = run_command(["git", "remote", "get-url", "origin"]).strip()
61
66
  except GitCommandError:
@@ -64,13 +69,11 @@ def git_push(branch):
64
69
  if remote_url and not is_ssh_url(remote_url) and check_connection():
65
70
  ssh_url = convert_https_to_ssh(remote_url)
66
71
  if ssh_url:
67
- run_command(["git", "remote", "set-url", "origin", ssh_url], loading_msg="Converting remote from HTTPS to SSH for secure push...")
68
- success(f"Remote updated to: {ssh_url}")
72
+ run_command(["git", "remote", "set-url", "origin", ssh_url], loading_msg="Converting remote from HTTPS to SSH for secure push...", ok_text=f"Remote updated to: {ssh_url}")
69
73
 
70
74
  try:
71
- run_command(["git", "push", "-u", "origin", branch], loading_msg=f"Pushing to remote branch '{branch}'...")
75
+ run_command(["git", "push", "-u", "origin", branch], loading_msg=f"Pushing to remote branch '{branch}'...", ok_text=ok_text, err_text="Push failed: verify your remote URL and SSH key, then try again.")
72
76
  except (GitCommandError, OSError) as e:
73
- error("Push failed — verify your remote URL and SSH key, then try again.")
74
77
  info("Run: git remote -v to inspect your current remote.")
75
78
  if "rebase in progress" in str(e):
76
79
  handle_rebase()
@@ -9,17 +9,16 @@ def add_remote_origin(repo_url):
9
9
  try:
10
10
  existing_remote = run_command(["git", "remote", "get-url", "origin"])
11
11
  warning(f"Remote origin already exists: {existing_remote}")
12
- run_command(["git", "remote", "set-url", "origin", clean_url], loading_msg="Updating remote URL...")
12
+ run_command(["git", "remote", "set-url", "origin", clean_url], loading_msg="Updating remote URL...", ok_text=f"Remote origin set to: {clean_url}")
13
13
  except GitCommandError:
14
- run_command(["git", "remote", "add", "origin", clean_url], loading_msg="Adding remote origin...")
14
+ run_command(["git", "remote", "add", "origin", clean_url], loading_msg="Adding remote origin...", ok_text=f"Remote origin set to: {clean_url}")
15
15
 
16
- success(f"Remote origin set to: {clean_url}")
17
16
 
18
-
19
- def confirm_remote_link():
17
+ def confirm_remote_link(ok_text=None):
20
18
  try:
21
- run_command(["git", "ls-remote", "origin"], loading_msg="Testing connection to remote...")
22
- success("Remote is reachable.")
19
+ if not ok_text:
20
+ ok_text = "Remote is reachable."
21
+ run_command(["git", "ls-remote", "origin"], loading_msg="Testing connection to remote...", ok_text=ok_text)
23
22
  return True
24
23
  except GitCommandError:
25
24
  error("Connection failed — verify the URL and your SSH key.")
@@ -44,14 +43,13 @@ def check_and_sync_branch(branch):
44
43
  )
45
44
  if behind_check and int(behind_check) > 0:
46
45
  warning(f"Local branch is behind remote by {behind_check} commit(s). Pulling changes...")
47
- output = run_command(["git", "pull", "--rebase", "origin", branch], loading_msg="Pulling changes from remote...")
46
+ output = run_command(["git", "pull", "--rebase", "origin", branch], loading_msg="Pulling changes from remote...", ok_text="Synced with remote.")
48
47
  if output:
49
48
  print(output)
50
- success("Synced with remote.")
51
49
  else:
52
- success("Branch is up to date.")
50
+ info("Branch is up to date.")
53
51
  else:
54
- success("Branch is already up to date.")
52
+ info("Branch is already up to date.")
55
53
  except (GitCommandError, ValueError):
56
54
  warning("Remote branch doesn't exist yet. First push will create it.")
57
55
  except (GitCommandError, OSError):
@@ -172,19 +172,31 @@ def _parse_template_slug(template):
172
172
 
173
173
 
174
174
  def _download_and_extract_template(template_slug, target_dir):
175
+ from yaspin import yaspin
176
+ import sys
175
177
  url = f"https://api.github.com/repos/{template_slug}/zipball"
176
178
  req = urllib.request.Request(url, headers={"User-Agent": "GitGo-CLI"})
177
- info(f"Downloading template from GitHub: {template_slug}...")
179
+ kwargs = {"text": f"Downloading template from GitHub: {template_slug}..."}
180
+ if sys.stdout.isatty():
181
+ kwargs["color"] = "cyan"
182
+ spinner = yaspin(**kwargs)
183
+ spinner.start()
178
184
  try:
179
185
  with urllib.request.urlopen(req, timeout=30) as response:
180
186
  zip_data = response.read()
181
187
  except urllib.error.HTTPError as e:
182
188
  if e.code == 404:
189
+ spinner.text = f"Template '{template_slug}' not found on GitHub."
190
+ spinner.fail("✖")
183
191
  raise GitGoError(
184
192
  f"Template repository '{template_slug}' not found on GitHub."
185
193
  )
194
+ spinner.text = f"Failed to download template: HTTP {e.code}"
195
+ spinner.fail("✖")
186
196
  raise GitGoError(f"Failed to download template: HTTP {e.code}")
187
197
  except Exception as e:
198
+ spinner.text = f"Network error downloading template: {e}"
199
+ spinner.fail("✖")
188
200
  raise GitGoError(f"Network error downloading template: {e}")
189
201
 
190
202
  try:
@@ -207,8 +219,11 @@ def _download_and_extract_template(template_slug, target_dir):
207
219
  os.makedirs(os.path.dirname(dest), exist_ok=True)
208
220
  with zf.open(member) as src, open(dest, "wb") as out:
209
221
  out.write(src.read())
210
- success("Template extracted successfully.")
222
+ spinner.text = "Template extracted successfully."
223
+ spinner.ok("✔")
211
224
  except Exception as e:
225
+ spinner.text = f"Failed to extract template: {e}"
226
+ spinner.fail("✖")
212
227
  raise GitGoError(f"Failed to extract template: {e}")
213
228
 
214
229
 
@@ -294,9 +309,8 @@ def init_operation(args):
294
309
  _scaffold_language(args.lang, target_dir, target_dir)
295
310
 
296
311
  os.chdir(target_dir)
297
- git_init()
312
+ git_init(ok_text=f"Initialized empty Git repository in {os.path.abspath('.')}")
298
313
 
299
- success(f"\nInitialized empty Git repository in {os.path.abspath('.')}")
300
314
  info("Next step: Create a remote repo with 'gitgo new <name>'")
301
315
 
302
316
  except Exception as e:
@@ -14,22 +14,20 @@ def undo_jump_operation(original_branch, stashed_code, created_branch=None):
14
14
  if created_branch:
15
15
  run_command(["git", "checkout", original_branch], loading_msg=f"Jumping you back to the original branch '{original_branch}'...")
16
16
  try:
17
- run_command(["git", "branch", "-D", created_branch], loading_msg=f"Removing the empty branch '{created_branch}'...")
17
+ ok_text = None if stashed_code else f"Canceled safely. Back on '{original_branch}'."
18
+ run_command(["git", "branch", "-D", created_branch], loading_msg=f"Removing the empty branch '{created_branch}'...", ok_text=ok_text)
18
19
  except GitCommandError:
19
20
  warning(f"Could not delete branch '{created_branch}'. Remove it manually with: git branch -D {created_branch}")
20
21
  else:
21
22
  run_command(["git", "reset", "--hard", "HEAD"], loading_msg="Canceling... Putting your files back exactly how they were...")
22
- run_command(['git', 'checkout', original_branch], loading_msg=f"Jumping you back to the original branch '{original_branch}'...")
23
+ ok_text = None if stashed_code else f"Canceled safely. Back on '{original_branch}'."
24
+ run_command(['git', 'checkout', original_branch], loading_msg=f"Jumping you back to the original branch '{original_branch}'...", ok_text=ok_text)
23
25
 
24
26
  if stashed_code:
25
- pop_result = git_stash_pop()
27
+ pop_result = git_stash_pop(ok_text=f"Canceled safely. Back on '{original_branch}'. Your code is safe.")
26
28
  if not pop_result:
27
29
  warning("Could not restore your unsaved changes automatically. Run 'gitgo state list' to recover them.")
28
30
 
29
- print()
30
- success(f"Canceled safely.")
31
- success(f"Back on '{original_branch}'. Your code is safe.")
32
-
33
31
 
34
32
  def _jump_interrupt_cleanup(original_branch, stashed_code, created_branch):
35
33
  try:
@@ -52,10 +50,8 @@ def _jump_interrupt_cleanup(original_branch, stashed_code, created_branch):
52
50
  if stashed_code:
53
51
  warning("Then restore your stash: git stash pop")
54
52
  elif stashed_code:
55
- pop_result = git_stash_pop()
56
- if pop_result:
57
- success("Your stashed changes have been restored.")
58
- else:
53
+ pop_result = git_stash_pop(ok_text="Your stashed changes have been restored.")
54
+ if not pop_result:
59
55
  warning("Could not restore stash automatically. Run 'gitgo state list' to find it.")
60
56
  else:
61
57
  success("No git state was changed. Your files are safe.")
@@ -107,18 +103,26 @@ def jump_operation(args):
107
103
  warning("Could not restore your unsaved changes automatically. Run 'gitgo state list' to recover them.")
108
104
  return
109
105
 
110
- git_new_branch(target_branch)
106
+ ok_text = f"Branch '{target_branch}' created." if stashed_code else f"On '{target_branch}'."
107
+ git_new_branch(target_branch, ok_text=ok_text)
111
108
  created_branch = target_branch
112
109
  else:
113
- run_command(['git', 'checkout', target_branch], loading_msg=f"Moving you to branch '{target_branch}'...")
114
-
115
- main_branch = get_main_branch()
116
-
117
- try:
118
- run_command(['git', 'pull', 'origin', main_branch], loading_msg=f"Downloading the latest updates from '{main_branch}'...")
119
- except GitCommandError:
120
- warning(f"Failed to pull updates from '{main_branch}'. No internet, or the remote branch doesn't exist yet.")
121
- info(f"On '{target_branch}', but without the latest updates from '{main_branch}'.")
110
+ if stashed_code:
111
+ run_command(['git', 'checkout', target_branch], loading_msg=f"Moving you to branch '{target_branch}'...", ok_text=f"Moved to branch '{target_branch}'.")
112
+ main_branch = get_main_branch()
113
+ try:
114
+ run_command(['git', 'pull', 'origin', main_branch], loading_msg=f"Downloading the latest updates from '{main_branch}'...", ok_text=f"Downloaded updates from '{main_branch}'.")
115
+ except GitCommandError:
116
+ warning(f"Failed to pull updates from '{main_branch}'. No internet, or the remote branch doesn't exist yet.")
117
+ info(f"On '{target_branch}', but without the latest updates from '{main_branch}'.")
118
+ else:
119
+ run_command(['git', 'checkout', target_branch], loading_msg=f"Moving you to branch '{target_branch}'...", ok_text=f"Moved to branch '{target_branch}'.")
120
+ main_branch = get_main_branch()
121
+ try:
122
+ run_command(['git', 'pull', 'origin', main_branch], loading_msg=f"Downloading the latest updates from '{main_branch}'...", ok_text=f"On '{target_branch}'. Up to date with '{main_branch}'.")
123
+ except GitCommandError:
124
+ warning(f"Failed to pull updates from '{main_branch}'. No internet, or the remote branch doesn't exist yet.")
125
+ info(f"On '{target_branch}', but without the latest updates from '{main_branch}'.")
122
126
 
123
127
  if stashed_code:
124
128
  apply_result = git_stash_apply(loading_msg="Unpacking your unsaved changes...")
@@ -141,15 +145,11 @@ def jump_operation(args):
141
145
  info("Your stash backup is still saved. Run 'gitgo state list' to see it.")
142
146
  return
143
147
  else:
144
- drop_result = git_stash_drop(loading_msg="Cleaning up the temporary stash...")
148
+ drop_result = git_stash_drop(loading_msg="Cleaning up the temporary stash...", ok_text=f"On '{target_branch}'. Your changes came with you.")
145
149
  if not drop_result:
146
150
  warning("Could not clean up the temporary stash. Run 'gitgo state list' to remove it manually.")
147
- print()
148
- success(f"On '{target_branch}'. Your changes came with you.")
149
151
  return
150
152
  else:
151
- print()
152
- success(f"On '{target_branch}'.")
153
153
  return
154
154
 
155
155
  except KeyboardInterrupt:
@@ -55,15 +55,13 @@ def link_core(repo_url, commit_message="Initial commit", silent=False, already_i
55
55
  add_remote_origin(repo_url)
56
56
  remote_added = True
57
57
 
58
- if confirm_remote_link():
59
- success("\nRemote linked to existing repository.")
60
- success(f"Ready to push with: gitgo push <branch> 'your message'\n")
58
+ if confirm_remote_link(ok_text="Remote linked to existing repository."):
59
+ info("Ready to push with: gitgo push <branch> 'your message'")
61
60
  return
62
61
 
63
- commit_made = git_commit(commit_message, loading_msg="Creating initial commit...")
62
+ commit_made = git_commit(commit_message, loading_msg="Creating initial commit...", ok_text="Initial commit created.")
64
63
  if commit_made:
65
64
  committed = True
66
- success("Initial commit created.")
67
65
 
68
66
  add_remote_origin(repo_url)
69
67
  remote_added = True
@@ -87,9 +85,9 @@ def link_core(repo_url, commit_message="Initial commit", silent=False, already_i
87
85
  try:
88
86
  run_command(
89
87
  ["git", "pull", "origin", main_branch, "--allow-unrelated-histories", "--no-edit"],
90
- loading_msg="Pulling and merging remote content..."
88
+ loading_msg="Pulling and merging remote content...",
89
+ ok_text="Remote content merged."
91
90
  )
92
- success("Remote content merged.")
93
91
  except GitCommandError:
94
92
  error("Failed to merge remote content. You may need to resolve conflicts manually.")
95
93
  warning(f"Run: git pull origin {main_branch} --allow-unrelated-histories")
@@ -15,8 +15,7 @@ def _pull_interrupt_cleanup():
15
15
  if rebase_in_progress:
16
16
  warning("A rebase is in progress from the interrupted pull.")
17
17
  try:
18
- run_command(["git", "rebase", "--abort"], loading_msg="Aborting interrupted rebase...")
19
- success("Rebase aborted. Branch is back to its pre-pull state.")
18
+ run_command(["git", "rebase", "--abort"], loading_msg="Aborting interrupted rebase...", ok_text="Rebase aborted. Branch is back to its pre-pull state.")
20
19
  except GitCommandError:
21
20
  error("Could not abort rebase automatically.")
22
21
  info("Run manually: git rebase --abort")
@@ -49,11 +48,10 @@ def pull_operation(args):
49
48
 
50
49
  run_command(
51
50
  ["git", "pull", "--rebase", "--autostash", "origin", branch],
52
- loading_msg=f"Downloading latest updates for '{branch}' (auto-saving your code)..."
51
+ loading_msg=f"Downloading latest updates for '{branch}' (auto-saving your code)...",
52
+ ok_text=f"Project is up to date with '{branch}'."
53
53
  )
54
54
 
55
- success(f"Project is up to date with '{branch}'.")
56
-
57
55
  except KeyboardInterrupt:
58
56
  print()
59
57
  warning("Pull interrupted (Ctrl+C).")