just playing with tangled
at diffedit3 217 lines 7.3 kB view raw
1// Copyright 2020 The Jujutsu Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// https://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15use std::collections::HashSet; 16use std::fmt::Write as _; 17use std::io::Write; 18use std::path::Path; 19 20use clap::Subcommand; 21use itertools::Itertools; 22use jj_lib::repo_path::RepoPathBuf; 23use jj_lib::settings::UserSettings; 24use tracing::instrument; 25 26use crate::cli_util::{ 27 edit_temp_file, print_checkout_stats, CommandHelper, WorkspaceCommandHelper, 28}; 29use crate::command_error::{ 30 internal_error, internal_error_with_message, user_error_with_message, CommandError, 31}; 32use crate::ui::Ui; 33 34/// Manage which paths from the working-copy commit are present in the working 35/// copy 36#[derive(Subcommand, Clone, Debug)] 37pub(crate) enum SparseArgs { 38 List(SparseListArgs), 39 Set(SparseSetArgs), 40 Reset(SparseResetArgs), 41 Edit(SparseEditArgs), 42} 43 44/// List the patterns that are currently present in the working copy 45/// 46/// By default, a newly cloned or initialized repo will have have a pattern 47/// matching all files from the repo root. That pattern is rendered as `.` (a 48/// single period). 49#[derive(clap::Args, Clone, Debug)] 50pub(crate) struct SparseListArgs {} 51 52/// Update the patterns that are present in the working copy 53/// 54/// For example, if all you need is the `README.md` and the `lib/` 55/// directory, use `jj sparse set --clear --add README.md --add lib`. 56/// If you no longer need the `lib` directory, use `jj sparse set --remove lib`. 57#[derive(clap::Args, Clone, Debug)] 58pub(crate) struct SparseSetArgs { 59 /// Patterns to add to the working copy 60 #[arg( 61 long, 62 value_hint = clap::ValueHint::AnyPath, 63 value_parser = |s: &str| RepoPathBuf::from_relative_path(s), 64 )] 65 add: Vec<RepoPathBuf>, 66 /// Patterns to remove from the working copy 67 #[arg( 68 long, 69 conflicts_with = "clear", 70 value_hint = clap::ValueHint::AnyPath, 71 value_parser = |s: &str| RepoPathBuf::from_relative_path(s), 72 )] 73 remove: Vec<RepoPathBuf>, 74 /// Include no files in the working copy (combine with --add) 75 #[arg(long)] 76 clear: bool, 77} 78 79/// Reset the patterns to include all files in the working copy 80#[derive(clap::Args, Clone, Debug)] 81pub(crate) struct SparseResetArgs {} 82 83/// Start an editor to update the patterns that are present in the working copy 84#[derive(clap::Args, Clone, Debug)] 85pub(crate) struct SparseEditArgs {} 86 87#[instrument(skip_all)] 88pub(crate) fn cmd_sparse( 89 ui: &mut Ui, 90 command: &CommandHelper, 91 args: &SparseArgs, 92) -> Result<(), CommandError> { 93 match args { 94 SparseArgs::List(sub_args) => cmd_sparse_list(ui, command, sub_args), 95 SparseArgs::Set(sub_args) => cmd_sparse_set(ui, command, sub_args), 96 SparseArgs::Reset(sub_args) => cmd_sparse_reset(ui, command, sub_args), 97 SparseArgs::Edit(sub_args) => cmd_sparse_edit(ui, command, sub_args), 98 } 99} 100 101#[instrument(skip_all)] 102fn cmd_sparse_list( 103 ui: &mut Ui, 104 command: &CommandHelper, 105 _args: &SparseListArgs, 106) -> Result<(), CommandError> { 107 let workspace_command = command.workspace_helper(ui)?; 108 for path in workspace_command.working_copy().sparse_patterns()? { 109 writeln!(ui.stdout(), "{}", path.to_fs_path(Path::new("")).display())?; 110 } 111 Ok(()) 112} 113 114#[instrument(skip_all)] 115fn cmd_sparse_set( 116 ui: &mut Ui, 117 command: &CommandHelper, 118 args: &SparseSetArgs, 119) -> Result<(), CommandError> { 120 let mut workspace_command = command.workspace_helper(ui)?; 121 update_sparse_patterns_with(ui, &mut workspace_command, |_ui, old_patterns| { 122 let mut new_patterns = HashSet::new(); 123 if !args.clear { 124 new_patterns.extend(old_patterns.iter().cloned()); 125 for path in &args.remove { 126 new_patterns.remove(path); 127 } 128 } 129 for path in &args.add { 130 new_patterns.insert(path.to_owned()); 131 } 132 Ok(new_patterns.into_iter().sorted_unstable().collect()) 133 }) 134} 135 136#[instrument(skip_all)] 137fn cmd_sparse_reset( 138 ui: &mut Ui, 139 command: &CommandHelper, 140 _args: &SparseResetArgs, 141) -> Result<(), CommandError> { 142 let mut workspace_command = command.workspace_helper(ui)?; 143 update_sparse_patterns_with(ui, &mut workspace_command, |_ui, _old_patterns| { 144 Ok(vec![RepoPathBuf::root()]) 145 }) 146} 147 148#[instrument(skip_all)] 149fn cmd_sparse_edit( 150 ui: &mut Ui, 151 command: &CommandHelper, 152 _args: &SparseEditArgs, 153) -> Result<(), CommandError> { 154 let mut workspace_command = command.workspace_helper(ui)?; 155 let repo_path = workspace_command.repo().repo_path().to_owned(); 156 update_sparse_patterns_with(ui, &mut workspace_command, |_ui, old_patterns| { 157 let mut new_patterns = edit_sparse(&repo_path, old_patterns, command.settings())?; 158 new_patterns.sort_unstable(); 159 new_patterns.dedup(); 160 Ok(new_patterns) 161 }) 162} 163 164fn edit_sparse( 165 repo_path: &Path, 166 sparse: &[RepoPathBuf], 167 settings: &UserSettings, 168) -> Result<Vec<RepoPathBuf>, CommandError> { 169 let mut content = String::new(); 170 for sparse_path in sparse { 171 let workspace_relative_sparse_path = sparse_path.to_fs_path(Path::new("")); 172 let path_string = workspace_relative_sparse_path.to_str().ok_or_else(|| { 173 internal_error(format!( 174 "Stored sparse path is not valid utf-8: {}", 175 workspace_relative_sparse_path.display() 176 )) 177 })?; 178 writeln!(&mut content, "{}", path_string).unwrap(); 179 } 180 181 let content = edit_temp_file( 182 "sparse patterns", 183 ".jjsparse", 184 repo_path, 185 &content, 186 settings, 187 )?; 188 189 content 190 .lines() 191 .filter(|line| !line.starts_with("JJ: ")) 192 .map(|line| line.trim()) 193 .filter(|line| !line.is_empty()) 194 .map(|line| { 195 RepoPathBuf::from_relative_path(line).map_err(|err| { 196 user_error_with_message(format!("Failed to parse sparse pattern: {line}"), err) 197 }) 198 }) 199 .try_collect() 200} 201 202fn update_sparse_patterns_with( 203 ui: &mut Ui, 204 workspace_command: &mut WorkspaceCommandHelper, 205 f: impl FnOnce(&mut Ui, &[RepoPathBuf]) -> Result<Vec<RepoPathBuf>, CommandError>, 206) -> Result<(), CommandError> { 207 let (mut locked_ws, wc_commit) = workspace_command.start_working_copy_mutation()?; 208 let new_patterns = f(ui, locked_ws.locked_wc().sparse_patterns()?)?; 209 let stats = locked_ws 210 .locked_wc() 211 .set_sparse_patterns(new_patterns) 212 .map_err(|err| internal_error_with_message("Failed to update working copy paths", err))?; 213 let operation_id = locked_ws.locked_wc().old_operation_id().clone(); 214 locked_ws.finish(operation_id)?; 215 print_checkout_stats(ui, stats, &wc_commit)?; 216 Ok(()) 217}