util: add exec command for arbitrary aliases
Some checks are pending
binaries / Build binary artifacts (linux-aarch64-gnu, ubuntu-24.04, aarch64-unknown-linux-gnu) (push) Waiting to run
binaries / Build binary artifacts (linux-aarch64-musl, ubuntu-24.04, aarch64-unknown-linux-musl) (push) Waiting to run
binaries / Build binary artifacts (linux-x86_64-gnu, ubuntu-24.04, x86_64-unknown-linux-gnu) (push) Waiting to run
binaries / Build binary artifacts (linux-x86_64-musl, ubuntu-24.04, x86_64-unknown-linux-musl) (push) Waiting to run
binaries / Build binary artifacts (macos-aarch64, macos-14, aarch64-apple-darwin) (push) Waiting to run
binaries / Build binary artifacts (macos-x86_64, macos-13, x86_64-apple-darwin) (push) Waiting to run
binaries / Build binary artifacts (win-x86_64, windows-2022, x86_64-pc-windows-msvc) (push) Waiting to run
nix / flake check (macos-14) (push) Waiting to run
nix / flake check (ubuntu-latest) (push) Waiting to run
build / build (, macos-13) (push) Waiting to run
build / build (, macos-14) (push) Waiting to run
build / build (, ubuntu-latest) (push) Waiting to run
build / build (, windows-latest) (push) Waiting to run
build / build (--all-features, ubuntu-latest) (push) Waiting to run
build / Build jj-lib without Git support (push) Waiting to run
build / Check protos (push) Waiting to run
build / Check formatting (push) Waiting to run
build / Check that MkDocs can build the docs (push) Waiting to run
build / Check that MkDocs can build the docs with Poetry 1.8 (push) Waiting to run
build / cargo-deny (advisories) (push) Waiting to run
build / cargo-deny (bans licenses sources) (push) Waiting to run
build / Clippy check (push) Waiting to run
Codespell / Codespell (push) Waiting to run
website / prerelease-docs-build-deploy (ubuntu-latest) (push) Waiting to run
Scorecards supply-chain security / Scorecards analysis (push) Waiting to run

This commit is contained in:
Remo Senekowitsch 2024-11-03 12:26:16 +01:00
parent d77ca1526a
commit db2b5890f8
6 changed files with 224 additions and 0 deletions

View file

@ -20,6 +20,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
* The `jj desc` and `jj st` aliases are now hidden to not interfere with shell * The `jj desc` and `jj st` aliases are now hidden to not interfere with shell
completion. They remain available. completion. They remain available.
* New command `jj util exec` that can be used for arbitrary aliases.
### Fixed bugs ### Fixed bugs
## [0.23.0] - 2024-11-06 ## [0.23.0] - 2024-11-06

View file

@ -0,0 +1,95 @@
// Copyright 2024 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 crate::cli_util::CommandHelper;
use crate::command_error::user_error;
use crate::command_error::user_error_with_message;
use crate::command_error::CommandError;
use crate::ui::Ui;
/// Execute an external command via jj
///
/// This is useful for arbitrary aliases.
///
/// !! WARNING !!
///
/// The following technique just provides a convenient syntax for running
/// arbitrary code on your system. Using it irresponsibly may cause damage
/// ranging from breaking the behavior of `jj undo` to wiping your file system.
/// Exercise the same amount of caution while writing these aliases as you would
/// when typing commands into the terminal!
///
/// This feature may be removed or replaced by an embedded scripting language in
/// the future.
///
/// Let's assume you have a script called "my-jj-script" in you $PATH and you
/// would like to execute it as "jj my-script". You would add the following line
/// to your configuration file to achieve that:
///
/// ```toml
/// [aliases]
/// my-script = ["util", "exec", "--", "my-jj-script"]
/// # ^^^^
/// # This makes sure that flags are passed to your script instead of parsed by jj.
/// ```
///
/// If you don't want to manage your script as a separate file, you can even
/// inline it into your config file:
///
/// ```toml
/// [aliases]
/// my-inline-script = ["util", "exec", "--", "bash", "-c", """
/// #!/usr/bin/env bash
/// set -euo pipefail
/// echo "Look Ma, everything in one file!"
/// echo "args: $@"
/// """, ""]
/// # ^^
/// # This last empty string will become "$0" in bash, so your actual arguments
/// # are all included in "$@" and start at "$1" as expected.
/// ```
#[derive(clap::Args, Clone, Debug)]
#[command(verbatim_doc_comment)]
pub(crate) struct UtilExecArgs {
/// External command to execute
command: String,
/// Arguments to pass to the external command
args: Vec<String>,
}
pub fn cmd_util_exec(
_ui: &mut Ui,
_command: &CommandHelper,
args: &UtilExecArgs,
) -> Result<(), CommandError> {
let status = std::process::Command::new(&args.command)
.args(&args.args)
.status()
.map_err(|err| {
user_error_with_message(
format!("Failed to execute external command '{}'", &args.command),
err,
)
})?;
if !status.success() {
let error_msg = if let Some(exit_code) = status.code() {
format!("External command exited with {exit_code}")
} else {
// signal
format!("External command was terminated by: {status}")
};
return Err(user_error(error_msg));
}
Ok(())
}

