Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

git: Append (You) to git blame author #21119

Closed
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions crates/collab/src/tests/editor_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1979,6 +1979,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
delay_ms: None,
min_column: None,
show_commit_summary: false,
author_display: None,
});
cx_a.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
Expand Down
26 changes: 26 additions & 0 deletions crates/git/src/blame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,22 @@ impl Blame {
content: &Rope,
remote_url: Option<String>,
provider_registry: Arc<GitHostingProviderRegistry>,
author_display_replace: Option<&str>,
) -> Result<Self> {
let output = run_git_blame(git_binary, working_directory, path, content)?;
let mut entries = parse_git_blame(&output)?;
entries.sort_unstable_by(|a, b| a.range.start.cmp(&b.range.start));

if let Some(replacement) = author_display_replace {
if let Some(username) = get_git_user_name(git_binary, working_directory)? {
let new_author = replacement.replace("{}", &username);
entries
.iter_mut()
.filter(|e| e.author.as_deref() == Some(&username))
.for_each(|e| e.author = Some(new_author.clone()));
}
}

let mut permalinks = HashMap::default();
let mut unique_shas = HashSet::default();
let parsed_remote_url = remote_url
Expand Down Expand Up @@ -119,6 +130,21 @@ fn run_git_blame(
Ok(String::from_utf8(output.stdout)?)
}

fn get_git_user_name(git_binary: &Path, working_directory: &Path) -> Result<Option<String>> {
let output = std::process::Command::new(git_binary)
.current_dir(working_directory)
.arg("config")
.arg("user.name")
.output()?;

if output.status.success() {
let name = String::from_utf8(output.stdout)?;
Ok(Some(name.trim().to_string()))
} else {
Ok(None)
}
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
pub struct BlameEntry {
pub sha: Oid,
Expand Down
22 changes: 19 additions & 3 deletions crates/git/src/repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ pub trait GitRepository: Send + Sync {
fn create_branch(&self, _: &str) -> Result<()>;
fn branch_exits(&self, _: &str) -> Result<bool>;

fn blame(&self, path: &Path, content: Rope) -> Result<crate::blame::Blame>;
fn blame(
&self,
path: &Path,
content: Rope,
author_display_replace: Option<&str>,
) -> Result<crate::blame::Blame>;

fn path(&self) -> PathBuf;
}
Expand Down Expand Up @@ -204,7 +209,12 @@ impl GitRepository for RealGitRepository {
Ok(())
}

fn blame(&self, path: &Path, content: Rope) -> Result<crate::blame::Blame> {
fn blame(
&self,
path: &Path,
content: Rope,
author_display_replace: Option<&str>,
) -> Result<crate::blame::Blame> {
let working_directory = self
.repository
.lock()
Expand All @@ -222,6 +232,7 @@ impl GitRepository for RealGitRepository {
&content,
remote_url,
self.hosting_provider_registry.clone(),
author_display_replace,
)
}
}
Expand Down Expand Up @@ -349,7 +360,12 @@ impl GitRepository for FakeGitRepository {
Ok(())
}

fn blame(&self, path: &Path, _content: Rope) -> Result<crate::blame::Blame> {
fn blame(
&self,
path: &Path,
_content: Rope,
_author_replace_display: Option<&str>,
) -> Result<crate::blame::Blame> {
let state = self.state.lock();
state
.blames
Expand Down
20 changes: 18 additions & 2 deletions crates/project/src/buffer_store.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::{
project_settings::GitAuthorDisplaySetting,
search::SearchQuery,
worktree_store::{WorktreeStore, WorktreeStoreEvent},
ProjectItem as _, ProjectPath,
ProjectItem as _, ProjectPath, ProjectSettings,
};
use ::git::{parse_git_remote_url, BuildPermalinkParams, GitHostingProviderRegistry};
use anyhow::{anyhow, Context as _, Result};
Expand All @@ -23,6 +24,7 @@ use language::{
Buffer, BufferEvent, Capability, DiskState, File as _, Language, Operation,
};
use rpc::{proto, AnyProtoClient, ErrorExt as _, TypedEnvelope};
use settings::Settings;
use smol::channel::Receiver;
use std::{io, ops::Range, path::Path, str::FromStr as _, sync::Arc, time::Instant};
use text::BufferId;
Expand Down Expand Up @@ -1063,11 +1065,25 @@ impl BufferStore {
anyhow::Ok(Some((repo, relative_path, content)))
});

let author_display_setting = ProjectSettings::get_global(cx)
.git
.inline_blame
.unwrap_or_default()
.author_display
.unwrap_or_default();

cx.background_executor().spawn(async move {
let Some((repo, relative_path, content)) = blame_params? else {
return Ok(None);
};
repo.blame(&relative_path, content)

let author_display_replace = match author_display_setting {
GitAuthorDisplaySetting::Author => None,
GitAuthorDisplaySetting::You => Some("You"),
GitAuthorDisplaySetting::AuthorYou => Some("{} (You)"),
};

repo.blame(&relative_path, content, author_display_replace)
.with_context(|| format!("Failed to blame {:?}", relative_path.0))
.map(Some)
})
Expand Down
16 changes: 16 additions & 0 deletions crates/project/src/project_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,22 @@ pub struct InlineBlameSettings {
/// Default: false
#[serde(default = "false_value")]
pub show_commit_summary: bool,
/// Author display format for git blame.
///
/// Default: author
pub author_display: Option<GitAuthorDisplaySetting>,
}

#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum GitAuthorDisplaySetting {
/// Only Author name.
#[default]
Author,
/// You
You,
/// Author (You)
AuthorYou,
}

const fn true_value() -> bool {
Expand Down
Loading