feat: implement git clone
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Kasper Juul Hermansen 2024-09-22 15:58:28 +02:00
parent 1eee1d9f3e
commit a330e4454e
Signed by: kjuulh
SSH Key Fingerprint: SHA256:RjXh0p7U6opxnfd3ga/Y9TCo18FYlHFdSpRIV72S/QM
4 changed files with 140 additions and 5 deletions

View File

@ -4,6 +4,7 @@ use crate::{
app::App, app::App,
cache::CacheApp, cache::CacheApp,
fuzzy_matcher::{FuzzyMatcher, FuzzyMatcherApp}, fuzzy_matcher::{FuzzyMatcher, FuzzyMatcherApp},
git_clone::GitCloneApp,
git_provider::Repository, git_provider::Repository,
interactive::InteractiveApp, interactive::InteractiveApp,
projects_list::ProjectsListApp, projects_list::ProjectsListApp,
@ -23,6 +24,7 @@ impl RootCommand {
&mut self, &mut self,
search: Option<impl Into<String>>, search: Option<impl Into<String>>,
cache: bool, cache: bool,
clone: bool,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
tracing::debug!("executing"); tracing::debug!("executing");
@ -41,7 +43,8 @@ impl RootCommand {
} else { } else {
self.app.projects_list().get_projects().await? self.app.projects_list().get_projects().await?
}; };
match search {
let repo = match search {
Some(needle) => { Some(needle) => {
let matched_repos = self let matched_repos = self
.app .app
@ -51,7 +54,9 @@ impl RootCommand {
let repo = matched_repos let repo = matched_repos
.first() .first()
.ok_or(anyhow::anyhow!("failed to find repository"))?; .ok_or(anyhow::anyhow!("failed to find repository"))?;
tracing::info!("selected repo: {}", repo.to_rel_path().display()); tracing::debug!("selected repo: {}", repo.to_rel_path().display());
repo.to_owned()
} }
None => { None => {
let repo = self let repo = self
@ -60,8 +65,14 @@ impl RootCommand {
.interactive_search(&repositories)? .interactive_search(&repositories)?
.ok_or(anyhow::anyhow!("failed to find a repository"))?; .ok_or(anyhow::anyhow!("failed to find a repository"))?;
tracing::info!("selected repo: {}", repo.to_rel_path().display()); tracing::debug!("selected repo: {}", repo.to_rel_path().display());
repo
} }
};
if clone {
self.app.git_clone().clone_repo(&repo).await?;
} }
Ok(()) Ok(())

View File

@ -14,10 +14,49 @@ pub struct Config {
#[derive(Debug, Default, Serialize, Deserialize, PartialEq)] #[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
pub struct Settings { pub struct Settings {
#[serde(default)]
pub projects: Projects,
#[serde(default)] #[serde(default)]
pub cache: Cache, pub cache: Cache,
} }
#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
pub struct Projects {
pub directory: ProjectLocation,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ProjectLocation(PathBuf);
impl From<PathBuf> for ProjectLocation {
fn from(value: PathBuf) -> Self {
Self(value)
}
}
impl From<ProjectLocation> for PathBuf {
fn from(value: ProjectLocation) -> Self {
value.0
}
}
impl Default for ProjectLocation {
fn default() -> Self {
let home = dirs::home_dir().unwrap_or_default();
Self(home.join("git"))
}
}
impl std::ops::Deref for ProjectLocation {
type Target = PathBuf;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug, Default, Serialize, Deserialize, PartialEq)] #[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
pub struct Cache { pub struct Cache {
#[serde(default)] #[serde(default)]
@ -231,6 +270,9 @@ mod test {
#[test] #[test]
fn test_can_parse_config() -> anyhow::Result<()> { fn test_can_parse_config() -> anyhow::Result<()> {
let content = r#" let content = r#"
[settings]
projects = { directory = "git" }
[settings.cache] [settings.cache]
location = ".cache/gitnow" location = ".cache/gitnow"
duration = { days = 2 } duration = { days = 2 }
@ -316,6 +358,9 @@ mod test {
hours: 0, hours: 0,
minutes: 0 minutes: 0
} }
},
projects: Projects {
directory: PathBuf::from("git").into()
} }
} }
}, },
@ -340,7 +385,8 @@ mod test {
gitea: vec![] gitea: vec![]
}, },
settings: Settings { settings: Settings {
cache: Cache::default() cache: Cache::default(),
projects: Projects::default()
} }
}, },
config config

View File

@ -0,0 +1,74 @@
use crate::{app::App, git_provider::Repository};
pub struct GitClone {
app: &'static App,
}
impl GitClone {
pub fn new(app: &'static App) -> Self {
Self { app }
}
pub async fn clone_repo(&self, repository: &Repository) -> anyhow::Result<()> {
let project_path = self
.app
.config
.settings
.projects
.directory
.join(repository.to_rel_path());
if project_path.exists() {
tracing::info!(
"project: {} already exists, skipping clone",
repository.to_rel_path().display()
);
return Ok(());
}
tracing::info!(
"cloning: {} into {}",
repository.ssh_url.as_str(),
&project_path.display().to_string(),
);
let mut cmd = tokio::process::Command::new("git");
cmd.args([
"clone",
repository.ssh_url.as_str(),
&project_path.display().to_string(),
]);
let output = cmd.output().await?;
match output.status.success() {
true => tracing::debug!(
"cloned {} into {}",
repository.ssh_url.as_str(),
&project_path.display().to_string(),
),
false => {
let stdout = std::str::from_utf8(&output.stdout).unwrap_or_default();
let stderr = std::str::from_utf8(&output.stderr).unwrap_or_default();
tracing::error!(
"failed to clone {} into {}, with output: {}, err: {}",
repository.ssh_url.as_str(),
&project_path.display().to_string(),
stdout,
stderr
)
}
}
Ok(())
}
}
pub trait GitCloneApp {
fn git_clone(&self) -> GitClone;
}
impl GitCloneApp for &'static App {
fn git_clone(&self) -> GitClone {
GitClone::new(self)
}
}

View File

@ -13,6 +13,7 @@ mod cache_codec;
mod commands; mod commands;
mod config; mod config;
mod fuzzy_matcher; mod fuzzy_matcher;
mod git_clone;
mod git_provider; mod git_provider;
mod interactive; mod interactive;
mod projects_list; mod projects_list;
@ -28,6 +29,9 @@ struct Command {
#[arg(long = "no-cache", default_value = "false")] #[arg(long = "no-cache", default_value = "false")]
no_cache: bool, no_cache: bool,
#[arg(long = "no-clone", default_value = "false")]
no_clone: bool,
} }
#[derive(Subcommand)] #[derive(Subcommand)]
@ -60,7 +64,7 @@ async fn main() -> anyhow::Result<()> {
Some(_) => todo!(), Some(_) => todo!(),
None => { None => {
RootCommand::new(app) RootCommand::new(app)
.execute(cli.search.as_ref(), !cli.no_cache) .execute(cli.search.as_ref(), !cli.no_cache, !cli.no_clone)
.await?; .await?;
} }
} }