1pub mod git_traversal;
2
3use crate::{
4 buffer_store::{BufferStore, BufferStoreEvent},
5 worktree_store::{WorktreeStore, WorktreeStoreEvent},
6 Project, ProjectEnvironment, ProjectItem, ProjectPath,
7};
8use anyhow::{anyhow, bail, Context as _, Result};
9use askpass::{AskPassDelegate, AskPassSession};
10use buffer_diff::{BufferDiff, BufferDiffEvent};
11use client::ProjectId;
12use collections::HashMap;
13use fs::Fs;
14use futures::{
15 channel::{mpsc, oneshot},
16 future::{self, OptionFuture, Shared},
17 FutureExt as _, StreamExt as _,
18};
19use git::{
20 blame::Blame,
21 parse_git_remote_url,
22 repository::{
23 Branch, CommitDetails, DiffType, GitRepository, GitRepositoryCheckpoint, PushOptions,
24 Remote, RemoteCommandOutput, RepoPath, ResetMode,
25 },
26 status::FileStatus,
27 BuildPermalinkParams, GitHostingProviderRegistry,
28};
29use gpui::{
30 App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Subscription, Task,
31 WeakEntity,
32};
33use language::{
34 proto::{deserialize_version, serialize_version},
35 Buffer, BufferEvent, Language, LanguageRegistry,
36};
37use parking_lot::Mutex;
38use rpc::{
39 proto::{self, git_reset, ToProto, SSH_PROJECT_ID},
40 AnyProtoClient, TypedEnvelope,
41};
42use serde::Deserialize;
43use settings::WorktreeId;
44use std::{
45 collections::{hash_map, VecDeque},
46 future::Future,
47 ops::Range,
48 path::{Path, PathBuf},
49 sync::Arc,
50};
51use text::BufferId;
52use util::{debug_panic, maybe, ResultExt};
53use worktree::{
54 File, ProjectEntryId, RepositoryEntry, StatusEntry, UpdatedGitRepositoriesSet, WorkDirectory,
55 Worktree,
56};
57
58pub struct GitStore {
59 state: GitStoreState,
60 buffer_store: Entity<BufferStore>,
61 _worktree_store: Entity<WorktreeStore>,
62 repositories: HashMap<ProjectEntryId, Entity<Repository>>,
63 active_repo_id: Option<ProjectEntryId>,
64 #[allow(clippy::type_complexity)]
65 loading_diffs:
66 HashMap<(BufferId, DiffKind), Shared<Task<Result<Entity<BufferDiff>, Arc<anyhow::Error>>>>>,
67 diffs: HashMap<BufferId, Entity<BufferDiffState>>,
68 update_sender: mpsc::UnboundedSender<GitJob>,
69 shared_diffs: HashMap<proto::PeerId, HashMap<BufferId, SharedDiffs>>,
70 _subscriptions: [Subscription; 2],
71}
72
73#[derive(Default)]
74struct SharedDiffs {
75 unstaged: Option<Entity<BufferDiff>>,
76 uncommitted: Option<Entity<BufferDiff>>,
77}
78
79#[derive(Default)]
80struct BufferDiffState {
81 unstaged_diff: Option<WeakEntity<BufferDiff>>,
82 uncommitted_diff: Option<WeakEntity<BufferDiff>>,
83 recalculate_diff_task: Option<Task<Result<()>>>,
84 language: Option<Arc<Language>>,
85 language_registry: Option<Arc<LanguageRegistry>>,
86 diff_updated_futures: Vec<oneshot::Sender<()>>,
87
88 head_text: Option<Arc<String>>,
89 index_text: Option<Arc<String>>,
90 head_changed: bool,
91 index_changed: bool,
92 language_changed: bool,
93}
94
95#[derive(Clone, Debug)]
96enum DiffBasesChange {
97 SetIndex(Option<String>),
98 SetHead(Option<String>),
99 SetEach {
100 index: Option<String>,
101 head: Option<String>,
102 },
103 SetBoth(Option<String>),
104}
105
106#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
107enum DiffKind {
108 Unstaged,
109 Uncommitted,
110}
111
112enum GitStoreState {
113 Local {
114 downstream_client: Option<(AnyProtoClient, ProjectId)>,
115 environment: Entity<ProjectEnvironment>,
116 fs: Arc<dyn Fs>,
117 },
118 Ssh {
119 upstream_client: AnyProtoClient,
120 upstream_project_id: ProjectId,
121 downstream_client: Option<(AnyProtoClient, ProjectId)>,
122 environment: Entity<ProjectEnvironment>,
123 },
124 Remote {
125 upstream_client: AnyProtoClient,
126 project_id: ProjectId,
127 },
128}
129
130#[derive(Clone)]
131pub struct GitStoreCheckpoint {
132 checkpoints_by_dot_git_abs_path: HashMap<PathBuf, GitRepositoryCheckpoint>,
133}
134
135pub struct Repository {
136 commit_message_buffer: Option<Entity<Buffer>>,
137 git_store: WeakEntity<GitStore>,
138 project_environment: Option<WeakEntity<ProjectEnvironment>>,
139 pub worktree_id: WorktreeId,
140 pub repository_entry: RepositoryEntry,
141 pub dot_git_abs_path: PathBuf,
142 pub worktree_abs_path: Arc<Path>,
143 pub is_from_single_file_worktree: bool,
144 pub merge_message: Option<String>,
145 pub completed_scan_id: usize,
146 git_repo: RepositoryState,
147 job_sender: mpsc::UnboundedSender<GitJob>,
148 askpass_delegates: Arc<Mutex<HashMap<u64, AskPassDelegate>>>,
149 latest_askpass_id: u64,
150}
151
152#[derive(Clone)]
153enum RepositoryState {
154 Local(Arc<dyn GitRepository>),
155 Remote {
156 project_id: ProjectId,
157 client: AnyProtoClient,
158 worktree_id: WorktreeId,
159 work_directory_id: ProjectEntryId,
160 },
161}
162
163#[derive(Debug)]
164pub enum GitEvent {
165 ActiveRepositoryChanged,
166 FileSystemUpdated,
167 GitStateUpdated,
168 IndexWriteError(anyhow::Error),
169}
170
171struct GitJob {
172 job: Box<dyn FnOnce(&mut AsyncApp) -> Task<()>>,
173 key: Option<GitJobKey>,
174}
175
176#[derive(PartialEq, Eq)]
177enum GitJobKey {
178 WriteIndex(RepoPath),
179 BatchReadIndex(ProjectEntryId),
180}
181
182impl EventEmitter<GitEvent> for GitStore {}
183
184impl GitStore {
185 pub fn local(
186 worktree_store: &Entity<WorktreeStore>,
187 buffer_store: Entity<BufferStore>,
188 environment: Entity<ProjectEnvironment>,
189 fs: Arc<dyn Fs>,
190 cx: &mut Context<Self>,
191 ) -> Self {
192 Self::new(
193 worktree_store.clone(),
194 buffer_store,
195 GitStoreState::Local {
196 downstream_client: None,
197 environment,
198 fs,
199 },
200 cx,
201 )
202 }
203
204 pub fn remote(
205 worktree_store: &Entity<WorktreeStore>,
206 buffer_store: Entity<BufferStore>,
207 upstream_client: AnyProtoClient,
208 project_id: ProjectId,
209 cx: &mut Context<Self>,
210 ) -> Self {
211 Self::new(
212 worktree_store.clone(),
213 buffer_store,
214 GitStoreState::Remote {
215 upstream_client,
216 project_id,
217 },
218 cx,
219 )
220 }
221
222 pub fn ssh(
223 worktree_store: &Entity<WorktreeStore>,
224 buffer_store: Entity<BufferStore>,
225 environment: Entity<ProjectEnvironment>,
226 upstream_client: AnyProtoClient,
227 cx: &mut Context<Self>,
228 ) -> Self {
229 Self::new(
230 worktree_store.clone(),
231 buffer_store,
232 GitStoreState::Ssh {
233 upstream_client,
234 upstream_project_id: ProjectId(SSH_PROJECT_ID),
235 downstream_client: None,
236 environment,
237 },
238 cx,
239 )
240 }
241
242 fn new(
243 worktree_store: Entity<WorktreeStore>,
244 buffer_store: Entity<BufferStore>,
245 state: GitStoreState,
246 cx: &mut Context<Self>,
247 ) -> Self {
248 let update_sender = Self::spawn_git_worker(cx);
249 let _subscriptions = [
250 cx.subscribe(&worktree_store, Self::on_worktree_store_event),
251 cx.subscribe(&buffer_store, Self::on_buffer_store_event),
252 ];
253
254 GitStore {
255 state,
256 buffer_store,
257 _worktree_store: worktree_store,
258 repositories: HashMap::default(),
259 active_repo_id: None,
260 update_sender,
261 _subscriptions,
262 loading_diffs: HashMap::default(),
263 shared_diffs: HashMap::default(),
264 diffs: HashMap::default(),
265 }
266 }
267
268 pub fn init(client: &AnyProtoClient) {
269 client.add_entity_request_handler(Self::handle_get_remotes);
270 client.add_entity_request_handler(Self::handle_get_branches);
271 client.add_entity_request_handler(Self::handle_change_branch);
272 client.add_entity_request_handler(Self::handle_create_branch);
273 client.add_entity_request_handler(Self::handle_git_init);
274 client.add_entity_request_handler(Self::handle_push);
275 client.add_entity_request_handler(Self::handle_pull);
276 client.add_entity_request_handler(Self::handle_fetch);
277 client.add_entity_request_handler(Self::handle_stage);
278 client.add_entity_request_handler(Self::handle_unstage);
279 client.add_entity_request_handler(Self::handle_commit);
280 client.add_entity_request_handler(Self::handle_reset);
281 client.add_entity_request_handler(Self::handle_show);
282 client.add_entity_request_handler(Self::handle_checkout_files);
283 client.add_entity_request_handler(Self::handle_open_commit_message_buffer);
284 client.add_entity_request_handler(Self::handle_set_index_text);
285 client.add_entity_request_handler(Self::handle_askpass);
286 client.add_entity_request_handler(Self::handle_check_for_pushed_commits);
287 client.add_entity_request_handler(Self::handle_git_diff);
288 client.add_entity_request_handler(Self::handle_open_unstaged_diff);
289 client.add_entity_request_handler(Self::handle_open_uncommitted_diff);
290 client.add_entity_message_handler(Self::handle_update_diff_bases);
291 client.add_entity_request_handler(Self::handle_get_permalink_to_line);
292 client.add_entity_request_handler(Self::handle_blame_buffer);
293 }
294
295 pub fn is_local(&self) -> bool {
296 matches!(self.state, GitStoreState::Local { .. })
297 }
298
299 pub fn shared(&mut self, remote_id: u64, client: AnyProtoClient, _cx: &mut App) {
300 match &mut self.state {
301 GitStoreState::Local {
302 downstream_client, ..
303 }
304 | GitStoreState::Ssh {
305 downstream_client, ..
306 } => {
307 *downstream_client = Some((client, ProjectId(remote_id)));
308 }
309 GitStoreState::Remote { .. } => {
310 debug_panic!("shared called on remote store");
311 }
312 }
313 }
314
315 pub fn unshared(&mut self, _cx: &mut Context<Self>) {
316 match &mut self.state {
317 GitStoreState::Local {
318 downstream_client, ..
319 }
320 | GitStoreState::Ssh {
321 downstream_client, ..
322 } => {
323 downstream_client.take();
324 }
325 GitStoreState::Remote { .. } => {
326 debug_panic!("unshared called on remote store");
327 }
328 }
329 self.shared_diffs.clear();
330 }
331
332 pub(crate) fn forget_shared_diffs_for(&mut self, peer_id: &proto::PeerId) {
333 self.shared_diffs.remove(peer_id);
334 }
335
336 pub fn active_repository(&self) -> Option<Entity<Repository>> {
337 self.active_repo_id
338 .as_ref()
339 .map(|id| self.repositories[&id].clone())
340 }
341
342 pub fn open_unstaged_diff(
343 &mut self,
344 buffer: Entity<Buffer>,
345 cx: &mut Context<Self>,
346 ) -> Task<Result<Entity<BufferDiff>>> {
347 let buffer_id = buffer.read(cx).remote_id();
348 if let Some(diff_state) = self.diffs.get(&buffer_id) {
349 if let Some(unstaged_diff) = diff_state
350 .read(cx)
351 .unstaged_diff
352 .as_ref()
353 .and_then(|weak| weak.upgrade())
354 {
355 if let Some(task) =
356 diff_state.update(cx, |diff_state, _| diff_state.wait_for_recalculation())
357 {
358 return cx.background_executor().spawn(async move {
359 task.await?;
360 Ok(unstaged_diff)
361 });
362 }
363 return Task::ready(Ok(unstaged_diff));
364 }
365 }
366
367 let task = match self.loading_diffs.entry((buffer_id, DiffKind::Unstaged)) {
368 hash_map::Entry::Occupied(e) => e.get().clone(),
369 hash_map::Entry::Vacant(entry) => {
370 let staged_text = self.state.load_staged_text(&buffer, &self.buffer_store, cx);
371 entry
372 .insert(
373 cx.spawn(async move |this, cx| {
374 Self::open_diff_internal(
375 this,
376 DiffKind::Unstaged,
377 staged_text.await.map(DiffBasesChange::SetIndex),
378 buffer,
379 cx,
380 )
381 .await
382 .map_err(Arc::new)
383 })
384 .shared(),
385 )
386 .clone()
387 }
388 };
389
390 cx.background_spawn(async move { task.await.map_err(|e| anyhow!("{e}")) })
391 }
392
393 pub fn open_uncommitted_diff(
394 &mut self,
395 buffer: Entity<Buffer>,
396 cx: &mut Context<Self>,
397 ) -> Task<Result<Entity<BufferDiff>>> {
398 let buffer_id = buffer.read(cx).remote_id();
399
400 if let Some(diff_state) = self.diffs.get(&buffer_id) {
401 if let Some(uncommitted_diff) = diff_state
402 .read(cx)
403 .uncommitted_diff
404 .as_ref()
405 .and_then(|weak| weak.upgrade())
406 {
407 if let Some(task) =
408 diff_state.update(cx, |diff_state, _| diff_state.wait_for_recalculation())
409 {
410 return cx.background_executor().spawn(async move {
411 task.await?;
412 Ok(uncommitted_diff)
413 });
414 }
415 return Task::ready(Ok(uncommitted_diff));
416 }
417 }
418
419 let task = match self.loading_diffs.entry((buffer_id, DiffKind::Uncommitted)) {
420 hash_map::Entry::Occupied(e) => e.get().clone(),
421 hash_map::Entry::Vacant(entry) => {
422 let changes = self
423 .state
424 .load_committed_text(&buffer, &self.buffer_store, cx);
425
426 entry
427 .insert(
428 cx.spawn(async move |this, cx| {
429 Self::open_diff_internal(
430 this,
431 DiffKind::Uncommitted,
432 changes.await,
433 buffer,
434 cx,
435 )
436 .await
437 .map_err(Arc::new)
438 })
439 .shared(),
440 )
441 .clone()
442 }
443 };
444
445 cx.background_spawn(async move { task.await.map_err(|e| anyhow!("{e}")) })
446 }
447
448 async fn open_diff_internal(
449 this: WeakEntity<Self>,
450 kind: DiffKind,
451 texts: Result<DiffBasesChange>,
452 buffer_entity: Entity<Buffer>,
453 cx: &mut AsyncApp,
454 ) -> Result<Entity<BufferDiff>> {
455 let diff_bases_change = match texts {
456 Err(e) => {
457 this.update(cx, |this, cx| {
458 let buffer = buffer_entity.read(cx);
459 let buffer_id = buffer.remote_id();
460 this.loading_diffs.remove(&(buffer_id, kind));
461 })?;
462 return Err(e);
463 }
464 Ok(change) => change,
465 };
466
467 this.update(cx, |this, cx| {
468 let buffer = buffer_entity.read(cx);
469 let buffer_id = buffer.remote_id();
470 let language = buffer.language().cloned();
471 let language_registry = buffer.language_registry();
472 let text_snapshot = buffer.text_snapshot();
473 this.loading_diffs.remove(&(buffer_id, kind));
474
475 let diff_state = this
476 .diffs
477 .entry(buffer_id)
478 .or_insert_with(|| cx.new(|_| BufferDiffState::default()));
479
480 let diff = cx.new(|cx| BufferDiff::new(&text_snapshot, cx));
481
482 cx.subscribe(&diff, Self::on_buffer_diff_event).detach();
483 diff_state.update(cx, |diff_state, cx| {
484 diff_state.language = language;
485 diff_state.language_registry = language_registry;
486
487 match kind {
488 DiffKind::Unstaged => diff_state.unstaged_diff = Some(diff.downgrade()),
489 DiffKind::Uncommitted => {
490 let unstaged_diff = if let Some(diff) = diff_state.unstaged_diff() {
491 diff
492 } else {
493 let unstaged_diff = cx.new(|cx| BufferDiff::new(&text_snapshot, cx));
494 diff_state.unstaged_diff = Some(unstaged_diff.downgrade());
495 unstaged_diff
496 };
497
498 diff.update(cx, |diff, _| diff.set_secondary_diff(unstaged_diff));
499 diff_state.uncommitted_diff = Some(diff.downgrade())
500 }
501 }
502
503 let rx = diff_state.diff_bases_changed(text_snapshot, diff_bases_change, cx);
504
505 anyhow::Ok(async move {
506 rx.await.ok();
507 Ok(diff)
508 })
509 })
510 })??
511 .await
512 }
513
514 pub fn get_unstaged_diff(&self, buffer_id: BufferId, cx: &App) -> Option<Entity<BufferDiff>> {
515 let diff_state = self.diffs.get(&buffer_id)?;
516 diff_state.read(cx).unstaged_diff.as_ref()?.upgrade()
517 }
518
519 pub fn get_uncommitted_diff(
520 &self,
521 buffer_id: BufferId,
522 cx: &App,
523 ) -> Option<Entity<BufferDiff>> {
524 let diff_state = self.diffs.get(&buffer_id)?;
525 diff_state.read(cx).uncommitted_diff.as_ref()?.upgrade()
526 }
527
528 pub fn project_path_git_status(
529 &self,
530 project_path: &ProjectPath,
531 cx: &App,
532 ) -> Option<FileStatus> {
533 let (repo, repo_path) = self.repository_and_path_for_project_path(project_path, cx)?;
534 Some(
535 repo.read(cx)
536 .repository_entry
537 .status_for_path(&repo_path)?
538 .status,
539 )
540 }
541
542 pub fn checkpoint(&self, cx: &App) -> Task<Result<GitStoreCheckpoint>> {
543 let mut dot_git_abs_paths = Vec::new();
544 let mut checkpoints = Vec::new();
545 for repository in self.repositories.values() {
546 let repository = repository.read(cx);
547 dot_git_abs_paths.push(repository.dot_git_abs_path.clone());
548 checkpoints.push(repository.checkpoint().map(|checkpoint| checkpoint?));
549 }
550
551 cx.background_executor().spawn(async move {
552 let checkpoints = future::try_join_all(checkpoints).await?;
553 Ok(GitStoreCheckpoint {
554 checkpoints_by_dot_git_abs_path: dot_git_abs_paths
555 .into_iter()
556 .zip(checkpoints)
557 .collect(),
558 })
559 })
560 }
561
562 pub fn restore_checkpoint(&self, checkpoint: GitStoreCheckpoint, cx: &App) -> Task<Result<()>> {
563 let repositories_by_dot_git_abs_path = self
564 .repositories
565 .values()
566 .map(|repo| (repo.read(cx).dot_git_abs_path.clone(), repo))
567 .collect::<HashMap<_, _>>();
568
569 let mut tasks = Vec::new();
570 for (dot_git_abs_path, checkpoint) in checkpoint.checkpoints_by_dot_git_abs_path {
571 if let Some(repository) = repositories_by_dot_git_abs_path.get(&dot_git_abs_path) {
572 let restore = repository.read(cx).restore_checkpoint(checkpoint);
573 tasks.push(async move { restore.await? });
574 }
575 }
576 cx.background_spawn(async move {
577 future::try_join_all(tasks).await?;
578 Ok(())
579 })
580 }
581
582 /// Compares two checkpoints, returning true if they are equal.
583 pub fn compare_checkpoints(
584 &self,
585 left: GitStoreCheckpoint,
586 mut right: GitStoreCheckpoint,
587 cx: &App,
588 ) -> Task<Result<bool>> {
589 let repositories_by_dot_git_abs_path = self
590 .repositories
591 .values()
592 .map(|repo| (repo.read(cx).dot_git_abs_path.clone(), repo))
593 .collect::<HashMap<_, _>>();
594
595 let mut tasks = Vec::new();
596 for (dot_git_abs_path, left_checkpoint) in left.checkpoints_by_dot_git_abs_path {
597 if let Some(right_checkpoint) = right
598 .checkpoints_by_dot_git_abs_path
599 .remove(&dot_git_abs_path)
600 {
601 if let Some(repository) = repositories_by_dot_git_abs_path.get(&dot_git_abs_path) {
602 let compare = repository
603 .read(cx)
604 .compare_checkpoints(left_checkpoint, right_checkpoint);
605 tasks.push(async move { compare.await? });
606 }
607 } else {
608 return Task::ready(Ok(false));
609 }
610 }
611 cx.background_spawn(async move {
612 Ok(future::try_join_all(tasks)
613 .await?
614 .into_iter()
615 .all(|result| result))
616 })
617 }
618
619 pub fn delete_checkpoint(&self, checkpoint: GitStoreCheckpoint, cx: &App) -> Task<Result<()>> {
620 let repositories_by_dot_git_abs_path = self
621 .repositories
622 .values()
623 .map(|repo| (repo.read(cx).dot_git_abs_path.clone(), repo))
624 .collect::<HashMap<_, _>>();
625
626 let mut tasks = Vec::new();
627 for (dot_git_abs_path, checkpoint) in checkpoint.checkpoints_by_dot_git_abs_path {
628 if let Some(repository) = repositories_by_dot_git_abs_path.get(&dot_git_abs_path) {
629 let delete = repository.read(cx).delete_checkpoint(checkpoint);
630 tasks.push(async move { delete.await? });
631 }
632 }
633 cx.background_spawn(async move {
634 future::try_join_all(tasks).await?;
635 Ok(())
636 })
637 }
638
639 /// Blames a buffer.
640 pub fn blame_buffer(
641 &self,
642 buffer: &Entity<Buffer>,
643 version: Option<clock::Global>,
644 cx: &App,
645 ) -> Task<Result<Option<Blame>>> {
646 let buffer = buffer.read(cx);
647 let Some(file) = File::from_dyn(buffer.file()) else {
648 return Task::ready(Err(anyhow!("buffer has no file")));
649 };
650
651 match file.worktree.clone().read(cx) {
652 Worktree::Local(worktree) => {
653 let worktree = worktree.snapshot();
654 let blame_params = maybe!({
655 let local_repo = match worktree.local_repo_for_path(&file.path) {
656 Some(repo_for_path) => repo_for_path,
657 None => return Ok(None),
658 };
659
660 let relative_path = local_repo
661 .relativize(&file.path)
662 .context("failed to relativize buffer path")?;
663
664 let repo = local_repo.repo().clone();
665
666 let content = match version {
667 Some(version) => buffer.rope_for_version(&version).clone(),
668 None => buffer.as_rope().clone(),
669 };
670
671 anyhow::Ok(Some((repo, relative_path, content)))
672 });
673
674 cx.spawn(async move |cx| {
675 let Some((repo, relative_path, content)) = blame_params? else {
676 return Ok(None);
677 };
678 repo.blame(relative_path.clone(), content, cx)
679 .await
680 .with_context(|| format!("Failed to blame {:?}", relative_path.0))
681 .map(Some)
682 })
683 }
684 Worktree::Remote(worktree) => {
685 let buffer_id = buffer.remote_id();
686 let version = buffer.version();
687 let project_id = worktree.project_id();
688 let client = worktree.client();
689 cx.spawn(async move |_| {
690 let response = client
691 .request(proto::BlameBuffer {
692 project_id,
693 buffer_id: buffer_id.into(),
694 version: serialize_version(&version),
695 })
696 .await?;
697 Ok(deserialize_blame_buffer_response(response))
698 })
699 }
700 }
701 }
702
703 pub fn get_permalink_to_line(
704 &self,
705 buffer: &Entity<Buffer>,
706 selection: Range<u32>,
707 cx: &App,
708 ) -> Task<Result<url::Url>> {
709 let buffer = buffer.read(cx);
710 let Some(file) = File::from_dyn(buffer.file()) else {
711 return Task::ready(Err(anyhow!("buffer has no file")));
712 };
713
714 match file.worktree.read(cx) {
715 Worktree::Local(worktree) => {
716 let worktree_path = worktree.abs_path().clone();
717 let Some((repo_entry, repo)) =
718 worktree.repository_for_path(&file.path).and_then(|entry| {
719 let repo = worktree.get_local_repo(&entry)?.repo().clone();
720 Some((entry, repo))
721 })
722 else {
723 // If we're not in a Git repo, check whether this is a Rust source
724 // file in the Cargo registry (presumably opened with go-to-definition
725 // from a normal Rust file). If so, we can put together a permalink
726 // using crate metadata.
727 if buffer
728 .language()
729 .is_none_or(|lang| lang.name() != "Rust".into())
730 {
731 return Task::ready(Err(anyhow!("no permalink available")));
732 }
733 let file_path = worktree_path.join(&file.path);
734 return cx.spawn(async move |cx| {
735 let provider_registry =
736 cx.update(GitHostingProviderRegistry::default_global)?;
737 get_permalink_in_rust_registry_src(provider_registry, file_path, selection)
738 .map_err(|_| anyhow!("no permalink available"))
739 });
740 };
741
742 let path = match repo_entry.relativize(&file.path) {
743 Ok(RepoPath(path)) => path,
744 Err(e) => return Task::ready(Err(e)),
745 };
746
747 let remote = repo_entry
748 .branch()
749 .and_then(|b| b.upstream.as_ref())
750 .and_then(|b| b.remote_name())
751 .unwrap_or("origin")
752 .to_string();
753
754 cx.spawn(async move |cx| {
755 let origin_url = repo
756 .remote_url(&remote)
757 .ok_or_else(|| anyhow!("remote \"{remote}\" not found"))?;
758
759 let sha = repo
760 .head_sha()
761 .ok_or_else(|| anyhow!("failed to read HEAD SHA"))?;
762
763 let provider_registry =
764 cx.update(GitHostingProviderRegistry::default_global)?;
765
766 let (provider, remote) =
767 parse_git_remote_url(provider_registry, &origin_url)
768 .ok_or_else(|| anyhow!("failed to parse Git remote URL"))?;
769
770 let path = path
771 .to_str()
772 .ok_or_else(|| anyhow!("failed to convert path to string"))?;
773
774 Ok(provider.build_permalink(
775 remote,
776 BuildPermalinkParams {
777 sha: &sha,
778 path,
779 selection: Some(selection),
780 },
781 ))
782 })
783 }
784 Worktree::Remote(worktree) => {
785 let buffer_id = buffer.remote_id();
786 let project_id = worktree.project_id();
787 let client = worktree.client();
788 cx.spawn(async move |_| {
789 let response = client
790 .request(proto::GetPermalinkToLine {
791 project_id,
792 buffer_id: buffer_id.into(),
793 selection: Some(proto::Range {
794 start: selection.start as u64,
795 end: selection.end as u64,
796 }),
797 })
798 .await?;
799
800 url::Url::parse(&response.permalink).context("failed to parse permalink")
801 })
802 }
803 }
804 }
805
806 fn downstream_client(&self) -> Option<(AnyProtoClient, ProjectId)> {
807 match &self.state {
808 GitStoreState::Local {
809 downstream_client, ..
810 }
811 | GitStoreState::Ssh {
812 downstream_client, ..
813 } => downstream_client.clone(),
814 GitStoreState::Remote { .. } => None,
815 }
816 }
817
818 fn upstream_client(&self) -> Option<AnyProtoClient> {
819 match &self.state {
820 GitStoreState::Local { .. } => None,
821 GitStoreState::Ssh {
822 upstream_client, ..
823 }
824 | GitStoreState::Remote {
825 upstream_client, ..
826 } => Some(upstream_client.clone()),
827 }
828 }
829
830 fn project_environment(&self) -> Option<Entity<ProjectEnvironment>> {
831 match &self.state {
832 GitStoreState::Local { environment, .. } => Some(environment.clone()),
833 GitStoreState::Ssh { environment, .. } => Some(environment.clone()),
834 GitStoreState::Remote { .. } => None,
835 }
836 }
837
838 fn project_id(&self) -> Option<ProjectId> {
839 match &self.state {
840 GitStoreState::Local { .. } => None,
841 GitStoreState::Ssh { .. } => Some(ProjectId(proto::SSH_PROJECT_ID)),
842 GitStoreState::Remote { project_id, .. } => Some(*project_id),
843 }
844 }
845
846 fn on_worktree_store_event(
847 &mut self,
848 worktree_store: Entity<WorktreeStore>,
849 event: &WorktreeStoreEvent,
850 cx: &mut Context<Self>,
851 ) {
852 let mut new_repositories = HashMap::default();
853 let git_store = cx.weak_entity();
854
855 worktree_store.update(cx, |worktree_store, cx| {
856 for worktree in worktree_store.worktrees() {
857 worktree.update(cx, |worktree, cx| {
858 let snapshot = worktree.snapshot();
859 for repo_entry in snapshot.repositories().iter() {
860 let git_repo_and_merge_message = worktree
861 .as_local()
862 .and_then(|local_worktree| local_worktree.get_local_repo(repo_entry))
863 .map(|local_repo| {
864 (
865 RepositoryState::Local(local_repo.repo().clone()),
866 local_repo.merge_message.clone(),
867 )
868 })
869 .or_else(|| {
870 let git_repo = RepositoryState::Remote {
871 project_id: self.project_id()?,
872 client: self
873 .upstream_client()
874 .context("no upstream client")
875 .log_err()?
876 .clone(),
877 worktree_id: worktree.id(),
878 work_directory_id: repo_entry.work_directory_id(),
879 };
880 Some((git_repo, None))
881 });
882
883 let Some((git_repo, merge_message)) = git_repo_and_merge_message else {
884 continue;
885 };
886
887 let existing_repo = self.repositories.values().find(|repo| {
888 repo.read(cx).id() == (worktree.id(), repo_entry.work_directory_id())
889 });
890
891 let repo = if let Some(existing_repo) = existing_repo {
892 // Update the statuses and merge message but keep everything else.
893 let existing_repo = existing_repo.clone();
894 existing_repo.update(cx, |existing_repo, _| {
895 existing_repo.repository_entry = repo_entry.clone();
896 if matches!(git_repo, RepositoryState::Local { .. }) {
897 existing_repo.merge_message = merge_message;
898 existing_repo.completed_scan_id = worktree.completed_scan_id();
899 }
900 });
901 existing_repo
902 } else {
903 cx.new(|_| Repository {
904 project_environment: self
905 .project_environment()
906 .as_ref()
907 .map(|env| env.downgrade()),
908 git_store: git_store.clone(),
909 worktree_id: worktree.id(),
910 askpass_delegates: Default::default(),
911 latest_askpass_id: 0,
912 repository_entry: repo_entry.clone(),
913 dot_git_abs_path: worktree
914 .dot_git_abs_path(&repo_entry.work_directory),
915 worktree_abs_path: worktree.abs_path(),
916 is_from_single_file_worktree: worktree.is_single_file(),
917 git_repo,
918 job_sender: self.update_sender.clone(),
919 merge_message,
920 commit_message_buffer: None,
921 completed_scan_id: worktree.completed_scan_id(),
922 })
923 };
924 new_repositories.insert(repo_entry.work_directory_id(), repo);
925 }
926 })
927 }
928 });
929
930 self.repositories = new_repositories;
931 if let Some(id) = self.active_repo_id.as_ref() {
932 if !self.repositories.contains_key(id) {
933 self.active_repo_id = None;
934 }
935 } else if let Some(&first_id) = self.repositories.keys().next() {
936 self.active_repo_id = Some(first_id);
937 }
938
939 match event {
940 WorktreeStoreEvent::WorktreeUpdatedGitRepositories(_) => {
941 cx.emit(GitEvent::GitStateUpdated);
942 }
943 WorktreeStoreEvent::WorktreeAdded(worktree) => {
944 if self.is_local() {
945 cx.subscribe(worktree, Self::on_worktree_event).detach();
946 }
947 }
948 _ => {
949 cx.emit(GitEvent::FileSystemUpdated);
950 }
951 }
952 }
953
954 fn on_worktree_event(
955 &mut self,
956 worktree: Entity<Worktree>,
957 event: &worktree::Event,
958 cx: &mut Context<Self>,
959 ) {
960 if let worktree::Event::UpdatedGitRepositories(changed_repos) = event {
961 self.local_worktree_git_repos_changed(worktree, changed_repos, cx);
962 }
963 }
964
965 fn on_buffer_store_event(
966 &mut self,
967 _: Entity<BufferStore>,
968 event: &BufferStoreEvent,
969 cx: &mut Context<Self>,
970 ) {
971 match event {
972 BufferStoreEvent::BufferAdded(buffer) => {
973 cx.subscribe(&buffer, |this, buffer, event, cx| {
974 if let BufferEvent::LanguageChanged = event {
975 let buffer_id = buffer.read(cx).remote_id();
976 if let Some(diff_state) = this.diffs.get(&buffer_id) {
977 diff_state.update(cx, |diff_state, cx| {
978 diff_state.buffer_language_changed(buffer, cx);
979 });
980 }
981 }
982 })
983 .detach();
984 }
985 BufferStoreEvent::SharedBufferClosed(peer_id, buffer_id) => {
986 if let Some(diffs) = self.shared_diffs.get_mut(peer_id) {
987 diffs.remove(buffer_id);
988 }
989 }
990 BufferStoreEvent::BufferDropped(buffer_id) => {
991 self.diffs.remove(&buffer_id);
992 for diffs in self.shared_diffs.values_mut() {
993 diffs.remove(buffer_id);
994 }
995 }
996
997 _ => {}
998 }
999 }
1000
1001 pub fn recalculate_buffer_diffs(
1002 &mut self,
1003 buffers: Vec<Entity<Buffer>>,
1004 cx: &mut Context<Self>,
1005 ) -> impl Future<Output = ()> {
1006 let mut futures = Vec::new();
1007 for buffer in buffers {
1008 if let Some(diff_state) = self.diffs.get_mut(&buffer.read(cx).remote_id()) {
1009 let buffer = buffer.read(cx).text_snapshot();
1010 futures.push(diff_state.update(cx, |diff_state, cx| {
1011 diff_state.recalculate_diffs(buffer, cx)
1012 }));
1013 }
1014 }
1015 async move {
1016 futures::future::join_all(futures).await;
1017 }
1018 }
1019
1020 fn on_buffer_diff_event(
1021 &mut self,
1022 diff: Entity<buffer_diff::BufferDiff>,
1023 event: &BufferDiffEvent,
1024 cx: &mut Context<Self>,
1025 ) {
1026 if let BufferDiffEvent::HunksStagedOrUnstaged(new_index_text) = event {
1027 let buffer_id = diff.read(cx).buffer_id;
1028 if let Some((repo, path)) = self.repository_and_path_for_buffer_id(buffer_id, cx) {
1029 let recv = repo.update(cx, |repo, cx| {
1030 log::debug!("updating index text for buffer {}", path.display());
1031 repo.spawn_set_index_text_job(
1032 path,
1033 new_index_text.as_ref().map(|rope| rope.to_string()),
1034 cx,
1035 )
1036 });
1037 let diff = diff.downgrade();
1038 cx.spawn(async move |this, cx| {
1039 if let Ok(Err(error)) = cx.background_spawn(recv).await {
1040 diff.update(cx, |diff, cx| {
1041 diff.clear_pending_hunks(cx);
1042 })
1043 .ok();
1044 this.update(cx, |_, cx| cx.emit(GitEvent::IndexWriteError(error)))
1045 .ok();
1046 }
1047 })
1048 .detach();
1049 }
1050 }
1051 }
1052
1053 fn local_worktree_git_repos_changed(
1054 &mut self,
1055 worktree: Entity<Worktree>,
1056 changed_repos: &UpdatedGitRepositoriesSet,
1057 cx: &mut Context<Self>,
1058 ) {
1059 debug_assert!(worktree.read(cx).is_local());
1060
1061 let Some(active_repo) = self.active_repository() else {
1062 log::error!("local worktree changed but we have no active repository");
1063 return;
1064 };
1065
1066 let mut diff_state_updates = HashMap::<ProjectEntryId, Vec<_>>::default();
1067 for (buffer_id, diff_state) in &self.diffs {
1068 let Some(buffer) = self.buffer_store.read(cx).get(*buffer_id) else {
1069 continue;
1070 };
1071 let Some(file) = File::from_dyn(buffer.read(cx).file()) else {
1072 continue;
1073 };
1074 if file.worktree != worktree {
1075 continue;
1076 }
1077 let Some(repo_id) = changed_repos
1078 .iter()
1079 .map(|(entry, _)| entry.id)
1080 .find(|repo_id| self.repositories().contains_key(&repo_id))
1081 else {
1082 continue;
1083 };
1084
1085 let diff_state = diff_state.read(cx);
1086 let has_unstaged_diff = diff_state
1087 .unstaged_diff
1088 .as_ref()
1089 .is_some_and(|diff| diff.is_upgradable());
1090 let has_uncommitted_diff = diff_state
1091 .uncommitted_diff
1092 .as_ref()
1093 .is_some_and(|set| set.is_upgradable());
1094
1095 let update = (
1096 buffer,
1097 file.path.clone(),
1098 has_unstaged_diff.then(|| diff_state.index_text.clone()),
1099 has_uncommitted_diff.then(|| diff_state.head_text.clone()),
1100 );
1101 diff_state_updates.entry(repo_id).or_default().push(update);
1102 }
1103
1104 if diff_state_updates.is_empty() {
1105 return;
1106 }
1107
1108 for (repo_id, repo_diff_state_updates) in diff_state_updates.into_iter() {
1109 let worktree = worktree.downgrade();
1110 let git_store = cx.weak_entity();
1111
1112 let _ = active_repo.read(cx).send_keyed_job(
1113 Some(GitJobKey::BatchReadIndex(repo_id)),
1114 |_, mut cx| async move {
1115 let snapshot = worktree.update(&mut cx, |tree, _| {
1116 tree.as_local().map(|local_tree| local_tree.snapshot())
1117 });
1118 let Ok(Some(snapshot)) = snapshot else {
1119 return;
1120 };
1121
1122 let mut diff_bases_changes_by_buffer = Vec::new();
1123 for (buffer, path, current_index_text, current_head_text) in
1124 &repo_diff_state_updates
1125 {
1126 let Some(local_repo) = snapshot.local_repo_for_path(&path) else {
1127 continue;
1128 };
1129 let Some(relative_path) = local_repo.relativize(&path).ok() else {
1130 continue;
1131 };
1132
1133 log::debug!("reloading git state for buffer {}", path.display());
1134 let index_text = if current_index_text.is_some() {
1135 local_repo
1136 .repo()
1137 .load_index_text(relative_path.clone(), cx.clone())
1138 .await
1139 } else {
1140 None
1141 };
1142 let head_text = if current_head_text.is_some() {
1143 local_repo
1144 .repo()
1145 .load_committed_text(relative_path, cx.clone())
1146 .await
1147 } else {
1148 None
1149 };
1150
1151 // Avoid triggering a diff update if the base text has not changed.
1152 if let Some((current_index, current_head)) =
1153 current_index_text.as_ref().zip(current_head_text.as_ref())
1154 {
1155 if current_index.as_deref() == index_text.as_ref()
1156 && current_head.as_deref() == head_text.as_ref()
1157 {
1158 continue;
1159 }
1160 }
1161
1162 let diff_bases_change =
1163 match (current_index_text.is_some(), current_head_text.is_some()) {
1164 (true, true) => Some(if index_text == head_text {
1165 DiffBasesChange::SetBoth(head_text)
1166 } else {
1167 DiffBasesChange::SetEach {
1168 index: index_text,
1169 head: head_text,
1170 }
1171 }),
1172 (true, false) => Some(DiffBasesChange::SetIndex(index_text)),
1173 (false, true) => Some(DiffBasesChange::SetHead(head_text)),
1174 (false, false) => None,
1175 };
1176
1177 diff_bases_changes_by_buffer.push((buffer, diff_bases_change))
1178 }
1179
1180 git_store
1181 .update(&mut cx, |git_store, cx| {
1182 for (buffer, diff_bases_change) in diff_bases_changes_by_buffer {
1183 let Some(diff_state) =
1184 git_store.diffs.get(&buffer.read(cx).remote_id())
1185 else {
1186 continue;
1187 };
1188 let Some(diff_bases_change) = diff_bases_change else {
1189 continue;
1190 };
1191
1192 let downstream_client = git_store.downstream_client();
1193 diff_state.update(cx, |diff_state, cx| {
1194 use proto::update_diff_bases::Mode;
1195
1196 let buffer = buffer.read(cx);
1197 if let Some((client, project_id)) = downstream_client {
1198 let (staged_text, committed_text, mode) =
1199 match diff_bases_change.clone() {
1200 DiffBasesChange::SetIndex(index) => {
1201 (index, None, Mode::IndexOnly)
1202 }
1203 DiffBasesChange::SetHead(head) => {
1204 (None, head, Mode::HeadOnly)
1205 }
1206 DiffBasesChange::SetEach { index, head } => {
1207 (index, head, Mode::IndexAndHead)
1208 }
1209 DiffBasesChange::SetBoth(text) => {
1210 (None, text, Mode::IndexMatchesHead)
1211 }
1212 };
1213 let message = proto::UpdateDiffBases {
1214 project_id: project_id.to_proto(),
1215 buffer_id: buffer.remote_id().to_proto(),
1216 staged_text,
1217 committed_text,
1218 mode: mode as i32,
1219 };
1220
1221 client.send(message).log_err();
1222 }
1223
1224 let _ = diff_state.diff_bases_changed(
1225 buffer.text_snapshot(),
1226 diff_bases_change,
1227 cx,
1228 );
1229 });
1230 }
1231 })
1232 .ok();
1233 },
1234 );
1235 }
1236 }
1237
1238 pub fn repositories(&self) -> &HashMap<ProjectEntryId, Entity<Repository>> {
1239 &self.repositories
1240 }
1241
1242 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
1243 let (repo, path) = self.repository_and_path_for_buffer_id(buffer_id, cx)?;
1244 let status = repo.read(cx).repository_entry.status_for_path(&path)?;
1245 Some(status.status)
1246 }
1247
1248 pub fn repository_and_path_for_buffer_id(
1249 &self,
1250 buffer_id: BufferId,
1251 cx: &App,
1252 ) -> Option<(Entity<Repository>, RepoPath)> {
1253 let buffer = self.buffer_store.read(cx).get(buffer_id)?;
1254 let project_path = buffer.read(cx).project_path(cx)?;
1255 self.repository_and_path_for_project_path(&project_path, cx)
1256 }
1257
1258 pub fn repository_and_path_for_project_path(
1259 &self,
1260 path: &ProjectPath,
1261 cx: &App,
1262 ) -> Option<(Entity<Repository>, RepoPath)> {
1263 let mut result: Option<(Entity<Repository>, RepoPath)> = None;
1264 for repo_handle in self.repositories.values() {
1265 let repo = repo_handle.read(cx);
1266 if repo.worktree_id == path.worktree_id {
1267 if let Ok(relative_path) = repo.repository_entry.relativize(&path.path) {
1268 if result
1269 .as_ref()
1270 .is_none_or(|(result, _)| !repo.contains_sub_repo(result, cx))
1271 {
1272 result = Some((repo_handle.clone(), relative_path))
1273 }
1274 }
1275 }
1276 }
1277 result
1278 }
1279
1280 fn spawn_git_worker(cx: &mut Context<GitStore>) -> mpsc::UnboundedSender<GitJob> {
1281 let (job_tx, mut job_rx) = mpsc::unbounded::<GitJob>();
1282
1283 cx.spawn(async move |_, cx| {
1284 let mut jobs = VecDeque::new();
1285 loop {
1286 while let Ok(Some(next_job)) = job_rx.try_next() {
1287 jobs.push_back(next_job);
1288 }
1289
1290 if let Some(job) = jobs.pop_front() {
1291 if let Some(current_key) = &job.key {
1292 if jobs
1293 .iter()
1294 .any(|other_job| other_job.key.as_ref() == Some(current_key))
1295 {
1296 continue;
1297 }
1298 }
1299 (job.job)(cx).await;
1300 } else if let Some(job) = job_rx.next().await {
1301 jobs.push_back(job);
1302 } else {
1303 break;
1304 }
1305 }
1306 })
1307 .detach();
1308 job_tx
1309 }
1310
1311 pub fn git_init(
1312 &self,
1313 path: Arc<Path>,
1314 fallback_branch_name: String,
1315 cx: &App,
1316 ) -> Task<Result<()>> {
1317 match &self.state {
1318 GitStoreState::Local { fs, .. } => {
1319 let fs = fs.clone();
1320 cx.background_executor()
1321 .spawn(async move { fs.git_init(&path, fallback_branch_name) })
1322 }
1323 GitStoreState::Ssh {
1324 upstream_client,
1325 upstream_project_id: project_id,
1326 ..
1327 }
1328 | GitStoreState::Remote {
1329 upstream_client,
1330 project_id,
1331 ..
1332 } => {
1333 let client = upstream_client.clone();
1334 let project_id = *project_id;
1335 cx.background_executor().spawn(async move {
1336 client
1337 .request(proto::GitInit {
1338 project_id: project_id.0,
1339 abs_path: path.to_string_lossy().to_string(),
1340 fallback_branch_name,
1341 })
1342 .await?;
1343 Ok(())
1344 })
1345 }
1346 }
1347 }
1348
1349 async fn handle_git_init(
1350 this: Entity<Self>,
1351 envelope: TypedEnvelope<proto::GitInit>,
1352 cx: AsyncApp,
1353 ) -> Result<proto::Ack> {
1354 let path: Arc<Path> = PathBuf::from(envelope.payload.abs_path).into();
1355 let name = envelope.payload.fallback_branch_name;
1356 cx.update(|cx| this.read(cx).git_init(path, name, cx))?
1357 .await?;
1358
1359 Ok(proto::Ack {})
1360 }
1361
1362 async fn handle_fetch(
1363 this: Entity<Self>,
1364 envelope: TypedEnvelope<proto::Fetch>,
1365 mut cx: AsyncApp,
1366 ) -> Result<proto::RemoteMessageResponse> {
1367 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1368 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1369 let repository_handle =
1370 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1371 let askpass_id = envelope.payload.askpass_id;
1372
1373 let askpass = make_remote_delegate(
1374 this,
1375 envelope.payload.project_id,
1376 worktree_id,
1377 work_directory_id,
1378 askpass_id,
1379 &mut cx,
1380 );
1381
1382 let remote_output = repository_handle
1383 .update(&mut cx, |repository_handle, cx| {
1384 repository_handle.fetch(askpass, cx)
1385 })?
1386 .await??;
1387
1388 Ok(proto::RemoteMessageResponse {
1389 stdout: remote_output.stdout,
1390 stderr: remote_output.stderr,
1391 })
1392 }
1393
1394 async fn handle_push(
1395 this: Entity<Self>,
1396 envelope: TypedEnvelope<proto::Push>,
1397 mut cx: AsyncApp,
1398 ) -> Result<proto::RemoteMessageResponse> {
1399 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1400 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1401 let repository_handle =
1402 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1403
1404 let askpass_id = envelope.payload.askpass_id;
1405 let askpass = make_remote_delegate(
1406 this,
1407 envelope.payload.project_id,
1408 worktree_id,
1409 work_directory_id,
1410 askpass_id,
1411 &mut cx,
1412 );
1413
1414 let options = envelope
1415 .payload
1416 .options
1417 .as_ref()
1418 .map(|_| match envelope.payload.options() {
1419 proto::push::PushOptions::SetUpstream => git::repository::PushOptions::SetUpstream,
1420 proto::push::PushOptions::Force => git::repository::PushOptions::Force,
1421 });
1422
1423 let branch_name = envelope.payload.branch_name.into();
1424 let remote_name = envelope.payload.remote_name.into();
1425
1426 let remote_output = repository_handle
1427 .update(&mut cx, |repository_handle, cx| {
1428 repository_handle.push(branch_name, remote_name, options, askpass, cx)
1429 })?
1430 .await??;
1431 Ok(proto::RemoteMessageResponse {
1432 stdout: remote_output.stdout,
1433 stderr: remote_output.stderr,
1434 })
1435 }
1436
1437 async fn handle_pull(
1438 this: Entity<Self>,
1439 envelope: TypedEnvelope<proto::Pull>,
1440 mut cx: AsyncApp,
1441 ) -> Result<proto::RemoteMessageResponse> {
1442 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1443 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1444 let repository_handle =
1445 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1446 let askpass_id = envelope.payload.askpass_id;
1447 let askpass = make_remote_delegate(
1448 this,
1449 envelope.payload.project_id,
1450 worktree_id,
1451 work_directory_id,
1452 askpass_id,
1453 &mut cx,
1454 );
1455
1456 let branch_name = envelope.payload.branch_name.into();
1457 let remote_name = envelope.payload.remote_name.into();
1458
1459 let remote_message = repository_handle
1460 .update(&mut cx, |repository_handle, cx| {
1461 repository_handle.pull(branch_name, remote_name, askpass, cx)
1462 })?
1463 .await??;
1464
1465 Ok(proto::RemoteMessageResponse {
1466 stdout: remote_message.stdout,
1467 stderr: remote_message.stderr,
1468 })
1469 }
1470
1471 async fn handle_stage(
1472 this: Entity<Self>,
1473 envelope: TypedEnvelope<proto::Stage>,
1474 mut cx: AsyncApp,
1475 ) -> Result<proto::Ack> {
1476 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1477 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1478 let repository_handle =
1479 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1480
1481 let entries = envelope
1482 .payload
1483 .paths
1484 .into_iter()
1485 .map(PathBuf::from)
1486 .map(RepoPath::new)
1487 .collect();
1488
1489 repository_handle
1490 .update(&mut cx, |repository_handle, cx| {
1491 repository_handle.stage_entries(entries, cx)
1492 })?
1493 .await?;
1494 Ok(proto::Ack {})
1495 }
1496
1497 async fn handle_unstage(
1498 this: Entity<Self>,
1499 envelope: TypedEnvelope<proto::Unstage>,
1500 mut cx: AsyncApp,
1501 ) -> Result<proto::Ack> {
1502 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1503 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1504 let repository_handle =
1505 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1506
1507 let entries = envelope
1508 .payload
1509 .paths
1510 .into_iter()
1511 .map(PathBuf::from)
1512 .map(RepoPath::new)
1513 .collect();
1514
1515 repository_handle
1516 .update(&mut cx, |repository_handle, cx| {
1517 repository_handle.unstage_entries(entries, cx)
1518 })?
1519 .await?;
1520
1521 Ok(proto::Ack {})
1522 }
1523
1524 async fn handle_set_index_text(
1525 this: Entity<Self>,
1526 envelope: TypedEnvelope<proto::SetIndexText>,
1527 mut cx: AsyncApp,
1528 ) -> Result<proto::Ack> {
1529 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1530 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1531 let repository_handle =
1532 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1533
1534 repository_handle
1535 .update(&mut cx, |repository_handle, cx| {
1536 repository_handle.spawn_set_index_text_job(
1537 RepoPath::from_str(&envelope.payload.path),
1538 envelope.payload.text,
1539 cx,
1540 )
1541 })?
1542 .await??;
1543 Ok(proto::Ack {})
1544 }
1545
1546 async fn handle_commit(
1547 this: Entity<Self>,
1548 envelope: TypedEnvelope<proto::Commit>,
1549 mut cx: AsyncApp,
1550 ) -> Result<proto::Ack> {
1551 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1552 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1553 let repository_handle =
1554 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1555
1556 let message = SharedString::from(envelope.payload.message);
1557 let name = envelope.payload.name.map(SharedString::from);
1558 let email = envelope.payload.email.map(SharedString::from);
1559
1560 repository_handle
1561 .update(&mut cx, |repository_handle, cx| {
1562 repository_handle.commit(message, name.zip(email), cx)
1563 })?
1564 .await??;
1565 Ok(proto::Ack {})
1566 }
1567
1568 async fn handle_get_remotes(
1569 this: Entity<Self>,
1570 envelope: TypedEnvelope<proto::GetRemotes>,
1571 mut cx: AsyncApp,
1572 ) -> Result<proto::GetRemotesResponse> {
1573 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1574 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1575 let repository_handle =
1576 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1577
1578 let branch_name = envelope.payload.branch_name;
1579
1580 let remotes = repository_handle
1581 .update(&mut cx, |repository_handle, _| {
1582 repository_handle.get_remotes(branch_name)
1583 })?
1584 .await??;
1585
1586 Ok(proto::GetRemotesResponse {
1587 remotes: remotes
1588 .into_iter()
1589 .map(|remotes| proto::get_remotes_response::Remote {
1590 name: remotes.name.to_string(),
1591 })
1592 .collect::<Vec<_>>(),
1593 })
1594 }
1595
1596 async fn handle_get_branches(
1597 this: Entity<Self>,
1598 envelope: TypedEnvelope<proto::GitGetBranches>,
1599 mut cx: AsyncApp,
1600 ) -> Result<proto::GitBranchesResponse> {
1601 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1602 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1603 let repository_handle =
1604 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1605
1606 let branches = repository_handle
1607 .update(&mut cx, |repository_handle, _| repository_handle.branches())?
1608 .await??;
1609
1610 Ok(proto::GitBranchesResponse {
1611 branches: branches
1612 .into_iter()
1613 .map(|branch| worktree::branch_to_proto(&branch))
1614 .collect::<Vec<_>>(),
1615 })
1616 }
1617 async fn handle_create_branch(
1618 this: Entity<Self>,
1619 envelope: TypedEnvelope<proto::GitCreateBranch>,
1620 mut cx: AsyncApp,
1621 ) -> Result<proto::Ack> {
1622 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1623 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1624 let repository_handle =
1625 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1626 let branch_name = envelope.payload.branch_name;
1627
1628 repository_handle
1629 .update(&mut cx, |repository_handle, _| {
1630 repository_handle.create_branch(branch_name)
1631 })?
1632 .await??;
1633
1634 Ok(proto::Ack {})
1635 }
1636
1637 async fn handle_change_branch(
1638 this: Entity<Self>,
1639 envelope: TypedEnvelope<proto::GitChangeBranch>,
1640 mut cx: AsyncApp,
1641 ) -> Result<proto::Ack> {
1642 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1643 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1644 let repository_handle =
1645 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1646 let branch_name = envelope.payload.branch_name;
1647
1648 repository_handle
1649 .update(&mut cx, |repository_handle, _| {
1650 repository_handle.change_branch(branch_name)
1651 })?
1652 .await??;
1653
1654 Ok(proto::Ack {})
1655 }
1656
1657 async fn handle_show(
1658 this: Entity<Self>,
1659 envelope: TypedEnvelope<proto::GitShow>,
1660 mut cx: AsyncApp,
1661 ) -> Result<proto::GitCommitDetails> {
1662 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1663 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1664 let repository_handle =
1665 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1666
1667 let commit = repository_handle
1668 .update(&mut cx, |repository_handle, _| {
1669 repository_handle.show(envelope.payload.commit)
1670 })?
1671 .await??;
1672 Ok(proto::GitCommitDetails {
1673 sha: commit.sha.into(),
1674 message: commit.message.into(),
1675 commit_timestamp: commit.commit_timestamp,
1676 committer_email: commit.committer_email.into(),
1677 committer_name: commit.committer_name.into(),
1678 })
1679 }
1680
1681 async fn handle_reset(
1682 this: Entity<Self>,
1683 envelope: TypedEnvelope<proto::GitReset>,
1684 mut cx: AsyncApp,
1685 ) -> Result<proto::Ack> {
1686 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1687 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1688 let repository_handle =
1689 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1690
1691 let mode = match envelope.payload.mode() {
1692 git_reset::ResetMode::Soft => ResetMode::Soft,
1693 git_reset::ResetMode::Mixed => ResetMode::Mixed,
1694 };
1695
1696 repository_handle
1697 .update(&mut cx, |repository_handle, cx| {
1698 repository_handle.reset(envelope.payload.commit, mode, cx)
1699 })?
1700 .await??;
1701 Ok(proto::Ack {})
1702 }
1703
1704 async fn handle_checkout_files(
1705 this: Entity<Self>,
1706 envelope: TypedEnvelope<proto::GitCheckoutFiles>,
1707 mut cx: AsyncApp,
1708 ) -> Result<proto::Ack> {
1709 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1710 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1711 let repository_handle =
1712 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1713 let paths = envelope
1714 .payload
1715 .paths
1716 .iter()
1717 .map(|s| RepoPath::from_str(s))
1718 .collect();
1719
1720 repository_handle
1721 .update(&mut cx, |repository_handle, cx| {
1722 repository_handle.checkout_files(&envelope.payload.commit, paths, cx)
1723 })?
1724 .await??;
1725 Ok(proto::Ack {})
1726 }
1727
1728 async fn handle_open_commit_message_buffer(
1729 this: Entity<Self>,
1730 envelope: TypedEnvelope<proto::OpenCommitMessageBuffer>,
1731 mut cx: AsyncApp,
1732 ) -> Result<proto::OpenBufferResponse> {
1733 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1734 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1735 let repository =
1736 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1737 let buffer = repository
1738 .update(&mut cx, |repository, cx| {
1739 repository.open_commit_buffer(None, this.read(cx).buffer_store.clone(), cx)
1740 })?
1741 .await?;
1742
1743 let buffer_id = buffer.read_with(&cx, |buffer, _| buffer.remote_id())?;
1744 this.update(&mut cx, |this, cx| {
1745 this.buffer_store.update(cx, |buffer_store, cx| {
1746 buffer_store
1747 .create_buffer_for_peer(
1748 &buffer,
1749 envelope.original_sender_id.unwrap_or(envelope.sender_id),
1750 cx,
1751 )
1752 .detach_and_log_err(cx);
1753 })
1754 })?;
1755
1756 Ok(proto::OpenBufferResponse {
1757 buffer_id: buffer_id.to_proto(),
1758 })
1759 }
1760
1761 async fn handle_askpass(
1762 this: Entity<Self>,
1763 envelope: TypedEnvelope<proto::AskPassRequest>,
1764 mut cx: AsyncApp,
1765 ) -> Result<proto::AskPassResponse> {
1766 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1767 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1768 let repository =
1769 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1770
1771 let delegates = cx.update(|cx| repository.read(cx).askpass_delegates.clone())?;
1772 let Some(mut askpass) = delegates.lock().remove(&envelope.payload.askpass_id) else {
1773 debug_panic!("no askpass found");
1774 return Err(anyhow::anyhow!("no askpass found"));
1775 };
1776
1777 let response = askpass.ask_password(envelope.payload.prompt).await?;
1778
1779 delegates
1780 .lock()
1781 .insert(envelope.payload.askpass_id, askpass);
1782
1783 Ok(proto::AskPassResponse { response })
1784 }
1785
1786 async fn handle_check_for_pushed_commits(
1787 this: Entity<Self>,
1788 envelope: TypedEnvelope<proto::CheckForPushedCommits>,
1789 mut cx: AsyncApp,
1790 ) -> Result<proto::CheckForPushedCommitsResponse> {
1791 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1792 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1793 let repository_handle =
1794 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1795
1796 let branches = repository_handle
1797 .update(&mut cx, |repository_handle, _| {
1798 repository_handle.check_for_pushed_commits()
1799 })?
1800 .await??;
1801 Ok(proto::CheckForPushedCommitsResponse {
1802 pushed_to: branches
1803 .into_iter()
1804 .map(|commit| commit.to_string())
1805 .collect(),
1806 })
1807 }
1808
1809 async fn handle_git_diff(
1810 this: Entity<Self>,
1811 envelope: TypedEnvelope<proto::GitDiff>,
1812 mut cx: AsyncApp,
1813 ) -> Result<proto::GitDiffResponse> {
1814 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1815 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1816 let repository_handle =
1817 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1818 let diff_type = match envelope.payload.diff_type() {
1819 proto::git_diff::DiffType::HeadToIndex => DiffType::HeadToIndex,
1820 proto::git_diff::DiffType::HeadToWorktree => DiffType::HeadToWorktree,
1821 };
1822
1823 let mut diff = repository_handle
1824 .update(&mut cx, |repository_handle, cx| {
1825 repository_handle.diff(diff_type, cx)
1826 })?
1827 .await??;
1828 const ONE_MB: usize = 1_000_000;
1829 if diff.len() > ONE_MB {
1830 diff = diff.chars().take(ONE_MB).collect()
1831 }
1832
1833 Ok(proto::GitDiffResponse { diff })
1834 }
1835
1836 async fn handle_open_unstaged_diff(
1837 this: Entity<Self>,
1838 request: TypedEnvelope<proto::OpenUnstagedDiff>,
1839 mut cx: AsyncApp,
1840 ) -> Result<proto::OpenUnstagedDiffResponse> {
1841 let buffer_id = BufferId::new(request.payload.buffer_id)?;
1842 let diff = this
1843 .update(&mut cx, |this, cx| {
1844 let buffer = this.buffer_store.read(cx).get(buffer_id)?;
1845 Some(this.open_unstaged_diff(buffer, cx))
1846 })?
1847 .ok_or_else(|| anyhow!("no such buffer"))?
1848 .await?;
1849 this.update(&mut cx, |this, _| {
1850 let shared_diffs = this
1851 .shared_diffs
1852 .entry(request.original_sender_id.unwrap_or(request.sender_id))
1853 .or_default();
1854 shared_diffs.entry(buffer_id).or_default().unstaged = Some(diff.clone());
1855 })?;
1856 let staged_text = diff.read_with(&cx, |diff, _| diff.base_text_string())?;
1857 Ok(proto::OpenUnstagedDiffResponse { staged_text })
1858 }
1859
1860 async fn handle_open_uncommitted_diff(
1861 this: Entity<Self>,
1862 request: TypedEnvelope<proto::OpenUncommittedDiff>,
1863 mut cx: AsyncApp,
1864 ) -> Result<proto::OpenUncommittedDiffResponse> {
1865 let buffer_id = BufferId::new(request.payload.buffer_id)?;
1866 let diff = this
1867 .update(&mut cx, |this, cx| {
1868 let buffer = this.buffer_store.read(cx).get(buffer_id)?;
1869 Some(this.open_uncommitted_diff(buffer, cx))
1870 })?
1871 .ok_or_else(|| anyhow!("no such buffer"))?
1872 .await?;
1873 this.update(&mut cx, |this, _| {
1874 let shared_diffs = this
1875 .shared_diffs
1876 .entry(request.original_sender_id.unwrap_or(request.sender_id))
1877 .or_default();
1878 shared_diffs.entry(buffer_id).or_default().uncommitted = Some(diff.clone());
1879 })?;
1880 diff.read_with(&cx, |diff, cx| {
1881 use proto::open_uncommitted_diff_response::Mode;
1882
1883 let unstaged_diff = diff.secondary_diff();
1884 let index_snapshot = unstaged_diff.and_then(|diff| {
1885 let diff = diff.read(cx);
1886 diff.base_text_exists().then(|| diff.base_text())
1887 });
1888
1889 let mode;
1890 let staged_text;
1891 let committed_text;
1892 if diff.base_text_exists() {
1893 let committed_snapshot = diff.base_text();
1894 committed_text = Some(committed_snapshot.text());
1895 if let Some(index_text) = index_snapshot {
1896 if index_text.remote_id() == committed_snapshot.remote_id() {
1897 mode = Mode::IndexMatchesHead;
1898 staged_text = None;
1899 } else {
1900 mode = Mode::IndexAndHead;
1901 staged_text = Some(index_text.text());
1902 }
1903 } else {
1904 mode = Mode::IndexAndHead;
1905 staged_text = None;
1906 }
1907 } else {
1908 mode = Mode::IndexAndHead;
1909 committed_text = None;
1910 staged_text = index_snapshot.as_ref().map(|buffer| buffer.text());
1911 }
1912
1913 proto::OpenUncommittedDiffResponse {
1914 committed_text,
1915 staged_text,
1916 mode: mode.into(),
1917 }
1918 })
1919 }
1920
1921 async fn handle_update_diff_bases(
1922 this: Entity<Self>,
1923 request: TypedEnvelope<proto::UpdateDiffBases>,
1924 mut cx: AsyncApp,
1925 ) -> Result<()> {
1926 let buffer_id = BufferId::new(request.payload.buffer_id)?;
1927 this.update(&mut cx, |this, cx| {
1928 if let Some(diff_state) = this.diffs.get_mut(&buffer_id) {
1929 if let Some(buffer) = this.buffer_store.read(cx).get(buffer_id) {
1930 let buffer = buffer.read(cx).text_snapshot();
1931 diff_state.update(cx, |diff_state, cx| {
1932 diff_state.handle_base_texts_updated(buffer, request.payload, cx);
1933 })
1934 }
1935 }
1936 })
1937 }
1938
1939 async fn handle_blame_buffer(
1940 this: Entity<Self>,
1941 envelope: TypedEnvelope<proto::BlameBuffer>,
1942 mut cx: AsyncApp,
1943 ) -> Result<proto::BlameBufferResponse> {
1944 let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
1945 let version = deserialize_version(&envelope.payload.version);
1946 let buffer = this.read_with(&cx, |this, cx| {
1947 this.buffer_store.read(cx).get_existing(buffer_id)
1948 })??;
1949 buffer
1950 .update(&mut cx, |buffer, _| {
1951 buffer.wait_for_version(version.clone())
1952 })?
1953 .await?;
1954 let blame = this
1955 .update(&mut cx, |this, cx| {
1956 this.blame_buffer(&buffer, Some(version), cx)
1957 })?
1958 .await?;
1959 Ok(serialize_blame_buffer_response(blame))
1960 }
1961
1962 async fn handle_get_permalink_to_line(
1963 this: Entity<Self>,
1964 envelope: TypedEnvelope<proto::GetPermalinkToLine>,
1965 mut cx: AsyncApp,
1966 ) -> Result<proto::GetPermalinkToLineResponse> {
1967 let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
1968 // let version = deserialize_version(&envelope.payload.version);
1969 let selection = {
1970 let proto_selection = envelope
1971 .payload
1972 .selection
1973 .context("no selection to get permalink for defined")?;
1974 proto_selection.start as u32..proto_selection.end as u32
1975 };
1976 let buffer = this.read_with(&cx, |this, cx| {
1977 this.buffer_store.read(cx).get_existing(buffer_id)
1978 })??;
1979 let permalink = this
1980 .update(&mut cx, |this, cx| {
1981 this.get_permalink_to_line(&buffer, selection, cx)
1982 })?
1983 .await?;
1984 Ok(proto::GetPermalinkToLineResponse {
1985 permalink: permalink.to_string(),
1986 })
1987 }
1988
1989 fn repository_for_request(
1990 this: &Entity<Self>,
1991 worktree_id: WorktreeId,
1992 work_directory_id: ProjectEntryId,
1993 cx: &mut AsyncApp,
1994 ) -> Result<Entity<Repository>> {
1995 this.update(cx, |this, cx| {
1996 this.repositories
1997 .values()
1998 .find(|repository_handle| {
1999 repository_handle.read(cx).worktree_id == worktree_id
2000 && repository_handle
2001 .read(cx)
2002 .repository_entry
2003 .work_directory_id()
2004 == work_directory_id
2005 })
2006 .context("missing repository handle")
2007 .cloned()
2008 })?
2009 }
2010}
2011
2012impl BufferDiffState {
2013 fn buffer_language_changed(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) {
2014 self.language = buffer.read(cx).language().cloned();
2015 self.language_changed = true;
2016 let _ = self.recalculate_diffs(buffer.read(cx).text_snapshot(), cx);
2017 }
2018
2019 fn unstaged_diff(&self) -> Option<Entity<BufferDiff>> {
2020 self.unstaged_diff.as_ref().and_then(|set| set.upgrade())
2021 }
2022
2023 fn uncommitted_diff(&self) -> Option<Entity<BufferDiff>> {
2024 self.uncommitted_diff.as_ref().and_then(|set| set.upgrade())
2025 }
2026
2027 fn handle_base_texts_updated(
2028 &mut self,
2029 buffer: text::BufferSnapshot,
2030 message: proto::UpdateDiffBases,
2031 cx: &mut Context<Self>,
2032 ) {
2033 use proto::update_diff_bases::Mode;
2034
2035 let Some(mode) = Mode::from_i32(message.mode) else {
2036 return;
2037 };
2038
2039 let diff_bases_change = match mode {
2040 Mode::HeadOnly => DiffBasesChange::SetHead(message.committed_text),
2041 Mode::IndexOnly => DiffBasesChange::SetIndex(message.staged_text),
2042 Mode::IndexMatchesHead => DiffBasesChange::SetBoth(message.committed_text),
2043 Mode::IndexAndHead => DiffBasesChange::SetEach {
2044 index: message.staged_text,
2045 head: message.committed_text,
2046 },
2047 };
2048
2049 let _ = self.diff_bases_changed(buffer, diff_bases_change, cx);
2050 }
2051
2052 pub fn wait_for_recalculation(&mut self) -> Option<oneshot::Receiver<()>> {
2053 if self.diff_updated_futures.is_empty() {
2054 return None;
2055 }
2056 let (tx, rx) = oneshot::channel();
2057 self.diff_updated_futures.push(tx);
2058 Some(rx)
2059 }
2060
2061 fn diff_bases_changed(
2062 &mut self,
2063 buffer: text::BufferSnapshot,
2064 diff_bases_change: DiffBasesChange,
2065 cx: &mut Context<Self>,
2066 ) -> oneshot::Receiver<()> {
2067 match diff_bases_change {
2068 DiffBasesChange::SetIndex(index) => {
2069 self.index_text = index.map(|mut index| {
2070 text::LineEnding::normalize(&mut index);
2071 Arc::new(index)
2072 });
2073 self.index_changed = true;
2074 }
2075 DiffBasesChange::SetHead(head) => {
2076 self.head_text = head.map(|mut head| {
2077 text::LineEnding::normalize(&mut head);
2078 Arc::new(head)
2079 });
2080 self.head_changed = true;
2081 }
2082 DiffBasesChange::SetBoth(text) => {
2083 let text = text.map(|mut text| {
2084 text::LineEnding::normalize(&mut text);
2085 Arc::new(text)
2086 });
2087 self.head_text = text.clone();
2088 self.index_text = text;
2089 self.head_changed = true;
2090 self.index_changed = true;
2091 }
2092 DiffBasesChange::SetEach { index, head } => {
2093 self.index_text = index.map(|mut index| {
2094 text::LineEnding::normalize(&mut index);
2095 Arc::new(index)
2096 });
2097 self.index_changed = true;
2098 self.head_text = head.map(|mut head| {
2099 text::LineEnding::normalize(&mut head);
2100 Arc::new(head)
2101 });
2102 self.head_changed = true;
2103 }
2104 }
2105
2106 self.recalculate_diffs(buffer, cx)
2107 }
2108
2109 fn recalculate_diffs(
2110 &mut self,
2111 buffer: text::BufferSnapshot,
2112 cx: &mut Context<Self>,
2113 ) -> oneshot::Receiver<()> {
2114 log::debug!("recalculate diffs");
2115 let (tx, rx) = oneshot::channel();
2116 self.diff_updated_futures.push(tx);
2117
2118 let language = self.language.clone();
2119 let language_registry = self.language_registry.clone();
2120 let unstaged_diff = self.unstaged_diff();
2121 let uncommitted_diff = self.uncommitted_diff();
2122 let head = self.head_text.clone();
2123 let index = self.index_text.clone();
2124 let index_changed = self.index_changed;
2125 let head_changed = self.head_changed;
2126 let language_changed = self.language_changed;
2127 let index_matches_head = match (self.index_text.as_ref(), self.head_text.as_ref()) {
2128 (Some(index), Some(head)) => Arc::ptr_eq(index, head),
2129 (None, None) => true,
2130 _ => false,
2131 };
2132 self.recalculate_diff_task = Some(cx.spawn(async move |this, cx| {
2133 let mut new_unstaged_diff = None;
2134 if let Some(unstaged_diff) = &unstaged_diff {
2135 new_unstaged_diff = Some(
2136 BufferDiff::update_diff(
2137 unstaged_diff.clone(),
2138 buffer.clone(),
2139 index,
2140 index_changed,
2141 language_changed,
2142 language.clone(),
2143 language_registry.clone(),
2144 cx,
2145 )
2146 .await?,
2147 );
2148 }
2149
2150 let mut new_uncommitted_diff = None;
2151 if let Some(uncommitted_diff) = &uncommitted_diff {
2152 new_uncommitted_diff = if index_matches_head {
2153 new_unstaged_diff.clone()
2154 } else {
2155 Some(
2156 BufferDiff::update_diff(
2157 uncommitted_diff.clone(),
2158 buffer.clone(),
2159 head,
2160 head_changed,
2161 language_changed,
2162 language.clone(),
2163 language_registry.clone(),
2164 cx,
2165 )
2166 .await?,
2167 )
2168 }
2169 }
2170
2171 let unstaged_changed_range = if let Some((unstaged_diff, new_unstaged_diff)) =
2172 unstaged_diff.as_ref().zip(new_unstaged_diff.clone())
2173 {
2174 unstaged_diff.update(cx, |diff, cx| {
2175 diff.set_snapshot(&buffer, new_unstaged_diff, language_changed, None, cx)
2176 })?
2177 } else {
2178 None
2179 };
2180
2181 if let Some((uncommitted_diff, new_uncommitted_diff)) =
2182 uncommitted_diff.as_ref().zip(new_uncommitted_diff.clone())
2183 {
2184 uncommitted_diff.update(cx, |uncommitted_diff, cx| {
2185 uncommitted_diff.set_snapshot(
2186 &buffer,
2187 new_uncommitted_diff,
2188 language_changed,
2189 unstaged_changed_range,
2190 cx,
2191 );
2192 })?;
2193 }
2194
2195 if let Some(this) = this.upgrade() {
2196 this.update(cx, |this, _| {
2197 this.index_changed = false;
2198 this.head_changed = false;
2199 this.language_changed = false;
2200 for tx in this.diff_updated_futures.drain(..) {
2201 tx.send(()).ok();
2202 }
2203 })?;
2204 }
2205
2206 Ok(())
2207 }));
2208
2209 rx
2210 }
2211}
2212
2213fn make_remote_delegate(
2214 this: Entity<GitStore>,
2215 project_id: u64,
2216 worktree_id: WorktreeId,
2217 work_directory_id: ProjectEntryId,
2218 askpass_id: u64,
2219 cx: &mut AsyncApp,
2220) -> AskPassDelegate {
2221 AskPassDelegate::new(cx, move |prompt, tx, cx| {
2222 this.update(cx, |this, cx| {
2223 let Some((client, _)) = this.downstream_client() else {
2224 return;
2225 };
2226 let response = client.request(proto::AskPassRequest {
2227 project_id,
2228 worktree_id: worktree_id.to_proto(),
2229 work_directory_id: work_directory_id.to_proto(),
2230 askpass_id,
2231 prompt,
2232 });
2233 cx.spawn(async move |_, _| {
2234 tx.send(response.await?.response).ok();
2235 anyhow::Ok(())
2236 })
2237 .detach_and_log_err(cx);
2238 })
2239 .log_err();
2240 })
2241}
2242
2243impl GitStoreState {
2244 fn load_staged_text(
2245 &self,
2246 buffer: &Entity<Buffer>,
2247 buffer_store: &Entity<BufferStore>,
2248 cx: &App,
2249 ) -> Task<Result<Option<String>>> {
2250 match self {
2251 GitStoreState::Local { .. } => {
2252 if let Some((worktree, path)) =
2253 buffer_store.read(cx).worktree_for_buffer(buffer, cx)
2254 {
2255 worktree.read(cx).load_staged_file(path.as_ref(), cx)
2256 } else {
2257 return Task::ready(Err(anyhow!("no such worktree")));
2258 }
2259 }
2260 GitStoreState::Ssh {
2261 upstream_client,
2262 upstream_project_id: project_id,
2263 ..
2264 }
2265 | GitStoreState::Remote {
2266 upstream_client,
2267 project_id,
2268 } => {
2269 let buffer_id = buffer.read(cx).remote_id();
2270 let project_id = *project_id;
2271 let client = upstream_client.clone();
2272 cx.background_spawn(async move {
2273 let response = client
2274 .request(proto::OpenUnstagedDiff {
2275 project_id: project_id.to_proto(),
2276 buffer_id: buffer_id.to_proto(),
2277 })
2278 .await?;
2279 Ok(response.staged_text)
2280 })
2281 }
2282 }
2283 }
2284
2285 fn load_committed_text(
2286 &self,
2287 buffer: &Entity<Buffer>,
2288 buffer_store: &Entity<BufferStore>,
2289 cx: &App,
2290 ) -> Task<Result<DiffBasesChange>> {
2291 match self {
2292 GitStoreState::Local { .. } => {
2293 if let Some((worktree, path)) =
2294 buffer_store.read(cx).worktree_for_buffer(buffer, cx)
2295 {
2296 let worktree = worktree.read(cx);
2297 let committed_text = worktree.load_committed_file(&path, cx);
2298 let staged_text = worktree.load_staged_file(&path, cx);
2299 cx.background_spawn(async move {
2300 let committed_text = committed_text.await?;
2301 let staged_text = staged_text.await?;
2302 let diff_bases_change = if committed_text == staged_text {
2303 DiffBasesChange::SetBoth(committed_text)
2304 } else {
2305 DiffBasesChange::SetEach {
2306 index: staged_text,
2307 head: committed_text,
2308 }
2309 };
2310 Ok(diff_bases_change)
2311 })
2312 } else {
2313 Task::ready(Err(anyhow!("no such worktree")))
2314 }
2315 }
2316 GitStoreState::Ssh {
2317 upstream_client,
2318 upstream_project_id: project_id,
2319 ..
2320 }
2321 | GitStoreState::Remote {
2322 upstream_client,
2323 project_id,
2324 } => {
2325 use proto::open_uncommitted_diff_response::Mode;
2326
2327 let buffer_id = buffer.read(cx).remote_id();
2328 let project_id = *project_id;
2329 let client = upstream_client.clone();
2330 cx.background_spawn(async move {
2331 let response = client
2332 .request(proto::OpenUncommittedDiff {
2333 project_id: project_id.to_proto(),
2334 buffer_id: buffer_id.to_proto(),
2335 })
2336 .await?;
2337 let mode =
2338 Mode::from_i32(response.mode).ok_or_else(|| anyhow!("Invalid mode"))?;
2339 let bases = match mode {
2340 Mode::IndexMatchesHead => DiffBasesChange::SetBoth(response.committed_text),
2341 Mode::IndexAndHead => DiffBasesChange::SetEach {
2342 head: response.committed_text,
2343 index: response.staged_text,
2344 },
2345 };
2346 Ok(bases)
2347 })
2348 }
2349 }
2350 }
2351}
2352
2353impl Repository {
2354 pub fn git_store(&self) -> Option<Entity<GitStore>> {
2355 self.git_store.upgrade()
2356 }
2357
2358 fn id(&self) -> (WorktreeId, ProjectEntryId) {
2359 (self.worktree_id, self.repository_entry.work_directory_id())
2360 }
2361
2362 pub fn current_branch(&self) -> Option<&Branch> {
2363 self.repository_entry.branch()
2364 }
2365
2366 pub fn status_for_path(&self, path: &RepoPath) -> Option<StatusEntry> {
2367 self.repository_entry.status_for_path(path)
2368 }
2369
2370 fn send_job<F, Fut, R>(&self, job: F) -> oneshot::Receiver<R>
2371 where
2372 F: FnOnce(RepositoryState, AsyncApp) -> Fut + 'static,
2373 Fut: Future<Output = R> + 'static,
2374 R: Send + 'static,
2375 {
2376 self.send_keyed_job(None, job)
2377 }
2378
2379 fn send_keyed_job<F, Fut, R>(&self, key: Option<GitJobKey>, job: F) -> oneshot::Receiver<R>
2380 where
2381 F: FnOnce(RepositoryState, AsyncApp) -> Fut + 'static,
2382 Fut: Future<Output = R> + 'static,
2383 R: Send + 'static,
2384 {
2385 let (result_tx, result_rx) = futures::channel::oneshot::channel();
2386 let git_repo = self.git_repo.clone();
2387 self.job_sender
2388 .unbounded_send(GitJob {
2389 key,
2390 job: Box::new(|cx: &mut AsyncApp| {
2391 let job = job(git_repo, cx.clone());
2392 cx.spawn(async move |_| {
2393 let result = job.await;
2394 result_tx.send(result).ok();
2395 })
2396 }),
2397 })
2398 .ok();
2399 result_rx
2400 }
2401
2402 pub fn display_name(&self, project: &Project, cx: &App) -> SharedString {
2403 maybe!({
2404 let project_path = self.repo_path_to_project_path(&"".into())?;
2405 let worktree_name = project
2406 .worktree_for_id(project_path.worktree_id, cx)?
2407 .read(cx)
2408 .root_name();
2409
2410 let mut path = PathBuf::new();
2411 path = path.join(worktree_name);
2412 if project_path.path.components().count() > 0 {
2413 path = path.join(project_path.path);
2414 }
2415 Some(path.to_string_lossy().to_string())
2416 })
2417 .unwrap_or_else(|| self.repository_entry.work_directory.display_name())
2418 .into()
2419 }
2420
2421 pub fn set_as_active_repository(&self, cx: &mut Context<Self>) {
2422 let Some(git_store) = self.git_store.upgrade() else {
2423 return;
2424 };
2425 let entity = cx.entity();
2426 git_store.update(cx, |git_store, cx| {
2427 let Some((&id, _)) = git_store
2428 .repositories
2429 .iter()
2430 .find(|(_, handle)| *handle == &entity)
2431 else {
2432 return;
2433 };
2434 git_store.active_repo_id = Some(id);
2435 cx.emit(GitEvent::ActiveRepositoryChanged);
2436 });
2437 }
2438
2439 pub fn status(&self) -> impl '_ + Iterator<Item = StatusEntry> {
2440 self.repository_entry.status()
2441 }
2442
2443 pub fn has_conflict(&self, path: &RepoPath) -> bool {
2444 self.repository_entry
2445 .current_merge_conflicts
2446 .contains(&path)
2447 }
2448
2449 pub fn repo_path_to_project_path(&self, path: &RepoPath) -> Option<ProjectPath> {
2450 let path = self.repository_entry.try_unrelativize(path)?;
2451 Some((self.worktree_id, path).into())
2452 }
2453
2454 pub fn project_path_to_repo_path(&self, path: &ProjectPath) -> Option<RepoPath> {
2455 self.worktree_id_path_to_repo_path(path.worktree_id, &path.path)
2456 }
2457
2458 // note: callers must verify these come from the same worktree
2459 pub fn contains_sub_repo(&self, other: &Entity<Self>, cx: &App) -> bool {
2460 let other_work_dir = &other.read(cx).repository_entry.work_directory;
2461 match (&self.repository_entry.work_directory, other_work_dir) {
2462 (WorkDirectory::InProject { .. }, WorkDirectory::AboveProject { .. }) => false,
2463 (WorkDirectory::AboveProject { .. }, WorkDirectory::InProject { .. }) => true,
2464 (
2465 WorkDirectory::InProject {
2466 relative_path: this_path,
2467 },
2468 WorkDirectory::InProject {
2469 relative_path: other_path,
2470 },
2471 ) => other_path.starts_with(this_path),
2472 (
2473 WorkDirectory::AboveProject {
2474 absolute_path: this_path,
2475 ..
2476 },
2477 WorkDirectory::AboveProject {
2478 absolute_path: other_path,
2479 ..
2480 },
2481 ) => other_path.starts_with(this_path),
2482 }
2483 }
2484
2485 pub fn worktree_id_path_to_repo_path(
2486 &self,
2487 worktree_id: WorktreeId,
2488 path: &Path,
2489 ) -> Option<RepoPath> {
2490 if worktree_id != self.worktree_id {
2491 return None;
2492 }
2493 self.repository_entry.relativize(path).log_err()
2494 }
2495
2496 pub fn local_repository(&self) -> Option<Arc<dyn GitRepository>> {
2497 match &self.git_repo {
2498 RepositoryState::Local(git_repository) => Some(git_repository.clone()),
2499 RepositoryState::Remote { .. } => None,
2500 }
2501 }
2502
2503 pub fn open_commit_buffer(
2504 &mut self,
2505 languages: Option<Arc<LanguageRegistry>>,
2506 buffer_store: Entity<BufferStore>,
2507 cx: &mut Context<Self>,
2508 ) -> Task<Result<Entity<Buffer>>> {
2509 if let Some(buffer) = self.commit_message_buffer.clone() {
2510 return Task::ready(Ok(buffer));
2511 }
2512
2513 if let RepositoryState::Remote {
2514 project_id,
2515 client,
2516 worktree_id,
2517 work_directory_id,
2518 } = self.git_repo.clone()
2519 {
2520 let client = client.clone();
2521 cx.spawn(async move |repository, cx| {
2522 let request = client.request(proto::OpenCommitMessageBuffer {
2523 project_id: project_id.0,
2524 worktree_id: worktree_id.to_proto(),
2525 work_directory_id: work_directory_id.to_proto(),
2526 });
2527 let response = request.await.context("requesting to open commit buffer")?;
2528 let buffer_id = BufferId::new(response.buffer_id)?;
2529 let buffer = buffer_store
2530 .update(cx, |buffer_store, cx| {
2531 buffer_store.wait_for_remote_buffer(buffer_id, cx)
2532 })?
2533 .await?;
2534 if let Some(language_registry) = languages {
2535 let git_commit_language =
2536 language_registry.language_for_name("Git Commit").await?;
2537 buffer.update(cx, |buffer, cx| {
2538 buffer.set_language(Some(git_commit_language), cx);
2539 })?;
2540 }
2541 repository.update(cx, |repository, _| {
2542 repository.commit_message_buffer = Some(buffer.clone());
2543 })?;
2544 Ok(buffer)
2545 })
2546 } else {
2547 self.open_local_commit_buffer(languages, buffer_store, cx)
2548 }
2549 }
2550
2551 fn open_local_commit_buffer(
2552 &mut self,
2553 language_registry: Option<Arc<LanguageRegistry>>,
2554 buffer_store: Entity<BufferStore>,
2555 cx: &mut Context<Self>,
2556 ) -> Task<Result<Entity<Buffer>>> {
2557 cx.spawn(async move |repository, cx| {
2558 let buffer = buffer_store
2559 .update(cx, |buffer_store, cx| buffer_store.create_buffer(cx))?
2560 .await?;
2561
2562 if let Some(language_registry) = language_registry {
2563 let git_commit_language = language_registry.language_for_name("Git Commit").await?;
2564 buffer.update(cx, |buffer, cx| {
2565 buffer.set_language(Some(git_commit_language), cx);
2566 })?;
2567 }
2568
2569 repository.update(cx, |repository, _| {
2570 repository.commit_message_buffer = Some(buffer.clone());
2571 })?;
2572 Ok(buffer)
2573 })
2574 }
2575
2576 pub fn checkout_files(
2577 &self,
2578 commit: &str,
2579 paths: Vec<RepoPath>,
2580 cx: &mut App,
2581 ) -> oneshot::Receiver<Result<()>> {
2582 let commit = commit.to_string();
2583 let env = self.worktree_environment(cx);
2584
2585 self.send_job(|git_repo, _| async move {
2586 match git_repo {
2587 RepositoryState::Local(repo) => repo.checkout_files(commit, paths, env.await).await,
2588 RepositoryState::Remote {
2589 project_id,
2590 client,
2591 worktree_id,
2592 work_directory_id,
2593 } => {
2594 client
2595 .request(proto::GitCheckoutFiles {
2596 project_id: project_id.0,
2597 worktree_id: worktree_id.to_proto(),
2598 work_directory_id: work_directory_id.to_proto(),
2599 commit,
2600 paths: paths
2601 .into_iter()
2602 .map(|p| p.to_string_lossy().to_string())
2603 .collect(),
2604 })
2605 .await?;
2606
2607 Ok(())
2608 }
2609 }
2610 })
2611 }
2612
2613 pub fn reset(
2614 &self,
2615 commit: String,
2616 reset_mode: ResetMode,
2617 cx: &mut App,
2618 ) -> oneshot::Receiver<Result<()>> {
2619 let commit = commit.to_string();
2620 let env = self.worktree_environment(cx);
2621 self.send_job(|git_repo, _| async move {
2622 match git_repo {
2623 RepositoryState::Local(git_repo) => {
2624 let env = env.await;
2625 git_repo.reset(commit, reset_mode, env).await
2626 }
2627 RepositoryState::Remote {
2628 project_id,
2629 client,
2630 worktree_id,
2631 work_directory_id,
2632 } => {
2633 client
2634 .request(proto::GitReset {
2635 project_id: project_id.0,
2636 worktree_id: worktree_id.to_proto(),
2637 work_directory_id: work_directory_id.to_proto(),
2638 commit,
2639 mode: match reset_mode {
2640 ResetMode::Soft => git_reset::ResetMode::Soft.into(),
2641 ResetMode::Mixed => git_reset::ResetMode::Mixed.into(),
2642 },
2643 })
2644 .await?;
2645
2646 Ok(())
2647 }
2648 }
2649 })
2650 }
2651
2652 pub fn show(&self, commit: String) -> oneshot::Receiver<Result<CommitDetails>> {
2653 self.send_job(|git_repo, cx| async move {
2654 match git_repo {
2655 RepositoryState::Local(git_repository) => git_repository.show(commit, cx).await,
2656 RepositoryState::Remote {
2657 project_id,
2658 client,
2659 worktree_id,
2660 work_directory_id,
2661 } => {
2662 let resp = client
2663 .request(proto::GitShow {
2664 project_id: project_id.0,
2665 worktree_id: worktree_id.to_proto(),
2666 work_directory_id: work_directory_id.to_proto(),
2667 commit,
2668 })
2669 .await?;
2670
2671 Ok(CommitDetails {
2672 sha: resp.sha.into(),
2673 message: resp.message.into(),
2674 commit_timestamp: resp.commit_timestamp,
2675 committer_email: resp.committer_email.into(),
2676 committer_name: resp.committer_name.into(),
2677 })
2678 }
2679 }
2680 })
2681 }
2682
2683 fn buffer_store(&self, cx: &App) -> Option<Entity<BufferStore>> {
2684 Some(self.git_store.upgrade()?.read(cx).buffer_store.clone())
2685 }
2686
2687 pub fn stage_entries(
2688 &self,
2689 entries: Vec<RepoPath>,
2690 cx: &mut Context<Self>,
2691 ) -> Task<anyhow::Result<()>> {
2692 if entries.is_empty() {
2693 return Task::ready(Ok(()));
2694 }
2695 let env = self.worktree_environment(cx);
2696
2697 let mut save_futures = Vec::new();
2698 if let Some(buffer_store) = self.buffer_store(cx) {
2699 buffer_store.update(cx, |buffer_store, cx| {
2700 for path in &entries {
2701 let Some(path) = self.repository_entry.try_unrelativize(path) else {
2702 continue;
2703 };
2704 let project_path = (self.worktree_id, path).into();
2705 if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
2706 if buffer
2707 .read(cx)
2708 .file()
2709 .map_or(false, |file| file.disk_state().exists())
2710 {
2711 save_futures.push(buffer_store.save_buffer(buffer, cx));
2712 }
2713 }
2714 }
2715 })
2716 }
2717
2718 cx.spawn(async move |this, cx| {
2719 for save_future in save_futures {
2720 save_future.await?;
2721 }
2722 let env = env.await;
2723
2724 this.update(cx, |this, _| {
2725 this.send_job(|git_repo, cx| async move {
2726 match git_repo {
2727 RepositoryState::Local(repo) => repo.stage_paths(entries, env, cx).await,
2728 RepositoryState::Remote {
2729 project_id,
2730 client,
2731 worktree_id,
2732 work_directory_id,
2733 } => {
2734 client
2735 .request(proto::Stage {
2736 project_id: project_id.0,
2737 worktree_id: worktree_id.to_proto(),
2738 work_directory_id: work_directory_id.to_proto(),
2739 paths: entries
2740 .into_iter()
2741 .map(|repo_path| repo_path.as_ref().to_proto())
2742 .collect(),
2743 })
2744 .await
2745 .context("sending stage request")?;
2746
2747 Ok(())
2748 }
2749 }
2750 })
2751 })?
2752 .await??;
2753
2754 Ok(())
2755 })
2756 }
2757
2758 pub fn unstage_entries(
2759 &self,
2760 entries: Vec<RepoPath>,
2761 cx: &mut Context<Self>,
2762 ) -> Task<anyhow::Result<()>> {
2763 if entries.is_empty() {
2764 return Task::ready(Ok(()));
2765 }
2766 let env = self.worktree_environment(cx);
2767
2768 let mut save_futures = Vec::new();
2769 if let Some(buffer_store) = self.buffer_store(cx) {
2770 buffer_store.update(cx, |buffer_store, cx| {
2771 for path in &entries {
2772 let Some(path) = self.repository_entry.try_unrelativize(path) else {
2773 continue;
2774 };
2775 let project_path = (self.worktree_id, path).into();
2776 if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
2777 if buffer
2778 .read(cx)
2779 .file()
2780 .map_or(false, |file| file.disk_state().exists())
2781 {
2782 save_futures.push(buffer_store.save_buffer(buffer, cx));
2783 }
2784 }
2785 }
2786 })
2787 }
2788
2789 cx.spawn(async move |this, cx| {
2790 for save_future in save_futures {
2791 save_future.await?;
2792 }
2793 let env = env.await;
2794
2795 this.update(cx, |this, _| {
2796 this.send_job(|git_repo, cx| async move {
2797 match git_repo {
2798 RepositoryState::Local(repo) => repo.unstage_paths(entries, env, cx).await,
2799 RepositoryState::Remote {
2800 project_id,
2801 client,
2802 worktree_id,
2803 work_directory_id,
2804 } => {
2805 client
2806 .request(proto::Unstage {
2807 project_id: project_id.0,
2808 worktree_id: worktree_id.to_proto(),
2809 work_directory_id: work_directory_id.to_proto(),
2810 paths: entries
2811 .into_iter()
2812 .map(|repo_path| repo_path.as_ref().to_proto())
2813 .collect(),
2814 })
2815 .await
2816 .context("sending unstage request")?;
2817
2818 Ok(())
2819 }
2820 }
2821 })
2822 })?
2823 .await??;
2824
2825 Ok(())
2826 })
2827 }
2828
2829 pub fn stage_all(&self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
2830 let to_stage = self
2831 .repository_entry
2832 .status()
2833 .filter(|entry| !entry.status.staging().is_fully_staged())
2834 .map(|entry| entry.repo_path.clone())
2835 .collect();
2836 self.stage_entries(to_stage, cx)
2837 }
2838
2839 pub fn unstage_all(&self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
2840 let to_unstage = self
2841 .repository_entry
2842 .status()
2843 .filter(|entry| entry.status.staging().has_staged())
2844 .map(|entry| entry.repo_path.clone())
2845 .collect();
2846 self.unstage_entries(to_unstage, cx)
2847 }
2848
2849 /// Get a count of all entries in the active repository, including
2850 /// untracked files.
2851 pub fn entry_count(&self) -> usize {
2852 self.repository_entry.status_len()
2853 }
2854
2855 fn worktree_environment(
2856 &self,
2857 cx: &mut App,
2858 ) -> impl Future<Output = HashMap<String, String>> + 'static {
2859 let task = self.project_environment.as_ref().and_then(|env| {
2860 env.update(cx, |env, cx| {
2861 env.get_environment(
2862 Some(self.worktree_id),
2863 Some(self.worktree_abs_path.clone()),
2864 cx,
2865 )
2866 })
2867 .ok()
2868 });
2869 async move { OptionFuture::from(task).await.flatten().unwrap_or_default() }
2870 }
2871
2872 pub fn commit(
2873 &self,
2874 message: SharedString,
2875 name_and_email: Option<(SharedString, SharedString)>,
2876 cx: &mut App,
2877 ) -> oneshot::Receiver<Result<()>> {
2878 let env = self.worktree_environment(cx);
2879 self.send_job(|git_repo, cx| async move {
2880 match git_repo {
2881 RepositoryState::Local(repo) => {
2882 let env = env.await;
2883 repo.commit(message, name_and_email, env, cx).await
2884 }
2885 RepositoryState::Remote {
2886 project_id,
2887 client,
2888 worktree_id,
2889 work_directory_id,
2890 } => {
2891 let (name, email) = name_and_email.unzip();
2892 client
2893 .request(proto::Commit {
2894 project_id: project_id.0,
2895 worktree_id: worktree_id.to_proto(),
2896 work_directory_id: work_directory_id.to_proto(),
2897 message: String::from(message),
2898 name: name.map(String::from),
2899 email: email.map(String::from),
2900 })
2901 .await
2902 .context("sending commit request")?;
2903
2904 Ok(())
2905 }
2906 }
2907 })
2908 }
2909
2910 pub fn fetch(
2911 &mut self,
2912 askpass: AskPassDelegate,
2913 cx: &mut App,
2914 ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
2915 let executor = cx.background_executor().clone();
2916 let askpass_delegates = self.askpass_delegates.clone();
2917 let askpass_id = util::post_inc(&mut self.latest_askpass_id);
2918 let env = self.worktree_environment(cx);
2919
2920 self.send_job(move |git_repo, cx| async move {
2921 match git_repo {
2922 RepositoryState::Local(git_repository) => {
2923 let askpass = AskPassSession::new(&executor, askpass).await?;
2924 let env = env.await;
2925 git_repository.fetch(askpass, env, cx).await
2926 }
2927 RepositoryState::Remote {
2928 project_id,
2929 client,
2930 worktree_id,
2931 work_directory_id,
2932 } => {
2933 askpass_delegates.lock().insert(askpass_id, askpass);
2934 let _defer = util::defer(|| {
2935 let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
2936 debug_assert!(askpass_delegate.is_some());
2937 });
2938
2939 let response = client
2940 .request(proto::Fetch {
2941 project_id: project_id.0,
2942 worktree_id: worktree_id.to_proto(),
2943 work_directory_id: work_directory_id.to_proto(),
2944 askpass_id,
2945 })
2946 .await
2947 .context("sending fetch request")?;
2948
2949 Ok(RemoteCommandOutput {
2950 stdout: response.stdout,
2951 stderr: response.stderr,
2952 })
2953 }
2954 }
2955 })
2956 }
2957
2958 pub fn push(
2959 &mut self,
2960 branch: SharedString,
2961 remote: SharedString,
2962 options: Option<PushOptions>,
2963 askpass: AskPassDelegate,
2964 cx: &mut App,
2965 ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
2966 let executor = cx.background_executor().clone();
2967 let askpass_delegates = self.askpass_delegates.clone();
2968 let askpass_id = util::post_inc(&mut self.latest_askpass_id);
2969 let env = self.worktree_environment(cx);
2970
2971 self.send_job(move |git_repo, cx| async move {
2972 match git_repo {
2973 RepositoryState::Local(git_repository) => {
2974 let env = env.await;
2975 let askpass = AskPassSession::new(&executor, askpass).await?;
2976 git_repository
2977 .push(
2978 branch.to_string(),
2979 remote.to_string(),
2980 options,
2981 askpass,
2982 env,
2983 cx,
2984 )
2985 .await
2986 }
2987 RepositoryState::Remote {
2988 project_id,
2989 client,
2990 worktree_id,
2991 work_directory_id,
2992 } => {
2993 askpass_delegates.lock().insert(askpass_id, askpass);
2994 let _defer = util::defer(|| {
2995 let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
2996 debug_assert!(askpass_delegate.is_some());
2997 });
2998 let response = client
2999 .request(proto::Push {
3000 project_id: project_id.0,
3001 worktree_id: worktree_id.to_proto(),
3002 work_directory_id: work_directory_id.to_proto(),
3003 askpass_id,
3004 branch_name: branch.to_string(),
3005 remote_name: remote.to_string(),
3006 options: options.map(|options| match options {
3007 PushOptions::Force => proto::push::PushOptions::Force,
3008 PushOptions::SetUpstream => proto::push::PushOptions::SetUpstream,
3009 } as i32),
3010 })
3011 .await
3012 .context("sending push request")?;
3013
3014 Ok(RemoteCommandOutput {
3015 stdout: response.stdout,
3016 stderr: response.stderr,
3017 })
3018 }
3019 }
3020 })
3021 }
3022
3023 pub fn pull(
3024 &mut self,
3025 branch: SharedString,
3026 remote: SharedString,
3027 askpass: AskPassDelegate,
3028 cx: &mut App,
3029 ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
3030 let executor = cx.background_executor().clone();
3031 let askpass_delegates = self.askpass_delegates.clone();
3032 let askpass_id = util::post_inc(&mut self.latest_askpass_id);
3033 let env = self.worktree_environment(cx);
3034
3035 self.send_job(move |git_repo, cx| async move {
3036 match git_repo {
3037 RepositoryState::Local(git_repository) => {
3038 let askpass = AskPassSession::new(&executor, askpass).await?;
3039 let env = env.await;
3040 git_repository
3041 .pull(branch.to_string(), remote.to_string(), askpass, env, cx)
3042 .await
3043 }
3044 RepositoryState::Remote {
3045 project_id,
3046 client,
3047 worktree_id,
3048 work_directory_id,
3049 } => {
3050 askpass_delegates.lock().insert(askpass_id, askpass);
3051 let _defer = util::defer(|| {
3052 let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
3053 debug_assert!(askpass_delegate.is_some());
3054 });
3055 let response = client
3056 .request(proto::Pull {
3057 project_id: project_id.0,
3058 worktree_id: worktree_id.to_proto(),
3059 work_directory_id: work_directory_id.to_proto(),
3060 askpass_id,
3061 branch_name: branch.to_string(),
3062 remote_name: remote.to_string(),
3063 })
3064 .await
3065 .context("sending pull request")?;
3066
3067 Ok(RemoteCommandOutput {
3068 stdout: response.stdout,
3069 stderr: response.stderr,
3070 })
3071 }
3072 }
3073 })
3074 }
3075
3076 fn spawn_set_index_text_job(
3077 &self,
3078 path: RepoPath,
3079 content: Option<String>,
3080 cx: &mut App,
3081 ) -> oneshot::Receiver<anyhow::Result<()>> {
3082 let env = self.worktree_environment(cx);
3083
3084 self.send_keyed_job(
3085 Some(GitJobKey::WriteIndex(path.clone())),
3086 |git_repo, cx| async {
3087 match git_repo {
3088 RepositoryState::Local(repo) => {
3089 repo.set_index_text(path, content, env.await, cx).await
3090 }
3091 RepositoryState::Remote {
3092 project_id,
3093 client,
3094 worktree_id,
3095 work_directory_id,
3096 } => {
3097 client
3098 .request(proto::SetIndexText {
3099 project_id: project_id.0,
3100 worktree_id: worktree_id.to_proto(),
3101 work_directory_id: work_directory_id.to_proto(),
3102 path: path.as_ref().to_proto(),
3103 text: content,
3104 })
3105 .await?;
3106 Ok(())
3107 }
3108 }
3109 },
3110 )
3111 }
3112
3113 pub fn get_remotes(
3114 &self,
3115 branch_name: Option<String>,
3116 ) -> oneshot::Receiver<Result<Vec<Remote>>> {
3117 self.send_job(|repo, cx| async move {
3118 match repo {
3119 RepositoryState::Local(git_repository) => {
3120 git_repository.get_remotes(branch_name, cx).await
3121 }
3122 RepositoryState::Remote {
3123 project_id,
3124 client,
3125 worktree_id,
3126 work_directory_id,
3127 } => {
3128 let response = client
3129 .request(proto::GetRemotes {
3130 project_id: project_id.0,
3131 worktree_id: worktree_id.to_proto(),
3132 work_directory_id: work_directory_id.to_proto(),
3133 branch_name,
3134 })
3135 .await?;
3136
3137 let remotes = response
3138 .remotes
3139 .into_iter()
3140 .map(|remotes| git::repository::Remote {
3141 name: remotes.name.into(),
3142 })
3143 .collect();
3144
3145 Ok(remotes)
3146 }
3147 }
3148 })
3149 }
3150
3151 pub fn branch(&self) -> Option<&Branch> {
3152 self.repository_entry.branch()
3153 }
3154
3155 pub fn branches(&self) -> oneshot::Receiver<Result<Vec<Branch>>> {
3156 self.send_job(|repo, cx| async move {
3157 match repo {
3158 RepositoryState::Local(git_repository) => {
3159 let git_repository = git_repository.clone();
3160 cx.background_spawn(async move { git_repository.branches().await })
3161 .await
3162 }
3163 RepositoryState::Remote {
3164 project_id,
3165 client,
3166 worktree_id,
3167 work_directory_id,
3168 } => {
3169 let response = client
3170 .request(proto::GitGetBranches {
3171 project_id: project_id.0,
3172 worktree_id: worktree_id.to_proto(),
3173 work_directory_id: work_directory_id.to_proto(),
3174 })
3175 .await?;
3176
3177 let branches = response
3178 .branches
3179 .into_iter()
3180 .map(|branch| worktree::proto_to_branch(&branch))
3181 .collect();
3182
3183 Ok(branches)
3184 }
3185 }
3186 })
3187 }
3188
3189 pub fn diff(&self, diff_type: DiffType, _cx: &App) -> oneshot::Receiver<Result<String>> {
3190 self.send_job(|repo, cx| async move {
3191 match repo {
3192 RepositoryState::Local(git_repository) => git_repository.diff(diff_type, cx).await,
3193 RepositoryState::Remote {
3194 project_id,
3195 client,
3196 worktree_id,
3197 work_directory_id,
3198 ..
3199 } => {
3200 let response = client
3201 .request(proto::GitDiff {
3202 project_id: project_id.0,
3203 worktree_id: worktree_id.to_proto(),
3204 work_directory_id: work_directory_id.to_proto(),
3205 diff_type: match diff_type {
3206 DiffType::HeadToIndex => {
3207 proto::git_diff::DiffType::HeadToIndex.into()
3208 }
3209 DiffType::HeadToWorktree => {
3210 proto::git_diff::DiffType::HeadToWorktree.into()
3211 }
3212 },
3213 })
3214 .await?;
3215
3216 Ok(response.diff)
3217 }
3218 }
3219 })
3220 }
3221
3222 pub fn create_branch(&self, branch_name: String) -> oneshot::Receiver<Result<()>> {
3223 self.send_job(|repo, cx| async move {
3224 match repo {
3225 RepositoryState::Local(git_repository) => {
3226 git_repository.create_branch(branch_name, cx).await
3227 }
3228 RepositoryState::Remote {
3229 project_id,
3230 client,
3231 worktree_id,
3232 work_directory_id,
3233 } => {
3234 client
3235 .request(proto::GitCreateBranch {
3236 project_id: project_id.0,
3237 worktree_id: worktree_id.to_proto(),
3238 work_directory_id: work_directory_id.to_proto(),
3239 branch_name,
3240 })
3241 .await?;
3242
3243 Ok(())
3244 }
3245 }
3246 })
3247 }
3248
3249 pub fn change_branch(&self, branch_name: String) -> oneshot::Receiver<Result<()>> {
3250 self.send_job(|repo, cx| async move {
3251 match repo {
3252 RepositoryState::Local(git_repository) => {
3253 git_repository.change_branch(branch_name, cx).await
3254 }
3255 RepositoryState::Remote {
3256 project_id,
3257 client,
3258 worktree_id,
3259 work_directory_id,
3260 } => {
3261 client
3262 .request(proto::GitChangeBranch {
3263 project_id: project_id.0,
3264 worktree_id: worktree_id.to_proto(),
3265 work_directory_id: work_directory_id.to_proto(),
3266 branch_name,
3267 })
3268 .await?;
3269
3270 Ok(())
3271 }
3272 }
3273 })
3274 }
3275
3276 pub fn check_for_pushed_commits(&self) -> oneshot::Receiver<Result<Vec<SharedString>>> {
3277 self.send_job(|repo, cx| async move {
3278 match repo {
3279 RepositoryState::Local(git_repository) => {
3280 git_repository.check_for_pushed_commit(cx).await
3281 }
3282 RepositoryState::Remote {
3283 project_id,
3284 client,
3285 worktree_id,
3286 work_directory_id,
3287 } => {
3288 let response = client
3289 .request(proto::CheckForPushedCommits {
3290 project_id: project_id.0,
3291 worktree_id: worktree_id.to_proto(),
3292 work_directory_id: work_directory_id.to_proto(),
3293 })
3294 .await?;
3295
3296 let branches = response.pushed_to.into_iter().map(Into::into).collect();
3297
3298 Ok(branches)
3299 }
3300 }
3301 })
3302 }
3303
3304 pub fn checkpoint(&self) -> oneshot::Receiver<Result<GitRepositoryCheckpoint>> {
3305 self.send_job(|repo, cx| async move {
3306 match repo {
3307 RepositoryState::Local(git_repository) => git_repository.checkpoint(cx).await,
3308 RepositoryState::Remote { .. } => Err(anyhow!("not implemented yet")),
3309 }
3310 })
3311 }
3312
3313 pub fn restore_checkpoint(
3314 &self,
3315 checkpoint: GitRepositoryCheckpoint,
3316 ) -> oneshot::Receiver<Result<()>> {
3317 self.send_job(move |repo, cx| async move {
3318 match repo {
3319 RepositoryState::Local(git_repository) => {
3320 git_repository.restore_checkpoint(checkpoint, cx).await
3321 }
3322 RepositoryState::Remote { .. } => Err(anyhow!("not implemented yet")),
3323 }
3324 })
3325 }
3326
3327 pub fn compare_checkpoints(
3328 &self,
3329 left: GitRepositoryCheckpoint,
3330 right: GitRepositoryCheckpoint,
3331 ) -> oneshot::Receiver<Result<bool>> {
3332 self.send_job(move |repo, cx| async move {
3333 match repo {
3334 RepositoryState::Local(git_repository) => {
3335 git_repository.compare_checkpoints(left, right, cx).await
3336 }
3337 RepositoryState::Remote { .. } => Err(anyhow!("not implemented yet")),
3338 }
3339 })
3340 }
3341
3342 pub fn delete_checkpoint(
3343 &self,
3344 checkpoint: GitRepositoryCheckpoint,
3345 ) -> oneshot::Receiver<Result<()>> {
3346 self.send_job(move |repo, cx| async move {
3347 match repo {
3348 RepositoryState::Local(git_repository) => {
3349 git_repository.delete_checkpoint(checkpoint, cx).await
3350 }
3351 RepositoryState::Remote { .. } => Err(anyhow!("not implemented yet")),
3352 }
3353 })
3354 }
3355}
3356
3357fn get_permalink_in_rust_registry_src(
3358 provider_registry: Arc<GitHostingProviderRegistry>,
3359 path: PathBuf,
3360 selection: Range<u32>,
3361) -> Result<url::Url> {
3362 #[derive(Deserialize)]
3363 struct CargoVcsGit {
3364 sha1: String,
3365 }
3366
3367 #[derive(Deserialize)]
3368 struct CargoVcsInfo {
3369 git: CargoVcsGit,
3370 path_in_vcs: String,
3371 }
3372
3373 #[derive(Deserialize)]
3374 struct CargoPackage {
3375 repository: String,
3376 }
3377
3378 #[derive(Deserialize)]
3379 struct CargoToml {
3380 package: CargoPackage,
3381 }
3382
3383 let Some((dir, cargo_vcs_info_json)) = path.ancestors().skip(1).find_map(|dir| {
3384 let json = std::fs::read_to_string(dir.join(".cargo_vcs_info.json")).ok()?;
3385 Some((dir, json))
3386 }) else {
3387 bail!("No .cargo_vcs_info.json found in parent directories")
3388 };
3389 let cargo_vcs_info = serde_json::from_str::<CargoVcsInfo>(&cargo_vcs_info_json)?;
3390 let cargo_toml = std::fs::read_to_string(dir.join("Cargo.toml"))?;
3391 let manifest = toml::from_str::<CargoToml>(&cargo_toml)?;
3392 let (provider, remote) = parse_git_remote_url(provider_registry, &manifest.package.repository)
3393 .ok_or_else(|| anyhow!("Failed to parse package.repository field of manifest"))?;
3394 let path = PathBuf::from(cargo_vcs_info.path_in_vcs).join(path.strip_prefix(dir).unwrap());
3395 let permalink = provider.build_permalink(
3396 remote,
3397 BuildPermalinkParams {
3398 sha: &cargo_vcs_info.git.sha1,
3399 path: &path.to_string_lossy(),
3400 selection: Some(selection),
3401 },
3402 );
3403 Ok(permalink)
3404}
3405
3406fn serialize_blame_buffer_response(blame: Option<git::blame::Blame>) -> proto::BlameBufferResponse {
3407 let Some(blame) = blame else {
3408 return proto::BlameBufferResponse {
3409 blame_response: None,
3410 };
3411 };
3412
3413 let entries = blame
3414 .entries
3415 .into_iter()
3416 .map(|entry| proto::BlameEntry {
3417 sha: entry.sha.as_bytes().into(),
3418 start_line: entry.range.start,
3419 end_line: entry.range.end,
3420 original_line_number: entry.original_line_number,
3421 author: entry.author.clone(),
3422 author_mail: entry.author_mail.clone(),
3423 author_time: entry.author_time,
3424 author_tz: entry.author_tz.clone(),
3425 committer: entry.committer_name.clone(),
3426 committer_mail: entry.committer_email.clone(),
3427 committer_time: entry.committer_time,
3428 committer_tz: entry.committer_tz.clone(),
3429 summary: entry.summary.clone(),
3430 previous: entry.previous.clone(),
3431 filename: entry.filename.clone(),
3432 })
3433 .collect::<Vec<_>>();
3434
3435 let messages = blame
3436 .messages
3437 .into_iter()
3438 .map(|(oid, message)| proto::CommitMessage {
3439 oid: oid.as_bytes().into(),
3440 message,
3441 })
3442 .collect::<Vec<_>>();
3443
3444 proto::BlameBufferResponse {
3445 blame_response: Some(proto::blame_buffer_response::BlameResponse {
3446 entries,
3447 messages,
3448 remote_url: blame.remote_url,
3449 }),
3450 }
3451}
3452
3453fn deserialize_blame_buffer_response(
3454 response: proto::BlameBufferResponse,
3455) -> Option<git::blame::Blame> {
3456 let response = response.blame_response?;
3457 let entries = response
3458 .entries
3459 .into_iter()
3460 .filter_map(|entry| {
3461 Some(git::blame::BlameEntry {
3462 sha: git::Oid::from_bytes(&entry.sha).ok()?,
3463 range: entry.start_line..entry.end_line,
3464 original_line_number: entry.original_line_number,
3465 committer_name: entry.committer,
3466 committer_time: entry.committer_time,
3467 committer_tz: entry.committer_tz,
3468 committer_email: entry.committer_mail,
3469 author: entry.author,
3470 author_mail: entry.author_mail,
3471 author_time: entry.author_time,
3472 author_tz: entry.author_tz,
3473 summary: entry.summary,
3474 previous: entry.previous,
3475 filename: entry.filename,
3476 })
3477 })
3478 .collect::<Vec<_>>();
3479
3480 let messages = response
3481 .messages
3482 .into_iter()
3483 .filter_map(|message| Some((git::Oid::from_bytes(&message.oid).ok()?, message.message)))
3484 .collect::<HashMap<_, _>>();
3485
3486 Some(Blame {
3487 entries,
3488 messages,
3489 remote_url: response.remote_url,
3490 })
3491}