Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 90 additions & 13 deletions src/events/git_push.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,10 +4,11 @@ use super::AtomicEvent;
use crate::bgit_error::{BGitError, BGitErrorWorkflowType, NO_RULE, NO_STEP};
use crate::rules::Rule;
use git2::{Cred, CredentialType, Repository};
use log::debug;

pub struct GitPush {
pub pre_check_rules: Vec<Box<dyn Rule + Send + Sync>>,
pub force: bool,
pub force_with_lease: bool,
pub set_upstream: bool,
}

Expand All@@ -18,7 +19,7 @@ impl AtomicEvent for GitPush {
{
GitPush {
pre_check_rules: vec![],
force: false,
force_with_lease: false,
set_upstream: false,
}
}
Expand DownExpand Up@@ -89,21 +90,24 @@ impl AtomicEvent for GitPush {
// Prepare push options with authentication
let mut push_options = Self::create_push_options();

// Check if we need to force push and validate state
if !self.force {
// Validation
if self.force_with_lease {
self.validate_force_with_lease(&repo, &head, branch_name)?;
} else {
self.validate_push_safety(&repo, &head, branch_name)?;
}

// Determine refspec
let refspec = if self.set_upstream {
format!("refs/heads/{}:refs/heads/{}", branch_name, branch_name)
} else {
format!("refs/heads/{}", branch_name)
};

// Perform the push
let refspecs = if self.force {
vec![format!("+{}", refspec)]
// Perform the push with force-with-lease if enabled
let refspecs = if self.force_with_lease {
let force_lease_refspec =
self.build_force_with_lease_refspec(&repo, branch_name, &refspec)?;
vec![force_lease_refspec]
} else {
vec![refspec]
};
Expand DownExpand Up@@ -131,23 +135,96 @@ impl AtomicEvent for GitPush {
}

impl GitPush {
pub fn set_force(&mut self, force: bool) -> &mut Self {
self.force = force;
pub fn with_force_with_lease(&mut self, force_with_lease: bool) -> &mut Self {
self.force_with_lease = force_with_lease;
self
}

pub fn set_upstream_flag(&mut self, set_upstream: bool) -> &mut Self {
pub fn with_upstream_flag(&mut self, set_upstream: bool) -> &mut Self {
self.set_upstream = set_upstream;
self
}

/// Validate force-with-lease conditions
fn validate_force_with_lease(
&self,
repo: &Repository,
head: &git2::Reference,
branch_name: &str,
) -> Result<(), Box<BGitError>> {
let local_commit = head.peel_to_commit().map_err(|e| {
Box::new(BGitError::new(
"BGitError",
&format!("Failed to get local commit: {}", e),
BGitErrorWorkflowType::AtomicEvent,
NO_STEP,
self.get_name(),
NO_RULE,
))
})?;

// Check if remote branch exists and validate
if let Ok(remote_ref) = repo.find_reference(&format!("refs/remotes/origin/{}", branch_name))
{
let remote_commit = remote_ref.peel_to_commit().map_err(|e| {
Box::new(BGitError::new(
"BGitError",
&format!("Failed to get remote commit: {}", e),
BGitErrorWorkflowType::AtomicEvent,
NO_STEP,
self.get_name(),
NO_RULE,
))
})?;

if local_commit.id() == remote_commit.id() {
debug!("Local branch is up to date with remote, no force-with-lease needed");
return Ok(());
}
}

Ok(())
}

fn build_force_with_lease_refspec(
&self,
repo: &Repository,
branch_name: &str,
base_refspec: &str,
) -> Result<String, Box<BGitError>> {
// Force-with-lease using the current remote tracking branch as the expected value
if let Ok(remote_ref) = repo.find_reference(&format!("refs/remotes/origin/{}", branch_name))
{
let remote_oid = remote_ref.target().ok_or_else(|| {
Box::new(BGitError::new(
"BGitError",
"Failed to get remote reference target for force-with-lease",
BGitErrorWorkflowType::AtomicEvent,
NO_STEP,
self.get_name(),
NO_RULE,
))
})?;

Ok(format!("+{}^{{{}}}", base_refspec, remote_oid))
} else {
Err(Box::new(BGitError::new(
"BGitError",
"Cannot perform force-with-lease: no remote tracking branch found",
BGitErrorWorkflowType::AtomicEvent,
NO_STEP,
self.get_name(),
NO_RULE,
)))
}
}

fn validate_push_safety(
&self,
repo: &Repository,
head: &git2::Reference,
branch_name: &str,
) -> Result<(), Box<BGitError>> {
// Check if we're up to date with remote
if let Ok(remote_ref) = repo.find_reference(&format!("refs/remotes/origin/{}", branch_name))
{
let local_commit = head.peel_to_commit().map_err(|e| {
Expand DownExpand Up@@ -194,7 +271,7 @@ impl GitPush {
if merge_base == local_commit.id() && local_commit.id() != remote_commit.id() {
return Err(Box::new(BGitError::new(
"BGitError",
"Local branch is behind remote. Pull changes first or use --force",
"Local branch is behind remote. Pull changes first",
Comment thread
rootCircle marked this conversation as resolved.
BGitErrorWorkflowType::AtomicEvent,
NO_STEP,
self.get_name(),
Expand Down
4 changes: 3 additions & 1 deletion src/workflows/default/action/ta09_pull_push.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -40,7 +40,9 @@ impl ActionStep for PullAndPush {
// Pull successful, now attempt push
let mut git_push = GitPush::new();
// Configure push options - you can customize these as needed
git_push.set_force(false).set_upstream_flag(false);
git_push
.with_force_with_lease(false)
.with_upstream_flag(false);

match git_push.execute() {
Ok(_) => {
Expand Down