dsh-vscode-mode 0.15.0 → 0.15.3
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.md +7 -3
- package/lib/client.js +335 -30
- package/lib/client.js.map +1 -1
- package/lib/index.js +221 -8
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/sidebar/panels/SearchPanel.ts +58 -1
- package/src/client/styles/editor.css +4 -0
- package/src/client/svnNotice.ts +1 -1
- package/src/client/ui/SvnUpdateDialog.ts +143 -29
- package/src/client/ui/svnUpdateModel.ts +45 -1
- package/src/rpc.ts +1 -0
- package/src/search/content.ts +6 -2
- package/src/shared/rpc.ts +6 -2
- package/src/shared/svn.ts +10 -0
- package/src/svn.ts +70 -5
- package/src/svnResolve.ts +33 -0
package/lib/index.js
CHANGED
|
@@ -7693,7 +7693,7 @@ var ContentSearcher = class {
|
|
|
7693
7693
|
excludeKey,
|
|
7694
7694
|
PROVIDER_VERSION
|
|
7695
7695
|
].join("|");
|
|
7696
|
-
const cached = this.cache.get(key);
|
|
7696
|
+
const cached = request.force ? void 0 : this.cache.get(key);
|
|
7697
7697
|
if (cached) return {
|
|
7698
7698
|
matches: cached.matches,
|
|
7699
7699
|
truncated: cached.truncated,
|
|
@@ -7721,7 +7721,7 @@ var ContentSearcher = class {
|
|
|
7721
7721
|
matches: [],
|
|
7722
7722
|
truncated: false
|
|
7723
7723
|
};
|
|
7724
|
-
this.cache.set(key, result);
|
|
7724
|
+
if (!request.force) this.cache.set(key, result);
|
|
7725
7725
|
return {
|
|
7726
7726
|
matches: result.matches,
|
|
7727
7727
|
truncated: result.truncated,
|
|
@@ -12364,7 +12364,8 @@ function buildHandlers(ctx, registry, searcher = newSearcher(ctx), contentSearch
|
|
|
12364
12364
|
regex: args.regex,
|
|
12365
12365
|
maxResults: args.maxResults,
|
|
12366
12366
|
include: args.include,
|
|
12367
|
-
exclude: args.exclude
|
|
12367
|
+
exclude: args.exclude,
|
|
12368
|
+
force: args.force
|
|
12368
12369
|
})
|
|
12369
12370
|
};
|
|
12370
12371
|
} catch (error) {
|
|
@@ -18681,6 +18682,46 @@ var SvnUpdateJobs = class {
|
|
|
18681
18682
|
}
|
|
18682
18683
|
};
|
|
18683
18684
|
//#endregion
|
|
18685
|
+
//#region src/svnResolve.ts
|
|
18686
|
+
/** @public @author ddj 2026年10月14号 @param value Requested strategy. @returns Whether SVN accepts this explicit strategy. */
|
|
18687
|
+
function validAccept(value) {
|
|
18688
|
+
return value === "mine-full" || value === "theirs-full" || value === "working";
|
|
18689
|
+
}
|
|
18690
|
+
/** @public @author ddj 2026年10月14号 @param value Untrusted relative target. @returns Whether the path names exactly one non-root workspace target. */
|
|
18691
|
+
function validTarget(value) {
|
|
18692
|
+
if (typeof value !== "string" || !value || /[\u0000-\u001f]/.test(value) || /^[A-Za-z]:|^[\\/]/.test(value)) return false;
|
|
18693
|
+
return value.replace(/\\/g, "/").split("/").every((part) => Boolean(part) && part !== "." && part !== ".." && part.toLowerCase() !== ".svn");
|
|
18694
|
+
}
|
|
18695
|
+
/** @public @author ddj 2026年10月14号 @param path Exact relative target. @returns Peg-safe SVN argument. */
|
|
18696
|
+
function pegTarget(path) {
|
|
18697
|
+
return path.includes("@") ? path + "@" : path;
|
|
18698
|
+
}
|
|
18699
|
+
/** @public @author ddj 2026年10月14号 @param xml Verbose depth-empty status. @param path Client target identity. @returns Confirmed conflict evidence. @throws Missing or incomplete status evidence. */
|
|
18700
|
+
function conflictOf(xml, path) {
|
|
18701
|
+
if (!/^\s*(?:<\?xml[^>]*>\s*)?<status\b/.test(xml) || !/<\/status>\s*$/.test(xml)) throw new Error("SVN 冲突状态输出为空或不完整,状态未确认");
|
|
18702
|
+
const statuses = [...scanXmlTags(xml, ["wc-status"])].filter((hit) => !hit.closing);
|
|
18703
|
+
if (statuses.length !== 1) throw new Error("SVN 未返回唯一目标状态,状态未确认");
|
|
18704
|
+
const attrs = statuses[0].attrs;
|
|
18705
|
+
const item = attrOf(attrs, "item");
|
|
18706
|
+
if (!item) throw new Error("SVN 目标状态缺失,状态未确认");
|
|
18707
|
+
return {
|
|
18708
|
+
path,
|
|
18709
|
+
text: item === "conflicted",
|
|
18710
|
+
props: attrOf(attrs, "props") === "conflicted",
|
|
18711
|
+
tree: attrOf(attrs, "tree-conflicted") === "true",
|
|
18712
|
+
versioned: ![
|
|
18713
|
+
"unversioned",
|
|
18714
|
+
"ignored",
|
|
18715
|
+
"external",
|
|
18716
|
+
"none"
|
|
18717
|
+
].includes(item)
|
|
18718
|
+
};
|
|
18719
|
+
}
|
|
18720
|
+
/** @public @author ddj 2026年10月14号 @param state Confirmed target evidence. @returns Whether any conflict remains. */
|
|
18721
|
+
function hasConflict(state) {
|
|
18722
|
+
return state.text || state.props || state.tree;
|
|
18723
|
+
}
|
|
18724
|
+
//#endregion
|
|
18684
18725
|
//#region src/shared/svnExplorer.ts
|
|
18685
18726
|
const ICON_KIND = {
|
|
18686
18727
|
normal: "clean",
|
|
@@ -19694,6 +19735,18 @@ function createSvnRpc(deps) {
|
|
|
19694
19735
|
const now = deps.now ?? (() => Date.now());
|
|
19695
19736
|
const findRoot = deps.findRoot ?? ((cwd) => findSvnRoot(cwd));
|
|
19696
19737
|
const findTortoise = deps.findTortoise ?? ((dir) => findTortoiseProc(dir));
|
|
19738
|
+
const conflictWrites = /* @__PURE__ */ new Set();
|
|
19739
|
+
/** @private @author ddj 2026年10月14号 @param root WC root. @returns Canonical lock identity across sessions. */
|
|
19740
|
+
const writeKey = (root) => (deps.platform ?? process.platform) === "win32" ? root.toLowerCase() : root;
|
|
19741
|
+
/** @private @author ddj 2026年10月14号 @param root WC root. @returns Whether a live update or conflict write owns the WC. */
|
|
19742
|
+
const writeBusy = (root) => {
|
|
19743
|
+
const job = updateJobs.active(root);
|
|
19744
|
+
return conflictWrites.has(writeKey(root)) || Boolean(job && [
|
|
19745
|
+
"starting",
|
|
19746
|
+
"running",
|
|
19747
|
+
"cancelling"
|
|
19748
|
+
].includes(job.phase));
|
|
19749
|
+
};
|
|
19697
19750
|
const statusCache = /* @__PURE__ */ new Map();
|
|
19698
19751
|
/** 运行中更新按会话目录复用工作副本根;TTL 到期才重新扫描 .svn。 */
|
|
19699
19752
|
const updateRootCache = /* @__PURE__ */ new Map();
|
|
@@ -19833,7 +19886,7 @@ function createSvnRpc(deps) {
|
|
|
19833
19886
|
* @param stdoutCap stdout 收集上限(缺省 1MB;大载荷场景如日志拉取需显式放大)
|
|
19834
19887
|
* @returns 退出码与输出
|
|
19835
19888
|
*/
|
|
19836
|
-
const runSvn = async (cwd, argv, graceMs, stdoutCap = OUTPUT_CAP) => {
|
|
19889
|
+
const runSvn = async (cwd, argv, graceMs, stdoutCap = OUTPUT_CAP, allowFallback = true) => {
|
|
19837
19890
|
const spawn = subprocess();
|
|
19838
19891
|
if (!spawn) throw new Error("缺少 subprocess 服务");
|
|
19839
19892
|
const fs = ctx.get("fs");
|
|
@@ -19868,7 +19921,8 @@ function createSvnRpc(deps) {
|
|
|
19868
19921
|
stdout,
|
|
19869
19922
|
stderr
|
|
19870
19923
|
};
|
|
19871
|
-
} catch {
|
|
19924
|
+
} catch (error) {
|
|
19925
|
+
if (!allowFallback) throw error;
|
|
19872
19926
|
const outcome = await attempt("inherit").done;
|
|
19873
19927
|
return {
|
|
19874
19928
|
code: outcome?.exitCode ?? outcome?.code ?? null,
|
|
@@ -19877,6 +19931,22 @@ function createSvnRpc(deps) {
|
|
|
19877
19931
|
};
|
|
19878
19932
|
}
|
|
19879
19933
|
};
|
|
19934
|
+
/** @private @author ddj 2026年10月14号 @param cwd Session workspace. @param root WC root. @param path Validated target. @returns Exact conflict evidence without recursive scanning. */
|
|
19935
|
+
const readConflict = async (cwd, root, path) => {
|
|
19936
|
+
const target = svnPathOf(cwd, root, path);
|
|
19937
|
+
if (!target) throw new Error("目标不在 SVN 工作副本内");
|
|
19938
|
+
const result = await runSvn(root, [
|
|
19939
|
+
"status",
|
|
19940
|
+
"--xml",
|
|
19941
|
+
"--verbose",
|
|
19942
|
+
"--depth",
|
|
19943
|
+
"empty",
|
|
19944
|
+
"--",
|
|
19945
|
+
pegTarget(target)
|
|
19946
|
+
], READ_GRACE_MS, OUTPUT_CAP, false);
|
|
19947
|
+
if (result.code !== 0) throw new Error(result.stderr || result.stdout || "SVN 状态读取失败");
|
|
19948
|
+
return conflictOf(result.stdout, path);
|
|
19949
|
+
};
|
|
19880
19950
|
/** 发射 TortoiseProc(GUI 长驻:不 await done,no-op catch 防未处理 rejection)。 */
|
|
19881
19951
|
const launchTortoise = (exe, action, absPath) => {
|
|
19882
19952
|
const spawn = subprocess();
|
|
@@ -19976,11 +20046,16 @@ function createSvnRpc(deps) {
|
|
|
19976
20046
|
error: "svn 命令不可用(可在设置页配置 svn 路径)"
|
|
19977
20047
|
};
|
|
19978
20048
|
try {
|
|
20049
|
+
const targets = rels.map((path) => svnPathOf(sc.cwd, status.wcRoot, path));
|
|
20050
|
+
if (targets.some((path) => path === null)) return {
|
|
20051
|
+
ok: false,
|
|
20052
|
+
error: "目标不在 SVN 工作副本内"
|
|
20053
|
+
};
|
|
19979
20054
|
const outcome = await runSvn(status.wcRoot, [
|
|
19980
20055
|
...verbArgs,
|
|
19981
20056
|
"--",
|
|
19982
|
-
...
|
|
19983
|
-
], MUTATE_GRACE_MS);
|
|
20057
|
+
...targets.map((path) => pegTarget(path))
|
|
20058
|
+
], MUTATE_GRACE_MS, OUTPUT_CAP, !verbArgs.includes("--depth"));
|
|
19984
20059
|
const result = batchResultOf(outcome.stdout, outcome.stderr, outcome.code, verb);
|
|
19985
20060
|
changesCache.delete(status.wcRoot);
|
|
19986
20061
|
if (outcome.code !== 0) return {
|
|
@@ -20498,8 +20573,142 @@ function createSvnRpc(deps) {
|
|
|
20498
20573
|
};
|
|
20499
20574
|
}
|
|
20500
20575
|
},
|
|
20576
|
+
"svn.conflictStatus": async (args) => {
|
|
20577
|
+
const scope = await updateScope(args.sessionId, true);
|
|
20578
|
+
if ("err" in scope) return {
|
|
20579
|
+
ok: false,
|
|
20580
|
+
error: scope.err
|
|
20581
|
+
};
|
|
20582
|
+
if (!scope.cli) return {
|
|
20583
|
+
ok: false,
|
|
20584
|
+
error: "svn 命令不可用"
|
|
20585
|
+
};
|
|
20586
|
+
if (!Array.isArray(args.paths) || args.paths.length > 64) return {
|
|
20587
|
+
ok: false,
|
|
20588
|
+
error: "冲突路径列表不合法或超限"
|
|
20589
|
+
};
|
|
20590
|
+
if (!args.paths.every(validTarget)) return {
|
|
20591
|
+
ok: false,
|
|
20592
|
+
error: "路径不合法"
|
|
20593
|
+
};
|
|
20594
|
+
const paths = [...new Set(args.paths.map(safeRel))];
|
|
20595
|
+
if (paths.some((path) => !path)) return {
|
|
20596
|
+
ok: false,
|
|
20597
|
+
error: "路径不合法"
|
|
20598
|
+
};
|
|
20599
|
+
try {
|
|
20600
|
+
const states = [];
|
|
20601
|
+
for (const path of paths) states.push(await readConflict(scope.cwd, scope.root, path));
|
|
20602
|
+
return {
|
|
20603
|
+
ok: true,
|
|
20604
|
+
states
|
|
20605
|
+
};
|
|
20606
|
+
} catch (error) {
|
|
20607
|
+
return {
|
|
20608
|
+
ok: false,
|
|
20609
|
+
error: String(error)
|
|
20610
|
+
};
|
|
20611
|
+
}
|
|
20612
|
+
},
|
|
20613
|
+
"svn.resolve": async (args) => {
|
|
20614
|
+
if (!validAccept(args.accept)) return {
|
|
20615
|
+
ok: false,
|
|
20616
|
+
error: "解决策略不合法"
|
|
20617
|
+
};
|
|
20618
|
+
if (!validTarget(args.path)) return {
|
|
20619
|
+
ok: false,
|
|
20620
|
+
error: "路径不合法"
|
|
20621
|
+
};
|
|
20622
|
+
const path = safeRel(args.path);
|
|
20623
|
+
if (!path) return {
|
|
20624
|
+
ok: false,
|
|
20625
|
+
error: "路径不合法"
|
|
20626
|
+
};
|
|
20627
|
+
const scope = await updateScope(args.sessionId, true);
|
|
20628
|
+
if ("err" in scope) return {
|
|
20629
|
+
ok: false,
|
|
20630
|
+
error: scope.err
|
|
20631
|
+
};
|
|
20632
|
+
if (!scope.cli) return {
|
|
20633
|
+
ok: false,
|
|
20634
|
+
error: "svn 命令不可用"
|
|
20635
|
+
};
|
|
20636
|
+
if (writeBusy(scope.root)) return {
|
|
20637
|
+
ok: false,
|
|
20638
|
+
error: "工作副本正在更新或解决冲突,请等待完成"
|
|
20639
|
+
};
|
|
20640
|
+
conflictWrites.add(writeKey(scope.root));
|
|
20641
|
+
try {
|
|
20642
|
+
const evidence = await readConflict(scope.cwd, scope.root, path);
|
|
20643
|
+
if (!hasConflict(evidence)) return {
|
|
20644
|
+
ok: false,
|
|
20645
|
+
error: "目标已无冲突,请刷新状态"
|
|
20646
|
+
};
|
|
20647
|
+
if (evidence.tree && args.accept !== "working") return {
|
|
20648
|
+
ok: false,
|
|
20649
|
+
error: "树冲突不支持通用完整版本选择,请针对性处理"
|
|
20650
|
+
};
|
|
20651
|
+
const target = svnPathOf(scope.cwd, scope.root, path);
|
|
20652
|
+
const outcome = await runSvn(scope.root, [
|
|
20653
|
+
"resolve",
|
|
20654
|
+
"--accept",
|
|
20655
|
+
args.accept,
|
|
20656
|
+
"--depth",
|
|
20657
|
+
"empty",
|
|
20658
|
+
"--",
|
|
20659
|
+
pegTarget(target)
|
|
20660
|
+
], MUTATE_GRACE_MS, OUTPUT_CAP, false);
|
|
20661
|
+
if (outcome.code !== 0) return {
|
|
20662
|
+
ok: false,
|
|
20663
|
+
error: outcome.stderr || outcome.stdout || "SVN 解决冲突失败"
|
|
20664
|
+
};
|
|
20665
|
+
return {
|
|
20666
|
+
ok: true,
|
|
20667
|
+
summary: "SVN 解决命令已执行,请核对剩余冲突",
|
|
20668
|
+
output: outcome.stdout
|
|
20669
|
+
};
|
|
20670
|
+
} catch (error) {
|
|
20671
|
+
return {
|
|
20672
|
+
ok: false,
|
|
20673
|
+
error: String(error)
|
|
20674
|
+
};
|
|
20675
|
+
} finally {
|
|
20676
|
+
conflictWrites.delete(writeKey(scope.root));
|
|
20677
|
+
changesCache.delete(scope.root);
|
|
20678
|
+
explorerCache.clear();
|
|
20679
|
+
}
|
|
20680
|
+
},
|
|
20501
20681
|
"svn.revert": async (args) => {
|
|
20502
|
-
return mutate("还原", ["revert", "-R"], args.sessionId, args.paths);
|
|
20682
|
+
if (args.depth === void 0) return mutate("还原", ["revert", "-R"], args.sessionId, args.paths);
|
|
20683
|
+
if (args.depth !== "empty") return {
|
|
20684
|
+
ok: false,
|
|
20685
|
+
error: "还原深度不合法"
|
|
20686
|
+
};
|
|
20687
|
+
if (!Array.isArray(args.paths) || !args.paths.length || !args.paths.every(validTarget)) return {
|
|
20688
|
+
ok: false,
|
|
20689
|
+
error: "路径不合法"
|
|
20690
|
+
};
|
|
20691
|
+
const scope = await updateScope(args.sessionId, true);
|
|
20692
|
+
if ("err" in scope) return {
|
|
20693
|
+
ok: false,
|
|
20694
|
+
error: scope.err
|
|
20695
|
+
};
|
|
20696
|
+
if (writeBusy(scope.root)) return {
|
|
20697
|
+
ok: false,
|
|
20698
|
+
error: "工作副本正在更新或解决冲突,请等待完成"
|
|
20699
|
+
};
|
|
20700
|
+
conflictWrites.add(writeKey(scope.root));
|
|
20701
|
+
try {
|
|
20702
|
+
return await mutate("还原", [
|
|
20703
|
+
"revert",
|
|
20704
|
+
"--depth",
|
|
20705
|
+
"empty"
|
|
20706
|
+
], args.sessionId, args.paths);
|
|
20707
|
+
} finally {
|
|
20708
|
+
conflictWrites.delete(writeKey(scope.root));
|
|
20709
|
+
changesCache.delete(scope.root);
|
|
20710
|
+
explorerCache.clear();
|
|
20711
|
+
}
|
|
20503
20712
|
},
|
|
20504
20713
|
"svn.add": async (args) => {
|
|
20505
20714
|
return mutate("加入版本控制", ["add"], args.sessionId, args.paths);
|
|
@@ -21354,6 +21563,10 @@ function createSvnRpc(deps) {
|
|
|
21354
21563
|
try {
|
|
21355
21564
|
const fs = ctx.get("fs");
|
|
21356
21565
|
const cwd = fs?.resolve && fs?.processPath ? fs.processPath(await fs.resolve(scope.cwd, {})) : scope.cwd;
|
|
21566
|
+
if (conflictWrites.has(writeKey(scope.root))) return {
|
|
21567
|
+
ok: false,
|
|
21568
|
+
error: "工作副本正在解决冲突,请等待完成"
|
|
21569
|
+
};
|
|
21357
21570
|
return {
|
|
21358
21571
|
ok: true,
|
|
21359
21572
|
...updateJobs.start({
|