lux_lib/operations/
pin.rs1use std::io;
2
3use crate::{
4 lockfile::{FlushLockfileError, LocalPackageId, PinnedState},
5 package::PackageSpec,
6 tree::{InstallTree, Tree, TreeError},
7};
8use fs_extra::dir::CopyOptions;
9use itertools::Itertools;
10use miette::Diagnostic;
11use thiserror::Error;
12
13#[derive(Error, Debug, Diagnostic)]
16pub enum PinError {
17 #[error("package with ID {0} not found in the 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 #[diagnostic(transparent)]
31 FlushLockfile(#[from] FlushLockfileError),
32 #[error(transparent)]
33 #[diagnostic(transparent)]
34 Tree(#[from] TreeError),
35 #[error("failed to move old package: {0}")]
36 MoveItemsFailure(#[from] fs_extra::error::Error),
37 #[error("cannot change pin state of {rock}, because it is not an entrypoint")]
38 NotAnEntrypoint { rock: PackageSpec },
39 #[error("error reading directory {0}:\n{1}")]
40 ReadDir(String, io::Error),
41 #[error("error creating directory {0}:\n{1}")]
42 CreateDir(String, io::Error),
43}
44
45pub fn set_pinned_state(
46 package_id: &LocalPackageId,
47 tree: &Tree,
48 pin: PinnedState,
49) -> Result<(), PinError> {
50 let lockfile = tree.lockfile()?;
51 let mut package = lockfile
52 .get(package_id)
53 .ok_or_else(|| PinError::PackageNotFound(package_id.clone()))?
54 .clone();
55
56 if !lockfile.is_entrypoint(&package.id()) {
57 return Err(PinError::NotAnEntrypoint {
58 rock: package.to_package(),
59 });
60 }
61
62 if pin == package.pinned() {
63 return Err(PinError::PinStateUnchanged {
64 pin_state: package.pinned(),
65 rock: package.to_package(),
66 });
67 }
68
69 let old_package = package.clone();
70 let package_root = tree.root_for(&package);
71 let items = std::fs::read_dir(&package_root)
72 .map_err(|err| PinError::ReadDir(package_root.to_string_lossy().to_string(), err))?
73 .filter_map(Result::ok)
74 .map(|dir| dir.path())
75 .collect_vec();
76
77 package.spec.pinned = pin;
78
79 if lockfile.get(&package.id()).is_some() {
80 return Err(PinError::PinStateConflict {
81 pin_state: package.pinned(),
82 rock: package.to_package(),
83 });
84 }
85
86 let new_root = tree.root_for(&package);
87
88 std::fs::create_dir_all(&new_root)
89 .map_err(|err| PinError::CreateDir(new_root.to_string_lossy().to_string(), err))?;
90
91 fs_extra::move_items(&items, new_root, &CopyOptions::new())?;
92
93 lockfile.map_then_flush(|lockfile| {
94 lockfile.remove(&old_package);
95 lockfile.add_entrypoint(&package);
96
97 Ok::<_, io::Error>(())
98 })?;
99
100 Ok(())
101}