2024-05-26 08:35:45 +01:00
|
|
|
//
|
2024-05-23 16:50:36 +01:00
|
|
|
use crate as git;
|
2024-05-26 08:35:45 +01:00
|
|
|
use git_next_config as config;
|
2024-05-23 16:50:36 +01:00
|
|
|
|
2024-06-09 10:21:09 +01:00
|
|
|
use tracing::{error, info, warn};
|
2024-05-26 08:35:45 +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>,
|
|
|
|
}
|
|
|
|
|
2024-05-26 08:35:45 +01:00
|
|
|
#[allow(clippy::cognitive_complexity)] // TODO: (#83) reduce complexity
|
|
|
|
pub fn validate_positions(
|
|
|
|
repository: &git::OpenRepository,
|
|
|
|
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();
|
2024-05-26 08:35:45 +01:00
|
|
|
// Collect Commit Histories for `main`, `next` and `dev` branches
|
|
|
|
repository.fetch()?;
|
2024-06-09 10:21:09 +01:00
|
|
|
let commit_histories =
|
|
|
|
get_commit_histories(repository, &repo_config).map_err(Error::CommitLog)?;
|
|
|
|
// 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) {
|
|
|
|
warn!("Dev '{dev_branch}' not based on main '{main_branch}' - user must rebase",);
|
|
|
|
return Err(Error::DevBranchNotBasedOn {
|
|
|
|
dev: dev_branch,
|
|
|
|
other: main_branch,
|
2024-06-03 08:04:48 +01:00
|
|
|
});
|
2024-05-26 08:35:45 +01:00
|
|
|
}
|
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
|
|
|
|
if is_not_based_on(&commit_histories.next[0..=1], &main) {
|
|
|
|
info!("Main not on same commit as next, or it's parent - resetting next to main",);
|
|
|
|
return reset_next_to_main(repository, repo_details, &main, &next, &next_branch);
|
2024-05-26 08:35:45 +01:00
|
|
|
}
|
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) {
|
|
|
|
info!("Next is not an ancestor of dev - resetting next to main");
|
|
|
|
return reset_next_to_main(repository, repo_details, &main, &next, &next_branch);
|
2024-05-26 08:35:45 +01:00
|
|
|
}
|
2024-06-09 10:21:09 +01:00
|
|
|
|
2024-05-26 08:56:01 +01:00
|
|
|
Ok(git::validation::positions::Positions {
|
2024-05-26 08:35:45 +01:00
|
|
|
main,
|
|
|
|
next,
|
|
|
|
dev,
|
|
|
|
dev_commit_history: commit_histories.dev,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-06-09 10:21:09 +01:00
|
|
|
fn reset_next_to_main(
|
|
|
|
repository: &crate::OpenRepository,
|
|
|
|
repo_details: &crate::RepoDetails,
|
|
|
|
main: &crate::Commit,
|
|
|
|
next: &crate::Commit,
|
|
|
|
next_branch: &config::BranchName,
|
|
|
|
) -> Result<Positions> {
|
|
|
|
git::push::reset(
|
|
|
|
repository,
|
|
|
|
repo_details,
|
|
|
|
next_branch,
|
|
|
|
&main.clone().into(),
|
|
|
|
&git::push::Force::From(next.clone().into()),
|
|
|
|
)
|
|
|
|
.map_err(|err| {
|
|
|
|
warn!(?err, "Failed to reset next to main");
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-05-26 08:35:45 +01:00
|
|
|
fn get_commit_histories(
|
|
|
|
repository: &git::repository::OpenRepository,
|
|
|
|
repo_config: &config::RepoConfig,
|
|
|
|
) -> git::commit::log::Result<git::commit::Histories> {
|
|
|
|
let main = (repository.commit_log(&repo_config.branches().main(), &[]))?;
|
|
|
|
let main_head = [main[0].clone()];
|
|
|
|
let next = repository.commit_log(&repo_config.branches().next(), &main_head)?;
|
|
|
|
let dev = 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
|
|
|
|
2024-06-03 08:04:48 +01:00
|
|
|
#[derive(Debug, thiserror::Error)]
|
2024-05-23 16:50:36 +01:00
|
|
|
pub enum Error {
|
2024-06-03 08:04:48 +01:00
|
|
|
#[error("fetch: {0}")]
|
|
|
|
Fetch(#[from] git::fetch::Error),
|
2024-05-25 11:29:08 +01:00
|
|
|
|
2024-06-03 08:04:48 +01:00
|
|
|
#[error("commit log: {0}")]
|
|
|
|
CommitLog(#[from] git::commit::log::Error),
|
2024-05-26 07:42:47 +01:00
|
|
|
|
2024-06-03 08:04:48 +01:00
|
|
|
#[error("failed to reset branch '{branch}' to {commit}")]
|
2024-05-23 16:50:36 +01:00
|
|
|
FailedToResetBranch {
|
2024-05-26 08:35:45 +01:00
|
|
|
branch: config::BranchName,
|
2024-05-23 16:50:36 +01:00
|
|
|
commit: git::Commit,
|
|
|
|
},
|
2024-05-25 11:29:08 +01:00
|
|
|
|
2024-06-03 08:04:48 +01:00
|
|
|
#[error("next branch '{0}' needs to be reset")]
|
|
|
|
NextBranchResetRequired(config::BranchName),
|
2024-05-25 11:29:08 +01:00
|
|
|
|
2024-06-03 08:04:48 +01:00
|
|
|
#[error("branch '{0}' has no commits")]
|
2024-05-26 08:35:45 +01:00
|
|
|
BranchHasNoCommits(config::BranchName),
|
2024-05-25 11:29:08 +01:00
|
|
|
|
2024-06-03 08:04:48 +01:00
|
|
|
#[error("dev branch '{dev}' not based on branch '{other}' ")]
|
|
|
|
DevBranchNotBasedOn {
|
|
|
|
dev: config::BranchName,
|
|
|
|
other: config::BranchName,
|
|
|
|
},
|
2024-05-25 11:29:08 +01:00
|
|
|
}
|