diff --git a/crates/git/src/git.rs b/crates/git/src/git.rs index 766378bf2e514d..da751abaf0d17c 100644 --- a/crates/git/src/git.rs +++ b/crates/git/src/git.rs @@ -1,6 +1,7 @@ pub mod blame; pub mod commit; mod hosting_provider; +mod jj; mod remote; pub mod repository; pub mod stash; diff --git a/crates/git/src/jj.rs b/crates/git/src/jj.rs new file mode 100644 index 00000000000000..70942cfa8f2d4a --- /dev/null +++ b/crates/git/src/jj.rs @@ -0,0 +1,46 @@ +use anyhow::{Context as _, Result}; + +use std::path::Path; +use util::command::new_command; + +pub(crate) async fn visible_heads(work_directory: &Path) -> Result>> { + if !work_directory.join(".jj").is_dir() || !jj_binary_is_available().await { + return Ok(None); + } + + let mut command = new_command("jj"); + command + .arg("--repository") + .arg(work_directory) + .arg("--ignore-working-copy") + .args(["--color", "never"]) + .arg("--quiet") + .arg("--no-pager") + .arg("log") + .args(["-r", "visible_heads()"]) + .arg("--no-graph") + .args(["-T", r#"commit_id ++ "\n""#]); + + let output = command.output().await?; + anyhow::ensure!( + output.status.success(), + "jj command failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + + let head_revisions = String::from_utf8(output.stdout)? + .lines() + .map(str::trim) + .filter(|line| !line.is_empty()) + .map(ToOwned::to_owned) + .collect::>(); + Ok(Some(head_revisions)) +} + +async fn jj_binary_is_available() -> bool { + new_command("jj") + .arg("--version") + .output() + .await + .is_ok_and(|output| output.status.success()) +} diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs index b1b9af106d93e0..54af733065ad15 100644 --- a/crates/git/src/repository.rs +++ b/crates/git/src/repository.rs @@ -1064,6 +1064,25 @@ impl RealGitRepository { *self.any_git_binary_help_output.lock() = Some(output.clone()); output } + + async fn resolve_log_source<'a>( + &self, + log_source: &'a LogSource, + ) -> Result<(&'a str, Option>)> { + match log_source { + LogSource::All => match crate::jj::visible_heads(&self.working_directory()?).await { + Ok(Some(head_revisions)) => return Ok(("--stdin", Some(head_revisions))), + Ok(None) => {} + Err(error) => { + log::warn!( + "resolve_log_source: failed to read visible heads from jj: {error:#}" + ); + } + }, + LogSource::Branch(_) | LogSource::Sha(_) => {} + } + Ok((log_source.get_arg()?, None)) + } } #[derive(Clone, Debug)] @@ -2874,17 +2893,27 @@ impl GitRepository for RealGitRepository { async move { let git = git_binary?; + let (log_source_arg, revisions) = self.resolve_log_source(&log_source).await?; let mut command = git.build_command(&[ "log", GRAPH_COMMIT_FORMAT, log_order.as_arg(), - log_source.get_arg()?, + log_source_arg, ]); + command.stdin(Stdio::piped()); command.stdout(Stdio::piped()); command.stderr(Stdio::piped()); let mut child = command.spawn()?; + if let Some(revisions) = revisions { + let mut stdin = child.stdin.take().context("failed to get stdin")?; + for revision in revisions { + stdin.write_all(revision.as_bytes()).await?; + stdin.write_all(b"\n").await?; + } + stdin.flush().await?; + } let stdout = child.stdout.take().context("failed to get stdout")?; let stderr = child.stderr.take().context("failed to get stderr")?; let mut reader = BufReader::new(stdout); @@ -2950,8 +2979,9 @@ impl GitRepository for RealGitRepository { async move { let git = git_binary?; + let (log_source_arg, revisions) = self.resolve_log_source(&log_source).await?; - let mut args = vec!["log", SEARCH_COMMIT_FORMAT, log_source.get_arg()?]; + let mut args = vec!["log", SEARCH_COMMIT_FORMAT, log_source_arg]; args.push("--fixed-strings"); @@ -2963,10 +2993,19 @@ impl GitRepository for RealGitRepository { args.push(search_args.query.as_str()); let mut command = git.build_command(&args); + command.stdin(Stdio::piped()); command.stdout(Stdio::piped()); command.stderr(Stdio::null()); let mut child = command.spawn()?; + if let Some(revisions) = revisions { + let mut stdin = child.stdin.take().context("failed to get stdin")?; + for revision in revisions { + stdin.write_all(revision.as_bytes()).await?; + stdin.write_all(b"\n").await?; + } + stdin.flush().await?; + } let stdout = child.stdout.take().context("failed to get stdout")?; let mut reader = BufReader::new(stdout);