git_bug/replica/entity/lamport/
repository.rs

1// git-bug-rs - A rust library for interfacing with git-bug repositories
2//
3// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
4// SPDX-License-Identifier: GPL-3.0-or-later
5//
6// This file is part of git-bug-rs/git-gub.
7//
8// You should have received a copy of the License along with this program.
9// If not, see <https://www.gnu.org/licenses/agpl.txt>.
10
11//! Lamport Clocks scoped to a git [`repository`][`gix::Repository`].
12
13use std::{fs, path::PathBuf};
14
15use gix::Repository;
16
17use super::persistent::PersistedClock;
18
19#[allow(missing_docs)]
20pub mod get {
21    use crate::replica::entity::lamport;
22
23    #[derive(Debug, thiserror::Error)]
24    pub enum Error {
25        #[error("Failed to read a clock named '{name}' from repo, because of {error}")]
26        LoadClock {
27            name: String,
28            error: lamport::persistent::write::Error,
29        },
30
31        #[error("Failed to created a clock named '{name}' from repo, because of {error}")]
32        CreateClock {
33            name: String,
34            error: lamport::persistent::write::Error,
35        },
36
37        #[error("Failed to create the directory for the repository clocks at the well known path.")]
38        ClockPathCreate(std::io::Error),
39    }
40}
41
42const SUBDIR_NAME: &str = "git-bug/clocks";
43
44fn clock_path(repo: &Repository, name: &str) -> PathBuf {
45    repo.path().join(SUBDIR_NAME).join(name)
46}
47
48/// Get a [`clock`][`PersistedClock`], which is scoped to this repository by
49/// name. If the clock does not exist, [`None`][`Option::None`] is returned.
50///
51/// # Errors
52/// If the [`clock`][`PersistedClock`] could not be loaded from disk.
53pub fn get_clock(repo: &Repository, name: &str) -> Result<Option<PersistedClock>, get::Error> {
54    let clock_path = clock_path(repo, name);
55
56    if clock_path.exists() {
57        let clock = PersistedClock::from_path(clock_path).map_err(|err| get::Error::LoadClock {
58            name: name.to_owned(),
59            error: err,
60        })?;
61
62        Ok(Some(clock))
63    } else {
64        Ok(None)
65    }
66}
67
68/// Get a [`clock`][`PersistedClock`] from a repository by name.
69/// Instead of [`get_clock`], this function will create a new clock if it was
70/// not previously initialized.
71///
72/// # Errors
73/// If the [`clock`][`PersistedClock`] could not be created or loaded from disk.
74// Only expects.
75#[allow(clippy::missing_panics_doc)]
76pub fn get_or_init_clock(repo: &Repository, name: &str) -> Result<PersistedClock, get::Error> {
77    if let Some(maybe_clock) = get_clock(repo, name)? {
78        Ok(maybe_clock)
79    } else {
80        let clock_path = clock_path(repo, name);
81
82        fs::create_dir_all(clock_path.parent().expect("Always has one"))
83            .map_err(get::Error::ClockPathCreate)?;
84
85        let new_clock = PersistedClock::new(clock_path).map_err(|err| get::Error::CreateClock {
86            name: name.to_owned(),
87            error: err,
88        })?;
89
90        Ok(new_clock)
91    }
92}