From fd37ea42faf623a33943a8145e59956262a61e05 Mon Sep 17 00:00:00 2001 From: Zhengshou Lai Date: Wed, 19 Aug 2026 21:17:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(git=20proxy):=20start=20=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E5=86=99=20git=20http(s).proxy=EF=BC=8Cstop=20=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E6=81=A2=E5=A4=8D=EF=BC=8Crestart=20=E7=BB=A7?= =?UTF-8?q?=E6=89=BF=E7=8A=B6=E6=80=81=EF=BC=88CI=20=E7=94=A8=20--no-set-g?= =?UTF-8?q?it=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../skills/mytoolkit/references/git-proxy.md | 13 ++--- mytoolkit/commands/git.py | 53 +++++++++++++++---- tests/test_git_proxy.py | 14 ++++- 3 files changed, 62 insertions(+), 18 deletions(-) diff --git a/.agents/skills/mytoolkit/references/git-proxy.md b/.agents/skills/mytoolkit/references/git-proxy.md index 0a824c7..458510f 100644 --- a/.agents/skills/mytoolkit/references/git-proxy.md +++ b/.agents/skills/mytoolkit/references/git-proxy.md @@ -5,17 +5,18 @@ ```bash mytoolkit git proxy list mytoolkit git proxy status # 全部守护进程 + git config -mytoolkit git proxy start fastgithub # 推荐:后台 + Parallels socat(默认不改 git config) -mytoolkit git proxy stop # 停「当前」 +mytoolkit git proxy start fastgithub # 推荐:后台 + Parallels socat(默认设 git http(s).proxy) +mytoolkit git proxy stop # 停「当前」,并恢复 start 前的 git proxy 旧值 mytoolkit git proxy stop fastgithub -mytoolkit git proxy restart # 重启「当前」 +mytoolkit git proxy restart # 重启「当前」,git 行为继承重启前的状态 +mytoolkit git proxy start --no-set-git fastgithub # 仅拉 daemon,不动全局 git config(CI 主机) mytoolkit git proxy set fastgithub # 仅写 git http(s).proxy mytoolkit git proxy unset -mytoolkit git proxy run fastgithub # 旧前台模式(设 git + Ctrl-C 清理);CI 主机请用 start +mytoolkit git proxy run fastgithub # 旧前台模式(设 git + Ctrl-C 清理) ``` -- `start` / `stop` / `restart`:端口 + PID(`lsof`),状态文件 `~/.mytoolkit/run/proxy-current.json` -- CI:主机 `start fastgithub`;job 用 `HEYUE_CI_GIT_PROXY=1` / `XIAOHE_CI_GIT_PROXY=1`(勿依赖全局 git proxy) +- `start` / `stop` / `restart`:端口 + PID(`lsof`),状态文件 `~/.mytoolkit/run/proxy-current.json`;`start` 默认写全局 git http(s).proxy,`stop` 按状态文件自动恢复旧值(`--set-git/--no-set-git` 可显式切换) +- CI:主机 `start --no-set-git fastgithub`(daemon only,勿动全局 git config);job 用 `HEYUE_CI_GIT_PROXY=1` / `XIAOHE_CI_GIT_PROXY=1`(勿依赖全局 git proxy) - Parallels guest 需 `brew install socat`;可选 `XIAOHE_CI_PARALLELS_HOST_IP` ## 新机器安装 FastGitHub diff --git a/mytoolkit/commands/git.py b/mytoolkit/commands/git.py index df26934..613c3ba 100644 --- a/mytoolkit/commands/git.py +++ b/mytoolkit/commands/git.py @@ -99,6 +99,19 @@ def _proxy_loopback_port(proxy_url: str) -> int | None: return 443 if parsed.scheme == "https" else 80 +def _restore_baseline(proxy: str, port: int | None) -> str: + """Git proxy value to record for restore-after-stop. + + A value already pointing at the daemon's loopback port (set manually or + by a prior `set`/`run`) is really "no proxy" once the daemon stops — + restoring it would dangle at a dead port. Foreign / non-loopback values + pass through unchanged. + """ + if port is not None and _proxy_loopback_port(proxy) == port: + return "(not set)" + return proxy + + def _parallels_host_ips() -> list[str]: """Mac IPs that Parallels guests use to reach this host (auto from ifconfig).""" if sys.platform != "darwin": @@ -354,7 +367,8 @@ def _clear_state() -> None: def _restore_stale_git_proxy() -> bool: """Restore git proxy if it points at a dead loopback port. - A session that ran `start --set-git` / `run` and exited without `stop` + A session that ran `start` (git-proxy on by default) / `run` and exited + without `stop` leaves the global git proxy pointing at the daemon port; if the daemon then died, every git call fails behind a dead proxy. Restore the pre-proxy value recorded in the state file (or unset). Returns True if anything changed. @@ -477,6 +491,11 @@ def _start_daemon(name: str, *, set_git: bool) -> None: old_http = _get_git_proxy("http.proxy") old_https = _get_git_proxy("https.proxy") + # A git proxy already pointing at this daemon's loopback port (a prior + # manual `set` or stale state) counts as "no proxy" for restore purposes — + # restoring the daemon URL after stop would leave a dangling pointer. + old_http = _restore_baseline(old_http, port) + old_https = _restore_baseline(old_https, port) proxy_pid: int | None = None forward_pids: list[int] = [] @@ -682,15 +701,16 @@ def proxy_unset() -> None: @proxy_cmd.command("start") @click.argument("name", shell_complete=_complete_proxy_names) @click.option( - "--set-git", - is_flag=True, - help="Also set global git http(s).proxy (default: process only)", + "--set-git/--no-set-git", + default=True, + help="Set global git http(s).proxy (default). --no-set-git = daemon only.", ) def proxy_start(name: str, set_git: bool) -> None: """Start a proxy daemon in the background (loopback + Parallels socat). - Does not change global git config unless --set-git is passed. - CI should keep using per-job GIT_CONFIG_PARAMETERS instead. + By default also sets global git http(s).proxy; ``stop`` restores the + previous value. CI hosts wanting daemon-only should pass ``--no-set-git`` + and keep using per-job GIT_CONFIG_PARAMETERS. """ _start_daemon(name, set_git=set_git) @@ -705,12 +725,17 @@ def proxy_stop(name: str | None) -> None: @proxy_cmd.command("restart") @click.argument("name", required=False, shell_complete=_complete_proxy_names) @click.option( - "--set-git", - is_flag=True, - help="Also set global git http(s).proxy after restart", + "--set-git/--no-set-git", + default=None, + help="Set global git http(s).proxy (default: inherit previous state). " + "--no-set-git = daemon only.", ) -def proxy_restart(name: str | None, set_git: bool) -> None: - """Restart a proxy daemon. Omit NAME to restart the current one.""" +def proxy_restart(name: str | None, set_git: bool | None) -> None: + """Restart a proxy daemon. Omit NAME to restart the current one. + + Git-proxy behavior defaults to inheriting the state of the daemon being + restarted; ``--no-set-git`` forces daemon-only. + """ if name is None: name = _current_daemon_name() if name is None: @@ -722,6 +747,12 @@ def proxy_restart(name: str | None, set_git: bool) -> None: ) sys.exit(1) click.echo(f"Restarting current: {name}") + if set_git is None: + state = _read_state() + if state and state.get("name") == name and "set_git" in state: + set_git = bool(state["set_git"]) + else: + set_git = True _stop_daemon(name) _start_daemon(name, set_git=set_git) diff --git a/tests/test_git_proxy.py b/tests/test_git_proxy.py index ba3db8f..1a22f24 100644 --- a/tests/test_git_proxy.py +++ b/tests/test_git_proxy.py @@ -1,6 +1,6 @@ """Unit tests for git proxy helpers.""" -from mytoolkit.commands.git import _proxy_loopback_port +from mytoolkit.commands.git import _proxy_loopback_port, _restore_baseline def test_proxy_loopback_port_http(): @@ -13,6 +13,18 @@ def test_proxy_loopback_port_rejects_remote(): assert _proxy_loopback_port("socks5://127.0.0.1:1080") is None +def test_restore_baseline_daemon_own_port_becomes_unset(): + # Git already pointing at the daemon's loopback port → treat as no proxy. + assert _restore_baseline("http://127.0.0.1:38457", 38457) == "(not set)" + assert _restore_baseline("http://localhost:38457", 38457) == "(not set)" + + +def test_restore_baseline_foreign_values_pass_through(): + assert _restore_baseline("http://127.0.0.1:10080", 38457) == "http://127.0.0.1:10080" + assert _restore_baseline("(not set)", 38457) == "(not set)" + assert _restore_baseline("http://127.0.0.1:38457", None) == "http://127.0.0.1:38457" + + def test_procs_matching_filters_by_command(): from mytoolkit.commands.git import _procs_matching import os