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: 1 addition & 7 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,37 +1,31 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*.pyo
*.pyd
*.py.class

# PyPI Build and Distribution files
build/
dist/
*.egg-info/
.eggs/

# Virtual Environments
venv/
env/
.venv/
env.bak/
venv.bak/

# IDEs and Text Editors
.vscode/
.idea/
*.swp
*.swo

# OS Generated Files
.DS_Store
Thumbs.db

# Project-Specific Ignores
*.txt
.chronotab/
TODO.md
docs/
todo/
.coverage
.coverage
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ Versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]

### Added
- (nothing yet)
- **Auto SSH Commit Signing:** GitGo now uses the SSH key generated during `gitgo user login` to automatically sign all commits using temporary `-c` flags, giving you the Verified badge on GitHub without modifying global git configs.

### Changed
- Refactored codebase to standardize internal API returns, remove redundant checks, and optimize stash operations.

### Fixed
- Fixed GitGo hanging indefinitely during login if an SSH passphrase prompt was triggered invisibly.
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ gitgo link https://github.com/username/repo.git "init"
- **State management:** Named, indexed stash. Run `state list` to see what you saved. No more `stash@{2}` archaeology.
- **Custom defaults:** Store your preferred branch name and default commit message. GitGo picks them up on every run.
- **Auto-update checker:** Checks PyPI for newer versions in a background thread. Results are cached for 7 days so startup isn't delayed.
- **SSH auto-setup:** Generates an `ed25519` key, loads it into `ssh-agent`, and opens your GitHub SSH settings page.
- **SSH auto-setup & signing:** Generates an `ed25519` key, loads it into `ssh-agent`, opens your GitHub SSH settings page, and automatically signs all future commits for the verified badge.
- **HTTPS-to-SSH conversion:** Detects HTTPS remotes and rewrites them before pushing if SSH is configured. No manual `git remote set-url`.
- **Termux support:** Detects the Termux environment, adjusts install paths, uses `termux-open` for browser actions, and patches the dubious ownership Git error.

Expand Down Expand Up @@ -129,7 +129,7 @@ pip install -e .

### 1. Set Up Your Identity

Run this once on a new machine. GitGo generates an SSH key, adds it to `ssh-agent`, prints the public key, and opens your GitHub SSH settings page.
Run this once on a new machine. GitGo generates an SSH key, adds it to `ssh-agent`, prints the public key, and opens your GitHub SSH settings page so you can add it for both authentication and commit signing.

```bash
gitgo user login
Expand Down Expand Up @@ -295,7 +295,7 @@ gitgo -r # verify GitGo is ready

## How It Works

- **SSH Auto-Setup:** `gitgo user login` generates an `ed25519` SSH key, adds it to `ssh-agent`, prints the public key, and opens `github.com/settings/ssh/new`.
- **SSH Auto-Setup & Signing:** `gitgo user login` generates an `ed25519` SSH key and prompts you to add it to GitHub twice (for authentication and signing). GitGo then injects temporary `-c` flags into every commit to automatically sign them with this key, without touching your global git config.
- **HTTPS to SSH Conversion:** If your remote is set to HTTPS and SSH is configured, GitGo rewrites the remote before pushing. No `git remote set-url` required.
- **Auto-Update Checker:** Spawns a non-blocking background thread on startup to query PyPI for newer versions. Results are cached locally for 7 days to prevent unnecessary network requests.
- **Termux Compatibility:** Detects Termux via environment variables, adjusts binary locations (`$PREFIX/bin`), uses `termux-open` for browser actions, and patches the `detected dubious ownership` Git error.
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.6.3"
version = "1.7.0b1"
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
9 changes: 4 additions & 5 deletions src/pygitgo/auth/account.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
from pygitgo.utils.colors import info, success, warning, error, BLUE, RESET
from pygitgo.utils.executor import run_command
import subprocess
from pygitgo.utils.executor import run_command, command_failed


def get_user():
try:
name = run_command(["git", "config", "--global", "user.name"], allow_fail=True)
email = run_command(["git", "config", "--global", "user.email"], allow_fail=True)

if not name or isinstance(name, subprocess.CalledProcessError):
if not name or command_failed(name):
name = None
if not email or isinstance(email, subprocess.CalledProcessError):
if not email or command_failed(email):
email = None
return name, email
except:
except Exception:
return None, None

def set_user(name, email):
Expand Down
8 changes: 6 additions & 2 deletions src/pygitgo/auth/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,15 @@ def login():
print("=" * len(pub_key) + "\n")

info("Copy the key above (between the lines).")
info("You need to add this key TWICE on GitHub:")
info(" 1. Once as 'Authentication Key' (so you can push and pull)")
info(" 2. Once as 'Signing Key' (so your commits show as Verified)")
info("Both entries use the exact same key text.")

ssh_utils.open_github_settings()

input(
"After pasting your key on GitHub and clicking 'Add SSH Key',\n"
"After adding both keys on GitHub,\n"
"come back here and press Enter to verify the connection..."
)

Expand Down
80 changes: 32 additions & 48 deletions src/pygitgo/auth/ssh_utils.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
from pygitgo.utils.colors import info, success, warning, error
from pygitgo.utils.executor import run_command
from pygitgo.utils import platform_utils
from pygitgo.utils.colors import info, success, warning, error
from pygitgo.utils.executor import run_command, command_failed
from pygitgo.exceptions import GitCommandError
from pathlib import Path
import webbrowser
import subprocess
import sys
import shutil
import os
import re


SSH_TIMEOUT_SECONDS = 10

Expand All @@ -25,7 +29,7 @@ def ensure_github_known_host():
info("Adding GitHub to known_hosts...")
result = run_command(["ssh-keyscan", "-H", "github.com"], allow_fail=True, return_complete=True)

if not isinstance(result, Exception) and result.stdout and "github.com" in result.stdout:
if not command_failed(result) and result.stdout and "github.com" in result.stdout:
with open(known_hosts, "a") as f:
f.write(result.stdout)
if not result.stdout.endswith("\n"):
Expand All @@ -34,39 +38,29 @@ def ensure_github_known_host():
else:
warning("Could not automatically add GitHub to known_hosts. You might be prompted.")

def check_connection():
ensure_github_known_host()
def _get_github_ssh_response():
try:
result = subprocess.run(
["ssh", "-T", "-o", "BatchMode=yes", "git@github.com"],
capture_output=True,
text=True,
timeout=SSH_TIMEOUT_SECONDS,
stdin=subprocess.DEVNULL,
capture_output=True, text=True,
timeout=SSH_TIMEOUT_SECONDS, stdin=subprocess.DEVNULL,
)
output = (result.stderr or "") + (result.stdout or "")
return "successfully authenticated" in output
return (result.stderr or "") + (result.stdout or "")
except (subprocess.TimeoutExpired, OSError):
return False
return None

def get_github_username():
try:
result = subprocess.run(
["ssh", "-T", "-o", "BatchMode=yes", "git@github.com"],
capture_output=True,
text=True,
timeout=SSH_TIMEOUT_SECONDS,
stdin=subprocess.DEVNULL,
)
output = (result.stderr or "") + (result.stdout or "")
def check_connection():
ensure_github_known_host()
output = _get_github_ssh_response()
return output is not None and "successfully authenticated" in output

if "Hi " in output and "!" in output:
try:
return output.split("Hi ")[1].split("!")[0]
except (IndexError, ValueError):
return None
except (subprocess.TimeoutExpired, OSError):
pass
def get_github_username():
output = _get_github_ssh_response()
if output and "Hi " in output and "!" in output:
try:
return output.split("Hi ")[1].split("!")[0]
except (IndexError, ValueError):
pass
return None

def get_ssh_key_path():
Expand Down Expand Up @@ -97,7 +91,7 @@ def generate_ssh_key(email):

try:
run_command(["ssh-add", str(key_path)], allow_fail=True)
except (subprocess.CalledProcessError, OSError):
except (GitCommandError, OSError):
pass

return key_path
Expand All @@ -107,33 +101,23 @@ def open_github_settings():
opened = False

try:
if platform_utils.is_windows():
os.system(f"start {url}")
opened = True
elif platform_utils.is_termux():
os.system(f"termux-open {url}")
opened = True
elif platform_utils.is_linux() or platform_utils.is_macos():
exit_code = os.system(f"xdg-open {url} 2>/dev/null")
opened = exit_code == 0
if platform_utils.is_termux():
if shutil.which("termux-open"):
subprocess.run(["termux-open", url], check=False)
opened = True
else:
import webbrowser
webbrowser.open(url)
opened = True
opened = webbrowser.open(url)
except Exception:
opened = False

if not opened:
warning("Could not open browser automatically.")

info(f"\nIf the browser did not open, visit this URL manually:")
info("\nIf the browser did not open, visit this URL manually:")
print(f"\n {url}\n")


def convert_https_to_ssh(url):

import re

def convert_https_to_ssh(url):
pattern = r'^https?://github\.com/([^/]+)/([^/]+?)(?:\.git)?/?$'
match = re.match(pattern, url.strip())

Expand Down
Loading
Loading