Skip to main content

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}