fix: guard shared background-job channels against concurrent overwrite
Bulk-import, import-directory, remove-git-from-all, create-repo, publish-existing, create-PR, create-issue, profile-push, and profile-pull all set state.pull_all_rx/create_repo_rx/profile_pull_rx unconditionally. Starting a second one of these before the first finished silently dropped the first job's progress/result (its sends now went to an orphaned receiver), and for the workspace-mutating jobs (remove-git-all racing an in-flight clone) could corrupt repos on disk. Each spawn site now checks the channel is free first and tells the user to wait instead.
This commit is contained in:
1 parent
a87db8433c
commit
f5fc22eae7
2 files changed
+36
No files matched your search
@@ -101,6 +101,10 @@ pub fn try_confirm(state: &mut HubState, modal: Modal) -> Option<Modal> {
|
||||
state.status_msg = Some(format!("Path does not exist: {}", src.display()));
|
||||
return None;
|
||||
}
|
||||
if state.pull_all_rx.is_some() {
|
||||
state.status_msg = Some("A workspace job is already running — wait for it to finish.".into());
|
||||
return None;
|
||||
}
|
||||
let workspace = state.workspace_root.clone();
|
||||
let (tx, rx) = std::sync::mpsc::channel::<String>();
|
||||
state.pull_all_rx = Some(rx);
|
||||
@@ -186,6 +190,10 @@ pub fn try_confirm(state: &mut HubState, modal: Modal) -> Option<Modal> {
|
||||
|
||||
// ── Remove .git from all workspace repos ──────────────────────────────
|
||||
Modal::Confirm { tag: ConfirmTag::RemoveGitAll, .. } => {
|
||||
if state.pull_all_rx.is_some() {
|
||||
state.status_msg = Some("A workspace job is already running — wait for it to finish.".into());
|
||||
return None;
|
||||
}
|
||||
let workspace = state.workspace_root.clone();
|
||||
let (tx, rx) = std::sync::mpsc::channel::<String>();
|
||||
state.pull_all_rx = Some(rx);
|
||||
|
||||
@@ -20,6 +20,10 @@ pub fn spawn_create_repo(
|
||||
commands: Vec<String>,
|
||||
owner: Option<String>,
|
||||
) {
|
||||
if state.create_repo_rx.is_some() {
|
||||
state.status_msg = Some("A create/publish/PR/issue job is already running — wait for it to finish.".into());
|
||||
return;
|
||||
}
|
||||
let (tx, rx) = std::sync::mpsc::channel::<CreateRepoMsg>();
|
||||
state.create_repo_rx = Some(rx);
|
||||
state.modal = Modal::Info {
|
||||
@@ -74,6 +78,10 @@ pub fn spawn_create_repo(
|
||||
/// Like `spawn_create_repo`, but for a folder that already has git commits —
|
||||
/// only creates the remote (if missing) and pushes; never stages or commits.
|
||||
pub fn spawn_publish_existing(state: &mut HubState, path: std::path::PathBuf, display_name: String, owner: Option<String>) {
|
||||
if state.create_repo_rx.is_some() {
|
||||
state.status_msg = Some("A create/publish/PR/issue job is already running — wait for it to finish.".into());
|
||||
return;
|
||||
}
|
||||
let (tx, rx) = std::sync::mpsc::channel::<CreateRepoMsg>();
|
||||
state.create_repo_rx = Some(rx);
|
||||
state.modal = Modal::Info {
|
||||
@@ -121,6 +129,10 @@ pub fn spawn_publish_existing(state: &mut HubState, path: std::path::PathBuf, di
|
||||
/// creation, but the generic Progress/Done shape and the `Modal::Info` polling
|
||||
/// already wired up in `keys.rs` apply just as well here.
|
||||
pub fn spawn_create_pr(state: &mut HubState, repo: String, title: String, head: String, base: String) {
|
||||
if state.create_repo_rx.is_some() {
|
||||
state.status_msg = Some("A create/publish/PR/issue job is already running — wait for it to finish.".into());
|
||||
return;
|
||||
}
|
||||
let (tx, rx) = std::sync::mpsc::channel::<CreateRepoMsg>();
|
||||
state.create_repo_rx = Some(rx);
|
||||
state.modal = Modal::Info {
|
||||
@@ -160,6 +172,10 @@ pub fn spawn_create_pr(state: &mut HubState, repo: String, title: String, head:
|
||||
/// Opens an issue via the Quick Actions panel. Same shared-channel pattern as
|
||||
/// `spawn_create_pr` above.
|
||||
pub fn spawn_create_issue(state: &mut HubState, repo: String, title: String) {
|
||||
if state.create_repo_rx.is_some() {
|
||||
state.status_msg = Some("A create/publish/PR/issue job is already running — wait for it to finish.".into());
|
||||
return;
|
||||
}
|
||||
let (tx, rx) = std::sync::mpsc::channel::<CreateRepoMsg>();
|
||||
state.create_repo_rx = Some(rx);
|
||||
state.modal = Modal::Info {
|
||||
@@ -330,6 +346,10 @@ pub fn spawn_convention_preview(state: &mut HubState, repo: std::path::PathBuf,
|
||||
/// `Modal::Info` pattern; reuses `CreateRepoMsg` since a push needs no
|
||||
/// payload back, just success/failure.
|
||||
pub fn spawn_profile_push(state: &mut HubState, snap: crate::profile::SettingsSnapshot) {
|
||||
if state.create_repo_rx.is_some() {
|
||||
state.status_msg = Some("A create/publish/PR/issue job is already running — wait for it to finish.".into());
|
||||
return;
|
||||
}
|
||||
let (tx, rx) = std::sync::mpsc::channel::<CreateRepoMsg>();
|
||||
state.create_repo_rx = Some(rx);
|
||||
let repo_name = crate::profile::SETTINGS_REPO_NAME.to_string();
|
||||
@@ -364,6 +384,10 @@ pub fn spawn_profile_push(state: &mut HubState, snap: crate::profile::SettingsSn
|
||||
/// instead of `CreateRepoMsg`.
|
||||
pub fn spawn_profile_pull(state: &mut HubState) {
|
||||
use super::state::ProfilePullMsg;
|
||||
if state.profile_pull_rx.is_some() {
|
||||
state.status_msg = Some("A settings pull is already running — wait for it to finish.".into());
|
||||
return;
|
||||
}
|
||||
let (tx, rx) = std::sync::mpsc::channel::<ProfilePullMsg>();
|
||||
state.profile_pull_rx = Some(rx);
|
||||
let repo_name = crate::profile::SETTINGS_REPO_NAME.to_string();
|
||||
@@ -411,6 +435,10 @@ pub fn open_bulk_import_picker(state: &mut HubState, acc_idx: usize) {
|
||||
/// it auto-imports it immediately, since landing on this flow already means
|
||||
/// "I trust this account's data" and a second confirmation would be noise.
|
||||
pub fn spawn_bulk_import(state: &mut HubState, acc_idx: usize, affiliation: crate::providers::RepoAffiliation) {
|
||||
if state.pull_all_rx.is_some() {
|
||||
state.status_msg = Some("A workspace job is already running — wait for it to finish.".into());
|
||||
return;
|
||||
}
|
||||
let Some(acc) = state.accounts.get(acc_idx).cloned() else { return; };
|
||||
let (tx, rx) = std::sync::mpsc::channel::<String>();
|
||||
state.pull_all_rx = Some(rx);
|
||||
|
||||
Reference in new issue
Block a user