fix(gitforge): use local repo or clone to allow git push

The `git push` command requires a git directory (bare at a minimum)
before it can work.
The git dir must also be up-to-date with the relevant references from
the remote, so
we do a `git fetch` before a `git push`.

Closes kemitix/git-next#51
This commit is contained in:
Paul Campbell 2024-05-03 17:50:50 +01:00
parent d42c8fb890
commit 5ba5a126c3
10 changed files with 253 additions and 73 deletions

View file

@ -1,6 +1,13 @@
use std::time::Duration;
use actix::prelude::*;
use tracing::{info, warn}; use tracing::{info, warn};
use crate::server::{config, gitforge}; use crate::server::{
actors::repo::{RepoActor, ValidateRepo},
config, 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)]
@ -9,6 +16,7 @@ pub async fn advance_next(
dev_commit_history: Vec<gitforge::Commit>, dev_commit_history: Vec<gitforge::Commit>,
repo_config: config::RepoConfig, repo_config: config::RepoConfig,
forge: gitforge::Forge, forge: gitforge::Forge,
addr: Addr<super::RepoActor>,
) { ) {
let next_commit = find_next_commit_on_dev(next, dev_commit_history); let next_commit = find_next_commit_on_dev(next, dev_commit_history);
let Some(commit) = next_commit else { let Some(commit) = next_commit else {
@ -27,6 +35,8 @@ pub async fn advance_next(
) { ) {
warn!(?err, "Failed") warn!(?err, "Failed")
} }
tokio::time::sleep(Duration::from_secs(10)).await;
addr.do_send(ValidateRepo)
} }
#[tracing::instrument] #[tracing::instrument]
@ -67,6 +77,7 @@ pub async fn advance_main(
next: gitforge::Commit, next: gitforge::Commit,
repo_config: config::RepoConfig, repo_config: config::RepoConfig,
forge: gitforge::Forge, forge: gitforge::Forge,
addr: Addr<RepoActor>,
) { ) {
info!("Advancing main to next"); info!("Advancing main to next");
if let Err(err) = forge.branch_reset( if let Err(err) = forge.branch_reset(
@ -76,6 +87,7 @@ pub async fn advance_main(
) { ) {
warn!(?err, "Failed") warn!(?err, "Failed")
}; };
addr.do_send(ValidateRepo)
} }
#[cfg(test)] #[cfg(test)]

View file

