git-next/crates/core/src/git/repo_details.rs

176 lines
5.9 KiB
Rust
Raw Normal View History

//
use crate::{
git::{
self,
repository::open::{oreal::RealOpenRepository, OpenRepositoryLike},
Generation,
},
pike, BranchName, ForgeAlias, ForgeConfig, ForgeDetails, GitDir, Hostname, RemoteUrl,
RepoAlias, RepoConfig, RepoPath, ServerRepoConfig, StoragePathType,
};
use std::sync::{Arc, RwLock};
use secrecy::{ExposeSecret, Secret};
use tracing::instrument;
/// The derived information about a repo, used to interact with it
#[derive(Clone, Debug, derive_more::Display, derive_with::With)]
2024-06-09 10:21:09 +01:00
#[display("gen-{}:{}:{}/{}", generation, forge.forge_type(), forge.forge_alias(), repo_alias )]
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 {
#[must_use]
pub fn new(
generation: Generation,
repo_alias: &RepoAlias,
server_repo_config: &ServerRepoConfig,
forge_alias: &ForgeAlias,
forge_config: &ForgeConfig,
gitdir: GitDir,
) -> Self {
Self {
generation,
repo_alias: repo_alias.clone(),
repo_path: server_repo_config.repo(),
repo_config: server_repo_config.repo_config(),
branch: server_repo_config.branch(),
gitdir,
forge: ForgeDetails::new(
forge_alias.clone(),
forge_config.forge_type(),
forge_config.hostname(),
forge_config.user(),
forge_config.token(),
),
}
}
pub(crate) 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;
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(crate) const fn gitdir(&self) -> &GitDir {
&self.gitdir
}
#[must_use]
pub fn with_hostname(mut self, hostname: Hostname) -> Self {
let forge = self.forge;
self.forge = forge.with_hostname(hostname);
self
}
// url is a secret as it contains auth token
pub(crate) fn url(&self) -> Secret<String> {
let user = self.forge.user();
let token = self.forge.token().expose_secret();
let auth_delim = if token.is_empty() { "" } else { ":" };
let hostname = self.forge.hostname();
let repo_path = &self.repo_path;
2024-07-26 08:24:35 +01:00
format!("https://{user}{auth_delim}{token}@{hostname}/{repo_path}.git").into()
}
#[allow(clippy::result_large_err)]
pub(crate) fn open(&self) -> Result<impl OpenRepositoryLike, git::validation::remotes::Error> {
let gix_repo = pike! {
self
|> Self::gitdir
|> GitDir::pathbuf
|> gix::ThreadSafeRepository::open
}?;
let repo = pike! {
gix_repo
|> RwLock::new
|> Arc::new
|> RealOpenRepository::new
};
Ok(repo)
}
#[must_use]
pub fn remote_url(&self) -> Option<RemoteUrl> {
use secrecy::ExposeSecret;
RemoteUrl::parse(self.url().expose_secret())
}
#[instrument]
pub fn assert_remote_url(&self, found: Option<RemoteUrl>) -> git::repository::Result<()> {
let Some(found) = found else {
tracing::debug!("No remote url found to assert");
return Ok(());
};
let Some(expected) = self.remote_url() else {
tracing::debug!("No remote url to assert against");
return Ok(());
};
2024-07-26 08:24:35 +01:00
if !found.matches(&expected) {
tracing::debug!(?found, ?expected, "urls differ");
match self.gitdir.storage_path_type() {
StoragePathType::External => {
tracing::debug!("Refusing to update an external repo - user must resolve this");
return Err(git::repository::Error::MismatchDefaultFetchRemote {
found: Box::new(found),
expected: Box::new(expected),
});
}
StoragePathType::Internal => {
tracing::debug!(?expected, "Need to update config with new url");
self.write_remote_url(&expected)?;
}
}
}
Ok(())
}
#[tracing::instrument]
pub fn write_remote_url(&self, url: &RemoteUrl) -> Result<(), kxio::fs::Error> {
if self.gitdir.storage_path_type() != StoragePathType::Internal {
return Ok(());
}
let fs = self.gitdir.as_fs();
// load config file
let config_filename = &self.gitdir.join("config");
let config_file = fs.file_read_to_string(config_filename)?;
let mut config_lines = config_file
.lines()
.map(ToOwned::to_owned)
.collect::<Vec<_>>();
tracing::debug!(?config_lines, "original file");
let url_line = format!(r#" url = "{url}""#);
if config_lines
.iter()
.any(|line| *line == r#"[remote "origin"]"#)
{
tracing::debug!("has an 'origin' remote");
config_lines
.iter_mut()
.filter(|line| line.starts_with(r" url = "))
.for_each(|line| line.clone_from(&url_line));
} else {
tracing::debug!("has no 'origin' remote");
config_lines.push(r#"[remote "origin"]"#.to_owned());
config_lines.push(url_line);
}
tracing::debug!(?config_lines, "updated file");
// write config file back out
fs.file_write(config_filename, config_lines.join("\n").as_str())?;
Ok(())
}
}