Skip to content

Commit

Permalink
cli: default to log when no subcommand is provided
Browse files Browse the repository at this point in the history
This is a convenience optimization to improve the default user
experience, since `jj log` is a frequently run command. Accessing the
help information explicitly still follows normal CLI conventions, and
instructions are displayed appropriately if the user happens to make a
mistake. Discoverability should not be adversely harmed.

Since clap does not natively support setting a default subcommand [1],
it will only parse global options when invoking `jj` in this way. This
limitation is also why we have to create the LogArgs struct literal
(with derived default values) to pass through to the cmd_log function.

Note that this behavior (and limitation) mirrors what Sapling does [2],
where `sl` will display the smartlog by default.

[1] clap-rs/clap#975
[2] https://sapling-scm.com/docs/overview/smartlog
  • Loading branch information
elasticdog committed Apr 14, 2023
1 parent 6a55ae6 commit 59a0696
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 63 deletions.
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 invokes `jj log` with the default options, instead
of showing help.

### Fixed bugs

* Modify/delete conflicts now include context lines
Expand Down
92 changes: 49 additions & 43 deletions src/commands/mod.rs
Expand Up @@ -325,7 +325,7 @@ struct ShowArgs {
struct StatusArgs {}

/// Show commit history
#[derive(clap::Args, Clone, Debug)]
#[derive(clap::Args, Clone, Debug, Default)]
struct LogArgs {
/// Which revisions to show. Defaults to the `ui.default-revset` setting,
/// or `@ | (remote_branches() | tags()).. | ((remote_branches() |
Expand Down Expand Up @@ -3474,57 +3474,63 @@ 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(
ui: &mut Ui,
command_helper: &CommandHelper,
matches: &ArgMatches,
) -> Result<(), CommandError> {
let derived_subcommands: Commands = Commands::from_arg_matches(matches).unwrap();
let derived_subcommands: Option<Commands> = Commands::from_arg_matches(matches).ok();
match &derived_subcommands {
Commands::Version(sub_args) => cmd_version(ui, command_helper, sub_args),
Commands::Init(sub_args) => cmd_init(ui, command_helper, sub_args),
Commands::Config(sub_args) => cmd_config(ui, command_helper, sub_args),
Commands::Checkout(sub_args) => cmd_checkout(ui, command_helper, sub_args),
Commands::Untrack(sub_args) => cmd_untrack(ui, command_helper, sub_args),
Commands::Files(sub_args) => cmd_files(ui, command_helper, sub_args),
Commands::Cat(sub_args) => cmd_cat(ui, command_helper, sub_args),
Commands::Diff(sub_args) => cmd_diff(ui, command_helper, sub_args),
Commands::Show(sub_args) => cmd_show(ui, command_helper, sub_args),
Commands::Status(sub_args) => cmd_status(ui, command_helper, sub_args),
Commands::Log(sub_args) => cmd_log(ui, command_helper, sub_args),
Commands::Interdiff(sub_args) => cmd_interdiff(ui, command_helper, sub_args),
Commands::Obslog(sub_args) => cmd_obslog(ui, command_helper, sub_args),
Commands::Describe(sub_args) => cmd_describe(ui, command_helper, sub_args),
Commands::Commit(sub_args) => cmd_commit(ui, command_helper, sub_args),
Commands::Duplicate(sub_args) => cmd_duplicate(ui, command_helper, sub_args),
Commands::Abandon(sub_args) => cmd_abandon(ui, command_helper, sub_args),
Commands::Edit(sub_args) => cmd_edit(ui, command_helper, sub_args),
Commands::New(sub_args) => cmd_new(ui, command_helper, sub_args),
Commands::Move(sub_args) => cmd_move(ui, command_helper, sub_args),
Commands::Squash(sub_args) => cmd_squash(ui, command_helper, sub_args),
Commands::Unsquash(sub_args) => cmd_unsquash(ui, command_helper, sub_args),
Commands::Restore(sub_args) => cmd_restore(ui, command_helper, sub_args),
Commands::Diffedit(sub_args) => cmd_diffedit(ui, command_helper, sub_args),
Commands::Split(sub_args) => cmd_split(ui, command_helper, sub_args),
Commands::Merge(sub_args) => cmd_merge(ui, command_helper, sub_args),
Commands::Rebase(sub_args) => cmd_rebase(ui, command_helper, sub_args),
Commands::Backout(sub_args) => cmd_backout(ui, command_helper, sub_args),
Commands::Resolve(sub_args) => cmd_resolve(ui, command_helper, sub_args),
Commands::Branch(sub_args) => branch::cmd_branch(ui, command_helper, sub_args),
Commands::Undo(sub_args) => operation::cmd_op_undo(ui, command_helper, sub_args),
Commands::Operation(sub_args) => operation::cmd_operation(ui, command_helper, sub_args),
Commands::Workspace(sub_args) => cmd_workspace(ui, command_helper, sub_args),
Commands::Sparse(sub_args) => cmd_sparse(ui, command_helper, sub_args),
Commands::Git(sub_args) => git::cmd_git(ui, command_helper, sub_args),
Commands::Util(sub_args) => cmd_util(ui, command_helper, sub_args),
Some(Commands::Version(sub_args)) => cmd_version(ui, command_helper, sub_args),
Some(Commands::Init(sub_args)) => cmd_init(ui, command_helper, sub_args),
Some(Commands::Config(sub_args)) => cmd_config(ui, command_helper, sub_args),
Some(Commands::Checkout(sub_args)) => cmd_checkout(ui, command_helper, sub_args),
Some(Commands::Untrack(sub_args)) => cmd_untrack(ui, command_helper, sub_args),
Some(Commands::Files(sub_args)) => cmd_files(ui, command_helper, sub_args),
Some(Commands::Cat(sub_args)) => cmd_cat(ui, command_helper, sub_args),
Some(Commands::Diff(sub_args)) => cmd_diff(ui, command_helper, sub_args),
Some(Commands::Show(sub_args)) => cmd_show(ui, command_helper, sub_args),
Some(Commands::Status(sub_args)) => cmd_status(ui, command_helper, sub_args),
Some(Commands::Log(sub_args)) => cmd_log(ui, command_helper, sub_args),
Some(Commands::Interdiff(sub_args)) => cmd_interdiff(ui, command_helper, sub_args),
Some(Commands::Obslog(sub_args)) => cmd_obslog(ui, command_helper, sub_args),
Some(Commands::Describe(sub_args)) => cmd_describe(ui, command_helper, sub_args),
Some(Commands::Commit(sub_args)) => cmd_commit(ui, command_helper, sub_args),
Some(Commands::Duplicate(sub_args)) => cmd_duplicate(ui, command_helper, sub_args),
Some(Commands::Abandon(sub_args)) => cmd_abandon(ui, command_helper, sub_args),
Some(Commands::Edit(sub_args)) => cmd_edit(ui, command_helper, sub_args),
Some(Commands::New(sub_args)) => cmd_new(ui, command_helper, sub_args),
Some(Commands::Move(sub_args)) => cmd_move(ui, command_helper, sub_args),
Some(Commands::Squash(sub_args)) => cmd_squash(ui, command_helper, sub_args),
Some(Commands::Unsquash(sub_args)) => cmd_unsquash(ui, command_helper, sub_args),
Some(Commands::Restore(sub_args)) => cmd_restore(ui, command_helper, sub_args),
Some(Commands::Diffedit(sub_args)) => cmd_diffedit(ui, command_helper, sub_args),
Some(Commands::Split(sub_args)) => cmd_split(ui, command_helper, sub_args),
Some(Commands::Merge(sub_args)) => cmd_merge(ui, command_helper, sub_args),
Some(Commands::Rebase(sub_args)) => cmd_rebase(ui, command_helper, sub_args),
Some(Commands::Backout(sub_args)) => cmd_backout(ui, command_helper, sub_args),
Some(Commands::Resolve(sub_args)) => cmd_resolve(ui, command_helper, sub_args),
Some(Commands::Branch(sub_args)) => branch::cmd_branch(ui, command_helper, sub_args),
Some(Commands::Undo(sub_args)) => operation::cmd_op_undo(ui, command_helper, sub_args),
Some(Commands::Operation(sub_args)) => {
operation::cmd_operation(ui, command_helper, sub_args)
}
Some(Commands::Workspace(sub_args)) => cmd_workspace(ui, command_helper, sub_args),
Some(Commands::Sparse(sub_args)) => cmd_sparse(ui, command_helper, sub_args),
Some(Commands::Git(sub_args)) => git::cmd_git(ui, command_helper, sub_args),
Some(Commands::Util(sub_args)) => cmd_util(ui, command_helper, sub_args),
#[cfg(feature = "bench")]
Commands::Bench(sub_args) => bench::cmd_bench(ui, command_helper, sub_args),
Commands::Debug(sub_args) => cmd_debug(ui, command_helper, sub_args),
Some(Commands::Bench(sub_args)) => bench::cmd_bench(ui, command_helper, sub_args),
Some(Commands::Debug(sub_args)) => cmd_debug(ui, command_helper, sub_args),
None => {
let sub_args = LogArgs {
..Default::default()
};
cmd_log(ui, command_helper, &sub_args)
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/diff_util.rs
Expand Up @@ -32,7 +32,7 @@ use jujutsu_lib::{conflicts, diff, files, rewrite, tree};
use crate::cli_util::{CommandError, WorkspaceCommandHelper};
use crate::formatter::Formatter;

#[derive(clap::Args, Clone, Debug)]
#[derive(clap::Args, Clone, Debug, Default)]
#[command(group(clap::ArgGroup::new("short-format").args(&["summary", "types"])))]
#[command(group(clap::ArgGroup::new("long-format").args(&["git", "color_words"])))]
pub struct DiffFormatArgs {
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
20 changes: 4 additions & 16 deletions tests/test_global_opts.rs
Expand Up @@ -47,23 +47,11 @@ 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)");

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");

let stdout = test_env.jj_cmd_success(test_env.env_root(), &["--version"]);
let sanitized = stdout.replace(|c: char| c.is_ascii_hexdigit(), "?");
assert!(
sanitized == "jj ?.?.?\n"
|| sanitized == "jj ?.?.?-????????????????????????????????????????\n",
"{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(&repo_path, &[]);
assert_eq!(stdout, test_env.jj_cmd_success(&repo_path, &["log"]));
}

#[test]
Expand Down

0 comments on commit 59a0696

Please sign in to comment.