From 725aec2554a8699d7ae1684ac48e50a26f3b8901 Mon Sep 17 00:00:00 2001 From: Antoine Cezar Date: Fri, 27 Oct 2023 01:13:08 +0200 Subject: [PATCH] commands: move chmod code to chmod.rs --- cli/src/commands/chmod.rs | 129 ++++++++++++++++++++++++++++++++++++++ cli/src/commands/mod.rs | 104 +----------------------------- 2 files changed, 132 insertions(+), 101 deletions(-) create mode 100644 cli/src/commands/chmod.rs diff --git a/cli/src/commands/chmod.rs b/cli/src/commands/chmod.rs new file mode 100644 index 0000000000..73e59def68 --- /dev/null +++ b/cli/src/commands/chmod.rs @@ -0,0 +1,129 @@ +// 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 itertools::Itertools; +use tracing::instrument; + +use jj_lib::{ + backend::{ObjectId, TreeValue}, + merged_tree::MergedTreeBuilder, +}; + +use crate::{ + cli_util::{user_error, CommandError, CommandHelper, RevisionArg}, + ui::Ui, +}; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] +enum ChmodMode { + /// Make a path non-executable (alias: normal) + // We use short names for enum values so that errors say that the possible values are `n, x`. + #[value(name = "n", alias("normal"))] + Normal, + /// Make a path executable (alias: executable) + #[value(name = "x", alias("executable"))] + Executable, +} + +/// Sets or removes the executable bit for paths in the repo +/// +/// Unlike the POSIX `chmod`, `jj chmod` also works on Windows, on conflicted +/// files, and on arbitrary revisions. +#[derive(clap::Args, Clone, Debug)] +pub(crate) struct ChmodArgs { + mode: ChmodMode, + /// The revision to update + #[arg(long, short, default_value = "@")] + revision: RevisionArg, + /// Paths to change the executable bit for + #[arg(required = true, value_hint = clap::ValueHint::AnyPath)] + paths: Vec, +} + +#[instrument(skip_all)] +pub(crate) fn cmd_chmod( + ui: &mut Ui, + command: &CommandHelper, + args: &ChmodArgs, +) -> Result<(), CommandError> { + let executable_bit = match args.mode { + ChmodMode::Executable => true, + ChmodMode::Normal => false, + }; + + let mut workspace_command = command.workspace_helper(ui)?; + let repo_paths: Vec<_> = args + .paths + .iter() + .map(|path| workspace_command.parse_file_path(path)) + .try_collect()?; + let commit = workspace_command.resolve_single_rev(&args.revision, ui)?; + workspace_command.check_rewritable([&commit])?; + + let mut tx = workspace_command.start_transaction(&format!( + "make paths {} in commit {}", + if executable_bit { + "executable" + } else { + "non-executable" + }, + commit.id().hex(), + )); + let tree = commit.tree()?; + let store = tree.store(); + let mut tree_builder = MergedTreeBuilder::new(commit.tree_id().clone()); + for repo_path in repo_paths { + let user_error_with_path = |msg: &str| { + user_error(format!( + "{msg} at '{}'.", + tx.base_workspace_helper().format_file_path(&repo_path) + )) + }; + let tree_value = tree.path_value(&repo_path); + if tree_value.is_absent() { + return Err(user_error_with_path("No such path")); + } + let all_files = tree_value + .adds() + .iter() + .flatten() + .all(|tree_value| matches!(tree_value, TreeValue::File { .. })); + if !all_files { + let message = if tree_value.is_resolved() { + "Found neither a file nor a conflict" + } else { + "Some of the sides of the conflict are not files" + }; + return Err(user_error_with_path(message)); + } + let new_tree_value = tree_value.map(|value| match value { + Some(TreeValue::File { id, executable: _ }) => Some(TreeValue::File { + id: id.clone(), + executable: executable_bit, + }), + Some(TreeValue::Conflict(_)) => { + panic!("Conflict sides must not themselves be conflicts") + } + value => value.clone(), + }); + tree_builder.set_or_remove(repo_path, new_tree_value); + } + + let new_tree_id = tree_builder.write_tree(store)?; + tx.mut_repo() + .rewrite_commit(command.settings(), &commit) + .set_tree_id(new_tree_id) + .write()?; + tx.finish(ui) +} diff --git a/cli/src/commands/mod.rs b/cli/src/commands/mod.rs index 7c311a4230..f2fce2da95 100644 --- a/cli/src/commands/mod.rs +++ b/cli/src/commands/mod.rs @@ -19,6 +19,7 @@ mod bench; mod branch; mod cat; mod checkout; +mod chmod; mod debug; mod git; mod operation; @@ -83,7 +84,7 @@ enum Commands { #[command(alias = "print")] Cat(cat::CatArgs), Checkout(checkout::CheckoutArgs), - Chmod(ChmodArgs), + Chmod(chmod::ChmodArgs), Commit(CommitArgs), #[command(subcommand)] Config(ConfigSubcommand), @@ -665,32 +666,6 @@ struct UnsquashArgs { interactive: bool, } -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] -enum ChmodMode { - /// Make a path non-executable (alias: normal) - // We use short names for enum values so that errors say that the possible values are `n, x`. - #[value(name = "n", alias("normal"))] - Normal, - /// Make a path executable (alias: executable) - #[value(name = "x", alias("executable"))] - Executable, -} - -/// Sets or removes the executable bit for paths in the repo -/// -/// Unlike the POSIX `chmod`, `jj chmod` also works on Windows, on conflicted -/// files, and on arbitrary revisions. -#[derive(clap::Args, Clone, Debug)] -struct ChmodArgs { - mode: ChmodMode, - /// The revision to update - #[arg(long, short, default_value = "@")] - revision: RevisionArg, - /// Paths to change the executable bit for - #[arg(required = true, value_hint = clap::ValueHint::AnyPath)] - paths: Vec, -} - /// Resolve a conflicted file with an external merge tool /// /// Only conflicts that can be resolved with a 3-way merge are supported. See @@ -2749,79 +2724,6 @@ aborted. Ok(()) } -#[instrument(skip_all)] -fn cmd_chmod(ui: &mut Ui, command: &CommandHelper, args: &ChmodArgs) -> Result<(), CommandError> { - let executable_bit = match args.mode { - ChmodMode::Executable => true, - ChmodMode::Normal => false, - }; - - let mut workspace_command = command.workspace_helper(ui)?; - let repo_paths: Vec<_> = args - .paths - .iter() - .map(|path| workspace_command.parse_file_path(path)) - .try_collect()?; - let commit = workspace_command.resolve_single_rev(&args.revision, ui)?; - workspace_command.check_rewritable([&commit])?; - - let mut tx = workspace_command.start_transaction(&format!( - "make paths {} in commit {}", - if executable_bit { - "executable" - } else { - "non-executable" - }, - commit.id().hex(), - )); - let tree = commit.tree()?; - let store = tree.store(); - let mut tree_builder = MergedTreeBuilder::new(commit.tree_id().clone()); - for repo_path in repo_paths { - let user_error_with_path = |msg: &str| { - user_error(format!( - "{msg} at '{}'.", - tx.base_workspace_helper().format_file_path(&repo_path) - )) - }; - let tree_value = tree.path_value(&repo_path); - if tree_value.is_absent() { - return Err(user_error_with_path("No such path")); - } - let all_files = tree_value - .adds() - .iter() - .flatten() - .all(|tree_value| matches!(tree_value, TreeValue::File { .. })); - if !all_files { - let message = if tree_value.is_resolved() { - "Found neither a file nor a conflict" - } else { - "Some of the sides of the conflict are not files" - }; - return Err(user_error_with_path(message)); - } - let new_tree_value = tree_value.map(|value| match value { - Some(TreeValue::File { id, executable: _ }) => Some(TreeValue::File { - id: id.clone(), - executable: executable_bit, - }), - Some(TreeValue::Conflict(_)) => { - panic!("Conflict sides must not themselves be conflicts") - } - value => value.clone(), - }); - tree_builder.set_or_remove(repo_path, new_tree_value); - } - - let new_tree_id = tree_builder.write_tree(store)?; - tx.mut_repo() - .rewrite_commit(command.settings(), &commit) - .set_tree_id(new_tree_id) - .write()?; - tx.finish(ui) -} - #[instrument(skip_all)] fn cmd_resolve( ui: &mut Ui, @@ -3880,7 +3782,7 @@ pub fn run_command(ui: &mut Ui, command_helper: &CommandHelper) -> Result<(), Co 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::Chmod(sub_args) => cmd_chmod(ui, command_helper, sub_args), + Commands::Chmod(sub_args) => chmod::cmd_chmod(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), #[cfg(feature = "bench")]