// 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::BTreeSet;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::ops::Range;use std::path::Path;use std::path::PathBuf;use std::pin::Pin;use std::rc::Rc;use std::sync::Arc;use std::sync::LazyLock;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 futures::TryStreamExt as _;use futures::future::try_join_all;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::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::ConfigValue;use jj_lib::config::StackedConfig;use jj_lib::conflicts::ConflictMarkerStyle;use jj_lib::default_backend_factories::default_backend_factories;use jj_lib::default_backend_factories::default_working_copy_factories;use jj_lib::fileset;use jj_lib::fileset::FilesetAliasesMap;use jj_lib::fileset::FilesetDiagnostics;use jj_lib::fileset::FilesetExpression;use jj_lib::fileset::FilesetParseContext;use jj_lib::gitignore::GitIgnoreError;use jj_lib::gitignore::GitIgnoreFile;use jj_lib::id_prefix::IdPrefixContext;use jj_lib::lock::FileLock;use jj_lib::matchers::Matcher;use jj_lib::matchers::NothingMatcher;use jj_lib::merge::Diff;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::RemoteName;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::RevsetParseContext;use jj_lib::revset::RevsetStreamExt as _;use jj_lib::revset::RevsetWorkspaceContext;use jj_lib::revset::SymbolResolverExtension;use jj_lib::revset::UserRevsetExpression;use jj_lib::rewrite::RebaseOptions;use jj_lib::rewrite::restore_tree;use jj_lib::settings::HumanByteSize;use jj_lib::settings::UserSettings;use jj_lib::store::Store;use jj_lib::str_util::StringExpression;use jj_lib::str_util::StringMatcher;use jj_lib::transaction::Transaction;use jj_lib::transaction::TransactionCommitError;use jj_lib::working_copy;use jj_lib::working_copy::CheckoutStats;use jj_lib::working_copy::LockedWorkingCopy;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::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_error_sources;use crate::command_error::print_parse_diagnostics;use crate::command_error::user_error;use crate::command_error::user_error_with_message;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::load_aliases_map;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::formatter::FormatterExt as _;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::revset_util::parse_union_name_patterns;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_with_change_offset(self)"; #[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 /// 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, ui: &Ui, workspace_root: &Path, ) -> Result<(UserSettings, ConfigEnv), 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(ui, &mut raw_config)?; config_env.reset_workspace_path(workspace_root); config_env.reload_workspace_config(ui, &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)?, config_env)) } /// 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 should_commit_transaction(&self) -> bool { !self.global_args().no_integrate_operation } async fn maybe_commit_transaction( &self, tx: Transaction, description: impl Into<String>, ) -> Result<Arc<ReadonlyRepo>, TransactionCommitError> { let unpublished_op = tx.write(description).await?; if self.should_commit_transaction() { unpublished_op.publish().await } else { Ok(unpublished_op.leave_unpublished()) } } 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 async fn workspace_helper(&self, ui: &Ui) -> Result<WorkspaceCommandHelper, CommandError> { let (workspace_command, stats, _) = self.workspace_helper_with_stats(ui).await?; print_snapshot_stats(ui, &stats, workspace_command.env().path_converter())?; Ok(workspace_command) } /// Loads workspace and repo, then snapshots the working copy if allowed. /// Returns [`SnapshotStats`] and a bool indicating if a snapshot was taken. /// /// 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 async fn workspace_helper_with_stats( &self, ui: &Ui, ) -> Result<(WorkspaceCommandHelper, SnapshotStats, bool), CommandError> { let workspace = self.load_workspace()?; let env = self.workspace_environment(ui, &workspace)?; // Acquire the lock to ensure that the loaded repo points to the head // operation whose refs should be synchronized with the Git repo. This // prevents races with other processes during Git HEAD and refs // import/export. let git_import_export_lock = self .is_working_copy_writable() .then(|| env.lock_git_import_export(&workspace)) .transpose()?; let mut workspace_command = self.load_from_workspace(ui, workspace, env).await?; let Some(git_import_export_lock) = git_import_export_lock else { return Ok((workspace_command, SnapshotStats::default(), false)); }; let old_repo = workspace_command.repo().clone(); let (workspace_command, stats) = match workspace_command .snapshot_impl(ui, &git_import_export_lock) .await { 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 {Lines 1–500 of 4799. 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::BTreeSet; 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::ops::Range; use std::path::Path; use std::path::PathBuf; use std::pin::Pin; use std::rc::Rc; use std::sync::Arc; use std::sync::LazyLock; 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 futures::TryStreamExt as _; use futures::future::try_join_all; 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::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::ConfigValue; use jj_lib::config::StackedConfig; use jj_lib::conflicts::ConflictMarkerStyle; use jj_lib::default_backend_factories::default_backend_factories; use jj_lib::default_backend_factories::default_working_copy_factories; use jj_lib::fileset; use jj_lib::fileset::FilesetAliasesMap; use jj_lib::fileset::FilesetDiagnostics; use jj_lib::fileset::FilesetExpression; use jj_lib::fileset::FilesetParseContext; use jj_lib::gitignore::GitIgnoreError; use jj_lib::gitignore::GitIgnoreFile; use jj_lib::id_prefix::IdPrefixContext; use jj_lib::lock::FileLock; use jj_lib::matchers::Matcher; use jj_lib::matchers::NothingMatcher; use jj_lib::merge::Diff; 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::RemoteName; 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::RevsetParseContext; use jj_lib::revset::RevsetStreamExt as _; use jj_lib::revset::RevsetWorkspaceContext; use jj_lib::revset::SymbolResolverExtension; use jj_lib::revset::UserRevsetExpression; use jj_lib::rewrite::RebaseOptions; use jj_lib::rewrite::restore_tree; use jj_lib::settings::HumanByteSize; use jj_lib::settings::UserSettings; use jj_lib::store::Store; use jj_lib::str_util::StringExpression; use jj_lib::str_util::StringMatcher; use jj_lib::transaction::Transaction; use jj_lib::transaction::TransactionCommitError; use jj_lib::working_copy; use jj_lib::working_copy::CheckoutStats; use jj_lib::working_copy::LockedWorkingCopy; 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::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_error_sources; use crate::command_error::print_parse_diagnostics; use crate::command_error::user_error; use crate::command_error::user_error_with_message; 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::load_aliases_map; 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::formatter::FormatterExt as _; 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::revset_util::parse_union_name_patterns; 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_with_change_offset(self)"; #[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 /// 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, ui: &Ui, workspace_root: &Path, ) -> Result<(UserSettings, ConfigEnv), 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(ui, &mut raw_config)?; config_env.reset_workspace_path(workspace_root); config_env.reload_workspace_config(ui, &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)?, config_env)) } /// 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 should_commit_transaction(&self) -> bool { !self.global_args().no_integrate_operation } async fn maybe_commit_transaction( &self, tx: Transaction, description: impl Into<String>, ) -> Result<Arc<ReadonlyRepo>, TransactionCommitError> { let unpublished_op = tx.write(description).await?; if self.should_commit_transaction() { unpublished_op.publish().await } else { Ok(unpublished_op.leave_unpublished()) } } 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 async fn workspace_helper(&self, ui: &Ui) -> Result<WorkspaceCommandHelper, CommandError> { let (workspace_command, stats, _) = self.workspace_helper_with_stats(ui).await?; print_snapshot_stats(ui, &stats, workspace_command.env().path_converter())?; Ok(workspace_command) } /// Loads workspace and repo, then snapshots the working copy if allowed. /// Returns [`SnapshotStats`] and a bool indicating if a snapshot was taken. /// /// 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 async fn workspace_helper_with_stats( &self, ui: &Ui, ) -> Result<(WorkspaceCommandHelper, SnapshotStats, bool), CommandError> { let workspace = self.load_workspace()?; let env = self.workspace_environment(ui, &workspace)?; // Acquire the lock to ensure that the loaded repo points to the head // operation whose refs should be synchronized with the Git repo. This // prevents races with other processes during Git HEAD and refs // import/export. let git_import_export_lock = self .is_working_copy_writable() .then(|| env.lock_git_import_export(&workspace)) .transpose()?; let mut workspace_command = self.load_from_workspace(ui, workspace, env).await?; let Some(git_import_export_lock) = git_import_export_lock else { return Ok((workspace_command, SnapshotStats::default(), false)); }; let old_repo = workspace_command.repo().clone(); let (workspace_command, stats) = match workspace_command .snapshot_impl(ui, &git_import_export_lock) .await { 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 {