forked from kemitix/git-next
feat(server/gitforge): replace git abstraction
This commit is contained in:
parent
968f9dd73d
commit
adb44d18c9
32 changed files with 1066 additions and 661 deletions
|
@ -6,6 +6,7 @@ edition = "2021"
|
||||||
[features]
|
[features]
|
||||||
default = ["forgejo"]
|
default = ["forgejo"]
|
||||||
forgejo = []
|
forgejo = []
|
||||||
|
github = []
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
@ -23,6 +24,7 @@ base64 = "0.22"
|
||||||
|
|
||||||
# git
|
# git
|
||||||
gix = "0.62"
|
gix = "0.62"
|
||||||
|
async-trait = "0.1"
|
||||||
|
|
||||||
# fs/network
|
# fs/network
|
||||||
kxio = { version = "1.0", features = [
|
kxio = { version = "1.0", features = [
|
||||||
|
@ -59,7 +61,7 @@ tokio = { version = "1.37", features = ["full"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
# Testing
|
# Testing
|
||||||
# assert2 = "0.3"
|
assert2 = "0.3"
|
||||||
test-log = "0.2"
|
test-log = "0.2"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,6 @@ mod server;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use kxio::{filesystem, network::Network};
|
use kxio::{filesystem, network::Network};
|
||||||
|
|
||||||
use crate::server::git::Git;
|
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[clap(version = clap::crate_version!(), author = clap::crate_authors!(), about = clap::crate_description!())]
|
#[clap(version = clap::crate_version!(), author = clap::crate_authors!(), about = clap::crate_description!())]
|
||||||
struct Commands {
|
struct Commands {
|
||||||
|
@ -28,7 +26,6 @@ enum Server {
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let fs = filesystem::FileSystem::new_real(None);
|
let fs = filesystem::FileSystem::new_real(None);
|
||||||
let net = Network::new_real();
|
let net = Network::new_real();
|
||||||
let git = Git::new_real();
|
|
||||||
let commands = Commands::parse();
|
let commands = Commands::parse();
|
||||||
|
|
||||||
match commands.command {
|
match commands.command {
|
||||||
|
@ -40,7 +37,7 @@ async fn main() {
|
||||||
server::init(fs);
|
server::init(fs);
|
||||||
}
|
}
|
||||||
Server::Start => {
|
Server::Start => {
|
||||||
server::start(fs, net, git).await;
|
server::start(fs, net).await;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,147 +1,17 @@
|
||||||
use actix::prelude::*;
|
use tracing::{info, warn};
|
||||||
|
|
||||||
use kxio::network::{self, Network};
|
|
||||||
use tracing::{error, info, warn};
|
|
||||||
|
|
||||||
use crate::server::{
|
use crate::server::{
|
||||||
config::{self, RepoConfig, RepoDetails},
|
config, forge,
|
||||||
forge::{self, CommitHistories},
|
gitforge::{self, Force, Forge},
|
||||||
git::Git,
|
|
||||||
types::ResetForce,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{RepoActor, StartMonitoring};
|
|
||||||
|
|
||||||
#[tracing::instrument(fields(forge_name = %repo_details.forge.name, repo_name = %repo_details.name), skip_all)]
|
|
||||||
pub async fn validate_positions(
|
|
||||||
repo_details: config::RepoDetails,
|
|
||||||
config: config::RepoConfig,
|
|
||||||
addr: Addr<RepoActor>,
|
|
||||||
git: Git,
|
|
||||||
net: network::Network,
|
|
||||||
) {
|
|
||||||
// Collect Commit Histories for `main`, `next` and `dev` branches
|
|
||||||
let commit_histories = get_commit_histories(&repo_details, &config, &net).await;
|
|
||||||
let commit_histories = match commit_histories {
|
|
||||||
Ok(commit_histories) => commit_histories,
|
|
||||||
Err(err) => {
|
|
||||||
error!(?err, "Failed to get commit histories");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Validations
|
|
||||||
let Some(main) = commit_histories.main.first().cloned() else {
|
|
||||||
warn!("No commits on main branch '{}'", config.branches().main());
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
// verify that next is an ancestor of dev, and force update to it main if it isn't
|
|
||||||
let Some(next) = commit_histories.next.first().cloned() else {
|
|
||||||
warn!("No commits on next branch '{}", config.branches().next());
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let next_is_ancestor_of_dev = commit_histories.dev.iter().any(|dev| dev == &next);
|
|
||||||
if !next_is_ancestor_of_dev {
|
|
||||||
info!("Next is not an ancestor of dev - resetting next to main");
|
|
||||||
reset_next_to_main(next, main, &repo_details, &config, &git);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let next_commits = commit_histories
|
|
||||||
.next
|
|
||||||
.into_iter()
|
|
||||||
.take(2)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
if !next_commits.contains(&main) {
|
|
||||||
warn!(
|
|
||||||
"Main branch '{}' is not on the same commit as next branch '{}', or it's parent - resetting next to main",
|
|
||||||
config.branches().main(),
|
|
||||||
config.branches().next()
|
|
||||||
);
|
|
||||||
reset_next_to_main(next, main, &repo_details, &config, &git);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let Some(next) = next_commits.first().cloned() else {
|
|
||||||
warn!("No commits on next branch '{}'", config.branches().next());
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let dev_has_next = commit_histories
|
|
||||||
.dev
|
|
||||||
.iter()
|
|
||||||
.any(|commit| commit == &next_commits[0]);
|
|
||||||
if !dev_has_next {
|
|
||||||
warn!(
|
|
||||||
"Dev branch '{}' is not based on next branch '{}' - next branch will be updated shortly",
|
|
||||||
config.branches().dev(),
|
|
||||||
config.branches().next()
|
|
||||||
);
|
|
||||||
return; // dev is not based on next
|
|
||||||
}
|
|
||||||
let dev_has_main = commit_histories.dev.iter().any(|commit| commit == &main);
|
|
||||||
if !dev_has_main {
|
|
||||||
warn!(
|
|
||||||
"Dev branch '{}' is not based on main branch '{}' - user should rebase onto main branch '{}'",
|
|
||||||
config.branches().dev(),
|
|
||||||
config.branches().main(),
|
|
||||||
config.branches().main(),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let Some(dev) = commit_histories.dev.first().cloned() else {
|
|
||||||
warn!("No commits on dev branch '{}'", config.branches().dev());
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
addr.do_send(StartMonitoring {
|
|
||||||
main,
|
|
||||||
next,
|
|
||||||
dev,
|
|
||||||
dev_commit_history: commit_histories.dev,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_commit_histories(
|
|
||||||
repo_details: &RepoDetails,
|
|
||||||
config: &RepoConfig,
|
|
||||||
net: &Network,
|
|
||||||
) -> Result<CommitHistories, network::NetworkError> {
|
|
||||||
match repo_details.forge.forge_type {
|
|
||||||
#[cfg(feature = "forgejo")]
|
|
||||||
config::ForgeType::ForgeJo => {
|
|
||||||
forge::forgejo::get_commit_histories(repo_details, config, net).await
|
|
||||||
}
|
|
||||||
#[cfg(test)]
|
|
||||||
config::ForgeType::MockForge => {
|
|
||||||
forge::mock::get_commit_histories(repo_details, config, net).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reset_next_to_main(
|
|
||||||
next: forge::Commit,
|
|
||||||
main: forge::Commit,
|
|
||||||
repo_details: &RepoDetails,
|
|
||||||
repo_config: &RepoConfig,
|
|
||||||
git: &Git,
|
|
||||||
) {
|
|
||||||
let reset = git.reset(
|
|
||||||
&repo_config.branches().next(),
|
|
||||||
main.into(),
|
|
||||||
ResetForce::Force(next.into()),
|
|
||||||
repo_details,
|
|
||||||
);
|
|
||||||
if let Err(err) = reset {
|
|
||||||
warn!(?err, "Failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// advance next to the next commit towards the head of the dev branch
|
// advance next to the next commit towards the head of the dev branch
|
||||||
#[tracing::instrument(fields(next), skip_all)]
|
#[tracing::instrument(fields(next), skip_all)]
|
||||||
pub async fn advance_next(
|
pub async fn advance_next(
|
||||||
next: forge::Commit,
|
next: forge::Commit,
|
||||||
dev_commit_history: Vec<forge::Commit>,
|
dev_commit_history: Vec<forge::Commit>,
|
||||||
repo_details: config::RepoDetails,
|
|
||||||
repo_config: config::RepoConfig,
|
repo_config: config::RepoConfig,
|
||||||
git: Git,
|
forge: Forge,
|
||||||
) {
|
) {
|
||||||
let next_commit = find_next_commit_on_dev(next, dev_commit_history);
|
let next_commit = find_next_commit_on_dev(next, dev_commit_history);
|
||||||
let Some(commit) = next_commit else {
|
let Some(commit) = next_commit else {
|
||||||
|
@ -153,14 +23,9 @@ pub async fn advance_next(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
info!("Advancing next to commit '{}'", commit);
|
info!("Advancing next to commit '{}'", commit);
|
||||||
if let Err(err) = git.reset(
|
if let Err(err) = forge.branch_reset(repo_config.branches().next(), commit.into(), Force::No) {
|
||||||
&repo_config.branches().next(),
|
|
||||||
commit.into(),
|
|
||||||
ResetForce::None,
|
|
||||||
&repo_details,
|
|
||||||
) {
|
|
||||||
warn!(?err, "Failed")
|
warn!(?err, "Failed")
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
|
@ -199,17 +64,11 @@ fn find_next_commit_on_dev(
|
||||||
#[tracing::instrument(fields(next), skip_all)]
|
#[tracing::instrument(fields(next), skip_all)]
|
||||||
pub async fn advance_main(
|
pub async fn advance_main(
|
||||||
next: forge::Commit,
|
next: forge::Commit,
|
||||||
repo_details: config::RepoDetails,
|
|
||||||
repo_config: config::RepoConfig,
|
repo_config: config::RepoConfig,
|
||||||
git: Git,
|
forge: gitforge::Forge,
|
||||||
) {
|
) {
|
||||||
info!("Advancing main to next");
|
info!("Advancing main to next");
|
||||||
if let Err(err) = git.reset(
|
if let Err(err) = forge.branch_reset(repo_config.branches().main(), next.into(), Force::No) {
|
||||||
&repo_config.branches().main(),
|
|
||||||
next.into(),
|
|
||||||
ResetForce::None,
|
|
||||||
&repo_details,
|
|
||||||
) {
|
|
||||||
warn!(?err, "Failed")
|
warn!(?err, "Failed")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,22 @@
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
use kxio::network::Network;
|
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use crate::server::{
|
use crate::server::{
|
||||||
config::{ForgeType, RepoDetails},
|
config::{ForgeType, RepoDetails},
|
||||||
forge,
|
forge, gitforge,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{LoadedConfig, RepoActor};
|
use super::{LoadedConfig, RepoActor};
|
||||||
|
|
||||||
pub async fn load(details: RepoDetails, addr: Addr<RepoActor>, net: Network) {
|
pub async fn load(details: RepoDetails, addr: Addr<RepoActor>, forge: gitforge::Forge) {
|
||||||
let config = match details.config {
|
let config = match details.config {
|
||||||
Some(config) => config,
|
Some(config) => config,
|
||||||
None => {
|
None => {
|
||||||
let config = match details.forge.forge_type {
|
let config = match details.forge.forge_type {
|
||||||
#[cfg(feature = "forgejo")]
|
#[cfg(feature = "forgejo")]
|
||||||
ForgeType::ForgeJo => forge::forgejo::config::load(&details, &net).await,
|
ForgeType::ForgeJo => forge::forgejo::config::load(&details, &forge).await,
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
ForgeType::MockForge => forge::mock::config::load(&details, &net).await,
|
ForgeType::MockForge => forge::mock::config::load(&details, &forge).await,
|
||||||
};
|
};
|
||||||
match config {
|
match config {
|
||||||
Ok(config) => config,
|
Ok(config) => config,
|
||||||
|
|
|
@ -10,8 +10,7 @@ use tracing::{info, warn};
|
||||||
use crate::server::{
|
use crate::server::{
|
||||||
actors::repo::webhook::WebhookAuth,
|
actors::repo::webhook::WebhookAuth,
|
||||||
config::{RepoConfig, RepoDetails, Webhook},
|
config::{RepoConfig, RepoDetails, Webhook},
|
||||||
forge,
|
forge, gitforge,
|
||||||
git::Git,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::webhook::WebhookId;
|
use self::webhook::WebhookId;
|
||||||
|
@ -25,15 +24,18 @@ pub struct RepoActor {
|
||||||
last_next_commit: Option<forge::Commit>,
|
last_next_commit: Option<forge::Commit>,
|
||||||
last_dev_commit: Option<forge::Commit>,
|
last_dev_commit: Option<forge::Commit>,
|
||||||
net: Network,
|
net: Network,
|
||||||
git: Git,
|
forge: gitforge::Forge,
|
||||||
}
|
}
|
||||||
impl RepoActor {
|
impl RepoActor {
|
||||||
pub(crate) const fn new(
|
pub(crate) fn new(details: RepoDetails, webhook: Webhook, net: Network) -> Self {
|
||||||
details: RepoDetails,
|
let forge = match details.forge.forge_type {
|
||||||
webhook: Webhook,
|
#[cfg(feature = "forgejo")]
|
||||||
net: Network,
|
crate::server::config::ForgeType::ForgeJo => {
|
||||||
git: Git,
|
gitforge::Forge::new_forgejo(details.clone(), net.clone())
|
||||||
) -> Self {
|
}
|
||||||
|
#[cfg(test)]
|
||||||
|
crate::server::config::ForgeType::MockForge => gitforge::Forge::new_mock(),
|
||||||
|
};
|
||||||
Self {
|
Self {
|
||||||
details,
|
details,
|
||||||
webhook,
|
webhook,
|
||||||
|
@ -43,7 +45,7 @@ impl RepoActor {
|
||||||
last_next_commit: None,
|
last_next_commit: None,
|
||||||
last_dev_commit: None,
|
last_dev_commit: None,
|
||||||
net,
|
net,
|
||||||
git,
|
forge,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,8 +75,10 @@ impl Handler<StartRepo> for RepoActor {
|
||||||
info!(%self.details, "Starting Repo");
|
info!(%self.details, "Starting Repo");
|
||||||
let details = self.details.clone();
|
let details = self.details.clone();
|
||||||
let addr = ctx.address();
|
let addr = ctx.address();
|
||||||
let net = self.net.clone();
|
let forge = self.forge.clone();
|
||||||
config::load(details, addr, net).into_actor(self).wait(ctx);
|
config::load(details, addr, forge)
|
||||||
|
.into_actor(self)
|
||||||
|
.wait(ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,11 +112,9 @@ impl Handler<ValidateRepo> for RepoActor {
|
||||||
type Result = ();
|
type Result = ();
|
||||||
fn handle(&mut self, _msg: ValidateRepo, ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, _msg: ValidateRepo, ctx: &mut Self::Context) -> Self::Result {
|
||||||
if let Some(repo_config) = self.details.config.clone() {
|
if let Some(repo_config) = self.details.config.clone() {
|
||||||
let repo_details = self.details.clone();
|
let forge = self.forge.clone();
|
||||||
let addr = ctx.address();
|
let addr = ctx.address();
|
||||||
let net = self.net.clone();
|
async move { forge.branches_validate_positions(repo_config, addr).await }
|
||||||
let git = self.git.clone();
|
|
||||||
branch::validate_positions(repo_details, repo_config, addr, git, net)
|
|
||||||
.into_actor(self)
|
.into_actor(self)
|
||||||
.wait(ctx);
|
.wait(ctx);
|
||||||
}
|
}
|
||||||
|
@ -143,20 +145,14 @@ impl Handler<StartMonitoring> for RepoActor {
|
||||||
let webhook = self.webhook.clone();
|
let webhook = self.webhook.clone();
|
||||||
let addr = ctx.address();
|
let addr = ctx.address();
|
||||||
let net = self.net.clone();
|
let net = self.net.clone();
|
||||||
let git = self.git.clone();
|
let forge = self.forge.clone();
|
||||||
|
|
||||||
if next_ahead_of_main {
|
if next_ahead_of_main {
|
||||||
status::check_next(msg.next, repo_details, addr, net)
|
status::check_next(msg.next, addr, forge)
|
||||||
.into_actor(self)
|
.into_actor(self)
|
||||||
.wait(ctx);
|
.wait(ctx);
|
||||||
} else if dev_ahead_of_next {
|
} else if dev_ahead_of_next {
|
||||||
branch::advance_next(
|
branch::advance_next(msg.next, msg.dev_commit_history, repo_config, forge)
|
||||||
msg.next,
|
|
||||||
msg.dev_commit_history,
|
|
||||||
repo_details,
|
|
||||||
repo_config,
|
|
||||||
git,
|
|
||||||
)
|
|
||||||
.into_actor(self)
|
.into_actor(self)
|
||||||
.wait(ctx);
|
.wait(ctx);
|
||||||
} else if self.webhook_id.is_none() {
|
} else if self.webhook_id.is_none() {
|
||||||
|
@ -184,13 +180,12 @@ pub struct AdvanceMainTo(pub forge::Commit);
|
||||||
impl Handler<AdvanceMainTo> for RepoActor {
|
impl Handler<AdvanceMainTo> for RepoActor {
|
||||||
type Result = ();
|
type Result = ();
|
||||||
fn handle(&mut self, msg: AdvanceMainTo, ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: AdvanceMainTo, ctx: &mut Self::Context) -> Self::Result {
|
||||||
let repo_details = self.details.clone();
|
|
||||||
let Some(repo_config) = self.details.config.clone() else {
|
let Some(repo_config) = self.details.config.clone() else {
|
||||||
warn!("No config loaded");
|
warn!("No config loaded");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let git = self.git.clone();
|
let forge = self.forge.clone();
|
||||||
branch::advance_main(msg.0, repo_details, repo_config, git)
|
branch::advance_main(msg.0, repo_config, forge)
|
||||||
.into_actor(self)
|
.into_actor(self)
|
||||||
.wait(ctx);
|
.wait(ctx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,49 +2,24 @@ use actix::prelude::*;
|
||||||
use gix::trace::warn;
|
use gix::trace::warn;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
use crate::server::{
|
use crate::server::{actors::repo::ValidateRepo, forge, gitforge};
|
||||||
actors::repo::ValidateRepo,
|
|
||||||
config::{self, ForgeType},
|
|
||||||
forge,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::AdvanceMainTo;
|
use super::AdvanceMainTo;
|
||||||
|
|
||||||
pub async fn check_next(
|
pub async fn check_next(next: forge::Commit, addr: Addr<super::RepoActor>, forge: gitforge::Forge) {
|
||||||
next: forge::Commit,
|
|
||||||
repo_details: config::RepoDetails,
|
|
||||||
addr: Addr<super::RepoActor>,
|
|
||||||
net: kxio::network::Network,
|
|
||||||
) {
|
|
||||||
// get the status - pass, fail, pending (all others map to fail, e.g. error)
|
// get the status - pass, fail, pending (all others map to fail, e.g. error)
|
||||||
let status = match repo_details.forge.forge_type {
|
let status = forge.commit_status(&next).await;
|
||||||
#[cfg(feature = "forgejo")]
|
|
||||||
ForgeType::ForgeJo => {
|
|
||||||
forge::forgejo::get_commit_status(next.clone(), &repo_details, net).await
|
|
||||||
}
|
|
||||||
#[cfg(test)]
|
|
||||||
ForgeType::MockForge => {
|
|
||||||
forge::mock::get_commit_status(next.clone(), &repo_details, &net).await
|
|
||||||
}
|
|
||||||
};
|
|
||||||
info!(?status, "Checking next branch");
|
info!(?status, "Checking next branch");
|
||||||
match status {
|
match status {
|
||||||
Status::Pass => {
|
gitforge::CommitStatus::Pass => {
|
||||||
addr.do_send(AdvanceMainTo(next));
|
addr.do_send(AdvanceMainTo(next));
|
||||||
}
|
}
|
||||||
Status::Pending => {
|
gitforge::CommitStatus::Pending => {
|
||||||
tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
|
tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
|
||||||
addr.do_send(ValidateRepo);
|
addr.do_send(ValidateRepo);
|
||||||
}
|
}
|
||||||
Status::Fail => {
|
gitforge::CommitStatus::Fail => {
|
||||||
warn!("Checks have failed");
|
warn!("Checks have failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Status {
|
|
||||||
Pass,
|
|
||||||
Fail,
|
|
||||||
Pending,
|
|
||||||
}
|
|
||||||
|
|
|
@ -61,9 +61,13 @@ pub struct RepoConfig {
|
||||||
branches: RepoBranches,
|
branches: RepoBranches,
|
||||||
}
|
}
|
||||||
impl RepoConfig {
|
impl RepoConfig {
|
||||||
#[allow(dead_code)]
|
#[cfg(test)]
|
||||||
pub(crate) fn load(toml: &str) -> Result<Self, OneOf<(toml::de::Error,)>> {
|
pub const fn new(branches: RepoBranches) -> Self {
|
||||||
toml::from_str(toml).map_err(OneOf::new)
|
Self { branches }
|
||||||
|
}
|
||||||
|
// #[cfg(test)]
|
||||||
|
pub fn load(toml: &str) -> Result<Self, toml::de::Error> {
|
||||||
|
toml::from_str(toml)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn branches(&self) -> &RepoBranches {
|
pub const fn branches(&self) -> &RepoBranches {
|
||||||
|
@ -84,6 +88,14 @@ pub struct RepoBranches {
|
||||||
dev: String,
|
dev: String,
|
||||||
}
|
}
|
||||||
impl RepoBranches {
|
impl RepoBranches {
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn new(main: impl Into<String>, next: impl Into<String>, dev: impl Into<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
main: main.into(),
|
||||||
|
next: next.into(),
|
||||||
|
dev: dev.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn main(&self) -> BranchName {
|
pub fn main(&self) -> BranchName {
|
||||||
BranchName(self.main.clone())
|
BranchName(self.main.clone())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
use base64::Engine;
|
use kxio::network;
|
||||||
use kxio::network::{self, Network};
|
|
||||||
use terrors::OneOf;
|
use terrors::OneOf;
|
||||||
use tracing::{error, info};
|
use tracing::error;
|
||||||
|
|
||||||
use crate::server::config::{BranchName, RepoConfig, RepoDetails};
|
use crate::server::{
|
||||||
|
config::{BranchName, RepoConfig, RepoDetails},
|
||||||
|
gitforge::{self, ForgeFileError},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RepoConfigFileNotFound;
|
pub struct RepoConfigFileNotFound;
|
||||||
|
@ -16,177 +18,56 @@ pub struct RepoConfigParseError;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RepoConfigUnknownError(pub network::StatusCode);
|
pub struct RepoConfigUnknownError(pub network::StatusCode);
|
||||||
|
|
||||||
type RepoConfigLoadErrors = (
|
|
||||||
RepoConfigFileNotFound,
|
|
||||||
RepoConfigIsNotFile,
|
|
||||||
RepoConfigDecodeError,
|
|
||||||
RepoConfigParseError,
|
|
||||||
RepoConfigUnknownError,
|
|
||||||
RepoConfigBranchNotFound,
|
|
||||||
);
|
|
||||||
|
|
||||||
pub async fn load(
|
pub async fn load(
|
||||||
details: &RepoDetails,
|
details: &RepoDetails,
|
||||||
net: &Network,
|
forge: &gitforge::Forge,
|
||||||
) -> Result<RepoConfig, OneOf<RepoConfigLoadErrors>> {
|
) -> Result<RepoConfig, OneOf<(ForgeFileError, toml::de::Error, RepoConfigValidationErrors)>> {
|
||||||
let hostname = &details.forge.hostname;
|
let contents = forge
|
||||||
let path = &details.repo;
|
.file_contents_get(&details.branch, ".git-next.toml")
|
||||||
let filepath = ".git-next.toml";
|
|
||||||
let branch = &details.branch;
|
|
||||||
use secrecy::ExposeSecret;
|
|
||||||
let token = details.forge.token.expose_secret();
|
|
||||||
let url = network::NetUrl::new(format!(
|
|
||||||
"https://{hostname}/api/v1/repos/{path}/contents/{filepath}?ref={branch}&token={token}"
|
|
||||||
));
|
|
||||||
|
|
||||||
info!(%url, "Loading config");
|
|
||||||
let request = network::NetRequest::new(
|
|
||||||
network::RequestMethod::Get,
|
|
||||||
url,
|
|
||||||
network::NetRequestHeaders::new(),
|
|
||||||
network::RequestBody::None,
|
|
||||||
network::ResponseType::Json,
|
|
||||||
None,
|
|
||||||
network::NetRequestLogging::None,
|
|
||||||
);
|
|
||||||
let result = net.get::<ForgeContentsResponse>(request).await;
|
|
||||||
let response = result.map_err(|e| match e {
|
|
||||||
network::NetworkError::RequestFailed(_, status_code, _)
|
|
||||||
| network::NetworkError::RequestError(_, status_code, _) => match status_code {
|
|
||||||
network::StatusCode::NOT_FOUND => OneOf::new(RepoConfigFileNotFound),
|
|
||||||
_ => OneOf::new(RepoConfigUnknownError(status_code)),
|
|
||||||
},
|
|
||||||
_ => OneOf::new(RepoConfigUnknownError(
|
|
||||||
network::StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
)),
|
|
||||||
})?;
|
|
||||||
let status = response.status_code();
|
|
||||||
let config = match response.response_body() {
|
|
||||||
Some(body) => {
|
|
||||||
// we need to decode (see encoding field) the value of 'content' from the response
|
|
||||||
match body.content_type {
|
|
||||||
ForgeContentsType::File => decode_config(body).map_err(OneOf::broaden),
|
|
||||||
_ => Err(OneOf::new(RepoConfigIsNotFile)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
error!(%status, "Failed to fetch repo config file");
|
|
||||||
Err(OneOf::new(RepoConfigUnknownError(status)))
|
|
||||||
}
|
|
||||||
}?;
|
|
||||||
let config = validate(details, config, net)
|
|
||||||
.await
|
.await
|
||||||
.map_err(OneOf::broaden)?;
|
.map_err(OneOf::new)?;
|
||||||
|
let config = RepoConfig::load(&contents).map_err(OneOf::new)?;
|
||||||
|
let config = validate(config, forge).await.map_err(OneOf::new)?;
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode_config(
|
|
||||||
body: ForgeContentsResponse,
|
|
||||||
) -> Result<RepoConfig, OneOf<(RepoConfigDecodeError, RepoConfigParseError)>> {
|
|
||||||
match body.encoding.as_str() {
|
|
||||||
"base64" => {
|
|
||||||
let decoded = base64::engine::general_purpose::STANDARD
|
|
||||||
.decode(body.content)
|
|
||||||
.map_err(|_| OneOf::new(RepoConfigDecodeError))?;
|
|
||||||
let decoded =
|
|
||||||
String::from_utf8(decoded).map_err(|_| OneOf::new(RepoConfigDecodeError))?;
|
|
||||||
let config = toml::from_str(&decoded).map_err(|_| OneOf::new(RepoConfigParseError))?;
|
|
||||||
Ok(config)
|
|
||||||
}
|
|
||||||
_ => Err(OneOf::new(RepoConfigDecodeError)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize)]
|
|
||||||
struct ForgeContentsResponse {
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
pub content_type: ForgeContentsType,
|
|
||||||
pub content: String,
|
|
||||||
pub encoding: String,
|
|
||||||
}
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize)]
|
|
||||||
enum ForgeContentsType {
|
|
||||||
#[serde(rename = "file")]
|
|
||||||
File,
|
|
||||||
#[serde(rename = "dir")]
|
|
||||||
Dir,
|
|
||||||
#[serde(rename = "symlink")]
|
|
||||||
Symlink,
|
|
||||||
#[serde(rename = "submodule")]
|
|
||||||
Submodule,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RepoConfigBranchNotFound(pub BranchName);
|
pub enum RepoConfigValidationErrors {
|
||||||
|
Forge(gitforge::ForgeBranchError),
|
||||||
type RepoConfigValidateErrors = (RepoConfigBranchNotFound, RepoConfigUnknownError);
|
BranchNotFound(BranchName),
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn validate(
|
pub async fn validate(
|
||||||
details: &RepoDetails,
|
|
||||||
config: RepoConfig,
|
config: RepoConfig,
|
||||||
net: &Network,
|
forge: &gitforge::Forge,
|
||||||
) -> Result<RepoConfig, OneOf<RepoConfigValidateErrors>> {
|
) -> Result<RepoConfig, RepoConfigValidationErrors> {
|
||||||
let hostname = &details.forge.hostname;
|
let branches = forge.branches_get_all().await.map_err(|e| {
|
||||||
let path = &details.repo;
|
|
||||||
use secrecy::ExposeSecret;
|
|
||||||
let token = details.forge.token.expose_secret();
|
|
||||||
let url = network::NetUrl::new(format!(
|
|
||||||
"https://{hostname}/api/v1/repos/{path}/branches?token={token}"
|
|
||||||
));
|
|
||||||
|
|
||||||
info!(%url, "Listing branches");
|
|
||||||
let request = network::NetRequest::new(
|
|
||||||
network::RequestMethod::Get,
|
|
||||||
url,
|
|
||||||
network::NetRequestHeaders::new(),
|
|
||||||
network::RequestBody::None,
|
|
||||||
network::ResponseType::Json,
|
|
||||||
None,
|
|
||||||
network::NetRequestLogging::None,
|
|
||||||
);
|
|
||||||
let result = net.get::<BranchList>(request).await;
|
|
||||||
let response = result.map_err(|e| {
|
|
||||||
error!(?e, "Failed to list branches");
|
error!(?e, "Failed to list branches");
|
||||||
OneOf::new(RepoConfigUnknownError(
|
RepoConfigValidationErrors::Forge(e)
|
||||||
network::StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
))
|
|
||||||
})?;
|
})?;
|
||||||
let branches = response.response_body().unwrap_or_default();
|
|
||||||
if !branches
|
if !branches
|
||||||
.iter()
|
.iter()
|
||||||
.any(|branch| branch.name() == config.branches().main())
|
.any(|branch| branch.name() == &config.branches().main())
|
||||||
{
|
{
|
||||||
return Err(OneOf::new(RepoConfigBranchNotFound(
|
return Err(RepoConfigValidationErrors::BranchNotFound(
|
||||||
config.branches().main(),
|
config.branches().main(),
|
||||||
)));
|
));
|
||||||
}
|
}
|
||||||
if !branches
|
if !branches
|
||||||
.iter()
|
.iter()
|
||||||
.any(|branch| branch.name() == config.branches().next())
|
.any(|branch| branch.name() == &config.branches().next())
|
||||||
{
|
{
|
||||||
return Err(OneOf::new(RepoConfigBranchNotFound(
|
return Err(RepoConfigValidationErrors::BranchNotFound(
|
||||||
config.branches().next(),
|
config.branches().next(),
|
||||||
)));
|
));
|
||||||
}
|
}
|
||||||
if !branches
|
if !branches
|
||||||
.iter()
|
.iter()
|
||||||
.any(|branch| branch.name() == config.branches().dev())
|
.any(|branch| branch.name() == &config.branches().dev())
|
||||||
{
|
{
|
||||||
return Err(OneOf::new(RepoConfigBranchNotFound(
|
return Err(RepoConfigValidationErrors::BranchNotFound(
|
||||||
config.branches().dev(),
|
config.branches().dev(),
|
||||||
)));
|
));
|
||||||
}
|
}
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
type BranchList = Vec<Branch>;
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize)]
|
|
||||||
struct Branch {
|
|
||||||
name: String,
|
|
||||||
}
|
|
||||||
impl Branch {
|
|
||||||
fn name(&self) -> BranchName {
|
|
||||||
BranchName(self.name.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,46 +1,10 @@
|
||||||
use kxio::network;
|
use kxio::network;
|
||||||
use secrecy::ExposeSecret;
|
use secrecy::ExposeSecret;
|
||||||
use tracing::{debug, error, warn};
|
|
||||||
|
|
||||||
use crate::server;
|
use crate::server::{self, config::BranchName, forge};
|
||||||
use crate::server::{actors::repo::status::Status, config::BranchName, forge};
|
|
||||||
|
|
||||||
use super::CommitHistories;
|
|
||||||
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
|
||||||
pub async fn get_commit_histories(
|
|
||||||
repo_details: &server::config::RepoDetails,
|
|
||||||
config: &server::config::RepoConfig,
|
|
||||||
net: &kxio::network::Network,
|
|
||||||
) -> Result<CommitHistories, network::NetworkError> {
|
|
||||||
let main = (get_commit_history(repo_details, &config.branches().main(), vec![], net).await)?;
|
|
||||||
let main_head = main[0].clone();
|
|
||||||
let next = (get_commit_history(
|
|
||||||
repo_details,
|
|
||||||
&config.branches().next(),
|
|
||||||
vec![main_head.clone()],
|
|
||||||
net,
|
|
||||||
)
|
|
||||||
.await)?;
|
|
||||||
let next_head = next[0].clone();
|
|
||||||
let dev = (get_commit_history(
|
|
||||||
repo_details,
|
|
||||||
&config.branches().dev(),
|
|
||||||
vec![next_head, main_head],
|
|
||||||
net,
|
|
||||||
)
|
|
||||||
.await)?;
|
|
||||||
debug!(
|
|
||||||
main = main.len(),
|
|
||||||
next = next.len(),
|
|
||||||
dev = dev.len(),
|
|
||||||
"Commit histories"
|
|
||||||
);
|
|
||||||
let histories = CommitHistories { main, next, dev };
|
|
||||||
Ok(histories)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(fields(%branch_name),skip_all)]
|
#[tracing::instrument(fields(%branch_name),skip_all)]
|
||||||
async fn get_commit_history(
|
async fn get_commit_history(
|
||||||
repo_details: &server::config::RepoDetails,
|
repo_details: &server::config::RepoDetails,
|
||||||
|
@ -114,51 +78,6 @@ impl From<Commit> for forge::Commit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_commit_status(
|
|
||||||
next: forge::Commit,
|
|
||||||
repo_details: &crate::server::config::RepoDetails,
|
|
||||||
net: network::Network,
|
|
||||||
) -> Status {
|
|
||||||
let hostname = &repo_details.forge.hostname;
|
|
||||||
let path = &repo_details.repo;
|
|
||||||
let token = repo_details.forge.token.expose_secret();
|
|
||||||
let url = network::NetUrl::new(format!(
|
|
||||||
"https://{hostname}/api/v1/repos/{path}/commits/{next}/status?token={token}"
|
|
||||||
));
|
|
||||||
|
|
||||||
let request = network::NetRequest::new(
|
|
||||||
network::RequestMethod::Get,
|
|
||||||
url,
|
|
||||||
network::NetRequestHeaders::new(),
|
|
||||||
network::RequestBody::None,
|
|
||||||
network::ResponseType::Json,
|
|
||||||
None,
|
|
||||||
network::NetRequestLogging::None,
|
|
||||||
);
|
|
||||||
let result = net.get::<CombinedStatus>(request).await;
|
|
||||||
match result {
|
|
||||||
Ok(response) => {
|
|
||||||
match response.response_body() {
|
|
||||||
Some(status) => match status.state {
|
|
||||||
CommitStatusState::Success => Status::Pass,
|
|
||||||
CommitStatusState::Pending => Status::Pending,
|
|
||||||
CommitStatusState::Failure => Status::Fail,
|
|
||||||
CommitStatusState::Error => Status::Fail,
|
|
||||||
CommitStatusState::Blank => Status::Pending,
|
|
||||||
},
|
|
||||||
None => {
|
|
||||||
warn!("No status found for commit");
|
|
||||||
Status::Pending // assume issue is transient and allow retry
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!(?e, "Failed to get commit status");
|
|
||||||
Status::Pending // assume issue is transient and allow retry
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize)]
|
#[derive(Debug, serde::Deserialize)]
|
||||||
pub struct CombinedStatus {
|
pub struct CombinedStatus {
|
||||||
pub state: CommitStatusState,
|
pub state: CommitStatusState,
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
|
use terrors::OneOf;
|
||||||
|
|
||||||
|
use crate::server::{
|
||||||
|
config::RepoConfig,
|
||||||
|
forge::forgejo::config::RepoConfigValidationErrors,
|
||||||
|
gitforge::{self, ForgeFileError},
|
||||||
|
};
|
||||||
|
|
||||||
pub async fn load(
|
pub async fn load(
|
||||||
_details: &crate::server::config::RepoDetails,
|
_details: &crate::server::config::RepoDetails,
|
||||||
_net: &kxio::network::Network,
|
_forge: &gitforge::Forge,
|
||||||
) -> Result<
|
) -> Result<RepoConfig, OneOf<(ForgeFileError, toml::de::Error, RepoConfigValidationErrors)>> {
|
||||||
crate::server::config::RepoConfig,
|
|
||||||
terrors::OneOf<(
|
|
||||||
crate::server::forge::forgejo::config::RepoConfigFileNotFound,
|
|
||||||
crate::server::forge::forgejo::config::RepoConfigIsNotFile,
|
|
||||||
crate::server::forge::forgejo::config::RepoConfigDecodeError,
|
|
||||||
crate::server::forge::forgejo::config::RepoConfigParseError,
|
|
||||||
crate::server::forge::forgejo::config::RepoConfigUnknownError,
|
|
||||||
crate::server::forge::forgejo::config::RepoConfigBranchNotFound,
|
|
||||||
)>,
|
|
||||||
> {
|
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1 @@
|
||||||
use kxio::network::{Network, NetworkError};
|
|
||||||
|
|
||||||
use crate::server::{
|
|
||||||
config::{RepoConfig, RepoDetails},
|
|
||||||
forge::CommitHistories,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
|
||||||
pub async fn get_commit_histories(
|
|
||||||
_repo_details: &RepoDetails,
|
|
||||||
_config: &RepoConfig,
|
|
||||||
_net: &Network,
|
|
||||||
) -> Result<CommitHistories, NetworkError> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_commit_status(
|
|
||||||
_clone: super::Commit,
|
|
||||||
_repo_details: &RepoDetails,
|
|
||||||
_net: &Network,
|
|
||||||
) -> crate::server::actors::repo::status::Status {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,47 +0,0 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use crate::server::{
|
|
||||||
config::{BranchName, RepoDetails},
|
|
||||||
git::GitResetResult,
|
|
||||||
types::{GitRef, ResetForce},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct MockGit {
|
|
||||||
reset: HashMap<(BranchName, GitRef), GitResetResult>,
|
|
||||||
}
|
|
||||||
impl MockGit {
|
|
||||||
pub(super) fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
reset: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[allow(dead_code)] // TODO: (#38) Add tests
|
|
||||||
pub const fn into_git(self) -> super::Git {
|
|
||||||
super::Git::Mock(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)] // TODO: (#38) Add tests
|
|
||||||
pub fn expect_reset_result(
|
|
||||||
&mut self,
|
|
||||||
branch_name: &BranchName,
|
|
||||||
gitref: &GitRef,
|
|
||||||
result: GitResetResult,
|
|
||||||
) {
|
|
||||||
self.reset
|
|
||||||
.insert((branch_name.clone(), gitref.clone()), result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl super::GitLike for MockGit {
|
|
||||||
fn reset(
|
|
||||||
&self,
|
|
||||||
branch: &BranchName,
|
|
||||||
gitref: GitRef,
|
|
||||||
_reset_force: ResetForce,
|
|
||||||
_repo_details: &RepoDetails,
|
|
||||||
) -> GitResetResult {
|
|
||||||
self.reset
|
|
||||||
.get(&(branch.clone(), gitref.clone()))
|
|
||||||
.map_or_else(|| panic!("unexpected: {} to {}", branch, gitref), |r| *r)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
use crate::server::{
|
|
||||||
config::{BranchName, RepoDetails},
|
|
||||||
git::{mock::MockGit, real::RealGit},
|
|
||||||
types::{GitRef, ResetForce},
|
|
||||||
};
|
|
||||||
|
|
||||||
mod mock;
|
|
||||||
mod real;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum Git {
|
|
||||||
Real(RealGit),
|
|
||||||
#[allow(dead_code)] // TODO: (#38) Add tests
|
|
||||||
Mock(MockGit),
|
|
||||||
}
|
|
||||||
impl Git {
|
|
||||||
pub const fn new_real() -> Self {
|
|
||||||
Self::Real(RealGit::new())
|
|
||||||
}
|
|
||||||
#[allow(dead_code)] // TODO: (#38) Add tests
|
|
||||||
pub fn new_mock() -> MockGit {
|
|
||||||
MockGit::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Deref for Git {
|
|
||||||
type Target = dyn GitLike;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
match self {
|
|
||||||
Self::Real(git) => git,
|
|
||||||
Self::Mock(git) => git,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait GitLike: Sync + Send {
|
|
||||||
fn reset(
|
|
||||||
&self,
|
|
||||||
branch: &BranchName,
|
|
||||||
gitref: GitRef,
|
|
||||||
reset_force: ResetForce,
|
|
||||||
repo_details: &RepoDetails,
|
|
||||||
) -> GitResetResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type GitResetResult = Result<(), ()>;
|
|
|
@ -1,57 +0,0 @@
|
||||||
use secrecy::ExposeSecret;
|
|
||||||
use tracing::{info, warn};
|
|
||||||
|
|
||||||
use crate::server::{
|
|
||||||
config::{BranchName, RepoDetails},
|
|
||||||
git::GitResetResult,
|
|
||||||
types::{GitRef, ResetForce},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct RealGit;
|
|
||||||
impl RealGit {
|
|
||||||
pub(super) const fn new() -> Self {
|
|
||||||
Self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl super::GitLike for RealGit {
|
|
||||||
fn reset(
|
|
||||||
&self,
|
|
||||||
branch: &BranchName,
|
|
||||||
gitref: GitRef,
|
|
||||||
reset_force: ResetForce,
|
|
||||||
repo_details: &RepoDetails,
|
|
||||||
) -> GitResetResult {
|
|
||||||
let user = &repo_details.forge.user;
|
|
||||||
let hostname = &repo_details.forge.hostname;
|
|
||||||
let path = &repo_details.repo;
|
|
||||||
let token = &repo_details.forge.token.expose_secret();
|
|
||||||
let origin = format!("https://{user}:{token}@{hostname}/{path}.git");
|
|
||||||
let force = match reset_force {
|
|
||||||
ResetForce::None => "".to_string(),
|
|
||||||
ResetForce::Force(old_ref) => format!("--force-with-lease={branch}:{old_ref}"),
|
|
||||||
};
|
|
||||||
// INFO: never log the command as it contains the API token
|
|
||||||
let command = format!("/usr/bin/git push {origin} {gitref}:{branch} {force}");
|
|
||||||
drop(origin);
|
|
||||||
info!("Resetting {branch} to {gitref}");
|
|
||||||
match gix::command::prepare(command)
|
|
||||||
.with_shell_allow_argument_splitting()
|
|
||||||
.stdout(std::process::Stdio::null())
|
|
||||||
.stderr(std::process::Stdio::null())
|
|
||||||
.spawn()
|
|
||||||
{
|
|
||||||
Ok(mut child) => match child.wait() {
|
|
||||||
Ok(_) => Ok(()),
|
|
||||||
Err(err) => {
|
|
||||||
warn!(?err, "Failed (wait)");
|
|
||||||
Err(())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(err) => {
|
|
||||||
warn!(?err, "Failed (spawn)");
|
|
||||||
Err(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
41
src/server/gitforge/errors.rs
Normal file
41
src/server/gitforge/errors.rs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
use crate::server::config::BranchName;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ForgeFileError {
|
||||||
|
NotFound(String),
|
||||||
|
ParseContent,
|
||||||
|
DecodeFromBase64,
|
||||||
|
DecodeFromUtf8,
|
||||||
|
UnknownEncoding(String),
|
||||||
|
NotFile(String),
|
||||||
|
Unknown(String),
|
||||||
|
}
|
||||||
|
impl std::error::Error for ForgeFileError {}
|
||||||
|
impl std::fmt::Display for ForgeFileError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::NotFound(file_path) => write!(f, "File not found: {file_path}"),
|
||||||
|
Self::NotFile(file_path) => write!(f, "Not a file: {file_path}"),
|
||||||
|
Self::DecodeFromBase64 => write!(f, "Unable to decode from base64"),
|
||||||
|
Self::DecodeFromUtf8 => write!(f, "Unable to decode from UTF-8"),
|
||||||
|
Self::UnknownEncoding(encoding) => write!(f, "Unknown file encoding: {encoding}"),
|
||||||
|
Self::ParseContent => write!(f, "Unable to parse file contents"),
|
||||||
|
Self::Unknown(status) => write!(f, "Unknown error (status: {status})"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ForgeBranchError {
|
||||||
|
NotFound(BranchName),
|
||||||
|
NoneFound,
|
||||||
|
}
|
||||||
|
impl std::error::Error for ForgeBranchError {}
|
||||||
|
impl std::fmt::Display for ForgeBranchError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::NotFound(branch_name) => write!(f, "Branch not found: {branch_name}"),
|
||||||
|
Self::NoneFound => write!(f, "Unable to find any branches"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
54
src/server/gitforge/forgejo/branch/get_all.rs
Normal file
54
src/server/gitforge/forgejo/branch/get_all.rs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
use kxio::network::{self, Network};
|
||||||
|
use tracing::{error, info};
|
||||||
|
|
||||||
|
use crate::server::{
|
||||||
|
config::{BranchName, RepoDetails},
|
||||||
|
gitforge::{self, ForgeBranchError},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn get_all(
|
||||||
|
repo_details: &RepoDetails,
|
||||||
|
net: &Network,
|
||||||
|
) -> Result<Vec<gitforge::Branch>, ForgeBranchError> {
|
||||||
|
let hostname = &repo_details.forge.hostname;
|
||||||
|
let path = &repo_details.repo;
|
||||||
|
use secrecy::ExposeSecret;
|
||||||
|
let token = repo_details.forge.token.expose_secret();
|
||||||
|
let url = network::NetUrl::new(format!(
|
||||||
|
"https://{hostname}/api/v1/repos/{path}/branches?token={token}"
|
||||||
|
));
|
||||||
|
|
||||||
|
info!(%url, "Listing branches");
|
||||||
|
let request = network::NetRequest::new(
|
||||||
|
network::RequestMethod::Get,
|
||||||
|
url,
|
||||||
|
network::NetRequestHeaders::new(),
|
||||||
|
network::RequestBody::None,
|
||||||
|
network::ResponseType::Json,
|
||||||
|
None,
|
||||||
|
network::NetRequestLogging::None,
|
||||||
|
);
|
||||||
|
let result = net.get::<Vec<Branch>>(request).await;
|
||||||
|
let response = result.map_err(|e| {
|
||||||
|
error!(?e, "Failed to list branches");
|
||||||
|
ForgeBranchError::NoneFound // BranchListNotAvailable
|
||||||
|
})?;
|
||||||
|
let branches = response
|
||||||
|
.response_body()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.map(|b| b.name())
|
||||||
|
.map(gitforge::Branch)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
Ok(branches)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
struct Branch {
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
impl Branch {
|
||||||
|
fn name(&self) -> BranchName {
|
||||||
|
BranchName(self.name.clone())
|
||||||
|
}
|
||||||
|
}
|
7
src/server/gitforge/forgejo/branch/mod.rs
Normal file
7
src/server/gitforge/forgejo/branch/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
mod get_all;
|
||||||
|
mod reset;
|
||||||
|
mod validate_positions;
|
||||||
|
|
||||||
|
pub use get_all::get_all;
|
||||||
|
pub use reset::reset;
|
||||||
|
pub use validate_positions::validate_positions;
|
48
src/server/gitforge/forgejo/branch/reset.rs
Normal file
48
src/server/gitforge/forgejo/branch/reset.rs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
use tracing::{info, warn};
|
||||||
|
|
||||||
|
use crate::server::{
|
||||||
|
config::{BranchName, RepoDetails},
|
||||||
|
gitforge::{BranchResetResult, Force},
|
||||||
|
types::GitRef,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn reset(
|
||||||
|
repo_details: &RepoDetails,
|
||||||
|
branch_name: BranchName,
|
||||||
|
to_commit: GitRef,
|
||||||
|
force: Force,
|
||||||
|
) -> BranchResetResult {
|
||||||
|
let user = &repo_details.forge.user;
|
||||||
|
let hostname = &repo_details.forge.hostname;
|
||||||
|
let path = &repo_details.repo;
|
||||||
|
use secrecy::ExposeSecret;
|
||||||
|
let expose_secret = &repo_details.forge.token;
|
||||||
|
let token = expose_secret.expose_secret();
|
||||||
|
let origin = format!("https://{user}:{token}@{hostname}/{path}.git");
|
||||||
|
let force = match force {
|
||||||
|
Force::No => "".to_string(),
|
||||||
|
Force::From(old_ref) => format!("--force-with-lease={branch_name}:{old_ref}"),
|
||||||
|
};
|
||||||
|
// INFO: never log the command as it contains the API token within the 'origin'
|
||||||
|
let command = format!("/usr/bin/git push {origin} {to_commit}:{branch_name} {force}");
|
||||||
|
drop(origin);
|
||||||
|
info!("Resetting {branch_name} to {to_commit}");
|
||||||
|
match gix::command::prepare(command)
|
||||||
|
.with_shell_allow_argument_splitting()
|
||||||
|
.stdout(std::process::Stdio::null())
|
||||||
|
.stderr(std::process::Stdio::null())
|
||||||
|
.spawn()
|
||||||
|
{
|
||||||
|
Ok(mut child) => match child.wait() {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(err) => {
|
||||||
|
warn!(?err, "Failed (wait)");
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
warn!(?err, "Failed (spawn)");
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
229
src/server/gitforge/forgejo/branch/validate_positions.rs
Normal file
229
src/server/gitforge/forgejo/branch/validate_positions.rs
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
use actix::prelude::*;
|
||||||
|
|
||||||
|
use kxio::network;
|
||||||
|
use tracing::{debug, error, info, warn};
|
||||||
|
|
||||||
|
use crate::server::{
|
||||||
|
self,
|
||||||
|
actors::repo::{RepoActor, StartMonitoring},
|
||||||
|
config::{BranchName, RepoConfig, RepoDetails},
|
||||||
|
forge,
|
||||||
|
gitforge::{forgejo::ForgeJoEnv, Force, ForgeLike},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn validate_positions(
|
||||||
|
forge: &ForgeJoEnv,
|
||||||
|
repo_config: RepoConfig,
|
||||||
|
addr: Addr<RepoActor>,
|
||||||
|
) {
|
||||||
|
let repo_details = &forge.repo_details;
|
||||||
|
// Collect Commit Histories for `main`, `next` and `dev` branches
|
||||||
|
let commit_histories = get_commit_histories(repo_details, &repo_config, &forge.net).await;
|
||||||
|
let commit_histories = match commit_histories {
|
||||||
|
Ok(commit_histories) => commit_histories,
|
||||||
|
Err(err) => {
|
||||||
|
error!(?err, "Failed to get commit histories");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Validations
|
||||||
|
let Some(main) = commit_histories.main.first().cloned() else {
|
||||||
|
warn!(
|
||||||
|
"No commits on main branch '{}'",
|
||||||
|
repo_config.branches().main()
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
// verify that next is an ancestor of dev, and force update to it main if it isn't
|
||||||
|
let Some(next) = commit_histories.next.first().cloned() else {
|
||||||
|
warn!(
|
||||||
|
"No commits on next branch '{}",
|
||||||
|
repo_config.branches().next()
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let next_is_ancestor_of_dev = commit_histories.dev.iter().any(|dev| dev == &next);
|
||||||
|
if !next_is_ancestor_of_dev {
|
||||||
|
info!("Next is not an ancestor of dev - resetting next to main");
|
||||||
|
if let Err(err) = forge.branch_reset(
|
||||||
|
repo_config.branches().next(),
|
||||||
|
main.into(),
|
||||||
|
Force::From(next.into()),
|
||||||
|
) {
|
||||||
|
warn!(?err, "Failed to reset next to main");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let next_commits = commit_histories
|
||||||
|
.next
|
||||||
|
.into_iter()
|
||||||
|
.take(2)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if !next_commits.contains(&main) {
|
||||||
|
warn!(
|
||||||
|
"Main branch '{}' is not on the same commit as next branch '{}', or it's parent - resetting next to main",
|
||||||
|
repo_config.branches().main(),
|
||||||
|
repo_config.branches().next()
|
||||||
|
);
|
||||||
|
if let Err(err) = forge.branch_reset(
|
||||||
|
repo_config.branches().next(),
|
||||||
|
main.into(),
|
||||||
|
Force::From(next.into()),
|
||||||
|
) {
|
||||||
|
warn!(?err, "Failed to reset next to main");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some(next) = next_commits.first().cloned() else {
|
||||||
|
warn!(
|
||||||
|
"No commits on next branch '{}'",
|
||||||
|
repo_config.branches().next()
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let dev_has_next = commit_histories
|
||||||
|
.dev
|
||||||
|
.iter()
|
||||||
|
.any(|commit| commit == &next_commits[0]);
|
||||||
|
if !dev_has_next {
|
||||||
|
warn!(
|
||||||
|
"Dev branch '{}' is not based on next branch '{}' - next branch will be updated shortly",
|
||||||
|
repo_config.branches().dev(),
|
||||||
|
repo_config.branches().next()
|
||||||
|
);
|
||||||
|
return; // dev is not based on next
|
||||||
|
}
|
||||||
|
let dev_has_main = commit_histories.dev.iter().any(|commit| commit == &main);
|
||||||
|
if !dev_has_main {
|
||||||
|
warn!(
|
||||||
|
"Dev branch '{}' is not based on main branch '{}' - user should rebase onto main branch '{}'",
|
||||||
|
repo_config.branches().dev(),
|
||||||
|
repo_config.branches().main(),
|
||||||
|
repo_config.branches().main(),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some(dev) = commit_histories.dev.first().cloned() else {
|
||||||
|
warn!(
|
||||||
|
"No commits on dev branch '{}'",
|
||||||
|
repo_config.branches().dev()
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
addr.do_send(StartMonitoring {
|
||||||
|
main,
|
||||||
|
next,
|
||||||
|
dev,
|
||||||
|
dev_commit_history: commit_histories.dev,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_commit_histories(
|
||||||
|
repo_details: &RepoDetails,
|
||||||
|
repo_config: &RepoConfig,
|
||||||
|
net: &network::Network,
|
||||||
|
) -> Result<forge::CommitHistories, network::NetworkError> {
|
||||||
|
let main =
|
||||||
|
(get_commit_history(repo_details, &repo_config.branches().main(), vec![], net).await)?;
|
||||||
|
let main_head = main[0].clone();
|
||||||
|
let next = (get_commit_history(
|
||||||
|
repo_details,
|
||||||
|
&repo_config.branches().next(),
|
||||||
|
vec![main_head.clone()],
|
||||||
|
net,
|
||||||
|
)
|
||||||
|
.await)?;
|
||||||
|
let next_head = next[0].clone();
|
||||||
|
let dev = (get_commit_history(
|
||||||
|
repo_details,
|
||||||
|
&repo_config.branches().dev(),
|
||||||
|
vec![next_head, main_head],
|
||||||
|
net,
|
||||||
|
)
|
||||||
|
.await)?;
|
||||||
|
debug!(
|
||||||
|
main = main.len(),
|
||||||
|
next = next.len(),
|
||||||
|
dev = dev.len(),
|
||||||
|
"Commit histories"
|
||||||
|
);
|
||||||
|
let histories = forge::CommitHistories { main, next, dev };
|
||||||
|
Ok(histories)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(fields(%branch_name),skip_all)]
|
||||||
|
async fn get_commit_history(
|
||||||
|
repo_details: &server::config::RepoDetails,
|
||||||
|
branch_name: &BranchName,
|
||||||
|
find_commits: Vec<forge::Commit>,
|
||||||
|
net: &kxio::network::Network,
|
||||||
|
) -> Result<Vec<forge::Commit>, network::NetworkError> {
|
||||||
|
let hostname = &repo_details.forge.hostname;
|
||||||
|
let path = &repo_details.repo;
|
||||||
|
|
||||||
|
let mut page = 1;
|
||||||
|
let limit = match find_commits.is_empty() {
|
||||||
|
true => 1,
|
||||||
|
false => 50,
|
||||||
|
};
|
||||||
|
let options = "stat=false&verification=false&files=false";
|
||||||
|
let mut all_commits = Vec::new();
|
||||||
|
loop {
|
||||||
|
let api_token = &repo_details.forge.token;
|
||||||
|
use secrecy::ExposeSecret;
|
||||||
|
let token = api_token.expose_secret();
|
||||||
|
let url = network::NetUrl::new(format!(
|
||||||
|
"https://{hostname}/api/v1/repos/{path}/commits?sha={branch_name}&{options}&token={token}&page={page}&limit={limit}"
|
||||||
|
));
|
||||||
|
|
||||||
|
let request = network::NetRequest::new(
|
||||||
|
network::RequestMethod::Get,
|
||||||
|
url,
|
||||||
|
network::NetRequestHeaders::new(),
|
||||||
|
network::RequestBody::None,
|
||||||
|
network::ResponseType::Json,
|
||||||
|
None,
|
||||||
|
network::NetRequestLogging::None,
|
||||||
|
);
|
||||||
|
let response = net.get::<Vec<Commit>>(request).await?;
|
||||||
|
let commits = response
|
||||||
|
.response_body()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.map(forge::Commit::from)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let found = find_commits.is_empty()
|
||||||
|
|| find_commits
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.any(|find_commit| commits.iter().any(|commit| commit == &find_commit));
|
||||||
|
let at_end = commits.len() < limit;
|
||||||
|
all_commits.extend(commits);
|
||||||
|
if found || at_end {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
page += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(all_commits)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug, Default, serde::Deserialize)]
|
||||||
|
struct Commit {
|
||||||
|
sha: String,
|
||||||
|
commit: RepoCommit,
|
||||||
|
}
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug, Default, serde::Deserialize)]
|
||||||
|
struct RepoCommit {
|
||||||
|
message: String,
|
||||||
|
}
|
||||||
|
impl From<Commit> for forge::Commit {
|
||||||
|
fn from(value: Commit) -> Self {
|
||||||
|
Self::new(&value.sha, &value.commit.message)
|
||||||
|
}
|
||||||
|
}
|
88
src/server/gitforge/forgejo/file/mod.rs
Normal file
88
src/server/gitforge/forgejo/file/mod.rs
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
use kxio::network::{self, Network};
|
||||||
|
use tracing::{error, info, warn};
|
||||||
|
|
||||||
|
use crate::server::{
|
||||||
|
config::{BranchName, RepoDetails},
|
||||||
|
gitforge::ForgeFileError,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(super) async fn contents_get(
|
||||||
|
repo_details: &RepoDetails,
|
||||||
|
net: &Network,
|
||||||
|
branch: &BranchName,
|
||||||
|
file_path: &str,
|
||||||
|
) -> Result<String, ForgeFileError> {
|
||||||
|
let hostname = &repo_details.forge.hostname;
|
||||||
|
let path = &repo_details.repo;
|
||||||
|
let api_token = &repo_details.forge.token;
|
||||||
|
use secrecy::ExposeSecret;
|
||||||
|
let token = api_token.expose_secret();
|
||||||
|
let url = network::NetUrl::new(format!(
|
||||||
|
"https://{hostname}/api/v1/repos/{path}/contents/{file_path}?ref={branch}&token={token}"
|
||||||
|
));
|
||||||
|
|
||||||
|
info!(%url, "Loading config");
|
||||||
|
let request = network::NetRequest::new(
|
||||||
|
network::RequestMethod::Get,
|
||||||
|
url,
|
||||||
|
network::NetRequestHeaders::new(),
|
||||||
|
network::RequestBody::None,
|
||||||
|
network::ResponseType::Json,
|
||||||
|
None,
|
||||||
|
network::NetRequestLogging::None,
|
||||||
|
);
|
||||||
|
let result = net.get::<ForgeContentsResponse>(request).await;
|
||||||
|
let response = result.map_err(|e| {
|
||||||
|
warn!(?e, "");
|
||||||
|
ForgeFileError::NotFound(file_path.to_string())
|
||||||
|
})?;
|
||||||
|
let status = response.status_code();
|
||||||
|
let contents = match response.response_body() {
|
||||||
|
Some(body) => {
|
||||||
|
// we need to decode (see encoding field) the value of 'content' from the response
|
||||||
|
match body.content_type {
|
||||||
|
ForgeContentsType::File => decode_config(body),
|
||||||
|
_ => Err(ForgeFileError::NotFile(file_path.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
error!(%status, "Failed to fetch repo config file");
|
||||||
|
Err(ForgeFileError::Unknown(status.to_string()))
|
||||||
|
}
|
||||||
|
}?;
|
||||||
|
Ok(contents)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_config(body: ForgeContentsResponse) -> Result<String, ForgeFileError> {
|
||||||
|
use base64::Engine;
|
||||||
|
match body.encoding.as_str() {
|
||||||
|
"base64" => {
|
||||||
|
let decoded = base64::engine::general_purpose::STANDARD
|
||||||
|
.decode(body.content)
|
||||||
|
.map_err(|_| ForgeFileError::DecodeFromBase64)?;
|
||||||
|
let decoded = String::from_utf8(decoded).map_err(|_| ForgeFileError::DecodeFromUtf8)?;
|
||||||
|
|
||||||
|
Ok(decoded)
|
||||||
|
}
|
||||||
|
encoding => Err(ForgeFileError::UnknownEncoding(encoding.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize)]
|
||||||
|
struct ForgeContentsResponse {
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub content_type: ForgeContentsType,
|
||||||
|
pub content: String,
|
||||||
|
pub encoding: String,
|
||||||
|
}
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize)]
|
||||||
|
enum ForgeContentsType {
|
||||||
|
#[serde(rename = "file")]
|
||||||
|
File,
|
||||||
|
#[serde(rename = "dir")]
|
||||||
|
Dir,
|
||||||
|
#[serde(rename = "symlink")]
|
||||||
|
Symlink,
|
||||||
|
#[serde(rename = "submodule")]
|
||||||
|
Submodule,
|
||||||
|
}
|
121
src/server/gitforge/forgejo/mod.rs
Normal file
121
src/server/gitforge/forgejo/mod.rs
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
mod branch;
|
||||||
|
mod file;
|
||||||
|
|
||||||
|
use actix::prelude::*;
|
||||||
|
|
||||||
|
use kxio::network::{self, Network};
|
||||||
|
use tracing::{error, warn};
|
||||||
|
|
||||||
|
use crate::server::{
|
||||||
|
actors::repo::RepoActor,
|
||||||
|
config::{BranchName, RepoConfig, RepoDetails},
|
||||||
|
forge::Commit,
|
||||||
|
gitforge::{BranchResetResult, CommitStatus, Force, ForgeBranchError, ForgeFileError},
|
||||||
|
types::GitRef,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ForgeJo;
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ForgeJoEnv {
|
||||||
|
repo_details: RepoDetails,
|
||||||
|
net: Network,
|
||||||
|
}
|
||||||
|
impl ForgeJoEnv {
|
||||||
|
pub(super) const fn new(repo_details: RepoDetails, net: Network) -> Self {
|
||||||
|
Self { repo_details, net }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl super::ForgeLike for ForgeJoEnv {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
"forgejo".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn branches_get_all(&self) -> Result<Vec<super::Branch>, ForgeBranchError> {
|
||||||
|
branch::get_all(&self.repo_details, &self.net).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn file_contents_get(
|
||||||
|
&self,
|
||||||
|
branch: &BranchName,
|
||||||
|
file_path: &str,
|
||||||
|
) -> Result<String, ForgeFileError> {
|
||||||
|
file::contents_get(&self.repo_details, &self.net, branch, file_path).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn branches_validate_positions(&self, repo_config: RepoConfig, addr: Addr<RepoActor>) {
|
||||||
|
branch::validate_positions(self, repo_config, addr).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn branch_reset(
|
||||||
|
&self,
|
||||||
|
branch_name: BranchName,
|
||||||
|
to_commit: GitRef,
|
||||||
|
force: Force,
|
||||||
|
) -> BranchResetResult {
|
||||||
|
branch::reset(&self.repo_details, branch_name, to_commit, force)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn commit_status(&self, commit: &Commit) -> CommitStatus {
|
||||||
|
let repo_details = &self.repo_details;
|
||||||
|
let hostname = &repo_details.forge.hostname;
|
||||||
|
let path = &repo_details.repo;
|
||||||
|
let api_token = &repo_details.forge.token;
|
||||||
|
use secrecy::ExposeSecret;
|
||||||
|
let token = api_token.expose_secret();
|
||||||
|
let url = network::NetUrl::new(format!(
|
||||||
|
"https://{hostname}/api/v1/repos/{path}/commits/{commit}/status?token={token}"
|
||||||
|
));
|
||||||
|
|
||||||
|
let request = network::NetRequest::new(
|
||||||
|
network::RequestMethod::Get,
|
||||||
|
url,
|
||||||
|
network::NetRequestHeaders::new(),
|
||||||
|
network::RequestBody::None,
|
||||||
|
network::ResponseType::Json,
|
||||||
|
None,
|
||||||
|
network::NetRequestLogging::None,
|
||||||
|
);
|
||||||
|
let result = self.net.get::<CombinedStatus>(request).await;
|
||||||
|
match result {
|
||||||
|
Ok(response) => {
|
||||||
|
match response.response_body() {
|
||||||
|
Some(status) => match status.state {
|
||||||
|
CommitStatusState::Success => CommitStatus::Pass,
|
||||||
|
CommitStatusState::Pending => CommitStatus::Pending,
|
||||||
|
CommitStatusState::Failure => CommitStatus::Fail,
|
||||||
|
CommitStatusState::Error => CommitStatus::Fail,
|
||||||
|
CommitStatusState::Blank => CommitStatus::Pending,
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
warn!("No status found for commit");
|
||||||
|
CommitStatus::Pending // assume issue is transient and allow retry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!(?e, "Failed to get commit status");
|
||||||
|
CommitStatus::Pending // assume issue is transient and allow retry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
pub struct CombinedStatus {
|
||||||
|
pub state: CommitStatusState,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
pub enum CommitStatusState {
|
||||||
|
#[serde(rename = "success")]
|
||||||
|
Success,
|
||||||
|
#[serde(rename = "pending")]
|
||||||
|
Pending,
|
||||||
|
#[serde(rename = "failure")]
|
||||||
|
Failure,
|
||||||
|
#[serde(rename = "error")]
|
||||||
|
Error,
|
||||||
|
#[serde(rename = "")]
|
||||||
|
Blank,
|
||||||
|
}
|
21
src/server/gitforge/github.rs
Normal file
21
src/server/gitforge/github.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
use crate::network::Network;
|
||||||
|
|
||||||
|
struct Github;
|
||||||
|
pub(super) struct GithubEnv {
|
||||||
|
net: Network,
|
||||||
|
}
|
||||||
|
impl GithubEnv {
|
||||||
|
pub(crate) const fn new(net: Network) -> GithubEnv {
|
||||||
|
Self { net }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl super::ForgeLike for GithubEnv {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
"github".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn branches_get_all(&self) -> Vec<super::Branch> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
55
src/server/gitforge/mock_forge.rs
Normal file
55
src/server/gitforge/mock_forge.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
use crate::server::{
|
||||||
|
actors::repo::RepoActor,
|
||||||
|
config::{BranchName, RepoConfig},
|
||||||
|
forge::Commit,
|
||||||
|
gitforge::{BranchResetResult, CommitStatus, Force, ForgeBranchError, ForgeFileError},
|
||||||
|
types::GitRef,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MockForge;
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct MockForgeEnv;
|
||||||
|
impl MockForgeEnv {
|
||||||
|
pub(crate) const fn new() -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl super::ForgeLike for MockForgeEnv {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
"mock".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn branches_get_all(&self) -> Result<Vec<super::Branch>, ForgeBranchError> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn file_contents_get(
|
||||||
|
&self,
|
||||||
|
_branch: &BranchName,
|
||||||
|
_file_path: &str,
|
||||||
|
) -> Result<String, ForgeFileError> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn branches_validate_positions(
|
||||||
|
&self,
|
||||||
|
_repo_config: RepoConfig,
|
||||||
|
_addr: actix::prelude::Addr<RepoActor>,
|
||||||
|
) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn branch_reset(
|
||||||
|
&self,
|
||||||
|
_branch_name: BranchName,
|
||||||
|
_to_commit: GitRef,
|
||||||
|
_force: Force,
|
||||||
|
) -> BranchResetResult {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn commit_status(&self, _commit: &Commit) -> CommitStatus {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
85
src/server/gitforge/mod.rs
Normal file
85
src/server/gitforge/mod.rs
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use kxio::network::Network;
|
||||||
|
|
||||||
|
#[cfg(feature = "forgejo")]
|
||||||
|
mod forgejo;
|
||||||
|
|
||||||
|
#[cfg(feature = "github")]
|
||||||
|
mod github;
|
||||||
|
|
||||||
|
mod mock_forge;
|
||||||
|
|
||||||
|
mod types;
|
||||||
|
pub use types::*;
|
||||||
|
|
||||||
|
mod errors;
|
||||||
|
pub use errors::*;
|
||||||
|
|
||||||
|
use crate::server::{
|
||||||
|
config::{BranchName, RepoConfig, RepoDetails},
|
||||||
|
forge::Commit,
|
||||||
|
types::GitRef,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
pub trait ForgeLike {
|
||||||
|
fn name(&self) -> String;
|
||||||
|
async fn branches_get_all(&self) -> Result<Vec<Branch>, ForgeBranchError>;
|
||||||
|
async fn file_contents_get(
|
||||||
|
&self,
|
||||||
|
branch: &super::config::BranchName,
|
||||||
|
file_path: &str,
|
||||||
|
) -> Result<String, ForgeFileError>;
|
||||||
|
async fn branches_validate_positions(
|
||||||
|
&self,
|
||||||
|
repo_config: RepoConfig,
|
||||||
|
addr: actix::prelude::Addr<super::actors::repo::RepoActor>,
|
||||||
|
);
|
||||||
|
fn branch_reset(
|
||||||
|
&self,
|
||||||
|
branch_name: BranchName,
|
||||||
|
to_commit: GitRef,
|
||||||
|
force: Force,
|
||||||
|
) -> BranchResetResult;
|
||||||
|
|
||||||
|
async fn commit_status(&self, commit: &Commit) -> CommitStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum Forge {
|
||||||
|
Mock(mock_forge::MockForgeEnv),
|
||||||
|
#[allow(clippy::enum_variant_names)]
|
||||||
|
#[cfg(feature = "forgejo")]
|
||||||
|
ForgeJo(forgejo::ForgeJoEnv),
|
||||||
|
#[cfg(feature = "github")]
|
||||||
|
Github(github::GithubEnv),
|
||||||
|
}
|
||||||
|
impl Forge {
|
||||||
|
pub const fn new_mock() -> Self {
|
||||||
|
Self::Mock(mock_forge::MockForgeEnv::new())
|
||||||
|
}
|
||||||
|
#[cfg(feature = "forgejo")]
|
||||||
|
pub const fn new_forgejo(repo_details: RepoDetails, net: Network) -> Self {
|
||||||
|
Self::ForgeJo(forgejo::ForgeJoEnv::new(repo_details, net))
|
||||||
|
}
|
||||||
|
#[cfg(feature = "github")]
|
||||||
|
pub const fn new_github(net: Network) -> Self {
|
||||||
|
Self::Github(github::GithubEnv::new(net))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::ops::Deref for Forge {
|
||||||
|
type Target = dyn ForgeLike;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
match self {
|
||||||
|
Self::Mock(env) => env,
|
||||||
|
#[cfg(feature = "forgejo")]
|
||||||
|
Self::ForgeJo(env) => env,
|
||||||
|
#[cfg(feature = "github")]
|
||||||
|
Forge::Github(env) => env,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
59
src/server/gitforge/tests/common.rs
Normal file
59
src/server/gitforge/tests/common.rs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
use crate::server::config::{
|
||||||
|
ApiToken, BranchName, ForgeDetails, ForgeName, ForgeType, Hostname, RepoAlias, RepoBranches,
|
||||||
|
RepoConfig, RepoDetails, RepoPath, User,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn forge_details(n: u32, forge_type: ForgeType) -> ForgeDetails {
|
||||||
|
ForgeDetails {
|
||||||
|
name: forge_name(n),
|
||||||
|
forge_type,
|
||||||
|
hostname: hostname(n),
|
||||||
|
user: user(n),
|
||||||
|
token: api_token(n),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn api_token(n: u32) -> ApiToken {
|
||||||
|
ApiToken::from(format!("api-{}", n))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn user(n: u32) -> User {
|
||||||
|
User(format!("user-{}", n))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hostname(n: u32) -> Hostname {
|
||||||
|
Hostname(format!("hostname-{}", n))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn forge_name(n: u32) -> ForgeName {
|
||||||
|
ForgeName(format!("forge-name-{}", n))
|
||||||
|
}
|
||||||
|
pub fn repo_details(n: u32, forge: ForgeDetails, config: Option<RepoConfig>) -> RepoDetails {
|
||||||
|
RepoDetails {
|
||||||
|
name: repo_alias(n),
|
||||||
|
repo: repo_path(n),
|
||||||
|
branch: branch_name(n),
|
||||||
|
forge,
|
||||||
|
config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn branch_name(n: u32) -> BranchName {
|
||||||
|
BranchName(format!("branch-name-{}", n))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn repo_path(n: u32) -> RepoPath {
|
||||||
|
RepoPath(format!("repo-path-{}", n))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn repo_alias(n: u32) -> RepoAlias {
|
||||||
|
RepoAlias(format!("repo-alias-{}", n))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn config(n: u32) -> RepoConfig {
|
||||||
|
RepoConfig::new(RepoBranches::new(
|
||||||
|
format!("main-{n}"),
|
||||||
|
format!("next-{n}"),
|
||||||
|
format!("dev-{n}"),
|
||||||
|
))
|
||||||
|
}
|
50
src/server/gitforge/tests/data-forgejo-branches-get.json
Normal file
50
src/server/gitforge/tests/data-forgejo-branches-get.json
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"commit": {
|
||||||
|
"added": [
|
||||||
|
"string"
|
||||||
|
],
|
||||||
|
"author": {
|
||||||
|
"email": "user@example.com",
|
||||||
|
"name": "string",
|
||||||
|
"username": "string"
|
||||||
|
},
|
||||||
|
"committer": {
|
||||||
|
"email": "user@example.com",
|
||||||
|
"name": "string",
|
||||||
|
"username": "string"
|
||||||
|
},
|
||||||
|
"id": "string",
|
||||||
|
"message": "string",
|
||||||
|
"modified": [
|
||||||
|
"string"
|
||||||
|
],
|
||||||
|
"removed": [
|
||||||
|
"string"
|
||||||
|
],
|
||||||
|
"timestamp": "2024-04-16T21:35:56.331Z",
|
||||||
|
"url": "string",
|
||||||
|
"verification": {
|
||||||
|
"payload": "string",
|
||||||
|
"reason": "string",
|
||||||
|
"signature": "string",
|
||||||
|
"signer": {
|
||||||
|
"email": "user@example.com",
|
||||||
|
"name": "string",
|
||||||
|
"username": "string"
|
||||||
|
},
|
||||||
|
"verified": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"effective_branch_protection_name": "string",
|
||||||
|
"enable_status_check": true,
|
||||||
|
"name": "string",
|
||||||
|
"protected": true,
|
||||||
|
"required_approvals": 0,
|
||||||
|
"status_check_contexts": [
|
||||||
|
"string"
|
||||||
|
],
|
||||||
|
"user_can_merge": true,
|
||||||
|
"user_can_push": true
|
||||||
|
}
|
||||||
|
]
|
48
src/server/gitforge/tests/forgejo.rs
Normal file
48
src/server/gitforge/tests/forgejo.rs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
use assert2::let_assert;
|
||||||
|
|
||||||
|
use kxio::network::{MockNetwork, StatusCode};
|
||||||
|
|
||||||
|
use crate::server::config::{BranchName, ForgeType};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_name() {
|
||||||
|
let net = Network::new_mock();
|
||||||
|
let repo_details = common::repo_details(
|
||||||
|
1,
|
||||||
|
common::forge_details(1, ForgeType::MockForge),
|
||||||
|
Some(common::config(1)),
|
||||||
|
);
|
||||||
|
let forge = Forge::new_forgejo(repo_details, net);
|
||||||
|
assert_eq!(forge.name(), "forgejo");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test_log::test(tokio::test)]
|
||||||
|
async fn test_branches_get() {
|
||||||
|
let mut net = MockNetwork::new();
|
||||||
|
let hostname = common::hostname(1);
|
||||||
|
let path = common::repo_path(1);
|
||||||
|
let api_token = common::api_token(1);
|
||||||
|
use secrecy::ExposeSecret;
|
||||||
|
let token = api_token.expose_secret();
|
||||||
|
let url = format!("https://{hostname}/api/v1/repos/{path}/branches?token={token}");
|
||||||
|
let body = include_str!("./data-forgejo-branches-get.json");
|
||||||
|
net.add_get_response(&url, StatusCode::OK, body);
|
||||||
|
let net = Network::from(net);
|
||||||
|
|
||||||
|
let repo_details = common::repo_details(
|
||||||
|
1,
|
||||||
|
common::forge_details(1, ForgeType::MockForge),
|
||||||
|
Some(common::config(1)),
|
||||||
|
);
|
||||||
|
|
||||||
|
let forge = Forge::new_forgejo(repo_details, net.clone());
|
||||||
|
|
||||||
|
let_assert!(Ok(branches) = forge.branches_get_all().await);
|
||||||
|
|
||||||
|
let_assert!(Some(requests) = net.mocked_requests());
|
||||||
|
assert_eq!(requests.len(), 1);
|
||||||
|
|
||||||
|
assert_eq!(branches, vec![Branch(BranchName("string".into()))]);
|
||||||
|
}
|
8
src/server/gitforge/tests/github.rs
Normal file
8
src/server/gitforge/tests/github.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_name() {
|
||||||
|
let net = Network::new_mock();
|
||||||
|
let forge = Forge::new_github(net);
|
||||||
|
assert_eq!(forge.name(), "github");
|
||||||
|
}
|
15
src/server/gitforge/tests/mod.rs
Normal file
15
src/server/gitforge/tests/mod.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
#[cfg(feature = "forgejo")]
|
||||||
|
mod forgejo;
|
||||||
|
|
||||||
|
#[cfg(feature = "github")]
|
||||||
|
mod github;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mock_name() {
|
||||||
|
let forge = Forge::new_mock();
|
||||||
|
assert_eq!(forge.name(), "mock");
|
||||||
|
}
|
29
src/server/gitforge/types.rs
Normal file
29
src/server/gitforge/types.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
use crate::server::config::BranchName;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct Branch(pub BranchName);
|
||||||
|
impl Branch {
|
||||||
|
pub const fn name(&self) -> &BranchName {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Force {
|
||||||
|
No,
|
||||||
|
From(crate::server::types::GitRef),
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for Force {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{:?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type BranchResetResult = Result<(), ()>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum CommitStatus {
|
||||||
|
Pass,
|
||||||
|
Fail,
|
||||||
|
Pending,
|
||||||
|
}
|
|
@ -1,10 +1,11 @@
|
||||||
mod actors;
|
mod actors;
|
||||||
mod config;
|
mod config;
|
||||||
pub mod forge;
|
pub mod forge;
|
||||||
pub mod git;
|
pub mod gitforge;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
|
|
||||||
use kxio::network::Network;
|
use kxio::network::Network;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
@ -16,7 +17,6 @@ use crate::{
|
||||||
server::{
|
server::{
|
||||||
actors::webhook,
|
actors::webhook,
|
||||||
config::{Forge, ForgeName, RepoAlias, Webhook},
|
config::{Forge, ForgeName, RepoAlias, Webhook},
|
||||||
git::Git,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ pub fn init(fs: FileSystem) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn start(fs: FileSystem, net: Network, git: Git) {
|
pub async fn start(fs: FileSystem, net: Network) {
|
||||||
let Ok(_) = init_logging() else {
|
let Ok(_) = init_logging() else {
|
||||||
eprintln!("Failed to initialize logging.");
|
eprintln!("Failed to initialize logging.");
|
||||||
return;
|
return;
|
||||||
|
@ -58,7 +58,7 @@ pub async fn start(fs: FileSystem, net: Network, git: Git) {
|
||||||
let webhook = server_config.webhook();
|
let webhook = server_config.webhook();
|
||||||
server_config
|
server_config
|
||||||
.forges()
|
.forges()
|
||||||
.flat_map(|(forge_name, forge)| create_forge_repos(forge, forge_name, webhook, &net, &git))
|
.flat_map(|(forge_name, forge)| create_forge_repos(forge, forge_name, webhook, &net))
|
||||||
.map(start_actor)
|
.map(start_actor)
|
||||||
.map(|(alias, addr)| webhook::AddWebhookRecipient(alias, addr.recipient()))
|
.map(|(alias, addr)| webhook::AddWebhookRecipient(alias, addr.recipient()))
|
||||||
.for_each(|msg| webhook_router.do_send(msg));
|
.for_each(|msg| webhook_router.do_send(msg));
|
||||||
|
@ -73,7 +73,6 @@ fn create_forge_repos(
|
||||||
forge_name: ForgeName,
|
forge_name: ForgeName,
|
||||||
webhook: &Webhook,
|
webhook: &Webhook,
|
||||||
net: &Network,
|
net: &Network,
|
||||||
git: &Git,
|
|
||||||
) -> Vec<(ForgeName, RepoAlias, RepoActor)> {
|
) -> Vec<(ForgeName, RepoAlias, RepoActor)> {
|
||||||
let forge = forge.clone();
|
let forge = forge.clone();
|
||||||
let span = tracing::info_span!("Forge", %forge_name, %forge);
|
let span = tracing::info_span!("Forge", %forge_name, %forge);
|
||||||
|
@ -81,7 +80,7 @@ fn create_forge_repos(
|
||||||
info!("Creating Forge");
|
info!("Creating Forge");
|
||||||
forge
|
forge
|
||||||
.repos()
|
.repos()
|
||||||
.map(create_actor(forge_name, forge.clone(), webhook, net, git))
|
.map(create_actor(forge_name, forge.clone(), webhook, net))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,11 +89,9 @@ fn create_actor(
|
||||||
forge: config::Forge,
|
forge: config::Forge,
|
||||||
webhook: &Webhook,
|
webhook: &Webhook,
|
||||||
net: &Network,
|
net: &Network,
|
||||||
git: &Git,
|
|
||||||
) -> impl Fn((RepoAlias, &Repo)) -> (ForgeName, RepoAlias, RepoActor) {
|
) -> impl Fn((RepoAlias, &Repo)) -> (ForgeName, RepoAlias, RepoActor) {
|
||||||
let webhook = webhook.clone();
|
let webhook = webhook.clone();
|
||||||
let net = net.clone();
|
let net = net.clone();
|
||||||
let git = git.clone();
|
|
||||||
move |(repo_name, repo)| {
|
move |(repo_name, repo)| {
|
||||||
let span = tracing::info_span!("Repo", %repo_name, %repo);
|
let span = tracing::info_span!("Repo", %repo_name, %repo);
|
||||||
let _guard = span.enter();
|
let _guard = span.enter();
|
||||||
|
@ -103,7 +100,6 @@ fn create_actor(
|
||||||
config::RepoDetails::new(&repo_name, repo, &forge_name, &forge),
|
config::RepoDetails::new(&repo_name, repo, &forge_name, &forge),
|
||||||
webhook.clone(),
|
webhook.clone(),
|
||||||
net.clone(),
|
net.clone(),
|
||||||
git.clone(),
|
|
||||||
);
|
);
|
||||||
info!("Created Repo");
|
info!("Created Repo");
|
||||||
(forge_name.clone(), repo_name, actor)
|
(forge_name.clone(), repo_name, actor)
|
||||||
|
|
|
@ -19,14 +19,3 @@ impl Display for GitRef {
|
||||||
write!(f, "{}", self.0)
|
write!(f, "{}", self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum ResetForce {
|
|
||||||
None,
|
|
||||||
Force(GitRef),
|
|
||||||
}
|
|
||||||
impl Display for ResetForce {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{:?}", self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue