diff --git a/dotme-cli/src/main.rs b/dotme-cli/src/main.rs index 719ddb5..f7c39a1 100644 --- a/dotme-cli/src/main.rs +++ b/dotme-cli/src/main.rs @@ -5,8 +5,10 @@ mod dotme_panic; +use std::path::PathBuf; + use clap::{Parser, Subcommand}; -use dotme_core::cmd::init; +use dotme_core::cmd; #[derive(Debug, Parser)] #[command(name = "dotme")] @@ -20,8 +22,11 @@ struct Cli { enum Commands { #[command(about = "Setup a new dotfile repo")] Init, - #[command(about = "Clone and install your dotfiles")] - Clone, + #[command( + about = "Clone and install your dotfiles", + arg_required_else_help = true + )] + Clone { remote: PathBuf }, #[command(about = "Configure your existing repo")] Config, #[command(about = "Uninstall your dotfiles and restore previous files")] @@ -33,14 +38,15 @@ fn main() { let args = Cli::parse(); match args.command { Commands::Init => { - match init::init() { + match cmd::init() { Ok(()) => println!("Init Complete!"), Err(err) => println!("{}", err), }; } - Commands::Clone => { - todo!() - } + Commands::Clone { remote } => match cmd::clone(&remote) { + Ok(()) => println!("Clone Complete!"), + Err(err) => println!("{}", err), + }, Commands::Config => { todo!() } diff --git a/dotme-core/src/cmd/clone.rs b/dotme-core/src/cmd/clone.rs new file mode 100644 index 0000000..caefc0f --- /dev/null +++ b/dotme-core/src/cmd/clone.rs @@ -0,0 +1,56 @@ +use std::path::{Path, PathBuf}; + +use anyhow::Result; +use dialoguer::{theme::ColorfulTheme, Input, Password}; +use git2::Cred; + +use crate::{ + path_utils::{is_ssh_path, parse_into_absolute}, + repo::Repo, +}; + +pub fn clone(remote: &Path) -> Result<()> { + println!("\n\nCloning a repository"); + + let default_path = String::from("$HOME/.cfg"); + + let repo_location: String = Input::with_theme(&ColorfulTheme::default()) + .with_prompt("Repo location") + .default(default_path) + .validate_with(|input: &String| { + let path = PathBuf::from(input); + if path.is_dir() { + Err("Directory already exists") + } else { + Ok(()) + } + }) + .interact_text()?; + + let repo_path = PathBuf::from(repo_location); + let absolute_repo_path = parse_into_absolute(&repo_path); + let is_remote_ssh = is_ssh_path(remote); + + let cred; + if is_remote_ssh { + let default_ssh_key = String::from("~/.ssh/id_rsa"); + let ssh_key = Input::with_theme(&ColorfulTheme::default()) + .with_prompt("SSH Key") + .default(default_ssh_key) + .interact_text()?; + let ssh_password = Password::with_theme(&ColorfulTheme::default()) + .with_prompt("SSH Password") + .interact(); + cred = Cred::ssh_key( + username_from_url.unwrap(), + None, + Path::new(&format!("{}/.ssh/id_rsa", env::var("HOME").unwrap())), + None, + )?; + } + + // let _result = Repo::clone(remote, &absolute_repo_path)?; + // println!("Found {}", remote); + + Ok(()) +} diff --git a/dotme-core/src/cmd/mod.rs b/dotme-core/src/cmd/mod.rs index 43763f1..f8aa67d 100644 --- a/dotme-core/src/cmd/mod.rs +++ b/dotme-core/src/cmd/mod.rs @@ -1 +1,5 @@ -pub mod init; +mod clone; +mod init; + +pub use clone::clone; +pub use init::init; diff --git a/dotme-core/src/path_utils.rs b/dotme-core/src/path_utils.rs index d662ba5..9f5f051 100644 --- a/dotme-core/src/path_utils.rs +++ b/dotme-core/src/path_utils.rs @@ -11,7 +11,6 @@ pub fn base_dirs() -> Result { BaseDirs::new().ok_or(anyhow!("Cannot get base directories",)) } -/// This function does a thing pub fn parse_into_absolute(path: &Path) -> PathBuf { path.strip_prefix("$HOME") .ok() @@ -21,3 +20,7 @@ pub fn parse_into_absolute(path: &Path) -> PathBuf { }) .unwrap_or(path.to_path_buf()) } + +pub fn is_ssh_path(path: &Path) -> bool { + !path.starts_with("http") +} diff --git a/dotme-core/src/repo.rs b/dotme-core/src/repo.rs index 727f738..3e6542b 100644 --- a/dotme-core/src/repo.rs +++ b/dotme-core/src/repo.rs @@ -1,8 +1,8 @@ -use std::path::Path; +use std::{env, path::Path}; use crate::path_utils::{self}; use anyhow::{anyhow, Result}; -use git2::{ErrorCode, Repository}; +use git2::{Cred, ErrorCode, FetchOptions, RemoteCallbacks, Repository}; /// A struct that wraps `crate::config::Config` and `git2::Repository` /// making it easy to load the dotme repo. @@ -63,6 +63,28 @@ impl Repo { config.set_str("status.showuntrackedfiles", "no")?; Ok(()) } + + pub fn clone(url: &str, destination: &Path) -> Result { + let mut callbacks = RemoteCallbacks::new(); + callbacks.credentials(|_url, username_from_url, _allowed_types| { + Cred::ssh_key( + username_from_url.unwrap(), + None, + Path::new(&format!("{}/.ssh/id_rsa", env::var("HOME").unwrap())), + None, + ) + }); + + let mut fo = FetchOptions::new(); + fo.remote_callbacks(callbacks); + + let mut builder = git2::build::RepoBuilder::new(); + builder.bare(true); + builder.fetch_options(fo); + + let repo = builder.clone(url, destination)?; + Ok(Repo { repo }) + } } #[cfg(test)]