use eyre::{Result, bail};
use serde::Deserialize;
use super::layout::MARKER_PATH;
use crate::system::history::shadow::HistoryRepo;
pub(crate) const FORMAT: u32 = 1;
#[derive(Deserialize)]
struct Marker {
format: u32,
}
pub(crate) fn marker_content() -> String {
format!(
"# This repository is a mise setup repository (`mise bootstrap dotfiles origin set`).\n# Do not edit: mise reads this to recognize the layout it publishes.\nformat = {FORMAT}\n"
)
}
pub(crate) fn parse_marker(bytes: &[u8]) -> Result<u32> {
let text = String::from_utf8_lossy(bytes);
let marker: Marker = toml::from_str(&text)?;
Ok(marker.format)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum RepoState {
Empty,
Marked(u32),
Unmarked,
}
impl RepoState {
pub(crate) fn check(&self) -> Result<()> {
if let Self::Marked(format) = self
&& *format != FORMAT
{
bail!(
"this setup repository uses format {format}; upgrade mise (this version supports format {FORMAT})"
);
}
Ok(())
}
}
pub(crate) fn detect(repo: &HistoryRepo, upstream: Option<&str>) -> Result<RepoState> {
let Some(commit) = upstream else {
return Ok(RepoState::Empty);
};
match repo.object_at(commit, MARKER_PATH)? {
Some((_, oid)) => Ok(RepoState::Marked(parse_marker(
&repo.cat_object_bounded(&oid, 64 * 1024)?,
)?)),
None => Ok(RepoState::Unmarked),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn marker_round_trips() {
assert_eq!(parse_marker(marker_content().as_bytes()).unwrap(), FORMAT);
assert!(RepoState::Marked(FORMAT).check().is_ok());
let err = RepoState::Marked(99).check().unwrap_err().to_string();
assert!(err.contains("format 99"), "{err}");
assert!(err.contains("upgrade mise"), "{err}");
assert!(RepoState::Unmarked.check().is_ok());
}
}