eazygit/commands/git_commands/
push_pull.rs1use crate::commands::{Command, CommandResult};
6use crate::services::GitService;
7use crate::app::{AppState, Action, reducer};
8use crate::errors::CommandError;
9use tracing::instrument;
10
11pub struct PushCommand {
13 pub force_with_lease: bool,
14}
15
16impl Command for PushCommand {
17 #[instrument(skip(self, git, state), fields(force = self.force_with_lease))]
18 fn execute(
19 &self,
20 git: &GitService,
21 state: &AppState,
22 ) -> Result<CommandResult, CommandError> {
23 let mut new_state = state.clone();
24 new_state = reducer(new_state, Action::SetOpStatus(Some("pushing…".into())));
25 if state.push_ff_only_enforce {
26 if let Ok((_ahead, behind)) = git.ahead_behind_remote(&state.repo_path, &state.merge_base_branch) {
27 if behind > 0 {
28 new_state = reducer(new_state, Action::AppendOpLog(format!("push blocked: behind {} on {}", behind, state.merge_base_branch)));
29 new_state = reducer(new_state, Action::SetStatusError(Some(format!("push blocked: behind {} on {}", behind, state.merge_base_branch))));
30 new_state = reducer(new_state, Action::SetOpStatus(None));
31 return Ok(CommandResult::StateUpdate(new_state));
32 }
33 }
34 }
35 match git.push(&state.repo_path, self.force_with_lease) {
36 Ok(_) => {
37 new_state = reducer(new_state, Action::SetFeedback(Some("Pushed".into())));
38 new_state = reducer(new_state, Action::AppendOpLog("push ok".into()));
39 if let Err(e) = git.fetch_all_prune(&state.repo_path) {
41 tracing::warn!(error = %e, "Failed to fetch after push");
42 }
44 new_state = reducer(new_state, Action::SetRefreshing(true));
45 new_state = reducer(new_state, Action::RefreshCommits); }
47 Err(e) => {
48 let error_msg = format!("{e}");
49 let needs_force = error_msg.contains("non-fast-forward")
51 || error_msg.contains("failed to push")
52 || error_msg.contains("rejected");
53 let full_error = if needs_force && !self.force_with_lease {
54 format!("push error: {e}\nTip: Use force push (fP or :force push) after amending commits")
55 } else {
56 format!("push error: {e}")
57 };
58 new_state = reducer(new_state, Action::AppendOpLog(format!("push error: {e}")));
59 new_state = reducer(new_state, Action::SetStatusError(Some(full_error)));
60 }
61 }
62 new_state = reducer(new_state, Action::SetOpStatus(None));
63 Ok(CommandResult::StateUpdate(new_state))
64 }
65}
66
67pub struct PullCommand {
69 pub ff_only: bool,
70 pub timeout_secs: u64,
71}
72
73impl Command for PullCommand {
74 #[instrument(skip(self, git, state), fields(ff_only = self.ff_only))]
75 fn execute(
76 &self,
77 git: &GitService,
78 state: &AppState,
79 ) -> Result<CommandResult, CommandError> {
80 let mut new_state = state.clone();
81 new_state = reducer(new_state, Action::SetOpStatus(Some(if self.ff_only { "pull ff-only…" } else { "pull…" }.into())));
82 match git.pull_ff_only(&state.repo_path, self.ff_only, self.timeout_secs) {
83 Ok(_) => {
84 new_state = reducer(new_state, Action::SetFeedback(Some("Pulled".into())));
85 new_state = reducer(new_state, Action::AppendOpLog("pull ok".into()));
86 new_state = reducer(new_state, Action::SetRefreshing(true));
87 new_state = reducer(new_state, Action::RefreshCommits); }
89 Err(e) => {
90 new_state = reducer(new_state, Action::AppendOpLog(format!("pull error: {e}")));
91 new_state = reducer(new_state, Action::SetStatusError(Some(format!("pull error: {e}"))));
92 }
93 }
94 new_state = reducer(new_state, Action::SetOpStatus(None));
95 Ok(CommandResult::StateUpdate(new_state))
96 }
97}
98
99pub struct PullRebaseCommand {
101 pub autostash: bool,
102 pub timeout_secs: u64,
103}
104
105impl Command for PullRebaseCommand {
106 #[instrument(skip(self, git, state), fields(autostash = self.autostash))]
107 fn execute(
108 &self,
109 git: &GitService,
110 state: &AppState,
111 ) -> Result<CommandResult, CommandError> {
112 let mut new_state = state.clone();
113 new_state = reducer(new_state, Action::SetOpStatus(Some(if self.autostash { "pull --rebase --autostash…" } else { "pull --rebase…" }.into())));
114 match git.pull_rebase(&state.repo_path, self.autostash, self.timeout_secs) {
115 Ok(_) => {
116 new_state = reducer(new_state, Action::SetFeedback(Some("Pull rebase ok".into())));
117 new_state = reducer(new_state, Action::AppendOpLog("pull --rebase ok".into()));
118 new_state = reducer(new_state, Action::SetRefreshing(true));
119 new_state = reducer(new_state, Action::RefreshCommits); }
121 Err(e) => {
122 new_state = reducer(new_state, Action::AppendOpLog(format!("pull --rebase error: {e}")));
123 new_state = reducer(new_state, Action::SetStatusError(Some(format!("pull --rebase error: {e}"))));
124 }
125 }
126 new_state = reducer(new_state, Action::SetOpStatus(None));
127 Ok(CommandResult::StateUpdate(new_state))
128 }
129}
130
131pub struct PullMergeCommand {
133 pub timeout_secs: u64,
134}
135
136impl Command for PullMergeCommand {
137 #[instrument(skip(self, git, state))]
138 fn execute(
139 &self,
140 git: &GitService,
141 state: &AppState,
142 ) -> Result<CommandResult, CommandError> {
143 let mut new_state = state.clone();
144 new_state = reducer(new_state, Action::SetOpStatus(Some("pull…".into())));
145 match git.pull(&state.repo_path, self.timeout_secs) {
146 Ok(_) => {
147 new_state = reducer(new_state, Action::SetFeedback(Some("Pulled".into())));
148 new_state = reducer(new_state, Action::AppendOpLog("pull ok".into()));
149 new_state = reducer(new_state, Action::SetRefreshing(true));
150 new_state = reducer(new_state, Action::RefreshCommits); }
152 Err(e) => {
153 new_state = reducer(new_state, Action::AppendOpLog(format!("pull error: {e}")));
154 new_state = reducer(new_state, Action::SetStatusError(Some(format!("pull error: {e}"))));
155 }
156 }
157 new_state = reducer(new_state, Action::SetOpStatus(None));
158 Ok(CommandResult::StateUpdate(new_state))
159 }
160}
161
162pub struct FetchAllPruneCommand;
164
165impl Command for FetchAllPruneCommand {
166 #[instrument(skip(self, git, state))]
167 fn execute(
168 &self,
169 git: &GitService,
170 state: &AppState,
171 ) -> Result<CommandResult, CommandError> {
172 let mut new_state = state.clone();
173 new_state = reducer(new_state, Action::SetOpStatus(Some("fetching all remotes (prune)…".into())));
174 match git.fetch_all_prune(&state.repo_path) {
175 Ok(_) => {
176 new_state = reducer(new_state, Action::SetFeedback(Some("Fetched all remotes".into())));
177 new_state = reducer(new_state, Action::AppendOpLog("fetch --all --prune ok".into()));
178 new_state = reducer(new_state, Action::SetRefreshing(true));
179 new_state = reducer(new_state, Action::RefreshCommits); }
181 Err(e) => {
182 new_state = reducer(new_state, Action::AppendOpLog(format!("fetch --all --prune error: {e}")));
183 new_state = reducer(new_state, Action::SetStatusError(Some(format!("fetch error: {e}"))));
184 }
185 }
186 new_state = reducer(new_state, Action::SetOpStatus(None));
187 Ok(CommandResult::StateUpdate(new_state))
188 }
189}
190