View file

@ -14,6 +14,7 @@
mod completion; mod completion;
mod config_schema; mod config_schema;
mod exec;
mod gc; mod gc;
mod mangen; mod mangen;
mod markdown_help; mod markdown_help;
@ -25,6 +26,8 @@ use self::completion::cmd_util_completion;
use self::completion::UtilCompletionArgs; use self::completion::UtilCompletionArgs;
use self::config_schema::cmd_util_config_schema; use self::config_schema::cmd_util_config_schema;
use self::config_schema::UtilConfigSchemaArgs; use self::config_schema::UtilConfigSchemaArgs;
use self::exec::cmd_util_exec;
use self::exec::UtilExecArgs;
use self::gc::cmd_util_gc; use self::gc::cmd_util_gc;
use self::gc::UtilGcArgs; use self::gc::UtilGcArgs;
use self::mangen::cmd_util_mangen; use self::mangen::cmd_util_mangen;
@ -40,6 +43,7 @@ use crate::ui::Ui;
pub(crate) enum UtilCommand { pub(crate) enum UtilCommand {
Completion(UtilCompletionArgs), Completion(UtilCompletionArgs),
ConfigSchema(UtilConfigSchemaArgs), ConfigSchema(UtilConfigSchemaArgs),
Exec(UtilExecArgs),
Gc(UtilGcArgs), Gc(UtilGcArgs),
Mangen(UtilMangenArgs), Mangen(UtilMangenArgs),
MarkdownHelp(UtilMarkdownHelp), MarkdownHelp(UtilMarkdownHelp),
@ -54,6 +58,7 @@ pub(crate) fn cmd_util(
match subcommand { match subcommand {
UtilCommand::Completion(args) => cmd_util_completion(ui, command, args), UtilCommand::Completion(args) => cmd_util_completion(ui, command, args),
UtilCommand::ConfigSchema(args) => cmd_util_config_schema(ui, command, args), UtilCommand::ConfigSchema(args) => cmd_util_config_schema(ui, command, args),
UtilCommand::Exec(args) => cmd_util_exec(ui, command, args),
UtilCommand::Gc(args) => cmd_util_gc(ui, command, args), UtilCommand::Gc(args) => cmd_util_gc(ui, command, args),
UtilCommand::Mangen(args) => cmd_util_mangen(ui, command, args), UtilCommand::Mangen(args) => cmd_util_mangen(ui, command, args),
UtilCommand::MarkdownHelp(args) => cmd_util_markdown_help(ui, command, args), UtilCommand::MarkdownHelp(args) => cmd_util_markdown_help(ui, command, args),

View file

@ -1,6 +1,7 @@
--- ---
source: cli/tests/test_generate_md_cli_help.rs source: cli/tests/test_generate_md_cli_help.rs
description: "AUTO-GENERATED FILE, DO NOT EDIT. This cli reference is generated by a test as an `insta` snapshot. MkDocs includes this snapshot from docs/cli-reference.md." description: "AUTO-GENERATED FILE, DO NOT EDIT. This cli reference is generated by a test as an `insta` snapshot. MkDocs includes this snapshot from docs/cli-reference.md."
snapshot_kind: text
--- ---
<!-- BEGIN MARKDOWN--> <!-- BEGIN MARKDOWN-->
@ -92,6 +93,7 @@ This document contains the help content for the `jj` command-line program.
* [`jj util`↴](#jj-util) * [`jj util`↴](#jj-util)
* [`jj util completion`↴](#jj-util-completion) * [`jj util completion`↴](#jj-util-completion)
* [`jj util config-schema`↴](#jj-util-config-schema) * [`jj util config-schema`↴](#jj-util-config-schema)
* [`jj util exec`↴](#jj-util-exec)
* [`jj util gc`↴](#jj-util-gc) * [`jj util gc`↴](#jj-util-gc)
* [`jj util mangen`↴](#jj-util-mangen) * [`jj util mangen`↴](#jj-util-mangen)
* [`jj util markdown-help`↴](#jj-util-markdown-help) * [`jj util markdown-help`↴](#jj-util-markdown-help)
@ -2117,6 +2119,7 @@ Infrequently used commands such as for generating shell completions
* `completion` — Print a command-line-completion script * `completion` — Print a command-line-completion script
* `config-schema` — Print the JSON schema for the jj TOML config format * `config-schema` — Print the JSON schema for the jj TOML config format
* `exec` — Execute an external command via jj
* `gc` — Run backend-dependent garbage collection * `gc` — Run backend-dependent garbage collection
* `mangen` — Print a ROFF (manpage) * `mangen` — Print a ROFF (manpage)
* `markdown-help` — Print the CLI help for all subcommands in Markdown * `markdown-help` — Print the CLI help for all subcommands in Markdown
@ -2162,6 +2165,59 @@ Print the JSON schema for the jj TOML config format
## `jj util exec`
Execute an external command via jj
This is useful for arbitrary aliases.
!! WARNING !!
The following technique just provides a convenient syntax for running
arbitrary code on your system. Using it irresponsibly may cause damage
ranging from breaking the behavior of `jj undo` to wiping your file system.
Exercise the same amount of caution while writing these aliases as you would
when typing commands into the terminal!
This feature may be removed or replaced by an embedded scripting language in
the future.
Let's assume you have a script called "my-jj-script" in you $PATH and you
would like to execute it as "jj my-script". You would add the following line
to your configuration file to achieve that:
```toml
[aliases]
my-script = ["util", "exec", "--", "my-jj-script"]
# ^^^^
# This makes sure that flags are passed to your script instead of parsed by jj.
```
If you don't want to manage your script as a separate file, you can even
inline it into your config file:
```toml
[aliases]
my-inline-script = ["util", "exec", "--", "bash", "-c", """
#!/usr/bin/env bash
set -euo pipefail
echo "Look Ma, everything in one file!"
echo "args: $@"
""", ""]
# ^^
# This last empty string will become "$0" in bash, so your actual arguments
# are all included in "$@" and start at "$1" as expected.
```
**Usage:** `jj util exec <COMMAND> [ARGS]...`
###### **Arguments:**
* `<COMMAND>` — External command to execute
* `<ARGS>` — Arguments to pass to the external command
## `jj util gc` ## `jj util gc`
Run backend-dependent garbage collection Run backend-dependent garbage collection

View file

@ -14,6 +14,7 @@
use insta::assert_snapshot; use insta::assert_snapshot;
use crate::common::strip_last_line;
use crate::common::TestEnvironment; use crate::common::TestEnvironment;
#[test] #[test]
@ -112,3 +113,33 @@ fn test_shell_completions() {
test("nushell"); test("nushell");
test("zsh"); test("zsh");
} }
#[test]
fn test_util_exec() {
let test_env = TestEnvironment::default();
let formatter_path = assert_cmd::cargo::cargo_bin("fake-formatter");
let (out, err) = test_env.jj_cmd_ok(
test_env.env_root(),
&[
"util",
"exec",
"--",
formatter_path.to_str().unwrap(),
"--append",
"hello",
],
);
insta::assert_snapshot!(out, @"hello");
// Ensures only stdout contains text
assert!(err.is_empty());
}
#[test]
fn test_util_exec_fail() {
let test_env = TestEnvironment::default();
let err = test_env.jj_cmd_failure(
test_env.env_root(),
&["util", "exec", "--", "missing-program"],
);
insta::assert_snapshot!(strip_last_line(&err), @"Error: Failed to execute external command 'missing-program'");
}

View file

@ -528,6 +528,41 @@ You can define aliases for commands, including their arguments. For example:
aliases.l = ["log", "-r", "(main..@):: | (main..@)-"] aliases.l = ["log", "-r", "(main..@):: | (main..@)-"]
``` ```
This alias syntax can only run a single jj command. However, you may want to
execute multiple jj commands with a single alias, or run arbitrary scripts that
complement your version control workflow. This can be done, but be aware of the
danger:
!!! warning
The following technique just provides a convenient syntax for running
arbitrary code on your system. Using it irresponsibly may cause damage
ranging from breaking the behavior of `jj undo` to wiping your file system.
Exercise the same amount of caution while writing these aliases as you would
when typing commands into the terminal!
This feature may be removed or replaced by an embedded scripting language in
the future.
The command `jj util exec` will simply run any command you pass to it as an
argument. Additional arguments are passed through. Here are some examples:
```toml
[aliases]
my-script = ["util", "exec", "--", "my-jj-script"]
# ^^^^
# This makes sure that flags are passed to your script instead of parsed by jj.
my-inline-script = ["util", "exec", "--", "bash", "-c", """
#!/usr/bin/env bash
set -euo pipefail
echo "Look Ma, everything in one file!"
echo "args: $@"
""", ""]
# ^^
# This last empty string will become "$0" in bash, so your actual arguments
# are all included in "$@" and start at "$1" as expected.
```
## Editor ## Editor
The default editor is set via `ui.editor`, though there are several places to The default editor is set via `ui.editor`, though there are several places to