wsb 0.275.7622

Workspace tools for every project under $PRJ: git across submodules, template stamping, GitHub, and the workbench
Documentation
//! `wsb upgrade` — rebuild the pair from wsb's repository and install it. See
//! [`crate::upgrade`].

use crate::output::emit;
use crate::output::record::Record;
use crate::upgrade::MachineStep;

pub const USAGE: &str = "\
Usage: wsb upgrade [--ref <branch|tag>] [--dry-run]

  Fetch wsb's repository at the ref (default main) into one checkout this
  machine keeps for it, build wsb and wsb-daemon with QuickRust, start both,
  and replace the installed pair in $QRUST_HOME/bin (else ~/.qrust/bin): both,
  or neither. The new wsb then stops the daemon, migrates the machine's state
  (`wsb migrate`), and a service installed with `wsb daemon install` is
  reinstalled by it and restarted onto the new daemon.

  --dry-run          build and start both, say what would be replaced, and
                     replace nothing

Speaks --jsonl.";

/// `args` are the arguments after `upgrade`.
pub fn dispatch(args: &[String]) -> u8 {
    let mut reference = crate::upgrade::DEFAULT_REF.to_string();
    let mut dry_run = false;
    let mut arguments = args.iter();
    while let Some(argument) = arguments.next() {
        match argument.as_str() {
            "--ref" => match arguments.next() {
                Some(value) => reference = value.clone(),
                None => {
                    crate::errln!("wsb upgrade: --ref needs a value\n\n{USAGE}");
                    return 2;
                }
            },
            "--dry-run" => dry_run = true,
            "help" | "--help" | "-h" => {
                crate::outln!("{USAGE}");
                return 0;
            }
            other => {
                crate::errln!("wsb upgrade: unknown argument '{other}'\n\n{USAGE}");
                return 2;
            }
        }
    }
    match upgrade(&reference, dry_run) {
        Ok(()) => 0,
        Err(message) => {
            crate::errln!("wsb upgrade: {message}");
            1
        }
    }
}

fn upgrade(reference: &str, dry_run: bool) -> Result<(), String> {
    let report = crate::upgrade::run(reference, dry_run)?;
    emit(Record::Upgrade {
        from_version: report.from_version.clone(),
        to_version: report.to_version.clone(),
        reference: report.reference.clone(),
        commit: report.commit.clone(),
        replaced: report.replaced.iter().map(|p| p.display().to_string()).collect(),
        dry_run: report.dry_run,
    });
    let from = report.from_version.as_deref().unwrap_or("a wsb that did not report its version");
    let at = &report.commit[..report.commit.len().min(12)];
    if report.dry_run {
        crate::outln!("upgrade: would replace {from} with {} ({} at {at})", report.to_version, report.reference);
        for path in &report.replaced {
            crate::outln!("  {}", path.display());
        }
        crate::outln!("Nothing was replaced: --dry-run.");
        return Ok(());
    }
    crate::outln!("upgrade: {from} \u{2192} {} ({} at {at})", report.to_version, report.reference);
    for path in &report.replaced {
        crate::outln!("  replaced {}", path.display());
    }
    let here = std::env::current_dir().map_err(|e| format!("cannot determine the current directory: {e}"))?;
    match crate::upgrade::bring_the_machine_over(&report.install_dir, &here)? {
        MachineStep::Restarted { worktree } => crate::outln!(
            "the machine's state was migrated, and the service was reinstalled by the new wsb and \
             restarted onto the new daemon (worktree {})",
            worktree.display()
        ),
        MachineStep::Stopped => crate::outln!(
            "No service is installed: the daemon was stopped and the machine's state migrated; the \
             next command starts the new daemon."
        ),
    }
    Ok(())
}