use crate::commands::{Command, CommandResult};
use crate::services::GitService;
use crate::app::{AppState, Action, reducer};
use crate::errors::CommandError;
use tracing::instrument;
pub struct PushCommand {
pub force_with_lease: bool,
}
impl Command for PushCommand {
#[instrument(skip(self, git, state), fields(force = self.force_with_lease))]
fn execute(
&self,
git: &GitService,
state: &AppState,
) -> Result<CommandResult, CommandError> {
let mut new_state = state.clone();
new_state = reducer(new_state, Action::SetOpStatus(Some("pushing…".into())));
if state.push_ff_only_enforce {
if let Ok((_ahead, behind)) = git.ahead_behind_remote(&state.repo_path, &state.merge_base_branch) {
if behind > 0 {
new_state = reducer(new_state, Action::AppendOpLog(format!("push blocked: behind {} on {}", behind, state.merge_base_branch)));
new_state = reducer(new_state, Action::SetStatusError(Some(format!("push blocked: behind {} on {}", behind, state.merge_base_branch))));
new_state = reducer(new_state, Action::SetOpStatus(None));
return Ok(CommandResult::StateUpdate(new_state));
}
}
}
match git.push(&state.repo_path, self.force_with_lease) {
Ok(_) => {
new_state = reducer(new_state, Action::SetFeedback(Some("Pushed".into())));
new_state = reducer(new_state, Action::AppendOpLog("push ok".into()));
if let Err(e) = git.fetch_all_prune(&state.repo_path) {
tracing::warn!(error = %e, "Failed to fetch after push");
}
new_state = reducer(new_state, Action::SetRefreshing(true));
new_state = reducer(new_state, Action::RefreshCommits); }
Err(e) => {
let error_msg = format!("{e}");
let needs_force = error_msg.contains("non-fast-forward")
|| error_msg.contains("failed to push")
|| error_msg.contains("rejected");
let full_error = if needs_force && !self.force_with_lease {
format!("push error: {e}\nTip: Use force push (fP or :force push) after amending commits")
} else {
format!("push error: {e}")
};
new_state = reducer(new_state, Action::AppendOpLog(format!("push error: {e}")));
new_state = reducer(new_state, Action::SetStatusError(Some(full_error)));
}
}
new_state = reducer(new_state, Action::SetOpStatus(None));
Ok(CommandResult::StateUpdate(new_state))
}
}
pub struct PullCommand {
pub ff_only: bool,
pub timeout_secs: u64,
}
impl Command for PullCommand {
#[instrument(skip(self, git, state), fields(ff_only = self.ff_only))]
fn execute(
&self,
git: &GitService,
state: &AppState,
) -> Result<CommandResult, CommandError> {
let mut new_state = state.clone();
new_state = reducer(new_state, Action::SetOpStatus(Some(if self.ff_only { "pull ff-only…" } else { "pull…" }.into())));
match git.pull_ff_only(&state.repo_path, self.ff_only, self.timeout_secs) {
Ok(_) => {
new_state = reducer(new_state, Action::SetFeedback(Some("Pulled".into())));
new_state = reducer(new_state, Action::AppendOpLog("pull ok".into()));
new_state = reducer(new_state, Action::SetRefreshing(true));
new_state = reducer(new_state, Action::RefreshCommits); }
Err(e) => {
new_state = reducer(new_state, Action::AppendOpLog(format!("pull error: {e}")));
new_state = reducer(new_state, Action::SetStatusError(Some(format!("pull error: {e}"))));
}
}
new_state = reducer(new_state, Action::SetOpStatus(None));
Ok(CommandResult::StateUpdate(new_state))
}
}
pub struct PullRebaseCommand {
pub autostash: bool,
pub timeout_secs: u64,
}
impl Command for PullRebaseCommand {
#[instrument(skip(self, git, state), fields(autostash = self.autostash))]
fn execute(
&self,
git: &GitService,
state: &AppState,
) -> Result<CommandResult, CommandError> {
let mut new_state = state.clone();
new_state = reducer(new_state, Action::SetOpStatus(Some(if self.autostash { "pull --rebase --autostash…" } else { "pull --rebase…" }.into())));
match git.pull_rebase(&state.repo_path, self.autostash, self.timeout_secs) {
Ok(_) => {
new_state = reducer(new_state, Action::SetFeedback(Some("Pull rebase ok".into())));
new_state = reducer(new_state, Action::AppendOpLog("pull --rebase ok".into()));
new_state = reducer(new_state, Action::SetRefreshing(true));
new_state = reducer(new_state, Action::RefreshCommits); }
Err(e) => {
new_state = reducer(new_state, Action::AppendOpLog(format!("pull --rebase error: {e}")));
new_state = reducer(new_state, Action::SetStatusError(Some(format!("pull --rebase error: {e}"))));
}
}
new_state = reducer(new_state, Action::SetOpStatus(None));
Ok(CommandResult::StateUpdate(new_state))
}
}
pub struct PullMergeCommand {
pub timeout_secs: u64,
}
impl Command for PullMergeCommand {
#[instrument(skip(self, git, state))]
fn execute(
&self,
git: &GitService,
state: &AppState,
) -> Result<CommandResult, CommandError> {
let mut new_state = state.clone();
new_state = reducer(new_state, Action::SetOpStatus(Some("pull…".into())));
match git.pull(&state.repo_path, self.timeout_secs) {
Ok(_) => {
new_state = reducer(new_state, Action::SetFeedback(Some("Pulled".into())));
new_state = reducer(new_state, Action::AppendOpLog("pull ok".into()));
new_state = reducer(new_state, Action::SetRefreshing(true));
new_state = reducer(new_state, Action::RefreshCommits); }
Err(e) => {
new_state = reducer(new_state, Action::AppendOpLog(format!("pull error: {e}")));
new_state = reducer(new_state, Action::SetStatusError(Some(format!("pull error: {e}"))));
}
}
new_state = reducer(new_state, Action::SetOpStatus(None));
Ok(CommandResult::StateUpdate(new_state))
}
}
pub struct FetchAllPruneCommand;
impl Command for FetchAllPruneCommand {
#[instrument(skip(self, git, state))]
fn execute(
&self,
git: &GitService,
state: &AppState,
) -> Result<CommandResult, CommandError> {
let mut new_state = state.clone();
new_state = reducer(new_state, Action::SetOpStatus(Some("fetching all remotes (prune)…".into())));
match git.fetch_all_prune(&state.repo_path) {
Ok(_) => {
new_state = reducer(new_state, Action::SetFeedback(Some("Fetched all remotes".into())));
new_state = reducer(new_state, Action::AppendOpLog("fetch --all --prune ok".into()));
new_state = reducer(new_state, Action::SetRefreshing(true));
new_state = reducer(new_state, Action::RefreshCommits); }
Err(e) => {
new_state = reducer(new_state, Action::AppendOpLog(format!("fetch --all --prune error: {e}")));
new_state = reducer(new_state, Action::SetStatusError(Some(format!("fetch error: {e}"))));
}
}
new_state = reducer(new_state, Action::SetOpStatus(None));
Ok(CommandResult::StateUpdate(new_state))
}
}