forked from kemitix/git-next
feat(config): use specified gitdir when supplied
The user can specify a gitdir for a repo in the `git-next-server.toml` file. When they do, then we should use that directory. Closes kemitix/git-next#71
This commit is contained in:
parent
bb67b7c66d
commit
31ef0c19fb
5 changed files with 90 additions and 58 deletions
|
@ -21,7 +21,15 @@ pub struct RepoConfigUnknownError(pub network::StatusCode);
|
||||||
pub async fn load(
|
pub async fn load(
|
||||||
details: &RepoDetails,
|
details: &RepoDetails,
|
||||||
forge: &gitforge::Forge,
|
forge: &gitforge::Forge,
|
||||||
) -> Result<RepoConfig, OneOf<(ForgeFileError, toml::de::Error, RepoConfigValidationErrors)>> {
|
) -> Result<
|
||||||
|
RepoConfig,
|
||||||
|
OneOf<(
|
||||||
|
ForgeFileError,
|
||||||
|
crate::server::config::Error,
|
||||||
|
toml::de::Error,
|
||||||
|
RepoConfigValidationErrors,
|
||||||
|
)>,
|
||||||
|
> {
|
||||||
let contents = forge
|
let contents = forge
|
||||||
.file_contents_get(&details.branch, ".git-next.toml")
|
.file_contents_get(&details.branch, ".git-next.toml")
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -17,6 +17,9 @@ pub enum Error {
|
||||||
KxIoFs(kxio::fs::Error),
|
KxIoFs(kxio::fs::Error),
|
||||||
TomlDe(toml::de::Error),
|
TomlDe(toml::de::Error),
|
||||||
}
|
}
|
||||||
|
impl std::error::Error for 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)]
|
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
||||||
|
@ -26,7 +29,7 @@ pub struct ServerConfig {
|
||||||
forge: HashMap<String, ForgeConfig>,
|
forge: HashMap<String, ForgeConfig>,
|
||||||
}
|
}
|
||||||
impl ServerConfig {
|
impl ServerConfig {
|
||||||
pub(crate) fn load(fs: &FileSystem) -> Result<Self, Error> {
|
pub(crate) fn load(fs: &FileSystem) -> Result<Self> {
|
||||||
let str = fs.file_read_to_string(&fs.base().join("git-next-server.toml"))?;
|
let str = fs.file_read_to_string(&fs.base().join("git-next-server.toml"))?;
|
||||||
toml::from_str(&str).map_err(Into::into)
|
toml::from_str(&str).map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
@ -90,8 +93,8 @@ impl RepoConfig {
|
||||||
Self { branches }
|
Self { branches }
|
||||||
}
|
}
|
||||||
// #[cfg(test)]
|
// #[cfg(test)]
|
||||||
pub fn load(toml: &str) -> Result<Self, toml::de::Error> {
|
pub fn load(toml: &str) -> Result<Self> {
|
||||||
toml::from_str(toml)
|
toml::from_str(toml).map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn branches(&self) -> &RepoBranches {
|
pub const fn branches(&self) -> &RepoBranches {
|
||||||
|
@ -186,7 +189,7 @@ impl Display for ForgeConfig {
|
||||||
pub struct ServerRepoConfig {
|
pub struct ServerRepoConfig {
|
||||||
repo: String,
|
repo: String,
|
||||||
branch: String,
|
branch: String,
|
||||||
gitdir: Option<PathBuf>, // TODO: (#71) use this when supplied
|
gitdir: Option<PathBuf>,
|
||||||
main: Option<String>,
|
main: Option<String>,
|
||||||
next: Option<String>,
|
next: Option<String>,
|
||||||
dev: Option<String>,
|
dev: Option<String>,
|
||||||
|
@ -352,18 +355,21 @@ impl RepoDetails {
|
||||||
forge_name: &ForgeName,
|
forge_name: &ForgeName,
|
||||||
forge_config: &ForgeConfig,
|
forge_config: &ForgeConfig,
|
||||||
server_storage: &ServerStorage,
|
server_storage: &ServerStorage,
|
||||||
) -> Self {
|
) -> Result<Self> {
|
||||||
Self {
|
let path_buf = server_repo_config.gitdir.clone().unwrap_or_else(|| {
|
||||||
|
server_storage
|
||||||
|
.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_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()),
|
||||||
gitdir: GitDir(
|
gitdir,
|
||||||
server_storage
|
|
||||||
.path
|
|
||||||
.join(forge_name.to_string())
|
|
||||||
.join(name.to_string()),
|
|
||||||
),
|
|
||||||
forge: ForgeDetails {
|
forge: ForgeDetails {
|
||||||
forge_name: forge_name.clone(),
|
forge_name: forge_name.clone(),
|
||||||
forge_type: forge_config.forge_type.clone(),
|
forge_type: forge_config.forge_type.clone(),
|
||||||
|
@ -371,7 +377,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;
|
||||||
|
@ -386,11 +392,11 @@ impl RepoDetails {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn validate_repo(&self) -> Result<(), RepoValidationError> {
|
pub fn validate_repo(&self) -> ValidationResult<()> {
|
||||||
self.gitdir.validate(self)
|
self.gitdir.validate(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_default_push_remote(&self) -> Result<GitRemote, RepoValidationError> {
|
pub fn find_default_push_remote(&self) -> ValidationResult<GitRemote> {
|
||||||
let repository = gix::open(self.gitdir.clone())
|
let repository = gix::open(self.gitdir.clone())
|
||||||
.map_err(|e| RepoValidationError::UnableToOpenRepo(e.to_string()))?;
|
.map_err(|e| RepoValidationError::UnableToOpenRepo(e.to_string()))?;
|
||||||
let Some(Ok(remote)) = repository.find_default_remote(gix::remote::Direction::Push) else {
|
let Some(Ok(remote)) = repository.find_default_remote(gix::remote::Direction::Push) else {
|
||||||
|
@ -431,6 +437,7 @@ impl Display for RepoDetails {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ValidationResult<T> = core::result::Result<T, RepoValidationError>;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum RepoValidationError {
|
pub enum RepoValidationError {
|
||||||
NoDefaultPushRemote,
|
NoDefaultPushRemote,
|
||||||
|
@ -443,6 +450,7 @@ pub enum RepoValidationError {
|
||||||
configured: GitRemote,
|
configured: GitRemote,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
impl std::error::Error for RepoValidationError {}
|
||||||
impl Display for RepoValidationError {
|
impl Display for RepoValidationError {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
@ -484,12 +492,12 @@ impl GitDir {
|
||||||
pub(crate) fn new(pathbuf: &std::path::Path) -> Self {
|
pub(crate) fn new(pathbuf: &std::path::Path) -> Self {
|
||||||
Self(pathbuf.to_path_buf())
|
Self(pathbuf.to_path_buf())
|
||||||
}
|
}
|
||||||
#[allow(dead_code)] // TODO:
|
|
||||||
pub const fn pathbuf(&self) -> &PathBuf {
|
pub const fn pathbuf(&self) -> &PathBuf {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate(&self, repo_details: &RepoDetails) -> Result<(), RepoValidationError> {
|
pub fn validate(&self, repo_details: &RepoDetails) -> ValidationResult<()> {
|
||||||
let configured = repo_details.git_remote();
|
let configured = repo_details.git_remote();
|
||||||
let found = repo_details.find_default_push_remote()?;
|
let found = repo_details.find_default_push_remote()?;
|
||||||
if configured != found {
|
if configured != found {
|
||||||
|
|
|
@ -7,8 +7,10 @@ use kxio::fs;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
type Result<T> = core::result::Result<T, Box<dyn std::error::Error>>;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn load_should_parse_server_config() -> Result<(), crate::server::config::Error> {
|
fn load_should_parse_server_config() -> Result<()> {
|
||||||
let fs = fs::temp()?;
|
let fs = fs::temp()?;
|
||||||
fs.file_write(
|
fs.file_write(
|
||||||
&fs.base().join("git-next-server.toml"),
|
&fs.base().join("git-next-server.toml"),
|
||||||
|
@ -111,7 +113,7 @@ fn load_should_parse_server_config() -> Result<(), crate::server::config::Error>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_repo_config_load() -> Result<(), crate::server::config::Error> {
|
fn test_repo_config_load() -> Result<()> {
|
||||||
let toml = r#"
|
let toml = r#"
|
||||||
[branches]
|
[branches]
|
||||||
main = "main"
|
main = "main"
|
||||||
|
@ -150,7 +152,7 @@ fn gitdir_should_display_as_pathbuf() {
|
||||||
// NOTE: this test assumes it is being run in a cloned worktree from the project's home repo:
|
// NOTE: this test assumes it is being run in a cloned worktree from the project's home repo:
|
||||||
// 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<(), RepoValidationError> {
|
fn repo_details_find_default_push_remote_finds_correct_remote() -> Result<()> {
|
||||||
let cwd = std::env::current_dir().map_err(RepoValidationError::Io)?;
|
let cwd = std::env::current_dir().map_err(RepoValidationError::Io)?;
|
||||||
let mut repo_details = common::repo_details(
|
let mut repo_details = common::repo_details(
|
||||||
1,
|
1,
|
||||||
|
@ -172,7 +174,7 @@ fn repo_details_find_default_push_remote_finds_correct_remote() -> Result<(), Re
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn gitdir_validate_should_pass_a_valid_git_repo() -> Result<(), RepoValidationError> {
|
fn gitdir_validate_should_pass_a_valid_git_repo() -> Result<()> {
|
||||||
let cwd = std::env::current_dir().map_err(RepoValidationError::Io)?;
|
let cwd = std::env::current_dir().map_err(RepoValidationError::Io)?;
|
||||||
let mut repo_details = common::repo_details(
|
let mut repo_details = common::repo_details(
|
||||||
1,
|
1,
|
||||||
|
|
|
@ -102,13 +102,13 @@ impl super::ForgeLike for ForgeJoEnv {
|
||||||
|
|
||||||
fn repo_clone(&self, gitdir: GitDir) -> Result<(), RepoCloneError> {
|
fn repo_clone(&self, gitdir: GitDir) -> Result<(), RepoCloneError> {
|
||||||
if gitdir.exists() {
|
if gitdir.exists() {
|
||||||
info!(?gitdir, "Gitdir already exists - validating...");
|
info!(%gitdir, "Gitdir already exists - validating...");
|
||||||
gitdir
|
gitdir
|
||||||
.validate(&self.repo_details)
|
.validate(&self.repo_details)
|
||||||
.map_err(|e| RepoCloneError::Validation(e.to_string()))
|
.map_err(|e| RepoCloneError::Validation(e.to_string()))
|
||||||
.inspect(|_| info!(?gitdir, "Validation - OK"))
|
.inspect(|_| info!(%gitdir, "Validation - OK"))
|
||||||
} else {
|
} else {
|
||||||
info!(?gitdir, "Gitdir doesn't exists - cloning...");
|
info!(%gitdir, "Gitdir doesn't exists - cloning...");
|
||||||
repo::clone(&self.repo_details, gitdir).inspect(|_| info!("Cloned - OK"))
|
repo::clone(&self.repo_details, gitdir).inspect(|_| info!("Cloned - OK"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,11 @@ pub enum Error {
|
||||||
FailedToCreateDataDirectory(kxio::fs::Error),
|
FailedToCreateDataDirectory(kxio::fs::Error),
|
||||||
|
|
||||||
#[display("The forge data path is not a directory: {path:?}")]
|
#[display("The forge data path is not a directory: {path:?}")]
|
||||||
ForgeDirIsNotDirectory { path: PathBuf },
|
ForgeDirIsNotDirectory {
|
||||||
|
path: PathBuf,
|
||||||
|
},
|
||||||
|
|
||||||
|
Config(crate::server::config::Error),
|
||||||
}
|
}
|
||||||
type Result<T> = core::result::Result<T, Error>;
|
type Result<T> = core::result::Result<T, Error>;
|
||||||
|
|
||||||
|
@ -81,14 +85,25 @@ pub async fn start(fs: FileSystem, net: Network) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
server_config
|
for (forge_name, forge_config) in server_config.forges() {
|
||||||
.forges()
|
if let Err(err) = create_forge_repos(
|
||||||
.flat_map(|(forge_name, forge_config)| {
|
forge_config,
|
||||||
create_forge_repos(forge_config, forge_name, server_storage, webhook, &net)
|
forge_name.clone(),
|
||||||
})
|
server_storage,
|
||||||
.map(start_actor)
|
webhook,
|
||||||
.map(|(alias, addr)| webhook::AddWebhookRecipient(alias, addr.recipient()))
|
&net,
|
||||||
.for_each(|msg| webhook_router.do_send(msg));
|
)
|
||||||
|
.map(|repos| {
|
||||||
|
repos
|
||||||
|
.into_iter()
|
||||||
|
.map(start_actor)
|
||||||
|
.map(|(alias, addr)| webhook::AddWebhookRecipient(alias, addr.recipient()))
|
||||||
|
.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;
|
||||||
info!("Ctrl-C received, shutting down...");
|
info!("Ctrl-C received, shutting down...");
|
||||||
|
@ -121,20 +136,22 @@ fn create_forge_repos(
|
||||||
server_storage: &ServerStorage,
|
server_storage: &ServerStorage,
|
||||||
webhook: &Webhook,
|
webhook: &Webhook,
|
||||||
net: &Network,
|
net: &Network,
|
||||||
) -> Vec<(ForgeName, RepoAlias, RepoActor)> {
|
) -> Result<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");
|
||||||
forge_config
|
let mut repos = vec![];
|
||||||
.repos()
|
let creator = create_actor(
|
||||||
.map(create_actor(
|
forge_name,
|
||||||
forge_name,
|
forge_config.clone(),
|
||||||
forge_config.clone(),
|
server_storage,
|
||||||
server_storage,
|
webhook,
|
||||||
webhook,
|
net,
|
||||||
net,
|
);
|
||||||
))
|
for (repo_alias, server_repo_config) in forge_config.repos() {
|
||||||
.collect::<Vec<_>>()
|
repos.push(creator((repo_alias, server_repo_config))?);
|
||||||
|
}
|
||||||
|
Ok(repos)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_actor(
|
fn create_actor(
|
||||||
|
@ -143,7 +160,7 @@ fn create_actor(
|
||||||
server_storage: &ServerStorage,
|
server_storage: &ServerStorage,
|
||||||
webhook: &Webhook,
|
webhook: &Webhook,
|
||||||
net: &Network,
|
net: &Network,
|
||||||
) -> impl Fn((RepoAlias, &ServerRepoConfig)) -> (ForgeName, RepoAlias, RepoActor) {
|
) -> impl Fn((RepoAlias, &ServerRepoConfig)) -> Result<(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();
|
||||||
|
@ -151,19 +168,16 @@ 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 actor = actors::repo::RepoActor::new(
|
let repo_details = config::RepoDetails::new(
|
||||||
config::RepoDetails::new(
|
&repo_name,
|
||||||
&repo_name,
|
server_repo_config,
|
||||||
server_repo_config,
|
&forge_name,
|
||||||
&forge_name,
|
&forge_config,
|
||||||
&forge_config,
|
&server_storage,
|
||||||
&server_storage,
|
)?;
|
||||||
),
|
let actor = actors::repo::RepoActor::new(repo_details, webhook.clone(), net.clone());
|
||||||
webhook.clone(),
|
|
||||||
net.clone(),
|
|
||||||
);
|
|
||||||
info!("Created Repo");
|
info!("Created Repo");
|
||||||
(forge_name.clone(), repo_name, actor)
|
Ok((forge_name.clone(), repo_name, actor))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue