Skip to main content

zoi_cli/cmd/
transaction.rs

1//! Logic for the `transaction` command.
2
3use anyhow::Result;
4use colored::Colorize;
5use comfy_table::presets::UTF8_FULL;
6use comfy_table::{ContentArrangement, Table};
7
8use crate::pkg::{local, transaction, types};
9
10/// Returns the source of the installed manifest.
11fn manifest_source(manifest: &types::InstallManifest) -> String {
12    local::installed_manifest_source(manifest)
13}
14
15/// List all transaction logs.
16///
17/// # Errors
18///
19/// Returns an error if the transaction list cannot be retrieved.
20pub fn list() -> Result<()> {
21    let transactions = transaction::list_transactions()?;
22    if transactions.is_empty() {
23        println!("No transaction logs found.");
24        return Ok(());
25    }
26
27    let mut table = Table::new();
28    table
29        .load_style(UTF8_FULL)
30        .set_content_arrangement(ContentArrangement::Dynamic)
31        .set_header(vec!["ID", "Started", "Operations"]);
32
33    for entry in transactions {
34        table.add_row(vec![
35            entry.id,
36            entry.start_time,
37            entry.operation_count.to_string(),
38        ]);
39    }
40
41    println!("{table}");
42    Ok(())
43}
44
45/// List modified files for a specific transaction.
46///
47/// # Errors
48///
49/// Returns an error if the modified files for the given transaction ID cannot
50/// be retrieved.
51pub fn files(transaction_id: &str) -> Result<()> {
52    let mut modified_files = transaction::get_modified_files(transaction_id)?;
53    modified_files.sort();
54
55    if modified_files.is_empty() {
56        println!(
57            "No modified files recorded for transaction '{transaction_id}'."
58        );
59        return Ok(());
60    }
61
62    println!(
63        "{} Files modified by transaction '{}':",
64        "::".bold().blue(),
65        transaction_id.cyan()
66    );
67    for path in modified_files {
68        println!("  - {path}");
69    }
70    Ok(())
71}
72
73/// Show details for a specific transaction.
74///
75/// # Errors
76///
77/// Returns an error if the transaction with the given ID cannot be read.
78pub fn show(transaction_id: &str) -> Result<()> {
79    let transaction = transaction::read_transaction(transaction_id)?;
80
81    println!(
82        "{} Transaction {}",
83        "::".bold().blue(),
84        transaction.id.cyan()
85    );
86    println!("Started: {}", transaction.start_time);
87    println!("Operations: {}", transaction.operations.len());
88
89    for (index, operation) in transaction.operations.iter().enumerate() {
90        match operation {
91            types::TransactionOperation::Install { manifest } => {
92                println!(
93                    "{}. install {}",
94                    index + 1,
95                    manifest_source(manifest).green()
96                );
97            }
98            types::TransactionOperation::Uninstall { manifest } => {
99                println!(
100                    "{}. uninstall {}",
101                    index + 1,
102                    manifest_source(manifest).red()
103                );
104            }
105            types::TransactionOperation::Upgrade {
106                old_manifest,
107                new_manifest
108            } => {
109                println!(
110                    "{}. upgrade {} -> {}",
111                    index + 1,
112                    manifest_source(old_manifest).yellow(),
113                    manifest_source(new_manifest).green()
114                );
115            }
116        }
117    }
118
119    Ok(())
120}
121
122/// Undoes a transaction by reverting all of its recorded operations in
123/// reverse order. Falls back to the most recent transaction when no ID is
124/// given.
125///
126/// # Errors
127///
128/// Returns an error if the transaction cannot be read or the revert fails.
129pub fn undo(id: Option<&str>, yes: bool, global_yes: bool) -> Result<()> {
130    use crate::utils;
131
132    let skip_confirm = yes || global_yes;
133    if !utils::ask_for_confirmation(
134        "This will revert all operations recorded in the transaction. Are you \
135         sure?",
136        skip_confirm
137    ) {
138        println!("Operation aborted.");
139        return Ok(());
140    }
141
142    let transaction_id = match id {
143        Some(id) => id.to_string(),
144        None => transaction::get_last_transaction_id()?
145            .ok_or_else(|| anyhow::anyhow!("No transactions found to undo."))?
146    };
147
148    println!("Undoing transaction {transaction_id}...");
149    transaction::rollback(&transaction_id)?;
150    println!("{}", "Transaction undone successfully.".green());
151    Ok(())
152}