aboutsummaryrefslogtreecommitdiff
path: root/src/local
diff options
context:
space:
mode:
authorMax Audron <audron@cocaine.farm>2022-06-07 12:28:18 +0200
committerMaximilian Manz <maximilian.manz@de.clara.net>2022-06-20 11:33:04 +0200
commite9dc01ffb547d0fa605bfe38b34672ddd5161be4 (patch)
tree5ca50547512b7cc2256ef457d468c4252ae23a0b /src/local
parentimplement cloning of new repos (diff)
reorganize file structure and cleanup lints
Diffstat (limited to '')
-rw-r--r--src/local/;a211
-rw-r--r--src/local/sync.rs115
-rw-r--r--src/local/update.rs120
-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