pub(crate) mod internal;
use anyhow::Result;
pub use internal::RepoEntry;
#[derive(Debug, Clone, clap::Subcommand)]
pub enum RepoCommands {
Add {
url: String,
#[arg(long)]
contrib: bool,
#[arg(long)]
with_issues: bool,
#[arg(long)]
no_oxidize: bool,
},
List {
#[arg(long)]
status: bool,
},
Update {
name: Option<String>,
#[arg(long)]
all: bool,
#[arg(long)]
oxidize: bool,
#[arg(long)]
with_issues: bool,
},
#[command(alias = "rm")]
Remove {
name: String,
},
Show {
name: String,
},
}
pub fn execute_cli(
command: Option<RepoCommands>,
url: Option<String>,
contrib: bool,
with_issues: bool,
) -> Result<()> {
let cmd = match (command, url) {
(
Some(RepoCommands::Add {
url,
contrib,
with_issues,
no_oxidize,
}),
_,
) => RepoCommand::Add {
url,
contrib,
with_issues,
no_oxidize,
},
(Some(RepoCommands::List { status }), _) => RepoCommand::List { status },
(
Some(RepoCommands::Update {
name,
all,
oxidize,
with_issues,
}),
_,
) => {
if all {
RepoCommand::Update {
name: None,
oxidize,
with_issues,
}
} else {
RepoCommand::Update {
name,
oxidize,
with_issues,
}
}
}
(Some(RepoCommands::Remove { name }), _) => RepoCommand::Remove { name },
(Some(RepoCommands::Show { name }), _) => RepoCommand::Show { name },
(None, Some(url)) => RepoCommand::Add {
url,
contrib,
with_issues,
no_oxidize: false,
},
(None, None) => RepoCommand::List { status: false },
};
execute(cmd)
}
pub fn add(url: &str, contrib: bool, with_issues: bool, no_oxidize: bool) -> Result<()> {
internal::add_repo(url, contrib, with_issues, no_oxidize)
}
pub fn list() -> Result<Vec<RepoEntry>> {
internal::list_repos()
}
pub fn update(name: &str, oxidize: bool, with_issues: bool) -> Result<()> {
internal::update_repo(name, oxidize, with_issues)
}
pub fn update_all(oxidize: bool, with_issues: bool) -> Result<()> {
internal::update_all_repos(oxidize, with_issues)
}
pub fn remove(name: &str) -> Result<()> {
internal::remove_repo(name)
}
pub fn show(name: &str) -> Result<()> {
internal::show_repo(name)
}
pub fn get_db_path(name: &str) -> Result<String> {
internal::get_repo_db_path(name)
}
pub fn get_path(name: &str) -> Result<std::path::PathBuf> {
internal::get_repo_path(name)
}
pub fn migrate_registry_paths() -> bool {
let Ok(mut registry) = internal::Registry::load() else {
return false;
};
if registry.repos.is_empty() {
return false;
}
let cache_base = patina::paths::repos::cache_dir();
let mut updated_any = false;
let mut updates: Vec<(String, String)> = Vec::new();
for (name, entry) in registry.repos.iter() {
let expected_path = cache_base.join(name);
let expected_path_str = expected_path.to_string_lossy().to_string();
if entry.path != expected_path_str {
if expected_path.join(".patina/local/data/patina.db").exists()
|| expected_path.join(".git").exists()
{
updates.push((name.clone(), expected_path_str));
}
}
}
if updates.is_empty() {
return false;
}
println!("📦 Updating registry paths to new cache location...");
for (name, new_path) in updates {
if let Some(entry) = registry.repos.get_mut(&name) {
entry.path = new_path.clone();
updated_any = true;
println!(" ✓ {} -> {}", name, new_path);
}
}
if updated_any {
if let Err(e) = registry.save() {
eprintln!("Warning: Could not save updated registry: {}", e);
return false;
}
println!();
}
updated_any
}
pub fn execute(command: RepoCommand) -> Result<()> {
match command {
RepoCommand::Add {
url,
contrib,
with_issues,
no_oxidize,
} => add(&url, contrib, with_issues, no_oxidize),
RepoCommand::List { status } => {
let repos = list()?;
if repos.is_empty() {
println!("No repositories registered.");
println!("\nAdd one with: patina repo <url>");
return Ok(());
}
println!("📚 Registered Repositories\n");
if status {
println!("{:<40} {:<8} STATUS", "NAME", "CONTRIB");
println!("{}", "─".repeat(80));
for repo in repos {
let contrib_str = if repo.contrib { "✓ fork" } else { "-" };
let status_str =
internal::check_repo_status(&repo.path, repo.synced_commit.as_deref());
println!("{:<40} {:<8} {}", repo.name, contrib_str, status_str);
}
} else {
println!("{:<40} {:<8} DOMAINS", "NAME", "CONTRIB");
println!("{}", "─".repeat(80));
for repo in repos {
let contrib_str = if repo.contrib { "✓ fork" } else { "-" };
let domains = repo.domains.join(", ");
println!("{:<40} {:<8} {}", repo.name, contrib_str, domains);
}
}
Ok(())
}
RepoCommand::Update {
name,
oxidize,
with_issues,
} => {
if let Some(n) = name {
update(&n, oxidize, with_issues)
} else {
update_all(oxidize, with_issues)
}
}
RepoCommand::Remove { name } => remove(&name),
RepoCommand::Show { name } => show(&name),
}
}
#[derive(Debug, Clone)]
pub enum RepoCommand {
Add {
url: String,
contrib: bool,
with_issues: bool,
no_oxidize: bool,
},
List {
status: bool,
},
Update {
name: Option<String>,
oxidize: bool,
with_issues: bool,
},
Remove {
name: String,
},
Show {
name: String,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_repo_command_variants() {
let add = RepoCommand::Add {
url: "https://github.com/test/repo".to_string(),
contrib: false,
with_issues: true,
no_oxidize: false,
};
assert!(matches!(add, RepoCommand::Add { .. }));
let list = RepoCommand::List { status: false };
assert!(matches!(list, RepoCommand::List { .. }));
}
}