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