WIP: merge config into core
All checks were successful
ci/woodpecker/push/cron-docker-builder Pipeline was successful
ci/woodpecker/push/tag-created Pipeline was successful
ci/woodpecker/push/push-next Pipeline was successful

This commit is contained in:
Paul Campbell 2024-07-25 09:02:43 +01:00
parent 48c968db2d
commit 8dbe98a7a4
69 changed files with 358 additions and 357 deletions

View file

@ -12,39 +12,39 @@ forgejo = []
github = [] github = []
[dependencies] [dependencies]
# logging # # logging
tracing = { workspace = true } # tracing = { workspace = true }
#
# fs/network # # fs/network
kxio = { workspace = true } # kxio = { workspace = true }
#
# TOML parsing # # TOML parsing
serde = { workspace = true } # serde = { workspace = true }
# serde_json = { workspace = true } # # serde_json = { workspace = true }
toml = { workspace = true } # toml = { workspace = true }
#
# Secrets and Password # # Secrets and Password
secrecy = { workspace = true } # secrecy = { workspace = true }
#
# Git # # Git
gix = { workspace = true } # gix = { workspace = true }
git-url-parse = { workspace = true } # git-url-parse = { workspace = true }
#
# Webhooks # # Webhooks
ulid = { workspace = true } # ulid = { workspace = true }
#
# boilerplate # # boilerplate
derive_more = { workspace = true } # derive_more = { workspace = true }
derive-with = { workspace = true } # derive-with = { workspace = true }
thiserror = { workspace = true } # thiserror = { workspace = true }
pike = { workspace = true } # pike = { workspace = true }
#
[dev-dependencies] # [dev-dependencies]
# # Testing # # # Testing
assert2 = { workspace = true } # assert2 = { workspace = true }
rand = { workspace = true } # rand = { workspace = true }
pretty_assertions = { workspace = true } # pretty_assertions = { workspace = true }
test-log = { workspace = true } # test-log = { workspace = true }
[lints.clippy] [lints.clippy]
nursery = { level = "warn", priority = -1 } nursery = { level = "warn", priority = -1 }

View file

@ -1,50 +1,2 @@
// // moved to crates/core/src/config/
mod api_token; // moved newtype to crates/core/src/macros/
mod branch_name;
pub mod common;
mod forge_alias;
mod forge_config;
mod forge_details;
mod forge_type;
pub mod git_dir;
mod host_name;
mod newtype;
mod registered_webhook;
mod remote_url;
mod repo_alias;
mod repo_branches;
mod repo_config;
mod repo_config_source;
mod repo_path;
pub mod server;
mod server_repo_config;
mod user;
pub mod webhook;
#[cfg(test)]
mod tests;
pub use api_token::ApiToken;
pub use branch_name::BranchName;
pub use forge_alias::ForgeAlias;
pub use forge_config::ForgeConfig;
pub use forge_details::ForgeDetails;
pub use forge_type::ForgeType;
pub use git_dir::GitDir;
pub use git_dir::StoragePathType;
pub use host_name::Hostname;
pub use registered_webhook::RegisteredWebhook;
pub use remote_url::RemoteUrl;
pub use repo_alias::RepoAlias;
pub use repo_branches::RepoBranches;
pub use repo_config::RepoConfig;
pub use repo_config_source::RepoConfigSource;
pub use repo_path::RepoPath;
pub use server_repo_config::ServerRepoConfig;
pub use user::User;
pub use webhook::auth::WebhookAuth;
pub use webhook::forge_notification::ForgeNotification;
pub use webhook::id::WebhookId;
// re-export
pub use pike::{pike, pike_opt, pike_res};

View file

@ -1,16 +0,0 @@
//
use crate as config;
#[derive(Debug, derive_more::Constructor)]
pub struct RegisteredWebhook {
id: config::WebhookId,
auth: config::WebhookAuth,
}
impl RegisteredWebhook {
pub const fn id(&self) -> &config::WebhookId {
&self.id
}
pub const fn auth(&self) -> &config::WebhookAuth {
&self.auth
}
}

View file

@ -6,6 +6,11 @@ license = { workspace = true }
repository = { workspace = true } repository = { workspace = true }
description = "core for git-next, the trunk-based development manager" description = "core for git-next, the trunk-based development manager"
[features]
default = ["forgejo", "github"]
forgejo = []
github = []
[dependencies] [dependencies]
# logging # logging
console-subscriber = { workspace = true } console-subscriber = { workspace = true }
@ -19,6 +24,26 @@ kxio = { workspace = true }
actix = { workspace = true } actix = { workspace = true }
actix-rt = { workspace = true } actix-rt = { workspace = true }
# TOML parsing
serde = { workspace = true }
toml = { workspace = true }
# Secrets and Password
secrecy = { workspace = true }
# Git
gix = { workspace = true }
git-url-parse = { workspace = true }
# Webhooks
ulid = { workspace = true }
# boilerplate
derive_more = { workspace = true }
derive-with = { workspace = true }
thiserror = { workspace = true }
pike = { workspace = true }
[dev-dependencies] [dev-dependencies]
# Testing # Testing
assert2 = { workspace = true } assert2 = { workspace = true }

View file

@ -1,4 +1,4 @@
use crate::{ use crate::config::{
ApiToken, BranchName, ForgeAlias, ForgeDetails, ForgeType, Hostname, RepoAlias, RepoBranches, ApiToken, BranchName, ForgeAlias, ForgeDetails, ForgeType, Hostname, RepoAlias, RepoBranches,
RepoConfig, RepoConfigSource, RepoPath, User, RepoConfig, RepoConfigSource, RepoPath, User,
}; };

View file

@ -1,6 +1,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use crate::{ApiToken, ForgeType, Hostname, RepoAlias, ServerRepoConfig, User}; use crate::config::{ApiToken, ForgeType, Hostname, RepoAlias, ServerRepoConfig, User};
/// Defines a Forge to connect to /// Defines a Forge to connect to
/// Maps from `git-next-server.toml` at `forge.{forge}` /// Maps from `git-next-server.toml` at `forge.{forge}`

View file

@ -1,4 +1,4 @@
use crate::{ApiToken, ForgeAlias, ForgeConfig, ForgeType, Hostname, User}; use crate::config::{ApiToken, ForgeAlias, ForgeConfig, ForgeType, Hostname, User};
/// The derived information about a Forge, used to create interactions with it /// The derived information about a Forge, used to create interactions with it
#[derive(Clone, Default, Debug, derive_more::Constructor, derive_with::With)] #[derive(Clone, Default, Debug, derive_more::Constructor, derive_with::With)]

View file

@ -0,0 +1,49 @@
//
mod api_token;
mod branch_name;
pub mod common;
mod forge_alias;
mod forge_config;
mod forge_details;
mod forge_type;
pub mod git_dir;
mod host_name;
mod registered_webhook;
mod remote_url;
mod repo_alias;
mod repo_branches;
mod repo_config;
mod repo_config_source;
mod repo_path;
pub mod server;
mod server_repo_config;
mod user;
pub mod webhook;
#[cfg(test)]
mod tests;
pub use api_token::ApiToken;
pub use branch_name::BranchName;
pub use forge_alias::ForgeAlias;
pub use forge_config::ForgeConfig;
pub use forge_details::ForgeDetails;
pub use forge_type::ForgeType;
pub use git_dir::GitDir;
pub use git_dir::StoragePathType;
pub use host_name::Hostname;
pub use registered_webhook::RegisteredWebhook;
pub use remote_url::RemoteUrl;
pub use repo_alias::RepoAlias;
pub use repo_branches::RepoBranches;
pub use repo_config::RepoConfig;
pub use repo_config_source::RepoConfigSource;
pub use repo_path::RepoPath;
pub use server_repo_config::ServerRepoConfig;
pub use user::User;
pub use webhook::auth::WebhookAuth;
pub use webhook::forge_notification::ForgeNotification;
pub use webhook::id::WebhookId;
// re-export
pub use pike::{pike, pike_opt, pike_res};

View file

@ -0,0 +1,16 @@
//
use crate::config::{WebhookAuth, WebhookId};
#[derive(Debug, derive_more::Constructor)]
pub struct RegisteredWebhook {
id: WebhookId,
auth: WebhookAuth,
}
impl RegisteredWebhook {
pub const fn id(&self) -> &WebhookId {
&self.id
}
pub const fn auth(&self) -> &WebhookAuth {
&self.auth
}
}

View file

@ -1,4 +1,4 @@
use crate::BranchName; use crate::config::BranchName;
/// Mapped from `.git-next.toml` file at `branches` /// Mapped from `.git-next.toml` file at `branches`
#[derive( #[derive(

View file

@ -1,5 +1,4 @@
use crate::RepoBranches; use crate::config::{RepoBranches, RepoConfigSource};
use crate::RepoConfigSource;
/// Mapped from `.git-next.toml` file in target repo /// Mapped from `.git-next.toml` file in target repo
/// Is also derived from the optional parameters in `git-next-server.toml` at /// Is also derived from the optional parameters in `git-next-server.toml` at

View file

@ -11,7 +11,10 @@ use kxio::fs::FileSystem;
use secrecy::Secret; use secrecy::Secret;
use tracing::info; use tracing::info;
use crate::{newtype, ForgeAlias, ForgeConfig, RepoAlias}; use crate::{
config::{ForgeAlias, ForgeConfig, RepoAlias},
newtype,
};
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {

View file

@ -1,6 +1,10 @@
//
use std::path::PathBuf; use std::path::PathBuf;
use crate::{BranchName, GitDir, RepoBranches, RepoConfig, RepoConfigSource, RepoPath}; use crate::config::{
git_dir::StoragePathType, BranchName, GitDir, RepoBranches, RepoConfig, RepoConfigSource,
RepoPath,
};
/// Defines a Repo within a ForgeConfig to be monitored by the server /// Defines a Repo within a ForgeConfig to be monitored by the server
/// Maps from `git-next-server.toml` at `forge.{forge}.repos.{name}` /// Maps from `git-next-server.toml` at `forge.{forge}.repos.{name}`
@ -38,7 +42,7 @@ impl ServerRepoConfig {
self.gitdir self.gitdir
.clone() .clone()
// Provenance is external as the gitdir is only used to specify non-internal paths // Provenance is external as the gitdir is only used to specify non-internal paths
.map(|dir| GitDir::new(dir, crate::git_dir::StoragePathType::External)) .map(|dir| GitDir::new(dir, StoragePathType::External))
} }
/// Returns a RepoConfig from the server configuration if ALL THREE branches were provided /// Returns a RepoConfig from the server configuration if ALL THREE branches were provided

View file

