diff --git a/apps/staged/src-tauri/src/managed_acp_tools.rs b/apps/staged/src-tauri/src/managed_acp_tools.rs
index 688723524..3aaeb9d37 100644
--- a/apps/staged/src-tauri/src/managed_acp_tools.rs
+++ b/apps/staged/src-tauri/src/managed_acp_tools.rs
@@ -1514,16 +1514,44 @@ mod tests {
} else {
"case \" $* \" in *\" --registry \"*) echo 'unexpected --registry' >&2; exit 43;; esac\n"
};
- std::fs::write(
- bin_dir.join("npm"),
- format!(
+ write_executable(
+ &bin_dir.join("npm"),
+ &format!(
"#!/bin/sh\ntest \"$1\" = view || exit 44\ntest \"$3\" = version || exit 45\n{registry_check}echo '{version}'\nexit {exit_code}\n"
),
- )
- .unwrap();
+ );
+ }
+
+ /// Writes `contents` to `path` as a 0755 script and returns only once it
+ /// can actually be exec'd. On Linux, a test on another thread that forks
+ /// while our write handle is open inherits it until its own exec, and
+ /// exec of a file anyone still holds open for writing fails with
+ /// `ETXTBSY` — which `npm_view_version` would faithfully report as
+ /// "unknown" and fail the test. Probe-spawn (and immediately kill) the
+ /// script until that window has passed.
+ fn write_executable(path: &Path, contents: &str) {
+ std::fs::write(path, contents).unwrap();
use std::os::unix::fs::PermissionsExt;
- std::fs::set_permissions(bin_dir.join("npm"), std::fs::Permissions::from_mode(0o755))
- .unwrap();
+ std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o755)).unwrap();
+ for _ in 0..1000 {
+ match std::process::Command::new(path)
+ .stdin(std::process::Stdio::null())
+ .stdout(std::process::Stdio::null())
+ .stderr(std::process::Stdio::null())
+ .spawn()
+ {
+ Ok(mut child) => {
+ let _ = child.kill();
+ let _ = child.wait();
+ return;
+ }
+ Err(error) if error.kind() == std::io::ErrorKind::ExecutableFileBusy => {
+ std::thread::sleep(Duration::from_millis(1));
+ }
+ Err(error) => panic!("{} is not executable: {error}", path.display()),
+ }
+ }
+ panic!("{} stayed text-busy", path.display());
}
fn path_env(bin_dir: &Path) -> Vec<(String, String)> {
@@ -1608,7 +1636,7 @@ mod tests {
);
// A hung lookup is cut off at the bound.
- std::fs::write(bin.join("npm"), "#!/bin/sh\nsleep 30\necho 9.9.9\n").unwrap();
+ write_executable(&bin.join("npm"), "#!/bin/sh\nsleep 30\necho 9.9.9\n");
let started = Instant::now();
assert_eq!(
npm_view_version(
diff --git a/apps/staged/src/lib/features/timeline/BranchTimeline.svelte b/apps/staged/src/lib/features/timeline/BranchTimeline.svelte
index c3e0d1ba1..c5baab409 100644
--- a/apps/staged/src/lib/features/timeline/BranchTimeline.svelte
+++ b/apps/staged/src/lib/features/timeline/BranchTimeline.svelte
@@ -1403,14 +1403,9 @@
- {#if onWriteNote}
- onWriteNote?.()}>
- Write note
-
- {#if overflowMenuActions.length > 0}
-
- {/if}
- {/if}
+
{#each overflowMenuActions as action (action)}
{#if action === 'note'}
{/if}
{/each}
+ {#if onWriteNote}
+ {#if overflowMenuActions.length > 0}
+
+ {/if}
+ onWriteNote?.()}>
+ Write note
+
+ {/if}
{/if}