with fuzzy-clone
This commit is contained in:
parent
66105004e0
commit
27f63cc1f9
@ -6,7 +6,7 @@ impl util::Cmd for Auth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn exec(_: &clap::ArgMatches) -> eyre::Result<()> {
|
fn exec(_: &clap::ArgMatches) -> eyre::Result<()> {
|
||||||
util::shell::run(&["gh", "auth", "login"])?;
|
util::shell::run(&["gh", "auth", "login"], None)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
258
crates/github/src/fuzzy_clone.rs
Normal file
258
crates/github/src/fuzzy_clone.rs
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use eyre::Context;
|
||||||
|
|
||||||
|
pub struct FuzzyClone;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct GitHubEntry {
|
||||||
|
org: String,
|
||||||
|
repo: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GitHubEntry {
|
||||||
|
fn from(raw: String) -> Option<Self> {
|
||||||
|
let (org, repo) = raw.split_once("/")?;
|
||||||
|
|
||||||
|
Some(GitHubEntry {
|
||||||
|
org: org.trim().to_string(),
|
||||||
|
repo: repo.trim().to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Settings {
|
||||||
|
orgs: Vec<String>,
|
||||||
|
git_root: String,
|
||||||
|
cache_dir: String,
|
||||||
|
cache_file_path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Settings {
|
||||||
|
fn new() -> eyre::Result<Self> {
|
||||||
|
let mut cache_dir =
|
||||||
|
dirs::cache_dir().ok_or(eyre::anyhow!("could not find a valid cache dir"))?;
|
||||||
|
cache_dir.push("kah-toolkit/github/fc");
|
||||||
|
|
||||||
|
let mut file_path = std::path::Path::new(&cache_dir).to_path_buf();
|
||||||
|
file_path.push("entries");
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
orgs: std::env::var("GITHUB_FC_ORGS")
|
||||||
|
.context("GITHUB_FC_ORGS is not set")?
|
||||||
|
.split(",")
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.collect(),
|
||||||
|
git_root: std::env::var("GITHUB_FC_ROOT").context("GITHUB_FC_ROOT is not set")?,
|
||||||
|
cache_dir: cache_dir.to_string_lossy().to_string(),
|
||||||
|
cache_file_path: file_path.to_string_lossy().to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FuzzyClone {
|
||||||
|
fn get_settings() -> eyre::Result<Settings> {
|
||||||
|
Settings::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_cache() -> eyre::Result<Vec<GitHubEntry>> {
|
||||||
|
let settings = Self::get_settings()?;
|
||||||
|
let entries = std::fs::read_to_string(settings.cache_file_path)?;
|
||||||
|
|
||||||
|
Self::parse_entries(entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_entries(raw: String) -> eyre::Result<Vec<GitHubEntry>> {
|
||||||
|
let entries = raw
|
||||||
|
.replace("\r\n", "\n")
|
||||||
|
.split("\n")
|
||||||
|
.map(|s| {
|
||||||
|
s.split_once("/").map(|(org, repo)| {
|
||||||
|
if let Some((repo, _)) = repo.split_once("\t") {
|
||||||
|
GitHubEntry {
|
||||||
|
org: org.to_string(),
|
||||||
|
repo: repo.to_string(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
GitHubEntry {
|
||||||
|
org: org.to_string(),
|
||||||
|
repo: repo.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.filter(|i| i.is_some())
|
||||||
|
.map(|i| i.unwrap())
|
||||||
|
.collect::<Vec<GitHubEntry>>();
|
||||||
|
|
||||||
|
Ok(entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cache_exists() -> eyre::Result<bool> {
|
||||||
|
let settings = Self::get_settings()?;
|
||||||
|
let cf_path = std::path::Path::new(&settings.cache_file_path);
|
||||||
|
if !cf_path.exists() {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
let metadata_time = cf_path.metadata()?.modified()?;
|
||||||
|
let cur_time = std::time::SystemTime::now() - std::time::Duration::new(60 * 60 * 24, 0);
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"metadata_time: {:?}, cur_time: {:?}",
|
||||||
|
metadata_time, cur_time
|
||||||
|
);
|
||||||
|
|
||||||
|
if metadata_time < cur_time {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_cache(input: &Vec<GitHubEntry>) -> eyre::Result<()> {
|
||||||
|
let settings = Self::get_settings()?;
|
||||||
|
let cache_file_path = std::path::Path::new(&settings.cache_file_path);
|
||||||
|
let mut fs = if cache_file_path.exists() {
|
||||||
|
std::fs::File::create(cache_file_path)?
|
||||||
|
} else {
|
||||||
|
let cache_dir_path = std::path::Path::new(&settings.cache_dir);
|
||||||
|
if !cache_dir_path.exists() {
|
||||||
|
std::fs::create_dir_all(cache_dir_path)?;
|
||||||
|
}
|
||||||
|
std::fs::File::create(cache_file_path)?
|
||||||
|
};
|
||||||
|
|
||||||
|
fs.write_all(
|
||||||
|
input
|
||||||
|
.iter()
|
||||||
|
.map(|ge| format!("{}/{}", ge.org, ge.repo))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\n")
|
||||||
|
.as_bytes(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_entries() -> eyre::Result<Vec<GitHubEntry>> {
|
||||||
|
let mut entries = Vec::new();
|
||||||
|
|
||||||
|
for org in Self::get_settings()?.orgs {
|
||||||
|
let private_entires = util::shell::run_with_input_and_output(
|
||||||
|
&[
|
||||||
|
"gh",
|
||||||
|
"repo",
|
||||||
|
"list",
|
||||||
|
&org,
|
||||||
|
"--visibility",
|
||||||
|
"private",
|
||||||
|
"--limit",
|
||||||
|
"1000",
|
||||||
|
],
|
||||||
|
"".into(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let public_entires = util::shell::run_with_input_and_output(
|
||||||
|
&[
|
||||||
|
"gh",
|
||||||
|
"repo",
|
||||||
|
"list",
|
||||||
|
&org,
|
||||||
|
"--visibility",
|
||||||
|
"public",
|
||||||
|
"--limit",
|
||||||
|
"1000",
|
||||||
|
],
|
||||||
|
"".into(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let private = std::str::from_utf8(private_entires.stdout.as_slice())?;
|
||||||
|
let public = std::str::from_utf8(public_entires.stdout.as_slice())?;
|
||||||
|
let raw_entries = format!("{private}{public}");
|
||||||
|
|
||||||
|
entries.push(Self::parse_entries(raw_entries)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(entries.into_iter().flat_map(|s| s).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clone(chosen: GitHubEntry) -> eyre::Result<std::path::PathBuf> {
|
||||||
|
let mut git_path = std::path::Path::new(&Self::get_settings()?.git_root).to_path_buf();
|
||||||
|
git_path.push(&chosen.org);
|
||||||
|
if !git_path.exists() {
|
||||||
|
std::fs::create_dir_all(&git_path)?;
|
||||||
|
}
|
||||||
|
let mut git_repo_path = git_path.clone();
|
||||||
|
git_repo_path.push(&chosen.repo);
|
||||||
|
if !git_repo_path.exists() {
|
||||||
|
util::shell::run(
|
||||||
|
&[
|
||||||
|
"gh",
|
||||||
|
"repo",
|
||||||
|
"clone",
|
||||||
|
format!("{}/{}", &chosen.org, &chosen.repo).as_str(),
|
||||||
|
git_repo_path
|
||||||
|
.to_str()
|
||||||
|
.ok_or(eyre::anyhow!("could you not transform to path"))?,
|
||||||
|
],
|
||||||
|
Some(util::shell::RunOptions {
|
||||||
|
path: git_path.clone(),
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
util::shell::run(&["git", "pull"], None)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(git_repo_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run() -> eyre::Result<()> {
|
||||||
|
let entries = if !Self::cache_exists()? {
|
||||||
|
let entries = Self::get_entries()?;
|
||||||
|
Self::set_cache(&entries)?;
|
||||||
|
entries
|
||||||
|
} else {
|
||||||
|
Self::get_cache()?
|
||||||
|
};
|
||||||
|
|
||||||
|
let entries_str = entries
|
||||||
|
.iter()
|
||||||
|
.map(|ge| format!("{}/{}", ge.org, ge.repo))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
let chosen = util::shell::run_with_input_and_output(&["fzf"], entries_str)?;
|
||||||
|
let chosen = std::str::from_utf8(&chosen.stdout)?;
|
||||||
|
|
||||||
|
Self::clone(GitHubEntry::from(chosen.to_string()).ok_or(eyre::anyhow!(
|
||||||
|
"could not parse choice as github entry <org>/<repo>"
|
||||||
|
))?)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update() -> eyre::Result<()> {
|
||||||
|
let entries = Self::get_entries()?;
|
||||||
|
Self::set_cache(&entries)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl util::Cmd for FuzzyClone {
|
||||||
|
fn cmd() -> eyre::Result<clap::Command> {
|
||||||
|
Ok(clap::Command::new("fuzzy-clone")
|
||||||
|
.alias("fc")
|
||||||
|
.alias("c")
|
||||||
|
.subcommand(clap::Command::new("update")))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exec(args: &clap::ArgMatches) -> eyre::Result<()> {
|
||||||
|
match args.subcommand() {
|
||||||
|
Some(("update", _)) => Self::update()?,
|
||||||
|
_ => Self::run()?,
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,7 @@ impl Gh {
|
|||||||
let mut cmd_args = vec!["gh", external];
|
let mut cmd_args = vec!["gh", external];
|
||||||
cmd_args.append(&mut raw.iter().map(|s| &**s).collect());
|
cmd_args.append(&mut raw.iter().map(|s| &**s).collect());
|
||||||
|
|
||||||
util::shell::run(cmd_args.as_slice())?;
|
util::shell::run(cmd_args.as_slice(), None)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -29,7 +29,7 @@ impl util::Cmd for Gh {
|
|||||||
match args.subcommand() {
|
match args.subcommand() {
|
||||||
Some((external, args)) => Self::run(external, args),
|
Some((external, args)) => Self::run(external, args),
|
||||||
_ => {
|
_ => {
|
||||||
util::shell::run(&["gh"])?;
|
util::shell::run(&["gh"], None)?;
|
||||||
|
|
||||||
Err(eyre::anyhow!("missing argument"))
|
Err(eyre::anyhow!("missing argument"))
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
mod auth;
|
mod auth;
|
||||||
|
mod fuzzy_clone;
|
||||||
mod gh;
|
mod gh;
|
||||||
|
|
||||||
pub struct GitHub;
|
pub struct GitHub;
|
||||||
@ -17,7 +18,7 @@ impl GitHub {
|
|||||||
let mut cmd_args = vec!["gh", external];
|
let mut cmd_args = vec!["gh", external];
|
||||||
cmd_args.append(&mut raw.iter().map(|s| &**s).collect());
|
cmd_args.append(&mut raw.iter().map(|s| &**s).collect());
|
||||||
|
|
||||||
util::shell::run(cmd_args.as_slice())?;
|
util::shell::run(cmd_args.as_slice(), None)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -26,13 +27,19 @@ impl GitHub {
|
|||||||
impl util::Cmd for GitHub {
|
impl util::Cmd for GitHub {
|
||||||
fn cmd() -> eyre::Result<clap::Command> {
|
fn cmd() -> eyre::Result<clap::Command> {
|
||||||
Ok(clap::Command::new("github")
|
Ok(clap::Command::new("github")
|
||||||
.subcommands(&[auth::Auth::cmd()?, gh::Gh::cmd()?])
|
.subcommands(&[
|
||||||
|
auth::Auth::cmd()?,
|
||||||
|
gh::Gh::cmd()?,
|
||||||
|
fuzzy_clone::FuzzyClone::cmd()?,
|
||||||
|
])
|
||||||
.allow_external_subcommands(true))
|
.allow_external_subcommands(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exec(args: &clap::ArgMatches) -> eyre::Result<()> {
|
fn exec(args: &clap::ArgMatches) -> eyre::Result<()> {
|
||||||
match args.subcommand() {
|
match args.subcommand() {
|
||||||
Some(("auth", subm)) => auth::Auth::exec(subm),
|
Some(("auth", subm)) => auth::Auth::exec(subm),
|
||||||
|
Some(("fuzzy-clone", subm)) => fuzzy_clone::FuzzyClone::exec(subm),
|
||||||
|
Some(("fc", subm)) => fuzzy_clone::FuzzyClone::exec(subm),
|
||||||
Some(("gh", subm)) => gh::Gh::exec(subm),
|
Some(("gh", subm)) => gh::Gh::exec(subm),
|
||||||
Some((external, args)) => Self::run(external, args),
|
Some((external, args)) => Self::run(external, args),
|
||||||
_ => Err(eyre::anyhow!("missing argument")),
|
_ => Err(eyre::anyhow!("missing argument")),
|
||||||
|
@ -6,7 +6,7 @@ impl util::Cmd for Auth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn exec(_: &clap::ArgMatches) -> eyre::Result<()> {
|
fn exec(_: &clap::ArgMatches) -> eyre::Result<()> {
|
||||||
util::shell::run(&["src", "login"])?;
|
util::shell::run(&["src", "login"], None)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ pub struct Sourcegraph;
|
|||||||
|
|
||||||
impl Sourcegraph {
|
impl Sourcegraph {
|
||||||
fn run() -> eyre::Result<()> {
|
fn run() -> eyre::Result<()> {
|
||||||
util::shell::run(&["src"])?;
|
util::shell::run(&["src"], None)?;
|
||||||
|
|
||||||
Err(eyre::anyhow!("missing argument"))
|
Err(eyre::anyhow!("missing argument"))
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ impl util::Cmd for Search {
|
|||||||
let mut cmd_args = vec!["src", "search", external];
|
let mut cmd_args = vec!["src", "search", external];
|
||||||
cmd_args.append(&mut raw.iter().map(|s| &**s).collect());
|
cmd_args.append(&mut raw.iter().map(|s| &**s).collect());
|
||||||
|
|
||||||
util::shell::run(cmd_args.as_slice())?;
|
util::shell::run(cmd_args.as_slice(), None)?;
|
||||||
}
|
}
|
||||||
_ => todo!(),
|
_ => todo!(),
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ impl util::Cmd for Update {
|
|||||||
.split(" ")
|
.split(" ")
|
||||||
.collect::<Vec<&str>>()
|
.collect::<Vec<&str>>()
|
||||||
.as_slice(),
|
.as_slice(),
|
||||||
|
None,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1,10 +1,20 @@
|
|||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
pub fn run(args: &[&str]) -> eyre::Result<()> {
|
pub struct RunOptions {
|
||||||
let output = std::process::Command::new(
|
pub path: std::path::PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(args: &[&str], opts: Option<RunOptions>) -> eyre::Result<()> {
|
||||||
|
let mut cmd = std::process::Command::new(
|
||||||
args.first()
|
args.first()
|
||||||
.ok_or(eyre::anyhow!("could not find first arg"))?,
|
.ok_or(eyre::anyhow!("could not find first arg"))?,
|
||||||
)
|
);
|
||||||
|
|
||||||
|
if let Some(opts) = opts {
|
||||||
|
cmd.current_dir(opts.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = cmd
|
||||||
.args(
|
.args(
|
||||||
args.to_vec()
|
args.to_vec()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
Reference in New Issue
Block a user