@ -1,5 +1,7 @@
// //
crate::newtype!(WebhookAuth: ulid::Ulid, derive_more::Display: r#"The unique token authorisation for the webhook. use crate::newtype;
newtype!(WebhookAuth: ulid::Ulid, derive_more::Display: r#"The unique token authorisation for the webhook.
Each monitored repository has it's own unique token, and it is different each time `git-next` runs."#); Each monitored repository has it's own unique token, and it is different each time `git-next` runs."#);
impl WebhookAuth { impl WebhookAuth {

View file

@ -1,21 +1,23 @@
// //
use crate as config; use crate::config::{ForgeAlias, RepoAlias};
use derive_more::Constructor;
use std::collections::BTreeMap; use std::collections::BTreeMap;
/// A notification receive from a Forge, typically via a Webhook. /// A notification receive from a Forge, typically via a Webhook.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, derive_more::Constructor)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, derive_more::Constructor)]
pub struct ForgeNotification { pub struct ForgeNotification {
forge_alias: config::ForgeAlias, forge_alias: ForgeAlias,
repo_alias: config::RepoAlias, repo_alias: RepoAlias,
headers: BTreeMap<String, String>, headers: BTreeMap<String, String>,
body: Body, body: Body,
} }
impl ForgeNotification { impl ForgeNotification {
pub const fn forge_alias(&self) -> &config::ForgeAlias { pub const fn forge_alias(&self) -> &ForgeAlias {
&self.forge_alias &self.forge_alias
} }
pub const fn repo_alias(&self) -> &config::RepoAlias { pub const fn repo_alias(&self) -> &RepoAlias {
&self.repo_alias &self.repo_alias
} }
pub const fn body(&self) -> &Body { pub const fn body(&self) -> &Body {
@ -26,7 +28,7 @@ impl ForgeNotification {
} }
} }
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, derive_more::Constructor)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Constructor)]
pub struct Body(String); pub struct Body(String);
impl Body { impl Body {
pub fn as_str(&self) -> &str { pub fn as_str(&self) -> &str {

View file

@ -1,16 +1,15 @@
// //
use crate as config; use crate::config::{BranchName, RepoBranches};
use derive_more::Constructor; use derive_more::Constructor;
#[derive(Debug, Constructor, derive_with::With)] #[derive(Debug, Constructor, derive_with::With)]
pub struct Push { pub struct Push {
branch: config::BranchName, branch: BranchName,
sha: String, sha: String,
message: String, message: String,
} }
impl Push { impl Push {
pub fn branch(&self, repo_branches: &crate::RepoBranches) -> Option<Branch> { pub fn branch(&self, repo_branches: &RepoBranches) -> Option<Branch> {
if self.branch == repo_branches.main() { if self.branch == repo_branches.main() {
return Some(Branch::Main); return Some(Branch::Main);
} }

View file

@ -1 +1,4 @@
mod config;
mod macros; mod macros;
pub use config::*;

View file

@ -1 +1,2 @@
mod message; mod message;
mod newtype;

View file

@ -7,7 +7,7 @@ repository = { workspace = true }
description = "Forgejo support for git-next, the trunk-based development manager" description = "Forgejo support for git-next, the trunk-based development manager"
[dependencies] [dependencies]
git-next-config = { workspace = true } git-next-core = { workspace = true }
git-next-git = { workspace = true } git-next-git = { workspace = true }
# logging # logging

View file

@ -5,7 +5,9 @@ mod tests;
mod webhook; mod webhook;
use git::forge::commit::Status; use git::forge::commit::Status;
use git_next_config as config; use git_next_core::{
self as core, server, ForgeNotification, RegisteredWebhook, WebhookAuth, WebhookId,
};
use git_next_git as git; use git_next_git as git;
use kxio::network::{self, Network}; use kxio::network::{self, Network};
@ -30,24 +32,20 @@ impl git::ForgeLike for ForgeJo {
"forgejo".to_string() "forgejo".to_string()
} }
fn is_message_authorised( fn is_message_authorised(&self, msg: &ForgeNotification, expected: &WebhookAuth) -> bool {
&self,
msg: &config::ForgeNotification,
expected: &config::WebhookAuth,
) -> bool {
let authorization = msg.header("authorization"); let authorization = msg.header("authorization");
tracing::info!(?authorization, %expected, "is message authorised?"); tracing::info!(?authorization, %expected, "is message authorised?");
authorization authorization
.and_then(|header| header.strip_prefix("Basic ").map(|v| v.to_owned())) .and_then(|header| header.strip_prefix("Basic ").map(|v| v.to_owned()))
.and_then(|value| config::WebhookAuth::try_new(value.as_str()).ok()) .and_then(|value| WebhookAuth::try_new(value.as_str()).ok())
.map(|auth| &auth == expected) .map(|auth| &auth == expected)
.unwrap_or(false) .unwrap_or(false)
} }
fn parse_webhook_body( fn parse_webhook_body(
&self, &self,
body: &config::webhook::forge_notification::Body, body: &core::webhook::forge_notification::Body,
) -> git::forge::webhook::Result<config::webhook::Push> { ) -> git::forge::webhook::Result<core::webhook::Push> {
webhook::parse_body(body) webhook::parse_body(body)
} }
@ -96,22 +94,19 @@ impl git::ForgeLike for ForgeJo {
async fn list_webhooks( async fn list_webhooks(
&self, &self,
webhook_url: &config::server::WebhookUrl, webhook_url: &server::WebhookUrl,
) -> git::forge::webhook::Result<Vec<config::WebhookId>> { ) -> git::forge::webhook::Result<Vec<WebhookId>> {
webhook::list(&self.repo_details, webhook_url, &self.net).await webhook::list(&self.repo_details, webhook_url, &self.net).await
} }
async fn unregister_webhook( async fn unregister_webhook(&self, webhook_id: &WebhookId) -> git::forge::webhook::Result<()> {
&self,
webhook_id: &config::WebhookId,
) -> git::forge::webhook::Result<()> {
webhook::unregister(webhook_id, &self.repo_details, &self.net).await webhook::unregister(webhook_id, &self.repo_details, &self.net).await
} }
async fn register_webhook( async fn register_webhook(
&self, &self,
webhook_url: &config::server::WebhookUrl, webhook_url: &server::WebhookUrl,
) -> git::forge::webhook::Result<config::RegisteredWebhook> { ) -> git::forge::webhook::Result<RegisteredWebhook> {
webhook::register(&self.repo_details, webhook_url, &self.net).await webhook::register(&self.repo_details, webhook_url, &self.net).await
} }
} }

View file

@ -1,16 +1,17 @@
// //
use git_next_config as config; use git_next_core::{server::WebhookUrl, WebhookId};
use git_next_git as git; use git_next_git as git;
use kxio::network; use kxio::network;
use crate::webhook::Hook; use crate::webhook::Hook;
pub async fn list( pub async fn list(
repo_details: &git::RepoDetails, repo_details: &git::RepoDetails,
webhook_url: &config::server::WebhookUrl, webhook_url: &WebhookUrl,
net: &network::Network, net: &network::Network,
) -> git::forge::webhook::Result<Vec<config::WebhookId>> { ) -> git::forge::webhook::Result<Vec<WebhookId>> {
let mut ids: Vec<config::WebhookId> = vec![]; let mut ids: Vec<WebhookId> = vec![];
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;
let mut page = 1; let mut page = 1;

View file

@ -1,6 +1,7 @@
// //
use git_next_config as config; use git_next_core::{webhook, BranchName, WebhookId};
use git_next_git as git; use git_next_git as git;
use std::collections::HashMap; use std::collections::HashMap;
mod list; mod list;
@ -19,8 +20,8 @@ struct Hook {
config: HashMap<String, String>, config: HashMap<String, String>,
} }
impl Hook { impl Hook {
fn id(&self) -> config::WebhookId { fn id(&self) -> WebhookId {
config::WebhookId::new(format!("{}", self.id)) WebhookId::new(format!("{}", self.id))
} }
} }
@ -32,14 +33,14 @@ pub struct Push {
head_commit: HeadCommit, head_commit: HeadCommit,
} }
impl TryFrom<Push> for config::webhook::Push { impl TryFrom<Push> for webhook::Push {
type Error = git::forge::webhook::Error; type Error = git::forge::webhook::Error;
fn try_from(push: Push) -> Result<Self, Self::Error> { fn try_from(push: Push) -> Result<Self, Self::Error> {
let branch = push let branch = push
.reference .reference
.splitn(3, '/') // should be of the form 'refs/heads/branchname' .splitn(3, '/') // should be of the form 'refs/heads/branchname'
.nth(2) .nth(2)
.map(config::BranchName::new) .map(BranchName::new)
.ok_or(git::forge::webhook::Error::UnknownBranch(push.reference))?; .ok_or(git::forge::webhook::Error::UnknownBranch(push.reference))?;
Ok(Self::new(branch, push.after, push.head_commit.message)) Ok(Self::new(branch, push.after, push.head_commit.message))
} }

View file

@ -1,10 +1,11 @@
// //
use crate as forgejo; use crate as forgejo;
use git_next_config as config;
use git_next_core::webhook;
use git_next_git as git; use git_next_git as git;
pub fn parse_body( pub fn parse_body(
body: &config::webhook::forge_notification::Body, body: &webhook::forge_notification::Body,
) -> git::forge::webhook::Result<config::webhook::Push> { ) -> git::forge::webhook::Result<webhook::Push> {
serde_json::from_str::<forgejo::webhook::Push>(body.as_str())?.try_into() serde_json::from_str::<forgejo::webhook::Push>(body.as_str())?.try_into()
} }

View file

@ -1,6 +1,5 @@
// //
use git_next_core::{server, RegisteredWebhook, WebhookAuth, WebhookId};
use git_next_config as config;
use git_next_git as git; use git_next_git as git;
use kxio::network; use kxio::network;
@ -12,9 +11,9 @@ use crate::webhook::Hook;
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn register( pub async fn register(
repo_details: &git::RepoDetails, repo_details: &git::RepoDetails,
webhook_url: &config::server::WebhookUrl, webhook_url: &server::WebhookUrl,
net: &network::Network, net: &network::Network,
) -> git::forge::webhook::Result<config::RegisteredWebhook> { ) -> git::forge::webhook::Result<RegisteredWebhook> {
let Some(repo_config) = repo_details.repo_config.clone() else { let Some(repo_config) = repo_details.repo_config.clone() else {
return Err(git::forge::webhook::Error::NoRepoConfig); return Err(git::forge::webhook::Error::NoRepoConfig);
}; };
@ -34,7 +33,7 @@ pub async fn register(
)); ));
let repo_alias = &repo_details.repo_alias; let repo_alias = &repo_details.repo_alias;
let headers = network::NetRequestHeaders::new().with("Content-Type", "application/json"); let headers = network::NetRequestHeaders::new().with("Content-Type", "application/json");
let authorisation = config::WebhookAuth::generate(); let authorisation = WebhookAuth::generate();
let body = network::json!({ let body = network::json!({
"active": true, "active": true,
"authorization_header": authorisation.header_value(), "authorization_header": authorisation.header_value(),
@ -64,8 +63,8 @@ pub async fn register(
return Err(git::forge::webhook::Error::NetworkResponseEmpty); return Err(git::forge::webhook::Error::NetworkResponseEmpty);
}; };
info!(webhook_id = %hook.id, "Webhook registered"); info!(webhook_id = %hook.id, "Webhook registered");
Ok(config::RegisteredWebhook::new( Ok(RegisteredWebhook::new(
config::WebhookId::new(format!("{}", hook.id)), WebhookId::new(format!("{}", hook.id)),
authorisation, authorisation,
)) ))
} }

View file

@ -1,11 +1,11 @@
// //
use git_next_config as config; use git_next_core::WebhookId;
use git_next_git as git; use git_next_git as git;
use kxio::network; use kxio::network;
pub async fn unregister( pub async fn unregister(
webhook_id: &config::WebhookId, webhook_id: &WebhookId,
repo_details: &git::RepoDetails, repo_details: &git::RepoDetails,
net: &network::Network, net: &network::Network,
) -> git::forge::webhook::Result<()> { ) -> git::forge::webhook::Result<()> {

View file

@ -7,7 +7,7 @@ repository = { workspace = true }
description = "GitHub support for git-next, the trunk-based development manager" description = "GitHub support for git-next, the trunk-based development manager"
[dependencies] [dependencies]
git-next-config = { workspace = true } git-next-core = { workspace = true }
git-next-git = { workspace = true } git-next-git = { workspace = true }
# own version for UserAgent requests to github.com # own version for UserAgent requests to github.com

View file

@ -7,7 +7,9 @@ mod webhook;
use crate as github; use crate as github;
use git::forge::commit::Status; use git::forge::commit::Status;
use git_next_config as config; use git_next_core::{
self as core, server, ForgeNotification, RegisteredWebhook, WebhookAuth, WebhookId,
};
use git_next_git as git; use git_next_git as git;
use derive_more::Constructor; use derive_more::Constructor;
@ -26,15 +28,11 @@ impl git::ForgeLike for Github {
"github".to_string() "github".to_string()
} }
fn is_message_authorised( fn is_message_authorised(&self, msg: &ForgeNotification, webhook_auth: &WebhookAuth) -> bool {
&self,
msg: &config::ForgeNotification,
webhook_auth: &config::WebhookAuth,
) -> bool {
github::webhook::is_authorised(msg, webhook_auth) github::webhook::is_authorised(msg, webhook_auth)
} }
fn should_ignore_message(&self, message: &config::ForgeNotification) -> bool { fn should_ignore_message(&self, message: &ForgeNotification) -> bool {
let Some(event) = message.header("x-github-event") else { let Some(event) = message.header("x-github-event") else {
return false; return false;
}; };
@ -48,8 +46,8 @@ impl git::ForgeLike for Github {
fn parse_webhook_body( fn parse_webhook_body(
&self, &self,
body: &config::webhook::forge_notification::Body, body: &core::webhook::forge_notification::Body,
) -> git::forge::webhook::Result<config::webhook::push::Push> { ) -> git::forge::webhook::Result<core::webhook::push::Push> {
github::webhook::parse_body(body) github::webhook::parse_body(body)
} }
@ -59,23 +57,20 @@ impl git::ForgeLike for Github {
async fn list_webhooks( async fn list_webhooks(
&self, &self,
webhook_url: &config::server::WebhookUrl, webhook_url: &server::WebhookUrl,
) -> git::forge::webhook::Result<Vec<config::WebhookId>> { ) -> git::forge::webhook::Result<Vec<WebhookId>> {
github::webhook::list(self, webhook_url).await github::webhook::list(self, webhook_url).await
} }
async fn unregister_webhook( async fn unregister_webhook(&self, webhook_id: &WebhookId) -> git::forge::webhook::Result<()> {
&self,
webhook_id: &config::WebhookId,
) -> git::forge::webhook::Result<()> {
github::webhook::unregister(self, webhook_id).await github::webhook::unregister(self, webhook_id).await
} }
// https://docs.github.com/en/rest/repos/webhooks?apiVersion=2022-11-28#create-a-repository-webhook // https://docs.github.com/en/rest/repos/webhooks?apiVersion=2022-11-28#create-a-repository-webhook
async fn register_webhook( async fn register_webhook(
&self, &self,
webhook_url: &config::server::WebhookUrl, webhook_url: &server::WebhookUrl,
) -> git::forge::webhook::Result<config::RegisteredWebhook> { ) -> git::forge::webhook::Result<RegisteredWebhook> {
github::webhook::register(self, webhook_url).await github::webhook::register(self, webhook_url).await
} }
} }
@ -105,11 +100,11 @@ struct GithubHook {
config: Config, config: Config,
} }
impl GithubHook { impl GithubHook {
pub fn id(&self) -> config::WebhookId { pub fn id(&self) -> WebhookId {
config::WebhookId::new(format!("{}", self.id)) WebhookId::new(format!("{}", self.id))
} }
pub fn url(&self) -> config::server::WebhookUrl { pub fn url(&self) -> server::WebhookUrl {
config::server::WebhookUrl::new(self.config.url.clone()) server::WebhookUrl::new(self.config.url.clone())
} }
} }
#[derive(Debug, serde::Deserialize)] #[derive(Debug, serde::Deserialize)]

View file

@ -1,7 +1,7 @@
// //
use git_next_config as config; use git_next_core::{webhook, ForgeNotification, WebhookAuth};
pub fn is_authorised(msg: &config::ForgeNotification, webhook_auth: &config::WebhookAuth) -> bool { pub fn is_authorised(msg: &ForgeNotification, webhook_auth: &WebhookAuth) -> bool {
msg.header("x-hub-signature-256") msg.header("x-hub-signature-256")
.map(|x| x.trim_matches('"').to_string()) .map(|x| x.trim_matches('"').to_string())
.and_then(|sha| sha.strip_prefix("sha256=").map(|k| k.to_string())) .and_then(|sha| sha.strip_prefix("sha256=").map(|k| k.to_string()))
@ -20,10 +20,11 @@ pub fn is_authorised(msg: &config::ForgeNotification, webhook_auth: &config::Web
#[cfg(test)] #[cfg(test)]
pub fn sign_body( pub fn sign_body(
webhook_auth: &config::WebhookAuth, webhook_auth: &WebhookAuth,
body: &config::webhook::forge_notification::Body, body: &webhook::forge_notification::Body,
) -> std::option::Option<String> { ) -> std::option::Option<String> {
let payload = body.as_str(); let payload = body.as_str();
use hmac::Mac; use hmac::Mac;
type HmacSha256 = hmac::Hmac<sha2::Sha256>; type HmacSha256 = hmac::Hmac<sha2::Sha256>;
let mut hmac = HmacSha256::new_from_slice(webhook_auth.to_string().as_bytes()).ok()?; let mut hmac = HmacSha256::new_from_slice(webhook_auth.to_string().as_bytes()).ok()?;

View file

@ -1,15 +1,16 @@
// //
use crate as github; use crate as github;
use git_next_config as config; use git_next_core::{server, WebhookId};
use git_next_git as git; use git_next_git as git;
use kxio::network; use kxio::network;
// https://docs.github.com/en/rest/repos/webhooks?apiVersion=2022-11-28#list-repository-webhooks // https://docs.github.com/en/rest/repos/webhooks?apiVersion=2022-11-28#list-repository-webhooks
pub async fn list( pub async fn list(
github: &github::Github, github: &github::Github,
webhook_url: &config::server::WebhookUrl, webhook_url: &server::WebhookUrl,
) -> git::forge::webhook::Result<Vec<config::WebhookId>> { ) -> git::forge::webhook::Result<Vec<WebhookId>> {
let mut ids: Vec<config::WebhookId> = vec![]; let mut ids: Vec<WebhookId> = vec![];
let repo_details = &github.repo_details; let repo_details = &github.repo_details;
let hostname = &repo_details.forge.hostname(); let hostname = &repo_details.forge.hostname();
let net = &github.net; let net = &github.net;

View file

@ -1,5 +1,5 @@
// //
use git_next_config as config; use git_next_core::{webhook, ApiToken, BranchName};
use git_next_git as git; use git_next_git as git;
mod authorised; mod authorised;
@ -17,7 +17,7 @@ pub use unregister::unregister;
#[cfg(test)] #[cfg(test)]
pub use authorised::sign_body; pub use authorised::sign_body;
pub fn headers(token: &config::ApiToken) -> kxio::network::NetRequestHeaders { pub fn headers(token: &ApiToken) -> kxio::network::NetRequestHeaders {
use secrecy::ExposeSecret; use secrecy::ExposeSecret;
kxio::network::NetRequestHeaders::default() kxio::network::NetRequestHeaders::default()
.with("Accept", "application/vnd.github+json") .with("Accept", "application/vnd.github+json")
@ -44,14 +44,14 @@ pub struct HeadCommit {
message: String, message: String,
} }
impl TryFrom<Push> for config::webhook::Push { impl TryFrom<Push> for webhook::Push {
type Error = git::forge::webhook::Error; type Error = git::forge::webhook::Error;
fn try_from(push: Push) -> Result<Self, Self::Error> { fn try_from(push: Push) -> Result<Self, Self::Error> {
let branch = push let branch = push
.reference .reference
.splitn(3, '/') // should be of the form 'refs/heads/branchname' .splitn(3, '/') // should be of the form 'refs/heads/branchname'
.nth(2) .nth(2)
.map(config::BranchName::new) .map(BranchName::new)
.ok_or(git::forge::webhook::Error::UnknownBranch(push.reference))?; .ok_or(git::forge::webhook::Error::UnknownBranch(push.reference))?;
Ok(Self::new(branch, push.after, push.head_commit.message)) Ok(Self::new(branch, push.after, push.head_commit.message))
} }

View file

@ -1,10 +1,11 @@
// //
use crate as github; use crate as github;
use git_next_config as config;
use git_next_core::webhook;
use git_next_git as git; use git_next_git as git;
pub fn parse_body( pub fn parse_body(
body: &config::webhook::forge_notification::Body, body: &webhook::forge_notification::Body,
) -> git::forge::webhook::Result<config::webhook::push::Push> { ) -> git::forge::webhook::Result<webhook::push::Push> {
serde_json::from_str::<github::webhook::Push>(body.as_str())?.try_into() serde_json::from_str::<github::webhook::Push>(body.as_str())?.try_into()
} }

View file

@ -1,6 +1,6 @@
// //
use crate::{self as github, webhook}; use crate::{self as github, webhook};
use git_next_config as config; use git_next_core::{server, RegisteredWebhook, WebhookAuth, WebhookId};
use git_next_git as git; use git_next_git as git;
use kxio::network; use kxio::network;
@ -8,8 +8,8 @@ use kxio::network;
// https://docs.github.com/en/rest/repos/webhooks?apiVersion=2022-11-28#create-a-repository-webhook // https://docs.github.com/en/rest/repos/webhooks?apiVersion=2022-11-28#create-a-repository-webhook
pub async fn register( pub async fn register(
github: &github::Github, github: &github::Github,
webhook_url: &config::server::WebhookUrl, webhook_url: &server::WebhookUrl,
) -> git::forge::webhook::Result<config::RegisteredWebhook> { ) -> git::forge::webhook::Result<RegisteredWebhook> {
let repo_details = &github.repo_details; let repo_details = &github.repo_details;
if repo_details.repo_config.is_none() { if repo_details.repo_config.is_none() {
return Err(git::forge::webhook::Error::NoRepoConfig); return Err(git::forge::webhook::Error::NoRepoConfig);
@ -23,7 +23,7 @@ pub async fn register(
let net = &github.net; let net = &github.net;
let hostname = repo_details.forge.hostname(); let hostname = repo_details.forge.hostname();
let authorisation = config::WebhookAuth::generate(); let authorisation = WebhookAuth::generate();
let request = network::NetRequest::new( let request = network::NetRequest::new(
network::RequestMethod::Post, network::RequestMethod::Post,
network::NetUrl::new(format!( network::NetUrl::new(format!(
@ -55,8 +55,8 @@ pub async fn register(
return Err(git::forge::webhook::Error::NetworkResponseEmpty); return Err(git::forge::webhook::Error::NetworkResponseEmpty);
}; };
tracing::info!(webhook_id = %hook.id, "Webhook registered"); tracing::info!(webhook_id = %hook.id, "Webhook registered");
Ok(config::RegisteredWebhook::new( Ok(RegisteredWebhook::new(
config::WebhookId::new(format!("{}", hook.id)), WebhookId::new(format!("{}", hook.id)),
authorisation, authorisation,
)) ))
} }

View file

@ -1,6 +1,7 @@
// //
use crate as github; use crate as github;
use git_next_config as config;
use git_next_core::WebhookId;
use git_next_git as git; use git_next_git as git;
use kxio::network; use kxio::network;
@ -8,7 +9,7 @@ use kxio::network;
// https://docs.github.com/en/rest/repos/webhooks?apiVersion=2022-11-28#delete-a-repository-webhook // https://docs.github.com/en/rest/repos/webhooks?apiVersion=2022-11-28#delete-a-repository-webhook
pub async fn unregister( pub async fn unregister(
github: &github::Github, github: &github::Github,
webhook_id: &config::WebhookId, webhook_id: &WebhookId,
) -> git::forge::webhook::Result<()> { ) -> git::forge::webhook::Result<()> {
let net = &github.net; let net = &github.net;
let repo_details = &github.repo_details; let repo_details = &github.repo_details;

View file

@ -12,7 +12,7 @@ forgejo = ["git-next-forge-forgejo"]
github = ["git-next-forge-github"] github = ["git-next-forge-github"]
[dependencies] [dependencies]
git-next-config = { workspace = true } git-next-core = { workspace = true }
git-next-git = { workspace = true } git-next-git = { workspace = true }
git-next-forge-forgejo = { workspace = true, optional = true } git-next-forge-forgejo = { workspace = true, optional = true }
git-next-forge-github = { workspace = true, optional = true } git-next-forge-github = { workspace = true, optional = true }

View file

@ -1,6 +1,5 @@
// //
use git_next_forge_forgejo as forgejo; use git_next_core::ForgeType;
use git_next_forge_github as github;
use git_next_git as git; use git_next_git as git;
use kxio::network::Network; use kxio::network::Network;
@ -18,12 +17,10 @@ impl Forge {
pub fn create(repo_details: git::RepoDetails, net: Network) -> Box<dyn git::ForgeLike> { pub fn create(repo_details: git::RepoDetails, net: Network) -> Box<dyn git::ForgeLike> {
match repo_details.forge.forge_type() { match repo_details.forge.forge_type() {
#[cfg(feature = "forgejo")] #[cfg(feature = "forgejo")]
git_next_config::ForgeType::ForgeJo => { ForgeType::ForgeJo => Box::new(git_next_forge_forgejo::ForgeJo::new(repo_details, net)),
Box::new(forgejo::ForgeJo::new(repo_details, net))
}
#[cfg(feature = "github")] #[cfg(feature = "github")]
git_next_config::ForgeType::GitHub => Box::new(github::Github::new(repo_details, net)), ForgeType::GitHub => Box::new(git_next_forge_github::Github::new(repo_details, net)),
git_next_config::ForgeType::MockForge => unreachable!(), ForgeType::MockForge => unreachable!(),
} }
} }
} }

View file

@ -7,18 +7,12 @@ repository = { workspace = true }
description = "git support for git-next, the trunk-based development manager" description = "git support for git-next, the trunk-based development manager"
[dependencies] [dependencies]
git-next-config = { workspace = true } git-next-core = { workspace = true }
# logging # logging
# console-subscriber = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
# tracing-subscriber = { workspace = true }
# # base64 decoding
# base64 = { workspace = true }
#
# git # git
# # gix = { workspace = true }
gix = { workspace = true } gix = { workspace = true }
async-trait = { workspace = true } async-trait = { workspace = true }
@ -27,7 +21,6 @@ kxio = { workspace = true }
# TOML parsing # TOML parsing
serde_json = { workspace = true } serde_json = { workspace = true }
# toml = { workspace = true }
# webhooks - user notification # webhooks - user notification
serde = { workspace = true } serde = { workspace = true }
@ -35,26 +28,13 @@ serde = { workspace = true }
# Secrets and Password # Secrets and Password
secrecy = { workspace = true } secrecy = { workspace = true }
# # Conventional Commit check
# git-conventional = { workspace = true }
#
# # Webhooks
# bytes = { workspace = true }
# ulid = { workspace = true }
# warp = { workspace = true }
# error handling # error handling
derive_more = { workspace = true } derive_more = { workspace = true }
derive-with = { workspace = true } derive-with = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
# # file watcher
# inotify = { workspace = true }
# Actors # Actors
actix = { workspace = true } actix = { workspace = true }
# actix-rt = { workspace = true }
# tokio = { workspace = true }
mockall = { workspace = true } mockall = { workspace = true }

View file

@ -1,7 +1,7 @@
use config::newtype;
use derive_more::Display;
// //
use git_next_config as config; use git_next_core::{newtype, webhook};
use derive_more::Display;
use serde::Serialize; use serde::Serialize;
#[derive( #[derive(
@ -30,8 +30,8 @@ impl Commit {
} }
} }
impl From<config::webhook::Push> for Commit { impl From<webhook::Push> for Commit {
fn from(value: config::webhook::Push) -> Self { fn from(value: webhook::Push) -> Self {
Self::new( Self::new(
Sha::new(value.sha().to_owned()), Sha::new(value.sha().to_owned()),
Message::new(value.message().to_owned()), Message::new(value.message().to_owned()),
@ -50,18 +50,14 @@ pub struct Histories {
} }
pub mod log { pub mod log {
use git_next_core::BranchName;
use git_next_config as config;
pub type Result<T> = core::result::Result<T, Error>; pub type Result<T> = core::result::Result<T, Error>;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {
#[error("branch: {branch}, error: {error}")] #[error("branch: {branch}, error: {error}")]
Gix { Gix { branch: BranchName, error: String },
branch: config::BranchName,
error: String,
},
#[error("lock")] #[error("lock")]
Lock, Lock,

View file

@ -1,6 +1,6 @@
// //
use git_next_config::{ use git_next_core::{
common::{branch_name, repo_alias, repo_path}, common::{branch_name, repo_alias, repo_path},
ForgeDetails, GitDir, RepoConfig, ForgeDetails, GitDir, RepoConfig,
}; };

View file

@ -1,5 +1,9 @@
//
use crate as git; use crate as git;
use git_next_config as config;
use git_next_core::{
server::WebhookUrl, webhook, ForgeNotification, RegisteredWebhook, WebhookAuth, WebhookId,
};
#[mockall::automock] #[mockall::automock]
#[async_trait::async_trait] #[async_trait::async_trait]
@ -8,43 +12,33 @@ pub trait ForgeLike: std::fmt::Debug + Send + Sync {
fn name(&self) -> String; fn name(&self) -> String;
/// Checks that the message has a valid authorisation. /// Checks that the message has a valid authorisation.
fn is_message_authorised( fn is_message_authorised(&self, message: &ForgeNotification, expected: &WebhookAuth) -> bool;
&self,
message: &config::ForgeNotification,
expected: &config::WebhookAuth,
) -> bool;
/// Checks if the message should be ignored. /// Checks if the message should be ignored.
/// ///
/// Default implementation says that no messages should be ignored. /// Default implementation says that no messages should be ignored.
fn should_ignore_message(&self, _message: &config::ForgeNotification) -> bool { fn should_ignore_message(&self, _message: &ForgeNotification) -> bool {
false false
} }
/// Parses the webhook body into Some(Push) struct if appropriate, or None if not. /// Parses the webhook body into Some(Push) struct if appropriate, or None if not.
fn parse_webhook_body( fn parse_webhook_body(
&self, &self,
body: &config::webhook::forge_notification::Body, body: &webhook::forge_notification::Body,
) -> git::forge::webhook::Result<config::webhook::push::Push>; ) -> git::forge::webhook::Result<webhook::push::Push>;
/// Checks the results of any (e.g. CI) status checks for the commit. /// Checks the results of any (e.g. CI) status checks for the commit.
async fn commit_status(&self, commit: &git::Commit) -> git::forge::commit::Status; async fn commit_status(&self, commit: &git::Commit) -> git::forge::commit::Status;
// Lists all the webhooks // Lists all the webhooks
async fn list_webhooks( async fn list_webhooks(&self, url: &WebhookUrl) -> git::forge::webhook::Result<Vec<WebhookId>>;
&self,
url: &config::server::WebhookUrl,
) -> git::forge::webhook::Result<Vec<config::WebhookId>>;
// Unregisters a webhook // Unregisters a webhook
async fn unregister_webhook( async fn unregister_webhook(&self, webhook: &WebhookId) -> git::forge::webhook::Result<()>;
&self,
webhook: &config::WebhookId,
) -> git::forge::webhook::Result<()>;
// Registers a webhook // Registers a webhook
async fn register_webhook( async fn register_webhook(
&self, &self,
webhook_url: &config::server::WebhookUrl, webhook_url: &WebhookUrl,
) -> git::forge::webhook::Result<config::RegisteredWebhook>; ) -> git::forge::webhook::Result<RegisteredWebhook>;
} }

View file

@ -1,5 +1,5 @@
use derive_more::Display; use derive_more::Display;
use git_next_config::newtype; use git_next_core::newtype;
newtype!(Generation: u32, Display, Default, Copy: r#"A counter for the server generation. newtype!(Generation: u32, Display, Default, Copy: r#"A counter for the server generation.

View file

@ -1,9 +1,9 @@
// //
use crate as git; use git_next_core::newtype;
use derive_more::Display;
use git_next_config::newtype;
use crate::Commit; use derive_more::Display;
use crate::{commit::Sha, Commit};
newtype!(GitRef: String, Display: "A git reference to a git commit."); newtype!(GitRef: String, Display: "A git reference to a git commit.");
impl From<Commit> for GitRef { impl From<Commit> for GitRef {
@ -11,8 +11,8 @@ impl From<Commit> for GitRef {
Self(value.sha().to_string()) Self(value.sha().to_string())
} }
} }
impl From<git::commit::Sha> for GitRef { impl From<Sha> for GitRef {
fn from(value: git::commit::Sha) -> Self { fn from(value: Sha) -> Self {
Self(value.to_string()) Self(value.to_string())
} }
} }

View file

@ -1,6 +1,9 @@
use git_next_config::{Hostname, RepoPath}; //
use derive_more::{Constructor, Display};
#[derive(Clone, Debug, PartialEq, Eq, derive_more::Constructor, derive_more::Display)] use git_next_core::{Hostname, RepoPath};
#[derive(Clone, Debug, PartialEq, Eq, Constructor, Display)]
#[display("{}:{}", host, repo_path)] #[display("{}:{}", host, repo_path)]
pub struct GitRemote { pub struct GitRemote {
host: Hostname, host: Hostname,

View file

@ -1,11 +1,12 @@
use super::GitRef; use git_next_core::BranchName;
//
use crate as git; use crate as git;
use git_next_config as config;
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum Force { pub enum Force {
No, No,
From(GitRef), From(git::GitRef),
} }
impl std::fmt::Display for Force { impl std::fmt::Display for Force {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@ -49,7 +50,7 @@ pub enum Error {
pub fn reset( pub fn reset(
open_repository: &dyn git::repository::OpenRepositoryLike, open_repository: &dyn git::repository::OpenRepositoryLike,
repo_details: &git::RepoDetails, repo_details: &git::RepoDetails,
branch_name: &config::BranchName, branch_name: &BranchName,
to_commit: &git::GitRef, to_commit: &git::GitRef,
force: &git::push::Force, force: &git::push::Force,
) -> Result<()> { ) -> Result<()> {

View file

@ -1,15 +1,16 @@
//
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use config::{ use git_next_core::{
BranchName, ForgeAlias, ForgeConfig, ForgeDetails, GitDir, RepoAlias, RepoConfig, RepoPath, pike, BranchName, ForgeAlias, ForgeConfig, ForgeDetails, GitDir, Hostname, RemoteUrl,
ServerRepoConfig, StoragePathType, RepoAlias, RepoConfig, RepoPath, ServerRepoConfig, StoragePathType,
}; };
use git_next_config::{self as config, pike, RemoteUrl};
use secrecy::Secret; use secrecy::Secret;
use crate::repository::{OpenRepositoryLike, RealOpenRepository}; use crate::{
repository::{OpenRepositoryLike, RealOpenRepository},
use super::{Generation, GitRemote}; Generation, GitRemote,
};
/// The derived information about a repo, used to interact with it /// The derived information about a repo, used to interact with it
#[derive(Clone, Debug, derive_more::Display, derive_with::With)] #[derive(Clone, Debug, derive_more::Display, derive_with::With)]
@ -68,7 +69,7 @@ impl RepoDetails {
GitRemote::new(self.forge.hostname().clone(), self.repo_path.clone()) GitRemote::new(self.forge.hostname().clone(), self.repo_path.clone())
} }
pub fn with_hostname(mut self, hostname: config::Hostname) -> Self { pub fn with_hostname(mut self, hostname: Hostname) -> Self {
let forge = self.forge; let forge = self.forge;
self.forge = forge.with_hostname(hostname); self.forge = forge.with_hostname(hostname);
self self
@ -101,7 +102,7 @@ impl RepoDetails {
Ok(repo) Ok(repo)
} }
pub fn remote_url(&self) -> Option<git_next_config::RemoteUrl> { pub fn remote_url(&self) -> Option<RemoteUrl> {
use secrecy::ExposeSecret; use secrecy::ExposeSecret;
RemoteUrl::parse(self.url().expose_secret()) RemoteUrl::parse(self.url().expose_secret())
} }

View file

@ -1,14 +1,14 @@
// //
use super::RepoDetails; pub use crate::repository::open::{
use crate::repository::test::TestRepository; otest::{OnFetch, OnPush},
use crate::validation::remotes::validate_default_remotes; OpenRepository, OpenRepositoryLike, RealOpenRepository,
pub use factory::RepositoryFactory; };
use git_next_config::{GitDir, RemoteUrl}; use crate::{
pub use open::otest::OnFetch; repository::test::TestRepository, validation::remotes::validate_default_remotes, RepoDetails,
pub use open::otest::OnPush; };
pub use open::OpenRepository;
pub use open::OpenRepositoryLike; use git_next_core::{GitDir, RemoteUrl};
pub use open::RealOpenRepository;
use tracing::info; use tracing::info;
pub mod factory; pub mod factory;
@ -85,7 +85,7 @@ pub type Result<T> = core::result::Result<T, Error>;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {
#[error("invalid git dir: {0}")] #[error("invalid git dir: {0}")]
InvalidGitDir(git_next_config::GitDir), InvalidGitDir(GitDir),
#[error("kxiofs: {0}")] #[error("kxiofs: {0}")]
KxioFs(#[from] kxio::fs::Error), KxioFs(#[from] kxio::fs::Error),

View file

@ -5,16 +5,16 @@ mod tests;
pub mod oreal; pub mod oreal;
pub mod otest; pub mod otest;
use git_next_core::{BranchName, GitDir, RemoteUrl};
use std::{ use std::{
path::Path, path::Path,
sync::{Arc, RwLock}, sync::{Arc, RwLock},
}; };
use crate as git; use crate as git;
use git::repository::Direction; pub use git::repository::open::oreal::RealOpenRepository;
use git_next_config::{self as config, RemoteUrl}; use git::repository::{open::otest::TestOpenRepository, Direction};
pub use oreal::RealOpenRepository;
pub use otest::TestOpenRepository;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum OpenRepository { pub enum OpenRepository {
@ -41,7 +41,7 @@ pub fn real(gix_repo: gix::Repository) -> OpenRepository {
#[cfg(not(tarpaulin_include))] // don't test mocks #[cfg(not(tarpaulin_include))] // don't test mocks
pub fn test( pub fn test(
gitdir: &config::GitDir, gitdir: &GitDir,
fs: kxio::fs::FileSystem, fs: kxio::fs::FileSystem,
on_fetch: Vec<otest::OnFetch>, on_fetch: Vec<otest::OnFetch>,
on_push: Vec<otest::OnPush>, on_push: Vec<otest::OnPush>,
@ -52,13 +52,13 @@ pub fn test(
#[mockall::automock] #[mockall::automock]
pub trait OpenRepositoryLike: std::fmt::Debug + Sync { pub trait OpenRepositoryLike: std::fmt::Debug + Sync {
fn duplicate(&self) -> Box<dyn OpenRepositoryLike>; fn duplicate(&self) -> Box<dyn OpenRepositoryLike>;
fn remote_branches(&self) -> git::push::Result<Vec<config::BranchName>>; fn remote_branches(&self) -> git::push::Result<Vec<BranchName>>;
fn find_default_remote(&self, direction: Direction) -> Option<RemoteUrl>; fn find_default_remote(&self, direction: Direction) -> Option<RemoteUrl>;
fn fetch(&self) -> Result<(), git::fetch::Error>; fn fetch(&self) -> Result<(), git::fetch::Error>;
fn push( fn push(
&self, &self,
repo_details: &git::RepoDetails, repo_details: &git::RepoDetails,
branch_name: &config::BranchName, branch_name: &BranchName,
to_commit: &git::GitRef, to_commit: &git::GitRef,
force: &git::push::Force, force: &git::push::Force,
) -> git::push::Result<()>; ) -> git::push::Result<()>;
@ -66,18 +66,14 @@ pub trait OpenRepositoryLike: std::fmt::Debug + Sync {
/// List of commits in a branch, optionally up-to any specified commit. /// List of commits in a branch, optionally up-to any specified commit.
fn commit_log( fn commit_log(
&self, &self,
branch_name: &config::BranchName, branch_name: &BranchName,
find_commits: &[git::Commit], find_commits: &[git::Commit],
) -> git::commit::log::Result<Vec<git::Commit>>; ) -> git::commit::log::Result<Vec<git::Commit>>;
/// Read the contents of a file as a string. /// Read the contents of a file as a string.
/// ///
/// Only handles files in the root of the repo. /// Only handles files in the root of the repo.
fn read_file( fn read_file(&self, branch_name: &BranchName, file_name: &Path) -> git::file::Result<String>;
&self,
branch_name: &config::BranchName,
file_name: &Path,
) -> git::file::Result<String>;
} }
pub fn mock() -> Box<MockOpenRepositoryLike> { pub fn mock() -> Box<MockOpenRepositoryLike> {

View file

@ -1,20 +1,21 @@
// //
use crate::{self as git, repository::OpenRepositoryLike}; use crate::{self as git, repository::OpenRepositoryLike};
use config::BranchName;
use derive_more::Constructor;
use git_next_config::{self as config, RemoteUrl};
use git_next_core::{BranchName, Hostname, RemoteUrl, RepoPath};
use derive_more::Constructor;
use gix::bstr::BStr; use gix::bstr::BStr;
use tracing::{info, warn};
use std::{ use std::{
path::Path, path::Path,
sync::{Arc, RwLock}, sync::{Arc, RwLock},
}; };
use tracing::{info, warn};
#[derive(Clone, Debug, Constructor)] #[derive(Clone, Debug, Constructor)]
pub struct RealOpenRepository(Arc<RwLock<gix::ThreadSafeRepository>>); pub struct RealOpenRepository(Arc<RwLock<gix::ThreadSafeRepository>>);
impl super::OpenRepositoryLike for RealOpenRepository { impl super::OpenRepositoryLike for RealOpenRepository {
fn remote_branches(&self) -> git::push::Result<Vec<config::BranchName>> { fn remote_branches(&self) -> git::push::Result<Vec<BranchName>> {
let refs = self let refs = self
.0 .0
.read() .read()
@ -87,7 +88,7 @@ impl super::OpenRepositoryLike for RealOpenRepository {
fn push( fn push(
&self, &self,
repo_details: &git::RepoDetails, repo_details: &git::RepoDetails,
branch_name: &config::BranchName, branch_name: &BranchName,
to_commit: &git::GitRef, to_commit: &git::GitRef,
force: &git::push::Force, force: &git::push::Force,
) -> Result<(), git::push::Error> { ) -> Result<(), git::push::Error> {
@ -126,7 +127,7 @@ impl super::OpenRepositoryLike for RealOpenRepository {
fn commit_log( fn commit_log(
&self, &self,
branch_name: &config::BranchName, branch_name: &BranchName,
find_commits: &[git::Commit], find_commits: &[git::Commit],
) -> Result<Vec<crate::Commit>, git::commit::log::Error> { ) -> Result<Vec<crate::Commit>, git::commit::log::Error> {
let limit = match find_commits.is_empty() { let limit = match find_commits.is_empty() {
@ -187,11 +188,7 @@ impl super::OpenRepositoryLike for RealOpenRepository {
} }
#[tracing::instrument(skip_all, fields(%branch_name, ?file_name))] #[tracing::instrument(skip_all, fields(%branch_name, ?file_name))]
fn read_file( fn read_file(&self, branch_name: &BranchName, file_name: &Path) -> git::file::Result<String> {
&self,
branch_name: &config::BranchName,
file_name: &Path,
) -> git::file::Result<String> {
self.0 self.0
.read() .read()
.map_err(|_| git::file::Error::Lock) .map_err(|_| git::file::Error::Lock)
@ -229,9 +226,6 @@ impl From<&RemoteUrl> for git::GitRemote {
let path = url.path.to_string(); let path = url.path.to_string();
let path = path.strip_prefix('/').map_or(path.as_str(), |path| path); let path = path.strip_prefix('/').map_or(path.as_str(), |path| path);
let path = path.strip_suffix(".git").map_or(path, |path| path); let path = path.strip_suffix(".git").map_or(path, |path| path);
Self::new( Self::new(Hostname::new(host), RepoPath::new(path.to_string()))
config::Hostname::new(host),
config::RepoPath::new(path.to_string()),
)
} }
} }

View file

@ -1,19 +1,22 @@
// //
use crate::{self as git, repository::OpenRepositoryLike}; use crate as git;
use git_next_core::{BranchName, GitDir, RemoteUrl, RepoBranches};
use derive_more::{Constructor, Deref}; use derive_more::{Constructor, Deref};
use git_next_config::{self as config, RemoteUrl};
use std::{ use std::{
path::Path, path::Path,
sync::{Arc, RwLock}, sync::{Arc, RwLock},
}; };
pub type OnFetchFn = use super::OpenRepositoryLike;
fn(&config::RepoBranches, &config::GitDir, &kxio::fs::FileSystem) -> git::fetch::Result<()>;
pub type OnFetchFn = fn(&RepoBranches, &GitDir, &kxio::fs::FileSystem) -> git::fetch::Result<()>;
#[derive(Clone, Debug, Constructor)] #[derive(Clone, Debug, Constructor)]
pub struct OnFetch { pub struct OnFetch {
repo_branches: config::RepoBranches, repo_branches: RepoBranches,
gitdir: config::GitDir, gitdir: GitDir,
fs: kxio::fs::FileSystem, fs: kxio::fs::FileSystem,
action: OnFetchFn, action: OnFetchFn,
} }
@ -25,17 +28,17 @@ impl OnFetch {
pub type OnPushFn = fn( pub type OnPushFn = fn(
&git::RepoDetails, &git::RepoDetails,
&config::BranchName, &BranchName,
&git::GitRef, &git::GitRef,
&git::push::Force, &git::push::Force,
&config::RepoBranches, &RepoBranches,
&config::GitDir, &GitDir,
&kxio::fs::FileSystem, &kxio::fs::FileSystem,
) -> git::push::Result<()>; ) -> git::push::Result<()>;
#[derive(Clone, Debug, Constructor)] #[derive(Clone, Debug, Constructor)]
pub struct OnPush { pub struct OnPush {
repo_branches: config::RepoBranches, repo_branches: RepoBranches,
gitdir: config::GitDir, gitdir: GitDir,
fs: kxio::fs::FileSystem, fs: kxio::fs::FileSystem,
action: OnPushFn, action: OnPushFn,
} }
@ -43,7 +46,7 @@ impl OnPush {
pub fn invoke( pub fn invoke(
&self, &self,
repo_details: &git::RepoDetails, repo_details: &git::RepoDetails,
branch_name: &config::BranchName, branch_name: &BranchName,
to_commit: &git::GitRef, to_commit: &git::GitRef,
force: &git::push::Force, force: &git::push::Force,
) -> git::push::Result<()> { ) -> git::push::Result<()> {
@ -69,7 +72,7 @@ pub struct TestOpenRepository {
} }
#[cfg(not(tarpaulin_include))] #[cfg(not(tarpaulin_include))]
impl git::repository::OpenRepositoryLike for TestOpenRepository { impl git::repository::OpenRepositoryLike for TestOpenRepository {
fn remote_branches(&self) -> git::push::Result<Vec<config::BranchName>> { fn remote_branches(&self) -> git::push::Result<Vec<BranchName>> {
self.real.remote_branches() self.real.remote_branches()
} }
@ -96,7 +99,7 @@ impl git::repository::OpenRepositoryLike for TestOpenRepository {
fn push( fn push(
&self, &self,
repo_details: &git::RepoDetails, repo_details: &git::RepoDetails,
branch_name: &config::BranchName, branch_name: &BranchName,
to_commit: &git::GitRef, to_commit: &git::GitRef,
force: &git::push::Force, force: &git::push::Force,
) -> git::push::Result<()> { ) -> git::push::Result<()> {
@ -120,17 +123,13 @@ impl git::repository::OpenRepositoryLike for TestOpenRepository {
fn commit_log( fn commit_log(
&self, &self,
branch_name: &config::BranchName, branch_name: &BranchName,
find_commits: &[git::Commit], find_commits: &[git::Commit],
) -> git::commit::log::Result<Vec<crate::Commit>> { ) -> git::commit::log::Result<Vec<crate::Commit>> {
self.real.commit_log(branch_name, find_commits) self.real.commit_log(branch_name, find_commits)
} }
fn read_file( fn read_file(&self, branch_name: &BranchName, file_name: &Path) -> git::file::Result<String> {
&self,
branch_name: &config::BranchName,
file_name: &Path,
) -> git::file::Result<String> {
self.real.read_file(branch_name, file_name) self.real.read_file(branch_name, file_name)
} }
@ -140,7 +139,7 @@ impl git::repository::OpenRepositoryLike for TestOpenRepository {
} }
impl TestOpenRepository { impl TestOpenRepository {
pub fn new( pub fn new(
gitdir: &config::GitDir, gitdir: &GitDir,
fs: kxio::fs::FileSystem, fs: kxio::fs::FileSystem,
on_fetch: Vec<OnFetch>, on_fetch: Vec<OnFetch>,
on_push: Vec<OnPush>, on_push: Vec<OnPush>,
@ -158,7 +157,7 @@ impl TestOpenRepository {
} }
} }
fn write_origin(gitdir: &config::GitDir, fs: &kxio::fs::FileSystem) { fn write_origin(gitdir: &GitDir, fs: &kxio::fs::FileSystem) {
let config_file = fs.base().join(gitdir.to_path_buf()).join(".git/config"); let config_file = fs.base().join(gitdir.to_path_buf()).join(".git/config");
#[allow(clippy::expect_used)] #[allow(clippy::expect_used)]
let contents = fs let contents = fs

View file

@ -1,9 +1,9 @@
// //
use derive_more::Constructor; use derive_more::Constructor;
use git_next_core::GitDir;
use crate as git; use crate as git;
use git::repository::RepositoryLike; use git::repository::RepositoryLike;
use git_next_config as config;
#[derive(Clone, Debug, Constructor)] #[derive(Clone, Debug, Constructor)]
pub struct TestRepository { pub struct TestRepository {
@ -21,7 +21,7 @@ impl TestRepository {
} }
} }
impl RepositoryLike for TestRepository { impl RepositoryLike for TestRepository {
fn open(&self, gitdir: &config::GitDir) -> super::Result<crate::OpenRepository> { fn open(&self, gitdir: &GitDir) -> super::Result<crate::OpenRepository> {
Ok(git::repository::open::test( Ok(git::repository::open::test(
gitdir, gitdir,
self.fs.clone(), self.fs.clone(),

View file

@ -1,5 +1,7 @@
//
use crate::Commit; use crate::Commit;
use git_next_config::{BranchName, ForgeAlias, RepoAlias};
use git_next_core::{BranchName, ForgeAlias, RepoAlias};
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum UserNotification { pub enum UserNotification {

View file

@ -1,6 +1,7 @@
// //
use crate::{self as git, UserNotification}; use crate::{self as git, UserNotification};
use git_next_config as config;
use git_next_core::{BranchName, RepoConfig};
pub type Result<T> = core::result::Result<T, Error>; pub type Result<T> = core::result::Result<T, Error>;
@ -15,7 +16,7 @@ pub struct Positions {
pub fn validate_positions( pub fn validate_positions(
open_repository: &dyn git::repository::OpenRepositoryLike, open_repository: &dyn git::repository::OpenRepositoryLike,
repo_details: &git::RepoDetails, repo_details: &git::RepoDetails,
repo_config: config::RepoConfig, repo_config: RepoConfig,
) -> Result<Positions> { ) -> Result<Positions> {
let main_branch = repo_config.branches().main(); let main_branch = repo_config.branches().main();
let next_branch = repo_config.branches().next(); let next_branch = repo_config.branches().next();
@ -81,7 +82,7 @@ fn reset_next_to_main(
repo_details: &crate::RepoDetails, repo_details: &crate::RepoDetails,
main: &crate::Commit, main: &crate::Commit,
next: &crate::Commit, next: &crate::Commit,
next_branch: &config::BranchName, next_branch: &BranchName,
) -> Result<Positions> { ) -> Result<Positions> {
git::push::reset( git::push::reset(
open_repository, open_repository,
@ -106,7 +107,7 @@ fn is_not_based_on(commits: &[crate::commit::Commit], needle: &crate::Commit) ->
fn get_commit_histories( fn get_commit_histories(
open_repository: &dyn git::repository::OpenRepositoryLike, open_repository: &dyn git::repository::OpenRepositoryLike,
repo_config: &config::RepoConfig, repo_config: &RepoConfig,
) -> git::commit::log::Result<git::commit::Histories> { ) -> git::commit::log::Result<git::commit::Histories> {
let main = (open_repository.commit_log(&repo_config.branches().main(), &[]))?; let main = (open_repository.commit_log(&repo_config.branches().main(), &[]))?;
let main_head = [main[0].clone()]; let main_head = [main[0].clone()];

View file

@ -1,8 +1,10 @@
use git_next_config::RemoteUrl; //
use tracing::info;
use crate as git; use crate as git;
use git_next_core::RemoteUrl;
use tracing::info;
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub fn validate_default_remotes( pub fn validate_default_remotes(
open_repository: &dyn git::repository::OpenRepositoryLike, open_repository: &dyn git::repository::OpenRepositoryLike,