diff options
| author | Max Audron <audron@cocaine.farm> | 2022-06-07 12:28:18 +0200 |
|---|---|---|
| committer | Maximilian Manz <maximilian.manz@de.clara.net> | 2022-06-20 11:33:04 +0200 |
| commit | ae13cbecdbeca984e0d389732356c2785eab66d9 (patch) | |
| tree | ccd76a92e16d47d23530b87ecdf21b57e40917ef /src | |
| parent | fix crash while walking non existing dir (diff) | |
implement cloning of new repos
Diffstat (limited to 'src')
| -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 |
7 files changed, 248 insertions, 68 deletions
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), + }; + } + } +} |
