// Copyright 2022 The Jujutsu Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License. use std::borrow::Cow;use std::cell::OnceCell;use std::collections::BTreeMap;use std::collections::HashSet;use std::env;use std::env::VarError;use std::ffi::OsString;use std::fmt;use std::fmt::Debug;use std::fs;use std::io;use std::io::Write as _;use std::mem;use std::path::Path;use std::path::PathBuf;use std::process::ExitCode;use std::rc::Rc;use std::str;use std::str::FromStr;use std::sync::Arc;use std::time::SystemTime; use bstr::ByteVec as _;use chrono::TimeZone;use clap::builder::MapValueParser;use clap::builder::NonEmptyStringValueParser;use clap::builder::TypedValueParser;use clap::builder::ValueParserFactory;use clap::error::ContextKind;use clap::error::ContextValue;use clap::ArgAction;use clap::ArgMatches;use clap::Command;use clap::FromArgMatches;use indexmap::IndexMap;use indexmap::IndexSet;use itertools::Itertools;use jj_lib::backend::ChangeId;use jj_lib::backend::CommitId;use jj_lib::backend::MergedTreeId;use jj_lib::backend::TreeValue;use jj_lib::commit::Commit;use jj_lib::dag_walk;use jj_lib::file_util;use jj_lib::fileset;use jj_lib::fileset::FilesetDiagnostics;use jj_lib::fileset::FilesetExpression;use jj_lib::git;use jj_lib::git_backend::GitBackend;use jj_lib::gitignore::GitIgnoreError;use jj_lib::gitignore::GitIgnoreFile;use jj_lib::id_prefix::IdPrefixContext;use jj_lib::matchers::Matcher;use jj_lib::merge::MergedTreeValue;use jj_lib::merged_tree::MergedTree;use jj_lib::object_id::ObjectId;use jj_lib::op_heads_store;use jj_lib::op_store::OpStoreError;use jj_lib::op_store::OperationId;use jj_lib::op_store::RefTarget;use jj_lib::op_store::WorkspaceId;use jj_lib::op_walk;use jj_lib::op_walk::OpsetEvaluationError;use jj_lib::operation::Operation;use jj_lib::repo::merge_factories_map;use jj_lib::repo::CheckOutCommitError;use jj_lib::repo::EditCommitError;use jj_lib::repo::MutableRepo;use jj_lib::repo::ReadonlyRepo;use jj_lib::repo::Repo;use jj_lib::repo::RepoLoader;use jj_lib::repo::StoreFactories;use jj_lib::repo::StoreLoadError;use jj_lib::repo_path::RepoPath;use jj_lib::repo_path::RepoPathBuf;use jj_lib::repo_path::RepoPathUiConverter;use jj_lib::repo_path::UiPathParseError;use jj_lib::revset;use jj_lib::revset::ResolvedRevsetExpression;use jj_lib::revset::RevsetAliasesMap;use jj_lib::revset::RevsetDiagnostics;use jj_lib::revset::RevsetExpression;use jj_lib::revset::RevsetExtensions;use jj_lib::revset::RevsetFilterPredicate;use jj_lib::revset::RevsetFunction;use jj_lib::revset::RevsetIteratorExt;use jj_lib::revset::RevsetModifier;use jj_lib::revset::RevsetParseContext;use jj_lib::revset::RevsetWorkspaceContext;use jj_lib::revset::SymbolResolverExtension;use jj_lib::revset::UserRevsetExpression;use jj_lib::rewrite::restore_tree;use jj_lib::settings::ConfigResultExt as _;use jj_lib::settings::UserSettings;use jj_lib::signing::SignInitError;use jj_lib::str_util::StringPattern;use jj_lib::transaction::Transaction;use jj_lib::view::View;use jj_lib::working_copy::CheckoutStats;use jj_lib::working_copy::LockedWorkingCopy;use jj_lib::working_copy::SnapshotOptions;use jj_lib::working_copy::WorkingCopy;use jj_lib::working_copy::WorkingCopyFactory;use jj_lib::workspace::default_working_copy_factories;use jj_lib::workspace::get_working_copy_factory;use jj_lib::workspace::DefaultWorkspaceLoaderFactory;use jj_lib::workspace::LockedWorkspace;use jj_lib::workspace::WorkingCopyFactories;use jj_lib::workspace::Workspace;use jj_lib::workspace::WorkspaceLoadError;use jj_lib::workspace::WorkspaceLoader;use jj_lib::workspace::WorkspaceLoaderFactory;use tracing::instrument;use tracing_chrome::ChromeLayerBuilder;use tracing_subscriber::prelude::*; use crate::command_error::cli_error;use crate::command_error::config_error_with_message;use crate::command_error::handle_command_result;use crate::command_error::internal_error;use crate::command_error::internal_error_with_message;use crate::command_error::print_parse_diagnostics;use crate::command_error::user_error;use crate::command_error::user_error_with_hint;use crate::command_error::user_error_with_message;use crate::command_error::CommandError;use crate::commit_templater::CommitTemplateLanguage;use crate::commit_templater::CommitTemplateLanguageExtension;use crate::config::new_config_path;use crate::config::AnnotatedValue;use crate::config::CommandNameAndArgs;use crate::config::ConfigNamePathBuf;use crate::config::ConfigSource;use crate::config::LayeredConfigs;use crate::diff_util;use crate::diff_util::DiffFormat;use crate::diff_util::DiffFormatArgs;use crate::diff_util::DiffRenderer;use crate::formatter::FormatRecorder;use crate::formatter::Formatter;use crate::formatter::PlainTextFormatter;use crate::git_util::is_colocated_git_workspace;use crate::git_util::print_failed_git_export;use crate::git_util::print_git_import_stats;use crate::merge_tools::DiffEditor;use crate::merge_tools::MergeEditor;use crate::merge_tools::MergeToolConfigError;use crate::operation_templater::OperationTemplateLanguage;use crate::operation_templater::OperationTemplateLanguageExtension;use crate::revset_util;use crate::revset_util::RevsetExpressionEvaluator;use crate::template_builder;use crate::template_builder::TemplateLanguage;use crate::template_parser::TemplateAliasesMap;use crate::template_parser::TemplateDiagnostics;use crate::templater::PropertyPlaceholder;use crate::templater::TemplateRenderer;use crate::text_util;use crate::ui::ColorChoice;use crate::ui::Ui; const SHORT_CHANGE_ID_TEMPLATE_TEXT: &str = "format_short_change_id(self.change_id())"; #[derive(Clone)]struct ChromeTracingFlushGuard { _inner: Option<Rc<tracing_chrome::FlushGuard>>,} impl Debug for ChromeTracingFlushGuard { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let Self { _inner } = self; f.debug_struct("ChromeTracingFlushGuard") .finish_non_exhaustive() }} /// Handle to initialize or change tracing subscription.#[derive(Clone, Debug)]pub struct TracingSubscription { reload_log_filter: tracing_subscriber::reload::Handle< tracing_subscriber::EnvFilter, tracing_subscriber::Registry, >, _chrome_tracing_flush_guard: ChromeTracingFlushGuard,} impl TracingSubscription { /// Initializes tracing with the default configuration. This should be /// called as early as possible. pub fn init() -> Self { let filter = tracing_subscriber::EnvFilter::builder() .with_default_directive(tracing::metadata::LevelFilter::ERROR.into()) .from_env_lossy(); let (filter, reload_log_filter) = tracing_subscriber::reload::Layer::new(filter); let (chrome_tracing_layer, chrome_tracing_flush_guard) = match std::env::var("JJ_TRACE") { Ok(filename) => { let filename = if filename.is_empty() { format!( "jj-trace-{}.json", SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .as_secs(), ) } else { filename }; let include_args = std::env::var("JJ_TRACE_INCLUDE_ARGS").is_ok(); let (layer, guard) = ChromeLayerBuilder::new() .file(filename) .include_args(include_args) .build(); ( Some(layer), ChromeTracingFlushGuard { _inner: Some(Rc::new(guard)), }, ) } Err(_) => (None, ChromeTracingFlushGuard { _inner: None }), }; tracing_subscriber::registry() .with( tracing_subscriber::fmt::Layer::default() .with_writer(std::io::stderr) .with_filter(filter), ) .with(chrome_tracing_layer) .init(); TracingSubscription { reload_log_filter, _chrome_tracing_flush_guard: chrome_tracing_flush_guard, } } pub fn enable_debug_logging(&self) -> Result<(), CommandError> { self.reload_log_filter .modify(|filter| { *filter = tracing_subscriber::EnvFilter::builder() .with_default_directive(tracing::metadata::LevelFilter::DEBUG.into()) .from_env_lossy(); }) .map_err(|err| internal_error_with_message("failed to enable debug logging", err))?; tracing::info!("debug logging enabled"); Ok(()) }} #[derive(Clone)]pub struct CommandHelper { data: Rc<CommandHelperData>,} struct CommandHelperData { app: Command, cwd: PathBuf, string_args: Vec<String>, matches: ArgMatches, global_args: GlobalArgs, settings: UserSettings, layered_configs: LayeredConfigs, revset_extensions: Arc<RevsetExtensions>, commit_template_extensions: Vec<Arc<dyn CommitTemplateLanguageExtension>>, operation_template_extensions: Vec<Arc<dyn OperationTemplateLanguageExtension>>, maybe_workspace_loader: Result<Box<dyn WorkspaceLoader>, CommandError>, store_factories: StoreFactories, working_copy_factories: WorkingCopyFactories,} impl CommandHelper { pub fn app(&self) -> &Command { &self.data.app } /// Canonical form of the current working directory path. /// /// A loaded `Workspace::workspace_root()` also returns a canonical path, so /// relative paths can be easily computed from these paths. pub fn cwd(&self) -> &Path { &self.data.cwd } pub fn string_args(&self) -> &Vec<String> { &self.data.string_args } pub fn matches(&self) -> &ArgMatches { &self.data.matches } pub fn global_args(&self) -> &GlobalArgs { &self.data.global_args } pub fn settings(&self) -> &UserSettings { &self.data.settings } pub fn resolved_config_values( &self, prefix: &ConfigNamePathBuf, ) -> Result<Vec<AnnotatedValue>, crate::config::ConfigError> { self.data.layered_configs.resolved_config_values(prefix) } pub fn revset_extensions(&self) -> &Arc<RevsetExtensions> { &self.data.revset_extensions } /// Loads template aliases from the configs. /// /// For most commands that depend on a loaded repo, you should use /// `WorkspaceCommandHelper::template_aliases_map()` instead. fn load_template_aliases(&self, ui: &Ui) -> Result<TemplateAliasesMap, CommandError> { load_template_aliases(ui, &self.data.layered_configs) } /// Parses template of the given language into evaluation tree. /// /// This function also loads template aliases from the settings. Use /// `WorkspaceCommandHelper::parse_template()` if you've already /// instantiated the workspace helper. pub fn parse_template<'a, C: Clone + 'a, L: TemplateLanguage<'a> + ?Sized>( &self, ui: &Ui, language: &L, template_text: &str, wrap_self: impl Fn(PropertyPlaceholder<C>) -> L::Property, ) -> Result<TemplateRenderer<'a, C>, CommandError> { let mut diagnostics = TemplateDiagnostics::new(); let aliases = self.load_template_aliases(ui)?; let template = template_builder::parse( language, &mut diagnostics, template_text, &aliases, wrap_self, )?; print_parse_diagnostics(ui, "In template expression", &diagnostics)?; Ok(template) } pub fn workspace_loader(&self) -> Result<&dyn WorkspaceLoader, CommandError> { self.data .maybe_workspace_loader .as_deref() .map_err(Clone::clone) } /// Loads workspace and repo, then snapshots the working copy if allowed. #[instrument(skip(self, ui))] pub fn workspace_helper(&self, ui: &Ui) -> Result<WorkspaceCommandHelper, CommandError> { let mut workspace_command = self.workspace_helper_no_snapshot(ui)?; workspace_command.maybe_snapshot(ui)?; Ok(workspace_command) } /// Loads workspace and repo, but never snapshots the working copy. Most /// commands should use `workspace_helper()` instead. #[instrument(skip(self, ui))] pub fn workspace_helper_no_snapshot( &self, ui: &Ui, ) -> Result<WorkspaceCommandHelper, CommandError> { let workspace = self.load_workspace()?; let op_head = self.resolve_operation(ui, workspace.repo_loader())?; let repo = workspace.repo_loader().load_at(&op_head)?; let env = self.workspace_environment(ui, &workspace)?; revset_util::warn_unresolvable_trunk(ui, repo.as_ref(), &env.revset_parse_context())?; WorkspaceCommandHelper::new(ui, workspace, repo, env, self.is_at_head_operation()) } pub fn get_store_factories(&self) -> &StoreFactories { &self.data.store_factories } pub fn get_working_copy_factory(&self) -> Result<&dyn WorkingCopyFactory, CommandError> { let loader = self.workspace_loader()?; // We convert StoreLoadError -> WorkspaceLoadError -> CommandError let factory: Result<_, WorkspaceLoadError> = get_working_copy_factory(loader, &self.data.working_copy_factories) .map_err(|e| e.into()); let factory = factory.map_err(|err| { map_workspace_load_error(err, self.data.global_args.repository.as_deref()) })?; Ok(factory) } #[instrument(skip_all)] pub fn load_workspace(&self) -> Result<Workspace, CommandError> { let loader = self.workspace_loader()?; loader .load( &self.data.settings, &self.data.store_factories, &self.data.working_copy_factories, ) .map_err(|err| { map_workspace_load_error(err, self.data.global_args.repository.as_deref()) }) } /// Loads command environment for the given `workspace`. pub fn workspace_environment( &self, ui: &Ui, workspace: &Workspace, ) -> Result<WorkspaceCommandEnvironment, CommandError> { WorkspaceCommandEnvironment::new(ui, self, workspace) } /// Returns true if the working copy to be loaded is writable, and therefore /// should usually be snapshotted. pub fn is_working_copy_writable(&self) -> bool { self.is_at_head_operation() && !self.data.global_args.ignore_working_copy } /// Returns true if the current operation is considered to be the head. pub fn is_at_head_operation(&self) -> bool { // 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.) matches!( self.data.global_args.at_operation.as_deref(), None | Some("@") ) } /// Resolves the current operation from the command-line argument. /// /// If no `--at-operation` is specified, the head operations will be /// loaded. If there are multiple heads, they'll be merged. #[instrument(skip_all)] pub fn resolve_operation( &self, ui: &Ui, repo_loader: &RepoLoader, ) -> Result<Operation, CommandError> { if let Some(op_str) = &self.data.global_args.at_operation { Ok(op_walk::resolve_op_for_load(repo_loader, op_str)?) } else { op_heads_store::resolve_op_heads( repo_loader.op_heads_store().as_ref(), repo_loader.op_store(), |op_heads| { writeln!( ui.status(), "Concurrent modification detected, resolving automatically.", )?; let base_repo = repo_loader.load_at(&op_heads[0])?; // TODO: It may be helpful to print each operation we're merging here let mut tx = start_repo_transaction( &base_repo, &self.data.settings, &self.data.string_args, ); for other_op_head in op_heads.into_iter().skip(1) { tx.merge_operation(other_op_head)?; let num_rebased = tx.repo_mut().rebase_descendants(&self.data.settings)?; if num_rebased > 0 { writeln!( ui.status(), "Rebased {num_rebased} descendant commits onto commits rewritten \ by other operation" )?; } } Ok(tx .write("reconcile divergent operations") .leave_unpublished() .operation() .clone()) }, ) } } /// Creates helper for the repo whose view is supposed to be in sync with /// the working copy. If `--ignore-working-copy` is not specified, the /// returned helper will attempt to update the working copy. #[instrument(skip_all)] pub fn for_workable_repo( &self, ui: &Ui,Lines 1–500 of 3473. Load more
// Copyright 2022 The Jujutsu Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use std::borrow::Cow; use std::cell::OnceCell; use std::collections::BTreeMap; use std::collections::HashSet; use std::env; use std::env::VarError; use std::ffi::OsString; use std::fmt; use std::fmt::Debug; use std::fs; use std::io; use std::io::Write as _; use std::mem; use std::path::Path; use std::path::PathBuf; use std::process::ExitCode; use std::rc::Rc; use std::str; use std::str::FromStr; use std::sync::Arc; use std::time::SystemTime; use bstr::ByteVec as _; use chrono::TimeZone; use clap::builder::MapValueParser; use clap::builder::NonEmptyStringValueParser; use clap::builder::TypedValueParser; use clap::builder::ValueParserFactory; use clap::error::ContextKind; use clap::error::ContextValue; use clap::ArgAction; use clap::ArgMatches; use clap::Command; use clap::FromArgMatches; use indexmap::IndexMap; use indexmap::IndexSet; use itertools::Itertools; use jj_lib::backend::ChangeId; use jj_lib::backend::CommitId; use jj_lib::backend::MergedTreeId; use jj_lib::backend::TreeValue; use jj_lib::commit::Commit; use jj_lib::dag_walk; use jj_lib::file_util; use jj_lib::fileset; use jj_lib::fileset::FilesetDiagnostics; use jj_lib::fileset::FilesetExpression; use jj_lib::git; use jj_lib::git_backend::GitBackend; use jj_lib::gitignore::GitIgnoreError; use jj_lib::gitignore::GitIgnoreFile; use jj_lib::id_prefix::IdPrefixContext; use jj_lib::matchers::Matcher; use jj_lib::merge::MergedTreeValue; use jj_lib::merged_tree::MergedTree; use jj_lib::object_id::ObjectId; use jj_lib::op_heads_store; use jj_lib::op_store::OpStoreError; use jj_lib::op_store::OperationId; use jj_lib::op_store::RefTarget; use jj_lib::op_store::WorkspaceId; use jj_lib::op_walk; use jj_lib::op_walk::OpsetEvaluationError; use jj_lib::operation::Operation; use jj_lib::repo::merge_factories_map; use jj_lib::repo::CheckOutCommitError; use jj_lib::repo::EditCommitError; use jj_lib::repo::MutableRepo; use jj_lib::repo::ReadonlyRepo; use jj_lib::repo::Repo; use jj_lib::repo::RepoLoader; use jj_lib::repo::StoreFactories; use jj_lib::repo::StoreLoadError; use jj_lib::repo_path::RepoPath; use jj_lib::repo_path::RepoPathBuf; use jj_lib::repo_path::RepoPathUiConverter; use jj_lib::repo_path::UiPathParseError; use jj_lib::revset; use jj_lib::revset::ResolvedRevsetExpression; use jj_lib::revset::RevsetAliasesMap; use jj_lib::revset::RevsetDiagnostics; use jj_lib::revset::RevsetExpression; use jj_lib::revset::RevsetExtensions; use jj_lib::revset::RevsetFilterPredicate; use jj_lib::revset::RevsetFunction; use jj_lib::revset::RevsetIteratorExt; use jj_lib::revset::RevsetModifier; use jj_lib::revset::RevsetParseContext; use jj_lib::revset::RevsetWorkspaceContext; use jj_lib::revset::SymbolResolverExtension; use jj_lib::revset::UserRevsetExpression; use jj_lib::rewrite::restore_tree; use jj_lib::settings::ConfigResultExt as _; use jj_lib::settings::UserSettings; use jj_lib::signing::SignInitError; use jj_lib::str_util::StringPattern; use jj_lib::transaction::Transaction; use jj_lib::view::View; use jj_lib::working_copy::CheckoutStats; use jj_lib::working_copy::LockedWorkingCopy; use jj_lib::working_copy::SnapshotOptions; use jj_lib::working_copy::WorkingCopy; use jj_lib::working_copy::WorkingCopyFactory; use jj_lib::workspace::default_working_copy_factories; use jj_lib::workspace::get_working_copy_factory; use jj_lib::workspace::DefaultWorkspaceLoaderFactory; use jj_lib::workspace::LockedWorkspace; use jj_lib::workspace::WorkingCopyFactories; use jj_lib::workspace::Workspace; use jj_lib::workspace::WorkspaceLoadError; use jj_lib::workspace::WorkspaceLoader; use jj_lib::workspace::WorkspaceLoaderFactory; use tracing::instrument; use tracing_chrome::ChromeLayerBuilder; use tracing_subscriber::prelude::*; use crate::command_error::cli_error; use crate::command_error::config_error_with_message; use crate::command_error::handle_command_result; use crate::command_error::internal_error; use crate::command_error::internal_error_with_message; use crate::command_error::print_parse_diagnostics; use crate::command_error::user_error; use crate::command_error::user_error_with_hint; use crate::command_error::user_error_with_message; use crate::command_error::CommandError; use crate::commit_templater::CommitTemplateLanguage; use crate::commit_templater::CommitTemplateLanguageExtension; use crate::config::new_config_path; use crate::config::AnnotatedValue; use crate::config::CommandNameAndArgs; use crate::config::ConfigNamePathBuf; use crate::config::ConfigSource; use crate::config::LayeredConfigs; use crate::diff_util; use crate::diff_util::DiffFormat; use crate::diff_util::DiffFormatArgs; use crate::diff_util::DiffRenderer; use crate::formatter::FormatRecorder; use crate::formatter::Formatter; use crate::formatter::PlainTextFormatter; use crate::git_util::is_colocated_git_workspace; use crate::git_util::print_failed_git_export; use crate::git_util::print_git_import_stats; use crate::merge_tools::DiffEditor; use crate::merge_tools::MergeEditor; use crate::merge_tools::MergeToolConfigError; use crate::operation_templater::OperationTemplateLanguage; use crate::operation_templater::OperationTemplateLanguageExtension; use crate::revset_util; use crate::revset_util::RevsetExpressionEvaluator; use crate::template_builder; use crate::template_builder::TemplateLanguage; use crate::template_parser::TemplateAliasesMap; use crate::template_parser::TemplateDiagnostics; use crate::templater::PropertyPlaceholder; use crate::templater::TemplateRenderer; use crate::text_util; use crate::ui::ColorChoice; use crate::ui::Ui; const SHORT_CHANGE_ID_TEMPLATE_TEXT: &str = "format_short_change_id(self.change_id())"; #[derive(Clone)] struct ChromeTracingFlushGuard { _inner: Option<Rc<tracing_chrome::FlushGuard>>, } impl Debug for ChromeTracingFlushGuard { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let Self { _inner } = self; f.debug_struct("ChromeTracingFlushGuard") .finish_non_exhaustive() } } /// Handle to initialize or change tracing subscription. #[derive(Clone, Debug)] pub struct TracingSubscription { reload_log_filter: tracing_subscriber::reload::Handle< tracing_subscriber::EnvFilter, tracing_subscriber::Registry, >, _chrome_tracing_flush_guard: ChromeTracingFlushGuard, } impl TracingSubscription { /// Initializes tracing with the default configuration. This should be /// called as early as possible. pub fn init() -> Self { let filter = tracing_subscriber::EnvFilter::builder() .with_default_directive(tracing::metadata::LevelFilter::ERROR.into()) .from_env_lossy(); let (filter, reload_log_filter) = tracing_subscriber::reload::Layer::new(filter); let (chrome_tracing_layer, chrome_tracing_flush_guard) = match std::env::var("JJ_TRACE") { Ok(filename) => { let filename = if filename.is_empty() { format!( "jj-trace-{}.json", SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .as_secs(), ) } else { filename }; let include_args = std::env::var("JJ_TRACE_INCLUDE_ARGS").is_ok(); let (layer, guard) = ChromeLayerBuilder::new() .file(filename) .include_args(include_args) .build(); ( Some(layer), ChromeTracingFlushGuard { _inner: Some(Rc::new(guard)), }, ) } Err(_) => (None, ChromeTracingFlushGuard { _inner: None }), }; tracing_subscriber::registry() .with( tracing_subscriber::fmt::Layer::default() .with_writer(std::io::stderr) .with_filter(filter), ) .with(chrome_tracing_layer) .init(); TracingSubscription { reload_log_filter, _chrome_tracing_flush_guard: chrome_tracing_flush_guard, } } pub fn enable_debug_logging(&self) -> Result<(), CommandError> { self.reload_log_filter .modify(|filter| { *filter = tracing_subscriber::EnvFilter::builder() .with_default_directive(tracing::metadata::LevelFilter::DEBUG.into()) .from_env_lossy(); }) .map_err(|err| internal_error_with_message("failed to enable debug logging", err))?; tracing::info!("debug logging enabled"); Ok(()) } } #[derive(Clone)] pub struct CommandHelper { data: Rc<CommandHelperData>, } struct CommandHelperData { app: Command, cwd: PathBuf, string_args: Vec<String>, matches: ArgMatches, global_args: GlobalArgs, settings: UserSettings, layered_configs: LayeredConfigs, revset_extensions: Arc<RevsetExtensions>, commit_template_extensions: Vec<Arc<dyn CommitTemplateLanguageExtension>>, operation_template_extensions: Vec<Arc<dyn OperationTemplateLanguageExtension>>, maybe_workspace_loader: Result<Box<dyn WorkspaceLoader>, CommandError>, store_factories: StoreFactories, working_copy_factories: WorkingCopyFactories, } impl CommandHelper { pub fn app(&self) -> &Command { &self.data.app } /// Canonical form of the current working directory path. /// /// A loaded `Workspace::workspace_root()` also returns a canonical path, so /// relative paths can be easily computed from these paths. pub fn cwd(&self) -> &Path { &self.data.cwd } pub fn string_args(&self) -> &Vec<String> { &self.data.string_args } pub fn matches(&self) -> &ArgMatches { &self.data.matches } pub fn global_args(&self) -> &GlobalArgs { &self.data.global_args } pub fn settings(&self) -> &UserSettings { &self.data.settings } pub fn resolved_config_values( &self, prefix: &ConfigNamePathBuf, ) -> Result<Vec<AnnotatedValue>, crate::config::ConfigError> { self.data.layered_configs.resolved_config_values(prefix) } pub fn revset_extensions(&self) -> &Arc<RevsetExtensions> { &self.data.revset_extensions } /// Loads template aliases from the configs. /// /// For most commands that depend on a loaded repo, you should use /// `WorkspaceCommandHelper::template_aliases_map()` instead. fn load_template_aliases(&self, ui: &Ui) -> Result<TemplateAliasesMap, CommandError> { load_template_aliases(ui, &self.data.layered_configs) } /// Parses template of the given language into evaluation tree. /// /// This function also loads template aliases from the settings. Use /// `WorkspaceCommandHelper::parse_template()` if you've already /// instantiated the workspace helper. pub fn parse_template<'a, C: Clone + 'a, L: TemplateLanguage<'a> + ?Sized>( &self, ui: &Ui, language: &L, template_text: &str, wrap_self: impl Fn(PropertyPlaceholder<C>) -> L::Property, ) -> Result<TemplateRenderer<'a, C>, CommandError> { let mut diagnostics = TemplateDiagnostics::new(); let aliases = self.load_template_aliases(ui)?; let template = template_builder::parse( language, &mut diagnostics, template_text, &aliases, wrap_self, )?; print_parse_diagnostics(ui, "In template expression", &diagnostics)?; Ok(template) } pub fn workspace_loader(&self) -> Result<&dyn WorkspaceLoader, CommandError> { self.data .maybe_workspace_loader .as_deref() .map_err(Clone::clone) } /// Loads workspace and repo, then snapshots the working copy if allowed. #[instrument(skip(self, ui))] pub fn workspace_helper(&self, ui: &Ui) -> Result<WorkspaceCommandHelper, CommandError> { let mut workspace_command = self.workspace_helper_no_snapshot(ui)?; workspace_command.maybe_snapshot(ui)?; Ok(workspace_command) } /// Loads workspace and repo, but never snapshots the working copy. Most /// commands should use `workspace_helper()` instead. #[instrument(skip(self, ui))] pub fn workspace_helper_no_snapshot( &self, ui: &Ui, ) -> Result<WorkspaceCommandHelper, CommandError> { let workspace = self.load_workspace()?; let op_head = self.resolve_operation(ui, workspace.repo_loader())?; let repo = workspace.repo_loader().load_at(&op_head)?; let env = self.workspace_environment(ui, &workspace)?; revset_util::warn_unresolvable_trunk(ui, repo.as_ref(), &env.revset_parse_context())?; WorkspaceCommandHelper::new(ui, workspace, repo, env, self.is_at_head_operation()) } pub fn get_store_factories(&self) -> &StoreFactories { &self.data.store_factories } pub fn get_working_copy_factory(&self) -> Result<&dyn WorkingCopyFactory, CommandError> { let loader = self.workspace_loader()?; // We convert StoreLoadError -> WorkspaceLoadError -> CommandError let factory: Result<_, WorkspaceLoadError> = get_working_copy_factory(loader, &self.data.working_copy_factories) .map_err(|e| e.into()); let factory = factory.map_err(|err| { map_workspace_load_error(err, self.data.global_args.repository.as_deref()) })?; Ok(factory) } #[instrument(skip_all)] pub fn load_workspace(&self) -> Result<Workspace, CommandError> { let loader = self.workspace_loader()?; loader .load( &self.data.settings, &self.data.store_factories, &self.data.working_copy_factories, ) .map_err(|err| { map_workspace_load_error(err, self.data.global_args.repository.as_deref()) }) } /// Loads command environment for the given `workspace`. pub fn workspace_environment( &self, ui: &Ui, workspace: &Workspace, ) -> Result<WorkspaceCommandEnvironment, CommandError> { WorkspaceCommandEnvironment::new(ui, self, workspace) } /// Returns true if the working copy to be loaded is writable, and therefore /// should usually be snapshotted. pub fn is_working_copy_writable(&self) -> bool { self.is_at_head_operation() && !self.data.global_args.ignore_working_copy } /// Returns true if the current operation is considered to be the head. pub fn is_at_head_operation(&self) -> bool { // 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.) matches!( self.data.global_args.at_operation.as_deref(), None | Some("@") ) } /// Resolves the current operation from the command-line argument. /// /// If no `--at-operation` is specified, the head operations will be /// loaded. If there are multiple heads, they'll be merged. #[instrument(skip_all)] pub fn resolve_operation( &self, ui: &Ui, repo_loader: &RepoLoader, ) -> Result<Operation, CommandError> { if let Some(op_str) = &self.data.global_args.at_operation { Ok(op_walk::resolve_op_for_load(repo_loader, op_str)?) } else { op_heads_store::resolve_op_heads( repo_loader.op_heads_store().as_ref(), repo_loader.op_store(), |op_heads| { writeln!( ui.status(), "Concurrent modification detected, resolving automatically.", )?; let base_repo = repo_loader.load_at(&op_heads[0])?; // TODO: It may be helpful to print each operation we're merging here let mut tx = start_repo_transaction( &base_repo, &self.data.settings, &self.data.string_args, ); for other_op_head in op_heads.into_iter().skip(1) { tx.merge_operation(other_op_head)?; let num_rebased = tx.repo_mut().rebase_descendants(&self.data.settings)?; if num_rebased > 0 { writeln!( ui.status(), "Rebased {num_rebased} descendant commits onto commits rewritten \ by other operation" )?; } } Ok(tx .write("reconcile divergent operations") .leave_unpublished() .operation() .clone()) }, ) } } /// Creates helper for the repo whose view is supposed to be in sync with /// the working copy. If `--ignore-working-copy` is not specified, the /// returned helper will attempt to update the working copy. #[instrument(skip_all)] pub fn for_workable_repo( &self, ui: &Ui,