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 | e9dc01ffb547d0fa605bfe38b34672ddd5161be4 (patch) | |
| tree | 5ca50547512b7cc2256ef457d468c4252ae23a0b /src/local | |
| parent | implement cloning of new repos (diff) | |
reorganize file structure and cleanup lints
Diffstat (limited to '')
| -rw-r--r-- | src/local/;a | 211 | ||||
| -rw-r--r-- | src/local/sync.rs | 115 | ||||
| -rw-r--r-- | src/local/update.rs | 120 | ||||
| -rw-r--r-- | src/repo/aggregate.rs (renamed from src/local/aggregate.rs) | 8 | ||||
| -rw-r--r-- | src/repo/mod.rs (renamed from src/local/mod.rs) | 29 | ||||
| -rw-r--r-- | src/repo/repostate.rs (renamed from src/local/repostate.rs) | 0 |
6 files changed, 14 insertions, 469 deletions
diff --git a/src/local/;a b/src/local/;a deleted file mode 100644 index 9d6ada5..0000000 --- a/src/local/;a +++ /dev/null @@ -1,211 +0,0 @@ -use std::{fmt::Debug, path::PathBuf}; - -use thiserror::Error; - -use git2::{AnnotatedCommit, Remote, Repository}; -use tracing::{debug, trace}; - -use crate::forge::Project; - -mod aggregate; -mod repostate; - -pub use aggregate::*; -pub use repostate::*; - -pub type Repos = Vec<Repo>; - -pub struct Repo { - pub name: String, - pub repo: Option<Repository>, - pub forge: Option<Project>, -} - -impl Repo { - /// Fetch any new state from the remote, - /// we get the default branch in the same run. - /// - /// Then check if the repo is to be considered clean, - /// no stale uncommitted changes, no in progress merges etc - #[tracing::instrument(level = "trace")] - pub fn update(&self) -> Result<(), RepoError> { - let mut remote = self.main_remote()?; - - let fetch_head = self.fetch(&mut remote)?; - - let default_branch = remote.default_branch()?.as_str().unwrap().to_string(); - debug!("default branch: {}", default_branch); - - if self.is_clean()? { - debug!("repo is clean"); - - self.merge(&default_branch, &fetch_head)?; - }; - - Ok(()) - } - - pub fn is_clean(&self) -> Result<bool, RepoError> { - if let Some(repo) = &self.repo { - debug!("repo state: {:?}", repo.state()); - let statuses: Vec<git2::Status> = repo - .statuses(None)? - .iter() - .filter_map(|status| { - if status.status().is_ignored() { - None - } else { - Some(status.status()) - } - }) - .collect(); - - debug!("got repo statuses: {:?}", statuses); - - if repo.state() == git2::RepositoryState::Clean && statuses.is_empty() { - Ok(true) - } else { - Ok(false) - } - } else { - Err(RepoError::NoLocalRepo) - } - } - - pub fn main_remote(&self) -> Result<git2::Remote, RepoError> { - if let Some(repo) = &self.repo { - let remotes = repo.remotes()?; - - let remote = if let Some(_) = remotes.iter().find(|x| *x == Some("origin")) { - "origin" - } else { - if let Some(remote) = remotes.get(0) { - remote - } else { - return Err(RepoError::NoRemoteFound); - } - }; - - return Ok(repo.find_remote(remote)?); - } else { - return Err(RepoError::NoLocalRepo); - } - } - - #[tracing::instrument(level = "trace", skip(remote))] - pub fn fetch(&self, remote: &mut Remote) -> Result<AnnotatedCommit, RepoError> { - // Pass an empty array as the refspec to fetch to fetch the "default" refspecs - // Type annotation is needed because type can't be guessed from the empty array - remote.fetch::<&str>( - &[], - Some(&mut crate::git::fetch_options()), - Some("gtree fetch"), - )?; - - let repo = self.repo.as_ref().unwrap(); - - let fetch_head = repo.find_reference("FETCH_HEAD")?; - - Ok(repo.reference_to_annotated_commit(&fetch_head)?) - // Ok(remote.default_branch()?.as_str().unwrap().to_string()) - } - - pub fn checkout(&self) -> Result<(), RepoError> { - if let Some(repo) = &self.repo { - repo.checkout_head(None).map_err(|e| e.into()) - } else { - Err(RepoError::NoLocalRepo) - } - } - - pub fn merge(&self, refname: &str, fetch_commit: &AnnotatedCommit) -> Result<(), RepoError> { - let repo = self.repo.as_ref().unwrap(); - - let analysis = repo.merge_analysis(&[fetch_commit])?; - - if analysis.0.is_fast_forward() { - trace!("Doing a fast forward"); - match repo.find_reference(&refname) { - Ok(mut r) => { - let name = match r.name() { - Some(s) => s.to_string(), - None => String::from_utf8_lossy(r.name_bytes()).to_string(), - }; - let msg = format!("gtree: update repo branch: {} to {}", name, fetch_commit.id()); - debug!("{}", msg); - - r.set_target(fetch_commit.id(), &msg)?; - repo.set_head(&name)?; - repo.checkout_head(Some(git2::build::CheckoutBuilder::default().force()))?; - } - Err(_) => (), - }; - } - - Ok(()) - } -} - -#[derive(Error, Debug)] -pub enum RepoError { - #[error("repo is not cloned locally")] - NoLocalRepo, - #[error("local git repo does not have a remote")] - NoRemoteFound, - #[error("error during git operation {0}")] - GitError(#[from] git2::Error), - #[error("unknown repo error")] - Unknown, -} - -impl Ord for Repo { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.name.cmp(&other.name) - } -} - -impl Eq for Repo {} - -impl PartialOrd for Repo { - fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { - self.name.partial_cmp(&other.name) - } -} - -impl PartialEq for Repo { - fn eq(&self, other: &Self) -> bool { - self.name == other.name - } -} - -impl From<Project> for Repo { - fn from(project: Project) -> Self { - Self { - name: project.path.clone(), - repo: None, - forge: Some(project), - } - } -} - -impl From<&Project> for Repo { - fn from(project: &Project) -> Self { - Self { - name: project.path.clone(), - repo: None, - forge: Some(project.to_owned()), - } - } -} - -impl std::fmt::Display for Repo { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("{} {}", RepoState::from(self), self.name)) - } -} - -impl Debug for Repo { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Repo").field("path", &self.name).finish() - } -} diff --git a/src/local/sync.rs b/src/local/sync.rs deleted file mode 100644 index 0ee1b59..0000000 --- a/src/local/sync.rs +++ /dev/null @@ -1,115 +0,0 @@ -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 deleted file mode 100644 index d9c9f3c..0000000 --- a/src/local/update.rs +++ /dev/null @@ -1,120 +0,0 @@ -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 { - NoChanges { - name: String, - }, - Dirty { - name: String, - }, - Merged { - name: String, - }, - Error { - name: String, - error: super::RepoError, - }, -} - -impl UpdateResult { - pub fn err(name: String, error: super::RepoError) -> UpdateResult { - UpdateResult::Error { name, error } - } - - pub fn merged(name: String) -> UpdateResult { - UpdateResult::Merged { name } - } - - pub fn dirty(name: String) -> UpdateResult { - UpdateResult::Dirty { name } - } - - pub fn no_changes(name: String) -> UpdateResult { - UpdateResult::NoChanges { name } - } -} - -impl Display for UpdateResult { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use ansi_term::Colour::{Blue, Green, Red, Yellow}; - - match self { - UpdateResult::NoChanges { name } => { - f.write_fmt(format_args!("{} {}", Blue.paint("FETCHED"), name)) - } - UpdateResult::Dirty { name } => { - f.write_fmt(format_args!("{} {}", Yellow.paint("DIRTY "), name)) - } - 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 - )), - } - } -} diff --git a/src/local/aggregate.rs b/src/repo/aggregate.rs index 21a1dad..cb4b00d 100644 --- a/src/local/aggregate.rs +++ b/src/repo/aggregate.rs @@ -1,8 +1,6 @@ -use std::path::PathBuf; - use git2::Repository; -use tracing::{debug, error}; +use tracing::error; use walkdir::WalkDir; use crate::forge::Project; @@ -40,7 +38,7 @@ impl Aggregator for Repos { if entry.file_type().is_dir() { let mut dir = std::fs::read_dir(entry.path()).unwrap(); - if let Some(_) = dir.find(|dir| { + if dir.any(|dir| { if let Ok(dir) = dir { dir.file_name() == ".git" } else { @@ -80,7 +78,7 @@ impl Aggregator for Repos { .map(|project| { let mut repo: Repo = project.into(); repo.path = [root, &repo.name].iter().collect(); - return repo; + repo }) .collect() } diff --git a/src/local/mod.rs b/src/repo/mod.rs index ec8985b..e3d1279 100644 --- a/src/local/mod.rs +++ b/src/repo/mod.rs @@ -1,24 +1,17 @@ -use std::{ - fmt::Debug, - path::{Path, PathBuf}, -}; +use std::{fmt::Debug, path::PathBuf}; use thiserror::Error; -use git2::{AnnotatedCommit, Branch, BranchType, Remote, Repository}; +use git2::{Branch, Remote, Repository}; use tracing::{debug, trace}; 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>; @@ -61,14 +54,12 @@ impl Repo { pub fn main_remote<'a>(&self, repo: &'a Repository) -> Result<git2::Remote<'a>, RepoError> { let remotes = repo.remotes()?; - let remote = if let Some(_) = remotes.iter().find(|x| *x == Some("origin")) { + let remote = if remotes.iter().any(|x| x == Some("origin")) { "origin" + } else if let Some(remote) = remotes.get(0) { + remote } else { - if let Some(remote) = remotes.get(0) { - remote - } else { - return Err(RepoError::NoRemoteFound); - } + return Err(RepoError::NoRemoteFound); }; return Ok(repo.find_remote(remote)?); @@ -92,7 +83,9 @@ impl Repo { 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)) + builder + .clone(url, &self.path) + .map_err(RepoError::GitError) } #[tracing::instrument(level = "trace")] @@ -118,8 +111,8 @@ impl Repo { local: &mut Branch, upstream: &Branch, ) -> Result<bool, RepoError> { - let local_name = Repo::branch_name(&local); - let upstream_name = Repo::branch_name(&upstream); + let local_name = Repo::branch_name(local); + let upstream_name = Repo::branch_name(upstream); let local_ref = local.get_mut(); let upstream_ref = upstream.get(); diff --git a/src/local/repostate.rs b/src/repo/repostate.rs index ea3c5a6..ea3c5a6 100644 --- a/src/local/repostate.rs +++ b/src/repo/repostate.rs |
