Add support for git branches on remote projects (#19755)

Release Notes:

- Fixed a bug where the branch switcher could not be used remotely.
This commit is contained in:
Mikayla Maki 2024-10-27 15:50:54 -07:00 committed by GitHub
parent 5506669b06
commit c69da2df70
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 993 additions and 127 deletions

View file

@ -1,8 +1,9 @@
use crate::GitHostingProviderRegistry;
use crate::{blame::Blame, status::GitStatus};
use anyhow::{Context, Result};
use collections::HashMap;
use collections::{HashMap, HashSet};
use git2::BranchType;
use gpui::SharedString;
use parking_lot::Mutex;
use rope::Rope;
use serde::{Deserialize, Serialize};
@ -17,7 +18,7 @@ use util::ResultExt;
#[derive(Clone, Debug, Hash, PartialEq)]
pub struct Branch {
pub is_head: bool,
pub name: Box<str>,
pub name: SharedString,
/// Timestamp of most recent commit, normalized to Unix Epoch format.
pub unix_timestamp: Option<i64>,
}
@ -41,6 +42,7 @@ pub trait GitRepository: Send + Sync {
fn branches(&self) -> Result<Vec<Branch>>;
fn change_branch(&self, _: &str) -> Result<()>;
fn create_branch(&self, _: &str) -> Result<()>;
fn branch_exits(&self, _: &str) -> Result<bool>;
fn blame(&self, path: &Path, content: Rope) -> Result<crate::blame::Blame>;
}
@ -132,6 +134,18 @@ impl GitRepository for RealGitRepository {
GitStatus::new(&self.git_binary_path, &working_directory, path_prefixes)
}
fn branch_exits(&self, name: &str) -> Result<bool> {
let repo = self.repository.lock();
let branch = repo.find_branch(name, BranchType::Local);
match branch {
Ok(_) => Ok(true),
Err(e) => match e.code() {
git2::ErrorCode::NotFound => Ok(false),
_ => Err(anyhow::anyhow!(e)),
},
}
}
fn branches(&self) -> Result<Vec<Branch>> {
let repo = self.repository.lock();
let local_branches = repo.branches(Some(BranchType::Local))?;
@ -139,7 +153,11 @@ impl GitRepository for RealGitRepository {
.filter_map(|branch| {
branch.ok().and_then(|(branch, _)| {
let is_head = branch.is_head();
let name = branch.name().ok().flatten().map(Box::from)?;
let name = branch
.name()
.ok()
.flatten()
.map(|name| name.to_string().into())?;
let timestamp = branch.get().peel_to_commit().ok()?.time();
let unix_timestamp = timestamp.seconds();
let timezone_offset = timestamp.offset_minutes();
@ -201,17 +219,20 @@ impl GitRepository for RealGitRepository {
}
}
#[derive(Debug, Clone, Default)]
#[derive(Debug, Clone)]
pub struct FakeGitRepository {
state: Arc<Mutex<FakeGitRepositoryState>>,
}
#[derive(Debug, Clone, Default)]
#[derive(Debug, Clone)]
pub struct FakeGitRepositoryState {
pub path: PathBuf,
pub event_emitter: smol::channel::Sender<PathBuf>,
pub index_contents: HashMap<PathBuf, String>,
pub blames: HashMap<PathBuf, Blame>,
pub worktree_statuses: HashMap<RepoPath, GitFileStatus>,
pub branch_name: Option<String>,
pub current_branch_name: Option<String>,
pub branches: HashSet<String>,
}
impl FakeGitRepository {
@ -220,6 +241,20 @@ impl FakeGitRepository {
}
}
impl FakeGitRepositoryState {
pub fn new(path: PathBuf, event_emitter: smol::channel::Sender<PathBuf>) -> Self {
FakeGitRepositoryState {
path,
event_emitter,
index_contents: Default::default(),
blames: Default::default(),
worktree_statuses: Default::default(),
current_branch_name: Default::default(),
branches: Default::default(),
}
}
}
impl GitRepository for FakeGitRepository {
fn reload_index(&self) {}
@ -234,7 +269,7 @@ impl GitRepository for FakeGitRepository {
fn branch_name(&self) -> Option<String> {
let state = self.state.lock();
state.branch_name.clone()
state.current_branch_name.clone()
}
fn head_sha(&self) -> Option<String> {
@ -264,18 +299,41 @@ impl GitRepository for FakeGitRepository {
}
fn branches(&self) -> Result<Vec<Branch>> {
Ok(vec![])
let state = self.state.lock();
let current_branch = &state.current_branch_name;
Ok(state
.branches
.iter()
.map(|branch_name| Branch {
is_head: Some(branch_name) == current_branch.as_ref(),
name: branch_name.into(),
unix_timestamp: None,
})
.collect())
}
fn branch_exits(&self, name: &str) -> Result<bool> {
let state = self.state.lock();
Ok(state.branches.contains(name))
}
fn change_branch(&self, name: &str) -> Result<()> {
let mut state = self.state.lock();
state.branch_name = Some(name.to_owned());
state.current_branch_name = Some(name.to_owned());
state
.event_emitter
.try_send(state.path.clone())
.expect("Dropped repo change event");
Ok(())
}
fn create_branch(&self, name: &str) -> Result<()> {
let mut state = self.state.lock();
state.branch_name = Some(name.to_owned());
state.branches.insert(name.to_owned());
state
.event_emitter
.try_send(state.path.clone())
.expect("Dropped repo change event");
Ok(())
}