use leo_errors::Result;
use serde::{Deserialize, Serialize};
use std::path::Path;
pub const LOCK_FILENAME: &str = "leo.lock";
const LOCK_VERSION: u32 = 1;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GitLockEntry {
pub name: String,
pub git: String,
pub reference: String,
pub commit: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Lock {
version: u32,
#[serde(default)]
git: Vec<GitLockEntry>,
}
impl Default for Lock {
fn default() -> Self {
Lock { version: LOCK_VERSION, git: Vec::new() }
}
}
impl Lock {
pub fn read(dir: &Path) -> Self {
let path = dir.join(LOCK_FILENAME);
let Ok(contents) = std::fs::read_to_string(&path) else {
return Lock::default();
};
match serde_json::from_str::<Lock>(&contents) {
Ok(lock) if lock.version == LOCK_VERSION => lock,
Ok(lock) => {
tracing::warn!(
"⚠️ Ignoring `{}`: unsupported lock version {} (expected {LOCK_VERSION}). It will be regenerated.",
path.display(),
lock.version,
);
Lock::default()
}
Err(err) => {
tracing::warn!("⚠️ Ignoring malformed `{}` ({err}). It will be regenerated.", path.display());
Lock::default()
}
}
}
pub fn commit_for(&self, name: &str, git: &str, reference: &str) -> Option<&str> {
self.git.iter().find(|e| e.name == name && e.git == git && e.reference == reference).map(|e| e.commit.as_str())
}
pub fn commit_for_source(&self, git: &str, reference: &str) -> Option<&str> {
self.git.iter().find(|e| e.git == git && e.reference == reference).map(|e| e.commit.as_str())
}
pub fn record(&mut self, name: String, git: String, reference: String, commit: String) {
self.git.retain(|e| !(e.name == name && e.git == git && e.reference == reference));
self.git.push(GitLockEntry { name, git, reference, commit });
}
pub fn carry_over(&mut self, old: &Lock, mut keep: impl FnMut(&GitLockEntry) -> bool) {
for entry in &old.git {
if self.commit_for(&entry.name, &entry.git, &entry.reference).is_none() && keep(entry) {
self.git.push(entry.clone());
}
}
}
pub fn remove_name(&mut self, name: &str) {
self.git.retain(|e| e.name != name);
}
pub fn write(&mut self, dir: &Path) -> Result<()> {
let path = dir.join(LOCK_FILENAME);
if self.is_empty() {
if path.exists() {
std::fs::remove_file(&path).map_err(|err| crate::errors::failed_to_write_lock(path.display(), err))?;
}
return Ok(());
}
self.git.sort_by(|a, b| (&a.name, &a.git, &a.reference).cmp(&(&b.name, &b.git, &b.reference)));
let mut contents = serde_json::to_string_pretty(self)
.map_err(|err| crate::errors::failed_to_serialize_lock(path.display(), err))?;
contents.push('\n');
std::fs::write(&path, contents).map_err(|err| crate::errors::failed_to_write_lock(path.display(), err))?;
Ok(())
}
pub fn is_empty(&self) -> bool {
self.git.is_empty()
}
}