diff options
| -rw-r--r-- | Cargo.lock | 133 | ||||
| -rw-r--r-- | Cargo.toml | 5 | ||||
| -rw-r--r-- | src/forge/gitlab/mod.rs | 1 | ||||
| -rw-r--r-- | src/local/aggregate.rs | 23 | ||||
| -rw-r--r-- | src/local/mod.rs | 72 | ||||
| -rw-r--r-- | src/local/sync.rs | 115 | ||||
| -rw-r--r-- | src/local/update.rs | 71 | ||||
| -rw-r--r-- | src/main.rs | 22 | ||||
| -rw-r--r-- | src/sync/mod.rs | 12 |
9 files changed, 263 insertions, 191 deletions
@@ -334,7 +334,6 @@ dependencies = [ "atomic", "pear", "serde", - "serde_yaml", "toml", "uncased", "version_check", @@ -578,6 +577,7 @@ dependencies = [ "thiserror", "tokio", "tracing", + "tracing-flame", "tracing-subscriber", "url", "walkdir", @@ -829,21 +829,6 @@ dependencies = [ ] [[package]] -name = "linked-hash-map" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" - -[[package]] -name = "lock_api" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" -dependencies = [ - "scopeguard", -] - -[[package]] name = "log" version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1004,29 +989,6 @@ dependencies = [ ] [[package]] -name = "parking_lot" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-sys", -] - -[[package]] name = "pear" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1268,12 +1230,6 @@ dependencies = [ ] [[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] name = "sct" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1327,18 +1283,6 @@ dependencies = [ ] [[package]] -name = "serde_yaml" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a521f2940385c165a24ee286aa8599633d162077a54bdcae2a6fd5a7bfa7a0" -dependencies = [ - "indexmap", - "ryu", - "serde", - "yaml-rust", -] - -[[package]] name = "sharded-slab" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1348,15 +1292,6 @@ dependencies = [ ] [[package]] -name = "signal-hook-registry" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" -dependencies = [ - "libc", -] - -[[package]] name = "slab" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1483,10 +1418,7 @@ dependencies = [ "memchr", "mio", "num_cpus", - "once_cell", - "parking_lot", "pin-project-lite", - "signal-hook-registry", "socket2", "tokio-macros", "winapi", @@ -1577,6 +1509,17 @@ dependencies = [ ] [[package]] +name = "tracing-flame" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bae117ee14789185e129aaee5d93750abe67fdc5a9a62650452bfe4e122a3a9" +dependencies = [ + "lazy_static", + "tracing", + "tracing-subscriber", +] + +[[package]] name = "tracing-log" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1859,49 +1802,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows-sys" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" -dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_msvc" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" - -[[package]] -name = "windows_i686_gnu" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" - -[[package]] -name = "windows_i686_msvc" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" - -[[package]] name = "winreg" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1920,15 +1820,6 @@ dependencies = [ ] [[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] - -[[package]] name = "yansi" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -tokio = { version = "1", features = ["full", "rt-multi-thread"] } +tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } futures = "0.3" gitlab = "0.1408" @@ -18,12 +18,13 @@ async-trait = "*" # Arg parsing and config clap = { version = "3", features = ["derive"] } -figment = { version = "0.10", features = ["toml", "yaml", "env"] } +figment = { version = "0.10", features = ["toml", "env"] } serde = "1" # logging tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tracing-flame = "0.2" # error handling anyhow = "1" diff --git a/src/forge/gitlab/mod.rs b/src/forge/gitlab/mod.rs index 6d60dbe..85efc76 100644 --- a/src/forge/gitlab/mod.rs +++ b/src/forge/gitlab/mod.rs @@ -31,6 +31,7 @@ impl Gitlab { #[async_trait::async_trait] impl super::ForgeTrait for Gitlab { + #[tracing::instrument(level = "trace")] async fn projects(&self, scope: &str) -> Result<Vec<super::Project>> { let query = Projects::build_query(projects::Variables { scope: scope.to_owned(), diff --git a/src/local/aggregate.rs b/src/local/aggregate.rs index f8b2a60..21a1dad 100644 --- a/src/local/aggregate.rs +++ b/src/local/aggregate.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use git2::Repository; use tracing::{debug, error}; @@ -10,18 +12,19 @@ use super::{Repo, Repos}; #[async_trait::async_trait] pub trait Aggregator { async fn from_local(root: &str, scope: &str) -> Repos; - async fn from_forge(projects: Vec<Project>) -> Repos; + async fn from_forge(root: &str, projects: Vec<Project>) -> Repos; async fn aggregate(mut local: Repos, mut remote: Repos) -> Repos; } #[async_trait::async_trait] impl Aggregator for Repos { + #[tracing::instrument(level = "trace")] async fn from_local(root: &str, scope: &str) -> Repos { let mut repos = Vec::new(); let path: std::path::PathBuf = [root, scope].iter().collect(); - if ! path.exists() { + if !path.exists() { return repos; } @@ -55,7 +58,7 @@ impl Aggregator for Repos { .to_str() .unwrap() .to_string(), - // path: entry.path().to_path_buf(), + path: entry.path().to_path_buf(), repo: Some(repo), ..Repo::default() }), @@ -70,11 +73,19 @@ impl Aggregator for Repos { return repos; } - async fn from_forge(projects: Vec<Project>) -> Repos { - projects.iter().map(|project| project.into()).collect() + #[tracing::instrument(level = "trace")] + async fn from_forge(root: &str, projects: Vec<Project>) -> Repos { + projects + .iter() + .map(|project| { + let mut repo: Repo = project.into(); + repo.path = [root, &repo.name].iter().collect(); + return repo; + }) + .collect() } - #[tracing::instrument(level = "debug", skip(local, remote))] + #[tracing::instrument(level = "trace", skip(local, remote))] async fn aggregate(mut local: Repos, mut remote: Repos) -> Repos { local = local .into_iter() diff --git a/src/local/mod.rs b/src/local/mod.rs index e2f4a9d..ec8985b 100644 --- a/src/local/mod.rs +++ b/src/local/mod.rs @@ -1,4 +1,7 @@ -use std::{fmt::Debug, path::PathBuf}; +use std::{ + fmt::Debug, + path::{Path, PathBuf}, +}; use thiserror::Error; @@ -9,75 +12,25 @@ use crate::forge::Project; mod aggregate; mod repostate; +mod sync; mod update; pub use aggregate::*; pub use repostate::*; +pub use sync::*; pub use update::*; pub type Repos = Vec<Repo>; pub struct Repo { pub name: String, + pub path: PathBuf, pub repo: Option<Repository>, pub forge: Option<Project>, pub default_branch: String, } impl Repo { - /// Fetch any new state from the remote and fast forward merge changes into local branches - #[tracing::instrument(level = "trace")] - pub fn update(&mut self) -> Result<UpdateResult, UpdateResult> { - let repo_name = self.name.clone(); - if self.repo.is_some() { - self.update_inner() - .map_err(|e| UpdateResult::err(repo_name, e.into())) - } else { - Ok(UpdateResult::err(repo_name, RepoError::NoLocalRepo)) - } - } - - fn update_inner(&mut self) -> Result<UpdateResult, RepoError> { - let repo = self.repo.as_ref().unwrap(); - let mut remote = self.main_remote(repo)?; - - self.fetch(&mut remote)?; - - self.default_branch = remote.default_branch()?.as_str().unwrap().to_string(); - - debug!("default branch: {}", self.default_branch); - - if self.is_clean()? { - debug!("repo is clean"); - - let merged = repo.branches(Some(BranchType::Local))? - .filter_map(|x| x.ok()) - .try_fold(false, |mut merged, (mut branch, _)| { - let name = format!("refs/heads/{}", Repo::branch_name(&branch)); - - if branch.upstream().is_ok() { - let upstream = branch.upstream().unwrap(); - - debug!("branch: {}", name); - - merged |= self.merge(repo, &mut branch, &upstream)?; - Ok::<bool, RepoError>(merged) - } else { - debug!("not updating branch: {}: branch does not have upstream tracking branch set", name); - Ok(merged) - } - })?; - - if merged { - Ok(UpdateResult::merged(self.name.clone())) - } else { - Ok(UpdateResult::no_changes(self.name.clone())) - } - } else { - Ok(UpdateResult::dirty(self.name.clone())) - } - } - pub fn is_clean(&self) -> Result<bool, RepoError> { if let Some(repo) = &self.repo { debug!("repo state: {:?}", repo.state()); @@ -134,6 +87,15 @@ impl Repo { Ok(()) } + #[tracing::instrument(level = "trace")] + pub fn clone(&self, url: &str) -> Result<Repository, RepoError> { + let mut builder = git2::build::RepoBuilder::new(); + builder.fetch_options(crate::git::fetch_options()); + + builder.clone(url, &self.path).map_err(|err| RepoError::GitError(err)) + } + + #[tracing::instrument(level = "trace")] pub fn checkout(&self) -> Result<(), RepoError> { if let Some(repo) = &self.repo { repo.checkout_head(None).map_err(|e| e.into()) @@ -149,6 +111,7 @@ impl Repo { } } + #[tracing::instrument(level = "trace", skip(repo, local, upstream))] pub fn merge( &self, repo: &Repository, @@ -264,6 +227,7 @@ impl Default for Repo { fn default() -> Self { Self { name: Default::default(), + path: Default::default(), repo: Default::default(), forge: Default::default(), default_branch: "main".to_string(), diff --git a/src/local/sync.rs b/src/local/sync.rs new file mode 100644 index 0000000..0ee1b59 --- /dev/null +++ b/src/local/sync.rs @@ -0,0 +1,115 @@ +use std::fmt::{Debug, Display}; + +use git2::{AnnotatedCommit, Branch, BranchType, Remote, Repository}; +use tracing::{debug, debug_span}; + +use super::{Repo, RepoError}; + +impl Repo { + /// Clone repos from forge and push new repos to forge + #[tracing::instrument(level = "trace")] + pub fn sync(&mut self) -> Result<SyncResult, SyncResult> { + let repo_name = self.name.clone(); + + if self.repo.is_some() + && !self + .is_clean() + .map_err(|err| SyncResult::err(repo_name.clone(), err))? + { + return Ok(SyncResult::dirty(repo_name)); + }; + + if self.repo.is_some() && self.forge.is_some() { + Ok(SyncResult::no_changes(repo_name)) + } else if self.repo.is_some() { + // do push stuff + Ok(SyncResult::pushed(repo_name)) + } else if self.forge.is_some() { + let url = self + .forge + .as_ref() + .unwrap() + .ssh_clone_url + .as_ref() + .ok_or(SyncResult::err(self.name.clone(), RepoError::NoRemoteFound))?; + + let repo = self + .clone(&url) + .map_err(|err| SyncResult::err(repo_name.clone(), err))?; + + self.repo = Some(repo); + Ok(SyncResult::cloned(repo_name)) + } else { + Ok(SyncResult::no_changes(repo_name)) + } + } +} + +#[derive(Debug)] +pub enum SyncResult { + NoChanges { + name: String, + }, + Dirty { + name: String, + }, + Cloned { + name: String, + }, + Pushed { + name: String, + }, + Error { + name: String, + error: super::RepoError, + }, +} + +impl SyncResult { + pub fn err(name: String, error: super::RepoError) -> SyncResult { + SyncResult::Error { name, error } + } + + pub fn cloned(name: String) -> SyncResult { + SyncResult::Cloned { name } + } + + pub fn pushed(name: String) -> SyncResult { + SyncResult::Pushed { name } + } + + pub fn dirty(name: String) -> SyncResult { + SyncResult::Dirty { name } + } + + pub fn no_changes(name: String) -> SyncResult { + SyncResult::NoChanges { name } + } +} + +impl Display for SyncResult { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use ansi_term::Colour::{Blue, Green, Red, Yellow}; + + match self { + SyncResult::NoChanges { name } => { + f.write_fmt(format_args!("{} {}", Blue.paint("NOCHANGE"), name)) + } + SyncResult::Dirty { name } => { + f.write_fmt(format_args!("{} {}", Yellow.paint("DIRTY "), name)) + } + SyncResult::Cloned { name } => { + f.write_fmt(format_args!("{} {}", Green.paint("CLONED "), name)) + } + SyncResult::Pushed { name } => { + f.write_fmt(format_args!("{} {}", Green.paint("PUSHED "), name)) + } + SyncResult::Error { name, error } => f.write_fmt(format_args!( + "{} {} [{}]", + Red.paint("ERROR "), + name, + error + )), + } + } +} diff --git a/src/local/update.rs b/src/local/update.rs index 95057a4..d9c9f3c 100644 --- a/src/local/update.rs +++ b/src/local/update.rs @@ -1,4 +1,64 @@ -use std::fmt::Display; +use std::fmt::{Debug, Display}; + +use git2::BranchType; +use tracing::debug; + +use super::{Repo, RepoError}; + +impl Repo { + /// Fetch any new state from the remote and fast forward merge changes into local branches + #[tracing::instrument(level = "trace")] + pub fn update(&mut self) -> Result<UpdateResult, UpdateResult> { + let repo_name = self.name.clone(); + if self.repo.is_some() { + self.update_inner() + .map_err(|e| UpdateResult::err(repo_name, e.into())) + } else { + Ok(UpdateResult::err(repo_name, RepoError::NoLocalRepo)) + } + } + + fn update_inner(&mut self) -> Result<UpdateResult, RepoError> { + let repo = self.repo.as_ref().unwrap(); + let mut remote = self.main_remote(repo)?; + + self.fetch(&mut remote)?; + + self.default_branch = remote.default_branch()?.as_str().unwrap().to_string(); + + debug!("default branch: {}", self.default_branch); + + if self.is_clean()? { + debug!("repo is clean"); + + let merged = repo.branches(Some(BranchType::Local))? + .filter_map(|x| x.ok()) + .try_fold(false, |mut merged, (mut branch, _)| { + let name = format!("refs/heads/{}", Repo::branch_name(&branch)); + + if branch.upstream().is_ok() { + let upstream = branch.upstream().unwrap(); + + debug!("branch: {}", name); + + merged |= self.merge(repo, &mut branch, &upstream)?; + Ok::<bool, RepoError>(merged) + } else { + debug!("not updating branch: {}: branch does not have upstream tracking branch set", name); + Ok(merged) + } + })?; + + if merged { + Ok(UpdateResult::merged(self.name.clone())) + } else { + Ok(UpdateResult::no_changes(self.name.clone())) + } + } else { + Ok(UpdateResult::dirty(self.name.clone())) + } + } +} #[derive(Debug)] pub enum UpdateResult { @@ -49,9 +109,12 @@ impl Display for UpdateResult { UpdateResult::Merged { name } => { f.write_fmt(format_args!("{} {}", Green.paint("PULLED "), name)) } - UpdateResult::Error { name, error } => { - f.write_fmt(format_args!("{} {} [{}]", Red.paint("ERROR "), name, error)) - } + UpdateResult::Error { name, error } => f.write_fmt(format_args!( + "{} {} [{}]", + Red.paint("ERROR "), + name, + error + )), } } } diff --git a/src/main.rs b/src/main.rs index 2a6592e..97929af 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,7 @@ use gtree::{ }; mod list; +mod sync; mod update; #[derive(Derivative)] @@ -20,11 +21,12 @@ struct GTree { config: config::Config, args: config::args::Args, forge: gtree::forge::Forge, - #[derivative(Debug="ignore")] + #[derivative(Debug = "ignore")] gitconfig: git2::Config, } impl GTree { + #[tracing::instrument(level = "trace")] pub async fn new() -> Result<GTree> { let args = config::args::Args::parse(); @@ -49,9 +51,11 @@ impl GTree { }) } + #[tracing::instrument(level = "trace")] pub async fn run(self) -> Result<()> { let scope = self.args.scope.as_ref().map_or("", |x| x); + // TODO select a specific forge let (_name, forge) = self .config .iter() @@ -60,13 +64,13 @@ impl GTree { let (local, remote) = tokio::join!( Repos::from_local(forge.root(), scope), - Repos::from_forge(self.forge.projects(scope).await?) + Repos::from_forge(forge.root(), self.forge.projects(scope).await?) ); let repos = Repos::aggregate(local, remote).await; match self.args.command { - config::args::Commands::Sync => todo!(), + config::args::Commands::Sync => self.sync(repos).await, config::args::Commands::Update => self.update(repos).await, config::args::Commands::List => self.list(repos).await?, }; @@ -77,6 +81,8 @@ impl GTree { #[tokio::main] async fn main() -> Result<()> { + use tracing_flame::FlameLayer; + let filter = tracing_subscriber::filter::Targets::new() .with_default(Level::TRACE) .with_target("hyper", LevelFilter::OFF) @@ -84,15 +90,23 @@ async fn main() -> Result<()> { let env_filter = EnvFilter::from_default_env(); + let (flame_layer, _guard) = FlameLayer::with_file("./tracing.folded").unwrap(); + let flameguard = flame_layer.flush_on_drop(); + tracing_subscriber::registry() .with(tracing_subscriber::fmt::layer().with_span_events(FmtSpan::ACTIVE)) .with(filter) .with(env_filter) + .with(flame_layer) .init(); debug!("starting"); let gtree = GTree::new().await?; - gtree.run().await + gtree.run().await?; + + flameguard.flush()?; + + Ok(()) } diff --git a/src/sync/mod.rs b/src/sync/mod.rs new file mode 100644 index 0000000..2edc04c --- /dev/null +++ b/src/sync/mod.rs @@ -0,0 +1,12 @@ +use gtree::local::Repos; + +impl crate::GTree { + pub async fn sync(&self, repos: Repos) { + for mut repo in repos { + match repo.sync() { + Ok(u) => println!("{}", u), + Err(u) => println!("{}", u), + }; + } + } +} |
