Skip to content

Commit 461eda7

Browse files
committed
fix: add GitOpGuard for push/pull/submodule panic recovery
GitPush, GitPull, GitPullRebase, GitPullSubmodules, and all GitSubmodule* operations set git_op = true but had no cleanup if the spawned task panicked. Added GitOpGuard RAII struct that sends RefreshRepo on Drop (which triggers a status query that clears git_op via update_status).
1 parent 6f11c70 commit 461eda7

File tree

1 file changed

+39
-0
lines changed

1 file changed

+39
-0
lines changed

src/app.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,37 @@ impl Drop for StatusGuard {
8888
}
8989
}
9090

91+
/// RAII guard for git operations (push/pull/submodule) that set `git_op = true`.
92+
/// If the spawned task panics without sending `GitOpComplete` or `RefreshRepo`,
93+
/// the guard sends `RefreshRepo` to trigger a status query that clears `git_op`.
94+
struct GitOpGuard {
95+
id: RepoId,
96+
tx: UnboundedSender<Action>,
97+
completed: bool,
98+
}
99+
100+
impl GitOpGuard {
101+
fn new(id: RepoId, tx: UnboundedSender<Action>) -> Self {
102+
Self {
103+
id,
104+
tx,
105+
completed: false,
106+
}
107+
}
108+
109+
fn complete(mut self) {
110+
self.completed = true;
111+
}
112+
}
113+
114+
impl Drop for GitOpGuard {
115+
fn drop(&mut self) {
116+
if !self.completed {
117+
let _ = self.tx.send(Action::RefreshRepo(self.id.clone()));
118+
}
119+
}
120+
}
121+
91122
pub(crate) struct App {
92123
config: Config,
93124
should_quit: bool,
@@ -656,13 +687,15 @@ impl App {
656687
let repo_id = id.clone();
657688
let tx = self.action_tx.clone();
658689
tokio::task::spawn_blocking(move || {
690+
let guard = GitOpGuard::new(repo_id.clone(), tx.clone());
659691
let output = std::process::Command::new("git")
660692
.arg("-C")
661693
.arg(&path)
662694
.args(&git_args)
663695
.output();
664696
match output {
665697
Ok(o) if o.status.success() => {
698+
guard.complete();
666699
let _ = tx.send(Action::GitOpComplete {
667700
id: repo_id,
668701
message: format!(
@@ -672,6 +705,7 @@ impl App {
672705
});
673706
}
674707
Ok(o) => {
708+
guard.complete();
675709
let stderr = String::from_utf8_lossy(&o.stderr);
676710
let first_line = stderr
677711
.lines()
@@ -686,6 +720,7 @@ impl App {
686720
let _ = tx.send(Action::RefreshRepo(repo_id));
687721
}
688722
Err(e) => {
723+
guard.complete();
689724
let _ = tx.send(Action::Error(format!(
690725
"git {} failed: {}",
691726
git_args.join(" "),
@@ -726,13 +761,15 @@ impl App {
726761
let repo_id = id.clone();
727762
let tx = self.action_tx.clone();
728763
tokio::task::spawn_blocking(move || {
764+
let guard = GitOpGuard::new(repo_id.clone(), tx.clone());
729765
let output = std::process::Command::new("git")
730766
.arg("-C")
731767
.arg(&path)
732768
.args(&git_args)
733769
.output();
734770
match output {
735771
Ok(o) if o.status.success() => {
772+
guard.complete();
736773
let _ = tx.send(Action::GitOpComplete {
737774
id: repo_id,
738775
message: format!(
@@ -742,6 +779,7 @@ impl App {
742779
});
743780
}
744781
Ok(o) => {
782+
guard.complete();
745783
let stderr = String::from_utf8_lossy(&o.stderr);
746784
let first_line = stderr
747785
.lines()
@@ -756,6 +794,7 @@ impl App {
756794
let _ = tx.send(Action::RefreshRepo(repo_id));
757795
}
758796
Err(e) => {
797+
guard.complete();
759798
let _ = tx.send(Action::Error(format!(
760799
"git {} failed: {}",
761800
git_args.join(" "),

0 commit comments

Comments
 (0)