#![deny(
clippy::all,
clippy::cargo,
clippy::nursery,
clippy::pedantic,
deprecated_in_future,
future_incompatible,
missing_docs,
nonstandard_style,
rust_2018_idioms,
rustdoc,
warnings,
unused_results,
unused_qualifications,
unused_lifetimes,
unused_import_braces,
unsafe_code,
unreachable_pub,
trivial_casts,
trivial_numeric_casts,
missing_debug_implementations,
missing_copy_implementations
)]
#![warn(variant_size_differences)]
#![allow(clippy::multiple_crate_versions, missing_doc_code_examples)]
#![doc(html_root_url = "https://docs.rs/automaat-processor-git-clone/0.1.0")]
use automaat_core::{Context, Processor};
use git2::{build::RepoBuilder, Cred, FetchOptions, RemoteCallbacks};
use serde::{Deserialize, Serialize};
use std::{error, fmt, path};
use url::Url;
#[cfg_attr(feature = "juniper", derive(juniper::GraphQLObject))]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct GitClone {
#[serde(with = "url_serde")]
pub url: Url,
pub username: Option<String>,
pub password: Option<String>,
pub path: Option<String>,
}
#[cfg(feature = "juniper")]
#[cfg_attr(feature = "juniper", derive(juniper::GraphQLInputObject))]
#[cfg_attr(feature = "juniper", graphql(name = "GitCloneInput"))]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct Input {
#[serde(with = "url_serde")]
url: Url,
username: Option<String>,
password: Option<String>,
path: Option<String>,
}
#[cfg(feature = "juniper")]
impl From<Input> for GitClone {
fn from(input: Input) -> Self {
Self {
username: input.username,
password: input.password,
url: input.url,
path: input.path,
}
}
}
impl<'a> Processor<'a> for GitClone {
const NAME: &'static str = "Git Clone";
type Error = Error;
type Output = String;
fn validate(&self) -> Result<(), Self::Error> {
if let Some(path) = &self.path {
let path = path::Path::new(path);
path.components().try_for_each(|c| match c {
path::Component::Normal(_) => Ok(()),
_ => Err(Error::Path),
})?;
};
Ok(())
}
fn run(&self, context: &Context) -> Result<Option<Self::Output>, Self::Error> {
let mut callbacks = RemoteCallbacks::new();
let mut fetch_options = FetchOptions::new();
let workspace = context.workspace_path();
let path = self
.path
.as_ref()
.map_or_else(|| workspace.into(), |path| workspace.join(path));
if let (Some(u), Some(p)) = (&self.username, &self.password) {
let _ = callbacks.credentials(move |_, _, _| Cred::userpass_plaintext(u, p));
let _ = fetch_options.remote_callbacks(callbacks);
};
RepoBuilder::new()
.fetch_options(fetch_options)
.clone(self.url.as_str(), &path)
.map(|_| None)
.map_err(Into::into)
}
}
#[derive(Debug)]
pub enum Error {
Path,
Git(git2::Error),
#[doc(hidden)]
__Unknown, }
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Error::Path => write!(f, "Path error: invalid path location"),
Error::Git(ref err) => write!(f, "Git error: {}", err),
Error::__Unknown => unreachable!(),
}
}
}
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match *self {
Error::Path => None,
Error::Git(ref err) => Some(err),
Error::__Unknown => unreachable!(),
}
}
}
impl From<git2::Error> for Error {
fn from(err: git2::Error) -> Self {
Error::Git(err)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn processor_stub() -> GitClone {
GitClone {
username: None,
password: None,
url: Url::parse("http://127.0.0.1").unwrap(),
path: None,
}
}
mod validate {
use super::*;
#[test]
fn no_path() {
let mut processor = processor_stub();
processor.path = None;
processor.validate().unwrap()
}
#[test]
fn relative_path() {
let mut processor = processor_stub();
processor.path = Some("hello/world".to_owned());
processor.validate().unwrap()
}
#[test]
#[should_panic]
fn prefix_path() {
let mut processor = processor_stub();
processor.path = Some("../parent".to_owned());
processor.validate().unwrap()
}
#[test]
#[should_panic]
fn absolute_path() {
let mut processor = processor_stub();
processor.path = Some("/etc".to_owned());
processor.validate().unwrap()
}
}
#[test]
fn test_readme_deps() {
version_sync::assert_markdown_deps_updated!("README.md");
}
#[test]
fn test_html_root_url() {
version_sync::assert_html_root_url_updated!("src/lib.rs");
}
}