31 Commits

Author SHA1 Message Date
27a4d2676d fix(deps): update all dependencies
Some checks failed
renovate/artifacts Artifact file update failure
continuous-integration/drone/push Build encountered an error
continuous-integration/drone/pr Build encountered an error
2026-02-11 01:31:14 +00:00
43aac23453 feat: install
Some checks failed
continuous-integration/drone/push Build encountered an error
2026-02-10 12:40:51 +01:00
f90dc1e8d5 feat: remove nightly features
Some checks failed
continuous-integration/drone/pr Build encountered an error
continuous-integration/drone/push Build encountered an error
2026-02-02 14:35:51 +01:00
aef7b65e0c feat: bogus commit
Some checks failed
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build encountered an error
2025-11-13 02:10:31 +01:00
313b971c74 feat: link to website 2025-11-13 02:04:51 +01:00
167d71024d fix(deps): update rust crate bytes to v1.10.1
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-03-06 01:41:55 +00:00
cuddle-please
e2102bfd99 chore(release): 0.3.5
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2025-03-04 12:16:38 +00:00
716adfab8b feat: allow clone all
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-03-04 13:11:37 +01:00
c77a9dcbef chore(deps): update all dependencies
Some checks failed
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is failing
2025-03-03 05:39:38 +00:00
7077c623ca chore(deps): update all dependencies
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-02-21 01:43:38 +00:00
80614fa667 fix(deps): update rust crate serde to v1.0.218
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-02-20 05:41:56 +00:00
6ff93b100a chore(deps): update rust crate clap to v4.5.29
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-02-13 05:36:34 +00:00
76b617fc2d fix(deps): update tokio-prost monorepo to v0.13.5
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-02-13 01:38:36 +00:00
d699432f14 fix(deps): update rust crate termwiz to 0.23.0
Some checks failed
renovate/artifacts Artifact file update failure
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-02-11 01:41:18 +00:00
5c46cca96f fix(deps): update all dependencies
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-02-06 01:41:02 +00:00
8d071981a3 fix(deps): update rust crate uuid to v1.13.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-02-05 05:43:47 +00:00
8f9e40b98f fix(deps): update all dependencies
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-02-04 01:41:54 +00:00
98d4402461 fix(deps): update rust crate async-trait to v0.1.86
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-02-01 05:40:38 +00:00
816a65a04c fix(deps): update rust crate uuid to v1.12.1
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-01-21 05:41:33 +00:00
d893e7e21a chore(deps): update rust crate clap to v4.5.27
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-01-21 01:38:57 +00:00
2a3c4fbb83 fix(deps): update rust crate octocrab to 0.43.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-01-18 01:38:50 +00:00
9d459f10d5 fix(deps): update rust crate uuid to v1.12.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-01-15 01:44:24 +00:00
5aa5702272 fix(deps): update rust crate dirs to v6
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-01-13 01:37:39 +00:00
45b45420f8 fix(deps): update rust crate uuid to v1.11.1
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-01-10 05:33:24 +00:00
b49da1ca71 chore(deps): update rust crate clap to v4.5.26
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-01-10 01:34:25 +00:00
3c63992632 chore(deps): update rust crate tokio to v1.43.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-01-09 01:36:46 +00:00
aaf9cd414e chore: update example
All checks were successful
continuous-integration/drone/push Build is passing
2025-01-08 23:03:41 +01:00
0f30aaadf5 chore(release): v0.3.4 (#57)
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
chore(release): 0.3.4

Co-authored-by: cuddle-please <bot@cuddle.sh>
Reviewed-on: https://git.front.kjuulh.io/kjuulh/gitnow/pulls/57
2025-01-08 22:55:40 +01:00
84941de931 fix: use correct post clone command
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-01-08 22:53:22 +01:00
aa1ef7041e fix: tests for config
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-01-08 22:49:13 +01:00
2f501c0efd feat/add-post-clone-command
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2025-01-08 22:47:23 +01:00
13 changed files with 1551 additions and 873 deletions

View File

@@ -6,11 +6,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [0.3.5] - 2025-03-04
### Added
- allow clone all
### Fixed
- *(deps)* update rust crate serde to v1.0.218
- *(deps)* update tokio-prost monorepo to v0.13.5
- *(deps)* update rust crate termwiz to 0.23.0
- *(deps)* update all dependencies
- *(deps)* update rust crate uuid to v1.13.0
- *(deps)* update all dependencies
- *(deps)* update rust crate async-trait to v0.1.86
- *(deps)* update rust crate uuid to v1.12.1
- *(deps)* update rust crate octocrab to 0.43.0
- *(deps)* update rust crate uuid to v1.12.0
- *(deps)* update rust crate dirs to v6
- *(deps)* update rust crate uuid to v1.11.1
### Other
- *(deps)* update all dependencies
- *(deps)* update all dependencies
- *(deps)* update rust crate clap to v4.5.29
- *(deps)* update rust crate clap to v4.5.27
- *(deps)* update rust crate clap to v4.5.26
- *(deps)* update rust crate tokio to v1.43.0
- update example
## [0.3.4] - 2025-01-08 ## [0.3.4] - 2025-01-08
### Added ### Added
- feat/add-post-clone-command
- add ability to specify custom command - add ability to specify custom command
### Fixed
- use correct post clone command
- tests for config
### Other ### Other
- add ability to specify multiple commands - add ability to specify multiple commands

2173
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@ members = ["crates/*"]
resolver = "2" resolver = "2"
[workspace.package] [workspace.package]
version = "0.3.4" version = "0.3.5"
[workspace.dependencies] [workspace.dependencies]

View File

@@ -1,5 +1,7 @@
# Git Now # Git Now
> https://gitnow.kjuulh.io/
Git Now is a utility for easily navigating git projects from common upstream providers. Search, Download, and Enter projects as quickly as you can type. Git Now is a utility for easily navigating git projects from common upstream providers. Search, Download, and Enter projects as quickly as you can type.
![example gif](./assets/gifs/example.gif) ![example gif](./assets/gifs/example.gif)

View File

@@ -1,7 +1,7 @@
[package] [package]
name = "gitnow" name = "gitnow"
description = "Git Now is a utility for easily navigating git projects from common upstream providers. Search, Download, and Enter projects as quickly as you can type." description = "Git Now is a utility for easily navigating git projects from common upstream providers. Search, Download, and Enter projects as quickly as you can type."
edition = "2021" edition = "2024"
readme = "../../README.md" readme = "../../README.md"
repository = "https://github.com/kjuulh/gitnow" repository = "https://github.com/kjuulh/gitnow"
homepage = "https://gitnow-client.prod.kjuulh.app" homepage = "https://gitnow-client.prod.kjuulh.app"
@@ -22,20 +22,21 @@ dotenvy.workspace = true
serde = { version = "1.0.197", features = ["derive"] } serde = { version = "1.0.197", features = ["derive"] }
uuid = { version = "1.7.0", features = ["v4"] } uuid = { version = "1.7.0", features = ["v4"] }
async-trait = "0.1.82" async-trait = "0.1.82"
toml = "0.8.19" toml = "0.9.0"
gitea-client = { version = "1.22.1" } gitea-client = { version = "1.22.1" }
url = "2.5.2" url = "2.5.2"
octocrab = "0.42.0" octocrab = "0.49.0"
dirs = "5.0.1" dirs = "6.0.0"
prost = "0.13.2" prost = "0.13.2"
prost-types = "0.13.2" prost-types = "0.13.2"
bytes = "1.7.1" bytes = "1.7.1"
nucleo-matcher = "0.3.1" nucleo-matcher = "0.3.1"
ratatui = { version = "0.29.0", features = ["termwiz"] } ratatui = { version = "0.30.0", features = ["termwiz"] }
crossterm = { version = "0.28.0", features = ["event-stream"] } crossterm = { version = "0.29.0", features = ["event-stream"] }
futures = "0.3.30" futures = "0.3.30"
termwiz = "0.22.0" termwiz = "0.23.0"
regex = "1.11.1"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "1.4.0" pretty_assertions = "1.4.0"

View File

@@ -1,3 +1,83 @@
pub mod root; pub mod root;
pub mod shell; pub mod shell;
pub mod update; pub mod update;
pub mod clone {
use std::sync::Arc;
use futures::future::join_all;
use regex::Regex;
use crate::{
app::App, cache::CacheApp, custom_command::CustomCommandApp, git_clone::GitCloneApp,
};
#[derive(clap::Parser)]
pub struct CloneCommand {
#[arg(long = "search")]
search: String,
}
impl CloneCommand {
pub async fn execute(&mut self, app: &'static App) -> anyhow::Result<()> {
let repos = app.cache().get().await?.ok_or(anyhow::anyhow!(
"failed to get cache, do a gitnow update first"
))?;
let search = Regex::new(&self.search)?;
let filtered_repos = repos
.iter()
.filter(|r| search.is_match(&r.to_rel_path().display().to_string()))
.collect::<Vec<_>>();
let concurrency_limit = Arc::new(tokio::sync::Semaphore::new(5));
let mut handles = Vec::default();
for repo in filtered_repos {
let config = app.config.clone();
let custom_command = app.custom_command();
let git_clone = app.git_clone();
let repo = repo.clone();
let concurrency = Arc::clone(&concurrency_limit);
let handle = tokio::spawn(async move {
let permit = concurrency.acquire().await?;
let project_path = config.settings.projects.directory.join(repo.to_rel_path());
if !project_path.exists() {
eprintln!("cloning repository: {}", repo.to_rel_path().display());
git_clone.clone_repo(&repo, false).await?;
custom_command
.execute_post_clone_command(&project_path)
.await?;
}
drop(permit);
Ok::<(), anyhow::Error>(())
});
handles.push(handle);
}
let res = join_all(handles).await;
for res in res {
match res {
Ok(Ok(())) => {}
Ok(Err(e)) => {
tracing::error!("failed to clone repo: {}", e);
anyhow::bail!(e)
}
Err(e) => {
tracing::error!("failed to clone repo: {}", e);
anyhow::bail!(e)
}
}
}
Ok(())
}
}
}

View File

@@ -111,18 +111,23 @@ impl RootCommand {
} else { } else {
eprintln!("cloning repository..."); eprintln!("cloning repository...");
git_clone.clone_repo(&repo, force_refresh).await?; git_clone.clone_repo(&repo, force_refresh).await?;
self.app
.custom_command()
.execute_post_clone_command(&project_path)
.await?;
} }
} else { } else {
tracing::info!("skipping clone for repo: {}", &repo.to_rel_path().display()); tracing::info!("skipping clone for repo: {}", &repo.to_rel_path().display());
} }
} else { } else {
tracing::info!("repository already exists"); tracing::info!("repository already exists");
}
self.app self.app
.custom_command() .custom_command()
.execute_post_update_command(&project_path) .execute_post_update_command(&project_path)
.await?; .await?;
}
if shell { if shell {
self.app.shell().spawn_shell(&repo).await?; self.app.shell().spawn_shell(&repo).await?;

View File

@@ -3,18 +3,16 @@ use std::time::Duration;
use crossterm::event::{EventStream, KeyCode}; use crossterm::event::{EventStream, KeyCode};
use futures::{FutureExt, StreamExt}; use futures::{FutureExt, StreamExt};
use ratatui::{ use ratatui::{
crossterm, TerminalOptions, Viewport, crossterm,
prelude::*, prelude::*,
widgets::{Block, Padding}, widgets::{Block, Padding},
TerminalOptions, Viewport,
}; };
use crate::components::BatchCommand; use crate::components::BatchCommand;
use super::{ use super::{
create_dispatch, Dispatch, IntoCommand, Msg, Receiver, create_dispatch,
spinner::{Spinner, SpinnerState}, spinner::{Spinner, SpinnerState},
Dispatch, IntoCommand, Msg, Receiver,
}; };
pub struct InlineCommand { pub struct InlineCommand {
@@ -120,7 +118,7 @@ impl InlineCommand {
return Ok(true); return Ok(true);
} }
let mut cmd = self.update_state(&msg); let mut cmd = self.update_state(msg);
loop { loop {
let msg = cmd.into_command().execute(dispatch); let msg = cmd.into_command().execute(dispatch);
@@ -128,7 +126,7 @@ impl InlineCommand {
match msg { match msg {
Some(Msg::Quit) => return Ok(true), Some(Msg::Quit) => return Ok(true),
Some(msg) => { Some(msg) => {
cmd = self.update_state(&msg); cmd = self.update_state(msg);
} }
None => break, None => break,
} }
@@ -163,7 +161,7 @@ impl InlineCommand {
None None
} }
fn update_state(&mut self, msg: &Msg) -> impl IntoCommand { fn update_state(&mut self, msg: Msg) -> impl IntoCommand {
tracing::debug!("handling message: {:?}", msg); tracing::debug!("handling message: {:?}", msg);
let mut batch = BatchCommand::default(); let mut batch = BatchCommand::default();
@@ -178,7 +176,7 @@ impl InlineCommand {
} }
} }
batch.with(self.spinner.update(msg)); batch.with(self.spinner.update(&msg));
batch.into_command() batch.into_command()
} }

View File

@@ -20,6 +20,7 @@ pub struct Settings {
#[serde(default)] #[serde(default)]
pub cache: Cache, pub cache: Cache,
pub post_clone_command: Option<PostCloneCommand>,
pub post_update_command: Option<PostUpdateCommand>, pub post_update_command: Option<PostUpdateCommand>,
} }
@@ -39,6 +40,22 @@ impl PostUpdateCommand {
} }
} }
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
#[serde(untagged)]
pub enum PostCloneCommand {
Single(String),
Multiple(Vec<String>),
}
impl PostCloneCommand {
pub fn get_commands(&self) -> Vec<String> {
match self.clone() {
PostCloneCommand::Single(item) => vec![item],
PostCloneCommand::Multiple(items) => items,
}
}
}
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone)] #[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone)]
pub struct Projects { pub struct Projects {
pub directory: ProjectLocation, pub directory: ProjectLocation,
@@ -131,7 +148,7 @@ impl CacheDuration {
hours, hours,
minutes, minutes,
} => Some( } => Some(
std::time::Duration::from_days(*days) std::time::Duration::from_hours(*days * 24)
+ std::time::Duration::from_hours(*hours) + std::time::Duration::from_hours(*hours)
+ std::time::Duration::from_mins(*minutes), + std::time::Duration::from_mins(*minutes),
), ),
@@ -381,6 +398,7 @@ mod test {
directory: PathBuf::from("git").into() directory: PathBuf::from("git").into()
}, },
post_update_command: None, post_update_command: None,
post_clone_command: None
} }
}, },
config config
@@ -407,6 +425,7 @@ mod test {
cache: Cache::default(), cache: Cache::default(),
projects: Projects::default(), projects: Projects::default(),
post_update_command: None, post_update_command: None,
post_clone_command: None
} }
}, },
config config

View File

@@ -11,6 +11,36 @@ impl CustomCommand {
Self { config } Self { config }
} }
pub async fn execute_post_clone_command(&self, path: &Path) -> anyhow::Result<()> {
if let Some(post_clone_command) = &self.config.settings.post_clone_command {
for command in post_clone_command.get_commands() {
let command_parts = command.split(' ').collect::<Vec<_>>();
let Some((first, rest)) = command_parts.split_first() else {
return Ok(());
};
let mut cmd = tokio::process::Command::new(first);
cmd.args(rest).current_dir(path);
eprintln!("running command: {}", command);
tracing::info!(
path = path.display().to_string(),
cmd = command,
"running custom post clone command"
);
let output = cmd.output().await?;
let stdout = std::str::from_utf8(&output.stdout)?;
tracing::info!(
stdout = stdout,
"finished running custom post clone command"
);
}
}
Ok(())
}
pub async fn execute_post_update_command(&self, path: &Path) -> anyhow::Result<()> { pub async fn execute_post_update_command(&self, path: &Path) -> anyhow::Result<()> {
if let Some(post_update_command) = &self.config.settings.post_update_command { if let Some(post_update_command) = &self.config.settings.post_update_command {
for command in post_update_command.get_commands() { for command in post_update_command.get_commands() {

View File

@@ -1,10 +1,8 @@
#![feature(duration_constructors)]
use std::path::PathBuf; use std::path::PathBuf;
use anyhow::Context; use anyhow::Context;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use commands::{root::RootCommand, shell::Shell, update::Update}; use commands::{clone::CloneCommand, root::RootCommand, shell::Shell, update::Update};
use config::Config; use config::Config;
use tracing::level_filters::LevelFilter; use tracing::level_filters::LevelFilter;
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
@@ -52,6 +50,7 @@ struct Command {
enum Commands { enum Commands {
Init(Shell), Init(Shell),
Update(Update), Update(Update),
Clone(CloneCommand),
} }
const DEFAULT_CONFIG_PATH: &str = ".config/gitnow/gitnow.toml"; const DEFAULT_CONFIG_PATH: &str = ".config/gitnow/gitnow.toml";
@@ -89,6 +88,9 @@ async fn main() -> anyhow::Result<()> {
Commands::Update(mut update) => { Commands::Update(mut update) => {
update.execute(app).await?; update.execute(app).await?;
} }
Commands::Clone(mut clone) => {
clone.execute(app).await?;
}
}, },
None => { None => {
RootCommand::new(app) RootCommand::new(app)

View File

@@ -1,7 +1,31 @@
[settings]
# Runs after a project is fetched for the first time, either a single string, or multiple in an array
post_clone_command = "jj git init --colocate"
# Runs after a project is jumped to if it already exists.
post_update_command = ["git pull", "jj git fetch"]
[[providers.github]] [[providers.github]]
# Who is the user running the clone command
current_user = "kjuulh"
# How to authenticate to github
access_token = { env = "GITHUB_ACCESS_TOKEN" }
# Which users to index
users = ["kjuulh"] users = ["kjuulh"]
# Which organisations to index
organisations = ["lunarway"] organisations = ["lunarway"]
[[providers.gitea]] [[providers.gitea]]
# WHich gitea instance to authenticate against
url = "https://git.front.kjuulh.io/api/v1"
# How to authenticate to gitea
current_user = "kjuulh"
# WHich token to use to authenticate
access_token = { env = "GITEA_ACCESS_TOKEN" }
# Which users to index
users = ["kjuulh"] users = ["kjuulh"]
# Which organisations to index
organisation = ["noorgplease"] organisation = ["noorgplease"]

6
mise.toml Normal file
View File

@@ -0,0 +1,6 @@
[tools]
rust = "1.93"
cargo = "latest"
[tasks.install]
run = "cargo install --path ./crates/gitnow"