aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock133
-rw-r--r--Cargo.toml5
-rw-r--r--src/forge/gitlab/mod.rs1
-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
-rw-r--r--src/main.rs22
-rw-r--r--src/sync/mod.rs12
9 files changed, 263 insertions, 191 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 8dc606d..613c674 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 18eb1eb..31df682 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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),
+ };
+ }
+ }
+}