forked from kemitix/git-next
refactor: extract repo-actor and gitforge crates
This commit is contained in:
parent
4c2bc19139
commit
db9b4220ee
42 changed files with 633 additions and 471 deletions
11
Cargo.toml
11
Cargo.toml
|
@ -1,6 +1,13 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = ["crates/cli", "crates/server", "crates/config", "crates/git"]
|
members = [
|
||||||
|
"crates/cli",
|
||||||
|
"crates/server",
|
||||||
|
"crates/config",
|
||||||
|
"crates/git",
|
||||||
|
"crates/gitforge",
|
||||||
|
"crates/repo-actor",
|
||||||
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
@ -16,6 +23,8 @@ expect_used = "warn"
|
||||||
git-next-server = { path = "crates/server" }
|
git-next-server = { path = "crates/server" }
|
||||||
git-next-config = { path = "crates/config" }
|
git-next-config = { path = "crates/config" }
|
||||||
git-next-git = { path = "crates/git" }
|
git-next-git = { path = "crates/git" }
|
||||||
|
git-next-gitforge = { path = "crates/gitforge" }
|
||||||
|
git-next-repo-actor = { path = "crates/repo-actor" }
|
||||||
|
|
||||||
# CLI parsing
|
# CLI parsing
|
||||||
clap = { version = "4.5", features = ["cargo", "derive"] }
|
clap = { version = "4.5", features = ["cargo", "derive"] }
|
||||||
|
|
13
README.md
13
README.md
|
@ -190,9 +190,22 @@ The following diagram shows the dependency between the crates that make up `git-
|
||||||
stateDiagram-v2
|
stateDiagram-v2
|
||||||
cli --> server
|
cli --> server
|
||||||
cli --> git
|
cli --> git
|
||||||
|
|
||||||
server --> config
|
server --> config
|
||||||
server --> git
|
server --> git
|
||||||
|
server --> gitforge
|
||||||
|
server --> repo_actor
|
||||||
|
|
||||||
git --> config
|
git --> config
|
||||||
|
|
||||||
|
gitforge --> config
|
||||||
|
gitforge --> git
|
||||||
|
|
||||||
|
repo_actor --> config
|
||||||
|
repo_actor --> git
|
||||||
|
repo_actor --> gitforge
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
|
@ -9,9 +9,9 @@ forgejo = []
|
||||||
github = []
|
github = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# # logging
|
# logging
|
||||||
# console-subscriber = { workspace = true }
|
# console-subscriber = { workspace = true }
|
||||||
# tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
# tracing-subscriber = { workspace = true }
|
# tracing-subscriber = { workspace = true }
|
||||||
|
|
||||||
# # base64 decoding
|
# # base64 decoding
|
||||||
|
@ -21,9 +21,9 @@ github = []
|
||||||
# # gix = { workspace = true }
|
# # gix = { workspace = true }
|
||||||
# gix = { workspace = true }
|
# gix = { workspace = true }
|
||||||
# async-trait = { workspace = true }
|
# async-trait = { workspace = true }
|
||||||
#
|
|
||||||
# # fs/network
|
# fs/network
|
||||||
# kxio = { workspace = true }
|
kxio = { workspace = true }
|
||||||
|
|
||||||
# TOML parsing
|
# TOML parsing
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
|
@ -47,12 +47,12 @@ derive-with = { workspace = true }
|
||||||
#
|
#
|
||||||
# # file watcher
|
# # file watcher
|
||||||
# inotify = { workspace = true }
|
# inotify = { workspace = true }
|
||||||
#
|
|
||||||
# # Actors
|
# Actors
|
||||||
# actix = { workspace = true }
|
actix = { workspace = true }
|
||||||
# actix-rt = { workspace = true }
|
# actix-rt = { workspace = true }
|
||||||
# tokio = { workspace = true }
|
# tokio = { workspace = true }
|
||||||
#
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
# # Testing
|
# # Testing
|
||||||
assert2 = { workspace = true }
|
assert2 = { workspace = true }
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
use git_next_config::{
|
use crate::{
|
||||||
ApiToken, BranchName, ForgeDetails, ForgeName, ForgeType, GitDir, Hostname, RepoAlias,
|
ApiToken, BranchName, ForgeDetails, ForgeName, ForgeType, Hostname, RepoAlias, RepoBranches,
|
||||||
RepoBranches, RepoConfig, RepoConfigSource, RepoPath, User,
|
RepoConfig, RepoConfigSource, RepoPath, User,
|
||||||
};
|
};
|
||||||
|
|
||||||
use git_next_git::{Generation, RepoDetails};
|
|
||||||
|
|
||||||
pub fn forge_details(n: u32, forge_type: ForgeType) -> ForgeDetails {
|
pub fn forge_details(n: u32, forge_type: ForgeType) -> ForgeDetails {
|
||||||
ForgeDetails::new(
|
ForgeDetails::new(
|
||||||
forge_name(n),
|
forge_name(n),
|
||||||
|
@ -30,23 +28,6 @@ pub fn hostname(n: u32) -> Hostname {
|
||||||
pub fn forge_name(n: u32) -> ForgeName {
|
pub fn forge_name(n: u32) -> ForgeName {
|
||||||
ForgeName::new(format!("forge-name-{}", n))
|
ForgeName::new(format!("forge-name-{}", n))
|
||||||
}
|
}
|
||||||
pub fn repo_details(
|
|
||||||
n: u32,
|
|
||||||
generation: Generation,
|
|
||||||
forge: ForgeDetails,
|
|
||||||
repo_config: Option<RepoConfig>,
|
|
||||||
gitdir: GitDir,
|
|
||||||
) -> RepoDetails {
|
|
||||||
RepoDetails {
|
|
||||||
generation,
|
|
||||||
repo_alias: repo_alias(n),
|
|
||||||
repo_path: repo_path(n),
|
|
||||||
gitdir,
|
|
||||||
branch: branch_name(n),
|
|
||||||
forge,
|
|
||||||
repo_config,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn branch_name(n: u32) -> BranchName {
|
pub fn branch_name(n: u32) -> BranchName {
|
||||||
BranchName::new(format!("branch-name-{}", n))
|
BranchName::new(format!("branch-name-{}", n))
|
|
@ -1,6 +1,7 @@
|
||||||
//
|
//
|
||||||
mod api_token;
|
mod api_token;
|
||||||
mod branch_name;
|
mod branch_name;
|
||||||
|
pub mod common;
|
||||||
mod forge_config;
|
mod forge_config;
|
||||||
mod forge_details;
|
mod forge_details;
|
||||||
mod forge_name;
|
mod forge_name;
|
||||||
|
@ -12,6 +13,7 @@ mod repo_branches;
|
||||||
mod repo_config;
|
mod repo_config;
|
||||||
mod repo_config_source;
|
mod repo_config_source;
|
||||||
mod repo_path;
|
mod repo_path;
|
||||||
|
pub mod server;
|
||||||
mod server_repo_config;
|
mod server_repo_config;
|
||||||
mod user;
|
mod user;
|
||||||
|
|
||||||
|
|
105
crates/config/src/server.rs
Normal file
105
crates/config/src/server.rs
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
//
|
||||||
|
|
||||||
|
use actix::prelude::*;
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
net::SocketAddr,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
|
use kxio::fs::FileSystem;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
|
use crate::{ForgeConfig, ForgeName};
|
||||||
|
|
||||||
|
#[derive(Debug, derive_more::From, derive_more::Display)]
|
||||||
|
pub enum Error {
|
||||||
|
Io(std::io::Error),
|
||||||
|
KxIoFs(kxio::fs::Error),
|
||||||
|
TomlDe(toml::de::Error),
|
||||||
|
AddressParse(std::net::AddrParseError),
|
||||||
|
}
|
||||||
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
|
type Result<T> = core::result::Result<T, Error>;
|
||||||
|
|
||||||
|
/// Mapped from the `git-next-server.toml` file
|
||||||
|
#[derive(Debug, PartialEq, Eq, serde::Deserialize, Message, derive_more::Constructor)]
|
||||||
|
#[rtype(result = "()")]
|
||||||
|
pub struct ServerConfig {
|
||||||
|
http: Http,
|
||||||
|
webhook: Webhook,
|
||||||
|
storage: ServerStorage,
|
||||||
|
pub forge: HashMap<String, ForgeConfig>,
|
||||||
|
}
|
||||||
|
impl ServerConfig {
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub fn load(fs: &FileSystem) -> Result<Self> {
|
||||||
|
let file = fs.base().join("git-next-server.toml");
|
||||||
|
info!(?file, "");
|
||||||
|
let str = fs.file_read_to_string(&file)?;
|
||||||
|
toml::from_str(&str).map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn forges(&self) -> impl Iterator<Item = (ForgeName, &ForgeConfig)> {
|
||||||
|
self.forge
|
||||||
|
.iter()
|
||||||
|
.map(|(name, forge)| (ForgeName::new(name.clone()), forge))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn storage(&self) -> &ServerStorage {
|
||||||
|
&self.storage
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn webhook(&self) -> &Webhook {
|
||||||
|
&self.webhook
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn http(&self) -> Result<SocketAddr> {
|
||||||
|
self.http.socket_addr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Defines the port the server will listen to for incoming webhooks messages
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, derive_more::Constructor)]
|
||||||
|
pub struct Http {
|
||||||
|
addr: String,
|
||||||
|
port: u16,
|
||||||
|
}
|
||||||
|
impl Http {
|
||||||
|
fn socket_addr(&self) -> Result<SocketAddr> {
|
||||||
|
Ok(SocketAddr::from_str(&format!(
|
||||||
|
"{}:{}",
|
||||||
|
self.addr, self.port
|
||||||
|
))?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Defines the Webhook Forges should send updates to
|
||||||
|
/// Must be an address that is accessible from the remote forge
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, derive_more::Constructor)]
|
||||||
|
pub struct Webhook {
|
||||||
|
url: String,
|
||||||
|
}
|
||||||
|
impl Webhook {
|
||||||
|
pub fn url(&self) -> WebhookUrl {
|
||||||
|
WebhookUrl(self.url.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The URL for the webhook where forges should send their updates
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, derive_more::AsRef)]
|
||||||
|
pub struct WebhookUrl(String);
|
||||||
|
|
||||||
|
/// The directory to store server data, such as cloned repos
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, derive_more::Constructor)]
|
||||||
|
pub struct ServerStorage {
|
||||||
|
path: PathBuf,
|
||||||
|
}
|
||||||
|
impl ServerStorage {
|
||||||
|
pub fn path(&self) -> &Path {
|
||||||
|
self.path.as_path()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,7 @@
|
||||||
type TestResult = Result<(), Box<dyn std::error::Error>>;
|
//
|
||||||
|
|
||||||
|
type Result<T> = core::result::Result<T, Box<dyn std::error::Error>>;
|
||||||
|
type TestResult = Result<()>;
|
||||||
|
|
||||||
mod server_repo_config {
|
mod server_repo_config {
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
@ -452,3 +455,123 @@ mod repo_branches {
|
||||||
assert_eq!(repo_branches.dev(), BranchName::new("dev"));
|
assert_eq!(repo_branches.dev(), BranchName::new("dev"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mod server {
|
||||||
|
mod load {
|
||||||
|
//
|
||||||
|
|
||||||
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
|
||||||
|
use assert2::let_assert;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
server::{Http, ServerConfig, ServerStorage, Webhook},
|
||||||
|
tests::TestResult,
|
||||||
|
ForgeConfig, ForgeType, RepoBranches, RepoConfig, RepoConfigSource, ServerRepoConfig,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_should_parse_server_config() -> TestResult {
|
||||||
|
let fs = kxio::fs::temp()?;
|
||||||
|
fs.file_write(
|
||||||
|
&fs.base().join("git-next-server.toml"),
|
||||||
|
r#"
|
||||||
|
[http]
|
||||||
|
addr = "0.0.0.0"
|
||||||
|
port = 8080
|
||||||
|
|
||||||
|
[webhook]
|
||||||
|
url = "http://localhost:9909/webhook"
|
||||||
|
|
||||||
|
[storage]
|
||||||
|
path = "/opt/git-next/data"
|
||||||
|
|
||||||
|
[forge.default]
|
||||||
|
forge_type = "MockForge"
|
||||||
|
hostname = "git.example.net"
|
||||||
|
user = "Bob"
|
||||||
|
token = "API-Token"
|
||||||
|
|
||||||
|
[forge.default.repos]
|
||||||
|
hello = { repo = "user/hello", branch = "main", gitdir = "/opt/git/user/hello.git" }
|
||||||
|
world = { repo = "user/world", branch = "master", main = "main", next = "next", dev = "dev" }
|
||||||
|
|
||||||
|
[forge.default.repos.sam]
|
||||||
|
repo = "user/sam"
|
||||||
|
branch = "main"
|
||||||
|
main = "master"
|
||||||
|
next = "upcoming"
|
||||||
|
dev = "sam-dev"
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
?;
|
||||||
|
let_assert!(Ok(config) = ServerConfig::load(&fs));
|
||||||
|
let expected = ServerConfig::new(
|
||||||
|
Http::new("0.0.0.0".to_string(), 8080),
|
||||||
|
Webhook::new("http://localhost:9909/webhook".to_string()),
|
||||||
|
ServerStorage::new("/opt/git-next/data".into()),
|
||||||
|
HashMap::from([(
|
||||||
|
"default".to_string(),
|
||||||
|
ForgeConfig::new(
|
||||||
|
ForgeType::MockForge,
|
||||||
|
"git.example.net".to_string(),
|
||||||
|
"Bob".to_string(),
|
||||||
|
"API-Token".to_string(),
|
||||||
|
BTreeMap::from([
|
||||||
|
(
|
||||||
|
"hello".to_string(),
|
||||||
|
ServerRepoConfig::new(
|
||||||
|
"user/hello".to_string(),
|
||||||
|
"main".to_string(),
|
||||||
|
Some("/opt/git/user/hello.git".into()),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"world".to_string(),
|
||||||
|
ServerRepoConfig::new(
|
||||||
|
"user/world".to_string(),
|
||||||
|
"master".to_string(),
|
||||||
|
None,
|
||||||
|
Some("main".to_string()),
|
||||||
|
Some("next".to_string()),
|
||||||
|
Some("dev".to_string()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"sam".to_string(),
|
||||||
|
ServerRepoConfig::new(
|
||||||
|
"user/sam".to_string(),
|
||||||
|
"main".to_string(),
|
||||||
|
None,
|
||||||
|
Some("master".to_string()),
|
||||||
|
Some("upcoming".to_string()),
|
||||||
|
Some("sam-dev".to_string()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
)]),
|
||||||
|
);
|
||||||
|
assert_eq!(config, expected, "ServerConfig");
|
||||||
|
|
||||||
|
if let Some(forge) = config.forge.get("world") {
|
||||||
|
if let Some(repo) = forge.get_repo("sam") {
|
||||||
|
let repo_config = repo.repo_config();
|
||||||
|
let expected = Some(RepoConfig::new(
|
||||||
|
RepoBranches::new(
|
||||||
|
"master".to_string(),
|
||||||
|
"upcoming".to_string(),
|
||||||
|
"sam-dev".to_string(),
|
||||||
|
),
|
||||||
|
RepoConfigSource::Server,
|
||||||
|
));
|
||||||
|
assert_eq!(repo_config, expected, "RepoConfig");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
crates/git/src/common.rs
Normal file
26
crates/git/src/common.rs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
//
|
||||||
|
|
||||||
|
use git_next_config::{
|
||||||
|
common::{branch_name, repo_alias, repo_path},
|
||||||
|
ForgeDetails, GitDir, RepoConfig,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{Generation, RepoDetails};
|
||||||
|
|
||||||
|
pub fn repo_details(
|
||||||
|
n: u32,
|
||||||
|
generation: Generation,
|
||||||
|
forge: ForgeDetails,
|
||||||
|
repo_config: Option<RepoConfig>,
|
||||||
|
gitdir: GitDir,
|
||||||
|
) -> RepoDetails {
|
||||||
|
RepoDetails {
|
||||||
|
generation,
|
||||||
|
repo_alias: repo_alias(n),
|
||||||
|
repo_path: repo_path(n),
|
||||||
|
gitdir,
|
||||||
|
branch: branch_name(n),
|
||||||
|
forge,
|
||||||
|
repo_config,
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
//
|
//
|
||||||
pub mod commit;
|
pub mod commit;
|
||||||
|
pub mod common;
|
||||||
pub mod fetch;
|
pub mod fetch;
|
||||||
mod generation;
|
mod generation;
|
||||||
mod git_ref;
|
mod git_ref;
|
||||||
|
|
64
crates/gitforge/Cargo.toml
Normal file
64
crates/gitforge/Cargo.toml
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
[package]
|
||||||
|
name = "git-next-gitforge"
|
||||||
|
version = { workspace = true }
|
||||||
|
edition = { workspace = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["forgejo"]
|
||||||
|
forgejo = []
|
||||||
|
github = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
git-next-config = { workspace = true }
|
||||||
|
git-next-git = { workspace = true }
|
||||||
|
|
||||||
|
# logging
|
||||||
|
console-subscriber = { workspace = true }
|
||||||
|
tracing = { workspace = true }
|
||||||
|
tracing-subscriber = { workspace = true }
|
||||||
|
|
||||||
|
# base64 decoding
|
||||||
|
base64 = { workspace = true }
|
||||||
|
|
||||||
|
# git
|
||||||
|
async-trait = { workspace = true }
|
||||||
|
|
||||||
|
# fs/network
|
||||||
|
kxio = { workspace = true }
|
||||||
|
|
||||||
|
# TOML parsing
|
||||||
|
serde = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
toml = { workspace = true }
|
||||||
|
|
||||||
|
# Secrets and Password
|
||||||
|
secrecy = { workspace = true }
|
||||||
|
|
||||||
|
# Conventional Commit check
|
||||||
|
git-conventional = { workspace = true }
|
||||||
|
|
||||||
|
# Webhooks
|
||||||
|
bytes = { workspace = true }
|
||||||
|
ulid = { workspace = true }
|
||||||
|
warp = { workspace = true }
|
||||||
|
|
||||||
|
# boilerplate
|
||||||
|
derive_more = { workspace = true }
|
||||||
|
|
||||||
|
# file watcher
|
||||||
|
inotify = { workspace = true }
|
||||||
|
|
||||||
|
# # Actors
|
||||||
|
# actix = { workspace = true }
|
||||||
|
# actix-rt = { workspace = true }
|
||||||
|
tokio = { workspace = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
# Testing
|
||||||
|
assert2 = { workspace = true }
|
||||||
|
|
||||||
|
[lints.clippy]
|
||||||
|
nursery = { level = "warn", priority = -1 }
|
||||||
|
# pedantic = "warn"
|
||||||
|
unwrap_used = "warn"
|
||||||
|
expect_used = "warn"
|
|
@ -3,7 +3,7 @@ use git_next_git::RepoDetails;
|
||||||
use kxio::network::{self, Network};
|
use kxio::network::{self, Network};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use crate::gitforge::ForgeBranchError;
|
use crate::ForgeBranchError;
|
||||||
|
|
||||||
pub async fn get_all(
|
pub async fn get_all(
|
||||||
repo_details: &RepoDetails,
|
repo_details: &RepoDetails,
|
|
@ -4,4 +4,3 @@ mod validate_positions;
|
||||||
pub use get_all::get_all;
|
pub use get_all::get_all;
|
||||||
|
|
||||||
pub use validate_positions::validate_positions;
|
pub use validate_positions::validate_positions;
|
||||||
pub use validate_positions::ValidatedPositions;
|
|
|
@ -3,34 +3,13 @@ use git_next_git::{self as git, RepoDetails};
|
||||||
use kxio::network;
|
use kxio::network;
|
||||||
use tracing::{debug, error, info, warn};
|
use tracing::{debug, error, info, warn};
|
||||||
|
|
||||||
use crate::gitforge::{self, ForgeLike};
|
use crate::{forgejo::ForgeJoEnv, validation, CommitHistories, ForgeLike as _};
|
||||||
|
|
||||||
#[derive(Debug, derive_more::Display)]
|
|
||||||
pub enum Error {
|
|
||||||
Network(Box<kxio::network::NetworkError>),
|
|
||||||
#[display("Failed to Reset Branch {branch} to {commit}")]
|
|
||||||
FailedToResetBranch {
|
|
||||||
branch: BranchName,
|
|
||||||
commit: git::Commit,
|
|
||||||
},
|
|
||||||
BranchReset(BranchName),
|
|
||||||
BranchHasNoCommits(BranchName),
|
|
||||||
DevBranchNotBasedOn(BranchName),
|
|
||||||
}
|
|
||||||
impl std::error::Error for Error {}
|
|
||||||
|
|
||||||
pub struct ValidatedPositions {
|
|
||||||
pub main: git::Commit,
|
|
||||||
pub next: git::Commit,
|
|
||||||
pub dev: git::Commit,
|
|
||||||
pub dev_commit_history: Vec<git::Commit>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn validate_positions(
|
pub async fn validate_positions(
|
||||||
forge: &gitforge::forgejo::ForgeJoEnv,
|
forge: &ForgeJoEnv,
|
||||||
repository: &git::OpenRepository,
|
repository: &git::OpenRepository,
|
||||||
repo_config: RepoConfig,
|
repo_config: RepoConfig,
|
||||||
) -> Result<ValidatedPositions, Error> {
|
) -> validation::Result {
|
||||||
let repo_details = &forge.repo_details;
|
let repo_details = &forge.repo_details;
|
||||||
// Collect Commit Histories for `main`, `next` and `dev` branches
|
// 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 = get_commit_histories(repo_details, &repo_config, &forge.net).await;
|
||||||
|
@ -38,7 +17,7 @@ pub async fn validate_positions(
|
||||||
Ok(commit_histories) => commit_histories,
|
Ok(commit_histories) => commit_histories,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!(?err, "Failed to get commit histories");
|
error!(?err, "Failed to get commit histories");
|
||||||
return Err(Error::Network(Box::new(err)));
|
return Err(validation::Error::Network(Box::new(err)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -48,7 +27,9 @@ pub async fn validate_positions(
|
||||||
"No commits on main branch '{}'",
|
"No commits on main branch '{}'",
|
||||||
repo_config.branches().main()
|
repo_config.branches().main()
|
||||||
);
|
);
|
||||||
return Err(Error::BranchHasNoCommits(repo_config.branches().main()));
|
return Err(validation::Error::BranchHasNoCommits(
|
||||||
|
repo_config.branches().main(),
|
||||||
|
));
|
||||||
};
|
};
|
||||||
// Dev must be on main branch, or user must rebase it
|
// Dev must be on main branch, or user must rebase it
|
||||||
let dev_has_main = commit_histories.dev.iter().any(|commit| commit == &main);
|
let dev_has_main = commit_histories.dev.iter().any(|commit| commit == &main);
|
||||||
|
@ -59,7 +40,9 @@ pub async fn validate_positions(
|
||||||
repo_config.branches().main(),
|
repo_config.branches().main(),
|
||||||
repo_config.branches().main(),
|
repo_config.branches().main(),
|
||||||
);
|
);
|
||||||
return Err(Error::DevBranchNotBasedOn(repo_config.branches().main()));
|
return Err(validation::Error::DevBranchNotBasedOn(
|
||||||
|
repo_config.branches().main(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
// verify that next is an ancestor of dev, and force update to it main if it isn't
|
// 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 {
|
let Some(next) = commit_histories.next.first().cloned() else {
|
||||||
|
@ -67,7 +50,9 @@ pub async fn validate_positions(
|
||||||
"No commits on next branch '{}",
|
"No commits on next branch '{}",
|
||||||
repo_config.branches().next()
|
repo_config.branches().next()
|
||||||
);
|
);
|
||||||
return Err(Error::BranchHasNoCommits(repo_config.branches().next()));
|
return Err(validation::Error::BranchHasNoCommits(
|
||||||
|
repo_config.branches().next(),
|
||||||
|
));
|
||||||
};
|
};
|
||||||
let next_is_ancestor_of_dev = commit_histories.dev.iter().any(|dev| dev == &next);
|
let next_is_ancestor_of_dev = commit_histories.dev.iter().any(|dev| dev == &next);
|
||||||
if !next_is_ancestor_of_dev {
|
if !next_is_ancestor_of_dev {
|
||||||
|
@ -80,12 +65,14 @@ pub async fn validate_positions(
|
||||||
git::push::Force::From(next.clone().into()),
|
git::push::Force::From(next.clone().into()),
|
||||||
) {
|
) {
|
||||||
warn!(?err, "Failed to reset next to main");
|
warn!(?err, "Failed to reset next to main");
|
||||||
return Err(Error::FailedToResetBranch {
|
return Err(validation::Error::FailedToResetBranch {
|
||||||
branch: repo_config.branches().next(),
|
branch: repo_config.branches().next(),
|
||||||
commit: next,
|
commit: next,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return Err(Error::BranchReset(repo_config.branches().next()));
|
return Err(validation::Error::BranchReset(
|
||||||
|
repo_config.branches().next(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let next_commits = commit_histories
|
let next_commits = commit_histories
|
||||||
|
@ -106,19 +93,23 @@ pub async fn validate_positions(
|
||||||
git::push::Force::From(next.clone().into()),
|
git::push::Force::From(next.clone().into()),
|
||||||
) {
|
) {
|
||||||
warn!(?err, "Failed to reset next to main");
|
warn!(?err, "Failed to reset next to main");
|
||||||
return Err(Error::FailedToResetBranch {
|
return Err(validation::Error::FailedToResetBranch {
|
||||||
branch: repo_config.branches().next(),
|
branch: repo_config.branches().next(),
|
||||||
commit: next,
|
commit: next,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return Err(Error::BranchReset(repo_config.branches().next()));
|
return Err(validation::Error::BranchReset(
|
||||||
|
repo_config.branches().next(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
let Some(next) = next_commits.first().cloned() else {
|
let Some(next) = next_commits.first().cloned() else {
|
||||||
warn!(
|
warn!(
|
||||||
"No commits on next branch '{}'",
|
"No commits on next branch '{}'",
|
||||||
repo_config.branches().next()
|
repo_config.branches().next()
|
||||||
);
|
);
|
||||||
return Err(Error::BranchHasNoCommits(repo_config.branches().next()));
|
return Err(validation::Error::BranchHasNoCommits(
|
||||||
|
repo_config.branches().next(),
|
||||||
|
));
|
||||||
};
|
};
|
||||||
let dev_has_next = commit_histories
|
let dev_has_next = commit_histories
|
||||||
.dev
|
.dev
|
||||||
|
@ -130,7 +121,9 @@ pub async fn validate_positions(
|
||||||
repo_config.branches().dev(),
|
repo_config.branches().dev(),
|
||||||
repo_config.branches().next()
|
repo_config.branches().next()
|
||||||
);
|
);
|
||||||
return Err(Error::DevBranchNotBasedOn(repo_config.branches().next())); // dev is not based on next
|
return Err(validation::Error::DevBranchNotBasedOn(
|
||||||
|
repo_config.branches().next(),
|
||||||
|
)); // dev is not based on next
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(dev) = commit_histories.dev.first().cloned() else {
|
let Some(dev) = commit_histories.dev.first().cloned() else {
|
||||||
|
@ -138,10 +131,12 @@ pub async fn validate_positions(
|
||||||
"No commits on dev branch '{}'",
|
"No commits on dev branch '{}'",
|
||||||
repo_config.branches().dev()
|
repo_config.branches().dev()
|
||||||
);
|
);
|
||||||
return Err(Error::BranchHasNoCommits(repo_config.branches().dev()));
|
return Err(validation::Error::BranchHasNoCommits(
|
||||||
|
repo_config.branches().dev(),
|
||||||
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(ValidatedPositions {
|
Ok(validation::Positions {
|
||||||
main,
|
main,
|
||||||
next,
|
next,
|
||||||
dev,
|
dev,
|
||||||
|
@ -153,7 +148,7 @@ async fn get_commit_histories(
|
||||||
repo_details: &RepoDetails,
|
repo_details: &RepoDetails,
|
||||||
repo_config: &RepoConfig,
|
repo_config: &RepoConfig,
|
||||||
net: &network::Network,
|
net: &network::Network,
|
||||||
) -> Result<gitforge::CommitHistories, network::NetworkError> {
|
) -> Result<CommitHistories, network::NetworkError> {
|
||||||
let main =
|
let main =
|
||||||
(get_commit_history(repo_details, &repo_config.branches().main(), vec![], net).await)?;
|
(get_commit_history(repo_details, &repo_config.branches().main(), vec![], net).await)?;
|
||||||
let main_head = main[0].clone();
|
let main_head = main[0].clone();
|
||||||
|
@ -178,7 +173,7 @@ async fn get_commit_histories(
|
||||||
dev = dev.len(),
|
dev = dev.len(),
|
||||||
"Commit histories"
|
"Commit histories"
|
||||||
);
|
);
|
||||||
let histories = gitforge::CommitHistories { main, next, dev };
|
let histories = CommitHistories { main, next, dev };
|
||||||
Ok(histories)
|
Ok(histories)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use git_next_git::RepoDetails;
|
||||||
use kxio::network::{self, Network};
|
use kxio::network::{self, Network};
|
||||||
use tracing::{error, warn};
|
use tracing::{error, warn};
|
||||||
|
|
||||||
use crate::gitforge::ForgeFileError;
|
use crate::ForgeFileError;
|
||||||
|
|
||||||
pub(super) async fn contents_get(
|
pub(super) async fn contents_get(
|
||||||
repo_details: &RepoDetails,
|
repo_details: &RepoDetails,
|
|
@ -1,20 +1,13 @@
|
||||||
pub mod branch;
|
pub mod branch;
|
||||||
mod file;
|
mod file;
|
||||||
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use actix::prelude::*;
|
|
||||||
|
|
||||||
use git::OpenRepository;
|
use git::OpenRepository;
|
||||||
use git_next_config::{BranchName, GitDir, RepoConfig};
|
use git_next_config::{BranchName, GitDir, RepoConfig};
|
||||||
use git_next_git::{self as git, GitRef, RepoDetails, Repository};
|
use git_next_git::{self as git, GitRef, RepoDetails, Repository};
|
||||||
use kxio::network::{self, Network};
|
use kxio::network::{self, Network};
|
||||||
use tracing::{error, info, warn};
|
use tracing::{error, info, warn};
|
||||||
|
|
||||||
use crate::{
|
use crate::{validation, CommitStatus, ForgeBranchError, ForgeFileError};
|
||||||
actors::repo::{RepoActor, StartMonitoring, ValidateRepo},
|
|
||||||
gitforge,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ForgeJo;
|
struct ForgeJo;
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -38,7 +31,7 @@ impl super::ForgeLike for ForgeJoEnv {
|
||||||
"forgejo".to_string()
|
"forgejo".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn branches_get_all(&self) -> Result<Vec<BranchName>, gitforge::ForgeBranchError> {
|
async fn branches_get_all(&self) -> Result<Vec<BranchName>, ForgeBranchError> {
|
||||||
branch::get_all(&self.repo_details, &self.net).await
|
branch::get_all(&self.repo_details, &self.net).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +39,7 @@ impl super::ForgeLike for ForgeJoEnv {
|
||||||
&self,
|
&self,
|
||||||
branch: &BranchName,
|
branch: &BranchName,
|
||||||
file_path: &str,
|
file_path: &str,
|
||||||
) -> Result<String, gitforge::ForgeFileError> {
|
) -> Result<String, ForgeFileError> {
|
||||||
file::contents_get(&self.repo_details, &self.net, branch, file_path).await
|
file::contents_get(&self.repo_details, &self.net, branch, file_path).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,24 +47,8 @@ impl super::ForgeLike for ForgeJoEnv {
|
||||||
&self,
|
&self,
|
||||||
repository: git::OpenRepository,
|
repository: git::OpenRepository,
|
||||||
repo_config: RepoConfig,
|
repo_config: RepoConfig,
|
||||||
addr: Addr<RepoActor>,
|
) -> validation::Result {
|
||||||
message_token: gitforge::MessageToken,
|
branch::validate_positions(self, &repository, repo_config).await
|
||||||
) {
|
|
||||||
match branch::validate_positions(self, &repository, repo_config).await {
|
|
||||||
Ok(branch::ValidatedPositions {
|
|
||||||
main,
|
|
||||||
next,
|
|
||||||
dev,
|
|
||||||
dev_commit_history,
|
|
||||||
}) => {
|
|
||||||
addr.do_send(StartMonitoring::new(main, next, dev, dev_commit_history));
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
warn!("{:?}", err);
|
|
||||||
tokio::time::sleep(Duration::from_secs(10)).await;
|
|
||||||
addr.do_send(ValidateRepo::new(message_token));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn branch_reset(
|
fn branch_reset(
|
||||||
|
@ -85,7 +62,7 @@ impl super::ForgeLike for ForgeJoEnv {
|
||||||
repository.push(&self.repo_details, branch_name, to_commit, force)
|
repository.push(&self.repo_details, branch_name, to_commit, force)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn commit_status(&self, commit: &git::Commit) -> gitforge::CommitStatus {
|
async fn commit_status(&self, commit: &git::Commit) -> CommitStatus {
|
||||||
let repo_details = &self.repo_details;
|
let repo_details = &self.repo_details;
|
||||||
let hostname = &repo_details.forge.hostname();
|
let hostname = &repo_details.forge.hostname();
|
||||||
let repo_path = &repo_details.repo_path;
|
let repo_path = &repo_details.repo_path;
|
||||||
|
@ -110,21 +87,21 @@ impl super::ForgeLike for ForgeJoEnv {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
match response.response_body() {
|
match response.response_body() {
|
||||||
Some(status) => match status.state {
|
Some(status) => match status.state {
|
||||||
CommitStatusState::Success => gitforge::CommitStatus::Pass,
|
CommitStatusState::Success => CommitStatus::Pass,
|
||||||
CommitStatusState::Pending => gitforge::CommitStatus::Pending,
|
CommitStatusState::Pending => CommitStatus::Pending,
|
||||||
CommitStatusState::Failure => gitforge::CommitStatus::Fail,
|
CommitStatusState::Failure => CommitStatus::Fail,
|
||||||
CommitStatusState::Error => gitforge::CommitStatus::Fail,
|
CommitStatusState::Error => CommitStatus::Fail,
|
||||||
CommitStatusState::Blank => gitforge::CommitStatus::Pending,
|
CommitStatusState::Blank => CommitStatus::Pending,
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
warn!("No status found for commit");
|
warn!("No status found for commit");
|
||||||
gitforge::CommitStatus::Pending // assume issue is transient and allow retry
|
CommitStatus::Pending // assume issue is transient and allow retry
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(?e, "Failed to get commit status");
|
error!(?e, "Failed to get commit status");
|
||||||
gitforge::CommitStatus::Pending // assume issue is transient and allow retry
|
CommitStatus::Pending // assume issue is transient and allow retry
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -39,9 +39,7 @@ pub trait ForgeLike {
|
||||||
&self,
|
&self,
|
||||||
repository: OpenRepository,
|
repository: OpenRepository,
|
||||||
repo_config: RepoConfig,
|
repo_config: RepoConfig,
|
||||||
addr: actix::prelude::Addr<super::actors::repo::RepoActor>,
|
) -> validation::Result;
|
||||||
message_token: MessageToken,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Moves a branch to a new commit.
|
/// Moves a branch to a new commit.
|
||||||
fn branch_reset(
|
fn branch_reset(
|
||||||
|
@ -98,5 +96,33 @@ impl std::ops::Deref for Forge {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod validation {
|
||||||
|
use git_next_config::BranchName;
|
||||||
|
use git_next_git as git;
|
||||||
|
|
||||||
|
pub type Result = core::result::Result<Positions, Error>;
|
||||||
|
|
||||||
|
pub struct Positions {
|
||||||
|
pub main: git::Commit,
|
||||||
|
pub next: git::Commit,
|
||||||
|
pub dev: git::Commit,
|
||||||
|
pub dev_commit_history: Vec<git::Commit>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, derive_more::Display)]
|
||||||
|
pub enum Error {
|
||||||
|
Network(Box<kxio::network::NetworkError>),
|
||||||
|
#[display("Failed to Reset Branch {branch} to {commit}")]
|
||||||
|
FailedToResetBranch {
|
||||||
|
branch: BranchName,
|
||||||
|
commit: git::Commit,
|
||||||
|
},
|
||||||
|
BranchReset(BranchName),
|
||||||
|
BranchHasNoCommits(BranchName),
|
||||||
|
DevBranchNotBasedOn(BranchName),
|
||||||
|
}
|
||||||
|
impl std::error::Error for Error {}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests;
|
pub mod tests;
|
|
@ -2,7 +2,7 @@ use git::OpenRepository;
|
||||||
use git_next_config::{BranchName, GitDir, RepoConfig};
|
use git_next_config::{BranchName, GitDir, RepoConfig};
|
||||||
use git_next_git::{self as git, GitRef};
|
use git_next_git::{self as git, GitRef};
|
||||||
|
|
||||||
use crate::{actors::repo::RepoActor, gitforge};
|
use crate::{validation, CommitStatus, ForgeBranchError, ForgeFileError};
|
||||||
|
|
||||||
struct MockForge;
|
struct MockForge;
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -18,7 +18,7 @@ impl super::ForgeLike for MockForgeEnv {
|
||||||
"mock".to_string()
|
"mock".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn branches_get_all(&self) -> Result<Vec<BranchName>, gitforge::ForgeBranchError> {
|
async fn branches_get_all(&self) -> Result<Vec<BranchName>, ForgeBranchError> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ impl super::ForgeLike for MockForgeEnv {
|
||||||
&self,
|
&self,
|
||||||
_branch: &BranchName,
|
_branch: &BranchName,
|
||||||
_file_path: &str,
|
_file_path: &str,
|
||||||
) -> Result<String, gitforge::ForgeFileError> {
|
) -> Result<String, ForgeFileError> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,9 +34,7 @@ impl super::ForgeLike for MockForgeEnv {
|
||||||
&self,
|
&self,
|
||||||
_repository: OpenRepository,
|
_repository: OpenRepository,
|
||||||
_repo_config: RepoConfig,
|
_repo_config: RepoConfig,
|
||||||
_addr: actix::prelude::Addr<RepoActor>,
|
) -> validation::Result {
|
||||||
_message_token: gitforge::MessageToken,
|
|
||||||
) {
|
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +48,7 @@ impl super::ForgeLike for MockForgeEnv {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn commit_status(&self, _commit: &git::Commit) -> gitforge::CommitStatus {
|
async fn commit_status(&self, _commit: &git::Commit) -> CommitStatus {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
|
//
|
||||||
use assert2::let_assert;
|
use assert2::let_assert;
|
||||||
|
|
||||||
use git_next_config::{ForgeType, RepoConfigSource};
|
use git_next_config::{self as config, ForgeType, RepoConfigSource};
|
||||||
use kxio::network::{MockNetwork, StatusCode};
|
use kxio::network::{MockNetwork, StatusCode};
|
||||||
|
|
||||||
use git_next_git::Generation;
|
use git_next_git::{self as git, Generation};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
@ -14,11 +15,11 @@ fn test_name() {
|
||||||
};
|
};
|
||||||
let net = Network::new_mock();
|
let net = Network::new_mock();
|
||||||
let (repo, _reality) = git::repository::mock();
|
let (repo, _reality) = git::repository::mock();
|
||||||
let repo_details = common::repo_details(
|
let repo_details = git::common::repo_details(
|
||||||
1,
|
1,
|
||||||
Generation::new(),
|
Generation::new(),
|
||||||
common::forge_details(1, ForgeType::MockForge),
|
config::common::forge_details(1, ForgeType::MockForge),
|
||||||
Some(common::repo_config(1, RepoConfigSource::Repo)),
|
Some(config::common::repo_config(1, RepoConfigSource::Repo)),
|
||||||
GitDir::new(fs.base()),
|
GitDir::new(fs.base()),
|
||||||
);
|
);
|
||||||
let forge = Forge::new_forgejo(repo_details, net, repo);
|
let forge = Forge::new_forgejo(repo_details, net, repo);
|
||||||
|
@ -31,9 +32,9 @@ async fn test_branches_get() {
|
||||||
panic!("fs")
|
panic!("fs")
|
||||||
};
|
};
|
||||||
let mut net = MockNetwork::new();
|
let mut net = MockNetwork::new();
|
||||||
let hostname = common::hostname(1);
|
let hostname = config::common::hostname(1);
|
||||||
let path = common::repo_path(1);
|
let path = config::common::repo_path(1);
|
||||||
let api_token = common::api_token(1);
|
let api_token = config::common::api_token(1);
|
||||||
use secrecy::ExposeSecret;
|
use secrecy::ExposeSecret;
|
||||||
let token = api_token.expose_secret();
|
let token = api_token.expose_secret();
|
||||||
let url = format!("https://{hostname}/api/v1/repos/{path}/branches?token={token}");
|
let url = format!("https://{hostname}/api/v1/repos/{path}/branches?token={token}");
|
||||||
|
@ -42,11 +43,11 @@ async fn test_branches_get() {
|
||||||
let net = Network::from(net);
|
let net = Network::from(net);
|
||||||
let (repo, _reality) = git::repository::mock();
|
let (repo, _reality) = git::repository::mock();
|
||||||
|
|
||||||
let repo_details = common::repo_details(
|
let repo_details = git::common::repo_details(
|
||||||
1,
|
1,
|
||||||
Generation::new(),
|
Generation::new(),
|
||||||
common::forge_details(1, ForgeType::MockForge),
|
config::common::forge_details(1, ForgeType::MockForge),
|
||||||
Some(common::repo_config(1, RepoConfigSource::Repo)),
|
Some(config::common::repo_config(1, RepoConfigSource::Repo)),
|
||||||
GitDir::new(fs.base()),
|
GitDir::new(fs.base()),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub mod common;
|
|
||||||
|
|
||||||
#[cfg(feature = "forgejo")]
|
#[cfg(feature = "forgejo")]
|
||||||
mod forgejo;
|
mod forgejo;
|
||||||
|
|
65
crates/repo-actor/Cargo.toml
Normal file
65
crates/repo-actor/Cargo.toml
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
[package]
|
||||||
|
name = "git-next-repo-actor"
|
||||||
|
version = { workspace = true }
|
||||||
|
edition = { workspace = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["forgejo"]
|
||||||
|
forgejo = []
|
||||||
|
github = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
git-next-config = { workspace = true }
|
||||||
|
git-next-git = { workspace = true }
|
||||||
|
git-next-gitforge = { workspace = true }
|
||||||
|
|
||||||
|
# logging
|
||||||
|
console-subscriber = { workspace = true }
|
||||||
|
tracing = { workspace = true }
|
||||||
|
tracing-subscriber = { workspace = true }
|
||||||
|
|
||||||
|
# base64 decoding
|
||||||
|
base64 = { workspace = true }
|
||||||
|
|
||||||
|
# git
|
||||||
|
async-trait = { workspace = true }
|
||||||
|
|
||||||
|
# fs/network
|
||||||
|
kxio = { workspace = true }
|
||||||
|
|
||||||
|
# TOML parsing
|
||||||
|
serde = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
toml = { workspace = true }
|
||||||
|
|
||||||
|
# Secrets and Password
|
||||||
|
secrecy = { workspace = true }
|
||||||
|
|
||||||
|
# Conventional Commit check
|
||||||
|
git-conventional = { workspace = true }
|
||||||
|
|
||||||
|
# Webhooks
|
||||||
|
bytes = { workspace = true }
|
||||||
|
ulid = { workspace = true }
|
||||||
|
warp = { workspace = true }
|
||||||
|
|
||||||
|
# boilerplate
|
||||||
|
derive_more = { workspace = true }
|
||||||
|
|
||||||
|
# file watcher
|
||||||
|
inotify = { workspace = true }
|
||||||
|
|
||||||
|
# Actors
|
||||||
|
actix = { workspace = true }
|
||||||
|
actix-rt = { workspace = true }
|
||||||
|
tokio = { workspace = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
# Testing
|
||||||
|
assert2 = { workspace = true }
|
||||||
|
|
||||||
|
[lints.clippy]
|
||||||
|
nursery = { level = "warn", priority = -1 }
|
||||||
|
# pedantic = "warn"
|
||||||
|
unwrap_used = "warn"
|
||||||
|
expect_used = "warn"
|
|
@ -2,14 +2,11 @@ use std::time::Duration;
|
||||||
|
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
|
|
||||||
use git_next_config::{RepoConfig, RepoConfigSource};
|
use git_next_config::RepoConfig;
|
||||||
use git_next_git as git;
|
use git_next_git as git;
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
|
||||||
use crate::{
|
use crate::{gitforge, ValidateRepo};
|
||||||
actors::repo::{LoadConfigFromRepo, RepoActor, ValidateRepo},
|
|
||||||
gitforge,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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)]
|
||||||
|
@ -80,23 +77,17 @@ pub 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: git::Commit,
|
next: git::Commit,
|
||||||
repo_config: RepoConfig,
|
repo_config: &RepoConfig,
|
||||||
forge: gitforge::Forge,
|
forge: &gitforge::Forge,
|
||||||
repository: git::OpenRepository,
|
repository: &git::OpenRepository,
|
||||||
addr: Addr<RepoActor>,
|
|
||||||
message_token: gitforge::MessageToken,
|
|
||||||
) {
|
) {
|
||||||
info!("Advancing main to next");
|
info!("Advancing main to next");
|
||||||
if let Err(err) = forge.branch_reset(
|
if let Err(err) = forge.branch_reset(
|
||||||
&repository,
|
repository,
|
||||||
repo_config.branches().main(),
|
repo_config.branches().main(),
|
||||||
next.into(),
|
next.into(),
|
||||||
git::push::Force::No,
|
git::push::Force::No,
|
||||||
) {
|
) {
|
||||||
warn!(?err, "Failed")
|
warn!(?err, "Failed")
|
||||||
};
|
};
|
||||||
match repo_config.source() {
|
|
||||||
RepoConfigSource::Repo => addr.do_send(LoadConfigFromRepo),
|
|
||||||
RepoConfigSource::Server => addr.do_send(ValidateRepo { message_token }),
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -2,7 +2,7 @@ use actix::prelude::*;
|
||||||
use git_next_git::RepoDetails;
|
use git_next_git::RepoDetails;
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
|
||||||
use crate::{config, gitforge};
|
use crate::{gitforge, load};
|
||||||
|
|
||||||
use super::{LoadedConfig, RepoActor};
|
use super::{LoadedConfig, RepoActor};
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ use super::{LoadedConfig, RepoActor};
|
||||||
#[tracing::instrument(skip_all, fields(branch = %repo_details.branch))]
|
#[tracing::instrument(skip_all, fields(branch = %repo_details.branch))]
|
||||||
pub async fn load(repo_details: RepoDetails, addr: Addr<RepoActor>, forge: gitforge::Forge) {
|
pub async fn load(repo_details: RepoDetails, addr: Addr<RepoActor>, forge: gitforge::Forge) {
|
||||||
info!("Loading .git-next.toml from repo");
|
info!("Loading .git-next.toml from repo");
|
||||||
let repo_config = match config::load::load(&repo_details, &forge).await {
|
let repo_config = match load::load(&repo_details, &forge).await {
|
||||||
Ok(repo_config) => repo_config,
|
Ok(repo_config) => repo_config,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!(?err, "Failed to load config");
|
error!(?err, "Failed to load config");
|
|
@ -1,19 +1,25 @@
|
||||||
mod branch;
|
mod branch;
|
||||||
mod config;
|
pub mod config;
|
||||||
|
mod load;
|
||||||
pub mod status;
|
pub mod status;
|
||||||
pub mod webhook;
|
pub mod webhook;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
use git::OpenRepository;
|
use git::OpenRepository;
|
||||||
use git_next_config::{ForgeType, RepoConfig};
|
use git_next_config::{server::Webhook, ForgeType, RepoConfig, RepoConfigSource};
|
||||||
use git_next_git::{self as git, Generation, RepoDetails};
|
use git_next_git::{self as git, Generation, RepoDetails};
|
||||||
|
use git_next_gitforge::{self as gitforge, validation};
|
||||||
use kxio::network::Network;
|
use kxio::network::Network;
|
||||||
use tracing::{debug, info, warn, Instrument};
|
use tracing::{debug, info, warn, Instrument};
|
||||||
|
|
||||||
use crate::{actors::repo::webhook::WebhookAuth, config::Webhook, gitforge};
|
// use crate::{actors::repo::webhook::WebhookAuth, config::Webhook, gitforge};
|
||||||
|
|
||||||
|
use crate::webhook::WebhookAuth;
|
||||||
|
|
||||||
use self::webhook::WebhookId;
|
use self::webhook::WebhookId;
|
||||||
|
|
||||||
|
@ -181,9 +187,24 @@ impl Handler<ValidateRepo> for RepoActor {
|
||||||
let addr = ctx.address();
|
let addr = ctx.address();
|
||||||
let message_token = self.message_token;
|
let message_token = self.message_token;
|
||||||
async move {
|
async move {
|
||||||
forge
|
match forge
|
||||||
.branches_validate_positions(repository, repo_config, addr, message_token)
|
.branches_validate_positions(repository, repo_config)
|
||||||
.await
|
.await
|
||||||
|
{
|
||||||
|
Ok(validation::Positions {
|
||||||
|
main,
|
||||||
|
next,
|
||||||
|
dev,
|
||||||
|
dev_commit_history,
|
||||||
|
}) => {
|
||||||
|
addr.do_send(StartMonitoring::new(main, next, dev, dev_commit_history));
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
warn!("{:?}", err);
|
||||||
|
tokio::time::sleep(Duration::from_secs(10)).await;
|
||||||
|
addr.do_send(ValidateRepo::new(message_token));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.in_current_span()
|
.in_current_span()
|
||||||
.into_actor(self)
|
.into_actor(self)
|
||||||
|
@ -271,14 +292,14 @@ impl Handler<AdvanceMainTo> for RepoActor {
|
||||||
};
|
};
|
||||||
let forge = self.forge.clone();
|
let forge = self.forge.clone();
|
||||||
let addr = ctx.address();
|
let addr = ctx.address();
|
||||||
branch::advance_main(
|
let message_token = self.message_token;
|
||||||
msg.0,
|
async move {
|
||||||
repo_config,
|
branch::advance_main(msg.0, &repo_config, &forge, &repository).await;
|
||||||
forge,
|
match repo_config.source() {
|
||||||
repository,
|
RepoConfigSource::Repo => addr.do_send(LoadConfigFromRepo),
|
||||||
addr,
|
RepoConfigSource::Server => addr.do_send(ValidateRepo { message_token }),
|
||||||
self.message_token,
|
}
|
||||||
)
|
}
|
||||||
.in_current_span()
|
.in_current_span()
|
||||||
.into_actor(self)
|
.into_actor(self)
|
||||||
.wait(ctx);
|
.wait(ctx);
|
|
@ -1,4 +1,4 @@
|
||||||
use git_next_config::{BranchName, RepoConfig};
|
use git_next_config::{self as config, BranchName, RepoConfig};
|
||||||
use git_next_git::RepoDetails;
|
use git_next_git::RepoDetails;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ pub async fn load(details: &RepoDetails, forge: &gitforge::Forge) -> Result<Repo
|
||||||
#[derive(Debug, derive_more::From, derive_more::Display)]
|
#[derive(Debug, derive_more::From, derive_more::Display)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
File(ForgeFileError),
|
File(ForgeFileError),
|
||||||
Config(crate::config::Error),
|
Config(config::server::Error),
|
||||||
Toml(toml::de::Error),
|
Toml(toml::de::Error),
|
||||||
Forge(gitforge::ForgeBranchError),
|
Forge(gitforge::ForgeBranchError),
|
||||||
BranchNotFound(BranchName),
|
BranchNotFound(BranchName),
|
|
@ -1,29 +1,32 @@
|
||||||
|
//
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
|
|
||||||
use git_next_git as git;
|
use git_next_git as git;
|
||||||
|
use git_next_gitforge::{CommitStatus, Forge, MessageToken};
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
|
||||||
use crate::{actors::repo::ValidateRepo, gitforge};
|
use crate::ValidateRepo;
|
||||||
|
|
||||||
use super::AdvanceMainTo;
|
use super::AdvanceMainTo;
|
||||||
|
|
||||||
pub async fn check_next(
|
pub async fn check_next(
|
||||||
next: git::Commit,
|
next: git::Commit,
|
||||||
addr: Addr<super::RepoActor>,
|
addr: Addr<super::RepoActor>,
|
||||||
forge: gitforge::Forge,
|
forge: Forge,
|
||||||
message_token: gitforge::MessageToken,
|
message_token: MessageToken,
|
||||||
) {
|
) {
|
||||||
// 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 = forge.commit_status(&next).await;
|
let status = forge.commit_status(&next).await;
|
||||||
info!(?status, "Checking next branch");
|
info!(?status, "Checking next branch");
|
||||||
match status {
|
match status {
|
||||||
gitforge::CommitStatus::Pass => {
|
CommitStatus::Pass => {
|
||||||
addr.do_send(AdvanceMainTo(next));
|
addr.do_send(AdvanceMainTo(next));
|
||||||
}
|
}
|
||||||
gitforge::CommitStatus::Pending => {
|
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 { message_token });
|
addr.do_send(ValidateRepo { message_token });
|
||||||
}
|
}
|
||||||
gitforge::CommitStatus::Fail => {
|
CommitStatus::Fail => {
|
||||||
warn!("Checks have failed");
|
warn!("Checks have failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,8 @@
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
use git_next_config::{BranchName, RepoBranches};
|
use git_next_config::{
|
||||||
|
server::{Webhook, WebhookUrl},
|
||||||
|
BranchName, RepoAlias, RepoBranches,
|
||||||
|
};
|
||||||
use git_next_git::{self as git, RepoDetails};
|
use git_next_git::{self as git, RepoDetails};
|
||||||
use kxio::network::{self, json};
|
use kxio::network::{self, json};
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
@ -7,13 +10,7 @@ use ulid::DecodeError;
|
||||||
|
|
||||||
use std::{collections::HashMap, str::FromStr};
|
use std::{collections::HashMap, str::FromStr};
|
||||||
|
|
||||||
use crate::{
|
use crate::{RepoActor, ValidateRepo, WebhookRegistered};
|
||||||
actors::{
|
|
||||||
repo::{RepoActor, ValidateRepo, WebhookRegistered},
|
|
||||||
webhook::WebhookMessage,
|
|
||||||
},
|
|
||||||
config::{Webhook, WebhookUrl},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Clone, Debug, PartialEq, Eq, derive_more::Constructor, derive_more::Deref, derive_more::Display,
|
Clone, Debug, PartialEq, Eq, derive_more::Constructor, derive_more::Deref, derive_more::Display,
|
||||||
|
@ -23,7 +20,7 @@ pub struct WebhookId(String);
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, derive_more::Deref, derive_more::Display)]
|
#[derive(Clone, Debug, PartialEq, Eq, derive_more::Deref, derive_more::Display)]
|
||||||
pub struct WebhookAuth(ulid::Ulid);
|
pub struct WebhookAuth(ulid::Ulid);
|
||||||
impl WebhookAuth {
|
impl WebhookAuth {
|
||||||
pub fn from_str(authorisation: &str) -> Result<Self, DecodeError> {
|
pub fn new(authorisation: &str) -> Result<Self, DecodeError> {
|
||||||
let id = ulid::Ulid::from_str(authorisation)?;
|
let id = ulid::Ulid::from_str(authorisation)?;
|
||||||
info!("Parse auth token: {}", id);
|
info!("Parse auth token: {}", id);
|
||||||
Ok(Self(id))
|
Ok(Self(id))
|
||||||
|
@ -307,3 +304,31 @@ pub enum Branch {
|
||||||
struct HeadCommit {
|
struct HeadCommit {
|
||||||
message: String,
|
message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Message, Debug, Clone, derive_more::Constructor)]
|
||||||
|
#[rtype(result = "()")]
|
||||||
|
pub struct WebhookMessage {
|
||||||
|
// forge // TODO: (#58) differentiate between multiple forges
|
||||||
|
repo_alias: RepoAlias,
|
||||||
|
authorisation: WebhookAuth,
|
||||||
|
body: Body,
|
||||||
|
}
|
||||||
|
impl WebhookMessage {
|
||||||
|
pub const fn repo_alias(&self) -> &RepoAlias {
|
||||||
|
&self.repo_alias
|
||||||
|
}
|
||||||
|
pub const fn body(&self) -> &Body {
|
||||||
|
&self.body
|
||||||
|
}
|
||||||
|
pub const fn authorisation(&self) -> &WebhookAuth {
|
||||||
|
&self.authorisation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, derive_more::Constructor)]
|
||||||
|
pub struct Body(String);
|
||||||
|
impl Body {
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
self.0.as_str()
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,8 @@ github = []
|
||||||
[dependencies]
|
[dependencies]
|
||||||
git-next-config = { workspace = true }
|
git-next-config = { workspace = true }
|
||||||
git-next-git = { workspace = true }
|
git-next-git = { workspace = true }
|
||||||
|
git-next-gitforge = { workspace = true }
|
||||||
|
git-next-repo-actor = { workspace = true }
|
||||||
|
|
||||||
# logging
|
# logging
|
||||||
console-subscriber = { workspace = true }
|
console-subscriber = { workspace = true }
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
pub mod file_watcher;
|
pub mod file_watcher;
|
||||||
pub mod repo;
|
|
||||||
pub mod server;
|
pub mod server;
|
||||||
pub mod webhook;
|
pub mod webhook;
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
|
//
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
|
|
||||||
use git_next_config::{ForgeConfig, ForgeName, GitDir, RepoAlias, ServerRepoConfig};
|
use config::server::{ServerConfig, ServerStorage, Webhook};
|
||||||
|
use git_next_config::{
|
||||||
|
self as config, ForgeConfig, ForgeName, GitDir, RepoAlias, ServerRepoConfig,
|
||||||
|
};
|
||||||
use git_next_git::{Generation, RepoDetails, Repository};
|
use git_next_git::{Generation, RepoDetails, Repository};
|
||||||
|
use git_next_repo_actor::{CloneRepo, RepoActor};
|
||||||
use kxio::{fs::FileSystem, network::Network};
|
use kxio::{fs::FileSystem, network::Network};
|
||||||
use tracing::{error, info, warn};
|
use tracing::{error, info, warn};
|
||||||
|
|
||||||
use crate::{
|
use crate::actors::{
|
||||||
actors::{
|
file_watcher::FileUpdated,
|
||||||
file_watcher::FileUpdated,
|
webhook::{AddWebhookRecipient, ShutdownWebhook, WebhookActor, WebhookRouter},
|
||||||
repo::{CloneRepo, RepoActor},
|
|
||||||
webhook::{AddWebhookRecipient, ShutdownWebhook, WebhookActor, WebhookRouter},
|
|
||||||
},
|
|
||||||
config::{ServerConfig, ServerStorage, Webhook},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, derive_more::Display, derive_more::From)]
|
#[derive(Debug, derive_more::Display, derive_more::From)]
|
||||||
|
@ -26,7 +27,7 @@ pub enum Error {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
},
|
},
|
||||||
|
|
||||||
Config(crate::config::Error),
|
Config(config::server::Error),
|
||||||
|
|
||||||
Io(std::io::Error),
|
Io(std::io::Error),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
//
|
|
||||||
use actix::prelude::*;
|
|
||||||
use git_next_config::RepoAlias;
|
|
||||||
|
|
||||||
use crate::actors::repo::webhook::WebhookAuth;
|
|
||||||
|
|
||||||
#[derive(Message, Debug, Clone, derive_more::Constructor)]
|
|
||||||
#[rtype(result = "()")]
|
|
||||||
pub struct WebhookMessage {
|
|
||||||
// forge // TODO: (#58) differentiate between multiple forges
|
|
||||||
repo_alias: RepoAlias,
|
|
||||||
authorisation: WebhookAuth,
|
|
||||||
body: Body,
|
|
||||||
}
|
|
||||||
impl WebhookMessage {
|
|
||||||
pub const fn repo_alias(&self) -> &RepoAlias {
|
|
||||||
&self.repo_alias
|
|
||||||
}
|
|
||||||
pub const fn body(&self) -> &Body {
|
|
||||||
&self.body
|
|
||||||
}
|
|
||||||
pub const fn authorisation(&self) -> &WebhookAuth {
|
|
||||||
&self.authorisation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, derive_more::Constructor)]
|
|
||||||
pub struct Body(String);
|
|
||||||
impl Body {
|
|
||||||
pub fn as_str(&self) -> &str {
|
|
||||||
self.0.as_str()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,5 @@
|
||||||
// crate::server::actors::webhook
|
// crate::server::actors::webhook
|
||||||
|
|
||||||
mod message;
|
|
||||||
mod router;
|
mod router;
|
||||||
mod server;
|
mod server;
|
||||||
|
|
||||||
|
@ -8,7 +7,7 @@ use std::net::SocketAddr;
|
||||||
|
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
|
|
||||||
pub use message::WebhookMessage;
|
use git_next_repo_actor::webhook::WebhookMessage;
|
||||||
pub use router::AddWebhookRecipient;
|
pub use router::AddWebhookRecipient;
|
||||||
pub use router::WebhookRouter;
|
pub use router::WebhookRouter;
|
||||||
use tracing::Instrument;
|
use tracing::Instrument;
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
|
//
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
use git_next_config::RepoAlias;
|
use git_next_config::RepoAlias;
|
||||||
|
use git_next_repo_actor::webhook::WebhookMessage;
|
||||||
use tracing::{debug, info};
|
use tracing::{debug, info};
|
||||||
|
|
||||||
use crate::actors::webhook::message::WebhookMessage;
|
|
||||||
|
|
||||||
pub struct WebhookRouter {
|
pub struct WebhookRouter {
|
||||||
span: tracing::Span,
|
span: tracing::Span,
|
||||||
repos: HashMap<RepoAlias, Recipient<WebhookMessage>>,
|
repos: HashMap<RepoAlias, Recipient<WebhookMessage>>,
|
||||||
|
|
|
@ -1,19 +1,14 @@
|
||||||
|
//
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
|
|
||||||
use git_next_config::RepoAlias;
|
use git_next_config::RepoAlias;
|
||||||
|
use git_next_repo_actor::webhook::{self, WebhookAuth, WebhookMessage};
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
use warp::reject::Rejection;
|
use warp::reject::Rejection;
|
||||||
|
|
||||||
use crate::actors::{repo::webhook::WebhookAuth, webhook::message::WebhookMessage};
|
pub async fn start(socket_addr: SocketAddr, address: actix::prelude::Recipient<WebhookMessage>) {
|
||||||
|
|
||||||
use super::message;
|
|
||||||
|
|
||||||
pub async fn start(
|
|
||||||
socket_addr: SocketAddr,
|
|
||||||
address: actix::prelude::Recipient<message::WebhookMessage>,
|
|
||||||
) {
|
|
||||||
// start webhook server
|
// start webhook server
|
||||||
use warp::Filter;
|
use warp::Filter;
|
||||||
// Define the Warp route to handle incoming HTTP requests
|
// Define the Warp route to handle incoming HTTP requests
|
||||||
|
@ -32,7 +27,7 @@ pub async fn start(
|
||||||
info!("POST received");
|
info!("POST received");
|
||||||
let repo_alias = RepoAlias::new(path);
|
let repo_alias = RepoAlias::new(path);
|
||||||
let bytes = body.to_vec();
|
let bytes = body.to_vec();
|
||||||
let body = message::Body::new(String::from_utf8_lossy(&bytes).to_string());
|
let body = webhook::Body::new(String::from_utf8_lossy(&bytes).to_string());
|
||||||
headers.get("Authorization").map_or_else(
|
headers.get("Authorization").map_or_else(
|
||||||
|| {
|
|| {
|
||||||
warn!("No Authorization header");
|
warn!("No Authorization header");
|
||||||
|
@ -70,7 +65,7 @@ pub async fn start(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_auth(authorization_header: &warp::http::HeaderValue) -> Result<WebhookAuth, Rejection> {
|
fn parse_auth(authorization_header: &warp::http::HeaderValue) -> Result<WebhookAuth, Rejection> {
|
||||||
WebhookAuth::from_str(
|
WebhookAuth::new(
|
||||||
authorization_header
|
authorization_header
|
||||||
.to_str()
|
.to_str()
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
|
|
|
@ -1,107 +1,2 @@
|
||||||
use actix::prelude::*;
|
|
||||||
|
|
||||||
pub mod load;
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
collections::HashMap,
|
|
||||||
net::SocketAddr,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
str::FromStr,
|
|
||||||
};
|
|
||||||
|
|
||||||
use git_next_config::{ForgeConfig, ForgeName};
|
|
||||||
use kxio::fs::FileSystem;
|
|
||||||
use tracing::info;
|
|
||||||
|
|
||||||
#[derive(Debug, derive_more::From, derive_more::Display)]
|
|
||||||
pub enum Error {
|
|
||||||
Io(std::io::Error),
|
|
||||||
KxIoFs(kxio::fs::Error),
|
|
||||||
TomlDe(toml::de::Error),
|
|
||||||
AddressParse(std::net::AddrParseError),
|
|
||||||
}
|
|
||||||
impl std::error::Error for Error {}
|
|
||||||
|
|
||||||
type Result<T> = core::result::Result<T, Error>;
|
|
||||||
|
|
||||||
/// Mapped from the `git-next-server.toml` file
|
|
||||||
#[derive(Debug, PartialEq, Eq, serde::Deserialize, Message)]
|
|
||||||
#[rtype(result = "()")]
|
|
||||||
pub struct ServerConfig {
|
|
||||||
http: Http,
|
|
||||||
webhook: Webhook,
|
|
||||||
storage: ServerStorage,
|
|
||||||
forge: HashMap<String, ForgeConfig>,
|
|
||||||
}
|
|
||||||
impl ServerConfig {
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
pub(crate) fn load(fs: &FileSystem) -> Result<Self> {
|
|
||||||
let file = fs.base().join("git-next-server.toml");
|
|
||||||
info!(?file, "");
|
|
||||||
let str = fs.file_read_to_string(&file)?;
|
|
||||||
toml::from_str(&str).map_err(Into::into)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn forges(&self) -> impl Iterator<Item = (ForgeName, &ForgeConfig)> {
|
|
||||||
self.forge
|
|
||||||
.iter()
|
|
||||||
.map(|(name, forge)| (ForgeName::new(name.clone()), forge))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn storage(&self) -> &ServerStorage {
|
|
||||||
&self.storage
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn webhook(&self) -> &Webhook {
|
|
||||||
&self.webhook
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn http(&self) -> Result<SocketAddr> {
|
|
||||||
self.http.socket_addr()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Defines the port the server will listen to for incoming webhooks messages
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)]
|
|
||||||
pub struct Http {
|
|
||||||
addr: String,
|
|
||||||
port: u16,
|
|
||||||
}
|
|
||||||
impl Http {
|
|
||||||
fn socket_addr(&self) -> Result<SocketAddr> {
|
|
||||||
Ok(SocketAddr::from_str(&format!(
|
|
||||||
"{}:{}",
|
|
||||||
self.addr, self.port
|
|
||||||
))?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Defines the Webhook Forges should send updates to
|
|
||||||
/// Must be an address that is accessible from the remote forge
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)]
|
|
||||||
pub struct Webhook {
|
|
||||||
url: String,
|
|
||||||
}
|
|
||||||
impl Webhook {
|
|
||||||
pub fn url(&self) -> WebhookUrl {
|
|
||||||
WebhookUrl(self.url.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The URL for the webhook where forges should send their updates
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, derive_more::AsRef)]
|
|
||||||
pub struct WebhookUrl(String);
|
|
||||||
|
|
||||||
/// The directory to store server data, such as cloned repos
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)]
|
|
||||||
pub struct ServerStorage {
|
|
||||||
path: PathBuf,
|
|
||||||
}
|
|
||||||
impl ServerStorage {
|
|
||||||
pub fn path(&self) -> &Path {
|
|
||||||
self.path.as_path()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
|
@ -1,132 +1,14 @@
|
||||||
use std::collections::BTreeMap;
|
//
|
||||||
|
|
||||||
use assert2::let_assert;
|
use assert2::let_assert;
|
||||||
use git::repository::Direction;
|
use git::repository::Direction;
|
||||||
use git_next_config::{
|
use git_next_config::{
|
||||||
ForgeType, GitDir, Hostname, RepoBranches, RepoConfig, RepoConfigSource, RepoPath,
|
self as config, ForgeType, GitDir, Hostname, RepoBranches, RepoConfig, RepoConfigSource,
|
||||||
ServerRepoConfig,
|
RepoPath,
|
||||||
};
|
};
|
||||||
use git_next_git::{self as git, Generation, GitRemote};
|
use git_next_git::{self as git, Generation, GitRemote};
|
||||||
use kxio::fs;
|
|
||||||
|
|
||||||
use crate::gitforge::tests::common;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
type Result<T> = core::result::Result<T, Box<dyn std::error::Error>>;
|
type Result<T> = core::result::Result<T, Box<dyn std::error::Error>>;
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn load_should_parse_server_config() -> Result<()> {
|
|
||||||
let fs = fs::temp()?;
|
|
||||||
fs.file_write(
|
|
||||||
&fs.base().join("git-next-server.toml"),
|
|
||||||
r#"
|
|
||||||
[http]
|
|
||||||
addr = "0.0.0.0"
|
|
||||||
port = 8080
|
|
||||||
|
|
||||||
[webhook]
|
|
||||||
url = "http://localhost:9909/webhook"
|
|
||||||
|
|
||||||
[storage]
|
|
||||||
path = "/opt/git-next/data"
|
|
||||||
|
|
||||||
[forge.default]
|
|
||||||
forge_type = "MockForge"
|
|
||||||
hostname = "git.example.net"
|
|
||||||
user = "Bob"
|
|
||||||
token = "API-Token"
|
|
||||||
|
|
||||||
[forge.default.repos]
|
|
||||||
hello = { repo = "user/hello", branch = "main", gitdir = "/opt/git/user/hello.git" }
|
|
||||||
world = { repo = "user/world", branch = "master", main = "main", next = "next", dev = "dev" }
|
|
||||||
|
|
||||||
[forge.default.repos.sam]
|
|
||||||
repo = "user/sam"
|
|
||||||
branch = "main"
|
|
||||||
main = "master"
|
|
||||||
next = "upcoming"
|
|
||||||
dev = "sam-dev"
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
?;
|
|
||||||
let_assert!(Ok(config) = ServerConfig::load(&fs));
|
|
||||||
let expected = ServerConfig {
|
|
||||||
http: Http {
|
|
||||||
addr: "0.0.0.0".to_string(),
|
|
||||||
port: 8080,
|
|
||||||
},
|
|
||||||
webhook: Webhook {
|
|
||||||
url: "http://localhost:9909/webhook".to_string(),
|
|
||||||
},
|
|
||||||
storage: ServerStorage {
|
|
||||||
path: "/opt/git-next/data".into(),
|
|
||||||
},
|
|
||||||
forge: HashMap::from([(
|
|
||||||
"default".to_string(),
|
|
||||||
ForgeConfig::new(
|
|
||||||
ForgeType::MockForge,
|
|
||||||
"git.example.net".to_string(),
|
|
||||||
"Bob".to_string(),
|
|
||||||
"API-Token".to_string(),
|
|
||||||
BTreeMap::from([
|
|
||||||
(
|
|
||||||
"hello".to_string(),
|
|
||||||
ServerRepoConfig::new(
|
|
||||||
"user/hello".to_string(),
|
|
||||||
"main".to_string(),
|
|
||||||
Some("/opt/git/user/hello.git".into()),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"world".to_string(),
|
|
||||||
ServerRepoConfig::new(
|
|
||||||
"user/world".to_string(),
|
|
||||||
"master".to_string(),
|
|
||||||
None,
|
|
||||||
Some("main".to_string()),
|
|
||||||
Some("next".to_string()),
|
|
||||||
Some("dev".to_string()),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"sam".to_string(),
|
|
||||||
ServerRepoConfig::new(
|
|
||||||
"user/sam".to_string(),
|
|
||||||
"main".to_string(),
|
|
||||||
None,
|
|
||||||
Some("master".to_string()),
|
|
||||||
Some("upcoming".to_string()),
|
|
||||||
Some("sam-dev".to_string()),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
)]),
|
|
||||||
};
|
|
||||||
assert_eq!(config, expected, "ServerConfig");
|
|
||||||
|
|
||||||
if let Some(forge) = config.forge.get("world") {
|
|
||||||
if let Some(repo) = forge.get_repo("sam") {
|
|
||||||
let repo_config = repo.repo_config();
|
|
||||||
let expected = Some(RepoConfig::new(
|
|
||||||
RepoBranches::new(
|
|
||||||
"master".to_string(),
|
|
||||||
"upcoming".to_string(),
|
|
||||||
"sam-dev".to_string(),
|
|
||||||
),
|
|
||||||
RepoConfigSource::Server,
|
|
||||||
));
|
|
||||||
assert_eq!(repo_config, expected, "RepoConfig");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_repo_config_load() -> Result<()> {
|
fn test_repo_config_load() -> Result<()> {
|
||||||
let toml = r#"[branches]
|
let toml = r#"[branches]
|
||||||
|
@ -166,10 +48,10 @@ fn gitdir_should_display_as_pathbuf() {
|
||||||
fn repo_details_find_default_push_remote_finds_correct_remote() -> Result<()> {
|
fn repo_details_find_default_push_remote_finds_correct_remote() -> Result<()> {
|
||||||
let cli_crate_dir = std::env::current_dir().map_err(git::validate::Error::Io)?;
|
let cli_crate_dir = std::env::current_dir().map_err(git::validate::Error::Io)?;
|
||||||
let_assert!(Some(Some(root)) = cli_crate_dir.parent().map(|p| p.parent()));
|
let_assert!(Some(Some(root)) = cli_crate_dir.parent().map(|p| p.parent()));
|
||||||
let mut repo_details = common::repo_details(
|
let mut repo_details = git::common::repo_details(
|
||||||
1,
|
1,
|
||||||
Generation::new(),
|
Generation::new(),
|
||||||
common::forge_details(1, ForgeType::MockForge),
|
config::common::forge_details(1, ForgeType::MockForge),
|
||||||
None,
|
None,
|
||||||
GitDir::new(root), // Server GitDir - should be ignored
|
GitDir::new(root), // Server GitDir - should be ignored
|
||||||
);
|
);
|
||||||
|
@ -197,10 +79,10 @@ fn repo_details_find_default_push_remote_finds_correct_remote() -> Result<()> {
|
||||||
fn gitdir_validate_should_pass_a_valid_git_repo() -> Result<()> {
|
fn gitdir_validate_should_pass_a_valid_git_repo() -> Result<()> {
|
||||||
let cli_crate_dir = std::env::current_dir().map_err(git::validate::Error::Io)?;
|
let cli_crate_dir = std::env::current_dir().map_err(git::validate::Error::Io)?;
|
||||||
let_assert!(Some(Some(root)) = cli_crate_dir.parent().map(|p| p.parent()));
|
let_assert!(Some(Some(root)) = cli_crate_dir.parent().map(|p| p.parent()));
|
||||||
let mut repo_details = common::repo_details(
|
let mut repo_details = git::common::repo_details(
|
||||||
1,
|
1,
|
||||||
Generation::new(),
|
Generation::new(),
|
||||||
common::forge_details(1, ForgeType::MockForge),
|
config::common::forge_details(1, ForgeType::MockForge),
|
||||||
None,
|
None,
|
||||||
GitDir::new(root), // Server GitDir - should be ignored
|
GitDir::new(root), // Server GitDir - should be ignored
|
||||||
);
|
);
|
||||||
|
@ -219,10 +101,10 @@ fn gitdir_validate_should_pass_a_valid_git_repo() -> Result<()> {
|
||||||
fn gitdir_validate_should_fail_a_git_repo_with_wrong_remote() -> Result<()> {
|
fn gitdir_validate_should_fail_a_git_repo_with_wrong_remote() -> Result<()> {
|
||||||
let_assert!(Ok(cli_crate_dir) = std::env::current_dir().map_err(git::validate::Error::Io));
|
let_assert!(Ok(cli_crate_dir) = std::env::current_dir().map_err(git::validate::Error::Io));
|
||||||
let_assert!(Some(Some(root)) = cli_crate_dir.parent().map(|p| p.parent()));
|
let_assert!(Some(Some(root)) = cli_crate_dir.parent().map(|p| p.parent()));
|
||||||
let mut repo_details = common::repo_details(
|
let mut repo_details = git::common::repo_details(
|
||||||
1,
|
1,
|
||||||
Generation::new(),
|
Generation::new(),
|
||||||
common::forge_details(1, ForgeType::MockForge),
|
config::common::forge_details(1, ForgeType::MockForge),
|
||||||
None,
|
None,
|
||||||
GitDir::new(root), // Server GitDir - should be ignored
|
GitDir::new(root), // Server GitDir - should be ignored
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
mod actors;
|
mod actors;
|
||||||
mod config;
|
mod config;
|
||||||
pub mod gitforge;
|
//
|
||||||
|
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
|
|
||||||
use git_next_git::Repository;
|
use git_next_git::Repository;
|
||||||
|
|
Loading…
Reference in a new issue