diff --git a/packages/tauri/src/virtual_branches/errors.rs b/packages/tauri/src/virtual_branches/errors.rs index b650e610d..e5394d254 100644 --- a/packages/tauri/src/virtual_branches/errors.rs +++ b/packages/tauri/src/virtual_branches/errors.rs @@ -246,6 +246,8 @@ pub enum SquashError { #[derive(Debug, thiserror::Error)] pub enum UpdateCommitMessageError { + #[error("force push not allowed")] + ForcePushNotAllowed(ForcePushNotAllowedError), #[error("empty message")] EmptyMessage, #[error("default target not set")] @@ -263,6 +265,7 @@ pub enum UpdateCommitMessageError { impl From for Error { fn from(value: UpdateCommitMessageError) -> Self { match value { + UpdateCommitMessageError::ForcePushNotAllowed(error) => error.into(), UpdateCommitMessageError::EmptyMessage => Error::UserError { message: "Commit message can not be empty".to_string(), code: crate::error::Code::Branches, diff --git a/packages/tauri/src/virtual_branches/virtual.rs b/packages/tauri/src/virtual_branches/virtual.rs index 05f14a806..a3b85d766 100644 --- a/packages/tauri/src/virtual_branches/virtual.rs +++ b/packages/tauri/src/virtual_branches/virtual.rs @@ -2837,18 +2837,17 @@ pub fn squash( .parent(0) .context("failed to find parent commit")?; - if branch - .upstream_head - .map_or_else( - || Ok(vec![]), - |upstream_head| { - project_repository.l( - upstream_head, - project_repository::LogUntil::Commit(default_target.sha), - ) - }, - )? - .contains(&parent_commit.id()) + let pushed_commit_oids = branch.upstream_head.map_or_else( + || Ok(vec![]), + |upstream_head| { + project_repository.l( + upstream_head, + project_repository::LogUntil::Commit(default_target.sha), + ) + }, + )?; + + if pushed_commit_oids.contains(&parent_commit.id()) && !project_repository.project().ok_with_force_push { // squashing into a pushed commit will cause a force push that is not allowed @@ -3025,6 +3024,26 @@ pub fn update_commit_message( return Err(errors::UpdateCommitMessageError::CommitNotFound(commit_oid)); } + let pushed_commit_oids = branch.upstream_head.map_or_else( + || Ok(vec![]), + |upstream_head| { + project_repository.l( + upstream_head, + project_repository::LogUntil::Commit(default_target.sha), + ) + }, + )?; + + if pushed_commit_oids.contains(&commit_oid) && !project_repository.project().ok_with_force_push + { + // updating the message of a pushed commit will cause a force push that is not allowed + return Err(errors::UpdateCommitMessageError::ForcePushNotAllowed( + errors::ForcePushNotAllowedError { + project_id: project_repository.project().id, + }, + )); + } + let target_commit = project_repository .git_repository .find_commit(commit_oid) diff --git a/packages/tauri/tests/virtual_branches.rs b/packages/tauri/tests/virtual_branches.rs index 176947b1d..e17c5e0d4 100644 --- a/packages/tauri/tests/virtual_branches.rs +++ b/packages/tauri/tests/virtual_branches.rs @@ -4383,6 +4383,121 @@ mod update_commit_message { ); } + #[tokio::test] + async fn forcepush_allowed() { + let Test { + repository, + project_id, + controller, + projects, + .. + } = Test::default(); + + controller + .set_base_branch(&project_id, &"refs/remotes/origin/master".parse().unwrap()) + .await + .unwrap(); + + projects + .update(&projects::UpdateRequest { + id: project_id, + ok_with_force_push: Some(true), + ..Default::default() + }) + .await + .unwrap(); + + let branch_id = controller + .create_virtual_branch(&project_id, &branch::BranchCreateRequest::default()) + .await + .unwrap(); + + let commit_one_oid = { + fs::write(repository.path().join("file one.txt"), "").unwrap(); + controller + .create_commit(&project_id, &branch_id, "commit one", None) + .await + .unwrap() + }; + + controller + .push_virtual_branch(&project_id, &branch_id, false) + .await + .unwrap(); + + controller + .update_commit_message( + &project_id, + &branch_id, + commit_one_oid, + "commit one updated", + ) + .await + .unwrap(); + + let branch = controller + .list_virtual_branches(&project_id) + .await + .unwrap() + .into_iter() + .find(|b| b.id == branch_id) + .unwrap(); + + let descriptions = branch + .commits + .iter() + .map(|c| c.description.clone()) + .collect::>(); + assert_eq!(descriptions, vec!["commit one updated"]); + assert!(branch.requires_force); + } + + #[tokio::test] + async fn forcepush_forbidden() { + let Test { + repository, + project_id, + controller, + .. + } = Test::default(); + + controller + .set_base_branch(&project_id, &"refs/remotes/origin/master".parse().unwrap()) + .await + .unwrap(); + + let branch_id = controller + .create_virtual_branch(&project_id, &branch::BranchCreateRequest::default()) + .await + .unwrap(); + + let commit_one_oid = { + fs::write(repository.path().join("file one.txt"), "").unwrap(); + controller + .create_commit(&project_id, &branch_id, "commit one", None) + .await + .unwrap() + }; + + controller + .push_virtual_branch(&project_id, &branch_id, false) + .await + .unwrap(); + + assert!(matches!( + controller + .update_commit_message( + &project_id, + &branch_id, + commit_one_oid, + "commit one updated", + ) + .await + .unwrap_err(), + ControllerError::Action(errors::UpdateCommitMessageError::ForcePushNotAllowed(_)) + )); + } + #[tokio::test] async fn root() { let Test {