feat(git proxy): start 默认写 git http(s).proxy,stop 自动恢复,restart 继承状态(CI 用 --no-set-git)

This commit is contained in:
Zhengshou Lai
2026-08-19 21:17:02 +08:00
parent 9dddb8e0c3
commit fd37ea42fa
3 changed files with 62 additions and 18 deletions
@@ -5,17 +5,18 @@
```bash ```bash
mytoolkit git proxy list mytoolkit git proxy list
mytoolkit git proxy status # 全部守护进程 + git config mytoolkit git proxy status # 全部守护进程 + git config
mytoolkit git proxy start fastgithub # 推荐:后台 + Parallels socat(默认不改 git config mytoolkit git proxy start fastgithub # 推荐:后台 + Parallels socat(默认 git http(s).proxy
mytoolkit git proxy stop # 停「当前」 mytoolkit git proxy stop # 停「当前」,并恢复 start 前的 git proxy 旧值
mytoolkit git proxy stop fastgithub mytoolkit git proxy stop fastgithub
mytoolkit git proxy restart # 重启「当前」 mytoolkit git proxy restart # 重启「当前」git 行为继承重启前的状态
mytoolkit git proxy start --no-set-git fastgithub # 仅拉 daemon,不动全局 git configCI 主机)
mytoolkit git proxy set fastgithub # 仅写 git http(s).proxy mytoolkit git proxy set fastgithub # 仅写 git http(s).proxy
mytoolkit git proxy unset 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` - `start` / `stop` / `restart`:端口 + PID`lsof`),状态文件 `~/.mytoolkit/run/proxy-current.json``start` 默认写全局 git http(s).proxy`stop` 按状态文件自动恢复旧值(`--set-git/--no-set-git` 可显式切换)
- CI:主机 `start fastgithub`job 用 `HEYUE_CI_GIT_PROXY=1` / `XIAOHE_CI_GIT_PROXY=1`(勿依赖全局 git proxy - CI:主机 `start --no-set-git fastgithub`daemon only,勿动全局 git configjob 用 `HEYUE_CI_GIT_PROXY=1` / `XIAOHE_CI_GIT_PROXY=1`(勿依赖全局 git proxy
- Parallels guest 需 `brew install socat`;可选 `XIAOHE_CI_PARALLELS_HOST_IP` - Parallels guest 需 `brew install socat`;可选 `XIAOHE_CI_PARALLELS_HOST_IP`
## 新机器安装 FastGitHub ## 新机器安装 FastGitHub
+42 -11
View File
@@ -99,6 +99,19 @@ def _proxy_loopback_port(proxy_url: str) -> int | None:
return 443 if parsed.scheme == "https" else 80 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]: def _parallels_host_ips() -> list[str]:
"""Mac IPs that Parallels guests use to reach this host (auto from ifconfig).""" """Mac IPs that Parallels guests use to reach this host (auto from ifconfig)."""
if sys.platform != "darwin": if sys.platform != "darwin":
@@ -354,7 +367,8 @@ def _clear_state() -> None:
def _restore_stale_git_proxy() -> bool: def _restore_stale_git_proxy() -> bool:
"""Restore git proxy if it points at a dead loopback port. """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 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 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. 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_http = _get_git_proxy("http.proxy")
old_https = _get_git_proxy("https.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 proxy_pid: int | None = None
forward_pids: list[int] = [] forward_pids: list[int] = []
@@ -682,15 +701,16 @@ def proxy_unset() -> None:
@proxy_cmd.command("start") @proxy_cmd.command("start")
@click.argument("name", shell_complete=_complete_proxy_names) @click.argument("name", shell_complete=_complete_proxy_names)
@click.option( @click.option(
"--set-git", "--set-git/--no-set-git",
is_flag=True, default=True,
help="Also set global git http(s).proxy (default: process only)", help="Set global git http(s).proxy (default). --no-set-git = daemon only.",
) )
def proxy_start(name: str, set_git: bool) -> None: def proxy_start(name: str, set_git: bool) -> None:
"""Start a proxy daemon in the background (loopback + Parallels socat). """Start a proxy daemon in the background (loopback + Parallels socat).
Does not change global git config unless --set-git is passed. By default also sets global git http(s).proxy; ``stop`` restores the
CI should keep using per-job GIT_CONFIG_PARAMETERS instead. 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) _start_daemon(name, set_git=set_git)
@@ -705,12 +725,17 @@ def proxy_stop(name: str | None) -> None:
@proxy_cmd.command("restart") @proxy_cmd.command("restart")
@click.argument("name", required=False, shell_complete=_complete_proxy_names) @click.argument("name", required=False, shell_complete=_complete_proxy_names)
@click.option( @click.option(
"--set-git", "--set-git/--no-set-git",
is_flag=True, default=None,
help="Also set global git http(s).proxy after restart", 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: def proxy_restart(name: str | None, set_git: bool | None) -> None:
"""Restart a proxy daemon. Omit NAME to restart the current one.""" """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: if name is None:
name = _current_daemon_name() name = _current_daemon_name()
if name is None: if name is None:
@@ -722,6 +747,12 @@ def proxy_restart(name: str | None, set_git: bool) -> None:
) )
sys.exit(1) sys.exit(1)
click.echo(f"Restarting current: {name}") 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) _stop_daemon(name)
_start_daemon(name, set_git=set_git) _start_daemon(name, set_git=set_git)
+13 -1
View File
@@ -1,6 +1,6 @@
"""Unit tests for git proxy helpers.""" """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(): 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 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(): def test_procs_matching_filters_by_command():
from mytoolkit.commands.git import _procs_matching from mytoolkit.commands.git import _procs_matching
import os import os