git: Add create git worktree hook to task system (#51337)
### Summary
This PR starts work on adding basic hook support in the `TaskStore`. To
enable users to setup tasks that are ran when the agent panel creates a
new git worktree to start a thread in. It also adds a new task variable
called `ZED_MAIN_GIT_WORKTREE` that's the absolute path to the main repo
that the newly created linked worktree is based off of.
### Follow Ups
- Get this hook working with the git worktree picker as well
- Make a more general approach to the hook system in `TaskStore`
- Add `ZED_PORT_{1..10}` task variables
- Migrate the task context creation code from `task_ui` to the basic
context provider
Before you mark this PR as ready for review, make sure that you have:
- [ ] Added a solid test coverage and/or screenshots from doing manual
testing
- [x] Done a self-review taking into account security and performance
aspects
- [x] Aligned any UI changes with the [UI
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
Release Notes:
- N/A *or* Added/Fixed/Improved ...
---------
Co-authored-by: Remco Smits <djsmits12@gmail.com>
Co-authored-by: Richard Feldman <oss@rtfeldman.com>
This commit is contained in:
1 parent
fb5dbc0940
commit
5d9469280c
13 files changed
+259
-63
No files matched your search
+3
-3
@@ -4,13 +4,13 @@
|
||||
"command": "./script/clippy",
|
||||
"args": [],
|
||||
"allow_concurrent_runs": true,
|
||||
"use_new_terminal": false
|
||||
"use_new_terminal": false,
|
||||
},
|
||||
{
|
||||
"label": "cargo run --profile release-fast",
|
||||
"command": "cargo",
|
||||
"args": ["run", "--profile", "release-fast"],
|
||||
"allow_concurrent_runs": true,
|
||||
"use_new_terminal": false
|
||||
}
|
||||
"use_new_terminal": false,
|
||||
},
|
||||
]
|
||||
@@ -2588,7 +2588,7 @@ impl AgentPanel {
|
||||
anyhow::Ok(())
|
||||
});
|
||||
|
||||
self._worktree_creation_task = Some(cx.foreground_executor().spawn(async move {
|
||||
self._worktree_creation_task = Some(cx.background_spawn(async move {
|
||||
task.await.log_err();
|
||||
}));
|
||||
}
|
||||
@@ -2745,6 +2745,10 @@ impl AgentPanel {
|
||||
|
||||
new_window_handle.update(cx, |multi_workspace, window, cx| {
|
||||
multi_workspace.activate(new_workspace.clone(), window, cx);
|
||||
|
||||
new_workspace.update(cx, |workspace, cx| {
|
||||
workspace.run_create_worktree_tasks(window, cx);
|
||||
})
|
||||
})?;
|
||||
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
|
||||
@@ -1799,6 +1799,26 @@ impl GitStore {
|
||||
&self.repositories
|
||||
}
|
||||
|
||||
/// Returns the original (main) repository working directory for the given worktree.
|
||||
/// For normal checkouts this equals the worktree's own path; for linked
|
||||
/// worktrees it points back to the original repo.
|
||||
pub fn original_repo_path_for_worktree(
|
||||
&self,
|
||||
worktree_id: WorktreeId,
|
||||
cx: &App,
|
||||
) -> Option<Arc<Path>> {
|
||||
self.active_repo_id
|
||||
.iter()
|
||||
.chain(self.worktree_ids.keys())
|
||||
.find(|repo_id| {
|
||||
self.worktree_ids
|
||||
.get(repo_id)
|
||||
.is_some_and(|ids| ids.contains(&worktree_id))
|
||||
})
|
||||
.and_then(|repo_id| self.repositories.get(repo_id))
|
||||
.map(|repo| repo.read(cx).snapshot().original_repo_abs_path)
|
||||
}
|
||||
|
||||
pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
|
||||
let (repo, path) = self.repository_and_path_for_buffer_id(buffer_id, cx)?;
|
||||
let status = repo.read(cx).snapshot.status_for_path(&path)?;
|
||||
|
||||
@@ -1230,12 +1230,23 @@ impl Project {
|
||||
)
|
||||
});
|
||||
|
||||
let git_store = cx.new(|cx| {
|
||||
GitStore::local(
|
||||
&worktree_store,
|
||||
buffer_store.clone(),
|
||||
environment.clone(),
|
||||
fs.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let task_store = cx.new(|cx| {
|
||||
TaskStore::local(
|
||||
buffer_store.downgrade(),
|
||||
worktree_store.clone(),
|
||||
toolchain_store.read(cx).as_language_toolchain_store(),
|
||||
environment.clone(),
|
||||
git_store.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -1271,16 +1282,6 @@ impl Project {
|
||||
)
|
||||
});
|
||||
|
||||
let git_store = cx.new(|cx| {
|
||||
GitStore::local(
|
||||
&worktree_store,
|
||||
buffer_store.clone(),
|
||||
environment.clone(),
|
||||
fs.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let agent_server_store = cx.new(|cx| {
|
||||
AgentServerStore::local(
|
||||
node.clone(),
|
||||
@@ -1415,30 +1416,6 @@ impl Project {
|
||||
)
|
||||
});
|
||||
|
||||
let task_store = cx.new(|cx| {
|
||||
TaskStore::remote(
|
||||
buffer_store.downgrade(),
|
||||
worktree_store.clone(),
|
||||
toolchain_store.read(cx).as_language_toolchain_store(),
|
||||
remote.read(cx).proto_client(),
|
||||
REMOTE_SERVER_PROJECT_ID,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let settings_observer = cx.new(|cx| {
|
||||
SettingsObserver::new_remote(
|
||||
fs.clone(),
|
||||
worktree_store.clone(),
|
||||
task_store.clone(),
|
||||
Some(remote_proto.clone()),
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
cx.subscribe(&settings_observer, Self::on_settings_observer_event)
|
||||
.detach();
|
||||
|
||||
let context_server_store = cx.new(|cx| {
|
||||
ContextServerStore::remote(
|
||||
rpc::proto::REMOTE_SERVER_PROJECT_ID,
|
||||
@@ -1503,6 +1480,31 @@ impl Project {
|
||||
)
|
||||
});
|
||||
|
||||
let task_store = cx.new(|cx| {
|
||||
TaskStore::remote(
|
||||
buffer_store.downgrade(),
|
||||
worktree_store.clone(),
|
||||
toolchain_store.read(cx).as_language_toolchain_store(),
|
||||
remote.read(cx).proto_client(),
|
||||
REMOTE_SERVER_PROJECT_ID,
|
||||
git_store.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let settings_observer = cx.new(|cx| {
|
||||
SettingsObserver::new_remote(
|
||||
fs.clone(),
|
||||
worktree_store.clone(),
|
||||
task_store.clone(),
|
||||
Some(remote_proto.clone()),
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
cx.subscribe(&settings_observer, Self::on_settings_observer_event)
|
||||
.detach();
|
||||
|
||||
let agent_server_store = cx.new(|_| {
|
||||
AgentServerStore::remote(
|
||||
REMOTE_SERVER_PROJECT_ID,
|
||||
@@ -1732,6 +1734,17 @@ impl Project {
|
||||
)
|
||||
});
|
||||
|
||||
let git_store = cx.new(|cx| {
|
||||
GitStore::remote(
|
||||
// In this remote case we pass None for the environment
|
||||
&worktree_store,
|
||||
buffer_store.clone(),
|
||||
client.clone().into(),
|
||||
remote_id,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let task_store = cx.new(|cx| {
|
||||
if run_tasks {
|
||||
TaskStore::remote(
|
||||
@@ -1740,6 +1753,7 @@ impl Project {
|
||||
Arc::new(EmptyToolchainStore),
|
||||
client.clone().into(),
|
||||
remote_id,
|
||||
git_store.clone(),
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
@@ -1758,17 +1772,6 @@ impl Project {
|
||||
)
|
||||
});
|
||||
|
||||
let git_store = cx.new(|cx| {
|
||||
GitStore::remote(
|
||||
// In this remote case we pass None for the environment
|
||||
&worktree_store,
|
||||
buffer_store.clone(),
|
||||
client.clone().into(),
|
||||
remote_id,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let agent_server_store = cx.new(|_cx| AgentServerStore::collab());
|
||||
let replica_id = ReplicaId::new(response.payload.replica_id as u16);
|
||||
|
||||
|
||||
@@ -21,14 +21,14 @@ use lsp::{LanguageServerId, LanguageServerName};
|
||||
use paths::{debug_task_file_name, task_file_name};
|
||||
use settings::{InvalidSettingsError, parse_json_with_comments};
|
||||
use task::{
|
||||
DebugScenario, ResolvedTask, SharedTaskContext, TaskContext, TaskId, TaskTemplate,
|
||||
DebugScenario, ResolvedTask, SharedTaskContext, TaskContext, TaskHook, TaskId, TaskTemplate,
|
||||
TaskTemplates, TaskVariables, VariableName,
|
||||
};
|
||||
use text::{BufferId, Point, ToPoint};
|
||||
use util::{NumericPrefixWithSuffix, ResultExt as _, post_inc, rel_path::RelPath};
|
||||
use worktree::WorktreeId;
|
||||
|
||||
use crate::{task_store::TaskSettingsLocation, worktree_store::WorktreeStore};
|
||||
use crate::{git_store::GitStore, task_store::TaskSettingsLocation, worktree_store::WorktreeStore};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct DebugScenarioContext {
|
||||
@@ -644,6 +644,19 @@ impl Inventory {
|
||||
self.last_scheduled_tasks.retain(|(_, task)| &task.id != id);
|
||||
}
|
||||
|
||||
/// Returns all task templates (worktree and global) that have at least one
|
||||
/// hook in the provided set.
|
||||
pub fn templates_with_hooks(
|
||||
&self,
|
||||
hooks: &HashSet<TaskHook>,
|
||||
worktree: WorktreeId,
|
||||
) -> Vec<(TaskSourceKind, TaskTemplate)> {
|
||||
self.worktree_templates_from_settings(worktree)
|
||||
.chain(self.global_templates_from_settings())
|
||||
.filter(|(_, template)| !template.hooks.is_disjoint(hooks))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn global_templates_from_settings(
|
||||
&self,
|
||||
) -> impl '_ + Iterator<Item = (TaskSourceKind, TaskTemplate)> {
|
||||
@@ -918,11 +931,15 @@ fn task_variables_preference(task: &ResolvedTask) -> Reverse<usize> {
|
||||
/// Applied as a base for every custom [`ContextProvider`] unless explicitly oped out.
|
||||
pub struct BasicContextProvider {
|
||||
worktree_store: Entity<WorktreeStore>,
|
||||
git_store: Entity<GitStore>,
|
||||
}
|
||||
|
||||
impl BasicContextProvider {
|
||||
pub fn new(worktree_store: Entity<WorktreeStore>) -> Self {
|
||||
Self { worktree_store }
|
||||
pub fn new(worktree_store: Entity<WorktreeStore>, git_store: Entity<GitStore>) -> Self {
|
||||
Self {
|
||||
worktree_store,
|
||||
git_store,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1002,6 +1019,19 @@ impl ContextProvider for BasicContextProvider {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(worktree_id) = location.buffer.read(cx).file().map(|f| f.worktree_id(cx)) {
|
||||
if let Some(path) = self
|
||||
.git_store
|
||||
.read(cx)
|
||||
.original_repo_path_for_worktree(worktree_id, cx)
|
||||
{
|
||||
task_variables.insert(
|
||||
VariableName::MainGitWorktree,
|
||||
path.to_string_lossy().into_owned(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(current_file) = current_file {
|
||||
let path = current_file.abs_path(cx);
|
||||
if let Some(filename) = path.file_name().and_then(|f| f.to_str()) {
|
||||
|
||||
@@ -19,7 +19,7 @@ use util::ResultExt;
|
||||
|
||||
use crate::{
|
||||
BasicContextProvider, Inventory, ProjectEnvironment, buffer_store::BufferStore,
|
||||
worktree_store::WorktreeStore,
|
||||
git_store::GitStore, worktree_store::WorktreeStore,
|
||||
};
|
||||
|
||||
// platform-dependent warning
|
||||
@@ -33,6 +33,7 @@ pub struct StoreState {
|
||||
task_inventory: Entity<Inventory>,
|
||||
buffer_store: WeakEntity<BufferStore>,
|
||||
worktree_store: Entity<WorktreeStore>,
|
||||
git_store: Entity<GitStore>,
|
||||
toolchain_store: Arc<dyn LanguageToolchainStore>,
|
||||
}
|
||||
|
||||
@@ -163,6 +164,7 @@ impl TaskStore {
|
||||
worktree_store: Entity<WorktreeStore>,
|
||||
toolchain_store: Arc<dyn LanguageToolchainStore>,
|
||||
environment: Entity<ProjectEnvironment>,
|
||||
git_store: Entity<GitStore>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
Self::Functional(StoreState {
|
||||
@@ -172,6 +174,7 @@ impl TaskStore {
|
||||
},
|
||||
task_inventory: Inventory::new(cx),
|
||||
buffer_store,
|
||||
git_store,
|
||||
toolchain_store,
|
||||
worktree_store,
|
||||
})
|
||||
@@ -183,6 +186,7 @@ impl TaskStore {
|
||||
toolchain_store: Arc<dyn LanguageToolchainStore>,
|
||||
upstream_client: AnyProtoClient,
|
||||
project_id: u64,
|
||||
git_store: Entity<GitStore>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
Self::Functional(StoreState {
|
||||
@@ -192,6 +196,7 @@ impl TaskStore {
|
||||
},
|
||||
task_inventory: Inventory::new(cx),
|
||||
buffer_store,
|
||||
git_store,
|
||||
toolchain_store,
|
||||
worktree_store,
|
||||
})
|
||||
@@ -207,6 +212,7 @@ impl TaskStore {
|
||||
TaskStore::Functional(state) => match &state.mode {
|
||||
StoreMode::Local { environment, .. } => local_task_context_for_location(
|
||||
state.worktree_store.clone(),
|
||||
state.git_store.clone(),
|
||||
state.toolchain_store.clone(),
|
||||
environment.clone(),
|
||||
captured_variables,
|
||||
@@ -220,6 +226,7 @@ impl TaskStore {
|
||||
*project_id,
|
||||
upstream_client.clone(),
|
||||
state.worktree_store.clone(),
|
||||
state.git_store.clone(),
|
||||
captured_variables,
|
||||
location,
|
||||
state.toolchain_store.clone(),
|
||||
@@ -302,6 +309,7 @@ impl TaskStore {
|
||||
|
||||
fn local_task_context_for_location(
|
||||
worktree_store: Entity<WorktreeStore>,
|
||||
git_store: Entity<GitStore>,
|
||||
toolchain_store: Arc<dyn LanguageToolchainStore>,
|
||||
environment: Entity<ProjectEnvironment>,
|
||||
captured_variables: TaskVariables,
|
||||
@@ -329,7 +337,7 @@ fn local_task_context_for_location(
|
||||
worktree_store.clone(),
|
||||
location,
|
||||
project_env.clone(),
|
||||
BasicContextProvider::new(worktree_store),
|
||||
BasicContextProvider::new(worktree_store, git_store),
|
||||
toolchain_store,
|
||||
cx,
|
||||
)
|
||||
@@ -351,6 +359,7 @@ fn remote_task_context_for_location(
|
||||
project_id: u64,
|
||||
upstream_client: AnyProtoClient,
|
||||
worktree_store: Entity<WorktreeStore>,
|
||||
git_store: Entity<GitStore>,
|
||||
captured_variables: TaskVariables,
|
||||
location: Location,
|
||||
toolchain_store: Arc<dyn LanguageToolchainStore>,
|
||||
@@ -362,7 +371,7 @@ fn remote_task_context_for_location(
|
||||
.update(|cx| {
|
||||
let worktree_root = worktree_root(&worktree_store, &location, cx);
|
||||
|
||||
BasicContextProvider::new(worktree_store).build_context(
|
||||
BasicContextProvider::new(worktree_store, git_store).build_context(
|
||||
&TaskVariables::default(),
|
||||
ContextLocation {
|
||||
fs: None,
|
||||
|
||||
@@ -23,6 +23,7 @@ mod go_locator {
|
||||
show_summary: true,
|
||||
show_command: true,
|
||||
save: SaveStrategy::default(),
|
||||
hooks: Default::default(),
|
||||
};
|
||||
|
||||
let scenario = locator
|
||||
@@ -51,6 +52,7 @@ mod go_locator {
|
||||
show_summary: true,
|
||||
show_command: true,
|
||||
save: SaveStrategy::default(),
|
||||
hooks: Default::default(),
|
||||
};
|
||||
|
||||
let scenario = locator
|
||||
@@ -190,6 +192,7 @@ mod go_locator {
|
||||
show_summary: true,
|
||||
show_command: true,
|
||||
save: SaveStrategy::default(),
|
||||
hooks: Default::default(),
|
||||
};
|
||||
|
||||
let scenario = locator
|
||||
@@ -225,6 +228,7 @@ mod python_locator {
|
||||
show_summary: false,
|
||||
show_command: false,
|
||||
save: task::SaveStrategy::default(),
|
||||
hooks: Default::default(),
|
||||
};
|
||||
|
||||
let expected_scenario = DebugScenario {
|
||||
|
||||
@@ -191,6 +191,7 @@ impl HeadlessProject {
|
||||
worktree_store.clone(),
|
||||
toolchain_store.read(cx).as_language_toolchain_store(),
|
||||
environment.clone(),
|
||||
git_store.clone(),
|
||||
cx,
|
||||
);
|
||||
task_store.shared(REMOTE_SERVER_PROJECT_ID, session.clone(), cx);
|
||||
|
||||
@@ -1431,9 +1431,7 @@ impl std::fmt::Display for InvalidSettingsError {
|
||||
| InvalidSettingsError::DefaultSettings { message }
|
||||
| InvalidSettingsError::Tasks { message, .. }
|
||||
| InvalidSettingsError::Editorconfig { message, .. }
|
||||
| InvalidSettingsError::Debug { message, .. } => {
|
||||
write!(f, "{message}")
|
||||
}
|
||||
| InvalidSettingsError::Debug { message, .. } => write!(f, "{message}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ pub use debug_format::{
|
||||
Request, TcpArgumentsTemplate, ZedDebugConfig,
|
||||
};
|
||||
pub use task_template::{
|
||||
DebugArgsRequest, HideStrategy, RevealStrategy, SaveStrategy, TaskTemplate, TaskTemplates,
|
||||
substitute_variables_in_map, substitute_variables_in_str,
|
||||
DebugArgsRequest, HideStrategy, RevealStrategy, SaveStrategy, TaskHook, TaskTemplate,
|
||||
TaskTemplates, substitute_variables_in_map, substitute_variables_in_str,
|
||||
};
|
||||
pub use util::shell::{Shell, ShellKind};
|
||||
pub use util::shell_builder::ShellBuilder;
|
||||
@@ -181,6 +181,10 @@ pub enum VariableName {
|
||||
/// Open a Picker to select a process ID to use in place
|
||||
/// Can only be used to debug configurations
|
||||
PickProcessId,
|
||||
/// An absolute path of the main (original) git worktree for the current repository.
|
||||
/// For normal checkouts, this equals the worktree root. For linked worktrees,
|
||||
/// this is the original repo's working directory.
|
||||
MainGitWorktree,
|
||||
/// Custom variable, provided by the plugin or other external source.
|
||||
/// Will be printed with `CUSTOM_` prefix to avoid potential conflicts with other variables.
|
||||
Custom(Cow<'static, str>),
|
||||
@@ -216,6 +220,7 @@ impl FromStr for VariableName {
|
||||
"LANGUAGE" => Self::Language,
|
||||
"ROW" => Self::Row,
|
||||
"COLUMN" => Self::Column,
|
||||
"MAIN_GIT_WORKTREE" => Self::MainGitWorktree,
|
||||
_ => {
|
||||
if let Some(custom_name) =
|
||||
without_prefix.strip_prefix(ZED_CUSTOM_VARIABLE_NAME_PREFIX)
|
||||
@@ -251,6 +256,7 @@ impl std::fmt::Display for VariableName {
|
||||
Self::Language => write!(f, "{ZED_VARIABLE_NAME_PREFIX}LANGUAGE"),
|
||||
Self::RunnableSymbol => write!(f, "{ZED_VARIABLE_NAME_PREFIX}RUNNABLE_SYMBOL"),
|
||||
Self::PickProcessId => write!(f, "{ZED_VARIABLE_NAME_PREFIX}PICK_PID"),
|
||||
Self::MainGitWorktree => write!(f, "{ZED_VARIABLE_NAME_PREFIX}MAIN_GIT_WORKTREE"),
|
||||
Self::Custom(s) => write!(
|
||||
f,
|
||||
"{ZED_VARIABLE_NAME_PREFIX}{ZED_CUSTOM_VARIABLE_NAME_PREFIX}{s}"
|
||||
|
||||
@@ -75,6 +75,9 @@ pub struct TaskTemplate {
|
||||
/// Which edited buffers to save before running the task.
|
||||
#[serde(default)]
|
||||
pub save: SaveStrategy,
|
||||
/// Hooks that this task runs when emitted.
|
||||
#[serde(default)]
|
||||
pub hooks: HashSet<TaskHook>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Eq, PartialEq, Clone, Debug)]
|
||||
@@ -86,6 +89,14 @@ pub enum DebugArgsRequest {
|
||||
Attach(AttachRequest),
|
||||
}
|
||||
|
||||
/// What to do with the terminal pane and tab, after the command was started.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TaskHook {
|
||||
#[serde(alias = "create_git_worktree")]
|
||||
CreateWorktree,
|
||||
}
|
||||
|
||||
/// What to do with the terminal pane and tab, after the command was started.
|
||||
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
|
||||
@@ -434,7 +434,9 @@ mod tests {
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
|
||||
let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
|
||||
let (worktree_store, git_store) = project.read_with(cx, |project, _| {
|
||||
(project.worktree_store(), project.git_store().clone())
|
||||
});
|
||||
let rust_language = Arc::new(
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
@@ -451,6 +453,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.with_context_provider(Some(Arc::new(BasicContextProvider::new(
|
||||
worktree_store.clone(),
|
||||
git_store.clone(),
|
||||
)))),
|
||||
);
|
||||
|
||||
@@ -474,6 +477,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.with_context_provider(Some(Arc::new(BasicContextProvider::new(
|
||||
worktree_store.clone(),
|
||||
git_store.clone(),
|
||||
)))),
|
||||
);
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use std::process::ExitStatus;
|
||||
|
||||
use anyhow::Result;
|
||||
use collections::HashSet;
|
||||
use gpui::{AppContext, Context, Entity, Task};
|
||||
use language::Buffer;
|
||||
use project::{TaskSourceKind, WorktreeId};
|
||||
use remote::ConnectionState;
|
||||
use task::{
|
||||
DebugScenario, ResolvedTask, SaveStrategy, SharedTaskContext, SpawnInTerminal, TaskContext,
|
||||
TaskTemplate,
|
||||
TaskHook, TaskTemplate, TaskVariables, VariableName,
|
||||
};
|
||||
use ui::Window;
|
||||
use util::TryFutureExt;
|
||||
@@ -164,6 +165,111 @@ impl Workspace {
|
||||
Task::ready(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_create_worktree_tasks(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let project = self.project().clone();
|
||||
let hooks = HashSet::from_iter([TaskHook::CreateWorktree]);
|
||||
|
||||
let worktree_tasks: Vec<(WorktreeId, TaskContext, Vec<TaskTemplate>)> = {
|
||||
let project = project.read(cx);
|
||||
let task_store = project.task_store();
|
||||
let Some(inventory) = task_store.read(cx).task_inventory().cloned() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let git_store = project.git_store().read(cx);
|
||||
|
||||
let mut worktree_tasks = Vec::new();
|
||||
for worktree in project.worktrees(cx) {
|
||||
let worktree = worktree.read(cx);
|
||||
let worktree_id = worktree.id();
|
||||
let worktree_abs_path = worktree.abs_path();
|
||||
|
||||
let templates: Vec<TaskTemplate> = inventory
|
||||
.read(cx)
|
||||
.templates_with_hooks(&hooks, worktree_id)
|
||||
.into_iter()
|
||||
.map(|(_, template)| template)
|
||||
.collect();
|
||||
|
||||
if templates.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut task_variables = TaskVariables::default();
|
||||
task_variables.insert(
|
||||
VariableName::WorktreeRoot,
|
||||
worktree_abs_path.to_string_lossy().into_owned(),
|
||||
);
|
||||
|
||||
if let Some(path) = git_store.original_repo_path_for_worktree(worktree_id, cx) {
|
||||
task_variables.insert(
|
||||
VariableName::MainGitWorktree,
|
||||
path.to_string_lossy().into_owned(),
|
||||
);
|
||||
}
|
||||
|
||||
let task_context = TaskContext {
|
||||
cwd: Some(worktree_abs_path.to_path_buf()),
|
||||
task_variables,
|
||||
project_env: Default::default(),
|
||||
};
|
||||
|
||||
worktree_tasks.push((worktree_id, task_context, templates));
|
||||
}
|
||||
worktree_tasks
|
||||
};
|
||||
|
||||
if worktree_tasks.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let task = cx.spawn_in(window, async move |workspace, cx| {
|
||||
let mut tasks = Vec::new();
|
||||
for (worktree_id, task_context, templates) in worktree_tasks {
|
||||
let id_base = format!("worktree_setup_{worktree_id}");
|
||||
|
||||
tasks.push(cx.spawn({
|
||||
let workspace = workspace.clone();
|
||||
async move |cx| {
|
||||
for task_template in templates {
|
||||
let Some(resolved) =
|
||||
task_template.resolve_task(&id_base, &task_context)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let status = workspace.update_in(cx, |workspace, window, cx| {
|
||||
workspace.spawn_in_terminal(resolved.resolved, window, cx)
|
||||
})?;
|
||||
|
||||
if let Some(result) = status.await {
|
||||
match result {
|
||||
Ok(exit_status) if !exit_status.success() => {
|
||||
log::error!(
|
||||
"Git worktree setup task failed with status: {:?}",
|
||||
exit_status.code()
|
||||
);
|
||||
break;
|
||||
}
|
||||
Err(error) => {
|
||||
log::error!("Git worktree setup task error: {error:#}");
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
anyhow::Ok(())
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
futures::future::join_all(tasks).await;
|
||||
anyhow::Ok(())
|
||||
});
|
||||
task.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in new issue
Block a user