From 02097a852724038935a961e8e3764c3816d73108 Mon Sep 17 00:00:00 2001 From: Zhengshou Lai Date: Wed, 6 May 2026 08:16:56 +0800 Subject: [PATCH] feat(git): add proxy run command with auto-unset on exit Add `git proxy run ` that sets proxy, runs the associated command, and auto-restores/unsets proxy on exit or Ctrl-C. Also fix click.Exit usage for Click 8.x compatibility. --- bin/commands/git.py | 90 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/bin/commands/git.py b/bin/commands/git.py index d679449..e6221cd 100644 --- a/bin/commands/git.py +++ b/bin/commands/git.py @@ -1,6 +1,9 @@ """Git utilities including proxy management.""" +import shutil +import signal import subprocess +import sys import click @@ -64,7 +67,7 @@ def proxy_set(name: str) -> None: if name not in proxies: click.echo(f"Unknown proxy: {name}", err=True) click.echo(f"Available: {', '.join(proxies.keys())}", err=True) - raise click.Exit(1) + sys.exit(1) proxy_url = proxies[name] subprocess.run( @@ -114,3 +117,88 @@ def proxy_list_cmd() -> None: click.secho("Available proxies:", fg="cyan") for name, url in proxies.items(): click.echo(f" {name:12} {url}") + + +@proxy_cmd.command("run") +@click.argument("name", shell_complete=_complete_proxy_names) +@click.argument("command", nargs=-1, required=False) +def proxy_run(name: str, command: tuple[str, ...]) -> None: + """Set proxy, run a command, auto-unset on exit. + + If no COMMAND is given, tries to run a program with the same name as the proxy. + If that program is not found, sets the proxy and waits for Ctrl-C. + """ + proxies = _get_proxies() + if name not in proxies: + click.echo(f"Unknown proxy: {name}", err=True) + click.echo(f"Available: {', '.join(proxies.keys())}", err=True) + sys.exit(1) + + # Save current state + old_http = _get_git_proxy("http.proxy") + old_https = _get_git_proxy("https.proxy") + + proxy_url = proxies[name] + subprocess.run( + ["git", "config", "--global", "http.proxy", proxy_url], check=True + ) + subprocess.run( + ["git", "config", "--global", "https.proxy", proxy_url], check=True + ) + click.secho(f"Git proxy set to {name}: {proxy_url}", fg="green") + + # Determine what to run + cmd_to_run = list(command) if command else None + if cmd_to_run is None: + if shutil.which(name): + cmd_to_run = [name] + + def cleanup(): + if old_http == "(not set)": + _unset_git_proxy("http.proxy") + else: + subprocess.run( + ["git", "config", "--global", "http.proxy", old_http], + capture_output=True, + check=False, + ) + if old_https == "(not set)": + _unset_git_proxy("https.proxy") + else: + subprocess.run( + ["git", "config", "--global", "https.proxy", old_https], + capture_output=True, + check=False, + ) + click.secho("Git proxy restored", fg="green") + + if cmd_to_run: + click.secho(f"Running: {' '.join(cmd_to_run)}", fg="cyan") + click.secho("Press Ctrl-C to stop and auto-unset proxy\n", fg="cyan") + proc = None + try: + proc = subprocess.Popen(cmd_to_run) + proc.wait() + except KeyboardInterrupt: + if proc is not None: + proc.send_signal(signal.SIGINT) + try: + proc.wait(timeout=5) + except subprocess.TimeoutExpired: + proc.terminate() + proc.wait() + finally: + cleanup() + if proc is not None: + sys.exit(proc.returncode) + sys.exit(0) + else: + click.secho( + f"Proxy active. Press Ctrl-C to stop and auto-unset proxy", fg="cyan" + ) + try: + signal.pause() + except KeyboardInterrupt: + pass + finally: + cleanup()