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