pygitgo 1.8.0__tar.gz → 1.8.2__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.2}/PKG-INFO +1 -1
- {pygitgo-1.8.0 → pygitgo-1.8.2}/pyproject.toml +1 -1
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo/auth/account.py +29 -2
- pygitgo-1.8.2/src/pygitgo/auth/manager.py +132 -0
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo/auth/ssh_utils.py +91 -9
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo/commands/config.py +4 -6
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo/commands/git_branch.py +7 -8
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo/commands/git_core.py +20 -13
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo/commands/git_remote.py +12 -18
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo/commands/init.py +24 -7
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo/commands/jump.py +40 -44
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo/commands/link.py +31 -29
- pygitgo-1.8.2/src/pygitgo/commands/new.py +66 -0
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo/commands/pull.py +12 -6
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo/commands/push.py +9 -6
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo/commands/repo.py +65 -12
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo/commands/staging.py +1 -1
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo/commands/stash.py +26 -12
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo/commands/state.py +5 -4
- pygitgo-1.8.2/src/pygitgo/commands/undo.py +117 -0
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo/commands/user.py +3 -2
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo/exceptions.py +0 -1
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo/main.py +2 -2
- pygitgo-1.8.2/src/pygitgo/utils/bootstrap.py +15 -0
- pygitgo-1.8.0/src/pygitgo/utils/colors.py → pygitgo-1.8.2/src/pygitgo/utils/cli_io.py +16 -15
- pygitgo-1.8.2/src/pygitgo/utils/colors.py +6 -0
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo/utils/config.py +1 -1
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo/utils/executor.py +16 -10
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo/utils/platform.py +3 -3
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo/utils/update_checker.py +5 -5
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo/utils/validators.py +1 -1
- {pygitgo-1.8.0 → pygitgo-1.8.2/src/pygitgo.egg-info}/PKG-INFO +1 -1
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo.egg-info/SOURCES.txt +5 -0
- {pygitgo-1.8.0 → pygitgo-1.8.2}/tests/test_account.py +49 -2
- pygitgo-1.8.2/tests/test_bootstrap.py +29 -0
- pygitgo-1.8.2/tests/test_cli_io.py +51 -0
- {pygitgo-1.8.0 → pygitgo-1.8.2}/tests/test_config_command.py +8 -12
- {pygitgo-1.8.0 → pygitgo-1.8.2}/tests/test_config_utils.py +0 -5
- pygitgo-1.8.2/tests/test_executor.py +92 -0
- {pygitgo-1.8.0 → pygitgo-1.8.2}/tests/test_git_branch.py +8 -8
- {pygitgo-1.8.0 → pygitgo-1.8.2}/tests/test_git_core.py +22 -19
- {pygitgo-1.8.0 → pygitgo-1.8.2}/tests/test_git_remote.py +26 -27
- {pygitgo-1.8.0 → pygitgo-1.8.2}/tests/test_init.py +0 -1
- pygitgo-1.8.2/tests/test_integration_git.py +73 -0
- {pygitgo-1.8.0 → pygitgo-1.8.2}/tests/test_jump.py +70 -20
- {pygitgo-1.8.0 → pygitgo-1.8.2}/tests/test_link.py +102 -12
- pygitgo-1.8.2/tests/test_main.py +116 -0
- pygitgo-1.8.2/tests/test_manager.py +145 -0
- pygitgo-1.8.2/tests/test_new.py +90 -0
- {pygitgo-1.8.0 → pygitgo-1.8.2}/tests/test_platform.py +63 -62
- {pygitgo-1.8.0 → pygitgo-1.8.2}/tests/test_pull.py +23 -12
- {pygitgo-1.8.0 → pygitgo-1.8.2}/tests/test_push.py +21 -8
- {pygitgo-1.8.0 → pygitgo-1.8.2}/tests/test_repo.py +0 -3
- pygitgo-1.8.2/tests/test_ssh_utils.py +220 -0
- {pygitgo-1.8.0 → pygitgo-1.8.2}/tests/test_staging.py +2 -4
- {pygitgo-1.8.0 → pygitgo-1.8.2}/tests/test_stash.py +16 -9
- {pygitgo-1.8.0 → pygitgo-1.8.2}/tests/test_state.py +83 -0
- {pygitgo-1.8.0 → pygitgo-1.8.2}/tests/test_undo.py +75 -47
- {pygitgo-1.8.0 → pygitgo-1.8.2}/tests/test_update_checker.py +2 -3
- pygitgo-1.8.0/src/pygitgo/auth/manager.py +0 -101
- pygitgo-1.8.0/src/pygitgo/commands/new.py +0 -21
- pygitgo-1.8.0/src/pygitgo/commands/undo.py +0 -114
- 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/tests/test_new.py +0 -27
- pygitgo-1.8.0/tests/test_ssh_utils.py +0 -119
- {pygitgo-1.8.0 → pygitgo-1.8.2}/LICENSE +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.2}/README.md +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.2}/setup.cfg +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo/__init__.py +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo/auth/__init__.py +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo/commands/__init__.py +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo/utils/__init__.py +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo.egg-info/dependency_links.txt +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo.egg-info/entry_points.txt +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo.egg-info/requires.txt +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.2}/src/pygitgo.egg-info/top_level.txt +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.2}/tests/test_user.py +0 -0
- {pygitgo-1.8.0 → pygitgo-1.8.2}/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.2"
|
|
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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
from pygitgo.utils.
|
|
1
|
+
from pygitgo.utils.cli_io import info, success, warning, error
|
|
2
2
|
from pygitgo.utils.executor import run_command
|
|
3
3
|
from pygitgo.exceptions import GitCommandError
|
|
4
|
+
from pygitgo.utils.colors import BLUE, RESET
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
def get_user():
|
|
@@ -24,7 +25,7 @@ def get_user():
|
|
|
24
25
|
def set_user(name, email):
|
|
25
26
|
run_command(["git", "config", "--global", "user.name", name])
|
|
26
27
|
run_command(["git", "config", "--global", "user.email", email])
|
|
27
|
-
success(
|
|
28
|
+
success("\nGit user configured successfully.")
|
|
28
29
|
print(f"{BLUE}Username{RESET} : {name}")
|
|
29
30
|
print(f"{BLUE}Email {RESET}: {email}")
|
|
30
31
|
|
|
@@ -65,3 +66,29 @@ def ensure_user_configure(default_email=None, default_username=None):
|
|
|
65
66
|
error("Invalid configuration. Name and Email are required.")
|
|
66
67
|
return False
|
|
67
68
|
|
|
69
|
+
def sanitize_signing_config():
|
|
70
|
+
try:
|
|
71
|
+
gpgsign = run_command(["git", "config", "--global", "commit.gpgsign"]).strip().lower()
|
|
72
|
+
except GitCommandError:
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
if gpgsign != "true":
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
fmt = run_command(["git", "config", "--global", "gpg.format"]).strip().lower()
|
|
80
|
+
except GitCommandError:
|
|
81
|
+
fmt = ""
|
|
82
|
+
|
|
83
|
+
if fmt == "ssh":
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
warning("Detected 'commit.gpgsign=true' with no GPG key configured.")
|
|
87
|
+
warning("Disabling global GPG signing to prevent commit failures.")
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
run_command(["git", "config", "--global", "--unset", "gpg.program"])
|
|
91
|
+
except GitCommandError:
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
from pygitgo.utils.cli_io 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, fail_text="Not connected to GitHub."):
|
|
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 entries.")
|
|
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.clear_ssh_cache()
|
|
80
|
+
|
|
81
|
+
if ssh_utils.check_connection(ok_text="Login successful. You are connected.", fail_text="SSH key not recognised by GitHub."):
|
|
82
|
+
github_username = ssh_utils.get_github_username()
|
|
83
|
+
ensure_user_configure(default_email=email, default_username=github_username)
|
|
84
|
+
return True
|
|
85
|
+
|
|
86
|
+
error("Login Failed. The SSH key may not have been added to GitHub correctly.")
|
|
87
|
+
info("Possible causes:")
|
|
88
|
+
info(" 1. The key was not pasted on GitHub")
|
|
89
|
+
|
|
90
|
+
if get_platform() == "windows":
|
|
91
|
+
info(" 2. SSH agent is not running — run in PowerShell (as Administrator):")
|
|
92
|
+
info(" Set-Service ssh-agent -StartupType Automatic")
|
|
93
|
+
info(" Start-Service ssh-agent")
|
|
94
|
+
info(" Then run 'gitgo user login' again.")
|
|
95
|
+
else:
|
|
96
|
+
info(" 2. SSH agent is not running (try: eval $(ssh-agent) && ssh-add)")
|
|
97
|
+
|
|
98
|
+
info(" 3. Network or firewall is blocking SSH connections")
|
|
99
|
+
info("Need help? Full guide: https://github.com/Huerte/GitGo/blob/main/docs/login-guide.md")
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def logout():
|
|
104
|
+
key_path = ssh_utils.get_ssh_key_path()
|
|
105
|
+
if not key_path.exists():
|
|
106
|
+
warning("You are already logged out (no keys found).")
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
|
|
111
|
+
os.remove(key_path)
|
|
112
|
+
|
|
113
|
+
pub_key_path = str(key_path) + ".pub"
|
|
114
|
+
if os.path.exists(pub_key_path):
|
|
115
|
+
os.remove(pub_key_path)
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
run_command(["git", "config", "--global", "--unset-all", "user.name"], loading_msg="Clearing Git username...", ok_text="Git username cleared.")
|
|
119
|
+
except GitCommandError:
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
run_command(["git", "config", "--global", "--unset-all", "user.email"], loading_msg="Clearing Git email...", ok_text="Git email cleared.")
|
|
124
|
+
except GitCommandError:
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
success("User successfully logged out.")
|
|
128
|
+
return True
|
|
129
|
+
|
|
130
|
+
except Exception as e:
|
|
131
|
+
error(f"Failed to remove SSH keys\nCAUSE OF ERROR: {e}")
|
|
132
|
+
return False
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
from pygitgo.exceptions import GitCommandError, GitGoError
|
|
2
|
-
from pygitgo.utils.
|
|
2
|
+
from pygitgo.utils.cli_io 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]
|
|
@@ -73,6 +115,9 @@ def generate_ssh_key(email):
|
|
|
73
115
|
key_path.parent.mkdir(parents=True)
|
|
74
116
|
|
|
75
117
|
if key_path.exists():
|
|
118
|
+
from pygitgo.utils.cli_io import confirm
|
|
119
|
+
if not confirm(f"SSH key {key_path} already exists. Overwrite it? (y/n): ", destructive=True):
|
|
120
|
+
raise GitGoError("SSH key generation aborted. Please back up your existing key or configure it manually.")
|
|
76
121
|
os.remove(key_path)
|
|
77
122
|
if (key_path.parent / f"{key_path.name}.pub").exists():
|
|
78
123
|
os.remove(key_path.parent / f"{key_path.name}.pub")
|
|
@@ -92,10 +137,8 @@ def generate_ssh_key(email):
|
|
|
92
137
|
"\nFailed to generate SSH key. Is 'ssh-keygen' installed on your system?\n"
|
|
93
138
|
f"Details: {e}"
|
|
94
139
|
)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
except (GitCommandError, OSError):
|
|
98
|
-
pass
|
|
140
|
+
|
|
141
|
+
ensure_ssh_agent(key_path, quiet=True)
|
|
99
142
|
|
|
100
143
|
return key_path
|
|
101
144
|
|
|
@@ -114,3 +157,42 @@ def convert_https_to_ssh(url):
|
|
|
114
157
|
|
|
115
158
|
def is_ssh_url(url):
|
|
116
159
|
return url.strip().startswith("git@")
|
|
160
|
+
|
|
161
|
+
def _try_ssh_add(key_path):
|
|
162
|
+
try:
|
|
163
|
+
run_command(["ssh-add", str(key_path)])
|
|
164
|
+
return True
|
|
165
|
+
except (GitCommandError, OSError):
|
|
166
|
+
return False
|
|
167
|
+
|
|
168
|
+
def ensure_ssh_agent(key_path, quiet=False):
|
|
169
|
+
if _try_ssh_add(key_path):
|
|
170
|
+
return True
|
|
171
|
+
|
|
172
|
+
if get_platform() == "windows":
|
|
173
|
+
try:
|
|
174
|
+
subprocess.run(
|
|
175
|
+
["sc", "start", "ssh-agent"],
|
|
176
|
+
capture_output=True, timeout=5
|
|
177
|
+
)
|
|
178
|
+
except Exception:
|
|
179
|
+
pass
|
|
180
|
+
|
|
181
|
+
time.sleep(1)
|
|
182
|
+
|
|
183
|
+
if _try_ssh_add(key_path):
|
|
184
|
+
return True
|
|
185
|
+
|
|
186
|
+
if not quiet:
|
|
187
|
+
warning("SSH agent is not running. Key may not persist across sessions.")
|
|
188
|
+
info("To fix this permanently, run in PowerShell (as Administrator):")
|
|
189
|
+
info(" Set-Service ssh-agent -StartupType Automatic")
|
|
190
|
+
info(" Start-Service ssh-agent")
|
|
191
|
+
|
|
192
|
+
else:
|
|
193
|
+
if not quiet:
|
|
194
|
+
warning("SSH agent is not running. Key may not persist across sessions.")
|
|
195
|
+
info("Run: eval $(ssh-agent) && ssh-add")
|
|
196
|
+
|
|
197
|
+
return False
|
|
198
|
+
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from pygitgo.utils.config import get_config, set_config
|
|
2
|
-
from pygitgo.utils.
|
|
2
|
+
from pygitgo.utils.cli_io import warning, info
|
|
3
|
+
from pygitgo.exceptions import GitGoError
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
def config_operation(args):
|
|
@@ -10,14 +11,11 @@ def config_operation(args):
|
|
|
10
11
|
|
|
11
12
|
VALID_KEYS = ["default-branch", "default-message"]
|
|
12
13
|
if key not in VALID_KEYS:
|
|
13
|
-
|
|
14
|
-
warning(f"Valid keys are: {', '.join(VALID_KEYS)}\n")
|
|
15
|
-
return
|
|
14
|
+
raise GitGoError(f"Invalid configuration key: '{key}'. Valid keys are: {', '.join(VALID_KEYS)}")
|
|
16
15
|
|
|
17
16
|
if action == 'set':
|
|
18
17
|
if not value:
|
|
19
|
-
|
|
20
|
-
return
|
|
18
|
+
raise GitGoError("You must provide a value to set!")
|
|
21
19
|
set_config(key, value)
|
|
22
20
|
elif action == 'get':
|
|
23
21
|
current_value = get_config(key, None)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from pygitgo.utils.
|
|
1
|
+
from pygitgo.utils.cli_io import success, error, info, confirm
|
|
2
2
|
from pygitgo.exceptions import GitCommandError, GitGoError
|
|
3
3
|
from pygitgo.utils.executor import run_command
|
|
4
4
|
from pygitgo.utils.config import get_config
|
|
@@ -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()
|
|
@@ -44,9 +44,8 @@ def git_new_branch(branch):
|
|
|
44
44
|
if current == branch:
|
|
45
45
|
info(f"Already on branch '{branch}'. Continuing...")
|
|
46
46
|
else:
|
|
47
|
-
error(f"Failed to create branch '{branch}'
|
|
48
|
-
|
|
49
|
-
if choice == "y":
|
|
47
|
+
error(f"Failed to create branch '{branch}'. It may already exist.")
|
|
48
|
+
if confirm(f"Switch to the existing branch '{branch}' instead? (y/n): "):
|
|
50
49
|
from pygitgo.commands.jump import jump_operation
|
|
51
50
|
jump_operation(Namespace(branch=branch))
|
|
52
51
|
else:
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from pygitgo.auth.ssh_utils import convert_https_to_ssh, get_ssh_key_path, is_ssh_url, check_connection
|
|
2
|
-
from pygitgo.utils.colors import info, success, warning, error
|
|
3
2
|
from pygitgo.exceptions import GitGoError, GitCommandError
|
|
3
|
+
from pygitgo.auth.account import sanitize_signing_config
|
|
4
4
|
from pygitgo.commands.git_remote import handle_rebase
|
|
5
5
|
from pygitgo.utils.config import get_default_branch
|
|
6
6
|
from pygitgo.utils.executor import run_command
|
|
7
|
+
from pygitgo.utils.cli_io import info, warning
|
|
7
8
|
import os
|
|
8
9
|
|
|
9
10
|
|
|
@@ -18,44 +19,52 @@ def _get_signing_flags():
|
|
|
18
19
|
]
|
|
19
20
|
|
|
20
21
|
|
|
21
|
-
def git_commit(commit_message, loading_msg="
|
|
22
|
+
def git_commit(commit_message, loading_msg="Committing changes...", skip_staging=False, ok_text=None):
|
|
23
|
+
if not ok_text:
|
|
24
|
+
ok_text = "Changes committed."
|
|
22
25
|
try:
|
|
23
26
|
status_result = run_command(["git", "status", "--porcelain"])
|
|
24
27
|
if not status_result.strip():
|
|
25
28
|
return False
|
|
26
29
|
except GitCommandError:
|
|
27
30
|
return False
|
|
31
|
+
|
|
32
|
+
sanitize_signing_config()
|
|
28
33
|
|
|
29
34
|
if not skip_staging:
|
|
30
|
-
run_command(["git", "add", "."], loading_msg="Staging files...")
|
|
35
|
+
run_command(["git", "add", "."], loading_msg="Staging files...", ok_text="Files staged.")
|
|
31
36
|
|
|
32
37
|
clean_message = commit_message.strip('"\'')
|
|
33
38
|
|
|
34
39
|
signing_flags = _get_signing_flags()
|
|
35
40
|
commit_command = ["git"] + signing_flags + ["commit", "-S", "-m", clean_message]
|
|
36
|
-
run_command(commit_command, loading_msg=loading_msg)
|
|
41
|
+
run_command(commit_command, loading_msg=loading_msg, ok_text=ok_text)
|
|
37
42
|
|
|
38
43
|
return True
|
|
39
44
|
|
|
40
45
|
|
|
41
|
-
def git_init():
|
|
46
|
+
def git_init(ok_text=None):
|
|
42
47
|
if os.path.isdir(".git"):
|
|
43
48
|
warning("Already a git repository! Skipping init...")
|
|
44
49
|
return False
|
|
45
50
|
|
|
46
51
|
default_main_branch = get_default_branch()
|
|
47
52
|
|
|
53
|
+
if not ok_text:
|
|
54
|
+
ok_text = "Git repository initialized."
|
|
55
|
+
|
|
48
56
|
try:
|
|
49
|
-
run_command(["git", "init", "-b", default_main_branch], loading_msg="Initializing git repository...")
|
|
57
|
+
run_command(["git", "init", "-b", default_main_branch], loading_msg="Initializing git repository...", ok_text=ok_text)
|
|
50
58
|
except GitCommandError:
|
|
51
59
|
run_command(["git", "init"], loading_msg="Initializing git repository...")
|
|
52
|
-
run_command(["git", "checkout", "-b", default_main_branch])
|
|
60
|
+
run_command(["git", "checkout", "-b", default_main_branch], ok_text=ok_text)
|
|
53
61
|
|
|
54
|
-
success("Git repository initialized.")
|
|
55
62
|
return True
|
|
56
63
|
|
|
57
64
|
|
|
58
|
-
def git_push(branch):
|
|
65
|
+
def git_push(branch, ok_text=None):
|
|
66
|
+
if not ok_text:
|
|
67
|
+
ok_text = f"Pushed to remote branch '{branch}'."
|
|
59
68
|
try:
|
|
60
69
|
remote_url = run_command(["git", "remote", "get-url", "origin"]).strip()
|
|
61
70
|
except GitCommandError:
|
|
@@ -64,13 +73,11 @@ def git_push(branch):
|
|
|
64
73
|
if remote_url and not is_ssh_url(remote_url) and check_connection():
|
|
65
74
|
ssh_url = convert_https_to_ssh(remote_url)
|
|
66
75
|
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}")
|
|
76
|
+
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
77
|
|
|
70
78
|
try:
|
|
71
|
-
run_command(["git", "push", "-u", "origin", branch], loading_msg=f"Pushing to remote branch '{branch}'...")
|
|
79
|
+
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
80
|
except (GitCommandError, OSError) as e:
|
|
73
|
-
error("Push failed — verify your remote URL and SSH key, then try again.")
|
|
74
81
|
info("Run: git remote -v to inspect your current remote.")
|
|
75
82
|
if "rebase in progress" in str(e):
|
|
76
83
|
handle_rebase()
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from pygitgo.utils.colors import info, success, warning, error
|
|
2
1
|
from pygitgo.exceptions import GitCommandError, GitGoError
|
|
2
|
+
from pygitgo.utils.cli_io import info, warning, error
|
|
3
3
|
from pygitgo.utils.executor import run_command
|
|
4
4
|
|
|
5
5
|
|
|
@@ -9,30 +9,25 @@ 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
|
-
|
|
17
|
+
def confirm_remote_link(ok_text=None):
|
|
18
|
+
if not ok_text:
|
|
19
|
+
ok_text = "Remote is reachable."
|
|
20
20
|
try:
|
|
21
|
-
run_command(["git", "ls-remote", "origin"], loading_msg="Testing connection to remote...")
|
|
22
|
-
success("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
|
-
error("Connection failed — verify the URL and your SSH key.")
|
|
26
24
|
info("Run: git remote -v to inspect your current remote.")
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
raise GitGoError("Connection failed — verify the URL and your SSH key.")
|
|
31
26
|
|
|
32
27
|
|
|
33
28
|
def check_and_sync_branch(branch):
|
|
34
29
|
try:
|
|
35
|
-
run_command(["git", "fetch", "origin"], loading_msg="Checking if branch is up to date...")
|
|
30
|
+
run_command(["git", "fetch", "origin"], loading_msg="Checking if branch is up to date...", ok_text="Remote fetched.")
|
|
36
31
|
|
|
37
32
|
try:
|
|
38
33
|
local_commit = run_command(["git", "rev-parse", branch])
|
|
@@ -44,14 +39,13 @@ def check_and_sync_branch(branch):
|
|
|
44
39
|
)
|
|
45
40
|
if behind_check and int(behind_check) > 0:
|
|
46
41
|
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...")
|
|
42
|
+
output = run_command(["git", "pull", "--rebase", "origin", branch], loading_msg="Pulling changes from remote...", ok_text="Synced with remote.")
|
|
48
43
|
if output:
|
|
49
44
|
print(output)
|
|
50
|
-
success("Synced with remote.")
|
|
51
45
|
else:
|
|
52
|
-
|
|
46
|
+
info("Branch is up to date.")
|
|
53
47
|
else:
|
|
54
|
-
|
|
48
|
+
info("Branch is already up to date.")
|
|
55
49
|
except (GitCommandError, ValueError):
|
|
56
50
|
warning("Remote branch doesn't exist yet. First push will create it.")
|
|
57
51
|
except (GitCommandError, OSError):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from pygitgo.commands.git_core import git_init
|
|
2
|
-
from pygitgo.utils.colors import info, success
|
|
3
2
|
from pygitgo.exceptions import GitGoError
|
|
3
|
+
from pygitgo.utils.cli_io import info
|
|
4
4
|
import urllib.request
|
|
5
5
|
import urllib.error
|
|
6
6
|
import zipfile
|
|
@@ -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
|
|
|
@@ -277,7 +292,7 @@ def _scaffold_language(lang, target_dir, name):
|
|
|
277
292
|
info(f"Created {name}.csproj and Program.cs")
|
|
278
293
|
|
|
279
294
|
|
|
280
|
-
def init_operation(args):
|
|
295
|
+
def init_operation(args, standalone=False):
|
|
281
296
|
target_dir = args.name
|
|
282
297
|
|
|
283
298
|
if os.path.exists(target_dir) and os.listdir(target_dir):
|
|
@@ -294,10 +309,12 @@ 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
|
-
|
|
300
|
-
|
|
314
|
+
if standalone:
|
|
315
|
+
from pygitgo.utils.cli_io import banner
|
|
316
|
+
banner("LOCAL SCAFFOLD CONSTRUCTED.", "PROJECT STRUCTURE AND REPOSITORY INITIALIZED.")
|
|
317
|
+
info(f"Next steps:\n cd {target_dir}\n gitgo repo\n gitgo link <url>")
|
|
301
318
|
|
|
302
319
|
except Exception as e:
|
|
303
320
|
if os.path.exists(target_dir) and not os.listdir(target_dir):
|