diff --git a/cli/src/commands/mod.rs b/cli/src/commands/mod.rs index ee85055ed..e0e384db2 100644 --- a/cli/src/commands/mod.rs +++ b/cli/src/commands/mod.rs @@ -50,6 +50,7 @@ mod split; mod squash; mod status; mod unsquash; +mod untrack; mod util; use std::fmt::Debug; @@ -62,19 +63,17 @@ use jj_lib::backend::ObjectId; use jj_lib::commit::Commit; use jj_lib::file_util; use jj_lib::matchers::EverythingMatcher; -use jj_lib::merge::Merge; -use jj_lib::merged_tree::{MergedTree, MergedTreeBuilder}; +use jj_lib::merged_tree::MergedTree; use jj_lib::op_store::WorkspaceId; use jj_lib::repo::{ReadonlyRepo, Repo}; use jj_lib::rewrite::merge_commit_trees; use jj_lib::settings::UserSettings; -use jj_lib::working_copy::SnapshotOptions; use jj_lib::workspace::{default_working_copy_initializer, Workspace}; use tracing::instrument; use crate::cli_util::{ - check_stale_working_copy, print_checkout_stats, run_ui_editor, user_error, - user_error_with_hint, Args, CommandError, CommandHelper, RevisionArg, WorkspaceCommandHelper, + check_stale_working_copy, print_checkout_stats, run_ui_editor, user_error, Args, CommandError, + CommandHelper, RevisionArg, WorkspaceCommandHelper, }; use crate::diff_util::{self, DiffFormat}; use crate::formatter::{Formatter, PlainTextFormatter}; @@ -145,7 +144,7 @@ enum Commands { /// Undo an operation (shortcut for `jj op undo`) Undo(operation::OperationUndoArgs), Unsquash(unsquash::UnsquashArgs), - Untrack(UntrackArgs), + Untrack(untrack::UntrackArgs), Version(VersionArgs), #[command(subcommand)] Workspace(WorkspaceCommands), @@ -155,14 +154,6 @@ enum Commands { #[derive(clap::Args, Clone, Debug)] struct VersionArgs {} -/// Stop tracking specified paths in the working copy -#[derive(clap::Args, Clone, Debug)] -struct UntrackArgs { - /// Paths to untrack - #[arg(required = true, value_hint = clap::ValueHint::AnyPath)] - paths: Vec, -} - /// Commands for working with workspaces /// /// Workspaces let you add additional working copies attached to the same repo. @@ -234,79 +225,6 @@ fn cmd_version( Ok(()) } -#[instrument(skip_all)] -fn cmd_untrack( - ui: &mut Ui, - command: &CommandHelper, - args: &UntrackArgs, -) -> Result<(), CommandError> { - let mut workspace_command = command.workspace_helper(ui)?; - let store = workspace_command.repo().store().clone(); - let matcher = workspace_command.matcher_from_values(&args.paths)?; - - let mut tx = workspace_command - .start_transaction("untrack paths") - .into_inner(); - let base_ignores = workspace_command.base_ignores(); - let (mut locked_ws, wc_commit) = workspace_command.start_working_copy_mutation()?; - // Create a new tree without the unwanted files - let mut tree_builder = MergedTreeBuilder::new(wc_commit.tree_id().clone()); - let wc_tree = wc_commit.tree()?; - for (path, _value) in wc_tree.entries_matching(matcher.as_ref()) { - tree_builder.set_or_remove(path, Merge::absent()); - } - let new_tree_id = tree_builder.write_tree(&store)?; - let new_tree = store.get_root_tree(&new_tree_id)?; - // Reset the working copy to the new tree - locked_ws.locked_wc().reset(&new_tree)?; - // Commit the working copy again so we can inform the user if paths couldn't be - // untracked because they're not ignored. - let wc_tree_id = locked_ws.locked_wc().snapshot(SnapshotOptions { - base_ignores, - fsmonitor_kind: command.settings().fsmonitor_kind()?, - progress: None, - max_new_file_size: command.settings().max_new_file_size()?, - })?; - if wc_tree_id != new_tree_id { - let wc_tree = store.get_root_tree(&wc_tree_id)?; - let added_back = wc_tree.entries_matching(matcher.as_ref()).collect_vec(); - if !added_back.is_empty() { - drop(locked_ws); - let path = &added_back[0].0; - let ui_path = workspace_command.format_file_path(path); - let message = if added_back.len() > 1 { - format!( - "'{}' and {} other files are not ignored.", - ui_path, - added_back.len() - 1 - ) - } else { - format!("'{ui_path}' is not ignored.") - }; - return Err(user_error_with_hint( - message, - "Files that are not ignored will be added back by the next command. -Make sure they're ignored, then try again.", - )); - } else { - // This means there were some concurrent changes made in the working copy. We - // don't want to mix those in, so reset the working copy again. - locked_ws.locked_wc().reset(&new_tree)?; - } - } - tx.mut_repo() - .rewrite_commit(command.settings(), &wc_commit) - .set_tree_id(new_tree_id) - .write()?; - let num_rebased = tx.mut_repo().rebase_descendants(command.settings())?; - if num_rebased > 0 { - writeln!(ui.stderr(), "Rebased {num_rebased} descendant commits")?; - } - let repo = tx.commit(); - locked_ws.finish(repo.op_id().clone())?; - Ok(()) -} - fn show_predecessor_patch( ui: &Ui, formatter: &mut dyn Formatter, @@ -715,7 +633,7 @@ pub fn run_command(ui: &mut Ui, command_helper: &CommandHelper) -> Result<(), Co Commands::Init(sub_args) => init::cmd_init(ui, command_helper, sub_args), Commands::Config(sub_args) => config::cmd_config(ui, command_helper, sub_args), Commands::Checkout(sub_args) => checkout::cmd_checkout(ui, command_helper, sub_args), - Commands::Untrack(sub_args) => cmd_untrack(ui, command_helper, sub_args), + Commands::Untrack(sub_args) => untrack::cmd_untrack(ui, command_helper, sub_args), Commands::Files(sub_args) => files::cmd_files(ui, command_helper, sub_args), Commands::Cat(sub_args) => cat::cmd_cat(ui, command_helper, sub_args), Commands::Diff(sub_args) => diff::cmd_diff(ui, command_helper, sub_args), diff --git a/cli/src/commands/untrack.rs b/cli/src/commands/untrack.rs new file mode 100644 index 000000000..621986e11 --- /dev/null +++ b/cli/src/commands/untrack.rs @@ -0,0 +1,106 @@ +// 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::io::Write; + +use itertools::Itertools; +use jj_lib::merge::Merge; +use jj_lib::merged_tree::MergedTreeBuilder; +use jj_lib::repo::Repo; +use jj_lib::working_copy::SnapshotOptions; +use tracing::instrument; + +use crate::cli_util::{user_error_with_hint, CommandError, CommandHelper}; +use crate::ui::Ui; + +/// Stop tracking specified paths in the working copy +#[derive(clap::Args, Clone, Debug)] +pub(crate) struct UntrackArgs { + /// Paths to untrack + #[arg(required = true, value_hint = clap::ValueHint::AnyPath)] + paths: Vec, +} + +#[instrument(skip_all)] +pub(crate) fn cmd_untrack( + ui: &mut Ui, + command: &CommandHelper, + args: &UntrackArgs, +) -> Result<(), CommandError> { + let mut workspace_command = command.workspace_helper(ui)?; + let store = workspace_command.repo().store().clone(); + let matcher = workspace_command.matcher_from_values(&args.paths)?; + + let mut tx = workspace_command + .start_transaction("untrack paths") + .into_inner(); + let base_ignores = workspace_command.base_ignores(); + let (mut locked_ws, wc_commit) = workspace_command.start_working_copy_mutation()?; + // Create a new tree without the unwanted files + let mut tree_builder = MergedTreeBuilder::new(wc_commit.tree_id().clone()); + let wc_tree = wc_commit.tree()?; + for (path, _value) in wc_tree.entries_matching(matcher.as_ref()) { + tree_builder.set_or_remove(path, Merge::absent()); + } + let new_tree_id = tree_builder.write_tree(&store)?; + let new_tree = store.get_root_tree(&new_tree_id)?; + // Reset the working copy to the new tree + locked_ws.locked_wc().reset(&new_tree)?; + // Commit the working copy again so we can inform the user if paths couldn't be + // untracked because they're not ignored. + let wc_tree_id = locked_ws.locked_wc().snapshot(SnapshotOptions { + base_ignores, + fsmonitor_kind: command.settings().fsmonitor_kind()?, + progress: None, + max_new_file_size: command.settings().max_new_file_size()?, + })?; + if wc_tree_id != new_tree_id { + let wc_tree = store.get_root_tree(&wc_tree_id)?; + let added_back = wc_tree.entries_matching(matcher.as_ref()).collect_vec(); + if !added_back.is_empty() { + drop(locked_ws); + let path = &added_back[0].0; + let ui_path = workspace_command.format_file_path(path); + let message = if added_back.len() > 1 { + format!( + "'{}' and {} other files are not ignored.", + ui_path, + added_back.len() - 1 + ) + } else { + format!("'{ui_path}' is not ignored.") + }; + return Err(user_error_with_hint( + message, + "Files that are not ignored will be added back by the next command. +Make sure they're ignored, then try again.", + )); + } else { + // This means there were some concurrent changes made in the working copy. We + // don't want to mix those in, so reset the working copy again. + locked_ws.locked_wc().reset(&new_tree)?; + } + } + tx.mut_repo() + .rewrite_commit(command.settings(), &wc_commit) + .set_tree_id(new_tree_id) + .write()?; + let num_rebased = tx.mut_repo().rebase_descendants(command.settings())?; + if num_rebased > 0 { + writeln!(ui.stderr(), "Rebased {num_rebased} descendant commits")?; + } + let repo = tx.commit(); + locked_ws.finish(repo.op_id().clone())?; + Ok(()) +}