mirror of
https://github.com/martinvonz/jj.git
synced 2025-05-19 06:04:26 +00:00
244 lines
7.5 KiB
Rust
244 lines
7.5 KiB
Rust
// Copyright 2020 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::io::Write;
|
|
|
|
use clap::builder::NonEmptyStringValueParser;
|
|
use itertools::Itertools;
|
|
use tracing::instrument;
|
|
|
|
use crate::cli_util::{
|
|
get_new_config_file_path, run_ui_editor, serialize_config_value, user_error,
|
|
write_config_value_to_file, CommandError, CommandHelper,
|
|
};
|
|
use crate::config::{AnnotatedValue, ConfigSource};
|
|
use crate::ui::Ui;
|
|
|
|
#[derive(clap::Args, Clone, Debug)]
|
|
#[command(group = clap::ArgGroup::new("config_level").multiple(false).required(true))]
|
|
pub(crate) struct ConfigArgs {
|
|
/// Target the user-level config
|
|
#[arg(long, group = "config_level")]
|
|
user: bool,
|
|
|
|
/// Target the repo-level config
|
|
#[arg(long, group = "config_level")]
|
|
repo: bool,
|
|
}
|
|
|
|
impl ConfigArgs {
|
|
fn get_source_kind(&self) -> ConfigSource {
|
|
if self.user {
|
|
ConfigSource::User
|
|
} else if self.repo {
|
|
ConfigSource::Repo
|
|
} else {
|
|
// Shouldn't be reachable unless clap ArgGroup is broken.
|
|
panic!("No config_level provided");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Manage config options
|
|
///
|
|
/// Operates on jj configuration, which comes from the config file and
|
|
/// environment variables.
|
|
///
|
|
/// For file locations, supported config options, and other details about jj
|
|
/// config, see https://github.com/martinvonz/jj/blob/main/docs/config.md.
|
|
#[derive(clap::Subcommand, Clone, Debug)]
|
|
pub(crate) enum ConfigSubcommand {
|
|
#[command(visible_alias("l"))]
|
|
List(ConfigListArgs),
|
|
#[command(visible_alias("g"))]
|
|
Get(ConfigGetArgs),
|
|
#[command(visible_alias("s"))]
|
|
Set(ConfigSetArgs),
|
|
#[command(visible_alias("e"))]
|
|
Edit(ConfigEditArgs),
|
|
}
|
|
|
|
/// List variables set in config file, along with their values.
|
|
#[derive(clap::Args, Clone, Debug)]
|
|
pub(crate) struct ConfigListArgs {
|
|
/// An optional name of a specific config option to look up.
|
|
#[arg(value_parser = NonEmptyStringValueParser::new())]
|
|
pub name: Option<String>,
|
|
/// Whether to explicitly include built-in default values in the list.
|
|
#[arg(long)]
|
|
pub include_defaults: bool,
|
|
// TODO(#1047): Support --show-origin using LayeredConfigs.
|
|
// TODO(#1047): Support ConfigArgs (--user or --repo).
|
|
}
|
|
|
|
/// Get the value of a given config option.
|
|
///
|
|
/// Unlike `jj config list`, the result of `jj config get` is printed without
|
|
/// extra formatting and therefore is usable in scripting. For example:
|
|
///
|
|
/// $ jj config list user.name
|
|
/// user.name="Martin von Zweigbergk"
|
|
/// $ jj config get user.name
|
|
/// Martin von Zweigbergk
|
|
#[derive(clap::Args, Clone, Debug)]
|
|
#[command(verbatim_doc_comment)]
|
|
pub(crate) struct ConfigGetArgs {
|
|
#[arg(required = true)]
|
|
name: String,
|
|
}
|
|
|
|
/// Update config file to set the given option to a given value.
|
|
#[derive(clap::Args, Clone, Debug)]
|
|
pub(crate) struct ConfigSetArgs {
|
|
#[arg(required = true)]
|
|
name: String,
|
|
#[arg(required = true)]
|
|
value: String,
|
|
#[clap(flatten)]
|
|
config_args: ConfigArgs,
|
|
}
|
|
|
|
/// Start an editor on a jj config file.
|
|
#[derive(clap::Args, Clone, Debug)]
|
|
pub(crate) struct ConfigEditArgs {
|
|
#[clap(flatten)]
|
|
pub config_args: ConfigArgs,
|
|
}
|
|
|
|
#[instrument(skip_all)]
|
|
pub(crate) fn cmd_config(
|
|
ui: &mut Ui,
|
|
command: &CommandHelper,
|
|
subcommand: &ConfigSubcommand,
|
|
) -> Result<(), CommandError> {
|
|
match subcommand {
|
|
ConfigSubcommand::List(sub_args) => cmd_config_list(ui, command, sub_args),
|
|
ConfigSubcommand::Get(sub_args) => cmd_config_get(ui, command, sub_args),
|
|
ConfigSubcommand::Set(sub_args) => cmd_config_set(ui, command, sub_args),
|
|
ConfigSubcommand::Edit(sub_args) => cmd_config_edit(ui, command, sub_args),
|
|
}
|
|
}
|
|
|
|
#[instrument(skip_all)]
|
|
pub(crate) fn cmd_config_list(
|
|
ui: &mut Ui,
|
|
command: &CommandHelper,
|
|
args: &ConfigListArgs,
|
|
) -> Result<(), CommandError> {
|
|
ui.request_pager();
|
|
let name_path = args
|
|
.name
|
|
.as_ref()
|
|
.map_or(vec![], |name| name.split('.').collect_vec());
|
|
let values = command.resolved_config_values(&name_path)?;
|
|
let mut wrote_values = false;
|
|
for AnnotatedValue {
|
|
path,
|
|
value,
|
|
source,
|
|
is_overridden,
|
|
} in &values
|
|
{
|
|
// Remove overridden values.
|
|
// TODO(#1047): Allow printing overridden values via `--include-overridden`.
|
|
if *is_overridden {
|
|
continue;
|
|
}
|
|
// Skip built-ins if not included.
|
|
if !args.include_defaults && *source == ConfigSource::Default {
|
|
continue;
|
|
}
|
|
writeln!(
|
|
ui.stdout(),
|
|
"{}={}",
|
|
path.join("."),
|
|
serialize_config_value(value)
|
|
)?;
|
|
wrote_values = true;
|
|
}
|
|
if !wrote_values {
|
|
// Note to stderr explaining why output is empty.
|
|
if let Some(name) = &args.name {
|
|
writeln!(ui.warning(), "No matching config key for {name}")?;
|
|
} else {
|
|
writeln!(ui.warning(), "No config to list")?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[instrument(skip_all)]
|
|
pub(crate) fn cmd_config_get(
|
|
ui: &mut Ui,
|
|
command: &CommandHelper,
|
|
args: &ConfigGetArgs,
|
|
) -> Result<(), CommandError> {
|
|
let value = command
|
|
.settings()
|
|
.config()
|
|
.get_string(&args.name)
|
|
.map_err(|err| match err {
|
|
config::ConfigError::Type {
|
|
origin,
|
|
unexpected,
|
|
expected,
|
|
key,
|
|
} => {
|
|
let expected = format!("a value convertible to {expected}");
|
|
// Copied from `impl fmt::Display for ConfigError`. We can't use
|
|
// the `Display` impl directly because `expected` is required to
|
|
// be a `'static str`.
|
|
let mut buf = String::new();
|
|
use std::fmt::Write;
|
|
write!(buf, "invalid type: {unexpected}, expected {expected}").unwrap();
|
|
if let Some(key) = key {
|
|
write!(buf, " for key `{key}`").unwrap();
|
|
}
|
|
if let Some(origin) = origin {
|
|
write!(buf, " in {origin}").unwrap();
|
|
}
|
|
CommandError::ConfigError(buf.to_string())
|
|
}
|
|
err => err.into(),
|
|
})?;
|
|
writeln!(ui.stdout(), "{value}")?;
|
|
Ok(())
|
|
}
|
|
|
|
#[instrument(skip_all)]
|
|
pub(crate) fn cmd_config_set(
|
|
_ui: &mut Ui,
|
|
command: &CommandHelper,
|
|
args: &ConfigSetArgs,
|
|
) -> Result<(), CommandError> {
|
|
let config_path = get_new_config_file_path(&args.config_args.get_source_kind(), command)?;
|
|
if config_path.is_dir() {
|
|
return Err(user_error(format!(
|
|
"Can't set config in path {path} (dirs not supported)",
|
|
path = config_path.display()
|
|
)));
|
|
}
|
|
write_config_value_to_file(&args.name, &args.value, &config_path)
|
|
}
|
|
|
|
#[instrument(skip_all)]
|
|
pub(crate) fn cmd_config_edit(
|
|
_ui: &mut Ui,
|
|
command: &CommandHelper,
|
|
args: &ConfigEditArgs,
|
|
) -> Result<(), CommandError> {
|
|
let config_path = get_new_config_file_path(&args.config_args.get_source_kind(), command)?;
|
|
run_ui_editor(command.settings(), &config_path)
|
|
}
|