2890 lines · 103.4 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::collections::{HashMap, HashSet, VecDeque};use std::env::{self, ArgsOs, VarError};use std::ffi::{OsStr, OsString};use std::fmt::Debug;use std::io::Write as _;use std::ops::Deref;use std::path::{Path, PathBuf};use std::process::ExitCode;use std::rc::Rc;use std::str::FromStr;use std::sync::Arc;use std::time::SystemTime;use std::{iter, str}; use clap::builder::{NonEmptyStringValueParser, TypedValueParser, ValueParserFactory};use clap::{Arg, ArgAction, ArgMatches, Command, FromArgMatches};use indexmap::IndexSet;use itertools::Itertools;use jj_lib::backend::{BackendError, ChangeId, CommitId, MergedTreeId, ObjectId};use jj_lib::commit::Commit;use jj_lib::git::{    FailedRefExport, FailedRefExportReason, GitConfigParseError, GitExportError, GitImportError,    GitImportStats, GitRemoteManagementError,};use jj_lib::git_backend::GitBackend;use jj_lib::gitignore::GitIgnoreFile;use jj_lib::hex_util::to_reverse_hex;use jj_lib::id_prefix::IdPrefixContext;use jj_lib::matchers::{EverythingMatcher, Matcher, PrefixMatcher};use jj_lib::merged_tree::MergedTree;use jj_lib::op_heads_store::{self, OpHeadResolutionError, OpHeadsStore};use jj_lib::op_store::{OpStore, OpStoreError, OperationId, WorkspaceId};use jj_lib::operation::Operation;use jj_lib::repo::{    CheckOutCommitError, EditCommitError, MutableRepo, ReadonlyRepo, Repo, RepoLoader,    RepoLoaderError, RewriteRootCommit, StoreFactories, StoreLoadError,};use jj_lib::repo_path::{FsPathParseError, RepoPath};use jj_lib::revset::{    DefaultSymbolResolver, Revset, RevsetAliasesMap, RevsetCommitRef, RevsetEvaluationError,    RevsetExpression, RevsetIteratorExt, RevsetParseContext, RevsetParseError,    RevsetParseErrorKind, RevsetResolutionError, RevsetWorkspaceContext,};use jj_lib::rewrite::restore_tree;use jj_lib::settings::{ConfigResultExt as _, UserSettings};use jj_lib::str_util::{StringPattern, StringPatternParseError};use jj_lib::transaction::Transaction;use jj_lib::tree::TreeMergeError;use jj_lib::working_copy::{    CheckoutStats, LockedWorkingCopy, ResetError, SnapshotError, SnapshotOptions, WorkingCopy,    WorkingCopyStateError,};use jj_lib::workspace::{    default_working_copy_factories, LockedWorkspace, WorkingCopyFactory, Workspace,    WorkspaceInitError, WorkspaceLoadError, WorkspaceLoader,};use jj_lib::{dag_walk, file_util, git, revset};use once_cell::unsync::OnceCell;use toml_edit;use tracing::instrument;use tracing_chrome::ChromeLayerBuilder;use tracing_subscriber::prelude::*; use crate::config::{    new_config_path, AnnotatedValue, CommandNameAndArgs, ConfigSource, LayeredConfigs,};use crate::formatter::{FormatRecorder, Formatter, PlainTextFormatter};use crate::merge_tools::{ConflictResolveError, DiffEditError, DiffGenerateError};use crate::template_parser::{TemplateAliasesMap, TemplateParseError};use crate::templater::Template;use crate::ui::{ColorChoice, Ui};use crate::{commit_templater, text_util}; #[derive(Clone, Debug)]pub enum CommandError {    UserError {        message: String,        hint: Option<String>,    },    ConfigError(String),    /// Invalid command line    CliError(String),    /// Invalid command line detected by clap    ClapCliError(Arc<clap::Error>),    BrokenPipe,    InternalError(String),} pub fn user_error(message: impl Into<String>) -> CommandError {    CommandError::UserError {        message: message.into(),        hint: None,    }}pub fn user_error_with_hint(message: impl Into<String>, hint: impl Into<String>) -> CommandError {    CommandError::UserError {        message: message.into(),        hint: Some(hint.into()),    }} fn format_similarity_hint<S: AsRef<str>>(candidates: &[S]) -> Option<String> {    match candidates {        [] => None,        names => {            let quoted_names = names                .iter()                .map(|s| format!(r#""{}""#, s.as_ref()))                .join(", ");            Some(format!("Did you mean {quoted_names}?"))        }    }} impl From<std::io::Error> for CommandError {    fn from(err: std::io::Error) -> Self {        if err.kind() == std::io::ErrorKind::BrokenPipe {            CommandError::BrokenPipe        } else {            // TODO: Record the error as a chained cause            CommandError::InternalError(format!("I/O error: {err}"))        }    }} impl From<config::ConfigError> for CommandError {    fn from(err: config::ConfigError) -> Self {        CommandError::ConfigError(err.to_string())    }} impl From<crate::config::ConfigError> for CommandError {    fn from(err: crate::config::ConfigError) -> Self {        CommandError::ConfigError(err.to_string())    }} impl From<RewriteRootCommit> for CommandError {    fn from(err: RewriteRootCommit) -> Self {        CommandError::InternalError(format!("Attempted to rewrite the root commit: {err}"))    }} impl From<EditCommitError> for CommandError {    fn from(err: EditCommitError) -> Self {        CommandError::InternalError(format!("Failed to edit a commit: {err}"))    }} impl From<CheckOutCommitError> for CommandError {    fn from(err: CheckOutCommitError) -> Self {        CommandError::InternalError(format!("Failed to check out a commit: {err}"))    }} impl From<BackendError> for CommandError {    fn from(err: BackendError) -> Self {        user_error(format!("Unexpected error from backend: {err}"))    }} impl From<WorkspaceInitError> for CommandError {    fn from(err: WorkspaceInitError) -> Self {        match err {            WorkspaceInitError::DestinationExists(_) => {                user_error("The target repo already exists")            }            WorkspaceInitError::NonUnicodePath => {                user_error("The target repo path contains non-unicode characters")            }            WorkspaceInitError::CheckOutCommit(err) => CommandError::InternalError(format!(                "Failed to check out the initial commit: {err}"            )),            WorkspaceInitError::Path(err) => {                CommandError::InternalError(format!("Failed to access the repository: {err}"))            }            WorkspaceInitError::Backend(err) => {                user_error(format!("Failed to access the repository: {err}"))            }            WorkspaceInitError::WorkingCopyState(err) => {                CommandError::InternalError(format!("Failed to access the repository: {err}"))            }        }    }} impl From<OpHeadResolutionError<CommandError>> for CommandError {    fn from(err: OpHeadResolutionError<CommandError>) -> Self {        match err {            OpHeadResolutionError::NoHeads => CommandError::InternalError(                "Corrupt repository: there are no operations".to_string(),            ),            OpHeadResolutionError::OpStore(err) => err.into(),            OpHeadResolutionError::Err(e) => e,        }    }} impl From<SnapshotError> for CommandError {    fn from(err: SnapshotError) -> Self {        match err {            SnapshotError::NewFileTooLarge { .. } => user_error_with_hint(                format!("Failed to snapshot the working copy: {err}"),                r#"Increase the value of the `snapshot.max-new-file-size` config option if youwant this file to be snapshotted. Otherwise add it to your `.gitignore` file."#,            ),            err => {                CommandError::InternalError(format!("Failed to snapshot the working copy: {err}"))            }        }    }} impl From<TreeMergeError> for CommandError {    fn from(err: TreeMergeError) -> Self {        CommandError::InternalError(format!("Merge failed: {err}"))    }} impl From<OpStoreError> for CommandError {    fn from(err: OpStoreError) -> Self {        CommandError::InternalError(format!("Failed to load an operation: {err}"))    }} impl From<RepoLoaderError> for CommandError {    fn from(err: RepoLoaderError) -> Self {        CommandError::InternalError(format!("Failed to load the repo: {err}"))    }} impl From<ResetError> for CommandError {    fn from(_: ResetError) -> Self {        CommandError::InternalError("Failed to reset the working copy".to_string())    }} impl From<DiffEditError> for CommandError {    fn from(err: DiffEditError) -> Self {        user_error(format!("Failed to edit diff: {err}"))    }} impl From<DiffGenerateError> for CommandError {    fn from(err: DiffGenerateError) -> Self {        user_error(format!("Failed to generate diff: {err}"))    }} impl From<ConflictResolveError> for CommandError {    fn from(err: ConflictResolveError) -> Self {        user_error(format!("Failed to resolve conflicts: {err}"))    }} impl From<git2::Error> for CommandError {    fn from(err: git2::Error) -> Self {        user_error(format!("Git operation failed: {err}"))    }} impl From<GitImportError> for CommandError {    fn from(err: GitImportError) -> Self {        let message = format!("Failed to import refs from underlying Git repo: {err}");        let hint = match &err {            GitImportError::MissingHeadTarget { .. }            | GitImportError::MissingRefAncestor { .. } => Some(                "\Is this Git repository a shallow or partial clone (cloned with the --depth or --filter \                 argument)?jj currently does not support shallow/partial clones. To use jj with this \                 repository, tryunshallowing the repository (https://stackoverflow.com/q/6802145) or re-cloning with the fullrepository contents."                    .to_string(),            ),            GitImportError::RemoteReservedForLocalGitRepo => {                Some("Run `jj git remote rename` to give different name.".to_string())            }            GitImportError::InternalGitError(_) => None,            GitImportError::UnexpectedBackend => None,        };        CommandError::UserError { message, hint }    }} impl From<GitExportError> for CommandError {    fn from(err: GitExportError) -> Self {        CommandError::InternalError(format!(            "Failed to export refs to underlying Git repo: {err}"        ))    }} impl From<GitRemoteManagementError> for CommandError {    fn from(err: GitRemoteManagementError) -> Self {        user_error(format!("{err}"))    }} impl From<RevsetEvaluationError> for CommandError {    fn from(err: RevsetEvaluationError) -> Self {        user_error(format!("{err}"))    }} impl From<RevsetParseError> for CommandError {    fn from(err: RevsetParseError) -> Self {        let message = iter::successors(Some(&err), |e| e.origin()).join("\n");        // Only for the top-level error as we can't attach hint to inner errors        let hint = match err.kind() {            RevsetParseErrorKind::NotPostfixOperator {                op: _,                similar_op,                description,            }            | RevsetParseErrorKind::NotInfixOperator {                op: _,                similar_op,                description,            } => Some(format!("Did you mean '{similar_op}' for {description}?")),            RevsetParseErrorKind::NoSuchFunction {                name: _,                candidates,            } => format_similarity_hint(candidates),            _ => None,        };        CommandError::UserError {            message: format!("Failed to parse revset: {message}"),            hint,        }    }} impl From<RevsetResolutionError> for CommandError {    fn from(err: RevsetResolutionError) -> Self {        let hint = match &err {            RevsetResolutionError::NoSuchRevision {                name: _,                candidates,            } => format_similarity_hint(candidates),            RevsetResolutionError::EmptyString            | RevsetResolutionError::WorkspaceMissingWorkingCopy { .. }            | RevsetResolutionError::AmbiguousCommitIdPrefix(_)            | RevsetResolutionError::AmbiguousChangeIdPrefix(_)            | RevsetResolutionError::StoreError(_) => None,        };         CommandError::UserError {            message: format!("{err}"),            hint,        }    }} impl From<TemplateParseError> for CommandError {    fn from(err: TemplateParseError) -> Self {        let message = iter::successors(Some(&err), |e| e.origin()).join("\n");        user_error(format!("Failed to parse template: {message}"))    }} impl From<FsPathParseError> for CommandError {    fn from(err: FsPathParseError) -> Self {        user_error(format!("{err}"))    }} impl From<clap::Error> for CommandError {    fn from(err: clap::Error) -> Self {        CommandError::ClapCliError(Arc::new(err))    }} impl From<GitConfigParseError> for CommandError {    fn from(err: GitConfigParseError) -> Self {        CommandError::InternalError(format!("Failed to parse Git config: {err} "))    }} impl From<WorkingCopyStateError> for CommandError {    fn from(err: WorkingCopyStateError) -> Self {        CommandError::InternalError(format!("Failed to access working copy state: {err}"))    }} #[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_verbose_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| {                CommandError::InternalError(format!("failed to enable verbose logging: {err}"))            })?;        tracing::info!("verbose logging enabled");        Ok(())    }} pub struct CommandHelper {    app: Command,    cwd: PathBuf,    string_args: Vec<String>,    matches: ArgMatches,    global_args: GlobalArgs,    settings: UserSettings,    layered_configs: LayeredConfigs,    maybe_workspace_loader: Result<WorkspaceLoader, CommandError>,    store_factories: StoreFactories,    working_copy_factories: HashMap<String, WorkingCopyFactory>,

Lines 1–500 of 2890.