Compare commits
2 Commits
c725af45db
...
9d413d4d84
Author | SHA1 | Date | |
---|---|---|---|
|
9d413d4d84 | ||
a330e4454e |
@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
## [0.2.1] - 2024-09-22
|
## [0.2.1] - 2024-09-22
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
- implement git clone
|
||||||
- include vhs demo
|
- include vhs demo
|
||||||
- add interactive search
|
- add interactive search
|
||||||
- implement naive fuzzy matcher
|
- implement naive fuzzy matcher
|
||||||
|
@ -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(())
|
||||||
|
@ -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
|
||||||
|
74
crates/gitnow/src/git_clone.rs
Normal file
74
crates/gitnow/src/git_clone.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
@ -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?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user