From 4b073dae1af11d2f661d7967daffa5480564d2fc Mon Sep 17 00:00:00 2001 From: Daniel Eades Date: Wed, 15 Apr 2020 07:36:54 +0100 Subject: [PATCH 1/3] first draft --- Cargo.toml | 2 +- src/config/index/cli.rs | 24 ------ src/config/index/git2.rs | 24 ------ src/config/index/mod.rs | 41 ---------- src/config/mod.rs | 7 +- src/index/mod.rs | 150 ++++++++++++++++++----------------- src/index/repository.rs | 50 ++++++++++++ src/index/repository/cli.rs | 69 ++++++++++++++++ src/index/repository/git2.rs | 130 ++++++++++++++++++++++++++++++ 9 files changed, 331 insertions(+), 166 deletions(-) delete mode 100644 src/config/index/cli.rs delete mode 100644 src/config/index/git2.rs delete mode 100644 src/config/index/mod.rs create mode 100644 src/index/repository.rs create mode 100644 src/index/repository/cli.rs create mode 100644 src/index/repository/git2.rs diff --git a/Cargo.toml b/Cargo.toml index 0eb5aaf7..50b6b532 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,7 +83,7 @@ mime_guess = { version = "2.0.1" } [features] # default = ["frontend", "sqlite"] # default = ["frontend", "mysql"] -default = ["frontend", "postgres"] +default = ["frontend", "postgres", "git2"] frontend = ["handlebars", "num-format", "bigdecimal", "cookie", "time"] mysql = ["diesel/mysql", "diesel_migrations/mysql"] sqlite = ["diesel/sqlite", "diesel_migrations/sqlite"] diff --git a/src/config/index/cli.rs b/src/config/index/cli.rs deleted file mode 100644 index 5c5d3b06..00000000 --- a/src/config/index/cli.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::path::PathBuf; - -use serde::{Deserialize, Serialize}; - -use crate::index::cli::CommandLineIndex; - -/// The configuration struct for the 'command-line' index management strategy. -/// -/// ```toml -/// [index] -/// type = "command-line" # required -/// path = "crate-index" # required -/// ``` -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct CommandLineIndexConfig { - /// The path to the local index repository. - pub path: PathBuf, -} - -impl From for CommandLineIndex { - fn from(config: CommandLineIndexConfig) -> CommandLineIndex { - CommandLineIndex::new(config.path) - } -} diff --git a/src/config/index/git2.rs b/src/config/index/git2.rs deleted file mode 100644 index ae2c2449..00000000 --- a/src/config/index/git2.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::path::PathBuf; - -use serde::{Deserialize, Serialize}; - -use crate::index::git2::Git2Index; - -/// The configuration struct for the 'git2' index management strategy. -/// -/// ```toml -/// [index] -/// type = "git2" # required -/// path = "crate-index" # required -/// ``` -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Git2IndexConfig { - /// The path to the local index repository. - pub path: PathBuf, -} - -impl From for Git2Index { - fn from(config: Git2IndexConfig) -> Git2Index { - Git2Index::new(config.path).expect("could not initialize the 'git2' index") - } -} diff --git a/src/config/index/mod.rs b/src/config/index/mod.rs deleted file mode 100644 index b864dfc6..00000000 --- a/src/config/index/mod.rs +++ /dev/null @@ -1,41 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// The 'command-line' configuration. -pub mod cli; - -/// The 'git2' configuration. -#[cfg(feature = "git2")] -pub mod git2; - -use crate::config::index::cli::CommandLineIndexConfig; -use crate::index::Index; - -#[cfg(feature = "git2")] -use crate::config::index::git2::Git2IndexConfig; - -/// The configuration enum for index management strategies. -/// -/// ```toml -/// [index] -/// type = "<...>" # required, replace "<...>" by the selected strategy. -/// ``` -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(tag = "type", rename_all = "kebab-case")] -pub enum IndexConfig { - /// The 'command-line' index management strategy (uses "git" shell command). - CommandLine(CommandLineIndexConfig), - /// The 'git2' index management strategy (uses [**`libgit2`**][libgit2]). - /// [libgit2]: https://libgit2.org - #[cfg(feature = "git2")] - Git2(Git2IndexConfig), -} - -impl From for Index { - fn from(config: IndexConfig) -> Index { - match config { - IndexConfig::CommandLine(config) => Index::CommandLine(config.into()), - #[cfg(feature = "git2")] - IndexConfig::Git2(config) => Index::Git2(config.into()), - } - } -} diff --git a/src/config/mod.rs b/src/config/mod.rs index 251f1fcb..89ddb350 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,11 +1,10 @@ +use std::convert::TryInto; use std::net; use serde::{Deserialize, Serialize}; /// Database configuration (`[database]` section). pub mod database; -/// Index management strategy configuration (`[index]` section). -pub mod index; /// Crate storage configuration (`[storage]` section). pub mod storage; /// Syntax-highlighting configurations (`[syntect.syntaxes]` and `[syntect.themes]` sections). @@ -20,9 +19,9 @@ use crate::storage::Storage; use crate::Repo; use crate::config::database::DatabaseConfig; -use crate::config::index::IndexConfig; use crate::config::storage::StorageConfig; use crate::config::syntect::{SyntectConfig, SyntectState}; +use crate::index::Config as IndexConfig; #[cfg(feature = "frontend")] pub use crate::config::frontend::*; @@ -72,7 +71,7 @@ pub struct State { impl From for State { fn from(config: Config) -> State { State { - index: config.index.into(), + index: config.index.try_into().unwrap(), storage: config.storage.into(), repo: Repo::new(&config.database), syntect: config.syntect.into(), diff --git a/src/index/mod.rs b/src/index/mod.rs index 82c27f6f..976eb295 100644 --- a/src/index/mod.rs +++ b/src/index/mod.rs @@ -1,9 +1,15 @@ use semver::{Version, VersionReq}; +use std::convert::TryFrom; +use std::path::PathBuf; /// Index management through `git` shell command invocations. pub mod cli; mod models; mod tree; +use tree::Tree; +mod repository; +use repository::Repository; +use serde::{Deserialize, Serialize}; /// Index management using [**`libgit2`**][libgit2]. /// [libgit2]: https://libgit2.org @@ -13,21 +19,45 @@ pub mod git2; pub use models::{CrateDependency, CrateDependencyKind, CrateVersion}; use crate::error::Error; -use crate::index::cli::CommandLineIndex; -#[cfg(feature = "git2")] -use crate::index::git2::Git2Index; +pub struct Index { + repo: Repository, + tree: Tree, +} -/// The crate indexing management strategy type. +/// The configuration struct for the 'git2' index management strategy. /// -/// It represents which index management strategy is currently used. -pub enum Index { - /// Manages the crate index through the invocation of the "git" shell command. - CommandLine(CommandLineIndex), - /// Manages the crate index using [**`libgit2`**]. - /// [libgit2]: https://libgit2.org - #[cfg(feature = "git2")] - Git2(Git2Index), +/// ```toml +/// [index] +/// type = "git2" | "command-line" # required +/// path = "crate-index" # required +/// ``` +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(tag = "type")] +#[serde(rename_all = "kebab-case")] +pub enum Config { + Git2 { path: PathBuf }, + CommandLine { path: PathBuf }, +} + +impl TryFrom for Index { + type Error = Error; + fn try_from(config: Config) -> Result { + let (path, repo) = match config { + Config::CommandLine { path } => { + let repo = Repository::new_cli(path.clone()); + (path, repo) + } + Config::Git2 { path } => { + let repo = Repository::new_git2(&path)?; + (path, repo) + } + }; + + let tree = Tree::new(path); + + Ok(Self { repo, tree }) + } } /// The required trait that any crate index management type must implement. @@ -62,85 +92,61 @@ pub trait Indexer { impl Indexer for Index { fn url(&self) -> Result { - match self { - Index::CommandLine(idx) => idx.url(), - #[cfg(feature = "git2")] - Index::Git2(idx) => idx.url(), - } + self.repo.url() } - fn refresh(&self) -> Result<(), Error> { - match self { - Index::CommandLine(idx) => idx.refresh(), - #[cfg(feature = "git2")] - Index::Git2(idx) => idx.refresh(), - } + self.repo.refresh() } - - fn commit_and_push(&self, msg: &str) -> Result<(), Error> { - match self { - Index::CommandLine(idx) => idx.commit_and_push(msg), - #[cfg(feature = "git2")] - Index::Git2(idx) => idx.commit_and_push(msg), - } - } - fn all_records(&self, name: &str) -> Result, Error> { - match self { - Index::CommandLine(idx) => idx.all_records(name), - #[cfg(feature = "git2")] - Index::Git2(idx) => idx.all_records(name), - } + self.tree.all_records(name) } - fn latest_record(&self, name: &str) -> Result { - match self { - Index::CommandLine(idx) => idx.latest_record(name), - #[cfg(feature = "git2")] - Index::Git2(idx) => idx.latest_record(name), - } + self.tree.latest_record(name) } - fn match_record(&self, name: &str, req: VersionReq) -> Result { - match self { - Index::CommandLine(idx) => idx.match_record(name, req), - #[cfg(feature = "git2")] - Index::Git2(idx) => idx.match_record(name, req), - } + self.tree.match_record(name, req) + } + fn commit_and_push(&self, msg: &str) -> Result<(), Error> { + self.repo.commit_and_push(msg) } - fn add_record(&self, record: CrateVersion) -> Result<(), Error> { - match self { - Index::CommandLine(idx) => idx.add_record(record), - #[cfg(feature = "git2")] - Index::Git2(idx) => idx.add_record(record), - } + self.tree.add_record(record) } - fn alter_record(&self, name: &str, version: Version, func: F) -> Result<(), Error> where F: FnOnce(&mut CrateVersion), { - match self { - Index::CommandLine(idx) => idx.alter_record(name, version, func), - #[cfg(feature = "git2")] - Index::Git2(idx) => idx.alter_record(name, version, func), - } + self.tree.alter_record(name, version, func) } +} - fn yank_record(&self, name: &str, version: Version) -> Result<(), Error> { - match self { - Index::CommandLine(idx) => idx.yank_record(name, version), - #[cfg(feature = "git2")] - Index::Git2(idx) => idx.yank_record(name, version), +#[cfg(test)] +mod tests { + use super::Config; + #[test] + fn from_config() { + match toml::from_str( + r#" + type = "git2" + path = "crate-index" + "#, + ) + .unwrap() + { + Config::Git2 { .. } => (), + Config::CommandLine { .. } => panic!("deserialization failed!"), } - } - fn unyank_record(&self, name: &str, version: Version) -> Result<(), Error> { - match self { - Index::CommandLine(idx) => idx.unyank_record(name, version), - #[cfg(feature = "git2")] - Index::Git2(idx) => idx.unyank_record(name, version), + match toml::from_str( + r#" + type = "command-line" + path = "crate-index" + "#, + ) + .unwrap() + { + Config::Git2 { .. } => panic!("deserialization failed!"), + Config::CommandLine { .. } => (), } } } diff --git a/src/index/repository.rs b/src/index/repository.rs new file mode 100644 index 00000000..df4bb566 --- /dev/null +++ b/src/index/repository.rs @@ -0,0 +1,50 @@ +mod cli; +#[cfg(feature = "git2")] +mod git2; +use crate::error::Error; +use std::path::{Path, PathBuf}; + +trait Repo { + fn url(&self) -> Result; + + fn refresh(&self) -> Result<(), Error>; + + fn commit_and_push(&self, msg: &str) -> Result<(), Error>; +} + +pub enum Repository { + #[cfg(feature = "git2")] + Git2(git2::Repository), + Cli(cli::Repository), +} + +impl Repository { + #[cfg(feature = "git2")] + pub fn new_git2>(path: P) -> Result { + Ok(Self::Git2(git2::Repository::new(path)?)) + } + + pub fn new_cli>(path: P) -> Self { + Self::Cli(cli::Repository::new(path)) + } + + fn inner(&self) -> &dyn Repo { + match self { + #[cfg(feature = "git2")] + Self::Git2(repo) => repo, + Self::Cli(repo) => repo, + } + } +} + +impl Repository { + pub fn url(&self) -> Result { + self.inner().url() + } + pub fn refresh(&self) -> Result<(), Error> { + self.inner().refresh() + } + pub fn commit_and_push(&self, msg: &str) -> Result<(), Error> { + self.inner().commit_and_push(msg) + } +} diff --git a/src/index/repository/cli.rs b/src/index/repository/cli.rs new file mode 100644 index 00000000..389152d4 --- /dev/null +++ b/src/index/repository/cli.rs @@ -0,0 +1,69 @@ +use std::path::PathBuf; +use std::process::{Command, Stdio}; + +use super::Repo; +use crate::error::Error; + +/// The 'command-line' crate index management strategy type. +/// +/// It manages the crate index through the invocation of "git" shell commands. +#[derive(Debug, Clone, PartialEq)] +pub struct Repository { + path: PathBuf, +} + +impl Repository { + pub fn new>(path: P) -> Self { + Self { path: path.into() } + } +} + +impl Repo for Repository { + fn url(&self) -> Result { + let output = Command::new("git") + .arg("remote") + .arg("get-url") + .arg("origin") + .stdout(Stdio::piped()) + .current_dir(self.path.canonicalize()?) + .output()?; + + Ok(String::from_utf8_lossy(output.stdout.as_slice()).into()) + } + + fn refresh(&self) -> Result<(), Error> { + Command::new("git") + .arg("pull") + .arg("--ff-only") + .current_dir(self.path.canonicalize()?) + .spawn()? + .wait()?; + + Ok(()) + } + + fn commit_and_push(&self, msg: &str) -> Result<(), Error> { + Command::new("git") + .arg("add") + .arg("--all") + .current_dir(&self.path) + .spawn()? + .wait()?; + Command::new("git") + .arg("commit") + .arg("-m") + .arg(msg) + .current_dir(&self.path) + .spawn()? + .wait()?; + Command::new("git") + .arg("push") + .arg("origin") + .arg("master") + .current_dir(&self.path) + .spawn()? + .wait()?; + + Ok(()) + } +} diff --git a/src/index/repository/git2.rs b/src/index/repository/git2.rs new file mode 100644 index 00000000..1363867d --- /dev/null +++ b/src/index/repository/git2.rs @@ -0,0 +1,130 @@ +use super::Repo; +use crate::error::Error; +use git2; +use std::path::Path; +use std::sync::Mutex; + +/// The 'git2' crate index management strategy type. +/// +/// It manages the crate index using the [**`libgit2`**][libgit2] library. +/// +/// [libgit2]: https://libgit2.org +pub struct Repository { + /// The path of the crate index. + repo: Mutex, +} + +impl Repository { + /// Create a Repository instance with the given path. + pub fn new>(path: P) -> Result { + let repo = Mutex::new(git2::Repository::open(path)?); + Ok(Repository { repo }) + } +} + +/// Helper to run git operations that require authentication. +/// +/// This is inspired by [the way Cargo handles this][cargo-impl]. +/// +/// [cargo-impl]: https://github.com/rust-lang/cargo/blob/94bf4781d0bbd266abe966c6fe1512bb1725d368/src/cargo/sources/git/utils.rs#L437 +fn with_credentials(repo: &git2::Repository, mut f: F) -> Result +where + F: FnMut(&mut git2::Credentials) -> Result, +{ + let config = repo.config()?; + + let mut tried_sshkey = false; + let mut tried_cred_helper = false; + let mut tried_default = false; + + f(&mut |url, username, allowed| { + if allowed.contains(git2::CredentialType::USERNAME) { + return Err(git2::Error::from_str("no username specified in remote URL")); + } + + if allowed.contains(git2::CredentialType::SSH_KEY) && !tried_sshkey { + tried_sshkey = true; + let username = username.unwrap(); + return git2::Cred::ssh_key_from_agent(username); + } + + if allowed.contains(git2::CredentialType::USER_PASS_PLAINTEXT) && !tried_cred_helper { + tried_cred_helper = true; + return git2::Cred::credential_helper(&config, url, username); + } + + if allowed.contains(git2::CredentialType::DEFAULT) && !tried_default { + tried_default = true; + return git2::Cred::default(); + } + + Err(git2::Error::from_str("no authentication methods succeeded")) + }) +} + +impl Repo for Repository { + fn url(&self) -> Result { + let repo = self.repo.lock().unwrap(); + let remote = repo.find_remote("origin")?; + Ok(remote.url().map_or_else(String::default, String::from)) + } + + fn refresh(&self) -> Result<(), Error> { + let repo = self.repo.lock().unwrap(); + let mut remote = repo.find_remote("origin")?; + let branch = repo + .branches(Some(git2::BranchType::Local))? + .flatten() + .map(|(branch, _)| branch) + .find(|branch| branch.is_head()) + .ok_or_else(|| git2::Error::from_str("detached HEAD not supported"))?; + let branch_name = branch.name()?.expect("branch name is invalid UTF-8"); + + with_credentials(&repo, |cred_callback| { + let mut opts = git2::FetchOptions::new(); + let mut callbacks = git2::RemoteCallbacks::new(); + callbacks.credentials(cred_callback); + opts.remote_callbacks(callbacks); + remote.fetch(&[branch_name], Some(&mut opts), None)?; + Ok(()) + })?; + + let fetch_head = repo.find_reference("FETCH_HEAD")?; + let fetch_commit = repo.reference_to_annotated_commit(&fetch_head)?; + let (analysis, _) = repo.merge_analysis(&[&fetch_commit])?; + if analysis.is_up_to_date() { + Ok(()) + } else if analysis.is_fast_forward() { + let refname = format!("refs/heads/{}", branch_name); + let mut reference = repo.find_reference(&refname)?; + reference.set_target(fetch_commit.id(), "Fast-forward")?; + repo.set_head(&refname)?; + repo.checkout_head(Some(git2::build::CheckoutBuilder::default().force()))?; + Ok(()) + } else { + Err(Error::from(git2::Error::from_str("fast-forward only!"))) + } + } + + fn commit_and_push(&self, msg: &str) -> Result<(), Error> { + let repo = self.repo.lock().unwrap(); + let oid = { + let mut index = repo.index()?; + index.add_all(&["."], git2::IndexAddOption::DEFAULT, None)?; + index.write_tree()? + }; + let signature = repo.signature()?; + let tree = repo.find_tree(oid)?; + let parent = { + let head = repo.head()?; + head.peel_to_commit()? + }; + + repo.commit(Some("HEAD"), &signature, &signature, msg, &tree, &[&parent])?; + + let mut remote = repo.find_remote("origin")?; + remote.push::<&'static str>(&[], None)?; + + Ok(()) + } +} From d25028d3ab52f58ef940f7ae43f2c533d1e9d5db Mon Sep 17 00:00:00 2001 From: Daniel Eades Date: Wed, 15 Apr 2020 07:51:23 +0100 Subject: [PATCH 2/3] remove unused files --- src/index/cli.rs | 119 ---------------------------------- src/index/git2.rs | 160 ---------------------------------------------- src/index/mod.rs | 7 -- 3 files changed, 286 deletions(-) delete mode 100644 src/index/cli.rs delete mode 100644 src/index/git2.rs diff --git a/src/index/cli.rs b/src/index/cli.rs deleted file mode 100644 index f8149d0c..00000000 --- a/src/index/cli.rs +++ /dev/null @@ -1,119 +0,0 @@ -use std::path::PathBuf; -use std::process::{Command, Stdio}; - -use semver::{Version, VersionReq}; - -use crate::error::Error; -use crate::index::tree::Tree; -use crate::index::{CrateVersion, Indexer}; - -/// The 'command-line' crate index management strategy type. -/// -/// It manages the crate index through the invocation of "git" shell commands. -#[derive(Debug, Clone, PartialEq)] -pub struct CommandLineIndex { - repo: Repository, - tree: Tree, -} - -impl CommandLineIndex { - /// Create a CommandLineIndex instance with the given path. - pub fn new>(path: P) -> CommandLineIndex { - let path = path.into(); - let repo = Repository { path: path.clone() }; - let tree = Tree::new(path); - CommandLineIndex { repo, tree } - } -} - -impl Indexer for CommandLineIndex { - fn url(&self) -> Result { - self.repo.url() - } - - fn refresh(&self) -> Result<(), Error> { - self.repo.refresh() - } - - fn commit_and_push(&self, msg: &str) -> Result<(), Error> { - self.repo.commit_and_push(msg) - } - - fn match_record(&self, name: &str, req: VersionReq) -> Result { - self.tree.match_record(name, req) - } - - fn all_records(&self, name: &str) -> Result, Error> { - self.tree.all_records(name) - } - - fn latest_record(&self, name: &str) -> Result { - self.tree.latest_record(name) - } - - fn add_record(&self, record: CrateVersion) -> Result<(), Error> { - self.tree.add_record(record) - } - - fn alter_record(&self, name: &str, version: Version, func: F) -> Result<(), Error> - where - F: FnOnce(&mut CrateVersion), - { - self.tree.alter_record(name, version, func) - } -} - -#[derive(Debug, Clone, PartialEq)] -struct Repository { - path: PathBuf, -} - -impl Repository { - fn url(&self) -> Result { - let output = Command::new("git") - .arg("remote") - .arg("get-url") - .arg("origin") - .stdout(Stdio::piped()) - .current_dir(self.path.canonicalize()?) - .output()?; - - Ok(String::from_utf8_lossy(output.stdout.as_slice()).into()) - } - - fn refresh(&self) -> Result<(), Error> { - Command::new("git") - .arg("pull") - .arg("--ff-only") - .current_dir(self.path.canonicalize()?) - .spawn()? - .wait()?; - - Ok(()) - } - - fn commit_and_push(&self, msg: &str) -> Result<(), Error> { - Command::new("git") - .arg("add") - .arg("--all") - .current_dir(&self.path) - .spawn()? - .wait()?; - Command::new("git") - .arg("commit") - .arg("-m") - .arg(msg) - .current_dir(&self.path) - .spawn()? - .wait()?; - Command::new("git") - .arg("push") - .arg("origin") - .arg("master") - .current_dir(&self.path) - .spawn()? - .wait()?; - - Ok(()) - } -} diff --git a/src/index/git2.rs b/src/index/git2.rs deleted file mode 100644 index 918cfb2d..00000000 --- a/src/index/git2.rs +++ /dev/null @@ -1,160 +0,0 @@ -use std::path::PathBuf; -use std::sync::Mutex; - -use semver::{Version, VersionReq}; - -use crate::error::Error; -use crate::index::tree::Tree; -use crate::index::{CrateVersion, Indexer}; - -/// The 'git2' crate index management strategy type. -/// -/// It manages the crate index using the [**`libgit2`**][libgit2] library. -/// -/// [libgit2]: https://libgit2.org -pub struct Git2Index { - /// The path of the crate index. - pub(crate) repo: Mutex, - tree: Tree, -} - -impl Git2Index { - /// Create a Git2Index instance with the given path. - pub fn new>(path: P) -> Result { - let path = path.into(); - let repo = git2::Repository::open(&path)?; - let repo = Mutex::new(repo); - let tree = Tree::new(path); - Ok(Git2Index { repo, tree }) - } -} - -/// Helper to run git operations that require authentication. -/// -/// This is inspired by [the way Cargo handles this][cargo-impl]. -/// -/// [cargo-impl]: https://github.com/rust-lang/cargo/blob/94bf4781d0bbd266abe966c6fe1512bb1725d368/src/cargo/sources/git/utils.rs#L437 -fn with_credentials(repo: &git2::Repository, mut f: F) -> Result -where - F: FnMut(&mut git2::Credentials) -> Result, -{ - let config = repo.config()?; - - let mut tried_sshkey = false; - let mut tried_cred_helper = false; - let mut tried_default = false; - - f(&mut |url, username, allowed| { - if allowed.contains(git2::CredentialType::USERNAME) { - return Err(git2::Error::from_str("no username specified in remote URL")); - } - - if allowed.contains(git2::CredentialType::SSH_KEY) && !tried_sshkey { - tried_sshkey = true; - let username = username.unwrap(); - return git2::Cred::ssh_key_from_agent(username); - } - - if allowed.contains(git2::CredentialType::USER_PASS_PLAINTEXT) && !tried_cred_helper { - tried_cred_helper = true; - return git2::Cred::credential_helper(&config, url, username); - } - - if allowed.contains(git2::CredentialType::DEFAULT) && !tried_default { - tried_default = true; - return git2::Cred::default(); - } - - Err(git2::Error::from_str("no authentication methods succeeded")) - }) -} - -impl Indexer for Git2Index { - fn url(&self) -> Result { - let repo = self.repo.lock().unwrap(); - let remote = repo.find_remote("origin")?; - Ok(remote.url().map_or_else(String::default, String::from)) - } - - fn refresh(&self) -> Result<(), Error> { - let repo = self.repo.lock().unwrap(); - let mut remote = repo.find_remote("origin")?; - let branch = repo - .branches(Some(git2::BranchType::Local))? - .flatten() - .map(|(branch, _)| branch) - .find(|branch| branch.is_head()) - .ok_or_else(|| git2::Error::from_str("detached HEAD not supported"))?; - let branch_name = branch.name()?.expect("branch name is invalid UTF-8"); - - with_credentials(&repo, |cred_callback| { - let mut opts = git2::FetchOptions::new(); - let mut callbacks = git2::RemoteCallbacks::new(); - callbacks.credentials(cred_callback); - opts.remote_callbacks(callbacks); - remote.fetch(&[branch_name], Some(&mut opts), None)?; - Ok(()) - })?; - - let fetch_head = repo.find_reference("FETCH_HEAD")?; - let fetch_commit = repo.reference_to_annotated_commit(&fetch_head)?; - let (analysis, _) = repo.merge_analysis(&[&fetch_commit])?; - if analysis.is_up_to_date() { - Ok(()) - } else if analysis.is_fast_forward() { - let refname = format!("refs/heads/{}", branch_name); - let mut reference = repo.find_reference(&refname)?; - reference.set_target(fetch_commit.id(), "Fast-forward")?; - repo.set_head(&refname)?; - repo.checkout_head(Some(git2::build::CheckoutBuilder::default().force()))?; - Ok(()) - } else { - Err(Error::from(git2::Error::from_str("fast-forward only!"))) - } - } - - fn commit_and_push(&self, msg: &str) -> Result<(), Error> { - let repo = self.repo.lock().unwrap(); - let oid = { - let mut index = repo.index()?; - index.add_all(&["."], git2::IndexAddOption::DEFAULT, None)?; - index.write_tree()? - }; - let signature = repo.signature()?; - let tree = repo.find_tree(oid)?; - let parent = { - let head = repo.head()?; - head.peel_to_commit()? - }; - - repo.commit(Some("HEAD"), &signature, &signature, msg, &tree, &[&parent])?; - - let mut remote = repo.find_remote("origin")?; - remote.push::<&'static str>(&[], None)?; - - Ok(()) - } - - fn match_record(&self, name: &str, req: VersionReq) -> Result { - self.tree.match_record(name, req) - } - - fn all_records(&self, name: &str) -> Result, Error> { - self.tree.all_records(name) - } - - fn latest_record(&self, name: &str) -> Result { - self.tree.latest_record(name) - } - - fn add_record(&self, record: CrateVersion) -> Result<(), Error> { - self.tree.add_record(record) - } - - fn alter_record(&self, name: &str, version: Version, func: F) -> Result<(), Error> - where - F: FnOnce(&mut CrateVersion), - { - self.tree.alter_record(name, version, func) - } -} diff --git a/src/index/mod.rs b/src/index/mod.rs index 976eb295..be7cdd9b 100644 --- a/src/index/mod.rs +++ b/src/index/mod.rs @@ -2,8 +2,6 @@ use semver::{Version, VersionReq}; use std::convert::TryFrom; use std::path::PathBuf; -/// Index management through `git` shell command invocations. -pub mod cli; mod models; mod tree; use tree::Tree; @@ -11,11 +9,6 @@ mod repository; use repository::Repository; use serde::{Deserialize, Serialize}; -/// Index management using [**`libgit2`**][libgit2]. -/// [libgit2]: https://libgit2.org -#[cfg(feature = "git2")] -pub mod git2; - pub use models::{CrateDependency, CrateDependencyKind, CrateVersion}; use crate::error::Error; From 740df21ed6890ba294a1bf1efc3fb2c8449c09af Mon Sep 17 00:00:00 2001 From: Daniel Eades Date: Wed, 15 Apr 2020 10:38:04 +0100 Subject: [PATCH 3/3] change repository enum to a boxed trait --- src/index/mod.rs | 26 ++++++++++++--------- src/index/repository.rs | 44 +++--------------------------------- src/index/repository/cli.rs | 8 +++---- src/index/repository/git2.rs | 12 +++++----- 4 files changed, 28 insertions(+), 62 deletions(-) diff --git a/src/index/mod.rs b/src/index/mod.rs index be7cdd9b..2b764ef5 100644 --- a/src/index/mod.rs +++ b/src/index/mod.rs @@ -14,10 +14,18 @@ pub use models::{CrateDependency, CrateDependencyKind, CrateVersion}; use crate::error::Error; pub struct Index { - repo: Repository, + repo: Box, tree: Tree, } +impl Index { + pub fn with_repo(path: PathBuf, repo: impl Repository + Send + Sync + 'static) -> Self { + let repo = Box::new(repo); + let tree = Tree::new(path); + Self { repo, tree } + } +} + /// The configuration struct for the 'git2' index management strategy. /// /// ```toml @@ -36,20 +44,16 @@ pub enum Config { impl TryFrom for Index { type Error = Error; fn try_from(config: Config) -> Result { - let (path, repo) = match config { + match config { Config::CommandLine { path } => { - let repo = Repository::new_cli(path.clone()); - (path, repo) + let repo = repository::cli::Repo::new(path.clone()); + Ok(Self::with_repo(path, repo)) } Config::Git2 { path } => { - let repo = Repository::new_git2(&path)?; - (path, repo) + let repo = repository::git2::Repo::new(&path)?; + Ok(Self::with_repo(path, repo)) } - }; - - let tree = Tree::new(path); - - Ok(Self { repo, tree }) + } } } diff --git a/src/index/repository.rs b/src/index/repository.rs index df4bb566..8a6923c2 100644 --- a/src/index/repository.rs +++ b/src/index/repository.rs @@ -1,50 +1,12 @@ -mod cli; +pub mod cli; #[cfg(feature = "git2")] -mod git2; +pub mod git2; use crate::error::Error; -use std::path::{Path, PathBuf}; -trait Repo { +pub trait Repository { fn url(&self) -> Result; fn refresh(&self) -> Result<(), Error>; fn commit_and_push(&self, msg: &str) -> Result<(), Error>; } - -pub enum Repository { - #[cfg(feature = "git2")] - Git2(git2::Repository), - Cli(cli::Repository), -} - -impl Repository { - #[cfg(feature = "git2")] - pub fn new_git2>(path: P) -> Result { - Ok(Self::Git2(git2::Repository::new(path)?)) - } - - pub fn new_cli>(path: P) -> Self { - Self::Cli(cli::Repository::new(path)) - } - - fn inner(&self) -> &dyn Repo { - match self { - #[cfg(feature = "git2")] - Self::Git2(repo) => repo, - Self::Cli(repo) => repo, - } - } -} - -impl Repository { - pub fn url(&self) -> Result { - self.inner().url() - } - pub fn refresh(&self) -> Result<(), Error> { - self.inner().refresh() - } - pub fn commit_and_push(&self, msg: &str) -> Result<(), Error> { - self.inner().commit_and_push(msg) - } -} diff --git a/src/index/repository/cli.rs b/src/index/repository/cli.rs index 389152d4..5e5df86e 100644 --- a/src/index/repository/cli.rs +++ b/src/index/repository/cli.rs @@ -1,24 +1,24 @@ use std::path::PathBuf; use std::process::{Command, Stdio}; -use super::Repo; +use super::Repository; use crate::error::Error; /// The 'command-line' crate index management strategy type. /// /// It manages the crate index through the invocation of "git" shell commands. #[derive(Debug, Clone, PartialEq)] -pub struct Repository { +pub struct Repo { path: PathBuf, } -impl Repository { +impl Repo { pub fn new>(path: P) -> Self { Self { path: path.into() } } } -impl Repo for Repository { +impl Repository for Repo { fn url(&self) -> Result { let output = Command::new("git") .arg("remote") diff --git a/src/index/repository/git2.rs b/src/index/repository/git2.rs index 1363867d..76e2c9aa 100644 --- a/src/index/repository/git2.rs +++ b/src/index/repository/git2.rs @@ -1,4 +1,4 @@ -use super::Repo; +use super::Repository; use crate::error::Error; use git2; use std::path::Path; @@ -9,16 +9,16 @@ use std::sync::Mutex; /// It manages the crate index using the [**`libgit2`**][libgit2] library. /// /// [libgit2]: https://libgit2.org -pub struct Repository { +pub struct Repo { /// The path of the crate index. repo: Mutex, } -impl Repository { +impl Repo { /// Create a Repository instance with the given path. - pub fn new>(path: P) -> Result { + pub fn new>(path: P) -> Result { let repo = Mutex::new(git2::Repository::open(path)?); - Ok(Repository { repo }) + Ok(Self { repo }) } } @@ -62,7 +62,7 @@ where }) } -impl Repo for Repository { +impl Repository for Repo { fn url(&self) -> Result { let repo = self.repo.lock().unwrap(); let remote = repo.find_remote("origin")?;