Add an undo button to the git panel (#24593)
Also prep infrastructure for pushing a commit Release Notes: - N/A --------- Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com> Co-authored-by: Conrad <conrad@zed.dev> Co-authored-by: Nate Butler <iamnbutler@gmail.com>
This commit is contained in:
co-authored by
Conrad Irwin
Conrad
Nate Butler
parent
df8adc8b11
commit
b014afa938
Generated
+2
@@ -5371,6 +5371,8 @@ dependencies = [
|
||||
"serde_json",
|
||||
"settings",
|
||||
"theme",
|
||||
"time",
|
||||
"time_format",
|
||||
"ui",
|
||||
"util",
|
||||
"windows 0.58.0",
|
||||
|
||||
@@ -631,7 +631,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitPanel || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || Welcome",
|
||||
"context": "ChangesList || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || Welcome",
|
||||
"bindings": {
|
||||
":": "command_palette::Toggle",
|
||||
"g /": "pane::DeploySearch"
|
||||
|
||||
@@ -101,6 +101,7 @@ CREATE TABLE "worktree_repositories" (
|
||||
"scan_id" INTEGER NOT NULL,
|
||||
"is_deleted" BOOL NOT NULL,
|
||||
"current_merge_conflicts" VARCHAR,
|
||||
"branch_summary" VARCHAR,
|
||||
PRIMARY KEY(project_id, worktree_id, work_directory_id),
|
||||
FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(project_id, worktree_id, work_directory_id) REFERENCES worktree_entries (project_id, worktree_id, id) ON DELETE CASCADE
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE worktree_repositories
|
||||
ADD COLUMN worktree_repositories VARCHAR NULL;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE worktree_repositories ADD COLUMN branch_summary TEXT NULL;
|
||||
@@ -326,16 +326,26 @@ impl Database {
|
||||
|
||||
if !update.updated_repositories.is_empty() {
|
||||
worktree_repository::Entity::insert_many(update.updated_repositories.iter().map(
|
||||
|repository| worktree_repository::ActiveModel {
|
||||
project_id: ActiveValue::set(project_id),
|
||||
worktree_id: ActiveValue::set(worktree_id),
|
||||
work_directory_id: ActiveValue::set(repository.work_directory_id as i64),
|
||||
scan_id: ActiveValue::set(update.scan_id as i64),
|
||||
branch: ActiveValue::set(repository.branch.clone()),
|
||||
is_deleted: ActiveValue::set(false),
|
||||
current_merge_conflicts: ActiveValue::Set(Some(
|
||||
serde_json::to_string(&repository.current_merge_conflicts).unwrap(),
|
||||
)),
|
||||
|repository| {
|
||||
worktree_repository::ActiveModel {
|
||||
project_id: ActiveValue::set(project_id),
|
||||
worktree_id: ActiveValue::set(worktree_id),
|
||||
work_directory_id: ActiveValue::set(
|
||||
repository.work_directory_id as i64,
|
||||
),
|
||||
scan_id: ActiveValue::set(update.scan_id as i64),
|
||||
branch: ActiveValue::set(repository.branch.clone()),
|
||||
is_deleted: ActiveValue::set(false),
|
||||
branch_summary: ActiveValue::Set(
|
||||
repository
|
||||
.branch_summary
|
||||
.as_ref()
|
||||
.map(|summary| serde_json::to_string(summary).unwrap()),
|
||||
),
|
||||
current_merge_conflicts: ActiveValue::Set(Some(
|
||||
serde_json::to_string(&repository.current_merge_conflicts).unwrap(),
|
||||
)),
|
||||
}
|
||||
},
|
||||
))
|
||||
.on_conflict(
|
||||
@@ -347,6 +357,8 @@ impl Database {
|
||||
.update_columns([
|
||||
worktree_repository::Column::ScanId,
|
||||
worktree_repository::Column::Branch,
|
||||
worktree_repository::Column::BranchSummary,
|
||||
worktree_repository::Column::CurrentMergeConflicts,
|
||||
])
|
||||
.to_owned(),
|
||||
)
|
||||
@@ -779,6 +791,13 @@ impl Database {
|
||||
.transpose()?
|
||||
.unwrap_or_default();
|
||||
|
||||
let branch_summary = db_repository_entry
|
||||
.branch_summary
|
||||
.as_ref()
|
||||
.map(|branch_summary| serde_json::from_str(&branch_summary))
|
||||
.transpose()?
|
||||
.unwrap_or_default();
|
||||
|
||||
worktree.repository_entries.insert(
|
||||
db_repository_entry.work_directory_id as u64,
|
||||
proto::RepositoryEntry {
|
||||
@@ -787,6 +806,7 @@ impl Database {
|
||||
updated_statuses,
|
||||
removed_statuses: Vec::new(),
|
||||
current_merge_conflicts,
|
||||
branch_summary,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -743,12 +743,20 @@ impl Database {
|
||||
.transpose()?
|
||||
.unwrap_or_default();
|
||||
|
||||
let branch_summary = db_repository
|
||||
.branch_summary
|
||||
.as_ref()
|
||||
.map(|branch_summary| serde_json::from_str(&branch_summary))
|
||||
.transpose()?
|
||||
.unwrap_or_default();
|
||||
|
||||
worktree.updated_repositories.push(proto::RepositoryEntry {
|
||||
work_directory_id: db_repository.work_directory_id as u64,
|
||||
branch: db_repository.branch,
|
||||
updated_statuses,
|
||||
removed_statuses,
|
||||
current_merge_conflicts,
|
||||
branch_summary,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ pub struct Model {
|
||||
pub is_deleted: bool,
|
||||
// JSON array typed string
|
||||
pub current_merge_conflicts: Option<String>,
|
||||
// A JSON object representing the current Branch values
|
||||
pub branch_summary: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
|
||||
@@ -395,6 +395,8 @@ impl Server {
|
||||
.add_request_handler(forward_mutating_project_request::<proto::Stage>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::Unstage>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::Commit>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::GitShow>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::GitReset>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::SetIndexText>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::OpenCommitMessageBuffer>)
|
||||
.add_message_handler(broadcast_project_message_from_host::<proto::AdvertiseContexts>)
|
||||
|
||||
@@ -2895,7 +2895,10 @@ async fn test_git_branch_name(
|
||||
assert_eq!(worktrees.len(), 1);
|
||||
let worktree = worktrees[0].clone();
|
||||
let root_entry = worktree.read(cx).snapshot().root_git_entry().unwrap();
|
||||
assert_eq!(root_entry.branch(), branch_name.map(Into::into));
|
||||
assert_eq!(
|
||||
root_entry.branch().map(|branch| branch.name.to_string()),
|
||||
branch_name
|
||||
);
|
||||
}
|
||||
|
||||
// Smoke test branch reading
|
||||
@@ -6783,7 +6786,7 @@ async fn test_remote_git_branches(
|
||||
})
|
||||
});
|
||||
|
||||
assert_eq!(host_branch.as_ref(), branches[2]);
|
||||
assert_eq!(host_branch.name, branches[2]);
|
||||
|
||||
// Also try creating a new branch
|
||||
cx_b.update(|cx| {
|
||||
@@ -6804,5 +6807,5 @@ async fn test_remote_git_branches(
|
||||
})
|
||||
});
|
||||
|
||||
assert_eq!(host_branch.as_ref(), "totally-new-branch");
|
||||
assert_eq!(host_branch.name, "totally-new-branch");
|
||||
}
|
||||
|
||||
@@ -314,7 +314,7 @@ async fn test_ssh_collaboration_git_branches(
|
||||
})
|
||||
});
|
||||
|
||||
assert_eq!(server_branch.as_ref(), branches[2]);
|
||||
assert_eq!(server_branch.name, branches[2]);
|
||||
|
||||
// Also try creating a new branch
|
||||
cx_b.update(|cx| {
|
||||
@@ -337,7 +337,7 @@ async fn test_ssh_collaboration_git_branches(
|
||||
})
|
||||
});
|
||||
|
||||
assert_eq!(server_branch.as_ref(), "totally-new-branch");
|
||||
assert_eq!(server_branch.name, "totally-new-branch");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
||||
@@ -1,28 +1,48 @@
|
||||
use futures::Future;
|
||||
use git::blame::BlameEntry;
|
||||
use git::Oid;
|
||||
use git::PullRequest;
|
||||
use gpui::{
|
||||
App, Asset, ClipboardItem, Element, ParentElement, Render, ScrollHandle,
|
||||
StatefulInteractiveElement, WeakEntity,
|
||||
};
|
||||
use language::ParsedMarkdown;
|
||||
use settings::Settings;
|
||||
use std::hash::Hash;
|
||||
use theme::ThemeSettings;
|
||||
use time::UtcOffset;
|
||||
use time::{OffsetDateTime, UtcOffset};
|
||||
use time_format::format_local_timestamp;
|
||||
use ui::{prelude::*, tooltip_container, Avatar, Divider, IconButtonShape};
|
||||
use url::Url;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::git::blame::{CommitDetails, GitRemote};
|
||||
use crate::git::blame::GitRemote;
|
||||
use crate::EditorStyle;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CommitDetails {
|
||||
pub sha: SharedString,
|
||||
pub committer_name: SharedString,
|
||||
pub committer_email: SharedString,
|
||||
pub commit_time: OffsetDateTime,
|
||||
pub message: Option<ParsedCommitMessage>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ParsedCommitMessage {
|
||||
pub message: SharedString,
|
||||
pub parsed_message: ParsedMarkdown,
|
||||
pub permalink: Option<Url>,
|
||||
pub pull_request: Option<PullRequest>,
|
||||
pub remote: Option<GitRemote>,
|
||||
}
|
||||
|
||||
struct CommitAvatar<'a> {
|
||||
details: Option<&'a CommitDetails>,
|
||||
sha: Oid,
|
||||
commit: &'a CommitDetails,
|
||||
}
|
||||
|
||||
impl<'a> CommitAvatar<'a> {
|
||||
fn new(details: Option<&'a CommitDetails>, sha: Oid) -> Self {
|
||||
Self { details, sha }
|
||||
fn new(details: &'a CommitDetails) -> Self {
|
||||
Self { commit: details }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,14 +50,16 @@ impl<'a> CommitAvatar<'a> {
|
||||
fn render(
|
||||
&'a self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<BlameEntryTooltip>,
|
||||
cx: &mut Context<CommitTooltip>,
|
||||
) -> Option<impl IntoElement> {
|
||||
let remote = self
|
||||
.details
|
||||
.commit
|
||||
.message
|
||||
.as_ref()
|
||||
.and_then(|details| details.remote.as_ref())
|
||||
.filter(|remote| remote.host_supports_avatars())?;
|
||||
|
||||
let avatar_url = CommitAvatarAsset::new(remote.clone(), self.sha);
|
||||
let avatar_url = CommitAvatarAsset::new(remote.clone(), self.commit.sha.clone());
|
||||
|
||||
let element = match window.use_asset::<CommitAvatarAsset>(&avatar_url, cx) {
|
||||
// Loading or no avatar found
|
||||
@@ -54,7 +76,7 @@ impl<'a> CommitAvatar<'a> {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct CommitAvatarAsset {
|
||||
sha: Oid,
|
||||
sha: SharedString,
|
||||
remote: GitRemote,
|
||||
}
|
||||
|
||||
@@ -66,7 +88,7 @@ impl Hash for CommitAvatarAsset {
|
||||
}
|
||||
|
||||
impl CommitAvatarAsset {
|
||||
fn new(remote: GitRemote, sha: Oid) -> Self {
|
||||
fn new(remote: GitRemote, sha: SharedString) -> Self {
|
||||
Self { remote, sha }
|
||||
}
|
||||
}
|
||||
@@ -91,50 +113,78 @@ impl Asset for CommitAvatarAsset {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct BlameEntryTooltip {
|
||||
blame_entry: BlameEntry,
|
||||
details: Option<CommitDetails>,
|
||||
pub struct CommitTooltip {
|
||||
commit: CommitDetails,
|
||||
editor_style: EditorStyle,
|
||||
workspace: Option<WeakEntity<Workspace>>,
|
||||
scroll_handle: ScrollHandle,
|
||||
}
|
||||
|
||||
impl BlameEntryTooltip {
|
||||
pub(crate) fn new(
|
||||
blame_entry: BlameEntry,
|
||||
details: Option<CommitDetails>,
|
||||
style: &EditorStyle,
|
||||
impl CommitTooltip {
|
||||
pub fn blame_entry(
|
||||
blame: BlameEntry,
|
||||
details: Option<ParsedCommitMessage>,
|
||||
style: EditorStyle,
|
||||
workspace: Option<WeakEntity<Workspace>>,
|
||||
) -> Self {
|
||||
let commit_time = blame
|
||||
.committer_time
|
||||
.and_then(|t| OffsetDateTime::from_unix_timestamp(t).ok())
|
||||
.unwrap_or(OffsetDateTime::now_utc());
|
||||
Self::new(
|
||||
CommitDetails {
|
||||
sha: blame.sha.to_string().into(),
|
||||
commit_time,
|
||||
committer_name: blame
|
||||
.committer_name
|
||||
.unwrap_or("<no name>".to_string())
|
||||
.into(),
|
||||
committer_email: blame.committer_email.unwrap_or("".to_string()).into(),
|
||||
message: details,
|
||||
},
|
||||
style,
|
||||
workspace,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
commit: CommitDetails,
|
||||
editor_style: EditorStyle,
|
||||
workspace: Option<WeakEntity<Workspace>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
editor_style: style.clone(),
|
||||
blame_entry,
|
||||
details,
|
||||
editor_style,
|
||||
commit,
|
||||
workspace,
|
||||
scroll_handle: ScrollHandle::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for BlameEntryTooltip {
|
||||
impl Render for CommitTooltip {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let avatar =
|
||||
CommitAvatar::new(self.details.as_ref(), self.blame_entry.sha).render(window, cx);
|
||||
let avatar = CommitAvatar::new(&self.commit).render(window, cx);
|
||||
|
||||
let author = self
|
||||
.blame_entry
|
||||
.author
|
||||
.clone()
|
||||
.unwrap_or("<no name>".to_string());
|
||||
let author = self.commit.committer_name.clone();
|
||||
|
||||
let author_email = self.blame_entry.author_mail.clone();
|
||||
let author_email = self.commit.committer_email.clone();
|
||||
|
||||
let short_commit_id = self.blame_entry.sha.display_short();
|
||||
let full_sha = self.blame_entry.sha.to_string().clone();
|
||||
let absolute_timestamp = blame_entry_absolute_timestamp(&self.blame_entry);
|
||||
let short_commit_id = self
|
||||
.commit
|
||||
.sha
|
||||
.get(0..8)
|
||||
.map(|sha| sha.to_string().into())
|
||||
.unwrap_or_else(|| self.commit.sha.clone());
|
||||
let full_sha = self.commit.sha.to_string().clone();
|
||||
let absolute_timestamp = format_local_timestamp(
|
||||
self.commit.commit_time,
|
||||
OffsetDateTime::now_utc(),
|
||||
time_format::TimestampFormat::MediumAbsolute,
|
||||
);
|
||||
|
||||
let message = self
|
||||
.details
|
||||
.commit
|
||||
.message
|
||||
.as_ref()
|
||||
.map(|details| {
|
||||
crate::render_parsed_markdown(
|
||||
@@ -149,7 +199,8 @@ impl Render for BlameEntryTooltip {
|
||||
.unwrap_or("<no commit message>".into_any());
|
||||
|
||||
let pull_request = self
|
||||
.details
|
||||
.commit
|
||||
.message
|
||||
.as_ref()
|
||||
.and_then(|details| details.pull_request.clone());
|
||||
|
||||
@@ -171,7 +222,7 @@ impl Render for BlameEntryTooltip {
|
||||
.flex_wrap()
|
||||
.children(avatar)
|
||||
.child(author)
|
||||
.when_some(author_email, |this, author_email| {
|
||||
.when(!author_email.is_empty(), |this| {
|
||||
this.child(
|
||||
div()
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
@@ -231,12 +282,16 @@ impl Render for BlameEntryTooltip {
|
||||
.icon_color(Color::Muted)
|
||||
.icon_position(IconPosition::Start)
|
||||
.disabled(
|
||||
self.details.as_ref().map_or(true, |details| {
|
||||
details.permalink.is_none()
|
||||
}),
|
||||
self.commit
|
||||
.message
|
||||
.as_ref()
|
||||
.map_or(true, |details| {
|
||||
details.permalink.is_none()
|
||||
}),
|
||||
)
|
||||
.when_some(
|
||||
self.details
|
||||
self.commit
|
||||
.message
|
||||
.as_ref()
|
||||
.and_then(|details| details.permalink.clone()),
|
||||
|this, url| {
|
||||
@@ -284,7 +339,3 @@ fn blame_entry_timestamp(blame_entry: &BlameEntry, format: time_format::Timestam
|
||||
pub fn blame_entry_relative_timestamp(blame_entry: &BlameEntry) -> String {
|
||||
blame_entry_timestamp(blame_entry, time_format::TimestampFormat::Relative)
|
||||
}
|
||||
|
||||
fn blame_entry_absolute_timestamp(blame_entry: &BlameEntry) -> String {
|
||||
blame_entry_timestamp(blame_entry, time_format::TimestampFormat::MediumAbsolute)
|
||||
}
|
||||
@@ -13,10 +13,10 @@
|
||||
//!
|
||||
//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
|
||||
pub mod actions;
|
||||
mod blame_entry_tooltip;
|
||||
mod blink_manager;
|
||||
mod clangd_ext;
|
||||
mod code_context_menus;
|
||||
pub mod commit_tooltip;
|
||||
pub mod display_map;
|
||||
mod editor_settings;
|
||||
mod editor_settings_controls;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
blame_entry_tooltip::{blame_entry_relative_timestamp, BlameEntryTooltip},
|
||||
code_context_menus::{CodeActionsMenu, MENU_ASIDE_MAX_WIDTH, MENU_ASIDE_MIN_WIDTH, MENU_GAP},
|
||||
commit_tooltip::{blame_entry_relative_timestamp, CommitTooltip, ParsedCommitMessage},
|
||||
display_map::{
|
||||
Block, BlockContext, BlockStyle, DisplaySnapshot, HighlightedChunk, ToDisplayPoint,
|
||||
},
|
||||
@@ -8,7 +8,7 @@ use crate::{
|
||||
CurrentLineHighlight, DoubleClickInMultibuffer, MultiCursorModifier, ScrollBeyondLastLine,
|
||||
ScrollbarDiagnostics, ShowScrollbar,
|
||||
},
|
||||
git::blame::{CommitDetails, GitBlame},
|
||||
git::blame::GitBlame,
|
||||
hover_popover::{
|
||||
self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
|
||||
},
|
||||
@@ -5939,7 +5939,8 @@ fn render_inline_blame_entry(
|
||||
|
||||
let details = blame.read(cx).details_for_entry(&blame_entry);
|
||||
|
||||
let tooltip = cx.new(|_| BlameEntryTooltip::new(blame_entry, details, style, workspace));
|
||||
let tooltip =
|
||||
cx.new(|_| CommitTooltip::blame_entry(blame_entry, details, style.clone(), workspace));
|
||||
|
||||
h_flex()
|
||||
.id("inline-blame")
|
||||
@@ -5989,8 +5990,14 @@ fn render_blame_entry(
|
||||
|
||||
let workspace = editor.read(cx).workspace.as_ref().map(|(w, _)| w.clone());
|
||||
|
||||
let tooltip =
|
||||
cx.new(|_| BlameEntryTooltip::new(blame_entry.clone(), details.clone(), style, workspace));
|
||||
let tooltip = cx.new(|_| {
|
||||
CommitTooltip::blame_entry(
|
||||
blame_entry.clone(),
|
||||
details.clone(),
|
||||
style.clone(),
|
||||
workspace,
|
||||
)
|
||||
});
|
||||
|
||||
h_flex()
|
||||
.w_full()
|
||||
@@ -6040,7 +6047,7 @@ fn render_blame_entry(
|
||||
|
||||
fn deploy_blame_entry_context_menu(
|
||||
blame_entry: &BlameEntry,
|
||||
details: Option<&CommitDetails>,
|
||||
details: Option<&ParsedCommitMessage>,
|
||||
editor: Entity<Editor>,
|
||||
position: gpui::Point<Pixels>,
|
||||
window: &mut Window,
|
||||
|
||||
@@ -2,7 +2,7 @@ use anyhow::Result;
|
||||
use collections::HashMap;
|
||||
use git::{
|
||||
blame::{Blame, BlameEntry},
|
||||
parse_git_remote_url, GitHostingProvider, GitHostingProviderRegistry, Oid, PullRequest,
|
||||
parse_git_remote_url, GitHostingProvider, GitHostingProviderRegistry, Oid,
|
||||
};
|
||||
use gpui::{App, Context, Entity, Subscription, Task};
|
||||
use http_client::HttpClient;
|
||||
@@ -12,8 +12,11 @@ use project::{Project, ProjectItem};
|
||||
use smallvec::SmallVec;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use sum_tree::SumTree;
|
||||
use ui::SharedString;
|
||||
use url::Url;
|
||||
|
||||
use crate::commit_tooltip::ParsedCommitMessage;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct GitBlameEntry {
|
||||
pub rows: u32,
|
||||
@@ -77,7 +80,11 @@ impl GitRemote {
|
||||
self.host.supports_avatars()
|
||||
}
|
||||
|
||||
pub async fn avatar_url(&self, commit: Oid, client: Arc<dyn HttpClient>) -> Option<Url> {
|
||||
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
|
||||
@@ -85,21 +92,11 @@ impl GitRemote {
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CommitDetails {
|
||||
pub message: String,
|
||||
pub parsed_message: ParsedMarkdown,
|
||||
pub permalink: Option<Url>,
|
||||
pub pull_request: Option<PullRequest>,
|
||||
pub remote: Option<GitRemote>,
|
||||
}
|
||||
|
||||
pub struct GitBlame {
|
||||
project: Entity<Project>,
|
||||
buffer: Entity<Buffer>,
|
||||
entries: SumTree<GitBlameEntry>,
|
||||
commit_details: HashMap<Oid, CommitDetails>,
|
||||
commit_details: HashMap<Oid, crate::commit_tooltip::ParsedCommitMessage>,
|
||||
buffer_snapshot: BufferSnapshot,
|
||||
buffer_edits: text::Subscription,
|
||||
task: Task<Result<()>>,
|
||||
@@ -187,7 +184,7 @@ impl GitBlame {
|
||||
self.generated
|
||||
}
|
||||
|
||||
pub fn details_for_entry(&self, entry: &BlameEntry) -> Option<CommitDetails> {
|
||||
pub fn details_for_entry(&self, entry: &BlameEntry) -> Option<ParsedCommitMessage> {
|
||||
self.commit_details.get(&entry.sha).cloned()
|
||||
}
|
||||
|
||||
@@ -480,7 +477,7 @@ async fn parse_commit_messages(
|
||||
deprecated_permalinks: &HashMap<Oid, Url>,
|
||||
provider_registry: Arc<GitHostingProviderRegistry>,
|
||||
languages: &Arc<LanguageRegistry>,
|
||||
) -> HashMap<Oid, CommitDetails> {
|
||||
) -> HashMap<Oid, ParsedCommitMessage> {
|
||||
let mut commit_details = HashMap::default();
|
||||
|
||||
let parsed_remote_url = remote_url
|
||||
@@ -519,8 +516,8 @@ async fn parse_commit_messages(
|
||||
|
||||
commit_details.insert(
|
||||
oid,
|
||||
CommitDetails {
|
||||
message,
|
||||
ParsedCommitMessage {
|
||||
message: message.into(),
|
||||
parsed_message,
|
||||
permalink,
|
||||
remote,
|
||||
|
||||
@@ -132,8 +132,8 @@ pub struct BlameEntry {
|
||||
pub author_time: Option<i64>,
|
||||
pub author_tz: Option<String>,
|
||||
|
||||
pub committer: Option<String>,
|
||||
pub committer_mail: Option<String>,
|
||||
pub committer_name: Option<String>,
|
||||
pub committer_email: Option<String>,
|
||||
pub committer_time: Option<i64>,
|
||||
pub committer_tz: Option<String>,
|
||||
|
||||
@@ -255,10 +255,12 @@ fn parse_git_blame(output: &str) -> Result<Vec<BlameEntry>> {
|
||||
.clone_from(&existing_entry.author_mail);
|
||||
new_entry.author_time = existing_entry.author_time;
|
||||
new_entry.author_tz.clone_from(&existing_entry.author_tz);
|
||||
new_entry.committer.clone_from(&existing_entry.committer);
|
||||
new_entry
|
||||
.committer_mail
|
||||
.clone_from(&existing_entry.committer_mail);
|
||||
.committer_name
|
||||
.clone_from(&existing_entry.committer_name);
|
||||
new_entry
|
||||
.committer_email
|
||||
.clone_from(&existing_entry.committer_email);
|
||||
new_entry.committer_time = existing_entry.committer_time;
|
||||
new_entry
|
||||
.committer_tz
|
||||
@@ -288,8 +290,8 @@ fn parse_git_blame(output: &str) -> Result<Vec<BlameEntry>> {
|
||||
}
|
||||
"author-tz" if is_committed => entry.author_tz = Some(value.into()),
|
||||
|
||||
"committer" if is_committed => entry.committer = Some(value.into()),
|
||||
"committer-mail" if is_committed => entry.committer_mail = Some(value.into()),
|
||||
"committer" if is_committed => entry.committer_name = Some(value.into()),
|
||||
"committer-mail" if is_committed => entry.committer_email = Some(value.into()),
|
||||
"committer-time" if is_committed => {
|
||||
entry.committer_time = Some(value.parse::<i64>()?)
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ actions!(
|
||||
StageAll,
|
||||
UnstageAll,
|
||||
RevertAll,
|
||||
Uncommit,
|
||||
Commit,
|
||||
ClearCommitMessage
|
||||
]
|
||||
|
||||
@@ -4,13 +4,11 @@ use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use collections::BTreeMap;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use gpui::{App, Global};
|
||||
use gpui::{App, Global, SharedString};
|
||||
use http_client::HttpClient;
|
||||
use parking_lot::RwLock;
|
||||
use url::Url;
|
||||
|
||||
use crate::Oid;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct PullRequest {
|
||||
pub number: u32,
|
||||
@@ -83,7 +81,7 @@ pub trait GitHostingProvider {
|
||||
&self,
|
||||
_repo_owner: &str,
|
||||
_repo: &str,
|
||||
_commit: Oid,
|
||||
_commit: SharedString,
|
||||
_http_client: Arc<dyn HttpClient>,
|
||||
) -> Result<Option<Url>> {
|
||||
Ok(None)
|
||||
|
||||
+275
-45
@@ -1,7 +1,7 @@
|
||||
use crate::status::FileStatus;
|
||||
use crate::GitHostingProviderRegistry;
|
||||
use crate::{blame::Blame, status::GitStatus};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use collections::{HashMap, HashSet};
|
||||
use git2::BranchType;
|
||||
use gpui::SharedString;
|
||||
@@ -20,12 +20,63 @@ use sum_tree::MapSeekTarget;
|
||||
use util::command::new_std_command;
|
||||
use util::ResultExt;
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq)]
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct Branch {
|
||||
pub is_head: bool,
|
||||
pub name: SharedString,
|
||||
/// Timestamp of most recent commit, normalized to Unix Epoch format.
|
||||
pub unix_timestamp: Option<i64>,
|
||||
pub upstream: Option<Upstream>,
|
||||
pub most_recent_commit: Option<CommitSummary>,
|
||||
}
|
||||
|
||||
impl Branch {
|
||||
pub fn priority_key(&self) -> (bool, Option<i64>) {
|
||||
(
|
||||
self.is_head,
|
||||
self.most_recent_commit
|
||||
.as_ref()
|
||||
.map(|commit| commit.commit_timestamp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct Upstream {
|
||||
pub ref_name: SharedString,
|
||||
pub tracking: Option<UpstreamTracking>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct UpstreamTracking {
|
||||
pub ahead: u32,
|
||||
pub behind: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct CommitSummary {
|
||||
pub sha: SharedString,
|
||||
pub subject: SharedString,
|
||||
/// This is a unix timestamp
|
||||
pub commit_timestamp: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct CommitDetails {
|
||||
pub sha: SharedString,
|
||||
pub message: SharedString,
|
||||
pub commit_timestamp: i64,
|
||||
pub committer_email: SharedString,
|
||||
pub committer_name: SharedString,
|
||||
}
|
||||
|
||||
pub enum ResetMode {
|
||||
// reset the branch pointer, leave index and worktree unchanged
|
||||
// (this will make it look like things that were committed are now
|
||||
// staged)
|
||||
Soft,
|
||||
// reset the branch pointer and index, leave worktree unchanged
|
||||
// (this makes it look as though things that were committed are now
|
||||
// unstaged)
|
||||
Mixed,
|
||||
}
|
||||
|
||||
pub trait GitRepository: Send + Sync {
|
||||
@@ -45,7 +96,6 @@ pub trait GitRepository: Send + Sync {
|
||||
|
||||
/// Returns the URL of the remote with the given name.
|
||||
fn remote_url(&self, name: &str) -> Option<String>;
|
||||
fn branch_name(&self) -> Option<String>;
|
||||
|
||||
/// Returns the SHA of the current HEAD.
|
||||
fn head_sha(&self) -> Option<String>;
|
||||
@@ -60,6 +110,10 @@ pub trait GitRepository: Send + Sync {
|
||||
fn create_branch(&self, _: &str) -> Result<()>;
|
||||
fn branch_exits(&self, _: &str) -> Result<bool>;
|
||||
|
||||
fn reset(&self, commit: &str, mode: ResetMode) -> Result<()>;
|
||||
|
||||
fn show(&self, commit: &str) -> Result<CommitDetails>;
|
||||
|
||||
fn blame(&self, path: &Path, content: Rope) -> Result<crate::blame::Blame>;
|
||||
|
||||
/// Returns the absolute path to the repository. For worktrees, this will be the path to the
|
||||
@@ -132,6 +186,53 @@ impl GitRepository for RealGitRepository {
|
||||
repo.commondir().into()
|
||||
}
|
||||
|
||||
fn show(&self, commit: &str) -> Result<CommitDetails> {
|
||||
let repo = self.repository.lock();
|
||||
let Ok(commit) = repo.revparse_single(commit)?.into_commit() else {
|
||||
anyhow::bail!("{} is not a commit", commit);
|
||||
};
|
||||
let details = CommitDetails {
|
||||
sha: commit.id().to_string().into(),
|
||||
message: String::from_utf8_lossy(commit.message_raw_bytes())
|
||||
.to_string()
|
||||
.into(),
|
||||
commit_timestamp: commit.time().seconds(),
|
||||
committer_email: String::from_utf8_lossy(commit.committer().email_bytes())
|
||||
.to_string()
|
||||
.into(),
|
||||
committer_name: String::from_utf8_lossy(commit.committer().name_bytes())
|
||||
.to_string()
|
||||
.into(),
|
||||
};
|
||||
Ok(details)
|
||||
}
|
||||
|
||||
fn reset(&self, commit: &str, mode: ResetMode) -> Result<()> {
|
||||
let working_directory = self
|
||||
.repository
|
||||
.lock()
|
||||
.workdir()
|
||||
.context("failed to read git work directory")?
|
||||
.to_path_buf();
|
||||
|
||||
let mode_flag = match mode {
|
||||
ResetMode::Mixed => "--mixed",
|
||||
ResetMode::Soft => "--soft",
|
||||
};
|
||||
|
||||
let output = new_std_command(&self.git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
.args(["reset", mode_flag, commit])
|
||||
.output()?;
|
||||
if !output.status.success() {
|
||||
return Err(anyhow!(
|
||||
"Failed to reset:\n{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_index_text(&self, path: &RepoPath) -> Option<String> {
|
||||
fn logic(repo: &git2::Repository, path: &RepoPath) -> Result<Option<String>> {
|
||||
const STAGE_NORMAL: i32 = 0;
|
||||
@@ -215,13 +316,6 @@ impl GitRepository for RealGitRepository {
|
||||
remote.url().map(|url| url.to_string())
|
||||
}
|
||||
|
||||
fn branch_name(&self) -> Option<String> {
|
||||
let repo = self.repository.lock();
|
||||
let head = repo.head().log_err()?;
|
||||
let branch = String::from_utf8_lossy(head.shorthand_bytes());
|
||||
Some(branch.to_string())
|
||||
}
|
||||
|
||||
fn head_sha(&self) -> Option<String> {
|
||||
Some(self.repository.lock().head().ok()?.target()?.to_string())
|
||||
}
|
||||
@@ -261,33 +355,62 @@ impl GitRepository for RealGitRepository {
|
||||
}
|
||||
|
||||
fn branches(&self) -> Result<Vec<Branch>> {
|
||||
let repo = self.repository.lock();
|
||||
let local_branches = repo.branches(Some(BranchType::Local))?;
|
||||
let valid_branches = local_branches
|
||||
.filter_map(|branch| {
|
||||
branch.ok().and_then(|(branch, _)| {
|
||||
let is_head = branch.is_head();
|
||||
let name = branch
|
||||
.name()
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|name| name.to_string().into())?;
|
||||
let timestamp = branch.get().peel_to_commit().ok()?.time();
|
||||
let unix_timestamp = timestamp.seconds();
|
||||
let timezone_offset = timestamp.offset_minutes();
|
||||
let utc_offset =
|
||||
time::UtcOffset::from_whole_seconds(timezone_offset * 60).ok()?;
|
||||
let unix_timestamp =
|
||||
time::OffsetDateTime::from_unix_timestamp(unix_timestamp).ok()?;
|
||||
Some(Branch {
|
||||
is_head,
|
||||
name,
|
||||
unix_timestamp: Some(unix_timestamp.to_offset(utc_offset).unix_timestamp()),
|
||||
})
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
Ok(valid_branches)
|
||||
let working_directory = self
|
||||
.repository
|
||||
.lock()
|
||||
.workdir()
|
||||
.context("failed to read git work directory")?
|
||||
.to_path_buf();
|
||||
let fields = [
|
||||
"%(HEAD)",
|
||||
"%(objectname)",
|
||||
"%(refname)",
|
||||
"%(upstream)",
|
||||
"%(upstream:track)",
|
||||
"%(committerdate:unix)",
|
||||
"%(contents:subject)",
|
||||
]
|
||||
.join("%00");
|
||||
let args = vec!["for-each-ref", "refs/heads/*", "--format", &fields];
|
||||
|
||||
let output = new_std_command(&self.git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
.args(args)
|
||||
.output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(anyhow!(
|
||||
"Failed to git git branches:\n{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
}
|
||||
|
||||
let input = String::from_utf8_lossy(&output.stdout);
|
||||
|
||||
let mut branches = parse_branch_input(&input)?;
|
||||
if branches.is_empty() {
|
||||
let args = vec!["symbolic-ref", "--quiet", "--short", "HEAD"];
|
||||
|
||||
let output = new_std_command(&self.git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
.args(args)
|
||||
.output()?;
|
||||
|
||||
// git symbolic-ref returns a non-0 exit code if HEAD points
|
||||
// to something other than a branch
|
||||
if output.status.success() {
|
||||
let name = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
|
||||
branches.push(Branch {
|
||||
name: name.into(),
|
||||
is_head: true,
|
||||
upstream: None,
|
||||
most_recent_commit: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(branches)
|
||||
}
|
||||
|
||||
fn change_branch(&self, name: &str) -> Result<()> {
|
||||
@@ -478,11 +601,6 @@ impl GitRepository for FakeGitRepository {
|
||||
None
|
||||
}
|
||||
|
||||
fn branch_name(&self) -> Option<String> {
|
||||
let state = self.state.lock();
|
||||
state.current_branch_name.clone()
|
||||
}
|
||||
|
||||
fn head_sha(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
@@ -491,6 +609,14 @@ impl GitRepository for FakeGitRepository {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn show(&self, _: &str) -> Result<CommitDetails> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn reset(&self, _: &str, _: ResetMode) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn path(&self) -> PathBuf {
|
||||
let state = self.state.lock();
|
||||
state.path.clone()
|
||||
@@ -533,7 +659,8 @@ impl GitRepository for FakeGitRepository {
|
||||
.map(|branch_name| Branch {
|
||||
is_head: Some(branch_name) == current_branch.as_ref(),
|
||||
name: branch_name.into(),
|
||||
unix_timestamp: None,
|
||||
most_recent_commit: None,
|
||||
upstream: None,
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
@@ -703,3 +830,106 @@ impl<'a> MapSeekTarget<RepoPath> for RepoPathDescendants<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_branch_input(input: &str) -> Result<Vec<Branch>> {
|
||||
let mut branches = Vec::new();
|
||||
for line in input.split('\n') {
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let mut fields = line.split('\x00');
|
||||
let is_current_branch = fields.next().context("no HEAD")? == "*";
|
||||
let head_sha: SharedString = fields.next().context("no objectname")?.to_string().into();
|
||||
let ref_name: SharedString = fields
|
||||
.next()
|
||||
.context("no refname")?
|
||||
.strip_prefix("refs/heads/")
|
||||
.context("unexpected format for refname")?
|
||||
.to_string()
|
||||
.into();
|
||||
let upstream_name = fields.next().context("no upstream")?.to_string();
|
||||
let upstream_tracking = parse_upstream_track(fields.next().context("no upstream:track")?)?;
|
||||
let commiterdate = fields.next().context("no committerdate")?.parse::<i64>()?;
|
||||
let subject: SharedString = fields
|
||||
.next()
|
||||
.context("no contents:subject")?
|
||||
.to_string()
|
||||
.into();
|
||||
|
||||
branches.push(Branch {
|
||||
is_head: is_current_branch,
|
||||
name: ref_name,
|
||||
most_recent_commit: Some(CommitSummary {
|
||||
sha: head_sha,
|
||||
subject,
|
||||
commit_timestamp: commiterdate,
|
||||
}),
|
||||
upstream: if upstream_name.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Upstream {
|
||||
ref_name: upstream_name.into(),
|
||||
tracking: upstream_tracking,
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
Ok(branches)
|
||||
}
|
||||
|
||||
fn parse_upstream_track(upstream_track: &str) -> Result<Option<UpstreamTracking>> {
|
||||
if upstream_track == "" {
|
||||
return Ok(Some(UpstreamTracking {
|
||||
ahead: 0,
|
||||
behind: 0,
|
||||
}));
|
||||
}
|
||||
|
||||
let upstream_track = upstream_track
|
||||
.strip_prefix("[")
|
||||
.ok_or_else(|| anyhow!("missing ["))?;
|
||||
let upstream_track = upstream_track
|
||||
.strip_suffix("]")
|
||||
.ok_or_else(|| anyhow!("missing ["))?;
|
||||
let mut ahead: u32 = 0;
|
||||
let mut behind: u32 = 0;
|
||||
for component in upstream_track.split(", ") {
|
||||
if component == "gone" {
|
||||
return Ok(None);
|
||||
}
|
||||
if let Some(ahead_num) = component.strip_prefix("ahead ") {
|
||||
ahead = ahead_num.parse::<u32>()?;
|
||||
}
|
||||
if let Some(behind_num) = component.strip_prefix("behind ") {
|
||||
behind = behind_num.parse::<u32>()?;
|
||||
}
|
||||
}
|
||||
Ok(Some(UpstreamTracking { ahead, behind }))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_branches_parsing() {
|
||||
// suppress "help: octal escapes are not supported, `\0` is always null"
|
||||
#[allow(clippy::octal_escapes)]
|
||||
let input = "*\0060964da10574cd9bf06463a53bf6e0769c5c45e\0refs/heads/zed-patches\0refs/remotes/origin/zed-patches\0\01733187470\0generated protobuf\n";
|
||||
assert_eq!(
|
||||
parse_branch_input(&input).unwrap(),
|
||||
vec![Branch {
|
||||
is_head: true,
|
||||
name: "zed-patches".into(),
|
||||
upstream: Some(Upstream {
|
||||
ref_name: "refs/remotes/origin/zed-patches".into(),
|
||||
tracking: Some(UpstreamTracking {
|
||||
ahead: 0,
|
||||
behind: 0
|
||||
})
|
||||
}),
|
||||
most_recent_commit: Some(CommitSummary {
|
||||
sha: "060964da10574cd9bf06463a53bf6e0769c5c45e".into(),
|
||||
subject: "generated protobuf".into(),
|
||||
commit_timestamp: 1733187470,
|
||||
})
|
||||
}]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
"author_mail": "<64036912+mmkaram@users.noreply.github.com>",
|
||||
"author_time": 1708621949,
|
||||
"author_tz": "-0800",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1708621949,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Add option to either use system clipboard or vim clipboard (#7936)",
|
||||
@@ -29,8 +29,8 @@
|
||||
"author_mail": "<64036912+mmkaram@users.noreply.github.com>",
|
||||
"author_time": 1708621949,
|
||||
"author_tz": "-0800",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1708621949,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Add option to either use system clipboard or vim clipboard (#7936)",
|
||||
@@ -48,8 +48,8 @@
|
||||
"author_mail": "<64036912+mmkaram@users.noreply.github.com>",
|
||||
"author_time": 1708621949,
|
||||
"author_tz": "-0800",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1708621949,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Add option to either use system clipboard or vim clipboard (#7936)",
|
||||
@@ -67,8 +67,8 @@
|
||||
"author_mail": "<64036912+mmkaram@users.noreply.github.com>",
|
||||
"author_time": 1708621949,
|
||||
"author_tz": "-0800",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1708621949,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Add option to either use system clipboard or vim clipboard (#7936)",
|
||||
@@ -86,8 +86,8 @@
|
||||
"author_mail": "<64036912+mmkaram@users.noreply.github.com>",
|
||||
"author_time": 1708621949,
|
||||
"author_tz": "-0800",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1708621949,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Add option to either use system clipboard or vim clipboard (#7936)",
|
||||
@@ -105,8 +105,8 @@
|
||||
"author_mail": "<64036912+mmkaram@users.noreply.github.com>",
|
||||
"author_time": 1708621949,
|
||||
"author_tz": "-0800",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1708621949,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Add option to either use system clipboard or vim clipboard (#7936)",
|
||||
@@ -124,8 +124,8 @@
|
||||
"author_mail": "<64036912+mmkaram@users.noreply.github.com>",
|
||||
"author_time": 1708621949,
|
||||
"author_tz": "-0800",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1708621949,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Add option to either use system clipboard or vim clipboard (#7936)",
|
||||
@@ -143,8 +143,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1707520689,
|
||||
"author_tz": "-0700",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1707520689,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Highlight selections on vim yank (#7638)",
|
||||
@@ -162,8 +162,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1707520689,
|
||||
"author_tz": "-0700",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1707520689,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Highlight selections on vim yank (#7638)",
|
||||
@@ -181,8 +181,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1707520689,
|
||||
"author_tz": "-0700",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1707520689,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Highlight selections on vim yank (#7638)",
|
||||
@@ -200,8 +200,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1707520689,
|
||||
"author_tz": "-0700",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1707520689,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Highlight selections on vim yank (#7638)",
|
||||
@@ -219,8 +219,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1707520689,
|
||||
"author_tz": "-0700",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1707520689,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Highlight selections on vim yank (#7638)",
|
||||
@@ -238,8 +238,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1707520689,
|
||||
"author_tz": "-0700",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1707520689,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Highlight selections on vim yank (#7638)",
|
||||
@@ -257,8 +257,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1707520689,
|
||||
"author_tz": "-0700",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1707520689,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Highlight selections on vim yank (#7638)",
|
||||
@@ -276,8 +276,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1707520689,
|
||||
"author_tz": "-0700",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1707520689,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Highlight selections on vim yank (#7638)",
|
||||
@@ -295,8 +295,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1707520689,
|
||||
"author_tz": "-0700",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1707520689,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Highlight selections on vim yank (#7638)",
|
||||
@@ -314,8 +314,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1707520689,
|
||||
"author_tz": "-0700",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1707520689,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Highlight selections on vim yank (#7638)",
|
||||
@@ -333,8 +333,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1707520689,
|
||||
"author_tz": "-0700",
|
||||
"committer": "GitHub",
|
||||
"committer_mail": "<noreply@github.com>",
|
||||
"committer_name": "GitHub",
|
||||
"committer_email": "<noreply@github.com>",
|
||||
"committer_time": 1707520689,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Highlight selections on vim yank (#7638)",
|
||||
@@ -352,8 +352,8 @@
|
||||
"author_mail": "<maxbrunsfeld@gmail.com>",
|
||||
"author_time": 1705619094,
|
||||
"author_tz": "-0800",
|
||||
"committer": "Max Brunsfeld",
|
||||
"committer_mail": "<maxbrunsfeld@gmail.com>",
|
||||
"committer_name": "Max Brunsfeld",
|
||||
"committer_email": "<maxbrunsfeld@gmail.com>",
|
||||
"committer_time": 1705619205,
|
||||
"committer_tz": "-0800",
|
||||
"summary": "Merge branch 'main' into language-api-docs",
|
||||
@@ -371,8 +371,8 @@
|
||||
"author_mail": "<maxbrunsfeld@gmail.com>",
|
||||
"author_time": 1705619094,
|
||||
"author_tz": "-0800",
|
||||
"committer": "Max Brunsfeld",
|
||||
"committer_mail": "<maxbrunsfeld@gmail.com>",
|
||||
"committer_name": "Max Brunsfeld",
|
||||
"committer_email": "<maxbrunsfeld@gmail.com>",
|
||||
"committer_time": 1705619205,
|
||||
"committer_tz": "-0800",
|
||||
"summary": "Merge branch 'main' into language-api-docs",
|
||||
@@ -390,8 +390,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1694798044,
|
||||
"author_tz": "-0600",
|
||||
"committer": "Conrad Irwin",
|
||||
"committer_mail": "<conrad@zed.dev>",
|
||||
"committer_name": "Conrad Irwin",
|
||||
"committer_email": "<conrad@zed.dev>",
|
||||
"committer_time": 1694798044,
|
||||
"committer_tz": "-0600",
|
||||
"summary": "Fix Y on last line with no trailing new line",
|
||||
@@ -409,8 +409,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1694798044,
|
||||
"author_tz": "-0600",
|
||||
"committer": "Conrad Irwin",
|
||||
"committer_mail": "<conrad@zed.dev>",
|
||||
"committer_name": "Conrad Irwin",
|
||||
"committer_email": "<conrad@zed.dev>",
|
||||
"committer_time": 1694798044,
|
||||
"committer_tz": "-0600",
|
||||
"summary": "Fix Y on last line with no trailing new line",
|
||||
@@ -428,8 +428,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1692855942,
|
||||
"author_tz": "-0600",
|
||||
"committer": "Conrad Irwin",
|
||||
"committer_mail": "<conrad@zed.dev>",
|
||||
"committer_name": "Conrad Irwin",
|
||||
"committer_email": "<conrad@zed.dev>",
|
||||
"committer_time": 1692856812,
|
||||
"committer_tz": "-0600",
|
||||
"summary": "vim: Fix linewise copy of last line with no trailing newline",
|
||||
@@ -447,8 +447,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1692855942,
|
||||
"author_tz": "-0600",
|
||||
"committer": "Conrad Irwin",
|
||||
"committer_mail": "<conrad@zed.dev>",
|
||||
"committer_name": "Conrad Irwin",
|
||||
"committer_email": "<conrad@zed.dev>",
|
||||
"committer_time": 1692856812,
|
||||
"committer_tz": "-0600",
|
||||
"summary": "vim: Fix linewise copy of last line with no trailing newline",
|
||||
@@ -466,8 +466,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1692855942,
|
||||
"author_tz": "-0600",
|
||||
"committer": "Conrad Irwin",
|
||||
"committer_mail": "<conrad@zed.dev>",
|
||||
"committer_name": "Conrad Irwin",
|
||||
"committer_email": "<conrad@zed.dev>",
|
||||
"committer_time": 1692856812,
|
||||
"committer_tz": "-0600",
|
||||
"summary": "vim: Fix linewise copy of last line with no trailing newline",
|
||||
@@ -485,8 +485,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1692855942,
|
||||
"author_tz": "-0600",
|
||||
"committer": "Conrad Irwin",
|
||||
"committer_mail": "<conrad@zed.dev>",
|
||||
"committer_name": "Conrad Irwin",
|
||||
"committer_email": "<conrad@zed.dev>",
|
||||
"committer_time": 1692856812,
|
||||
"committer_tz": "-0600",
|
||||
"summary": "vim: Fix linewise copy of last line with no trailing newline",
|
||||
@@ -504,8 +504,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1692855942,
|
||||
"author_tz": "-0600",
|
||||
"committer": "Conrad Irwin",
|
||||
"committer_mail": "<conrad@zed.dev>",
|
||||
"committer_name": "Conrad Irwin",
|
||||
"committer_email": "<conrad@zed.dev>",
|
||||
"committer_time": 1692856812,
|
||||
"committer_tz": "-0600",
|
||||
"summary": "vim: Fix linewise copy of last line with no trailing newline",
|
||||
@@ -523,8 +523,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1692644159,
|
||||
"author_tz": "-0600",
|
||||
"committer": "Conrad Irwin",
|
||||
"committer_mail": "<conrad@zed.dev>",
|
||||
"committer_name": "Conrad Irwin",
|
||||
"committer_email": "<conrad@zed.dev>",
|
||||
"committer_time": 1692732477,
|
||||
"committer_tz": "-0600",
|
||||
"summary": "Rewrite paste",
|
||||
@@ -542,8 +542,8 @@
|
||||
"author_mail": "<conrad@zed.dev>",
|
||||
"author_time": 1692644159,
|
||||
"author_tz": "-0600",
|
||||
"committer": "Conrad Irwin",
|
||||
"committer_mail": "<conrad@zed.dev>",
|
||||
"committer_name": "Conrad Irwin",
|
||||
"committer_email": "<conrad@zed.dev>",
|
||||
"committer_time": 1692732477,
|
||||
"committer_tz": "-0600",
|
||||
"summary": "Rewrite paste",
|
||||
@@ -561,8 +561,8 @@
|
||||
"author_mail": "<maxbrunsfeld@gmail.com>",
|
||||
"author_time": 1659072896,
|
||||
"author_tz": "-0700",
|
||||
"committer": "Max Brunsfeld",
|
||||
"committer_mail": "<maxbrunsfeld@gmail.com>",
|
||||
"committer_name": "Max Brunsfeld",
|
||||
"committer_email": "<maxbrunsfeld@gmail.com>",
|
||||
"committer_time": 1659073230,
|
||||
"committer_tz": "-0700",
|
||||
"summary": ":art: Rename and simplify some autoindent stuff",
|
||||
@@ -580,8 +580,8 @@
|
||||
"author_mail": "<kay@the-simmons.net>",
|
||||
"author_time": 1653424557,
|
||||
"author_tz": "-0700",
|
||||
"committer": "Kaylee Simmons",
|
||||
"committer_mail": "<kay@the-simmons.net>",
|
||||
"committer_name": "Kaylee Simmons",
|
||||
"committer_email": "<kay@the-simmons.net>",
|
||||
"committer_time": 1653609725,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Unify visual line_mode and non line_mode operators",
|
||||
@@ -599,8 +599,8 @@
|
||||
"author_mail": "<kay@the-simmons.net>",
|
||||
"author_time": 1653007350,
|
||||
"author_tz": "-0700",
|
||||
"committer": "Kaylee Simmons",
|
||||
"committer_mail": "<kay@the-simmons.net>",
|
||||
"committer_name": "Kaylee Simmons",
|
||||
"committer_email": "<kay@the-simmons.net>",
|
||||
"committer_time": 1653609725,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Enable copy and paste in vim mode",
|
||||
@@ -618,8 +618,8 @@
|
||||
"author_mail": "<kay@the-simmons.net>",
|
||||
"author_time": 1653007350,
|
||||
"author_tz": "-0700",
|
||||
"committer": "Kaylee Simmons",
|
||||
"committer_mail": "<kay@the-simmons.net>",
|
||||
"committer_name": "Kaylee Simmons",
|
||||
"committer_email": "<kay@the-simmons.net>",
|
||||
"committer_time": 1653609725,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Enable copy and paste in vim mode",
|
||||
@@ -637,8 +637,8 @@
|
||||
"author_mail": "<kay@the-simmons.net>",
|
||||
"author_time": 1653007350,
|
||||
"author_tz": "-0700",
|
||||
"committer": "Kaylee Simmons",
|
||||
"committer_mail": "<kay@the-simmons.net>",
|
||||
"committer_name": "Kaylee Simmons",
|
||||
"committer_email": "<kay@the-simmons.net>",
|
||||
"committer_time": 1653609725,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Enable copy and paste in vim mode",
|
||||
@@ -656,8 +656,8 @@
|
||||
"author_mail": "<kay@the-simmons.net>",
|
||||
"author_time": 1653007350,
|
||||
"author_tz": "-0700",
|
||||
"committer": "Kaylee Simmons",
|
||||
"committer_mail": "<kay@the-simmons.net>",
|
||||
"committer_name": "Kaylee Simmons",
|
||||
"committer_email": "<kay@the-simmons.net>",
|
||||
"committer_time": 1653609725,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Enable copy and paste in vim mode",
|
||||
@@ -675,8 +675,8 @@
|
||||
"author_mail": "<kay@the-simmons.net>",
|
||||
"author_time": 1653007350,
|
||||
"author_tz": "-0700",
|
||||
"committer": "Kaylee Simmons",
|
||||
"committer_mail": "<kay@the-simmons.net>",
|
||||
"committer_name": "Kaylee Simmons",
|
||||
"committer_email": "<kay@the-simmons.net>",
|
||||
"committer_time": 1653609725,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Enable copy and paste in vim mode",
|
||||
@@ -694,8 +694,8 @@
|
||||
"author_mail": "<kay@the-simmons.net>",
|
||||
"author_time": 1653007350,
|
||||
"author_tz": "-0700",
|
||||
"committer": "Kaylee Simmons",
|
||||
"committer_mail": "<kay@the-simmons.net>",
|
||||
"committer_name": "Kaylee Simmons",
|
||||
"committer_email": "<kay@the-simmons.net>",
|
||||
"committer_time": 1653609725,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Enable copy and paste in vim mode",
|
||||
@@ -713,8 +713,8 @@
|
||||
"author_mail": "<kay@the-simmons.net>",
|
||||
"author_time": 1653007350,
|
||||
"author_tz": "-0700",
|
||||
"committer": "Kaylee Simmons",
|
||||
"committer_mail": "<kay@the-simmons.net>",
|
||||
"committer_name": "Kaylee Simmons",
|
||||
"committer_email": "<kay@the-simmons.net>",
|
||||
"committer_time": 1653609725,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Enable copy and paste in vim mode",
|
||||
@@ -732,8 +732,8 @@
|
||||
"author_mail": "<kay@the-simmons.net>",
|
||||
"author_time": 1653007350,
|
||||
"author_tz": "-0700",
|
||||
"committer": "Kaylee Simmons",
|
||||
"committer_mail": "<kay@the-simmons.net>",
|
||||
"committer_name": "Kaylee Simmons",
|
||||
"committer_email": "<kay@the-simmons.net>",
|
||||
"committer_time": 1653609725,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Enable copy and paste in vim mode",
|
||||
@@ -751,8 +751,8 @@
|
||||
"author_mail": "<kay@the-simmons.net>",
|
||||
"author_time": 1653007350,
|
||||
"author_tz": "-0700",
|
||||
"committer": "Kaylee Simmons",
|
||||
"committer_mail": "<kay@the-simmons.net>",
|
||||
"committer_name": "Kaylee Simmons",
|
||||
"committer_email": "<kay@the-simmons.net>",
|
||||
"committer_time": 1653609725,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Enable copy and paste in vim mode",
|
||||
@@ -770,8 +770,8 @@
|
||||
"author_mail": "<kay@the-simmons.net>",
|
||||
"author_time": 1653007350,
|
||||
"author_tz": "-0700",
|
||||
"committer": "Kaylee Simmons",
|
||||
"committer_mail": "<kay@the-simmons.net>",
|
||||
"committer_name": "Kaylee Simmons",
|
||||
"committer_email": "<kay@the-simmons.net>",
|
||||
"committer_time": 1653609725,
|
||||
"committer_tz": "-0700",
|
||||
"summary": "Enable copy and paste in vim mode",
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
"author_mail": "<mrnugget@gmail.com>",
|
||||
"author_time": 1710764113,
|
||||
"author_tz": "+0100",
|
||||
"committer": "Thorsten Ball",
|
||||
"committer_mail": "<mrnugget@gmail.com>",
|
||||
"committer_name": "Thorsten Ball",
|
||||
"committer_email": "<mrnugget@gmail.com>",
|
||||
"committer_time": 1710764113,
|
||||
"committer_tz": "+0100",
|
||||
"summary": "Another commit",
|
||||
@@ -29,8 +29,8 @@
|
||||
"author_mail": "<mrnugget@gmail.com>",
|
||||
"author_time": 1710764113,
|
||||
"author_tz": "+0100",
|
||||
"committer": "Thorsten Ball",
|
||||
"committer_mail": "<mrnugget@gmail.com>",
|
||||
"committer_name": "Thorsten Ball",
|
||||
"committer_email": "<mrnugget@gmail.com>",
|
||||
"committer_time": 1710764113,
|
||||
"committer_tz": "+0100",
|
||||
"summary": "Another commit",
|
||||
@@ -48,8 +48,8 @@
|
||||
"author_mail": "<mrnugget@gmail.com>",
|
||||
"author_time": 1710764087,
|
||||
"author_tz": "+0100",
|
||||
"committer": "Thorsten Ball",
|
||||
"committer_mail": "<mrnugget@gmail.com>",
|
||||
"committer_name": "Thorsten Ball",
|
||||
"committer_email": "<mrnugget@gmail.com>",
|
||||
"committer_time": 1710764087,
|
||||
"committer_tz": "+0100",
|
||||
"summary": "Another commit",
|
||||
@@ -67,8 +67,8 @@
|
||||
"author_mail": "<mrnugget@gmail.com>",
|
||||
"author_time": 1710764087,
|
||||
"author_tz": "+0100",
|
||||
"committer": "Thorsten Ball",
|
||||
"committer_mail": "<mrnugget@gmail.com>",
|
||||
"committer_name": "Thorsten Ball",
|
||||
"committer_email": "<mrnugget@gmail.com>",
|
||||
"committer_time": 1710764087,
|
||||
"committer_tz": "+0100",
|
||||
"summary": "Another commit",
|
||||
@@ -86,8 +86,8 @@
|
||||
"author_mail": "<mrnugget@gmail.com>",
|
||||
"author_time": 1709299737,
|
||||
"author_tz": "+0100",
|
||||
"committer": "Thorsten Ball",
|
||||
"committer_mail": "<mrnugget@gmail.com>",
|
||||
"committer_name": "Thorsten Ball",
|
||||
"committer_email": "<mrnugget@gmail.com>",
|
||||
"committer_time": 1709299737,
|
||||
"committer_tz": "+0100",
|
||||
"summary": "Initial",
|
||||
@@ -105,8 +105,8 @@
|
||||
"author_mail": "<mrnugget@gmail.com>",
|
||||
"author_time": 1709299737,
|
||||
"author_tz": "+0100",
|
||||
"committer": "Thorsten Ball",
|
||||
"committer_mail": "<mrnugget@gmail.com>",
|
||||
"committer_name": "Thorsten Ball",
|
||||
"committer_email": "<mrnugget@gmail.com>",
|
||||
"committer_time": 1709299737,
|
||||
"committer_tz": "+0100",
|
||||
"summary": "Initial",
|
||||
@@ -124,8 +124,8 @@
|
||||
"author_mail": "<mrnugget@gmail.com>",
|
||||
"author_time": 1709299737,
|
||||
"author_tz": "+0100",
|
||||
"committer": "Thorsten Ball",
|
||||
"committer_mail": "<mrnugget@gmail.com>",
|
||||
"committer_name": "Thorsten Ball",
|
||||
"committer_email": "<mrnugget@gmail.com>",
|
||||
"committer_time": 1709299737,
|
||||
"committer_tz": "+0100",
|
||||
"summary": "Initial",
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
"author_mail": "<mrnugget@example.com>",
|
||||
"author_time": 1709808710,
|
||||
"author_tz": "+0100",
|
||||
"committer": "Thorsten Ball",
|
||||
"committer_mail": "<mrnugget@example.com>",
|
||||
"committer_name": "Thorsten Ball",
|
||||
"committer_email": "<mrnugget@example.com>",
|
||||
"committer_time": 1709808710,
|
||||
"committer_tz": "+0100",
|
||||
"summary": "Make a commit",
|
||||
@@ -29,8 +29,8 @@
|
||||
"author_mail": "<joe.schmoe@example.com>",
|
||||
"author_time": 1709741400,
|
||||
"author_tz": "+0100",
|
||||
"committer": "Joe Schmoe",
|
||||
"committer_mail": "<joe.schmoe@example.com>",
|
||||
"committer_name": "Joe Schmoe",
|
||||
"committer_email": "<joe.schmoe@example.com>",
|
||||
"committer_time": 1709741400,
|
||||
"committer_tz": "+0100",
|
||||
"summary": "Joe's cool commit",
|
||||
@@ -48,8 +48,8 @@
|
||||
"author_mail": "<joe.schmoe@example.com>",
|
||||
"author_time": 1709741400,
|
||||
"author_tz": "+0100",
|
||||
"committer": "Joe Schmoe",
|
||||
"committer_mail": "<joe.schmoe@example.com>",
|
||||
"committer_name": "Joe Schmoe",
|
||||
"committer_email": "<joe.schmoe@example.com>",
|
||||
"committer_time": 1709741400,
|
||||
"committer_tz": "+0100",
|
||||
"summary": "Joe's cool commit",
|
||||
@@ -67,8 +67,8 @@
|
||||
"author_mail": "<joe.schmoe@example.com>",
|
||||
"author_time": 1709741400,
|
||||
"author_tz": "+0100",
|
||||
"committer": "Joe Schmoe",
|
||||
"committer_mail": "<joe.schmoe@example.com>",
|
||||
"committer_name": "Joe Schmoe",
|
||||
"committer_email": "<joe.schmoe@example.com>",
|
||||
"committer_time": 1709741400,
|
||||
"committer_tz": "+0100",
|
||||
"summary": "Joe's cool commit",
|
||||
@@ -86,8 +86,8 @@
|
||||
"author_mail": "<mrnugget@example.com>",
|
||||
"author_time": 1709129122,
|
||||
"author_tz": "+0100",
|
||||
"committer": "Thorsten Ball",
|
||||
"committer_mail": "<mrnugget@example.com>",
|
||||
"committer_name": "Thorsten Ball",
|
||||
"committer_email": "<mrnugget@example.com>",
|
||||
"committer_time": 1709129122,
|
||||
"committer_tz": "+0100",
|
||||
"summary": "Get to a state where eslint would change code and imports",
|
||||
@@ -105,8 +105,8 @@
|
||||
"author_mail": "<mrnugget@example.com>",
|
||||
"author_time": 1709128963,
|
||||
"author_tz": "+0100",
|
||||
"committer": "Thorsten Ball",
|
||||
"committer_mail": "<mrnugget@example.com>",
|
||||
"committer_name": "Thorsten Ball",
|
||||
"committer_email": "<mrnugget@example.com>",
|
||||
"committer_time": 1709128963,
|
||||
"committer_tz": "+0100",
|
||||
"summary": "Add some stuff",
|
||||
@@ -124,8 +124,8 @@
|
||||
"author_mail": "<mrnugget@example.com>",
|
||||
"author_time": 1709128963,
|
||||
"author_tz": "+0100",
|
||||
"committer": "Thorsten Ball",
|
||||
"committer_mail": "<mrnugget@example.com>",
|
||||
"committer_name": "Thorsten Ball",
|
||||
"committer_email": "<mrnugget@example.com>",
|
||||
"committer_time": 1709128963,
|
||||
"committer_tz": "+0100",
|
||||
"summary": "Add some stuff",
|
||||
|
||||
@@ -4,12 +4,13 @@ use std::sync::Arc;
|
||||
use anyhow::{bail, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::AsyncReadExt;
|
||||
use gpui::SharedString;
|
||||
use http_client::{AsyncBody, HttpClient, HttpRequestExt, Request};
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
use git::{
|
||||
BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, Oid, ParsedGitRemote,
|
||||
BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, ParsedGitRemote,
|
||||
RemoteUrl,
|
||||
};
|
||||
|
||||
@@ -160,7 +161,7 @@ impl GitHostingProvider for Codeberg {
|
||||
&self,
|
||||
repo_owner: &str,
|
||||
repo: &str,
|
||||
commit: Oid,
|
||||
commit: SharedString,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
) -> Result<Option<Url>> {
|
||||
let commit = commit.to_string();
|
||||
|
||||
@@ -4,13 +4,14 @@ use std::sync::{Arc, LazyLock};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::AsyncReadExt;
|
||||
use gpui::SharedString;
|
||||
use http_client::{AsyncBody, HttpClient, HttpRequestExt, Request};
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
use git::{
|
||||
BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, Oid, ParsedGitRemote,
|
||||
BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, ParsedGitRemote,
|
||||
PullRequest, RemoteUrl,
|
||||
};
|
||||
|
||||
@@ -178,7 +179,7 @@ impl GitHostingProvider for Github {
|
||||
&self,
|
||||
repo_owner: &str,
|
||||
repo: &str,
|
||||
commit: Oid,
|
||||
commit: SharedString,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
) -> Result<Option<Url>> {
|
||||
let commit = commit.to_string();
|
||||
|
||||
@@ -36,6 +36,8 @@ serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
theme.workspace = true
|
||||
time.workspace = true
|
||||
time_format.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
@@ -190,9 +190,7 @@ impl PickerDelegate for BranchListDelegate {
|
||||
// Truncate list of recent branches
|
||||
// Do a partial sort to show recent-ish branches first.
|
||||
branches.select_nth_unstable_by(RECENT_BRANCHES_COUNT - 1, |lhs, rhs| {
|
||||
rhs.is_head
|
||||
.cmp(&lhs.is_head)
|
||||
.then(rhs.unix_timestamp.cmp(&lhs.unix_timestamp))
|
||||
rhs.priority_key().cmp(&lhs.priority_key())
|
||||
});
|
||||
branches.truncate(RECENT_BRANCHES_COUNT);
|
||||
}
|
||||
@@ -255,6 +253,25 @@ impl PickerDelegate for BranchListDelegate {
|
||||
let Some(branch) = self.matches.get(self.selected_index()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let current_branch = self
|
||||
.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace
|
||||
.project()
|
||||
.read(cx)
|
||||
.active_repository(cx)
|
||||
.and_then(|repo| repo.read(cx).branch())
|
||||
.map(|branch| branch.name.to_string())
|
||||
})
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
if current_branch == Some(branch.name().to_string()) {
|
||||
cx.emit(DismissEvent);
|
||||
return;
|
||||
}
|
||||
|
||||
cx.spawn_in(window, {
|
||||
let branch = branch.clone();
|
||||
|picker, mut cx| async move {
|
||||
|
||||
+233
-32
@@ -6,13 +6,15 @@ use crate::{
|
||||
};
|
||||
use collections::HashMap;
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use editor::commit_tooltip::CommitTooltip;
|
||||
use editor::{
|
||||
actions::MoveToEnd, scroll::ScrollbarAutoHide, Editor, EditorElement, EditorMode,
|
||||
EditorSettings, MultiBuffer, ShowScrollbar,
|
||||
};
|
||||
use git::repository::{CommitDetails, ResetMode};
|
||||
use git::{repository::RepoPath, status::FileStatus, Commit, ToggleStaged};
|
||||
use gpui::*;
|
||||
use language::{Buffer, File};
|
||||
use language::{markdown, Buffer, File, ParsedMarkdown};
|
||||
use menu::{SelectFirst, SelectLast, SelectNext, SelectPrev};
|
||||
use multi_buffer::ExcerptInfo;
|
||||
use panel::{panel_editor_container, panel_editor_style, panel_filled_button, PanelHeader};
|
||||
@@ -23,6 +25,7 @@ use project::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::Settings as _;
|
||||
use std::{collections::HashSet, path::PathBuf, sync::Arc, time::Duration, usize};
|
||||
use time::OffsetDateTime;
|
||||
use ui::{
|
||||
prelude::*, ButtonLike, Checkbox, CheckboxWithLabel, Divider, DividerColor, ElevationIndex,
|
||||
IndentGuideColors, ListItem, ListItemSpacing, Scrollbar, ScrollbarState, Tooltip,
|
||||
@@ -207,7 +210,7 @@ impl GitPanel {
|
||||
) -> Entity<Self> {
|
||||
let fs = workspace.app_state().fs.clone();
|
||||
let project = workspace.project().clone();
|
||||
let git_state = project.read(cx).git_state().clone();
|
||||
let git_store = project.read(cx).git_store().clone();
|
||||
let active_repository = project.read(cx).active_repository(cx);
|
||||
let workspace = cx.entity().downgrade();
|
||||
|
||||
@@ -231,14 +234,14 @@ impl GitPanel {
|
||||
let scroll_handle = UniformListScrollHandle::new();
|
||||
|
||||
cx.subscribe_in(
|
||||
&git_state,
|
||||
&git_store,
|
||||
window,
|
||||
move |this, git_state, event, window, cx| match event {
|
||||
move |this, git_store, event, window, cx| match event {
|
||||
GitEvent::FileSystemUpdated => {
|
||||
this.schedule_update(false, window, cx);
|
||||
}
|
||||
GitEvent::ActiveRepositoryChanged | GitEvent::GitStateUpdated => {
|
||||
this.active_repository = git_state.read(cx).active_repository();
|
||||
this.active_repository = git_store.read(cx).active_repository();
|
||||
this.schedule_update(true, window, cx);
|
||||
}
|
||||
},
|
||||
@@ -744,6 +747,40 @@ impl GitPanel {
|
||||
self.pending_commit = Some(task);
|
||||
}
|
||||
|
||||
fn uncommit(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let Some(repo) = self.active_repository.clone() else {
|
||||
return;
|
||||
};
|
||||
let prior_head = self.load_commit_details("HEAD", cx);
|
||||
|
||||
let task = cx.spawn(|_, mut cx| async move {
|
||||
let prior_head = prior_head.await?;
|
||||
|
||||
repo.update(&mut cx, |repo, _| repo.reset("HEAD^", ResetMode::Soft))?
|
||||
.await??;
|
||||
|
||||
Ok(prior_head)
|
||||
});
|
||||
|
||||
let task = cx.spawn_in(window, |this, mut cx| async move {
|
||||
let result = task.await;
|
||||
this.update_in(&mut cx, |this, window, cx| {
|
||||
this.pending_commit.take();
|
||||
match result {
|
||||
Ok(prior_commit) => {
|
||||
this.commit_editor.update(cx, |editor, cx| {
|
||||
editor.set_text(prior_commit.message, window, cx)
|
||||
});
|
||||
}
|
||||
Err(e) => this.show_err_toast(e, cx),
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
|
||||
self.pending_commit = Some(task);
|
||||
}
|
||||
|
||||
fn fill_co_authors(&mut self, _: &FillCoAuthors, window: &mut Window, cx: &mut Context<Self>) {
|
||||
const CO_AUTHOR_PREFIX: &str = "Co-authored-by: ";
|
||||
|
||||
@@ -1131,16 +1168,10 @@ impl GitPanel {
|
||||
let all_repositories = self
|
||||
.project
|
||||
.read(cx)
|
||||
.git_state()
|
||||
.git_store()
|
||||
.read(cx)
|
||||
.all_repositories();
|
||||
|
||||
let branch = self
|
||||
.active_repository
|
||||
.as_ref()
|
||||
.and_then(|repository| repository.read(cx).branch())
|
||||
.unwrap_or_else(|| "(no current branch)".into());
|
||||
|
||||
let has_repo_above = all_repositories.iter().any(|repo| {
|
||||
repo.read(cx)
|
||||
.repository_entry
|
||||
@@ -1148,26 +1179,7 @@ impl GitPanel {
|
||||
.is_above_project()
|
||||
});
|
||||
|
||||
let icon_button = Button::new("branch-selector", branch)
|
||||
.color(Color::Muted)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.icon(IconName::GitBranch)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.size(ButtonSize::Compact)
|
||||
.icon_position(IconPosition::Start)
|
||||
.tooltip(Tooltip::for_action_title(
|
||||
"Switch Branch",
|
||||
&zed_actions::git::Branch,
|
||||
))
|
||||
.on_click(cx.listener(|_, _, window, cx| {
|
||||
window.dispatch_action(zed_actions::git::Branch.boxed_clone(), cx);
|
||||
}))
|
||||
.style(ButtonStyle::Transparent);
|
||||
|
||||
self.panel_header_container(window, cx)
|
||||
.child(h_flex().pl_1().child(icon_button))
|
||||
.child(div().flex_grow())
|
||||
.when(all_repositories.len() > 1 || has_repo_above, |el| {
|
||||
el.child(self.render_repository_selector(cx))
|
||||
})
|
||||
@@ -1200,6 +1212,7 @@ impl GitPanel {
|
||||
&& !editor.read(cx).is_empty(cx)
|
||||
&& !self.has_unstaged_conflicts()
|
||||
&& self.has_write_access(cx);
|
||||
|
||||
// let can_commit_all =
|
||||
// !self.commit_pending && self.can_commit_all && !editor.read(cx).is_empty(cx);
|
||||
let panel_editor_style = panel_editor_style(true, window, cx);
|
||||
@@ -1274,10 +1287,108 @@ impl GitPanel {
|
||||
)
|
||||
}
|
||||
|
||||
fn render_previous_commit(&self, cx: &mut Context<Self>) -> Option<impl IntoElement> {
|
||||
let active_repository = self.active_repository.as_ref()?;
|
||||
let branch = active_repository.read(cx).branch()?;
|
||||
let commit = branch.most_recent_commit.as_ref()?.clone();
|
||||
|
||||
if branch.upstream.as_ref().is_some_and(|upstream| {
|
||||
if let Some(tracking) = &upstream.tracking {
|
||||
tracking.ahead == 0
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let _branch_selector = Button::new("branch-selector", branch.name.clone())
|
||||
.color(Color::Muted)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.icon(IconName::GitBranch)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.size(ButtonSize::Compact)
|
||||
.icon_position(IconPosition::Start)
|
||||
.tooltip(Tooltip::for_action_title(
|
||||
"Switch Branch",
|
||||
&zed_actions::git::Branch,
|
||||
))
|
||||
.on_click(cx.listener(|_, _, window, cx| {
|
||||
window.dispatch_action(zed_actions::git::Branch.boxed_clone(), cx);
|
||||
}))
|
||||
.style(ButtonStyle::Transparent);
|
||||
|
||||
let _timestamp = Label::new(time_format::format_local_timestamp(
|
||||
OffsetDateTime::from_unix_timestamp(commit.commit_timestamp).log_err()?,
|
||||
OffsetDateTime::now_utc(),
|
||||
time_format::TimestampFormat::Relative,
|
||||
))
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted);
|
||||
|
||||
let tooltip = if self.has_staged_changes() {
|
||||
"git reset HEAD^ --soft"
|
||||
} else {
|
||||
"git reset HEAD^"
|
||||
};
|
||||
|
||||
let this = cx.entity();
|
||||
Some(
|
||||
h_flex()
|
||||
.items_center()
|
||||
.py_1p5()
|
||||
.px(px(8.))
|
||||
.bg(cx.theme().colors().background)
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.gap_1p5()
|
||||
.child(
|
||||
div()
|
||||
.flex_grow()
|
||||
.overflow_hidden()
|
||||
.max_w(relative(0.6))
|
||||
.h_full()
|
||||
.child(
|
||||
Label::new(commit.subject.clone())
|
||||
.size(LabelSize::Small)
|
||||
.text_ellipsis(),
|
||||
)
|
||||
.id("commit-msg-hover")
|
||||
.hoverable_tooltip(move |window, cx| {
|
||||
GitPanelMessageTooltip::new(
|
||||
this.clone(),
|
||||
commit.sha.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.into()
|
||||
}),
|
||||
)
|
||||
.child(div().flex_1())
|
||||
.child(
|
||||
panel_filled_button("Uncommit")
|
||||
.icon(IconName::Undo)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_position(IconPosition::Start)
|
||||
.tooltip(Tooltip::for_action_title(tooltip, &git::Uncommit))
|
||||
.on_click(cx.listener(|this, _, window, cx| this.uncommit(window, cx))),
|
||||
// .child(
|
||||
// panel_filled_button("Push")
|
||||
// .icon(IconName::ArrowUp)
|
||||
// .icon_size(IconSize::Small)
|
||||
// .icon_color(Color::Muted)
|
||||
// .icon_position(IconPosition::Start), // .disabled(true),
|
||||
// ),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_empty_state(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
h_flex()
|
||||
.h_full()
|
||||
.flex_1()
|
||||
.flex_grow()
|
||||
.justify_center()
|
||||
.items_center()
|
||||
.child(
|
||||
@@ -1563,6 +1674,17 @@ impl GitPanel {
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn load_commit_details(
|
||||
&self,
|
||||
sha: &str,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<CommitDetails>> {
|
||||
let Some(repo) = self.active_repository.clone() else {
|
||||
return Task::ready(Err(anyhow::anyhow!("no active repo")));
|
||||
};
|
||||
repo.update(cx, |repo, cx| repo.show(sha, cx))
|
||||
}
|
||||
|
||||
fn render_entry(
|
||||
&self,
|
||||
ix: usize,
|
||||
@@ -1757,6 +1879,7 @@ impl Render for GitPanel {
|
||||
} else {
|
||||
self.render_empty_state(cx).into_any_element()
|
||||
})
|
||||
.children(self.render_previous_commit(cx))
|
||||
.child(self.render_commit_editor(window, cx))
|
||||
}
|
||||
}
|
||||
@@ -1843,3 +1966,81 @@ impl Panel for GitPanel {
|
||||
}
|
||||
|
||||
impl PanelHeader for GitPanel {}
|
||||
|
||||
struct GitPanelMessageTooltip {
|
||||
commit_tooltip: Option<Entity<CommitTooltip>>,
|
||||
}
|
||||
|
||||
impl GitPanelMessageTooltip {
|
||||
fn new(
|
||||
git_panel: Entity<GitPanel>,
|
||||
sha: SharedString,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Entity<Self> {
|
||||
let workspace = git_panel.read(cx).workspace.clone();
|
||||
cx.new(|cx| {
|
||||
cx.spawn_in(window, |this, mut cx| async move {
|
||||
let language_registry = workspace.update(&mut cx, |workspace, _cx| {
|
||||
workspace.app_state().languages.clone()
|
||||
})?;
|
||||
|
||||
let details = git_panel
|
||||
.update(&mut cx, |git_panel, cx| {
|
||||
git_panel.load_commit_details(&sha, cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
let mut parsed_message = ParsedMarkdown::default();
|
||||
markdown::parse_markdown_block(
|
||||
&details.message,
|
||||
Some(&language_registry),
|
||||
None,
|
||||
&mut parsed_message.text,
|
||||
&mut parsed_message.highlights,
|
||||
&mut parsed_message.region_ranges,
|
||||
&mut parsed_message.regions,
|
||||
)
|
||||
.await;
|
||||
|
||||
let commit_details = editor::commit_tooltip::CommitDetails {
|
||||
sha: details.sha.clone(),
|
||||
committer_name: details.committer_name.clone(),
|
||||
committer_email: details.committer_email.clone(),
|
||||
commit_time: OffsetDateTime::from_unix_timestamp(details.commit_timestamp)?,
|
||||
message: Some(editor::commit_tooltip::ParsedCommitMessage {
|
||||
message: details.message.clone(),
|
||||
parsed_message,
|
||||
..Default::default()
|
||||
}),
|
||||
};
|
||||
|
||||
this.update_in(&mut cx, |this: &mut GitPanelMessageTooltip, window, cx| {
|
||||
this.commit_tooltip = Some(cx.new(move |cx| {
|
||||
CommitTooltip::new(
|
||||
commit_details,
|
||||
panel_editor_style(true, window, cx),
|
||||
Some(workspace),
|
||||
)
|
||||
}));
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.detach();
|
||||
|
||||
Self {
|
||||
commit_tooltip: None,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for GitPanelMessageTooltip {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<'_, Self>) -> impl IntoElement {
|
||||
if let Some(commit_tooltip) = &self.commit_tooltip {
|
||||
commit_tooltip.clone().into_any_element()
|
||||
} else {
|
||||
gpui::Empty.into_any_element()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ use gpui::{
|
||||
};
|
||||
use language::{Anchor, Buffer, Capability, OffsetRangeExt, Point};
|
||||
use multi_buffer::{MultiBuffer, PathKey};
|
||||
use project::{git::GitState, Project, ProjectPath};
|
||||
use project::{git::GitStore, Project, ProjectPath};
|
||||
use theme::ActiveTheme;
|
||||
use ui::prelude::*;
|
||||
use util::ResultExt as _;
|
||||
@@ -31,7 +31,7 @@ pub(crate) struct ProjectDiff {
|
||||
editor: Entity<Editor>,
|
||||
project: Entity<Project>,
|
||||
git_panel: Entity<GitPanel>,
|
||||
git_state: Entity<GitState>,
|
||||
git_store: Entity<GitStore>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
focus_handle: FocusHandle,
|
||||
update_needed: postage::watch::Sender<()>,
|
||||
@@ -137,11 +137,11 @@ impl ProjectDiff {
|
||||
cx.subscribe_in(&editor, window, Self::handle_editor_event)
|
||||
.detach();
|
||||
|
||||
let git_state = project.read(cx).git_state().clone();
|
||||
let git_state_subscription = cx.subscribe_in(
|
||||
&git_state,
|
||||
let git_store = project.read(cx).git_store().clone();
|
||||
let git_store_subscription = cx.subscribe_in(
|
||||
&git_store,
|
||||
window,
|
||||
move |this, _git_state, _event, _window, _cx| {
|
||||
move |this, _git_store, _event, _window, _cx| {
|
||||
*this.update_needed.borrow_mut() = ();
|
||||
},
|
||||
);
|
||||
@@ -156,7 +156,7 @@ impl ProjectDiff {
|
||||
|
||||
Self {
|
||||
project,
|
||||
git_state: git_state.clone(),
|
||||
git_store: git_store.clone(),
|
||||
git_panel: git_panel.clone(),
|
||||
workspace: workspace.downgrade(),
|
||||
focus_handle,
|
||||
@@ -165,7 +165,7 @@ impl ProjectDiff {
|
||||
pending_scroll: None,
|
||||
update_needed: send,
|
||||
_task: worker,
|
||||
_subscription: git_state_subscription,
|
||||
_subscription: git_store_subscription,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ impl ProjectDiff {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(git_repo) = self.git_state.read(cx).active_repository() else {
|
||||
let Some(git_repo) = self.git_store.read(cx).active_repository() else {
|
||||
return;
|
||||
};
|
||||
let repo = git_repo.read(cx);
|
||||
@@ -248,7 +248,7 @@ impl ProjectDiff {
|
||||
}
|
||||
|
||||
fn load_buffers(&mut self, cx: &mut Context<Self>) -> Vec<Task<Result<DiffBuffer>>> {
|
||||
let Some(repo) = self.git_state.read(cx).active_repository() else {
|
||||
let Some(repo) = self.git_store.read(cx).active_repository() else {
|
||||
self.multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.clear(cx);
|
||||
});
|
||||
|
||||
@@ -98,7 +98,7 @@ impl QuickCommitModal {
|
||||
commit_message_buffer: Option<Entity<Buffer>>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let git_state = project.read(cx).git_state().clone();
|
||||
let git_store = project.read(cx).git_store().clone();
|
||||
let active_repository = project.read(cx).active_repository(cx);
|
||||
|
||||
let focus_handle = cx.focus_handle();
|
||||
@@ -130,7 +130,7 @@ impl QuickCommitModal {
|
||||
let all_repositories = self
|
||||
.project
|
||||
.read(cx)
|
||||
.git_state()
|
||||
.git_store()
|
||||
.read(cx)
|
||||
.all_repositories();
|
||||
let entry_count = self
|
||||
|
||||
@@ -4,7 +4,7 @@ use gpui::{
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use project::{
|
||||
git::{GitState, Repository},
|
||||
git::{GitStore, Repository},
|
||||
Project,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
@@ -20,8 +20,8 @@ pub struct RepositorySelector {
|
||||
|
||||
impl RepositorySelector {
|
||||
pub fn new(project: Entity<Project>, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let git_state = project.read(cx).git_state().clone();
|
||||
let all_repositories = git_state.read(cx).all_repositories();
|
||||
let git_store = project.read(cx).git_store().clone();
|
||||
let all_repositories = git_store.read(cx).all_repositories();
|
||||
let filtered_repositories = all_repositories.clone();
|
||||
let delegate = RepositorySelectorDelegate {
|
||||
project: project.downgrade(),
|
||||
@@ -38,7 +38,7 @@ impl RepositorySelector {
|
||||
});
|
||||
|
||||
let _subscriptions =
|
||||
vec![cx.subscribe_in(&git_state, window, Self::handle_project_git_event)];
|
||||
vec![cx.subscribe_in(&git_store, window, Self::handle_project_git_event)];
|
||||
|
||||
RepositorySelector {
|
||||
picker,
|
||||
@@ -49,7 +49,7 @@ impl RepositorySelector {
|
||||
|
||||
fn handle_project_git_event(
|
||||
&mut self,
|
||||
git_state: &Entity<GitState>,
|
||||
git_store: &Entity<GitStore>,
|
||||
_event: &project::git::GitEvent,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
@@ -57,7 +57,7 @@ impl RepositorySelector {
|
||||
// TODO handle events individually
|
||||
let task = self.picker.update(cx, |this, cx| {
|
||||
let query = this.query(cx);
|
||||
this.delegate.repository_entries = git_state.read(cx).all_repositories();
|
||||
this.delegate.repository_entries = git_store.read(cx).all_repositories();
|
||||
this.delegate.update_matches(query, window, cx)
|
||||
});
|
||||
self.update_matches_task = Some(task);
|
||||
|
||||
@@ -2722,8 +2722,8 @@ fn serialize_blame_buffer_response(blame: Option<git::blame::Blame>) -> proto::B
|
||||
author_mail: entry.author_mail.clone(),
|
||||
author_time: entry.author_time,
|
||||
author_tz: entry.author_tz.clone(),
|
||||
committer: entry.committer.clone(),
|
||||
committer_mail: entry.committer_mail.clone(),
|
||||
committer: entry.committer_name.clone(),
|
||||
committer_mail: entry.committer_email.clone(),
|
||||
committer_time: entry.committer_time,
|
||||
committer_tz: entry.committer_tz.clone(),
|
||||
summary: entry.summary.clone(),
|
||||
@@ -2772,10 +2772,10 @@ fn deserialize_blame_buffer_response(
|
||||
sha: git::Oid::from_bytes(&entry.sha).ok()?,
|
||||
range: entry.start_line..entry.end_line,
|
||||
original_line_number: entry.original_line_number,
|
||||
committer: entry.committer,
|
||||
committer_name: entry.committer,
|
||||
committer_time: entry.committer_time,
|
||||
committer_tz: entry.committer_tz,
|
||||
committer_mail: entry.committer_mail,
|
||||
committer_email: entry.committer_mail,
|
||||
author: entry.author,
|
||||
author_mail: entry.author_mail,
|
||||
author_time: entry.author_time,
|
||||
|
||||
+328
-30
@@ -1,20 +1,22 @@
|
||||
use crate::buffer_store::BufferStore;
|
||||
use crate::worktree_store::{WorktreeStore, WorktreeStoreEvent};
|
||||
use crate::{Project, ProjectPath};
|
||||
use anyhow::Context as _;
|
||||
use anyhow::{Context as _, Result};
|
||||
use client::ProjectId;
|
||||
use futures::channel::{mpsc, oneshot};
|
||||
use futures::StreamExt as _;
|
||||
use git::repository::{Branch, CommitDetails, ResetMode};
|
||||
use git::{
|
||||
repository::{GitRepository, RepoPath},
|
||||
status::{GitSummary, TrackedSummary},
|
||||
};
|
||||
use gpui::{
|
||||
App, AppContext, Context, Entity, EventEmitter, SharedString, Subscription, Task, WeakEntity,
|
||||
App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Subscription, Task,
|
||||
WeakEntity,
|
||||
};
|
||||
use language::{Buffer, LanguageRegistry};
|
||||
use rpc::proto::ToProto;
|
||||
use rpc::{proto, AnyProtoClient};
|
||||
use rpc::proto::{git_reset, ToProto};
|
||||
use rpc::{proto, AnyProtoClient, TypedEnvelope};
|
||||
use settings::WorktreeId;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
@@ -22,22 +24,23 @@ use text::BufferId;
|
||||
use util::{maybe, ResultExt};
|
||||
use worktree::{ProjectEntryId, RepositoryEntry, StatusEntry};
|
||||
|
||||
pub struct GitState {
|
||||
pub struct GitStore {
|
||||
pub(super) project_id: Option<ProjectId>,
|
||||
pub(super) client: Option<AnyProtoClient>,
|
||||
pub update_sender: mpsc::UnboundedSender<(Message, oneshot::Sender<anyhow::Result<()>>)>,
|
||||
buffer_store: Entity<BufferStore>,
|
||||
repositories: Vec<Entity<Repository>>,
|
||||
active_index: Option<usize>,
|
||||
update_sender: mpsc::UnboundedSender<(Message, oneshot::Sender<Result<()>>)>,
|
||||
_subscription: Subscription,
|
||||
}
|
||||
|
||||
pub struct Repository {
|
||||
commit_message_buffer: Option<Entity<Buffer>>,
|
||||
git_state: WeakEntity<GitState>,
|
||||
git_store: WeakEntity<GitStore>,
|
||||
pub worktree_id: WorktreeId,
|
||||
pub repository_entry: RepositoryEntry,
|
||||
pub git_repo: GitRepo,
|
||||
update_sender: mpsc::UnboundedSender<(Message, oneshot::Sender<anyhow::Result<()>>)>,
|
||||
update_sender: mpsc::UnboundedSender<(Message, oneshot::Sender<Result<()>>)>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -57,6 +60,11 @@ pub enum Message {
|
||||
message: SharedString,
|
||||
name_and_email: Option<(SharedString, SharedString)>,
|
||||
},
|
||||
Reset {
|
||||
repo: GitRepo,
|
||||
commit: SharedString,
|
||||
reset_mode: ResetMode,
|
||||
},
|
||||
Stage(GitRepo, Vec<RepoPath>),
|
||||
Unstage(GitRepo, Vec<RepoPath>),
|
||||
SetIndexText(GitRepo, RepoPath, Option<String>),
|
||||
@@ -68,11 +76,12 @@ pub enum GitEvent {
|
||||
GitStateUpdated,
|
||||
}
|
||||
|
||||
impl EventEmitter<GitEvent> for GitState {}
|
||||
impl EventEmitter<GitEvent> for GitStore {}
|
||||
|
||||
impl GitState {
|
||||
impl GitStore {
|
||||
pub fn new(
|
||||
worktree_store: &Entity<WorktreeStore>,
|
||||
buffer_store: Entity<BufferStore>,
|
||||
client: Option<AnyProtoClient>,
|
||||
project_id: Option<ProjectId>,
|
||||
cx: &mut Context<'_, Self>,
|
||||
@@ -80,9 +89,10 @@ impl GitState {
|
||||
let update_sender = Self::spawn_git_worker(cx);
|
||||
let _subscription = cx.subscribe(worktree_store, Self::on_worktree_store_event);
|
||||
|
||||
GitState {
|
||||
GitStore {
|
||||
project_id,
|
||||
client,
|
||||
buffer_store,
|
||||
repositories: Vec::new(),
|
||||
active_index: None,
|
||||
update_sender,
|
||||
@@ -90,6 +100,16 @@ impl GitState {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(client: &AnyProtoClient) {
|
||||
client.add_entity_request_handler(Self::handle_stage);
|
||||
client.add_entity_request_handler(Self::handle_unstage);
|
||||
client.add_entity_request_handler(Self::handle_commit);
|
||||
client.add_entity_request_handler(Self::handle_reset);
|
||||
client.add_entity_request_handler(Self::handle_show);
|
||||
client.add_entity_request_handler(Self::handle_open_commit_message_buffer);
|
||||
client.add_entity_request_handler(Self::handle_set_index_text);
|
||||
}
|
||||
|
||||
pub fn active_repository(&self) -> Option<Entity<Repository>> {
|
||||
self.active_index
|
||||
.map(|index| self.repositories[index].clone())
|
||||
@@ -153,7 +173,7 @@ impl GitState {
|
||||
existing_handle
|
||||
} else {
|
||||
cx.new(|_| Repository {
|
||||
git_state: this.clone(),
|
||||
git_store: this.clone(),
|
||||
worktree_id,
|
||||
repository_entry: repo.clone(),
|
||||
git_repo,
|
||||
@@ -189,10 +209,10 @@ impl GitState {
|
||||
}
|
||||
|
||||
fn spawn_git_worker(
|
||||
cx: &mut Context<'_, GitState>,
|
||||
) -> mpsc::UnboundedSender<(Message, oneshot::Sender<anyhow::Result<()>>)> {
|
||||
cx: &mut Context<'_, GitStore>,
|
||||
) -> mpsc::UnboundedSender<(Message, oneshot::Sender<Result<()>>)> {
|
||||
let (update_sender, mut update_receiver) =
|
||||
mpsc::unbounded::<(Message, oneshot::Sender<anyhow::Result<()>>)>();
|
||||
mpsc::unbounded::<(Message, oneshot::Sender<Result<()>>)>();
|
||||
cx.spawn(|_, cx| async move {
|
||||
while let Some((msg, respond)) = update_receiver.next().await {
|
||||
let result = cx
|
||||
@@ -206,7 +226,7 @@ impl GitState {
|
||||
update_sender
|
||||
}
|
||||
|
||||
async fn process_git_msg(msg: Message) -> Result<(), anyhow::Error> {
|
||||
async fn process_git_msg(msg: Message) -> Result<()> {
|
||||
match msg {
|
||||
Message::Stage(repo, paths) => {
|
||||
match repo {
|
||||
@@ -233,6 +253,35 @@ impl GitState {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Message::Reset {
|
||||
repo,
|
||||
commit,
|
||||
reset_mode,
|
||||
} => {
|
||||
match repo {
|
||||
GitRepo::Local(repo) => repo.reset(&commit, reset_mode)?,
|
||||
GitRepo::Remote {
|
||||
project_id,
|
||||
client,
|
||||
worktree_id,
|
||||
work_directory_id,
|
||||
} => {
|
||||
client
|
||||
.request(proto::GitReset {
|
||||
project_id: project_id.0,
|
||||
worktree_id: worktree_id.to_proto(),
|
||||
work_directory_id: work_directory_id.to_proto(),
|
||||
commit: commit.into(),
|
||||
mode: match reset_mode {
|
||||
ResetMode::Soft => git_reset::ResetMode::Soft.into(),
|
||||
ResetMode::Mixed => git_reset::ResetMode::Mixed.into(),
|
||||
},
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Message::Unstage(repo, paths) => {
|
||||
match repo {
|
||||
GitRepo::Local(repo) => repo.unstage_paths(&paths)?,
|
||||
@@ -309,20 +358,219 @@ impl GitState {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_stage(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::Stage>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::Ack> {
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
|
||||
let repository_handle =
|
||||
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||
|
||||
let entries = envelope
|
||||
.payload
|
||||
.paths
|
||||
.into_iter()
|
||||
.map(PathBuf::from)
|
||||
.map(RepoPath::new)
|
||||
.collect();
|
||||
|
||||
repository_handle
|
||||
.update(&mut cx, |repository_handle, _| {
|
||||
repository_handle.stage_entries(entries)
|
||||
})?
|
||||
.await??;
|
||||
Ok(proto::Ack {})
|
||||
}
|
||||
|
||||
async fn handle_unstage(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::Unstage>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::Ack> {
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
|
||||
let repository_handle =
|
||||
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||
|
||||
let entries = envelope
|
||||
.payload
|
||||
.paths
|
||||
.into_iter()
|
||||
.map(PathBuf::from)
|
||||
.map(RepoPath::new)
|
||||
.collect();
|
||||
|
||||
repository_handle
|
||||
.update(&mut cx, |repository_handle, _| {
|
||||
repository_handle.unstage_entries(entries)
|
||||
})?
|
||||
.await??;
|
||||
|
||||
Ok(proto::Ack {})
|
||||
}
|
||||
|
||||
async fn handle_set_index_text(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::SetIndexText>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::Ack> {
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
|
||||
let repository_handle =
|
||||
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||
|
||||
repository_handle
|
||||
.update(&mut cx, |repository_handle, _| {
|
||||
repository_handle.set_index_text(
|
||||
&RepoPath::from_str(&envelope.payload.path),
|
||||
envelope.payload.text,
|
||||
)
|
||||
})?
|
||||
.await??;
|
||||
Ok(proto::Ack {})
|
||||
}
|
||||
|
||||
async fn handle_commit(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::Commit>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::Ack> {
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
|
||||
let repository_handle =
|
||||
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||
|
||||
let message = SharedString::from(envelope.payload.message);
|
||||
let name = envelope.payload.name.map(SharedString::from);
|
||||
let email = envelope.payload.email.map(SharedString::from);
|
||||
|
||||
repository_handle
|
||||
.update(&mut cx, |repository_handle, _| {
|
||||
repository_handle.commit(message, name.zip(email))
|
||||
})?
|
||||
.await??;
|
||||
Ok(proto::Ack {})
|
||||
}
|
||||
|
||||
async fn handle_show(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::GitShow>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::GitCommitDetails> {
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
|
||||
let repository_handle =
|
||||
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||
|
||||
let commit = repository_handle
|
||||
.update(&mut cx, |repository_handle, cx| {
|
||||
repository_handle.show(&envelope.payload.commit, cx)
|
||||
})?
|
||||
.await?;
|
||||
Ok(proto::GitCommitDetails {
|
||||
sha: commit.sha.into(),
|
||||
message: commit.message.into(),
|
||||
commit_timestamp: commit.commit_timestamp,
|
||||
committer_email: commit.committer_email.into(),
|
||||
committer_name: commit.committer_name.into(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle_reset(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::GitReset>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::Ack> {
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
|
||||
let repository_handle =
|
||||
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||
|
||||
let mode = match envelope.payload.mode() {
|
||||
git_reset::ResetMode::Soft => ResetMode::Soft,
|
||||
git_reset::ResetMode::Mixed => ResetMode::Mixed,
|
||||
};
|
||||
|
||||
repository_handle
|
||||
.update(&mut cx, |repository_handle, _| {
|
||||
repository_handle.reset(&envelope.payload.commit, mode)
|
||||
})?
|
||||
.await??;
|
||||
Ok(proto::Ack {})
|
||||
}
|
||||
|
||||
async fn handle_open_commit_message_buffer(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::OpenCommitMessageBuffer>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::OpenBufferResponse> {
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
|
||||
let repository =
|
||||
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||
let buffer = repository
|
||||
.update(&mut cx, |repository, cx| {
|
||||
repository.open_commit_buffer(None, this.read(cx).buffer_store.clone(), cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
let buffer_id = buffer.read_with(&cx, |buffer, _| buffer.remote_id())?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.buffer_store.update(cx, |buffer_store, cx| {
|
||||
buffer_store
|
||||
.create_buffer_for_peer(
|
||||
&buffer,
|
||||
envelope.original_sender_id.unwrap_or(envelope.sender_id),
|
||||
cx,
|
||||
)
|
||||
.detach_and_log_err(cx);
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(proto::OpenBufferResponse {
|
||||
buffer_id: buffer_id.to_proto(),
|
||||
})
|
||||
}
|
||||
|
||||
fn repository_for_request(
|
||||
this: &Entity<Self>,
|
||||
worktree_id: WorktreeId,
|
||||
work_directory_id: ProjectEntryId,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<Entity<Repository>> {
|
||||
this.update(cx, |this, cx| {
|
||||
let repository_handle = this
|
||||
.all_repositories()
|
||||
.into_iter()
|
||||
.find(|repository_handle| {
|
||||
repository_handle.read(cx).worktree_id == worktree_id
|
||||
&& repository_handle
|
||||
.read(cx)
|
||||
.repository_entry
|
||||
.work_directory_id()
|
||||
== work_directory_id
|
||||
})
|
||||
.context("missing repository handle")?;
|
||||
anyhow::Ok(repository_handle)
|
||||
})?
|
||||
}
|
||||
}
|
||||
|
||||
impl GitRepo {}
|
||||
|
||||
impl Repository {
|
||||
pub fn git_state(&self) -> Option<Entity<GitState>> {
|
||||
self.git_state.upgrade()
|
||||
pub fn git_store(&self) -> Option<Entity<GitStore>> {
|
||||
self.git_store.upgrade()
|
||||
}
|
||||
|
||||
fn id(&self) -> (WorktreeId, ProjectEntryId) {
|
||||
(self.worktree_id, self.repository_entry.work_directory_id())
|
||||
}
|
||||
|
||||
pub fn branch(&self) -> Option<Arc<str>> {
|
||||
pub fn branch(&self) -> Option<&Branch> {
|
||||
self.repository_entry.branch()
|
||||
}
|
||||
|
||||
@@ -344,19 +592,19 @@ impl Repository {
|
||||
}
|
||||
|
||||
pub fn activate(&self, cx: &mut Context<Self>) {
|
||||
let Some(git_state) = self.git_state.upgrade() else {
|
||||
let Some(git_store) = self.git_store.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let entity = cx.entity();
|
||||
git_state.update(cx, |git_state, cx| {
|
||||
let Some(index) = git_state
|
||||
git_store.update(cx, |git_store, cx| {
|
||||
let Some(index) = git_store
|
||||
.repositories
|
||||
.iter()
|
||||
.position(|handle| *handle == entity)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
git_state.active_index = Some(index);
|
||||
git_store.active_index = Some(index);
|
||||
cx.emit(GitEvent::ActiveRepositoryChanged);
|
||||
});
|
||||
}
|
||||
@@ -396,7 +644,7 @@ impl Repository {
|
||||
languages: Option<Arc<LanguageRegistry>>,
|
||||
buffer_store: Entity<BufferStore>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<anyhow::Result<Entity<Buffer>>> {
|
||||
) -> Task<Result<Entity<Buffer>>> {
|
||||
if let Some(buffer) = self.commit_message_buffer.clone() {
|
||||
return Task::ready(Ok(buffer));
|
||||
}
|
||||
@@ -444,7 +692,7 @@ impl Repository {
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
buffer_store: Entity<BufferStore>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<anyhow::Result<Entity<Buffer>>> {
|
||||
) -> Task<Result<Entity<Buffer>>> {
|
||||
cx.spawn(|repository, mut cx| async move {
|
||||
let buffer = buffer_store
|
||||
.update(&mut cx, |buffer_store, cx| buffer_store.create_buffer(cx))?
|
||||
@@ -464,7 +712,57 @@ impl Repository {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn stage_entries(&self, entries: Vec<RepoPath>) -> oneshot::Receiver<anyhow::Result<()>> {
|
||||
pub fn reset(&self, commit: &str, reset_mode: ResetMode) -> oneshot::Receiver<Result<()>> {
|
||||
let (result_tx, result_rx) = futures::channel::oneshot::channel();
|
||||
let commit = commit.to_string().into();
|
||||
self.update_sender
|
||||
.unbounded_send((
|
||||
Message::Reset {
|
||||
repo: self.git_repo.clone(),
|
||||
commit,
|
||||
reset_mode,
|
||||
},
|
||||
result_tx,
|
||||
))
|
||||
.ok();
|
||||
result_rx
|
||||
}
|
||||
|
||||
pub fn show(&self, commit: &str, cx: &Context<Self>) -> Task<Result<CommitDetails>> {
|
||||
let commit = commit.to_string();
|
||||
match self.git_repo.clone() {
|
||||
GitRepo::Local(git_repository) => {
|
||||
let commit = commit.to_string();
|
||||
cx.background_executor()
|
||||
.spawn(async move { git_repository.show(&commit) })
|
||||
}
|
||||
GitRepo::Remote {
|
||||
project_id,
|
||||
client,
|
||||
worktree_id,
|
||||
work_directory_id,
|
||||
} => cx.background_executor().spawn(async move {
|
||||
let resp = client
|
||||
.request(proto::GitShow {
|
||||
project_id: project_id.0,
|
||||
worktree_id: worktree_id.to_proto(),
|
||||
work_directory_id: work_directory_id.to_proto(),
|
||||
commit,
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(CommitDetails {
|
||||
sha: resp.sha.into(),
|
||||
message: resp.message.into(),
|
||||
commit_timestamp: resp.commit_timestamp,
|
||||
committer_email: resp.committer_email.into(),
|
||||
committer_name: resp.committer_name.into(),
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stage_entries(&self, entries: Vec<RepoPath>) -> oneshot::Receiver<Result<()>> {
|
||||
let (result_tx, result_rx) = futures::channel::oneshot::channel();
|
||||
if entries.is_empty() {
|
||||
result_tx.send(Ok(())).ok();
|
||||
@@ -476,7 +774,7 @@ impl Repository {
|
||||
result_rx
|
||||
}
|
||||
|
||||
pub fn unstage_entries(&self, entries: Vec<RepoPath>) -> oneshot::Receiver<anyhow::Result<()>> {
|
||||
pub fn unstage_entries(&self, entries: Vec<RepoPath>) -> oneshot::Receiver<Result<()>> {
|
||||
let (result_tx, result_rx) = futures::channel::oneshot::channel();
|
||||
if entries.is_empty() {
|
||||
result_tx.send(Ok(())).ok();
|
||||
@@ -488,7 +786,7 @@ impl Repository {
|
||||
result_rx
|
||||
}
|
||||
|
||||
pub fn stage_all(&self) -> oneshot::Receiver<anyhow::Result<()>> {
|
||||
pub fn stage_all(&self) -> oneshot::Receiver<Result<()>> {
|
||||
let to_stage = self
|
||||
.repository_entry
|
||||
.status()
|
||||
@@ -498,7 +796,7 @@ impl Repository {
|
||||
self.stage_entries(to_stage)
|
||||
}
|
||||
|
||||
pub fn unstage_all(&self) -> oneshot::Receiver<anyhow::Result<()>> {
|
||||
pub fn unstage_all(&self) -> oneshot::Receiver<Result<()>> {
|
||||
let to_unstage = self
|
||||
.repository_entry
|
||||
.status()
|
||||
@@ -530,7 +828,7 @@ impl Repository {
|
||||
&self,
|
||||
message: SharedString,
|
||||
name_and_email: Option<(SharedString, SharedString)>,
|
||||
) -> oneshot::Receiver<anyhow::Result<()>> {
|
||||
) -> oneshot::Receiver<Result<()>> {
|
||||
let (result_tx, result_rx) = futures::channel::oneshot::channel();
|
||||
self.update_sender
|
||||
.unbounded_send((
|
||||
|
||||
+24
-157
@@ -27,7 +27,7 @@ use git::Repository;
|
||||
pub mod search_history;
|
||||
mod yarn;
|
||||
|
||||
use crate::git::GitState;
|
||||
use crate::git::GitStore;
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use buffer_store::{BufferStore, BufferStoreEvent};
|
||||
use client::{
|
||||
@@ -161,7 +161,7 @@ pub struct Project {
|
||||
fs: Arc<dyn Fs>,
|
||||
ssh_client: Option<Entity<SshRemoteClient>>,
|
||||
client_state: ProjectClientState,
|
||||
git_state: Entity<GitState>,
|
||||
git_store: Entity<GitStore>,
|
||||
collaborators: HashMap<proto::PeerId, Collaborator>,
|
||||
client_subscriptions: Vec<client::Subscription>,
|
||||
worktree_store: Entity<WorktreeStore>,
|
||||
@@ -610,15 +610,10 @@ impl Project {
|
||||
client.add_entity_request_handler(Self::handle_open_new_buffer);
|
||||
client.add_entity_message_handler(Self::handle_create_buffer_for_peer);
|
||||
|
||||
client.add_entity_request_handler(Self::handle_stage);
|
||||
client.add_entity_request_handler(Self::handle_unstage);
|
||||
client.add_entity_request_handler(Self::handle_commit);
|
||||
client.add_entity_request_handler(Self::handle_set_index_text);
|
||||
client.add_entity_request_handler(Self::handle_open_commit_message_buffer);
|
||||
|
||||
WorktreeStore::init(&client);
|
||||
BufferStore::init(&client);
|
||||
LspStore::init(&client);
|
||||
GitStore::init(&client);
|
||||
SettingsObserver::init(&client);
|
||||
TaskStore::init(Some(&client));
|
||||
ToolchainStore::init(&client);
|
||||
@@ -705,7 +700,8 @@ impl Project {
|
||||
)
|
||||
});
|
||||
|
||||
let git_state = cx.new(|cx| GitState::new(&worktree_store, None, None, cx));
|
||||
let git_store =
|
||||
cx.new(|cx| GitStore::new(&worktree_store, buffer_store.clone(), None, None, cx));
|
||||
|
||||
cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
|
||||
|
||||
@@ -718,7 +714,7 @@ impl Project {
|
||||
lsp_store,
|
||||
join_project_response_message_id: 0,
|
||||
client_state: ProjectClientState::Local,
|
||||
git_state,
|
||||
git_store,
|
||||
client_subscriptions: Vec::new(),
|
||||
_subscriptions: vec![cx.on_release(Self::release)],
|
||||
active_entry: None,
|
||||
@@ -825,9 +821,10 @@ impl Project {
|
||||
});
|
||||
cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
|
||||
|
||||
let git_state = cx.new(|cx| {
|
||||
GitState::new(
|
||||
let git_store = cx.new(|cx| {
|
||||
GitStore::new(
|
||||
&worktree_store,
|
||||
buffer_store.clone(),
|
||||
Some(ssh_proto.clone()),
|
||||
Some(ProjectId(SSH_PROJECT_ID)),
|
||||
cx,
|
||||
@@ -846,7 +843,7 @@ impl Project {
|
||||
lsp_store,
|
||||
join_project_response_message_id: 0,
|
||||
client_state: ProjectClientState::Local,
|
||||
git_state,
|
||||
git_store,
|
||||
client_subscriptions: Vec::new(),
|
||||
_subscriptions: vec![
|
||||
cx.on_release(Self::release),
|
||||
@@ -896,6 +893,7 @@ impl Project {
|
||||
ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.worktree_store);
|
||||
ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.lsp_store);
|
||||
ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.settings_observer);
|
||||
ssh.subscribe_to_entity(SSH_PROJECT_ID, &this.git_store);
|
||||
|
||||
ssh_proto.add_entity_message_handler(Self::handle_create_buffer_for_peer);
|
||||
ssh_proto.add_entity_message_handler(Self::handle_update_worktree);
|
||||
@@ -909,6 +907,7 @@ impl Project {
|
||||
SettingsObserver::init(&ssh_proto);
|
||||
TaskStore::init(Some(&ssh_proto));
|
||||
ToolchainStore::init(&ssh_proto);
|
||||
GitStore::init(&ssh_proto);
|
||||
|
||||
this
|
||||
})
|
||||
@@ -1030,9 +1029,10 @@ impl Project {
|
||||
SettingsObserver::new_remote(worktree_store.clone(), task_store.clone(), cx)
|
||||
})?;
|
||||
|
||||
let git_state = cx.new(|cx| {
|
||||
GitState::new(
|
||||
let git_store = cx.new(|cx| {
|
||||
GitStore::new(
|
||||
&worktree_store,
|
||||
buffer_store.clone(),
|
||||
Some(client.clone().into()),
|
||||
Some(ProjectId(remote_id)),
|
||||
cx,
|
||||
@@ -1089,7 +1089,7 @@ impl Project {
|
||||
remote_id,
|
||||
replica_id,
|
||||
},
|
||||
git_state,
|
||||
git_store,
|
||||
buffers_needing_diff: Default::default(),
|
||||
git_diff_debouncer: DebouncedDelay::new(),
|
||||
terminals: Terminals {
|
||||
@@ -1675,6 +1675,9 @@ impl Project {
|
||||
self.client
|
||||
.subscribe_to_entity(project_id)?
|
||||
.set_entity(&self.settings_observer, &mut cx.to_async()),
|
||||
self.client
|
||||
.subscribe_to_entity(project_id)?
|
||||
.set_entity(&self.git_store, &mut cx.to_async()),
|
||||
]);
|
||||
|
||||
self.buffer_store.update(cx, |buffer_store, cx| {
|
||||
@@ -4038,142 +4041,6 @@ impl Project {
|
||||
Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
|
||||
}
|
||||
|
||||
async fn handle_stage(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::Stage>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::Ack> {
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
|
||||
let repository_handle =
|
||||
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||
|
||||
let entries = envelope
|
||||
.payload
|
||||
.paths
|
||||
.into_iter()
|
||||
.map(PathBuf::from)
|
||||
.map(RepoPath::new)
|
||||
.collect();
|
||||
|
||||
repository_handle
|
||||
.update(&mut cx, |repository_handle, _| {
|
||||
repository_handle.stage_entries(entries)
|
||||
})?
|
||||
.await??;
|
||||
Ok(proto::Ack {})
|
||||
}
|
||||
|
||||
async fn handle_unstage(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::Unstage>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::Ack> {
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
|
||||
let repository_handle =
|
||||
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||
|
||||
let entries = envelope
|
||||
.payload
|
||||
.paths
|
||||
.into_iter()
|
||||
.map(PathBuf::from)
|
||||
.map(RepoPath::new)
|
||||
.collect();
|
||||
|
||||
repository_handle
|
||||
.update(&mut cx, |repository_handle, _| {
|
||||
repository_handle.unstage_entries(entries)
|
||||
})?
|
||||
.await??;
|
||||
Ok(proto::Ack {})
|
||||
}
|
||||
|
||||
async fn handle_commit(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::Commit>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::Ack> {
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
|
||||
let repository_handle =
|
||||
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||
|
||||
let message = SharedString::from(envelope.payload.message);
|
||||
let name = envelope.payload.name.map(SharedString::from);
|
||||
let email = envelope.payload.email.map(SharedString::from);
|
||||
repository_handle
|
||||
.update(&mut cx, |repository_handle, _| {
|
||||
repository_handle.commit(message, name.zip(email))
|
||||
})?
|
||||
.await??;
|
||||
Ok(proto::Ack {})
|
||||
}
|
||||
|
||||
async fn handle_set_index_text(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::SetIndexText>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::Ack> {
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
|
||||
let repository_handle =
|
||||
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||
|
||||
repository_handle
|
||||
.update(&mut cx, |repository_handle, _| {
|
||||
repository_handle.set_index_text(
|
||||
&RepoPath::from_str(&envelope.payload.path),
|
||||
envelope.payload.text,
|
||||
)
|
||||
})?
|
||||
.await??;
|
||||
Ok(proto::Ack {})
|
||||
}
|
||||
|
||||
async fn handle_open_commit_message_buffer(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::OpenCommitMessageBuffer>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::OpenBufferResponse> {
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
|
||||
let repository_handle =
|
||||
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||
let buffer = repository_handle
|
||||
.update(&mut cx, |repository_handle, cx| {
|
||||
repository_handle.open_commit_buffer(None, this.read(cx).buffer_store.clone(), cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
let peer_id = envelope.original_sender_id()?;
|
||||
Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
|
||||
}
|
||||
|
||||
fn repository_for_request(
|
||||
this: &Entity<Self>,
|
||||
worktree_id: WorktreeId,
|
||||
work_directory_id: ProjectEntryId,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<Entity<Repository>> {
|
||||
this.update(cx, |project, cx| {
|
||||
let repository_handle = project
|
||||
.git_state()
|
||||
.read(cx)
|
||||
.all_repositories()
|
||||
.into_iter()
|
||||
.find(|repository_handle| {
|
||||
let repository_handle = repository_handle.read(cx);
|
||||
repository_handle.worktree_id == worktree_id
|
||||
&& repository_handle.repository_entry.work_directory_id()
|
||||
== work_directory_id
|
||||
})
|
||||
.context("missing repository handle")?;
|
||||
anyhow::Ok(repository_handle)
|
||||
})?
|
||||
}
|
||||
|
||||
fn respond_to_open_buffer_request(
|
||||
this: Entity<Self>,
|
||||
buffer: Entity<Buffer>,
|
||||
@@ -4365,16 +4232,16 @@ impl Project {
|
||||
&self.buffer_store
|
||||
}
|
||||
|
||||
pub fn git_state(&self) -> &Entity<GitState> {
|
||||
&self.git_state
|
||||
pub fn git_store(&self) -> &Entity<GitStore> {
|
||||
&self.git_store
|
||||
}
|
||||
|
||||
pub fn active_repository(&self, cx: &App) -> Option<Entity<Repository>> {
|
||||
self.git_state.read(cx).active_repository()
|
||||
self.git_store.read(cx).active_repository()
|
||||
}
|
||||
|
||||
pub fn all_repositories(&self, cx: &App) -> Vec<Entity<Repository>> {
|
||||
self.git_state.read(cx).all_repositories()
|
||||
self.git_store.read(cx).all_repositories()
|
||||
}
|
||||
|
||||
pub fn repository_and_path_for_buffer_id(
|
||||
@@ -4386,7 +4253,7 @@ impl Project {
|
||||
.buffer_for_id(buffer_id, cx)?
|
||||
.read(cx)
|
||||
.project_path(cx)?;
|
||||
self.git_state
|
||||
self.git_store
|
||||
.read(cx)
|
||||
.all_repositories()
|
||||
.into_iter()
|
||||
|
||||
@@ -12,6 +12,7 @@ use futures::{
|
||||
future::{BoxFuture, Shared},
|
||||
FutureExt, SinkExt,
|
||||
};
|
||||
use git::repository::Branch;
|
||||
use gpui::{App, AsyncApp, Context, Entity, EntityId, EventEmitter, Task, WeakEntity};
|
||||
use postage::oneshot;
|
||||
use rpc::{
|
||||
@@ -24,7 +25,10 @@ use smol::{
|
||||
};
|
||||
use text::ReplicaId;
|
||||
use util::{paths::SanitizedPath, ResultExt};
|
||||
use worktree::{Entry, ProjectEntryId, UpdatedEntriesSet, Worktree, WorktreeId, WorktreeSettings};
|
||||
use worktree::{
|
||||
branch_to_proto, Entry, ProjectEntryId, UpdatedEntriesSet, Worktree, WorktreeId,
|
||||
WorktreeSettings,
|
||||
};
|
||||
|
||||
use crate::{search::SearchQuery, ProjectPath};
|
||||
|
||||
@@ -133,11 +137,12 @@ impl WorktreeStore {
|
||||
.find(|worktree| worktree.read(cx).id() == id)
|
||||
}
|
||||
|
||||
pub fn current_branch(&self, repository: ProjectPath, cx: &App) -> Option<Arc<str>> {
|
||||
pub fn current_branch(&self, repository: ProjectPath, cx: &App) -> Option<Branch> {
|
||||
self.worktree_for_id(repository.worktree_id, cx)?
|
||||
.read(cx)
|
||||
.git_entry(repository.path)?
|
||||
.branch()
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub fn worktree_for_entry(
|
||||
@@ -938,9 +943,24 @@ impl WorktreeStore {
|
||||
.map(|proto_branch| git::repository::Branch {
|
||||
is_head: proto_branch.is_head,
|
||||
name: proto_branch.name.into(),
|
||||
unix_timestamp: proto_branch
|
||||
.unix_timestamp
|
||||
.map(|timestamp| timestamp as i64),
|
||||
upstream: proto_branch.upstream.map(|upstream| {
|
||||
git::repository::Upstream {
|
||||
ref_name: upstream.ref_name.into(),
|
||||
tracking: upstream.tracking.map(|tracking| {
|
||||
git::repository::UpstreamTracking {
|
||||
ahead: tracking.ahead as u32,
|
||||
behind: tracking.behind as u32,
|
||||
}
|
||||
}),
|
||||
}
|
||||
}),
|
||||
most_recent_commit: proto_branch.most_recent_commit.map(|commit| {
|
||||
git::repository::CommitSummary {
|
||||
sha: commit.sha.into(),
|
||||
subject: commit.subject.into(),
|
||||
commit_timestamp: commit.commit_timestamp,
|
||||
}
|
||||
}),
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -1126,14 +1146,7 @@ impl WorktreeStore {
|
||||
.await?;
|
||||
|
||||
Ok(proto::GitBranchesResponse {
|
||||
branches: branches
|
||||
.into_iter()
|
||||
.map(|branch| proto::Branch {
|
||||
is_head: branch.is_head,
|
||||
name: branch.name.to_string(),
|
||||
unix_timestamp: branch.unix_timestamp.map(|timestamp| timestamp as u64),
|
||||
})
|
||||
.collect(),
|
||||
branches: branches.iter().map(branch_to_proto).collect(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -316,6 +316,9 @@ message Envelope {
|
||||
|
||||
OpenUncommittedDiff open_uncommitted_diff = 297;
|
||||
OpenUncommittedDiffResponse open_uncommitted_diff_response = 298;
|
||||
GitShow git_show = 300;
|
||||
GitReset git_reset = 301;
|
||||
GitCommitDetails git_commit_details = 302;
|
||||
|
||||
SetIndexText set_index_text = 299; // current max
|
||||
}
|
||||
@@ -1800,12 +1803,14 @@ message Entry {
|
||||
|
||||
message RepositoryEntry {
|
||||
uint64 work_directory_id = 1;
|
||||
optional string branch = 2;
|
||||
optional string branch = 2; // deprecated
|
||||
optional Branch branch_summary = 6;
|
||||
repeated StatusEntry updated_statuses = 3;
|
||||
repeated string removed_statuses = 4;
|
||||
repeated string current_merge_conflicts = 5;
|
||||
}
|
||||
|
||||
|
||||
message StatusEntry {
|
||||
string repo_path = 1;
|
||||
// Can be removed once collab's min version is >=0.171.0.
|
||||
@@ -2615,10 +2620,26 @@ message ActiveToolchainResponse {
|
||||
optional Toolchain toolchain = 1;
|
||||
}
|
||||
|
||||
message CommitSummary {
|
||||
string sha = 1;
|
||||
string subject = 2;
|
||||
int64 commit_timestamp = 3;
|
||||
}
|
||||
|
||||
message Branch {
|
||||
bool is_head = 1;
|
||||
string name = 2;
|
||||
optional uint64 unix_timestamp = 3;
|
||||
optional GitUpstream upstream = 4;
|
||||
optional CommitSummary most_recent_commit = 5;
|
||||
}
|
||||
message GitUpstream {
|
||||
string ref_name = 1;
|
||||
optional UpstreamTracking tracking = 2;
|
||||
}
|
||||
message UpstreamTracking {
|
||||
uint64 ahead = 1;
|
||||
uint64 behind = 2;
|
||||
}
|
||||
|
||||
message GitBranches {
|
||||
@@ -2639,6 +2660,33 @@ message UpdateGitBranch {
|
||||
message GetPanicFiles {
|
||||
}
|
||||
|
||||
message GitShow {
|
||||
uint64 project_id = 1;
|
||||
uint64 worktree_id = 2;
|
||||
uint64 work_directory_id = 3;
|
||||
string commit = 4;
|
||||
}
|
||||
|
||||
message GitCommitDetails {
|
||||
string sha = 1;
|
||||
string message = 2;
|
||||
int64 commit_timestamp = 3;
|
||||
string committer_email = 4;
|
||||
string committer_name = 5;
|
||||
}
|
||||
|
||||
message GitReset {
|
||||
uint64 project_id = 1;
|
||||
uint64 worktree_id = 2;
|
||||
uint64 work_directory_id = 3;
|
||||
string commit = 4;
|
||||
ResetMode mode = 5;
|
||||
enum ResetMode {
|
||||
SOFT = 0;
|
||||
MIXED = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message GetPanicFilesResponse {
|
||||
repeated string file_contents = 2;
|
||||
}
|
||||
|
||||
@@ -440,6 +440,9 @@ messages!(
|
||||
(SyncExtensionsResponse, Background),
|
||||
(InstallExtension, Background),
|
||||
(RegisterBufferWithLanguageServers, Background),
|
||||
(GitReset, Background),
|
||||
(GitShow, Background),
|
||||
(GitCommitDetails, Background),
|
||||
(SetIndexText, Background),
|
||||
);
|
||||
|
||||
@@ -574,6 +577,8 @@ request_messages!(
|
||||
(SyncExtensions, SyncExtensionsResponse),
|
||||
(InstallExtension, Ack),
|
||||
(RegisterBufferWithLanguageServers, Ack),
|
||||
(GitShow, GitCommitDetails),
|
||||
(GitReset, Ack),
|
||||
(SetIndexText, Ack),
|
||||
);
|
||||
|
||||
@@ -667,6 +672,8 @@ entity_messages!(
|
||||
GetPathMetadata,
|
||||
CancelLanguageServerWork,
|
||||
RegisterBufferWithLanguageServers,
|
||||
GitShow,
|
||||
GitReset,
|
||||
SetIndexText,
|
||||
);
|
||||
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
use ::proto::{FromProto, ToProto};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use anyhow::{anyhow, Result};
|
||||
use extension::ExtensionHostProxy;
|
||||
use extension_host::headless_host::HeadlessExtensionStore;
|
||||
use fs::Fs;
|
||||
use git::repository::RepoPath;
|
||||
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, PromptLevel, SharedString};
|
||||
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, PromptLevel};
|
||||
use http_client::HttpClient;
|
||||
use language::{proto::serialize_operation, Buffer, BufferEvent, LanguageRegistry};
|
||||
use node_runtime::NodeRuntime;
|
||||
use project::{
|
||||
buffer_store::{BufferStore, BufferStoreEvent},
|
||||
git::{GitState, Repository},
|
||||
git::GitStore,
|
||||
project_settings::SettingsObserver,
|
||||
search::SearchQuery,
|
||||
task_store::TaskStore,
|
||||
worktree_store::WorktreeStore,
|
||||
LspStore, LspStoreEvent, PrettierStore, ProjectEntryId, ProjectPath, ToolchainStore,
|
||||
WorktreeId,
|
||||
LspStore, LspStoreEvent, PrettierStore, ProjectPath, ToolchainStore, WorktreeId,
|
||||
};
|
||||
use remote::ssh_session::ChannelClient;
|
||||
use rpc::{
|
||||
@@ -44,7 +42,7 @@ pub struct HeadlessProject {
|
||||
pub next_entry_id: Arc<AtomicUsize>,
|
||||
pub languages: Arc<LanguageRegistry>,
|
||||
pub extensions: Entity<HeadlessExtensionStore>,
|
||||
pub git_state: Entity<GitState>,
|
||||
pub git_store: Entity<GitStore>,
|
||||
}
|
||||
|
||||
pub struct HeadlessAppState {
|
||||
@@ -83,13 +81,14 @@ impl HeadlessProject {
|
||||
store
|
||||
});
|
||||
|
||||
let git_state = cx.new(|cx| GitState::new(&worktree_store, None, None, cx));
|
||||
|
||||
let buffer_store = cx.new(|cx| {
|
||||
let mut buffer_store = BufferStore::local(worktree_store.clone(), cx);
|
||||
buffer_store.shared(SSH_PROJECT_ID, session.clone().into(), cx);
|
||||
buffer_store
|
||||
});
|
||||
|
||||
let git_store =
|
||||
cx.new(|cx| GitStore::new(&worktree_store, buffer_store.clone(), None, None, cx));
|
||||
let prettier_store = cx.new(|cx| {
|
||||
PrettierStore::new(
|
||||
node_runtime.clone(),
|
||||
@@ -180,6 +179,7 @@ impl HeadlessProject {
|
||||
session.subscribe_to_entity(SSH_PROJECT_ID, &task_store);
|
||||
session.subscribe_to_entity(SSH_PROJECT_ID, &toolchain_store);
|
||||
session.subscribe_to_entity(SSH_PROJECT_ID, &settings_observer);
|
||||
session.subscribe_to_entity(SSH_PROJECT_ID, &git_store);
|
||||
|
||||
client.add_request_handler(cx.weak_entity(), Self::handle_list_remote_directory);
|
||||
client.add_request_handler(cx.weak_entity(), Self::handle_get_path_metadata);
|
||||
@@ -197,12 +197,6 @@ impl HeadlessProject {
|
||||
client.add_entity_request_handler(BufferStore::handle_update_buffer);
|
||||
client.add_entity_message_handler(BufferStore::handle_close_buffer);
|
||||
|
||||
client.add_entity_request_handler(Self::handle_stage);
|
||||
client.add_entity_request_handler(Self::handle_unstage);
|
||||
client.add_entity_request_handler(Self::handle_commit);
|
||||
client.add_entity_request_handler(Self::handle_set_index_text);
|
||||
client.add_entity_request_handler(Self::handle_open_commit_message_buffer);
|
||||
|
||||
client.add_request_handler(
|
||||
extensions.clone().downgrade(),
|
||||
HeadlessExtensionStore::handle_sync_extensions,
|
||||
@@ -218,6 +212,7 @@ impl HeadlessProject {
|
||||
LspStore::init(&client);
|
||||
TaskStore::init(Some(&client));
|
||||
ToolchainStore::init(&client);
|
||||
GitStore::init(&client);
|
||||
|
||||
HeadlessProject {
|
||||
session: client,
|
||||
@@ -230,7 +225,7 @@ impl HeadlessProject {
|
||||
next_entry_id: Default::default(),
|
||||
languages,
|
||||
extensions,
|
||||
git_state,
|
||||
git_store,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -616,157 +611,6 @@ impl HeadlessProject {
|
||||
log::debug!("Received ping from client");
|
||||
Ok(proto::Ack {})
|
||||
}
|
||||
|
||||
async fn handle_stage(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::Stage>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::Ack> {
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
|
||||
let repository_handle =
|
||||
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||
|
||||
let entries = envelope
|
||||
.payload
|
||||
.paths
|
||||
.into_iter()
|
||||
.map(PathBuf::from)
|
||||
.map(RepoPath::new)
|
||||
.collect();
|
||||
|
||||
repository_handle
|
||||
.update(&mut cx, |repository_handle, _| {
|
||||
repository_handle.stage_entries(entries)
|
||||
})?
|
||||
.await??;
|
||||
Ok(proto::Ack {})
|
||||
}
|
||||
|
||||
async fn handle_unstage(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::Unstage>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::Ack> {
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
|
||||
let repository_handle =
|
||||
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||
|
||||
let entries = envelope
|
||||
.payload
|
||||
.paths
|
||||
.into_iter()
|
||||
.map(PathBuf::from)
|
||||
.map(RepoPath::new)
|
||||
.collect();
|
||||
|
||||
repository_handle
|
||||
.update(&mut cx, |repository_handle, _| {
|
||||
repository_handle.unstage_entries(entries)
|
||||
})?
|
||||
.await??;
|
||||
|
||||
Ok(proto::Ack {})
|
||||
}
|
||||
|
||||
async fn handle_commit(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::Commit>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::Ack> {
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
|
||||
let repository_handle =
|
||||
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||
|
||||
let message = SharedString::from(envelope.payload.message);
|
||||
let name = envelope.payload.name.map(SharedString::from);
|
||||
let email = envelope.payload.email.map(SharedString::from);
|
||||
|
||||
repository_handle
|
||||
.update(&mut cx, |repository_handle, _| {
|
||||
repository_handle.commit(message, name.zip(email))
|
||||
})?
|
||||
.await??;
|
||||
Ok(proto::Ack {})
|
||||
}
|
||||
|
||||
async fn handle_set_index_text(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::SetIndexText>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::Ack> {
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
|
||||
let repository =
|
||||
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||
repository
|
||||
.update(&mut cx, |repository, _| {
|
||||
repository.set_index_text(
|
||||
&RepoPath::from(envelope.payload.path.as_str()),
|
||||
envelope.payload.text,
|
||||
)
|
||||
})?
|
||||
.await??;
|
||||
Ok(proto::Ack {})
|
||||
}
|
||||
|
||||
async fn handle_open_commit_message_buffer(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::OpenCommitMessageBuffer>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::OpenBufferResponse> {
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
|
||||
let repository =
|
||||
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||
let buffer = repository
|
||||
.update(&mut cx, |repository, cx| {
|
||||
repository.open_commit_buffer(None, this.read(cx).buffer_store.clone(), cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
let buffer_id = buffer.read_with(&cx, |buffer, _| buffer.remote_id())?;
|
||||
this.update(&mut cx, |headless_project, cx| {
|
||||
headless_project
|
||||
.buffer_store
|
||||
.update(cx, |buffer_store, cx| {
|
||||
buffer_store
|
||||
.create_buffer_for_peer(&buffer, SSH_PEER_ID, cx)
|
||||
.detach_and_log_err(cx);
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(proto::OpenBufferResponse {
|
||||
buffer_id: buffer_id.to_proto(),
|
||||
})
|
||||
}
|
||||
|
||||
fn repository_for_request(
|
||||
this: &Entity<Self>,
|
||||
worktree_id: WorktreeId,
|
||||
work_directory_id: ProjectEntryId,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<Entity<Repository>> {
|
||||
this.update(cx, |project, cx| {
|
||||
let repository_handle = project
|
||||
.git_state
|
||||
.read(cx)
|
||||
.all_repositories()
|
||||
.into_iter()
|
||||
.find(|repository_handle| {
|
||||
repository_handle.read(cx).worktree_id == worktree_id
|
||||
&& repository_handle
|
||||
.read(cx)
|
||||
.repository_entry
|
||||
.work_directory_id()
|
||||
== work_directory_id
|
||||
})
|
||||
.context("missing repository handle")?;
|
||||
anyhow::Ok(repository_handle)
|
||||
})?
|
||||
}
|
||||
}
|
||||
|
||||
fn prompt_to_proto(
|
||||
|
||||
@@ -1364,7 +1364,7 @@ async fn test_remote_git_branches(cx: &mut TestAppContext, server_cx: &mut TestA
|
||||
})
|
||||
});
|
||||
|
||||
assert_eq!(server_branch.as_ref(), branches[2]);
|
||||
assert_eq!(server_branch.name, branches[2]);
|
||||
|
||||
// Also try creating a new branch
|
||||
cx.update(|cx| {
|
||||
@@ -1387,7 +1387,7 @@ async fn test_remote_git_branches(cx: &mut TestAppContext, server_cx: &mut TestA
|
||||
})
|
||||
});
|
||||
|
||||
assert_eq!(server_branch.as_ref(), "totally-new-branch");
|
||||
assert_eq!(server_branch.name, "totally-new-branch");
|
||||
}
|
||||
|
||||
pub async fn init_test(
|
||||
|
||||
@@ -24,19 +24,21 @@ pub fn format_localized_timestamp(
|
||||
) -> String {
|
||||
let timestamp_local = timestamp.to_offset(timezone);
|
||||
let reference_local = reference.to_offset(timezone);
|
||||
format_local_timestamp(timestamp_local, reference_local, format)
|
||||
}
|
||||
|
||||
/// Formats a timestamp, which respects the user's date and time preferences/custom format.
|
||||
pub fn format_local_timestamp(
|
||||
timestamp: OffsetDateTime,
|
||||
reference: OffsetDateTime,
|
||||
format: TimestampFormat,
|
||||
) -> String {
|
||||
match format {
|
||||
TimestampFormat::Absolute => {
|
||||
format_absolute_timestamp(timestamp_local, reference_local, false)
|
||||
}
|
||||
TimestampFormat::EnhancedAbsolute => {
|
||||
format_absolute_timestamp(timestamp_local, reference_local, true)
|
||||
}
|
||||
TimestampFormat::MediumAbsolute => {
|
||||
format_absolute_timestamp_medium(timestamp_local, reference_local)
|
||||
}
|
||||
TimestampFormat::Relative => format_relative_time(timestamp_local, reference_local)
|
||||
.unwrap_or_else(|| format_relative_date(timestamp_local, reference_local)),
|
||||
TimestampFormat::Absolute => format_absolute_timestamp(timestamp, reference, false),
|
||||
TimestampFormat::EnhancedAbsolute => format_absolute_timestamp(timestamp, reference, true),
|
||||
TimestampFormat::MediumAbsolute => format_absolute_timestamp_medium(timestamp, reference),
|
||||
TimestampFormat::Relative => format_relative_time(timestamp, reference)
|
||||
.unwrap_or_else(|| format_relative_date(timestamp, reference)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -521,6 +521,7 @@ impl TitleBar {
|
||||
let branch_name = entry
|
||||
.as_ref()
|
||||
.and_then(|entry| entry.branch())
|
||||
.map(|branch| branch.name.clone())
|
||||
.map(|branch| util::truncate_and_trailoff(&branch, MAX_BRANCH_NAME_LENGTH))?;
|
||||
Some(
|
||||
Button::new("project_branch_trigger", branch_name)
|
||||
|
||||
+114
-44
@@ -19,7 +19,7 @@ use futures::{
|
||||
};
|
||||
use fuzzy::CharBag;
|
||||
use git::{
|
||||
repository::{GitRepository, RepoPath},
|
||||
repository::{Branch, GitRepository, RepoPath},
|
||||
status::{
|
||||
FileStatus, GitSummary, StatusCode, TrackedStatus, UnmergedStatus, UnmergedStatusCode,
|
||||
},
|
||||
@@ -201,7 +201,7 @@ pub struct RepositoryEntry {
|
||||
pub(crate) statuses_by_path: SumTree<StatusEntry>,
|
||||
work_directory_id: ProjectEntryId,
|
||||
pub work_directory: WorkDirectory,
|
||||
pub(crate) branch: Option<Arc<str>>,
|
||||
pub(crate) branch: Option<Branch>,
|
||||
pub current_merge_conflicts: TreeSet<RepoPath>,
|
||||
}
|
||||
|
||||
@@ -214,8 +214,8 @@ impl Deref for RepositoryEntry {
|
||||
}
|
||||
|
||||
impl RepositoryEntry {
|
||||
pub fn branch(&self) -> Option<Arc<str>> {
|
||||
self.branch.clone()
|
||||
pub fn branch(&self) -> Option<&Branch> {
|
||||
self.branch.as_ref()
|
||||
}
|
||||
|
||||
pub fn work_directory_id(&self) -> ProjectEntryId {
|
||||
@@ -243,7 +243,8 @@ impl RepositoryEntry {
|
||||
pub fn initial_update(&self) -> proto::RepositoryEntry {
|
||||
proto::RepositoryEntry {
|
||||
work_directory_id: self.work_directory_id.to_proto(),
|
||||
branch: self.branch.as_ref().map(|branch| branch.to_string()),
|
||||
branch: self.branch.as_ref().map(|branch| branch.name.to_string()),
|
||||
branch_summary: self.branch.as_ref().map(branch_to_proto),
|
||||
updated_statuses: self
|
||||
.statuses_by_path
|
||||
.iter()
|
||||
@@ -302,7 +303,8 @@ impl RepositoryEntry {
|
||||
|
||||
proto::RepositoryEntry {
|
||||
work_directory_id: self.work_directory_id.to_proto(),
|
||||
branch: self.branch.as_ref().map(|branch| branch.to_string()),
|
||||
branch: self.branch.as_ref().map(|branch| branch.name.to_string()),
|
||||
branch_summary: self.branch.as_ref().map(branch_to_proto),
|
||||
updated_statuses,
|
||||
removed_statuses,
|
||||
current_merge_conflicts: self
|
||||
@@ -314,6 +316,61 @@ impl RepositoryEntry {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn branch_to_proto(branch: &git::repository::Branch) -> proto::Branch {
|
||||
proto::Branch {
|
||||
is_head: branch.is_head,
|
||||
name: branch.name.to_string(),
|
||||
unix_timestamp: branch
|
||||
.most_recent_commit
|
||||
.as_ref()
|
||||
.map(|commit| commit.commit_timestamp as u64),
|
||||
upstream: branch.upstream.as_ref().map(|upstream| proto::GitUpstream {
|
||||
ref_name: upstream.ref_name.to_string(),
|
||||
tracking: upstream
|
||||
.tracking
|
||||
.as_ref()
|
||||
.map(|upstream| proto::UpstreamTracking {
|
||||
ahead: upstream.ahead as u64,
|
||||
behind: upstream.behind as u64,
|
||||
}),
|
||||
}),
|
||||
most_recent_commit: branch
|
||||
.most_recent_commit
|
||||
.as_ref()
|
||||
.map(|commit| proto::CommitSummary {
|
||||
sha: commit.sha.to_string(),
|
||||
subject: commit.subject.to_string(),
|
||||
commit_timestamp: commit.commit_timestamp,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn proto_to_branch(proto: &proto::Branch) -> git::repository::Branch {
|
||||
git::repository::Branch {
|
||||
is_head: proto.is_head,
|
||||
name: proto.name.clone().into(),
|
||||
upstream: proto
|
||||
.upstream
|
||||
.as_ref()
|
||||
.map(|upstream| git::repository::Upstream {
|
||||
ref_name: upstream.ref_name.to_string().into(),
|
||||
tracking: upstream.tracking.as_ref().map(|tracking| {
|
||||
git::repository::UpstreamTracking {
|
||||
ahead: tracking.ahead as u32,
|
||||
behind: tracking.behind as u32,
|
||||
}
|
||||
}),
|
||||
}),
|
||||
most_recent_commit: proto.most_recent_commit.as_ref().map(|commit| {
|
||||
git::repository::CommitSummary {
|
||||
sha: commit.sha.to_string().into(),
|
||||
subject: commit.subject.to_string().into(),
|
||||
commit_timestamp: commit.commit_timestamp,
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// This path corresponds to the 'content path' of a repository in relation
|
||||
/// to Zed's project root.
|
||||
/// In the majority of the cases, this is the folder that contains the .git folder.
|
||||
@@ -2625,7 +2682,7 @@ impl Snapshot {
|
||||
|
||||
self.repositories
|
||||
.update(&PathKey(work_dir_entry.path.clone()), &(), |repo| {
|
||||
repo.branch = repository.branch.map(Into::into);
|
||||
repo.branch = repository.branch_summary.as_ref().map(proto_to_branch);
|
||||
repo.statuses_by_path.edit(edits, &());
|
||||
repo.current_merge_conflicts = conflicted_paths
|
||||
});
|
||||
@@ -2647,7 +2704,7 @@ impl Snapshot {
|
||||
work_directory: WorkDirectory::InProject {
|
||||
relative_path: work_dir_entry.path.clone(),
|
||||
},
|
||||
branch: repository.branch.map(Into::into),
|
||||
branch: repository.branch_summary.as_ref().map(proto_to_branch),
|
||||
statuses_by_path: statuses,
|
||||
current_merge_conflicts: conflicted_paths,
|
||||
},
|
||||
@@ -3449,7 +3506,7 @@ impl BackgroundScannerState {
|
||||
RepositoryEntry {
|
||||
work_directory_id: work_dir_id,
|
||||
work_directory: work_directory.clone(),
|
||||
branch: repository.branch_name().map(Into::into),
|
||||
branch: None,
|
||||
statuses_by_path: Default::default(),
|
||||
current_merge_conflicts: Default::default(),
|
||||
},
|
||||
@@ -4198,6 +4255,7 @@ impl BackgroundScanner {
|
||||
// the git repository in an ancestor directory. Find any gitignore files
|
||||
// in ancestor directories.
|
||||
let root_abs_path = self.state.lock().snapshot.abs_path.clone();
|
||||
let mut containing_git_repository = None;
|
||||
for (index, ancestor) in root_abs_path.as_path().ancestors().enumerate() {
|
||||
if index != 0 {
|
||||
if let Ok(ignore) =
|
||||
@@ -4227,7 +4285,7 @@ impl BackgroundScanner {
|
||||
{
|
||||
// We associate the external git repo with our root folder and
|
||||
// also mark where in the git repo the root folder is located.
|
||||
self.state.lock().insert_git_repository_for_path(
|
||||
let local_repository = self.state.lock().insert_git_repository_for_path(
|
||||
WorkDirectory::AboveProject {
|
||||
absolute_path: ancestor.into(),
|
||||
location_in_repo: root_abs_path
|
||||
@@ -4236,10 +4294,14 @@ impl BackgroundScanner {
|
||||
.unwrap()
|
||||
.into(),
|
||||
},
|
||||
ancestor_dot_git.into(),
|
||||
ancestor_dot_git.clone().into(),
|
||||
self.fs.as_ref(),
|
||||
self.watcher.as_ref(),
|
||||
);
|
||||
|
||||
if local_repository.is_some() {
|
||||
containing_git_repository = Some(ancestor_dot_git)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4285,6 +4347,9 @@ impl BackgroundScanner {
|
||||
self.process_events(paths.into_iter().map(Into::into).collect())
|
||||
.await;
|
||||
}
|
||||
if let Some(abs_path) = containing_git_repository {
|
||||
self.process_events(vec![abs_path]).await;
|
||||
}
|
||||
|
||||
// Continue processing events until the worktree is dropped.
|
||||
self.phase = BackgroundScannerPhase::Events;
|
||||
@@ -4703,7 +4768,7 @@ impl BackgroundScanner {
|
||||
);
|
||||
|
||||
if let Some(local_repo) = repo {
|
||||
self.update_git_statuses(UpdateGitStatusesJob {
|
||||
self.update_git_repository(UpdateGitRepoJob {
|
||||
local_repository: local_repo,
|
||||
});
|
||||
}
|
||||
@@ -5255,15 +5320,6 @@ impl BackgroundScanner {
|
||||
if local_repository.git_dir_scan_id == scan_id {
|
||||
continue;
|
||||
}
|
||||
let Some(work_dir) = state
|
||||
.snapshot
|
||||
.entry_for_id(local_repository.work_directory_id)
|
||||
.map(|entry| entry.path.clone())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let branch = local_repository.repo_ptr.branch_name();
|
||||
local_repository.repo_ptr.reload_index();
|
||||
|
||||
state.snapshot.git_repositories.update(
|
||||
@@ -5273,17 +5329,12 @@ impl BackgroundScanner {
|
||||
entry.status_scan_id = scan_id;
|
||||
},
|
||||
);
|
||||
state.snapshot.snapshot.repositories.update(
|
||||
&PathKey(work_dir.clone()),
|
||||
&(),
|
||||
|entry| entry.branch = branch.map(Into::into),
|
||||
);
|
||||
|
||||
local_repository
|
||||
}
|
||||
};
|
||||
|
||||
repo_updates.push(UpdateGitStatusesJob { local_repository });
|
||||
repo_updates.push(UpdateGitRepoJob { local_repository });
|
||||
}
|
||||
|
||||
// Remove any git repositories whose .git entry no longer exists.
|
||||
@@ -5319,7 +5370,7 @@ impl BackgroundScanner {
|
||||
.scoped(|scope| {
|
||||
scope.spawn(async {
|
||||
for repo_update in repo_updates {
|
||||
self.update_git_statuses(repo_update);
|
||||
self.update_git_repository(repo_update);
|
||||
}
|
||||
updates_done_tx.blocking_send(()).ok();
|
||||
});
|
||||
@@ -5343,22 +5394,37 @@ impl BackgroundScanner {
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Update the git statuses for a given batch of entries.
|
||||
fn update_git_statuses(&self, job: UpdateGitStatusesJob) {
|
||||
fn update_branches(&self, job: &UpdateGitRepoJob) -> Result<()> {
|
||||
let branches = job.local_repository.repo().branches()?;
|
||||
let snapshot = self.state.lock().snapshot.snapshot.clone();
|
||||
|
||||
let mut repository = snapshot
|
||||
.repository(job.local_repository.work_directory.path_key())
|
||||
.context("Missing repository")?;
|
||||
|
||||
repository.branch = branches.into_iter().find(|branch| branch.is_head);
|
||||
|
||||
let mut state = self.state.lock();
|
||||
state
|
||||
.snapshot
|
||||
.repositories
|
||||
.insert_or_replace(repository, &());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_statuses(&self, job: &UpdateGitRepoJob) -> Result<()> {
|
||||
log::trace!(
|
||||
"updating git statuses for repo {:?}",
|
||||
job.local_repository.work_directory.display_name()
|
||||
);
|
||||
let t0 = Instant::now();
|
||||
|
||||
let Some(statuses) = job
|
||||
let statuses = job
|
||||
.local_repository
|
||||
.repo()
|
||||
.status(&[git::WORK_DIRECTORY_REPO_PATH.clone()])
|
||||
.log_err()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
.status(&[git::WORK_DIRECTORY_REPO_PATH.clone()])?;
|
||||
|
||||
log::trace!(
|
||||
"computed git statuses for repo {:?} in {:?}",
|
||||
job.local_repository.work_directory.display_name(),
|
||||
@@ -5369,13 +5435,9 @@ impl BackgroundScanner {
|
||||
let mut changed_paths = Vec::new();
|
||||
let snapshot = self.state.lock().snapshot.snapshot.clone();
|
||||
|
||||
let Some(mut repository) =
|
||||
snapshot.repository(job.local_repository.work_directory.path_key())
|
||||
else {
|
||||
// happens when a folder is deleted
|
||||
log::debug!("Got an UpdateGitStatusesJob for a repository that isn't in the snapshot");
|
||||
return;
|
||||
};
|
||||
let mut repository = snapshot
|
||||
.repository(job.local_repository.work_directory.path_key())
|
||||
.context("Got an UpdateGitStatusesJob for a repository that isn't in the snapshot")?;
|
||||
|
||||
let merge_head_shas = job.local_repository.repo().merge_head_shas();
|
||||
if merge_head_shas != job.local_repository.current_merge_head_shas {
|
||||
@@ -5403,6 +5465,7 @@ impl BackgroundScanner {
|
||||
}
|
||||
|
||||
repository.statuses_by_path = new_entries_by_path;
|
||||
|
||||
let mut state = self.state.lock();
|
||||
state
|
||||
.snapshot
|
||||
@@ -5428,6 +5491,13 @@ impl BackgroundScanner {
|
||||
job.local_repository.work_directory.display_name(),
|
||||
t0.elapsed(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update the git statuses for a given batch of entries.
|
||||
fn update_git_repository(&self, job: UpdateGitRepoJob) {
|
||||
self.update_branches(&job).log_err();
|
||||
self.update_statuses(&job).log_err();
|
||||
}
|
||||
|
||||
fn build_change_set(
|
||||
@@ -5637,7 +5707,7 @@ struct UpdateIgnoreStatusJob {
|
||||
scan_queue: Sender<ScanJob>,
|
||||
}
|
||||
|
||||
struct UpdateGitStatusesJob {
|
||||
struct UpdateGitRepoJob {
|
||||
local_repository: LocalRepositoryEntry,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user