More work on ssh copy

This commit is contained in:
Kovid Goyal 2022-02-28 07:58:49 +05:30
parent fadae42715
commit 4b6bfaffba
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 49 additions and 17 deletions

View File

@ -5,7 +5,9 @@
import glob
import os
import shlex
from typing import Iterable, Iterator, List, Optional, Sequence, Tuple
from typing import (
Iterable, Iterator, List, NamedTuple, Optional, Sequence, Tuple
)
from kitty.cli import parse_args
from kitty.cli_stub import CopyCLIOptions
@ -24,8 +26,15 @@ Interpret file arguments as glob patterns.
The destination on the remote computer to copy to. Relative paths are resolved
relative to HOME on the remote machine. When this option is not specified, the
local file path is used as the remote destination (with the HOME directory
getting automatically replaced by the remote HOME). Note that enviroment
getting automatically replaced by the remote HOME). Note that environment
variables and ~ are not expanded.
--exclude
type=list
A glob pattern. Files whose names would match this pattern after transfer
are excluded from being transferred. Useful when adding directories. Can
be specified multiple times, if any of the patterns match the file will be excluded.
'''
@ -58,7 +67,23 @@ class CopyCLIError(ValueError):
pass
def parse_copy_instructions(val: str) -> Iterable[Tuple[str, CopyCLIOptions]]:
def get_arcname(loc: str, dest: Optional[str], home: str) -> str:
if dest:
arcname = dest
else:
arcname = os.path.normpath(loc)
if arcname.startswith(home):
arcname = os.path.relpath(arcname, home)
arcname = os.path.normpath(arcname).replace(os.sep, '/')
return arcname
class CopyInstruction(NamedTuple):
arcname: str
exclude_patterns: Tuple[str, ...]
def parse_copy_instructions(val: str) -> Iterable[Tuple[str, CopyInstruction]]:
opts, args = parse_copy_args(shlex.split(val))
locations: List[str] = []
for a in args:
@ -67,5 +92,7 @@ def parse_copy_instructions(val: str) -> Iterable[Tuple[str, CopyCLIOptions]]:
raise CopyCLIError('No files to copy specified')
if len(locations) > 1 and opts.dest:
raise CopyCLIError('Specifying a remote location with more than one file is not supported')
home = os.path.expanduser('~')
for loc in locations:
yield loc, opts
arcname = get_arcname(loc, opts.dest, home)
yield loc, CopyInstruction(arcname, tuple(opts.exclude))

View File

@ -2,6 +2,7 @@
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import atexit
import fnmatch
import io
import json
import os
@ -15,7 +16,7 @@ import traceback
from base64 import standard_b64decode
from contextlib import suppress
from typing import (
Any, Dict, Iterator, List, NoReturn, Optional, Set, Tuple, Union
Any, Callable, Dict, Iterator, List, NoReturn, Optional, Set, Tuple, Union
)
from kitty.constants import cache_dir, shell_integration_dir, terminfo_dir
@ -66,10 +67,13 @@ def make_tarfile(ssh_opts: SSHOptions, base_env: Dict[str, str]) -> bytes:
tf.addfile(ans, io.BytesIO(data))
return ans
def filter_files(tarinfo: tarfile.TarInfo) -> Optional[tarfile.TarInfo]:
if tarinfo.name.endswith('ssh/bootstrap.sh'):
return None
return normalize_tarinfo(tarinfo)
def filter_from_globs(*pats: str) -> Callable[[tarfile.TarInfo], Optional[tarfile.TarInfo]]:
def filter(tarinfo: tarfile.TarInfo) -> Optional[tarfile.TarInfo]:
for pat in pats:
if fnmatch.fnmatch(tarinfo.name, pat):
return None
return normalize_tarinfo(tarinfo)
return filter
from kitty.shell_integration import get_effective_ksi_env_var
if ssh_opts.shell_integration == 'inherit':
@ -94,10 +98,13 @@ def make_tarfile(ssh_opts: SSHOptions, base_env: Dict[str, str]) -> bytes:
buf = io.BytesIO()
with tarfile.open(mode='w:bz2', fileobj=buf, encoding='utf-8') as tf:
rd = ssh_opts.remote_dir.rstrip('/')
for location, ci in ssh_opts.copy.items():
tf.add(location, arcname=ci.arcname, filter=filter_from_globs(*ci.exclude_patterns))
add_data_as_file(tf, 'kitty-ssh-kitten-data.sh', env_script)
if ksi:
tf.add(shell_integration_dir, arcname=rd + '/shell-integration', filter=filter_files)
tf.add(terminfo_dir, arcname='.terminfo', filter=filter_files)
arcname = rd + '/shell-integration'
tf.add(shell_integration_dir, arcname=arcname, filter=filter_from_globs(f'{arcname}/ssh/bootstrap.*'))
tf.add(terminfo_dir, arcname='.terminfo', filter=normalize_tarinfo)
return buf.getvalue()

View File

@ -1,7 +1,7 @@
# generated by gen-config.py DO NOT edit
import typing
import kitty.cli_stub
import kittens.ssh.copy
option_names = ( # {{{
@ -12,7 +12,7 @@ class Options:
hostname: str = '*'
remote_dir: str = '.local/share/kitty-ssh-kitten'
shell_integration: str = 'inherit'
copy: typing.Dict[str, kitty.cli_stub.CLIOptions] = {}
copy: typing.Dict[str, kittens.ssh.copy.CopyInstruction] = {}
env: typing.Dict[str, str] = {}
config_paths: typing.Tuple[str, ...] = ()
config_overrides: typing.Tuple[str, ...] = ()

View File

@ -4,9 +4,7 @@
import posixpath
from typing import Any, Dict, Iterable, Optional, Tuple
from kitty.cli_stub import CopyCLIOptions
from ..copy import parse_copy_instructions
from ..copy import CopyInstruction, parse_copy_instructions
DELETE_ENV_VAR = '_delete_this_env_var_'
@ -33,7 +31,7 @@ def env(val: str, current_val: Dict[str, str]) -> Iterable[Tuple[str, str]]:
yield val, DELETE_ENV_VAR
def copy(val: str, current_val: Dict[str, str]) -> Iterable[Tuple[str, CopyCLIOptions]]:
def copy(val: str, current_val: Dict[str, str]) -> Iterable[Tuple[str, CopyInstruction]]:
yield from parse_copy_instructions(val)