Add file history view (#42441)

Closes #16827

Release Notes:

- Added: File history view accessible via right-click context menu on
files in the editor or project panel. Shows commit history for the
selected file with author, timestamp, and commit message. Clicking a
commit opens a diff view filtered to show only changes for that specific
file.

<img width="1293" height="834" alt="Screenshot 2025-11-11 at 16 31 32"
src="https://github.com/user-attachments/assets/3780d21b-a719-40b3-955c-d928c45a47cc"
/>
<img width="1283" height="836" alt="Screenshot 2025-11-11 at 16 31 24"
src="https://github.com/user-attachments/assets/1dc4e56b-b225-4ffa-a2af-c5dcfb2efaa0"
/>

---------

Co-authored-by: cameron <cameron.studdstreet@gmail.com>
This commit is contained in:
ozzy
2025-12-01 13:25:33 +00:00
committed by GitHub
co-authored by cameron
parent 747dc23138
commit 05c2028068
19 changed files with 1703 additions and 394 deletions
+108
View File
@@ -488,6 +488,7 @@ impl GitStore {
client.add_entity_request_handler(Self::handle_reset);
client.add_entity_request_handler(Self::handle_show);
client.add_entity_request_handler(Self::handle_load_commit_diff);
client.add_entity_request_handler(Self::handle_file_history);
client.add_entity_request_handler(Self::handle_checkout_files);
client.add_entity_request_handler(Self::handle_open_commit_message_buffer);
client.add_entity_request_handler(Self::handle_set_index_text);
@@ -1057,6 +1058,30 @@ impl GitStore {
})
}
pub fn file_history(
&self,
repo: &Entity<Repository>,
path: RepoPath,
cx: &mut App,
) -> Task<Result<git::repository::FileHistory>> {
let rx = repo.update(cx, |repo, _| repo.file_history(path));
cx.spawn(|_: &mut AsyncApp| async move { rx.await? })
}
pub fn file_history_paginated(
&self,
repo: &Entity<Repository>,
path: RepoPath,
skip: usize,
limit: Option<usize>,
cx: &mut App,
) -> Task<Result<git::repository::FileHistory>> {
let rx = repo.update(cx, |repo, _| repo.file_history_paginated(path, skip, limit));
cx.spawn(|_: &mut AsyncApp| async move { rx.await? })
}
pub fn get_permalink_to_line(
&self,
buffer: &Entity<Buffer>,
@@ -2314,6 +2339,40 @@ impl GitStore {
})
}
async fn handle_file_history(
this: Entity<Self>,
envelope: TypedEnvelope<proto::GitFileHistory>,
mut cx: AsyncApp,
) -> Result<proto::GitFileHistoryResponse> {
let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
let path = RepoPath::from_proto(&envelope.payload.path)?;
let skip = envelope.payload.skip as usize;
let limit = envelope.payload.limit.map(|l| l as usize);
let file_history = repository_handle
.update(&mut cx, |repository_handle, _| {
repository_handle.file_history_paginated(path, skip, limit)
})?
.await??;
Ok(proto::GitFileHistoryResponse {
entries: file_history
.entries
.into_iter()
.map(|entry| proto::FileHistoryEntry {
sha: entry.sha.to_string(),
subject: entry.subject.to_string(),
message: entry.message.to_string(),
commit_timestamp: entry.commit_timestamp,
author_name: entry.author_name.to_string(),
author_email: entry.author_email.to_string(),
})
.collect(),
path: file_history.path.to_proto(),
})
}
async fn handle_reset(
this: Entity<Self>,
envelope: TypedEnvelope<proto::GitReset>,
@@ -4016,6 +4075,55 @@ impl Repository {
})
}
pub fn file_history(
&mut self,
path: RepoPath,
) -> oneshot::Receiver<Result<git::repository::FileHistory>> {
self.file_history_paginated(path, 0, None)
}
pub fn file_history_paginated(
&mut self,
path: RepoPath,
skip: usize,
limit: Option<usize>,
) -> oneshot::Receiver<Result<git::repository::FileHistory>> {
let id = self.id;
self.send_job(None, move |git_repo, _cx| async move {
match git_repo {
RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
backend.file_history_paginated(path, skip, limit).await
}
RepositoryState::Remote(RemoteRepositoryState { client, project_id }) => {
let response = client
.request(proto::GitFileHistory {
project_id: project_id.0,
repository_id: id.to_proto(),
path: path.to_proto(),
skip: skip as u64,
limit: limit.map(|l| l as u64),
})
.await?;
Ok(git::repository::FileHistory {
entries: response
.entries
.into_iter()
.map(|entry| git::repository::FileHistoryEntry {
sha: entry.sha.into(),
subject: entry.subject.into(),
message: entry.message.into(),
commit_timestamp: entry.commit_timestamp,
author_name: entry.author_name.into(),
author_email: entry.author_email.into(),
})
.collect(),
path: RepoPath::from_proto(&response.path)?,
})
}
}
})
}
fn buffer_store(&self, cx: &App) -> Option<Entity<BufferStore>> {
Some(self.git_store.upgrade()?.read(cx).buffer_store.clone())
}