refactor(server,config,git): extract modules config and git from server
This commit is contained in:
parent
740419ffb8
commit
4d352f005d
54 changed files with 1085 additions and 918 deletions
|
@ -1,6 +1,6 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = ["crates/cli", "crates/server"]
|
members = ["crates/cli", "crates/server", "crates/config", "crates/git"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
@ -14,6 +14,8 @@ expect_used = "warn"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
git-next-server = { path = "crates/server" }
|
git-next-server = { path = "crates/server" }
|
||||||
|
git-next-config = { path = "crates/config" }
|
||||||
|
git-next-git = { path = "crates/git" }
|
||||||
|
|
||||||
# CLI parsing
|
# CLI parsing
|
||||||
clap = { version = "4.5", features = ["cargo", "derive"] }
|
clap = { version = "4.5", features = ["cargo", "derive"] }
|
||||||
|
|
70
crates/config/Cargo.toml
Normal file
70
crates/config/Cargo.toml
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
[package]
|
||||||
|
name = "git-next-config"
|
||||||
|
version = { workspace = true }
|
||||||
|
edition = { workspace = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["forgejo"]
|
||||||
|
forgejo = []
|
||||||
|
github = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# # logging
|
||||||
|
# console-subscriber = { workspace = true }
|
||||||
|
# tracing = { workspace = true }
|
||||||
|
# tracing-subscriber = { workspace = true }
|
||||||
|
|
||||||
|
# # base64 decoding
|
||||||
|
# base64 = { workspace = true }
|
||||||
|
#
|
||||||
|
# # git
|
||||||
|
# # gix = { workspace = true }
|
||||||
|
# gix = { workspace = true }
|
||||||
|
# async-trait = { workspace = true }
|
||||||
|
#
|
||||||
|
# # fs/network
|
||||||
|
# kxio = { workspace = true }
|
||||||
|
#
|
||||||
|
# # fs
|
||||||
|
# tempfile = { 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 }
|
||||||
|
#
|
||||||
|
# # error handling
|
||||||
|
# derive_more = { workspace = true }
|
||||||
|
# terrors = { workspace = true }
|
||||||
|
#
|
||||||
|
# # file watcher
|
||||||
|
# inotify = { workspace = true }
|
||||||
|
#
|
||||||
|
# # Actors
|
||||||
|
# actix = { workspace = true }
|
||||||
|
# actix-rt = { workspace = true }
|
||||||
|
# tokio = { workspace = true }
|
||||||
|
#
|
||||||
|
# [dev-dependencies]
|
||||||
|
# # Testing
|
||||||
|
# assert2 = { workspace = true }
|
||||||
|
# pretty_assertions = { workspace = true }
|
||||||
|
# test-log = { workspace = true }
|
||||||
|
# anyhow = { workspace = true }
|
||||||
|
|
||||||
|
[lints.clippy]
|
||||||
|
nursery = { level = "warn", priority = -1 }
|
||||||
|
# pedantic = "warn"
|
||||||
|
unwrap_used = "warn"
|
||||||
|
expect_used = "warn"
|
16
crates/config/src/api_token.rs
Normal file
16
crates/config/src/api_token.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
/// The API Token for the [user]
|
||||||
|
/// ForgeJo: https://{hostname}/user/settings/applications
|
||||||
|
/// Github: https://github.com/settings/tokens
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ApiToken(pub secrecy::Secret<String>);
|
||||||
|
impl From<String> for ApiToken {
|
||||||
|
fn from(value: String) -> Self {
|
||||||
|
Self(value.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// The API Token is in effect a password, so it must be explicitly exposed to access its value
|
||||||
|
impl secrecy::ExposeSecret<String> for ApiToken {
|
||||||
|
fn expose_secret(&self) -> &String {
|
||||||
|
self.0.expose_secret()
|
||||||
|
}
|
||||||
|
}
|
17
crates/config/src/branch_name.rs
Normal file
17
crates/config/src/branch_name.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
use std::fmt::Formatter;
|
||||||
|
|
||||||
|
/// The name of a Branch
|
||||||
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
|
pub struct BranchName(pub String);
|
||||||
|
impl std::fmt::Display for BranchName {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::ops::Deref for BranchName {
|
||||||
|
type Target = String;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
51
crates/config/src/forge_config.rs
Normal file
51
crates/config/src/forge_config.rs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::{ApiToken, ForgeType, Hostname, RepoAlias, ServerRepoConfig, User};
|
||||||
|
|
||||||
|
/// Defines a Forge to connect to
|
||||||
|
/// Maps from `git-next-server.toml` at `forge.{forge}`
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
||||||
|
pub struct ForgeConfig {
|
||||||
|
pub forge_type: ForgeType,
|
||||||
|
pub hostname: String,
|
||||||
|
pub user: String,
|
||||||
|
pub token: String,
|
||||||
|
pub repos: HashMap<String, ServerRepoConfig>,
|
||||||
|
}
|
||||||
|
impl ForgeConfig {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub const fn forge_type(&self) -> &ForgeType {
|
||||||
|
&self.forge_type
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hostname(&self) -> Hostname {
|
||||||
|
Hostname(self.hostname.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn user(&self) -> User {
|
||||||
|
User(self.user.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn token(&self) -> ApiToken {
|
||||||
|
ApiToken(self.token.clone().into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn repos(&self) -> impl Iterator<Item = (RepoAlias, &ServerRepoConfig)> {
|
||||||
|
self.repos
|
||||||
|
.iter()
|
||||||
|
.map(|(name, repo)| (RepoAlias(name.clone()), repo))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for ForgeConfig {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}:{}@{}",
|
||||||
|
self.forge_type.to_string().to_lowercase(),
|
||||||
|
self.user,
|
||||||
|
self.hostname
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
24
crates/config/src/forge_details.rs
Normal file
24
crates/config/src/forge_details.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
use crate::{ApiToken, ForgeConfig, ForgeName, ForgeType, Hostname, User};
|
||||||
|
|
||||||
|
/// The derived information about a Forge, used to create interactions with it
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ForgeDetails {
|
||||||
|
pub forge_name: ForgeName,
|
||||||
|
pub forge_type: ForgeType,
|
||||||
|
pub hostname: Hostname,
|
||||||
|
pub user: User,
|
||||||
|
pub token: ApiToken,
|
||||||
|
// API Token
|
||||||
|
// Private SSH Key Path
|
||||||
|
}
|
||||||
|
impl From<(&ForgeName, &ForgeConfig)> for ForgeDetails {
|
||||||
|
fn from(forge: (&ForgeName, &ForgeConfig)) -> Self {
|
||||||
|
Self {
|
||||||
|
forge_name: forge.0.clone(),
|
||||||
|
forge_type: forge.1.forge_type.clone(),
|
||||||
|
hostname: forge.1.hostname(),
|
||||||
|
user: forge.1.user(),
|
||||||
|
token: forge.1.token(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
crates/config/src/forge_name.rs
Normal file
15
crates/config/src/forge_name.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
/// The name of a Forge to connect to
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct ForgeName(pub String);
|
||||||
|
impl std::fmt::Display for ForgeName {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<&ForgeName> for PathBuf {
|
||||||
|
fn from(value: &ForgeName) -> Self {
|
||||||
|
Self::from(&value.0)
|
||||||
|
}
|
||||||
|
}
|
16
crates/config/src/forge_type.rs
Normal file
16
crates/config/src/forge_type.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
/// Identifier for the type of Forge
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)]
|
||||||
|
pub enum ForgeType {
|
||||||
|
#[cfg(feature = "forgejo")]
|
||||||
|
ForgeJo,
|
||||||
|
// Gitea,
|
||||||
|
// GitHub,
|
||||||
|
// GitLab,
|
||||||
|
// BitBucket,
|
||||||
|
MockForge,
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for ForgeType {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", format!("{:?}", self).to_lowercase())
|
||||||
|
}
|
||||||
|
}
|
45
crates/config/src/git_dir.rs
Normal file
45
crates/config/src/git_dir.rs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
use std::{ops::Deref, path::PathBuf};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
|
||||||
|
pub struct GitDir(PathBuf);
|
||||||
|
impl GitDir {
|
||||||
|
pub fn new(pathbuf: &std::path::Path) -> Self {
|
||||||
|
Self(pathbuf.to_path_buf())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn pathbuf(&self) -> &PathBuf {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Deref for GitDir {
|
||||||
|
type Target = PathBuf;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for GitDir {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", &self.0.display())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<&str> for GitDir {
|
||||||
|
fn from(value: &str) -> Self {
|
||||||
|
Self(value.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<&GitDir> for PathBuf {
|
||||||
|
fn from(value: &GitDir) -> Self {
|
||||||
|
value.to_path_buf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<PathBuf> for GitDir {
|
||||||
|
fn from(value: PathBuf) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<GitDir> for PathBuf {
|
||||||
|
fn from(value: GitDir) -> Self {
|
||||||
|
value.0
|
||||||
|
}
|
||||||
|
}
|
10
crates/config/src/host_name.rs
Normal file
10
crates/config/src/host_name.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
|
/// The hostname of a forge
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct Hostname(pub String);
|
||||||
|
impl Display for Hostname {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
32
crates/config/src/lib.rs
Normal file
32
crates/config/src/lib.rs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
//
|
||||||
|
mod api_token;
|
||||||
|
mod branch_name;
|
||||||
|
mod forge_config;
|
||||||
|
mod forge_details;
|
||||||
|
mod forge_name;
|
||||||
|
mod forge_type;
|
||||||
|
pub mod git_dir;
|
||||||
|
mod host_name;
|
||||||
|
mod repo_alias;
|
||||||
|
mod repo_branches;
|
||||||
|
mod repo_config;
|
||||||
|
mod repo_config_source;
|
||||||
|
mod repo_path;
|
||||||
|
mod server_repo_config;
|
||||||
|
mod user;
|
||||||
|
|
||||||
|
pub use api_token::ApiToken;
|
||||||
|
pub use branch_name::BranchName;
|
||||||
|
pub use forge_config::ForgeConfig;
|
||||||
|
pub use forge_details::ForgeDetails;
|
||||||
|
pub use forge_name::ForgeName;
|
||||||
|
pub use forge_type::ForgeType;
|
||||||
|
pub use git_dir::GitDir;
|
||||||
|
pub use host_name::Hostname;
|
||||||
|
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;
|
9
crates/config/src/repo_alias.rs
Normal file
9
crates/config/src/repo_alias.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
/// The alias of a repo
|
||||||
|
/// This is the alias for the repo within `git-next-server.toml`
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct RepoAlias(pub String);
|
||||||
|
impl std::fmt::Display for RepoAlias {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
34
crates/config/src/repo_branches.rs
Normal file
34
crates/config/src/repo_branches.rs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
use crate::BranchName;
|
||||||
|
|
||||||
|
/// Mapped from `.git-next.toml` file at `branches`
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)]
|
||||||
|
pub struct RepoBranches {
|
||||||
|
pub main: String,
|
||||||
|
pub next: String,
|
||||||
|
pub dev: String,
|
||||||
|
}
|
||||||
|
impl RepoBranches {
|
||||||
|
pub fn new(main: impl Into<String>, next: impl Into<String>, dev: impl Into<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
main: main.into(),
|
||||||
|
next: next.into(),
|
||||||
|
dev: dev.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn main(&self) -> BranchName {
|
||||||
|
BranchName(self.main.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&self) -> BranchName {
|
||||||
|
BranchName(self.next.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dev(&self) -> BranchName {
|
||||||
|
BranchName(self.dev.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for RepoBranches {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{},{},{}", self.main, self.next, self.dev)
|
||||||
|
}
|
||||||
|
}
|
33
crates/config/src/repo_config.rs
Normal file
33
crates/config/src/repo_config.rs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
use crate::RepoBranches;
|
||||||
|
use crate::RepoConfigSource;
|
||||||
|
|
||||||
|
/// Mapped from `.git-next.toml` file in target repo
|
||||||
|
/// Is also derived from the optional parameters in `git-next-server.toml` at
|
||||||
|
/// `forge.{forge}.repos.{repo}.(main|next|dev)`
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)]
|
||||||
|
pub struct RepoConfig {
|
||||||
|
pub branches: RepoBranches,
|
||||||
|
pub source: RepoConfigSource,
|
||||||
|
}
|
||||||
|
impl RepoConfig {
|
||||||
|
pub const fn new(branches: RepoBranches, source: RepoConfigSource) -> Self {
|
||||||
|
Self { branches, source }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load(toml: &str) -> Result<Self, toml::de::Error> {
|
||||||
|
toml::from_str(format!("source = \"Repo\"\n{}", toml).as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn branches(&self) -> &RepoBranches {
|
||||||
|
&self.branches
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn source(&self) -> RepoConfigSource {
|
||||||
|
self.source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for RepoConfig {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.branches)
|
||||||
|
}
|
||||||
|
}
|
5
crates/config/src/repo_config_source.rs
Normal file
5
crates/config/src/repo_config_source.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, serde::Deserialize)]
|
||||||
|
pub enum RepoConfigSource {
|
||||||
|
Repo,
|
||||||
|
Server,
|
||||||
|
}
|
12
crates/config/src/repo_path.rs
Normal file
12
crates/config/src/repo_path.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
|
/// The path for the repo within the forge.
|
||||||
|
/// Typically this is composed of the user or organisation and the name of the repo
|
||||||
|
/// e.g. `{user}/{repo}`
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct RepoPath(pub String);
|
||||||
|
impl Display for RepoPath {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
55
crates/config/src/server_repo_config.rs
Normal file
55
crates/config/src/server_repo_config.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::{BranchName, GitDir, RepoBranches, RepoConfig, RepoConfigSource, RepoPath};
|
||||||
|
|
||||||
|
/// Defines a Repo within a ForgeConfig to be monitored by the server
|
||||||
|
/// Maps from `git-next-server.toml` at `forge.{forge}.repos.{name}`
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)]
|
||||||
|
pub struct ServerRepoConfig {
|
||||||
|
pub repo: String,
|
||||||
|
pub branch: String,
|
||||||
|
pub gitdir: Option<PathBuf>,
|
||||||
|
pub main: Option<String>,
|
||||||
|
pub next: Option<String>,
|
||||||
|
pub dev: Option<String>,
|
||||||
|
}
|
||||||
|
impl ServerRepoConfig {
|
||||||
|
pub fn repo(&self) -> RepoPath {
|
||||||
|
RepoPath(self.repo.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn branch(&self) -> BranchName {
|
||||||
|
BranchName(self.branch.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gitdir(&self) -> Option<GitDir> {
|
||||||
|
self.gitdir.clone().map(GitDir::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a RepoConfig from the server configuration if ALL THREE branches were provided
|
||||||
|
pub fn repo_config(&self) -> Option<RepoConfig> {
|
||||||
|
match (&self.main, &self.next, &self.dev) {
|
||||||
|
(Some(main), Some(next), Some(dev)) => Some(RepoConfig {
|
||||||
|
branches: RepoBranches {
|
||||||
|
main: main.to_string(),
|
||||||
|
next: next.to_string(),
|
||||||
|
dev: dev.to_string(),
|
||||||
|
},
|
||||||
|
source: RepoConfigSource::Server,
|
||||||
|
}),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(test)]
|
||||||
|
impl AsRef<Self> for ServerRepoConfig {
|
||||||
|
fn as_ref(&self) -> &Self {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for ServerRepoConfig {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}@{}", self.repo, self.branch)
|
||||||
|
}
|
||||||
|
}
|
8
crates/config/src/user.rs
Normal file
8
crates/config/src/user.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
/// The user within the forge to connect as
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct User(pub String);
|
||||||
|
impl std::fmt::Display for User {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
72
crates/git/Cargo.toml
Normal file
72
crates/git/Cargo.toml
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
[package]
|
||||||
|
name = "git-next-git"
|
||||||
|
version = { workspace = true }
|
||||||
|
edition = { workspace = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["forgejo"]
|
||||||
|
forgejo = []
|
||||||
|
github = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
git-next-config = { workspace = true }
|
||||||
|
|
||||||
|
# logging
|
||||||
|
# console-subscriber = { workspace = true }
|
||||||
|
tracing = { workspace = true }
|
||||||
|
# tracing-subscriber = { workspace = true }
|
||||||
|
|
||||||
|
# # base64 decoding
|
||||||
|
# base64 = { workspace = true }
|
||||||
|
#
|
||||||
|
# git
|
||||||
|
# # gix = { workspace = true }
|
||||||
|
gix = { workspace = true }
|
||||||
|
# async-trait = { workspace = true }
|
||||||
|
#
|
||||||
|
# # fs/network
|
||||||
|
# kxio = { workspace = true }
|
||||||
|
#
|
||||||
|
# # fs
|
||||||
|
# tempfile = { 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 }
|
||||||
|
|
||||||
|
# error handling
|
||||||
|
derive_more = { workspace = true }
|
||||||
|
# terrors = { workspace = true }
|
||||||
|
|
||||||
|
# # file watcher
|
||||||
|
# inotify = { workspace = true }
|
||||||
|
#
|
||||||
|
# # Actors
|
||||||
|
# actix = { workspace = true }
|
||||||
|
# actix-rt = { workspace = true }
|
||||||
|
# tokio = { workspace = true }
|
||||||
|
#
|
||||||
|
# [dev-dependencies]
|
||||||
|
# # Testing
|
||||||
|
# assert2 = { workspace = true }
|
||||||
|
# pretty_assertions = { workspace = true }
|
||||||
|
# test-log = { workspace = true }
|
||||||
|
# anyhow = { workspace = true }
|
||||||
|
|
||||||
|
[lints.clippy]
|
||||||
|
nursery = { level = "warn", priority = -1 }
|
||||||
|
# pedantic = "warn"
|
||||||
|
unwrap_used = "warn"
|
||||||
|
expect_used = "warn"
|
50
crates/git/src/commit.rs
Normal file
50
crates/git/src/commit.rs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct Commit {
|
||||||
|
sha: Sha,
|
||||||
|
message: Message,
|
||||||
|
}
|
||||||
|
impl Commit {
|
||||||
|
pub fn new(sha: &str, message: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
sha: Sha::new(sha.to_string()),
|
||||||
|
message: Message::new(message.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub const fn sha(&self) -> &Sha {
|
||||||
|
&self.sha
|
||||||
|
}
|
||||||
|
pub const fn message(&self) -> &Message {
|
||||||
|
&self.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for Commit {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.sha)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct Sha(String);
|
||||||
|
impl Sha {
|
||||||
|
pub const fn new(value: String) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for Sha {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct Message(String);
|
||||||
|
impl Message {
|
||||||
|
pub const fn new(value: String) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for Message {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ use std::ops::Deref;
|
||||||
|
|
||||||
use tracing::{debug, info};
|
use tracing::{debug, info};
|
||||||
|
|
||||||
use crate::{config::RepoDetails, gitforge::Repository};
|
use super::{RepoDetails, Repository};
|
||||||
|
|
||||||
#[derive(Debug, derive_more::From, derive_more::Display)]
|
#[derive(Debug, derive_more::From, derive_more::Display)]
|
||||||
pub enum Error {
|
pub enum Error {
|
15
crates/git/src/generation.rs
Normal file
15
crates/git/src/generation.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct Generation(u32);
|
||||||
|
impl Generation {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
pub fn inc(&mut self) {
|
||||||
|
self.0 += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for Generation {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
21
crates/git/src/git_ref.rs
Normal file
21
crates/git/src/git_ref.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
use git_next_config::BranchName;
|
||||||
|
|
||||||
|
use crate::Commit;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
|
pub struct GitRef(pub String);
|
||||||
|
impl From<Commit> for GitRef {
|
||||||
|
fn from(value: Commit) -> Self {
|
||||||
|
Self(value.sha().to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<BranchName> for GitRef {
|
||||||
|
fn from(value: BranchName) -> Self {
|
||||||
|
Self(value.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for GitRef {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
23
crates/git/src/git_remote.rs
Normal file
23
crates/git/src/git_remote.rs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
use git_next_config::{Hostname, RepoPath};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct GitRemote {
|
||||||
|
host: Hostname,
|
||||||
|
repo_path: RepoPath,
|
||||||
|
}
|
||||||
|
impl GitRemote {
|
||||||
|
pub const fn new(host: Hostname, repo_path: RepoPath) -> Self {
|
||||||
|
Self { host, repo_path }
|
||||||
|
}
|
||||||
|
pub const fn host(&self) -> &Hostname {
|
||||||
|
&self.host
|
||||||
|
}
|
||||||
|
pub const fn repo_path(&self) -> &RepoPath {
|
||||||
|
&self.repo_path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for GitRemote {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}:{}", self.host, self.repo_path)
|
||||||
|
}
|
||||||
|
}
|
20
crates/git/src/lib.rs
Normal file
20
crates/git/src/lib.rs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
//
|
||||||
|
pub mod commit;
|
||||||
|
pub mod fetch;
|
||||||
|
mod generation;
|
||||||
|
mod git_ref;
|
||||||
|
mod git_remote;
|
||||||
|
mod repo_details;
|
||||||
|
pub mod repository;
|
||||||
|
pub mod reset;
|
||||||
|
pub mod validate;
|
||||||
|
|
||||||
|
pub use commit::Commit;
|
||||||
|
pub use fetch::fetch;
|
||||||
|
pub use generation::Generation;
|
||||||
|
pub use git_ref::GitRef;
|
||||||
|
pub use git_remote::GitRemote;
|
||||||
|
pub use repo_details::RepoDetails;
|
||||||
|
pub use repository::Repository;
|
||||||
|
pub use reset::reset;
|
||||||
|
pub use validate::validate;
|
75
crates/git/src/repo_details.rs
Normal file
75
crates/git/src/repo_details.rs
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
use git_next_config::{
|
||||||
|
BranchName, ForgeConfig, ForgeDetails, ForgeName, GitDir, RepoAlias, RepoConfig, RepoPath,
|
||||||
|
ServerRepoConfig,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{Generation, GitRemote};
|
||||||
|
|
||||||
|
/// The derived information about a repo, used to interact with it
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct RepoDetails {
|
||||||
|
pub generation: Generation,
|
||||||
|
pub repo_alias: RepoAlias,
|
||||||
|
pub repo_path: RepoPath,
|
||||||
|
pub branch: BranchName,
|
||||||
|
pub forge: ForgeDetails,
|
||||||
|
pub repo_config: Option<RepoConfig>,
|
||||||
|
pub gitdir: GitDir,
|
||||||
|
}
|
||||||
|
impl RepoDetails {
|
||||||
|
pub fn new(
|
||||||
|
generation: Generation,
|
||||||
|
repo_alias: &RepoAlias,
|
||||||
|
server_repo_config: &ServerRepoConfig,
|
||||||
|
forge_name: &ForgeName,
|
||||||
|
forge_config: &ForgeConfig,
|
||||||
|
gitdir: GitDir,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
generation,
|
||||||
|
repo_alias: repo_alias.clone(),
|
||||||
|
repo_path: RepoPath(server_repo_config.repo.clone()),
|
||||||
|
repo_config: server_repo_config.repo_config(),
|
||||||
|
branch: BranchName(server_repo_config.branch.clone()),
|
||||||
|
gitdir,
|
||||||
|
forge: ForgeDetails {
|
||||||
|
forge_name: forge_name.clone(),
|
||||||
|
forge_type: forge_config.forge_type.clone(),
|
||||||
|
hostname: forge_config.hostname(),
|
||||||
|
user: forge_config.user(),
|
||||||
|
token: forge_config.token(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn origin(&self) -> secrecy::Secret<String> {
|
||||||
|
let repo_details = self;
|
||||||
|
let user = &repo_details.forge.user;
|
||||||
|
let hostname = &repo_details.forge.hostname;
|
||||||
|
let repo_path = &repo_details.repo_path;
|
||||||
|
use secrecy::ExposeSecret;
|
||||||
|
let expose_secret = &repo_details.forge.token;
|
||||||
|
let token = expose_secret.expose_secret();
|
||||||
|
let origin = format!("https://{user}:{token}@{hostname}/{repo_path}.git");
|
||||||
|
origin.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn git_remote(&self) -> GitRemote {
|
||||||
|
GitRemote::new(self.forge.hostname.clone(), self.repo_path.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for RepoDetails {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"gen-{}:{}:{}/{}:{}@{}/{}@{}",
|
||||||
|
self.generation,
|
||||||
|
self.forge.forge_type,
|
||||||
|
self.forge.forge_name,
|
||||||
|
self.repo_alias,
|
||||||
|
self.forge.user,
|
||||||
|
self.forge.hostname,
|
||||||
|
self.repo_path,
|
||||||
|
self.branch,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
70
crates/git/src/repository.rs
Normal file
70
crates/git/src/repository.rs
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
//
|
||||||
|
use std::{ops::Deref as _, path::PathBuf, sync::atomic::AtomicBool};
|
||||||
|
|
||||||
|
use super::RepoDetails;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, derive_more::From)]
|
||||||
|
pub struct Repository(gix::ThreadSafeRepository);
|
||||||
|
impl Repository {
|
||||||
|
pub fn open(gitdir: impl Into<PathBuf>) -> Result<Self, Error> {
|
||||||
|
Ok(Self(gix::ThreadSafeRepository::open(gitdir.into())?))
|
||||||
|
}
|
||||||
|
pub fn clone(repo_details: &RepoDetails) -> Result<Self, Error> {
|
||||||
|
use secrecy::ExposeSecret;
|
||||||
|
let origin = repo_details.origin();
|
||||||
|
let (repository, _outcome) =
|
||||||
|
gix::prepare_clone_bare(origin.expose_secret().as_str(), repo_details.gitdir.deref())?
|
||||||
|
.fetch_only(gix::progress::Discard, &AtomicBool::new(false))?;
|
||||||
|
|
||||||
|
Ok(repository.into_sync().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::ops::Deref for Repository {
|
||||||
|
type Target = gix::ThreadSafeRepository;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
InvalidGitDir(git_next_config::GitDir),
|
||||||
|
Io(std::io::Error),
|
||||||
|
Wait(std::io::Error),
|
||||||
|
Spawn(std::io::Error),
|
||||||
|
Validation(String),
|
||||||
|
GixClone(Box<gix::clone::Error>),
|
||||||
|
GixOpen(Box<gix::open::Error>),
|
||||||
|
GixFetch(Box<gix::clone::fetch::Error>),
|
||||||
|
}
|
||||||
|
impl std::error::Error for Error {}
|
||||||
|
impl std::fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::InvalidGitDir(gitdir) => write!(f, "Invalid Git dir: {:?}", gitdir),
|
||||||
|
Self::Io(err) => write!(f, "IO error: {:?}", err),
|
||||||
|
Self::Wait(err) => write!(f, "Waiting for command: {:?}", err),
|
||||||
|
Self::Spawn(err) => write!(f, "Spawning comming: {:?}", err),
|
||||||
|
Self::Validation(err) => write!(f, "Validation: {}", err),
|
||||||
|
Self::GixClone(err) => write!(f, "Clone: {:?}", err),
|
||||||
|
Self::GixOpen(err) => write!(f, "Open: {:?}", err),
|
||||||
|
Self::GixFetch(err) => write!(f, "Fetch: {:?}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<gix::clone::Error> for Error {
|
||||||
|
fn from(value: gix::clone::Error) -> Self {
|
||||||
|
Self::GixClone(Box::new(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<gix::open::Error> for Error {
|
||||||
|
fn from(value: gix::open::Error) -> Self {
|
||||||
|
Self::GixOpen(Box::new(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<gix::clone::fetch::Error> for Error {
|
||||||
|
fn from(value: gix::clone::fetch::Error) -> Self {
|
||||||
|
Self::GixFetch(Box::new(value))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,24 @@
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use git_next_config::BranchName;
|
||||||
use secrecy::ExposeSecret;
|
use secrecy::ExposeSecret;
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
|
||||||
use crate::{
|
use super::{GitRef, RepoDetails, Repository};
|
||||||
config::{BranchName, RepoDetails},
|
|
||||||
gitforge::{BranchResetError, BranchResetResult, Force, Repository},
|
#[derive(Debug)]
|
||||||
types::GitRef,
|
pub enum Force {
|
||||||
};
|
No,
|
||||||
|
From(GitRef),
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for Force {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::No => write!(f, "fast-foward"),
|
||||||
|
Self::From(from) => write!(f, "force-if-from:{}", from),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: (#72) reimplement using `gix`
|
// TODO: (#72) reimplement using `gix`
|
||||||
#[tracing::instrument(skip_all, fields(branch = %branch_name, to = %to_commit, force = %force))]
|
#[tracing::instrument(skip_all, fields(branch = %branch_name, to = %to_commit, force = %force))]
|
||||||
|
@ -17,7 +28,7 @@ pub fn reset(
|
||||||
branch_name: BranchName,
|
branch_name: BranchName,
|
||||||
to_commit: GitRef,
|
to_commit: GitRef,
|
||||||
force: Force,
|
force: Force,
|
||||||
) -> BranchResetResult {
|
) -> Result {
|
||||||
let origin = repo_details.origin();
|
let origin = repo_details.origin();
|
||||||
let force = match force {
|
let force = match force {
|
||||||
Force::No => "".to_string(),
|
Force::No => "".to_string(),
|
||||||
|
@ -56,3 +67,13 @@ pub fn reset(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, derive_more::From, derive_more::Display)]
|
||||||
|
pub enum BranchResetError {
|
||||||
|
Open(Box<gix::open::Error>),
|
||||||
|
Fetch(super::fetch::Error),
|
||||||
|
Push,
|
||||||
|
}
|
||||||
|
impl std::error::Error for BranchResetError {}
|
||||||
|
|
||||||
|
pub type Result = core::result::Result<(), BranchResetError>;
|
93
crates/git/src/validate.rs
Normal file
93
crates/git/src/validate.rs
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
use std::ops::Deref as _;
|
||||||
|
|
||||||
|
use git_next_config::{Hostname, RepoPath};
|
||||||
|
use gix::remote::Direction;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
|
use super::{GitRemote, RepoDetails, Repository};
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub fn validate(repository: &Repository, repo_details: &RepoDetails) -> Result<()> {
|
||||||
|
let git_remote = repo_details.git_remote();
|
||||||
|
let push_remote = find_default_remote(repository, Direction::Push)?;
|
||||||
|
let fetch_remote = find_default_remote(repository, Direction::Fetch)?;
|
||||||
|
info!(config = %git_remote, push = %push_remote, fetch = %fetch_remote, "Check remotes match");
|
||||||
|
if git_remote != push_remote {
|
||||||
|
return Err(Error::MismatchDefaultPushRemote {
|
||||||
|
found: push_remote,
|
||||||
|
expected: git_remote,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if git_remote != fetch_remote {
|
||||||
|
return Err(Error::MismatchDefaultFetchRemote {
|
||||||
|
found: fetch_remote,
|
||||||
|
expected: git_remote,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all, fields(?direction))]
|
||||||
|
pub fn find_default_remote(
|
||||||
|
repository: &Repository,
|
||||||
|
direction: gix::remote::Direction,
|
||||||
|
) -> Result<GitRemote> {
|
||||||
|
let repository = repository.deref().to_thread_local();
|
||||||
|
let Some(Ok(remote)) = repository.find_default_remote(gix::remote::Direction::Push) else {
|
||||||
|
return Err(Error::NoDefaultPushRemote);
|
||||||
|
};
|
||||||
|
let Some(url) = remote.url(direction) else {
|
||||||
|
return Err(Error::NoUrlForDefaultPushRemote);
|
||||||
|
};
|
||||||
|
let Some(host) = url.host() else {
|
||||||
|
return Err(Error::NoHostnameForDefaultPushRemote);
|
||||||
|
};
|
||||||
|
let path = url.path.to_string();
|
||||||
|
let path = path.strip_prefix('/').map_or(path.as_str(), |path| path);
|
||||||
|
let path = path.strip_suffix(".git").map_or(path, |path| path);
|
||||||
|
info!(%host, %path, "found");
|
||||||
|
Ok(GitRemote::new(
|
||||||
|
Hostname(host.to_string()),
|
||||||
|
RepoPath(path.to_string()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Result<T> = core::result::Result<T, Error>;
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
NoDefaultPushRemote,
|
||||||
|
NoUrlForDefaultPushRemote,
|
||||||
|
NoHostnameForDefaultPushRemote,
|
||||||
|
UnableToOpenRepo(String),
|
||||||
|
Io(std::io::Error),
|
||||||
|
MismatchDefaultPushRemote {
|
||||||
|
found: GitRemote,
|
||||||
|
expected: GitRemote,
|
||||||
|
},
|
||||||
|
MismatchDefaultFetchRemote {
|
||||||
|
found: GitRemote,
|
||||||
|
expected: GitRemote,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
impl std::error::Error for Error {}
|
||||||
|
impl std::fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::NoDefaultPushRemote => write!(f, "There is no default push remote"),
|
||||||
|
Self::NoUrlForDefaultPushRemote => write!(f, "The default push remote has no url"),
|
||||||
|
Self::NoHostnameForDefaultPushRemote => {
|
||||||
|
write!(f, "The default push remote has no hostname")
|
||||||
|
}
|
||||||
|
Self::UnableToOpenRepo(err) => write!(f, "Unable to open the git dir: {err}"),
|
||||||
|
Self::Io(err) => write!(f, "IO Error: {err:?}"),
|
||||||
|
Self::MismatchDefaultPushRemote { found, expected } => write!(
|
||||||
|
f,
|
||||||
|
"The default push remote doesn't match: {found}, expected: {expected}"
|
||||||
|
),
|
||||||
|
Self::MismatchDefaultFetchRemote { found, expected } => write!(
|
||||||
|
f,
|
||||||
|
"The default fetch remote doesn't match: {found}, expected: {expected}"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,9 @@ forgejo = []
|
||||||
github = []
|
github = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
git-next-config = { workspace = true }
|
||||||
|
git-next-git = { workspace = true }
|
||||||
|
|
||||||
# logging
|
# logging
|
||||||
console-subscriber = { workspace = true }
|
console-subscriber = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
|
|
|
@ -2,22 +2,23 @@ use std::time::Duration;
|
||||||
|
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
|
|
||||||
|
use git_next_config::{RepoConfig, RepoConfigSource};
|
||||||
|
use git_next_git as git;
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
actors::repo::{LoadConfigFromRepo, RepoActor, ValidateRepo},
|
actors::repo::{LoadConfigFromRepo, RepoActor, ValidateRepo},
|
||||||
config::{self, RepoConfigSource},
|
|
||||||
gitforge,
|
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)]
|
||||||
pub async fn advance_next(
|
pub async fn advance_next(
|
||||||
next: gitforge::Commit,
|
next: git::Commit,
|
||||||
dev_commit_history: Vec<gitforge::Commit>,
|
dev_commit_history: Vec<git::Commit>,
|
||||||
repo_config: config::RepoConfig,
|
repo_config: RepoConfig,
|
||||||
forge: gitforge::Forge,
|
forge: gitforge::Forge,
|
||||||
repository: gitforge::Repository,
|
repository: git::Repository,
|
||||||
addr: Addr<super::RepoActor>,
|
addr: Addr<super::RepoActor>,
|
||||||
message_token: super::MessageToken,
|
message_token: super::MessageToken,
|
||||||
) {
|
) {
|
||||||
|
@ -35,7 +36,7 @@ pub async fn advance_next(
|
||||||
&repository,
|
&repository,
|
||||||
repo_config.branches().next(),
|
repo_config.branches().next(),
|
||||||
commit.into(),
|
commit.into(),
|
||||||
gitforge::Force::No,
|
git::reset::Force::No,
|
||||||
) {
|
) {
|
||||||
warn!(?err, "Failed")
|
warn!(?err, "Failed")
|
||||||
}
|
}
|
||||||
|
@ -44,7 +45,7 @@ pub async fn advance_next(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
fn validate_commit_message(message: &gitforge::Message) -> Option<String> {
|
fn validate_commit_message(message: &git::commit::Message) -> Option<String> {
|
||||||
let message = &message.to_string();
|
let message = &message.to_string();
|
||||||
if message.to_ascii_lowercase().starts_with("wip") {
|
if message.to_ascii_lowercase().starts_with("wip") {
|
||||||
return Some("Is Work-In-Progress".to_string());
|
return Some("Is Work-In-Progress".to_string());
|
||||||
|
@ -62,10 +63,10 @@ fn validate_commit_message(message: &gitforge::Message) -> Option<String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_next_commit_on_dev(
|
fn find_next_commit_on_dev(
|
||||||
next: gitforge::Commit,
|
next: git::Commit,
|
||||||
dev_commit_history: Vec<gitforge::Commit>,
|
dev_commit_history: Vec<git::Commit>,
|
||||||
) -> Option<gitforge::Commit> {
|
) -> Option<git::Commit> {
|
||||||
let mut next_commit: Option<gitforge::Commit> = None;
|
let mut next_commit: Option<git::Commit> = None;
|
||||||
for commit in dev_commit_history.into_iter() {
|
for commit in dev_commit_history.into_iter() {
|
||||||
if commit == next {
|
if commit == next {
|
||||||
break;
|
break;
|
||||||
|
@ -78,10 +79,10 @@ fn find_next_commit_on_dev(
|
||||||
// advance main branch to the commit 'next'
|
// advance main branch to the commit 'next'
|
||||||
#[tracing::instrument(fields(next), skip_all)]
|
#[tracing::instrument(fields(next), skip_all)]
|
||||||
pub async fn advance_main(
|
pub async fn advance_main(
|
||||||
next: gitforge::Commit,
|
next: git::Commit,
|
||||||
repo_config: config::RepoConfig,
|
repo_config: RepoConfig,
|
||||||
forge: gitforge::Forge,
|
forge: gitforge::Forge,
|
||||||
repository: gitforge::Repository,
|
repository: git::Repository,
|
||||||
addr: Addr<RepoActor>,
|
addr: Addr<RepoActor>,
|
||||||
message_token: super::MessageToken,
|
message_token: super::MessageToken,
|
||||||
) {
|
) {
|
||||||
|
@ -90,7 +91,7 @@ pub async fn advance_main(
|
||||||
&repository,
|
&repository,
|
||||||
repo_config.branches().main(),
|
repo_config.branches().main(),
|
||||||
next.into(),
|
next.into(),
|
||||||
gitforge::Force::No,
|
git::reset::Force::No,
|
||||||
) {
|
) {
|
||||||
warn!(?err, "Failed")
|
warn!(?err, "Failed")
|
||||||
};
|
};
|
||||||
|
@ -106,13 +107,13 @@ mod tests {
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_find_next_commit_on_dev() {
|
async fn test_find_next_commit_on_dev() {
|
||||||
let next = gitforge::Commit::new("current-next", "foo");
|
let next = git::Commit::new("current-next", "foo");
|
||||||
let expected = gitforge::Commit::new("dev-next", "next-should-go-here");
|
let expected = git::Commit::new("dev-next", "next-should-go-here");
|
||||||
let dev_commit_history = vec![
|
let dev_commit_history = vec![
|
||||||
gitforge::Commit::new("dev", "future"),
|
git::Commit::new("dev", "future"),
|
||||||
expected.clone(),
|
expected.clone(),
|
||||||
next.clone(),
|
next.clone(),
|
||||||
gitforge::Commit::new("current-main", "history"),
|
git::Commit::new("current-main", "history"),
|
||||||
];
|
];
|
||||||
let next_commit = find_next_commit_on_dev(next, dev_commit_history);
|
let next_commit = find_next_commit_on_dev(next, dev_commit_history);
|
||||||
assert_eq!(next_commit, Some(expected));
|
assert_eq!(next_commit, Some(expected));
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
|
use git_next_git::RepoDetails;
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
|
||||||
use crate::{config, gitforge};
|
use crate::{config, gitforge};
|
||||||
|
@ -7,11 +8,7 @@ use super::{LoadedConfig, RepoActor};
|
||||||
|
|
||||||
/// Loads the [RepoConfig] from the `.git-next.toml` file in the repository
|
/// Loads the [RepoConfig] from the `.git-next.toml` file in the repository
|
||||||
#[tracing::instrument(skip_all, fields(branch = %repo_details.branch))]
|
#[tracing::instrument(skip_all, fields(branch = %repo_details.branch))]
|
||||||
pub async fn load(
|
pub async fn load(repo_details: RepoDetails, addr: Addr<RepoActor>, forge: gitforge::Forge) {
|
||||||
repo_details: config::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 config::load::load(&repo_details, &forge).await {
|
||||||
Ok(repo_config) => repo_config,
|
Ok(repo_config) => repo_config,
|
||||||
|
|
|
@ -4,46 +4,40 @@ pub mod status;
|
||||||
pub mod webhook;
|
pub mod webhook;
|
||||||
|
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
|
use git_next_config::{ForgeType, RepoConfig};
|
||||||
|
use git_next_git::{self as git, Generation, RepoDetails};
|
||||||
use kxio::network::Network;
|
use kxio::network::Network;
|
||||||
use tracing::{debug, info, warn, Instrument};
|
use tracing::{debug, info, warn, Instrument};
|
||||||
|
|
||||||
use crate::{
|
use crate::{actors::repo::webhook::WebhookAuth, config::Webhook, gitforge, types::MessageToken};
|
||||||
actors::repo::webhook::WebhookAuth,
|
|
||||||
config::{RepoConfig, RepoDetails, Webhook},
|
|
||||||
gitforge::{self, Repository},
|
|
||||||
types::{MessageToken, ServerGeneration},
|
|
||||||
};
|
|
||||||
|
|
||||||
use self::webhook::WebhookId;
|
use self::webhook::WebhookId;
|
||||||
|
|
||||||
pub struct RepoActor {
|
pub struct RepoActor {
|
||||||
generation: ServerGeneration,
|
generation: Generation,
|
||||||
message_token: MessageToken,
|
message_token: MessageToken,
|
||||||
details: RepoDetails,
|
details: RepoDetails,
|
||||||
webhook: Webhook,
|
webhook: Webhook,
|
||||||
webhook_id: Option<WebhookId>, // INFO: if [None] then no webhook is configured
|
webhook_id: Option<WebhookId>, // INFO: if [None] then no webhook is configured
|
||||||
webhook_auth: Option<WebhookAuth>, // INFO: if [None] then no webhook is configured
|
webhook_auth: Option<WebhookAuth>, // INFO: if [None] then no webhook is configured
|
||||||
last_main_commit: Option<gitforge::Commit>,
|
last_main_commit: Option<git::Commit>,
|
||||||
last_next_commit: Option<gitforge::Commit>,
|
last_next_commit: Option<git::Commit>,
|
||||||
last_dev_commit: Option<gitforge::Commit>,
|
last_dev_commit: Option<git::Commit>,
|
||||||
repository: Option<Repository>,
|
repository: Option<git::Repository>,
|
||||||
net: Network,
|
net: Network,
|
||||||
forge: gitforge::Forge,
|
forge: gitforge::Forge,
|
||||||
}
|
}
|
||||||
impl RepoActor {
|
impl RepoActor {
|
||||||
pub(crate) fn new(
|
pub fn new(
|
||||||
details: RepoDetails,
|
details: RepoDetails,
|
||||||
webhook: Webhook,
|
webhook: Webhook,
|
||||||
generation: ServerGeneration,
|
generation: Generation,
|
||||||
net: Network,
|
net: Network,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let forge = match details.forge.forge_type {
|
let forge = match details.forge.forge_type {
|
||||||
#[cfg(feature = "forgejo")]
|
#[cfg(feature = "forgejo")]
|
||||||
crate::config::ForgeType::ForgeJo => {
|
ForgeType::ForgeJo => gitforge::Forge::new_forgejo(details.clone(), net.clone()),
|
||||||
gitforge::Forge::new_forgejo(details.clone(), net.clone())
|
ForgeType::MockForge => gitforge::Forge::new_mock(),
|
||||||
}
|
|
||||||
#[cfg(test)]
|
|
||||||
crate::config::ForgeType::MockForge => gitforge::Forge::new_mock(),
|
|
||||||
};
|
};
|
||||||
debug!(?forge, "new");
|
debug!(?forge, "new");
|
||||||
Self {
|
Self {
|
||||||
|
@ -212,10 +206,10 @@ impl Handler<ValidateRepo> for RepoActor {
|
||||||
#[derive(Debug, Message)]
|
#[derive(Debug, Message)]
|
||||||
#[rtype(result = "()")]
|
#[rtype(result = "()")]
|
||||||
pub struct StartMonitoring {
|
pub struct StartMonitoring {
|
||||||
pub main: gitforge::Commit,
|
pub main: git::Commit,
|
||||||
pub next: gitforge::Commit,
|
pub next: git::Commit,
|
||||||
pub dev: gitforge::Commit,
|
pub dev: git::Commit,
|
||||||
pub dev_commit_history: Vec<gitforge::Commit>,
|
pub dev_commit_history: Vec<git::Commit>,
|
||||||
}
|
}
|
||||||
impl Handler<StartMonitoring> for RepoActor {
|
impl Handler<StartMonitoring> for RepoActor {
|
||||||
type Result = ();
|
type Result = ();
|
||||||
|
@ -273,7 +267,7 @@ impl Handler<WebhookRegistered> for RepoActor {
|
||||||
|
|
||||||
#[derive(Message)]
|
#[derive(Message)]
|
||||||
#[rtype(result = "()")]
|
#[rtype(result = "()")]
|
||||||
pub struct AdvanceMainTo(pub gitforge::Commit);
|
pub struct AdvanceMainTo(pub git::Commit);
|
||||||
impl Handler<AdvanceMainTo> for RepoActor {
|
impl Handler<AdvanceMainTo> for RepoActor {
|
||||||
type Result = ();
|
type Result = ();
|
||||||
#[tracing::instrument(name = "RepoActor::AdvanceMainTo", skip_all, fields(repo = %self.details, commit = %msg.0))]
|
#[tracing::instrument(name = "RepoActor::AdvanceMainTo", skip_all, fields(repo = %self.details, commit = %msg.0))]
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
|
use git_next_git as git;
|
||||||
use gix::trace::warn;
|
use gix::trace::warn;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
|
@ -7,7 +8,7 @@ use crate::{actors::repo::ValidateRepo, gitforge, types::MessageToken};
|
||||||
use super::AdvanceMainTo;
|
use super::AdvanceMainTo;
|
||||||
|
|
||||||
pub async fn check_next(
|
pub async fn check_next(
|
||||||
next: gitforge::Commit,
|
next: git::Commit,
|
||||||
addr: Addr<super::RepoActor>,
|
addr: Addr<super::RepoActor>,
|
||||||
forge: gitforge::Forge,
|
forge: gitforge::Forge,
|
||||||
message_token: MessageToken,
|
message_token: MessageToken,
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
|
use git_next_config::RepoBranches;
|
||||||
|
use git_next_git::{self as git, RepoDetails};
|
||||||
use kxio::network::{self, json};
|
use kxio::network::{self, json};
|
||||||
use tracing::{debug, info, warn};
|
use tracing::{debug, info, warn};
|
||||||
use ulid::DecodeError;
|
use ulid::DecodeError;
|
||||||
|
@ -10,8 +12,7 @@ use crate::{
|
||||||
repo::{RepoActor, ValidateRepo, WebhookRegistered},
|
repo::{RepoActor, ValidateRepo, WebhookRegistered},
|
||||||
webhook::WebhookMessage,
|
webhook::WebhookMessage,
|
||||||
},
|
},
|
||||||
config::{RepoBranches, Webhook, WebhookUrl},
|
config::{Webhook, WebhookUrl},
|
||||||
gitforge,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
@ -58,11 +59,7 @@ impl Deref for WebhookAuth {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(%webhook_id))]
|
#[tracing::instrument(skip_all, fields(%webhook_id))]
|
||||||
pub async fn unregister(
|
pub async fn unregister(webhook_id: WebhookId, repo_details: RepoDetails, net: network::Network) {
|
||||||
webhook_id: WebhookId,
|
|
||||||
repo_details: crate::config::RepoDetails,
|
|
||||||
net: network::Network,
|
|
||||||
) {
|
|
||||||
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;
|
||||||
use secrecy::ExposeSecret;
|
use secrecy::ExposeSecret;
|
||||||
|
@ -88,7 +85,7 @@ pub async fn unregister(
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn register(
|
pub async fn register(
|
||||||
repo_details: crate::config::RepoDetails,
|
repo_details: RepoDetails,
|
||||||
webhook: Webhook,
|
webhook: Webhook,
|
||||||
addr: actix::prelude::Addr<super::RepoActor>,
|
addr: actix::prelude::Addr<super::RepoActor>,
|
||||||
net: network::Network,
|
net: network::Network,
|
||||||
|
@ -147,7 +144,7 @@ pub async fn register(
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn find_existing_webhooks(
|
async fn find_existing_webhooks(
|
||||||
repo_details: &crate::config::RepoDetails,
|
repo_details: &RepoDetails,
|
||||||
webhook_url: &WebhookUrl,
|
webhook_url: &WebhookUrl,
|
||||||
net: &network::Network,
|
net: &network::Network,
|
||||||
) -> Vec<WebhookId> {
|
) -> Vec<WebhookId> {
|
||||||
|
@ -301,8 +298,8 @@ impl Push {
|
||||||
warn!(branch, "Unexpected branch");
|
warn!(branch, "Unexpected branch");
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
pub fn commit(&self) -> gitforge::Commit {
|
pub fn commit(&self) -> git::Commit {
|
||||||
gitforge::Commit::new(&self.after, &self.head_commit.message)
|
git::Commit::new(&self.after, &self.head_commit.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,8 @@ use std::path::PathBuf;
|
||||||
|
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
|
|
||||||
|
use git_next_config::{ForgeConfig, ForgeName, GitDir, RepoAlias, ServerRepoConfig};
|
||||||
|
use git_next_git::{Generation, RepoDetails};
|
||||||
use kxio::{fs::FileSystem, network::Network};
|
use kxio::{fs::FileSystem, network::Network};
|
||||||
use tracing::{error, info, warn};
|
use tracing::{error, info, warn};
|
||||||
|
|
||||||
|
@ -11,11 +13,7 @@ use crate::{
|
||||||
repo::{CloneRepo, RepoActor},
|
repo::{CloneRepo, RepoActor},
|
||||||
webhook::{AddWebhookRecipient, ShutdownWebhook, WebhookActor, WebhookRouter},
|
webhook::{AddWebhookRecipient, ShutdownWebhook, WebhookActor, WebhookRouter},
|
||||||
},
|
},
|
||||||
config::{
|
config::{ServerConfig, ServerStorage, Webhook},
|
||||||
ForgeConfig, ForgeName, GitDir, RepoAlias, RepoDetails, ServerConfig, ServerRepoConfig,
|
|
||||||
ServerStorage, Webhook,
|
|
||||||
},
|
|
||||||
types::ServerGeneration,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, derive_more::Display, derive_more::From)]
|
#[derive(Debug, derive_more::Display, derive_more::From)]
|
||||||
|
@ -35,7 +33,7 @@ pub enum Error {
|
||||||
type Result<T> = core::result::Result<T, Error>;
|
type Result<T> = core::result::Result<T, Error>;
|
||||||
|
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
generation: ServerGeneration,
|
generation: Generation,
|
||||||
webhook: Option<Addr<WebhookActor>>,
|
webhook: Option<Addr<WebhookActor>>,
|
||||||
fs: FileSystem,
|
fs: FileSystem,
|
||||||
net: Network,
|
net: Network,
|
||||||
|
@ -113,7 +111,7 @@ impl Handler<ServerConfig> for Server {
|
||||||
}
|
}
|
||||||
impl Server {
|
impl Server {
|
||||||
pub fn new(fs: FileSystem, net: Network) -> Self {
|
pub fn new(fs: FileSystem, net: Network) -> Self {
|
||||||
let generation = ServerGeneration::new();
|
let generation = Generation::new();
|
||||||
Self {
|
Self {
|
||||||
generation,
|
generation,
|
||||||
webhook: None,
|
webhook: None,
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
|
use git_next_config::RepoAlias;
|
||||||
use tracing::{debug, info};
|
use tracing::{debug, info};
|
||||||
|
|
||||||
use crate::{actors::webhook::message::WebhookMessage, config::RepoAlias};
|
use crate::actors::webhook::message::WebhookMessage;
|
||||||
|
|
||||||
pub struct WebhookRouter {
|
pub struct WebhookRouter {
|
||||||
span: tracing::Span,
|
span: tracing::Span,
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
|
use git_next_config::{BranchName, RepoConfig};
|
||||||
|
use git_next_git::RepoDetails;
|
||||||
use terrors::OneOf;
|
use terrors::OneOf;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use crate::{
|
use crate::gitforge::{self, ForgeFileError};
|
||||||
config::{BranchName, RepoConfig, RepoDetails},
|
|
||||||
gitforge::{self, ForgeFileError},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub async fn load(
|
pub async fn load(
|
||||||
details: &RepoDetails,
|
details: &RepoDetails,
|
||||||
|
@ -43,7 +42,7 @@ pub async fn validate(
|
||||||
})?;
|
})?;
|
||||||
if !branches
|
if !branches
|
||||||
.iter()
|
.iter()
|
||||||
.any(|branch| branch.name() == &config.branches().main())
|
.any(|branch| branch == &config.branches().main())
|
||||||
{
|
{
|
||||||
return Err(RepoConfigValidationErrors::BranchNotFound(
|
return Err(RepoConfigValidationErrors::BranchNotFound(
|
||||||
config.branches().main(),
|
config.branches().main(),
|
||||||
|
@ -51,7 +50,7 @@ pub async fn validate(
|
||||||
}
|
}
|
||||||
if !branches
|
if !branches
|
||||||
.iter()
|
.iter()
|
||||||
.any(|branch| branch.name() == &config.branches().next())
|
.any(|branch| branch == &config.branches().next())
|
||||||
{
|
{
|
||||||
return Err(RepoConfigValidationErrors::BranchNotFound(
|
return Err(RepoConfigValidationErrors::BranchNotFound(
|
||||||
config.branches().next(),
|
config.branches().next(),
|
||||||
|
@ -59,7 +58,7 @@ pub async fn validate(
|
||||||
}
|
}
|
||||||
if !branches
|
if !branches
|
||||||
.iter()
|
.iter()
|
||||||
.any(|branch| branch.name() == &config.branches().dev())
|
.any(|branch| branch == &config.branches().dev())
|
||||||
{
|
{
|
||||||
return Err(RepoConfigValidationErrors::BranchNotFound(
|
return Err(RepoConfigValidationErrors::BranchNotFound(
|
||||||
config.branches().dev(),
|
config.branches().dev(),
|
||||||
|
|
|
@ -4,20 +4,15 @@ pub mod load;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fmt::{Display, Formatter},
|
|
||||||
net::SocketAddr,
|
net::SocketAddr,
|
||||||
ops::Deref,
|
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
use serde::Deserialize;
|
use git_next_config::{ForgeConfig, ForgeName};
|
||||||
|
|
||||||
use kxio::fs::FileSystem;
|
use kxio::fs::FileSystem;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
use crate::{gitforge::Repository, types::ServerGeneration};
|
|
||||||
|
|
||||||
#[derive(Debug, derive_more::From, derive_more::Display)]
|
#[derive(Debug, derive_more::From, derive_more::Display)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
Io(std::io::Error),
|
Io(std::io::Error),
|
||||||
|
@ -30,7 +25,7 @@ impl std::error::Error for Error {}
|
||||||
type Result<T> = core::result::Result<T, Error>;
|
type Result<T> = core::result::Result<T, Error>;
|
||||||
|
|
||||||
/// Mapped from the `git-next-server.toml` file
|
/// Mapped from the `git-next-server.toml` file
|
||||||
#[derive(Debug, PartialEq, Eq, Deserialize, Message)]
|
#[derive(Debug, PartialEq, Eq, serde::Deserialize, Message)]
|
||||||
#[rtype(result = "()")]
|
#[rtype(result = "()")]
|
||||||
pub struct ServerConfig {
|
pub struct ServerConfig {
|
||||||
http: Http,
|
http: Http,
|
||||||
|
@ -67,7 +62,7 @@ impl ServerConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Defines the port the server will listen to for incoming webhooks messages
|
/// Defines the port the server will listen to for incoming webhooks messages
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)]
|
||||||
pub struct Http {
|
pub struct Http {
|
||||||
addr: String,
|
addr: String,
|
||||||
port: u16,
|
port: u16,
|
||||||
|
@ -83,7 +78,7 @@ impl Http {
|
||||||
|
|
||||||
/// Defines the Webhook Forges should send updates to
|
/// Defines the Webhook Forges should send updates to
|
||||||
/// Must be an address that is accessible from the remote forge
|
/// Must be an address that is accessible from the remote forge
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)]
|
||||||
pub struct Webhook {
|
pub struct Webhook {
|
||||||
url: String,
|
url: String,
|
||||||
}
|
}
|
||||||
|
@ -94,7 +89,7 @@ impl Webhook {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The URL for the webhook where forges should send their updates
|
/// The URL for the webhook where forges should send their updates
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)]
|
||||||
pub struct WebhookUrl(String);
|
pub struct WebhookUrl(String);
|
||||||
impl AsRef<str> for WebhookUrl {
|
impl AsRef<str> for WebhookUrl {
|
||||||
fn as_ref(&self) -> &str {
|
fn as_ref(&self) -> &str {
|
||||||
|
@ -103,7 +98,7 @@ impl AsRef<str> for WebhookUrl {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The directory to store server data, such as cloned repos
|
/// The directory to store server data, such as cloned repos
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)]
|
||||||
pub struct ServerStorage {
|
pub struct ServerStorage {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
}
|
}
|
||||||
|
@ -113,533 +108,5 @@ impl ServerStorage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mapped from `.git-next.toml` file in target repo
|
|
||||||
/// Is also derived from the optional parameters in `git-next-server.toml` at
|
|
||||||
/// `forge.{forge}.repos.{repo}.(main|next|dev)`
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
|
||||||
pub struct RepoConfig {
|
|
||||||
branches: RepoBranches,
|
|
||||||
source: RepoConfigSource,
|
|
||||||
}
|
|
||||||
impl RepoConfig {
|
|
||||||
#[cfg(test)]
|
|
||||||
pub const fn new(branches: RepoBranches, source: RepoConfigSource) -> Self {
|
|
||||||
Self { branches, source }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load(toml: &str) -> Result<Self> {
|
|
||||||
toml::from_str(format!("source = \"Repo\"\n{}", toml).as_str()).map_err(Into::into)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn branches(&self) -> &RepoBranches {
|
|
||||||
&self.branches
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn source(&self) -> RepoConfigSource {
|
|
||||||
self.source
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Display for RepoConfig {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.branches)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
|
|
||||||
pub enum RepoConfigSource {
|
|
||||||
Repo,
|
|
||||||
Server,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mapped from `.git-next.toml` file at `branches`
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
|
||||||
pub struct RepoBranches {
|
|
||||||
main: String,
|
|
||||||
next: String,
|
|
||||||
dev: String,
|
|
||||||
}
|
|
||||||
impl RepoBranches {
|
|
||||||
#[cfg(test)]
|
|
||||||
pub fn new(main: impl Into<String>, next: impl Into<String>, dev: impl Into<String>) -> Self {
|
|
||||||
Self {
|
|
||||||
main: main.into(),
|
|
||||||
next: next.into(),
|
|
||||||
dev: dev.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn main(&self) -> BranchName {
|
|
||||||
BranchName(self.main.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn next(&self) -> BranchName {
|
|
||||||
BranchName(self.next.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dev(&self) -> BranchName {
|
|
||||||
BranchName(self.dev.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Display for RepoBranches {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{},{},{}", self.main, self.next, self.dev)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Defines a Forge to connect to
|
|
||||||
/// Maps from `git-next-server.toml` at `forge.{forge}`
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
|
||||||
pub struct ForgeConfig {
|
|
||||||
forge_type: ForgeType,
|
|
||||||
hostname: String,
|
|
||||||
user: String,
|
|
||||||
token: String,
|
|
||||||
// API Token
|
|
||||||
// Private SSH Key Path
|
|
||||||
repos: HashMap<String, ServerRepoConfig>,
|
|
||||||
}
|
|
||||||
impl ForgeConfig {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub const fn forge_type(&self) -> &ForgeType {
|
|
||||||
&self.forge_type
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn hostname(&self) -> Hostname {
|
|
||||||
Hostname(self.hostname.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn user(&self) -> User {
|
|
||||||
User(self.user.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn token(&self) -> ApiToken {
|
|
||||||
ApiToken(self.token.clone().into())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn repos(&self) -> impl Iterator<Item = (RepoAlias, &ServerRepoConfig)> {
|
|
||||||
self.repos
|
|
||||||
.iter()
|
|
||||||
.map(|(name, repo)| (RepoAlias(name.clone()), repo))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Display for ForgeConfig {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{}:{}@{}",
|
|
||||||
self.forge_type.to_string().to_lowercase(),
|
|
||||||
self.user,
|
|
||||||
self.hostname
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Defines a Repo within a ForgeConfig to be monitored by the server
|
|
||||||
/// Maps from `git-next-server.toml` at `forge.{forge}.repos.{name}`
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
|
||||||
pub struct ServerRepoConfig {
|
|
||||||
repo: String,
|
|
||||||
branch: String,
|
|
||||||
gitdir: Option<PathBuf>,
|
|
||||||
main: Option<String>,
|
|
||||||
next: Option<String>,
|
|
||||||
dev: Option<String>,
|
|
||||||
}
|
|
||||||
impl ServerRepoConfig {
|
|
||||||
pub fn repo(&self) -> RepoPath {
|
|
||||||
RepoPath(self.repo.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn branch(&self) -> BranchName {
|
|
||||||
BranchName(self.branch.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn gitdir(&self) -> Option<GitDir> {
|
|
||||||
self.gitdir.clone().map(GitDir::from)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a RepoConfig from the server configuration if ALL THREE branches were provided
|
|
||||||
pub fn repo_config(&self) -> Option<RepoConfig> {
|
|
||||||
match (&self.main, &self.next, &self.dev) {
|
|
||||||
(Some(main), Some(next), Some(dev)) => Some(RepoConfig {
|
|
||||||
branches: RepoBranches {
|
|
||||||
main: main.to_string(),
|
|
||||||
next: next.to_string(),
|
|
||||||
dev: dev.to_string(),
|
|
||||||
},
|
|
||||||
source: RepoConfigSource::Server,
|
|
||||||
}),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(test)]
|
|
||||||
impl AsRef<Self> for ServerRepoConfig {
|
|
||||||
fn as_ref(&self) -> &Self {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Display for ServerRepoConfig {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}@{}", self.repo, self.branch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The name of a Forge to connect to
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct ForgeName(pub String);
|
|
||||||
impl Display for ForgeName {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<&ForgeName> for PathBuf {
|
|
||||||
fn from(value: &ForgeName) -> Self {
|
|
||||||
Self::from(&value.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The hostname of a forge
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Hostname(pub String);
|
|
||||||
impl Display for Hostname {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The user within the forge to connect as
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct User(pub String);
|
|
||||||
impl Display for User {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The API Token for the [user]
|
|
||||||
/// ForgeJo: https://{hostname}/user/settings/applications
|
|
||||||
/// Github: https://github.com/settings/tokens
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct ApiToken(pub secrecy::Secret<String>);
|
|
||||||
impl From<String> for ApiToken {
|
|
||||||
fn from(value: String) -> Self {
|
|
||||||
Self(value.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// The API Token is in effect a password, so it must be explicitly exposed to access its value
|
|
||||||
impl secrecy::ExposeSecret<String> for ApiToken {
|
|
||||||
fn expose_secret(&self) -> &String {
|
|
||||||
self.0.expose_secret()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The derived information about a Forge, used to create interactions with it
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct ForgeDetails {
|
|
||||||
pub forge_name: ForgeName,
|
|
||||||
pub forge_type: ForgeType,
|
|
||||||
pub hostname: Hostname,
|
|
||||||
pub user: User,
|
|
||||||
pub token: ApiToken,
|
|
||||||
// API Token
|
|
||||||
// Private SSH Key Path
|
|
||||||
}
|
|
||||||
impl From<(&ForgeName, &ForgeConfig)> for ForgeDetails {
|
|
||||||
fn from(forge: (&ForgeName, &ForgeConfig)) -> Self {
|
|
||||||
Self {
|
|
||||||
forge_name: forge.0.clone(),
|
|
||||||
forge_type: forge.1.forge_type.clone(),
|
|
||||||
hostname: forge.1.hostname(),
|
|
||||||
user: forge.1.user(),
|
|
||||||
token: forge.1.token(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The alias of a repo
|
|
||||||
/// This is the alias for the repo within `git-next-server.toml`
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub struct RepoAlias(pub String);
|
|
||||||
impl Display for RepoAlias {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The path for the repo within the forge.
|
|
||||||
/// Typically this is composed of the user or organisation and the name of the repo
|
|
||||||
/// e.g. `{user}/{repo}`
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct RepoPath(pub String);
|
|
||||||
impl Display for RepoPath {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The name of a Branch
|
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
|
||||||
pub struct BranchName(pub String);
|
|
||||||
impl Display for BranchName {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Deref for BranchName {
|
|
||||||
type Target = String;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The derived information about a repo, used to interact with it
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct RepoDetails {
|
|
||||||
pub generation: ServerGeneration,
|
|
||||||
pub repo_alias: RepoAlias,
|
|
||||||
pub repo_path: RepoPath,
|
|
||||||
pub branch: BranchName,
|
|
||||||
pub forge: ForgeDetails,
|
|
||||||
pub repo_config: Option<RepoConfig>,
|
|
||||||
pub gitdir: GitDir,
|
|
||||||
}
|
|
||||||
impl RepoDetails {
|
|
||||||
pub fn new(
|
|
||||||
generation: ServerGeneration,
|
|
||||||
repo_alias: &RepoAlias,
|
|
||||||
server_repo_config: &ServerRepoConfig,
|
|
||||||
forge_name: &ForgeName,
|
|
||||||
forge_config: &ForgeConfig,
|
|
||||||
gitdir: GitDir,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
generation,
|
|
||||||
repo_alias: repo_alias.clone(),
|
|
||||||
repo_path: RepoPath(server_repo_config.repo.clone()),
|
|
||||||
repo_config: server_repo_config.repo_config(),
|
|
||||||
branch: BranchName(server_repo_config.branch.clone()),
|
|
||||||
gitdir,
|
|
||||||
forge: ForgeDetails {
|
|
||||||
forge_name: forge_name.clone(),
|
|
||||||
forge_type: forge_config.forge_type.clone(),
|
|
||||||
hostname: forge_config.hostname(),
|
|
||||||
user: forge_config.user(),
|
|
||||||
token: forge_config.token(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn origin(&self) -> secrecy::Secret<String> {
|
|
||||||
let repo_details = self;
|
|
||||||
let user = &repo_details.forge.user;
|
|
||||||
let hostname = &repo_details.forge.hostname;
|
|
||||||
let repo_path = &repo_details.repo_path;
|
|
||||||
use secrecy::ExposeSecret;
|
|
||||||
let expose_secret = &repo_details.forge.token;
|
|
||||||
let token = expose_secret.expose_secret();
|
|
||||||
let origin = format!("https://{user}:{token}@{hostname}/{repo_path}.git");
|
|
||||||
origin.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn git_remote(&self) -> GitRemote {
|
|
||||||
GitRemote::new(self.forge.hostname.clone(), self.repo_path.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Display for RepoDetails {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"gen-{}:{}:{}/{}:{}@{}/{}@{}",
|
|
||||||
self.generation,
|
|
||||||
self.forge.forge_type,
|
|
||||||
self.forge.forge_name,
|
|
||||||
self.repo_alias,
|
|
||||||
self.forge.user,
|
|
||||||
self.forge.hostname,
|
|
||||||
self.repo_path,
|
|
||||||
self.branch,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ValidationResult<T> = core::result::Result<T, RepoValidationError>;
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum RepoValidationError {
|
|
||||||
NoDefaultPushRemote,
|
|
||||||
NoUrlForDefaultPushRemote,
|
|
||||||
NoHostnameForDefaultPushRemote,
|
|
||||||
UnableToOpenRepo(String),
|
|
||||||
Io(std::io::Error),
|
|
||||||
MismatchDefaultPushRemote {
|
|
||||||
found: GitRemote,
|
|
||||||
expected: GitRemote,
|
|
||||||
},
|
|
||||||
MismatchDefaultFetchRemote {
|
|
||||||
found: GitRemote,
|
|
||||||
expected: GitRemote,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
impl std::error::Error for RepoValidationError {}
|
|
||||||
impl Display for RepoValidationError {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::NoDefaultPushRemote => write!(f, "There is no default push remote"),
|
|
||||||
Self::NoUrlForDefaultPushRemote => write!(f, "The default push remote has no url"),
|
|
||||||
Self::NoHostnameForDefaultPushRemote => {
|
|
||||||
write!(f, "The default push remote has no hostname")
|
|
||||||
}
|
|
||||||
Self::UnableToOpenRepo(err) => write!(f, "Unable to open the git dir: {err}"),
|
|
||||||
Self::Io(err) => write!(f, "IO Error: {err:?}"),
|
|
||||||
Self::MismatchDefaultPushRemote { found, expected } => write!(
|
|
||||||
f,
|
|
||||||
"The default push remote doesn't match: {found}, expected: {expected}"
|
|
||||||
),
|
|
||||||
Self::MismatchDefaultFetchRemote { found, expected } => write!(
|
|
||||||
f,
|
|
||||||
"The default fetch remote doesn't match: {found}, expected: {expected}"
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Identifier for the type of Forge
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
|
||||||
pub enum ForgeType {
|
|
||||||
#[cfg(feature = "forgejo")]
|
|
||||||
ForgeJo,
|
|
||||||
// Gitea,
|
|
||||||
// GitHub,
|
|
||||||
// GitLab,
|
|
||||||
// BitBucket,
|
|
||||||
#[cfg(test)]
|
|
||||||
MockForge,
|
|
||||||
}
|
|
||||||
impl Display for ForgeType {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", format!("{:?}", self).to_lowercase())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
|
||||||
pub struct GitDir(PathBuf);
|
|
||||||
impl GitDir {
|
|
||||||
#[cfg(test)]
|
|
||||||
pub(crate) fn new(pathbuf: &std::path::Path) -> Self {
|
|
||||||
info!("GitDir::new({pathbuf:?})");
|
|
||||||
Self(pathbuf.to_path_buf())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn pathbuf(&self) -> &PathBuf {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
pub fn validate(
|
|
||||||
&self,
|
|
||||||
repository: &Repository,
|
|
||||||
repo_details: &RepoDetails,
|
|
||||||
) -> ValidationResult<()> {
|
|
||||||
let git_remote = repo_details.git_remote();
|
|
||||||
use gix::remote::Direction;
|
|
||||||
let push_remote = self.find_default_remote(repository, Direction::Push)?;
|
|
||||||
let fetch_remote = self.find_default_remote(repository, Direction::Fetch)?;
|
|
||||||
info!(config = %git_remote, push = %push_remote, fetch = %fetch_remote, "Check remotes match");
|
|
||||||
if git_remote != push_remote {
|
|
||||||
return Err(RepoValidationError::MismatchDefaultPushRemote {
|
|
||||||
found: push_remote,
|
|
||||||
expected: git_remote,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if git_remote != fetch_remote {
|
|
||||||
return Err(RepoValidationError::MismatchDefaultFetchRemote {
|
|
||||||
found: fetch_remote,
|
|
||||||
expected: git_remote,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(?direction))]
|
|
||||||
fn find_default_remote(
|
|
||||||
&self,
|
|
||||||
repository: &Repository,
|
|
||||||
direction: gix::remote::Direction,
|
|
||||||
) -> ValidationResult<GitRemote> {
|
|
||||||
let repository = repository.deref().to_thread_local();
|
|
||||||
let Some(Ok(remote)) = repository.find_default_remote(gix::remote::Direction::Push) else {
|
|
||||||
return Err(RepoValidationError::NoDefaultPushRemote);
|
|
||||||
};
|
|
||||||
let Some(url) = remote.url(direction) else {
|
|
||||||
return Err(RepoValidationError::NoUrlForDefaultPushRemote);
|
|
||||||
};
|
|
||||||
let Some(host) = url.host() else {
|
|
||||||
return Err(RepoValidationError::NoHostnameForDefaultPushRemote);
|
|
||||||
};
|
|
||||||
let path = url.path.to_string();
|
|
||||||
let path = path.strip_prefix('/').map_or(path.as_str(), |path| path);
|
|
||||||
let path = path.strip_suffix(".git").map_or(path, |path| path);
|
|
||||||
info!(%host, %path, "found");
|
|
||||||
Ok(GitRemote::new(
|
|
||||||
Hostname(host.to_string()),
|
|
||||||
RepoPath(path.to_string()),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Deref for GitDir {
|
|
||||||
type Target = PathBuf;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for GitDir {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", &self.0.display())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<&str> for GitDir {
|
|
||||||
fn from(value: &str) -> Self {
|
|
||||||
Self(value.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<&GitDir> for PathBuf {
|
|
||||||
fn from(value: &GitDir) -> Self {
|
|
||||||
value.to_path_buf()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<PathBuf> for GitDir {
|
|
||||||
fn from(value: PathBuf) -> Self {
|
|
||||||
Self(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<GitDir> for PathBuf {
|
|
||||||
fn from(value: GitDir) -> Self {
|
|
||||||
value.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct GitRemote {
|
|
||||||
host: Hostname,
|
|
||||||
repo_path: RepoPath,
|
|
||||||
}
|
|
||||||
impl GitRemote {
|
|
||||||
pub const fn new(host: Hostname, repo_path: RepoPath) -> Self {
|
|
||||||
Self { host, repo_path }
|
|
||||||
}
|
|
||||||
pub const fn host(&self) -> &Hostname {
|
|
||||||
&self.host
|
|
||||||
}
|
|
||||||
pub const fn repo_path(&self) -> &RepoPath {
|
|
||||||
&self.repo_path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Display for GitRemote {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}:{}", self.host, self.repo_path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
use assert2::let_assert;
|
use assert2::let_assert;
|
||||||
|
use git_next_config::{
|
||||||
|
ForgeType, GitDir, Hostname, RepoBranches, RepoConfig, RepoConfigSource, RepoPath,
|
||||||
|
ServerRepoConfig,
|
||||||
|
};
|
||||||
|
use git_next_git::{self as git, Generation, GitRemote, Repository};
|
||||||
use gix::remote::Direction;
|
use gix::remote::Direction;
|
||||||
|
use kxio::fs;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
use crate::{gitforge::tests::common /* server::gitforge::tests::common */};
|
use crate::gitforge::tests::common;
|
||||||
|
|
||||||
use kxio::fs;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
@ -163,11 +167,11 @@ fn gitdir_should_display_as_pathbuf() {
|
||||||
// git.kemitix.net:kemitix/git-next
|
// git.kemitix.net:kemitix/git-next
|
||||||
// If the default push remote is something else, then this test will fail
|
// If the default push remote is something else, then this test will fail
|
||||||
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(RepoValidationError::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 = common::repo_details(
|
||||||
1,
|
1,
|
||||||
ServerGeneration::new(),
|
Generation::new(),
|
||||||
common::forge_details(1, ForgeType::MockForge),
|
common::forge_details(1, ForgeType::MockForge),
|
||||||
None,
|
None,
|
||||||
GitDir::new(root), // Server GitDir - should be ignored
|
GitDir::new(root), // Server GitDir - should be ignored
|
||||||
|
@ -176,7 +180,7 @@ fn repo_details_find_default_push_remote_finds_correct_remote() -> Result<()> {
|
||||||
repo_details.repo_path = RepoPath("kemitix/git-next".to_string());
|
repo_details.repo_path = RepoPath("kemitix/git-next".to_string());
|
||||||
let gitdir = &repo_details.gitdir;
|
let gitdir = &repo_details.gitdir;
|
||||||
let repository = Repository::open(gitdir)?;
|
let repository = Repository::open(gitdir)?;
|
||||||
let found_git_remote = gitdir.find_default_remote(&repository, Direction::Push)?;
|
let found_git_remote = git::validate::find_default_remote(&repository, Direction::Push)?;
|
||||||
let config_git_remote = repo_details.git_remote();
|
let config_git_remote = repo_details.git_remote();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -189,11 +193,11 @@ fn repo_details_find_default_push_remote_finds_correct_remote() -> Result<()> {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
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(RepoValidationError::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 = common::repo_details(
|
||||||
1,
|
1,
|
||||||
ServerGeneration::new(),
|
Generation::new(),
|
||||||
common::forge_details(1, ForgeType::MockForge),
|
common::forge_details(1, ForgeType::MockForge),
|
||||||
None,
|
None,
|
||||||
GitDir::new(root), // Server GitDir - should be ignored
|
GitDir::new(root), // Server GitDir - should be ignored
|
||||||
|
@ -202,18 +206,18 @@ fn gitdir_validate_should_pass_a_valid_git_repo() -> Result<()> {
|
||||||
repo_details.repo_path = RepoPath("kemitix/git-next".to_string());
|
repo_details.repo_path = RepoPath("kemitix/git-next".to_string());
|
||||||
let gitdir = &repo_details.gitdir;
|
let gitdir = &repo_details.gitdir;
|
||||||
let repository = Repository::open(gitdir)?;
|
let repository = Repository::open(gitdir)?;
|
||||||
gitdir.validate(&repository, &repo_details)?;
|
git::validate(&repository, &repo_details)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
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(RepoValidationError::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 = common::repo_details(
|
||||||
1,
|
1,
|
||||||
ServerGeneration::new(),
|
Generation::new(),
|
||||||
common::forge_details(1, ForgeType::MockForge),
|
common::forge_details(1, ForgeType::MockForge),
|
||||||
None,
|
None,
|
||||||
GitDir::new(root), // Server GitDir - should be ignored
|
GitDir::new(root), // Server GitDir - should be ignored
|
||||||
|
@ -222,7 +226,7 @@ fn gitdir_validate_should_fail_a_git_repo_with_wrong_remote() -> Result<()> {
|
||||||
repo_details.repo_path = RepoPath("hello/world".to_string());
|
repo_details.repo_path = RepoPath("hello/world".to_string());
|
||||||
let gitdir = &repo_details.gitdir;
|
let gitdir = &repo_details.gitdir;
|
||||||
let repository = Repository::open(gitdir)?;
|
let repository = Repository::open(gitdir)?;
|
||||||
let_assert!(Err(_) = gitdir.validate(&repository, &repo_details));
|
let_assert!(Err(_) = git::validate(&repository, &repo_details));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
pub mod reset;
|
|
||||||
|
|
||||||
pub use reset::reset;
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::config::{BranchName, GitDir};
|
use git_next_config::BranchName;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ForgeFileError {
|
pub enum ForgeFileError {
|
||||||
|
@ -39,45 +39,3 @@ impl std::fmt::Display for ForgeBranchError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum RepoCloneError {
|
|
||||||
InvalidGitDir(GitDir),
|
|
||||||
Io(std::io::Error),
|
|
||||||
Wait(std::io::Error),
|
|
||||||
Spawn(std::io::Error),
|
|
||||||
Validation(String),
|
|
||||||
GixClone(Box<gix::clone::Error>),
|
|
||||||
GixOpen(Box<gix::open::Error>),
|
|
||||||
GixFetch(Box<gix::clone::fetch::Error>),
|
|
||||||
}
|
|
||||||
impl std::error::Error for RepoCloneError {}
|
|
||||||
impl std::fmt::Display for RepoCloneError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::InvalidGitDir(gitdir) => write!(f, "Invalid Git dir: {:?}", gitdir),
|
|
||||||
Self::Io(err) => write!(f, "IO error: {:?}", err),
|
|
||||||
Self::Wait(err) => write!(f, "Waiting for command: {:?}", err),
|
|
||||||
Self::Spawn(err) => write!(f, "Spawning comming: {:?}", err),
|
|
||||||
Self::Validation(err) => write!(f, "Validation: {}", err),
|
|
||||||
Self::GixClone(err) => write!(f, "Clone: {:?}", err),
|
|
||||||
Self::GixOpen(err) => write!(f, "Open: {:?}", err),
|
|
||||||
Self::GixFetch(err) => write!(f, "Fetch: {:?}", err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<gix::clone::Error> for RepoCloneError {
|
|
||||||
fn from(value: gix::clone::Error) -> Self {
|
|
||||||
Self::GixClone(Box::new(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<gix::open::Error> for RepoCloneError {
|
|
||||||
fn from(value: gix::open::Error) -> Self {
|
|
||||||
Self::GixOpen(Box::new(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<gix::clone::fetch::Error> for RepoCloneError {
|
|
||||||
fn from(value: gix::clone::fetch::Error) -> Self {
|
|
||||||
Self::GixFetch(Box::new(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
|
use git_next_config::BranchName;
|
||||||
|
use git_next_git::RepoDetails;
|
||||||
use kxio::network::{self, Network};
|
use kxio::network::{self, Network};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use crate::{
|
use crate::gitforge::ForgeBranchError;
|
||||||
config::{BranchName, RepoDetails},
|
|
||||||
gitforge::{self, ForgeBranchError},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub async fn get_all(
|
pub async fn get_all(
|
||||||
repo_details: &RepoDetails,
|
repo_details: &RepoDetails,
|
||||||
net: &Network,
|
net: &Network,
|
||||||
) -> Result<Vec<gitforge::Branch>, ForgeBranchError> {
|
) -> Result<Vec<BranchName>, ForgeBranchError> {
|
||||||
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;
|
||||||
use secrecy::ExposeSecret;
|
use secrecy::ExposeSecret;
|
||||||
|
@ -37,8 +36,7 @@ pub async fn get_all(
|
||||||
.response_body()
|
.response_body()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|b| b.name())
|
.map(BranchName::from)
|
||||||
.map(gitforge::Branch)
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
Ok(branches)
|
Ok(branches)
|
||||||
}
|
}
|
||||||
|
@ -47,8 +45,8 @@ pub async fn get_all(
|
||||||
struct Branch {
|
struct Branch {
|
||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
impl Branch {
|
impl From<Branch> for BranchName {
|
||||||
fn name(&self) -> BranchName {
|
fn from(value: Branch) -> Self {
|
||||||
BranchName(self.name.clone())
|
Self(value.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
pub mod fetch;
|
|
||||||
mod get_all;
|
mod get_all;
|
||||||
mod validate_positions;
|
mod validate_positions;
|
||||||
|
|
||||||
pub use fetch::fetch;
|
|
||||||
pub use get_all::get_all;
|
pub use get_all::get_all;
|
||||||
|
|
||||||
pub use validate_positions::validate_positions;
|
pub use validate_positions::validate_positions;
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
|
use git_next_config::{BranchName, RepoConfig};
|
||||||
|
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::{
|
use crate::gitforge::{self, ForgeLike};
|
||||||
config::{BranchName, RepoConfig, RepoDetails},
|
|
||||||
gitforge::{self, ForgeLike},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, derive_more::Display)]
|
#[derive(Debug, derive_more::Display)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
@ -12,7 +11,7 @@ pub enum Error {
|
||||||
#[display("Failed to Reset Branch {branch} to {commit}")]
|
#[display("Failed to Reset Branch {branch} to {commit}")]
|
||||||
FailedToResetBranch {
|
FailedToResetBranch {
|
||||||
branch: BranchName,
|
branch: BranchName,
|
||||||
commit: gitforge::Commit,
|
commit: git::Commit,
|
||||||
},
|
},
|
||||||
BranchReset(BranchName),
|
BranchReset(BranchName),
|
||||||
BranchHasNoCommits(BranchName),
|
BranchHasNoCommits(BranchName),
|
||||||
|
@ -21,15 +20,15 @@ pub enum Error {
|
||||||
impl std::error::Error for Error {}
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
pub struct ValidatedPositions {
|
pub struct ValidatedPositions {
|
||||||
pub main: gitforge::Commit,
|
pub main: git::Commit,
|
||||||
pub next: gitforge::Commit,
|
pub next: git::Commit,
|
||||||
pub dev: gitforge::Commit,
|
pub dev: git::Commit,
|
||||||
pub dev_commit_history: Vec<gitforge::Commit>,
|
pub dev_commit_history: Vec<git::Commit>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn validate_positions(
|
pub async fn validate_positions(
|
||||||
forge: &gitforge::forgejo::ForgeJoEnv,
|
forge: &gitforge::forgejo::ForgeJoEnv,
|
||||||
repository: &gitforge::Repository,
|
repository: &git::Repository,
|
||||||
repo_config: RepoConfig,
|
repo_config: RepoConfig,
|
||||||
) -> Result<ValidatedPositions, Error> {
|
) -> Result<ValidatedPositions, Error> {
|
||||||
let repo_details = &forge.repo_details;
|
let repo_details = &forge.repo_details;
|
||||||
|
@ -78,7 +77,7 @@ pub async fn validate_positions(
|
||||||
repository,
|
repository,
|
||||||
repo_config.branches().next(),
|
repo_config.branches().next(),
|
||||||
main.into(),
|
main.into(),
|
||||||
gitforge::Force::From(next.clone().into()),
|
git::reset::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(Error::FailedToResetBranch {
|
||||||
|
@ -104,7 +103,7 @@ pub async fn validate_positions(
|
||||||
repository,
|
repository,
|
||||||
repo_config.branches().next(),
|
repo_config.branches().next(),
|
||||||
main.into(),
|
main.into(),
|
||||||
gitforge::Force::From(next.clone().into()),
|
git::reset::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(Error::FailedToResetBranch {
|
||||||
|
@ -186,11 +185,11 @@ async fn get_commit_histories(
|
||||||
|
|
||||||
#[tracing::instrument(fields(%branch_name),skip_all)]
|
#[tracing::instrument(fields(%branch_name),skip_all)]
|
||||||
async fn get_commit_history(
|
async fn get_commit_history(
|
||||||
repo_details: &crate::config::RepoDetails,
|
repo_details: &RepoDetails,
|
||||||
branch_name: &BranchName,
|
branch_name: &BranchName,
|
||||||
find_commits: Vec<gitforge::Commit>,
|
find_commits: Vec<git::Commit>,
|
||||||
net: &kxio::network::Network,
|
net: &kxio::network::Network,
|
||||||
) -> Result<Vec<gitforge::Commit>, network::NetworkError> {
|
) -> Result<Vec<git::Commit>, network::NetworkError> {
|
||||||
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;
|
||||||
|
|
||||||
|
@ -223,7 +222,7 @@ async fn get_commit_history(
|
||||||
.response_body()
|
.response_body()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(gitforge::Commit::from)
|
.map(git::Commit::from)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let found = find_commits.is_empty()
|
let found = find_commits.is_empty()
|
||||||
|
@ -253,7 +252,7 @@ struct Commit {
|
||||||
struct RepoCommit {
|
struct RepoCommit {
|
||||||
message: String,
|
message: String,
|
||||||
}
|
}
|
||||||
impl From<Commit> for gitforge::Commit {
|
impl From<Commit> for git::Commit {
|
||||||
fn from(value: Commit) -> Self {
|
fn from(value: Commit) -> Self {
|
||||||
Self::new(&value.sha, &value.commit.message)
|
Self::new(&value.sha, &value.commit.message)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
|
use git_next_config::BranchName;
|
||||||
|
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::{
|
use crate::gitforge::ForgeFileError;
|
||||||
config::{BranchName, RepoDetails},
|
|
||||||
gitforge::ForgeFileError,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub(super) async fn contents_get(
|
pub(super) async fn contents_get(
|
||||||
repo_details: &RepoDetails,
|
repo_details: &RepoDetails,
|
||||||
|
|
|
@ -5,15 +5,15 @@ use std::time::Duration;
|
||||||
|
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
|
|
||||||
|
use git_next_config::{BranchName, GitDir, RepoConfig};
|
||||||
|
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::{
|
||||||
actors::repo::{RepoActor, StartMonitoring, ValidateRepo},
|
actors::repo::{RepoActor, StartMonitoring, ValidateRepo},
|
||||||
config::{BranchName, GitDir, RepoConfig, RepoDetails},
|
gitforge::{self, forgejo::branch::ValidatedPositions},
|
||||||
git,
|
types::MessageToken,
|
||||||
gitforge::{self, forgejo::branch::ValidatedPositions, RepoCloneError, Repository},
|
|
||||||
types::{GitRef, MessageToken},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ForgeJo;
|
struct ForgeJo;
|
||||||
|
@ -33,7 +33,7 @@ impl super::ForgeLike for ForgeJoEnv {
|
||||||
"forgejo".to_string()
|
"forgejo".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn branches_get_all(&self) -> Result<Vec<super::Branch>, gitforge::ForgeBranchError> {
|
async fn branches_get_all(&self) -> Result<Vec<BranchName>, gitforge::ForgeBranchError> {
|
||||||
branch::get_all(&self.repo_details, &self.net).await
|
branch::get_all(&self.repo_details, &self.net).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ impl super::ForgeLike for ForgeJoEnv {
|
||||||
|
|
||||||
async fn branches_validate_positions(
|
async fn branches_validate_positions(
|
||||||
&self,
|
&self,
|
||||||
repository: Repository,
|
repository: git::Repository,
|
||||||
repo_config: RepoConfig,
|
repo_config: RepoConfig,
|
||||||
addr: Addr<RepoActor>,
|
addr: Addr<RepoActor>,
|
||||||
message_token: MessageToken,
|
message_token: MessageToken,
|
||||||
|
@ -76,12 +76,12 @@ impl super::ForgeLike for ForgeJoEnv {
|
||||||
|
|
||||||
fn branch_reset(
|
fn branch_reset(
|
||||||
&self,
|
&self,
|
||||||
repository: &Repository,
|
repository: &git::Repository,
|
||||||
branch_name: BranchName,
|
branch_name: BranchName,
|
||||||
to_commit: GitRef,
|
to_commit: GitRef,
|
||||||
force: gitforge::Force,
|
force: git::reset::Force,
|
||||||
) -> gitforge::BranchResetResult {
|
) -> git::reset::Result {
|
||||||
branch::fetch(repository, &self.repo_details)?;
|
git::fetch(repository, &self.repo_details)?;
|
||||||
git::reset(
|
git::reset(
|
||||||
repository,
|
repository,
|
||||||
&self.repo_details,
|
&self.repo_details,
|
||||||
|
@ -91,7 +91,7 @@ impl super::ForgeLike for ForgeJoEnv {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn commit_status(&self, commit: &gitforge::Commit) -> gitforge::CommitStatus {
|
async fn commit_status(&self, commit: &git::Commit) -> gitforge::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;
|
||||||
|
@ -135,17 +135,16 @@ impl super::ForgeLike for ForgeJoEnv {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn repo_clone(&self, gitdir: GitDir) -> Result<Repository, RepoCloneError> {
|
fn repo_clone(&self, gitdir: GitDir) -> Result<Repository, git::repository::Error> {
|
||||||
let repository = if !gitdir.exists() {
|
let repository = if !gitdir.exists() {
|
||||||
info!("Local copy not found - cloning...");
|
info!("Local copy not found - cloning...");
|
||||||
Repository::clone(&self.repo_details)?
|
Repository::clone(&self.repo_details)?
|
||||||
} else {
|
} else {
|
||||||
Repository::open(gitdir.clone())?
|
Repository::open(gitdir)?
|
||||||
};
|
};
|
||||||
info!("Validating...");
|
info!("Validating...");
|
||||||
gitdir
|
git::validate(&repository, &self.repo_details)
|
||||||
.validate(&repository, &self.repo_details)
|
.map_err(|e| git::repository::Error::Validation(e.to_string()))
|
||||||
.map_err(|e| RepoCloneError::Validation(e.to_string()))
|
|
||||||
.inspect(|_| info!("Validation - OK"))?;
|
.inspect(|_| info!("Validation - OK"))?;
|
||||||
Ok(repository)
|
Ok(repository)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
use crate::{
|
use git_next_config::{BranchName, GitDir, RepoConfig};
|
||||||
actors::repo::RepoActor,
|
use git_next_git::{self as git, GitRef, Repository};
|
||||||
config::{BranchName, GitDir, RepoConfig},
|
|
||||||
gitforge::{self, RepoCloneError, Repository},
|
use crate::{actors::repo::RepoActor, gitforge, types::MessageToken};
|
||||||
types::{GitRef, MessageToken},
|
|
||||||
};
|
|
||||||
|
|
||||||
struct MockForge;
|
struct MockForge;
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -19,7 +17,7 @@ impl super::ForgeLike for MockForgeEnv {
|
||||||
"mock".to_string()
|
"mock".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn branches_get_all(&self) -> Result<Vec<super::Branch>, gitforge::ForgeBranchError> {
|
async fn branches_get_all(&self) -> Result<Vec<BranchName>, gitforge::ForgeBranchError> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,16 +44,16 @@ impl super::ForgeLike for MockForgeEnv {
|
||||||
_repository: &Repository,
|
_repository: &Repository,
|
||||||
_branch_name: BranchName,
|
_branch_name: BranchName,
|
||||||
_to_commit: GitRef,
|
_to_commit: GitRef,
|
||||||
_force: gitforge::Force,
|
_force: git::reset::Force,
|
||||||
) -> gitforge::BranchResetResult {
|
) -> git::reset::Result {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn commit_status(&self, _commit: &gitforge::Commit) -> gitforge::CommitStatus {
|
async fn commit_status(&self, _commit: &git::Commit) -> gitforge::CommitStatus {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn repo_clone(&self, _gitdir: GitDir) -> Result<Repository, RepoCloneError> {
|
fn repo_clone(&self, _gitdir: GitDir) -> Result<Repository, git::repository::Error> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use git_next_config::{BranchName, GitDir, RepoConfig};
|
||||||
|
use git_next_git::{self as git, GitRef, RepoDetails, Repository};
|
||||||
use kxio::network::Network;
|
use kxio::network::Network;
|
||||||
|
|
||||||
#[cfg(feature = "forgejo")]
|
#[cfg(feature = "forgejo")]
|
||||||
|
@ -16,22 +18,19 @@ pub use types::*;
|
||||||
mod errors;
|
mod errors;
|
||||||
pub use errors::*;
|
pub use errors::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::types::MessageToken;
|
||||||
config::{BranchName, GitDir, RepoConfig, RepoDetails},
|
|
||||||
types::{GitRef, MessageToken},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait ForgeLike {
|
pub trait ForgeLike {
|
||||||
fn name(&self) -> String;
|
fn name(&self) -> String;
|
||||||
|
|
||||||
/// Returns a list of all branches in the repo.
|
/// Returns a list of all branches in the repo.
|
||||||
async fn branches_get_all(&self) -> Result<Vec<Branch>, ForgeBranchError>;
|
async fn branches_get_all(&self) -> Result<Vec<BranchName>, ForgeBranchError>;
|
||||||
|
|
||||||
/// Returns the contents of the file.
|
/// Returns the contents of the file.
|
||||||
async fn file_contents_get(
|
async fn file_contents_get(
|
||||||
&self,
|
&self,
|
||||||
branch: &super::config::BranchName,
|
branch: &BranchName,
|
||||||
file_path: &str,
|
file_path: &str,
|
||||||
) -> Result<String, ForgeFileError>;
|
) -> Result<String, ForgeFileError>;
|
||||||
|
|
||||||
|
@ -51,14 +50,14 @@ pub trait ForgeLike {
|
||||||
repository: &Repository,
|
repository: &Repository,
|
||||||
branch_name: BranchName,
|
branch_name: BranchName,
|
||||||
to_commit: GitRef,
|
to_commit: GitRef,
|
||||||
force: Force,
|
force: git::reset::Force,
|
||||||
) -> BranchResetResult;
|
) -> git::reset::Result;
|
||||||
|
|
||||||
/// 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: &Commit) -> CommitStatus;
|
async fn commit_status(&self, commit: &git::Commit) -> CommitStatus;
|
||||||
|
|
||||||
/// Clones a repo to disk.
|
/// Clones a repo to disk.
|
||||||
fn repo_clone(&self, gitdir: GitDir) -> Result<Repository, RepoCloneError>;
|
fn repo_clone(&self, gitdir: GitDir) -> Result<Repository, git::repository::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
use crate::{
|
use git_next_config::{
|
||||||
config::{
|
ApiToken, BranchName, ForgeDetails, ForgeName, ForgeType, GitDir, Hostname, RepoAlias,
|
||||||
ApiToken, BranchName, ForgeDetails, ForgeName, ForgeType, GitDir, Hostname, RepoAlias,
|
RepoBranches, RepoConfig, RepoConfigSource, RepoPath, User,
|
||||||
RepoBranches, RepoConfig, RepoConfigSource, RepoDetails, RepoPath, User,
|
|
||||||
},
|
|
||||||
types::ServerGeneration,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 {
|
ForgeDetails {
|
||||||
forge_name: forge_name(n),
|
forge_name: forge_name(n),
|
||||||
|
@ -33,7 +32,7 @@ pub fn forge_name(n: u32) -> ForgeName {
|
||||||
}
|
}
|
||||||
pub fn repo_details(
|
pub fn repo_details(
|
||||||
n: u32,
|
n: u32,
|
||||||
generation: ServerGeneration,
|
generation: Generation,
|
||||||
forge: ForgeDetails,
|
forge: ForgeDetails,
|
||||||
repo_config: Option<RepoConfig>,
|
repo_config: Option<RepoConfig>,
|
||||||
gitdir: GitDir,
|
gitdir: GitDir,
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
use assert2::let_assert;
|
use assert2::let_assert;
|
||||||
|
|
||||||
|
use git_next_config::{ForgeType, RepoConfigSource};
|
||||||
use kxio::network::{MockNetwork, StatusCode};
|
use kxio::network::{MockNetwork, StatusCode};
|
||||||
|
|
||||||
use crate::{
|
use git_next_git::Generation;
|
||||||
config::{BranchName, ForgeType, RepoConfigSource},
|
|
||||||
types::ServerGeneration,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
@ -17,7 +15,7 @@ fn test_name() {
|
||||||
let net = Network::new_mock();
|
let net = Network::new_mock();
|
||||||
let repo_details = common::repo_details(
|
let repo_details = common::repo_details(
|
||||||
1,
|
1,
|
||||||
ServerGeneration::new(),
|
Generation::new(),
|
||||||
common::forge_details(1, ForgeType::MockForge),
|
common::forge_details(1, ForgeType::MockForge),
|
||||||
Some(common::repo_config(1, RepoConfigSource::Repo)),
|
Some(common::repo_config(1, RepoConfigSource::Repo)),
|
||||||
GitDir::new(fs.base()),
|
GitDir::new(fs.base()),
|
||||||
|
@ -44,7 +42,7 @@ async fn test_branches_get() {
|
||||||
|
|
||||||
let repo_details = common::repo_details(
|
let repo_details = common::repo_details(
|
||||||
1,
|
1,
|
||||||
ServerGeneration::new(),
|
Generation::new(),
|
||||||
common::forge_details(1, ForgeType::MockForge),
|
common::forge_details(1, ForgeType::MockForge),
|
||||||
Some(common::repo_config(1, RepoConfigSource::Repo)),
|
Some(common::repo_config(1, RepoConfigSource::Repo)),
|
||||||
GitDir::new(fs.base()),
|
GitDir::new(fs.base()),
|
||||||
|
@ -57,5 +55,5 @@ async fn test_branches_get() {
|
||||||
let_assert!(Some(requests) = net.mocked_requests());
|
let_assert!(Some(requests) = net.mocked_requests());
|
||||||
assert_eq!(requests.len(), 1);
|
assert_eq!(requests.len(), 1);
|
||||||
|
|
||||||
assert_eq!(branches, vec![Branch(BranchName("string".into()))]);
|
assert_eq!(branches, vec![BranchName("string".into())]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +1,4 @@
|
||||||
use std::{ops::Deref as _, path::PathBuf, sync::atomic::AtomicBool};
|
use git_next_git as git;
|
||||||
|
|
||||||
use crate::{
|
|
||||||
config::{BranchName, RepoDetails},
|
|
||||||
gitforge::RepoCloneError,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
pub struct Branch(pub BranchName);
|
|
||||||
impl Branch {
|
|
||||||
pub const fn name(&self) -> &BranchName {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Force {
|
|
||||||
No,
|
|
||||||
From(crate::types::GitRef),
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for Force {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::No => write!(f, "fast-foward"),
|
|
||||||
Self::From(from) => write!(f, "force-if-from:{}", from),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, derive_more::From, derive_more::Display)]
|
|
||||||
pub enum BranchResetError {
|
|
||||||
Open(Box<gix::open::Error>),
|
|
||||||
Fetch(crate::gitforge::forgejo::branch::fetch::Error),
|
|
||||||
Push,
|
|
||||||
}
|
|
||||||
impl std::error::Error for BranchResetError {}
|
|
||||||
|
|
||||||
pub type BranchResetResult = Result<(), BranchResetError>;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum CommitStatus {
|
pub enum CommitStatus {
|
||||||
|
@ -46,82 +9,7 @@ pub enum CommitStatus {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct CommitHistories {
|
pub struct CommitHistories {
|
||||||
pub main: Vec<Commit>,
|
pub main: Vec<git::Commit>,
|
||||||
pub next: Vec<Commit>,
|
pub next: Vec<git::Commit>,
|
||||||
pub dev: Vec<Commit>,
|
pub dev: Vec<git::Commit>,
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Commit {
|
|
||||||
sha: Sha,
|
|
||||||
message: Message,
|
|
||||||
}
|
|
||||||
impl Commit {
|
|
||||||
pub fn new(sha: &str, message: &str) -> Self {
|
|
||||||
Self {
|
|
||||||
sha: Sha::new(sha.to_string()),
|
|
||||||
message: Message::new(message.to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub const fn sha(&self) -> &Sha {
|
|
||||||
&self.sha
|
|
||||||
}
|
|
||||||
pub const fn message(&self) -> &Message {
|
|
||||||
&self.message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for Commit {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.sha)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Sha(String);
|
|
||||||
impl Sha {
|
|
||||||
pub const fn new(value: String) -> Self {
|
|
||||||
Self(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for Sha {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Message(String);
|
|
||||||
impl Message {
|
|
||||||
pub const fn new(value: String) -> Self {
|
|
||||||
Self(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for Message {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, derive_more::From)]
|
|
||||||
pub struct Repository(gix::ThreadSafeRepository);
|
|
||||||
impl Repository {
|
|
||||||
pub fn open(gitdir: impl Into<PathBuf>) -> Result<Self, RepoCloneError> {
|
|
||||||
Ok(Self(gix::ThreadSafeRepository::open(gitdir.into())?))
|
|
||||||
}
|
|
||||||
pub fn clone(repo_details: &RepoDetails) -> Result<Self, RepoCloneError> {
|
|
||||||
use secrecy::ExposeSecret;
|
|
||||||
let origin = repo_details.origin();
|
|
||||||
let (repository, _outcome) =
|
|
||||||
gix::prepare_clone_bare(origin.expose_secret().as_str(), repo_details.gitdir.deref())?
|
|
||||||
.fetch_only(gix::progress::Discard, &AtomicBool::new(false))?;
|
|
||||||
|
|
||||||
Ok(repository.into_sync().into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl std::ops::Deref for Repository {
|
|
||||||
type Target = gix::ThreadSafeRepository;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
mod actors;
|
mod actors;
|
||||||
mod config;
|
mod config;
|
||||||
pub mod git;
|
|
||||||
pub mod gitforge;
|
pub mod gitforge;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,3 @@
|
||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
use crate::{config::BranchName, gitforge};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
|
||||||
pub struct GitRef(pub String);
|
|
||||||
impl From<gitforge::Commit> for GitRef {
|
|
||||||
fn from(value: gitforge::Commit) -> Self {
|
|
||||||
Self(value.sha().to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<BranchName> for GitRef {
|
|
||||||
fn from(value: BranchName) -> Self {
|
|
||||||
Self(value.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Display for GitRef {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct MessageToken(u32);
|
pub struct MessageToken(u32);
|
||||||
impl MessageToken {
|
impl MessageToken {
|
||||||
|
@ -35,19 +13,3 @@ impl std::fmt::Display for MessageToken {
|
||||||
write!(f, "{}", self.0)
|
write!(f, "{}", self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub struct ServerGeneration(u32);
|
|
||||||
impl ServerGeneration {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
pub fn inc(&mut self) {
|
|
||||||
self.0 += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for ServerGeneration {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue