Add repo add and repo show commands
This commit is contained in:
parent
b922af9283
commit
2c5ff584db
10 changed files with 219 additions and 4 deletions
|
|
@ -2,6 +2,7 @@ use clap::Parser;
|
|||
|
||||
use crate::Environment;
|
||||
|
||||
mod repo;
|
||||
mod status;
|
||||
mod tidy;
|
||||
|
||||
|
|
@ -9,6 +10,10 @@ mod tidy;
|
|||
pub enum Command {
|
||||
Status(status::Command),
|
||||
Tidy(tidy::Command),
|
||||
Repo {
|
||||
#[command(subcommand)]
|
||||
command: repo::Command,
|
||||
},
|
||||
}
|
||||
|
||||
impl Command {
|
||||
|
|
@ -16,6 +21,7 @@ impl Command {
|
|||
match self {
|
||||
Self::Status(command) => command.run(env),
|
||||
Self::Tidy(command) => command.run(env),
|
||||
Self::Repo { command } => command.run(env),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
22
gdn-cli/src/commands/repo.rs
Normal file
22
gdn-cli/src/commands/repo.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
mod add;
|
||||
mod show;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use crate::Environment;
|
||||
|
||||
/// Perform repo operations.
|
||||
#[derive(Debug, Parser)]
|
||||
pub enum Command {
|
||||
Show(show::Command),
|
||||
Add(add::Command),
|
||||
}
|
||||
|
||||
impl Command {
|
||||
pub fn run(self, env: &Environment) -> anyhow::Result<()> {
|
||||
match self {
|
||||
Self::Show(command) => command.run(env),
|
||||
Self::Add(command) => command.run(env),
|
||||
}
|
||||
}
|
||||
}
|
||||
18
gdn-cli/src/commands/repo/add.rs
Normal file
18
gdn-cli/src/commands/repo/add.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
use clap::Parser;
|
||||
|
||||
use crate::Environment;
|
||||
|
||||
/// Add a new repository.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Command {
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
pub fn run(self, env: &Environment) -> anyhow::Result<()> {
|
||||
let data = gdn::data::open_and_migrate(env.data_dir.clone())?;
|
||||
let id = gdn::data::add_repo(&data, self.name.clone())?;
|
||||
println!("Added repo {} ({id}).", self.name);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
30
gdn-cli/src/commands/repo/show.rs
Normal file
30
gdn-cli/src/commands/repo/show.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
use clap::Parser;
|
||||
use gdn::data::REPO_VERSION;
|
||||
|
||||
use crate::Environment;
|
||||
|
||||
/// Show info about a repository.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Command {
|
||||
repo: String,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
pub fn run(self, env: &Environment) -> anyhow::Result<()> {
|
||||
let data = gdn::data::open(env.data_dir.clone())?;
|
||||
let state = gdn::data::load_state(&data)?;
|
||||
|
||||
let Some(id) = state.resolve_repo_identifier(&self.repo) else {
|
||||
println!("No repo found for identifier {}.", self.repo);
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let version = gdn::data::load_repo_version(&data, id)?;
|
||||
let repo = gdn::data::load_repo(&data, id)?;
|
||||
|
||||
println!("Repo version: {version} (latest: {REPO_VERSION})",);
|
||||
println!("Number of notes: {}", repo.notes.len());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -8,9 +8,11 @@ mod lockfile;
|
|||
mod v0;
|
||||
mod v1;
|
||||
|
||||
pub use crate::repo::VERSION as REPO_VERSION;
|
||||
|
||||
pub use self::{
|
||||
datadir::{LockedDataDir, UnlockedDataDir},
|
||||
v1::{State, VERSION, load_state, tidy},
|
||||
v1::{State, VERSION, add_repo, load_repo, load_repo_version, load_state, tidy},
|
||||
};
|
||||
|
||||
fn migrate(dir: &LockedDataDir) -> anyhow::Result<()> {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,10 @@ use std::{collections::HashMap, fs, path::PathBuf};
|
|||
use anyhow::anyhow;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::ids::RepoId;
|
||||
use crate::{
|
||||
ids::RepoId,
|
||||
repo::{self, Repo},
|
||||
};
|
||||
|
||||
use super::{LockedDataDir, UnlockedDataDir};
|
||||
|
||||
|
|
@ -23,6 +26,23 @@ impl State {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_repo_identifier(&self, identifier: &str) -> Option<RepoId> {
|
||||
// If the identifier is a valid repo id, always interpret it as such.
|
||||
// There must always be an unambiguous way to refer to repos.
|
||||
if let Ok(id) = identifier.parse::<RepoId>() {
|
||||
if self.repos.contains_key(&id) {
|
||||
return Some(id);
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, interpret the identifier as a repo name and find the
|
||||
// corresponding id.
|
||||
self.repos
|
||||
.iter()
|
||||
.find(|(_, name)| *name == identifier)
|
||||
.map(|(id, _)| *id)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn state_file(dir: &UnlockedDataDir) -> PathBuf {
|
||||
|
|
@ -46,6 +66,14 @@ pub fn save_state(dir: &LockedDataDir, mut state: State) -> anyhow::Result<()> {
|
|||
dir.write_json(&state_file(dir), &state)
|
||||
}
|
||||
|
||||
pub fn load_repo_version(dir: &UnlockedDataDir, id: RepoId) -> anyhow::Result<u32> {
|
||||
repo::load_version(&repo_dir(dir, id))
|
||||
}
|
||||
|
||||
pub fn load_repo(dir: &UnlockedDataDir, id: RepoId) -> anyhow::Result<Repo> {
|
||||
repo::load(&repo_dir(dir, id))
|
||||
}
|
||||
|
||||
pub fn add_repo(dir: &LockedDataDir, name: String) -> anyhow::Result<RepoId> {
|
||||
let id = RepoId::new();
|
||||
|
||||
|
|
@ -53,8 +81,8 @@ pub fn add_repo(dir: &LockedDataDir, name: String) -> anyhow::Result<RepoId> {
|
|||
state.repos.insert(id, name);
|
||||
save_state(dir, state)?;
|
||||
|
||||
fs::create_dir_all(repos_dir(dir))?;
|
||||
// TODO Initialize bare repo
|
||||
repo::init(&repo_dir(dir, id))?;
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
pub mod data;
|
||||
pub mod ids;
|
||||
mod repo;
|
||||
|
||||
pub const PROPER_NAME: &str = "GedächtNAS";
|
||||
pub const TECHNICAL_NAME: &str = "gedaechtnas";
|
||||
|
|
|
|||
59
gdn/src/repo.rs
Normal file
59
gdn/src/repo.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
mod v0;
|
||||
mod v1;
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::{anyhow, bail};
|
||||
use git2::{ErrorCode, Repository};
|
||||
|
||||
pub use self::v1::{Note, Repo, VERSION};
|
||||
|
||||
const VERSION_FILE: &str = "VERSION";
|
||||
|
||||
pub fn init(path: &Path) -> anyhow::Result<()> {
|
||||
Repository::init_bare(path)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_version(repo: &Repository) -> anyhow::Result<u32> {
|
||||
let head = match repo.head() {
|
||||
Ok(head) => head,
|
||||
Err(error) if error.code() == ErrorCode::UnbornBranch => return Ok(0),
|
||||
Err(error) => Err(error)?,
|
||||
};
|
||||
|
||||
let object = head
|
||||
.peel_to_commit()?
|
||||
.tree()?
|
||||
.get_path(VERSION_FILE.as_ref())?
|
||||
.to_object(repo)?;
|
||||
|
||||
let blob = object
|
||||
.as_blob()
|
||||
.ok_or(anyhow!("Failed to read file {VERSION_FILE}"))?;
|
||||
|
||||
let content = blob.content().to_vec();
|
||||
let version = String::from_utf8(content)?.trim().parse::<u32>()?;
|
||||
|
||||
Ok(version)
|
||||
}
|
||||
|
||||
pub fn load_version(path: &Path) -> anyhow::Result<u32> {
|
||||
let repo = Repository::open_bare(path)?;
|
||||
let version = read_version(&repo)?;
|
||||
Ok(version)
|
||||
}
|
||||
|
||||
pub fn load(path: &Path) -> anyhow::Result<Repo> {
|
||||
let repository = Repository::open_bare(path)?;
|
||||
let version = read_version(&repository)?;
|
||||
|
||||
#[expect(unused_qualifications)]
|
||||
let repo = match version {
|
||||
0 => v0::Repo::load().migrate(),
|
||||
1 => v1::Repo::load(&repository)?.migrate(),
|
||||
n => bail!("invalid repo version {n}"),
|
||||
};
|
||||
|
||||
Ok(repo)
|
||||
}
|
||||
20
gdn/src/repo/v0.rs
Normal file
20
gdn/src/repo/v0.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use super::v1;
|
||||
|
||||
pub const VERSION: u32 = 0;
|
||||
|
||||
pub struct Repo;
|
||||
|
||||
impl Repo {
|
||||
pub fn load() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
pub fn migrate(self) -> super::Repo {
|
||||
v1::Repo {
|
||||
notes: HashMap::new(),
|
||||
}
|
||||
.migrate()
|
||||
}
|
||||
}
|
||||
29
gdn/src/repo/v1.rs
Normal file
29
gdn/src/repo/v1.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use git2::Repository;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::ids::NoteId;
|
||||
|
||||
pub const VERSION: u32 = 1;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Note {
|
||||
pub text: String,
|
||||
pub children: Vec<NoteId>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Repo {
|
||||
pub notes: HashMap<NoteId, Note>,
|
||||
}
|
||||
|
||||
impl Repo {
|
||||
pub fn load(repository: &Repository) -> anyhow::Result<Self> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn migrate(self) -> super::Repo {
|
||||
self
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue