diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs index 6c88ee4e688336..192ea802fb1173 100644 --- a/crates/git/src/repository.rs +++ b/crates/git/src/repository.rs @@ -757,20 +757,22 @@ pub enum LogSource { } impl LogSource { - fn get_args(&self) -> Result> { + fn get_args(&self) -> Vec { match self { - LogSource::All => Ok(vec![ - "--ignore-missing", // needed in case of unborn HEAD - "--branches", - "--remotes", - "--tags", - "HEAD", - ]), - LogSource::Branch(branch) => Ok(vec![branch.as_str()]), - LogSource::Sha(oid) => Ok(vec![ - str::from_utf8(oid.as_bytes()).context("Failed to build str from sha")?, - ]), - LogSource::Path(path) => Ok(vec!["--follow", "--", path.as_unix_str()]), + LogSource::All => vec![ + OsString::from("--ignore-missing"), // needed in case of unborn HEAD + OsString::from("--branches"), + OsString::from("--remotes"), + OsString::from("--tags"), + OsString::from("HEAD"), + ], + LogSource::Branch(branch) => vec![OsString::from(branch.as_ref())], + LogSource::Sha(oid) => vec![OsString::from(oid.to_string())], + LogSource::Path(path) => vec![ + OsString::from("--follow"), + OsString::from("--"), + OsString::from(path.as_unix_str()), + ], } } } @@ -3111,8 +3113,12 @@ impl GitRepository for RealGitRepository { let git = self.git_binary(); async move { - let mut git_log_command = vec!["log", GRAPH_COMMIT_FORMAT, log_order.as_arg()]; - git_log_command.extend(log_source.get_args()?); + let mut git_log_command = vec![ + OsString::from("log"), + OsString::from(GRAPH_COMMIT_FORMAT), + OsString::from(log_order.as_arg()), + ]; + git_log_command.extend(log_source.get_args()); let mut command = git.build_command(&git_log_command); command.stdout(Stdio::piped()); command.stderr(Stdio::piped()); @@ -3182,22 +3188,22 @@ impl GitRepository for RealGitRepository { let git = self.git_binary(); async move { - let mut args = vec!["log", SEARCH_COMMIT_FORMAT]; + let mut args = vec![OsString::from("log"), OsString::from(SEARCH_COMMIT_FORMAT)]; let hash_query = commit_hash_search_query(search_args.query.as_str()) .map(|query| query.to_ascii_lowercase()); if hash_query.is_none() { - args.push("--fixed-strings"); + args.push(OsString::from("--fixed-strings")); if !search_args.case_sensitive { - args.push("--regexp-ignore-case"); + args.push(OsString::from("--regexp-ignore-case")); } - args.push("--grep"); - args.push(search_args.query.as_str()); + args.push(OsString::from("--grep")); + args.push(OsString::from(search_args.query.as_ref())); } - args.extend(log_source.get_args()?); + args.extend(log_source.get_args()); let mut command = git.build_command(&args); command.stdout(Stdio::piped()); command.stderr(Stdio::null()); @@ -3970,6 +3976,18 @@ mod tests { git_command(path, ["init", "-b", "main"]); } + #[test] + fn test_log_source_sha_uses_hex_arg() { + let sha = "0123456789abcdef0123456789abcdef01234567"; + let args = LogSource::Sha(sha.parse().unwrap()) + .get_args() + .into_iter() + .map(|arg| arg.into_string().unwrap()) + .collect::>(); + + assert_eq!(args, vec![sha]); + } + fn clone_remote_repository_with_main_and_feature(temp_dir: &Path) -> (PathBuf, PathBuf) { let remote_directory = temp_dir.join("remote.git"); let seed_directory = temp_dir.join("seed"); diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 590efaca2b74ff..59af5e16c26c27 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -5838,12 +5838,17 @@ impl GitPanel { return; }; - let Some(branch) = active_repository.read(cx).branch.as_ref() else { + let log_source = { + let repository = active_repository.read(cx); + Self::commit_history_log_source( + repository.branch.as_ref(), + repository.head_commit.as_ref(), + ) + }; + let Some(log_source) = log_source else { return; }; - let branch_name = branch.name().to_string(); - let log_source = LogSource::Branch(branch_name.into()); let log_order = LogOrder::DateOrder; // Kick off the git log fetch so data is ready when the user switches to History. @@ -5883,22 +5888,56 @@ impl GitPanel { return; }; - let Some(branch) = active_repository.read(cx).branch.as_ref() else { + let log_source = { + let repository = active_repository.read(cx); + Self::commit_history_log_source( + repository.branch.as_ref(), + repository.head_commit.as_ref(), + ) + }; + let Some(log_source) = log_source else { + self.commit_history_entries = Some(Rc::from([])); return; }; - let branch_name = branch.name().to_string(); - let log_source = LogSource::Branch(branch_name.into()); let log_order = LogOrder::DateOrder; - self.commit_history_entries = Some(active_repository.update(cx, |repository, cx| { - let response = repository.graph_data(log_source, log_order, 0..usize::MAX, cx); - response - .commits - .iter() - .map(CommitHistoryEntry::from) - .collect() - })); + let (entries, is_loading): (Rc<[CommitHistoryEntry]>, bool) = + active_repository.update(cx, |repository, cx| { + let response = repository.graph_data(log_source, log_order, 0..usize::MAX, cx); + ( + response + .commits + .iter() + .map(CommitHistoryEntry::from) + .collect(), + response.is_loading, + ) + }); + self.commit_history_entries = if is_loading && entries.is_empty() { + None + } else { + Some(entries) + }; + } + + fn commit_history_log_source( + branch: Option<&Branch>, + head_commit: Option<&CommitDetails>, + ) -> Option { + if let Some(branch) = branch { + return Some(LogSource::Branch(branch.name().to_string().into())); + } + + let sha = match head_commit?.sha.as_ref().parse::() { + Ok(sha) => sha, + Err(error) => { + log::warn!("failed to parse HEAD commit sha for commit history: {error}"); + return None; + } + }; + + Some(LogSource::Sha(sha)) } fn git_remote(&self, cx: &mut App) -> Option { @@ -8156,7 +8195,7 @@ pub(crate) fn commit_title_exceeds_limit(title: &str, max_length: usize) -> bool #[cfg(test)] mod tests { use git::{ - repository::repo_path, + repository::{CommitDetails, repo_path}, status::{StatusCode, UnmergedStatus, UnmergedStatusCode}, }; use gpui::{TestAppContext, UpdateGlobal, VisualTestContext, px}; @@ -8268,6 +8307,25 @@ mod tests { handle.await; } + #[test] + fn test_commit_history_log_source_uses_head_commit_when_detached() { + let sha = "0123456789abcdef0123456789abcdef01234567"; + let source = GitPanel::commit_history_log_source( + None, + Some(&CommitDetails { + sha: sha.into(), + ..Default::default() + }), + ); + + assert_eq!(source, Some(LogSource::Sha(sha.parse().unwrap()))); + } + + #[test] + fn test_commit_history_log_source_is_empty_without_branch_or_head() { + assert_eq!(GitPanel::commit_history_log_source(None, None), None); + } + fn assert_editor_opened_with_path( workspace: &Entity, expected_path: &Path,