dsh-github-flow 0.5.0 → 0.6.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/README.en.md +16 -4
- package/README.md +16 -4
- package/docs/UPGRADE-NOTES.md +8 -8
- package/docs/architecture.md +33 -17
- package/docs/images/1.png +0 -0
- package/lib/api/routes-actions.js +139 -8
- package/lib/api/routes-overview.js +147 -29
- package/lib/cache.d.ts +77 -4
- package/lib/cache.js +114 -7
- package/lib/client.d.ts +7 -3
- package/lib/client.js +3309 -314
- package/lib/executor/error-classifier.d.ts +55 -0
- package/lib/executor/error-classifier.js +199 -0
- package/lib/executor/git-parser.d.ts +124 -0
- package/lib/executor/git-parser.js +397 -0
- package/lib/executor.d.ts +122 -4
- package/lib/executor.js +685 -80
- package/lib/tools/help.js +22 -7
- package/lib/tools/image-utils.d.ts +27 -0
- package/lib/tools/image-utils.js +73 -3
- package/lib/types.d.ts +331 -0
- package/lib/ui/help-modal.d.ts +6 -0
- package/lib/ui/help-modal.js +113 -15
- package/lib/ui/issue-modal.d.ts +14 -0
- package/lib/ui/issue-modal.js +366 -0
- package/lib/ui/markdown-lite.d.ts +66 -0
- package/lib/ui/markdown-lite.js +312 -0
- package/lib/ui/network-banner.d.ts +12 -0
- package/lib/ui/network-banner.js +35 -0
- package/lib/ui/pr-modal.d.ts +17 -0
- package/lib/ui/pr-modal.js +516 -0
- package/lib/ui/repo-badges.d.ts +43 -0
- package/lib/ui/repo-badges.js +82 -0
- package/lib/ui/repo-selector.d.ts +14 -0
- package/lib/ui/repo-selector.js +112 -0
- package/lib/ui/storage.d.ts +47 -0
- package/lib/ui/storage.js +139 -0
- package/lib/ui/styles.d.ts +1 -1
- package/lib/ui/styles.js +960 -0
- package/lib/ui/utils.js +13 -1
- package/lib/ui/workspace-panel.js +169 -27
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gh 错误语义分类(确定性配置错误 vs 可重试的传输层故障)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么必须区分:`gh` 失败时一律以非零退出码返回,调用方只能看到一段 stderr
|
|
5
|
+
* 文本。过去上层(Web 概览路由)把所有非零退出统一当成「网络错误」,于是
|
|
6
|
+
* 「仓库关闭了 Issues」这种永久性配置事实被伪装成网络抖动——既弹出误导性的
|
|
7
|
+
* 「网络连接失败」横幅,又触发 3 轮阶梯退避重试与每 5-10 分钟一轮的自动同步,
|
|
8
|
+
* 把确定性失败当成偶发抖动反复空烧。
|
|
9
|
+
*
|
|
10
|
+
* 判据全部来自 gh 2.96 实测 stderr:
|
|
11
|
+
* - the 'o/r' repository has disabled issues
|
|
12
|
+
* - the 'o/r' repository has discussions disabled
|
|
13
|
+
* - GraphQL: Could not resolve to a Repository with the name 'o/r'. (repository)
|
|
14
|
+
* - HTTP 403 / Not Found (HTTP 404)
|
|
15
|
+
*
|
|
16
|
+
* 纯文本判定,不做任何 I/O——它是 GhResult.errorKind 的唯一事实源。
|
|
17
|
+
*/
|
|
18
|
+
/** gh 失败结果的语义种类 */
|
|
19
|
+
export type GhErrorKind =
|
|
20
|
+
/** 传输层故障(超时、DNS、连接重置、代理阻断):值得重试,允许回退历史快照 */
|
|
21
|
+
'transient'
|
|
22
|
+
/** 确定性失败(仓库关闭 issues、仓库不存在、权限不足):重试永远无意义 */
|
|
23
|
+
| 'deterministic'
|
|
24
|
+
/** 命令失败但无明确归属,按可重试处理(保守不误杀) */
|
|
25
|
+
| 'unknown';
|
|
26
|
+
/**
|
|
27
|
+
* 判定一次 gh 失败的语义种类。
|
|
28
|
+
*
|
|
29
|
+
* 优先级设计:确定性 > 限流放行 > 传输层 > unknown。
|
|
30
|
+
* 为什么不把「同时命中确定性与传输层」判为 transient:若仓库关闭了 Issues,
|
|
31
|
+
* 网络恢复后该命令依然失败。判成 transient 会让调用方每轮回退旧快照、
|
|
32
|
+
* 永远收敛不到正确状态。确定性一旦命中就该立刻停止重试。
|
|
33
|
+
*/
|
|
34
|
+
export declare function classifyGhError(errorText: string | undefined | null): GhErrorKind;
|
|
35
|
+
/**
|
|
36
|
+
* 把确定性失败翻译成面向用户的友好说明。
|
|
37
|
+
* 返回 null 表示不属于确定性失败,调用方应走原有网络故障路径。
|
|
38
|
+
*/
|
|
39
|
+
export declare function describeDeterministicError(errorText: string | undefined | null): string | null;
|
|
40
|
+
/** 该错误是否值得重试(供 UI 层决定是否走阶梯退避) */
|
|
41
|
+
export declare function isRetryableError(errorText: string | undefined | null): boolean;
|
|
42
|
+
export declare function isPermissionError(errorText: string | undefined | null): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* 把「权限不足」翻译成可操作的处置指引。
|
|
45
|
+
*
|
|
46
|
+
* 为什么不能只回一句「无权限」:Agent 与用户都需要知道**下一步做什么**。
|
|
47
|
+
* GitHub 的权限错误有明确解法路径(提权到 Triage/Write、或改用 fork 工作流),
|
|
48
|
+
* 把这条路径说出来,比让用户自己搜错误文本有用得多。
|
|
49
|
+
*
|
|
50
|
+
* @param errorText gh 的 stderr
|
|
51
|
+
* @param action 可选:正在执行的动作标识(如 'merge' / 'close' / 'add_label'),
|
|
52
|
+
* 用于在错误文本特征不足时精准匹配
|
|
53
|
+
* @returns 指引文本;非权限错误返回 null(不产生噪音)
|
|
54
|
+
*/
|
|
55
|
+
export declare function permissionRemedy(errorText: string | undefined | null, action?: string): string | null;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gh 错误语义分类(确定性配置错误 vs 可重试的传输层故障)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么必须区分:`gh` 失败时一律以非零退出码返回,调用方只能看到一段 stderr
|
|
5
|
+
* 文本。过去上层(Web 概览路由)把所有非零退出统一当成「网络错误」,于是
|
|
6
|
+
* 「仓库关闭了 Issues」这种永久性配置事实被伪装成网络抖动——既弹出误导性的
|
|
7
|
+
* 「网络连接失败」横幅,又触发 3 轮阶梯退避重试与每 5-10 分钟一轮的自动同步,
|
|
8
|
+
* 把确定性失败当成偶发抖动反复空烧。
|
|
9
|
+
*
|
|
10
|
+
* 判据全部来自 gh 2.96 实测 stderr:
|
|
11
|
+
* - the 'o/r' repository has disabled issues
|
|
12
|
+
* - the 'o/r' repository has discussions disabled
|
|
13
|
+
* - GraphQL: Could not resolve to a Repository with the name 'o/r'. (repository)
|
|
14
|
+
* - HTTP 403 / Not Found (HTTP 404)
|
|
15
|
+
*
|
|
16
|
+
* 纯文本判定,不做任何 I/O——它是 GhResult.errorKind 的唯一事实源。
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* 确定性失败的 stderr 特征表。
|
|
20
|
+
*
|
|
21
|
+
* 为什么用特征串而不是退出码:gh 对上述场景统一返回 exit 1,退出码无法区分
|
|
22
|
+
* 「仓库关了 Issues」与「网线掉了」。文本特征是唯一可用的判据。
|
|
23
|
+
* 每条都必须来自实测输出——误判会把真实网络故障降级成静默空数据,比误报更糟。
|
|
24
|
+
*/
|
|
25
|
+
const DETERMINISTIC_PATTERNS = [
|
|
26
|
+
// 仓库级开关关闭(Issues / Discussions / Projects / Wiki)。
|
|
27
|
+
// 注意 gh 的措辞不统一(实测):
|
|
28
|
+
// issues → "repository has disabled issues"
|
|
29
|
+
// discussions → "repository has discussions disabled"
|
|
30
|
+
// 词序相反,所以两种形态都要覆盖,不能只写一种。
|
|
31
|
+
{
|
|
32
|
+
re: /repository has (?:disabled )?(issues|discussions|projects|wiki)(?: disabled)?/i,
|
|
33
|
+
reason: '该仓库未开启该项协作功能',
|
|
34
|
+
},
|
|
35
|
+
// 仓库不可达(不存在 / 无权限 / 已改名):GraphQL 与 REST 两条通道
|
|
36
|
+
{ re: /Could not resolve to a Repository/i, reason: '仓库不存在或当前账号无权访问' },
|
|
37
|
+
{ re: /Not Found \(HTTP 404\)/i, reason: '仓库或资源不存在' },
|
|
38
|
+
// 分支 / PR / Issue 单体不存在
|
|
39
|
+
{ re: /Branch not found/i, reason: '分支不存在' },
|
|
40
|
+
// GraphQL 通道的权限不足(实测形态):
|
|
41
|
+
// GraphQL: ItQianChen does not have the correct permissions to execute `AddLabelsToLabelable` (addLabelsToLabelable)
|
|
42
|
+
// GraphQL: ItQianChen does not have the correct permissions to execute `DeleteIssue` (deleteIssue)
|
|
43
|
+
// 这类文本里没有 "HTTP 403",若不单列会被归为 unknown,进而被当成可重试抖动——
|
|
44
|
+
// 而权限不足重试一万次也不会变。
|
|
45
|
+
{ re: /does not have the correct permissions to execute/i, reason: '当前账号对该仓库的权限不足' },
|
|
46
|
+
];
|
|
47
|
+
/** 把「仓库未开启某项功能」翻译成点名说明(比笼统的“未开启协作功能”有用) */
|
|
48
|
+
const DISABLED_FEATURE_LABEL = {
|
|
49
|
+
issues: 'Issues',
|
|
50
|
+
discussions: 'Discussions',
|
|
51
|
+
projects: 'Projects',
|
|
52
|
+
wiki: 'Wiki',
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* 限流类 403 必须先于通用 403 放行。
|
|
56
|
+
* 为什么单列:GitHub 的 `HTTP 403` 既可能是「无权限」(确定性),也可能是
|
|
57
|
+
* 「API rate limit exceeded / secondary rate limit」(几分钟后可恢复)。
|
|
58
|
+
* 把限流误判成确定性会让学生等一会儿就能好的请求被判死刑。
|
|
59
|
+
*/
|
|
60
|
+
const RATE_LIMIT_RE = /rate limit|secondary rate limit|abuse detection/i;
|
|
61
|
+
/** 传输层故障特征(显式白名单,用于给出「可重试」的准确语义) */
|
|
62
|
+
const TRANSIENT_PATTERNS = [
|
|
63
|
+
/命令执行超时/,
|
|
64
|
+
/\bETIMEDOUT\b/i,
|
|
65
|
+
/\bECONNRESET\b/i,
|
|
66
|
+
/\bECONNREFUSED\b/i,
|
|
67
|
+
/\bENOTFOUND\b/i,
|
|
68
|
+
/\bEAI_AGAIN\b/i,
|
|
69
|
+
/socket hang up/i,
|
|
70
|
+
/TLS handshake/i,
|
|
71
|
+
/proxyconnect/i,
|
|
72
|
+
/spawn EPERM/i,
|
|
73
|
+
/network is unreachable/i,
|
|
74
|
+
/Could not resolve host/i,
|
|
75
|
+
/no such host/i,
|
|
76
|
+
];
|
|
77
|
+
/**
|
|
78
|
+
* 判定一次 gh 失败的语义种类。
|
|
79
|
+
*
|
|
80
|
+
* 优先级设计:确定性 > 限流放行 > 传输层 > unknown。
|
|
81
|
+
* 为什么不把「同时命中确定性与传输层」判为 transient:若仓库关闭了 Issues,
|
|
82
|
+
* 网络恢复后该命令依然失败。判成 transient 会让调用方每轮回退旧快照、
|
|
83
|
+
* 永远收敛不到正确状态。确定性一旦命中就该立刻停止重试。
|
|
84
|
+
*/
|
|
85
|
+
export function classifyGhError(errorText) {
|
|
86
|
+
const text = String(errorText || '');
|
|
87
|
+
if (!text)
|
|
88
|
+
return 'unknown';
|
|
89
|
+
// 限流 403 先放行,避免被下面的通用 403 规则当成确定性失败
|
|
90
|
+
if (RATE_LIMIT_RE.test(text))
|
|
91
|
+
return 'transient';
|
|
92
|
+
for (const { re } of DETERMINISTIC_PATTERNS) {
|
|
93
|
+
if (re.test(text))
|
|
94
|
+
return 'deterministic';
|
|
95
|
+
}
|
|
96
|
+
// 通用 403 放在确定性表之后、transient 之前:无权限属于确定性
|
|
97
|
+
if (/HTTP 403/i.test(text))
|
|
98
|
+
return 'deterministic';
|
|
99
|
+
for (const re of TRANSIENT_PATTERNS) {
|
|
100
|
+
if (re.test(text))
|
|
101
|
+
return 'transient';
|
|
102
|
+
}
|
|
103
|
+
return 'unknown';
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* 把确定性失败翻译成面向用户的友好说明。
|
|
107
|
+
* 返回 null 表示不属于确定性失败,调用方应走原有网络故障路径。
|
|
108
|
+
*/
|
|
109
|
+
export function describeDeterministicError(errorText) {
|
|
110
|
+
const text = String(errorText || '');
|
|
111
|
+
if (!text)
|
|
112
|
+
return null;
|
|
113
|
+
if (RATE_LIMIT_RE.test(text))
|
|
114
|
+
return null;
|
|
115
|
+
// 先处理「仓库关闭某项功能」:点名到具体功能,比笼统措辞更容易让用户行动
|
|
116
|
+
const disabled = /repository has (?:disabled )?(issues|discussions|projects|wiki)(?: disabled)?/i.exec(text);
|
|
117
|
+
if (disabled) {
|
|
118
|
+
const label = DISABLED_FEATURE_LABEL[disabled[1].toLowerCase()] || disabled[1];
|
|
119
|
+
return `该仓库未开启 ${label}`;
|
|
120
|
+
}
|
|
121
|
+
for (const { re, reason } of DETERMINISTIC_PATTERNS) {
|
|
122
|
+
if (re.test(text))
|
|
123
|
+
return reason;
|
|
124
|
+
}
|
|
125
|
+
if (/HTTP 403/i.test(text))
|
|
126
|
+
return '当前账号无此操作权限';
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
/** 该错误是否值得重试(供 UI 层决定是否走阶梯退避) */
|
|
130
|
+
export function isRetryableError(errorText) {
|
|
131
|
+
return classifyGhError(errorText) !== 'deterministic';
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 判断一段错误是否属于「权限不足」(而非仓库配置、不存在等确定性错误)。
|
|
135
|
+
*
|
|
136
|
+
* 为什么单独判定:确定性错误里只有权限这一类有「解法路径」(提权 / 换 fork 工作流),
|
|
137
|
+
* 其余(仓库关了 Issues、分支不存在)说再多也没用。
|
|
138
|
+
*/
|
|
139
|
+
const PERMISSION_RE = /does not have the correct permissions to execute|HTTP 403|Resource not accessible|Forbidden|must have (?:admin|push|write|triage|maintain) (?:access|permissions?)/i;
|
|
140
|
+
export function isPermissionError(errorText) {
|
|
141
|
+
const text = String(errorText || '');
|
|
142
|
+
if (!text)
|
|
143
|
+
return false;
|
|
144
|
+
// 限流也常以 403 出现,但它不是权限问题——放行会让用户去改权限,做无用功
|
|
145
|
+
if (RATE_LIMIT_RE.test(text))
|
|
146
|
+
return false;
|
|
147
|
+
return PERMISSION_RE.test(text);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* 已知操作 → 所需权限档位 的映射。
|
|
151
|
+
*
|
|
152
|
+
* 键取自 GitHub 的 GraphQL mutation 名与 gh 子命令动词(实测捕获)。
|
|
153
|
+
* 为什么不用完整枚举:GitHub 有上百个 mutation,穷举既不可能也没必要——
|
|
154
|
+
* 未命中的一律走默认指引,仍然是对的。
|
|
155
|
+
*/
|
|
156
|
+
const ACTION_PERMISSION_MAP = [
|
|
157
|
+
{
|
|
158
|
+
// 同时覆盖 GraphQL mutation 名(AddLabelsToLabelable)与 CLI 参数形态
|
|
159
|
+
// (--add-label / --remove-label)——两者出现在不同层级的错误文本里。
|
|
160
|
+
re: /AddLabelsToLabelable|RemoveLabelsFromLabelable|UpdateIssue|CloseIssue|TransferIssue|add-?label|remove-?label|close-?issue|issue (?:edit|close|pin|lock|transfer)/i,
|
|
161
|
+
level: 'Triage',
|
|
162
|
+
hint: '管理他人 Issue/PR(加标签、关闭、指派)需要 Triage 及以上权限。可请仓库管理员授予,或改用评论表达意见。',
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
re: /mergePullRequest|MergePullRequest|\bmerge\b|CreateRef|createRef|\bpush\b|CreateCommitOnBranch/i,
|
|
166
|
+
level: 'Write',
|
|
167
|
+
hint: '合并 PR / 推送代码需要 Write 及以上权限。若是他人仓库,请先 fork 到自己的账号,在 fork 上提交后向上游发起 PR。',
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
re: /DeleteIssue|deleteIssue|DeleteRepository|deleteRepository|TransferRepository|\bdelete\b/i,
|
|
171
|
+
level: 'Admin',
|
|
172
|
+
hint: '物理删除与仓库级操作需要管理员权限。这类操作不可逆,请联系仓库管理员执行。',
|
|
173
|
+
},
|
|
174
|
+
];
|
|
175
|
+
/**
|
|
176
|
+
* 把「权限不足」翻译成可操作的处置指引。
|
|
177
|
+
*
|
|
178
|
+
* 为什么不能只回一句「无权限」:Agent 与用户都需要知道**下一步做什么**。
|
|
179
|
+
* GitHub 的权限错误有明确解法路径(提权到 Triage/Write、或改用 fork 工作流),
|
|
180
|
+
* 把这条路径说出来,比让用户自己搜错误文本有用得多。
|
|
181
|
+
*
|
|
182
|
+
* @param errorText gh 的 stderr
|
|
183
|
+
* @param action 可选:正在执行的动作标识(如 'merge' / 'close' / 'add_label'),
|
|
184
|
+
* 用于在错误文本特征不足时精准匹配
|
|
185
|
+
* @returns 指引文本;非权限错误返回 null(不产生噪音)
|
|
186
|
+
*/
|
|
187
|
+
export function permissionRemedy(errorText, action) {
|
|
188
|
+
if (!isPermissionError(errorText))
|
|
189
|
+
return null;
|
|
190
|
+
const haystack = `${String(errorText || '')} ${String(action || '')}`;
|
|
191
|
+
for (const { re, hint } of ACTION_PERMISSION_MAP) {
|
|
192
|
+
if (re.test(haystack))
|
|
193
|
+
return hint;
|
|
194
|
+
}
|
|
195
|
+
// 默认指引:不指定具体档位。
|
|
196
|
+
// 为什么:只知道「权限不足」时猜档位很容易猜错(我们实测就猜错过——
|
|
197
|
+
// READ 用户能建 Issue,而不是处处受限),说错比不说更糟。
|
|
198
|
+
return '当前账号对该仓库的权限不足。可运行 `gh auth status` 查看作用域,或请仓库管理员提升权限。';
|
|
199
|
+
}
|
|
@@ -55,3 +55,127 @@ export interface GitStatParsedResult {
|
|
|
55
55
|
* 提取文件路径与 `X files changed, Y insertions(+), Z deletions(-)` 汇总。
|
|
56
56
|
*/
|
|
57
57
|
export declare function parseGitStatText(statText: string): GitStatParsedResult | null;
|
|
58
|
+
/**
|
|
59
|
+
* 提取 `.git/config` 中所有 `[remote "..."]` 段内的 url。
|
|
60
|
+
*
|
|
61
|
+
* 为什么必须按段扫描而不是全文抓 `url = `:`[submodule "..."]` 段同样带 url
|
|
62
|
+
* (实测 `[submodule "lib"] url = https://github.com/x/y.git`)。全文正则会把
|
|
63
|
+
* 子模块的远程算进指纹,导致一次 `git submodule add` 就触发整个面板缓存的
|
|
64
|
+
* 无谓作废。子模块的 url 与本工作区关联哪个 GitHub 仓库毫无关系。
|
|
65
|
+
*
|
|
66
|
+
* 输出语义:保持文件中的出现顺序、已去重、不含空值。
|
|
67
|
+
* 排序交给调用方(指纹需要稳定顺序,但那是调用方的语义,纯函数不该替它决定)。
|
|
68
|
+
*/
|
|
69
|
+
export declare function parseRemoteUrls(configText: string): string[];
|
|
70
|
+
/** 单个 remote 段的解析结果 */
|
|
71
|
+
export interface ParsedRemote {
|
|
72
|
+
name: string;
|
|
73
|
+
url: string;
|
|
74
|
+
}
|
|
75
|
+
/** `.git/config` + `HEAD` 的解析结果,是「这个工作区关联哪个仓库」的唯一事实源 */
|
|
76
|
+
export interface ParsedGitConfig {
|
|
77
|
+
/** 每个 remote 名对应的第一个 url(身份解析用;同名多 url 时取首个) */
|
|
78
|
+
remotes: ParsedRemote[];
|
|
79
|
+
/**
|
|
80
|
+
* 全部 remote url 行(指纹用)。
|
|
81
|
+
*
|
|
82
|
+
* 为什么与 remotes 分开:身份解析需要「remote 名 → url」的一对一映射,
|
|
83
|
+
* 而指纹需要「配置里出现过的所有 url」。同一个 remote 允许写多行 url
|
|
84
|
+
* (git 认首个,但配置确实是多行),若指纹也按名字去重,第二行 url 的
|
|
85
|
+
* 变化就检测不到——那会让「用户改了备用 url」这种情况静默漏检。
|
|
86
|
+
*/
|
|
87
|
+
allUrls: string[];
|
|
88
|
+
/** 当前分支名(来自 HEAD 的 `ref: refs/heads/<name>`);detached HEAD 时为 null */
|
|
89
|
+
currentBranch: string | null;
|
|
90
|
+
/** `branch.<currentBranch>.remote` 的值;无配置或无当前分支时为 null */
|
|
91
|
+
branchRemote: string | null;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* 解析 `.git/config` 与 `HEAD`,产出与 git 语义一致的远程视图。
|
|
95
|
+
*
|
|
96
|
+
* 为什么把 HEAD 一起解析:`branch.<name>.remote` 的 `<name>` 是**当前分支**,
|
|
97
|
+
* 不知道当前分支就无法按 git 的规则选出 remote,只能退化成「取第一个」——
|
|
98
|
+
* 那正是本次要修的错误。
|
|
99
|
+
*
|
|
100
|
+
* @param configText `.git/config` 全文
|
|
101
|
+
* @param headText HEAD 文件全文。worktree 场景必须传**局部 gitdir** 下的那个,
|
|
102
|
+
* 传共享目录的会拿到主工作树分支名(实测两者不同)。
|
|
103
|
+
*/
|
|
104
|
+
export declare function parseGitConfig(configText: string, headText?: string | null): ParsedGitConfig;
|
|
105
|
+
/**
|
|
106
|
+
* 按 git 的规则选出「这个工作区关联的 remote」。
|
|
107
|
+
*
|
|
108
|
+
* 优先级与 git 一致:
|
|
109
|
+
* 1. `branch.<当前分支>.remote` —— 当前分支显式跟踪的 remote
|
|
110
|
+
* 2. `origin` —— git 的默认远程
|
|
111
|
+
* 3. 第一个 remote —— 兜底(保持既有行为)
|
|
112
|
+
*
|
|
113
|
+
* 特殊值 `.` 表示跟踪**本地分支**而非远程(实测可设置),跳过第 1 级——
|
|
114
|
+
* 拿 "." 去查 `[remote "."]` 必然查不到,且语义上它根本不是远程。
|
|
115
|
+
*/
|
|
116
|
+
export declare function resolveRemoteTarget(cfg: ParsedGitConfig): ParsedRemote | null;
|
|
117
|
+
/**
|
|
118
|
+
* 从 remote url 中提取 GitHub 的 owner/repo。
|
|
119
|
+
*
|
|
120
|
+
* 覆盖形态(均实测于真实 clone / hand-written config):
|
|
121
|
+
* https://github.com/o/r.git
|
|
122
|
+
* git@github.com:o/r.git
|
|
123
|
+
* ssh://git@github.com/o/r.git
|
|
124
|
+
* https://user:token@github.com/o/r.git
|
|
125
|
+
* 带尾斜杠 / 不带 .git
|
|
126
|
+
*
|
|
127
|
+
* 非 github.com 的 host 一律返回 null(GHE 不在支持范围)。
|
|
128
|
+
* 为什么绝不"猜一个":错误身份比没有身份更有害——面板会显示一个
|
|
129
|
+
* 用户根本不认识的仓库,且所有后续判断都建立在错误前提上。
|
|
130
|
+
* 正则两端锚定,杜绝 `github.com.evil.com` 这类子串误匹配。
|
|
131
|
+
*/
|
|
132
|
+
export declare function parseGithubUrl(url: string): {
|
|
133
|
+
owner: string;
|
|
134
|
+
repo: string;
|
|
135
|
+
} | null;
|
|
136
|
+
/**
|
|
137
|
+
* 从 remote url 中提取 host(小写)。
|
|
138
|
+
*
|
|
139
|
+
* 覆盖:https://host/o/r、git@host:o/r、ssh://git@host:2222/o/r、带 token 的 https。
|
|
140
|
+
* 无法解析返回 null。
|
|
141
|
+
*
|
|
142
|
+
* 为什么不直接复用 parseGithubUrl:本函数要处理**任意 host**(GHE / GitLab),
|
|
143
|
+
* 用于判断「这个 remote 是不是 GitHub」,而不是「这个 remote 属于哪个 GitHub 仓库」。
|
|
144
|
+
*/
|
|
145
|
+
export declare function parseRemoteHost(url: string): string | null;
|
|
146
|
+
/**
|
|
147
|
+
* 判断工作区处于哪种远程布局。
|
|
148
|
+
*
|
|
149
|
+
* 为什么需要分类而不是顺序盲扫:S2 的兜底逻辑按 `.git/config` 的书写顺序
|
|
150
|
+
* 挑第一个能解析出 GitHub 身份的 remote,而用户执行 `git remote rename` /
|
|
151
|
+
* `git remote add` 的先后会改变这个顺序——同一个工作区在两次配置操作后
|
|
152
|
+
* 可能解析出不同仓库。分类后每种布局的处置规则固定,与顺序无关。
|
|
153
|
+
*
|
|
154
|
+
* 分类只看 host 与 git 选中的 remote,不看 URL 的仓库名——后者是身份层的事。
|
|
155
|
+
*/
|
|
156
|
+
export declare function classifyRepoLayout(cfg: ParsedGitConfig): 'github-only' | 'github-mixed' | 'no-github' | 'no-remote';
|
|
157
|
+
/**
|
|
158
|
+
* 在混合布局中确定性地选出一个 GitHub remote。
|
|
159
|
+
*
|
|
160
|
+
* 规则(与书写顺序无关):
|
|
161
|
+
* 1. 名字为 `origin` 的优先
|
|
162
|
+
* 2. 否则按 remote 名字字典序最小的
|
|
163
|
+
*
|
|
164
|
+
* 为什么用字典序而不是「第一个出现的」:后者让同一个工作区的解析结果
|
|
165
|
+
* 依赖用户执行 git remote 命令的先后,属于不可复现的行为。
|
|
166
|
+
* 字典序是可复现、可断言的确定性规则。
|
|
167
|
+
*/
|
|
168
|
+
export declare function pickGithubRemote(cfg: ParsedGitConfig): ParsedRemote | null;
|
|
169
|
+
/**
|
|
170
|
+
* 将整段 Git Unified Diff 纯文本按变动文件切分为字典映射。
|
|
171
|
+
*
|
|
172
|
+
* 为什么需要按文件切片:PR 可能会修改数十个文件,前端需要支持「点击某个文件即刻查看该文件改动」。
|
|
173
|
+
* 在内存做切片索引后,切换文件无需发起网络请求(0ms 响应),体验极致流畅。
|
|
174
|
+
*
|
|
175
|
+
* 键必须与 `gh pr view --json files` 返回的 `files[].path` **逐字一致**,
|
|
176
|
+
* 否则点击文件查不到 diff。因此需要处理 core.quotePath 的八进制转义。
|
|
177
|
+
*
|
|
178
|
+
* @param rawDiff gh pr diff 或 git diff 输出的原始文本
|
|
179
|
+
* @returns 映射字典:{ [filePath: string]: string (该文件的完整 diff 文本段) }
|
|
180
|
+
*/
|
|
181
|
+
export declare function splitUnifiedDiff(rawDiff: string): Record<string, string>;
|