Save repos
This commit is contained in:
parent
d08922e753
commit
357de970ee
10 changed files with 252 additions and 47 deletions
57
Cargo.lock
generated
57
Cargo.lock
generated
|
|
@ -1316,6 +1316,7 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"directories",
|
"directories",
|
||||||
"git2",
|
"git2",
|
||||||
|
"jiff",
|
||||||
"rand 0.9.1",
|
"rand 0.9.1",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
@ -1960,6 +1961,47 @@ dependencies = [
|
||||||
"system-deps",
|
"system-deps",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jiff"
|
||||||
|
version = "0.2.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "27e77966151130221b079bcec80f1f34a9e414fa489d99152a201c07fd2182bc"
|
||||||
|
dependencies = [
|
||||||
|
"jiff-static",
|
||||||
|
"jiff-tzdb-platform",
|
||||||
|
"log",
|
||||||
|
"portable-atomic",
|
||||||
|
"portable-atomic-util",
|
||||||
|
"serde",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jiff-static"
|
||||||
|
version = "0.2.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "97265751f8a9a4228476f2fc17874a9e7e70e96b893368e42619880fe143b48a"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.101",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jiff-tzdb"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jiff-tzdb-platform"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8"
|
||||||
|
dependencies = [
|
||||||
|
"jiff-tzdb",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jni"
|
name = "jni"
|
||||||
version = "0.21.1"
|
version = "0.21.1"
|
||||||
|
|
@ -2913,6 +2955,21 @@ dependencies = [
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "portable-atomic"
|
||||||
|
version = "1.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "portable-atomic-util"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
|
||||||
|
dependencies = [
|
||||||
|
"portable-atomic",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "powerfmt"
|
name = "powerfmt"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ clap = { version = "4.5.37", features = ["derive", "deprecated"] }
|
||||||
directories = "6.0.0"
|
directories = "6.0.0"
|
||||||
gdn = { path = "gdn" }
|
gdn = { path = "gdn" }
|
||||||
git2 = { version = "0.20.1", features = ["vendored-libgit2", "vendored-openssl"] }
|
git2 = { version = "0.20.1", features = ["vendored-libgit2", "vendored-openssl"] }
|
||||||
|
jiff = "0.2.11"
|
||||||
rand = "0.9.1"
|
rand = "0.9.1"
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
serde_json = "1.0.140"
|
serde_json = "1.0.140"
|
||||||
|
|
|
||||||
|
|
@ -14,18 +14,17 @@ impl Command {
|
||||||
println!("No repo selected");
|
println!("No repo selected");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
let repo = gdn::data::load_repo(&data, selected)?;
|
let mut repo = gdn::data::load_repo(&data, selected)?;
|
||||||
let mut notes = repo.notes.into_iter().collect::<Vec<_>>();
|
repo.notes.sort_unstable_by_key(|it| it.id);
|
||||||
notes.sort_unstable_by_key(|(id, _)| *id);
|
|
||||||
|
|
||||||
if notes.is_empty() {
|
if repo.notes.is_empty() {
|
||||||
println!("No notes");
|
println!("No notes");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (id, note) in notes {
|
for note in repo.notes {
|
||||||
if note.children.is_empty() {
|
if note.children.is_empty() {
|
||||||
println!("{id}: {}", note.text);
|
println!("{}: {}", note.id, note.text);
|
||||||
} else {
|
} else {
|
||||||
let children = note
|
let children = note
|
||||||
.children
|
.children
|
||||||
|
|
@ -33,7 +32,7 @@ impl Command {
|
||||||
.map(|it| it.to_string())
|
.map(|it| it.to_string())
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(", ");
|
.join(", ");
|
||||||
println!("{id}: {} [{children}]", note.text);
|
println!("{}: {} [{children}]", note.id, note.text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ edition = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
directories = { workspace = true }
|
directories = { workspace = true }
|
||||||
git2 = { workspace = true }
|
git2 = { workspace = true }
|
||||||
|
jiff = { workspace = true }
|
||||||
rand = { workspace = true }
|
rand = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ pub use crate::repo::VERSION as REPO_VERSION;
|
||||||
pub use self::{
|
pub use self::{
|
||||||
datadir::{LockedDataDir, UnlockedDataDir},
|
datadir::{LockedDataDir, UnlockedDataDir},
|
||||||
v1::{
|
v1::{
|
||||||
State, VERSION, add_repo, load_repo, load_repo_version, load_state, remove_repo,
|
State, VERSION, add_repo, load_repo, load_repo_version, load_state, remove_repo, save_repo,
|
||||||
select_repo, tidy,
|
select_repo, tidy,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use std::{collections::HashMap, fs, path::PathBuf};
|
use std::{collections::HashMap, fs, path::PathBuf};
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
use git2::Oid;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -82,6 +83,10 @@ pub fn load_repo(dir: &UnlockedDataDir, id: RepoId) -> anyhow::Result<Repo> {
|
||||||
repo::load(&repo_dir(dir, id))
|
repo::load(&repo_dir(dir, id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn save_repo(dir: &LockedDataDir, id: RepoId, repo: Repo) -> anyhow::Result<Oid> {
|
||||||
|
repo::save(&repo_dir(dir, id), repo)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_repo(dir: &LockedDataDir, name: String) -> anyhow::Result<RepoId> {
|
pub fn add_repo(dir: &LockedDataDir, name: String) -> anyhow::Result<RepoId> {
|
||||||
let id = RepoId::new();
|
let id = RepoId::new();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use std::{fmt, ops::Shl, str::FromStr, time::SystemTime};
|
use std::{fmt, str::FromStr};
|
||||||
|
|
||||||
|
use jiff::{Timestamp, Zoned, tz::TimeZone};
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
|
||||||
/// A timestamp- and randomness-based id.
|
/// A timestamp- and randomness-based id.
|
||||||
|
|
@ -13,19 +14,18 @@ struct TimestampId(u64);
|
||||||
|
|
||||||
impl TimestampId {
|
impl TimestampId {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
let secs = SystemTime::now()
|
let secs: u64 = Timestamp::now()
|
||||||
.duration_since(SystemTime::UNIX_EPOCH)
|
.as_second()
|
||||||
.expect("duration is positive")
|
.try_into()
|
||||||
.as_secs();
|
.expect("timestamp out of range");
|
||||||
|
assert!(secs < 0x000000FF_FFFFFFFF_u64, "timestamp out of range");
|
||||||
|
let random = rand::random::<u64>() & 0x00000000_00FFFFFF_u64;
|
||||||
|
Self(secs << (3 * 8) | random)
|
||||||
|
}
|
||||||
|
|
||||||
let random = rand::random::<u64>();
|
fn timestamp(self) -> Timestamp {
|
||||||
|
let secs = self.0 >> (3 * 8);
|
||||||
// Zeroing the last three bytes just in case. They should already be
|
Timestamp::from_second(secs as i64).expect("timestamp out of range")
|
||||||
// zero under normal circumstances.
|
|
||||||
let first_part = secs.shl(3 * 8) & 0xFFFFFFFF_FF000000_u64;
|
|
||||||
let second_part = random & 0x00000000_00FFFFFF_u64;
|
|
||||||
|
|
||||||
Self(first_part | second_part)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,6 +62,14 @@ impl NoteId {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self(TimestampId::new())
|
Self(TimestampId::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn timestamp(self) -> Timestamp {
|
||||||
|
self.0.timestamp()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn time_utc(self) -> Zoned {
|
||||||
|
self.timestamp().to_zoned(TimeZone::UTC)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for NoteId {
|
impl fmt::Debug for NoteId {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ mod v1;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::{anyhow, bail};
|
use anyhow::{anyhow, bail};
|
||||||
use git2::{ErrorCode, Repository};
|
use git2::{Commit, ErrorCode, Oid, Reference, Repository};
|
||||||
|
use jiff::Zoned;
|
||||||
|
|
||||||
pub use self::v1::{Repo, VERSION};
|
pub use self::v1::{Repo, VERSION};
|
||||||
|
|
||||||
|
|
@ -15,18 +16,19 @@ pub fn init(path: &Path) -> anyhow::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_version(repo: &Repository) -> anyhow::Result<u32> {
|
fn read_head(repository: &Repository) -> anyhow::Result<Option<Reference<'_>>> {
|
||||||
let head = match repo.head() {
|
match repository.head() {
|
||||||
Ok(head) => head,
|
Ok(head) => Ok(Some(head)),
|
||||||
Err(error) if error.code() == ErrorCode::UnbornBranch => return Ok(0),
|
Err(error) if error.code() == ErrorCode::UnbornBranch => Ok(None),
|
||||||
Err(error) => Err(error)?,
|
Err(error) => Err(error)?,
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let object = head
|
fn read_version(repository: &Repository, commit: &Commit<'_>) -> anyhow::Result<u32> {
|
||||||
.peel_to_commit()?
|
let object = commit
|
||||||
.tree()?
|
.tree()?
|
||||||
.get_path(VERSION_FILE.as_ref())?
|
.get_path(VERSION_FILE.as_ref())?
|
||||||
.to_object(repo)?;
|
.to_object(repository)?;
|
||||||
|
|
||||||
let blob = object
|
let blob = object
|
||||||
.as_blob()
|
.as_blob()
|
||||||
|
|
@ -39,21 +41,59 @@ fn read_version(repo: &Repository) -> anyhow::Result<u32> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_version(path: &Path) -> anyhow::Result<u32> {
|
pub fn load_version(path: &Path) -> anyhow::Result<u32> {
|
||||||
let repo = Repository::open_bare(path)?;
|
let repository = Repository::open_bare(path)?;
|
||||||
let version = read_version(&repo)?;
|
let Some(head) = read_head(&repository)? else {
|
||||||
|
return Ok(v0::VERSION);
|
||||||
|
};
|
||||||
|
let commit = head.peel_to_commit()?;
|
||||||
|
let version = read_version(&repository, &commit)?;
|
||||||
Ok(version)
|
Ok(version)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load(path: &Path) -> anyhow::Result<Repo> {
|
pub fn load(path: &Path) -> anyhow::Result<Repo> {
|
||||||
let repository = Repository::open_bare(path)?;
|
let repository = Repository::open_bare(path)?;
|
||||||
let version = read_version(&repository)?;
|
let Some(head) = read_head(&repository)? else {
|
||||||
|
return Ok(v0::Repo::load().migrate());
|
||||||
|
};
|
||||||
|
let commit = head.peel_to_commit()?;
|
||||||
|
let version = read_version(&repository, &commit)?;
|
||||||
|
let tree = commit.tree()?;
|
||||||
|
|
||||||
#[expect(unused_qualifications)]
|
#[expect(unused_qualifications)]
|
||||||
let repo = match version {
|
let repo = match version {
|
||||||
v0::VERSION => v0::Repo::load().migrate(),
|
v1::VERSION => v1::Repo::load_from_tree(&repository, &tree)?.migrate(),
|
||||||
v1::VERSION => v1::Repo::load(&repository)?.migrate(),
|
|
||||||
n => bail!("invalid repo version {n}"),
|
n => bail!("invalid repo version {n}"),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(repo)
|
Ok(repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn save(path: &Path, repo: Repo) -> anyhow::Result<Oid> {
|
||||||
|
let repository = Repository::open_bare(path)?;
|
||||||
|
|
||||||
|
let tree = repo.save_to_tree(&repository)?;
|
||||||
|
|
||||||
|
let signature = repository.signature()?;
|
||||||
|
let message = Zoned::now().to_string();
|
||||||
|
|
||||||
|
// TODO Check that the repo is actually based on this commit.
|
||||||
|
let parent = match read_head(&repository)? {
|
||||||
|
None => None,
|
||||||
|
Some(parent) => Some(parent.peel_to_commit()?),
|
||||||
|
};
|
||||||
|
let parents = match &parent {
|
||||||
|
None => vec![],
|
||||||
|
Some(parent) => vec![parent],
|
||||||
|
};
|
||||||
|
|
||||||
|
let oid = repository.commit(
|
||||||
|
Some("HEAD"),
|
||||||
|
&signature,
|
||||||
|
&signature,
|
||||||
|
&message,
|
||||||
|
&tree,
|
||||||
|
&parents,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(oid)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use super::v1;
|
use super::v1;
|
||||||
|
|
||||||
pub const VERSION: u32 = 0;
|
pub const VERSION: u32 = 0;
|
||||||
|
|
@ -12,9 +10,6 @@ impl Repo {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn migrate(self) -> super::Repo {
|
pub fn migrate(self) -> super::Repo {
|
||||||
v1::Repo {
|
v1::Repo { notes: vec![] }.migrate()
|
||||||
notes: HashMap::new(),
|
|
||||||
}
|
|
||||||
.migrate()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use std::collections::HashMap;
|
use anyhow::anyhow;
|
||||||
|
use git2::{FileMode, Repository, Tree, TreeBuilder, TreeEntry, TreeWalkMode, TreeWalkResult};
|
||||||
use git2::Repository;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::ids::NoteId;
|
use crate::ids::NoteId;
|
||||||
|
|
@ -9,18 +8,118 @@ pub const VERSION: u32 = 1;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct Note {
|
pub struct Note {
|
||||||
|
pub id: NoteId,
|
||||||
pub text: String,
|
pub text: String,
|
||||||
pub children: Vec<NoteId>,
|
pub children: Vec<NoteId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Repo {
|
pub struct Repo {
|
||||||
pub notes: HashMap<NoteId, Note>,
|
pub notes: Vec<Note>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_note_to_tree(
|
||||||
|
repository: &Repository,
|
||||||
|
target: &mut TreeBuilder<'_>,
|
||||||
|
note: &Note,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let filename = format!("{}.json", note.id);
|
||||||
|
let oid = repository.blob(&serde_json::to_vec(note)?)?;
|
||||||
|
target.insert(filename, oid, FileMode::Blob.into())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_tree_to_tree(
|
||||||
|
target: &mut TreeBuilder<'_>,
|
||||||
|
tree: &TreeBuilder<'_>,
|
||||||
|
filename: String,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
if tree.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let oid = tree.write()?;
|
||||||
|
target.insert(filename, oid, FileMode::Tree.into())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_note(
|
||||||
|
repository: &Repository,
|
||||||
|
entry: &TreeEntry<'_>,
|
||||||
|
notes: &mut Vec<Note>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let object = entry.to_object(repository)?;
|
||||||
|
let content = object
|
||||||
|
.as_blob()
|
||||||
|
.ok_or(anyhow!("json file is not a blob!?"))?
|
||||||
|
.content();
|
||||||
|
let note = serde_json::from_slice(content)?;
|
||||||
|
notes.push(note);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Repo {
|
impl Repo {
|
||||||
pub fn load(repository: &Repository) -> anyhow::Result<Self> {
|
pub fn load_from_tree(repository: &Repository, tree: &Tree<'_>) -> anyhow::Result<Self> {
|
||||||
todo!()
|
let mut notes = vec![];
|
||||||
|
let mut error: Option<anyhow::Error> = None;
|
||||||
|
|
||||||
|
tree.walk(TreeWalkMode::PreOrder, |name, entry| {
|
||||||
|
if name.ends_with(".json") {
|
||||||
|
if let Err(err) = load_note(repository, entry, &mut notes) {
|
||||||
|
error = Some(err);
|
||||||
|
return TreeWalkResult::Abort;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TreeWalkResult::Ok
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if let Some(err) = error {
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self { notes })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_to_tree(mut self, repository: &Repository) -> anyhow::Result<Tree<'_>> {
|
||||||
|
self.notes.sort_unstable_by_key(|it| it.id);
|
||||||
|
|
||||||
|
let mut root_tree = repository.treebuilder(None)?;
|
||||||
|
let mut year = 0;
|
||||||
|
let mut year_tree = repository.treebuilder(None)?;
|
||||||
|
let mut month = 0;
|
||||||
|
let mut month_tree = repository.treebuilder(None)?;
|
||||||
|
let mut day = 0;
|
||||||
|
let mut day_tree = repository.treebuilder(None)?;
|
||||||
|
|
||||||
|
for note in self.notes {
|
||||||
|
let time = note.id.time_utc();
|
||||||
|
|
||||||
|
if day != time.day() || month != time.month() || year != time.year() {
|
||||||
|
add_tree_to_tree(&mut month_tree, &day_tree, format!("{day:02}"))?;
|
||||||
|
day_tree.clear()?;
|
||||||
|
day = time.day();
|
||||||
|
}
|
||||||
|
|
||||||
|
if month != time.month() || year != time.year() {
|
||||||
|
add_tree_to_tree(&mut year_tree, &month_tree, format!("{month:02}"))?;
|
||||||
|
month_tree.clear()?;
|
||||||
|
month = time.month();
|
||||||
|
}
|
||||||
|
|
||||||
|
if year != time.year() {
|
||||||
|
add_tree_to_tree(&mut root_tree, &year_tree, format!("{year:04}"))?;
|
||||||
|
year_tree.clear()?;
|
||||||
|
year = time.year();
|
||||||
|
}
|
||||||
|
|
||||||
|
add_note_to_tree(repository, &mut day_tree, ¬e)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
add_tree_to_tree(&mut month_tree, &day_tree, format!("{day:02}"))?;
|
||||||
|
add_tree_to_tree(&mut year_tree, &month_tree, format!("{month:02}"))?;
|
||||||
|
add_tree_to_tree(&mut root_tree, &year_tree, format!("{year:04}"))?;
|
||||||
|
|
||||||
|
let tree = repository.find_tree(root_tree.write()?)?;
|
||||||
|
Ok(tree)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn migrate(self) -> Self {
|
pub fn migrate(self) -> Self {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue