Skip to content

Commit

Permalink
Add a config edit command to open jj config in editor
Browse files Browse the repository at this point in the history
Part of #531 to define the overall `config` command.
  • Loading branch information
dbarnett committed Jan 12, 2023
1 parent ba70167 commit 8979c28
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 38 deletions.
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

* `jj git push` now accepts multiple `--branch`/`--change` arguments

* `jj config list` command prints values from config (with other subcommands
coming soon).
* `jj config list` command prints values from config and `config edit` opens
the config in an editor.

* `jj debug config-schema` command prints out JSON schema for the jj TOML config
file format.
Expand Down
21 changes: 20 additions & 1 deletion src/cli_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ use jujutsu_lib::{dag_walk, file_util, git, revset};
use thiserror::Error;
use tracing_subscriber::prelude::*;

use crate::config::LayeredConfigs;
use crate::config::{FullCommandArgs, LayeredConfigs};
use crate::formatter::Formatter;
use crate::merge_tools::{ConflictResolveError, DiffEditError};
use crate::templater::TemplateFormatter;
Expand Down Expand Up @@ -1376,6 +1376,25 @@ fn serialize_config_value(value: config::Value) -> String {
}
}

pub fn run_ui_editor(settings: &UserSettings, edit_path: &PathBuf) -> Result<(), CommandError> {
let editor: FullCommandArgs = settings
.config()
.get("ui.editor")
.unwrap_or_else(|_| "pico".into());
let exit_status = editor
.to_command()
.arg(edit_path)
.status()
.map_err(|_| user_error(format!("Failed to run editor '{editor}'")))?;
if !exit_status.success() {
return Err(user_error(format!(
"Editor '{editor}' exited with an error"
)));
}

Ok(())
}

pub fn short_commit_description(commit: &Commit) -> String {
let first_line = commit.description().split('\n').next().unwrap();
format!("{} ({})", short_commit_hash(commit.id()), first_line)
Expand Down
91 changes: 58 additions & 33 deletions src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,18 @@ use jujutsu_lib::settings::UserSettings;
use jujutsu_lib::store::Store;
use jujutsu_lib::tree::{merge_trees, Tree};
use jujutsu_lib::view::View;
use jujutsu_lib::workspace::Workspace;
use jujutsu_lib::workspace::{Workspace, WorkspaceLoader};
use jujutsu_lib::{conflicts, file_util, git, revset};
use maplit::{hashmap, hashset};
use pest::Parser;

use crate::cli_util::{
self, check_stale_working_copy, print_checkout_stats, print_failed_git_export,
resolve_base_revs, short_commit_description, short_commit_hash, user_error,
resolve_base_revs, run_ui_editor, short_commit_description, short_commit_hash, user_error,
user_error_with_hint, write_commit_summary, write_config_entry, Args, CommandError,
CommandHelper, DescriptionArg, RevisionArg, WorkspaceCommandHelper,
};
use crate::config::FullCommandArgs;
use crate::config::config_path;
use crate::diff_util::{self, DiffFormat, DiffFormatArgs};
use crate::formatter::{Formatter, PlainTextFormatter};
use crate::graphlog::{AsciiGraphDrawer, Edge};
Expand Down Expand Up @@ -145,18 +145,31 @@ struct InitArgs {
git_repo: Option<String>,
}

/// Get config options
#[derive(clap::Args, Clone, Debug)]
#[command(group = clap::ArgGroup::new("config_level").multiple(false).required(true))]
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,
}

/// Manage config options
///
/// Operates on jj configuration, which comes from the config file and
/// environment variables. Uses the config file at ~/.jjconfig.toml or
/// $XDG_CONFIG_HOME/jj/config.toml, unless overridden with the JJ_CONFIG
/// environment variable.
/// environment variable, combined with repo config at ~/.jj/repo/config.toml
/// if present.
///
/// For supported config options and more details about jj config, see
/// https://github.com/martinvonz/jj/blob/main/docs/config.md.
///
/// Note: Currently only supports getting config options, but support for
/// setting options and editing config files is also planned (see
/// Note: Currently only supports getting config options and editing config
/// files, but support for setting options is also planned (see
/// https://github.com/martinvonz/jj/issues/531).
#[derive(clap::Subcommand, Clone, Debug)]
enum ConfigSubcommand {
Expand All @@ -166,7 +179,13 @@ enum ConfigSubcommand {
/// An optional name of a specific config option to look up.
#[arg(value_parser=NonEmptyStringValueParser::new())]
name: Option<String>,
// TODO: Support --show-origin once mehcode/config-rs#319 is done.
// TODO(#531): Support --show-origin using LayeredConfigs.
// TODO(#531): Support ConfigArgs (--user or --repo) and --all.
},
#[command(visible_alias("e"))]
Edit {
#[clap(flatten)]
config_args: ConfigArgs,
},
}

