Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement freezing and thawing of pins #78

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,15 @@ OPTIONS:

SUBCOMMANDS:
add Adds a new pin entry
freeze Freeze a pin entry
help Prints this message or the help of the given subcommand(s)
import-flake Try to import entries from flake.lock
import-niv Try to import entries from Niv
init Intializes the npins directory. Running this multiple times will restore/upgrade the
`default.nix` and never touch your sources.json
remove Removes one pin entry
show Lists the current pin entries
thaw Thaw a pin entry
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get that logically freeze <-> thaw makes sense but from a UX perspective I would prefer to have a more explicit indication that those two commands belong together.

Hence I'd prefer freeze and unfreeze - WDYT @andir ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also prefer unfreeze, with an optional alias to thaw for those who find that more intuitive

update Updates all or the given pin to the latest version
upgrade Upgrade the sources.json and default.nix to the latest format version. This may occasionally
break Nix evaluation!
Expand Down
62 changes: 62 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,13 @@ pub struct ImportFlakeOpts {
pub name: Option<String>,
}

#[derive(Debug, StructOpt)]
pub struct FreezeOpts {
/// Names of the pin(s)
#[structopt(required = true)]
pub names: Vec<String>,
}

#[derive(Debug, StructOpt)]
pub enum Command {
/// Intializes the npins directory. Running this multiple times will restore/upgrade the
Expand Down Expand Up @@ -414,6 +421,12 @@ pub enum Command {

/// Try to import entries from flake.lock
ImportFlake(ImportFlakeOpts),

/// Freeze a pin entry
Freeze(FreezeOpts),

/// Thaw a pin entry
Thaw(FreezeOpts),
}

use structopt::clap::AppSettings;
Expand Down Expand Up @@ -589,6 +602,11 @@ impl Opts {

if opts.names.is_empty() {
for (name, pin) in pins.pins.iter_mut() {
if pin.is_frozen() {
log::info!("Skipping pin {} as it is frozen.", name);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit for consistency:

Suggested change
log::info!("Skipping pin {} as it is frozen.", name);
log::info!("Skipping pin '{}' as it is frozen.", name);

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was planning on doing this for the entire code base. We've a few cases where that isn't the case.

continue;
}

log::info!("Updating '{}' …", name);
self.update_one(pin, strategy, true).await?;
}
Expand All @@ -597,6 +615,11 @@ impl Opts {
match pins.pins.get_mut(name) {
None => return Err(anyhow::anyhow!("Could not find a pin for '{}'.", name)),
Some(pin) => {
if pin.is_frozen() {
log::info!("Skipping {} as it is frozen.", name);
continue;
}

log::info!("Updating '{}' …", name);
self.update_one(pin, strategy, true).await?;
},
Expand Down Expand Up @@ -663,6 +686,43 @@ impl Opts {
Ok(())
}

async fn freeze(&self, o: &FreezeOpts) -> Result<()> {
let mut pins = self.read_pins()?;

for name in o.names.iter() {
let pin = match pins.pins.get_mut(name) {
None => return Err(anyhow::anyhow!("Couldn't find the pin {} to freeze.", name)),
Some(pin) => pin,
};

pin.freeze();
log::info!("Froze pin {}", name);
}

self.write_pins(&pins)?;

Ok(())
}

async fn thaw(&self, o: &FreezeOpts) -> Result<()> {
let mut pins = self.read_pins()?;

for name in o.names.iter() {
let pin = match pins.pins.get_mut(name) {
None => return Err(anyhow::anyhow!("Couldn't find the pin {} to thaw.", name)),
Some(pin) => pin,
};

pin.thaw();

log::info!("Thawed pin {}", name);
}

self.write_pins(&pins)?;

Ok(())
}

async fn import_niv(&self, o: &ImportOpts) -> Result<()> {
let mut pins = self.read_pins()?;

Expand Down Expand Up @@ -829,6 +889,8 @@ impl Opts {
Command::Remove(r) => self.remove(r)?,
Command::ImportNiv(o) => self.import_niv(o).await?,
Command::ImportFlake(o) => self.import_flake(o).await?,
Command::Freeze(o) => self.freeze(o).await?,
Command::Thaw(o) => self.thaw(o).await?,
};

Ok(())
Expand Down
102 changes: 98 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,14 +123,16 @@ macro_rules! mkPin {
version: Option<<$input_name as Updatable>::Version>,
#[serde(flatten)]
hashes: Option<<$input_name as Updatable>::Hashes>,
#[serde(default)]
frozen: Frozen,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why doesn't our CI/rustfmt complain on this line? (Mixed usage of tabs and spaces)

}
),*
}

impl Pin {
/* Constructors */
$(fn $lower_name(input: $input_name, version: Option<<$input_name as Updatable>::Version>) -> Self {
Self::$name { input, version, hashes: None }
Self::$name { input, version, hashes: None, frozen: Frozen::default() }
})*

/* If an error is returned, `self` remains unchanged */
Expand All @@ -149,7 +151,7 @@ macro_rules! mkPin {
*/
async fn fetch(&mut self) -> Result<Vec<diff::DiffEntry>> {
Ok(match self {
$(Self::$name { input, version, hashes } => {
$(Self::$name { input, version, hashes, .. } => {
let version = version.as_ref()
.ok_or_else(|| anyhow::format_err!("No version information available, call `update` first or manually set one"))?;
/* Use very explicit syntax to force the correct types and get good compile errors */
Expand Down Expand Up @@ -177,16 +179,39 @@ macro_rules! mkPin {
$(Self::$name { ..} => $human_name ),*
}
}


/// Thaw a pin
pub fn thaw(&mut self) {
match self {
$(Self::$name { ref mut frozen, .. } => frozen.thaw()),*
}
}

/// Freeze a pin
pub fn freeze(&mut self) {
match self {
$(Self::$name { ref mut frozen, .. } => frozen.freeze()),*
}
}

/// Is frozen
pub fn is_frozen(&self) -> bool {
match self {
$(Self::$name { frozen, .. } => frozen.is_frozen()),*
}
}
}

impl std::fmt::Display for Pin {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
$(Self::$name { input, version, hashes } => {
$(Self::$name { input, version, hashes, frozen } => {
/* Concat all properties and then print them */
let properties = input.properties().into_iter()
.chain(version.iter().flat_map(Diff::properties))
.chain(hashes.iter().flat_map(Diff::properties));
.chain(hashes.iter().flat_map(Diff::properties))
.chain(frozen.properties());
for (key, value) in properties {
writeln!(fmt, " {}: {}", key, value)?;
}
Expand Down Expand Up @@ -259,6 +284,40 @@ impl diff::Diff for GenericVersion {
}
}

/// The Frozen field in a Pin
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct Frozen(bool);

impl Frozen {
fn new(value: bool) -> Self {
Frozen(value)
}

fn freeze(&mut self) {
self.0 = true;
}

fn thaw(&mut self) {
self.0 = false;
}

fn is_frozen(&self) -> bool {
self.0
}
}

impl diff::Diff for Frozen {
fn properties(&self) -> Vec<(String, String)> {
vec![("frozen".into(), self.0.to_string())]
}
}

impl std::default::Default for Frozen {
fn default() -> Self {
Frozen(false)
}
}

/// An URL and its hash
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct GenericUrlHashes {
Expand Down Expand Up @@ -287,3 +346,38 @@ async fn main() -> Result<()> {
opts.run().await?;
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;

#[cfg(test)]
fn test_frozen() {
assert!(!Frozen::default().is_frozen());
assert!(!{
let mut x = Frozen::default();
x.thaw();
x
}
.is_frozen());
assert!({
let mut x = Frozen::default();
x.freeze();
x
}
.is_frozen());
assert!(Frozen::new(true).is_frozen());
assert!({
let mut x = Frozen::new(true);
x.freeze();
x
}
.is_frozen());
assert!(!{
let mut x = Frozen::new(true);
x.thaw();
x
}
.is_frozen());
}
}
46 changes: 25 additions & 21 deletions src/versions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,27 +277,31 @@ mod test {
pins,
NixPins {
pins: btreemap![
"nixos-mailserver".into() => Pin::Git {
input: git::GitPin::git("https://gitlab.com/simple-nixos-mailserver/nixos-mailserver.git".parse().unwrap(), "nixos-21.11".into()),
version: Some(git::GitRevision { revision: "6e3a7b2ea6f0d68b82027b988aa25d3423787303".into() }),
hashes: Some(git::OptionalUrlHashes { url: None, hash: "1i56llz037x416bw698v8j6arvv622qc0vsycd20lx3yx8n77n44".into() } ),
},
"nixpkgs".into() => Pin::Git {
input: git::GitPin::github("nixos", "nixpkgs", "nixpkgs-unstable".into()),
version: Some(git::GitRevision { revision: "5c37ad87222cfc1ec36d6cd1364514a9efc2f7f2".into() }),
hashes: Some(git::OptionalUrlHashes { url: Some("https://github.com/nixos/nixpkgs/archive/5c37ad87222cfc1ec36d6cd1364514a9efc2f7f2.tar.gz".parse().unwrap()), hash: "1r74afnalgcbpv7b9sbdfbnx1kfj0kp1yfa60bbbv27n36vqdhbb".into() }),
},
"streamlit".into() => Pin::PyPi {
input: pypi::Pin { name: "streamlit".into(), version_upper_bound: None },
version: Some(GenericVersion { version: "1.3.1".into() }),
hashes: Some(GenericUrlHashes { url: "https://files.pythonhosted.org/packages/c3/9d/ac871992617220442832af12c3808716f4349ab05ff939d695fe8b542f00/streamlit-1.3.1.tar.gz".parse().unwrap(), hash: "adec7935c9cf774b9115b2456cf2f48c4f49b9f67159a97db0fe228357c1afdf".into() } )
},
"youtube-dl".into() => Pin::GitRelease {
input: git::GitReleasePin::github("ytdl-org", "youtube-dl", false, None, None),
version: Some(GenericVersion { version: "youtube-dl 2021.12.17".into() }),
hashes: None,
}
],
"nixos-mailserver".into() => Pin::Git {
input: git::GitPin::git("https://gitlab.com/simple-nixos-mailserver/nixos-mailserver.git".parse().unwrap(), "nixos-21.11".into()),
version: Some(git::GitRevision { revision: "6e3a7b2ea6f0d68b82027b988aa25d3423787303".into() }),
hashes: Some(git::OptionalUrlHashes { url: None, hash: "1i56llz037x416bw698v8j6arvv622qc0vsycd20lx3yx8n77n44".into() } ),
frozen: Frozen::default(),
},
"nixpkgs".into() => Pin::Git {
input: git::GitPin::github("nixos", "nixpkgs", "nixpkgs-unstable".into()),
version: Some(git::GitRevision { revision: "5c37ad87222cfc1ec36d6cd1364514a9efc2f7f2".into() }),
hashes: Some(git::OptionalUrlHashes { url: Some("https://github.com/nixos/nixpkgs/archive/5c37ad87222cfc1ec36d6cd1364514a9efc2f7f2.tar.gz".parse().unwrap()), hash: "1r74afnalgcbpv7b9sbdfbnx1kfj0kp1yfa60bbbv27n36vqdhbb".into() }),
frozen: Frozen::default(),
},
"streamlit".into() => Pin::PyPi {
input: pypi::Pin { name: "streamlit".into(), version_upper_bound: None },
version: Some(GenericVersion { version: "1.3.1".into() }),
hashes: Some(GenericUrlHashes { url: "https://files.pythonhosted.org/packages/c3/9d/ac871992617220442832af12c3808716f4349ab05ff939d695fe8b542f00/streamlit-1.3.1.tar.gz".parse().unwrap(), hash: "adec7935c9cf774b9115b2456cf2f48c4f49b9f67159a97db0fe228357c1afdf".into() } ),
frozen: Frozen::default(),
},
"youtube-dl".into() => Pin::GitRelease {
input: git::GitReleasePin::github("ytdl-org", "youtube-dl", false, None, None),
version: Some(GenericVersion { version: "youtube-dl 2021.12.17".into() }),
hashes: None,
frozen: Frozen::default(),
}
],
}
);
}
Expand Down