1use leo_errors::Result;
23
24use serde::{Deserialize, Serialize};
25use std::path::Path;
26
27pub const LOCK_FILENAME: &str = "leo.lock";
29
30const LOCK_VERSION: u32 = 1;
31
32#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34pub struct GitLockEntry {
35 pub name: String,
36 pub git: String,
37 pub reference: String,
39 pub commit: String,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct Lock {
45 version: u32,
46 #[serde(default)]
47 git: Vec<GitLockEntry>,
48}
49
50impl Default for Lock {
51 fn default() -> Self {
52 Lock { version: LOCK_VERSION, git: Vec::new() }
53 }
54}
55
56impl Lock {
57 pub fn read(dir: &Path) -> Self {
60 let path = dir.join(LOCK_FILENAME);
61 let Ok(contents) = std::fs::read_to_string(&path) else {
62 return Lock::default();
63 };
64 match serde_json::from_str::<Lock>(&contents) {
65 Ok(lock) if lock.version == LOCK_VERSION => lock,
66 Ok(lock) => {
67 tracing::warn!(
68 "⚠️ Ignoring `{}`: unsupported lock version {} (expected {LOCK_VERSION}). It will be regenerated.",
69 path.display(),
70 lock.version,
71 );
72 Lock::default()
73 }
74 Err(err) => {
75 tracing::warn!("⚠️ Ignoring malformed `{}` ({err}). It will be regenerated.", path.display());
76 Lock::default()
77 }
78 }
79 }
80
81 pub fn commit_for(&self, name: &str, git: &str, reference: &str) -> Option<&str> {
83 self.git.iter().find(|e| e.name == name && e.git == git && e.reference == reference).map(|e| e.commit.as_str())
84 }
85
86 pub fn commit_for_source(&self, git: &str, reference: &str) -> Option<&str> {
89 self.git.iter().find(|e| e.git == git && e.reference == reference).map(|e| e.commit.as_str())
90 }
91
92 pub fn record(&mut self, name: String, git: String, reference: String, commit: String) {
96 self.git.retain(|e| !(e.name == name && e.git == git && e.reference == reference));
97 self.git.push(GitLockEntry { name, git, reference, commit });
98 }
99
100 pub fn carry_over(&mut self, old: &Lock, mut keep: impl FnMut(&GitLockEntry) -> bool) {
102 for entry in &old.git {
103 if self.commit_for(&entry.name, &entry.git, &entry.reference).is_none() && keep(entry) {
104 self.git.push(entry.clone());
105 }
106 }
107 }
108
109 pub fn remove_name(&mut self, name: &str) {
111 self.git.retain(|e| e.name != name);
112 }
113
114 pub fn write(&mut self, dir: &Path) -> Result<()> {
117 let path = dir.join(LOCK_FILENAME);
118 if self.is_empty() {
119 if path.exists() {
120 std::fs::remove_file(&path).map_err(|err| crate::errors::failed_to_write_lock(path.display(), err))?;
121 }
122 return Ok(());
123 }
124 self.git.sort_by(|a, b| (&a.name, &a.git, &a.reference).cmp(&(&b.name, &b.git, &b.reference)));
125
126 let mut contents = serde_json::to_string_pretty(self)
127 .map_err(|err| crate::errors::failed_to_serialize_lock(path.display(), err))?;
128 contents.push('\n');
129 std::fs::write(&path, contents).map_err(|err| crate::errors::failed_to_write_lock(path.display(), err))?;
130 Ok(())
131 }
132
133 pub fn is_empty(&self) -> bool {
135 self.git.is_empty()
136 }
137}