git-next/crates/git/src/validation/positions.rs

147 lines
4.8 KiB
Rust
Raw Normal View History

//
2024-05-23 16:50:36 +01:00
use crate as git;
use git_next_config as config;
2024-05-23 16:50:36 +01:00
pub type Result<T> = core::result::Result<T, Error>;
2024-05-23 16:50:36 +01:00
2024-06-09 10:21:09 +01:00
#[derive(Debug)]
2024-05-23 16:50:36 +01:00
pub struct Positions {
pub main: git::Commit,
pub next: git::Commit,
pub dev: git::Commit,
pub dev_commit_history: Vec<git::Commit>,
}
// #[allow(clippy::cognitive_complexity)] // TODO: (#83) reduce complexity
pub fn validate_positions(
open_repository: &dyn git::repository::OpenRepositoryLike,
repo_details: &git::RepoDetails,
repo_config: config::RepoConfig,
) -> Result<Positions> {
2024-06-09 10:21:09 +01:00
let main_branch = repo_config.branches().main();
let next_branch = repo_config.branches().next();
let dev_branch = repo_config.branches().dev();
// Collect Commit Histories for `main`, `next` and `dev` branches
open_repository.fetch()?;
2024-06-09 10:21:09 +01:00
let commit_histories =
get_commit_histories(open_repository, &repo_config).map_err(Error::CommitLog)?;
2024-06-09 10:21:09 +01:00
// branch tips
let main = commit_histories
.main
.first()
.cloned()
.ok_or_else(|| Error::BranchHasNoCommits(main_branch.clone()))?;
let next = commit_histories
.next
.first()
.cloned()
.ok_or_else(|| Error::BranchHasNoCommits(next_branch.clone()))?;
let dev = commit_histories
.dev
.first()
.cloned()
.ok_or_else(|| Error::BranchHasNoCommits(dev_branch.clone()))?;
// Validations:
// Dev must be on main branch, else the USER must rebase it
if is_not_based_on(&commit_histories.dev, &main) {
tracing::warn!("Dev '{dev_branch}' not based on main '{main_branch}' - user must rebase",);
2024-06-09 10:21:09 +01:00
return Err(Error::DevBranchNotBasedOn {
dev: dev_branch,
other: main_branch,
});
}
2024-06-09 10:21:09 +01:00
// verify that next is on main or at most one commit on top of main, else reset it back to main
2024-06-28 20:40:34 +01:00
if is_not_based_on(
commit_histories
.next
.iter()
.take(2)
.cloned()
.collect::<Vec<_>>()
.as_slice(),
&main,
) {
tracing::info!("Main not on same commit as next, or it's parent - resetting next to main",);
return reset_next_to_main(open_repository, repo_details, &main, &next, &next_branch);
}
2024-06-09 10:21:09 +01:00
// verify that next is an ancestor of dev, else reset it back to main
if is_not_based_on(&commit_histories.dev, &next) {
tracing::info!("Next is not an ancestor of dev - resetting next to main");
return reset_next_to_main(open_repository, repo_details, &main, &next, &next_branch);
}
2024-06-09 10:21:09 +01:00
Ok(git::validation::positions::Positions {
main,
next,
dev,
dev_commit_history: commit_histories.dev,
})
}
2024-06-09 10:21:09 +01:00
fn reset_next_to_main(
open_repository: &dyn crate::repository::OpenRepositoryLike,
2024-06-09 10:21:09 +01:00
repo_details: &crate::RepoDetails,
main: &crate::Commit,
next: &crate::Commit,
next_branch: &config::BranchName,
) -> Result<Positions> {
git::push::reset(
open_repository,
2024-06-09 10:21:09 +01:00
repo_details,
next_branch,
&main.clone().into(),
&git::push::Force::From(next.clone().into()),
)
.map_err(|err| {
tracing::warn!(?err, "Failed to reset next to main");
2024-06-09 10:21:09 +01:00
Error::FailedToResetBranch {
branch: next_branch.clone(),
commit: next.clone(),
}
})?;
Err(Error::NextBranchResetRequired(next_branch.clone()))
}
fn is_not_based_on(commits: &[crate::commit::Commit], needle: &crate::Commit) -> bool {
commits.iter().filter(|commit| *commit == needle).count() == 0
}
fn get_commit_histories(
open_repository: &dyn git::repository::OpenRepositoryLike,
repo_config: &config::RepoConfig,
) -> git::commit::log::Result<git::commit::Histories> {
let main = (open_repository.commit_log(&repo_config.branches().main(), &[]))?;
let main_head = [main[0].clone()];
let next = open_repository.commit_log(&repo_config.branches().next(), &main_head)?;
let dev = open_repository.commit_log(&repo_config.branches().dev(), &main_head)?;
let histories = git::commit::Histories { main, next, dev };
Ok(histories)
}
2024-06-09 10:21:09 +01:00
#[derive(Debug, thiserror::Error)]
2024-05-23 16:50:36 +01:00
pub enum Error {
#[error("fetch: {0}")]
Fetch(#[from] git::fetch::Error),
#[error("commit log: {0}")]
CommitLog(#[from] git::commit::log::Error),
#[error("failed to reset branch '{branch}' to {commit}")]
2024-05-23 16:50:36 +01:00
FailedToResetBranch {
branch: config::BranchName,
2024-05-23 16:50:36 +01:00
commit: git::Commit,
},
#[error("next branch '{0}' needs to be reset")]
NextBranchResetRequired(config::BranchName),
#[error("branch '{0}' has no commits")]
BranchHasNoCommits(config::BranchName),
#[error("dev branch '{dev}' not based on branch '{other}' ")]
DevBranchNotBasedOn {
dev: config::BranchName,
other: config::BranchName,
},
}