Compare commits
74 Commits
v0.2.2
...
cuddle-ple
Author | SHA1 | Date | |
---|---|---|---|
|
2880ce3466 | ||
167d71024d | |||
|
e2102bfd99 | ||
716adfab8b | |||
c77a9dcbef | |||
7077c623ca | |||
80614fa667 | |||
6ff93b100a | |||
76b617fc2d | |||
d699432f14 | |||
5c46cca96f | |||
8d071981a3 | |||
8f9e40b98f | |||
98d4402461 | |||
816a65a04c | |||
d893e7e21a | |||
2a3c4fbb83 | |||
9d459f10d5 | |||
5aa5702272 | |||
45b45420f8 | |||
b49da1ca71 | |||
3c63992632 | |||
aaf9cd414e | |||
0f30aaadf5 | |||
84941de931
|
|||
aa1ef7041e
|
|||
2f501c0efd
|
|||
962425c515
|
|||
708f8d6388
|
|||
|
5e97f68a77 | ||
eb684ef8e9 | |||
176dc9f094 | |||
1b9f8d96fc | |||
|
69d008feb6 | ||
ada020e283
|
|||
708180f9a2 | |||
33ba27bf77 | |||
74a1daac4c
|
|||
3bd243b45c | |||
15620da103 | |||
a228ec82a3 | |||
503680d81c | |||
d13dc5b0a2 | |||
a1a993957b | |||
916beffb6d | |||
291d056dd7 | |||
dfacf96967 | |||
3524847d9f | |||
48b77deb5c | |||
c3f626cce0 | |||
52d522107d | |||
4dd8e58122 | |||
aa399e11c1 | |||
e670dc0384 | |||
8eb2970f5d | |||
f94ef8e919 | |||
62c9166677 | |||
5e859f99a9 | |||
4d78609839 | |||
90b7fe9b03 | |||
79ad2e872c | |||
ab963600f3 | |||
1aab9c2970 | |||
9dfcb52010 | |||
659f2247ff | |||
76f82f9060 | |||
bdfd001458 | |||
f4a2b52c72 | |||
aa78933f0e | |||
b242128d52 | |||
17cb06904f
|
|||
e3292b0c73 | |||
cc70131101
|
|||
784c7303a5 |
111
CHANGELOG.md
111
CHANGELOG.md
@@ -6,6 +6,117 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.3.6] - 2025-03-06
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- *(deps)* update rust crate bytes to v1.10.1
|
||||||
|
|
||||||
|
## [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
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- feat/add-post-clone-command
|
||||||
|
|
||||||
|
- add ability to specify custom command
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- use correct post clone command
|
||||||
|
- tests for config
|
||||||
|
|
||||||
|
### Other
|
||||||
|
- add ability to specify multiple commands
|
||||||
|
|
||||||
|
|
||||||
|
## [0.3.3] - 2025-01-07
|
||||||
|
|
||||||
|
### Other
|
||||||
|
- replace dotenv with dotenvy, a slightly more maintained version of the same library (#50)
|
||||||
|
|
||||||
|
## [0.3.2] - 2025-01-07
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- *(deps)* update rust crate async-trait to v0.1.85
|
||||||
|
|
||||||
|
## [0.3.1] - 2025-01-02
|
||||||
|
|
||||||
|
### Other
|
||||||
|
- enable publish
|
||||||
|
|
||||||
|
## [0.3.0] - 2025-01-01
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- add small help to see how much time is left in cache
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- *(deps)* update rust crate serde to v1.0.217
|
||||||
|
- *(deps)* update rust crate serde to v1.0.216
|
||||||
|
- *(deps)* update tokio-prost monorepo to v0.13.4
|
||||||
|
- *(deps)* update rust crate bytes to v1.9.0
|
||||||
|
- *(deps)* update all dependencies
|
||||||
|
- *(deps)* update rust crate octocrab to 0.42.0
|
||||||
|
- *(deps)* update rust crate serde to v1.0.215
|
||||||
|
- *(deps)* update rust crate url to v2.5.3
|
||||||
|
- *(deps)* update rust crate serde to v1.0.214
|
||||||
|
- *(deps)* update rust crate serde to v1.0.213
|
||||||
|
- *(deps)* update all dependencies
|
||||||
|
- *(deps)* update all dependencies
|
||||||
|
- *(deps)* update rust crate octocrab to v0.41.1
|
||||||
|
- *(deps)* update rust crate futures to v0.3.31
|
||||||
|
- *(deps)* update rust crate octocrab to 0.41.0
|
||||||
|
|
||||||
|
### Other
|
||||||
|
- bump default cache duration to 7 days
|
||||||
|
- *(deps)* update rust crate anyhow to v1.0.95
|
||||||
|
- *(deps)* update rust crate clap to v4.5.23
|
||||||
|
- *(deps)* update all dependencies
|
||||||
|
- *(deps)* update rust crate tracing-subscriber to v0.3.19
|
||||||
|
- *(deps)* update rust crate tracing to v0.1.41
|
||||||
|
- *(deps)* update rust crate clap to v4.5.21
|
||||||
|
- *(deps)* update rust crate tokio to v1.41.1
|
||||||
|
- *(deps)* update rust crate anyhow to v1.0.93
|
||||||
|
- *(deps)* update rust crate anyhow to v1.0.92
|
||||||
|
- *(deps)* update all dependencies to v1.0.91
|
||||||
|
- *(deps)* update rust crate anyhow to v1.0.90
|
||||||
|
- *(deps)* update rust crate clap to v4.5.20
|
||||||
|
- *(deps)* update rust crate clap to v4.5.19
|
||||||
|
|
||||||
|
## [0.2.3] - 2024-09-26
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- add update command
|
||||||
|
- only do clone if not exists
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- *(deps)* update rust crate async-trait to v0.1.83
|
||||||
|
- *(deps)* update rust crate octocrab to 0.40.0
|
||||||
|
|
||||||
## [0.2.2] - 2024-09-23
|
## [0.2.2] - 2024-09-23
|
||||||
|
|
||||||
### Other
|
### Other
|
||||||
|
1381
Cargo.lock
generated
1381
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ members = ["crates/*"]
|
|||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.2.2"
|
version = "0.3.6"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
|
|
||||||
@@ -12,4 +12,4 @@ tokio = { version = "1", features = ["full"] }
|
|||||||
tracing = { version = "0.1", features = ["log"] }
|
tracing = { version = "0.1", features = ["log"] }
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||||
clap = { version = "4", features = ["derive", "env"] }
|
clap = { version = "4", features = ["derive", "env"] }
|
||||||
dotenv = { version = "0.15" }
|
dotenvy = { version = "0.15.7" }
|
||||||
|
@@ -17,7 +17,7 @@ tokio.workspace = true
|
|||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
tracing-subscriber.workspace = true
|
tracing-subscriber.workspace = true
|
||||||
clap.workspace = true
|
clap.workspace = true
|
||||||
dotenv.workspace = true
|
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"] }
|
||||||
@@ -26,16 +26,17 @@ toml = "0.8.19"
|
|||||||
|
|
||||||
gitea-client = { version = "1.22.1" }
|
gitea-client = { version = "1.22.1" }
|
||||||
url = "2.5.2"
|
url = "2.5.2"
|
||||||
octocrab = "0.39.0"
|
octocrab = "0.43.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.28.1", features = ["termwiz"] }
|
ratatui = { version = "0.29.0", features = ["termwiz"] }
|
||||||
crossterm = { version = "0.28.0", features = ["event-stream"] }
|
crossterm = { version = "0.28.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"
|
||||||
|
@@ -1,9 +1,16 @@
|
|||||||
function git-now {
|
function git-now {
|
||||||
|
# Run an update in the background
|
||||||
|
(
|
||||||
|
nohup gitnow update > /dev/null 2>&1 &
|
||||||
|
)
|
||||||
|
|
||||||
|
# Find the repository of choice
|
||||||
choice=$(gitnow "$@" --no-shell)
|
choice=$(gitnow "$@" --no-shell)
|
||||||
if [[ $? -ne 0 ]]; then
|
if [[ $? -ne 0 ]]; then
|
||||||
return $?
|
return $?
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Enter local repository path
|
||||||
cd "$(echo "$choice" | tail --lines 1)"
|
cd "$(echo "$choice" | tail --lines 1)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -79,6 +79,11 @@ impl Cache {
|
|||||||
tracing::debug!("cache has expired");
|
tracing::debug!("cache has expired");
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tracing::debug!(
|
||||||
|
"cache is valid for: {} mins",
|
||||||
|
cache_duration.saturating_sub(file_modified_last).as_secs() / 60
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,2 +1,83 @@
|
|||||||
pub mod root;
|
pub mod root;
|
||||||
pub mod shell;
|
pub mod shell;
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -4,6 +4,7 @@ use crate::{
|
|||||||
app::App,
|
app::App,
|
||||||
cache::CacheApp,
|
cache::CacheApp,
|
||||||
components::inline_command::InlineCommand,
|
components::inline_command::InlineCommand,
|
||||||
|
custom_command::CustomCommandApp,
|
||||||
fuzzy_matcher::{FuzzyMatcher, FuzzyMatcherApp},
|
fuzzy_matcher::{FuzzyMatcher, FuzzyMatcherApp},
|
||||||
git_clone::GitCloneApp,
|
git_clone::GitCloneApp,
|
||||||
git_provider::Repository,
|
git_provider::Repository,
|
||||||
@@ -85,26 +86,47 @@ impl RootCommand {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if clone {
|
let project_path = self
|
||||||
let git_clone = self.app.git_clone();
|
.app
|
||||||
|
.config
|
||||||
|
.settings
|
||||||
|
.projects
|
||||||
|
.directory
|
||||||
|
.join(repo.to_rel_path());
|
||||||
|
if !project_path.exists() {
|
||||||
|
if clone {
|
||||||
|
let git_clone = self.app.git_clone();
|
||||||
|
|
||||||
if std::io::stdout().is_terminal() && shell {
|
if std::io::stdout().is_terminal() && shell {
|
||||||
let mut wrap_cmd =
|
let mut wrap_cmd =
|
||||||
InlineCommand::new(format!("cloning: {}", repo.to_rel_path().display()));
|
InlineCommand::new(format!("cloning: {}", repo.to_rel_path().display()));
|
||||||
let repo = repo.clone();
|
let repo = repo.clone();
|
||||||
wrap_cmd
|
wrap_cmd
|
||||||
.execute(move || async move {
|
.execute(move || async move {
|
||||||
git_clone.clone_repo(&repo, force_refresh).await?;
|
git_clone.clone_repo(&repo, force_refresh).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
} else {
|
||||||
|
eprintln!("cloning repository...");
|
||||||
|
git_clone.clone_repo(&repo, force_refresh).await?;
|
||||||
|
|
||||||
|
self.app
|
||||||
|
.custom_command()
|
||||||
|
.execute_post_clone_command(&project_path)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
eprintln!("cloning repository...");
|
tracing::info!("skipping clone for repo: {}", &repo.to_rel_path().display());
|
||||||
git_clone.clone_repo(&repo, force_refresh).await?;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tracing::info!("skipping clone for repo: {}", &repo.to_rel_path().display());
|
tracing::info!("repository already exists");
|
||||||
|
|
||||||
|
self.app
|
||||||
|
.custom_command()
|
||||||
|
.execute_post_update_command(&project_path)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if shell {
|
if shell {
|
||||||
|
14
crates/gitnow/src/commands/update.rs
Normal file
14
crates/gitnow/src/commands/update.rs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
use crate::{app::App, cache::CacheApp, projects_list::ProjectsListApp};
|
||||||
|
|
||||||
|
#[derive(clap::Parser)]
|
||||||
|
pub struct Update {}
|
||||||
|
|
||||||
|
impl Update {
|
||||||
|
pub async fn execute(&mut self, app: &'static App) -> anyhow::Result<()> {
|
||||||
|
let repositories = app.projects_list().get_projects().await?;
|
||||||
|
|
||||||
|
app.cache().update(&repositories).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@@ -23,7 +23,7 @@ impl<'a> Spinner<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> StatefulWidget for Spinner<'a> {
|
impl StatefulWidget for Spinner<'_> {
|
||||||
type State = SpinnerState;
|
type State = SpinnerState;
|
||||||
|
|
||||||
fn render(
|
fn render(
|
||||||
|
@@ -3,7 +3,7 @@ use std::path::{Path, PathBuf};
|
|||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub settings: Settings,
|
pub settings: Settings,
|
||||||
@@ -12,16 +12,51 @@ pub struct Config {
|
|||||||
pub providers: Providers,
|
pub providers: Providers,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub projects: Projects,
|
pub projects: Projects,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub cache: Cache,
|
pub cache: Cache,
|
||||||
|
|
||||||
|
pub post_clone_command: Option<PostCloneCommand>,
|
||||||
|
pub post_update_command: Option<PostUpdateCommand>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum PostUpdateCommand {
|
||||||
|
Single(String),
|
||||||
|
Multiple(Vec<String>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PostUpdateCommand {
|
||||||
|
pub fn get_commands(&self) -> Vec<String> {
|
||||||
|
match self.clone() {
|
||||||
|
PostUpdateCommand::Single(item) => vec![item],
|
||||||
|
PostUpdateCommand::Multiple(items) => items,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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)]
|
||||||
pub struct Projects {
|
pub struct Projects {
|
||||||
pub directory: ProjectLocation,
|
pub directory: ProjectLocation,
|
||||||
}
|
}
|
||||||
@@ -57,7 +92,7 @@ impl std::ops::Deref for ProjectLocation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone)]
|
||||||
pub struct Cache {
|
pub struct Cache {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub location: CacheLocation,
|
pub location: CacheLocation,
|
||||||
@@ -89,7 +124,7 @@ impl Default for CacheLocation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum CacheDuration {
|
pub enum CacheDuration {
|
||||||
Enabled(bool),
|
Enabled(bool),
|
||||||
@@ -124,14 +159,14 @@ impl CacheDuration {
|
|||||||
impl Default for CacheDuration {
|
impl Default for CacheDuration {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Precise {
|
Self::Precise {
|
||||||
days: 1,
|
days: 7,
|
||||||
hours: 0,
|
hours: 0,
|
||||||
minutes: 0,
|
minutes: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone)]
|
||||||
pub struct Providers {
|
pub struct Providers {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub github: Vec<GitHub>,
|
pub github: Vec<GitHub>,
|
||||||
@@ -139,7 +174,7 @@ pub struct Providers {
|
|||||||
pub gitea: Vec<Gitea>,
|
pub gitea: Vec<Gitea>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||||
pub struct GitHub {
|
pub struct GitHub {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub url: Option<String>,
|
pub url: Option<String>,
|
||||||
@@ -155,7 +190,7 @@ pub struct GitHub {
|
|||||||
pub organisations: Vec<GitHubOrganisation>,
|
pub organisations: Vec<GitHubOrganisation>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||||
pub struct GitHubUser(String);
|
pub struct GitHubUser(String);
|
||||||
|
|
||||||
impl From<GitHubUser> for String {
|
impl From<GitHubUser> for String {
|
||||||
@@ -170,7 +205,7 @@ impl<'a> From<&'a GitHubUser> for &'a str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||||
pub struct GitHubOrganisation(String);
|
pub struct GitHubOrganisation(String);
|
||||||
|
|
||||||
impl From<GitHubOrganisation> for String {
|
impl From<GitHubOrganisation> for String {
|
||||||
@@ -185,7 +220,7 @@ impl<'a> From<&'a GitHubOrganisation> for &'a str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||||
pub struct Gitea {
|
pub struct Gitea {
|
||||||
pub url: String,
|
pub url: String,
|
||||||
|
|
||||||
@@ -201,21 +236,21 @@ pub struct Gitea {
|
|||||||
pub organisations: Vec<GiteaOrganisation>,
|
pub organisations: Vec<GiteaOrganisation>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum GiteaAccessToken {
|
pub enum GiteaAccessToken {
|
||||||
Direct(String),
|
Direct(String),
|
||||||
Env { env: String },
|
Env { env: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum GitHubAccessToken {
|
pub enum GitHubAccessToken {
|
||||||
Direct(String),
|
Direct(String),
|
||||||
Env { env: String },
|
Env { env: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||||
pub struct GiteaUser(String);
|
pub struct GiteaUser(String);
|
||||||
|
|
||||||
impl From<GiteaUser> for String {
|
impl From<GiteaUser> for String {
|
||||||
@@ -230,7 +265,7 @@ impl<'a> From<&'a GiteaUser> for &'a str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||||
pub struct GiteaOrganisation(String);
|
pub struct GiteaOrganisation(String);
|
||||||
impl From<GiteaOrganisation> for String {
|
impl From<GiteaOrganisation> for String {
|
||||||
fn from(value: GiteaOrganisation) -> Self {
|
fn from(value: GiteaOrganisation) -> Self {
|
||||||
@@ -361,7 +396,9 @@ mod test {
|
|||||||
},
|
},
|
||||||
projects: Projects {
|
projects: Projects {
|
||||||
directory: PathBuf::from("git").into()
|
directory: PathBuf::from("git").into()
|
||||||
}
|
},
|
||||||
|
post_update_command: None,
|
||||||
|
post_clone_command: None
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
config
|
config
|
||||||
@@ -386,7 +423,9 @@ mod test {
|
|||||||
},
|
},
|
||||||
settings: Settings {
|
settings: Settings {
|
||||||
cache: Cache::default(),
|
cache: Cache::default(),
|
||||||
projects: Projects::default()
|
projects: Projects::default(),
|
||||||
|
post_update_command: None,
|
||||||
|
post_clone_command: None
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
config
|
config
|
||||||
|
83
crates/gitnow/src/custom_command.rs
Normal file
83
crates/gitnow/src/custom_command.rs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use crate::{app::App, config::Config};
|
||||||
|
|
||||||
|
pub struct CustomCommand {
|
||||||
|
config: Config,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CustomCommand {
|
||||||
|
pub fn new(config: Config) -> Self {
|
||||||
|
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<()> {
|
||||||
|
if let Some(post_update_command) = &self.config.settings.post_update_command {
|
||||||
|
for command in post_update_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 update command"
|
||||||
|
);
|
||||||
|
let output = cmd.output().await?;
|
||||||
|
let stdout = std::str::from_utf8(&output.stdout)?;
|
||||||
|
tracing::info!(
|
||||||
|
stdout = stdout,
|
||||||
|
"finished running custom post update command"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait CustomCommandApp {
|
||||||
|
fn custom_command(&self) -> CustomCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CustomCommandApp for App {
|
||||||
|
fn custom_command(&self) -> CustomCommand {
|
||||||
|
CustomCommand::new(self.config.clone())
|
||||||
|
}
|
||||||
|
}
|
@@ -36,6 +36,7 @@ impl InteractiveApp for &'static crate::app::App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mod app {
|
mod app {
|
||||||
|
use crossterm::event::KeyModifiers;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
crossterm::event::{self, Event, KeyCode},
|
crossterm::event::{self, Event, KeyCode},
|
||||||
layout::{Constraint, Layout},
|
layout::{Constraint, Layout},
|
||||||
@@ -92,6 +93,12 @@ mod app {
|
|||||||
terminal.draw(|frame| self.draw(frame))?;
|
terminal.draw(|frame| self.draw(frame))?;
|
||||||
|
|
||||||
if let Event::Key(key) = event::read()? {
|
if let Event::Key(key) = event::read()? {
|
||||||
|
if let KeyCode::Char('c') = key.code {
|
||||||
|
if key.modifiers.contains(KeyModifiers::CONTROL) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match key.code {
|
match key.code {
|
||||||
KeyCode::Char(letter) => {
|
KeyCode::Char(letter) => {
|
||||||
self.current_search.push(letter);
|
self.current_search.push(letter);
|
||||||
|
@@ -4,7 +4,7 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use commands::{root::RootCommand, shell::Shell};
|
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;
|
||||||
@@ -15,6 +15,7 @@ mod cache_codec;
|
|||||||
mod commands;
|
mod commands;
|
||||||
mod components;
|
mod components;
|
||||||
mod config;
|
mod config;
|
||||||
|
mod custom_command;
|
||||||
mod fuzzy_matcher;
|
mod fuzzy_matcher;
|
||||||
mod git_clone;
|
mod git_clone;
|
||||||
mod git_provider;
|
mod git_provider;
|
||||||
@@ -50,13 +51,15 @@ struct Command {
|
|||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
enum Commands {
|
enum Commands {
|
||||||
Init(Shell),
|
Init(Shell),
|
||||||
|
Update(Update),
|
||||||
|
Clone(CloneCommand),
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_CONFIG_PATH: &str = ".config/gitnow/gitnow.toml";
|
const DEFAULT_CONFIG_PATH: &str = ".config/gitnow/gitnow.toml";
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
dotenv::dotenv().ok();
|
dotenvy::dotenv().ok();
|
||||||
tracing_subscriber::fmt()
|
tracing_subscriber::fmt()
|
||||||
.with_env_filter(
|
.with_env_filter(
|
||||||
EnvFilter::builder()
|
EnvFilter::builder()
|
||||||
@@ -80,7 +83,17 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
tracing::debug!("Starting cli");
|
tracing::debug!("Starting cli");
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
Some(Commands::Init(mut shell)) => shell.execute().await?,
|
Some(cmd) => match cmd {
|
||||||
|
Commands::Init(mut shell) => {
|
||||||
|
shell.execute().await?;
|
||||||
|
}
|
||||||
|
Commands::Update(mut update) => {
|
||||||
|
update.execute(app).await?;
|
||||||
|
}
|
||||||
|
Commands::Clone(mut clone) => {
|
||||||
|
clone.execute(app).await?;
|
||||||
|
}
|
||||||
|
},
|
||||||
None => {
|
None => {
|
||||||
RootCommand::new(app)
|
RootCommand::new(app)
|
||||||
.execute(
|
.execute(
|
||||||
|
@@ -6,6 +6,9 @@ vars:
|
|||||||
service: "gitnow"
|
service: "gitnow"
|
||||||
registry: kasperhermansen
|
registry: kasperhermansen
|
||||||
|
|
||||||
|
rust:
|
||||||
|
publish: {}
|
||||||
|
|
||||||
please:
|
please:
|
||||||
project:
|
project:
|
||||||
owner: kjuulh
|
owner: kjuulh
|
||||||
|
@@ -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"]
|
||||||
|
Reference in New Issue
Block a user