Check we are on the right branch in verify_branch

Ensures we don't accidentally run commands while HEAD is pointing to something other than the gitbutler/integration branch.
This commit is contained in:
Mattias Granlund 2024-05-07 18:37:28 +01:00
parent 2911155e33
commit e7f25e29c5
5 changed files with 61 additions and 9 deletions

View File

@ -608,8 +608,8 @@ pub fn update_base_branch(
..target ..target
})?; })?;
// Rewriting the integration commit is necessary after changing target sha.
super::integration::update_gitbutler_integration(&vb_state, project_repository)?; super::integration::update_gitbutler_integration(&vb_state, project_repository)?;
Ok(()) Ok(())
} }

View File

@ -65,9 +65,13 @@ pub enum VerifyError {
DetachedHead, DetachedHead,
#[error("head is {0}")] #[error("head is {0}")]
InvalidHead(String), InvalidHead(String),
#[error("head not found")]
HeadNotFound,
#[error("integration commit not found")] #[error("integration commit not found")]
NoIntegrationCommit, NoIntegrationCommit,
#[error(transparent)] #[error(transparent)]
GitError(#[from] git::Error),
#[error(transparent)]
Other(#[from] anyhow::Error), Other(#[from] anyhow::Error),
} }
@ -93,6 +97,12 @@ impl ErrorWithContext for VerifyError {
Code::ProjectHead, Code::ProjectHead,
"GibButler's integration commit not found on head.", "GibButler's integration commit not found on head.",
), ),
VerifyError::HeadNotFound => {
error::Context::new_static(Code::Validation, "Repo HEAD is unavailable")
}
VerifyError::GitError(error) => {
error::Context::new(Code::Validation, error.to_string())
}
VerifyError::Other(error) => return error.custom_context_or_root_cause().into(), VerifyError::Other(error) => return error.custom_context_or_root_cause().into(),
}) })
} }

View File

@ -4,7 +4,7 @@ use anyhow::{anyhow, Context, Result};
use bstr::ByteSlice; use bstr::ByteSlice;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use super::{errors, VirtualBranchesHandle}; use super::{errors::VerifyError, VirtualBranchesHandle};
use crate::{ use crate::{
git::{self}, git::{self},
project_repository::{self, LogUntil}, project_repository::{self, LogUntil},
@ -274,7 +274,8 @@ pub fn update_gitbutler_integration(
pub fn verify_branch( pub fn verify_branch(
project_repository: &project_repository::Repository, project_repository: &project_repository::Repository,
) -> Result<(), errors::VerifyError> { ) -> Result<(), VerifyError> {
verify_current_branch_name(project_repository)?;
verify_head_is_set(project_repository)?; verify_head_is_set(project_repository)?;
verify_head_is_clean(project_repository)?; verify_head_is_clean(project_repository)?;
Ok(()) Ok(())
@ -282,7 +283,7 @@ pub fn verify_branch(
fn verify_head_is_clean( fn verify_head_is_clean(
project_repository: &project_repository::Repository, project_repository: &project_repository::Repository,
) -> Result<(), errors::VerifyError> { ) -> Result<(), VerifyError> {
let head_commit = project_repository let head_commit = project_repository
.git_repository .git_repository
.head() .head()
@ -303,7 +304,7 @@ fn verify_head_is_clean(
if integration_commit.is_none() { if integration_commit.is_none() {
// no integration commit found // no integration commit found
return Err(errors::VerifyError::NoIntegrationCommit); return Err(VerifyError::NoIntegrationCommit);
} }
if extra_commits.is_empty() { if extra_commits.is_empty() {
@ -377,17 +378,32 @@ fn verify_head_is_clean(
fn verify_head_is_set( fn verify_head_is_set(
project_repository: &project_repository::Repository, project_repository: &project_repository::Repository,
) -> Result<(), errors::VerifyError> { ) -> Result<(), VerifyError> {
match project_repository match project_repository
.get_head() .get_head()
.context("failed to get head") .context("failed to get head")
.map_err(errors::VerifyError::Other)? .map_err(VerifyError::Other)?
.name() .name()
{ {
Some(refname) if refname.to_string() == GITBUTLER_INTEGRATION_REFERENCE.to_string() => { Some(refname) if refname.to_string() == GITBUTLER_INTEGRATION_REFERENCE.to_string() => {
Ok(()) Ok(())
} }
None => Err(errors::VerifyError::DetachedHead), None => Err(VerifyError::DetachedHead),
Some(head_name) => Err(errors::VerifyError::InvalidHead(head_name.to_string())), Some(head_name) => Err(VerifyError::InvalidHead(head_name.to_string())),
}
}
// Returns an error if repo head is not pointing to the integration branch.
pub fn verify_current_branch_name(
project_repository: &project_repository::Repository,
) -> Result<bool, VerifyError> {
match project_repository.get_head()?.name() {
Some(head) => {
if head.to_string() != GITBUTLER_INTEGRATION_REFERENCE.to_string() {
return Err(VerifyError::InvalidHead(head.to_string()));
}
Ok(true)
}
None => Err(VerifyError::HeadNotFound),
} }
} }

View File

@ -72,6 +72,7 @@ mod undo_commit;
mod update_base_branch; mod update_base_branch;
mod update_commit_message; mod update_commit_message;
mod upstream; mod upstream;
mod verify_branch;
#[tokio::test] #[tokio::test]
async fn resolve_conflict_flow() { async fn resolve_conflict_flow() {

View File

@ -0,0 +1,25 @@
use gitbutler_core::virtual_branches::errors::VerifyError;
use super::*;
// Ensures that `verify_branch` returns an error when not on the integration branch.
#[tokio::test]
async fn should_fail_on_incorrect_branch() {
let Test {
repository,
project_id,
controller,
..
} = &Test::default();
let branch_name: git::LocalRefname = "refs/heads/somebranch".parse().unwrap();
repository.checkout(&branch_name);
let result = controller.list_virtual_branches(project_id).await;
let error = result.err();
assert!(&error.is_some());
let error = error.unwrap();
let error = error.downcast_ref::<VerifyError>();
assert!(matches!(error, Some(VerifyError::InvalidHead(_))));
}