mirror of
https://github.com/martinvonz/jj.git
synced 2025-05-13 19:24:25 +00:00
commands: move chmod code to chmod.rs
This commit is contained in:
parent
3a43b22f96
commit
6badf159fa
124
cli/src/commands/chmod.rs
Normal file
124
cli/src/commands/chmod.rs
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
// 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 jj_lib::backend::{ObjectId, TreeValue};
|
||||||
|
use jj_lib::merged_tree::MergedTreeBuilder;
|
||||||
|
use tracing::instrument;
|
||||||
|
|
||||||
|
use crate::cli_util::{user_error, CommandError, CommandHelper, RevisionArg};
|
||||||
|
use crate::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<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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)
|
||||||
|
}
|
@ -19,6 +19,7 @@ mod bench;
|
|||||||
mod branch;
|
mod branch;
|
||||||
mod cat;
|
mod cat;
|
||||||
mod checkout;
|
mod checkout;
|
||||||
|
mod chmod;
|
||||||
mod debug;
|
mod debug;
|
||||||
mod git;
|
mod git;
|
||||||
mod operation;
|
mod operation;
|
||||||
@ -83,7 +84,7 @@ enum Commands {
|
|||||||
#[command(alias = "print")]
|
#[command(alias = "print")]
|
||||||
Cat(cat::CatArgs),
|
Cat(cat::CatArgs),
|
||||||
Checkout(checkout::CheckoutArgs),
|
Checkout(checkout::CheckoutArgs),
|
||||||
Chmod(ChmodArgs),
|
Chmod(chmod::ChmodArgs),
|
||||||
Commit(CommitArgs),
|
Commit(CommitArgs),
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
Config(ConfigSubcommand),
|
Config(ConfigSubcommand),
|
||||||
@ -665,32 +666,6 @@ struct UnsquashArgs {
|
|||||||
interactive: bool,
|
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<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resolve a conflicted file with an external merge tool
|
/// Resolve a conflicted file with an external merge tool
|
||||||
///
|
///
|
||||||
/// Only conflicts that can be resolved with a 3-way merge are supported. See
|
/// Only conflicts that can be resolved with a 3-way merge are supported. See
|
||||||
@ -2749,79 +2724,6 @@ aborted.
|
|||||||
Ok(())
|
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)]
|
#[instrument(skip_all)]
|
||||||
fn cmd_resolve(
|
fn cmd_resolve(
|
||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
@ -3893,7 +3795,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::Operation(sub_args) => operation::cmd_operation(ui, command_helper, sub_args),
|
||||||
Commands::Workspace(sub_args) => cmd_workspace(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::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::Git(sub_args) => git::cmd_git(ui, command_helper, sub_args),
|
||||||
Commands::Util(sub_args) => cmd_util(ui, command_helper, sub_args),
|
Commands::Util(sub_args) => cmd_util(ui, command_helper, sub_args),
|
||||||
#[cfg(feature = "bench")]
|
#[cfg(feature = "bench")]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user