Expand Down Expand Up @@ -1215,23 +1234,42 @@ fn cmd_config(
subcommand: &ConfigSubcommand,
) -> Result<(), CommandError> {
ui.request_pager();
let settings = command.settings();
match subcommand {
ConfigSubcommand::List { name } => {
let raw_values = match name {
Some(name) => command
.settings()
.config()
.get::<config::Value>(name)
.map_err(|e| match e {
config::ConfigError::NotFound { .. } => {
user_error("key not found in config")
}
_ => e.into(),
})?,
None => command.settings().config().collect()?.into(),
Some(name) => {
settings
.config()
.get::<config::Value>(name)
.map_err(|e| match e {
config::ConfigError::NotFound { .. } => {
user_error("key not found in config")
}
_ => e.into(),
})?
}
None => settings.config().collect()?.into(),
};
write_config_entry(ui, name.as_deref().unwrap_or(""), raw_values)?;
}
ConfigSubcommand::Edit { config_args } => {
let edit_path = if config_args.user {
// TODO(#531): Special-case for editors that can't handle viewing directories?
config_path()?.ok_or_else(|| user_error("No repo config path found to edit"))?
} else if config_args.repo {
let workspace_command = command.workspace_helper(ui)?;
let workspace_path = workspace_command.workspace_root();
WorkspaceLoader::init(workspace_path)
.unwrap()
.repo_path()
.join("config.toml")
} else {
// Shouldn't be reachable unless clap ArgGroup is broken.
panic!("No config_level provided");
};
run_ui_editor(settings, &edit_path)?;
}
}

Ok(())
Expand Down Expand Up @@ -1898,20 +1936,7 @@ fn edit_description(
))
})?;

let editor: FullCommandArgs = settings
.config()
.get("ui.editor")
.unwrap_or_else(|_| "pico".into());
let exit_status = editor
.to_command()
.arg(&description_file_path)
.status()
.map_err(|_| user_error(format!("Failed to run editor '{editor}'")))?;
if !exit_status.success() {
return Err(user_error(format!(
"Editor '{editor}' exited with an error"
)));
}
run_ui_editor(settings, &description_file_path)?;

let description = fs::read_to_string(&description_file_path).map_err(|e| {
user_error(format!(
Expand Down
2 changes: 1 addition & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ impl LayeredConfigs {
}
}

fn config_path() -> Result<Option<PathBuf>, ConfigError> {
pub fn config_path() -> Result<Option<PathBuf>, ConfigError> {
if let Ok(config_path) = env::var("JJ_CONFIG") {
// TODO: We should probably support colon-separated (std::env::split_paths)
// paths here
Expand Down
12 changes: 11 additions & 1 deletion testing/fake-editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,18 @@ fn main() {
exit(1)
}
}
["expectpath"] => {
let actual = args.file.to_str().unwrap();
if actual != payload {
eprintln!("fake-editor: Unexpected path.\n");
eprintln!("EXPECTED: <{payload}>\nRECEIVED: <{actual}>");
exit(1)
}
}
["write"] => {
fs::write(&args.file, payload).unwrap();
fs::write(&args.file, payload).unwrap_or_else(|_| {
panic!("Failed to write file {}", args.file.to_str().unwrap())
});
}
_ => {
eprintln!("fake-editor: unexpected command: {command}");
Expand Down
4 changes: 4 additions & 0 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ impl TestEnvironment {
&self.home_dir
}

pub fn config_dir(&self) -> &PathBuf {
&self.config_dir
}

pub fn add_config(&self, content: &[u8]) {
// Concatenating two valid TOML files does not (generally) result in a valid
// TOML file, so we use create a new file every time instead.
Expand Down
42 changes: 42 additions & 0 deletions tests/test_config_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,48 @@ fn test_config_layer_workspace() {
"###);
}

#[test]
fn test_config_edit_missing_opt() {
let test_env = TestEnvironment::default();
let stderr = test_env.jj_cmd_failure(test_env.env_root(), &["config", "edit"]);
insta::assert_snapshot!(stderr, @r###"
Error: Must specify which config to edit (--user or --repo)
"###);
}

#[test]
fn test_config_edit_user() {
let mut test_env = TestEnvironment::default();
test_env.jj_cmd_success(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");
let edit_script = test_env.set_up_fake_editor();

std::fs::write(
edit_script,
format!("expectpath\n{}", test_env.config_dir().to_str().unwrap()),
)
.unwrap();
test_env.jj_cmd_success(&repo_path, &["config", "edit", "--user"]);
}

#[test]
fn test_config_edit_repo() {
let mut test_env = TestEnvironment::default();
test_env.jj_cmd_success(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");
let edit_script = test_env.set_up_fake_editor();

std::fs::write(
edit_script,
format!(
"expectpath\n{}",
repo_path.join(".jj/repo/config.toml").to_str().unwrap()
),
)
.unwrap();
test_env.jj_cmd_success(&repo_path, &["config", "edit", "--repo"]);
}

fn find_stdout_lines(keyname_pattern: &str, stdout: &str) -> String {
let key_line_re = Regex::new(&format!(r"(?m)^{keyname_pattern}=.*$")).unwrap();
key_line_re
Expand Down

0 comments on commit 8979c28

Please sign in to comment.