diff --git a/.github/workflows/impactifier.yml b/.github/workflows/impactifier.yml index db2b6d9..d7a5877 100644 --- a/.github/workflows/impactifier.yml +++ b/.github/workflows/impactifier.yml @@ -11,17 +11,18 @@ permissions: issues: write pull-requests: write - jobs: impactifier: runs-on: ubuntu-latest steps: + # 1. Checkout the repository with full history - name: Checkout Repository uses: actions/checkout@v4 with: - fetch-depth: 0 + fetch-depth: 0 # Fetch all history for all branches + # 2. Cache Cargo Registry - name: Cache Cargo Registry uses: actions/cache@v3 with: @@ -30,6 +31,7 @@ jobs: restore-keys: | ${{ runner.os }}-cargo-registry- + # 3. Cache Cargo Build - name: Cache Cargo Build uses: actions/cache@v3 with: @@ -38,24 +40,98 @@ jobs: restore-keys: | ${{ runner.os }}-cargo-build- + # 4. Build Impactifier - name: Build Impactifier run: | cargo build --release --manifest-path Cargo.toml + # 5. Run Impactifier and generate diff.json - name: Run Impactifier + id: run_impactifier env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - ./target/release/impactifier --config impactifier-config.yaml + ./target/release/impactifier --tracing-level=0 --from-branch=main --to-branch=refactor + + # 6. (Optional) Output diff.json for debugging + - name: Output diff.json (Debug) + if: ${{ github.event_name == 'pull_request' }} + run: | + cat diff.json + # 7. Post Comment on Pull Request with the diff - name: Post Comment on Pull Request if: github.event_name == 'pull_request' uses: actions/github-script@v6 with: script: | - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: `## Impactifier Report` - }); + const fs = require('fs'); + const path = 'diff.json'; // Path to the diff JSON file + + // Check if the diff file exists + if (!fs.existsSync(path)) { + console.log('No diff.json file found.'); + return; + } + + // Read and parse the diff JSON + let diffData; + try { + const rawData = fs.readFileSync(path, 'utf8'); + diffData = JSON.parse(rawData); + } catch (error) { + console.error('Failed to read or parse diff.json:', error); + return; + } + + // Format the diff for the comment + let formattedDiff = ''; + if (diffData.deltas && Array.isArray(diffData.deltas)) { + diffData.deltas.forEach(delta => { + if (delta.value) { + // Escape backticks in the delta.value to prevent breaking the Markdown + const safeValue = delta.value.replace(/`/g, '\\`'); + formattedDiff += `${safeValue}\n`; + } + }); + } else { + formattedDiff = 'No differences found.'; + } + + // Handle large diffs by truncating (optional) + const maxLength = 60000; // GitHub comment limit + let truncatedDiff = formattedDiff; + if (formattedDiff.length > maxLength) { + truncatedDiff = formattedDiff.substring(0, maxLength) + '\n... (diff truncated)'; + } + + // Create a summary based on the number of deltas + let summary = ''; + if (diffData.deltas && diffData.deltas.length > 0) { + summary = `**Total Changes:** ${diffData.deltas.length} file(s) changed.\n\n`; + } else { + summary = 'No changes detected between the specified branches.\n\n'; + } + + // Create the comment body with summary and diff + const commentBody = `## Impactifier Report + + ${summary} + + \`\`\`diff + ${truncatedDiff} + \`\`\``; + + // Post the comment to the pull request + try { + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: commentBody + }); + console.log('Impactifier report posted successfully.'); + } catch (error) { + console.error('Failed to post Impactifier report:', error); + } + diff --git a/Cargo.lock b/Cargo.lock index 0f157c5..c39b024 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,6 +65,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "anyhow" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" + [[package]] name = "async-trait" version = "0.1.81" @@ -351,6 +357,7 @@ dependencies = [ name = "impactifier" version = "0.1.0" dependencies = [ + "anyhow", "clap", "config", "git2", @@ -358,7 +365,9 @@ dependencies = [ "rhai", "serde", "serde_derive", + "serde_json", "serde_yaml", + "thiserror", "toml", "tracing", "tracing-subscriber", @@ -766,9 +775,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.125" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", @@ -872,18 +881,18 @@ checksum = "a38c90d48152c236a3ab59271da4f4ae63d678c5d7ad6b7714d7cb9760be5e4b" [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 91725db..a2845e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1.0.89" clap = { version = "4.5.16", features = ["derive"] } config = "0.14.0" git2 = "0.19.0" @@ -13,7 +14,9 @@ lazy_static = "1.5.0" rhai = "1.19.0" serde = "1.0.208" serde_derive = "1.0.208" +serde_json = "1.0.128" serde_yaml = "0.9.34" +thiserror = "1.0.64" toml = "0.8.19" tracing = "0.1.40" tracing-subscriber = "0.3.18" diff --git a/src/cli.rs b/src/cli.rs index 1ec7843..8713831 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,16 +1,22 @@ +use anyhow::{anyhow, Context}; +use std::fs::File; +use std::io::Write; use std::path::Path; use clap::Parser; use git2::Repository; +use serde_json::to_string_pretty; +use thiserror::Error; use tracing::{error, info, trace, Level}; -use uuid::Uuid; +use url::Url; -use crate::config::{Config, RepositoryConfig}; -use crate::git::clone_repo; +use crate::config::Config; +use crate::git; use crate::transform::init_registry; use crate::utils; +use anyhow::Result; -#[derive(Parser, Debug)] +#[derive(Parser, Debug, Clone)] #[command( version, about = "Impactifier is a tool for analyzing code changes and assessing their impact.", @@ -26,7 +32,7 @@ use crate::utils; creates repository struct from local path from_branch fallbacks to default after opening if branches specified & local changes detected, optionally includes those - if no branch & commit specified, tries to analyze local changes + if no branch & commit specified, tries (not yet) to analyze local changes if no local changes fails as there is nothing to compare "# )] @@ -38,16 +44,23 @@ struct Args { #[arg(short, long, default_value_t = String::from("impactifier-config.yaml"))] config: String, - #[arg(short, long)] + /// From what branch changes should be compared. + #[arg(long)] from_branch: Option, - #[arg(long)] - to_branch: Option, + /// To what branch changes should be compared. + /// + /// Defaults to "main" + #[arg(long, default_value_t=String::from("main"))] + to_branch: String, + /// Commit of which changes should be analyzed. Takes precedence over + /// branch changes, if `from_branch` or `to_branch` is specified. #[arg(long)] of_commit: Option, - #[arg(long, help = "Fetch latest changes before comparison")] + /// Fetch last changes before impact analysis + #[arg(long)] fetch: bool, /// Sets max tracing level. Available options: @@ -59,133 +72,133 @@ struct Args { /// 4 = Error #[arg(long, default_value_t = 2)] tracing_level: u8, + + #[arg(long, default_value_t=String::from("origin"))] + origin: String, } -pub fn run() -> Result<(), Box> { +// TODO: add more credentials variants +pub enum Credentials<'a> { + UsernamePassword { + username: &'a str, + password: &'a str, + }, +} + +pub fn run() -> Result<(), CliError> { let args = Args::parse(); setup_logging(args.tracing_level); let cfg = match load_config(Path::new(&args.config)) { Ok(config) => config, - Err(e) => return Err(e), + Err(e) => { + error!("initial config load failed. Exciting..."); + return Err(e); + } }; + trace!("Initial config load succeeded"); init_registry(cfg.custom_transform_scripts()); + trace!("Transform functions initialized successfully"); - let _repository = match &cfg.repository.url { - Some(url) => { - match try_retrieve_repo_from_url( - url.as_str(), - args.from_branch, - args.to_branch, - args.of_commit, - cfg.options.clone_into, - &cfg.repository, - ) { - Ok(repo) => repo, - Err(_) => { - return Err(Box::from("Either repository url or path must be specified")); - } - } - } + // TODO: Retrieve properly from args + let mock_credentials = utils::get_mock_credentials(); + + let clone_into = match cfg.options.clone_into.as_deref() { + Some(path) => path, + None => Path::new("cloned_repository"), + }; + + let repository_retrieval_result = match cfg.repository.url { + Some(url) => try_retrieve_repo_from_url(mock_credentials, &url, clone_into), None => match &cfg.repository.path { - Some(path) => { - match try_retrieve_repo_from_path( - (*path) - .to_str() - .expect("Path is expected to be validated during serialization"), - ) { - Ok(repo) => repo, - Err(_) => { - return Err(Box::from("Either repository url or path must be specified")); - } - } - } + Some(path) => try_retrieve_repo_from_path(path), None => { - error!("Repository url and path are unspecified"); - return Err(Box::from("Either repository url or path must be specified")); + return Err(CliError::InvalidArgs { + err: Some(anyhow!("Either path or url must be specified")), + }); } }, }; - Ok(()) -} + let repository = match repository_retrieval_result { + Ok(repository) => repository, + Err(err) => return Err(CliError::Unknown { err: Some(err) }), + }; + trace!("Successfully retrieved repository"); + + if let Err(fetch_err) = git::fetch_remote(&repository, &args.origin, &mock_credentials) { + error!("Failed to fetch remote"); + return Err(CliError::Unknown { + err: Some(fetch_err), + }); + } + trace!("Successfully fetched remote"); + + // TODO: Support other DiffOptions + // + // Current one is temporary, just for testing purposes + let diff = match git::extract_difference( + &repository, + &git::DiffOptions::Branches { + from: &args.from_branch.unwrap(), + to: &args.to_branch, + }, + ) { + Ok(diff) => diff, + Err(err) => { + error!("Failed to extract difference"); + return Err(CliError::Unknown { err: Some(err) }); + } + }; + trace!("Successfuly extracted difference"); + + // Temporary, for testing purposes + let serialized_diff = to_string_pretty(&diff).unwrap(); + + let mut file = File::create("./diff.json").unwrap(); + file.write_all(serialized_diff.as_bytes()).unwrap(); -fn try_retrieve_repo_from_path(path: &str) -> Result> { - trace!( - "attempt to retireve repo path specified repository.\nPath:{}", - path - ); - todo!(); + Ok(()) } -fn try_retrieve_repo_from_url( - url: &str, - from_branch: Option, - to_branch: Option, - commit_id: Option, - clone_into: Option>, - config: &RepositoryConfig, -) -> Result> { - trace!("attempt to start from url-specified repository"); - match (from_branch, to_branch) { - (None, None) => { - info!("No branches specified"); - match commit_id { - Some(_commit_id) => todo!(), //try_analyze_commit(&commit_id), - None => { - error!("No commit specified. Nothing to analyze"); - Err(Box::from( - "No branches and no commit specified. Nothing to analyze.", - )) - } - } +fn try_retrieve_repo_from_path(path: &Path) -> Result { + match git::open_repo(&path) { + Ok(repository) => { + info!("sucessfully retrieved repository from path"); + Ok(repository) } - (None, Some(_)) => todo!(), - (Some(_), None) => { - error!("Incorrect CLI arguments. Specifying `from_branch` requires `to_branch` to be specified"); - Err(Box::from("Incorrect arguments")) - } - (Some(from_branch), Some(to_branch)) => { - info!( - "Attempting to compare branch {} with branch {}", - from_branch, to_branch - ); - info!("Attempting to clone repository from url: {}", url); - let clone_into_path = &clone_into.unwrap_or_else(|| { - let path = Path::new(&format!("repository{}", Uuid::new_v4())).into(); - trace!("set fallback clone_into path to {:?}", path); - path - }); - match utils::prepare_directory(&clone_into_path) { - Ok(_) => { - trace!("Starting to clone repository"); - let cloned_repo = - match clone_repo(&config, &clone_into_path, Some(&from_branch)) { - Ok(repo) => repo, - Err(e) => { - error!("Failed to clone repository. error: {}", e); - return Err(e.into()); - } - }; - info!("Repository cloned successfuly"); - Ok(cloned_repo) - } - Err(e) => { - error!("Failed to prepare directory for cloning"); - return Err(e.into()); - } - } + Err(err) => { + return Err(anyhow!( + "Failed to retrieve repository from path: {:?}.\nError:{}", + path, + err, + )); } } } -fn try_analyze_commit(_commit_id: &str) -> Result<(), Box> { - todo!() -} +fn try_retrieve_repo_from_url( + credentials: &Credentials, + url: &Url, + clone_into: &Path, +) -> Result { + trace!("attempt to start from url-specified repository"); + utils::prepare_directory(&clone_into) + .with_context(|| "Failed to prepare directory for cloning")?; + + trace!("Starting to clone repository"); + let cloned_repo = match git::clone_repo(&credentials, url, &clone_into) { + Ok(repo) => repo, + Err(err) => { + return Err(anyhow!( + "Failed to clone repository from url.\nError: {}", + err + )); + } + }; -fn try_analyze_local_changes() -> Result<(), Box> { - todo!() + Ok(cloned_repo) } fn setup_logging(tracing_level: u8) { @@ -203,16 +216,26 @@ fn setup_logging(tracing_level: u8) { .init(); } -fn load_config(path: &Path) -> Result> { +fn load_config(path: &Path) -> Result { trace!("Starting loading config from {:?}", path); match Config::load_from_file(path) { Ok(config) => { info!("Config loaded successfully"); Ok(config) } - Err(e) => { + Err(err) => { error!("Failed to read configuration from {:?}", path); - return Err(e.into()); + return Err(CliError::InvalidConfigPath { err: Some(err) }); } } } + +#[derive(Error, Debug)] +pub enum CliError { + #[error("Invalid arguments. Error:{:?}", err)] + InvalidArgs { err: Option }, + #[error("Config can not be retrieved")] + InvalidConfigPath { err: Option }, + #[error("Unknown error: {:?}", err)] + Unknown { err: Option }, +} diff --git a/src/config.rs b/src/config.rs index 75c9091..6c1691a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,12 +1,19 @@ +use anyhow::Result; use serde::{Deserialize, Deserializer}; +use tracing::error; use std::cmp; -use std::env; use std::fmt; use std::path::Path; -use std::path::PathBuf; +use thiserror::Error; use tracing::debug; use url::Url; +#[derive(Error, Debug)] +pub enum ConfigError { + #[error("Failed to read config from path: {}. Error:{}", path, msg)] + ReadFailure { path: String, msg: String }, +} + #[derive(Debug, Deserialize)] pub struct Config { pub repository: RepositoryConfig, @@ -20,7 +27,6 @@ pub struct RepositoryConfig { pub url: Option, pub path: Option>, pub access_token: Option, - pub branch: String, } #[derive(Debug, Deserialize)] @@ -28,17 +34,6 @@ pub struct OptionsConfig { pub clone_into: Option>, } -#[derive(Debug, Deserialize)] -pub struct Trigger { - pub path: Box, - - #[serde(default)] - pub pattern: Option, - - #[serde(default)] - pub analyze_dependencies: bool, -} - #[derive(Debug, Deserialize)] pub struct TransformStep { pub name: String, @@ -48,18 +43,12 @@ pub struct TransformStep { #[derive(Debug, Deserialize)] pub struct Transform { - pub name: String, + // pub name: String, #[serde(default)] pub steps: Vec, } -#[derive(Debug, Deserialize)] -pub struct Matcher { - pub path: PathBuf, - pub pattern: String, -} - #[derive(Debug, Deserialize)] pub enum AlertLevel { Info, @@ -69,17 +58,13 @@ pub enum AlertLevel { #[derive(Debug, Deserialize)] pub struct Action { - pub alert_level: AlertLevel, - pub message: String, + // pub alert_level: AlertLevel, + // pub message: String, } #[derive(Debug, Deserialize)] pub struct Rule { - pub name: String, - pub trigger: Trigger, pub transform: Transform, - pub matcher: Matcher, - pub action: Action, } pub struct CustomStep { @@ -88,18 +73,22 @@ pub struct CustomStep { } impl Config { - pub fn load_from_file(file_path: &Path) -> Result> { + // TODO(wiktor.zajac) improve error handling + pub fn load_from_file(file_path: &Path) -> Result { let yaml_content = match std::fs::read_to_string(file_path) { Ok(content) => { debug!("Succesfully read yaml config file:\n{}", content); content } Err(e) => { - return Err(e.into()); + error!("Failed to read yaml config"); + return Err(ConfigError::ReadFailure { + path: String::from(file_path.to_string_lossy()), + msg: e.to_string(), + } + .into()) } }; - let yaml_content = replace_env_vars(&yaml_content); - debug!("replaced env variables in yaml config"); let cfg = serde_yaml::from_str(&yaml_content)?; debug!("Deserialized config:\n{}", cfg); @@ -124,7 +113,7 @@ impl Config { .and_then(|args| args.get("script")) .and_then(|script_value| script_value.as_str()) .map(|s| s.to_string()) - .unwrap() + .unwrap(), }) }) .collect(); @@ -172,18 +161,6 @@ impl fmt::Display for Config { } } -// TODO: Probably could be globally handled with regex, -// but it first needs to be ensured that the performance -// will not be affected. If somehow the regex will affect -// performance strong enough, replacable variables should -// be predefined instead of being hardcoded here. -fn replace_env_vars(yaml_content: &str) -> String { - yaml_content.replace( - "${GITHUB_ACCESS_TOKEN}", - &env::var("GITHUB_ACCESS_TOKEN").unwrap_or_default(), - ) -} - fn deserialize_url<'a, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'a>, diff --git a/src/git.rs b/src/git.rs index 0e4e1fc..8148823 100644 --- a/src/git.rs +++ b/src/git.rs @@ -1,35 +1,152 @@ -use std::{env, path::Path}; +use anyhow::Result; +use serde::Serialize; +use std::path::Path; +use thiserror::Error; use git2::{Cred, RemoteCallbacks, Repository}; -use tracing::error; +use std::str; +use tracing::{error, info, trace}; +use url::Url; -use crate::config::RepositoryConfig; +use crate::cli::Credentials; + +#[derive(Error, Debug)] +pub enum GitError { + #[error("Failed to authorize git request, due to authentication failure. Error:{err}")] + NoAccess { err: git2::Error }, + #[error( + "Failed to clone repository from url {} to given path: {}.\nError: {}", + url, + path, + err + )] + CloneFailure { + url: String, + path: String, + err: git2::Error, + }, + #[error("Failed to open repository from path: {}. Error: {}", path, err)] + OpenRepositoryFailure { path: String, err: git2::Error }, +} + +#[derive(Debug, Serialize)] +pub struct Diff { + pub deltas: Vec, +} + +#[derive(Debug, Serialize)] +pub struct FileDelta { + pub value: String, +} + +impl FileDelta { + fn from(value: String) -> Self { + Self { value } + } +} + +pub enum DiffOptions<'a> { + Branches { from: &'a str, to: &'a str }, +} + +pub fn extract_difference(repo: &Repository, options: &DiffOptions) -> Result { + match options { + DiffOptions::Branches { from, to } => extract_difference_branches(repo, from, to), + } +} + +pub fn fetch_remote(repo: &Repository, remote_name: &str, credentials: &Credentials) -> Result<()> { + let mut remote = repo.find_remote(remote_name)?; + + let mut callback = RemoteCallbacks::new(); + callback.credentials(|_url, _username_from_url, _allowed_types| credentials.into()); + + let mut fetch_options = git2::FetchOptions::new(); + fetch_options.remote_callbacks(callback); + + let refspecs = ["+refs/heads/*:refs/remotes/origin/*"]; + + remote.fetch(&refspecs, Some(&mut fetch_options), None)?; + + Ok(()) +} + +pub fn extract_difference_branches( + repo: &Repository, + from_branch: &str, + to_branch: &str, +) -> Result { + // TODO: Those refs values most likely should not be hardcoded + let ref_from = repo.find_reference(&format!("refs/heads/{}", from_branch))?; + let ref_to = repo.find_reference(&format!("refs/remotes/origin/{}", to_branch))?; + + let commit_a = ref_from.peel_to_commit()?; + let commit_b = ref_to.peel_to_commit()?; + + let tree_a = commit_a.tree()?; + let tree_b = commit_b.tree()?; + + let diff = repo.diff_tree_to_tree(Some(&tree_a), Some(&tree_b), None)?; + let mut diff_output = Vec::new(); + diff.print(git2::DiffFormat::Patch, |_delta, _hunk, line| { + diff_output.extend_from_slice(line.content()); + true + })?; + + let diff_str = str::from_utf8(&diff_output) + .map_err(|e| git2::Error::from_str(&format!("UTF-8 conversion error: {}", e)))? + .to_string(); + + Ok(Diff { + deltas: vec![FileDelta::from(diff_str)], + }) +} + +pub fn open_repo(path: &Path) -> Result { + info!("start opening repository"); + + match Repository::open(path) { + Ok(repository) => { + trace!("repository opened successfuly"); + Ok(repository) + } + Err(err) => { + error!("failed to open repository"); + Err(GitError::OpenRepositoryFailure { + path: String::from(path.to_string_lossy()), + err, + }) + } + } +} + +impl Credentials<'_> { + fn into(&self) -> Result { + let credentials = match self { + Credentials::UsernamePassword { username, password } => { + Cred::userpass_plaintext(&username, &password) + } + }; + + match credentials { + Ok(credentials) => Ok(credentials), + Err(err) => Err(err), + } + } +} pub fn clone_repo( - options: &RepositoryConfig, + credentials: &Credentials, + url: &Url, clone_into: &Path, - branch: Option<&str>, -) -> Result> { - let token = match options.access_token.to_owned() { - Some(token) => token, - None => match env::var("GITHUB_ACCESS_TOKEN_2") { - Ok(token) => token, - Err(_) => { - return Err(Box::from("No access token provided")); - } - }, - }; +) -> Result { + info!("start cloning repository"); let mut callbacks = RemoteCallbacks::new(); - callbacks.credentials(|_url, _username_from_url, _allowed_types| { - Cred::userpass_plaintext("wzslr321", &token) - }); + callbacks.credentials(|_url, _username_from_url, _allowed_types| credentials.into()); + trace!("Callback credentials set to userpass_plaintext"); let mut builder = git2::build::RepoBuilder::new(); - builder.bare(true); - if let Some(branch) = branch { - builder.branch(&branch); - } let mut fetch_options = git2::FetchOptions::new(); fetch_options.remote_callbacks(callbacks); @@ -37,14 +154,19 @@ pub fn clone_repo( builder.fetch_options(fetch_options); - match &options.url { - Some(url) => match builder.clone(url.as_str(), &clone_into) { - Ok(repository) => Ok(repository), - Err(e) => Err(e.into()), - }, - None => { - error!("Failed to clone the repository. Url not specified."); - Err(Box::from("Repository url not specified")) + match builder.clone(url.as_str(), &clone_into) { + Ok(repository) => Ok(repository), + Err(e) => { + error!("failed to clone repository"); + let err = match e.code() { + git2::ErrorCode::Auth => GitError::NoAccess { err: e }, + _ => GitError::CloneFailure { + url: url.to_string(), + path: String::from(clone_into.to_string_lossy()), + err: e, + }, + }; + Err(err) } } } diff --git a/src/main.rs b/src/main.rs index 6e7638f..32ac406 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,8 +4,9 @@ mod git; mod utils; mod transform; -use std::error::Error; +use cli::CliError; +use anyhow::Result; -fn main() -> Result<(), Box> { - cli::run() +fn main() -> Result<(), CliError> { + cli::run() } diff --git a/src/transform.rs b/src/transform.rs index c4f01cf..6467ee4 100644 --- a/src/transform.rs +++ b/src/transform.rs @@ -1,9 +1,10 @@ use crate::config::CustomStep; +use anyhow::Result; use rhai::{Dynamic, Engine, Map, Scope}; -use tracing::trace; use std::collections::HashMap; use std::path::PathBuf; use std::sync::Mutex; +use tracing::trace; #[derive(Debug, Clone)] pub struct Context { @@ -34,13 +35,15 @@ fn register_transform(name: &str, func: Box) { } pub fn init_registry(custom_steps: Option>) { - trace!("init_registry started"); + trace!("Starting to register transform scripts functions"); + register_transform("toUpperCase", Box::new(ToLowerCase)); register_transform("replace", Box::new(Replace)); - trace!("standard functions registered"); + trace!("Standard functions registered"); + if let Some(steps) = custom_steps { for step in steps { - trace!("initializing custom function {}", &step.name); + trace!("Initializing custom function {}", &step.name); register_transform( &step.name, Box::new(CustomFunction { @@ -49,7 +52,6 @@ pub fn init_registry(custom_steps: Option>) { ); } } - trace!("init_registry finished"); } pub struct ToLowerCase; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 2263e79..36c25f0 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,7 +1,10 @@ use std::{fs, path::Path}; use tracing::{info, trace}; +use anyhow::Result; -pub fn prepare_directory(path: &Path) -> Result<(), Box> { +use crate::cli::Credentials; + +pub fn prepare_directory(path: &Path) -> Result<()> { if path.exists() { if path.read_dir()?.next().is_some() { info!("Directory is not empty, removing existing files..."); @@ -15,3 +18,10 @@ pub fn prepare_directory(path: &Path) -> Result<(), Box> trace!("Successfully prepared directory for cloning"); Ok(()) } + +pub fn get_mock_credentials<'a>() -> &'a Credentials<'a> { + &Credentials::UsernamePassword { + username: "wzslr321", + password: "TEST", + } +}