zoi_cli/cmd/package/delta.rs
1//! Implementation of the `package delta` command.
2//!
3//! Generates a `.zdelta` file that transforms an older `.zpa` archive into a
4//! newer one using content-addressed pool matching and bsdiff patches.
5
6use std::path::PathBuf;
7
8use anyhow::Result;
9use colored::Colorize;
10
11/// Arguments for the `zoi package delta` command.
12#[derive(clap::Parser, Debug)]
13pub struct DeltaCommand {
14 /// The older `.zpa` archive (the delta's base).
15 pub old: PathBuf,
16
17 /// The newer `.zpa` archive (the delta's target).
18 pub new: PathBuf,
19
20 /// Where to write the `.zdelta`. Defaults to
21 /// `<new>.from-v<old>-to-v<new>.zdelta` next to the new archive when the
22 /// archives carry version suffixes, otherwise `<new>.zdelta`.
23 #[arg(long, short = 'o')]
24 pub output: Option<PathBuf>,
25
26 /// Sign the delta patch with a PGP key from the Zoi keyring.
27 #[arg(long)]
28 pub sign: Option<String>
29}
30
31/// Runs the `package delta` command.
32///
33/// # Errors
34///
35/// Returns an error if either archive cannot be read or the delta cannot be
36/// generated.
37pub fn run(cmd: &DeltaCommand) -> Result<()> {
38 let output = cmd
39 .output
40 .clone()
41 .unwrap_or_else(|| default_output(&cmd.new));
42 crate::pkg::delta::create_zpa_delta(
43 &cmd.old,
44 &cmd.new,
45 &output,
46 cmd.sign.as_deref()
47 )?;
48 println!(
49 "{} Apply it with 'zoi package install --apply-delta' or distribute \
50 it alongside the release.",
51 "::".bold().blue()
52 );
53 Ok(())
54}
55
56/// Derives a default output path for the delta file from the new archive
57/// name.
58fn default_output(new_archive: &std::path::Path) -> PathBuf {
59 // Best-effort convention: <name>-vA-to-vB.zdelta derived from the new
60 // archive name; falls back to appending .zdelta.
61 let stem = new_archive.file_name().map_or_else(
62 || "package.zpa".to_string(),
63 |f| f.to_string_lossy().to_string()
64 );
65 new_archive.with_file_name(format!("{stem}.zdelta"))
66}