mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-24 11:01:54 +00:00
Move git state to Project (#23208)
This restores its visibility outside of git_ui, which we'll need soon, while preserving its per-project character. Release Notes: - N/A
This commit is contained in:
parent
614eaec278
commit
b7726238ad
10 changed files with 363 additions and 317 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -5129,7 +5129,6 @@ dependencies = [
|
||||||
"collections",
|
"collections",
|
||||||
"db",
|
"db",
|
||||||
"editor",
|
"editor",
|
||||||
"futures 0.3.31",
|
|
||||||
"git",
|
"git",
|
||||||
"gpui",
|
"gpui",
|
||||||
"language",
|
"language",
|
||||||
|
@ -5140,7 +5139,6 @@ dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"settings",
|
"settings",
|
||||||
"sum_tree",
|
|
||||||
"theme",
|
"theme",
|
||||||
"ui",
|
"ui",
|
||||||
"util",
|
"util",
|
||||||
|
|
|
@ -2942,7 +2942,7 @@ pub mod tests {
|
||||||
.update(cx, |editor, cx| {
|
.update(cx, |editor, cx| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
vec!["main hint #0".to_string(), "other hint #0".to_string()],
|
vec!["main hint #0".to_string(), "other hint #0".to_string()],
|
||||||
cached_hint_labels(editor),
|
sorted_cached_hint_labels(editor),
|
||||||
"Cache should update for both excerpts despite hints display was disabled"
|
"Cache should update for both excerpts despite hints display was disabled"
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
|
|
|
@ -7,6 +7,7 @@ pub mod repository;
|
||||||
pub mod status;
|
pub mod status;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
use gpui::actions;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
@ -24,6 +25,24 @@ pub static FSMONITOR_DAEMON: LazyLock<&'static OsStr> =
|
||||||
LazyLock::new(|| OsStr::new("fsmonitor--daemon"));
|
LazyLock::new(|| OsStr::new("fsmonitor--daemon"));
|
||||||
pub static GITIGNORE: LazyLock<&'static OsStr> = LazyLock::new(|| OsStr::new(".gitignore"));
|
pub static GITIGNORE: LazyLock<&'static OsStr> = LazyLock::new(|| OsStr::new(".gitignore"));
|
||||||
|
|
||||||
|
actions!(
|
||||||
|
git,
|
||||||
|
[
|
||||||
|
StageFile,
|
||||||
|
UnstageFile,
|
||||||
|
ToggleStaged,
|
||||||
|
// Revert actions are currently in the editor crate:
|
||||||
|
// editor::RevertFile,
|
||||||
|
// editor::RevertSelectedHunks
|
||||||
|
StageAll,
|
||||||
|
UnstageAll,
|
||||||
|
RevertAll,
|
||||||
|
CommitChanges,
|
||||||
|
CommitAllChanges,
|
||||||
|
ClearCommitMessage
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
|
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
|
||||||
pub struct Oid(libgit::Oid);
|
pub struct Oid(libgit::Oid);
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@ anyhow.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
db.workspace = true
|
db.workspace = true
|
||||||
editor.workspace = true
|
editor.workspace = true
|
||||||
futures.workspace = true
|
|
||||||
git.workspace = true
|
git.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
|
@ -28,7 +27,6 @@ serde.workspace = true
|
||||||
serde_derive.workspace = true
|
serde_derive.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
sum_tree.workspace = true
|
|
||||||
theme.workspace = true
|
theme.workspace = true
|
||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
use crate::git_panel_settings::StatusStyle;
|
use crate::git_panel_settings::StatusStyle;
|
||||||
use crate::{first_repository_in_project, first_worktree_repository};
|
use crate::{git_panel_settings::GitPanelSettings, git_status_icon};
|
||||||
use crate::{
|
|
||||||
git_panel_settings::GitPanelSettings, git_status_icon, CommitAllChanges, CommitChanges,
|
|
||||||
GitState, GitViewMode, RevertAll, StageAll, ToggleStaged, UnstageAll,
|
|
||||||
};
|
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use db::kvp::KEY_VALUE_STORE;
|
use db::kvp::KEY_VALUE_STORE;
|
||||||
use editor::scroll::ScrollbarAutoHide;
|
use editor::scroll::ScrollbarAutoHide;
|
||||||
use editor::{Editor, EditorSettings, ShowScrollbar};
|
use editor::{Editor, EditorSettings, ShowScrollbar};
|
||||||
use git::{repository::RepoPath, status::FileStatus};
|
use git::repository::{GitRepository, RepoPath};
|
||||||
|
use git::status::FileStatus;
|
||||||
|
use git::{CommitAllChanges, CommitChanges, RevertAll, StageAll, ToggleStaged, UnstageAll};
|
||||||
use gpui::*;
|
use gpui::*;
|
||||||
use language::Buffer;
|
use language::Buffer;
|
||||||
use menu::{SelectFirst, SelectLast, SelectNext, SelectPrev};
|
use menu::{SelectFirst, SelectLast, SelectNext, SelectPrev};
|
||||||
use project::{Fs, Project, ProjectPath};
|
use project::git::GitState;
|
||||||
|
use project::{Fs, Project, ProjectPath, WorktreeId};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::Settings as _;
|
use settings::Settings as _;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
@ -21,12 +20,13 @@ use theme::ThemeSettings;
|
||||||
use ui::{
|
use ui::{
|
||||||
prelude::*, Checkbox, Divider, DividerColor, ElevationIndex, Scrollbar, ScrollbarState, Tooltip,
|
prelude::*, Checkbox, Divider, DividerColor, ElevationIndex, Scrollbar, ScrollbarState, Tooltip,
|
||||||
};
|
};
|
||||||
use util::{ResultExt, TryFutureExt};
|
use util::{maybe, ResultExt, TryFutureExt};
|
||||||
use workspace::notifications::DetachAndPromptErr;
|
use workspace::notifications::DetachAndPromptErr;
|
||||||
use workspace::{
|
use workspace::{
|
||||||
dock::{DockPosition, Panel, PanelEvent},
|
dock::{DockPosition, Panel, PanelEvent},
|
||||||
Workspace,
|
Workspace,
|
||||||
};
|
};
|
||||||
|
use worktree::RepositoryEntry;
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
git_panel,
|
git_panel,
|
||||||
|
@ -87,7 +87,6 @@ pub struct GitPanel {
|
||||||
selected_entry: Option<usize>,
|
selected_entry: Option<usize>,
|
||||||
show_scrollbar: bool,
|
show_scrollbar: bool,
|
||||||
rebuild_requested: Arc<AtomicBool>,
|
rebuild_requested: Arc<AtomicBool>,
|
||||||
git_state: GitState,
|
|
||||||
commit_editor: View<Editor>,
|
commit_editor: View<Editor>,
|
||||||
/// The visible entries in the list, accounting for folding & expanded state.
|
/// The visible entries in the list, accounting for folding & expanded state.
|
||||||
///
|
///
|
||||||
|
@ -99,6 +98,44 @@ pub struct GitPanel {
|
||||||
reveal_in_editor: Task<()>,
|
reveal_in_editor: Task<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn first_worktree_repository(
|
||||||
|
project: &Model<Project>,
|
||||||
|
worktree_id: WorktreeId,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) -> Option<(RepositoryEntry, Arc<dyn GitRepository>)> {
|
||||||
|
project
|
||||||
|
.read(cx)
|
||||||
|
.worktree_for_id(worktree_id, cx)
|
||||||
|
.and_then(|worktree| {
|
||||||
|
let snapshot = worktree.read(cx).snapshot();
|
||||||
|
let repo = snapshot.repositories().iter().next()?.clone();
|
||||||
|
let git_repo = worktree
|
||||||
|
.read(cx)
|
||||||
|
.as_local()?
|
||||||
|
.get_local_repo(&repo)?
|
||||||
|
.repo()
|
||||||
|
.clone();
|
||||||
|
Some((repo, git_repo))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn first_repository_in_project(
|
||||||
|
project: &Model<Project>,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) -> Option<(WorktreeId, RepositoryEntry, Arc<dyn GitRepository>)> {
|
||||||
|
project.read(cx).worktrees(cx).next().and_then(|worktree| {
|
||||||
|
let snapshot = worktree.read(cx).snapshot();
|
||||||
|
let repo = snapshot.repositories().iter().next()?.clone();
|
||||||
|
let git_repo = worktree
|
||||||
|
.read(cx)
|
||||||
|
.as_local()?
|
||||||
|
.get_local_repo(&repo)?
|
||||||
|
.repo()
|
||||||
|
.clone();
|
||||||
|
Some((snapshot.id(), repo, git_repo))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
impl GitPanel {
|
impl GitPanel {
|
||||||
pub fn load(
|
pub fn load(
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
|
@ -110,9 +147,11 @@ impl GitPanel {
|
||||||
pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
|
pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
|
||||||
let fs = workspace.app_state().fs.clone();
|
let fs = workspace.app_state().fs.clone();
|
||||||
let project = workspace.project().clone();
|
let project = workspace.project().clone();
|
||||||
|
let git_state = project.read(cx).git_state().cloned();
|
||||||
let language_registry = workspace.app_state().languages.clone();
|
let language_registry = workspace.app_state().languages.clone();
|
||||||
let mut git_state = GitState::new(cx);
|
let current_commit_message = git_state
|
||||||
let current_commit_message = git_state.commit_message.clone();
|
.as_ref()
|
||||||
|
.and_then(|git_state| git_state.read(cx).commit_message.clone());
|
||||||
|
|
||||||
let git_panel = cx.new_view(|cx: &mut ViewContext<Self>| {
|
let git_panel = cx.new_view(|cx: &mut ViewContext<Self>| {
|
||||||
let focus_handle = cx.focus_handle();
|
let focus_handle = cx.focus_handle();
|
||||||
|
@ -124,17 +163,20 @@ impl GitPanel {
|
||||||
cx.subscribe(&project, move |this, project, event, cx| {
|
cx.subscribe(&project, move |this, project, event, cx| {
|
||||||
use project::Event;
|
use project::Event;
|
||||||
|
|
||||||
let git_state = &mut this.git_state;
|
|
||||||
let first_worktree_id = project.read(cx).worktrees(cx).next().map(|worktree| {
|
let first_worktree_id = project.read(cx).worktrees(cx).next().map(|worktree| {
|
||||||
let snapshot = worktree.read(cx).snapshot();
|
let snapshot = worktree.read(cx).snapshot();
|
||||||
snapshot.id()
|
snapshot.id()
|
||||||
});
|
});
|
||||||
let first_repo_in_project = first_repository_in_project(&project, cx);
|
let first_repo_in_project = first_repository_in_project(&project, cx);
|
||||||
|
|
||||||
|
let Some(git_state) = project.read(cx).git_state().cloned() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
git_state.update(cx, |git_state, _| {
|
||||||
match event {
|
match event {
|
||||||
project::Event::WorktreeRemoved(id) => {
|
project::Event::WorktreeRemoved(id) => {
|
||||||
git_state.all_repositories.remove(id);
|
let Some((worktree_id, _, _)) = git_state.active_repository.as_ref()
|
||||||
let Some((worktree_id, _, _)) = git_state.active_repository.as_ref() else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if worktree_id == id {
|
if worktree_id == id {
|
||||||
|
@ -156,14 +198,7 @@ impl GitPanel {
|
||||||
this.schedule_update();
|
this.schedule_update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::WorktreeAdded(id) => {
|
Event::WorktreeAdded(_) => {
|
||||||
let Some(worktree) = project.read(cx).worktree_for_id(*id, cx) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let snapshot = worktree.read(cx).snapshot();
|
|
||||||
git_state
|
|
||||||
.all_repositories
|
|
||||||
.insert(*id, snapshot.repositories().clone());
|
|
||||||
let Some(first_id) = first_worktree_id else {
|
let Some(first_id) = first_worktree_id else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
@ -196,10 +231,10 @@ impl GitPanel {
|
||||||
project::Event::Closed => {
|
project::Event::Closed => {
|
||||||
this.reveal_in_editor = Task::ready(());
|
this.reveal_in_editor = Task::ready(());
|
||||||
this.visible_entries.clear();
|
this.visible_entries.clear();
|
||||||
// TODO cancel/clear task?
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
|
@ -259,10 +294,12 @@ impl GitPanel {
|
||||||
if let Some(first_worktree) = first_worktree {
|
if let Some(first_worktree) = first_worktree {
|
||||||
let snapshot = first_worktree.read(cx).snapshot();
|
let snapshot = first_worktree.read(cx).snapshot();
|
||||||
|
|
||||||
if let Some((repo, git_repo)) =
|
if let Some(((repo, git_repo), git_state)) =
|
||||||
first_worktree_repository(&project, snapshot.id(), cx)
|
first_worktree_repository(&project, snapshot.id(), cx).zip(git_state)
|
||||||
{
|
{
|
||||||
|
git_state.update(cx, |git_state, _| {
|
||||||
git_state.activate_repository(snapshot.id(), repo, git_repo);
|
git_state.activate_repository(snapshot.id(), repo, git_repo);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -300,7 +337,6 @@ impl GitPanel {
|
||||||
hide_scrollbar_task: None,
|
hide_scrollbar_task: None,
|
||||||
rebuild_requested,
|
rebuild_requested,
|
||||||
commit_editor,
|
commit_editor,
|
||||||
git_state,
|
|
||||||
reveal_in_editor: Task::ready(()),
|
reveal_in_editor: Task::ready(()),
|
||||||
project,
|
project,
|
||||||
};
|
};
|
||||||
|
@ -327,6 +363,19 @@ impl GitPanel {
|
||||||
git_panel
|
git_panel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn git_state<'a>(&self, cx: &'a AppContext) -> Option<&'a Model<GitState>> {
|
||||||
|
self.project.read(cx).git_state()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn active_repository<'a>(
|
||||||
|
&self,
|
||||||
|
cx: &'a AppContext,
|
||||||
|
) -> Option<&'a (WorktreeId, RepositoryEntry, Arc<dyn GitRepository>)> {
|
||||||
|
let git_state = self.git_state(cx)?;
|
||||||
|
let active_repository = git_state.read(cx).active_repository.as_ref()?;
|
||||||
|
Some(active_repository)
|
||||||
|
}
|
||||||
|
|
||||||
fn serialize(&mut self, cx: &mut ViewContext<Self>) {
|
fn serialize(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
// TODO: we can store stage status here
|
// TODO: we can store stage status here
|
||||||
let width = self.width;
|
let width = self.width;
|
||||||
|
@ -549,14 +598,20 @@ impl GitPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_staged_for_entry(&mut self, entry: &GitListEntry, cx: &mut ViewContext<Self>) {
|
fn toggle_staged_for_entry(&mut self, entry: &GitListEntry, cx: &mut ViewContext<Self>) {
|
||||||
match entry.status.is_staged() {
|
let Some(git_state) = self.git_state(cx).cloned() else {
|
||||||
Some(true) | None => self.git_state.unstage_entry(entry.repo_path.clone()),
|
return;
|
||||||
Some(false) => self.git_state.stage_entry(entry.repo_path.clone()),
|
};
|
||||||
|
git_state.update(cx, |git_state, _| {
|
||||||
|
if entry.status.is_staged().unwrap_or(false) {
|
||||||
|
git_state.unstage_entry(entry.repo_path.clone());
|
||||||
|
} else {
|
||||||
|
git_state.stage_entry(entry.repo_path.clone());
|
||||||
}
|
}
|
||||||
|
});
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_staged_for_selected(&mut self, _: &ToggleStaged, cx: &mut ViewContext<Self>) {
|
fn toggle_staged_for_selected(&mut self, _: &git::ToggleStaged, cx: &mut ViewContext<Self>) {
|
||||||
if let Some(selected_entry) = self.get_selected_entry().cloned() {
|
if let Some(selected_entry) = self.get_selected_entry().cloned() {
|
||||||
self.toggle_staged_for_entry(&selected_entry, cx);
|
self.toggle_staged_for_entry(&selected_entry, cx);
|
||||||
}
|
}
|
||||||
|
@ -572,14 +627,12 @@ impl GitPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_entry(&self, entry: &GitListEntry, cx: &mut ViewContext<Self>) {
|
fn open_entry(&self, entry: &GitListEntry, cx: &mut ViewContext<Self>) {
|
||||||
let Some((worktree_id, path)) =
|
let Some((worktree_id, path)) = maybe!({
|
||||||
self.git_state
|
let git_state = self.git_state(cx)?;
|
||||||
.active_repository
|
let (id, repo, _) = git_state.read(cx).active_repository.as_ref()?;
|
||||||
.as_ref()
|
let path = repo.work_directory.unrelativize(&entry.repo_path)?;
|
||||||
.and_then(|(id, repo, _)| {
|
Some((*id, path))
|
||||||
Some((*id, repo.work_directory.unrelativize(&entry.repo_path)?))
|
}) else {
|
||||||
})
|
|
||||||
else {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let path = (worktree_id, path).into();
|
let path = (worktree_id, path).into();
|
||||||
|
@ -592,7 +645,7 @@ impl GitPanel {
|
||||||
cx.emit(Event::OpenedEntry { path });
|
cx.emit(Event::OpenedEntry { path });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stage_all(&mut self, _: &StageAll, _cx: &mut ViewContext<Self>) {
|
fn stage_all(&mut self, _: &git::StageAll, cx: &mut ViewContext<Self>) {
|
||||||
let to_stage = self
|
let to_stage = self
|
||||||
.visible_entries
|
.visible_entries
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
|
@ -603,31 +656,42 @@ impl GitPanel {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
self.all_staged = Some(true);
|
self.all_staged = Some(true);
|
||||||
self.git_state.stage_entries(to_stage);
|
let Some(git_state) = self.git_state(cx).cloned() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
git_state.update(cx, |git_state, _| git_state.stage_entries(to_stage));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unstage_all(&mut self, _: &UnstageAll, _cx: &mut ViewContext<Self>) {
|
fn unstage_all(&mut self, _: &git::UnstageAll, cx: &mut ViewContext<Self>) {
|
||||||
// This should only be called when all entries are staged.
|
// This should only be called when all entries are staged.
|
||||||
for entry in &mut self.visible_entries {
|
for entry in &mut self.visible_entries {
|
||||||
entry.is_staged = Some(false);
|
entry.is_staged = Some(false);
|
||||||
}
|
}
|
||||||
self.all_staged = Some(false);
|
self.all_staged = Some(false);
|
||||||
self.git_state.unstage_all();
|
let Some(git_state) = self.git_state(cx).cloned() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
git_state.update(cx, |git_state, _| git_state.unstage_all());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn discard_all(&mut self, _: &RevertAll, _cx: &mut ViewContext<Self>) {
|
fn discard_all(&mut self, _: &git::RevertAll, _cx: &mut ViewContext<Self>) {
|
||||||
// TODO: Implement discard all
|
// TODO: Implement discard all
|
||||||
println!("Discard all triggered");
|
println!("Discard all triggered");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_message(&mut self, cx: &mut ViewContext<Self>) {
|
fn clear_message(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
self.git_state.clear_commit_message();
|
let Some(git_state) = self.git_state(cx).cloned() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
git_state.update(cx, |git_state, _| {
|
||||||
|
git_state.clear_commit_message();
|
||||||
|
});
|
||||||
self.commit_editor
|
self.commit_editor
|
||||||
.update(cx, |editor, cx| editor.set_text("", cx));
|
.update(cx, |editor, cx| editor.set_text("", cx));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Commit all staged changes
|
/// Commit all staged changes
|
||||||
fn commit_changes(&mut self, _: &CommitChanges, cx: &mut ViewContext<Self>) {
|
fn commit_changes(&mut self, _: &git::CommitChanges, cx: &mut ViewContext<Self>) {
|
||||||
self.clear_message(cx);
|
self.clear_message(cx);
|
||||||
|
|
||||||
// TODO: Implement commit all staged
|
// TODO: Implement commit all staged
|
||||||
|
@ -635,7 +699,7 @@ impl GitPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Commit all changes, regardless of whether they are staged or not
|
/// Commit all changes, regardless of whether they are staged or not
|
||||||
fn commit_all_changes(&mut self, _: &CommitAllChanges, cx: &mut ViewContext<Self>) {
|
fn commit_all_changes(&mut self, _: &git::CommitAllChanges, cx: &mut ViewContext<Self>) {
|
||||||
self.clear_message(cx);
|
self.clear_message(cx);
|
||||||
|
|
||||||
// TODO: Implement commit all changes
|
// TODO: Implement commit all changes
|
||||||
|
@ -691,7 +755,7 @@ impl GitPanel {
|
||||||
fn update_visible_entries(&mut self, cx: &mut ViewContext<Self>) {
|
fn update_visible_entries(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
self.visible_entries.clear();
|
self.visible_entries.clear();
|
||||||
|
|
||||||
let Some((_, repo, _)) = self.git_state.active_repository().as_ref() else {
|
let Some((_, repo, _)) = self.active_repository(cx) else {
|
||||||
// Just clear entries if no repository is active.
|
// Just clear entries if no repository is active.
|
||||||
cx.notify();
|
cx.notify();
|
||||||
return;
|
return;
|
||||||
|
@ -764,7 +828,12 @@ impl GitPanel {
|
||||||
if let language::BufferEvent::Reparsed | language::BufferEvent::Edited = event {
|
if let language::BufferEvent::Reparsed | language::BufferEvent::Edited = event {
|
||||||
let commit_message = self.commit_editor.update(cx, |editor, cx| editor.text(cx));
|
let commit_message = self.commit_editor.update(cx, |editor, cx| editor.text(cx));
|
||||||
|
|
||||||
self.git_state.commit_message = Some(commit_message.into());
|
let Some(git_state) = self.git_state(cx).cloned() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
git_state.update(cx, |git_state, _| {
|
||||||
|
git_state.commit_message = Some(commit_message.into())
|
||||||
|
});
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
@ -1094,7 +1163,7 @@ impl GitPanel {
|
||||||
let entry_id = ElementId::Name(format!("entry_{}", entry_details.display_name).into());
|
let entry_id = ElementId::Name(format!("entry_{}", entry_details.display_name).into());
|
||||||
let checkbox_id =
|
let checkbox_id =
|
||||||
ElementId::Name(format!("checkbox_{}", entry_details.display_name).into());
|
ElementId::Name(format!("checkbox_{}", entry_details.display_name).into());
|
||||||
let view_mode = self.git_state.list_view_mode;
|
let is_tree_view = false;
|
||||||
let handle = cx.view().downgrade();
|
let handle = cx.view().downgrade();
|
||||||
|
|
||||||
let end_slot = h_flex()
|
let end_slot = h_flex()
|
||||||
|
@ -1125,7 +1194,7 @@ impl GitPanel {
|
||||||
this.hover(|this| this.bg(cx.theme().colors().ghost_element_hover))
|
this.hover(|this| this.bg(cx.theme().colors().ghost_element_hover))
|
||||||
});
|
});
|
||||||
|
|
||||||
if view_mode == GitViewMode::Tree {
|
if is_tree_view {
|
||||||
entry = entry.pl(px(8. + 12. * entry_details.depth as f32))
|
entry = entry.pl(px(8. + 12. * entry_details.depth as f32))
|
||||||
} else {
|
} else {
|
||||||
entry = entry.pl(px(8.))
|
entry = entry.pl(px(8.))
|
||||||
|
@ -1152,19 +1221,22 @@ impl GitPanel {
|
||||||
let Some(this) = handle.upgrade() else {
|
let Some(this) = handle.upgrade() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
this.update(cx, |this, _| {
|
this.update(cx, |this, cx| {
|
||||||
this.visible_entries[ix].is_staged = match *toggle {
|
this.visible_entries[ix].is_staged = match *toggle {
|
||||||
ToggleState::Selected => Some(true),
|
ToggleState::Selected => Some(true),
|
||||||
ToggleState::Unselected => Some(false),
|
ToggleState::Unselected => Some(false),
|
||||||
ToggleState::Indeterminate => None,
|
ToggleState::Indeterminate => None,
|
||||||
};
|
};
|
||||||
let repo_path = repo_path.clone();
|
let repo_path = repo_path.clone();
|
||||||
match toggle {
|
let Some(git_state) = this.git_state(cx).cloned() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
git_state.update(cx, |git_state, _| match toggle {
|
||||||
ToggleState::Selected | ToggleState::Indeterminate => {
|
ToggleState::Selected | ToggleState::Indeterminate => {
|
||||||
this.git_state.stage_entry(repo_path);
|
git_state.stage_entry(repo_path);
|
||||||
}
|
|
||||||
ToggleState::Unselected => this.git_state.unstage_entry(repo_path),
|
|
||||||
}
|
}
|
||||||
|
ToggleState::Unselected => git_state.unstage_entry(repo_path),
|
||||||
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -1,209 +1,16 @@
|
||||||
use ::settings::Settings;
|
use ::settings::Settings;
|
||||||
use collections::HashMap;
|
|
||||||
use futures::channel::mpsc;
|
|
||||||
use futures::StreamExt as _;
|
|
||||||
use git::repository::{GitRepository, RepoPath};
|
|
||||||
use git::status::FileStatus;
|
use git::status::FileStatus;
|
||||||
use git_panel_settings::GitPanelSettings;
|
use git_panel_settings::GitPanelSettings;
|
||||||
use gpui::{actions, AppContext, Hsla, Model};
|
use gpui::{AppContext, Hsla};
|
||||||
use project::{Project, WorktreeId};
|
use ui::{Color, Icon, IconName, IntoElement};
|
||||||
use std::sync::Arc;
|
|
||||||
use sum_tree::SumTree;
|
|
||||||
use ui::{Color, Icon, IconName, IntoElement, SharedString};
|
|
||||||
use util::ResultExt as _;
|
|
||||||
use worktree::RepositoryEntry;
|
|
||||||
|
|
||||||
pub mod git_panel;
|
pub mod git_panel;
|
||||||
mod git_panel_settings;
|
mod git_panel_settings;
|
||||||
|
|
||||||
actions!(
|
|
||||||
git,
|
|
||||||
[
|
|
||||||
StageFile,
|
|
||||||
UnstageFile,
|
|
||||||
ToggleStaged,
|
|
||||||
// Revert actions are currently in the editor crate:
|
|
||||||
// editor::RevertFile,
|
|
||||||
// editor::RevertSelectedHunks
|
|
||||||
StageAll,
|
|
||||||
UnstageAll,
|
|
||||||
RevertAll,
|
|
||||||
CommitChanges,
|
|
||||||
CommitAllChanges,
|
|
||||||
ClearCommitMessage
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
GitPanelSettings::register(cx);
|
GitPanelSettings::register(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
|
|
||||||
pub enum GitViewMode {
|
|
||||||
#[default]
|
|
||||||
List,
|
|
||||||
Tree,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
enum StatusAction {
|
|
||||||
Stage,
|
|
||||||
Unstage,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct GitState {
|
|
||||||
/// The current commit message being composed.
|
|
||||||
commit_message: Option<SharedString>,
|
|
||||||
|
|
||||||
/// When a git repository is selected, this is used to track which repository's changes
|
|
||||||
/// are currently being viewed or modified in the UI.
|
|
||||||
active_repository: Option<(WorktreeId, RepositoryEntry, Arc<dyn GitRepository>)>,
|
|
||||||
|
|
||||||
updater_tx: mpsc::UnboundedSender<(Arc<dyn GitRepository>, Vec<RepoPath>, StatusAction)>,
|
|
||||||
|
|
||||||
all_repositories: HashMap<WorktreeId, SumTree<RepositoryEntry>>,
|
|
||||||
|
|
||||||
list_view_mode: GitViewMode,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GitState {
|
|
||||||
pub fn new(cx: &AppContext) -> Self {
|
|
||||||
let (updater_tx, mut updater_rx) =
|
|
||||||
mpsc::unbounded::<(Arc<dyn GitRepository>, Vec<RepoPath>, StatusAction)>();
|
|
||||||
cx.spawn(|cx| async move {
|
|
||||||
while let Some((git_repo, paths, action)) = updater_rx.next().await {
|
|
||||||
cx.background_executor()
|
|
||||||
.spawn(async move {
|
|
||||||
match action {
|
|
||||||
StatusAction::Stage => git_repo.stage_paths(&paths),
|
|
||||||
StatusAction::Unstage => git_repo.unstage_paths(&paths),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.log_err();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
GitState {
|
|
||||||
commit_message: None,
|
|
||||||
active_repository: None,
|
|
||||||
updater_tx,
|
|
||||||
list_view_mode: GitViewMode::default(),
|
|
||||||
all_repositories: HashMap::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn activate_repository(
|
|
||||||
&mut self,
|
|
||||||
worktree_id: WorktreeId,
|
|
||||||
active_repository: RepositoryEntry,
|
|
||||||
git_repo: Arc<dyn GitRepository>,
|
|
||||||
) {
|
|
||||||
self.active_repository = Some((worktree_id, active_repository, git_repo));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn active_repository(
|
|
||||||
&self,
|
|
||||||
) -> Option<&(WorktreeId, RepositoryEntry, Arc<dyn GitRepository>)> {
|
|
||||||
self.active_repository.as_ref()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn commit_message(&mut self, message: Option<SharedString>) {
|
|
||||||
self.commit_message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear_commit_message(&mut self) {
|
|
||||||
self.commit_message = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stage_entry(&mut self, repo_path: RepoPath) {
|
|
||||||
if let Some((_, _, git_repo)) = self.active_repository.as_ref() {
|
|
||||||
let _ = self.updater_tx.unbounded_send((
|
|
||||||
git_repo.clone(),
|
|
||||||
vec![repo_path],
|
|
||||||
StatusAction::Stage,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unstage_entry(&mut self, repo_path: RepoPath) {
|
|
||||||
if let Some((_, _, git_repo)) = self.active_repository.as_ref() {
|
|
||||||
let _ = self.updater_tx.unbounded_send((
|
|
||||||
git_repo.clone(),
|
|
||||||
vec![repo_path],
|
|
||||||
StatusAction::Unstage,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stage_entries(&mut self, entries: Vec<RepoPath>) {
|
|
||||||
if let Some((_, _, git_repo)) = self.active_repository.as_ref() {
|
|
||||||
let _ =
|
|
||||||
self.updater_tx
|
|
||||||
.unbounded_send((git_repo.clone(), entries, StatusAction::Stage));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn act_on_all(&mut self, action: StatusAction) {
|
|
||||||
if let Some((_, active_repository, git_repo)) = self.active_repository.as_ref() {
|
|
||||||
let _ = self.updater_tx.unbounded_send((
|
|
||||||
git_repo.clone(),
|
|
||||||
active_repository
|
|
||||||
.status()
|
|
||||||
.map(|entry| entry.repo_path)
|
|
||||||
.collect(),
|
|
||||||
action,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stage_all(&mut self) {
|
|
||||||
self.act_on_all(StatusAction::Stage);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unstage_all(&mut self) {
|
|
||||||
self.act_on_all(StatusAction::Unstage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn first_worktree_repository(
|
|
||||||
project: &Model<Project>,
|
|
||||||
worktree_id: WorktreeId,
|
|
||||||
cx: &mut AppContext,
|
|
||||||
) -> Option<(RepositoryEntry, Arc<dyn GitRepository>)> {
|
|
||||||
project
|
|
||||||
.read(cx)
|
|
||||||
.worktree_for_id(worktree_id, cx)
|
|
||||||
.and_then(|worktree| {
|
|
||||||
let snapshot = worktree.read(cx).snapshot();
|
|
||||||
let repo = snapshot.repositories().iter().next()?.clone();
|
|
||||||
let git_repo = worktree
|
|
||||||
.read(cx)
|
|
||||||
.as_local()?
|
|
||||||
.get_local_repo(&repo)?
|
|
||||||
.repo()
|
|
||||||
.clone();
|
|
||||||
Some((repo, git_repo))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn first_repository_in_project(
|
|
||||||
project: &Model<Project>,
|
|
||||||
cx: &mut AppContext,
|
|
||||||
) -> Option<(WorktreeId, RepositoryEntry, Arc<dyn GitRepository>)> {
|
|
||||||
project.read(cx).worktrees(cx).next().and_then(|worktree| {
|
|
||||||
let snapshot = worktree.read(cx).snapshot();
|
|
||||||
let repo = snapshot.repositories().iter().next()?.clone();
|
|
||||||
let git_repo = worktree
|
|
||||||
.read(cx)
|
|
||||||
.as_local()?
|
|
||||||
.get_local_repo(&repo)?
|
|
||||||
.repo()
|
|
||||||
.clone();
|
|
||||||
Some((snapshot.id(), repo, git_repo))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const ADDED_COLOR: Hsla = Hsla {
|
const ADDED_COLOR: Hsla = Hsla {
|
||||||
h: 142. / 360.,
|
h: 142. / 360.,
|
||||||
s: 0.68,
|
s: 0.68,
|
||||||
|
|
124
crates/project/src/git.rs
Normal file
124
crates/project/src/git.rs
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use futures::channel::mpsc;
|
||||||
|
use futures::StreamExt as _;
|
||||||
|
use git::repository::{GitRepository, RepoPath};
|
||||||
|
use gpui::{AppContext, SharedString};
|
||||||
|
use settings::WorktreeId;
|
||||||
|
use util::ResultExt as _;
|
||||||
|
use worktree::RepositoryEntry;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum StatusAction {
|
||||||
|
Stage,
|
||||||
|
Unstage,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GitState {
|
||||||
|
/// The current commit message being composed.
|
||||||
|
pub commit_message: Option<SharedString>,
|
||||||
|
|
||||||
|
/// When a git repository is selected, this is used to track which repository's changes
|
||||||
|
/// are currently being viewed or modified in the UI.
|
||||||
|
pub active_repository: Option<(WorktreeId, RepositoryEntry, Arc<dyn GitRepository>)>,
|
||||||
|
|
||||||
|
pub update_sender: mpsc::UnboundedSender<(Arc<dyn GitRepository>, Vec<RepoPath>, StatusAction)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GitState {
|
||||||
|
pub fn new(cx: &AppContext) -> Self {
|
||||||
|
let (tx, mut rx) =
|
||||||
|
mpsc::unbounded::<(Arc<dyn GitRepository>, Vec<RepoPath>, StatusAction)>();
|
||||||
|
cx.spawn(|cx| async move {
|
||||||
|
while let Some((git_repo, paths, action)) = rx.next().await {
|
||||||
|
cx.background_executor()
|
||||||
|
.spawn(async move {
|
||||||
|
match action {
|
||||||
|
StatusAction::Stage => git_repo.stage_paths(&paths),
|
||||||
|
StatusAction::Unstage => git_repo.unstage_paths(&paths),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
GitState {
|
||||||
|
commit_message: None,
|
||||||
|
active_repository: None,
|
||||||
|
update_sender: tx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn activate_repository(
|
||||||
|
&mut self,
|
||||||
|
worktree_id: WorktreeId,
|
||||||
|
active_repository: RepositoryEntry,
|
||||||
|
git_repo: Arc<dyn GitRepository>,
|
||||||
|
) {
|
||||||
|
self.active_repository = Some((worktree_id, active_repository, git_repo));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn active_repository(
|
||||||
|
&self,
|
||||||
|
) -> Option<&(WorktreeId, RepositoryEntry, Arc<dyn GitRepository>)> {
|
||||||
|
self.active_repository.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn commit_message(&mut self, message: Option<SharedString>) {
|
||||||
|
self.commit_message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_commit_message(&mut self) {
|
||||||
|
self.commit_message = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stage_entry(&mut self, repo_path: RepoPath) {
|
||||||
|
if let Some((_, _, git_repo)) = self.active_repository.as_ref() {
|
||||||
|
let _ = self.update_sender.unbounded_send((
|
||||||
|
git_repo.clone(),
|
||||||
|
vec![repo_path],
|
||||||
|
StatusAction::Stage,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unstage_entry(&mut self, repo_path: RepoPath) {
|
||||||
|
if let Some((_, _, git_repo)) = self.active_repository.as_ref() {
|
||||||
|
let _ = self.update_sender.unbounded_send((
|
||||||
|
git_repo.clone(),
|
||||||
|
vec![repo_path],
|
||||||
|
StatusAction::Unstage,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stage_entries(&mut self, entries: Vec<RepoPath>) {
|
||||||
|
if let Some((_, _, git_repo)) = self.active_repository.as_ref() {
|
||||||
|
let _ =
|
||||||
|
self.update_sender
|
||||||
|
.unbounded_send((git_repo.clone(), entries, StatusAction::Stage));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn act_on_all(&mut self, action: StatusAction) {
|
||||||
|
if let Some((_, active_repository, git_repo)) = self.active_repository.as_ref() {
|
||||||
|
let _ = self.update_sender.unbounded_send((
|
||||||
|
git_repo.clone(),
|
||||||
|
active_repository
|
||||||
|
.status()
|
||||||
|
.map(|entry| entry.repo_path)
|
||||||
|
.collect(),
|
||||||
|
action,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stage_all(&mut self) {
|
||||||
|
self.act_on_all(StatusAction::Stage);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unstage_all(&mut self) {
|
||||||
|
self.act_on_all(StatusAction::Unstage);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ pub mod buffer_store;
|
||||||
mod color_extractor;
|
mod color_extractor;
|
||||||
pub mod connection_manager;
|
pub mod connection_manager;
|
||||||
pub mod debounced_delay;
|
pub mod debounced_delay;
|
||||||
|
pub mod git;
|
||||||
pub mod image_store;
|
pub mod image_store;
|
||||||
pub mod lsp_command;
|
pub mod lsp_command;
|
||||||
pub mod lsp_ext_command;
|
pub mod lsp_ext_command;
|
||||||
|
@ -24,6 +25,7 @@ pub use environment::EnvironmentErrorMessage;
|
||||||
pub mod search_history;
|
pub mod search_history;
|
||||||
mod yarn;
|
mod yarn;
|
||||||
|
|
||||||
|
use crate::git::GitState;
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
use buffer_store::{BufferChangeSet, BufferStore, BufferStoreEvent};
|
use buffer_store::{BufferChangeSet, BufferStore, BufferStoreEvent};
|
||||||
use client::{proto, Client, Collaborator, PendingEntitySubscription, TypedEnvelope, UserStore};
|
use client::{proto, Client, Collaborator, PendingEntitySubscription, TypedEnvelope, UserStore};
|
||||||
|
@ -39,7 +41,11 @@ use futures::{
|
||||||
pub use image_store::{ImageItem, ImageStore};
|
pub use image_store::{ImageItem, ImageStore};
|
||||||
use image_store::{ImageItemEvent, ImageStoreEvent};
|
use image_store::{ImageItemEvent, ImageStoreEvent};
|
||||||
|
|
||||||
use git::{blame::Blame, repository::GitRepository, status::FileStatus};
|
use ::git::{
|
||||||
|
blame::Blame,
|
||||||
|
repository::{Branch, GitRepository},
|
||||||
|
status::FileStatus,
|
||||||
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyModel, AppContext, AsyncAppContext, BorrowAppContext, Context as _, EventEmitter, Hsla,
|
AnyModel, AppContext, AsyncAppContext, BorrowAppContext, Context as _, EventEmitter, Hsla,
|
||||||
Model, ModelContext, SharedString, Task, WeakModel, WindowContext,
|
Model, ModelContext, SharedString, Task, WeakModel, WindowContext,
|
||||||
|
@ -148,6 +154,7 @@ pub struct Project {
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
ssh_client: Option<Model<SshRemoteClient>>,
|
ssh_client: Option<Model<SshRemoteClient>>,
|
||||||
client_state: ProjectClientState,
|
client_state: ProjectClientState,
|
||||||
|
git_state: Option<Model<GitState>>,
|
||||||
collaborators: HashMap<proto::PeerId, Collaborator>,
|
collaborators: HashMap<proto::PeerId, Collaborator>,
|
||||||
client_subscriptions: Vec<client::Subscription>,
|
client_subscriptions: Vec<client::Subscription>,
|
||||||
worktree_store: Model<WorktreeStore>,
|
worktree_store: Model<WorktreeStore>,
|
||||||
|
@ -685,6 +692,9 @@ impl Project {
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let git_state = Some(cx.new_model(|cx| GitState::new(cx)));
|
||||||
|
|
||||||
cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
|
cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
@ -696,6 +706,7 @@ impl Project {
|
||||||
lsp_store,
|
lsp_store,
|
||||||
join_project_response_message_id: 0,
|
join_project_response_message_id: 0,
|
||||||
client_state: ProjectClientState::Local,
|
client_state: ProjectClientState::Local,
|
||||||
|
git_state,
|
||||||
client_subscriptions: Vec::new(),
|
client_subscriptions: Vec::new(),
|
||||||
_subscriptions: vec![cx.on_release(Self::release)],
|
_subscriptions: vec![cx.on_release(Self::release)],
|
||||||
active_entry: None,
|
active_entry: None,
|
||||||
|
@ -814,6 +825,7 @@ impl Project {
|
||||||
lsp_store,
|
lsp_store,
|
||||||
join_project_response_message_id: 0,
|
join_project_response_message_id: 0,
|
||||||
client_state: ProjectClientState::Local,
|
client_state: ProjectClientState::Local,
|
||||||
|
git_state: None,
|
||||||
client_subscriptions: Vec::new(),
|
client_subscriptions: Vec::new(),
|
||||||
_subscriptions: vec![
|
_subscriptions: vec![
|
||||||
cx.on_release(Self::release),
|
cx.on_release(Self::release),
|
||||||
|
@ -1045,6 +1057,7 @@ impl Project {
|
||||||
remote_id,
|
remote_id,
|
||||||
replica_id,
|
replica_id,
|
||||||
},
|
},
|
||||||
|
git_state: None,
|
||||||
buffers_needing_diff: Default::default(),
|
buffers_needing_diff: Default::default(),
|
||||||
git_diff_debouncer: DebouncedDelay::new(),
|
git_diff_debouncer: DebouncedDelay::new(),
|
||||||
terminals: Terminals {
|
terminals: Terminals {
|
||||||
|
@ -3534,7 +3547,7 @@ impl Project {
|
||||||
&self,
|
&self,
|
||||||
project_path: ProjectPath,
|
project_path: ProjectPath,
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
) -> Task<Result<Vec<git::repository::Branch>>> {
|
) -> Task<Result<Vec<Branch>>> {
|
||||||
self.worktree_store().read(cx).branches(project_path, cx)
|
self.worktree_store().read(cx).branches(project_path, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4154,6 +4167,10 @@ impl Project {
|
||||||
pub fn buffer_store(&self) -> &Model<BufferStore> {
|
pub fn buffer_store(&self) -> &Model<BufferStore> {
|
||||||
&self.buffer_store
|
&self.buffer_store
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn git_state(&self) -> Option<&Model<GitState>> {
|
||||||
|
self.git_state.as_ref()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_code_actions(code_actions: &HashMap<String, bool>) -> Vec<lsp::CodeActionKind> {
|
fn deserialize_code_actions(code_actions: &HashMap<String, bool>) -> Vec<lsp::CodeActionKind> {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{Event, *};
|
use crate::{Event, *};
|
||||||
|
use ::git::diff::assert_hunks;
|
||||||
use fs::FakeFs;
|
use fs::FakeFs;
|
||||||
use futures::{future, StreamExt};
|
use futures::{future, StreamExt};
|
||||||
use git::diff::assert_hunks;
|
|
||||||
use gpui::{AppContext, SemanticVersion, UpdateGlobal};
|
use gpui::{AppContext, SemanticVersion, UpdateGlobal};
|
||||||
use http_client::Url;
|
use http_client::Url;
|
||||||
use language::{
|
use language::{
|
||||||
|
|
|
@ -2583,6 +2583,17 @@ impl Snapshot {
|
||||||
&self.repositories
|
&self.repositories
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn repositories_with_abs_paths(
|
||||||
|
&self,
|
||||||
|
) -> impl '_ + Iterator<Item = (&RepositoryEntry, PathBuf)> {
|
||||||
|
let base = self.abs_path();
|
||||||
|
self.repositories.iter().map(|repo| {
|
||||||
|
let path = repo.work_directory.location_in_repo.as_deref();
|
||||||
|
let path = path.unwrap_or(repo.work_directory.as_ref());
|
||||||
|
(repo, base.join(path))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the repository whose work directory corresponds to the given path.
|
/// Get the repository whose work directory corresponds to the given path.
|
||||||
pub(crate) fn repository(&self, work_directory: PathKey) -> Option<RepositoryEntry> {
|
pub(crate) fn repository(&self, work_directory: PathKey) -> Option<RepositoryEntry> {
|
||||||
self.repositories.get(&work_directory, &()).cloned()
|
self.repositories.get(&work_directory, &()).cloned()
|
||||||
|
|
Loading…
Reference in a new issue