Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 39 additions & 34 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ impl App {

// Step 7: Clipboard / dry-run / commit
if self.cli.clipboard {
Self::copy_to_clipboard(&message)?;
Self::copy_to_clipboard(message.clone()).await?;
eprintln!("{} Copied to clipboard!", style("✓").green().bold());
println!("{}", message);
return Ok(());
Expand Down Expand Up @@ -553,7 +553,7 @@ impl App {
clap_complete::generate(*shell, &mut cmd, "commitbee", &mut std::io::stdout());
Ok(())
}
Commands::Hook { action } => self.handle_hook(action),
Commands::Hook { action } => self.handle_hook(action).await,
#[cfg(feature = "secure-storage")]
Commands::SetKey { provider } => self.set_api_key(provider),
#[cfg(feature = "secure-storage")]
Expand Down Expand Up @@ -1009,28 +1009,25 @@ impl App {

// ─── Hook Commands ───

fn handle_hook(&self, action: &HookAction) -> Result<()> {
async fn handle_hook(&self, action: &HookAction) -> Result<()> {
match action {
HookAction::Install => self.hook_install(),
HookAction::Uninstall => self.hook_uninstall(),
HookAction::Status => self.hook_status(),
HookAction::Install => self.hook_install().await,
HookAction::Uninstall => self.hook_uninstall().await,
HookAction::Status => self.hook_status().await,
}
}

fn hook_dir(&self) -> Result<PathBuf> {
async fn hook_dir(&self) -> Result<PathBuf> {
// Verify we're in a git repo first
let _git = GitService::discover()?;

// `hook_dir` runs under the Tokio runtime (`main` is `#[tokio::main]`
// and hook commands reach here via `app.run().await`), so this
// `std::process::Command` can block a worker thread. F-002 will
// migrate the hook / clipboard paths to `tokio::process::Command`;
// until then, allow the lint locally so the new clippy.toml rule
// does not block unrelated PRs.
#[allow(clippy::disallowed_methods)]
let output = std::process::Command::new("git")
// Use `tokio::process::Command` so the spawn does not block a Tokio
// worker thread — `hook_dir` is reached via `app.run().await`, so it
// is always invoked under the runtime.
let output: std::process::Output = tokio::process::Command::new("git")
.args(["rev-parse", "--git-dir"])
.output()?;
.output()
.await?;

if !output.status.success() {
return Err(Error::Git("Cannot find .git directory".into()));
Expand All @@ -1040,12 +1037,12 @@ impl App {
Ok(PathBuf::from(git_dir).join("hooks"))
}

fn hook_path(&self) -> Result<PathBuf> {
Ok(self.hook_dir()?.join("prepare-commit-msg"))
async fn hook_path(&self) -> Result<PathBuf> {
Ok(self.hook_dir().await?.join("prepare-commit-msg"))
}

fn hook_install(&self) -> Result<()> {
let hooks_dir = self.hook_dir()?;
async fn hook_install(&self) -> Result<()> {
let hooks_dir = self.hook_dir().await?;
let hook_path = hooks_dir.join("prepare-commit-msg");
let backup_path = hooks_dir.join("prepare-commit-msg.commitbee-backup");

Expand Down Expand Up @@ -1127,8 +1124,8 @@ fi
Ok(())
}

fn hook_uninstall(&self) -> Result<()> {
let hooks_dir = self.hook_dir()?;
async fn hook_uninstall(&self) -> Result<()> {
let hooks_dir = self.hook_dir().await?;
let hook_path = hooks_dir.join("prepare-commit-msg");
let backup_path = hooks_dir.join("prepare-commit-msg.commitbee-backup");

Expand Down Expand Up @@ -1175,8 +1172,8 @@ fi
Ok(())
}

fn hook_status(&self) -> Result<()> {
let hook_path = self.hook_path()?;
async fn hook_status(&self) -> Result<()> {
let hook_path = self.hook_path().await?;

if !hook_path.exists() {
eprintln!(
Expand Down Expand Up @@ -1555,18 +1552,26 @@ fi
// ─── Clipboard Helpers ───

/// Copy text to the system clipboard using the arboard crate.
fn copy_to_clipboard(text: &str) -> Result<()> {
let mut clipboard = arboard::Clipboard::new().map_err(|e| {
Error::Config(format!(
"Failed to initialize clipboard: {e}. If on Linux, ensure x11 or wayland dependencies are installed."
))
})?;
///
/// `arboard` is a synchronous library and may briefly block on the X11 /
/// Wayland / Cocoa clipboard daemon, so we delegate to
/// `tokio::task::spawn_blocking` to avoid stalling a runtime worker.
async fn copy_to_clipboard(text: String) -> Result<()> {
tokio::task::spawn_blocking(move || -> Result<()> {
let mut clipboard = arboard::Clipboard::new().map_err(|e| {
Error::Config(format!(
"Failed to initialize clipboard: {e}. If on Linux, ensure x11 or wayland dependencies are installed."
))
})?;

clipboard
.set_text(text)
.map_err(|e| Error::Config(format!("Failed to copy to clipboard: {e}")))?;
clipboard
.set_text(text)
.map_err(|e| Error::Config(format!("Failed to copy to clipboard: {e}")))?;

Ok(())
Ok(())
})
.await
.map_err(|e| Error::Config(format!("clipboard task failed to join: {e}")))?
}
}

Expand Down
Loading