cli: make CommandHelper cheaply cloneable

This wraps all the fields in `CommandHelper` in an `Rc` so
`CommandHelper` itself becomes cheap to clone (thanks to @yuja for the
idea). I'll use that next to avoid some cloning in
`WorkspaceCommandHelper`.
This commit is contained in:
Martin von Zweigbergk 2024-09-02 07:00:13 -07:00 committed by Martin von Zweigbergk
parent da7b1e9e18
commit 34425a2501

View file

@ -255,7 +255,12 @@ impl TracingSubscription {
} }
} }
#[derive(Clone)]
pub struct CommandHelper { pub struct CommandHelper {
data: Rc<CommandHelperData>,
}
struct CommandHelperData {
app: Command, app: Command,
cwd: PathBuf, cwd: PathBuf,
string_args: Vec<String>, string_args: Vec<String>,
@ -273,7 +278,7 @@ pub struct CommandHelper {
impl CommandHelper { impl CommandHelper {
pub fn app(&self) -> &Command { pub fn app(&self) -> &Command {
&self.app &self.data.app
} }
/// Canonical form of the current working directory path. /// Canonical form of the current working directory path.
@ -281,34 +286,34 @@ impl CommandHelper {
/// A loaded `Workspace::workspace_root()` also returns a canonical path, so /// A loaded `Workspace::workspace_root()` also returns a canonical path, so
/// relative paths can be easily computed from these paths. /// relative paths can be easily computed from these paths.
pub fn cwd(&self) -> &Path { pub fn cwd(&self) -> &Path {
&self.cwd &self.data.cwd
} }
pub fn string_args(&self) -> &Vec<String> { pub fn string_args(&self) -> &Vec<String> {
&self.string_args &self.data.string_args
} }
pub fn matches(&self) -> &ArgMatches { pub fn matches(&self) -> &ArgMatches {
&self.matches &self.data.matches
} }
pub fn global_args(&self) -> &GlobalArgs { pub fn global_args(&self) -> &GlobalArgs {
&self.global_args &self.data.global_args
} }
pub fn settings(&self) -> &UserSettings { pub fn settings(&self) -> &UserSettings {
&self.settings &self.data.settings
} }
pub fn resolved_config_values( pub fn resolved_config_values(
&self, &self,
prefix: &ConfigNamePathBuf, prefix: &ConfigNamePathBuf,
) -> Result<Vec<AnnotatedValue>, crate::config::ConfigError> { ) -> Result<Vec<AnnotatedValue>, crate::config::ConfigError> {
self.layered_configs.resolved_config_values(prefix) self.data.layered_configs.resolved_config_values(prefix)
} }
pub fn revset_extensions(&self) -> &Arc<RevsetExtensions> { pub fn revset_extensions(&self) -> &Arc<RevsetExtensions> {
&self.revset_extensions &self.data.revset_extensions
} }
/// Loads template aliases from the configs. /// Loads template aliases from the configs.
@ -316,7 +321,7 @@ impl CommandHelper {
/// For most commands that depend on a loaded repo, you should use /// For most commands that depend on a loaded repo, you should use
/// `WorkspaceCommandHelper::template_aliases_map()` instead. /// `WorkspaceCommandHelper::template_aliases_map()` instead.
fn load_template_aliases(&self, ui: &Ui) -> Result<TemplateAliasesMap, CommandError> { fn load_template_aliases(&self, ui: &Ui) -> Result<TemplateAliasesMap, CommandError> {
load_template_aliases(ui, &self.layered_configs) load_template_aliases(ui, &self.data.layered_configs)
} }
/// Parses template of the given language into evaluation tree. /// Parses template of the given language into evaluation tree.
@ -341,11 +346,14 @@ impl CommandHelper {
} }
pub fn operation_template_extensions(&self) -> &[Arc<dyn OperationTemplateLanguageExtension>] { pub fn operation_template_extensions(&self) -> &[Arc<dyn OperationTemplateLanguageExtension>] {
&self.operation_template_extensions &self.data.operation_template_extensions
} }
pub fn workspace_loader(&self) -> Result<&dyn WorkspaceLoader, CommandError> { pub fn workspace_loader(&self) -> Result<&dyn WorkspaceLoader, CommandError> {
self.maybe_workspace_loader.as_deref().map_err(Clone::clone) self.data
.maybe_workspace_loader
.as_deref()
.map_err(Clone::clone)
} }
/// Loads workspace and repo, then snapshots the working copy if allowed. /// Loads workspace and repo, then snapshots the working copy if allowed.
@ -374,9 +382,11 @@ impl CommandHelper {
// We convert StoreLoadError -> WorkspaceLoadError -> CommandError // We convert StoreLoadError -> WorkspaceLoadError -> CommandError
let factory: Result<_, WorkspaceLoadError> = let factory: Result<_, WorkspaceLoadError> =
get_working_copy_factory(loader, &self.working_copy_factories).map_err(|e| e.into()); get_working_copy_factory(loader, &self.data.working_copy_factories)
let factory = factory .map_err(|e| e.into());
.map_err(|err| map_workspace_load_error(err, self.global_args.repository.as_deref()))?; let factory = factory.map_err(|err| {
map_workspace_load_error(err, self.data.global_args.repository.as_deref())
})?;
Ok(factory) Ok(factory)
} }
@ -385,24 +395,29 @@ impl CommandHelper {
let loader = self.workspace_loader()?; let loader = self.workspace_loader()?;
loader loader
.load( .load(
&self.settings, &self.data.settings,
&self.store_factories, &self.data.store_factories,
&self.working_copy_factories, &self.data.working_copy_factories,
) )
.map_err(|err| map_workspace_load_error(err, self.global_args.repository.as_deref())) .map_err(|err| {
map_workspace_load_error(err, self.data.global_args.repository.as_deref())
})
} }
/// Returns true if the working copy to be loaded is writable, and therefore /// Returns true if the working copy to be loaded is writable, and therefore
/// should usually be snapshotted. /// should usually be snapshotted.
pub fn is_working_copy_writable(&self) -> bool { pub fn is_working_copy_writable(&self) -> bool {
self.is_at_head_operation() && !self.global_args.ignore_working_copy self.is_at_head_operation() && !self.data.global_args.ignore_working_copy
} }
/// Returns true if the current operation is considered to be the head. /// Returns true if the current operation is considered to be the head.
pub fn is_at_head_operation(&self) -> bool { pub fn is_at_head_operation(&self) -> bool {
// TODO: should we accept --at-op=<head_id> as the head op? or should we // TODO: should we accept --at-op=<head_id> as the head op? or should we
// make --at-op=@ imply --ignore-workign-copy (i.e. not at the head.) // make --at-op=@ imply --ignore-workign-copy (i.e. not at the head.)
matches!(self.global_args.at_operation.as_deref(), None | Some("@")) matches!(
self.data.global_args.at_operation.as_deref(),
None | Some("@")
)
} }
/// Resolves the current operation from the command-line argument. /// Resolves the current operation from the command-line argument.
@ -415,7 +430,7 @@ impl CommandHelper {
ui: &mut Ui, ui: &mut Ui,
repo_loader: &RepoLoader, repo_loader: &RepoLoader,
) -> Result<Operation, CommandError> { ) -> Result<Operation, CommandError> {
if let Some(op_str) = &self.global_args.at_operation { if let Some(op_str) = &self.data.global_args.at_operation {
Ok(op_walk::resolve_op_for_load(repo_loader, op_str)?) Ok(op_walk::resolve_op_for_load(repo_loader, op_str)?)
} else { } else {
op_heads_store::resolve_op_heads( op_heads_store::resolve_op_heads(
@ -428,11 +443,14 @@ impl CommandHelper {
)?; )?;
let base_repo = repo_loader.load_at(&op_heads[0])?; let base_repo = repo_loader.load_at(&op_heads[0])?;
// TODO: It may be helpful to print each operation we're merging here // TODO: It may be helpful to print each operation we're merging here
let mut tx = let mut tx = start_repo_transaction(
start_repo_transaction(&base_repo, &self.settings, &self.string_args); &base_repo,
&self.data.settings,
&self.data.string_args,
);
for other_op_head in op_heads.into_iter().skip(1) { for other_op_head in op_heads.into_iter().skip(1) {
tx.merge_operation(other_op_head)?; tx.merge_operation(other_op_head)?;
let num_rebased = tx.mut_repo().rebase_descendants(&self.settings)?; let num_rebased = tx.mut_repo().rebase_descendants(&self.data.settings)?;
if num_rebased > 0 { if num_rebased > 0 {
writeln!( writeln!(
ui.status(), ui.status(),
@ -598,26 +616,28 @@ impl WorkspaceCommandHelper {
repo: Arc<ReadonlyRepo>, repo: Arc<ReadonlyRepo>,
loaded_at_head: bool, loaded_at_head: bool,
) -> Result<Self, CommandError> { ) -> Result<Self, CommandError> {
let settings = command.settings.clone(); let settings = command.data.settings.clone();
let commit_summary_template_text = let commit_summary_template_text =
settings.config().get_string("templates.commit_summary")?; settings.config().get_string("templates.commit_summary")?;
let revset_aliases_map = revset_util::load_revset_aliases(ui, &command.layered_configs)?; let revset_aliases_map =
revset_util::load_revset_aliases(ui, &command.data.layered_configs)?;
let template_aliases_map = command.load_template_aliases(ui)?; let template_aliases_map = command.load_template_aliases(ui)?;
let may_update_working_copy = loaded_at_head && !command.global_args.ignore_working_copy; let may_update_working_copy =
loaded_at_head && !command.data.global_args.ignore_working_copy;
let working_copy_shared_with_git = is_colocated_git_workspace(&workspace, &repo); let working_copy_shared_with_git = is_colocated_git_workspace(&workspace, &repo);
let path_converter = RepoPathUiConverter::Fs { let path_converter = RepoPathUiConverter::Fs {
cwd: command.cwd.clone(), cwd: command.data.cwd.clone(),
base: workspace.workspace_root().clone(), base: workspace.workspace_root().clone(),
}; };
let helper = Self { let helper = Self {
string_args: command.string_args.clone(), string_args: command.data.string_args.clone(),
global_args: command.global_args.clone(), global_args: command.data.global_args.clone(),
settings, settings,
workspace, workspace,
user_repo: ReadonlyUserRepo::new(repo), user_repo: ReadonlyUserRepo::new(repo),
revset_extensions: command.revset_extensions.clone(), revset_extensions: command.data.revset_extensions.clone(),
commit_summary_template_text, commit_summary_template_text,
commit_template_extensions: command.commit_template_extensions.clone(), commit_template_extensions: command.data.commit_template_extensions.clone(),
revset_aliases_map, revset_aliases_map,
template_aliases_map, template_aliases_map,
may_update_working_copy, may_update_working_copy,
@ -1333,7 +1353,7 @@ See https://github.com/martinvonz/jj/blob/main/docs/working-copy.md#stale-workin
"Run `jj workspace update-stale` to recover. "Run `jj workspace update-stale` to recover.
See https://github.com/martinvonz/jj/blob/main/docs/working-copy.md#stale-working-copy \ See https://github.com/martinvonz/jj/blob/main/docs/working-copy.md#stale-working-copy \
for more information.", for more information.",
)) ));
} }
Err(e) => return Err(e.into()), Err(e) => return Err(e.into()),
}; };
@ -3065,7 +3085,7 @@ impl CliRunner {
} }
let settings = UserSettings::from_config(config); let settings = UserSettings::from_config(config);
let command_helper = CommandHelper { let command_helper_data = CommandHelperData {
app: self.app, app: self.app,
cwd, cwd,
string_args, string_args,
@ -3080,6 +3100,9 @@ impl CliRunner {
store_factories: self.store_factories, store_factories: self.store_factories,
working_copy_factories: self.working_copy_factories, working_copy_factories: self.working_copy_factories,
}; };
let command_helper = CommandHelper {
data: Rc::new(command_helper_data),
};
for start_hook_fn in self.start_hook_fns { for start_hook_fn in self.start_hook_fns {
start_hook_fn(ui, &command_helper)?; start_hook_fn(ui, &command_helper)?;
} }