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