Git context menu (#24844)
Adds the non-entry specific right click menu to the panel, and the features contained therin: * Stage all * Discard Tracked Changes * Trash Untracked Files Also changes the naming from "Changes"/"New" to better match Git's terminology (though not convinced on this, it was awkward to describe "Discard Changes" without a way to distinguish between the changes and the files containing them). Release Notes: - N/A
This commit is contained in:
parent
bd105a5fc7
commit
be83074243
9 changed files with 488 additions and 82 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -5370,6 +5370,7 @@ dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"settings",
|
"settings",
|
||||||
|
"strum",
|
||||||
"theme",
|
"theme",
|
||||||
"time",
|
"time",
|
||||||
"ui",
|
"ui",
|
||||||
|
|
|
@ -397,6 +397,7 @@ impl Server {
|
||||||
.add_request_handler(forward_mutating_project_request::<proto::Commit>)
|
.add_request_handler(forward_mutating_project_request::<proto::Commit>)
|
||||||
.add_request_handler(forward_read_only_project_request::<proto::GitShow>)
|
.add_request_handler(forward_read_only_project_request::<proto::GitShow>)
|
||||||
.add_request_handler(forward_read_only_project_request::<proto::GitReset>)
|
.add_request_handler(forward_read_only_project_request::<proto::GitReset>)
|
||||||
|
.add_request_handler(forward_read_only_project_request::<proto::GitCheckoutFiles>)
|
||||||
.add_request_handler(forward_mutating_project_request::<proto::SetIndexText>)
|
.add_request_handler(forward_mutating_project_request::<proto::SetIndexText>)
|
||||||
.add_request_handler(forward_mutating_project_request::<proto::OpenCommitMessageBuffer>)
|
.add_request_handler(forward_mutating_project_request::<proto::OpenCommitMessageBuffer>)
|
||||||
.add_message_handler(broadcast_project_message_from_host::<proto::AdvertiseContexts>)
|
.add_message_handler(broadcast_project_message_from_host::<proto::AdvertiseContexts>)
|
||||||
|
|
|
@ -37,7 +37,8 @@ actions!(
|
||||||
// editor::RevertSelectedHunks
|
// editor::RevertSelectedHunks
|
||||||
StageAll,
|
StageAll,
|
||||||
UnstageAll,
|
UnstageAll,
|
||||||
RevertAll,
|
DiscardTrackedChanges,
|
||||||
|
TrashUntrackedFiles,
|
||||||
Uncommit,
|
Uncommit,
|
||||||
Commit,
|
Commit,
|
||||||
ClearCommitMessage
|
ClearCommitMessage
|
||||||
|
|
|
@ -111,6 +111,7 @@ pub trait GitRepository: Send + Sync {
|
||||||
fn branch_exits(&self, _: &str) -> Result<bool>;
|
fn branch_exits(&self, _: &str) -> Result<bool>;
|
||||||
|
|
||||||
fn reset(&self, commit: &str, mode: ResetMode) -> Result<()>;
|
fn reset(&self, commit: &str, mode: ResetMode) -> Result<()>;
|
||||||
|
fn checkout_files(&self, commit: &str, paths: &[RepoPath]) -> Result<()>;
|
||||||
|
|
||||||
fn show(&self, commit: &str) -> Result<CommitDetails>;
|
fn show(&self, commit: &str) -> Result<CommitDetails>;
|
||||||
|
|
||||||
|
@ -233,6 +234,31 @@ impl GitRepository for RealGitRepository {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn checkout_files(&self, commit: &str, paths: &[RepoPath]) -> Result<()> {
|
||||||
|
if paths.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let working_directory = self
|
||||||
|
.repository
|
||||||
|
.lock()
|
||||||
|
.workdir()
|
||||||
|
.context("failed to read git work directory")?
|
||||||
|
.to_path_buf();
|
||||||
|
|
||||||
|
let output = new_std_command(&self.git_binary_path)
|
||||||
|
.current_dir(&working_directory)
|
||||||
|
.args(["checkout", commit, "--"])
|
||||||
|
.args(paths.iter().map(|path| path.as_ref()))
|
||||||
|
.output()?;
|
||||||
|
if !output.status.success() {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"Failed to checkout files:\n{}",
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn load_index_text(&self, path: &RepoPath) -> Option<String> {
|
fn load_index_text(&self, path: &RepoPath) -> Option<String> {
|
||||||
fn logic(repo: &git2::Repository, path: &RepoPath) -> Result<Option<String>> {
|
fn logic(repo: &git2::Repository, path: &RepoPath) -> Result<Option<String>> {
|
||||||
const STAGE_NORMAL: i32 = 0;
|
const STAGE_NORMAL: i32 = 0;
|
||||||
|
@ -617,6 +643,10 @@ impl GitRepository for FakeGitRepository {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn checkout_files(&self, _: &str, _: &[RepoPath]) -> Result<()> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
fn path(&self) -> PathBuf {
|
fn path(&self) -> PathBuf {
|
||||||
let state = self.state.lock();
|
let state = self.state.lock();
|
||||||
state.path.clone()
|
state.path.clone()
|
||||||
|
|
|
@ -36,6 +36,7 @@ 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
|
||||||
|
strum.workspace = true
|
||||||
theme.workspace = true
|
theme.workspace = true
|
||||||
time.workspace = true
|
time.workspace = true
|
||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use crate::git_panel_settings::StatusStyle;
|
use crate::git_panel_settings::StatusStyle;
|
||||||
use crate::repository_selector::RepositorySelectorPopoverMenu;
|
use crate::repository_selector::RepositorySelectorPopoverMenu;
|
||||||
use crate::ProjectDiff;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
git_panel_settings::GitPanelSettings, git_status_icon, repository_selector::RepositorySelector,
|
git_panel_settings::GitPanelSettings, git_status_icon, repository_selector::RepositorySelector,
|
||||||
};
|
};
|
||||||
|
use crate::{project_diff, ProjectDiff};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use db::kvp::KEY_VALUE_STORE;
|
use db::kvp::KEY_VALUE_STORE;
|
||||||
use editor::commit_tooltip::CommitTooltip;
|
use editor::commit_tooltip::CommitTooltip;
|
||||||
|
@ -13,6 +13,7 @@ use editor::{
|
||||||
};
|
};
|
||||||
use git::repository::{CommitDetails, ResetMode};
|
use git::repository::{CommitDetails, ResetMode};
|
||||||
use git::{repository::RepoPath, status::FileStatus, Commit, ToggleStaged};
|
use git::{repository::RepoPath, status::FileStatus, Commit, ToggleStaged};
|
||||||
|
use git::{DiscardTrackedChanges, StageAll, TrashUntrackedFiles, UnstageAll};
|
||||||
use gpui::*;
|
use gpui::*;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use language::{markdown, Buffer, File, ParsedMarkdown};
|
use language::{markdown, Buffer, File, ParsedMarkdown};
|
||||||
|
@ -26,13 +27,13 @@ use project::{
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::Settings as _;
|
use settings::Settings as _;
|
||||||
use std::{collections::HashSet, path::PathBuf, sync::Arc, time::Duration, usize};
|
use std::{collections::HashSet, path::PathBuf, sync::Arc, time::Duration, usize};
|
||||||
|
use strum::{IntoEnumIterator, VariantNames};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use ui::{
|
use ui::{
|
||||||
prelude::*, ButtonLike, Checkbox, ContextMenu, Divider, DividerColor, ElevationIndex, ListItem,
|
prelude::*, ButtonLike, Checkbox, ContextMenu, Divider, DividerColor, ElevationIndex, ListItem,
|
||||||
ListItemSpacing, Scrollbar, ScrollbarState, Tooltip,
|
ListItemSpacing, Scrollbar, ScrollbarState, Tooltip,
|
||||||
};
|
};
|
||||||
use util::{maybe, ResultExt, TryFutureExt};
|
use util::{maybe, ResultExt, TryFutureExt};
|
||||||
use workspace::SaveIntent;
|
|
||||||
use workspace::{
|
use workspace::{
|
||||||
dock::{DockPosition, Panel, PanelEvent},
|
dock::{DockPosition, Panel, PanelEvent},
|
||||||
notifications::{DetachAndPromptErr, NotificationId},
|
notifications::{DetachAndPromptErr, NotificationId},
|
||||||
|
@ -51,6 +52,21 @@ actions!(
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
fn prompt<T>(msg: &str, detail: Option<&str>, window: &mut Window, cx: &mut App) -> Task<Result<T>>
|
||||||
|
where
|
||||||
|
T: IntoEnumIterator + VariantNames + 'static,
|
||||||
|
{
|
||||||
|
let rx = window.prompt(PromptLevel::Info, msg, detail, &T::VARIANTS, cx);
|
||||||
|
cx.spawn(|_| async move { Ok(T::iter().nth(rx.await?).unwrap()) })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(strum::EnumIter, strum::VariantNames)]
|
||||||
|
#[strum(serialize_all = "title_case")]
|
||||||
|
enum TrashCancel {
|
||||||
|
Trash,
|
||||||
|
Cancel,
|
||||||
|
}
|
||||||
|
|
||||||
const GIT_PANEL_KEY: &str = "GitPanel";
|
const GIT_PANEL_KEY: &str = "GitPanel";
|
||||||
|
|
||||||
const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
|
const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
|
||||||
|
@ -112,8 +128,8 @@ impl GitHeaderEntry {
|
||||||
pub fn title(&self) -> &'static str {
|
pub fn title(&self) -> &'static str {
|
||||||
match self.header {
|
match self.header {
|
||||||
Section::Conflict => "Conflicts",
|
Section::Conflict => "Conflicts",
|
||||||
Section::Tracked => "Changes",
|
Section::Tracked => "Tracked",
|
||||||
Section::New => "New",
|
Section::New => "Untracked",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,9 +158,17 @@ pub struct GitStatusEntry {
|
||||||
pub(crate) is_staged: Option<bool>,
|
pub(crate) is_staged: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
enum TargetStatus {
|
||||||
|
Staged,
|
||||||
|
Unstaged,
|
||||||
|
Reverted,
|
||||||
|
Unchanged,
|
||||||
|
}
|
||||||
|
|
||||||
struct PendingOperation {
|
struct PendingOperation {
|
||||||
finished: bool,
|
finished: bool,
|
||||||
will_become_staged: bool,
|
target_status: TargetStatus,
|
||||||
repo_paths: HashSet<RepoPath>,
|
repo_paths: HashSet<RepoPath>,
|
||||||
op_id: usize,
|
op_id: usize,
|
||||||
}
|
}
|
||||||
|
@ -599,7 +623,7 @@ impl GitPanel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn revert(
|
fn revert_selected(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &editor::actions::RevertFile,
|
_: &editor::actions::RevertFile,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
|
@ -608,28 +632,37 @@ impl GitPanel {
|
||||||
maybe!({
|
maybe!({
|
||||||
let list_entry = self.entries.get(self.selected_entry?)?.clone();
|
let list_entry = self.entries.get(self.selected_entry?)?.clone();
|
||||||
let entry = list_entry.status_entry()?;
|
let entry = list_entry.status_entry()?;
|
||||||
let active_repo = self.active_repository.as_ref()?;
|
self.revert_entry(&entry, window, cx);
|
||||||
|
Some(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn revert_entry(
|
||||||
|
&mut self,
|
||||||
|
entry: &GitStatusEntry,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
maybe!({
|
||||||
|
let active_repo = self.active_repository.clone()?;
|
||||||
let path = active_repo
|
let path = active_repo
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.repo_path_to_project_path(&entry.repo_path)?;
|
.repo_path_to_project_path(&entry.repo_path)?;
|
||||||
let workspace = self.workspace.clone();
|
let workspace = self.workspace.clone();
|
||||||
|
|
||||||
if entry.status.is_staged() != Some(false) {
|
if entry.status.is_staged() != Some(false) {
|
||||||
self.update_staging_area_for_entries(false, vec![entry.repo_path.clone()], cx);
|
self.perform_stage(false, vec![entry.repo_path.clone()], cx);
|
||||||
}
|
}
|
||||||
|
let filename = path.path.file_name()?.to_string_lossy();
|
||||||
|
|
||||||
if entry.status.is_created() {
|
if !entry.status.is_created() {
|
||||||
let prompt = window.prompt(
|
self.perform_checkout(vec![entry.repo_path.clone()], cx);
|
||||||
PromptLevel::Info,
|
} else {
|
||||||
"Do you want to trash this file?",
|
let prompt = prompt(&format!("Trash {}?", filename), None, window, cx);
|
||||||
None,
|
|
||||||
&["Trash", "Cancel"],
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
cx.spawn_in(window, |_, mut cx| async move {
|
cx.spawn_in(window, |_, mut cx| async move {
|
||||||
match prompt.await {
|
match prompt.await? {
|
||||||
Ok(0) => {}
|
TrashCancel::Trash => {}
|
||||||
_ => return Ok(()),
|
TrashCancel::Cancel => return Ok(()),
|
||||||
}
|
}
|
||||||
let task = workspace.update(&mut cx, |workspace, cx| {
|
let task = workspace.update(&mut cx, |workspace, cx| {
|
||||||
workspace
|
workspace
|
||||||
|
@ -647,45 +680,235 @@ impl GitPanel {
|
||||||
cx,
|
cx,
|
||||||
|e, _, _| Some(format!("{e}")),
|
|e, _, _| Some(format!("{e}")),
|
||||||
);
|
);
|
||||||
return Some(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let open_path = workspace.update(cx, |workspace, cx| {
|
|
||||||
workspace.open_path_preview(path, None, true, false, window, cx)
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.spawn_in(window, |_, mut cx| async move {
|
|
||||||
let item = open_path?.await?;
|
|
||||||
let editor = cx.update(|_, cx| {
|
|
||||||
item.act_as::<Editor>(cx)
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("didn't open editor"))
|
|
||||||
})??;
|
|
||||||
|
|
||||||
if let Some(task) =
|
|
||||||
editor.update(&mut cx, |editor, _| editor.wait_for_diff_to_load())?
|
|
||||||
{
|
|
||||||
task.await
|
|
||||||
};
|
|
||||||
|
|
||||||
editor.update_in(&mut cx, |editor, window, cx| {
|
|
||||||
editor.revert_file(&Default::default(), window, cx);
|
|
||||||
})?;
|
|
||||||
|
|
||||||
workspace
|
|
||||||
.update_in(&mut cx, |workspace, window, cx| {
|
|
||||||
workspace.save_active_item(SaveIntent::Save, window, cx)
|
|
||||||
})?
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.detach_and_prompt_err("Failed to open file", window, cx, |e, _, _| {
|
|
||||||
Some(format!("{e}"))
|
|
||||||
});
|
|
||||||
|
|
||||||
Some(())
|
Some(())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn perform_checkout(&mut self, repo_paths: Vec<RepoPath>, cx: &mut Context<Self>) {
|
||||||
|
let workspace = self.workspace.clone();
|
||||||
|
let Some(active_repository) = self.active_repository.clone() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let op_id = self.pending.iter().map(|p| p.op_id).max().unwrap_or(0) + 1;
|
||||||
|
self.pending.push(PendingOperation {
|
||||||
|
op_id,
|
||||||
|
target_status: TargetStatus::Reverted,
|
||||||
|
repo_paths: repo_paths.iter().cloned().collect(),
|
||||||
|
finished: false,
|
||||||
|
});
|
||||||
|
self.update_visible_entries(cx);
|
||||||
|
let task = cx.spawn(|_, mut cx| async move {
|
||||||
|
let tasks: Vec<_> = workspace.update(&mut cx, |workspace, cx| {
|
||||||
|
workspace.project().update(cx, |project, cx| {
|
||||||
|
repo_paths
|
||||||
|
.iter()
|
||||||
|
.filter_map(|repo_path| {
|
||||||
|
let path = active_repository
|
||||||
|
.read(cx)
|
||||||
|
.repo_path_to_project_path(&repo_path)?;
|
||||||
|
Some(project.open_buffer(path, cx))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let buffers = futures::future::join_all(tasks).await;
|
||||||
|
|
||||||
|
active_repository
|
||||||
|
.update(&mut cx, |repo, _| repo.checkout_files("HEAD", repo_paths))?
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let tasks: Vec<_> = cx.update(|cx| {
|
||||||
|
buffers
|
||||||
|
.iter()
|
||||||
|
.filter_map(|buffer| {
|
||||||
|
buffer.as_ref().ok()?.update(cx, |buffer, cx| {
|
||||||
|
buffer.is_dirty().then(|| buffer.reload(cx))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
})?;
|
||||||
|
|
||||||
|
futures::future::join_all(tasks).await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.spawn(|this, mut cx| async move {
|
||||||
|
let result = task.await;
|
||||||
|
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
for pending in this.pending.iter_mut() {
|
||||||
|
if pending.op_id == op_id {
|
||||||
|
pending.finished = true;
|
||||||
|
if result.is_err() {
|
||||||
|
pending.target_status = TargetStatus::Unchanged;
|
||||||
|
this.update_visible_entries(cx);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
.map_err(|e| {
|
||||||
|
this.show_err_toast(e, cx);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn discard_tracked_changes(
|
||||||
|
&mut self,
|
||||||
|
_: &DiscardTrackedChanges,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
let entries = self
|
||||||
|
.entries
|
||||||
|
.iter()
|
||||||
|
.filter_map(|entry| entry.status_entry().cloned())
|
||||||
|
.filter(|status_entry| !status_entry.status.is_created())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
match entries.len() {
|
||||||
|
0 => return,
|
||||||
|
1 => return self.revert_entry(&entries[0], window, cx),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
let details = entries
|
||||||
|
.iter()
|
||||||
|
.filter_map(|entry| entry.repo_path.0.file_name())
|
||||||
|
.map(|filename| filename.to_string_lossy())
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
#[derive(strum::EnumIter, strum::VariantNames)]
|
||||||
|
#[strum(serialize_all = "title_case")]
|
||||||
|
enum DiscardCancel {
|
||||||
|
DiscardTrackedChanges,
|
||||||
|
Cancel,
|
||||||
|
}
|
||||||
|
let prompt = prompt(
|
||||||
|
"Discard changes to these files?",
|
||||||
|
Some(&details),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
cx.spawn(|this, mut cx| async move {
|
||||||
|
match prompt.await {
|
||||||
|
Ok(DiscardCancel::DiscardTrackedChanges) => {
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
let repo_paths = entries.into_iter().map(|entry| entry.repo_path).collect();
|
||||||
|
this.perform_checkout(repo_paths, cx);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clean_all(&mut self, _: &TrashUntrackedFiles, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
let workspace = self.workspace.clone();
|
||||||
|
let Some(active_repo) = self.active_repository.clone() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let to_delete = self
|
||||||
|
.entries
|
||||||
|
.iter()
|
||||||
|
.filter_map(|entry| entry.status_entry())
|
||||||
|
.filter(|status_entry| status_entry.status.is_created())
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
match to_delete.len() {
|
||||||
|
0 => return,
|
||||||
|
1 => return self.revert_entry(&to_delete[0], window, cx),
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
let details = to_delete
|
||||||
|
.iter()
|
||||||
|
.map(|entry| {
|
||||||
|
entry
|
||||||
|
.repo_path
|
||||||
|
.0
|
||||||
|
.file_name()
|
||||||
|
.map(|f| f.to_string_lossy())
|
||||||
|
.unwrap_or_default()
|
||||||
|
})
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
let prompt = prompt("Trash these files?", Some(&details), window, cx);
|
||||||
|
cx.spawn_in(window, |this, mut cx| async move {
|
||||||
|
match prompt.await? {
|
||||||
|
TrashCancel::Trash => {}
|
||||||
|
TrashCancel::Cancel => return Ok(()),
|
||||||
|
}
|
||||||
|
let tasks = workspace.update(&mut cx, |workspace, cx| {
|
||||||
|
to_delete
|
||||||
|
.iter()
|
||||||
|
.filter_map(|entry| {
|
||||||
|
workspace.project().update(cx, |project, cx| {
|
||||||
|
let project_path = active_repo
|
||||||
|
.read(cx)
|
||||||
|
.repo_path_to_project_path(&entry.repo_path)?;
|
||||||
|
project.delete_file(project_path, true, cx)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})?;
|
||||||
|
let to_unstage = to_delete
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|entry| {
|
||||||
|
if entry.status.is_staged() != Some(false) {
|
||||||
|
Some(entry.repo_path.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.perform_stage(false, to_unstage, cx)
|
||||||
|
})?;
|
||||||
|
for task in tasks {
|
||||||
|
task.await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.detach_and_prompt_err("Failed to trash files", window, cx, |e, _, _| {
|
||||||
|
Some(format!("{e}"))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stage_all(&mut self, _: &StageAll, _window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
let repo_paths = self
|
||||||
|
.entries
|
||||||
|
.iter()
|
||||||
|
.filter_map(|entry| entry.status_entry())
|
||||||
|
.filter(|status_entry| status_entry.is_staged != Some(true))
|
||||||
|
.map(|status_entry| status_entry.repo_path.clone())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
self.perform_stage(true, repo_paths, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unstage_all(&mut self, _: &UnstageAll, _window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
let repo_paths = self
|
||||||
|
.entries
|
||||||
|
.iter()
|
||||||
|
.filter_map(|entry| entry.status_entry())
|
||||||
|
.filter(|status_entry| status_entry.is_staged != Some(false))
|
||||||
|
.map(|status_entry| status_entry.repo_path.clone())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
self.perform_stage(false, repo_paths, cx);
|
||||||
|
}
|
||||||
|
|
||||||
fn toggle_staged_for_entry(
|
fn toggle_staged_for_entry(
|
||||||
&mut self,
|
&mut self,
|
||||||
entry: &GitListEntry,
|
entry: &GitListEntry,
|
||||||
|
@ -720,22 +943,21 @@ impl GitPanel {
|
||||||
(goal_staged_state, entries)
|
(goal_staged_state, entries)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.update_staging_area_for_entries(stage, repo_paths, cx);
|
self.perform_stage(stage, repo_paths, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_staging_area_for_entries(
|
fn perform_stage(&mut self, stage: bool, repo_paths: Vec<RepoPath>, cx: &mut Context<Self>) {
|
||||||
&mut self,
|
|
||||||
stage: bool,
|
|
||||||
repo_paths: Vec<RepoPath>,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
let Some(active_repository) = self.active_repository.clone() else {
|
let Some(active_repository) = self.active_repository.clone() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let op_id = self.pending.iter().map(|p| p.op_id).max().unwrap_or(0) + 1;
|
let op_id = self.pending.iter().map(|p| p.op_id).max().unwrap_or(0) + 1;
|
||||||
self.pending.push(PendingOperation {
|
self.pending.push(PendingOperation {
|
||||||
op_id,
|
op_id,
|
||||||
will_become_staged: stage,
|
target_status: if stage {
|
||||||
|
TargetStatus::Staged
|
||||||
|
} else {
|
||||||
|
TargetStatus::Unstaged
|
||||||
|
},
|
||||||
repo_paths: repo_paths.iter().cloned().collect(),
|
repo_paths: repo_paths.iter().cloned().collect(),
|
||||||
finished: false,
|
finished: false,
|
||||||
});
|
});
|
||||||
|
@ -1105,6 +1327,14 @@ impl GitPanel {
|
||||||
let is_new = entry.status.is_created();
|
let is_new = entry.status.is_created();
|
||||||
let is_staged = entry.status.is_staged();
|
let is_staged = entry.status.is_staged();
|
||||||
|
|
||||||
|
if self.pending.iter().any(|pending| {
|
||||||
|
pending.target_status == TargetStatus::Reverted
|
||||||
|
&& !pending.finished
|
||||||
|
&& pending.repo_paths.contains(&entry.repo_path)
|
||||||
|
}) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let display_name = if difference > 1 {
|
let display_name = if difference > 1 {
|
||||||
// Show partial path for deeply nested files
|
// Show partial path for deeply nested files
|
||||||
entry
|
entry
|
||||||
|
@ -1236,7 +1466,12 @@ impl GitPanel {
|
||||||
fn entry_is_staged(&self, entry: &GitStatusEntry) -> Option<bool> {
|
fn entry_is_staged(&self, entry: &GitStatusEntry) -> Option<bool> {
|
||||||
for pending in self.pending.iter().rev() {
|
for pending in self.pending.iter().rev() {
|
||||||
if pending.repo_paths.contains(&entry.repo_path) {
|
if pending.repo_paths.contains(&entry.repo_path) {
|
||||||
return Some(pending.will_become_staged);
|
match pending.target_status {
|
||||||
|
TargetStatus::Staged => return Some(true),
|
||||||
|
TargetStatus::Unstaged => return Some(false),
|
||||||
|
TargetStatus::Reverted => continue,
|
||||||
|
TargetStatus::Unchanged => continue,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
entry.is_staged
|
entry.is_staged
|
||||||
|
@ -1248,6 +1483,10 @@ impl GitPanel {
|
||||||
|| self.conflicted_staged_count > 0
|
|| self.conflicted_staged_count > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn has_conflicts(&self) -> bool {
|
||||||
|
self.conflicted_count > 0
|
||||||
|
}
|
||||||
|
|
||||||
fn has_tracked_changes(&self) -> bool {
|
fn has_tracked_changes(&self) -> bool {
|
||||||
self.tracked_count > 0
|
self.tracked_count > 0
|
||||||
}
|
}
|
||||||
|
@ -1316,10 +1555,17 @@ impl GitPanel {
|
||||||
.is_above_project()
|
.is_above_project()
|
||||||
});
|
});
|
||||||
|
|
||||||
self.panel_header_container(window, cx)
|
self.panel_header_container(window, cx).when(
|
||||||
.when(all_repositories.len() > 1 || has_repo_above, |el| {
|
all_repositories.len() > 1 || has_repo_above,
|
||||||
el.child(self.render_repository_selector(cx))
|
|el| {
|
||||||
})
|
el.child(
|
||||||
|
Label::new("Repository")
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
)
|
||||||
|
.child(self.render_repository_selector(cx))
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_repository_selector(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
pub fn render_repository_selector(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
@ -1701,6 +1947,12 @@ impl GitPanel {
|
||||||
.with_horizontal_sizing_behavior(ListHorizontalSizingBehavior::Unconstrained)
|
.with_horizontal_sizing_behavior(ListHorizontalSizingBehavior::Unconstrained)
|
||||||
.track_scroll(self.scroll_handle.clone()),
|
.track_scroll(self.scroll_handle.clone()),
|
||||||
)
|
)
|
||||||
|
.on_mouse_down(
|
||||||
|
MouseButton::Right,
|
||||||
|
cx.listener(move |this, event: &MouseDownEvent, window, cx| {
|
||||||
|
this.deploy_panel_context_menu(event.position, window, cx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
.children(self.render_scrollbar(cx))
|
.children(self.render_scrollbar(cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1743,7 +1995,7 @@ impl GitPanel {
|
||||||
repo.update(cx, |repo, cx| repo.show(sha, cx))
|
repo.update(cx, |repo, cx| repo.show(sha, cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deploy_context_menu(
|
fn deploy_entry_context_menu(
|
||||||
&mut self,
|
&mut self,
|
||||||
position: Point<Pixels>,
|
position: Point<Pixels>,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
|
@ -1768,7 +2020,38 @@ impl GitPanel {
|
||||||
.action("Open Diff", Confirm.boxed_clone())
|
.action("Open Diff", Confirm.boxed_clone())
|
||||||
.action("Open File", SecondaryConfirm.boxed_clone())
|
.action("Open File", SecondaryConfirm.boxed_clone())
|
||||||
});
|
});
|
||||||
|
self.selected_entry = Some(ix);
|
||||||
|
self.set_context_menu(context_menu, position, window, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deploy_panel_context_menu(
|
||||||
|
&mut self,
|
||||||
|
position: Point<Pixels>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
let context_menu = ContextMenu::build(window, cx, |context_menu, _, _| {
|
||||||
|
context_menu
|
||||||
|
.action("Stage All", StageAll.boxed_clone())
|
||||||
|
.action("Unstage All", UnstageAll.boxed_clone())
|
||||||
|
.action("Open Diff", project_diff::Diff.boxed_clone())
|
||||||
|
.separator()
|
||||||
|
.action(
|
||||||
|
"Discard Tracked Changes",
|
||||||
|
DiscardTrackedChanges.boxed_clone(),
|
||||||
|
)
|
||||||
|
.action("Trash Untracked Files", TrashUntrackedFiles.boxed_clone())
|
||||||
|
});
|
||||||
|
self.set_context_menu(context_menu, position, window, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_context_menu(
|
||||||
|
&mut self,
|
||||||
|
context_menu: Entity<ContextMenu>,
|
||||||
|
position: Point<Pixels>,
|
||||||
|
window: &Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
let subscription = cx.subscribe_in(
|
let subscription = cx.subscribe_in(
|
||||||
&context_menu,
|
&context_menu,
|
||||||
window,
|
window,
|
||||||
|
@ -1782,7 +2065,6 @@ impl GitPanel {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
self.selected_entry = Some(ix);
|
|
||||||
self.context_menu = Some((context_menu, position, subscription));
|
self.context_menu = Some((context_menu, position, subscription));
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
@ -1834,14 +2116,14 @@ impl GitPanel {
|
||||||
|
|
||||||
let mut is_staged: ToggleState = self.entry_is_staged(entry).into();
|
let mut is_staged: ToggleState = self.entry_is_staged(entry).into();
|
||||||
|
|
||||||
if !self.has_staged_changes() && !entry.status.is_created() {
|
if !self.has_staged_changes() && !self.has_conflicts() && !entry.status.is_created() {
|
||||||
is_staged = ToggleState::Selected;
|
is_staged = ToggleState::Selected;
|
||||||
}
|
}
|
||||||
|
|
||||||
let checkbox = Checkbox::new(id, is_staged)
|
let checkbox = Checkbox::new(id, is_staged)
|
||||||
.disabled(!has_write_access)
|
.disabled(!has_write_access)
|
||||||
.fill()
|
.fill()
|
||||||
.placeholder(!self.has_staged_changes())
|
.placeholder(!self.has_staged_changes() && !self.has_conflicts())
|
||||||
.elevation(ElevationIndex::Surface)
|
.elevation(ElevationIndex::Surface)
|
||||||
.on_click({
|
.on_click({
|
||||||
let entry = entry.clone();
|
let entry = entry.clone();
|
||||||
|
@ -1888,7 +2170,8 @@ impl GitPanel {
|
||||||
})
|
})
|
||||||
.on_secondary_mouse_down(cx.listener(
|
.on_secondary_mouse_down(cx.listener(
|
||||||
move |this, event: &MouseDownEvent, window, cx| {
|
move |this, event: &MouseDownEvent, window, cx| {
|
||||||
this.deploy_context_menu(event.position, ix, window, cx)
|
this.deploy_entry_context_menu(event.position, ix, window, cx);
|
||||||
|
cx.stop_propagation();
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
.child(
|
.child(
|
||||||
|
@ -1921,12 +2204,7 @@ impl GitPanel {
|
||||||
impl Render for GitPanel {
|
impl Render for GitPanel {
|
||||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let project = self.project.read(cx);
|
let project = self.project.read(cx);
|
||||||
let has_entries = self
|
let has_entries = self.entries.len() > 0;
|
||||||
.active_repository
|
|
||||||
.as_ref()
|
|
||||||
.map_or(false, |active_repository| {
|
|
||||||
active_repository.read(cx).entry_count() > 0
|
|
||||||
});
|
|
||||||
let room = self
|
let room = self
|
||||||
.workspace
|
.workspace
|
||||||
.upgrade()
|
.upgrade()
|
||||||
|
@ -1959,10 +2237,14 @@ impl Render for GitPanel {
|
||||||
.on_action(cx.listener(Self::close_panel))
|
.on_action(cx.listener(Self::close_panel))
|
||||||
.on_action(cx.listener(Self::open_diff))
|
.on_action(cx.listener(Self::open_diff))
|
||||||
.on_action(cx.listener(Self::open_file))
|
.on_action(cx.listener(Self::open_file))
|
||||||
.on_action(cx.listener(Self::revert))
|
.on_action(cx.listener(Self::revert_selected))
|
||||||
.on_action(cx.listener(Self::focus_changes_list))
|
.on_action(cx.listener(Self::focus_changes_list))
|
||||||
.on_action(cx.listener(Self::focus_editor))
|
.on_action(cx.listener(Self::focus_editor))
|
||||||
.on_action(cx.listener(Self::toggle_staged_for_selected))
|
.on_action(cx.listener(Self::toggle_staged_for_selected))
|
||||||
|
.on_action(cx.listener(Self::stage_all))
|
||||||
|
.on_action(cx.listener(Self::unstage_all))
|
||||||
|
.on_action(cx.listener(Self::discard_tracked_changes))
|
||||||
|
.on_action(cx.listener(Self::clean_all))
|
||||||
.when(has_write_access && has_co_authors, |git_panel| {
|
.when(has_write_access && has_co_authors, |git_panel| {
|
||||||
git_panel.on_action(cx.listener(Self::toggle_fill_co_authors))
|
git_panel.on_action(cx.listener(Self::toggle_fill_co_authors))
|
||||||
})
|
})
|
||||||
|
|
|
@ -65,6 +65,11 @@ pub enum Message {
|
||||||
commit: SharedString,
|
commit: SharedString,
|
||||||
reset_mode: ResetMode,
|
reset_mode: ResetMode,
|
||||||
},
|
},
|
||||||
|
CheckoutFiles {
|
||||||
|
repo: GitRepo,
|
||||||
|
commit: SharedString,
|
||||||
|
paths: Vec<RepoPath>,
|
||||||
|
},
|
||||||
Stage(GitRepo, Vec<RepoPath>),
|
Stage(GitRepo, Vec<RepoPath>),
|
||||||
Unstage(GitRepo, Vec<RepoPath>),
|
Unstage(GitRepo, Vec<RepoPath>),
|
||||||
SetIndexText(GitRepo, RepoPath, Option<String>),
|
SetIndexText(GitRepo, RepoPath, Option<String>),
|
||||||
|
@ -106,6 +111,7 @@ impl GitStore {
|
||||||
client.add_entity_request_handler(Self::handle_commit);
|
client.add_entity_request_handler(Self::handle_commit);
|
||||||
client.add_entity_request_handler(Self::handle_reset);
|
client.add_entity_request_handler(Self::handle_reset);
|
||||||
client.add_entity_request_handler(Self::handle_show);
|
client.add_entity_request_handler(Self::handle_show);
|
||||||
|
client.add_entity_request_handler(Self::handle_checkout_files);
|
||||||
client.add_entity_request_handler(Self::handle_open_commit_message_buffer);
|
client.add_entity_request_handler(Self::handle_open_commit_message_buffer);
|
||||||
client.add_entity_request_handler(Self::handle_set_index_text);
|
client.add_entity_request_handler(Self::handle_set_index_text);
|
||||||
}
|
}
|
||||||
|
@ -121,8 +127,6 @@ impl GitStore {
|
||||||
event: &WorktreeStoreEvent,
|
event: &WorktreeStoreEvent,
|
||||||
cx: &mut Context<'_, Self>,
|
cx: &mut Context<'_, Self>,
|
||||||
) {
|
) {
|
||||||
// TODO inspect the event
|
|
||||||
|
|
||||||
let mut new_repositories = Vec::new();
|
let mut new_repositories = Vec::new();
|
||||||
let mut new_active_index = None;
|
let mut new_active_index = None;
|
||||||
let this = cx.weak_entity();
|
let this = cx.weak_entity();
|
||||||
|
@ -282,6 +286,36 @@ impl GitStore {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Message::CheckoutFiles {
|
||||||
|
repo,
|
||||||
|
commit,
|
||||||
|
paths,
|
||||||
|
} => {
|
||||||
|
match repo {
|
||||||
|
GitRepo::Local(repo) => repo.checkout_files(&commit, &paths)?,
|
||||||
|
GitRepo::Remote {
|
||||||
|
project_id,
|
||||||
|
client,
|
||||||
|
worktree_id,
|
||||||
|
work_directory_id,
|
||||||
|
} => {
|
||||||
|
client
|
||||||
|
.request(proto::GitCheckoutFiles {
|
||||||
|
project_id: project_id.0,
|
||||||
|
worktree_id: worktree_id.to_proto(),
|
||||||
|
work_directory_id: work_directory_id.to_proto(),
|
||||||
|
commit: commit.into(),
|
||||||
|
paths: paths
|
||||||
|
.into_iter()
|
||||||
|
.map(|p| p.to_string_lossy().to_string())
|
||||||
|
.collect(),
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Message::Unstage(repo, paths) => {
|
Message::Unstage(repo, paths) => {
|
||||||
match repo {
|
match repo {
|
||||||
GitRepo::Local(repo) => repo.unstage_paths(&paths)?,
|
GitRepo::Local(repo) => repo.unstage_paths(&paths)?,
|
||||||
|
@ -502,6 +536,30 @@ impl GitStore {
|
||||||
Ok(proto::Ack {})
|
Ok(proto::Ack {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_checkout_files(
|
||||||
|
this: Entity<Self>,
|
||||||
|
envelope: TypedEnvelope<proto::GitCheckoutFiles>,
|
||||||
|
mut cx: AsyncApp,
|
||||||
|
) -> Result<proto::Ack> {
|
||||||
|
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||||
|
let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
|
||||||
|
let repository_handle =
|
||||||
|
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
|
||||||
|
let paths = envelope
|
||||||
|
.payload
|
||||||
|
.paths
|
||||||
|
.iter()
|
||||||
|
.map(|s| RepoPath::from_str(s))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
repository_handle
|
||||||
|
.update(&mut cx, |repository_handle, _| {
|
||||||
|
repository_handle.checkout_files(&envelope.payload.commit, paths)
|
||||||
|
})?
|
||||||
|
.await??;
|
||||||
|
Ok(proto::Ack {})
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_open_commit_message_buffer(
|
async fn handle_open_commit_message_buffer(
|
||||||
this: Entity<Self>,
|
this: Entity<Self>,
|
||||||
envelope: TypedEnvelope<proto::OpenCommitMessageBuffer>,
|
envelope: TypedEnvelope<proto::OpenCommitMessageBuffer>,
|
||||||
|
@ -712,6 +770,26 @@ impl Repository {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn checkout_files(
|
||||||
|
&self,
|
||||||
|
commit: &str,
|
||||||
|
paths: Vec<RepoPath>,
|
||||||
|
) -> oneshot::Receiver<Result<()>> {
|
||||||
|
let (result_tx, result_rx) = futures::channel::oneshot::channel();
|
||||||
|
let commit = commit.to_string().into();
|
||||||
|
self.update_sender
|
||||||
|
.unbounded_send((
|
||||||
|
Message::CheckoutFiles {
|
||||||
|
repo: self.git_repo.clone(),
|
||||||
|
commit,
|
||||||
|
paths,
|
||||||
|
},
|
||||||
|
result_tx,
|
||||||
|
))
|
||||||
|
.ok();
|
||||||
|
result_rx
|
||||||
|
}
|
||||||
|
|
||||||
pub fn reset(&self, commit: &str, reset_mode: ResetMode) -> oneshot::Receiver<Result<()>> {
|
pub fn reset(&self, commit: &str, reset_mode: ResetMode) -> oneshot::Receiver<Result<()>> {
|
||||||
let (result_tx, result_rx) = futures::channel::oneshot::channel();
|
let (result_tx, result_rx) = futures::channel::oneshot::channel();
|
||||||
let commit = commit.to_string().into();
|
let commit = commit.to_string().into();
|
||||||
|
|
|
@ -320,7 +320,8 @@ message Envelope {
|
||||||
GitReset git_reset = 301;
|
GitReset git_reset = 301;
|
||||||
GitCommitDetails git_commit_details = 302;
|
GitCommitDetails git_commit_details = 302;
|
||||||
|
|
||||||
SetIndexText set_index_text = 299; // current max
|
SetIndexText set_index_text = 299;
|
||||||
|
GitCheckoutFiles git_checkout_files = 303; // current max
|
||||||
}
|
}
|
||||||
|
|
||||||
reserved 87 to 88;
|
reserved 87 to 88;
|
||||||
|
@ -2688,6 +2689,14 @@ message GitReset {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message GitCheckoutFiles {
|
||||||
|
uint64 project_id = 1;
|
||||||
|
uint64 worktree_id = 2;
|
||||||
|
uint64 work_directory_id = 3;
|
||||||
|
string commit = 4;
|
||||||
|
repeated string paths = 5;
|
||||||
|
}
|
||||||
|
|
||||||
message GetPanicFilesResponse {
|
message GetPanicFilesResponse {
|
||||||
repeated string file_contents = 2;
|
repeated string file_contents = 2;
|
||||||
}
|
}
|
||||||
|
|
|
@ -441,6 +441,7 @@ messages!(
|
||||||
(InstallExtension, Background),
|
(InstallExtension, Background),
|
||||||
(RegisterBufferWithLanguageServers, Background),
|
(RegisterBufferWithLanguageServers, Background),
|
||||||
(GitReset, Background),
|
(GitReset, Background),
|
||||||
|
(GitCheckoutFiles, Background),
|
||||||
(GitShow, Background),
|
(GitShow, Background),
|
||||||
(GitCommitDetails, Background),
|
(GitCommitDetails, Background),
|
||||||
(SetIndexText, Background),
|
(SetIndexText, Background),
|
||||||
|
@ -579,6 +580,7 @@ request_messages!(
|
||||||
(RegisterBufferWithLanguageServers, Ack),
|
(RegisterBufferWithLanguageServers, Ack),
|
||||||
(GitShow, GitCommitDetails),
|
(GitShow, GitCommitDetails),
|
||||||
(GitReset, Ack),
|
(GitReset, Ack),
|
||||||
|
(GitCheckoutFiles, Ack),
|
||||||
(SetIndexText, Ack),
|
(SetIndexText, Ack),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -674,6 +676,7 @@ entity_messages!(
|
||||||
RegisterBufferWithLanguageServers,
|
RegisterBufferWithLanguageServers,
|
||||||
GitShow,
|
GitShow,
|
||||||
GitReset,
|
GitReset,
|
||||||
|
GitCheckoutFiles,
|
||||||
SetIndexText,
|
SetIndexText,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue