From 98591ce1b2d95480ab5be4c0be17bacb9520c48a Mon Sep 17 00:00:00 2001 From: Huerte Date: Fri, 7 Aug 2026 14:18:50 +0800 Subject: [PATCH] fix: stop spinner before re-raising Ctrl+C and guard GitHub-only features for non-GitHub hosts --- CHANGELOG.md | 8 ++++++++ pyproject.toml | 2 +- src/pygitgo/commands/init.py | 8 ++++++-- src/pygitgo/commands/jump.py | 2 +- src/pygitgo/commands/link.py | 10 ++++++---- src/pygitgo/commands/pull.py | 2 +- src/pygitgo/commands/repo.py | 1 + src/pygitgo/commands/undo.py | 2 +- src/pygitgo/utils/executor.py | 21 +++++++++++++-------- tests/test_pull.py | 2 +- 10 files changed, 39 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f0a19b..1e81fed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,14 @@ Versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html). --- +## [1.10.3] - 2026-08-07 + +### Fixed +- Fixed a bug where hitting Ctrl+C during any command with a loading spinner (like `gitgo pull` or `gitgo jump`) would cause the spinner to keep running and leave broken text on the terminal. The spinner is now stopped cleanly on interrupt. +- Fixed `gitgo init --template` and `gitgo link` failing with cryptic errors when using non-GitHub URLs (like GitLab or self-hosted servers). The SSH checks and template downloads now correctly detect and handle the remote host. + +--- + ## [1.10.2] - 2026-08-07 ### Added diff --git a/pyproject.toml b/pyproject.toml index e1de27d..a40791a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "pygitgo" -version = "1.10.2" +version = "1.10.3" description = "GitGo CLI - Your Fast Git Companion. Simplifies git push, link, stash, and user management." readme = "README.md" license = {text = "GPL-3.0-or-later"} diff --git a/src/pygitgo/commands/init.py b/src/pygitgo/commands/init.py index 320c541..5e9079f 100644 --- a/src/pygitgo/commands/init.py +++ b/src/pygitgo/commands/init.py @@ -158,11 +158,15 @@ def _fetch_gitignore(resolved_lang): def _parse_template_slug(template): - # For github url + non_github = re.search(r"https?://([^/]+)", template) + if non_github and "github.com" not in non_github.group(1): + raise GitGoError( + f"Template download only supports GitHub repositories.\n" + f"Provide an 'owner/repo' slug or a github.com URL." + ) match = re.search(r"github\.com[/:]([^/]+/[^/.]+)", template) if match: return match.group(1) - # For repo slug if re.match(r"^[^/]+/[^/]+$", template): return template raise GitGoError( diff --git a/src/pygitgo/commands/jump.py b/src/pygitgo/commands/jump.py index 40cd0c8..d24933d 100644 --- a/src/pygitgo/commands/jump.py +++ b/src/pygitgo/commands/jump.py @@ -31,7 +31,7 @@ def undo_jump_operation(original_branch, stashed_code, created_branch=None): def _jump_interrupt_cleanup(original_branch, stashed_code, created_branch): try: - run_command(["git", "rebase", "--abort"], loading_msg="Aborting in-progress rebase...", ok_text="Rebase aborted.") + run_command(["git", "rebase", "--abort"]) info("In-progress rebase aborted.") except GitCommandError: pass diff --git a/src/pygitgo/commands/link.py b/src/pygitgo/commands/link.py index 17adc7b..2f943a2 100644 --- a/src/pygitgo/commands/link.py +++ b/src/pygitgo/commands/link.py @@ -2,7 +2,7 @@ from pygitgo.utils.cli_io import success, warning, error, info, banner, write from pygitgo.commands.git_core import git_init, git_commit, git_push from pygitgo.commands.git_branch import get_current_branch -from pygitgo.auth.ssh_utils import ensure_github_known_host, convert_https_to_ssh, is_ssh_url, check_connection +from pygitgo.auth.ssh_utils import ensure_known_host, get_remote_host, convert_https_to_ssh, is_ssh_url, check_connection from pygitgo.exceptions import GitCommandError, GitGoError from pygitgo.utils.validators import validate_repo_url from pygitgo.utils.config import get_default_branch @@ -43,12 +43,14 @@ def link_core(repo_url, commit_message, silent=False, already_initialized=False) if not validate_repo_url(repo_url): raise GitGoError(f"Invalid remote repository URL: '{repo_url}'") - ensure_github_known_host() + host = get_remote_host(repo_url) or "github.com" + ensure_known_host(host) if not is_ssh_url(repo_url): ssh_ok = check_connection( - ok_text="GitHub SSH verified. Switching to SSH URL.", - fail_text="SSH unavailable. Keeping HTTPS URL." + ok_text=f"{host} SSH verified. Switching to SSH URL.", + fail_text=f"SSH unavailable for {host}. Keeping HTTPS URL.", + host=host, ) if ssh_ok: ssh_url = convert_https_to_ssh(repo_url) diff --git a/src/pygitgo/commands/pull.py b/src/pygitgo/commands/pull.py index 34399f1..cd804c3 100644 --- a/src/pygitgo/commands/pull.py +++ b/src/pygitgo/commands/pull.py @@ -10,7 +10,7 @@ def _pull_interrupt_cleanup(): if is_rebase_in_progress(): warning("A rebase is in progress from the interrupted pull.") try: - run_command(["git", "rebase", "--abort"], loading_msg="Aborting interrupted rebase...", ok_text="Rebase aborted. Branch is back to its pre-pull state.") + run_command(["git", "rebase", "--abort"]) except GitCommandError: error("Could not abort rebase automatically.") info("Run manually: git rebase --abort") diff --git a/src/pygitgo/commands/repo.py b/src/pygitgo/commands/repo.py index ee7806c..eaa82ea 100644 --- a/src/pygitgo/commands/repo.py +++ b/src/pygitgo/commands/repo.py @@ -113,6 +113,7 @@ def create_github_repo(name, private=False, description="", token=None, retry_co def repo_operation(args, silent=False): + info("This command creates a repository on GitHub. A GitHub account and token are required.") if args.name: repo_name = args.name else: diff --git a/src/pygitgo/commands/undo.py b/src/pygitgo/commands/undo.py index 51feb70..51a9acc 100644 --- a/src/pygitgo/commands/undo.py +++ b/src/pygitgo/commands/undo.py @@ -41,7 +41,7 @@ def undo_changes(): if reset_done: warning("Interrupted during file removal. Finishing cleanup...") try: - run_command(["git", "clean", "-fd"], loading_msg="Removing new files...", ok_text="Working tree reset. All changes discarded.") + run_command(["git", "clean", "-fd"]) except GitCommandError: warning("Could not finish cleanup. Run 'git clean -fd' manually.") else: diff --git a/src/pygitgo/utils/executor.py b/src/pygitgo/utils/executor.py index 57cdfbf..3c4252d 100644 --- a/src/pygitgo/utils/executor.py +++ b/src/pygitgo/utils/executor.py @@ -30,14 +30,19 @@ def run_command(command, return_complete=False, loading_msg=None, ok_text=None, cmd_str = " ".join(command) if isinstance(command, list) else command print(f"[DEBUG] Running command: {cmd_str}") - result = subprocess.run( - command, - check=True, - capture_output=True, - text=True, - stdin=subprocess.DEVNULL, - env=env, - ) + try: + result = subprocess.run( + command, + check=True, + capture_output=True, + text=True, + stdin=subprocess.DEVNULL, + env=env, + ) + except KeyboardInterrupt: + if spinner: + spinner.stop() + raise if _VERBOSE: if result.stdout.strip(): diff --git a/tests/test_pull.py b/tests/test_pull.py index 223c26b..b8da051 100644 --- a/tests/test_pull.py +++ b/tests/test_pull.py @@ -104,7 +104,7 @@ def side_effect_fn(*args, **kwargs): pull_operation(args) assert sys_exit.value.code == 130 - mock_run_command.assert_any_call(["git", "rebase", "--abort"], loading_msg="Aborting interrupted rebase...", ok_text="Rebase aborted. Branch is back to its pre-pull state.") + mock_run_command.assert_any_call(["git", "rebase", "--abort"]) mock_warning.assert_any_call("Pull interrupted (Ctrl+C).") mock_warning.assert_any_call("A rebase is in progress from the interrupted pull.") mock_success.assert_not_called()