BuildTarget::source_only carried two states for what is a three-way
distinction now that pkh test exists: replace it with a TargetKind
(Source/Binary/Test) and derive the tee log prefix (build-/deb-/test-)
from it, so test runs get their own log family instead of borrowing
the deb one.
pkh put only spoke SFTP to the PPA queue, so a failure of the SSH
transport itself (TCP, banner exchange) failed the upload even though
dput happily pushes the same files: its plain ppa: profile goes over
the anonymous FTP queue of ppa.launchpad.net, the same destination
over another port.
Classify the SSH connection failures: Transport (the connection never
came up: resolution, TCP, banner or key exchange) degrades to that FTP
queue — the upload order (payload first, .changes last), the
reverse-order DELE cleanup of a failed upload and the per-chunk
progress reporting all mirror the SFTP path, sharing cleanup_list.
Refused failures (host key not accepted, no matching authentication)
stay errors: silently switching transport would bypass the refusal.
The FTP client is suppaftp's blocking stream, with the time bounds it
does not carry by itself: the control channel's reads and writes, the
data channel's writes and connect (through a custom passive stream
builder), and the NAT workaround for PASV replies announcing an
unroutable address. The queue endpoints (host, port) join
data/launchpad.yml next to the SFTP ones, and the FTP transport is
covered by unit tests against an in-process fake queue plus a live
control-channel handshake with the real server (ignored, network).
Instead of carrying raw UI in core, the ports now represent everything
the CLI used to do inline:
- Prompter::present shows context outside of a question (the wizard
summary screen, the vendoring notice spacing); TerminalPrompter
prints it on stdout exactly like the println!s it replaces, server
embeds forward it as a display event.
- generate_entry returns the generated entry (package, versions,
series, path) instead of printing; the CLI renders the same lines.
- BuildTarget carries a flow-composed display line and a tee_log flag:
the terminal adapter renders it verbatim ("Building source package
...", "Building ... for series/arch", "Uploading ... to ...") and
uploads open no build log.
- The unmet build-dependency diagnostics are rendered by the CLI from
the typed error, in the original order (details, then summary).
- --verbose constructs no live view at all (an idle widget used to
linger), and the re-vendor offer only logs when it is actually
asked, so headless runs print the error exactly once.
put() loses its MultiProgress parameter: the summary, pre-flight and
connection spinners become view messages, the per-file SFTP transfer
reports determinate progress through view.progress (upload_file takes
a byte-count callback instead of an indicatif bar), and the display is
released through view.suspend on every exit path. The hardcoded
trust-on-first-use prompt in the SSH host-key verification becomes the
Prompter::accept_host_key port (fail-closed by default; the terminal
prompter prints the authenticity banner and confirms), so a remote
frontend can surface its own host-key dialog.
Prompter gains interactive(), select() and text() (with the Validator
type), and confirm() now propagates cancellation as Err so flows abort
instead of silently taking a default when the user hits Ctrl+C. The
terminal prompter implements the full port; the port also re-exports
the path display helper, which is pure presentation formatting used by
events and messages rather than terminal code.
The Section check always read debian/control from the current working
directory, so 'pkh put --changes ../other/pkg_changes' validated the
wrong tree. With an explicit --changes the check now runs against that
file's own directory when it holds debian/control, and is skipped with
a warning otherwise; tree uploads are unchanged.
A failed or interrupted upload left the already-uploaded payloads — or
a truncated .changes — in the PPA's incoming area. On failure the
already-uploaded files are now removed best-effort in reverse upload
order with the failed file first, so a .changes never outlives the
payloads it references; the original upload error keeps precedence over
cleanup failures, and record-after-success semantics are unchanged (a
failed upload must not count as uploaded).
An unreadable or unparsable known_hosts file was swallowed with
'let _', silently downgrading to prompt-and-accept without telling the
user why their configuration was ignored: warn naming the file, then
continue. And when the pinned Launchpad fingerprint matches, a
DIFFERENT key recorded for that host in known_hosts was silently
bypassed: warn about the stale entry (diagnostic only — the published
fingerprint stays authoritative).
TcpStream::connect and the blocking libssh2 session had no timeouts: a
black-holed host hung pkh put forever, mid-resolution, mid-handshake or
mid-upload. Connect attempts now get a 15 s timeout per resolved
address, the session gets a 30 s API timeout for the handshake/auth
phase and a 300 s per-call timeout for SFTP operations (per low-level
libssh2 call, not per transfer — documented); failures name the
operation and host.
A corrupt upload log was silently treated as 'never uploaded',
disabling the duplicate-upload guard without a diagnostic, and
record_upload truncated the file in place — a crash mid-write produced
exactly that corrupt state. Parse failures now log an error, back the
file up to uploads.json.bak (so a later successful upload cannot
destroy the recoverable history) and continue with an empty log; the
log itself is written to a temp file and renamed into place.
Host pattern lists were evaluated per-pattern with 'any', so
'Host * !*.launchpad.net' matched ppa.launchpad.net via the wildcard;
a block now applies only if a positive pattern matches and no negated
one does (OpenSSH's rule). The system ssh_config was read first with
first-obtained-wins, inverting OpenSSH's user-over-system precedence;
the user file is read first now. A Match block also no longer leaks
the previous Host block's match state (its options are ignored until
the next Host).
ssh2::File's Drop discards the close-handshake error ('too late to
recover'), so a quota or server-side abort surfacing in the final ACKs
was recorded as a successful upload of a truncated file. Close upload
handles explicitly and propagate the error; also applies to the ssh
context driver's write_file and upload_recursive, which had the same
silent-drop issue.
IdentityFile values were stored verbatim, so the near-universal
'IdentityFile ~/.ssh/key' spelling never matched an existing file and
the key was silently skipped during authentication. Expand a leading
~ (only that form; ~user and embedded tildes stay verbatim) against
the user's home directory when parsing.
Upload built source packages over SFTP with host-key verification
(Launchpad fingerprints pinned in host_keys.yml, ask-to-accept
otherwise), Launchpad account discovery (git config lp.user), and
pre-flight checks the upload queue itself never does: changes file
discovery/validation, PPA existence via the Launchpad API, target
series validity, and debian/control Section validity (sections
bundled in distro_info.yml). Upload log prevents duplicate uploads
unless --force.