fix: speed up tran apply-patch
This commit is contained in:
+100
-21
@@ -7,9 +7,11 @@ export interface ParsedSshArgs {
|
||||
invocationKind: SshInvocationKind;
|
||||
stdinPrefix?: string;
|
||||
stdinSuffix?: string;
|
||||
requiredHelpers?: SshHelperName[];
|
||||
}
|
||||
|
||||
export type SshInvocationKind = "interactive" | "argv" | "helper" | "ssh-like";
|
||||
export type SshHelperName = "apply_patch" | "glob" | "skill-discover";
|
||||
|
||||
export interface ParsedSshRoute {
|
||||
providerId: string;
|
||||
@@ -104,6 +106,65 @@ line_number_for_prefix() {
|
||||
printf '%s\n' $((newline_count + 1))
|
||||
}
|
||||
|
||||
replace_once_with_perl() {
|
||||
command -v perl >/dev/null 2>&1 || return 127
|
||||
perl -0777 -e '
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
sub fail {
|
||||
print STDERR "apply_patch: ", @_, "\n";
|
||||
exit 1;
|
||||
}
|
||||
|
||||
sub read_all {
|
||||
my ($path, $label) = @_;
|
||||
open my $fh, "<", $path or fail("failed to read $label");
|
||||
binmode $fh;
|
||||
local $/;
|
||||
my $data = <$fh>;
|
||||
close $fh;
|
||||
return defined $data ? $data : "";
|
||||
}
|
||||
|
||||
my ($target, $search_file, $replace_file, $hunk_id, $allow_loose, $out) = @ARGV;
|
||||
my $old = read_all($target, $target);
|
||||
my $search = read_all($search_file, "hunk search");
|
||||
my $replacement = read_all($replace_file, "hunk replacement");
|
||||
my $new;
|
||||
|
||||
if ($search eq "") {
|
||||
fail("hunk $hunk_id in $target has no context; add unchanged/deleted anchor lines or pass --allow-loose after manual review") if $allow_loose ne "1";
|
||||
print STDERR "apply_patch: hunk $hunk_id matched $target:1 (loose)\n";
|
||||
$new = $replacement . $old;
|
||||
} else {
|
||||
my $pos = -1;
|
||||
my $offset = 0;
|
||||
my $count = 0;
|
||||
while (1) {
|
||||
my $found = index($old, $search, $offset);
|
||||
last if $found < 0;
|
||||
$pos = $found if $count == 0;
|
||||
$count += 1;
|
||||
last if $count > 1 && $allow_loose ne "1";
|
||||
$offset = $found + length($search);
|
||||
}
|
||||
fail("hunk $hunk_id context not found in $target") if $count == 0;
|
||||
fail("hunk $hunk_id context matched multiple locations in $target; add more unchanged context or pass --allow-loose after manual review") if $count > 1 && $allow_loose ne "1";
|
||||
my $prefix = substr($old, 0, $pos);
|
||||
my $suffix = substr($old, $pos + length($search));
|
||||
my $line = ($prefix =~ tr/\n//) + 1;
|
||||
print STDERR "apply_patch: hunk $hunk_id matched $target:$line\n";
|
||||
$new = $prefix . $replacement . $suffix;
|
||||
}
|
||||
|
||||
open my $ofh, ">", $out or fail("failed to render patched file");
|
||||
binmode $ofh;
|
||||
print $ofh $new or fail("failed to render patched file");
|
||||
close $ofh or fail("failed to render patched file");
|
||||
' "$@"
|
||||
}
|
||||
|
||||
replace_once() {
|
||||
target=$1
|
||||
search_file=$2
|
||||
@@ -111,6 +172,17 @@ replace_once() {
|
||||
hunk_id=$4
|
||||
[ -e "$target" ] || die "file not found: $target"
|
||||
|
||||
fast_out=$(mk_temp)
|
||||
if replace_once_with_perl "$target" "$search_file" "$replace_file" "$hunk_id" "$allow_loose" "$fast_out"; then
|
||||
write_file "$target" "$fast_out"
|
||||
rm -f "$fast_out"
|
||||
return 0
|
||||
else
|
||||
status=$?
|
||||
fi
|
||||
rm -f "$fast_out"
|
||||
[ "$status" = 127 ] || exit "$status"
|
||||
|
||||
marker="__UNIDESK_APPLY_PATCH_EOF_$$__"
|
||||
old=$(cat "$target"; printf '%s' "$marker") || die "failed to read $target"
|
||||
old=${"$"}{old%"$marker"}
|
||||
@@ -707,11 +779,11 @@ export function parseSshArgs(args: string[]): ParsedSshArgs {
|
||||
const subcommand = args[0] ?? "";
|
||||
if (isSshSkillDiscoveryArgs(args)) {
|
||||
const toolArgs = subcommand === "skill" ? ["skill-discover", ...args.slice(2)] : ["skill-discover", ...args.slice(1)];
|
||||
return { remoteCommand: shellArgv(toolArgs), requiresStdin: false, invocationKind: "helper" };
|
||||
return { remoteCommand: shellArgv(toolArgs), requiresStdin: false, invocationKind: "helper", requiredHelpers: ["skill-discover"] };
|
||||
}
|
||||
if (subcommand === "apply-patch" || subcommand === "patch") {
|
||||
const toolArgs = ["apply_patch", ...args.slice(1)];
|
||||
return { remoteCommand: shellArgv(toolArgs), requiresStdin: true, invocationKind: "helper" };
|
||||
return { remoteCommand: shellArgv(toolArgs), requiresStdin: true, invocationKind: "helper", requiredHelpers: ["apply_patch"] };
|
||||
}
|
||||
if (subcommand === "py") {
|
||||
return { remoteCommand: buildPythonStdinCommand(args.slice(1)), requiresStdin: true, invocationKind: "helper" };
|
||||
@@ -728,7 +800,7 @@ export function parseSshArgs(args: string[]): ParsedSshArgs {
|
||||
return { remoteCommand: buildFindCommand(args.slice(1)), requiresStdin: false, invocationKind: "helper" };
|
||||
}
|
||||
if (subcommand === "glob") {
|
||||
return { remoteCommand: shellArgv(["glob", ...args.slice(1)]), requiresStdin: false, invocationKind: "helper" };
|
||||
return { remoteCommand: shellArgv(["glob", ...args.slice(1)]), requiresStdin: false, invocationKind: "helper", requiredHelpers: ["glob"] };
|
||||
}
|
||||
if (subcommand === "k3s") {
|
||||
throw new Error("ssh k3s shorthand is unsupported; put k3s in the route, for example: ssh D601:k3s kubectl get nodes");
|
||||
@@ -1412,27 +1484,34 @@ function buildPythonStdinCommand(args: string[]): string {
|
||||
].join("; ");
|
||||
}
|
||||
|
||||
function remoteToolBootstrapCommand(): string {
|
||||
const encodedApplyPatch = Buffer.from(remoteApplyPatchSource, "utf8").toString("base64");
|
||||
const encodedGlob = Buffer.from(remoteGlobSource, "utf8").toString("base64");
|
||||
const encodedSkillDiscover = Buffer.from(remoteSkillDiscoverSource, "utf8").toString("base64");
|
||||
return [
|
||||
const remoteToolSources: Record<SshHelperName, string> = {
|
||||
apply_patch: remoteApplyPatchSource,
|
||||
glob: remoteGlobSource,
|
||||
"skill-discover": remoteSkillDiscoverSource,
|
||||
};
|
||||
|
||||
function remoteToolBootstrapCommand(helpers: readonly SshHelperName[] = []): string {
|
||||
const uniqueHelpers = [...new Set(helpers)];
|
||||
if (uniqueHelpers.length === 0) return "";
|
||||
const commands = [
|
||||
"UNIDESK_SSH_TOOL_DIR=/tmp/unidesk-ssh-tools",
|
||||
'mkdir -p "$UNIDESK_SSH_TOOL_DIR"',
|
||||
`printf %s ${shellQuote(encodedApplyPatch)} | base64 -d > "$UNIDESK_SSH_TOOL_DIR/apply_patch"`,
|
||||
`printf %s ${shellQuote(encodedGlob)} | base64 -d > "$UNIDESK_SSH_TOOL_DIR/glob"`,
|
||||
`printf %s ${shellQuote(encodedSkillDiscover)} | base64 -d > "$UNIDESK_SSH_TOOL_DIR/skill-discover"`,
|
||||
'chmod 700 "$UNIDESK_SSH_TOOL_DIR/apply_patch"',
|
||||
'chmod 700 "$UNIDESK_SSH_TOOL_DIR/glob"',
|
||||
'chmod 700 "$UNIDESK_SSH_TOOL_DIR/skill-discover"',
|
||||
'export PATH="$UNIDESK_SSH_TOOL_DIR:$PATH"',
|
||||
].join("; ");
|
||||
];
|
||||
for (const helper of uniqueHelpers) {
|
||||
const source = remoteToolSources[helper];
|
||||
const encoded = Buffer.from(source, "utf8").toString("base64");
|
||||
commands.push(`printf %s ${shellQuote(encoded)} | base64 -d > "$UNIDESK_SSH_TOOL_DIR/${helper}"`);
|
||||
commands.push(`chmod 700 "$UNIDESK_SSH_TOOL_DIR/${helper}"`);
|
||||
}
|
||||
commands.push('export PATH="$UNIDESK_SSH_TOOL_DIR:$PATH"');
|
||||
return commands.join("; ");
|
||||
}
|
||||
|
||||
export function wrapSshRemoteCommand(command: string | null): string {
|
||||
const bootstrap = remoteToolBootstrapCommand();
|
||||
if (command === null) return `${bootstrap}; exec "\${SHELL:-/bin/bash}" -l`;
|
||||
return `${bootstrap}; stty -echo 2>/dev/null || true; ${command}`;
|
||||
export function wrapSshRemoteCommand(command: string | null, helpers: readonly SshHelperName[] = []): string {
|
||||
const bootstrap = remoteToolBootstrapCommand(helpers);
|
||||
const prefix = bootstrap.length > 0 ? `${bootstrap}; ` : "";
|
||||
if (command === null) return `${prefix}exec "\${SHELL:-/bin/bash}" -l`;
|
||||
return `${prefix}stty -echo 2>/dev/null || true; ${command}`;
|
||||
}
|
||||
|
||||
function safeProviderId(providerId: string): string {
|
||||
@@ -1615,7 +1694,7 @@ export async function runSsh(config: UniDeskConfig, providerId: string, args: st
|
||||
const openTimeoutMs = Math.max(15000, Number(process.env.UNIDESK_SSH_OPEN_TIMEOUT_MS || 60000));
|
||||
const payload = {
|
||||
providerId: invocation.providerId,
|
||||
command: wrapSshRemoteCommand(parsed.remoteCommand),
|
||||
command: wrapSshRemoteCommand(parsed.remoteCommand, parsed.requiredHelpers),
|
||||
cwd: invocation.route.plane === "host" ? invocation.route.workspace ?? undefined : undefined,
|
||||
tty: parsed.remoteCommand === null,
|
||||
stdinEotOnEnd: parsed.remoteCommand !== null,
|
||||
|
||||
Reference in New Issue
Block a user