git.rs

   1use crate::buffer_store::BufferStore;
   2use crate::worktree_store::{WorktreeStore, WorktreeStoreEvent};
   3use crate::{Project, ProjectPath};
   4use anyhow::{Context as _, Result};
   5use client::ProjectId;
   6use futures::channel::{mpsc, oneshot};
   7use futures::StreamExt as _;
   8use git::repository::{Branch, CommitDetails, PushOptions, Remote, ResetMode};
   9use git::{
  10    repository::{GitRepository, RepoPath},
  11    status::{GitSummary, TrackedSummary},
  12};
  13use gpui::{
  14    App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, SharedString, Subscription,
  15    Task, WeakEntity,
  16};
  17use language::{Buffer, LanguageRegistry};
  18use rpc::proto::{git_reset, ToProto};
  19use rpc::{proto, AnyProtoClient, TypedEnvelope};
  20use settings::WorktreeId;
  21use std::path::{Path, PathBuf};
  22use std::sync::Arc;
  23use text::BufferId;
  24use util::{maybe, ResultExt};
  25use worktree::{ProjectEntryId, RepositoryEntry, StatusEntry};
  26
  27pub struct GitStore {
  28    buffer_store: Entity<BufferStore>,
  29    pub(super) project_id: Option<ProjectId>,
  30    pub(super) client: Option<AnyProtoClient>,
  31    repositories: Vec<Entity<Repository>>,
  32    active_index: Option<usize>,
  33    update_sender: mpsc::UnboundedSender<(Message, oneshot::Sender<Result<()>>)>,
  34    _subscription: Subscription,
  35}
  36
  37pub struct Repository {
  38    commit_message_buffer: Option<Entity<Buffer>>,
  39    git_store: WeakEntity<GitStore>,
  40    pub worktree_id: WorktreeId,
  41    pub repository_entry: RepositoryEntry,
  42    pub git_repo: GitRepo,
  43    pub merge_message: Option<String>,
  44    update_sender: mpsc::UnboundedSender<(Message, oneshot::Sender<Result<()>>)>,
  45}
  46
  47#[derive(Clone)]
  48pub enum GitRepo {
  49    Local(Arc<dyn GitRepository>),
  50    Remote {
  51        project_id: ProjectId,
  52        client: AnyProtoClient,
  53        worktree_id: WorktreeId,
  54        work_directory_id: ProjectEntryId,
  55    },
  56}
  57
  58pub enum Message {
  59    Commit {
  60        git_repo: GitRepo,
  61        message: SharedString,
  62        name_and_email: Option<(SharedString, SharedString)>,
  63    },
  64    Reset {
  65        repo: GitRepo,
  66        commit: SharedString,
  67        reset_mode: ResetMode,
  68    },
  69    CheckoutFiles {
  70        repo: GitRepo,
  71        commit: SharedString,
  72        paths: Vec<RepoPath>,
  73    },
  74    Stage(GitRepo, Vec<RepoPath>),
  75    Unstage(GitRepo, Vec<RepoPath>),
  76    SetIndexText(GitRepo, RepoPath, Option<String>),
  77    Push {
  78        repo: GitRepo,
  79        branch_name: SharedString,
  80        remote_name: SharedString,
  81        options: Option<PushOptions>,
  82    },
  83    Pull {
  84        repo: GitRepo,
  85        branch_name: SharedString,
  86        remote_name: SharedString,
  87    },
  88    Fetch(GitRepo),
  89}
  90
  91#[derive(Debug)]
  92pub enum GitEvent {
  93    ActiveRepositoryChanged,
  94    FileSystemUpdated,
  95    GitStateUpdated,
  96}
  97
  98impl EventEmitter<GitEvent> for GitStore {}
  99
 100impl GitStore {
 101    pub fn new(
 102        worktree_store: &Entity<WorktreeStore>,
 103        buffer_store: Entity<BufferStore>,
 104        client: Option<AnyProtoClient>,
 105        project_id: Option<ProjectId>,
 106        cx: &mut Context<'_, Self>,
 107    ) -> Self {
 108        let update_sender = Self::spawn_git_worker(cx);
 109        let _subscription = cx.subscribe(worktree_store, Self::on_worktree_store_event);
 110
 111        GitStore {
 112            project_id,
 113            client,
 114            buffer_store,
 115            repositories: Vec::new(),
 116            active_index: None,
 117            update_sender,
 118            _subscription,
 119        }
 120    }
 121
 122    pub fn init(client: &AnyProtoClient) {
 123        client.add_entity_request_handler(Self::handle_get_remotes);
 124        client.add_entity_request_handler(Self::handle_push);
 125        client.add_entity_request_handler(Self::handle_pull);
 126        client.add_entity_request_handler(Self::handle_fetch);
 127        client.add_entity_request_handler(Self::handle_stage);
 128        client.add_entity_request_handler(Self::handle_unstage);
 129        client.add_entity_request_handler(Self::handle_commit);
 130        client.add_entity_request_handler(Self::handle_reset);
 131        client.add_entity_request_handler(Self::handle_show);
 132        client.add_entity_request_handler(Self::handle_checkout_files);
 133        client.add_entity_request_handler(Self::handle_open_commit_message_buffer);
 134        client.add_entity_request_handler(Self::handle_set_index_text);
 135    }
 136
 137    pub fn active_repository(&self) -> Option<Entity<Repository>> {
 138        self.active_index
 139            .map(|index| self.repositories[index].clone())
 140    }
 141
 142    fn on_worktree_store_event(
 143        &mut self,
 144        worktree_store: Entity<WorktreeStore>,
 145        event: &WorktreeStoreEvent,
 146        cx: &mut Context<'_, Self>,
 147    ) {
 148        let mut new_repositories = Vec::new();
 149        let mut new_active_index = None;
 150        let this = cx.weak_entity();
 151        let client = self.client.clone();
 152        let project_id = self.project_id;
 153
 154        worktree_store.update(cx, |worktree_store, cx| {
 155            for worktree in worktree_store.worktrees() {
 156                worktree.update(cx, |worktree, cx| {
 157                    let snapshot = worktree.snapshot();
 158                    for repo in snapshot.repositories().iter() {
 159                        let git_data = worktree
 160                            .as_local()
 161                            .and_then(|local_worktree| local_worktree.get_local_repo(repo))
 162                            .map(|local_repo| {
 163                                (
 164                                    GitRepo::Local(local_repo.repo().clone()),
 165                                    local_repo.merge_message.clone(),
 166                                )
 167                            })
 168                            .or_else(|| {
 169                                let client = client.clone()?;
 170                                let project_id = project_id?;
 171                                Some((
 172                                    GitRepo::Remote {
 173                                        project_id,
 174                                        client,
 175                                        worktree_id: worktree.id(),
 176                                        work_directory_id: repo.work_directory_id(),
 177                                    },
 178                                    None,
 179                                ))
 180                            });
 181                        let Some((git_repo, merge_message)) = git_data else {
 182                            continue;
 183                        };
 184                        let worktree_id = worktree.id();
 185                        let existing =
 186                            self.repositories
 187                                .iter()
 188                                .enumerate()
 189                                .find(|(_, existing_handle)| {
 190                                    existing_handle.read(cx).id()
 191                                        == (worktree_id, repo.work_directory_id())
 192                                });
 193                        let handle = if let Some((index, handle)) = existing {
 194                            if self.active_index == Some(index) {
 195                                new_active_index = Some(new_repositories.len());
 196                            }
 197                            // Update the statuses and merge message but keep everything else.
 198                            let existing_handle = handle.clone();
 199                            existing_handle.update(cx, |existing_handle, cx| {
 200                                existing_handle.repository_entry = repo.clone();
 201                                if matches!(git_repo, GitRepo::Local { .. })
 202                                    && existing_handle.merge_message != merge_message
 203                                {
 204                                    if let (Some(merge_message), Some(buffer)) =
 205                                        (&merge_message, &existing_handle.commit_message_buffer)
 206                                    {
 207                                        buffer.update(cx, |buffer, cx| {
 208                                            if buffer.is_empty() {
 209                                                buffer.set_text(merge_message.as_str(), cx);
 210                                            }
 211                                        })
 212                                    }
 213                                    existing_handle.merge_message = merge_message;
 214                                }
 215                            });
 216                            existing_handle
 217                        } else {
 218                            cx.new(|_| Repository {
 219                                git_store: this.clone(),
 220                                worktree_id,
 221                                repository_entry: repo.clone(),
 222                                git_repo,
 223                                update_sender: self.update_sender.clone(),
 224                                merge_message,
 225                                commit_message_buffer: None,
 226                            })
 227                        };
 228                        new_repositories.push(handle);
 229                    }
 230                })
 231            }
 232        });
 233
 234        if new_active_index == None && new_repositories.len() > 0 {
 235            new_active_index = Some(0);
 236        }
 237
 238        self.repositories = new_repositories;
 239        self.active_index = new_active_index;
 240
 241        match event {
 242            WorktreeStoreEvent::WorktreeUpdatedGitRepositories(_) => {
 243                cx.emit(GitEvent::GitStateUpdated);
 244            }
 245            _ => {
 246                cx.emit(GitEvent::FileSystemUpdated);
 247            }
 248        }
 249    }
 250
 251    pub fn all_repositories(&self) -> Vec<Entity<Repository>> {
 252        self.repositories.clone()
 253    }
 254
 255    fn spawn_git_worker(
 256        cx: &mut Context<'_, GitStore>,
 257    ) -> mpsc::UnboundedSender<(Message, oneshot::Sender<Result<()>>)> {
 258        let (update_sender, mut update_receiver) =
 259            mpsc::unbounded::<(Message, oneshot::Sender<Result<()>>)>();
 260        cx.spawn(|_, cx| async move {
 261            while let Some((msg, respond)) = update_receiver.next().await {
 262                if !respond.is_canceled() {
 263                    let result = cx.background_spawn(Self::process_git_msg(msg)).await;
 264                    respond.send(result).ok();
 265                }
 266            }
 267        })
 268        .detach();
 269        update_sender
 270    }
 271
 272    async fn process_git_msg(msg: Message) -> Result<()> {
 273        match msg {
 274            Message::Fetch(repo) => {
 275                match repo {
 276                    GitRepo::Local(git_repository) => git_repository.fetch()?,
 277                    GitRepo::Remote {
 278                        project_id,
 279                        client,
 280                        worktree_id,
 281                        work_directory_id,
 282                    } => {
 283                        client
 284                            .request(proto::Fetch {
 285                                project_id: project_id.0,
 286                                worktree_id: worktree_id.to_proto(),
 287                                work_directory_id: work_directory_id.to_proto(),
 288                            })
 289                            .await
 290                            .context("sending fetch request")?;
 291                    }
 292                }
 293                Ok(())
 294            }
 295
 296            Message::Pull {
 297                repo,
 298                branch_name,
 299                remote_name,
 300            } => {
 301                match repo {
 302                    GitRepo::Local(git_repository) => {
 303                        git_repository.pull(&branch_name, &remote_name)?
 304                    }
 305                    GitRepo::Remote {
 306                        project_id,
 307                        client,
 308                        worktree_id,
 309                        work_directory_id,
 310                    } => {
 311                        client
 312                            .request(proto::Pull {
 313                                project_id: project_id.0,
 314                                worktree_id: worktree_id.to_proto(),
 315                                work_directory_id: work_directory_id.to_proto(),
 316                                branch_name: branch_name.to_string(),
 317                                remote_name: remote_name.to_string(),
 318                            })
 319                            .await
 320                            .context("sending pull request")?;
 321                    }
 322                }
 323                Ok(())
 324            }
 325            Message::Push {
 326                repo,
 327                branch_name,
 328                remote_name,
 329                options,
 330            } => {
 331                match repo {
 332                    GitRepo::Local(git_repository) => {
 333                        git_repository.push(&branch_name, &remote_name, options)?
 334                    }
 335                    GitRepo::Remote {
 336                        project_id,
 337                        client,
 338                        worktree_id,
 339                        work_directory_id,
 340                    } => {
 341                        client
 342                            .request(proto::Push {
 343                                project_id: project_id.0,
 344                                worktree_id: worktree_id.to_proto(),
 345                                work_directory_id: work_directory_id.to_proto(),
 346                                branch_name: branch_name.to_string(),
 347                                remote_name: remote_name.to_string(),
 348                                options: options.map(|options| match options {
 349                                    PushOptions::Force => proto::push::PushOptions::Force,
 350                                    PushOptions::SetUpstream => {
 351                                        proto::push::PushOptions::SetUpstream
 352                                    }
 353                                }
 354                                    as i32),
 355                            })
 356                            .await
 357                            .context("sending push request")?;
 358                    }
 359                }
 360                Ok(())
 361            }
 362            Message::Stage(repo, paths) => {
 363                match repo {
 364                    GitRepo::Local(repo) => repo.stage_paths(&paths)?,
 365                    GitRepo::Remote {
 366                        project_id,
 367                        client,
 368                        worktree_id,
 369                        work_directory_id,
 370                    } => {
 371                        client
 372                            .request(proto::Stage {
 373                                project_id: project_id.0,
 374                                worktree_id: worktree_id.to_proto(),
 375                                work_directory_id: work_directory_id.to_proto(),
 376                                paths: paths
 377                                    .into_iter()
 378                                    .map(|repo_path| repo_path.as_ref().to_proto())
 379                                    .collect(),
 380                            })
 381                            .await
 382                            .context("sending stage request")?;
 383                    }
 384                }
 385                Ok(())
 386            }
 387            Message::Reset {
 388                repo,
 389                commit,
 390                reset_mode,
 391            } => {
 392                match repo {
 393                    GitRepo::Local(repo) => repo.reset(&commit, reset_mode)?,
 394                    GitRepo::Remote {
 395                        project_id,
 396                        client,
 397                        worktree_id,
 398                        work_directory_id,
 399                    } => {
 400                        client
 401                            .request(proto::GitReset {
 402                                project_id: project_id.0,
 403                                worktree_id: worktree_id.to_proto(),
 404                                work_directory_id: work_directory_id.to_proto(),
 405                                commit: commit.into(),
 406                                mode: match reset_mode {
 407                                    ResetMode::Soft => git_reset::ResetMode::Soft.into(),
 408                                    ResetMode::Mixed => git_reset::ResetMode::Mixed.into(),
 409                                },
 410                            })
 411                            .await?;
 412                    }
 413                }
 414                Ok(())
 415            }
 416
 417            Message::CheckoutFiles {
 418                repo,
 419                commit,
 420                paths,
 421            } => {
 422                match repo {
 423                    GitRepo::Local(repo) => repo.checkout_files(&commit, &paths)?,
 424                    GitRepo::Remote {
 425                        project_id,
 426                        client,
 427                        worktree_id,
 428                        work_directory_id,
 429                    } => {
 430                        client
 431                            .request(proto::GitCheckoutFiles {
 432                                project_id: project_id.0,
 433                                worktree_id: worktree_id.to_proto(),
 434                                work_directory_id: work_directory_id.to_proto(),
 435                                commit: commit.into(),
 436                                paths: paths
 437                                    .into_iter()
 438                                    .map(|p| p.to_string_lossy().to_string())
 439                                    .collect(),
 440                            })
 441                            .await?;
 442                    }
 443                }
 444                Ok(())
 445            }
 446            Message::Unstage(repo, paths) => {
 447                match repo {
 448                    GitRepo::Local(repo) => repo.unstage_paths(&paths)?,
 449                    GitRepo::Remote {
 450                        project_id,
 451                        client,
 452                        worktree_id,
 453                        work_directory_id,
 454                    } => {
 455                        client
 456                            .request(proto::Unstage {
 457                                project_id: project_id.0,
 458                                worktree_id: worktree_id.to_proto(),
 459                                work_directory_id: work_directory_id.to_proto(),
 460                                paths: paths
 461                                    .into_iter()
 462                                    .map(|repo_path| repo_path.as_ref().to_proto())
 463                                    .collect(),
 464                            })
 465                            .await
 466                            .context("sending unstage request")?;
 467                    }
 468                }
 469                Ok(())
 470            }
 471            Message::Commit {
 472                git_repo,
 473                message,
 474                name_and_email,
 475            } => {
 476                match git_repo {
 477                    GitRepo::Local(repo) => repo.commit(
 478                        message.as_ref(),
 479                        name_and_email
 480                            .as_ref()
 481                            .map(|(name, email)| (name.as_ref(), email.as_ref())),
 482                    )?,
 483                    GitRepo::Remote {
 484                        project_id,
 485                        client,
 486                        worktree_id,
 487                        work_directory_id,
 488                    } => {
 489                        let (name, email) = name_and_email.unzip();
 490                        client
 491                            .request(proto::Commit {
 492                                project_id: project_id.0,
 493                                worktree_id: worktree_id.to_proto(),
 494                                work_directory_id: work_directory_id.to_proto(),
 495                                message: String::from(message),
 496                                name: name.map(String::from),
 497                                email: email.map(String::from),
 498                            })
 499                            .await
 500                            .context("sending commit request")?;
 501                    }
 502                }
 503                Ok(())
 504            }
 505            Message::SetIndexText(git_repo, path, text) => match git_repo {
 506                GitRepo::Local(repo) => repo.set_index_text(&path, text),
 507                GitRepo::Remote {
 508                    project_id,
 509                    client,
 510                    worktree_id,
 511                    work_directory_id,
 512                } => client.send(proto::SetIndexText {
 513                    project_id: project_id.0,
 514                    worktree_id: worktree_id.to_proto(),
 515                    work_directory_id: work_directory_id.to_proto(),
 516                    path: path.as_ref().to_proto(),
 517                    text,
 518                }),
 519            },
 520        }
 521    }
 522
 523    async fn handle_fetch(
 524        this: Entity<Self>,
 525        envelope: TypedEnvelope<proto::Fetch>,
 526        mut cx: AsyncApp,
 527    ) -> Result<proto::Ack> {
 528        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 529        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 530        let repository_handle =
 531            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 532
 533        repository_handle
 534            .update(&mut cx, |repository_handle, _cx| repository_handle.fetch())?
 535            .await??;
 536        Ok(proto::Ack {})
 537    }
 538
 539    async fn handle_push(
 540        this: Entity<Self>,
 541        envelope: TypedEnvelope<proto::Push>,
 542        mut cx: AsyncApp,
 543    ) -> Result<proto::Ack> {
 544        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 545        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 546        let repository_handle =
 547            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 548
 549        let options = envelope
 550            .payload
 551            .options
 552            .as_ref()
 553            .map(|_| match envelope.payload.options() {
 554                proto::push::PushOptions::SetUpstream => git::repository::PushOptions::SetUpstream,
 555                proto::push::PushOptions::Force => git::repository::PushOptions::Force,
 556            });
 557
 558        let branch_name = envelope.payload.branch_name.into();
 559        let remote_name = envelope.payload.remote_name.into();
 560
 561        repository_handle
 562            .update(&mut cx, |repository_handle, _cx| {
 563                repository_handle.push(branch_name, remote_name, options)
 564            })?
 565            .await??;
 566        Ok(proto::Ack {})
 567    }
 568
 569    async fn handle_pull(
 570        this: Entity<Self>,
 571        envelope: TypedEnvelope<proto::Pull>,
 572        mut cx: AsyncApp,
 573    ) -> Result<proto::Ack> {
 574        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 575        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 576        let repository_handle =
 577            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 578
 579        let branch_name = envelope.payload.branch_name.into();
 580        let remote_name = envelope.payload.remote_name.into();
 581
 582        repository_handle
 583            .update(&mut cx, |repository_handle, _cx| {
 584                repository_handle.pull(branch_name, remote_name)
 585            })?
 586            .await??;
 587        Ok(proto::Ack {})
 588    }
 589
 590    async fn handle_stage(
 591        this: Entity<Self>,
 592        envelope: TypedEnvelope<proto::Stage>,
 593        mut cx: AsyncApp,
 594    ) -> Result<proto::Ack> {
 595        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 596        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 597        let repository_handle =
 598            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 599
 600        let entries = envelope
 601            .payload
 602            .paths
 603            .into_iter()
 604            .map(PathBuf::from)
 605            .map(RepoPath::new)
 606            .collect();
 607
 608        repository_handle
 609            .update(&mut cx, |repository_handle, cx| {
 610                repository_handle.stage_entries(entries, cx)
 611            })?
 612            .await?;
 613        Ok(proto::Ack {})
 614    }
 615
 616    async fn handle_unstage(
 617        this: Entity<Self>,
 618        envelope: TypedEnvelope<proto::Unstage>,
 619        mut cx: AsyncApp,
 620    ) -> Result<proto::Ack> {
 621        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 622        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 623        let repository_handle =
 624            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 625
 626        let entries = envelope
 627            .payload
 628            .paths
 629            .into_iter()
 630            .map(PathBuf::from)
 631            .map(RepoPath::new)
 632            .collect();
 633
 634        repository_handle
 635            .update(&mut cx, |repository_handle, cx| {
 636                repository_handle.unstage_entries(entries, cx)
 637            })?
 638            .await?;
 639
 640        Ok(proto::Ack {})
 641    }
 642
 643    async fn handle_set_index_text(
 644        this: Entity<Self>,
 645        envelope: TypedEnvelope<proto::SetIndexText>,
 646        mut cx: AsyncApp,
 647    ) -> Result<proto::Ack> {
 648        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 649        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 650        let repository_handle =
 651            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 652
 653        repository_handle
 654            .update(&mut cx, |repository_handle, _| {
 655                repository_handle.set_index_text(
 656                    &RepoPath::from_str(&envelope.payload.path),
 657                    envelope.payload.text,
 658                )
 659            })?
 660            .await??;
 661        Ok(proto::Ack {})
 662    }
 663
 664    async fn handle_commit(
 665        this: Entity<Self>,
 666        envelope: TypedEnvelope<proto::Commit>,
 667        mut cx: AsyncApp,
 668    ) -> Result<proto::Ack> {
 669        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 670        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 671        let repository_handle =
 672            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 673
 674        let message = SharedString::from(envelope.payload.message);
 675        let name = envelope.payload.name.map(SharedString::from);
 676        let email = envelope.payload.email.map(SharedString::from);
 677
 678        repository_handle
 679            .update(&mut cx, |repository_handle, _| {
 680                repository_handle.commit(message, name.zip(email))
 681            })?
 682            .await??;
 683        Ok(proto::Ack {})
 684    }
 685
 686    async fn handle_get_remotes(
 687        this: Entity<Self>,
 688        envelope: TypedEnvelope<proto::GetRemotes>,
 689        mut cx: AsyncApp,
 690    ) -> Result<proto::GetRemotesResponse> {
 691        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 692        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 693        let repository_handle =
 694            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 695
 696        let branch_name = envelope.payload.branch_name;
 697
 698        let remotes = repository_handle
 699            .update(&mut cx, |repository_handle, cx| {
 700                repository_handle.get_remotes(branch_name, cx)
 701            })?
 702            .await?;
 703
 704        Ok(proto::GetRemotesResponse {
 705            remotes: remotes
 706                .into_iter()
 707                .map(|remotes| proto::get_remotes_response::Remote {
 708                    name: remotes.name.to_string(),
 709                })
 710                .collect::<Vec<_>>(),
 711        })
 712    }
 713
 714    async fn handle_show(
 715        this: Entity<Self>,
 716        envelope: TypedEnvelope<proto::GitShow>,
 717        mut cx: AsyncApp,
 718    ) -> Result<proto::GitCommitDetails> {
 719        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 720        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 721        let repository_handle =
 722            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 723
 724        let commit = repository_handle
 725            .update(&mut cx, |repository_handle, cx| {
 726                repository_handle.show(&envelope.payload.commit, cx)
 727            })?
 728            .await?;
 729        Ok(proto::GitCommitDetails {
 730            sha: commit.sha.into(),
 731            message: commit.message.into(),
 732            commit_timestamp: commit.commit_timestamp,
 733            committer_email: commit.committer_email.into(),
 734            committer_name: commit.committer_name.into(),
 735        })
 736    }
 737
 738    async fn handle_reset(
 739        this: Entity<Self>,
 740        envelope: TypedEnvelope<proto::GitReset>,
 741        mut cx: AsyncApp,
 742    ) -> Result<proto::Ack> {
 743        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 744        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 745        let repository_handle =
 746            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 747
 748        let mode = match envelope.payload.mode() {
 749            git_reset::ResetMode::Soft => ResetMode::Soft,
 750            git_reset::ResetMode::Mixed => ResetMode::Mixed,
 751        };
 752
 753        repository_handle
 754            .update(&mut cx, |repository_handle, _| {
 755                repository_handle.reset(&envelope.payload.commit, mode)
 756            })?
 757            .await??;
 758        Ok(proto::Ack {})
 759    }
 760
 761    async fn handle_checkout_files(
 762        this: Entity<Self>,
 763        envelope: TypedEnvelope<proto::GitCheckoutFiles>,
 764        mut cx: AsyncApp,
 765    ) -> Result<proto::Ack> {
 766        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 767        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 768        let repository_handle =
 769            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 770        let paths = envelope
 771            .payload
 772            .paths
 773            .iter()
 774            .map(|s| RepoPath::from_str(s))
 775            .collect();
 776
 777        repository_handle
 778            .update(&mut cx, |repository_handle, _| {
 779                repository_handle.checkout_files(&envelope.payload.commit, paths)
 780            })?
 781            .await??;
 782        Ok(proto::Ack {})
 783    }
 784
 785    async fn handle_open_commit_message_buffer(
 786        this: Entity<Self>,
 787        envelope: TypedEnvelope<proto::OpenCommitMessageBuffer>,
 788        mut cx: AsyncApp,
 789    ) -> Result<proto::OpenBufferResponse> {
 790        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 791        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 792        let repository =
 793            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 794        let buffer = repository
 795            .update(&mut cx, |repository, cx| {
 796                repository.open_commit_buffer(None, this.read(cx).buffer_store.clone(), cx)
 797            })?
 798            .await?;
 799
 800        let buffer_id = buffer.read_with(&cx, |buffer, _| buffer.remote_id())?;
 801        this.update(&mut cx, |this, cx| {
 802            this.buffer_store.update(cx, |buffer_store, cx| {
 803                buffer_store
 804                    .create_buffer_for_peer(
 805                        &buffer,
 806                        envelope.original_sender_id.unwrap_or(envelope.sender_id),
 807                        cx,
 808                    )
 809                    .detach_and_log_err(cx);
 810            })
 811        })?;
 812
 813        Ok(proto::OpenBufferResponse {
 814            buffer_id: buffer_id.to_proto(),
 815        })
 816    }
 817
 818    fn repository_for_request(
 819        this: &Entity<Self>,
 820        worktree_id: WorktreeId,
 821        work_directory_id: ProjectEntryId,
 822        cx: &mut AsyncApp,
 823    ) -> Result<Entity<Repository>> {
 824        this.update(cx, |this, cx| {
 825            let repository_handle = this
 826                .all_repositories()
 827                .into_iter()
 828                .find(|repository_handle| {
 829                    repository_handle.read(cx).worktree_id == worktree_id
 830                        && repository_handle
 831                            .read(cx)
 832                            .repository_entry
 833                            .work_directory_id()
 834                            == work_directory_id
 835                })
 836                .context("missing repository handle")?;
 837            anyhow::Ok(repository_handle)
 838        })?
 839    }
 840}
 841
 842impl GitRepo {}
 843
 844impl Repository {
 845    pub fn git_store(&self) -> Option<Entity<GitStore>> {
 846        self.git_store.upgrade()
 847    }
 848
 849    fn id(&self) -> (WorktreeId, ProjectEntryId) {
 850        (self.worktree_id, self.repository_entry.work_directory_id())
 851    }
 852
 853    pub fn current_branch(&self) -> Option<&Branch> {
 854        self.repository_entry.branch()
 855    }
 856
 857    pub fn display_name(&self, project: &Project, cx: &App) -> SharedString {
 858        maybe!({
 859            let project_path = self.repo_path_to_project_path(&"".into())?;
 860            let worktree_name = project
 861                .worktree_for_id(project_path.worktree_id, cx)?
 862                .read(cx)
 863                .root_name();
 864
 865            let mut path = PathBuf::new();
 866            path = path.join(worktree_name);
 867            path = path.join(project_path.path);
 868            Some(path.to_string_lossy().to_string())
 869        })
 870        .unwrap_or_else(|| self.repository_entry.work_directory.display_name())
 871        .into()
 872    }
 873
 874    pub fn activate(&self, cx: &mut Context<Self>) {
 875        let Some(git_store) = self.git_store.upgrade() else {
 876            return;
 877        };
 878        let entity = cx.entity();
 879        git_store.update(cx, |git_store, cx| {
 880            let Some(index) = git_store
 881                .repositories
 882                .iter()
 883                .position(|handle| *handle == entity)
 884            else {
 885                return;
 886            };
 887            git_store.active_index = Some(index);
 888            cx.emit(GitEvent::ActiveRepositoryChanged);
 889        });
 890    }
 891
 892    pub fn status(&self) -> impl '_ + Iterator<Item = StatusEntry> {
 893        self.repository_entry.status()
 894    }
 895
 896    pub fn has_conflict(&self, path: &RepoPath) -> bool {
 897        self.repository_entry
 898            .current_merge_conflicts
 899            .contains(&path)
 900    }
 901
 902    pub fn repo_path_to_project_path(&self, path: &RepoPath) -> Option<ProjectPath> {
 903        let path = self.repository_entry.unrelativize(path)?;
 904        Some((self.worktree_id, path).into())
 905    }
 906
 907    pub fn project_path_to_repo_path(&self, path: &ProjectPath) -> Option<RepoPath> {
 908        self.worktree_id_path_to_repo_path(path.worktree_id, &path.path)
 909    }
 910
 911    pub fn worktree_id_path_to_repo_path(
 912        &self,
 913        worktree_id: WorktreeId,
 914        path: &Path,
 915    ) -> Option<RepoPath> {
 916        if worktree_id != self.worktree_id {
 917            return None;
 918        }
 919        self.repository_entry.relativize(path).log_err()
 920    }
 921
 922    pub fn open_commit_buffer(
 923        &mut self,
 924        languages: Option<Arc<LanguageRegistry>>,
 925        buffer_store: Entity<BufferStore>,
 926        cx: &mut Context<Self>,
 927    ) -> Task<Result<Entity<Buffer>>> {
 928        if let Some(buffer) = self.commit_message_buffer.clone() {
 929            return Task::ready(Ok(buffer));
 930        }
 931
 932        if let GitRepo::Remote {
 933            project_id,
 934            client,
 935            worktree_id,
 936            work_directory_id,
 937        } = self.git_repo.clone()
 938        {
 939            let client = client.clone();
 940            cx.spawn(|repository, mut cx| async move {
 941                let request = client.request(proto::OpenCommitMessageBuffer {
 942                    project_id: project_id.0,
 943                    worktree_id: worktree_id.to_proto(),
 944                    work_directory_id: work_directory_id.to_proto(),
 945                });
 946                let response = request.await.context("requesting to open commit buffer")?;
 947                let buffer_id = BufferId::new(response.buffer_id)?;
 948                let buffer = buffer_store
 949                    .update(&mut cx, |buffer_store, cx| {
 950                        buffer_store.wait_for_remote_buffer(buffer_id, cx)
 951                    })?
 952                    .await?;
 953                if let Some(language_registry) = languages {
 954                    let git_commit_language =
 955                        language_registry.language_for_name("Git Commit").await?;
 956                    buffer.update(&mut cx, |buffer, cx| {
 957                        buffer.set_language(Some(git_commit_language), cx);
 958                    })?;
 959                }
 960                repository.update(&mut cx, |repository, _| {
 961                    repository.commit_message_buffer = Some(buffer.clone());
 962                })?;
 963                Ok(buffer)
 964            })
 965        } else {
 966            self.open_local_commit_buffer(languages, buffer_store, cx)
 967        }
 968    }
 969
 970    fn open_local_commit_buffer(
 971        &mut self,
 972        language_registry: Option<Arc<LanguageRegistry>>,
 973        buffer_store: Entity<BufferStore>,
 974        cx: &mut Context<Self>,
 975    ) -> Task<Result<Entity<Buffer>>> {
 976        let merge_message = self.merge_message.clone();
 977        cx.spawn(|repository, mut cx| async move {
 978            let buffer = buffer_store
 979                .update(&mut cx, |buffer_store, cx| buffer_store.create_buffer(cx))?
 980                .await?;
 981
 982            if let Some(language_registry) = language_registry {
 983                let git_commit_language = language_registry.language_for_name("Git Commit").await?;
 984                buffer.update(&mut cx, |buffer, cx| {
 985                    buffer.set_language(Some(git_commit_language), cx);
 986                })?;
 987            }
 988
 989            if let Some(merge_message) = merge_message {
 990                buffer.update(&mut cx, |buffer, cx| {
 991                    buffer.set_text(merge_message.as_str(), cx)
 992                })?;
 993            }
 994
 995            repository.update(&mut cx, |repository, _| {
 996                repository.commit_message_buffer = Some(buffer.clone());
 997            })?;
 998            Ok(buffer)
 999        })
1000    }
1001
1002    pub fn checkout_files(
1003        &self,
1004        commit: &str,
1005        paths: Vec<RepoPath>,
1006    ) -> oneshot::Receiver<Result<()>> {
1007        self.send_message(Message::CheckoutFiles {
1008            repo: self.git_repo.clone(),
1009            commit: commit.to_string().into(),
1010            paths,
1011        })
1012    }
1013
1014    pub fn reset(&self, commit: &str, reset_mode: ResetMode) -> oneshot::Receiver<Result<()>> {
1015        self.send_message(Message::Reset {
1016            repo: self.git_repo.clone(),
1017            commit: commit.to_string().into(),
1018            reset_mode,
1019        })
1020    }
1021
1022    pub fn show(&self, commit: &str, cx: &Context<Self>) -> Task<Result<CommitDetails>> {
1023        let commit = commit.to_string();
1024        match self.git_repo.clone() {
1025            GitRepo::Local(git_repository) => {
1026                let commit = commit.to_string();
1027                cx.background_spawn(async move { git_repository.show(&commit) })
1028            }
1029            GitRepo::Remote {
1030                project_id,
1031                client,
1032                worktree_id,
1033                work_directory_id,
1034            } => cx.background_spawn(async move {
1035                let resp = client
1036                    .request(proto::GitShow {
1037                        project_id: project_id.0,
1038                        worktree_id: worktree_id.to_proto(),
1039                        work_directory_id: work_directory_id.to_proto(),
1040                        commit,
1041                    })
1042                    .await?;
1043
1044                Ok(CommitDetails {
1045                    sha: resp.sha.into(),
1046                    message: resp.message.into(),
1047                    commit_timestamp: resp.commit_timestamp,
1048                    committer_email: resp.committer_email.into(),
1049                    committer_name: resp.committer_name.into(),
1050                })
1051            }),
1052        }
1053    }
1054
1055    fn buffer_store(&self, cx: &App) -> Option<Entity<BufferStore>> {
1056        Some(self.git_store.upgrade()?.read(cx).buffer_store.clone())
1057    }
1058
1059    pub fn stage_entries(&self, entries: Vec<RepoPath>, cx: &mut App) -> Task<anyhow::Result<()>> {
1060        let (result_tx, result_rx) = futures::channel::oneshot::channel();
1061        if entries.is_empty() {
1062            return Task::ready(Ok(()));
1063        }
1064
1065        let mut save_futures = Vec::new();
1066        if let Some(buffer_store) = self.buffer_store(cx) {
1067            buffer_store.update(cx, |buffer_store, cx| {
1068                for path in &entries {
1069                    let Some(path) = self.repository_entry.unrelativize(path) else {
1070                        continue;
1071                    };
1072                    let project_path = (self.worktree_id, path).into();
1073                    if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
1074                        save_futures.push(buffer_store.save_buffer(buffer, cx));
1075                    }
1076                }
1077            })
1078        }
1079
1080        let update_sender = self.update_sender.clone();
1081        let git_repo = self.git_repo.clone();
1082        cx.spawn(|_| async move {
1083            for save_future in save_futures {
1084                save_future.await?;
1085            }
1086            update_sender
1087                .unbounded_send((Message::Stage(git_repo, entries), result_tx))
1088                .ok();
1089            result_rx.await.anyhow()??;
1090            Ok(())
1091        })
1092    }
1093
1094    pub fn unstage_entries(
1095        &self,
1096        entries: Vec<RepoPath>,
1097        cx: &mut App,
1098    ) -> Task<anyhow::Result<()>> {
1099        let (result_tx, result_rx) = futures::channel::oneshot::channel();
1100        if entries.is_empty() {
1101            return Task::ready(Ok(()));
1102        }
1103
1104        let mut save_futures = Vec::new();
1105        if let Some(buffer_store) = self.buffer_store(cx) {
1106            buffer_store.update(cx, |buffer_store, cx| {
1107                for path in &entries {
1108                    let Some(path) = self.repository_entry.unrelativize(path) else {
1109                        continue;
1110                    };
1111                    let project_path = (self.worktree_id, path).into();
1112                    if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
1113                        save_futures.push(buffer_store.save_buffer(buffer, cx));
1114                    }
1115                }
1116            })
1117        }
1118
1119        let update_sender = self.update_sender.clone();
1120        let git_repo = self.git_repo.clone();
1121        cx.spawn(|_| async move {
1122            for save_future in save_futures {
1123                save_future.await?;
1124            }
1125            update_sender
1126                .unbounded_send((Message::Unstage(git_repo, entries), result_tx))
1127                .ok();
1128            result_rx.await.anyhow()??;
1129            Ok(())
1130        })
1131    }
1132
1133    pub fn stage_all(&self, cx: &mut App) -> Task<anyhow::Result<()>> {
1134        let to_stage = self
1135            .repository_entry
1136            .status()
1137            .filter(|entry| !entry.status.is_staged().unwrap_or(false))
1138            .map(|entry| entry.repo_path.clone())
1139            .collect();
1140        self.stage_entries(to_stage, cx)
1141    }
1142
1143    pub fn unstage_all(&self, cx: &mut App) -> Task<anyhow::Result<()>> {
1144        let to_unstage = self
1145            .repository_entry
1146            .status()
1147            .filter(|entry| entry.status.is_staged().unwrap_or(true))
1148            .map(|entry| entry.repo_path.clone())
1149            .collect();
1150        self.unstage_entries(to_unstage, cx)
1151    }
1152
1153    /// Get a count of all entries in the active repository, including
1154    /// untracked files.
1155    pub fn entry_count(&self) -> usize {
1156        self.repository_entry.status_len()
1157    }
1158
1159    fn have_changes(&self) -> bool {
1160        self.repository_entry.status_summary() != GitSummary::UNCHANGED
1161    }
1162
1163    fn have_staged_changes(&self) -> bool {
1164        self.repository_entry.status_summary().index != TrackedSummary::UNCHANGED
1165    }
1166
1167    pub fn can_commit(&self, commit_all: bool) -> bool {
1168        return self.have_changes() && (commit_all || self.have_staged_changes());
1169    }
1170
1171    pub fn commit(
1172        &self,
1173        message: SharedString,
1174        name_and_email: Option<(SharedString, SharedString)>,
1175    ) -> oneshot::Receiver<Result<()>> {
1176        self.send_message(Message::Commit {
1177            git_repo: self.git_repo.clone(),
1178            message,
1179            name_and_email,
1180        })
1181    }
1182
1183    pub fn fetch(&self) -> oneshot::Receiver<Result<()>> {
1184        self.send_message(Message::Fetch(self.git_repo.clone()))
1185    }
1186
1187    pub fn push(
1188        &self,
1189        branch: SharedString,
1190        remote: SharedString,
1191        options: Option<PushOptions>,
1192    ) -> oneshot::Receiver<Result<()>> {
1193        self.send_message(Message::Push {
1194            repo: self.git_repo.clone(),
1195            branch_name: branch,
1196            remote_name: remote,
1197            options,
1198        })
1199    }
1200
1201    pub fn pull(
1202        &self,
1203        branch: SharedString,
1204        remote: SharedString,
1205    ) -> oneshot::Receiver<Result<()>> {
1206        self.send_message(Message::Pull {
1207            repo: self.git_repo.clone(),
1208            branch_name: branch,
1209            remote_name: remote,
1210        })
1211    }
1212
1213    pub fn set_index_text(
1214        &self,
1215        path: &RepoPath,
1216        content: Option<String>,
1217    ) -> oneshot::Receiver<anyhow::Result<()>> {
1218        self.send_message(Message::SetIndexText(
1219            self.git_repo.clone(),
1220            path.clone(),
1221            content,
1222        ))
1223    }
1224
1225    pub fn get_remotes(&self, branch_name: Option<String>, cx: &App) -> Task<Result<Vec<Remote>>> {
1226        match self.git_repo.clone() {
1227            GitRepo::Local(git_repository) => {
1228                cx.background_spawn(
1229                    async move { git_repository.get_remotes(branch_name.as_deref()) },
1230                )
1231            }
1232            GitRepo::Remote {
1233                project_id,
1234                client,
1235                worktree_id,
1236                work_directory_id,
1237            } => cx.background_spawn(async move {
1238                let response = client
1239                    .request(proto::GetRemotes {
1240                        project_id: project_id.0,
1241                        worktree_id: worktree_id.to_proto(),
1242                        work_directory_id: work_directory_id.to_proto(),
1243                        branch_name,
1244                    })
1245                    .await?;
1246
1247                Ok(response
1248                    .remotes
1249                    .into_iter()
1250                    .map(|remotes| git::repository::Remote {
1251                        name: remotes.name.into(),
1252                    })
1253                    .collect())
1254            }),
1255        }
1256    }
1257
1258    fn send_message(&self, message: Message) -> oneshot::Receiver<anyhow::Result<()>> {
1259        let (result_tx, result_rx) = futures::channel::oneshot::channel();
1260        self.update_sender.unbounded_send((message, result_tx)).ok();
1261        result_rx
1262    }
1263}