Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
Expand Down
8 changes: 6 additions & 2 deletions src/pygitgo/commands/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion src/pygitgo/commands/jump.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 6 additions & 4 deletions src/pygitgo/commands/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/pygitgo/commands/pull.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
1 change: 1 addition & 0 deletions src/pygitgo/commands/repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion src/pygitgo/commands/undo.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
21 changes: 13 additions & 8 deletions src/pygitgo/utils/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down
2 changes: 1 addition & 1 deletion tests/test_pull.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Loading