Skip to main content

flake_edit/app/commands/
pin.rs

1//! `flake-edit pin` and `flake-edit unpin`: freeze or release a
2//! specific revision on an input.
3//!
4//! `pin` reads `flake.lock` to default the target rev when the user
5//! does not supply one. `unpin`'s interactive picker filters to
6//! inputs whose URL already carries a `ref_or_rev`.
7
8use nix_uri::FlakeRef;
9
10use crate::edit::{FlakeEdit, sorted_input_ids};
11use crate::follows::AttrPath;
12
13use super::super::editor::Editor;
14use super::super::state::AppState;
15use super::{Error, Result, interactive_single_select, load_flake_lock, updater};
16
17fn lock_path_display(state: &AppState) -> std::path::PathBuf {
18    state
19        .lock_file
20        .clone()
21        .unwrap_or_else(|| std::path::PathBuf::from("flake.lock"))
22}
23
24pub fn pin(
25    editor: &Editor,
26    flake_edit: &mut FlakeEdit,
27    state: &AppState,
28    id: Option<String>,
29    rev: Option<String>,
30) -> Result<()> {
31    let inputs = flake_edit.list().clone();
32    let input_ids = sorted_input_ids(&inputs)
33        .into_iter()
34        .cloned()
35        .collect::<Vec<_>>();
36
37    if let Some(id) = id {
38        let lock = load_flake_lock(state).map_err(|source| Error::LockFile {
39            path: lock_path_display(state),
40            source,
41        })?;
42        let target_rev = if let Some(rev) = rev {
43            rev
44        } else {
45            let path = AttrPath::parse(&id).map_err(|source| Error::InvalidInputId {
46                id: id.clone(),
47                source,
48            })?;
49            lock.rev_for(&path)?
50        };
51        let mut updater = updater(editor, inputs);
52        updater
53            .pin_input_to_ref(&id, &target_rev)
54            .map_err(|id| Error::InputNotPinnable { id })?;
55        let change = updater.get_changes();
56        editor.apply_or_diff(&change, state)?;
57        if !state.diff {
58            println!("Pinned input: {} to {}", id, target_rev);
59        }
60    } else if state.interactive {
61        if input_ids.is_empty() {
62            return Err(Error::NoInputs);
63        }
64        let lock = load_flake_lock(state).map_err(|source| Error::LockFile {
65            path: lock_path_display(state),
66            source,
67        })?;
68
69        interactive_single_select(
70            editor,
71            state,
72            "Pin",
73            "Select input",
74            input_ids,
75            |id| {
76                let path = AttrPath::parse(id).map_err(|source| Error::InvalidInputId {
77                    id: id.to_string(),
78                    source,
79                })?;
80                let target_rev = lock.rev_for(&path)?;
81                let mut updater = updater(editor, inputs.clone());
82                updater
83                    .pin_input_to_ref(id, &target_rev)
84                    .map_err(|id| Error::InputNotPinnable { id })?;
85                Ok((updater.get_changes(), target_rev))
86            },
87            |id, target_rev| println!("Pinned input: {} to {}", id, target_rev),
88        )?;
89    } else {
90        return Err(Error::NoId);
91    }
92
93    Ok(())
94}
95
96pub fn unpin(
97    editor: &Editor,
98    flake_edit: &mut FlakeEdit,
99    state: &AppState,
100    id: Option<String>,
101) -> Result<()> {
102    let inputs = flake_edit.list().clone();
103    let input_ids = sorted_input_ids(&inputs)
104        .into_iter()
105        .cloned()
106        .collect::<Vec<_>>();
107
108    if let Some(id) = id {
109        let mut updater = updater(editor, inputs);
110        updater
111            .unpin_input(&id)
112            .map_err(|id| Error::InputNotPinnable { id })?;
113        let change = updater.get_changes();
114        editor.apply_or_diff(&change, state)?;
115        if !state.diff {
116            println!("Unpinned input: {}", id);
117        }
118    } else if state.interactive {
119        let pinned_ids: Vec<String> = input_ids
120            .into_iter()
121            .filter(|id| {
122                inputs[id]
123                    .url()
124                    .parse::<FlakeRef>()
125                    .is_ok_and(|f| f.ref_kind() != nix_uri::RefKind::None)
126            })
127            .collect();
128
129        if pinned_ids.is_empty() {
130            return Err(Error::NoInputs);
131        }
132
133        interactive_single_select(
134            editor,
135            state,
136            "Unpin",
137            "Select pinned input",
138            pinned_ids,
139            |id| {
140                let mut updater = updater(editor, inputs.clone());
141                updater
142                    .unpin_input(id)
143                    .map_err(|id| Error::InputNotPinnable { id })?;
144                Ok((updater.get_changes(), ()))
145            },
146            |id, ()| println!("Unpinned input: {}", id),
147        )?;
148    } else {
149        return Err(Error::NoId);
150    }
151
152    Ok(())
153}