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;
|
use crate::Environment;
|
||||||
|
|
||||||
|
mod repo;
|
||||||
mod status;
|
mod status;
|
||||||
mod tidy;
|
mod tidy;
|
||||||
|
|
||||||
|
|
@ -9,6 +10,10 @@ mod tidy;
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
Status(status::Command),
|
Status(status::Command),
|
||||||
Tidy(tidy::Command),
|
Tidy(tidy::Command),
|
||||||
|
Repo {
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: repo::Command,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command {
|
impl Command {
|
||||||
|
|
@ -16,6 +21,7 @@ impl Command {
|
||||||
match self {
|
match self {
|
||||||
Self::Status(command) => command.run(env),
|
Self::Status(command) => command.run(env),
|
||||||
Self::Tidy(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 v0;
|
||||||
mod v1;
|
mod v1;
|
||||||
|
|
||||||
|
pub use crate::repo::VERSION as REPO_VERSION;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
datadir::{LockedDataDir, UnlockedDataDir},
|
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<()> {
|
fn migrate(dir: &LockedDataDir) -> anyhow::Result<()> {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,10 @@ use std::{collections::HashMap, fs, path::PathBuf};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::ids::RepoId;
|
use crate::{
|
||||||
|
ids::RepoId,
|
||||||
|
repo::{self, Repo},
|
||||||
|
};
|
||||||
|
|
||||||
use super::{LockedDataDir, UnlockedDataDir};
|
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 {
|
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)
|
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> {
|
pub fn add_repo(dir: &LockedDataDir, name: String) -> anyhow::Result<RepoId> {
|
||||||
let id = RepoId::new();
|
let id = RepoId::new();
|
||||||
|
|
||||||
|
|
@ -53,8 +81,8 @@ pub fn add_repo(dir: &LockedDataDir, name: String) -> anyhow::Result<RepoId> {
|
||||||
state.repos.insert(id, name);
|
state.repos.insert(id, name);
|
||||||
save_state(dir, state)?;
|
save_state(dir, state)?;
|
||||||
|
|
||||||
fs::create_dir_all(repos_dir(dir))?;
|
repo::init(&repo_dir(dir, id))?;
|
||||||
// TODO Initialize bare repo
|
|
||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
pub mod data;
|
pub mod data;
|
||||||
pub mod ids;
|
pub mod ids;
|
||||||
|
mod repo;
|
||||||
|
|
||||||
pub const PROPER_NAME: &str = "GedächtNAS";
|
pub const PROPER_NAME: &str = "GedächtNAS";
|
||||||
pub const TECHNICAL_NAME: &str = "gedaechtnas";
|
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