Allow viewing past commits in Zed (#27636)
This PR adds functionality for loading the diff for an arbitrary git commit, and displaying it in a tab. To retrieve the diff for the commit, I'm using a single `git cat-file --batch` invocation to efficiently load both the old and new versions of each file that was changed in the commit. Todo * Features * [x] Open the commit view when clicking the most recent commit message in the commit panel * [x] Open the commit view when clicking a SHA in a git blame column * [x] Open the commit view when clicking a SHA in a commit tooltip * [x] Make it work over RPC * [x] Allow buffer search in commit view * [x] Command palette action to open the commit for the current blame line * Styling * [x] Add a header that shows the author, timestamp, and the full commit message * [x] Remove stage/unstage buttons in commit view * [x] Truncate the commit message in the tab * Bugs * [x] Dedup commit tabs within a pane * [x] Add a tooltip to the tab Release Notes: - Added the ability to show past commits in Zed. You can view the most recent commit by clicking its message in the commit panel. And when viewing a git blame, you can show any commit by clicking its sha.
This commit is contained in:
+104
-44
@@ -1,22 +1,22 @@
|
||||
use crate::Editor;
|
||||
use anyhow::Result;
|
||||
use collections::HashMap;
|
||||
use git::{
|
||||
GitHostingProvider, GitHostingProviderRegistry, Oid,
|
||||
blame::{Blame, BlameEntry},
|
||||
GitHostingProviderRegistry, GitRemote, Oid,
|
||||
blame::{Blame, BlameEntry, ParsedCommitMessage},
|
||||
parse_git_remote_url,
|
||||
};
|
||||
use gpui::{App, AppContext as _, Context, Entity, Subscription, Task};
|
||||
use http_client::HttpClient;
|
||||
use gpui::{
|
||||
AnyElement, App, AppContext as _, Context, Entity, Hsla, Subscription, Task, TextStyle,
|
||||
WeakEntity, Window,
|
||||
};
|
||||
use language::{Bias, Buffer, BufferSnapshot, Edit};
|
||||
use multi_buffer::RowInfo;
|
||||
use project::{Project, ProjectItem};
|
||||
use project::{Project, ProjectItem, git_store::Repository};
|
||||
use smallvec::SmallVec;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use sum_tree::SumTree;
|
||||
use ui::SharedString;
|
||||
use url::Url;
|
||||
|
||||
use crate::commit_tooltip::ParsedCommitMessage;
|
||||
use workspace::Workspace;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct GitBlameEntry {
|
||||
@@ -59,45 +59,11 @@ impl<'a> sum_tree::Dimension<'a, GitBlameEntrySummary> for u32 {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GitRemote {
|
||||
pub host: Arc<dyn GitHostingProvider + Send + Sync + 'static>,
|
||||
pub owner: String,
|
||||
pub repo: String,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for GitRemote {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("GitRemote")
|
||||
.field("host", &self.host.name())
|
||||
.field("owner", &self.owner)
|
||||
.field("repo", &self.repo)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl GitRemote {
|
||||
pub fn host_supports_avatars(&self) -> bool {
|
||||
self.host.supports_avatars()
|
||||
}
|
||||
|
||||
pub async fn avatar_url(
|
||||
&self,
|
||||
commit: SharedString,
|
||||
client: Arc<dyn HttpClient>,
|
||||
) -> Option<Url> {
|
||||
self.host
|
||||
.commit_author_avatar_url(&self.owner, &self.repo, commit, client)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
pub struct GitBlame {
|
||||
project: Entity<Project>,
|
||||
buffer: Entity<Buffer>,
|
||||
entries: SumTree<GitBlameEntry>,
|
||||
commit_details: HashMap<Oid, crate::commit_tooltip::ParsedCommitMessage>,
|
||||
commit_details: HashMap<Oid, ParsedCommitMessage>,
|
||||
buffer_snapshot: BufferSnapshot,
|
||||
buffer_edits: text::Subscription,
|
||||
task: Task<Result<()>>,
|
||||
@@ -109,6 +75,91 @@ pub struct GitBlame {
|
||||
_regenerate_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
pub trait BlameRenderer {
|
||||
fn max_author_length(&self) -> usize;
|
||||
|
||||
fn render_blame_entry(
|
||||
&self,
|
||||
_: &TextStyle,
|
||||
_: BlameEntry,
|
||||
_: Option<ParsedCommitMessage>,
|
||||
_: Entity<Repository>,
|
||||
_: WeakEntity<Workspace>,
|
||||
_: Entity<Editor>,
|
||||
_: usize,
|
||||
_: Hsla,
|
||||
_: &mut App,
|
||||
) -> Option<AnyElement>;
|
||||
|
||||
fn render_inline_blame_entry(
|
||||
&self,
|
||||
_: &TextStyle,
|
||||
_: BlameEntry,
|
||||
_: Option<ParsedCommitMessage>,
|
||||
_: Entity<Repository>,
|
||||
_: WeakEntity<Workspace>,
|
||||
_: Entity<Editor>,
|
||||
_: &mut App,
|
||||
) -> Option<AnyElement>;
|
||||
|
||||
fn open_blame_commit(
|
||||
&self,
|
||||
_: BlameEntry,
|
||||
_: Entity<Repository>,
|
||||
_: WeakEntity<Workspace>,
|
||||
_: &mut Window,
|
||||
_: &mut App,
|
||||
);
|
||||
}
|
||||
|
||||
impl BlameRenderer for () {
|
||||
fn max_author_length(&self) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
fn render_blame_entry(
|
||||
&self,
|
||||
_: &TextStyle,
|
||||
_: BlameEntry,
|
||||
_: Option<ParsedCommitMessage>,
|
||||
_: Entity<Repository>,
|
||||
_: WeakEntity<Workspace>,
|
||||
_: Entity<Editor>,
|
||||
_: usize,
|
||||
_: Hsla,
|
||||
_: &mut App,
|
||||
) -> Option<AnyElement> {
|
||||
None
|
||||
}
|
||||
|
||||
fn render_inline_blame_entry(
|
||||
&self,
|
||||
_: &TextStyle,
|
||||
_: BlameEntry,
|
||||
_: Option<ParsedCommitMessage>,
|
||||
_: Entity<Repository>,
|
||||
_: WeakEntity<Workspace>,
|
||||
_: Entity<Editor>,
|
||||
_: &mut App,
|
||||
) -> Option<AnyElement> {
|
||||
None
|
||||
}
|
||||
|
||||
fn open_blame_commit(
|
||||
&self,
|
||||
_: BlameEntry,
|
||||
_: Entity<Repository>,
|
||||
_: WeakEntity<Workspace>,
|
||||
_: &mut Window,
|
||||
_: &mut App,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct GlobalBlameRenderer(pub Arc<dyn BlameRenderer>);
|
||||
|
||||
impl gpui::Global for GlobalBlameRenderer {}
|
||||
|
||||
impl GitBlame {
|
||||
pub fn new(
|
||||
buffer: Entity<Buffer>,
|
||||
@@ -181,6 +232,15 @@ impl GitBlame {
|
||||
this
|
||||
}
|
||||
|
||||
pub fn repository(&self, cx: &App) -> Option<Entity<Repository>> {
|
||||
self.project
|
||||
.read(cx)
|
||||
.git_store()
|
||||
.read(cx)
|
||||
.repository_and_path_for_buffer_id(self.buffer.read(cx).remote_id(), cx)
|
||||
.map(|(repo, _)| repo)
|
||||
}
|
||||
|
||||
pub fn has_generated_entries(&self) -> bool {
|
||||
self.generated
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user