Skip to content

Commit

Permalink
wip: continue impl
Browse files Browse the repository at this point in the history
  • Loading branch information
cilki committed May 8, 2024
1 parent cdbfa38 commit f7cf064
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 56 deletions.
43 changes: 24 additions & 19 deletions src/cmd/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use std::error::Error;
use std::str::FromStr;

use anyhow::bail;
use anyhow::Result;
use regex::Regex;
Expand All @@ -10,32 +13,34 @@ pub mod open;
#[derive(Debug, Eq, PartialEq)]
pub struct RepoPattern {
/// The workspace name
pub workspace: Option<String>,

/// The remote name
pub remote: Option<String>,
pub workspace_name: Option<String>,

/// The repo path
pub path: String,
}

impl RepoPattern {
pub fn parse(path: &str) -> Result<Self> {
match Regex::new(r"^([^/]+:)?([^/]+)?(.*)$")?.captures(path) {
Some(captures) => {captures.len();
Ok(Self {
workspace: captures
impl FromStr for RepoPattern {
type Err = Box<dyn Error>;

fn from_str(path: &str) -> std::prelude::v1::Result<Self, Self::Err> {
match Regex::new(r"^([^/]+:)?(.*)$")?.captures(path) {
Some(captures) => Ok(Self {
workspace_name: captures
.get(1)
.map(|m| m.as_str().to_string())
.map(|s| s[..s.len() - 1].to_string()),
path: captures.get(2).unwrap().as_str().to_string(),
remote: todo!(), },
}),
None => bail!("Invalid repository path pattern"),
None => Err("Invalid repository path pattern".into()),
}
}
}

pub fn maybe_provider(&self) -> Option<(String, String)> {
impl RepoPattern {
/// Try to parse the remote from the repo path. A `RepoPattern` doesn't
/// have enough information to know whether this is actually the remote, so
/// leave that decision up to the caller.
pub fn maybe_remote(&self) -> Option<(String, String)> {
let parts: Vec<&str> = self.path.splitn(2, "/").collect();
if parts.len() == 2 && parts[0] != "" {
Some((parts[0].to_string(), parts[1].to_string()))
Expand All @@ -48,21 +53,21 @@ impl RepoPattern {
#[cfg(test)]
mod test_repo_pattern {
use super::RepoPattern;
use anyhow::Result;
use std::error::Error;

#[test]
fn test_parse() -> Result<()> {
fn test_parse() -> Result<(), Box<dyn Error>> {
assert_eq!(
RepoPattern::parse("workspace12:remote1/abc/123")?,
str::parse::<RepoPattern>("workspace12:remote1/abc/123")?,
RepoPattern {
workspace: Some("workspace12".to_string()),
workspace_name: Some("workspace12".to_string()),
path: "remote1/abc/123".to_string()
}
);
assert_eq!(
RepoPattern::parse("123")?,
str::parse::<RepoPattern>("123")?,
RepoPattern {
workspace: None,
workspace_name: None,
path: "123".to_string()
}
);
Expand Down
23 changes: 18 additions & 5 deletions src/cmd/open.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,37 @@
use crate::cmd::RepoPattern;
use crate::Config;
use anyhow::Result;
use anyhow::{bail, Result};
use cmd_lib::*;
use tracing::debug;

/// Open one or more repositories in the workspace
pub fn run_open(config: &Config, path: Option<String>) -> Result<()> {
debug!("Open requested for: {:?}", &path);
let path = match path {
Some(path) => path,
None => bail!("No pattern given"),
};

let pattern = match str::parse::<RepoPattern>(&path) {
Ok(pattern) => pattern,
Err(_) => bail!("Failed to parse pattern"),
};

// Check the cache first
if let Some(cache) = &config.cache {}
debug!(pattern = ?pattern, "Opening repos");

// Check the cache
if let Some(cache) = &config.cache {
// TODO cache.search
}

let repos = match path {
Some(p) => config.resolve_local(&RepoPattern::parse(&p)?), // TODO remote
Some(p) => config.search_local(&RepoPattern::parse(&p)?), // TODO remote
None => Vec::new(),
};

for repo in repos {
let out = run_fun!(git -C $repo status --porcelain)?;
println!("{}", out);
}

Ok(())
}
69 changes: 37 additions & 32 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use sha2::{Digest, Sha512};
use std::path::Path;
use std::path::PathBuf;
use tracing::debug;
use tracing::warn;

pub mod cmd;
pub mod remote;
Expand All @@ -23,8 +24,6 @@ pub struct Config {
}

impl Default for Config {
// Place the cache according to platform

fn default() -> Self {
let home = home::home_dir().expect("the home directory exists");

Expand Down Expand Up @@ -68,43 +67,34 @@ impl Config {
Ok(config)
}

/// Resolve a repository pattern against local repositories.
pub fn resolve_local(&self, pattern: &RepoPattern) -> Vec<PathBuf> {
// Either find the specified workspace or choose the first available
let workspace: &Workspace = match &pattern.workspace {
Some(workspace_name) => self
.workspace
.iter()
.find(|&w| match &w.name {
Some(name) => name == workspace_name,
None => false,
})
.unwrap(),
None => self.workspace.first().unwrap(),
};
/// Find a configured workspace by name.
pub fn workspace_by_name(&self, workspace_name: &str) -> Option<&Workspace> {
self.workspace.iter().find(|&w| match &w.name {
Some(name) => name == workspace_name,
None => false,
})
}

let (remote, path) = match pattern.maybe_provider() {
Some((provider, path)) => {
debug!("{} {}", provider, path);
(
workspace
.remotes
.iter()
.find(|&p| p.name == provider)
.unwrap(),
path,
)
/// Resolve a repository pattern against local repositories.
pub fn search_local(&self, pattern: &RepoPattern) -> Result<Vec<PathBuf>> {
match &pattern.workspace_name {
Some(workspace_name) => {
if let Some(workspace) = self.workspace_by_name(&workspace_name) {
workspace.search(pattern)
} else {
bail!("Workspace not found")
}
}
None => (workspace.remotes.first().unwrap(), pattern.path.clone()),
};

find_git_dir(&format!("{}/{}/{}", workspace.path, remote.name, path)).unwrap()
None => {
todo!()
}
}
}
}

/// Recursively find "top-level" git repositories.
fn find_git_dir(path: &str) -> Result<Vec<PathBuf>> {
debug!("Searching for git repositories in: {}", path);
debug!(path = %path, "Recursively searching for git repositories");
let mut found: Vec<PathBuf> = Vec::new();

match std::fs::metadata(format!("{}/.git", &path)) {
Expand Down Expand Up @@ -153,6 +143,21 @@ impl Workspace {
.unwrap(),
}
}

/// Search the workspace for local repos matching the given pattern.
pub fn search(&self, pattern: &RepoPattern) -> Result<Vec<PathBuf>> {
let repos = find_git_dir(&format!("{}/{}", self.path, pattern.path))?;

// Try each remote if there were no matches immediately
// if repos.len() == 0 {
// for remote in self.remotes.iter() {
// let repos = find_git_dir(&format!("{}/{}/{}", self.path, remote.name(), pattern.path))?;
// if repos.len() == 0 {}
// }
// }

Ok(repos)
}
}

/// Caches repositories that are dropped from a `Workspace` in a separate directory.
Expand Down
5 changes: 5 additions & 0 deletions src/remote/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ pub trait ListRepos {
fn list_repo_paths(&self) -> Result<Vec<String>>;
}

#[enum_dispatch]
pub trait Metadata {
fn name(&self) -> String;
}

#[derive(Debug, Serialize, Deserialize)]
#[enum_dispatch(ListRepos)]
pub enum Remote {
Expand Down

0 comments on commit f7cf064

Please sign in to comment.