diff --git a/Cargo.lock b/Cargo.lock index 63a7023..29b2857 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2560,6 +2560,8 @@ dependencies = [ "regex", "reqwest 0.13.1", "sd-notify", + "serde", + "serde_json", "tokio", "tracing", "tracing-journald", @@ -3326,6 +3328,8 @@ dependencies = [ "rustls", "rustls-pki-types", "rustls-platform-verifier", + "serde", + "serde_json", "sync_wrapper", "tokio", "tokio-rustls", @@ -3652,14 +3656,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.143" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", ] [[package]] @@ -4046,9 +4051,21 @@ dependencies = [ "mio", "pin-project-lite", "socket2", + "tokio-macros", "windows-sys 0.61.2", ] +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "tokio-native-tls" version = "0.3.1" @@ -4893,3 +4910,9 @@ dependencies = [ "quote", "syn 2.0.100", ] + +[[package]] +name = "zmij" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea" diff --git a/Cargo.toml b/Cargo.toml index cedf5db..a640e38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,8 +13,10 @@ gitlab = "0.1807.0" gix = "0.70.0" octocrab = "0.49.5" regex = "1.12.2" -reqwest = "0.13.1" +reqwest = { version = "0.13.1", features = ["json"] } sd-notify = "0.4.5" -tokio = { version = "1.49.0", features = ["rt-multi-thread"] } +serde = "1.0.228" +serde_json = "1.0.149" +tokio = { version = "1.49.0", features = ["macros", "rt-multi-thread"] } tracing = "0.1.41" tracing-journald = "0.3.1" diff --git a/example-config.json b/example-config.json new file mode 100644 index 0000000..3da3302 --- /dev/null +++ b/example-config.json @@ -0,0 +1,29 @@ +{ + "source_hosts": { + "pvv-git": { + "type": "gitea", + "host": "git.pvv.ntnu.no", + "token": null + } + }, + "sources": { + "oysteikt-repos": { + "type": "user", + "source_host": "pvv-git", + "name": "oysteikt", + "clone": true + }, + "drift-repos": { + "type": "organization", + "source_host": "pvv-git", + "name": "drift", + "clone": true + }, + "oysteikt-stars": { + "type": "user-stars", + "source_host": "pvv-git", + "name": "oysteikt", + "clone": true + } + } +} diff --git a/src/config.rs b/src/config.rs index 8b13789..92a1809 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1 +1,197 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::path::PathBuf; +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Config { + pub source_hosts: HashMap, + + pub sources: HashMap, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SourceHostConfig { + #[serde(rename = "type")] + pub _type: SourceHostType, + pub host: Option, + pub token_file: Option, + pub token: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum SourceHostType { + Github, + Gitlab, + Gitea, + Forgejo, + Git, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type")] +#[serde(rename_all = "kebab-case")] +pub enum SourceConfig { + User { + /// Which source host to use (as defined in source_hosts) + /// You can also specify either of the following built-in source hosts: + /// + /// - "github" for GitHub at github.com + /// - "gitlab" for GitLab at gitlab.com + /// - "gitea" for Gitea at gitea.com + /// - "codeberg" for Forgejo at codeberg.org + source_host: String, + + /// The user name + name: String, + + /// Filters applied when selecting repositories + #[serde(default)] + repository_filters: RepositoryFilterConfig, + + /// Whether to clone the selected repositories + #[serde(default)] + clone: bool, + /// Filters applied when cloning repositories + #[serde(default)] + clone_filters: CloneFilterConfig, + + /// Whether to export issues for the selected repositories + #[serde(default)] + export_issues: bool, + /// Filters applied when exporting issues + #[serde(default)] + issue_filters: IssueFilterConfig, + }, + Organization { + /// Which source host to use (as defined in source_hosts) + /// You can also specify either of the following built-in source hosts: + /// + /// - "github" for GitHub at github.com + /// - "gitlab" for GitLab at gitlab.com + /// - "gitea" for Gitea at gitea.com + /// - "codeberg" for Forgejo at codeberg.org + source_host: String, + + /// The organization name + name: String, + + /// Whether to recurse into the organization's groups/subgroups (if supported) + #[serde(default)] + recurse_groups: bool, + + /// Filters applied when selecting repositories + #[serde(default)] + repository_filters: RepositoryFilterConfig, + + /// Whether to clone the selected repositories + #[serde(default)] + clone: bool, + /// Filters applied when cloning repositories + #[serde(default)] + clone_filters: CloneFilterConfig, + + /// Whether to export issues for the selected repositories + #[serde(default)] + export_issues: bool, + /// Filters applied when exporting issues + #[serde(default)] + issue_filters: IssueFilterConfig, + }, + UserStars { + /// Which source host to use (as defined in source_hosts) + /// You can also specify either of the following built-in source hosts: + /// + /// - "github" for GitHub at github.com + /// - "gitlab" for GitLab at gitlab.com + /// - "gitea" for Gitea at gitea.com + /// - "codeberg" for Forgejo at codeberg.org + source_host: String, + + /// The username whose stars to fetch + name: String, + + /// Filters applied when selecting repositories + #[serde(default)] + repository_filters: RepositoryFilterConfig, + + /// Whether to clone the selected repositories + #[serde(default)] + clone: bool, + /// Filters applied when cloning repositories + #[serde(default)] + clone_filters: CloneFilterConfig, + + /// Whether to export issues for the selected repositories + #[serde(default)] + export_issues: bool, + /// Filters applied when exporting issues + #[serde(default)] + issue_filters: IssueFilterConfig, + }, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RepositoryFilterConfig { + pub filter_regex: Vec, + pub include_forks: bool, + pub include_archived: bool, + pub min_stars: Option, + pub max_stars: Option, + pub include_public: bool, + pub include_internal: bool, + pub include_private: bool, +} + +impl Default for RepositoryFilterConfig { + fn default() -> Self { + Self { + filter_regex: Vec::new(), + include_forks: true, + include_archived: true, + min_stars: None, + max_stars: None, + include_public: true, + include_internal: true, + include_private: true, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum RegexFilter { + Include(String), + Exclude(String), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct IssueFilterConfig { + // - Filter by label(s) + // - Filter by assignee(s) + // - Filter by milestone(s) + // - Filter by state (open, closed, all) +} + +impl Default for IssueFilterConfig { + fn default() -> Self { + Self { + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CloneFilterConfig { + // - Use native API if available + // - Which branches to get + // - Which tags to get + // - Get LFS objects + // - Get Releases + // - Get Pull Requests +} + +impl Default for CloneFilterConfig { + fn default() -> Self { + Self { + } + } +} diff --git a/src/main.rs b/src/main.rs index 8ee742a..38ca70a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,28 +1,33 @@ pub mod config; pub mod sources; +use std::fs; use std::path::PathBuf; +use anyhow::Context; use clap::{CommandFactory, Parser}; use clap_complete::{Shell, generate}; +use config::Config; + #[derive(Parser, Debug)] #[command(version, about)] struct Args { #[command(subcommand)] command: Command, - #[arg(short, long)] + /// Path to the configuration file. + #[arg(short, long, global = true, default_value = "./config.json")] config: PathBuf, } #[derive(Parser, Debug, Clone)] enum Command { /// Mirror repositories as specified in the config - Rror, + // Rror, /// Create a linktree of the latest backups of the repositories, for use with cgit et al. - Linktree, + // Linktree, /// Validate the configuration provided configuration file ValidateConfig, @@ -38,20 +43,23 @@ struct GenerateCompletionArgs { shell: Shell, } -fn main() { +#[tokio::main] +async fn main() -> anyhow::Result<()> { let args = Args::parse(); - match args.command { - Command::GenerateCompletions(args) => { - generate( - args.shell, - &mut Args::command(), - "kagami", - &mut std::io::stdout(), - ); - } - _ => { - unimplemented!() - } + if let Command::GenerateCompletions(args) = args.command { + generate( + args.shell, + &mut Args::command(), + "kagami", + &mut std::io::stdout(), + ); + return Ok(()); } + + let raw_config = fs::read_to_string(&args.config).context("failed to read config file")?; + let _config: Config = + serde_json::from_str(&raw_config).context("failed to parse config file")?; + + unimplemented!() }