git-next/crates/cli/src/repo/branch.rs
Paul Campbell d2ea93f05e feat: avoid resetting next to main when dev is ahead of main
When dev is not based on next, next is reset to main, however, it should
reset to the next commit towards dev when when is ahead of main.

Closes kemitix/git-next#111
2024-07-28 20:32:08 +01:00

116 lines
3 KiB
Rust

//
use crate::repo::messages::MessageToken;
use git_next_core::{
git::{
commit::Message,
push::{reset, Force},
repository::open::OpenRepositoryLike,
Commit, GitRef, RepoDetails,
},
RepoConfig,
};
use derive_more::Display;
use tracing::{info, instrument, warn};
// advance next to the next commit towards the head of the dev branch
#[instrument(fields(next), skip_all)]
pub fn advance_next(
next: &Commit,
main: &Commit,
dev_commit_history: &[Commit],
repo_details: RepoDetails,
repo_config: RepoConfig,
open_repository: &dyn OpenRepositoryLike,
message_token: MessageToken,
) -> Result<MessageToken> {
let (commit, force) = find_next_commit_on_dev(next, main, dev_commit_history);
let commit = commit.ok_or_else(|| Error::NextAtDev)?;
validate_commit_message(commit.message())?;
info!("Advancing next to commit '{}'", commit);
reset(
open_repository,
&repo_details,
&repo_config.branches().next(),
&commit.into(),
&force,
)?;
Ok(message_token)
}
#[instrument]
fn validate_commit_message(message: &Message) -> Result<()> {
let message = &message.to_string();
if message.to_ascii_lowercase().starts_with("wip") {
return Err(Error::IsWorkInProgress);
}
match ::git_conventional::Commit::parse(message) {
Ok(commit) => {
info!(?commit, "Pass");
Ok(())
}
Err(err) => {
warn!(?err, "Fail");
Err(Error::InvalidCommitMessage {
reason: err.kind().to_string(),
})
}
}
}
pub fn find_next_commit_on_dev(
next: &Commit,
main: &Commit,
dev_commit_history: &[Commit],
) -> (Option<Commit>, Force) {
let mut next_commit: Option<&Commit> = None;
let mut force = Force::No;
for commit in dev_commit_history.iter() {
if commit == next {
break;
};
if commit == main {
force = Force::From(GitRef::from(next.sha().clone()));
break;
};
next_commit.replace(commit);
}
(next_commit.cloned(), force)
}
// advance main branch to the commit 'next'
#[instrument(fields(next), skip_all)]
pub fn advance_main(
next: Commit,
repo_details: &RepoDetails,
repo_config: &RepoConfig,
open_repository: &dyn OpenRepositoryLike,
) -> Result<()> {
info!("Advancing main to next");
reset(
open_repository,
repo_details,
&repo_config.branches().main(),
&next.into(),
&Force::No,
)?;
Ok(())
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, thiserror::Error, Display)]
pub enum Error {
#[display("push: {}", 0)]
Push(#[from] crate::git::push::Error),
#[display("no commits to advance next to")]
NextAtDev,
#[display("commit is a Work-in-progress")]
IsWorkInProgress,
#[display("commit message is not in conventional commit format: {reason}")]
InvalidCommitMessage { reason: String },
}