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:
Mikayla Maki 2025-02-12 14:57:08 -08:00 committed by GitHub
parent df8adc8b11
commit b014afa938
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 1437 additions and 738 deletions

View file

@ -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,

View file

@ -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((

View file

@ -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()

View file

@ -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(),
})
}