dsh-git-branch-manage 1.2.0 → 1.3.0
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 +17 -0
- package/lib/client.js +189 -13
- 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,23 @@
|
|
|
2
2
|
|
|
3
3
|
本文件记录 `dsh-git-branch-manage` 的重要变更。版本号遵循语义化版本。
|
|
4
4
|
|
|
5
|
+
## [1.3.0] - 2026-11-04
|
|
6
|
+
|
|
7
|
+
### 新增
|
|
8
|
+
|
|
9
|
+
- **网络备用通道与代理配置**(决策 0042、0043):直连发生可重试网络故障时,对 GitHub 地址使用运行时 DoH 查询和真实 Git 请求竞速选择可用 IP;支持显式配置 HTTP(S)/SOCKS 代理,并提供只读系统代理探测建议与「检测网络」诊断。
|
|
10
|
+
- **网络操作状态诊断**(决策 0041):按探测、读取、写入和超时核验分别设置预算;推送前探测远端,推送超时后核验远端 ref,并按失败类型给出建议。
|
|
11
|
+
|
|
12
|
+
### 变更
|
|
13
|
+
|
|
14
|
+
- **镜像仅作为读取加速**(决策 0044):镜像 fetch/update 失败后自动恢复原始 URL 回直连,再按备用通道链尝试;推送不走镜像,避免将写入交给第三方。
|
|
15
|
+
- SSH 远端不再进行无效的 HTTP 代理/IP 通道探测,错误信息改为提示可执行的 HTTPS 方案。
|
|
16
|
+
- 加速前缀改为拼接完整 GitHub URL(决策 0040),并用真实 Git URL 解析与提取覆盖回归场景。
|
|
17
|
+
|
|
18
|
+
### 修复
|
|
19
|
+
|
|
20
|
+
- **CI 在非 Windows 上失败**(决策 0046):系统代理探测的用例此前无条件假设 Windows 注册表,在 ubuntu-latest 上必然失败;现按平台分派,Windows 侧保留注册表行为覆盖,非 Windows 侧断言不读取注册表。
|
|
21
|
+
|
|
5
22
|
## [1.2.0] - 2026-11-02
|
|
6
23
|
|
|
7
24
|
### 新增
|
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.
|
|
@@ -3558,6 +3600,8 @@ window.__ModuleLoader__.load({
|
|
|
3558
3600
|
/** The fields this form edits. */
|
|
3559
3601
|
const ACCEL_ENABLED_FIELD = "cnAccelerate";
|
|
3560
3602
|
const ACCEL_URL_FIELD = "cnAccelerateUrl";
|
|
3603
|
+
/** The proxy channel's config key (0043); blank = no proxy channel. */
|
|
3604
|
+
const NETWORK_PROXY_FIELD = "networkProxy";
|
|
3561
3605
|
/**
|
|
3562
3606
|
* Stages the cn-accelerate edits over the `dsh-git-branch` Host entry.
|
|
3563
3607
|
*
|
|
@@ -3571,6 +3615,7 @@ window.__ModuleLoader__.load({
|
|
|
3571
3615
|
listeners = /* @__PURE__ */ new Set();
|
|
3572
3616
|
enabledDraft;
|
|
3573
3617
|
urlDraft;
|
|
3618
|
+
proxyDraft;
|
|
3574
3619
|
saving = false;
|
|
3575
3620
|
failed = false;
|
|
3576
3621
|
/**
|
|
@@ -3611,6 +3656,11 @@ window.__ModuleLoader__.load({
|
|
|
3611
3656
|
this.failed = false;
|
|
3612
3657
|
this.publish();
|
|
3613
3658
|
},
|
|
3659
|
+
editNetworkProxy: (text) => {
|
|
3660
|
+
this.proxyDraft = text;
|
|
3661
|
+
this.failed = false;
|
|
3662
|
+
this.publish();
|
|
3663
|
+
},
|
|
3614
3664
|
restoreAccelDefault: () => {
|
|
3615
3665
|
this.urlDraft = ACCEL_DEFAULT_URL;
|
|
3616
3666
|
this.failed = false;
|
|
@@ -3618,9 +3668,10 @@ window.__ModuleLoader__.load({
|
|
|
3618
3668
|
},
|
|
3619
3669
|
saveAccel: () => this.save(),
|
|
3620
3670
|
discardAccel: () => {
|
|
3621
|
-
if (this.enabledDraft === void 0 && this.urlDraft === void 0 && !this.failed) return;
|
|
3671
|
+
if (this.enabledDraft === void 0 && this.urlDraft === void 0 && this.proxyDraft === void 0 && !this.failed) return;
|
|
3622
3672
|
this.enabledDraft = void 0;
|
|
3623
3673
|
this.urlDraft = void 0;
|
|
3674
|
+
this.proxyDraft = void 0;
|
|
3624
3675
|
this.failed = false;
|
|
3625
3676
|
this.publish();
|
|
3626
3677
|
}
|
|
@@ -3632,6 +3683,11 @@ window.__ModuleLoader__.load({
|
|
|
3632
3683
|
const value = this.scope.getSnapshot().value?.[ACCEL_URL_FIELD];
|
|
3633
3684
|
return this.urlDraft ?? value ?? "https://gh-proxy.com/";
|
|
3634
3685
|
}
|
|
3686
|
+
/** The proxy as the user sees it: the live draft, else the stored value. */
|
|
3687
|
+
effectiveProxyText() {
|
|
3688
|
+
const value = this.scope.getSnapshot().value?.[NETWORK_PROXY_FIELD];
|
|
3689
|
+
return this.proxyDraft ?? (typeof value === "string" ? value : "");
|
|
3690
|
+
}
|
|
3635
3691
|
/**
|
|
3636
3692
|
* Write the switch and the normalized URL TOGETHER, then re-seed from what
|
|
3637
3693
|
* the Host accepted. The URL is validated first — an unusable draft blocks
|
|
@@ -3640,11 +3696,14 @@ window.__ModuleLoader__.load({
|
|
|
3640
3696
|
* A save that did not land keeps its drafts so the user can correct them.
|
|
3641
3697
|
*/
|
|
3642
3698
|
async save() {
|
|
3643
|
-
if (this.enabledDraft === void 0 && this.urlDraft === void 0 || this.saving) return;
|
|
3699
|
+
if (this.enabledDraft === void 0 && this.urlDraft === void 0 && this.proxyDraft === void 0 || this.saving) return;
|
|
3644
3700
|
const urlText = this.effectiveUrlText();
|
|
3645
3701
|
if (accelerateUrlError(urlText) !== void 0) return;
|
|
3702
|
+
const proxyText = this.effectiveProxyText().trim();
|
|
3703
|
+
if (proxyText !== "" && proxyUrlError(proxyText) !== void 0) return;
|
|
3646
3704
|
const intendedEnabled = this.enabledDraft ?? this.scope.getSnapshot().value?.["cnAccelerate"] ?? false;
|
|
3647
3705
|
const intendedUrl = normalizeAccelerateUrl(urlText);
|
|
3706
|
+
const writeProxy = this.proxyDraft !== void 0;
|
|
3648
3707
|
this.saving = true;
|
|
3649
3708
|
this.failed = false;
|
|
3650
3709
|
this.publish();
|
|
@@ -3652,14 +3711,16 @@ window.__ModuleLoader__.load({
|
|
|
3652
3711
|
try {
|
|
3653
3712
|
await this.scope.set(ACCEL_ENABLED_FIELD, intendedEnabled);
|
|
3654
3713
|
await this.scope.set(ACCEL_URL_FIELD, intendedUrl);
|
|
3714
|
+
if (writeProxy) await this.scope.set(NETWORK_PROXY_FIELD, proxyText);
|
|
3655
3715
|
const user = this.userLayer();
|
|
3656
|
-
if (user?.["cnAccelerate"] !== intendedEnabled || user?.["cnAccelerateUrl"] !== intendedUrl) landed = false;
|
|
3716
|
+
if (user?.["cnAccelerate"] !== intendedEnabled || user?.["cnAccelerateUrl"] !== intendedUrl || writeProxy && (user?.["networkProxy"] ?? "") !== proxyText) landed = false;
|
|
3657
3717
|
} catch (_settingsWriteFailure) {
|
|
3658
3718
|
landed = false;
|
|
3659
3719
|
}
|
|
3660
3720
|
if (landed) {
|
|
3661
3721
|
this.enabledDraft = void 0;
|
|
3662
3722
|
this.urlDraft = void 0;
|
|
3723
|
+
this.proxyDraft = void 0;
|
|
3663
3724
|
}
|
|
3664
3725
|
this.saving = false;
|
|
3665
3726
|
this.failed = !landed;
|
|
@@ -3675,17 +3736,22 @@ window.__ModuleLoader__.load({
|
|
|
3675
3736
|
const storedEnabled = snapshot.value?.["cnAccelerate"] ?? false;
|
|
3676
3737
|
const storedUrl = snapshot.value?.["cnAccelerateUrl"] ?? "https://gh-proxy.com/";
|
|
3677
3738
|
const urlText = this.effectiveUrlText();
|
|
3739
|
+
const storedProxy = typeof snapshot.value?.["networkProxy"] === "string" ? snapshot.value[NETWORK_PROXY_FIELD] : "";
|
|
3740
|
+
const proxyText = this.effectiveProxyText();
|
|
3678
3741
|
const enabledDirty = this.enabledDraft !== void 0 && this.enabledDraft !== storedEnabled;
|
|
3679
3742
|
const urlDirty = this.urlDraft !== void 0 && this.urlDraft !== storedUrl;
|
|
3743
|
+
const proxyDirty = this.proxyDraft !== void 0 && this.proxyDraft !== storedProxy;
|
|
3680
3744
|
return {
|
|
3681
3745
|
available: snapshot.status === "ready",
|
|
3682
3746
|
writable: snapshot.writable,
|
|
3683
3747
|
enabled: this.enabledDraft ?? storedEnabled,
|
|
3684
3748
|
urlText,
|
|
3685
|
-
dirty: enabledDirty || urlDirty,
|
|
3749
|
+
dirty: enabledDirty || urlDirty || proxyDirty,
|
|
3686
3750
|
saving: this.saving,
|
|
3687
3751
|
failed: this.failed,
|
|
3688
|
-
urlError: accelerateUrlError(urlText) ?? null
|
|
3752
|
+
urlError: accelerateUrlError(urlText) ?? null,
|
|
3753
|
+
proxyText,
|
|
3754
|
+
proxyError: proxyText.trim() === "" ? null : proxyUrlError(proxyText.trim()) ?? null
|
|
3689
3755
|
};
|
|
3690
3756
|
}
|
|
3691
3757
|
/** Replace the snapshot reference and notify, only when the fact moved. */
|
|
@@ -4620,8 +4686,17 @@ window.__ModuleLoader__.load({
|
|
|
4620
4686
|
*
|
|
4621
4687
|
* @module git-worktree/client/panel/CnAcceleratePopover
|
|
4622
4688
|
*/
|
|
4689
|
+
/** Channel id → its display name; the probe rows stay in host order. */
|
|
4690
|
+
const CHANNEL_LABELS = {
|
|
4691
|
+
direct: (t) => t("accel.channel.direct"),
|
|
4692
|
+
proxy: (t) => t("accel.channel.proxy"),
|
|
4693
|
+
ip: (t) => t("accel.channel.ip")
|
|
4694
|
+
};
|
|
4623
4695
|
function CnAcceleratePopover(props) {
|
|
4624
|
-
const { accel, t, pushToast, onClose } = props;
|
|
4696
|
+
const { accel, t, pushToast, cwd, onClose } = props;
|
|
4697
|
+
const [probe, setProbe] = (0, react.useState)(null);
|
|
4698
|
+
const [probing, setProbing] = (0, react.useState)(false);
|
|
4699
|
+
const [probeError, setProbeError] = (0, react.useState)(null);
|
|
4625
4700
|
const rootRef = (0, react.useRef)(null);
|
|
4626
4701
|
const state = (0, react.useSyncExternalStore)(accel.store.subscribe, accel.store.getSnapshot);
|
|
4627
4702
|
(0, react.useEffect)(() => {
|
|
@@ -4640,7 +4715,7 @@ window.__ModuleLoader__.load({
|
|
|
4640
4715
|
}, [onClose]);
|
|
4641
4716
|
const locked = state.saving || !state.writable;
|
|
4642
4717
|
const onSave = (0, react.useCallback)(() => {
|
|
4643
|
-
if (state.saving || state.urlError !== null || !state.writable) return;
|
|
4718
|
+
if (state.saving || state.urlError !== null || state.proxyError !== null || !state.writable) return;
|
|
4644
4719
|
(async () => {
|
|
4645
4720
|
await accel.saveAccel();
|
|
4646
4721
|
const after = accel.store.getSnapshot();
|
|
@@ -4660,6 +4735,25 @@ window.__ModuleLoader__.load({
|
|
|
4660
4735
|
state.writable,
|
|
4661
4736
|
t
|
|
4662
4737
|
]);
|
|
4738
|
+
const onProbe = (0, react.useCallback)(() => {
|
|
4739
|
+
if (cwd === void 0 || probing) return;
|
|
4740
|
+
setProbing(true);
|
|
4741
|
+
setProbeError(null);
|
|
4742
|
+
(async () => {
|
|
4743
|
+
const result = await requestNetworkProbe(cwd);
|
|
4744
|
+
setProbing(false);
|
|
4745
|
+
if (!result.ok) {
|
|
4746
|
+
setProbe(null);
|
|
4747
|
+
setProbeError(result.error);
|
|
4748
|
+
return;
|
|
4749
|
+
}
|
|
4750
|
+
setProbe({
|
|
4751
|
+
url: result.url,
|
|
4752
|
+
channels: result.channels,
|
|
4753
|
+
...result.systemProxy === void 0 ? {} : { systemProxy: result.systemProxy }
|
|
4754
|
+
});
|
|
4755
|
+
})();
|
|
4756
|
+
}, [cwd, probing]);
|
|
4663
4757
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4664
4758
|
className: GitHistoryPanel_module_default.cnPop,
|
|
4665
4759
|
ref: rootRef,
|
|
@@ -4715,6 +4809,46 @@ window.__ModuleLoader__.load({
|
|
|
4715
4809
|
className: GitHistoryPanel_module_default.cnError,
|
|
4716
4810
|
children: state.urlError === "empty" ? t("accel.urlEmpty") : t("accel.urlInvalid")
|
|
4717
4811
|
}) : null,
|
|
4812
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4813
|
+
className: GitHistoryPanel_module_default.cnFieldHead,
|
|
4814
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4815
|
+
className: GitHistoryPanel_module_default.cnFieldLabel,
|
|
4816
|
+
children: t("accel.proxyLabel")
|
|
4817
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4818
|
+
type: "button",
|
|
4819
|
+
className: GitHistoryPanel_module_default.cnLink,
|
|
4820
|
+
disabled: locked || cwd === void 0 || probing,
|
|
4821
|
+
onClick: onProbe,
|
|
4822
|
+
children: probing ? t("accel.probing") : t("accel.probe")
|
|
4823
|
+
})]
|
|
4824
|
+
}),
|
|
4825
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4826
|
+
type: "text",
|
|
4827
|
+
className: state.proxyError !== null ? GitHistoryPanel_module_default.cnInputBad : GitHistoryPanel_module_default.cnInput,
|
|
4828
|
+
value: state.proxyText,
|
|
4829
|
+
spellCheck: false,
|
|
4830
|
+
disabled: locked,
|
|
4831
|
+
placeholder: "http://127.0.0.1:7890",
|
|
4832
|
+
"aria-invalid": state.proxyError !== null,
|
|
4833
|
+
onChange: (event) => accel.editNetworkProxy(event.target.value)
|
|
4834
|
+
}),
|
|
4835
|
+
state.proxyError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4836
|
+
className: GitHistoryPanel_module_default.cnError,
|
|
4837
|
+
children: t("accel.proxyInvalid")
|
|
4838
|
+
}) : null,
|
|
4839
|
+
probe !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4840
|
+
className: GitHistoryPanel_module_default.cnHint,
|
|
4841
|
+
children: probe.channels.map((channel) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
4842
|
+
channel.ok ? "✓ " : "✗ ",
|
|
4843
|
+
CHANNEL_LABELS[channel.channel](t),
|
|
4844
|
+
` \u00b7 ${String(channel.elapsedMs)} ms`,
|
|
4845
|
+
channel.detail === void 0 ? null : ` \u00b7 ${channel.detail}`
|
|
4846
|
+
] }, channel.channel))
|
|
4847
|
+
}) : null,
|
|
4848
|
+
probeError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4849
|
+
className: GitHistoryPanel_module_default.cnError,
|
|
4850
|
+
children: probeError
|
|
4851
|
+
}) : null,
|
|
4718
4852
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4719
4853
|
className: GitHistoryPanel_module_default.cnFooter,
|
|
4720
4854
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -4725,7 +4859,7 @@ window.__ModuleLoader__.load({
|
|
|
4725
4859
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4726
4860
|
type: "button",
|
|
4727
4861
|
className: GitHistoryPanel_module_default.cnSave,
|
|
4728
|
-
disabled: state.saving || state.urlError !== null || !state.writable,
|
|
4862
|
+
disabled: state.saving || state.urlError !== null || state.proxyError !== null || !state.writable,
|
|
4729
4863
|
onClick: onSave,
|
|
4730
4864
|
children: t("cardSave")
|
|
4731
4865
|
})]
|
|
@@ -7125,6 +7259,7 @@ window.__ModuleLoader__.load({
|
|
|
7125
7259
|
accel,
|
|
7126
7260
|
t,
|
|
7127
7261
|
pushToast,
|
|
7262
|
+
cwd,
|
|
7128
7263
|
onClose: closeAccel
|
|
7129
7264
|
}) : null]
|
|
7130
7265
|
}),
|
|
@@ -7946,7 +8081,7 @@ window.__ModuleLoader__.load({
|
|
|
7946
8081
|
children: t("cardDescription")
|
|
7947
8082
|
});
|
|
7948
8083
|
const lockInput = !state.writable;
|
|
7949
|
-
const lockActions = !state.dirty && !accelState.dirty || state.saving || accelState.saving || !state.keepWorktreesValid || accelState.urlError !== null;
|
|
8084
|
+
const lockActions = !state.dirty && !accelState.dirty || state.saving || accelState.saving || !state.keepWorktreesValid || accelState.urlError !== null || accelState.proxyError !== null;
|
|
7950
8085
|
const legacyRootText = storage?.legacyRoot ?? (state.rootDir === "" ? "—" : state.rootDir);
|
|
7951
8086
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7952
8087
|
className: GitWorktreeCard_module_default.page,
|
|
@@ -8170,6 +8305,33 @@ window.__ModuleLoader__.load({
|
|
|
8170
8305
|
}) : null
|
|
8171
8306
|
]
|
|
8172
8307
|
}),
|
|
8308
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
8309
|
+
className: `${GitWorktreeCard_module_default.field} ${GitWorktreeCard_module_default.copyFilesRow}`,
|
|
8310
|
+
htmlFor: "git-worktree-card-network-proxy",
|
|
8311
|
+
children: [
|
|
8312
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8313
|
+
className: GitWorktreeCard_module_default.fieldLabel,
|
|
8314
|
+
children: t("accel.proxyLabel")
|
|
8315
|
+
}),
|
|
8316
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
8317
|
+
id: "git-worktree-card-network-proxy",
|
|
8318
|
+
className: GitWorktreeCard_module_default.accelInput,
|
|
8319
|
+
type: "text",
|
|
8320
|
+
spellCheck: false,
|
|
8321
|
+
placeholder: "http://127.0.0.1:7890",
|
|
8322
|
+
disabled: lockInput || accelState.saving,
|
|
8323
|
+
value: accelState.proxyText,
|
|
8324
|
+
onChange: (event) => {
|
|
8325
|
+
props.editNetworkProxy(event.target.value);
|
|
8326
|
+
}
|
|
8327
|
+
}),
|
|
8328
|
+
accelState.proxyError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8329
|
+
className: GitWorktreeCard_module_default.keepBad,
|
|
8330
|
+
role: "alert",
|
|
8331
|
+
children: t("accel.proxyInvalid")
|
|
8332
|
+
}) : null
|
|
8333
|
+
]
|
|
8334
|
+
}),
|
|
8173
8335
|
history.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8174
8336
|
className: `${GitWorktreeCard_module_default.field} ${GitWorktreeCard_module_default.historyRow}`,
|
|
8175
8337
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -8479,11 +8641,18 @@ window.__ModuleLoader__.load({
|
|
|
8479
8641
|
"panel.stUnmerged": "conflict",
|
|
8480
8642
|
"panel.cnAccelerate": "China acceleration",
|
|
8481
8643
|
"accel.enable": "Enable acceleration",
|
|
8482
|
-
"accel.hint": "
|
|
8644
|
+
"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
8645
|
"accel.urlLabel": "Acceleration URL",
|
|
8484
8646
|
"accel.restoreDefault": "Restore default",
|
|
8485
8647
|
"accel.urlInvalid": "Enter an http(s):// URL",
|
|
8486
8648
|
"accel.urlEmpty": "Enter an acceleration URL",
|
|
8649
|
+
"accel.proxyLabel": "Proxy (optional)",
|
|
8650
|
+
"accel.proxyInvalid": "Enter an http(s)/socks5 proxy URL as host:port, without credentials",
|
|
8651
|
+
"accel.probe": "Test network",
|
|
8652
|
+
"accel.probing": "Testing...",
|
|
8653
|
+
"accel.channel.direct": "Direct",
|
|
8654
|
+
"accel.channel.proxy": "Proxy",
|
|
8655
|
+
"accel.channel.ip": "Pinned IP",
|
|
8487
8656
|
"accel.saveFailed": "The change did not save. Try again.",
|
|
8488
8657
|
"accel.savedOn": "China acceleration enabled: {url}",
|
|
8489
8658
|
"accel.savedOff": "China acceleration disabled",
|
|
@@ -8769,11 +8938,18 @@ window.__ModuleLoader__.load({
|
|
|
8769
8938
|
"panel.stUnmerged": "冲突",
|
|
8770
8939
|
"panel.cnAccelerate": "国内加速",
|
|
8771
8940
|
"accel.enable": "启用加速",
|
|
8772
|
-
"accel.hint": "
|
|
8941
|
+
"accel.hint": "填写加速站前缀(如 https://gh-proxy.com/),提取/更新时会在其后拼接完整 GitHub 地址;推送不走加速。第三方加速站可看到仓库内容,请勿用于私有/敏感仓库。",
|
|
8773
8942
|
"accel.urlLabel": "加速地址",
|
|
8774
8943
|
"accel.restoreDefault": "恢复默认",
|
|
8775
8944
|
"accel.urlInvalid": "请输入以 http(s):// 开头的地址",
|
|
8776
8945
|
"accel.urlEmpty": "请输入加速地址",
|
|
8946
|
+
"accel.proxyLabel": "代理(可选)",
|
|
8947
|
+
"accel.proxyInvalid": "请输入 主机:端口 形式的 http(s)/socks5 代理地址,且不带账号密码",
|
|
8948
|
+
"accel.probe": "检测网络",
|
|
8949
|
+
"accel.probing": "检测中...",
|
|
8950
|
+
"accel.channel.direct": "直连",
|
|
8951
|
+
"accel.channel.proxy": "代理",
|
|
8952
|
+
"accel.channel.ip": "固定 IP",
|
|
8777
8953
|
"accel.saveFailed": "保存未生效,请重试。",
|
|
8778
8954
|
"accel.savedOn": "已启用国内加速:{url}",
|
|
8779
8955
|
"accel.savedOff": "已关闭国内加速",
|
package/lib/git.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export interface ExecResult {
|
|
|
11
11
|
stderr: string;
|
|
12
12
|
/** Node-level failure text when the process never produced stderr. */
|
|
13
13
|
errorMessage?: string;
|
|
14
|
+
/** True when the executor killed the process at its timeout budget. */
|
|
15
|
+
timedOut?: boolean;
|
|
14
16
|
}
|
|
15
17
|
/**
|
|
16
18
|
* Process executor seam. Runs `file args` with cwd set, captures output as
|
|
@@ -19,14 +21,25 @@ export interface ExecResult {
|
|
|
19
21
|
export type Exec = (file: string, args: readonly string[], options: {
|
|
20
22
|
cwd: string;
|
|
21
23
|
timeoutMs?: number;
|
|
24
|
+
env?: Record<string, string | undefined>;
|
|
22
25
|
}) => Promise<ExecResult>;
|
|
23
26
|
/** Failure of one git invocation: non-zero exit with the stderr text. */
|
|
24
27
|
export declare class GitError extends Error {
|
|
25
28
|
readonly args: readonly string[];
|
|
26
29
|
readonly code: number;
|
|
27
30
|
readonly stderr: string;
|
|
28
|
-
|
|
31
|
+
/** True when the executor killed the command at its budget — no verdict. */
|
|
32
|
+
readonly timedOut: boolean;
|
|
33
|
+
constructor(args: readonly string[], code: number, stderr: string,
|
|
34
|
+
/** True when the executor killed the command at its budget — no verdict. */
|
|
35
|
+
timedOut?: boolean);
|
|
29
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Whether an endpoint speaks the SSH transport (`ssh://…`, `git@host:…`, or
|
|
39
|
+
* any `user@host:path` scp-style URL) — none of the network channels apply.
|
|
40
|
+
* @param url - a remote URL as git printed it, or undefined.
|
|
41
|
+
*/
|
|
42
|
+
export declare function isSshEndpoint(url: string | undefined): boolean;
|
|
30
43
|
/** Facts the status route assembles: branches, worktrees, repo identity. */
|
|
31
44
|
export interface RepoFacts {
|
|
32
45
|
/** Absolute main worktree path (`dirname` of `--git-common-dir`, not the
|
|
@@ -58,15 +71,15 @@ export interface RepoFacts {
|
|
|
58
71
|
/** Default hard timeout for one git call: generous for every LOCAL
|
|
59
72
|
* operation (status/log/commit/worktree) while still bounding hangs. */
|
|
60
73
|
export declare const GIT_TIMEOUT_MS = 20000;
|
|
61
|
-
/** Network
|
|
62
|
-
* a real pack transfer outruns the local default, and a killed
|
|
63
|
-
* ALREADY updated the remote refs — an error toast for a push
|
|
64
|
-
*
|
|
65
|
-
export declare const NETWORK_TIMEOUT_MS = 120000;
|
|
74
|
+
/** Network budgets live in `network.ts` (probe/fetch/push/verify): a slow
|
|
75
|
+
* link with a real pack transfer outruns the local default, and a killed
|
|
76
|
+
* push may have ALREADY updated the remote refs — an error toast for a push
|
|
77
|
+
* that actually landed. */
|
|
66
78
|
/** Real executor over node:child_process with a hard timeout. */
|
|
67
79
|
export declare function childProcessExec(file: string, args: readonly string[], options: {
|
|
68
80
|
cwd: string;
|
|
69
81
|
timeoutMs?: number;
|
|
82
|
+
env?: Record<string, string | undefined>;
|
|
70
83
|
}): Promise<ExecResult>;
|
|
71
84
|
/** Directory-existence seam over the parsed worktree registrations. */
|
|
72
85
|
export type DirExists = (path: string) => boolean;
|
|
@@ -256,7 +269,7 @@ export declare function accelArgs(prefix: string | undefined): readonly string[]
|
|
|
256
269
|
* @param authArgs - optional per-host credential `-c` pairs (see
|
|
257
270
|
* `credentials.ts`); prepended before the subcommand like the accel rewrite.
|
|
258
271
|
*/
|
|
259
|
-
export declare function fetchAll(exec: Exec, repoRoot: string, accelPrefix?: string, authArgs?: readonly string[]): Promise<void>;
|
|
272
|
+
export declare function fetchAll(exec: Exec, repoRoot: string, accelPrefix?: string, authArgs?: readonly string[], options?: NetworkOptions): Promise<void>;
|
|
260
273
|
/** Outcome of {@link updateBranch}: the branch touched and whether it moved. */
|
|
261
274
|
export interface UpdateOutcome {
|
|
262
275
|
branch: string;
|
|
@@ -283,7 +296,62 @@ export declare function updateBranch(exec: Exec, cwd: string, accelPrefix?: stri
|
|
|
283
296
|
export interface PushOutcome {
|
|
284
297
|
branch: string;
|
|
285
298
|
pushed: boolean;
|
|
299
|
+
/** Set when the push ran out of time but the remote ref already matches
|
|
300
|
+
* the local HEAD — the push landed, the connection just did not say so. */
|
|
301
|
+
note?: 'timed-out-but-landed';
|
|
302
|
+
}
|
|
303
|
+
/** The endpoint a write would use, resolved so it can be probed first. */
|
|
304
|
+
export interface PushTarget {
|
|
305
|
+
/** Remote name taken from the upstream (`origin`). */
|
|
306
|
+
remote: string;
|
|
307
|
+
/** The URL `git push` uses (a separate `pushurl` is respected); absent
|
|
308
|
+
* when git could not name one, in which case the remote name is the
|
|
309
|
+
* endpoint and the probe resolves it the same way push does. */
|
|
310
|
+
url?: string;
|
|
311
|
+
}
|
|
312
|
+
/** Per-command routing for a network command (a proxy's `-c` pairs). */
|
|
313
|
+
export interface NetworkOptions {
|
|
314
|
+
/** Argv placed before the subcommand that selects a network channel;
|
|
315
|
+
* empty/absent = the direct connection. */
|
|
316
|
+
channelArgs?: readonly string[];
|
|
317
|
+
/**
|
|
318
|
+
* Consulted ONLY after the direct channel's probe failed in a way another
|
|
319
|
+
* channel could plausibly fix (unreachable/reset/timeout), in order, until
|
|
320
|
+
* one of them answers argv that ALSO passes its own probe. Each provider
|
|
321
|
+
* answers undefined when it does not apply (a mirror URL, an unconfigured
|
|
322
|
+
* proxy) — that is a skip, not a failure. Empty/absent = direct only.
|
|
323
|
+
*/
|
|
324
|
+
fallbackChannels?: readonly ChannelProvider[];
|
|
286
325
|
}
|
|
326
|
+
/** Answers the argv for one replacement channel, or undefined when it does not apply. */
|
|
327
|
+
export type ChannelProvider = (endpoint: string) => Promise<readonly string[] | undefined>;
|
|
328
|
+
/**
|
|
329
|
+
* Resolve where a write to `@{u}` would go, WITHOUT sending anything.
|
|
330
|
+
* `remote get-url --push` is the one source of truth for the endpoint: a
|
|
331
|
+
* remote may configure a separate `pushurl`, and probing the fetch URL
|
|
332
|
+
* would validate a host the push never talks to.
|
|
333
|
+
* @param exec - executor seam.
|
|
334
|
+
* @param cwd - directory whose checked-out branch supplies the upstream.
|
|
335
|
+
* @param authArgs - per-host credential argv (kept for symmetry; `rev-parse`
|
|
336
|
+
* and `remote` are local and never authenticate).
|
|
337
|
+
* @returns the remote (and its push URL when git names one), or undefined
|
|
338
|
+
* when the branch has no upstream — that case belongs to git's own message.
|
|
339
|
+
*/
|
|
340
|
+
export declare function resolvePushTarget(exec: Exec, cwd: string, authArgs?: readonly string[]): Promise<PushTarget | undefined>;
|
|
341
|
+
/**
|
|
342
|
+
* Reachability probe for a write: a READ of the push endpoint. A host that
|
|
343
|
+
* cannot answer `ls-remote` cannot receive a pack either, so failing here
|
|
344
|
+
* costs the probe budget (10s) instead of the push budget (600s), and the
|
|
345
|
+
* caller learns which endpoint was unreachable before any object moved.
|
|
346
|
+
* Terminal prompts are disabled for the probe: a probe must fail, never
|
|
347
|
+
* block on a credential dialog the user cannot see.
|
|
348
|
+
* @param exec - executor seam.
|
|
349
|
+
* @param cwd - any directory inside the repository.
|
|
350
|
+
* @param target - endpoint from {@link resolvePushTarget}.
|
|
351
|
+
* @param options - channel argv (proxy) to route the probe the same way.
|
|
352
|
+
* @param authArgs - per-host credential argv.
|
|
353
|
+
*/
|
|
354
|
+
export declare function probePushTarget(exec: Exec, cwd: string, target: PushTarget, options?: NetworkOptions, authArgs?: readonly string[]): Promise<void>;
|
|
287
355
|
/**
|
|
288
356
|
* Push the branch checked out by `cwd` to its upstream (`@{u}`) — the
|
|
289
357
|
* counterpart of {@link updateBranch}. Deliberately a bare `git push`: the
|
|
@@ -298,11 +366,13 @@ export interface PushOutcome {
|
|
|
298
366
|
* pushed content to a third party.
|
|
299
367
|
* @param exec - executor seam.
|
|
300
368
|
* @param cwd - directory whose checked-out branch is pushed.
|
|
369
|
+
* @param authArgs - per-host credential argv.
|
|
370
|
+
* @param options - channel argv (proxy) applied to the probe and the push.
|
|
301
371
|
* @returns the branch and whether the remote-tracking ref changed. A branch
|
|
302
372
|
* without an upstream can still be handed to `git push`, so Git's own
|
|
303
373
|
* push-specific diagnostic is preserved; in that case `pushed` is false.
|
|
304
374
|
*/
|
|
305
|
-
export declare function pushBranch(exec: Exec, cwd: string, authArgs?: readonly string[]): Promise<PushOutcome>;
|
|
375
|
+
export declare function pushBranch(exec: Exec, cwd: string, authArgs?: readonly string[], options?: NetworkOptions): Promise<PushOutcome>;
|
|
306
376
|
/** Every LOCAL tag name of the repository, in git's own sort order. */
|
|
307
377
|
export declare function listTagNames(exec: Exec, cwd: string): Promise<string[]>;
|
|
308
378
|
/** Where a one-off push should go, or why no remote could be picked. */
|
|
@@ -328,6 +398,9 @@ export interface TagPushOutcome {
|
|
|
328
398
|
tag: string;
|
|
329
399
|
remote: string;
|
|
330
400
|
pushed: boolean;
|
|
401
|
+
/** Set when the push timed out but the remote tag already names the local
|
|
402
|
+
* object — landed, with the connection gone before it could say so. */
|
|
403
|
+
note?: 'timed-out-but-landed';
|
|
331
404
|
}
|
|
332
405
|
/**
|
|
333
406
|
* Push ONE existing local tag to `remote` (`git push <remote>
|
|
@@ -353,7 +426,7 @@ export interface TagPushOutcome {
|
|
|
353
426
|
* @param remote - the remote to publish to (see {@link resolvePushRemote}).
|
|
354
427
|
* @returns the tag, the remote, and whether the push had to run.
|
|
355
428
|
*/
|
|
356
|
-
export declare function pushTag(exec: Exec, cwd: string, name: string, remote: string, authArgs?: readonly string[]): Promise<TagPushOutcome>;
|
|
429
|
+
export declare function pushTag(exec: Exec, cwd: string, name: string, remote: string, authArgs?: readonly string[], options?: NetworkOptions): Promise<TagPushOutcome>;
|
|
357
430
|
/** Guard for route inputs: a non-empty absolute directory path. */
|
|
358
431
|
export declare function isAbsoluteDir(value: string): boolean;
|
|
359
432
|
/**
|