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.
- {pygitgo-1.8.0/src/pygitgo.egg-info → pygitgo-1.8.1}/PKG-INFO +1 -1
- {pygitgo-1.8.0 → pygitgo-1.8.1}/pyproject.toml +1 -1
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/auth/account.py +26 -0
- pygitgo-1.8.1/src/pygitgo/auth/manager.py +134 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/auth/ssh_utils.py +87 -8
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/git_branch.py +4 -4
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/git_core.py +14 -11
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/git_remote.py +9 -11
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/init.py +18 -4
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/jump.py +26 -26
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/link.py +5 -7
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/pull.py +3 -5
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/repo.py +24 -9
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/stash.py +14 -12
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/undo.py +4 -6
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/exceptions.py +0 -1
- pygitgo-1.8.1/src/pygitgo/utils/bootstrap.py +15 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/utils/executor.py +10 -2
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/utils/validators.py +1 -1
- {pygitgo-1.8.0 → pygitgo-1.8.1/src/pygitgo.egg-info}/PKG-INFO +1 -1
- {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_account.py +49 -1
- pygitgo-1.8.1/tests/test_bootstrap.py +29 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_config_command.py +1 -1
- {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_config_utils.py +0 -5
- {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_git_branch.py +6 -5
- {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_git_core.py +22 -17
- {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_git_remote.py +13 -18
- {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_jump.py +17 -17
- {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_link.py +6 -5
- pygitgo-1.8.1/tests/test_manager.py +146 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_pull.py +8 -7
- {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_push.py +1 -2
- {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_repo.py +0 -2
- {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_ssh_utils.py +69 -2
- {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_stash.py +16 -8
- {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_undo.py +4 -9
- pygitgo-1.8.0/src/pygitgo/auth/manager.py +0 -101
- pygitgo-1.8.0/src/pygitgo/utils/bootstrap.py +0 -26
- pygitgo-1.8.0/tests/test_bootstrap.py +0 -54
- pygitgo-1.8.0/tests/test_manager.py +0 -79
- {pygitgo-1.8.0 → pygitgo-1.8.1}/LICENSE +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/README.md +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/setup.cfg +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/__init__.py +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/auth/__init__.py +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/__init__.py +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/config.py +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/new.py +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/push.py +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/staging.py +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/state.py +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/commands/user.py +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/main.py +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/utils/__init__.py +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/utils/colors.py +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/utils/config.py +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/utils/platform.py +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo/utils/update_checker.py +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo.egg-info/SOURCES.txt +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo.egg-info/dependency_links.txt +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo.egg-info/entry_points.txt +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo.egg-info/requires.txt +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/src/pygitgo.egg-info/top_level.txt +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_init.py +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_new.py +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_platform.py +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_staging.py +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_state.py +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_update_checker.py +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_user.py +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.1}/tests/test_validators.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pygitgo"
|
|
7
|
-
version = "1.8.
|
|
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
|
-
|
|
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
|
-
|
|
53
|
-
|
|
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 =
|
|
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
|
-
|
|
96
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
22
|
-
|
|
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
|
-
|
|
50
|
+
info("Branch is up to date.")
|
|
53
51
|
else:
|
|
54
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
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).")
|