3473 lines · 128.2 KB · Rust
RawDownload
// 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.