@ -65,18 +65,28 @@ impl Actor for RepoActor {
} }
} }
} }
impl std::fmt::Display for RepoActor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"RepoActor: {}/{}",
self.details.forge.forge_name, self.details.repo_alias
)
}
}
#[derive(Message)] #[derive(Message)]
#[rtype(result = "()")] #[rtype(result = "()")]
pub struct CloneRepo; pub struct CloneRepo;
impl Handler<CloneRepo> for RepoActor { impl Handler<CloneRepo> for RepoActor {
type Result = (); type Result = ();
#[tracing::instrument(skip_all, fields(%self))]
fn handle(&mut self, _msg: CloneRepo, ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, _msg: CloneRepo, ctx: &mut Self::Context) -> Self::Result {
info!(%self.details, "Clone/Update Repo"); info!(%self.details, "Clone/Update Repo");
let gitdir = self.details.gitdir.clone(); let gitdir = self.details.gitdir.clone();
match self.forge.repo_clone(gitdir) { match self.forge.repo_clone(gitdir) {
Ok(_) => ctx.address().do_send(LoadConfigFromRepo), Ok(_) => ctx.address().do_send(LoadConfigFromRepo),
Err(err) => warn!(?err, "Could not Clone repo"), Err(err) => warn!("Could not Clone repo: {err}"),
} }
} }
} }
@ -86,6 +96,7 @@ impl Handler<CloneRepo> for RepoActor {
pub struct LoadConfigFromRepo; pub struct LoadConfigFromRepo;
impl Handler<LoadConfigFromRepo> for RepoActor { impl Handler<LoadConfigFromRepo> for RepoActor {
type Result = (); type Result = ();
#[tracing::instrument(skip_all, fields(%self))]
fn handle(&mut self, _msg: LoadConfigFromRepo, ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, _msg: LoadConfigFromRepo, ctx: &mut Self::Context) -> Self::Result {
info!(%self.details, "Loading .git-next.toml from repo"); info!(%self.details, "Loading .git-next.toml from repo");
let details = self.details.clone(); let details = self.details.clone();
@ -102,6 +113,7 @@ impl Handler<LoadConfigFromRepo> for RepoActor {
struct LoadedConfig(pub RepoConfig); struct LoadedConfig(pub RepoConfig);
impl Handler<LoadedConfig> for RepoActor { impl Handler<LoadedConfig> for RepoActor {
type Result = (); type Result = ();
#[tracing::instrument(skip_all, fields(%self))]
fn handle(&mut self, msg: LoadedConfig, ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, msg: LoadedConfig, ctx: &mut Self::Context) -> Self::Result {
let repo_config = msg.0; let repo_config = msg.0;
info!(%self.details, %repo_config, "Config loaded"); info!(%self.details, %repo_config, "Config loaded");
@ -125,7 +137,9 @@ impl Handler<LoadedConfig> for RepoActor {
pub struct ValidateRepo; pub struct ValidateRepo;
impl Handler<ValidateRepo> for RepoActor { impl Handler<ValidateRepo> for RepoActor {
type Result = (); type Result = ();
#[tracing::instrument(skip_all, fields(%self))]
fn handle(&mut self, _msg: ValidateRepo, ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, _msg: ValidateRepo, ctx: &mut Self::Context) -> Self::Result {
info!("ValidateRepo");
if let Some(repo_config) = self.details.repo_config.clone() { if let Some(repo_config) = self.details.repo_config.clone() {
let forge = self.forge.clone(); let forge = self.forge.clone();
let addr = ctx.address(); let addr = ctx.address();
@ -146,7 +160,9 @@ pub struct StartMonitoring {
} }
impl Handler<StartMonitoring> for RepoActor { impl Handler<StartMonitoring> for RepoActor {
type Result = (); type Result = ();
#[tracing::instrument(skip_all, fields(%self))]
fn handle(&mut self, msg: StartMonitoring, ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, msg: StartMonitoring, ctx: &mut Self::Context) -> Self::Result {
info!("StartMonitoring");
let Some(repo_config) = self.details.repo_config.clone() else { let Some(repo_config) = self.details.repo_config.clone() else {
warn!("No config loaded"); warn!("No config loaded");
return; return;
@ -167,7 +183,7 @@ impl Handler<StartMonitoring> for RepoActor {
.into_actor(self) .into_actor(self)
.wait(ctx); .wait(ctx);
} else if dev_ahead_of_next { } else if dev_ahead_of_next {
branch::advance_next(msg.next, msg.dev_commit_history, repo_config, forge) branch::advance_next(msg.next, msg.dev_commit_history, repo_config, forge, addr)
.into_actor(self) .into_actor(self)
.wait(ctx); .wait(ctx);
} else if self.webhook_id.is_none() { } else if self.webhook_id.is_none() {
@ -183,6 +199,7 @@ impl Handler<StartMonitoring> for RepoActor {
pub struct WebhookRegistered(pub WebhookId, pub WebhookAuth); pub struct WebhookRegistered(pub WebhookId, pub WebhookAuth);
impl Handler<WebhookRegistered> for RepoActor { impl Handler<WebhookRegistered> for RepoActor {
type Result = (); type Result = ();
#[tracing::instrument(skip_all, fields(%self))]
fn handle(&mut self, msg: WebhookRegistered, _ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, msg: WebhookRegistered, _ctx: &mut Self::Context) -> Self::Result {
self.webhook_id.replace(msg.0); self.webhook_id.replace(msg.0);
self.webhook_auth.replace(msg.1); self.webhook_auth.replace(msg.1);
@ -194,13 +211,15 @@ impl Handler<WebhookRegistered> for RepoActor {
pub struct AdvanceMainTo(pub gitforge::Commit); pub struct AdvanceMainTo(pub gitforge::Commit);
impl Handler<AdvanceMainTo> for RepoActor { impl Handler<AdvanceMainTo> for RepoActor {
type Result = (); type Result = ();
#[tracing::instrument(skip_all, fields(%self))]
fn handle(&mut self, msg: AdvanceMainTo, ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, msg: AdvanceMainTo, ctx: &mut Self::Context) -> Self::Result {
let Some(repo_config) = self.details.repo_config.clone() else { let Some(repo_config) = self.details.repo_config.clone() else {
warn!("No config loaded"); warn!("No config loaded");
return; return;
}; };
let forge = self.forge.clone(); let forge = self.forge.clone();
branch::advance_main(msg.0, repo_config, forge) let addr = ctx.address();
branch::advance_main(msg.0, repo_config, forge, addr)
.into_actor(self) .into_actor(self)
.wait(ctx); .wait(ctx);
} }

View file

@ -10,6 +10,7 @@ use std::{
use serde::Deserialize; use serde::Deserialize;
use kxio::fs::FileSystem; use kxio::fs::FileSystem;
use tracing::info;
#[derive(Debug, derive_more::From, derive_more::Display)] #[derive(Debug, derive_more::From, derive_more::Display)]
pub enum Error { pub enum Error {
@ -203,6 +204,11 @@ impl ServerRepoConfig {
pub fn branch(&self) -> BranchName { pub fn branch(&self) -> BranchName {
BranchName(self.branch.clone()) 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 /// Returns a RepoConfig from the server configuration if ALL THREE branches were provided
pub fn repo_config(&self) -> Option<RepoConfig> { pub fn repo_config(&self) -> Option<RepoConfig> {
match (&self.main, &self.next, &self.dev) { match (&self.main, &self.next, &self.dev) {
@ -350,22 +356,14 @@ pub struct RepoDetails {
} }
impl RepoDetails { impl RepoDetails {
pub fn new( pub fn new(
name: &RepoAlias, repo_alias: &RepoAlias,
server_repo_config: &ServerRepoConfig, server_repo_config: &ServerRepoConfig,
forge_name: &ForgeName, forge_name: &ForgeName,
forge_config: &ForgeConfig, forge_config: &ForgeConfig,
server_storage: &ServerStorage, gitdir: GitDir,
) -> Result<Self> { ) -> Self {
let path_buf = server_repo_config.gitdir.clone().unwrap_or_else(|| { Self {
server_storage repo_alias: repo_alias.clone(),
.path
.join(forge_name.to_string())
.join(name.to_string())
});
let path_buf = std::fs::canonicalize(path_buf)?;
let gitdir = GitDir(path_buf);
Ok(Self {
repo_alias: name.clone(),
repo_path: RepoPath(server_repo_config.repo.clone()), repo_path: RepoPath(server_repo_config.repo.clone()),
repo_config: server_repo_config.repo_config(), repo_config: server_repo_config.repo_config(),
branch: BranchName(server_repo_config.branch.clone()), branch: BranchName(server_repo_config.branch.clone()),
@ -377,7 +375,7 @@ impl RepoDetails {
user: forge_config.user(), user: forge_config.user(),
token: forge_config.token(), token: forge_config.token(),
}, },
}) }
} }
pub fn origin(&self) -> secrecy::Secret<String> { pub fn origin(&self) -> secrecy::Secret<String> {
let repo_details = self; let repo_details = self;
@ -396,27 +394,6 @@ impl RepoDetails {
self.gitdir.validate(self) self.gitdir.validate(self)
} }
pub fn find_default_push_remote(&self) -> ValidationResult<GitRemote> {
let repository = gix::open(self.gitdir.clone())
.map_err(|e| RepoValidationError::UnableToOpenRepo(e.to_string()))?;
let Some(Ok(remote)) = repository.find_default_remote(gix::remote::Direction::Push) else {
return Err(RepoValidationError::NoDefaultPushRemote);
};
let Some(url) = remote.url(gix::remote::Direction::Push) 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);
Ok(GitRemote::new(
Hostname(host.to_string()),
RepoPath(path.to_string()),
))
}
pub fn git_remote(&self) -> GitRemote { pub fn git_remote(&self) -> GitRemote {
GitRemote::new(self.forge.hostname.clone(), self.repo_path.clone()) GitRemote::new(self.forge.hostname.clone(), self.repo_path.clone())
} }
@ -447,7 +424,11 @@ pub enum RepoValidationError {
Io(std::io::Error), Io(std::io::Error),
MismatchDefaultPushRemote { MismatchDefaultPushRemote {
found: GitRemote, found: GitRemote,
configured: GitRemote, expected: GitRemote,
},
MismatchDefaultFetchRemote {
found: GitRemote,
expected: GitRemote,
}, },
} }
impl std::error::Error for RepoValidationError {} impl std::error::Error for RepoValidationError {}
@ -461,8 +442,14 @@ impl Display for RepoValidationError {
} }
Self::UnableToOpenRepo(err) => write!(f, "Unable to open the git dir: {err}"), Self::UnableToOpenRepo(err) => write!(f, "Unable to open the git dir: {err}"),
Self::Io(err) => write!(f, "IO Error: {err:?}"), Self::Io(err) => write!(f, "IO Error: {err:?}"),
Self::MismatchDefaultPushRemote{found, configured} => Self::MismatchDefaultPushRemote { found, expected } => write!(
write!(f, "The default push remote for the git dir doesn't match the configuration: found: {found}, expected: {configured}") 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}"
),
} }
} }
} }
@ -490,6 +477,7 @@ pub struct GitDir(PathBuf);
impl GitDir { impl GitDir {
#[cfg(test)] #[cfg(test)]
pub(crate) fn new(pathbuf: &std::path::Path) -> Self { pub(crate) fn new(pathbuf: &std::path::Path) -> Self {
info!("GitDir::new({pathbuf:?})");
Self(pathbuf.to_path_buf()) Self(pathbuf.to_path_buf())
} }
@ -498,13 +486,53 @@ impl GitDir {
} }
pub fn validate(&self, repo_details: &RepoDetails) -> ValidationResult<()> { pub fn validate(&self, repo_details: &RepoDetails) -> ValidationResult<()> {
let configured = repo_details.git_remote(); let git_remote = repo_details.git_remote();
let found = repo_details.find_default_push_remote()?; use gix::remote::Direction;
if configured != found { let push_remote = self.find_default_remote(Direction::Push)?;
return Err(RepoValidationError::MismatchDefaultPushRemote { found, configured }); let fetch_remote = self.find_default_remote(Direction::Fetch)?;
info!(gitdir = %self, ?git_remote, ?push_remote, ?fetch_remote, "Gitdir::validate");
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(()) Ok(())
} }
#[tracing::instrument]
fn find_default_remote(
&self,
direction: gix::remote::Direction,
) -> ValidationResult<GitRemote> {
let repository = gix::ThreadSafeRepository::open(self.deref())
.map_err(|e| RepoValidationError::UnableToOpenRepo(e.to_string()))?
.to_thread_local();
info!(?repository, from = ?self.deref(), "gix::discover");
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 { impl Deref for GitDir {
type Target = PathBuf; type Target = PathBuf;
@ -520,9 +548,16 @@ impl std::fmt::Display for GitDir {
} }
impl From<&str> for GitDir { impl From<&str> for GitDir {
fn from(value: &str) -> Self { fn from(value: &str) -> Self {
info!("GitDir::from::<&str>({value:?})");
Self(value.into()) Self(value.into())
} }
} }
impl From<PathBuf> for GitDir {
fn from(value: PathBuf) -> Self {
info!("GitDir::from::<PathBuf>({value:?})");
Self(value)
}
}
impl From<GitDir> for PathBuf { impl From<GitDir> for PathBuf {
fn from(value: GitDir) -> Self { fn from(value: GitDir) -> Self {
value.0 value.0

View file

@ -1,4 +1,5 @@
use assert2::let_assert; use assert2::let_assert;
use gix::remote::Direction;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use crate::{server::gitforge::tests::common /* server::gitforge::tests::common */}; use crate::{server::gitforge::tests::common /* server::gitforge::tests::common */};
@ -162,7 +163,8 @@ fn repo_details_find_default_push_remote_finds_correct_remote() -> Result<()> {
); );
repo_details.forge.hostname = Hostname("git.kemitix.net".to_string()); repo_details.forge.hostname = Hostname("git.kemitix.net".to_string());
repo_details.repo_path = RepoPath("kemitix/git-next".to_string()); repo_details.repo_path = RepoPath("kemitix/git-next".to_string());
let found_git_remote = repo_details.find_default_push_remote()?; let gitdir = &repo_details.gitdir;
let found_git_remote = gitdir.find_default_remote(Direction::Push)?;
let config_git_remote = repo_details.git_remote(); let config_git_remote = repo_details.git_remote();
assert_eq!( assert_eq!(

View file

@ -0,0 +1,72 @@
use std::ops::Deref;
use tracing::info;
use crate::server::config::RepoDetails;
#[derive(Debug, derive_more::From, derive_more::Display)]
pub enum Error {
UnableToOpenRepo(Box<gix::open::Error>),
NoFetchRemoteFound,
Connect(Box<gix::remote::connect::Error>),
Fetch(String),
}
impl std::error::Error for Error {}
#[tracing::instrument]
pub fn fetch(repo_details: &RepoDetails) -> Result<(), Error> {
// INFO: gitdir validate tests that the default fetch remote matches the configured remote
let repository = gix::ThreadSafeRepository::open(repo_details.gitdir.deref())
.map_err(Box::new)?
.to_thread_local();
info!(?repository, "opened repo");
let Some(remote) = repository.find_default_remote(gix::remote::Direction::Fetch) else {
return Err(Error::NoFetchRemoteFound);
};
info!(?remote, "fetch remote");
remote
.map_err(|e| Error::Fetch(e.to_string()))?
.connect(gix::remote::Direction::Fetch)
.map_err(Box::new)?
.prepare_fetch(gix::progress::Discard, Default::default())
.map_err(|e| Error::Fetch(e.to_string()))?
.receive(gix::progress::Discard, &Default::default())
.map_err(|e| Error::Fetch(e.to_string()))?;
info!("fetched");
Ok(())
// INFO: never log the command as it contains the API token within the 'origin'
// let command: secrecy::Secret<String> = format!(
// "/usr/bin/git push {} {to_commit}:{branch_name} {force}",
// origin.expose_secret()
// )
// .into();
// info!("Resetting {branch_name} to {to_commit}");
// let ctx = gix::diff::command::Context {
// git_dir: Some(repo_details.gitdir.deref().clone()),
// ..Default::default()
// };
// // info!(?ctx, command = command.expose_secret(), "prepare");
// match gix::command::prepare(command.expose_secret())
// .with_context(ctx)
// .with_shell_allow_argument_splitting()
// // .stdout(std::process::Stdio::null())
// // .stderr(std::process::Stdio::null())
// .spawn()
// {
// Ok(mut child) => match child.wait() {
// Ok(_) => Ok(()),
// Err(err) => {
// warn!(?err, "Failed (wait)");
// Err(())
// }
// },
// Err(err) => {
// warn!(?err, "Failed (spawn)");
// Err(())
// }
// }
}

View file

@ -1,7 +1,9 @@
pub mod fetch;
mod get_all; mod get_all;
mod reset; mod reset;
mod validate_positions; mod validate_positions;
pub use fetch::fetch;
pub use get_all::get_all; pub use get_all::get_all;
pub use reset::reset; pub use reset::reset;
pub use validate_positions::validate_positions; pub use validate_positions::validate_positions;

View file

@ -1,18 +1,26 @@
use std::ops::Deref;
use secrecy::ExposeSecret; use secrecy::ExposeSecret;
use tracing::{info, warn}; use tracing::{info, warn};
use crate::server::{ use crate::server::{
config::{BranchName, RepoDetails}, config::{BranchName, RepoDetails},
gitforge::{BranchResetResult, Force}, gitforge::{BranchResetError, BranchResetResult, Force},
types::GitRef, types::GitRef,
}; };
// TODO: (#72) reimplement using `gix`
#[tracing::instrument]
pub fn reset( pub fn reset(
repo_details: &RepoDetails, repo_details: &RepoDetails,
branch_name: BranchName, branch_name: BranchName,
to_commit: GitRef, to_commit: GitRef,
force: Force, force: Force,
) -> BranchResetResult { ) -> BranchResetResult {
let repository = gix::ThreadSafeRepository::open(repo_details.gitdir.deref())
.map_err(Box::new)?
.to_thread_local();
let gitdir = repository.git_dir();
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(),
@ -25,22 +33,28 @@ pub fn reset(
) )
.into(); .into();
info!("Resetting {branch_name} to {to_commit}"); info!("Resetting {branch_name} to {to_commit}");
let ctx = gix::diff::command::Context {
git_dir: Some(gitdir.to_path_buf()),
..Default::default()
};
// info!(?ctx, command = command.expose_secret(), "prepare");
match gix::command::prepare(command.expose_secret()) match gix::command::prepare(command.expose_secret())
.with_context(ctx)
.with_shell_allow_argument_splitting() .with_shell_allow_argument_splitting()
.stdout(std::process::Stdio::null()) // .stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null()) // .stderr(std::process::Stdio::null())
.spawn() .spawn()
{ {
Ok(mut child) => match child.wait() { Ok(mut child) => match child.wait() {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(err) => { Err(err) => {
warn!(?err, "Failed (wait)"); warn!(?err, "Failed (wait)");
Err(()) Err(BranchResetError::Push)
} }
}, },
Err(err) => { Err(err) => {
warn!(?err, "Failed (spawn)"); warn!(?err, "Failed (spawn)");
Err(()) Err(BranchResetError::Push)
} }
} }
} }

View file

@ -1,4 +1,4 @@
mod branch; pub mod branch;
mod file; mod file;
mod repo; mod repo;
@ -53,6 +53,7 @@ impl super::ForgeLike for ForgeJoEnv {
to_commit: GitRef, to_commit: GitRef,
force: gitforge::Force, force: gitforge::Force,
) -> gitforge::BranchResetResult { ) -> gitforge::BranchResetResult {
branch::fetch(&self.repo_details)?;
branch::reset(&self.repo_details, branch_name, to_commit, force) branch::reset(&self.repo_details, branch_name, to_commit, force)
} }

View file

@ -19,7 +19,15 @@ impl std::fmt::Display for Force {
} }
} }
pub type BranchResetResult = Result<(), ()>; #[derive(Debug, derive_more::From, derive_more::Display)]
pub enum BranchResetError {
Open(Box<gix::open::Error>),
Fetch(crate::server::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 {

View file

@ -15,7 +15,7 @@ use crate::{
fs::FileSystem, fs::FileSystem,
server::{ server::{
actors::webhook, actors::webhook,
config::{ForgeConfig, ForgeName, RepoAlias, ServerStorage, Webhook}, config::{ForgeConfig, ForgeName, GitDir, RepoAlias, ServerStorage, Webhook},
}, },
}; };
@ -32,6 +32,8 @@ pub enum Error {
}, },
Config(crate::server::config::Error), Config(crate::server::config::Error),
Io(std::io::Error),
} }
type Result<T> = core::result::Result<T, Error>; type Result<T> = core::result::Result<T, Error>;
@ -86,23 +88,17 @@ pub async fn start(fs: FileSystem, net: Network) {
} }
for (forge_name, forge_config) in server_config.forges() { for (forge_name, forge_config) in server_config.forges() {
if let Err(err) = create_forge_repos( create_forge_repos(
forge_config, forge_config,
forge_name.clone(), forge_name.clone(),
server_storage, server_storage,
webhook, webhook,
&net, &net,
) )
.map(|repos| {
repos
.into_iter() .into_iter()
.map(start_actor) .map(start_actor)
.map(|(alias, addr)| webhook::AddWebhookRecipient(alias, addr.recipient())) .map(|(alias, addr)| webhook::AddWebhookRecipient(alias, addr.recipient()))
.for_each(|msg| webhook_router.do_send(msg)); .for_each(|msg| webhook_router.do_send(msg));
}) {
error!(?err, ?forge_name, "Failed to create forge repo actor");
return;
}
} }
let webhook_server = webhook::WebhookActor::new(webhook_router.recipient()).start(); let webhook_server = webhook::WebhookActor::new(webhook_router.recipient()).start();
let _ = actix_rt::signal::ctrl_c().await; let _ = actix_rt::signal::ctrl_c().await;
@ -136,7 +132,7 @@ fn create_forge_repos(
server_storage: &ServerStorage, server_storage: &ServerStorage,
webhook: &Webhook, webhook: &Webhook,
net: &Network, net: &Network,
) -> Result<Vec<(ForgeName, RepoAlias, RepoActor)>> { ) -> Vec<(ForgeName, RepoAlias, RepoActor)> {
let span = tracing::info_span!("Forge", %forge_name, %forge_config); let span = tracing::info_span!("Forge", %forge_name, %forge_config);
let _guard = span.enter(); let _guard = span.enter();
info!("Creating Forge"); info!("Creating Forge");
@ -149,9 +145,15 @@ fn create_forge_repos(
net, net,
); );
for (repo_alias, server_repo_config) in forge_config.repos() { for (repo_alias, server_repo_config) in forge_config.repos() {
repos.push(creator((repo_alias, server_repo_config))?); let forge_repo = creator((repo_alias, server_repo_config));
info!(
forge = %forge_repo.0,
alias = %forge_repo.1,
"created forge repo"
);
repos.push(forge_repo);
} }
Ok(repos) repos
} }
fn create_actor( fn create_actor(
@ -160,7 +162,7 @@ fn create_actor(
server_storage: &ServerStorage, server_storage: &ServerStorage,
webhook: &Webhook, webhook: &Webhook,
net: &Network, net: &Network,
) -> impl Fn((RepoAlias, &ServerRepoConfig)) -> Result<(ForgeName, RepoAlias, RepoActor)> { ) -> impl Fn((RepoAlias, &ServerRepoConfig)) -> (ForgeName, RepoAlias, RepoActor) {
let server_storage = server_storage.clone(); let server_storage = server_storage.clone();
let webhook = webhook.clone(); let webhook = webhook.clone();
let net = net.clone(); let net = net.clone();
@ -168,16 +170,29 @@ fn create_actor(
let span = tracing::info_span!("Repo", %repo_name, %server_repo_config); let span = tracing::info_span!("Repo", %repo_name, %server_repo_config);
let _guard = span.enter(); let _guard = span.enter();
info!("Creating Repo"); info!("Creating Repo");
let gitdir = server_repo_config.gitdir().map_or_else(
|| {
GitDir::from(
server_storage
.path()
.join(forge_name.to_string())
.join(repo_name.to_string()),
)
},
|gitdir| gitdir,
);
// INFO: can't canonicalise gitdir as the path needs to exist to do that and we may not
// have cloned the repo yet
let repo_details = config::RepoDetails::new( let repo_details = config::RepoDetails::new(
&repo_name, &repo_name,
server_repo_config, server_repo_config,
&forge_name, &forge_name,
&forge_config, &forge_config,
&server_storage, gitdir,
)?; );
let actor = actors::repo::RepoActor::new(repo_details, webhook.clone(), net.clone()); let actor = actors::repo::RepoActor::new(repo_details, webhook.clone(), net.clone());
info!("Created Repo"); info!("Created Repo");
Ok((forge_name.clone(), repo_name, actor)) (forge_name.clone(), repo_name, actor)
} }
} }