Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cli: default to log when no subcommand is provided #1525

Merged
merged 1 commit into from Apr 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Expand Up @@ -79,6 +79,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

* `jj git fetch` and `jj git push` will now use the single defined remote even if it is not named "origin".

* `jj` with no subcommand now defaults to `jj log` instead of showing help. This
command can be overridden by setting `ui.default-command`.

### Fixed bugs

* Modify/delete conflicts now include context lines
Expand Down
10 changes: 10 additions & 0 deletions docs/config.md
Expand Up @@ -124,6 +124,16 @@ Which elements can be colored is not yet documented, but see
the [default color configuration](https://github.com/martinvonz/jj/blob/main/src/config/colors.toml)
for some examples of what's possible.

### Default command

When `jj` is run with no explicit subcommand, the value of the
`ui.default-command` setting will used instead. Possible values are any valid
subcommand name, subcommand alias, or user-defined alias (defaults to `"log"`).

```toml
ui.default-command = "log"
```

elasticdog marked this conversation as resolved.
Show resolved Hide resolved
### Diff format

```toml
Expand Down
47 changes: 46 additions & 1 deletion src/cli_util.rs
Expand Up @@ -2028,6 +2028,49 @@ impl ValueParserFactory for RevisionArg {
}
}

fn resolve_default_command(
ui: &mut Ui,
config: &config::Config,
app: &Command,
string_args: &mut Vec<String>,
) -> Result<(), CommandError> {
const PRIORITY_FLAGS: &[&str] = &["help", "--help", "-h", "--version", "-V"];

let has_priority_flag = string_args
.iter()
.any(|arg| PRIORITY_FLAGS.contains(&arg.as_str()));
if has_priority_flag {
return Ok(());
}

let app_clone = app
.clone()
.allow_external_subcommands(true)
.ignore_errors(true);
elasticdog marked this conversation as resolved.
Show resolved Hide resolved
let matches = app_clone.try_get_matches_from(string_args.clone()).ok();

if let Some(matches) = matches {
if matches.subcommand_name().is_none() {
if config.get_string("ui.default-command").is_err() {
writeln!(
ui.hint(),
"Hint: Use `jj -h` for a list of available commands."
)?;
writeln!(
ui.hint(),
"Set the config `ui.default-command = \"log\"` to disable this message."
)?;
}
let default_command = config
.get_string("ui.default-command")
.unwrap_or("log".to_string());
// Insert the default command directly after the path to the binary.
string_args.insert(1, default_command);
}
}
Ok(())
}

fn resolve_aliases(
config: &config::Config,
app: &Command,
Expand Down Expand Up @@ -2119,6 +2162,7 @@ fn handle_early_args(
}

pub fn expand_args(
ui: &mut Ui,
app: &Command,
args_os: ArgsOs,
config: &config::Config,
Expand All @@ -2132,6 +2176,7 @@ pub fn expand_args(
}
}

resolve_default_command(ui, config, app, &mut string_args)?;
resolve_aliases(config, app, &string_args)
}

Expand Down Expand Up @@ -2299,7 +2344,7 @@ impl CliRunner {
layered_configs.read_user_config()?;
let config = layered_configs.merge();
ui.reset(&config)?;
let string_args = expand_args(&self.app, std::env::args_os(), &config)?;
let string_args = expand_args(ui, &self.app, std::env::args_os(), &config)?;
let (matches, args) = parse_args(
ui,
&self.app,
Expand Down
4 changes: 1 addition & 3 deletions src/commands/mod.rs
Expand Up @@ -3474,9 +3474,7 @@ fn cmd_sparse(ui: &mut Ui, command: &CommandHelper, args: &SparseArgs) -> Result

pub fn default_app() -> Command {
let app: Command = Commands::augment_subcommands(Args::command());
app.arg_required_else_help(true)
.subcommand_required(true)
.version(env!("JJ_VERSION"))
app.version(env!("JJ_VERSION"))
}

pub fn run_command(
Expand Down
5 changes: 5 additions & 0 deletions src/config-schema.json
Expand Up @@ -51,6 +51,11 @@
"description": "Whether to allow initializing a repo with the native backend",
"default": false
},
"default-command": {
"type": "string",
"description": "Default command to run when no explicit command is given",
"default": "log"
},
"default-revset": {
"type": "string",
"description": "Default set of revisions to show when no explicit revset is given for jj log and similar commands",
Expand Down
6 changes: 3 additions & 3 deletions tests/test_alias.rs
Expand Up @@ -69,7 +69,7 @@ fn test_alias_bad_name() {
insta::assert_snapshot!(stderr, @r###"
error: unrecognized subcommand 'foo.'

Usage: jj [OPTIONS] <COMMAND>
Usage: jj [OPTIONS] [COMMAND]

For more information, try '--help'.
"###);
Expand All @@ -86,7 +86,7 @@ fn test_alias_calls_unknown_command() {
insta::assert_snapshot!(stderr, @r###"
error: unrecognized subcommand 'nonexistent'

Usage: jj [OPTIONS] <COMMAND>
Usage: jj [OPTIONS] [COMMAND]

For more information, try '--help'.
"###);
Expand Down Expand Up @@ -123,7 +123,7 @@ fn test_alias_calls_help() {

To get started, see the tutorial at https://github.com/martinvonz/jj/blob/main/docs/tutorial.md.

Usage: jj [OPTIONS] <COMMAND>
Usage: jj [OPTIONS] [COMMAND]
"###);
}

Expand Down
45 changes: 39 additions & 6 deletions tests/test_global_opts.rs
Expand Up @@ -47,12 +47,25 @@ fn test_non_utf8_arg() {
#[test]
fn test_no_subcommand() {
let 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 stderr = test_env.jj_cmd_cli_error(test_env.env_root(), &[]);
insta::assert_snapshot!(stderr.lines().next().unwrap(), @"Jujutsu (An experimental VCS)");
// Outside of a repo.
let stderr = test_env.jj_cmd_failure(test_env.env_root(), &[]);
insta::assert_snapshot!(stderr, @r###"
Hint: Use `jj -h` for a list of available commands.
Set the config `ui.default-command = "log"` to disable this message.
Error: There is no jj repo in "."
"###);

let stderr = test_env.jj_cmd_cli_error(test_env.env_root(), &["-R."]);
insta::assert_snapshot!(stderr.lines().next().unwrap(), @"error: 'jj' requires a subcommand but one was not provided");
test_env.add_config(r#"ui.default-command="log""#);
let stderr = test_env.jj_cmd_failure(test_env.env_root(), &[]);
insta::assert_snapshot!(stderr, @r###"
Error: There is no jj repo in "."
"###);

let stdout = test_env.jj_cmd_success(test_env.env_root(), &["--help"]);
insta::assert_snapshot!(stdout.lines().next().unwrap(), @"Jujutsu (An experimental VCS)");

let stdout = test_env.jj_cmd_success(test_env.env_root(), &["--version"]);
let sanitized = stdout.replace(|c: char| c.is_ascii_hexdigit(), "?");
Expand All @@ -62,8 +75,28 @@ fn test_no_subcommand() {
"{sanitized}"
);

let stdout = test_env.jj_cmd_success(test_env.env_root(), &["--help"]);
insta::assert_snapshot!(stdout.lines().next().unwrap(), @"Jujutsu (An experimental VCS)");
let stdout = test_env.jj_cmd_success(test_env.env_root(), &["-R", "repo"]);
assert_eq!(stdout, test_env.jj_cmd_success(&repo_path, &["log"]));

// Inside of a repo.
let stdout = test_env.jj_cmd_success(&repo_path, &[]);
assert_eq!(stdout, test_env.jj_cmd_success(&repo_path, &["log"]));

let stdout = test_env.jj_cmd_success(&repo_path, &["-T", "show"]);
let stdout = stdout.lines().skip(2).join("\n");
insta::assert_snapshot!(stdout, @r###"
│ Author: Test User <test.user@example.com> (2001-02-03 04:05:07.000 +07:00)
│ Committer: Test User <test.user@example.com> (2001-02-03 04:05:07.000 +07:00)
│ (no description set)
◉ Commit ID: 0000000000000000000000000000000000000000
Change ID: zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
Author: <> (1970-01-01 00:00:00.000 +00:00)
Committer: <> (1970-01-01 00:00:00.000 +00:00)

(no description set)
"###);
}

#[test]
Expand Down