dsh-git-branch-manage 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/README.md +165 -1
- package/lib/client.js +204 -26
- package/lib/git.d.ts +82 -9
- package/lib/git.js +260 -24
- package/lib/index.d.ts +11 -0
- package/lib/index.js +34 -3
- package/lib/network-ip.d.ts +99 -0
- package/lib/network-ip.js +206 -0
- package/lib/network-probe.d.ts +28 -0
- package/lib/network-probe.js +47 -0
- package/lib/network-proxy.d.ts +35 -0
- package/lib/network-proxy.js +55 -0
- package/lib/network-tool.js +3 -2
- package/lib/network.d.ts +58 -0
- package/lib/network.js +128 -0
- package/lib/normalize.d.ts +14 -2
- package/lib/normalize.js +38 -2
- package/lib/routes.d.ts +24 -3
- package/lib/routes.js +97 -8
- package/lib/wire.d.ts +35 -0
- package/lib/wire.js +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
本文件记录 `dsh-git-branch-manage` 的重要变更。版本号遵循语义化版本。
|
|
4
4
|
|
|
5
|
+
## [1.3.1] - 2026-11-15
|
|
6
|
+
|
|
7
|
+
### 修复
|
|
8
|
+
|
|
9
|
+
- **会话框分支菜单藏起已被工作树占用的本地分支**(决策 0048):`buildBranchRows` 曾把已被 linked worktree 检出的本地分支从「本地分支」组剔除,而主仓库会话开始后又整体不渲染「工作树」组,于是这些分支在菜单里完全不可见,容易被误认为分支丢失。现在菜单如实列出全部本地分支;同一分支在本目录检出仍由 git 自己拒绝并报错,不额外加标注或拦截。
|
|
10
|
+
|
|
11
|
+
## [1.3.0] - 2026-11-04
|
|
12
|
+
|
|
13
|
+
### 新增
|
|
14
|
+
|
|
15
|
+
- **网络备用通道与代理配置**(决策 0042、0043):直连发生可重试网络故障时,对 GitHub 地址使用运行时 DoH 查询和真实 Git 请求竞速选择可用 IP;支持显式配置 HTTP(S)/SOCKS 代理,并提供只读系统代理探测建议与「检测网络」诊断。
|
|
16
|
+
- **网络操作状态诊断**(决策 0041):按探测、读取、写入和超时核验分别设置预算;推送前探测远端,推送超时后核验远端 ref,并按失败类型给出建议。
|
|
17
|
+
|
|
18
|
+
### 变更
|
|
19
|
+
|
|
20
|
+
- **镜像仅作为读取加速**(决策 0044):镜像 fetch/update 失败后自动恢复原始 URL 回直连,再按备用通道链尝试;推送不走镜像,避免将写入交给第三方。
|
|
21
|
+
- SSH 远端不再进行无效的 HTTP 代理/IP 通道探测,错误信息改为提示可执行的 HTTPS 方案。
|
|
22
|
+
- 加速前缀改为拼接完整 GitHub URL(决策 0040),并用真实 Git URL 解析与提取覆盖回归场景。
|
|
23
|
+
|
|
24
|
+
### 修复
|
|
25
|
+
|
|
26
|
+
- **CI 在非 Windows 上失败**(决策 0046):系统代理探测的用例此前无条件假设 Windows 注册表,在 ubuntu-latest 上必然失败;现按平台分派,Windows 侧保留注册表行为覆盖,非 Windows 侧断言不读取注册表。
|
|
27
|
+
|
|
5
28
|
## [1.2.0] - 2026-11-02
|
|
6
29
|
|
|
7
30
|
### 新增
|
package/README.md
CHANGED
|
@@ -1,5 +1,169 @@
|
|
|
1
1
|
# dsh-git-branch
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/dsh-git-branch-manage)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
|
|
6
|
+
DSH(DeepSeek Harness)插件:在对话框里提供 git 分支管理以及代码推送、拉取功能。
|
|
7
|
+
|
|
3
8
|
提供 git 分支与 worktree 管理:输入框工具栏显示当前分支、可原地切换;支持 IDEA 风格分支选择器、远程分支检出、右键操作菜单(新建/重命名/删除/复制)、推送与更新当前分支,以及把分支隔离到独立 worktree 工作区,并管理其创建与删除。
|
|
4
9
|
|
|
5
|
-
|
|
10
|
+
> npm 包名 [`dsh-git-branch-manage`](https://www.npmjs.com/package/dsh-git-branch-manage),插件 id `dsh-git-branch`,展示名「git 分支管理」([包名由来见决策 0003](./docs/decisions/0003-rename-package-to-dsh-git-branch-manage.md))。
|
|
11
|
+
|
|
12
|
+
## 目录
|
|
13
|
+
|
|
14
|
+
- [截图](#截图)
|
|
15
|
+
- [功能](#功能)
|
|
16
|
+
- [输入框分支工具栏](#输入框分支工具栏)
|
|
17
|
+
- [Git 管理面板(侧边栏)](#git-管理面板侧边栏)
|
|
18
|
+
- [worktree 隔离工作区](#worktree-隔离工作区)
|
|
19
|
+
- [凭据管理](#凭据管理)
|
|
20
|
+
- [远程管理](#远程管理)
|
|
21
|
+
- [网络可靠性](#网络可靠性)
|
|
22
|
+
- [AI 工具](#ai-工具)
|
|
23
|
+
- [安装](#安装)
|
|
24
|
+
- [配置](#配置)
|
|
25
|
+
- [开发](#开发)
|
|
26
|
+
- [文档](#文档)
|
|
27
|
+
- [许可](#许可)
|
|
28
|
+
|
|
29
|
+
## 截图
|
|
30
|
+
|
|
31
|
+
输入框工具栏的分支 chip 与 IDEA 风格分支选择器(右键菜单含新建、新建并检出、新建分支并创建工作树、重命名、复制):
|
|
32
|
+
|
|
33
|
+

|
|
34
|
+
|
|
35
|
+
侧边栏 Git 管理面板(未提交变更 / 分支 / 提交历史图):
|
|
36
|
+
|
|
37
|
+

|
|
38
|
+
|
|
39
|
+
## 功能
|
|
40
|
+
|
|
41
|
+
### 输入框分支工具栏
|
|
42
|
+
|
|
43
|
+
- 当前分支 chip 常驻输入框工具栏;会话目录不是 git 仓库时不渲染。
|
|
44
|
+
- IDEA 风格分支选择器:**本地分支 / 远程分支 / 工作树** 分组、分支搜索框、全部展开/折叠、一键定位当前分支。
|
|
45
|
+
- 分支行右键菜单:检出、跳转到该分支的工作树、新建、新建并检出、新建分支并创建工作树、重命名、删除、复制分支名 / 复制路径。
|
|
46
|
+
- 一键 **fetch**、**更新当前分支**、**推送当前分支**(无可推送提交时禁用,[决策 0009](./docs/decisions/0009-disable-empty-push.md))。
|
|
47
|
+
- 原地 `git switch`:失败时 toast 透出 git 原始错误并回显原分支;已被其他 worktree 检出的分支在菜单里预防性禁用。
|
|
48
|
+
- 会话启动后目录固定,切换只作用于当前会话自己的检出,不动主 checkout。
|
|
49
|
+
|
|
50
|
+
### Git 管理面板(侧边栏)
|
|
51
|
+
|
|
52
|
+
注册为右侧边栏的一个页签(通过可选插件 `dsh-better-sidebar` 发布的 `ctx.betterSidebar` 注册,[决策 0013](./docs/decisions/0013-git-history-panel.md))。**该插件是可选的**:未安装时本页签不出现,输入框 chip 与设置卡照常工作。
|
|
53
|
+
|
|
54
|
+
- **未提交变更**:勾选文件加入暂存区、提交信息输入、**AI 生成**提交信息、提交;顶部另有 提取 / 更新当前分支 / 推送当前分支。
|
|
55
|
+
- **分支**:搜索框、本地/远程分组树、全部展开/折叠、新建分支、管理远程。
|
|
56
|
+
- **提交历史**:提交连线图([决策 0019](./docs/decisions/0019-git-history-graph-panel.md))、按提交人 / 分支 / 范围筛选、右键推送标签([决策 0020](./docs/decisions/0020-push-tag-action.md))。
|
|
57
|
+
- 顶栏:仓库路径 pill、刷新、**国内加速** 配置浮层、**密钥配置** 入口。
|
|
58
|
+
|
|
59
|
+
### worktree 隔离工作区
|
|
60
|
+
|
|
61
|
+
- 勾选 worktree 后基于所选分支创建独立 worktree 并**注册为 DSH workspace、直接开始会话**;同名 worktree 已存在则幂等复用([决策 0015](./docs/decisions/0015-branch-context-menu-and-switch-target.md))。
|
|
62
|
+
- 目录规划 `<rootDir>/<仓库名>/<规范化分支名>`:`/` → `-`,剔除 Windows 非法字符与尾随点/空格,避免多仓库同名分支冲突。
|
|
63
|
+
- 创建前可选 fetch 所有远程(失败不阻断创建);创建后可选惰性清理陈旧 worktree,`keepWorktrees` 控制保留上限。
|
|
64
|
+
- 创建后可拷贝配置文件(`postCreateCopyFiles`);存在 `.worktreeinclude` 时不拷贝。
|
|
65
|
+
- **worktree 管理对话框**:全量列表、脏工作区检查、删除 / 清除目录、孤儿(已删目录的残留注册)识别;目录内仍有 RUNNING 会话时拒绝删除,删除会连同会话归档与 workspace 注册一起清理,被检出的分支本身保留([决策 0014](./docs/decisions/0014-panel-card-height-and-branch-collapse.md))。
|
|
66
|
+
|
|
67
|
+
### 凭据管理
|
|
68
|
+
|
|
69
|
+
- 面板里存一次,**直接读写本机 `~/.git-credentials`**(权限 0600),并经 `~/.gitconfig` 的 URL 作用域 `include` 装上 git 内置 `store` helper([决策 0037](./docs/decisions/0037-local-git-credentials-backend.md))——因此面板、任何 shell 里的 `git push/pull`、AI 工具走的是同一账号。
|
|
70
|
+
- 启动时**自动从本机导入**:按用户 gitconfig 声明过的域名逐个 `git credential fill` 探测(串行 + 20 秒超时),`gh`、GCM、`store` 里的账号一次接管([决策 0038](./docs/decisions/0038-import-machine-credentials.md));面板另有手动「从本机导入」。
|
|
71
|
+
- 表单支持**浏览器登录**与令牌创建入口([决策 0024](./docs/decisions/0024-credentials-browser-sign-in.md))。
|
|
72
|
+
- 文件里不属于本插件的行(注释、`ssh://` 等)原样保留,**卸载插件后凭据仍属于用户**;未存域名 fail-fast(`GIT_TERMINAL_PROMPT=0`),不弹凭据对话框。
|
|
73
|
+
- 面板元数据(`method` / `updatedAt`)另存不含 secret 的旁文件。
|
|
74
|
+
|
|
75
|
+
### 远程管理
|
|
76
|
+
|
|
77
|
+
面板内管理仓库的多个远程地址(新增 / 编辑 / 删除,[决策 0022](./docs/decisions/0022-remote-address-management.md))。
|
|
78
|
+
|
|
79
|
+
### 网络可靠性
|
|
80
|
+
|
|
81
|
+
面向大陆网络下 GitHub 直连不稳定的三层处理([设计文档](./docs/design/network-reliability.md)):
|
|
82
|
+
|
|
83
|
+
- **国内加速**:读操作(fetch / update)把 `https://github.com/` 重写为「加速前缀 + 完整 URL」(默认前缀 `https://gh-proxy.com/`,[决策 0040](./docs/decisions/0040-accelerate-complete-github-url.md));**推送永不走镜像**,镜像读失败自动回直连再走备用链([决策 0044](./docs/decisions/0044-mirror-as-read-accelerator-and-ssh-hint.md))。
|
|
84
|
+
- **显式代理**:`networkProxy` 支持 `http(s)://` / `socks5://`,另可只读探测系统代理给出建议。
|
|
85
|
+
- **IP 优选备用通道**:直连发生可重试故障时,对 GitHub 地址做运行时 DoH 查询并用真实 git 请求竞速选路([决策 0042](./docs/decisions/0042-ip-optimized-fallback-channel.md))。
|
|
86
|
+
- **分级预算与核验**:按探测 / 读取 / 写入 / 超时核验分别设预算;推送前探测远端,推送超时后核验远端 ref,按失败类型给出建议([决策 0041](./docs/decisions/0041-network-budgets-and-push-verification.md))。
|
|
87
|
+
- 面板「**检测网络**」诊断入口;SSH 远端不做无效的 HTTP 通道探测,错误信息改为提示 HTTPS 方案。
|
|
88
|
+
|
|
89
|
+
### AI 工具
|
|
90
|
+
|
|
91
|
+
插件向模型注册两个只读优先的工具([决策 0029](./docs/decisions/0029-ai-network-credentials-tool.md)):
|
|
92
|
+
|
|
93
|
+
| 工具 | 作用 | 边界 |
|
|
94
|
+
| --- | --- | --- |
|
|
95
|
+
| `git_credentials` | 列出已配置的 git 主机与掩码后的凭据 | 只读;`includeSecret` 才返回明文 |
|
|
96
|
+
| `git_network` | 用本机凭据执行一条 git 网络命令 | 子命令白名单 `fetch` / `pull` / `push` / `ls-remote`;`repoPath` 必须绝对路径;禁止 `--force`、`--delete`、`-c`、`--upload-pack` / `--receive-pack` 等 |
|
|
97
|
+
|
|
98
|
+
凭据经 git 原生 `credential-store` 通道生效,**不进入命令行参数、不进入对话上下文**。
|
|
99
|
+
|
|
100
|
+
## 安装
|
|
101
|
+
|
|
102
|
+
前置条件:
|
|
103
|
+
|
|
104
|
+
- 已安装 DSH(DeepSeek Harness);
|
|
105
|
+
- `git` 在 PATH 中可用;
|
|
106
|
+
- Node.js `>= 22.19`(插件运行环境要求,见 `package.json` 的 `engines`)。
|
|
107
|
+
|
|
108
|
+
安装方式(任选其一):
|
|
109
|
+
|
|
110
|
+
1. DSH 的插件管理(Plugins)页签里搜索并安装 npm 包 **`dsh-git-branch-manage`**;
|
|
111
|
+
2. CLI:`dsh plugin add dsh-git-branch-manage`(见 [notes/FIRST-SPEC.md](./notes/FIRST-SPEC.md))。
|
|
112
|
+
|
|
113
|
+
peer 依赖(`@deepseek-ai/cordis`、`@deepseek-ai/dsh-settings`、`@deepseek-ai/dsh-host-webserver`、`@deepseek-ai/schemastery`)由 DSH 宿主提供,无需单独安装。侧边栏 Git 面板需要额外安装可选插件 `dsh-better-sidebar`。
|
|
114
|
+
|
|
115
|
+
## 配置
|
|
116
|
+
|
|
117
|
+
在 DSH 设置 → 插件 → **git 分支管理**(Host 条目 `dsh-git-branch`)的配置卡中修改;所有字段即时生效并写回 profile 的 `cordis.patch.yml`。未知配置键会被直接拒绝(防止拼写错误被默认值掩盖)。
|
|
118
|
+
|
|
119
|
+
| 配置项 | 默认值 | 说明 |
|
|
120
|
+
| --- | --- | --- |
|
|
121
|
+
| `rootDir` | `$DSH_HOME/gitworktree`(即 `~/.dsh/gitworktree`) | worktree 存放根路径;必须是绝对路径 |
|
|
122
|
+
| `groupSidebar` | `true` | 侧边栏把同一仓库的 worktree 归入一个分组 |
|
|
123
|
+
| `fetchBeforeCreate` | `false` | 创建 worktree 前 fetch 所有远程(失败不阻断创建) |
|
|
124
|
+
| `autoPruneWorktrees` | `false` | 创建后惰性清理陈旧 worktree |
|
|
125
|
+
| `keepWorktrees` | `30` | 清理保留上限,整数且 `>= 1`,只统计有效的 git worktree |
|
|
126
|
+
| `postCreateCopyFiles` | `[]` | 无 `.worktreeinclude` 时,创建后拷贝的配置文件列表 |
|
|
127
|
+
| `cnAccelerate` | `false` | 国内加速:仅重写 fetch / update 读操作,**push 永不重写** |
|
|
128
|
+
| `cnAccelerateUrl` | `https://gh-proxy.com/` | 加速前缀,须为不含空白的 http(s) URL |
|
|
129
|
+
| `networkProxy` | 空 | 显式代理,`http(s)` / `socks5` URL,不允许携带凭据与路径 |
|
|
130
|
+
|
|
131
|
+
## 开发
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
pnpm install # 需要 pnpm 11.8.0(packageManager 字段)
|
|
135
|
+
pnpm run build # 服务端:tsc -p tsconfig.json → lib/
|
|
136
|
+
pnpm run build:client # 客户端:tsdown → lib/client.js
|
|
137
|
+
pnpm run build:all # 上面两步
|
|
138
|
+
pnpm run watch:client # 客户端增量构建
|
|
139
|
+
pnpm run test # vitest run
|
|
140
|
+
pnpm run typecheck # tsc -p tsconfig.client.json --noEmit
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`prepublishOnly` 会依次执行 `typecheck` → `test` → `build:all`。
|
|
144
|
+
|
|
145
|
+
目录速览:
|
|
146
|
+
|
|
147
|
+
| 路径 | 内容 |
|
|
148
|
+
| --- | --- |
|
|
149
|
+
| `src/index.ts` | host 半边:设置 schema 与校验、HTTP 路由挂载、AI 工具注册 |
|
|
150
|
+
| `src/client/` | 浏览器半边:分支 chip、Git 面板、设置卡、worktree 对话框 |
|
|
151
|
+
| `src/wire.ts` | 双端共享的路由常量与请求/响应类型(零依赖契约) |
|
|
152
|
+
| `src/git.ts` | git 执行器:参数拼装 + porcelain 解析,exec 注入式 seam |
|
|
153
|
+
| `src/credential-service.ts` | 凭据唯一入口(store / include / shell 环境) |
|
|
154
|
+
| `src/network*.ts` | 网络通道:加速、代理、IP 优选、探测与预算 |
|
|
155
|
+
| `tests/*.spec.ts` | vitest 用例(37 个 spec),含真实 git 端到端回归 `shell-bridge-e2e` |
|
|
156
|
+
| `locale/*.json` | 插件展示元数据(标题/描述)的中英文词典;界面文案在 `src/client/locales.ts` |
|
|
157
|
+
| `docs/decisions/` | 决策记录(ADR),非平凡变更须随同一提交更新 |
|
|
158
|
+
| `.github/workflows/` | CI 与 tag 触发的 npm 发布([决策 0010](./docs/decisions/0010-github-actions-ci-and-npm-release.md)) |
|
|
159
|
+
|
|
160
|
+
## 文档
|
|
161
|
+
|
|
162
|
+
- [CHANGELOG.md](./CHANGELOG.md) — 版本变更记录(语义化版本)
|
|
163
|
+
- [docs/decisions/README.md](./docs/decisions/README.md) — 决策记录索引与规范
|
|
164
|
+
- [docs/design/network-reliability.md](./docs/design/network-reliability.md)、[docs/design/cn-accelerate.md](./docs/design/cn-accelerate.md) — 网络可靠性与国内加速设计
|
|
165
|
+
- [notes/FIRST-SPEC.md](./notes/FIRST-SPEC.md)、[notes/GIT-PANEL-SPEC.md](./notes/GIT-PANEL-SPEC.md) — 初始需求与面板规格
|
|
166
|
+
|
|
167
|
+
## 许可
|
|
168
|
+
|
|
169
|
+
[MIT](./LICENSE) © GaoHu1997
|
package/lib/client.js
CHANGED
|
@@ -65,8 +65,8 @@ window.__ModuleLoader__.load({
|
|
|
65
65
|
}
|
|
66
66
|
/**
|
|
67
67
|
* Normalize an accepted accelerate URL: trim, and append the trailing `/`
|
|
68
|
-
* the prefix concatenation requires (`https://gh-proxy.com/` +
|
|
69
|
-
*
|
|
68
|
+
* the prefix concatenation requires (`https://gh-proxy.com/` + the complete
|
|
69
|
+
* `https://github.com/o/r.git` URL). accelArgs builds the Git rewrite base.
|
|
70
70
|
* @param value - an accepted (non-empty, http(s), whitespace-free) URL.
|
|
71
71
|
* @returns the prefix ending in `/`.
|
|
72
72
|
*/
|
|
@@ -74,6 +74,37 @@ window.__ModuleLoader__.load({
|
|
|
74
74
|
const trimmed = value.trim();
|
|
75
75
|
return trimmed.endsWith("/") ? trimmed : `${trimmed}/`;
|
|
76
76
|
}
|
|
77
|
+
/** Schemes git's libcurl accepts for an HTTP(S) remote. */
|
|
78
|
+
const PROXY_SCHEMES = [
|
|
79
|
+
"http",
|
|
80
|
+
"https",
|
|
81
|
+
"socks5",
|
|
82
|
+
"socks5h"
|
|
83
|
+
];
|
|
84
|
+
/**
|
|
85
|
+
* Why this proxy value cannot be used, or undefined when it can.
|
|
86
|
+
*
|
|
87
|
+
* Credentials are rejected on purpose: the value is stored in the plugin
|
|
88
|
+
* config and passed as one `-c` argument, so `user:pass@` would be readable
|
|
89
|
+
* in the settings UI and in any diagnostics (0043).
|
|
90
|
+
* @param value - candidate proxy URL.
|
|
91
|
+
* @returns the user-facing reason, or undefined.
|
|
92
|
+
*/
|
|
93
|
+
function proxyUrlError(value) {
|
|
94
|
+
let url;
|
|
95
|
+
try {
|
|
96
|
+
url = new URL(value);
|
|
97
|
+
} catch {
|
|
98
|
+
return "must be an absolute proxy URL such as http://127.0.0.1:7890";
|
|
99
|
+
}
|
|
100
|
+
if (!PROXY_SCHEMES.includes(url.protocol.replace(":", ""))) return `scheme must be one of ${PROXY_SCHEMES.join(", ")}`;
|
|
101
|
+
if (url.hostname === "") return "must name a proxy host";
|
|
102
|
+
if (url.port === "") return "must name a proxy port";
|
|
103
|
+
const port = Number(url.port);
|
|
104
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) return "proxy port must be 1-65535";
|
|
105
|
+
if (url.username !== "" || url.password !== "") return "credentials in the proxy URL are not supported";
|
|
106
|
+
if (url.pathname !== "" && url.pathname !== "/" || url.search !== "" || url.hash !== "") return "must not carry a path, query, or fragment";
|
|
107
|
+
}
|
|
77
108
|
//#endregion
|
|
78
109
|
//#region src/wire.ts
|
|
79
110
|
/**
|
|
@@ -131,6 +162,8 @@ window.__ModuleLoader__.load({
|
|
|
131
162
|
const ROUTE_TAG = `${ROUTE_PREFIX}/tag`;
|
|
132
163
|
/** POST ROUTE_PREFIX/tag-push — push ONE existing local tag to its remote. */
|
|
133
164
|
const ROUTE_TAG_PUSH = `${ROUTE_PREFIX}/tag-push`;
|
|
165
|
+
/** POST ROUTE_PREFIX/network-probe — read the endpoint through every configured channel (diagnostics; no state change). */
|
|
166
|
+
const ROUTE_NETWORK_PROBE = `${ROUTE_PREFIX}/network-probe`;
|
|
134
167
|
/** POST ROUTE_PREFIX/credentials — the global per-host credentials, secrets
|
|
135
168
|
* masked (the stored secret never leaves the host). */
|
|
136
169
|
const ROUTE_CREDENTIALS = `${ROUTE_PREFIX}/credentials`;
|
|
@@ -223,6 +256,15 @@ window.__ModuleLoader__.load({
|
|
|
223
256
|
}
|
|
224
257
|
}
|
|
225
258
|
/**
|
|
259
|
+
* Probe every network channel (direct, proxy, pinned IP) against the
|
|
260
|
+
* repository's push URL; failures come back per channel rather than as one
|
|
261
|
+
* error, so the popover can show which route works right now (0043).
|
|
262
|
+
* @param repoPath - absolute directory inside the repository.
|
|
263
|
+
*/
|
|
264
|
+
function requestNetworkProbe(repoPath) {
|
|
265
|
+
return post(ROUTE_NETWORK_PROBE, { repoPath });
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
226
268
|
* Create or reuse the worktree for a branch.
|
|
227
269
|
* @param repoPath - absolute directory inside the repository.
|
|
228
270
|
* @param branch - branch display name.
|
|
@@ -2577,12 +2619,13 @@ window.__ModuleLoader__.load({
|
|
|
2577
2619
|
* a fact-spelling confirm (uncommitted count, ahead commits, sessions to
|
|
2578
2620
|
* be archived — the branch itself survives; a directory holding a running
|
|
2579
2621
|
* session withholds the verb). On the main checkout's BLANK session the
|
|
2580
|
-
* group
|
|
2581
|
-
*
|
|
2582
|
-
*
|
|
2583
|
-
*
|
|
2584
|
-
* gains the main checkout row (see
|
|
2585
|
-
* started MAIN-checkout session drops
|
|
2622
|
+
* group is also the direct hop into a branch held by another worktree —
|
|
2623
|
+
* that branch now ALSO keeps its local-group row (it is the user's branch
|
|
2624
|
+
* and hiding it read as missing), while the hop row is the way to open a
|
|
2625
|
+
* session already on it without touching the checkout. A linked-worktree
|
|
2626
|
+
* session keeps that group and gains the main checkout row (see
|
|
2627
|
+
* {@link buildLinkedWorktreeRows}); a started MAIN-checkout session drops
|
|
2628
|
+
* the group — its directory is fixed.
|
|
2586
2629
|
*/
|
|
2587
2630
|
/**
|
|
2588
2631
|
* Read the repository status for a directory; refetch on demand.
|
|
@@ -2633,11 +2676,13 @@ window.__ModuleLoader__.load({
|
|
|
2633
2676
|
}
|
|
2634
2677
|
/**
|
|
2635
2678
|
* Build the branch rows for the picker on the MAIN checkout, in three
|
|
2636
|
-
* kinds:
|
|
2637
|
-
*
|
|
2638
|
-
*
|
|
2639
|
-
*
|
|
2640
|
-
*
|
|
2679
|
+
* kinds: EVERY local branch, REMOTE branches, and one WORKTREE row per
|
|
2680
|
+
* linked worktree. A local branch held by another worktree STAYS in the
|
|
2681
|
+
* local group: hiding it made the picker look like branches had gone
|
|
2682
|
+
* missing. It is still listed as a worktree row too (the direct session
|
|
2683
|
+
* hop, see the owner's onAdoptWorktree) — the local row keeps the branch
|
|
2684
|
+
* visible and named; checking it out in place stays git's call, which
|
|
2685
|
+
* refuses a branch already checked out elsewhere.
|
|
2641
2686
|
*
|
|
2642
2687
|
* The worktree group is "every worktree EXCEPT the one this session sits
|
|
2643
2688
|
* in" — here that is the main checkout itself (the session IS the main
|
|
@@ -2653,10 +2698,9 @@ window.__ModuleLoader__.load({
|
|
|
2653
2698
|
* "not usable in this directory".
|
|
2654
2699
|
*/
|
|
2655
2700
|
function buildBranchRows$1(branches, worktrees) {
|
|
2656
|
-
const held = new Set(worktrees.flatMap((w) => w.main || w.branch === void 0 ? [] : [w.branch]));
|
|
2657
2701
|
const localMap = new Map(branches.filter((b) => b.kind === "local").map((b) => [b.name, b]));
|
|
2658
2702
|
return [
|
|
2659
|
-
...branches.filter((b) => b.kind === "local"
|
|
2703
|
+
...branches.filter((b) => b.kind === "local").map((b) => ({
|
|
2660
2704
|
name: b.name,
|
|
2661
2705
|
kind: "local",
|
|
2662
2706
|
...b.ahead === void 0 ? {} : { ahead: b.ahead },
|
|
@@ -3558,6 +3602,8 @@ window.__ModuleLoader__.load({
|
|
|
3558
3602
|
/** The fields this form edits. */
|
|
3559
3603
|
const ACCEL_ENABLED_FIELD = "cnAccelerate";
|
|
3560
3604
|
const ACCEL_URL_FIELD = "cnAccelerateUrl";
|
|
3605
|
+
/** The proxy channel's config key (0043); blank = no proxy channel. */
|
|
3606
|
+
const NETWORK_PROXY_FIELD = "networkProxy";
|
|
3561
3607
|
/**
|
|
3562
3608
|
* Stages the cn-accelerate edits over the `dsh-git-branch` Host entry.
|
|
3563
3609
|
*
|
|
@@ -3571,6 +3617,7 @@ window.__ModuleLoader__.load({
|
|
|
3571
3617
|
listeners = /* @__PURE__ */ new Set();
|
|
3572
3618
|
enabledDraft;
|
|
3573
3619
|
urlDraft;
|
|
3620
|
+
proxyDraft;
|
|
3574
3621
|
saving = false;
|
|
3575
3622
|
failed = false;
|
|
3576
3623
|
/**
|
|
@@ -3611,6 +3658,11 @@ window.__ModuleLoader__.load({
|
|
|
3611
3658
|
this.failed = false;
|
|
3612
3659
|
this.publish();
|
|
3613
3660
|
},
|
|
3661
|
+
editNetworkProxy: (text) => {
|
|
3662
|
+
this.proxyDraft = text;
|
|
3663
|
+
this.failed = false;
|
|
3664
|
+
this.publish();
|
|
3665
|
+
},
|
|
3614
3666
|
restoreAccelDefault: () => {
|
|
3615
3667
|
this.urlDraft = ACCEL_DEFAULT_URL;
|
|
3616
3668
|
this.failed = false;
|
|
@@ -3618,9 +3670,10 @@ window.__ModuleLoader__.load({
|
|
|
3618
3670
|
},
|
|
3619
3671
|
saveAccel: () => this.save(),
|
|
3620
3672
|
discardAccel: () => {
|
|
3621
|
-
if (this.enabledDraft === void 0 && this.urlDraft === void 0 && !this.failed) return;
|
|
3673
|
+
if (this.enabledDraft === void 0 && this.urlDraft === void 0 && this.proxyDraft === void 0 && !this.failed) return;
|
|
3622
3674
|
this.enabledDraft = void 0;
|
|
3623
3675
|
this.urlDraft = void 0;
|
|
3676
|
+
this.proxyDraft = void 0;
|
|
3624
3677
|
this.failed = false;
|
|
3625
3678
|
this.publish();
|
|
3626
3679
|
}
|
|
@@ -3632,6 +3685,11 @@ window.__ModuleLoader__.load({
|
|
|
3632
3685
|
const value = this.scope.getSnapshot().value?.[ACCEL_URL_FIELD];
|
|
3633
3686
|
return this.urlDraft ?? value ?? "https://gh-proxy.com/";
|
|
3634
3687
|
}
|
|
3688
|
+
/** The proxy as the user sees it: the live draft, else the stored value. */
|
|
3689
|
+
effectiveProxyText() {
|
|
3690
|
+
const value = this.scope.getSnapshot().value?.[NETWORK_PROXY_FIELD];
|
|
3691
|
+
return this.proxyDraft ?? (typeof value === "string" ? value : "");
|
|
3692
|
+
}
|
|
3635
3693
|
/**
|
|
3636
3694
|
* Write the switch and the normalized URL TOGETHER, then re-seed from what
|
|
3637
3695
|
* the Host accepted. The URL is validated first — an unusable draft blocks
|
|
@@ -3640,11 +3698,14 @@ window.__ModuleLoader__.load({
|
|
|
3640
3698
|
* A save that did not land keeps its drafts so the user can correct them.
|
|
3641
3699
|
*/
|
|
3642
3700
|
async save() {
|
|
3643
|
-
if (this.enabledDraft === void 0 && this.urlDraft === void 0 || this.saving) return;
|
|
3701
|
+
if (this.enabledDraft === void 0 && this.urlDraft === void 0 && this.proxyDraft === void 0 || this.saving) return;
|
|
3644
3702
|
const urlText = this.effectiveUrlText();
|
|
3645
3703
|
if (accelerateUrlError(urlText) !== void 0) return;
|
|
3704
|
+
const proxyText = this.effectiveProxyText().trim();
|
|
3705
|
+
if (proxyText !== "" && proxyUrlError(proxyText) !== void 0) return;
|
|
3646
3706
|
const intendedEnabled = this.enabledDraft ?? this.scope.getSnapshot().value?.["cnAccelerate"] ?? false;
|
|
3647
3707
|
const intendedUrl = normalizeAccelerateUrl(urlText);
|
|
3708
|
+
const writeProxy = this.proxyDraft !== void 0;
|
|
3648
3709
|
this.saving = true;
|
|
3649
3710
|
this.failed = false;
|
|
3650
3711
|
this.publish();
|
|
@@ -3652,14 +3713,16 @@ window.__ModuleLoader__.load({
|
|
|
3652
3713
|
try {
|
|
3653
3714
|
await this.scope.set(ACCEL_ENABLED_FIELD, intendedEnabled);
|
|
3654
3715
|
await this.scope.set(ACCEL_URL_FIELD, intendedUrl);
|
|
3716
|
+
if (writeProxy) await this.scope.set(NETWORK_PROXY_FIELD, proxyText);
|
|
3655
3717
|
const user = this.userLayer();
|
|
3656
|
-
if (user?.["cnAccelerate"] !== intendedEnabled || user?.["cnAccelerateUrl"] !== intendedUrl) landed = false;
|
|
3718
|
+
if (user?.["cnAccelerate"] !== intendedEnabled || user?.["cnAccelerateUrl"] !== intendedUrl || writeProxy && (user?.["networkProxy"] ?? "") !== proxyText) landed = false;
|
|
3657
3719
|
} catch (_settingsWriteFailure) {
|
|
3658
3720
|
landed = false;
|
|
3659
3721
|
}
|
|
3660
3722
|
if (landed) {
|
|
3661
3723
|
this.enabledDraft = void 0;
|
|
3662
3724
|
this.urlDraft = void 0;
|
|
3725
|
+
this.proxyDraft = void 0;
|
|
3663
3726
|
}
|
|
3664
3727
|
this.saving = false;
|
|
3665
3728
|
this.failed = !landed;
|
|
@@ -3675,17 +3738,22 @@ window.__ModuleLoader__.load({
|
|
|
3675
3738
|
const storedEnabled = snapshot.value?.["cnAccelerate"] ?? false;
|
|
3676
3739
|
const storedUrl = snapshot.value?.["cnAccelerateUrl"] ?? "https://gh-proxy.com/";
|
|
3677
3740
|
const urlText = this.effectiveUrlText();
|
|
3741
|
+
const storedProxy = typeof snapshot.value?.["networkProxy"] === "string" ? snapshot.value[NETWORK_PROXY_FIELD] : "";
|
|
3742
|
+
const proxyText = this.effectiveProxyText();
|
|
3678
3743
|
const enabledDirty = this.enabledDraft !== void 0 && this.enabledDraft !== storedEnabled;
|
|
3679
3744
|
const urlDirty = this.urlDraft !== void 0 && this.urlDraft !== storedUrl;
|
|
3745
|
+
const proxyDirty = this.proxyDraft !== void 0 && this.proxyDraft !== storedProxy;
|
|
3680
3746
|
return {
|
|
3681
3747
|
available: snapshot.status === "ready",
|
|
3682
3748
|
writable: snapshot.writable,
|
|
3683
3749
|
enabled: this.enabledDraft ?? storedEnabled,
|
|
3684
3750
|
urlText,
|
|
3685
|
-
dirty: enabledDirty || urlDirty,
|
|
3751
|
+
dirty: enabledDirty || urlDirty || proxyDirty,
|
|
3686
3752
|
saving: this.saving,
|
|
3687
3753
|
failed: this.failed,
|
|
3688
|
-
urlError: accelerateUrlError(urlText) ?? null
|
|
3754
|
+
urlError: accelerateUrlError(urlText) ?? null,
|
|
3755
|
+
proxyText,
|
|
3756
|
+
proxyError: proxyText.trim() === "" ? null : proxyUrlError(proxyText.trim()) ?? null
|
|
3689
3757
|
};
|
|
3690
3758
|
}
|
|
3691
3759
|
/** Replace the snapshot reference and notify, only when the fact moved. */
|
|
@@ -4620,8 +4688,17 @@ window.__ModuleLoader__.load({
|
|
|
4620
4688
|
*
|
|
4621
4689
|
* @module git-worktree/client/panel/CnAcceleratePopover
|
|
4622
4690
|
*/
|
|
4691
|
+
/** Channel id → its display name; the probe rows stay in host order. */
|
|
4692
|
+
const CHANNEL_LABELS = {
|
|
4693
|
+
direct: (t) => t("accel.channel.direct"),
|
|
4694
|
+
proxy: (t) => t("accel.channel.proxy"),
|
|
4695
|
+
ip: (t) => t("accel.channel.ip")
|
|
4696
|
+
};
|
|
4623
4697
|
function CnAcceleratePopover(props) {
|
|
4624
|
-
const { accel, t, pushToast, onClose } = props;
|
|
4698
|
+
const { accel, t, pushToast, cwd, onClose } = props;
|
|
4699
|
+
const [probe, setProbe] = (0, react.useState)(null);
|
|
4700
|
+
const [probing, setProbing] = (0, react.useState)(false);
|
|
4701
|
+
const [probeError, setProbeError] = (0, react.useState)(null);
|
|
4625
4702
|
const rootRef = (0, react.useRef)(null);
|
|
4626
4703
|
const state = (0, react.useSyncExternalStore)(accel.store.subscribe, accel.store.getSnapshot);
|
|
4627
4704
|
(0, react.useEffect)(() => {
|
|
@@ -4640,7 +4717,7 @@ window.__ModuleLoader__.load({
|
|
|
4640
4717
|
}, [onClose]);
|
|
4641
4718
|
const locked = state.saving || !state.writable;
|
|
4642
4719
|
const onSave = (0, react.useCallback)(() => {
|
|
4643
|
-
if (state.saving || state.urlError !== null || !state.writable) return;
|
|
4720
|
+
if (state.saving || state.urlError !== null || state.proxyError !== null || !state.writable) return;
|
|
4644
4721
|
(async () => {
|
|
4645
4722
|
await accel.saveAccel();
|
|
4646
4723
|
const after = accel.store.getSnapshot();
|
|
@@ -4660,6 +4737,25 @@ window.__ModuleLoader__.load({
|
|
|
4660
4737
|
state.writable,
|
|
4661
4738
|
t
|
|
4662
4739
|
]);
|
|
4740
|
+
const onProbe = (0, react.useCallback)(() => {
|
|
4741
|
+
if (cwd === void 0 || probing) return;
|
|
4742
|
+
setProbing(true);
|
|
4743
|
+
setProbeError(null);
|
|
4744
|
+
(async () => {
|
|
4745
|
+
const result = await requestNetworkProbe(cwd);
|
|
4746
|
+
setProbing(false);
|
|
4747
|
+
if (!result.ok) {
|
|
4748
|
+
setProbe(null);
|
|
4749
|
+
setProbeError(result.error);
|
|
4750
|
+
return;
|
|
4751
|
+
}
|
|
4752
|
+
setProbe({
|
|
4753
|
+
url: result.url,
|
|
4754
|
+
channels: result.channels,
|
|
4755
|
+
...result.systemProxy === void 0 ? {} : { systemProxy: result.systemProxy }
|
|
4756
|
+
});
|
|
4757
|
+
})();
|
|
4758
|
+
}, [cwd, probing]);
|
|
4663
4759
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4664
4760
|
className: GitHistoryPanel_module_default.cnPop,
|
|
4665
4761
|
ref: rootRef,
|
|
@@ -4715,6 +4811,46 @@ window.__ModuleLoader__.load({
|
|
|
4715
4811
|
className: GitHistoryPanel_module_default.cnError,
|
|
4716
4812
|
children: state.urlError === "empty" ? t("accel.urlEmpty") : t("accel.urlInvalid")
|
|
4717
4813
|
}) : null,
|
|
4814
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4815
|
+
className: GitHistoryPanel_module_default.cnFieldHead,
|
|
4816
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4817
|
+
className: GitHistoryPanel_module_default.cnFieldLabel,
|
|
4818
|
+
children: t("accel.proxyLabel")
|
|
4819
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4820
|
+
type: "button",
|
|
4821
|
+
className: GitHistoryPanel_module_default.cnLink,
|
|
4822
|
+
disabled: locked || cwd === void 0 || probing,
|
|
4823
|
+
onClick: onProbe,
|
|
4824
|
+
children: probing ? t("accel.probing") : t("accel.probe")
|
|
4825
|
+
})]
|
|
4826
|
+
}),
|
|
4827
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4828
|
+
type: "text",
|
|
4829
|
+
className: state.proxyError !== null ? GitHistoryPanel_module_default.cnInputBad : GitHistoryPanel_module_default.cnInput,
|
|
4830
|
+
value: state.proxyText,
|
|
4831
|
+
spellCheck: false,
|
|
4832
|
+
disabled: locked,
|
|
4833
|
+
placeholder: "http://127.0.0.1:7890",
|
|
4834
|
+
"aria-invalid": state.proxyError !== null,
|
|
4835
|
+
onChange: (event) => accel.editNetworkProxy(event.target.value)
|
|
4836
|
+
}),
|
|
4837
|
+
state.proxyError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4838
|
+
className: GitHistoryPanel_module_default.cnError,
|
|
4839
|
+
children: t("accel.proxyInvalid")
|
|
4840
|
+
}) : null,
|
|
4841
|
+
probe !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4842
|
+
className: GitHistoryPanel_module_default.cnHint,
|
|
4843
|
+
children: probe.channels.map((channel) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
4844
|
+
channel.ok ? "✓ " : "✗ ",
|
|
4845
|
+
CHANNEL_LABELS[channel.channel](t),
|
|
4846
|
+
` \u00b7 ${String(channel.elapsedMs)} ms`,
|
|
4847
|
+
channel.detail === void 0 ? null : ` \u00b7 ${channel.detail}`
|
|
4848
|
+
] }, channel.channel))
|
|
4849
|
+
}) : null,
|
|
4850
|
+
probeError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4851
|
+
className: GitHistoryPanel_module_default.cnError,
|
|
4852
|
+
children: probeError
|
|
4853
|
+
}) : null,
|
|
4718
4854
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4719
4855
|
className: GitHistoryPanel_module_default.cnFooter,
|
|
4720
4856
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -4725,7 +4861,7 @@ window.__ModuleLoader__.load({
|
|
|
4725
4861
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4726
4862
|
type: "button",
|
|
4727
4863
|
className: GitHistoryPanel_module_default.cnSave,
|
|
4728
|
-
disabled: state.saving || state.urlError !== null || !state.writable,
|
|
4864
|
+
disabled: state.saving || state.urlError !== null || state.proxyError !== null || !state.writable,
|
|
4729
4865
|
onClick: onSave,
|
|
4730
4866
|
children: t("cardSave")
|
|
4731
4867
|
})]
|
|
@@ -7125,6 +7261,7 @@ window.__ModuleLoader__.load({
|
|
|
7125
7261
|
accel,
|
|
7126
7262
|
t,
|
|
7127
7263
|
pushToast,
|
|
7264
|
+
cwd,
|
|
7128
7265
|
onClose: closeAccel
|
|
7129
7266
|
}) : null]
|
|
7130
7267
|
}),
|
|
@@ -7946,7 +8083,7 @@ window.__ModuleLoader__.load({
|
|
|
7946
8083
|
children: t("cardDescription")
|
|
7947
8084
|
});
|
|
7948
8085
|
const lockInput = !state.writable;
|
|
7949
|
-
const lockActions = !state.dirty && !accelState.dirty || state.saving || accelState.saving || !state.keepWorktreesValid || accelState.urlError !== null;
|
|
8086
|
+
const lockActions = !state.dirty && !accelState.dirty || state.saving || accelState.saving || !state.keepWorktreesValid || accelState.urlError !== null || accelState.proxyError !== null;
|
|
7950
8087
|
const legacyRootText = storage?.legacyRoot ?? (state.rootDir === "" ? "—" : state.rootDir);
|
|
7951
8088
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7952
8089
|
className: GitWorktreeCard_module_default.page,
|
|
@@ -8170,6 +8307,33 @@ window.__ModuleLoader__.load({
|
|
|
8170
8307
|
}) : null
|
|
8171
8308
|
]
|
|
8172
8309
|
}),
|
|
8310
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
8311
|
+
className: `${GitWorktreeCard_module_default.field} ${GitWorktreeCard_module_default.copyFilesRow}`,
|
|
8312
|
+
htmlFor: "git-worktree-card-network-proxy",
|
|
8313
|
+
children: [
|
|
8314
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8315
|
+
className: GitWorktreeCard_module_default.fieldLabel,
|
|
8316
|
+
children: t("accel.proxyLabel")
|
|
8317
|
+
}),
|
|
8318
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
8319
|
+
id: "git-worktree-card-network-proxy",
|
|
8320
|
+
className: GitWorktreeCard_module_default.accelInput,
|
|
8321
|
+
type: "text",
|
|
8322
|
+
spellCheck: false,
|
|
8323
|
+
placeholder: "http://127.0.0.1:7890",
|
|
8324
|
+
disabled: lockInput || accelState.saving,
|
|
8325
|
+
value: accelState.proxyText,
|
|
8326
|
+
onChange: (event) => {
|
|
8327
|
+
props.editNetworkProxy(event.target.value);
|
|
8328
|
+
}
|
|
8329
|
+
}),
|
|
8330
|
+
accelState.proxyError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8331
|
+
className: GitWorktreeCard_module_default.keepBad,
|
|
8332
|
+
role: "alert",
|
|
8333
|
+
children: t("accel.proxyInvalid")
|
|
8334
|
+
}) : null
|
|
8335
|
+
]
|
|
8336
|
+
}),
|
|
8173
8337
|
history.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8174
8338
|
className: `${GitWorktreeCard_module_default.field} ${GitWorktreeCard_module_default.historyRow}`,
|
|
8175
8339
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -8479,11 +8643,18 @@ window.__ModuleLoader__.load({
|
|
|
8479
8643
|
"panel.stUnmerged": "conflict",
|
|
8480
8644
|
"panel.cnAccelerate": "China acceleration",
|
|
8481
8645
|
"accel.enable": "Enable acceleration",
|
|
8482
|
-
"accel.hint": "
|
|
8646
|
+
"accel.hint": "Enter a proxy prefix (e.g. https://gh-proxy.com/); fetch/update appends the complete GitHub URL. Push stays direct. The mirror sees repository content — do not use it for private or sensitive repositories.",
|
|
8483
8647
|
"accel.urlLabel": "Acceleration URL",
|
|
8484
8648
|
"accel.restoreDefault": "Restore default",
|
|
8485
8649
|
"accel.urlInvalid": "Enter an http(s):// URL",
|
|
8486
8650
|
"accel.urlEmpty": "Enter an acceleration URL",
|
|
8651
|
+
"accel.proxyLabel": "Proxy (optional)",
|
|
8652
|
+
"accel.proxyInvalid": "Enter an http(s)/socks5 proxy URL as host:port, without credentials",
|
|
8653
|
+
"accel.probe": "Test network",
|
|
8654
|
+
"accel.probing": "Testing...",
|
|
8655
|
+
"accel.channel.direct": "Direct",
|
|
8656
|
+
"accel.channel.proxy": "Proxy",
|
|
8657
|
+
"accel.channel.ip": "Pinned IP",
|
|
8487
8658
|
"accel.saveFailed": "The change did not save. Try again.",
|
|
8488
8659
|
"accel.savedOn": "China acceleration enabled: {url}",
|
|
8489
8660
|
"accel.savedOff": "China acceleration disabled",
|
|
@@ -8769,11 +8940,18 @@ window.__ModuleLoader__.load({
|
|
|
8769
8940
|
"panel.stUnmerged": "冲突",
|
|
8770
8941
|
"panel.cnAccelerate": "国内加速",
|
|
8771
8942
|
"accel.enable": "启用加速",
|
|
8772
|
-
"accel.hint": "
|
|
8943
|
+
"accel.hint": "填写加速站前缀(如 https://gh-proxy.com/),提取/更新时会在其后拼接完整 GitHub 地址;推送不走加速。第三方加速站可看到仓库内容,请勿用于私有/敏感仓库。",
|
|
8773
8944
|
"accel.urlLabel": "加速地址",
|
|
8774
8945
|
"accel.restoreDefault": "恢复默认",
|
|
8775
8946
|
"accel.urlInvalid": "请输入以 http(s):// 开头的地址",
|
|
8776
8947
|
"accel.urlEmpty": "请输入加速地址",
|
|
8948
|
+
"accel.proxyLabel": "代理(可选)",
|
|
8949
|
+
"accel.proxyInvalid": "请输入 主机:端口 形式的 http(s)/socks5 代理地址,且不带账号密码",
|
|
8950
|
+
"accel.probe": "检测网络",
|
|
8951
|
+
"accel.probing": "检测中...",
|
|
8952
|
+
"accel.channel.direct": "直连",
|
|
8953
|
+
"accel.channel.proxy": "代理",
|
|
8954
|
+
"accel.channel.ip": "固定 IP",
|
|
8777
8955
|
"accel.saveFailed": "保存未生效,请重试。",
|
|
8778
8956
|
"accel.savedOn": "已启用国内加速:{url}",
|
|
8779
8957
|
"accel.savedOff": "已关闭国内加速",
|