mirror of
https://github.com/martinvonz/jj.git
synced 2025-05-05 23:42:50 +00:00
I'm going to rewrite the error output to print source chain one by one. The "{message}: {err}" pattern is wrapped in an error struct. InternalError variant could be a { message, source } pair, but not all errors need an additional message. This will also apply to UserError.
202 lines
6.4 KiB
Rust
202 lines
6.4 KiB
Rust
// 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 std::collections::HashSet;
|
|
use std::fmt::Write as _;
|
|
use std::io::{self, Write};
|
|
use std::path::Path;
|
|
|
|
use clap::Subcommand;
|
|
use itertools::Itertools;
|
|
use jj_lib::file_util;
|
|
use jj_lib::repo_path::RepoPathBuf;
|
|
use jj_lib::settings::UserSettings;
|
|
use tracing::instrument;
|
|
|
|
use crate::cli_util::{
|
|
edit_temp_file, internal_error_with_message, print_checkout_stats, CommandError, CommandHelper,
|
|
};
|
|
use crate::ui::Ui;
|
|
|
|
/// Manage which paths from the working-copy commit are present in the working
|
|
/// copy
|
|
#[derive(Subcommand, Clone, Debug)]
|
|
pub(crate) enum SparseArgs {
|
|
List(SparseListArgs),
|
|
Set(SparseSetArgs),
|
|
}
|
|
|
|
/// List the patterns that are currently present in the working copy
|
|
///
|
|
/// By default, a newly cloned or initialized repo will have have a pattern
|
|
/// matching all files from the repo root. That pattern is rendered as `.` (a
|
|
/// single period).
|
|
#[derive(clap::Args, Clone, Debug)]
|
|
pub(crate) struct SparseListArgs {}
|
|
|
|
/// Update the patterns that are present in the working copy
|
|
///
|
|
/// For example, if all you need is the `README.md` and the `lib/`
|
|
/// directory, use `jj sparse set --clear --add README.md --add lib`.
|
|
/// If you no longer need the `lib` directory, use `jj sparse set --remove lib`.
|
|
#[derive(clap::Args, Clone, Debug)]
|
|
pub(crate) struct SparseSetArgs {
|
|
/// Patterns to add to the working copy
|
|
#[arg(long, value_hint = clap::ValueHint::AnyPath)]
|
|
add: Vec<String>,
|
|
/// Patterns to remove from the working copy
|
|
#[arg(long, conflicts_with = "clear", value_hint = clap::ValueHint::AnyPath)]
|
|
remove: Vec<String>,
|
|
/// Include no files in the working copy (combine with --add)
|
|
#[arg(long)]
|
|
clear: bool,
|
|
/// Edit patterns with $EDITOR
|
|
#[arg(long)]
|
|
edit: bool,
|
|
/// Include all files in the working copy
|
|
#[arg(long, conflicts_with_all = &["add", "remove", "clear"])]
|
|
reset: bool,
|
|
}
|
|
|
|
#[instrument(skip_all)]
|
|
pub(crate) fn cmd_sparse(
|
|
ui: &mut Ui,
|
|
command: &CommandHelper,
|
|
args: &SparseArgs,
|
|
) -> Result<(), CommandError> {
|
|
match args {
|
|
SparseArgs::List(sub_args) => cmd_sparse_list(ui, command, sub_args),
|
|
SparseArgs::Set(sub_args) => cmd_sparse_set(ui, command, sub_args),
|
|
}
|
|
}
|
|
|
|
#[instrument(skip_all)]
|
|
fn cmd_sparse_list(
|
|
ui: &mut Ui,
|
|
command: &CommandHelper,
|
|
_args: &SparseListArgs,
|
|
) -> Result<(), CommandError> {
|
|
let workspace_command = command.workspace_helper(ui)?;
|
|
for path in workspace_command.working_copy().sparse_patterns()? {
|
|
let ui_path = workspace_command.format_file_path(path);
|
|
writeln!(ui.stdout(), "{ui_path}")?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[instrument(skip_all)]
|
|
fn cmd_sparse_set(
|
|
ui: &mut Ui,
|
|
command: &CommandHelper,
|
|
args: &SparseSetArgs,
|
|
) -> Result<(), CommandError> {
|
|
let mut workspace_command = command.workspace_helper(ui)?;
|
|
let paths_to_add: Vec<_> = args
|
|
.add
|
|
.iter()
|
|
.map(|v| workspace_command.parse_file_path(v))
|
|
.try_collect()?;
|
|
let paths_to_remove: Vec<_> = args
|
|
.remove
|
|
.iter()
|
|
.map(|v| workspace_command.parse_file_path(v))
|
|
.try_collect()?;
|
|
// Determine inputs of `edit` operation now, since `workspace_command` is
|
|
// inaccessible while the working copy is locked.
|
|
let edit_inputs = args.edit.then(|| {
|
|
(
|
|
workspace_command.repo().clone(),
|
|
workspace_command.workspace_root().clone(),
|
|
)
|
|
});
|
|
let (mut locked_ws, wc_commit) = workspace_command.start_working_copy_mutation()?;
|
|
let mut new_patterns = HashSet::new();
|
|
if args.reset {
|
|
new_patterns.insert(RepoPathBuf::root());
|
|
} else {
|
|
if !args.clear {
|
|
new_patterns.extend(locked_ws.locked_wc().sparse_patterns()?.iter().cloned());
|
|
for path in paths_to_remove {
|
|
new_patterns.remove(&path);
|
|
}
|
|
}
|
|
for path in paths_to_add {
|
|
new_patterns.insert(path);
|
|
}
|
|
}
|
|
let mut new_patterns = new_patterns.into_iter().collect_vec();
|
|
new_patterns.sort();
|
|
if let Some((repo, workspace_root)) = edit_inputs {
|
|
new_patterns = edit_sparse(
|
|
&workspace_root,
|
|
repo.repo_path(),
|
|
&new_patterns,
|
|
command.settings(),
|
|
)?;
|
|
new_patterns.sort();
|
|
}
|
|
let stats = locked_ws
|
|
.locked_wc()
|
|
.set_sparse_patterns(new_patterns)
|
|
.map_err(|err| internal_error_with_message("Failed to update working copy paths", err))?;
|
|
let operation_id = locked_ws.locked_wc().old_operation_id().clone();
|
|
locked_ws.finish(operation_id)?;
|
|
print_checkout_stats(ui, stats, &wc_commit)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn edit_sparse(
|
|
workspace_root: &Path,
|
|
repo_path: &Path,
|
|
sparse: &[RepoPathBuf],
|
|
settings: &UserSettings,
|
|
) -> Result<Vec<RepoPathBuf>, CommandError> {
|
|
let mut content = String::new();
|
|
for sparse_path in sparse {
|
|
let workspace_relative_sparse_path =
|
|
file_util::relative_path(workspace_root, &sparse_path.to_fs_path(workspace_root));
|
|
let path_string = workspace_relative_sparse_path.to_str().ok_or_else(|| {
|
|
io::Error::new(
|
|
io::ErrorKind::InvalidData,
|
|
format!(
|
|
"stored sparse path is not valid utf-8: {}",
|
|
workspace_relative_sparse_path.display()
|
|
),
|
|
)
|
|
})?;
|
|
writeln!(&mut content, "{}", path_string).unwrap();
|
|
}
|
|
|
|
let content = edit_temp_file(
|
|
"sparse patterns",
|
|
".jjsparse",
|
|
repo_path,
|
|
&content,
|
|
settings,
|
|
)?;
|
|
|
|
content
|
|
.lines()
|
|
.filter(|line| !line.starts_with("JJ: ") && !line.trim().is_empty())
|
|
.map(|line| {
|
|
Ok::<_, CommandError>(RepoPathBuf::parse_fs_path(
|
|
workspace_root,
|
|
workspace_root,
|
|
line.trim(),
|
|
)?)
|
|
})
|
|
.try_collect()
|
|
}
|