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
+84
View File
@@ -14,7 +14,9 @@ use editor::{
},
};
use file_icons::FileIcons;
use git;
use git::status::GitSummary;
use git_ui;
use git_ui::file_diff_view::FileDiffView;
use gpui::{
Action, AnyElement, App, AsyncWindowContext, Bounds, ClipboardItem, Context, CursorStyle,
@@ -442,6 +444,72 @@ pub fn init(cx: &mut App) {
panel.update(cx, |panel, cx| panel.delete(action, window, cx));
}
});
workspace.register_action(|workspace, _: &git::FileHistory, window, cx| {
// First try to get from project panel if it's focused
if let Some(panel) = workspace.panel::<ProjectPanel>(cx) {
let maybe_project_path = panel.read(cx).state.selection.and_then(|selection| {
let project = workspace.project().read(cx);
let worktree = project.worktree_for_id(selection.worktree_id, cx)?;
let entry = worktree.read(cx).entry_for_id(selection.entry_id)?;
if entry.is_file() {
Some(ProjectPath {
worktree_id: selection.worktree_id,
path: entry.path.clone(),
})
} else {
None
}
});
if let Some(project_path) = maybe_project_path {
let project = workspace.project();
let git_store = project.read(cx).git_store();
if let Some((repo, repo_path)) = git_store
.read(cx)
.repository_and_path_for_project_path(&project_path, cx)
{
git_ui::file_history_view::FileHistoryView::open(
repo_path,
git_store.downgrade(),
repo.downgrade(),
workspace.weak_handle(),
window,
cx,
);
return;
}
}
}
// Fallback: try to get from active editor
if let Some(active_item) = workspace.active_item(cx)
&& let Some(editor) = active_item.downcast::<Editor>()
&& let Some(buffer) = editor.read(cx).buffer().read(cx).as_singleton()
&& let Some(file) = buffer.read(cx).file()
{
let worktree_id = file.worktree_id(cx);
let project_path = ProjectPath {
worktree_id,
path: file.path().clone(),
};
let project = workspace.project();
let git_store = project.read(cx).git_store();
if let Some((repo, repo_path)) = git_store
.read(cx)
.repository_and_path_for_project_path(&project_path, cx)
{
git_ui::file_history_view::FileHistoryView::open(
repo_path,
git_store.downgrade(),
repo.downgrade(),
workspace.weak_handle(),
window,
cx,
);
}
}
});
})
.detach();
}
@@ -1010,6 +1078,18 @@ impl ProjectPanel {
|| (settings.hide_root && visible_worktrees_count == 1));
let should_show_compare = !is_dir && self.file_abs_paths_to_diff(cx).is_some();
let has_git_repo = !is_dir && {
let project_path = project::ProjectPath {
worktree_id,
path: entry.path.clone(),
};
project
.git_store()
.read(cx)
.repository_and_path_for_project_path(&project_path, cx)
.is_some()
};
let context_menu = ContextMenu::build(window, cx, |menu, _, _| {
menu.context(self.focus_handle.clone()).map(|menu| {
if is_read_only {
@@ -1060,6 +1140,10 @@ impl ProjectPanel {
"Copy Relative Path",
Box::new(zed_actions::workspace::CopyRelativePath),
)
.when(has_git_repo, |menu| {
menu.separator()
.action("File History", Box::new(git::FileHistory))
})
.when(!should_hide_rename, |menu| {
menu.separator().action("Rename", Box::new(Rename))
})