lux_lib/operations/
pin.rs

1use std::io;
2
3use fs_extra::dir::CopyOptions;
4use itertools::Itertools;
5use thiserror::Error;
6
7use crate::{
8    lockfile::{LocalPackageId, PinnedState},
9    package::PackageSpec,
10    tree::{Tree, TreeError},
11};
12
13// TODO(vhyrro): Differentiate pinned LocalPackages at the type level?
14
15#[derive(Error, Debug)]
16pub enum PinError {
17    #[error("package with ID {0} not found in lockfile")]
18    PackageNotFound(LocalPackageId),
19    #[error("rock {rock} is already {}pinned!", if *.pin_state == PinnedState::Unpinned { "un" } else { "" })]
20    PinStateUnchanged {
21        pin_state: PinnedState,
22        rock: PackageSpec,
23    },
24    #[error("cannot change pin state of {rock}, since a second version of {rock} is already installed with `pin: {}`", .pin_state.as_bool())]
25    PinStateConflict {
26        pin_state: PinnedState,
27        rock: PackageSpec,
28    },
29    #[error(transparent)]
30    Io(#[from] io::Error),
31    #[error(transparent)]
32    Tree(#[from] TreeError),
33    #[error("failed to move old package: {0}")]
34    MoveItemsFailure(#[from] fs_extra::error::Error),
35    #[error("cannot change pin state of {rock}, because it is not an entrypoint")]
36    NotAnEntrypoint { rock: PackageSpec },
37}
38
39pub fn set_pinned_state(
40    package_id: &LocalPackageId,
41    tree: &Tree,
42    pin: PinnedState,
43) -> Result<(), PinError> {
44    let lockfile = tree.lockfile()?;
45    let mut package = lockfile
46        .get(package_id)
47        .ok_or_else(|| PinError::PackageNotFound(package_id.clone()))?
48        .clone();
49
50    if !lockfile.is_entrypoint(&package.id()) {
51        return Err(PinError::NotAnEntrypoint {
52            rock: package.to_package(),
53        });
54    }
55
56    if pin == package.pinned() {
57        return Err(PinError::PinStateUnchanged {
58            pin_state: package.pinned(),
59            rock: package.to_package(),
60        });
61    }
62
63    let old_package = package.clone();
64    let items = std::fs::read_dir(tree.root_for(&package))?
65        .filter_map(Result::ok)
66        .map(|dir| dir.path())
67        .collect_vec();
68
69    package.spec.pinned = pin;
70
71    if lockfile.get(&package.id()).is_some() {
72        return Err(PinError::PinStateConflict {
73            pin_state: package.pinned(),
74            rock: package.to_package(),
75        });
76    }
77
78    let new_root = tree.root_for(&package);
79
80    std::fs::create_dir_all(&new_root)?;
81
82    fs_extra::move_items(&items, new_root, &CopyOptions::new())?;
83
84    lockfile.map_then_flush(|lockfile| {
85        lockfile.remove(&old_package);
86        lockfile.add_entrypoint(&package);
87
88        Ok::<_, io::Error>(())
89    })?;
90
91    Ok(())
92}