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
commitae13cbecdbeca984e0d389732356c2785eab66d9 (patch)
treeccd76a92e16d47d23530b87ecdf21b57e40917ef /src/local
parentfix crash while walking non existing dir (diff)
implement cloning of new repos
Diffstat (limited to 'src/local')
-rw-r--r--src/local/aggregate.rs23
-rw-r--r--src/local/mod.rs72
-rw-r--r--src/local/sync.rs115
-rw-r--r--src/local/update.rs71
4 files changed, 217 insertions, 64 deletions
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
+ )),
}
}
}