// 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::HashMap;use std::collections::HashSet;use std::env;use std::ffi::OsString;use std::fmt;use std::fmt::Debug;use std::io;use std::io::Write as _;use std::mem;use std::path::Path;use std::path::PathBuf;use std::rc::Rc;use std::slice;use std::str;use std::str::FromStr;use std::sync::Arc;use std::time::SystemTime; use bstr::ByteVec as _;use chrono::TimeZone as _;use clap::ArgAction;use clap::ArgMatches;use clap::Command;use clap::FromArgMatches as _;use clap::builder::MapValueParser;use clap::builder::NonEmptyStringValueParser;use clap::builder::TypedValueParser as _;use clap::builder::ValueParserFactory;use clap::error::ContextKind;use clap::error::ContextValue;use clap_complete::ArgValueCandidates;use clap_complete::ArgValueCompleter;use indexmap::IndexMap;use indexmap::IndexSet;use indoc::indoc;use indoc::writedoc;use itertools::Itertools as _;use jj_lib::backend::BackendResult;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::config::ConfigGetError;use jj_lib::config::ConfigGetResultExt as _;use jj_lib::config::ConfigLayer;use jj_lib::config::ConfigMigrationRule;use jj_lib::config::ConfigNamePathBuf;use jj_lib::config::ConfigSource;use jj_lib::config::StackedConfig;use jj_lib::conflicts::ConflictMarkerStyle;use jj_lib::fileset;use jj_lib::fileset::FilesetDiagnostics;use jj_lib::fileset::FilesetExpression;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 as _;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_walk;use jj_lib::op_walk::OpsetEvaluationError;use jj_lib::operation::Operation;use jj_lib::ref_name::RefName;use jj_lib::ref_name::RefNameBuf;use jj_lib::ref_name::WorkspaceName;use jj_lib::ref_name::WorkspaceNameBuf;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::merge_factories_map;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 as _;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::HumanByteSize;use jj_lib::settings::UserSettings;use jj_lib::str_util::StringPattern;use jj_lib::transaction::Transaction;use jj_lib::view::View;use jj_lib::working_copy;use jj_lib::working_copy::CheckoutStats;use jj_lib::working_copy::SnapshotOptions;use jj_lib::working_copy::SnapshotStats;use jj_lib::working_copy::UntrackedReason;use jj_lib::working_copy::WorkingCopy;use jj_lib::working_copy::WorkingCopyFactory;use jj_lib::working_copy::WorkingCopyFreshness;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 jj_lib::workspace::default_working_copy_factories;use jj_lib::workspace::get_working_copy_factory;use pollster::FutureExt as _;use tracing::instrument;use tracing_chrome::ChromeLayerBuilder;use tracing_subscriber::prelude::*; use crate::command_error::CommandError;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::commit_templater::CommitTemplateLanguage;use crate::commit_templater::CommitTemplateLanguageExtension;use crate::complete;use crate::config::ConfigArgKind;use crate::config::ConfigEnv;use crate::config::RawConfig;use crate::config::config_from_environment;use crate::config::parse_config_args;use crate::description_util::TextEditor;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::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::TemplateRenderer;use crate::templater::WrapTemplateProperty;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 { const ENV_VAR_NAME: &str = "JJ_LOG"; /// 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()) .with_env_var(Self::ENV_VAR_NAME) .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(); Self { 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| { // The default is INFO. // jj-lib and jj-cli are whitelisted for DEBUG logging. // This ensures that other crates' logging doesn't show up by default. *filter = tracing_subscriber::EnvFilter::builder() .with_default_directive(tracing::metadata::LevelFilter::INFO.into()) .with_env_var(Self::ENV_VAR_NAME) .from_env_lossy() .add_directive("jj_lib=debug".parse().unwrap()) .add_directive("jj_cli=debug".parse().unwrap()); }) .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, config_env: ConfigEnv, config_migrations: Vec<ConfigMigrationRule>, raw_config: RawConfig, settings: UserSettings, 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, workspace_loader_factory: Box<dyn WorkspaceLoaderFactory>,} 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 config_env(&self) -> &ConfigEnv { &self.data.config_env } /// Unprocessed (or unresolved) configuration data. /// /// Use this only if the unmodified config data is needed. For example, `jj /// config set` should use this to write updated data back to file. pub fn raw_config(&self) -> &RawConfig { &self.data.raw_config } /// Settings for the current command and workspace. /// /// This may be different from the settings for new workspace created by /// e.g. `jj git init`. There may be conditional variables and repo config /// `.jj/repo/config.toml` loaded for the cwd workspace. pub fn settings(&self) -> &UserSettings { &self.data.settings } /// Resolves configuration for new workspace located at the specified path. pub fn settings_for_new_workspace( &self, workspace_root: &Path, ) -> Result<UserSettings, CommandError> { let mut config_env = self.data.config_env.clone(); let mut raw_config = self.data.raw_config.clone(); let repo_path = workspace_root.join(".jj").join("repo"); config_env.reset_repo_path(&repo_path); config_env.reload_repo_config(&mut raw_config)?; let mut config = config_env.resolve_config(&raw_config)?; // No migration messages here, which would usually be emitted before. jj_lib::config::migrate(&mut config, &self.data.config_migrations)?; Ok(self.data.settings.with_new_config(config)?) } /// Loads text editor from the settings. pub fn text_editor(&self) -> Result<TextEditor, ConfigGetError> { TextEditor::from_settings(self.settings()) } pub fn revset_extensions(&self) -> &Arc<RevsetExtensions> { &self.data.revset_extensions } /// 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, L>( &self, ui: &Ui, language: &L, template_text: &str, ) -> Result<TemplateRenderer<'a, C>, CommandError> where C: Clone + 'a, L: TemplateLanguage<'a> + ?Sized, L::Property: WrapTemplateProperty<'a, C>, { let mut diagnostics = TemplateDiagnostics::new(); let aliases = load_template_aliases(ui, self.settings().config())?; let template = template_builder::parse(language, &mut diagnostics, template_text, &aliases)?; 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) } fn new_workspace_loader_at( &self, workspace_root: &Path, ) -> Result<Box<dyn WorkspaceLoader>, CommandError> { self.data .workspace_loader_factory .create(workspace_root) .map_err(|err| map_workspace_load_error(err, None)) } /// 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 (workspace_command, stats) = self.workspace_helper_with_stats(ui)?; print_snapshot_stats(ui, &stats, workspace_command.env().path_converter())?; Ok(workspace_command) } /// Loads workspace and repo, then snapshots the working copy if allowed and /// returns the SnapshotStats. /// /// Note that unless you have a good reason not to do so, you should always /// call [`print_snapshot_stats`] with the [`SnapshotStats`] returned by /// this function to present possible untracked files to the user. #[instrument(skip(self, ui))] pub fn workspace_helper_with_stats( &self, ui: &Ui, ) -> Result<(WorkspaceCommandHelper, SnapshotStats), CommandError> { let mut workspace_command = self.workspace_helper_no_snapshot(ui)?; let (workspace_command, stats) = match workspace_command.maybe_snapshot_impl(ui) { Ok(stats) => (workspace_command, stats), Err(SnapshotWorkingCopyError::Command(err)) => return Err(err), Err(SnapshotWorkingCopyError::StaleWorkingCopy(err)) => { let auto_update_stale = self.settings().get_bool("snapshot.auto-update-stale")?; if !auto_update_stale { return Err(err); } // We detected the working copy was stale and the client is configured to // auto-update-stale, so let's do that now. We need to do it up here, not at a // lower level (e.g. inside snapshot_working_copy()) to avoid recursive locking // of the working copy. self.recover_stale_working_copy(ui)? } }; Ok((workspace_command, stats)) } /// 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_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) } /// Loads workspace for the current command. #[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())Lines 1–500 of 4069. 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::HashMap; use std::collections::HashSet; use std::env; use std::ffi::OsString; use std::fmt; use std::fmt::Debug; use std::io; use std::io::Write as _; use std::mem; use std::path::Path; use std::path::PathBuf; use std::rc::Rc; use std::slice; use std::str; use std::str::FromStr; use std::sync::Arc; use std::time::SystemTime; use bstr::ByteVec as _; use chrono::TimeZone as _; use clap::ArgAction; use clap::ArgMatches; use clap::Command; use clap::FromArgMatches as _; use clap::builder::MapValueParser; use clap::builder::NonEmptyStringValueParser; use clap::builder::TypedValueParser as _; use clap::builder::ValueParserFactory; use clap::error::ContextKind; use clap::error::ContextValue; use clap_complete::ArgValueCandidates; use clap_complete::ArgValueCompleter; use indexmap::IndexMap; use indexmap::IndexSet; use indoc::indoc; use indoc::writedoc; use itertools::Itertools as _; use jj_lib::backend::BackendResult; 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::config::ConfigGetError; use jj_lib::config::ConfigGetResultExt as _; use jj_lib::config::ConfigLayer; use jj_lib::config::ConfigMigrationRule; use jj_lib::config::ConfigNamePathBuf; use jj_lib::config::ConfigSource; use jj_lib::config::StackedConfig; use jj_lib::conflicts::ConflictMarkerStyle; use jj_lib::fileset; use jj_lib::fileset::FilesetDiagnostics; use jj_lib::fileset::FilesetExpression; 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 as _; 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_walk; use jj_lib::op_walk::OpsetEvaluationError; use jj_lib::operation::Operation; use jj_lib::ref_name::RefName; use jj_lib::ref_name::RefNameBuf; use jj_lib::ref_name::WorkspaceName; use jj_lib::ref_name::WorkspaceNameBuf; 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::merge_factories_map; 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 as _; 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::HumanByteSize; use jj_lib::settings::UserSettings; use jj_lib::str_util::StringPattern; use jj_lib::transaction::Transaction; use jj_lib::view::View; use jj_lib::working_copy; use jj_lib::working_copy::CheckoutStats; use jj_lib::working_copy::SnapshotOptions; use jj_lib::working_copy::SnapshotStats; use jj_lib::working_copy::UntrackedReason; use jj_lib::working_copy::WorkingCopy; use jj_lib::working_copy::WorkingCopyFactory; use jj_lib::working_copy::WorkingCopyFreshness; 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 jj_lib::workspace::default_working_copy_factories; use jj_lib::workspace::get_working_copy_factory; use pollster::FutureExt as _; use tracing::instrument; use tracing_chrome::ChromeLayerBuilder; use tracing_subscriber::prelude::*; use crate::command_error::CommandError; 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::commit_templater::CommitTemplateLanguage; use crate::commit_templater::CommitTemplateLanguageExtension; use crate::complete; use crate::config::ConfigArgKind; use crate::config::ConfigEnv; use crate::config::RawConfig; use crate::config::config_from_environment; use crate::config::parse_config_args; use crate::description_util::TextEditor; 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::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::TemplateRenderer; use crate::templater::WrapTemplateProperty; 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 { const ENV_VAR_NAME: &str = "JJ_LOG"; /// 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()) .with_env_var(Self::ENV_VAR_NAME) .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(); Self { 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| { // The default is INFO. // jj-lib and jj-cli are whitelisted for DEBUG logging. // This ensures that other crates' logging doesn't show up by default. *filter = tracing_subscriber::EnvFilter::builder() .with_default_directive(tracing::metadata::LevelFilter::INFO.into()) .with_env_var(Self::ENV_VAR_NAME) .from_env_lossy() .add_directive("jj_lib=debug".parse().unwrap()) .add_directive("jj_cli=debug".parse().unwrap()); }) .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, config_env: ConfigEnv, config_migrations: Vec<ConfigMigrationRule>, raw_config: RawConfig, settings: UserSettings, 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, workspace_loader_factory: Box<dyn WorkspaceLoaderFactory>, } 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 config_env(&self) -> &ConfigEnv { &self.data.config_env } /// Unprocessed (or unresolved) configuration data. /// /// Use this only if the unmodified config data is needed. For example, `jj /// config set` should use this to write updated data back to file. pub fn raw_config(&self) -> &RawConfig { &self.data.raw_config } /// Settings for the current command and workspace. /// /// This may be different from the settings for new workspace created by /// e.g. `jj git init`. There may be conditional variables and repo config /// `.jj/repo/config.toml` loaded for the cwd workspace. pub fn settings(&self) -> &UserSettings { &self.data.settings } /// Resolves configuration for new workspace located at the specified path. pub fn settings_for_new_workspace( &self, workspace_root: &Path, ) -> Result<UserSettings, CommandError> { let mut config_env = self.data.config_env.clone(); let mut raw_config = self.data.raw_config.clone(); let repo_path = workspace_root.join(".jj").join("repo"); config_env.reset_repo_path(&repo_path); config_env.reload_repo_config(&mut raw_config)?; let mut config = config_env.resolve_config(&raw_config)?; // No migration messages here, which would usually be emitted before. jj_lib::config::migrate(&mut config, &self.data.config_migrations)?; Ok(self.data.settings.with_new_config(config)?) } /// Loads text editor from the settings. pub fn text_editor(&self) -> Result<TextEditor, ConfigGetError> { TextEditor::from_settings(self.settings()) } pub fn revset_extensions(&self) -> &Arc<RevsetExtensions> { &self.data.revset_extensions } /// 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, L>( &self, ui: &Ui, language: &L, template_text: &str, ) -> Result<TemplateRenderer<'a, C>, CommandError> where C: Clone + 'a, L: TemplateLanguage<'a> + ?Sized, L::Property: WrapTemplateProperty<'a, C>, { let mut diagnostics = TemplateDiagnostics::new(); let aliases = load_template_aliases(ui, self.settings().config())?; let template = template_builder::parse(language, &mut diagnostics, template_text, &aliases)?; 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) } fn new_workspace_loader_at( &self, workspace_root: &Path, ) -> Result<Box<dyn WorkspaceLoader>, CommandError> { self.data .workspace_loader_factory .create(workspace_root) .map_err(|err| map_workspace_load_error(err, None)) } /// 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 (workspace_command, stats) = self.workspace_helper_with_stats(ui)?; print_snapshot_stats(ui, &stats, workspace_command.env().path_converter())?; Ok(workspace_command) } /// Loads workspace and repo, then snapshots the working copy if allowed and /// returns the SnapshotStats. /// /// Note that unless you have a good reason not to do so, you should always /// call [`print_snapshot_stats`] with the [`SnapshotStats`] returned by /// this function to present possible untracked files to the user. #[instrument(skip(self, ui))] pub fn workspace_helper_with_stats( &self, ui: &Ui, ) -> Result<(WorkspaceCommandHelper, SnapshotStats), CommandError> { let mut workspace_command = self.workspace_helper_no_snapshot(ui)?; let (workspace_command, stats) = match workspace_command.maybe_snapshot_impl(ui) { Ok(stats) => (workspace_command, stats), Err(SnapshotWorkingCopyError::Command(err)) => return Err(err), Err(SnapshotWorkingCopyError::StaleWorkingCopy(err)) => { let auto_update_stale = self.settings().get_bool("snapshot.auto-update-stale")?; if !auto_update_stale { return Err(err); } // We detected the working copy was stale and the client is configured to // auto-update-stale, so let's do that now. We need to do it up here, not at a // lower level (e.g. inside snapshot_working_copy()) to avoid recursive locking // of the working copy. self.recover_stale_working_copy(ui)? } }; Ok((workspace_command, stats)) } /// 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_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) } /// Loads workspace for the current command. #[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())