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