just playing with tangled

Compare changes

Choose any two refs to compare.

+358 -1515
+7 -8
CHANGELOG.md
··· 67 * Templates can now do arithmetic on integers with the `+`, `-`, `*`, `/`, and `%` 68 infix operators. 69 70 - * `jj split` assigns the change id and the bookmarks of the source revision 71 - to the revision with the non-selected changes. 72 - You can opt out of this change by setting `split.legacy-bookmark-behavior = true`, 73 - but this will likely be removed in a future release. 74 - 75 * Evolution history is now stored in the operation log. `jj evolog` can show 76 associated operations for commits created by new jj. 77 ··· 83 84 * `jj parallelize` can now parallelize groups of changes that _start_ with an 85 immutable change, but do not contain any other immutable changes. 86 87 ### Packaging changes 88 ··· 120 `template-aliases.default_commit_description`. Please also consider using 121 [`templates.draft_commit_description`](docs/config.md#default-description), 122 and/or [`templates.commit_trailers`](docs/config.md#commit-trailers). 123 124 ### New features 125 ··· 324 325 * The 'how to resolve conflicts' hint that is shown when conflicts appear can 326 be hidden by setting `hints.resolving-conflicts = false`. 327 - 328 - * `jj squash` now has a `--restore-descendants` option to preserve the snapshots 329 - of the children of the modified commits. 330 331 * `jj op diff` and `jj op log --op-diff` now show changes to which commits 332 correspond to working copies.
··· 67 * Templates can now do arithmetic on integers with the `+`, `-`, `*`, `/`, and `%` 68 infix operators. 69 70 * Evolution history is now stored in the operation log. `jj evolog` can show 71 associated operations for commits created by new jj. 72 ··· 78 79 * `jj parallelize` can now parallelize groups of changes that _start_ with an 80 immutable change, but do not contain any other immutable changes. 81 + 82 + * `jj` will no longer warn about deprecated paths on macOS if the configured 83 + XDG directory is the deprecated one (~/Library/Application Support). 84 85 ### Packaging changes 86 ··· 118 `template-aliases.default_commit_description`. Please also consider using 119 [`templates.draft_commit_description`](docs/config.md#default-description), 120 and/or [`templates.commit_trailers`](docs/config.md#commit-trailers). 121 + 122 + * On macOS, config.toml files in `~/Library/Application Support/jj` are 123 + deprecated; one should instead use `$XDG_CONFIG_HOME/jj` 124 + (defaults to `~/.config/jj`) 125 126 ### New features 127 ··· 326 327 * The 'how to resolve conflicts' hint that is shown when conflicts appear can 328 be hidden by setting `hints.resolving-conflicts = false`. 329 330 * `jj op diff` and `jj op log --op-diff` now show changes to which commits 331 correspond to working copies.
+2 -6
cli/build.rs
··· 61 "log", 62 "--no-graph", 63 "-r=@-", 64 - "-T=commit_id ++ '-'", 65 ]) 66 .output() 67 { 68 if output.status.success() { 69 - let mut parent_commits = String::from_utf8(output.stdout).unwrap(); 70 - // TODO(ilyagr): The `test_version` integration test shoult be fixed 71 - // to succeed even if there is more than one parent commit. 72 - parent_commits.truncate(parent_commits.trim_end_matches('-').len()); 73 - return Some(parent_commits); 74 } 75 } 76
··· 61 "log", 62 "--no-graph", 63 "-r=@-", 64 + "-T=commit_id", 65 ]) 66 .output() 67 { 68 if output.status.success() { 69 + return Some(String::from_utf8(output.stdout).unwrap()); 70 } 71 } 72
+13 -3
cli/src/cli_util.rs
··· 3858 "Did you update to a commit where the directory doesn't exist?", 3859 ) 3860 })?; 3861 - let mut config_env = ConfigEnv::from_environment(); 3862 let mut last_config_migration_descriptions = Vec::new(); 3863 let mut migrate_config = |config: &mut StackedConfig| -> Result<(), CommandError> { 3864 last_config_migration_descriptions = ··· 3937 ui.reset(&config)?; 3938 3939 // Print only the last migration messages to omit duplicates. 3940 - for desc in &last_config_migration_descriptions { 3941 - writeln!(ui.warning_default(), "Deprecated config: {desc}")?; 3942 } 3943 3944 if args.global_args.repository.is_some() {
··· 3858 "Did you update to a commit where the directory doesn't exist?", 3859 ) 3860 })?; 3861 + let mut config_env = ConfigEnv::from_environment(ui); 3862 let mut last_config_migration_descriptions = Vec::new(); 3863 let mut migrate_config = |config: &mut StackedConfig| -> Result<(), CommandError> { 3864 last_config_migration_descriptions = ··· 3937 ui.reset(&config)?; 3938 3939 // Print only the last migration messages to omit duplicates. 3940 + for (source, desc) in &last_config_migration_descriptions { 3941 + let source_str = match source { 3942 + ConfigSource::Default => "default-provided", 3943 + ConfigSource::EnvBase | ConfigSource::EnvOverrides => "environment-provided", 3944 + ConfigSource::User => "user-level", 3945 + ConfigSource::Repo => "repo-level", 3946 + ConfigSource::CommandArg => "CLI-provided", 3947 + }; 3948 + writeln!( 3949 + ui.warning_default(), 3950 + "Deprecated {source_str} config: {desc}" 3951 + )?; 3952 } 3953 3954 if args.global_args.repository.is_some() {
+1 -6
cli/src/commands/commit.rs
··· 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 - use clap_complete::ArgValueCandidates; 16 use clap_complete::ArgValueCompleter; 17 use indoc::writedoc; 18 use jj_lib::backend::Signature; ··· 38 #[arg(short, long)] 39 interactive: bool, 40 /// Specify diff editor to be used (implies --interactive) 41 - #[arg( 42 - long, 43 - value_name = "NAME", 44 - add = ArgValueCandidates::new(complete::diff_editors), 45 - )] 46 tool: Option<String>, 47 /// The change description to use (don't open editor) 48 #[arg(long = "message", short, value_name = "MESSAGE")]
··· 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 use clap_complete::ArgValueCompleter; 16 use indoc::writedoc; 17 use jj_lib::backend::Signature; ··· 37 #[arg(short, long)] 38 interactive: bool, 39 /// Specify diff editor to be used (implies --interactive) 40 + #[arg(long, value_name = "NAME")] 41 tool: Option<String>, 42 /// The change description to use (don't open editor) 43 #[arg(long = "message", short, value_name = "MESSAGE")]
+1 -6
cli/src/commands/diffedit.rs
··· 14 15 use std::io::Write as _; 16 17 - use clap_complete::ArgValueCandidates; 18 use clap_complete::ArgValueCompleter; 19 use itertools::Itertools as _; 20 use jj_lib::matchers::EverythingMatcher; ··· 78 )] 79 to: Option<RevisionArg>, 80 /// Specify diff editor to be used 81 - #[arg( 82 - long, 83 - value_name = "NAME", 84 - add = ArgValueCandidates::new(complete::diff_editors), 85 - )] 86 tool: Option<String>, 87 /// Preserve the content (not the diff) when rebasing descendants 88 ///
··· 14 15 use std::io::Write as _; 16 17 use clap_complete::ArgValueCompleter; 18 use itertools::Itertools as _; 19 use jj_lib::matchers::EverythingMatcher; ··· 77 )] 78 to: Option<RevisionArg>, 79 /// Specify diff editor to be used 80 + #[arg(long, value_name = "NAME")] 81 tool: Option<String>, 82 /// Preserve the content (not the diff) when rebasing descendants 83 ///
+9 -60
cli/src/commands/evolog.rs
··· 19 use clap_complete::ArgValueCompleter; 20 use itertools::Itertools as _; 21 use jj_lib::commit::Commit; 22 - use jj_lib::copies::CopyRecords; 23 use jj_lib::evolution::walk_predecessors; 24 use jj_lib::graph::reverse_graph; 25 use jj_lib::graph::GraphEdge; 26 use jj_lib::matchers::EverythingMatcher; 27 - use jj_lib::repo::Repo as _; 28 - use jj_lib::rewrite::merge_commit_trees; 29 use tracing::instrument; 30 31 use super::log::get_node_template; ··· 35 use crate::cli_util::RevisionArg; 36 use crate::command_error::CommandError; 37 use crate::complete; 38 - use crate::diff_util::get_copy_records; 39 use crate::diff_util::DiffFormatArgs; 40 use crate::graphlog::get_graphlog; 41 use crate::graphlog::GraphStyle; ··· 89 /// contaminated by unrelated changes. 90 #[arg(long, short = 'p')] 91 patch: bool, 92 - /// Changes the behavior of `--patch`, `--git`, etc to show diffs from 93 - /// rebases 94 - /// 95 - /// Implies `--patch` if no other diff format is requested. 96 - /// 97 - /// Normally, `jj evolog -p` shows a so-called "interdiff", temporarily 98 - /// rebasing the versions of a revision to the same parents, in order to 99 - /// omit differences in the file contents that are caused by rebases. 100 - /// 101 - /// This option disables this behavior, and shows diffs between the contents 102 - /// of the different versions without modification (as snapshots). 103 - /// 104 - /// Sometimes, `--diff-snapshots` can show fewer differences to be shown. 105 - /// For example, let's say the current revision is not empty and we perform 106 - /// `jj squash --keep-empty -r @` to make it empty. Then, `jj evolog -p 107 - /// --diff-snapshots` will not show any changes since the contents of the 108 - /// files in the current revision did not change. However, `jj evolog -p` 109 - /// will show a change, representing the fact that a non-empty revision 110 - /// became empty. 111 - #[arg(long)] 112 - diff_snapshots: bool, 113 #[command(flatten)] 114 diff_format: DiffFormatArgs, 115 } ··· 124 125 let start_commit = workspace_command.resolve_single_rev(ui, &args.revision)?; 126 127 - let diff_renderer = workspace_command 128 - .diff_renderer_for_log(&args.diff_format, args.patch || args.diff_snapshots)?; 129 let graph_style = GraphStyle::from_settings(workspace_command.settings())?; 130 let with_content_format = LogContentFormat::new(ui, workspace_command.settings())?; 131 ··· 199 if let Some(renderer) = &diff_renderer { 200 let predecessors: Vec<_> = entry.predecessors().try_collect()?; 201 let mut formatter = ui.new_formatter(&mut buffer); 202 - if args.diff_snapshots { 203 - let mut copy_records = CopyRecords::default(); 204 - for p in &predecessors { 205 - let records = get_copy_records( 206 - workspace_command.repo().store(), 207 - p.id(), 208 - entry.commit.id(), 209 - &EverythingMatcher, 210 - )?; 211 - copy_records.add_records(records)?; 212 - } 213 - let from_tree = 214 - merge_commit_trees(workspace_command.repo().as_ref(), &predecessors)?; 215 - renderer.show_diff( 216 - ui, 217 - formatter.as_mut(), 218 - &from_tree, 219 - &entry.commit.tree()?, 220 - &EverythingMatcher, 221 - &copy_records, 222 - within_graph.width(), 223 - )?; 224 - } else { 225 - renderer.show_inter_diff( 226 - ui, 227 - formatter.as_mut(), 228 - &predecessors, 229 - &entry.commit, 230 - &EverythingMatcher, 231 - within_graph.width(), 232 - )?; 233 - } 234 } 235 let node_symbol = format_template(ui, &Some(entry.commit.clone()), &node_template); 236 graph.add_node( ··· 262 if let Some(renderer) = &diff_renderer { 263 let predecessors: Vec<_> = entry.predecessors().try_collect()?; 264 let width = ui.term_width(); 265 - // TODO 266 renderer.show_inter_diff( 267 ui, 268 formatter,
··· 19 use clap_complete::ArgValueCompleter; 20 use itertools::Itertools as _; 21 use jj_lib::commit::Commit; 22 use jj_lib::evolution::walk_predecessors; 23 use jj_lib::graph::reverse_graph; 24 use jj_lib::graph::GraphEdge; 25 use jj_lib::matchers::EverythingMatcher; 26 use tracing::instrument; 27 28 use super::log::get_node_template; ··· 32 use crate::cli_util::RevisionArg; 33 use crate::command_error::CommandError; 34 use crate::complete; 35 use crate::diff_util::DiffFormatArgs; 36 use crate::graphlog::get_graphlog; 37 use crate::graphlog::GraphStyle; ··· 85 /// contaminated by unrelated changes. 86 #[arg(long, short = 'p')] 87 patch: bool, 88 #[command(flatten)] 89 diff_format: DiffFormatArgs, 90 } ··· 99 100 let start_commit = workspace_command.resolve_single_rev(ui, &args.revision)?; 101 102 + let diff_renderer = workspace_command.diff_renderer_for_log(&args.diff_format, args.patch)?; 103 let graph_style = GraphStyle::from_settings(workspace_command.settings())?; 104 let with_content_format = LogContentFormat::new(ui, workspace_command.settings())?; 105 ··· 173 if let Some(renderer) = &diff_renderer { 174 let predecessors: Vec<_> = entry.predecessors().try_collect()?; 175 let mut formatter = ui.new_formatter(&mut buffer); 176 + renderer.show_inter_diff( 177 + ui, 178 + formatter.as_mut(), 179 + &predecessors, 180 + &entry.commit, 181 + &EverythingMatcher, 182 + within_graph.width(), 183 + )?; 184 } 185 let node_symbol = format_template(ui, &Some(entry.commit.clone()), &node_template); 186 graph.add_node( ··· 212 if let Some(renderer) = &diff_renderer { 213 let predecessors: Vec<_> = entry.predecessors().try_collect()?; 214 let width = ui.term_width(); 215 renderer.show_inter_diff( 216 ui, 217 formatter,
+4 -10
cli/src/commands/git/fetch.rs
··· 189 branches: &[StringPattern], 190 remotes: &[&RemoteName], 191 ) -> Result<(), CommandError> { 192 - let mut missing_branches = vec![]; 193 for branch in branches { 194 let matches = remotes.iter().any(|&remote| { 195 let remote = StringPattern::exact(remote); ··· 206 .is_some() 207 }); 208 if !matches { 209 - missing_branches.push(branch); 210 } 211 - } 212 - 213 - if !missing_branches.is_empty() { 214 - writeln!( 215 - ui.warning_default(), 216 - "No branch matching {} found on any specified/configured remote", 217 - missing_branches.iter().map(|b| format!("`{b}`")).join(", ") 218 - )?; 219 } 220 221 Ok(())
··· 189 branches: &[StringPattern], 190 remotes: &[&RemoteName], 191 ) -> Result<(), CommandError> { 192 for branch in branches { 193 let matches = remotes.iter().any(|&remote| { 194 let remote = StringPattern::exact(remote); ··· 205 .is_some() 206 }); 207 if !matches { 208 + writeln!( 209 + ui.warning_default(), 210 + "No branch matching `{branch}` found on any specified/configured remote", 211 + )?; 212 } 213 } 214 215 Ok(())
+1 -7
cli/src/commands/resolve.rs
··· 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 - use clap_complete::ArgValueCandidates; 16 use clap_complete::ArgValueCompleter; 17 use itertools::Itertools as _; 18 use jj_lib::object_id::ObjectId as _; ··· 62 /// 63 /// The built-in merge tools `:ours` and `:theirs` can be used to choose 64 /// side #1 and side #2 of the conflict respectively. 65 - #[arg( 66 - long, 67 - conflicts_with = "list", 68 - value_name = "NAME", 69 - add = ArgValueCandidates::new(complete::merge_tools), 70 - )] 71 tool: Option<String>, 72 /// Only resolve conflicts in these paths. You can use the `--list` argument 73 /// to find paths to use here.
··· 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 use clap_complete::ArgValueCompleter; 16 use itertools::Itertools as _; 17 use jj_lib::object_id::ObjectId as _; ··· 61 /// 62 /// The built-in merge tools `:ours` and `:theirs` can be used to choose 63 /// side #1 and side #2 of the conflict respectively. 64 + #[arg(long, conflicts_with = "list", value_name = "NAME")] 65 tool: Option<String>, 66 /// Only resolve conflicts in these paths. You can use the `--list` argument 67 /// to find paths to use here.
+1 -6
cli/src/commands/restore.rs
··· 14 15 use std::io::Write as _; 16 17 - use clap_complete::ArgValueCandidates; 18 use clap_complete::ArgValueCompleter; 19 use indoc::formatdoc; 20 use itertools::Itertools as _; ··· 97 #[arg(long, short)] 98 interactive: bool, 99 /// Specify diff editor to be used (implies --interactive) 100 - #[arg( 101 - long, 102 - value_name = "NAME", 103 - add = ArgValueCandidates::new(complete::diff_editors), 104 - )] 105 tool: Option<String>, 106 /// Preserve the content (not the diff) when rebasing descendants 107 #[arg(long)]
··· 14 15 use std::io::Write as _; 16 17 use clap_complete::ArgValueCompleter; 18 use indoc::formatdoc; 19 use itertools::Itertools as _; ··· 96 #[arg(long, short)] 97 interactive: bool, 98 /// Specify diff editor to be used (implies --interactive) 99 + #[arg(long, value_name = "NAME")] 100 tool: Option<String>, 101 /// Preserve the content (not the diff) when rebasing descendants 102 #[arg(long)]
+11 -15
cli/src/commands/split.rs
··· 14 use std::collections::HashMap; 15 use std::io::Write as _; 16 17 - use clap_complete::ArgValueCandidates; 18 use clap_complete::ArgValueCompleter; 19 use jj_lib::backend::CommitId; 20 use jj_lib::commit::Commit; ··· 50 /// 51 /// Starts a [diff editor] on the changes in the revision. Edit the right side 52 /// of the diff until it has the content you want in the new revision. Once 53 - /// you close the editor, your edited content will be put in a new revision 54 - /// before the original revision, while the remaining changes will replace the 55 - /// original revision. 56 /// 57 /// [diff editor]: 58 /// https://jj-vcs.github.io/jj/latest/config/#editing-diffs ··· 72 #[arg(long, short)] 73 interactive: bool, 74 /// Specify diff editor to be used (implies --interactive) 75 - #[arg( 76 - long, 77 - value_name = "NAME", 78 - add = ArgValueCandidates::new(complete::diff_editors), 79 - )] 80 tool: Option<String>, 81 /// The revision to split 82 #[arg( ··· 225 // Prompt the user to select the changes they want for the first commit. 226 let target = select_diff(ui, &tx, &target_commit, &matcher, &diff_selector)?; 227 228 - let legacy_bookmark_behavior = 229 - !use_move_flags && tx.settings().get_bool("split.legacy-bookmark-behavior")?; 230 - 231 // Create the first commit, which includes the changes selected by the user. 232 let first_commit = { 233 let mut commit_builder = tx.repo_mut().rewrite_commit(&target.commit).detach(); 234 commit_builder.set_tree_id(target.selected_tree.id()); 235 - if !legacy_bookmark_behavior { 236 commit_builder 237 // Generate a new change id so that the commit being split doesn't 238 // become divergent. ··· 279 commit_builder 280 .set_parents(parents) 281 .set_tree_id(new_tree.id()); 282 - if legacy_bookmark_behavior { 283 commit_builder 284 // Generate a new change id so that the commit being split doesn't 285 // become divergent. ··· 416 tx.repo_mut() 417 .transform_descendants(vec![target.commit.id().clone()], |mut rewriter| { 418 num_rebased += 1; 419 - if parallel { 420 rewriter 421 .replace_parent(second_commit.id(), [first_commit.id(), second_commit.id()]); 422 } 423 rewriter.rebase()?.write()?; 424 Ok(())
··· 14 use std::collections::HashMap; 15 use std::io::Write as _; 16 17 use clap_complete::ArgValueCompleter; 18 use jj_lib::backend::CommitId; 19 use jj_lib::commit::Commit; ··· 49 /// 50 /// Starts a [diff editor] on the changes in the revision. Edit the right side 51 /// of the diff until it has the content you want in the new revision. Once 52 + /// you close the editor, your edited content will replace the previous 53 + /// revision. The remaining changes will be put in a new revision on top. 54 /// 55 /// [diff editor]: 56 /// https://jj-vcs.github.io/jj/latest/config/#editing-diffs ··· 70 #[arg(long, short)] 71 interactive: bool, 72 /// Specify diff editor to be used (implies --interactive) 73 + #[arg(long, value_name = "NAME")] 74 tool: Option<String>, 75 /// The revision to split 76 #[arg( ··· 219 // Prompt the user to select the changes they want for the first commit. 220 let target = select_diff(ui, &tx, &target_commit, &matcher, &diff_selector)?; 221 222 // Create the first commit, which includes the changes selected by the user. 223 let first_commit = { 224 let mut commit_builder = tx.repo_mut().rewrite_commit(&target.commit).detach(); 225 commit_builder.set_tree_id(target.selected_tree.id()); 226 + if use_move_flags { 227 commit_builder 228 // Generate a new change id so that the commit being split doesn't 229 // become divergent. ··· 270 commit_builder 271 .set_parents(parents) 272 .set_tree_id(new_tree.id()); 273 + if !use_move_flags { 274 commit_builder 275 // Generate a new change id so that the commit being split doesn't 276 // become divergent. ··· 407 tx.repo_mut() 408 .transform_descendants(vec![target.commit.id().clone()], |mut rewriter| { 409 num_rebased += 1; 410 + if parallel && legacy_bookmark_behavior { 411 + // The old_parent is the second commit due to the rewrite above. 412 rewriter 413 .replace_parent(second_commit.id(), [first_commit.id(), second_commit.id()]); 414 + } else if parallel { 415 + rewriter.replace_parent(first_commit.id(), [first_commit.id(), second_commit.id()]); 416 + } else { 417 + rewriter.replace_parent(first_commit.id(), [second_commit.id()]); 418 } 419 rewriter.rebase()?.write()?; 420 Ok(())
+3 -162
cli/src/commands/squash.rs
··· 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 - use clap_complete::ArgValueCandidates; 16 use clap_complete::ArgValueCompleter; 17 use indoc::formatdoc; 18 use itertools::Itertools as _; ··· 23 use jj_lib::repo::Repo as _; 24 use jj_lib::rewrite; 25 use jj_lib::rewrite::CommitWithSelection; 26 - use jj_lib::rewrite::SquashOptions; 27 use tracing::instrument; 28 29 use crate::cli_util::CommandHelper; ··· 101 #[arg(long, short)] 102 interactive: bool, 103 /// Specify diff editor to be used (implies --interactive) 104 - #[arg( 105 - long, 106 - value_name = "NAME", 107 - add = ArgValueCandidates::new(complete::diff_editors), 108 - )] 109 tool: Option<String>, 110 /// Move only changes to these paths (instead of all paths) 111 #[arg( ··· 118 /// The source revision will not be abandoned 119 #[arg(long, short)] 120 keep_emptied: bool, 121 - /// Preserve the content (not the diff) when rebasing descendants of the 122 - /// source and target commits 123 - /// 124 - /// Only the snapshots of the `--from` and the `--into` commits will be 125 - /// modified. 126 - /// 127 - /// If you'd like to preserve the content of *only* the target's descendants 128 - /// (or *only* the source's), consider using `jj rebase -r` or `jj 129 - /// duplicate` before squashing. 130 - // 131 - // See "NOTE: Not implementing `--restore-{target,source}-descendants`" in 132 - // squash.rs. 133 - // 134 - // TODO: Once it's implemented, we should recommend `jj rebase -r 135 - // --restore-descendants` instead of `jj duplicate`, since you actually 136 - // would need to `squash` twice with `duplicate`. 137 - #[arg(long)] 138 - restore_descendants: bool, 139 } 140 141 - // NOTE: Not implementing `--restore-{target,source}-descendants` 142 - // -------------------------------------------------------------- 143 - // 144 - // We have `jj squash --restore-descendants --from X --into Y` preserve the 145 - // snapshots of both the descendants of `X` and those of the descendants of `Y`. 146 - // This behavior makes it simple to understand; it does the same thing to the 147 - // child of any commit `jj squash` rewrites. As @yuja pointed out it could even 148 - // be a global flag that would apply to any command that rewrites commits. 149 - // 150 - // In this note, we explain why we choose not to have a flag for `jj squash` 151 - // that preserves *only* the descendants of the source (call it 152 - // `--restore-source-descendants`) or a similar `--restore-target-descendants` 153 - // flag, even though they might seem easy to implement at a glance. 154 - // 155 - // (The same argument applies to `jj rebase --restore-???-descendants`.) 156 - // 157 - // Firstly, such extra flags seem to only be useful in rare cases. If needed, 158 - // they can be simulated. Instead of `squash --restore-target-descendants`, you 159 - // could do `jj rebase -r X -d all:X-; jj squash --restore-descendants --from X 160 - // --into Y`. Instead of `squash --restore-source-descendants`, you could do `jj 161 - // duplicate -r X; jj squash --restore-descendants --from copy_of_X --into Y; jj 162 - // abandon --restore-descendants X`. (TODO: When `jj rebase -r 163 - // --restore-descendants` is implemented, this will become 2 commands instead of 164 - // 3). 165 - // 166 - // Secondly, the behavior of these flags would get confusing in corner cases, 167 - // when the target is an ancestor or descendant of the source, or for ancestors 168 - // of merge commits. For example, consider this commit graph with merge commit 169 - // `Z` where `A` is *not* empty (thanks to @lilyball for suggesting the merge 170 - // commit example): 171 - // 172 - // ``` 173 - // A -> X - 174 - // \ (Example I) 175 - // B -> Y --->Z 176 - // ``` 177 - // 178 - // The behavior of `jj squash --from A --into B --restore-descendants` is easy 179 - // to understand: the snapshots of `X` and `Y` remain the same, and all of their 180 - // descendants also remain the same by normal rebasing rules. 181 - // 182 - // If we allowed `jj squash --from A --into B --restore-target-descendants`, 183 - // what should it mean? It seems clear that `X`'s snapshot should remain the 184 - // same, and `X`'s will change. However, should `Z`'s snapshot change? If we 185 - // follow the logic that Z had one of its parents change and the other stay the 186 - // same, it seems that yes, it should. This is also what the equivalence with 187 - // `jj rebase -r A -d A-; jj squash --from A --into B --restore-descendants` 188 - // would imply. 189 - // 190 - // (A contrarian mind could argue that `Z`'s snapshot should be preserved since 191 - // `Z` is a descendant of the target `B`. We'll put this thought aside for a 192 - // moment and keep going, to see how things get even more confusing.) 193 - // 194 - // Now, let's pretend we squashed `X` and `Y` into `Z` and ask the same 195 - // question. Our graph is now: 196 - // 197 - // ``` 198 - // A - 199 - // \ (Example II) 200 - // B --->Z 201 - // ``` 202 - // 203 - // By the logic above, the snapshot of `Z` will again change after `jj squash 204 - // --from A --into B --restore-target-descendants`. This is unsatisfying and 205 - // would probably be unexpected, since `Z` is a direct child of the target 206 - // commit `B`, so the user might expect its snapshot to be preserved. 207 - // 208 - // Now, there are a few options: 209 - // 210 - // 1. Allow the confusing but seemingly correct definition of 211 - // `--restore-target-descendants` as above. 212 - // 2. Allow `--restore-target-descendants`, but forbid it in some set of 213 - // situations we deem too confusing. 214 - // 3. Have the effect of `jj squash --from A --into B 215 - // --restore-target-descendants` on `Z`'s snapshot differ between Example I 216 - // and Example II. In other words, the behavior will depend on whether there 217 - // are commits (even if they are empty commits!) between `A` and `Z`, or 218 - // between `B` and `Z`. 219 - // 4. Declare that in both Example I and Example II above, the snapshot of `Z` 220 - // should be preserved. 221 - // 222 - // The first problem with this (and with option 3 above) would be that 223 - // `--restore-target-descendants` would now be equivalent to a rebase 224 - // followed by `squash --restore-descendants` *almost* always, but would 225 - // differ in corner cases. 226 - // 227 - // Perhaps more importantly, this would break the important property of `jj 228 - // squash --restore-target-descendants` that its difference from the 229 - // behavior of normal `jj squash` is local; affects only the direct children 230 - // of the modified commits. All others can normally be rebased by normal 231 - // `jj` rules. 232 - // 233 - // If `jj squash --restore-target-descendants` preserved the snapshot of `Z` 234 - // even if there are 100 commit between it and `A`, this would change its 235 - // diff relative to its parents, possibly without any awareness from the 236 - // user that this happened or that `Z` even existed. 237 - // 5. Do not provide `--restore-target-descendants` ourselves, and recommend 238 - // that the user manually does `jj rebase -r X -d all:X-; jj squash 239 - // --restore-descendants --from X --into Y` if they really need it. 240 - // 241 - // The last option seems easiest. It also has the advantage of requiring fewer 242 - // tests and being the simplest to maintain. 243 - // 244 - // Aside: the merge example is probably the easiest to understand and the most 245 - // problematic, but for `X -> A -> B -> C -> D`, both `jj squash --from C 246 - // --into A --restore-target-descendants` and `jj squash --from A --into C 247 - // --restore-source-descendants` have similar problems. 248 - 249 #[instrument(skip_all)] 250 pub(crate) fn cmd_squash( 251 ui: &mut Ui, ··· 298 .check_rewritable(sources.iter().chain(std::iter::once(&destination)).ids())?; 299 300 let mut tx = workspace_command.start_transaction(); 301 - let tx_description = format!( 302 - "squash commits into {}{}", 303 - destination.id().hex(), 304 - if args.restore_descendants { 305 - " while preserving descendant contents" 306 - } else { 307 - "" 308 - } 309 - ); 310 let source_commits = select_diff(&tx, &sources, &destination, &matcher, &diff_selector)?; 311 if let Some(squashed) = rewrite::squash_commits( 312 tx.repo_mut(), 313 &source_commits, 314 &destination, 315 - SquashOptions { 316 - keep_emptied: args.keep_emptied, 317 - // See "NOTE: Not implementing `--restore-{target,source}-descendants`" in 318 - // squash.rs. 319 - restore_descendants: args.restore_descendants, 320 - }, 321 )? { 322 let mut commit_builder = squashed.commit_builder.detach(); 323 let new_description = match description { ··· 365 }; 366 commit_builder.set_description(new_description); 367 commit_builder.write(tx.repo_mut())?; 368 - 369 - if args.restore_descendants { 370 - // If !args.restore_descendants, the corresponding steps are done inside 371 - // tx.finish() 372 - let num_reparented = tx.repo_mut().reparent_descendants()?; 373 - if let Some(mut formatter) = ui.status_formatter() { 374 - writeln!( 375 - formatter, 376 - "Rebased {num_reparented} descendant commits (while preserving their content)", 377 - )?; 378 - } 379 - } 380 } else { 381 if diff_selector.is_interactive() { 382 return Err(user_error("No changes selected")); ··· 398 } 399 } 400 } 401 - // TODO: Show the "Rebase NNN descendant commits message", add " (while 402 - // preserving their content)" in the --restore-descendants mode 403 tx.finish(ui, tx_description)?; 404 Ok(()) 405 }
··· 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 use clap_complete::ArgValueCompleter; 16 use indoc::formatdoc; 17 use itertools::Itertools as _; ··· 22 use jj_lib::repo::Repo as _; 23 use jj_lib::rewrite; 24 use jj_lib::rewrite::CommitWithSelection; 25 use tracing::instrument; 26 27 use crate::cli_util::CommandHelper; ··· 99 #[arg(long, short)] 100 interactive: bool, 101 /// Specify diff editor to be used (implies --interactive) 102 + #[arg(long, value_name = "NAME")] 103 tool: Option<String>, 104 /// Move only changes to these paths (instead of all paths) 105 #[arg( ··· 112 /// The source revision will not be abandoned 113 #[arg(long, short)] 114 keep_emptied: bool, 115 } 116 117 #[instrument(skip_all)] 118 pub(crate) fn cmd_squash( 119 ui: &mut Ui, ··· 166 .check_rewritable(sources.iter().chain(std::iter::once(&destination)).ids())?; 167 168 let mut tx = workspace_command.start_transaction(); 169 + let tx_description = format!("squash commits into {}", destination.id().hex()); 170 let source_commits = select_diff(&tx, &sources, &destination, &matcher, &diff_selector)?; 171 if let Some(squashed) = rewrite::squash_commits( 172 tx.repo_mut(), 173 &source_commits, 174 &destination, 175 + args.keep_emptied, 176 )? { 177 let mut commit_builder = squashed.commit_builder.detach(); 178 let new_description = match description { ··· 220 }; 221 commit_builder.set_description(new_description); 222 commit_builder.write(tx.repo_mut())?; 223 } else { 224 if diff_selector.is_interactive() { 225 return Err(user_error("No changes selected")); ··· 241 } 242 } 243 } 244 tx.finish(ui, tx_description)?; 245 Ok(()) 246 }
+1 -49
cli/src/complete.rs
··· 34 use crate::config::ConfigArgKind; 35 use crate::config::ConfigEnv; 36 use crate::config::CONFIG_SCHEMA; 37 - use crate::merge_tools::configured_merge_tools; 38 - use crate::merge_tools::MergeEditor; 39 use crate::revset_util::load_revset_aliases; 40 use crate::ui::Ui; 41 ··· 476 .collect()) 477 }) 478 } 479 - pub fn merge_tools() -> Vec<CompletionCandidate> { 480 - with_jj(|_, settings| { 481 - Ok([":builtin", ":ours", ":theirs"] 482 - .into_iter() 483 - .chain( 484 - configured_merge_tools(settings) 485 - .filter(|name| MergeEditor::dummy_with_name(name, settings).is_ok()), 486 - ) 487 - .map(CompletionCandidate::new) 488 - .collect()) 489 - }) 490 - } 491 - 492 - /// Approximate list of known diff editors 493 - /// 494 - /// Diff tools can be used without configuration. Some merge tools that are 495 - /// configured for 3-way merging may not work for diffing/diff editing, and we 496 - /// can't tell which these are. So, this not reliable, but probably good enough 497 - /// for command-line completion. 498 - pub fn diff_editors() -> Vec<CompletionCandidate> { 499 - let builtin_format_kinds: Vec<String> = crate::diff_util::BuiltinFormatKind::ALL_VARIANTS 500 - .iter() 501 - .map(|kind| format!(":{}", kind.to_arg_name())) 502 - .collect(); 503 - with_jj(|_, settings| { 504 - Ok(std::iter::once(":builtin") 505 - .chain(builtin_format_kinds.iter().map(|s| s.as_str())) 506 - .chain(configured_merge_tools(settings)) 507 - .map(CompletionCandidate::new) 508 - .collect()) 509 - }) 510 - } 511 - 512 - /// Approximate list of known diff tools 513 - /// 514 - /// Diff tools can be used without configuration. Some merge tools that are 515 - /// configured for 3-way merging may not work for diffing/diff editing, and we 516 - /// can't tell which these are. So, this not reliable, but probably good enough 517 - /// for command-line completion. 518 - pub fn diff_tools() -> Vec<CompletionCandidate> { 519 - with_jj(|_, settings| { 520 - Ok(configured_merge_tools(settings) 521 - .map(CompletionCandidate::new) 522 - .collect()) 523 - }) 524 - } 525 526 fn config_keys_rec( 527 prefix: ConfigNamePathBuf, ··· 943 .and_then(dunce::canonicalize) 944 .map_err(user_error)?; 945 // No config migration for completion. Simply ignore deprecated variables. 946 - let mut config_env = ConfigEnv::from_environment(); 947 let maybe_cwd_workspace_loader = DefaultWorkspaceLoaderFactory.create(find_workspace_dir(&cwd)); 948 let _ = config_env.reload_user_config(&mut raw_config); 949 if let Ok(loader) = &maybe_cwd_workspace_loader {
··· 34 use crate::config::ConfigArgKind; 35 use crate::config::ConfigEnv; 36 use crate::config::CONFIG_SCHEMA; 37 use crate::revset_util::load_revset_aliases; 38 use crate::ui::Ui; 39 ··· 474 .collect()) 475 }) 476 } 477 478 fn config_keys_rec( 479 prefix: ConfigNamePathBuf, ··· 895 .and_then(dunce::canonicalize) 896 .map_err(user_error)?; 897 // No config migration for completion. Simply ignore deprecated variables. 898 + let mut config_env = ConfigEnv::from_environment(&ui); 899 let maybe_cwd_workspace_loader = DefaultWorkspaceLoaderFactory.create(find_workspace_dir(&cwd)); 900 let _ = config_env.reload_user_config(&mut raw_config); 901 if let Ok(loader) = &maybe_cwd_workspace_loader {
+1 -1
cli/src/config/misc.toml
··· 54 # The behavior when this flag is set to false is experimental and may be changed 55 # in the future. 56 [split] 57 - legacy-bookmark-behavior = false
··· 54 # The behavior when this flag is set to false is experimental and may be changed 55 # in the future. 56 [split] 57 + legacy-bookmark-behavior = true
+34 -5
cli/src/config.rs
··· 43 use crate::command_error::config_error_with_message; 44 use crate::command_error::CommandError; 45 use crate::text_util; 46 47 // TODO(#879): Consider generating entire schema dynamically vs. static file. 48 pub const CONFIG_SCHEMA: &str = include_str!("config-schema.json"); ··· 192 fn as_path(&self) -> &Path { 193 &self.path 194 } 195 - 196 fn exists(&self) -> bool { 197 match self.state { 198 ConfigPathState::Exists => true, ··· 218 #[derive(Clone, Default, Debug)] 219 struct UnresolvedConfigEnv { 220 config_dir: Option<PathBuf>, 221 macos_legacy_config_dir: Option<PathBuf>, 222 home_dir: Option<PathBuf>, 223 jj_config: Option<String>, 224 } 225 226 impl UnresolvedConfigEnv { 227 - fn resolve(self) -> Vec<ConfigPath> { 228 if let Some(paths) = self.jj_config { 229 return split_paths(&paths) 230 .filter(|path| !path.as_os_str().is_empty()) ··· 283 284 if let Some(path) = legacy_platform_config_path { 285 if path.exists() { 286 paths.push(path); 287 } 288 } 289 if let Some(path) = legacy_platform_config_dir { 290 if path.exists() { 291 paths.push(path); 292 } 293 } 294 295 paths 296 } 297 } 298 299 #[derive(Clone, Debug)] ··· 307 308 impl ConfigEnv { 309 /// Initializes configuration loader based on environment variables. 310 - pub fn from_environment() -> Self { 311 let config_dir = etcetera::choose_base_strategy() 312 .ok() 313 .map(|s| s.config_dir()); ··· 322 // Library/Preferences is supposed to be exclusively plists 323 s.data_dir() 324 }) 325 } else { 326 None 327 }; ··· 341 ConfigEnv { 342 home_dir, 343 repo_path: None, 344 - user_config_paths: env.resolve(), 345 repo_config_path: None, 346 command: None, 347 } ··· 1728 ConfigEnv { 1729 home_dir, 1730 repo_path: None, 1731 - user_config_paths: env.resolve(), 1732 repo_config_path: None, 1733 command: None, 1734 }
··· 43 use crate::command_error::config_error_with_message; 44 use crate::command_error::CommandError; 45 use crate::text_util; 46 + use crate::ui::Ui; 47 48 // TODO(#879): Consider generating entire schema dynamically vs. static file. 49 pub const CONFIG_SCHEMA: &str = include_str!("config-schema.json"); ··· 193 fn as_path(&self) -> &Path { 194 &self.path 195 } 196 fn exists(&self) -> bool { 197 match self.state { 198 ConfigPathState::Exists => true, ··· 218 #[derive(Clone, Default, Debug)] 219 struct UnresolvedConfigEnv { 220 config_dir: Option<PathBuf>, 221 + // TODO: remove after jj 0.35 222 macos_legacy_config_dir: Option<PathBuf>, 223 home_dir: Option<PathBuf>, 224 jj_config: Option<String>, 225 } 226 227 impl UnresolvedConfigEnv { 228 + fn resolve(self, ui: &Ui) -> Vec<ConfigPath> { 229 if let Some(paths) = self.jj_config { 230 return split_paths(&paths) 231 .filter(|path| !path.as_os_str().is_empty()) ··· 284 285 if let Some(path) = legacy_platform_config_path { 286 if path.exists() { 287 + Self::warn_for_deprecated_path( 288 + ui, 289 + path.as_path(), 290 + "~/Library/Application Support/jj", 291 + "~/.config/jj", 292 + ); 293 paths.push(path); 294 } 295 } 296 if let Some(path) = legacy_platform_config_dir { 297 if path.exists() { 298 + Self::warn_for_deprecated_path( 299 + ui, 300 + path.as_path(), 301 + "~/Library/Application Support/jj", 302 + "~/.config/jj", 303 + ); 304 paths.push(path); 305 } 306 } 307 308 paths 309 } 310 + 311 + fn warn_for_deprecated_path(ui: &Ui, path: &Path, old: &str, new: &str) { 312 + let _ = indoc::writedoc!( 313 + ui.warning_default(), 314 + r" 315 + Deprecated configuration file `{}`. 316 + Configuration files in `{old}` are deprecated, and support will be removed in a future release. 317 + Instead, move your configuration files to `{new}`. 318 + ", 319 + path.display(), 320 + ); 321 + } 322 } 323 324 #[derive(Clone, Debug)] ··· 332 333 impl ConfigEnv { 334 /// Initializes configuration loader based on environment variables. 335 + pub fn from_environment(ui: &Ui) -> Self { 336 let config_dir = etcetera::choose_base_strategy() 337 .ok() 338 .map(|s| s.config_dir()); ··· 347 // Library/Preferences is supposed to be exclusively plists 348 s.data_dir() 349 }) 350 + .filter(|data_dir| { 351 + // User might've purposefully set their config dir to the deprecated one 352 + Some(data_dir) != config_dir.as_ref() 353 + }) 354 } else { 355 None 356 }; ··· 370 ConfigEnv { 371 home_dir, 372 repo_path: None, 373 + user_config_paths: env.resolve(ui), 374 repo_config_path: None, 375 command: None, 376 } ··· 1757 ConfigEnv { 1758 home_dir, 1759 repo_path: None, 1760 + user_config_paths: env.resolve(&Ui::null()), 1761 repo_config_path: None, 1762 command: None, 1763 }
+3 -19
cli/src/diff_util.rs
··· 23 24 use bstr::BStr; 25 use bstr::BString; 26 - use clap_complete::ArgValueCandidates; 27 use futures::executor::block_on_stream; 28 use futures::stream::BoxStream; 29 use futures::StreamExt as _; ··· 125 /// 126 /// A builtin format can also be specified as `:<name>`. For example, 127 /// `--tool=:git` is equivalent to `--git`. 128 - #[arg( 129 - long, 130 - add = ArgValueCandidates::new(crate::complete::diff_tools), 131 - )] 132 pub tool: Option<String>, 133 /// Number of lines of context to show 134 #[arg(long)] ··· 156 } 157 158 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 159 - pub enum BuiltinFormatKind { 160 Summary, 161 Stat, 162 Types, ··· 166 } 167 168 impl BuiltinFormatKind { 169 - // Alternatively, we could use or vendor one of the crates `strum`, 170 - // `enum-iterator`, or `variant_count` (for a check that the length of the array 171 - // is correct). The latter is very simple and is also a nightly feature. 172 - pub const ALL_VARIANTS: &[BuiltinFormatKind] = &[ 173 - Self::Summary, 174 - Self::Stat, 175 - Self::Types, 176 - Self::NameOnly, 177 - Self::Git, 178 - Self::ColorWords, 179 - ]; 180 - 181 fn from_name(name: &str) -> Result<Self, String> { 182 match name { 183 "summary" => Ok(Self::Summary), ··· 221 } 222 } 223 224 - pub fn to_arg_name(self) -> &'static str { 225 match self { 226 Self::Summary => "summary", 227 Self::Stat => "stat",
··· 23 24 use bstr::BStr; 25 use bstr::BString; 26 use futures::executor::block_on_stream; 27 use futures::stream::BoxStream; 28 use futures::StreamExt as _; ··· 124 /// 125 /// A builtin format can also be specified as `:<name>`. For example, 126 /// `--tool=:git` is equivalent to `--git`. 127 + #[arg(long)] 128 pub tool: Option<String>, 129 /// Number of lines of context to show 130 #[arg(long)] ··· 152 } 153 154 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 155 + enum BuiltinFormatKind { 156 Summary, 157 Stat, 158 Types, ··· 162 } 163 164 impl BuiltinFormatKind { 165 fn from_name(name: &str) -> Result<Self, String> { 166 match name { 167 "summary" => Ok(Self::Summary), ··· 205 } 206 } 207 208 + fn to_arg_name(self) -> &'static str { 209 match self { 210 Self::Summary => "summary", 211 Self::Stat => "stat",
+6 -22
cli/src/merge_tools/mod.rs
··· 207 } 208 } 209 210 - /// List configured merge tools (diff editors, diff tools, merge editors) 211 - pub fn configured_merge_tools(settings: &UserSettings) -> impl Iterator<Item = &str> { 212 - settings.table_keys("merge-tools") 213 - } 214 - 215 /// Loads external diff/merge tool options from `[merge-tools.<name>]`. 216 pub fn get_external_tool_config( 217 settings: &UserSettings, ··· 383 let tool = MergeTool::get_tool_config(settings, name)? 384 .unwrap_or_else(|| MergeTool::external(ExternalMergeTool::with_program(name))); 385 Self::new_inner(name, tool, path_converter, conflict_marker_style) 386 - } 387 - 388 - /// For the purposes of testing or checking basic config 389 - pub fn dummy_with_name( 390 - name: &str, 391 - settings: &UserSettings, 392 - ) -> Result<Self, MergeToolConfigError> { 393 - Self::with_name( 394 - name, 395 - settings, 396 - RepoPathUiConverter::Fs { 397 - cwd: "".into(), 398 - base: "".into(), 399 - }, 400 - ConflictMarkerStyle::Diff, 401 - ) 402 } 403 404 /// Loads the default 3-way merge editor from the settings. ··· 786 let get = |name, config_text| { 787 let config = config_from_string(config_text); 788 let settings = UserSettings::from_config(config).unwrap(); 789 - MergeEditor::dummy_with_name(name, &settings).map(|editor| editor.tool) 790 }; 791 792 insta::assert_debug_snapshot!(get(":builtin", "").unwrap(), @"Builtin");
··· 207 } 208 } 209 210 /// Loads external diff/merge tool options from `[merge-tools.<name>]`. 211 pub fn get_external_tool_config( 212 settings: &UserSettings, ··· 378 let tool = MergeTool::get_tool_config(settings, name)? 379 .unwrap_or_else(|| MergeTool::external(ExternalMergeTool::with_program(name))); 380 Self::new_inner(name, tool, path_converter, conflict_marker_style) 381 } 382 383 /// Loads the default 3-way merge editor from the settings. ··· 765 let get = |name, config_text| { 766 let config = config_from_string(config_text); 767 let settings = UserSettings::from_config(config).unwrap(); 768 + let path_converter = RepoPathUiConverter::Fs { 769 + cwd: "".into(), 770 + base: "".into(), 771 + }; 772 + MergeEditor::with_name(name, &settings, path_converter, ConflictMarkerStyle::Diff) 773 + .map(|editor| editor.tool) 774 }; 775 776 insta::assert_debug_snapshot!(get(":builtin", "").unwrap(), @"Builtin");
+1 -15
cli/tests/cli-reference@.md.snap
··· 906 * `-p`, `--patch` โ€” Show patch compared to the previous version of this change 907 908 If the previous version has different parents, it will be temporarily rebased to the parents of the new version, so the diff is not contaminated by unrelated changes. 909 - * `--diff-snapshots` โ€” Changes the behavior of `--patch`, `--git`, etc to show diffs from rebases 910 - 911 - Implies `--patch` if no other diff format is requested. 912 - 913 - Normally, `jj evolog -p` shows a so-called "interdiff", temporarily rebasing the versions of a revision to the same parents, in order to omit differences in the file contents that are caused by rebases. 914 - 915 - This option disables this behavior, and shows diffs between the contents of the different versions without modification (as snapshots). 916 - 917 - Sometimes, `--diff-snapshots` can show fewer differences to be shown. For example, let's say the current revision is not empty and we perform `jj squash --keep-empty -r @` to make it empty. Then, `jj evolog -p --diff-snapshots` will not show any changes since the contents of the files in the current revision did not change. However, `jj evolog -p` will show a change, representing the fact that a non-empty revision became empty. 918 * `-s`, `--summary` โ€” For each path, show only whether it was modified, added, or deleted 919 * `--stat` โ€” Show a histogram of the changes 920 * `--types` โ€” For each path, show only its type before and after ··· 2470 2471 Split a revision in two 2472 2473 - Starts a [diff editor] on the changes in the revision. Edit the right side of the diff until it has the content you want in the new revision. Once you close the editor, your edited content will be put in a new revision before the original revision, while the remaining changes will replace the original revision. 2474 2475 [diff editor]: https://jj-vcs.github.io/jj/latest/config/#editing-diffs 2476 ··· 2533 * `-i`, `--interactive` โ€” Interactively choose which parts to squash 2534 * `--tool <NAME>` โ€” Specify diff editor to be used (implies --interactive) 2535 * `-k`, `--keep-emptied` โ€” The source revision will not be abandoned 2536 - * `--restore-descendants` โ€” Preserve the content (not the diff) when rebasing descendants of the source and target commits 2537 - 2538 - Only the snapshots of the `--from` and the `--into` commits will be modified. 2539 - 2540 - If you'd like to preserve the content of *only* the target's descendants (or *only* the source's), consider using `jj rebase -r` or `jj duplicate` before squashing. 2541 2542 2543
··· 906 * `-p`, `--patch` โ€” Show patch compared to the previous version of this change 907 908 If the previous version has different parents, it will be temporarily rebased to the parents of the new version, so the diff is not contaminated by unrelated changes. 909 * `-s`, `--summary` โ€” For each path, show only whether it was modified, added, or deleted 910 * `--stat` โ€” Show a histogram of the changes 911 * `--types` โ€” For each path, show only its type before and after ··· 2461 2462 Split a revision in two 2463 2464 + Starts a [diff editor] on the changes in the revision. Edit the right side of the diff until it has the content you want in the new revision. Once you close the editor, your edited content will replace the previous revision. The remaining changes will be put in a new revision on top. 2465 2466 [diff editor]: https://jj-vcs.github.io/jj/latest/config/#editing-diffs 2467 ··· 2524 * `-i`, `--interactive` โ€” Interactively choose which parts to squash 2525 * `--tool <NAME>` โ€” Specify diff editor to be used (implies --interactive) 2526 * `-k`, `--keep-emptied` โ€” The source revision will not be abandoned 2527 2528 2529
+1 -1
cli/tests/test_commit_command.rs
··· 298 โ—† 000000000000 299 [EOF] 300 ------- stderr ------- 301 - Warning: Deprecated config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 302 [EOF] 303 "#); 304 insta::assert_snapshot!(
··· 298 โ—† 000000000000 299 [EOF] 300 ------- stderr ------- 301 + Warning: Deprecated user-level config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 302 [EOF] 303 "#); 304 insta::assert_snapshot!(
-61
cli/tests/test_completion.rs
··· 1089 "); 1090 } 1091 1092 - #[test] 1093 - fn test_merge_tools() { 1094 - let mut test_env = TestEnvironment::default(); 1095 - test_env.add_env_var("COMPLETE", "fish"); 1096 - let dir = test_env.env_root(); 1097 - 1098 - let output = test_env.run_jj_in(dir, ["--", "jj", "diff", "--tool", ""]); 1099 - insta::assert_snapshot!(output, @r" 1100 - diffedit3 1101 - diffedit3-ssh 1102 - difft 1103 - kdiff3 1104 - meld 1105 - meld-3 1106 - mergiraf 1107 - smerge 1108 - vimdiff 1109 - vscode 1110 - vscodium 1111 - [EOF] 1112 - "); 1113 - // Includes :builtin 1114 - let output = test_env.run_jj_in(dir, ["--", "jj", "diffedit", "--tool", ""]); 1115 - insta::assert_snapshot!(output, @r" 1116 - :builtin 1117 - :summary 1118 - :stat 1119 - :types 1120 - :name-only 1121 - :git 1122 - :color-words 1123 - diffedit3 1124 - diffedit3-ssh 1125 - difft 1126 - kdiff3 1127 - meld 1128 - meld-3 1129 - mergiraf 1130 - smerge 1131 - vimdiff 1132 - vscode 1133 - vscodium 1134 - [EOF] 1135 - "); 1136 - // Only includes configured merge editors 1137 - let output = test_env.run_jj_in(dir, ["--", "jj", "resolve", "--tool", ""]); 1138 - insta::assert_snapshot!(output, @r" 1139 - :builtin 1140 - :ours 1141 - :theirs 1142 - kdiff3 1143 - meld 1144 - mergiraf 1145 - smerge 1146 - vimdiff 1147 - vscode 1148 - vscodium 1149 - [EOF] 1150 - "); 1151 - } 1152 - 1153 fn create_commit( 1154 work_dir: &TestWorkDir, 1155 name: &str,
··· 1089 "); 1090 } 1091 1092 fn create_commit( 1093 work_dir: &TestWorkDir, 1094 name: &str,
+20
cli/tests/test_config_command.rs
··· 971 insta::assert_snapshot!(output, @r" 972 Make sure I can pick this up 973 [EOF] 974 "); 975 976 // if you set JJ_CONFIG, you shouldn't get a warning
··· 971 insta::assert_snapshot!(output, @r" 972 Make sure I can pick this up 973 [EOF] 974 + ------- stderr ------- 975 + Warning: Deprecated configuration file `$TEST_ENV/home/Library/Application Support/jj/config.toml`. 976 + Configuration files in `~/Library/Application Support/jj` are deprecated, and support will be removed in a future release. 977 + Instead, move your configuration files to `~/.config/jj`. 978 + [EOF] 979 + "); 980 + 981 + // if XDG_CONFIG_HOME is ~/Library/Application Support, 982 + // you shouldn't get a warning 983 + let output = test_env.run_jj_with(|cmd| { 984 + cmd.env_remove("JJ_CONFIG") 985 + .env( 986 + "XDG_CONFIG_HOME", 987 + test_env.home_dir().join("Library/Application Support"), 988 + ) 989 + .args(["config", "get", "foo.bar"]) 990 + }); 991 + insta::assert_snapshot!(output, @r" 992 + Make sure I can pick this up 993 + [EOF] 994 "); 995 996 // if you set JJ_CONFIG, you shouldn't get a warning
+2 -2
cli/tests/test_describe_command.rs
··· 618 let output = work_dir.run_jj(["describe"]); 619 insta::assert_snapshot!(output, @r#" 620 ------- stderr ------- 621 - Warning: Deprecated config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 622 Working copy (@) now at: qpvuntsm 7276dfff TESTED=TODO 623 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set) 624 [EOF] ··· 639 let output = work_dir.run_jj(["describe", "--no-edit", "--reset-author"]); 640 insta::assert_snapshot!(output, @r#" 641 ------- stderr ------- 642 - Warning: Deprecated config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 643 Working copy (@) now at: kkmpptxz 7118bcb8 (empty) (no description set) 644 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set) 645 [EOF]
··· 618 let output = work_dir.run_jj(["describe"]); 619 insta::assert_snapshot!(output, @r#" 620 ------- stderr ------- 621 + Warning: Deprecated user-level config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 622 Working copy (@) now at: qpvuntsm 7276dfff TESTED=TODO 623 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set) 624 [EOF] ··· 639 let output = work_dir.run_jj(["describe", "--no-edit", "--reset-author"]); 640 insta::assert_snapshot!(output, @r#" 641 ------- stderr ------- 642 + Warning: Deprecated user-level config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 643 Working copy (@) now at: kkmpptxz 7118bcb8 (empty) (no description set) 644 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set) 645 [EOF]
+3 -3
cli/tests/test_diff_command.rs
··· 253 -4 254 [EOF] 255 ------- stderr ------- 256 - Warning: Deprecated config: ui.diff.format is updated to ui.diff-formatter = ":git" 257 [EOF] 258 "#); 259 ··· 3138 file3 3139 [EOF] 3140 ------- stderr ------- 3141 - Warning: Deprecated config: ui.diff.tool is renamed to ui.diff-formatter 3142 - Warning: Deprecated config: ui.diff.format is deleted (superseded by ui.diff-formatter) 3143 [EOF] 3144 "); 3145
··· 253 -4 254 [EOF] 255 ------- stderr ------- 256 + Warning: Deprecated CLI-provided config: ui.diff.format is updated to ui.diff-formatter = ":git" 257 [EOF] 258 "#); 259 ··· 3138 file3 3139 [EOF] 3140 ------- stderr ------- 3141 + Warning: Deprecated CLI-provided config: ui.diff.tool is renamed to ui.diff-formatter 3142 + Warning: Deprecated CLI-provided config: ui.diff.format is deleted (superseded by ui.diff-formatter) 3143 [EOF] 3144 "); 3145
-46
cli/tests/test_evolog_command.rs
··· 171 -- operation e0f8e58b3800 (2001-02-03 08:05:08) new empty commit 172 [EOF] 173 "); 174 - 175 - // With `--diff-snapshots`, the rebase does show a diff 176 - // TODO: Bug, not implemented yet with --no-graph 177 - let output = work_dir.run_jj(["evolog", "--no-graph", "--git", "--diff-snapshots"]); 178 - insta::assert_snapshot!(output, @r" 179 - rlvkpnrz test.user@example.com 2001-02-03 08:05:10 33c10ace 180 - my description 181 - -- operation 3499115d3831 (2001-02-03 08:05:10) snapshot working copy 182 - diff --git a/file1 b/file1 183 - index 0000000000..2ab19ae607 100644 184 - --- a/file1 185 - +++ b/file1 186 - @@ -1,7 +1,1 @@ 187 - -<<<<<<< Conflict 1 of 1 188 - -%%%%%%% Changes from base to side #1 189 - --foo 190 - -+++++++ Contents of side #2 191 - -foo 192 - -bar 193 - ->>>>>>> Conflict 1 of 1 ends 194 - +resolved 195 - rlvkpnrz hidden test.user@example.com 2001-02-03 08:05:09 7f56b2a0 conflict 196 - my description 197 - -- operation eb87ec366530 (2001-02-03 08:05:09) rebase commit 51e08f95160c897080d035d330aead3ee6ed5588 198 - rlvkpnrz hidden test.user@example.com 2001-02-03 08:05:09 51e08f95 199 - my description 200 - -- operation 18a971ce330a (2001-02-03 08:05:09) snapshot working copy 201 - diff --git a/file1 b/file1 202 - index 257cc5642c..3bd1f0e297 100644 203 - --- a/file1 204 - +++ b/file1 205 - @@ -1,1 +1,2 @@ 206 - foo 207 - +bar 208 - diff --git a/file2 b/file2 209 - new file mode 100644 210 - index 0000000000..257cc5642c 211 - --- /dev/null 212 - +++ b/file2 213 - @@ -0,0 +1,1 @@ 214 - +foo 215 - rlvkpnrz hidden test.user@example.com 2001-02-03 08:05:08 b955b72e 216 - (empty) my description 217 - -- operation e0f8e58b3800 (2001-02-03 08:05:08) new empty commit 218 - [EOF] 219 - "); 220 } 221 222 #[test]
··· 171 -- operation e0f8e58b3800 (2001-02-03 08:05:08) new empty commit 172 [EOF] 173 "); 174 } 175 176 #[test]
+2 -1
cli/tests/test_git_fetch.rs
··· 1038 ]); 1039 insta::assert_snapshot!(output, @r" 1040 ------- stderr ------- 1041 - Warning: No branch matching `noexist1`, `noexist2` found on any specified/configured remote 1042 Nothing changed. 1043 [EOF] 1044 ");
··· 1038 ]); 1039 insta::assert_snapshot!(output, @r" 1040 ------- stderr ------- 1041 + Warning: No branch matching `noexist1` found on any specified/configured remote 1042 + Warning: No branch matching `noexist2` found on any specified/configured remote 1043 Nothing changed. 1044 [EOF] 1045 ");
+140 -140
cli/tests/test_split_command.rs
··· 73 let output = work_dir.run_jj(["split", "file2"]); 74 insta::assert_snapshot!(output, @r" 75 ------- stderr ------- 76 - Selected changes : zsuskuln 8a73f71d (no description set) 77 - Remaining changes: qpvuntsm c4d8ebac (no description set) 78 - Working copy (@) now at: qpvuntsm c4d8ebac (no description set) 79 - Parent commit (@-) : zsuskuln 8a73f71d (no description set) 80 [EOF] 81 "); 82 insta::assert_snapshot!( ··· 92 assert!(!test_env.env_root().join("editor1").exists()); 93 94 insta::assert_snapshot!(get_log_output(&work_dir), @r" 95 - @ qpvuntsmwlqt false 96 - โ—‹ zsuskulnrvyr false 97 โ—† zzzzzzzzzzzz true 98 [EOF] 99 "); ··· 128 ------- stderr ------- 129 Warning: All changes have been selected, so the original revision will become empty 130 Rebased 1 descendant commits 131 - Selected changes : znkkpsqq d6e65134 (no description set) 132 - Remaining changes: zsuskuln aa27eaa3 (empty) (no description set) 133 - Working copy (@) now at: qpvuntsm e94cab21 (no description set) 134 - Parent commit (@-) : zsuskuln aa27eaa3 (empty) (no description set) 135 [EOF] 136 "); 137 138 insta::assert_snapshot!(get_log_output(&work_dir), @r" 139 - @ qpvuntsmwlqt false 140 - โ—‹ zsuskulnrvyr true 141 - โ—‹ znkkpsqqskkl false 142 โ—† zzzzzzzzzzzz true 143 [EOF] 144 "); ··· 159 ------- stderr ------- 160 Warning: No changes have been selected, so the new revision will be empty 161 Rebased 1 descendant commits 162 - Selected changes : lylxulpl 3d639d71 (empty) (no description set) 163 - Remaining changes: znkkpsqq 706a0e77 (no description set) 164 - Working copy (@) now at: qpvuntsm 502cf440 (no description set) 165 - Parent commit (@-) : znkkpsqq 706a0e77 (no description set) 166 [EOF] 167 "); 168 169 insta::assert_snapshot!(get_log_output(&work_dir), @r" 170 - @ qpvuntsmwlqt false 171 - โ—‹ znkkpsqqskkl false 172 - โ—‹ lylxulplsnyw true 173 โ—† zzzzzzzzzzzz true 174 [EOF] 175 "); ··· 207 let output = work_dir.run_jj(["split", "file1"]); 208 insta::assert_snapshot!(output, @r#" 209 ------- stderr ------- 210 - Warning: Deprecated config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 211 - Selected changes : kkmpptxz 530f78ed part 1 212 - Remaining changes: qpvuntsm 88189e08 part 2 213 - Working copy (@) now at: qpvuntsm 88189e08 part 2 214 - Parent commit (@-) : kkmpptxz 530f78ed part 1 215 [EOF] 216 "#); 217 ··· 236 JJ: Lines starting with "JJ:" (like this one) will be removed. 237 "#); 238 insta::assert_snapshot!(get_log_output(&work_dir), @r#" 239 - @ qpvuntsmwlqt false part 2 240 - โ—‹ kkmpptxzrspx false part 1 241 โ—† zzzzzzzzzzzz true 242 [EOF] 243 ------- stderr ------- 244 - Warning: Deprecated config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 245 [EOF] 246 "#); 247 } ··· 265 let output = work_dir.run_jj(["split", "file1"]); 266 insta::assert_snapshot!(output, @r#" 267 ------- stderr ------- 268 - Warning: Deprecated config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 269 - Selected changes : rlvkpnrz 16dc7e13 TESTED=TODO 270 - Remaining changes: qpvuntsm f40d53f2 (no description set) 271 - Working copy (@) now at: qpvuntsm f40d53f2 (no description set) 272 - Parent commit (@-) : rlvkpnrz 16dc7e13 TESTED=TODO 273 [EOF] 274 "#); 275 ··· 291 "#); 292 assert!(!test_env.env_root().join("editor2").exists()); 293 insta::assert_snapshot!(get_log_output(&work_dir), @r#" 294 - @ qpvuntsmwlqt false 295 - โ—‹ rlvkpnrzqnoo false TESTED=TODO 296 โ—† zzzzzzzzzzzz true 297 [EOF] 298 ------- stderr ------- 299 - Warning: Deprecated config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 300 [EOF] 301 "#); 302 } ··· 346 insta::assert_snapshot!(output, @r" 347 ------- stderr ------- 348 Rebased 2 descendant commits 349 - Selected changes : royxmykx e13e94b9 Add file1 350 - Remaining changes: qpvuntsm cf8ebbab Add file2 351 - Working copy (@) now at: kkmpptxz 73a16519 Add file4 352 - Parent commit (@-) : rlvkpnrz ec4d3a14 Add file3 353 [EOF] 354 "); 355 - insta::assert_snapshot!(get_log_output(&work_dir), @r" 356 @ kkmpptxzrspx false Add file4 357 โ—‹ rlvkpnrzqnoo false Add file3 358 - โ—‹ qpvuntsmwlqt false Add file2 359 - โ—‹ royxmykxtrkr false Add file1 360 โ—† zzzzzzzzzzzz true 361 [EOF] 362 - "); 363 364 // The commit we're splitting has a description, so the user will be 365 // prompted to enter a description for each of the commits. ··· 388 // - The initial empty commit. 389 // - The rewritten commit from the snapshot after the files were added. 390 // - The rewritten commit once the description is added during `jj commit`. 391 - // - The rewritten commit after the split with a new change ID. 392 - let evolog_1 = work_dir.run_jj(["evolog", "-r", "royxm"]); 393 insta::assert_snapshot!(evolog_1, @r" 394 - โ—‹ royxmykx test.user@example.com 2001-02-03 08:05:12 e13e94b9 395 โ”‚ Add file1 396 - โ”‚ -- operation a8006fdd66fd (2001-02-03 08:05:12) split commit 1d2499e72cefc8a2b87ebb47569140857b96189f 397 โ—‹ qpvuntsm hidden test.user@example.com 2001-02-03 08:05:08 1d2499e7 398 โ”‚ Add file1 & file2 399 โ”‚ -- operation adf4f33386c9 (2001-02-03 08:05:08) commit f5700f8ef89e290e4e90ae6adc0908707e0d8c85 ··· 407 "); 408 409 // The evolog for the second commit is the same, except that the change id 410 - // doesn't change after the split. 411 - let evolog_2 = work_dir.run_jj(["evolog", "-r", "qpvun"]); 412 insta::assert_snapshot!(evolog_2, @r" 413 - โ—‹ qpvuntsm test.user@example.com 2001-02-03 08:05:12 cf8ebbab 414 โ”‚ Add file2 415 - โ”‚ -- operation a8006fdd66fd (2001-02-03 08:05:12) split commit 1d2499e72cefc8a2b87ebb47569140857b96189f 416 โ—‹ qpvuntsm hidden test.user@example.com 2001-02-03 08:05:08 1d2499e7 417 โ”‚ Add file1 & file2 418 โ”‚ -- operation adf4f33386c9 (2001-02-03 08:05:08) commit f5700f8ef89e290e4e90ae6adc0908707e0d8c85 ··· 461 insta::assert_snapshot!(output, @r" 462 ------- stderr ------- 463 Rebased 1 descendant commits 464 - Selected changes : royxmykx ad21dad2 Add file1 465 - Remaining changes: kkmpptxz 0922bd25 Add file2 466 - Working copy (@) now at: zsuskuln f59cd990 (empty) 2 467 Parent commit (@-) : qpvuntsm 884fe9b9 (empty) 1 468 - Parent commit (@-) : kkmpptxz 0922bd25 Add file2 469 [EOF] 470 "); 471 insta::assert_snapshot!(get_log_output(&work_dir), @r" 472 @ zsuskulnrvyr true 2 473 โ”œโ”€โ•ฎ 474 - โ”‚ โ—‹ kkmpptxzrspx false Add file2 475 - โ”‚ โ—‹ royxmykxtrkr false Add file1 476 โ—‹ โ”‚ qpvuntsmwlqt true 1 477 โ”œโ”€โ•ฏ 478 โ—† zzzzzzzzzzzz true ··· 498 โ—† zzzzzzzzzzzz true 499 [EOF] 500 ------- stderr ------- 501 - Warning: Deprecated config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 502 [EOF] 503 "#); 504 ··· 510 let output = work_dir.run_jj(["split", "--parallel", "file1"]); 511 insta::assert_snapshot!(output, @r#" 512 ------- stderr ------- 513 - Warning: Deprecated config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 514 - Selected changes : kkmpptxz bd9b3db1 TESTED=TODO 515 - Remaining changes: qpvuntsm 5597b805 (no description set) 516 - Working copy (@) now at: qpvuntsm 5597b805 (no description set) 517 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set) 518 Added 0 files, modified 0 files, removed 1 files 519 [EOF] 520 "#); 521 insta::assert_snapshot!(get_log_output(&work_dir), @r#" 522 - @ qpvuntsmwlqt false 523 - โ”‚ โ—‹ kkmpptxzrspx false TESTED=TODO 524 โ”œโ”€โ•ฏ 525 โ—† zzzzzzzzzzzz true 526 [EOF] 527 ------- stderr ------- 528 - Warning: Deprecated config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 529 [EOF] 530 "#); 531 ··· 550 // Check the evolog for the first commit. It shows three entries: 551 // - The initial empty commit. 552 // - The rewritten commit from the snapshot after the files were added. 553 - // - The rewritten commit after the split with a new change ID. 554 - let evolog_1 = work_dir.run_jj(["evolog", "-r", "kkmpp"]); 555 insta::assert_snapshot!(evolog_1, @r#" 556 - โ—‹ kkmpptxz test.user@example.com 2001-02-03 08:05:09 bd9b3db1 557 โ”‚ TESTED=TODO 558 - โ”‚ -- operation 372a3799b434 (2001-02-03 08:05:09) split commit f5700f8ef89e290e4e90ae6adc0908707e0d8c85 559 โ—‹ qpvuntsm hidden test.user@example.com 2001-02-03 08:05:08 f5700f8e 560 โ”‚ (no description set) 561 โ”‚ -- operation 1663cd1cc445 (2001-02-03 08:05:08) snapshot working copy ··· 564 -- operation 8f47435a3990 (2001-02-03 08:05:07) add workspace 'default' 565 [EOF] 566 ------- stderr ------- 567 - Warning: Deprecated config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 568 [EOF] 569 "#); 570 571 // The evolog for the second commit is the same, except that the change id 572 - // doesn't change after the split. 573 - let evolog_2 = work_dir.run_jj(["evolog", "-r", "qpvun"]); 574 insta::assert_snapshot!(evolog_2, @r#" 575 - @ qpvuntsm test.user@example.com 2001-02-03 08:05:09 5597b805 576 โ”‚ (no description set) 577 - โ”‚ -- operation 372a3799b434 (2001-02-03 08:05:09) split commit f5700f8ef89e290e4e90ae6adc0908707e0d8c85 578 โ—‹ qpvuntsm hidden test.user@example.com 2001-02-03 08:05:08 f5700f8e 579 โ”‚ (no description set) 580 โ”‚ -- operation 1663cd1cc445 (2001-02-03 08:05:08) snapshot working copy ··· 583 -- operation 8f47435a3990 (2001-02-03 08:05:07) add workspace 'default' 584 [EOF] 585 ------- stderr ------- 586 - Warning: Deprecated config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 587 [EOF] 588 "#); 589 } ··· 637 insta::assert_snapshot!(output, @r" 638 ------- stderr ------- 639 Rebased 2 descendant commits 640 - Selected changes : vruxwmqv 3f0980cb Add file1 641 - Remaining changes: qpvuntsm dff79d19 Add file2 642 - Working copy (@) now at: qpvuntsm dff79d19 Add file2 643 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set) 644 Added 0 files, modified 0 files, removed 1 files 645 [EOF] ··· 648 โ—‹ kkmpptxzrspx false Add file4 649 โ—‹ rlvkpnrzqnoo false Add file3 650 โ”œโ”€โ•ฎ 651 - โ”‚ @ qpvuntsmwlqt false Add file2 652 - โ—‹ โ”‚ vruxwmqvtpmx false Add file1 653 โ”œโ”€โ•ฏ 654 โ—† zzzzzzzzzzzz true 655 [EOF] ··· 714 insta::assert_snapshot!(output, @r" 715 ------- stderr ------- 716 Rebased 1 descendant commits 717 - Selected changes : royxmykx ad21dad2 Add file1 718 - Remaining changes: kkmpptxz 23a2daac Add file2 719 - Working copy (@) now at: zsuskuln f1fcb7a6 (empty) 2 720 Parent commit (@-) : qpvuntsm 884fe9b9 (empty) 1 721 - Parent commit (@-) : royxmykx ad21dad2 Add file1 722 - Parent commit (@-) : kkmpptxz 23a2daac Add file2 723 [EOF] 724 "); 725 insta::assert_snapshot!(get_log_output(&work_dir), @r" 726 @ zsuskulnrvyr true 2 727 โ”œโ”€โ”ฌโ”€โ•ฎ 728 - โ”‚ โ”‚ โ—‹ kkmpptxzrspx false Add file2 729 - โ”‚ โ—‹ โ”‚ royxmykxtrkr false Add file1 730 โ”‚ โ”œโ”€โ•ฏ 731 โ—‹ โ”‚ qpvuntsmwlqt true 1 732 โ”œโ”€โ•ฏ ··· 793 let output = work_dir.run_jj(["split"]); 794 insta::assert_snapshot!(output, @r" 795 ------- stderr ------- 796 - Selected changes : rlvkpnrz 1ff7a783 (no description set) 797 - Remaining changes: qpvuntsm 429f292f (no description set) 798 - Working copy (@) now at: qpvuntsm 429f292f (no description set) 799 - Parent commit (@-) : rlvkpnrz 1ff7a783 (no description set) 800 [EOF] 801 "); 802 ··· 824 825 let output = work_dir.run_jj(["log", "--summary"]); 826 insta::assert_snapshot!(output, @r" 827 - @ qpvuntsm test.user@example.com 2001-02-03 08:05:08 429f292f 828 โ”‚ (no description set) 829 โ”‚ A file2 830 - โ—‹ rlvkpnrz test.user@example.com 2001-02-03 08:05:08 1ff7a783 831 โ”‚ (no description set) 832 โ”‚ A file1 833 โ—† zzzzzzzz root() 00000000 ··· 869 let output = work_dir.run_jj(["split", "-i", "file1", "file2"]); 870 insta::assert_snapshot!(output, @r" 871 ------- stderr ------- 872 - Selected changes : kkmpptxz 0a5bea34 (no description set) 873 - Remaining changes: rlvkpnrz 7326e6fd (no description set) 874 - Working copy (@) now at: rlvkpnrz 7326e6fd (no description set) 875 - Parent commit (@-) : kkmpptxz 0a5bea34 (no description set) 876 [EOF] 877 "); 878 ··· 889 890 let output = work_dir.run_jj(["log", "--summary"]); 891 insta::assert_snapshot!(output, @r" 892 - @ rlvkpnrz test.user@example.com 2001-02-03 08:05:09 7326e6fd 893 โ”‚ (no description set) 894 โ”‚ M file2 895 โ”‚ M file3 896 - โ—‹ kkmpptxz test.user@example.com 2001-02-03 08:05:09 0a5bea34 897 โ”‚ (no description set) 898 โ”‚ A file1 899 โ—‹ qpvuntsm test.user@example.com 2001-02-03 08:05:08 ff687a2f ··· 947 main_dir.run_jj(["split", "file2"]).success(); 948 // The working copy for both workspaces will be the second split commit. 949 insta::assert_snapshot!(get_workspace_log_output(&main_dir), @r" 950 - @ qpvuntsmwlqt default@ second@ second-commit 951 - โ—‹ royxmykxtrkr first-commit 952 โ—† zzzzzzzzzzzz 953 [EOF] 954 "); ··· 962 .unwrap(); 963 main_dir.run_jj(["split", "file2", "--parallel"]).success(); 964 insta::assert_snapshot!(get_workspace_log_output(&main_dir), @r" 965 - @ qpvuntsmwlqt default@ second@ second-commit 966 - โ”‚ โ—‹ yostqsxwqrlt first-commit 967 โ”œโ”€โ•ฏ 968 โ—† zzzzzzzzzzzz 969 [EOF] ··· 1006 main_dir.run_jj(["split", "file2"]).success(); 1007 // Only the working copy commit for the default workspace changes. 1008 insta::assert_snapshot!(get_workspace_log_output(&main_dir), @r" 1009 - @ qpvuntsmwlqt default@ second-commit 1010 - โ—‹ mzvwutvlkqwt first-commit 1011 โ”‚ โ—‹ pmmvwywvzvvn second@ 1012 โ”œโ”€โ•ฏ 1013 โ—† zzzzzzzzzzzz ··· 1023 .unwrap(); 1024 main_dir.run_jj(["split", "file2", "--parallel"]).success(); 1025 insta::assert_snapshot!(get_workspace_log_output(&main_dir), @r" 1026 - @ qpvuntsmwlqt default@ second-commit 1027 - โ”‚ โ—‹ vruxwmqvtpmx first-commit 1028 โ”œโ”€โ•ฏ 1029 โ”‚ โ—‹ pmmvwywvzvvn second@ 1030 โ”œโ”€โ•ฏ ··· 1064 let output = work_dir.run_jj(["split", "file1"]); 1065 insta::assert_snapshot!(output, @r#" 1066 ------- stderr ------- 1067 - Warning: Deprecated config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 1068 - Selected changes : kkmpptxz 530f78ed part 1 1069 - Remaining changes: qpvuntsm 88189e08 part 2 1070 - Working copy (@) now at: qpvuntsm 88189e08 part 2 1071 - Parent commit (@-) : kkmpptxz 530f78ed part 1 1072 [EOF] 1073 "#); 1074 ··· 1097 JJ: Lines starting with "JJ:" (like this one) will be removed. 1098 "#); 1099 insta::assert_snapshot!(get_log_output(&work_dir), @r#" 1100 - @ qpvuntsmwlqt false part 2 1101 - โ—‹ kkmpptxzrspx false part 1 1102 โ—† zzzzzzzzzzzz true 1103 [EOF] 1104 ------- stderr ------- 1105 - Warning: Deprecated config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 1106 [EOF] 1107 "#); 1108 } ··· 1120 let output = work_dir.run_jj(["split", "-m", "fix in file1", "file1"]); 1121 insta::assert_snapshot!(output, @r" 1122 ------- stderr ------- 1123 - Selected changes : kkmpptxz b246503a fix in file1 1124 - Remaining changes: qpvuntsm e05b5012 my feature 1125 - Working copy (@) now at: qpvuntsm e05b5012 my feature 1126 - Parent commit (@-) : kkmpptxz b246503a fix in file1 1127 [EOF] 1128 "); 1129 1130 insta::assert_snapshot!(get_log_output(&work_dir), @r" 1131 - @ qpvuntsmwlqt false my feature 1132 - โ—‹ kkmpptxzrspx false fix in file1 1133 โ—† zzzzzzzzzzzz true 1134 [EOF] 1135 "); ··· 1146 ]); 1147 insta::assert_snapshot!(output, @r" 1148 ------- stderr ------- 1149 - Selected changes : royxmykx 87fbb488 fix in file1 1150 - Remaining changes: qpvuntsm fb598346 my feature 1151 - Working copy (@) now at: qpvuntsm fb598346 my feature 1152 - Parent commit (@-) : royxmykx 87fbb488 fix in file1 1153 [EOF] 1154 "); 1155 1156 insta::assert_snapshot!(get_log_output(&work_dir), @r" 1157 - @ qpvuntsmwlqt false my feature 1158 - โ—‹ royxmykxtrkr false fix in file1 1159 โ”‚ 1160 โ”‚ CC: test.user@example.com 1161 โ—† zzzzzzzzzzzz true ··· 1470 .unwrap(); 1471 let output = main_dir.run_jj(["split", "file2"]); 1472 match bookmark_behavior { 1473 - BookmarkBehavior::Default | BookmarkBehavior::LeaveBookmarkWithTarget => { 1474 insta::allow_duplicates! { 1475 insta::assert_snapshot!(output, @r#" 1476 ------- stderr ------- 1477 - Selected changes : mzvwutvl ac5cf500 first-commit 1478 - Remaining changes: qpvuntsm a13c536a "*le-signet*" | second-commit 1479 - Working copy (@) now at: qpvuntsm a13c536a "*le-signet*" | second-commit 1480 - Parent commit (@-) : mzvwutvl ac5cf500 first-commit 1481 [EOF] 1482 "#); 1483 } 1484 insta::allow_duplicates! { 1485 insta::assert_snapshot!(get_log_output(&main_dir), @r#" 1486 - @ qpvuntsmwlqt false "*le-signet*" second-commit 1487 - โ—‹ mzvwutvlkqwt false first-commit 1488 โ—† zzzzzzzzzzzz true 1489 [EOF] 1490 "#); 1491 } 1492 } 1493 - BookmarkBehavior::MoveBookmarkToChild => { 1494 insta::allow_duplicates! { 1495 insta::assert_snapshot!(output, @r#" 1496 ------- stderr ------- ··· 1521 .unwrap(); 1522 main_dir.run_jj(["split", "file2", "--parallel"]).success(); 1523 match bookmark_behavior { 1524 - BookmarkBehavior::Default | BookmarkBehavior::LeaveBookmarkWithTarget => { 1525 insta::allow_duplicates! { 1526 insta::assert_snapshot!(get_log_output(&main_dir), @r#" 1527 - @ qpvuntsmwlqt false "*le-signet*" second-commit 1528 - โ”‚ โ—‹ vruxwmqvtpmx false first-commit 1529 โ”œโ”€โ•ฏ 1530 โ—† zzzzzzzzzzzz true 1531 [EOF] 1532 "#); 1533 } 1534 } 1535 - BookmarkBehavior::MoveBookmarkToChild => { 1536 insta::allow_duplicates! { 1537 insta::assert_snapshot!(get_log_output(&main_dir), @r#" 1538 @ vruxwmqvtpmx false "*le-signet*" second-commit
··· 73 let output = work_dir.run_jj(["split", "file2"]); 74 insta::assert_snapshot!(output, @r" 75 ------- stderr ------- 76 + Selected changes : qpvuntsm 6dbc7747 (no description set) 77 + Remaining changes: zsuskuln 42cbbc02 (no description set) 78 + Working copy (@) now at: zsuskuln 42cbbc02 (no description set) 79 + Parent commit (@-) : qpvuntsm 6dbc7747 (no description set) 80 [EOF] 81 "); 82 insta::assert_snapshot!( ··· 92 assert!(!test_env.env_root().join("editor1").exists()); 93 94 insta::assert_snapshot!(get_log_output(&work_dir), @r" 95 + @ zsuskulnrvyr false 96 + โ—‹ qpvuntsmwlqt false 97 โ—† zzzzzzzzzzzz true 98 [EOF] 99 "); ··· 128 ------- stderr ------- 129 Warning: All changes have been selected, so the original revision will become empty 130 Rebased 1 descendant commits 131 + Selected changes : qpvuntsm 9fd1c9e1 (no description set) 132 + Remaining changes: znkkpsqq 41e0da21 (empty) (no description set) 133 + Working copy (@) now at: zsuskuln a06e40b8 (no description set) 134 + Parent commit (@-) : znkkpsqq 41e0da21 (empty) (no description set) 135 [EOF] 136 "); 137 138 insta::assert_snapshot!(get_log_output(&work_dir), @r" 139 + @ zsuskulnrvyr false 140 + โ—‹ znkkpsqqskkl true 141 + โ—‹ qpvuntsmwlqt false 142 โ—† zzzzzzzzzzzz true 143 [EOF] 144 "); ··· 159 ------- stderr ------- 160 Warning: No changes have been selected, so the new revision will be empty 161 Rebased 1 descendant commits 162 + Selected changes : qpvuntsm 49416632 (empty) (no description set) 163 + Remaining changes: lylxulpl 718afbf5 (no description set) 164 + Working copy (@) now at: zsuskuln 0ed53ee6 (no description set) 165 + Parent commit (@-) : lylxulpl 718afbf5 (no description set) 166 [EOF] 167 "); 168 169 insta::assert_snapshot!(get_log_output(&work_dir), @r" 170 + @ zsuskulnrvyr false 171 + โ—‹ lylxulplsnyw false 172 + โ—‹ qpvuntsmwlqt true 173 โ—† zzzzzzzzzzzz true 174 [EOF] 175 "); ··· 207 let output = work_dir.run_jj(["split", "file1"]); 208 insta::assert_snapshot!(output, @r#" 209 ------- stderr ------- 210 + Warning: Deprecated user-level config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 211 + Selected changes : qpvuntsm c7f7b14b part 1 212 + Remaining changes: kkmpptxz ac33a5a9 part 2 213 + Working copy (@) now at: kkmpptxz ac33a5a9 part 2 214 + Parent commit (@-) : qpvuntsm c7f7b14b part 1 215 [EOF] 216 "#); 217 ··· 236 JJ: Lines starting with "JJ:" (like this one) will be removed. 237 "#); 238 insta::assert_snapshot!(get_log_output(&work_dir), @r#" 239 + @ kkmpptxzrspx false part 2 240 + โ—‹ qpvuntsmwlqt false part 1 241 โ—† zzzzzzzzzzzz true 242 [EOF] 243 ------- stderr ------- 244 + Warning: Deprecated user-level config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 245 [EOF] 246 "#); 247 } ··· 265 let output = work_dir.run_jj(["split", "file1"]); 266 insta::assert_snapshot!(output, @r#" 267 ------- stderr ------- 268 + Warning: Deprecated user-level config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 269 + Selected changes : qpvuntsm ff633dcc TESTED=TODO 270 + Remaining changes: rlvkpnrz b1d20b7e (no description set) 271 + Working copy (@) now at: rlvkpnrz b1d20b7e (no description set) 272 + Parent commit (@-) : qpvuntsm ff633dcc TESTED=TODO 273 [EOF] 274 "#); 275 ··· 291 "#); 292 assert!(!test_env.env_root().join("editor2").exists()); 293 insta::assert_snapshot!(get_log_output(&work_dir), @r#" 294 + @ rlvkpnrzqnoo false 295 + โ—‹ qpvuntsmwlqt false TESTED=TODO 296 โ—† zzzzzzzzzzzz true 297 [EOF] 298 ------- stderr ------- 299 + Warning: Deprecated user-level config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 300 [EOF] 301 "#); 302 } ··· 346 insta::assert_snapshot!(output, @r" 347 ------- stderr ------- 348 Rebased 2 descendant commits 349 + Selected changes : qpvuntsm 74306e35 Add file1 350 + Remaining changes: royxmykx 0a37745e Add file2 351 + Working copy (@) now at: kkmpptxz 7ee84812 Add file4 352 + Parent commit (@-) : rlvkpnrz d335bd94 Add file3 353 [EOF] 354 "); 355 + insta::assert_snapshot!(get_log_output(&work_dir), @r###" 356 @ kkmpptxzrspx false Add file4 357 โ—‹ rlvkpnrzqnoo false Add file3 358 + โ—‹ royxmykxtrkr false Add file2 359 + โ—‹ qpvuntsmwlqt false Add file1 360 โ—† zzzzzzzzzzzz true 361 [EOF] 362 + "###); 363 364 // The commit we're splitting has a description, so the user will be 365 // prompted to enter a description for each of the commits. ··· 388 // - The initial empty commit. 389 // - The rewritten commit from the snapshot after the files were added. 390 // - The rewritten commit once the description is added during `jj commit`. 391 + // - The rewritten commit after the split. 392 + let evolog_1 = work_dir.run_jj(["evolog", "-r", "qpvun"]); 393 insta::assert_snapshot!(evolog_1, @r" 394 + โ—‹ qpvuntsm test.user@example.com 2001-02-03 08:05:12 74306e35 395 โ”‚ Add file1 396 + โ”‚ -- operation 994b490f285d (2001-02-03 08:05:12) split commit 1d2499e72cefc8a2b87ebb47569140857b96189f 397 โ—‹ qpvuntsm hidden test.user@example.com 2001-02-03 08:05:08 1d2499e7 398 โ”‚ Add file1 & file2 399 โ”‚ -- operation adf4f33386c9 (2001-02-03 08:05:08) commit f5700f8ef89e290e4e90ae6adc0908707e0d8c85 ··· 407 "); 408 409 // The evolog for the second commit is the same, except that the change id 410 + // changes after the split. 411 + let evolog_2 = work_dir.run_jj(["evolog", "-r", "royxm"]); 412 insta::assert_snapshot!(evolog_2, @r" 413 + โ—‹ royxmykx test.user@example.com 2001-02-03 08:05:12 0a37745e 414 โ”‚ Add file2 415 + โ”‚ -- operation 994b490f285d (2001-02-03 08:05:12) split commit 1d2499e72cefc8a2b87ebb47569140857b96189f 416 โ—‹ qpvuntsm hidden test.user@example.com 2001-02-03 08:05:08 1d2499e7 417 โ”‚ Add file1 & file2 418 โ”‚ -- operation adf4f33386c9 (2001-02-03 08:05:08) commit f5700f8ef89e290e4e90ae6adc0908707e0d8c85 ··· 461 insta::assert_snapshot!(output, @r" 462 ------- stderr ------- 463 Rebased 1 descendant commits 464 + Selected changes : kkmpptxz cc199567 Add file1 465 + Remaining changes: royxmykx e488409f Add file2 466 + Working copy (@) now at: zsuskuln ace61421 (empty) 2 467 Parent commit (@-) : qpvuntsm 884fe9b9 (empty) 1 468 + Parent commit (@-) : royxmykx e488409f Add file2 469 [EOF] 470 "); 471 insta::assert_snapshot!(get_log_output(&work_dir), @r" 472 @ zsuskulnrvyr true 2 473 โ”œโ”€โ•ฎ 474 + โ”‚ โ—‹ royxmykxtrkr false Add file2 475 + โ”‚ โ—‹ kkmpptxzrspx false Add file1 476 โ—‹ โ”‚ qpvuntsmwlqt true 1 477 โ”œโ”€โ•ฏ 478 โ—† zzzzzzzzzzzz true ··· 498 โ—† zzzzzzzzzzzz true 499 [EOF] 500 ------- stderr ------- 501 + Warning: Deprecated user-level config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 502 [EOF] 503 "#); 504 ··· 510 let output = work_dir.run_jj(["split", "--parallel", "file1"]); 511 insta::assert_snapshot!(output, @r#" 512 ------- stderr ------- 513 + Warning: Deprecated user-level config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 514 + Selected changes : qpvuntsm 7bcd474c TESTED=TODO 515 + Remaining changes: kkmpptxz 431886f6 (no description set) 516 + Working copy (@) now at: kkmpptxz 431886f6 (no description set) 517 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set) 518 Added 0 files, modified 0 files, removed 1 files 519 [EOF] 520 "#); 521 insta::assert_snapshot!(get_log_output(&work_dir), @r#" 522 + @ kkmpptxzrspx false 523 + โ”‚ โ—‹ qpvuntsmwlqt false TESTED=TODO 524 โ”œโ”€โ•ฏ 525 โ—† zzzzzzzzzzzz true 526 [EOF] 527 ------- stderr ------- 528 + Warning: Deprecated user-level config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 529 [EOF] 530 "#); 531 ··· 550 // Check the evolog for the first commit. It shows three entries: 551 // - The initial empty commit. 552 // - The rewritten commit from the snapshot after the files were added. 553 + // - The rewritten commit after the split. 554 + let evolog_1 = work_dir.run_jj(["evolog", "-r", "qpvun"]); 555 insta::assert_snapshot!(evolog_1, @r#" 556 + โ—‹ qpvuntsm test.user@example.com 2001-02-03 08:05:09 7bcd474c 557 โ”‚ TESTED=TODO 558 + โ”‚ -- operation 2b21c33e1596 (2001-02-03 08:05:09) split commit f5700f8ef89e290e4e90ae6adc0908707e0d8c85 559 โ—‹ qpvuntsm hidden test.user@example.com 2001-02-03 08:05:08 f5700f8e 560 โ”‚ (no description set) 561 โ”‚ -- operation 1663cd1cc445 (2001-02-03 08:05:08) snapshot working copy ··· 564 -- operation 8f47435a3990 (2001-02-03 08:05:07) add workspace 'default' 565 [EOF] 566 ------- stderr ------- 567 + Warning: Deprecated user-level config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 568 [EOF] 569 "#); 570 571 // The evolog for the second commit is the same, except that the change id 572 + // changes after the split. 573 + let evolog_2 = work_dir.run_jj(["evolog", "-r", "kkmpp"]); 574 insta::assert_snapshot!(evolog_2, @r#" 575 + @ kkmpptxz test.user@example.com 2001-02-03 08:05:09 431886f6 576 โ”‚ (no description set) 577 + โ”‚ -- operation 2b21c33e1596 (2001-02-03 08:05:09) split commit f5700f8ef89e290e4e90ae6adc0908707e0d8c85 578 โ—‹ qpvuntsm hidden test.user@example.com 2001-02-03 08:05:08 f5700f8e 579 โ”‚ (no description set) 580 โ”‚ -- operation 1663cd1cc445 (2001-02-03 08:05:08) snapshot working copy ··· 583 -- operation 8f47435a3990 (2001-02-03 08:05:07) add workspace 'default' 584 [EOF] 585 ------- stderr ------- 586 + Warning: Deprecated user-level config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 587 [EOF] 588 "#); 589 } ··· 637 insta::assert_snapshot!(output, @r" 638 ------- stderr ------- 639 Rebased 2 descendant commits 640 + Selected changes : qpvuntsm 18c85f56 Add file1 641 + Remaining changes: vruxwmqv cbdfd9cf Add file2 642 + Working copy (@) now at: vruxwmqv cbdfd9cf Add file2 643 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set) 644 Added 0 files, modified 0 files, removed 1 files 645 [EOF] ··· 648 โ—‹ kkmpptxzrspx false Add file4 649 โ—‹ rlvkpnrzqnoo false Add file3 650 โ”œโ”€โ•ฎ 651 + โ”‚ @ vruxwmqvtpmx false Add file2 652 + โ—‹ โ”‚ qpvuntsmwlqt false Add file1 653 โ”œโ”€โ•ฏ 654 โ—† zzzzzzzzzzzz true 655 [EOF] ··· 714 insta::assert_snapshot!(output, @r" 715 ------- stderr ------- 716 Rebased 1 descendant commits 717 + Selected changes : kkmpptxz cc199567 Add file1 718 + Remaining changes: royxmykx 82a5c527 Add file2 719 + Working copy (@) now at: zsuskuln b7cdcdec (empty) 2 720 Parent commit (@-) : qpvuntsm 884fe9b9 (empty) 1 721 + Parent commit (@-) : kkmpptxz cc199567 Add file1 722 + Parent commit (@-) : royxmykx 82a5c527 Add file2 723 [EOF] 724 "); 725 insta::assert_snapshot!(get_log_output(&work_dir), @r" 726 @ zsuskulnrvyr true 2 727 โ”œโ”€โ”ฌโ”€โ•ฎ 728 + โ”‚ โ”‚ โ—‹ royxmykxtrkr false Add file2 729 + โ”‚ โ—‹ โ”‚ kkmpptxzrspx false Add file1 730 โ”‚ โ”œโ”€โ•ฏ 731 โ—‹ โ”‚ qpvuntsmwlqt true 1 732 โ”œโ”€โ•ฏ ··· 793 let output = work_dir.run_jj(["split"]); 794 insta::assert_snapshot!(output, @r" 795 ------- stderr ------- 796 + Selected changes : qpvuntsm c664a51b (no description set) 797 + Remaining changes: rlvkpnrz 7e5d65b1 (no description set) 798 + Working copy (@) now at: rlvkpnrz 7e5d65b1 (no description set) 799 + Parent commit (@-) : qpvuntsm c664a51b (no description set) 800 [EOF] 801 "); 802 ··· 824 825 let output = work_dir.run_jj(["log", "--summary"]); 826 insta::assert_snapshot!(output, @r" 827 + @ rlvkpnrz test.user@example.com 2001-02-03 08:05:08 7e5d65b1 828 โ”‚ (no description set) 829 โ”‚ A file2 830 + โ—‹ qpvuntsm test.user@example.com 2001-02-03 08:05:08 c664a51b 831 โ”‚ (no description set) 832 โ”‚ A file1 833 โ—† zzzzzzzz root() 00000000 ··· 869 let output = work_dir.run_jj(["split", "-i", "file1", "file2"]); 870 insta::assert_snapshot!(output, @r" 871 ------- stderr ------- 872 + Selected changes : rlvkpnrz cdc9960a (no description set) 873 + Remaining changes: kkmpptxz 7255f070 (no description set) 874 + Working copy (@) now at: kkmpptxz 7255f070 (no description set) 875 + Parent commit (@-) : rlvkpnrz cdc9960a (no description set) 876 [EOF] 877 "); 878 ··· 889 890 let output = work_dir.run_jj(["log", "--summary"]); 891 insta::assert_snapshot!(output, @r" 892 + @ kkmpptxz test.user@example.com 2001-02-03 08:05:09 7255f070 893 โ”‚ (no description set) 894 โ”‚ M file2 895 โ”‚ M file3 896 + โ—‹ rlvkpnrz test.user@example.com 2001-02-03 08:05:09 cdc9960a 897 โ”‚ (no description set) 898 โ”‚ A file1 899 โ—‹ qpvuntsm test.user@example.com 2001-02-03 08:05:08 ff687a2f ··· 947 main_dir.run_jj(["split", "file2"]).success(); 948 // The working copy for both workspaces will be the second split commit. 949 insta::assert_snapshot!(get_workspace_log_output(&main_dir), @r" 950 + @ royxmykxtrkr default@ second@ second-commit 951 + โ—‹ qpvuntsmwlqt first-commit 952 โ—† zzzzzzzzzzzz 953 [EOF] 954 "); ··· 962 .unwrap(); 963 main_dir.run_jj(["split", "file2", "--parallel"]).success(); 964 insta::assert_snapshot!(get_workspace_log_output(&main_dir), @r" 965 + @ yostqsxwqrlt default@ second@ second-commit 966 + โ”‚ โ—‹ qpvuntsmwlqt first-commit 967 โ”œโ”€โ•ฏ 968 โ—† zzzzzzzzzzzz 969 [EOF] ··· 1006 main_dir.run_jj(["split", "file2"]).success(); 1007 // Only the working copy commit for the default workspace changes. 1008 insta::assert_snapshot!(get_workspace_log_output(&main_dir), @r" 1009 + @ mzvwutvlkqwt default@ second-commit 1010 + โ—‹ qpvuntsmwlqt first-commit 1011 โ”‚ โ—‹ pmmvwywvzvvn second@ 1012 โ”œโ”€โ•ฏ 1013 โ—† zzzzzzzzzzzz ··· 1023 .unwrap(); 1024 main_dir.run_jj(["split", "file2", "--parallel"]).success(); 1025 insta::assert_snapshot!(get_workspace_log_output(&main_dir), @r" 1026 + @ vruxwmqvtpmx default@ second-commit 1027 + โ”‚ โ—‹ qpvuntsmwlqt first-commit 1028 โ”œโ”€โ•ฏ 1029 โ”‚ โ—‹ pmmvwywvzvvn second@ 1030 โ”œโ”€โ•ฏ ··· 1064 let output = work_dir.run_jj(["split", "file1"]); 1065 insta::assert_snapshot!(output, @r#" 1066 ------- stderr ------- 1067 + Warning: Deprecated user-level config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 1068 + Selected changes : qpvuntsm c7f7b14b part 1 1069 + Remaining changes: kkmpptxz ac33a5a9 part 2 1070 + Working copy (@) now at: kkmpptxz ac33a5a9 part 2 1071 + Parent commit (@-) : qpvuntsm c7f7b14b part 1 1072 [EOF] 1073 "#); 1074 ··· 1097 JJ: Lines starting with "JJ:" (like this one) will be removed. 1098 "#); 1099 insta::assert_snapshot!(get_log_output(&work_dir), @r#" 1100 + @ kkmpptxzrspx false part 2 1101 + โ—‹ qpvuntsmwlqt false part 1 1102 โ—† zzzzzzzzzzzz true 1103 [EOF] 1104 ------- stderr ------- 1105 + Warning: Deprecated user-level config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 1106 [EOF] 1107 "#); 1108 } ··· 1120 let output = work_dir.run_jj(["split", "-m", "fix in file1", "file1"]); 1121 insta::assert_snapshot!(output, @r" 1122 ------- stderr ------- 1123 + Selected changes : qpvuntsm f2a70519 fix in file1 1124 + Remaining changes: kkmpptxz cac11766 my feature 1125 + Working copy (@) now at: kkmpptxz cac11766 my feature 1126 + Parent commit (@-) : qpvuntsm f2a70519 fix in file1 1127 [EOF] 1128 "); 1129 1130 insta::assert_snapshot!(get_log_output(&work_dir), @r" 1131 + @ kkmpptxzrspx false my feature 1132 + โ—‹ qpvuntsmwlqt false fix in file1 1133 โ—† zzzzzzzzzzzz true 1134 [EOF] 1135 "); ··· 1146 ]); 1147 insta::assert_snapshot!(output, @r" 1148 ------- stderr ------- 1149 + Selected changes : qpvuntsm d01cf12d fix in file1 1150 + Remaining changes: royxmykx b1556ed9 my feature 1151 + Working copy (@) now at: royxmykx b1556ed9 my feature 1152 + Parent commit (@-) : qpvuntsm d01cf12d fix in file1 1153 [EOF] 1154 "); 1155 1156 insta::assert_snapshot!(get_log_output(&work_dir), @r" 1157 + @ royxmykxtrkr false my feature 1158 + โ—‹ qpvuntsmwlqt false fix in file1 1159 โ”‚ 1160 โ”‚ CC: test.user@example.com 1161 โ—† zzzzzzzzzzzz true ··· 1470 .unwrap(); 1471 let output = main_dir.run_jj(["split", "file2"]); 1472 match bookmark_behavior { 1473 + BookmarkBehavior::LeaveBookmarkWithTarget => { 1474 insta::allow_duplicates! { 1475 insta::assert_snapshot!(output, @r#" 1476 ------- stderr ------- 1477 + Selected changes : qpvuntsm a481fe8a "*le-signet*" | first-commit 1478 + Remaining changes: mzvwutvl 5f597a6e second-commit 1479 + Working copy (@) now at: mzvwutvl 5f597a6e second-commit 1480 + Parent commit (@-) : qpvuntsm a481fe8a "*le-signet*" | first-commit 1481 [EOF] 1482 "#); 1483 } 1484 insta::allow_duplicates! { 1485 insta::assert_snapshot!(get_log_output(&main_dir), @r#" 1486 + @ mzvwutvlkqwt false second-commit 1487 + โ—‹ qpvuntsmwlqt false "*le-signet*" first-commit 1488 โ—† zzzzzzzzzzzz true 1489 [EOF] 1490 "#); 1491 } 1492 } 1493 + BookmarkBehavior::Default | BookmarkBehavior::MoveBookmarkToChild => { 1494 insta::allow_duplicates! { 1495 insta::assert_snapshot!(output, @r#" 1496 ------- stderr ------- ··· 1521 .unwrap(); 1522 main_dir.run_jj(["split", "file2", "--parallel"]).success(); 1523 match bookmark_behavior { 1524 + BookmarkBehavior::LeaveBookmarkWithTarget => { 1525 insta::allow_duplicates! { 1526 insta::assert_snapshot!(get_log_output(&main_dir), @r#" 1527 + @ vruxwmqvtpmx false second-commit 1528 + โ”‚ โ—‹ qpvuntsmwlqt false "*le-signet*" first-commit 1529 โ”œโ”€โ•ฏ 1530 โ—† zzzzzzzzzzzz true 1531 [EOF] 1532 "#); 1533 } 1534 } 1535 + BookmarkBehavior::Default | BookmarkBehavior::MoveBookmarkToChild => { 1536 insta::allow_duplicates! { 1537 insta::assert_snapshot!(get_log_output(&main_dir), @r#" 1538 @ vruxwmqvtpmx false "*le-signet*" second-commit
-775
cli/tests/test_squash_command.rs
··· 746 } 747 748 #[test] 749 - fn test_squash_working_copy_restore_descendants() { 750 - let test_env = TestEnvironment::default(); 751 - test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 752 - let work_dir = test_env.work_dir("repo"); 753 - 754 - // Create history like this: 755 - // Y 756 - // | 757 - // B X@ 758 - // |/ 759 - // A 760 - // 761 - // Each commit adds a file named the same as the commit 762 - let create_commit = |name: &str| { 763 - work_dir 764 - .run_jj(["bookmark", "create", "-r@", name]) 765 - .success(); 766 - work_dir.write_file(name, format!("test {name}\n")); 767 - }; 768 - 769 - create_commit("a"); 770 - work_dir.run_jj(["new"]).success(); 771 - create_commit("b"); 772 - work_dir.run_jj(["new", "a"]).success(); 773 - create_commit("x"); 774 - work_dir.run_jj(["new"]).success(); 775 - create_commit("y"); 776 - work_dir.run_jj(["edit", "x"]).success(); 777 - 778 - let template = r#"separate( 779 - " ", 780 - commit_id.short(), 781 - bookmarks, 782 - description, 783 - if(empty, "(empty)") 784 - )"#; 785 - let run_log = || work_dir.run_jj(["log", "-r=::", "--summary", "-T", template]); 786 - 787 - // Verify the setup 788 - insta::assert_snapshot!(run_log(), @r" 789 - โ—‹ 3f45d7a3ae69 y 790 - โ”‚ A y 791 - @ 5b4046443e64 x 792 - โ”‚ A x 793 - โ”‚ โ—‹ b1e1eea2f666 b 794 - โ”œโ”€โ•ฏ A b 795 - โ—‹ 7468364c89fc a 796 - โ”‚ A a 797 - โ—† 000000000000 (empty) 798 - [EOF] 799 - "); 800 - let output = work_dir.run_jj(["file", "list", "-r=a"]); 801 - insta::assert_snapshot!(output, @r" 802 - a 803 - [EOF] 804 - "); 805 - let output = work_dir.run_jj(["file", "list", "-r=b"]); 806 - insta::assert_snapshot!(output, @r" 807 - a 808 - b 809 - [EOF] 810 - "); 811 - let output = work_dir.run_jj(["file", "list"]); 812 - insta::assert_snapshot!(output, @r" 813 - a 814 - x 815 - [EOF] 816 - "); 817 - let output = work_dir.run_jj(["file", "list", "-r=y"]); 818 - insta::assert_snapshot!(output, @r" 819 - a 820 - x 821 - y 822 - [EOF] 823 - "); 824 - 825 - let output = work_dir.run_jj(["squash", "--restore-descendants"]); 826 - insta::assert_snapshot!(output, @r" 827 - ------- stderr ------- 828 - Rebased 2 descendant commits (while preserving their content) 829 - Working copy (@) now at: kxryzmor 7ec5499d (empty) (no description set) 830 - Parent commit (@-) : qpvuntsm 1c6a069e a x | (no description set) 831 - [EOF] 832 - "); 833 - insta::assert_snapshot!(run_log(), @r" 834 - @ 7ec5499d9141 (empty) 835 - โ”‚ โ—‹ ddfef0b279f8 y 836 - โ”œโ”€โ•ฏ A y 837 - โ”‚ โ—‹ 640ba5e85507 b 838 - โ”œโ”€โ•ฏ A b 839 - โ”‚ D x 840 - โ—‹ 1c6a069ec7e3 a x 841 - โ”‚ A a 842 - โ”‚ A x 843 - โ—† 000000000000 (empty) 844 - [EOF] 845 - "); 846 - 847 - let output = work_dir.run_jj(["diff", "--summary"]); 848 - // The current commit becomes empty. 849 - insta::assert_snapshot!(output, @""); 850 - // Should coincide with the working copy commit before 851 - let output = work_dir.run_jj(["file", "list", "-r=a"]); 852 - insta::assert_snapshot!(output, @r" 853 - a 854 - x 855 - [EOF] 856 - "); 857 - // Commit b should be the same as before 858 - let output = work_dir.run_jj(["file", "list", "-r=b"]); 859 - insta::assert_snapshot!(output, @r" 860 - a 861 - b 862 - [EOF] 863 - "); 864 - let output = work_dir.run_jj(["file", "list", "-r=y"]); 865 - insta::assert_snapshot!(output, @r" 866 - a 867 - x 868 - y 869 - [EOF] 870 - "); 871 - } 872 - 873 - #[test] 874 - fn test_squash_from_to_restore_descendants() { 875 - let test_env = TestEnvironment::default(); 876 - test_env.run_jj_in(".", ["git", "init", "repo"]).success(); 877 - let work_dir = test_env.work_dir("repo"); 878 - 879 - // Create history like this: 880 - // F 881 - // |\ 882 - // E C 883 - // | | 884 - // D B 885 - // |/ 886 - // A 887 - // 888 - // Each commit adds a file named the same as the commit 889 - let create_commit = |name: &str| { 890 - work_dir 891 - .run_jj(["bookmark", "create", "-r@", name]) 892 - .success(); 893 - work_dir.write_file(name, format!("test {name}\n")); 894 - }; 895 - 896 - create_commit("a"); 897 - work_dir.run_jj(["new"]).success(); 898 - create_commit("b"); 899 - work_dir.run_jj(["new"]).success(); 900 - create_commit("c"); 901 - work_dir.run_jj(["new", "a"]).success(); 902 - create_commit("d"); 903 - work_dir.run_jj(["new"]).success(); 904 - create_commit("e"); 905 - work_dir.run_jj(["new", "e", "c"]).success(); 906 - create_commit("f"); 907 - 908 - let template = r#"separate( 909 - " ", 910 - commit_id.short(), 911 - bookmarks, 912 - description, 913 - if(empty, "(empty)") 914 - )"#; 915 - let run_log = || work_dir.run_jj(["log", "-r=::", "--summary", "-T", template]); 916 - 917 - // ========== Part 1 ========= 918 - // Verify the setup 919 - insta::assert_snapshot!(run_log(), @r" 920 - @ 42acd0537c88 f 921 - โ”œโ”€โ•ฎ A f 922 - โ”‚ โ—‹ 4fb9706b0f47 c 923 - โ”‚ โ”‚ A c 924 - โ”‚ โ—‹ b1e1eea2f666 b 925 - โ”‚ โ”‚ A b 926 - โ—‹ โ”‚ b4e3197108ba e 927 - โ”‚ โ”‚ A e 928 - โ—‹ โ”‚ d707102f499f d 929 - โ”œโ”€โ•ฏ A d 930 - โ—‹ 7468364c89fc a 931 - โ”‚ A a 932 - โ—† 000000000000 (empty) 933 - [EOF] 934 - "); 935 - let beginning = work_dir.current_operation_id(); 936 - test_env.advance_test_rng_seed_to_multiple_of(200_000); 937 - 938 - // Squash without --restore-descendants for comparison 939 - work_dir 940 - .run_jj(["operation", "restore", &beginning]) 941 - .success(); 942 - let output = work_dir.run_jj(["squash", "--from=b", "--into=d"]); 943 - insta::assert_snapshot!(output, @r" 944 - ------- stderr ------- 945 - Rebased 3 descendant commits 946 - Working copy (@) now at: kpqxywon e462100a f | (no description set) 947 - Parent commit (@-) : yostqsxw 6944fd03 e | (no description set) 948 - Parent commit (@-) : mzvwutvl 6cd5d5c1 c | (no description set) 949 - [EOF] 950 - "); 951 - insta::assert_snapshot!(run_log(), @r" 952 - @ e462100ae7c3 f 953 - โ”œโ”€โ•ฎ A f 954 - โ”‚ โ—‹ 6cd5d5c1daf7 c 955 - โ”‚ โ”‚ A c 956 - โ—‹ โ”‚ 6944fd03dc5d e 957 - โ”‚ โ”‚ A e 958 - โ—‹ โ”‚ 1befcf027d1b d 959 - โ”œโ”€โ•ฏ A b 960 - โ”‚ A d 961 - โ—‹ 7468364c89fc a b 962 - โ”‚ A a 963 - โ—† 000000000000 (empty) 964 - [EOF] 965 - "); 966 - let output = work_dir.run_jj(["file", "list", "-r=d"]); 967 - insta::assert_snapshot!(output, @r" 968 - a 969 - b 970 - d 971 - [EOF] 972 - "); 973 - let output = work_dir.run_jj(["file", "list", "-r=c"]); 974 - insta::assert_snapshot!(output, @r" 975 - a 976 - c 977 - [EOF] 978 - "); 979 - let output = work_dir.run_jj(["file", "list", "-r=e"]); 980 - insta::assert_snapshot!(output, @r" 981 - a 982 - b 983 - d 984 - e 985 - [EOF] 986 - "); 987 - let output = work_dir.run_jj(["file", "list", "-r=f"]); 988 - insta::assert_snapshot!(output, @r" 989 - a 990 - b 991 - c 992 - d 993 - e 994 - f 995 - [EOF] 996 - "); 997 - 998 - // --restore-descendants 999 - work_dir 1000 - .run_jj(["operation", "restore", &beginning]) 1001 - .success(); 1002 - let output = work_dir.run_jj(["squash", "--from=b", "--into=d", "--restore-descendants"]); 1003 - insta::assert_snapshot!(output, @r" 1004 - ------- stderr ------- 1005 - Rebased 3 descendant commits (while preserving their content) 1006 - Working copy (@) now at: kpqxywon 1d64ccbf f | (no description set) 1007 - Parent commit (@-) : yostqsxw cb90d752 e | (no description set) 1008 - Parent commit (@-) : mzvwutvl 4e6702ae c | (no description set) 1009 - [EOF] 1010 - "); 1011 - // `d`` becomes the same as in the above example, 1012 - // but `c` does not lose file `b` and `e` still does not contain file `b` 1013 - // regardless of what happened to their parents. 1014 - insta::assert_snapshot!(run_log(), @r" 1015 - @ 1d64ccbf4608 f 1016 - โ”œโ”€โ•ฎ A f 1017 - โ”‚ โ—‹ 4e6702ae494c c 1018 - โ”‚ โ”‚ A b 1019 - โ”‚ โ”‚ A c 1020 - โ—‹ โ”‚ cb90d75271b4 e 1021 - โ”‚ โ”‚ D b 1022 - โ”‚ โ”‚ A e 1023 - โ—‹ โ”‚ 853ea07451aa d 1024 - โ”œโ”€โ•ฏ A b 1025 - โ”‚ A d 1026 - โ—‹ 7468364c89fc a b 1027 - โ”‚ A a 1028 - โ—† 000000000000 (empty) 1029 - [EOF] 1030 - "); 1031 - let output = work_dir.run_jj(["file", "list", "-r=d"]); 1032 - insta::assert_snapshot!(output, @r" 1033 - a 1034 - b 1035 - d 1036 - [EOF] 1037 - "); 1038 - let output = work_dir.run_jj(["file", "list", "-r=c"]); 1039 - insta::assert_snapshot!(output, @r" 1040 - a 1041 - b 1042 - c 1043 - [EOF] 1044 - "); 1045 - let output = work_dir.run_jj(["file", "list", "-r=e"]); 1046 - insta::assert_snapshot!(output, @r" 1047 - a 1048 - d 1049 - e 1050 - [EOF] 1051 - "); 1052 - let output = work_dir.run_jj(["file", "list", "-r=f"]); 1053 - insta::assert_snapshot!(output, @r" 1054 - a 1055 - b 1056 - c 1057 - d 1058 - e 1059 - f 1060 - [EOF] 1061 - "); 1062 - 1063 - // --restore-descendants works with --keep-emptied, same result except for 1064 - // leaving an empty commit 1065 - work_dir 1066 - .run_jj(["operation", "restore", &beginning]) 1067 - .success(); 1068 - let output = work_dir.run_jj([ 1069 - "squash", 1070 - "--from=b", 1071 - "--into=d", 1072 - "--restore-descendants", 1073 - "--keep-emptied", 1074 - ]); 1075 - insta::assert_snapshot!(output, @r" 1076 - ------- stderr ------- 1077 - Rebased 3 descendant commits (while preserving their content) 1078 - Working copy (@) now at: kpqxywon 3c13920f f | (no description set) 1079 - Parent commit (@-) : yostqsxw aa73012d e | (no description set) 1080 - Parent commit (@-) : mzvwutvl d323deaa c | (no description set) 1081 - [EOF] 1082 - "); 1083 - // `d`` becomes the same as in the above example, 1084 - // but `c` does not lose file `b` and `e` still does not contain file `b` 1085 - // regardless of what happened to their parents. 1086 - insta::assert_snapshot!(run_log(), @r" 1087 - @ 3c13920f1e9a f 1088 - โ”œโ”€โ•ฎ A f 1089 - โ”‚ โ—‹ d323deaa04c2 c 1090 - โ”‚ โ”‚ A b 1091 - โ”‚ โ”‚ A c 1092 - โ”‚ โ—‹ a55451e8808f b (empty) 1093 - โ—‹ โ”‚ aa73012df9cd e 1094 - โ”‚ โ”‚ D b 1095 - โ”‚ โ”‚ A e 1096 - โ—‹ โ”‚ d00e73142243 d 1097 - โ”œโ”€โ•ฏ A b 1098 - โ”‚ A d 1099 - โ—‹ 7468364c89fc a 1100 - โ”‚ A a 1101 - โ—† 000000000000 (empty) 1102 - [EOF] 1103 - "); 1104 - let output = work_dir.run_jj(["file", "list", "-r=d"]); 1105 - insta::assert_snapshot!(output, @r" 1106 - a 1107 - b 1108 - d 1109 - [EOF] 1110 - "); 1111 - let output = work_dir.run_jj(["file", "list", "-r=c"]); 1112 - insta::assert_snapshot!(output, @r" 1113 - a 1114 - b 1115 - c 1116 - [EOF] 1117 - "); 1118 - let output = work_dir.run_jj(["file", "list", "-r=e"]); 1119 - insta::assert_snapshot!(output, @r" 1120 - a 1121 - d 1122 - e 1123 - [EOF] 1124 - "); 1125 - 1126 - // ========== Part 2 ========= 1127 - // Reminder of the setup 1128 - test_env.advance_test_rng_seed_to_multiple_of(200_000); 1129 - work_dir 1130 - .run_jj(["operation", "restore", &beginning]) 1131 - .success(); 1132 - insta::assert_snapshot!(run_log(), @r" 1133 - @ 42acd0537c88 f 1134 - โ”œโ”€โ•ฎ A f 1135 - โ”‚ โ—‹ 4fb9706b0f47 c 1136 - โ”‚ โ”‚ A c 1137 - โ”‚ โ—‹ b1e1eea2f666 b 1138 - โ”‚ โ”‚ A b 1139 - โ—‹ โ”‚ b4e3197108ba e 1140 - โ”‚ โ”‚ A e 1141 - โ—‹ โ”‚ d707102f499f d 1142 - โ”œโ”€โ•ฏ A d 1143 - โ—‹ 7468364c89fc a 1144 - โ”‚ A a 1145 - โ—† 000000000000 (empty) 1146 - [EOF] 1147 - "); 1148 - let output = work_dir.run_jj(["file", "list", "-r=c"]); 1149 - insta::assert_snapshot!(output, @r" 1150 - a 1151 - b 1152 - c 1153 - [EOF] 1154 - "); 1155 - let output = work_dir.run_jj(["file", "list", "-r=d"]); 1156 - insta::assert_snapshot!(output, @r" 1157 - a 1158 - d 1159 - [EOF] 1160 - "); 1161 - 1162 - // --restore-descendants works when squashing from parent to child 1163 - work_dir 1164 - .run_jj(["operation", "restore", &beginning]) 1165 - .success(); 1166 - let output = work_dir.run_jj(["squash", "--from=a", "--into=b", "--restore-descendants"]); 1167 - insta::assert_snapshot!(output, @r" 1168 - ------- stderr ------- 1169 - Rebased 2 descendant commits (while preserving their content) 1170 - Working copy (@) now at: kpqxywon 7fa445c9 f | (no description set) 1171 - Parent commit (@-) : yostqsxw 102e6106 e | (no description set) 1172 - Parent commit (@-) : mzvwutvl a2ff7c27 c | (no description set) 1173 - [EOF] 1174 - "); 1175 - insta::assert_snapshot!(run_log(), @r" 1176 - @ 7fa445c9e606 f 1177 - โ”œโ”€โ•ฎ A f 1178 - โ”‚ โ—‹ a2ff7c27dbba c 1179 - โ”‚ โ”‚ A c 1180 - โ”‚ โ—‹ 2bf81678391c b 1181 - โ”‚ โ”‚ A a 1182 - โ”‚ โ”‚ A b 1183 - โ—‹ โ”‚ 102e61065eb2 e 1184 - โ”‚ โ”‚ A e 1185 - โ—‹ โ”‚ 7b1493a2027e d 1186 - โ”œโ”€โ•ฏ A a 1187 - โ”‚ A d 1188 - โ—† 000000000000 a (empty) 1189 - [EOF] 1190 - "); 1191 - let output = work_dir.run_jj(["file", "list", "-r=b"]); 1192 - insta::assert_snapshot!(output, @r" 1193 - a 1194 - b 1195 - [EOF] 1196 - "); 1197 - let output = work_dir.run_jj(["file", "list", "-r=c"]); 1198 - insta::assert_snapshot!(output, @r" 1199 - a 1200 - b 1201 - c 1202 - [EOF] 1203 - "); 1204 - let output = work_dir.run_jj(["file", "list", "-r=d"]); 1205 - insta::assert_snapshot!(output, @r" 1206 - a 1207 - d 1208 - [EOF] 1209 - "); 1210 - 1211 - // --restore-descendants --keep-emptied works when squashing from parent to 1212 - // child 1213 - work_dir 1214 - .run_jj(["operation", "restore", &beginning]) 1215 - .success(); 1216 - let output = work_dir.run_jj([ 1217 - "squash", 1218 - "--from=a", 1219 - "--into=b", 1220 - "--restore-descendants", 1221 - "--keep-emptied", 1222 - ]); 1223 - insta::assert_snapshot!(output, @r" 1224 - ------- stderr ------- 1225 - Rebased 2 descendant commits (while preserving their content) 1226 - Working copy (@) now at: kpqxywon 30c1ec1b f | (no description set) 1227 - Parent commit (@-) : yostqsxw c20a2a7a e | (no description set) 1228 - Parent commit (@-) : mzvwutvl 601223f5 c | (no description set) 1229 - [EOF] 1230 - "); 1231 - insta::assert_snapshot!(run_log(), @r" 1232 - @ 30c1ec1b6264 f 1233 - โ”œโ”€โ•ฎ A f 1234 - โ”‚ โ—‹ 601223f5faa8 c 1235 - โ”‚ โ”‚ A c 1236 - โ”‚ โ—‹ 28223a4af36c b 1237 - โ”‚ โ”‚ A a 1238 - โ”‚ โ”‚ A b 1239 - โ—‹ โ”‚ c20a2a7a24ba e 1240 - โ”‚ โ”‚ A e 1241 - โ—‹ โ”‚ a224ba6ebde8 d 1242 - โ”œโ”€โ•ฏ A a 1243 - โ”‚ A d 1244 - โ—‹ 367fe826e43e a (empty) 1245 - โ—† 000000000000 (empty) 1246 - [EOF] 1247 - "); 1248 - let output = work_dir.run_jj(["file", "list", "-r=b"]); 1249 - insta::assert_snapshot!(output, @r" 1250 - a 1251 - b 1252 - [EOF] 1253 - "); 1254 - let output = work_dir.run_jj(["file", "list", "-r=c"]); 1255 - insta::assert_snapshot!(output, @r" 1256 - a 1257 - b 1258 - c 1259 - [EOF] 1260 - "); 1261 - let output = work_dir.run_jj(["file", "list", "-r=d"]); 1262 - insta::assert_snapshot!(output, @r" 1263 - a 1264 - d 1265 - [EOF] 1266 - "); 1267 - 1268 - // --restore-descendants works when squashing from child to parent 1269 - work_dir 1270 - .run_jj(["operation", "restore", &beginning]) 1271 - .success(); 1272 - let output = work_dir.run_jj(["squash", "--from=b", "--into=a", "--restore-descendants"]); 1273 - insta::assert_snapshot!(output, @r" 1274 - ------- stderr ------- 1275 - Rebased 4 descendant commits (while preserving their content) 1276 - Working copy (@) now at: kpqxywon 6ad1c62a f | (no description set) 1277 - Parent commit (@-) : yostqsxw e259f026 e | (no description set) 1278 - Parent commit (@-) : mzvwutvl 36192c59 c | (no description set) 1279 - [EOF] 1280 - "); 1281 - insta::assert_snapshot!(run_log(), @r" 1282 - @ 6ad1c62aec5b f 1283 - โ”œโ”€โ•ฎ A b 1284 - โ”‚ โ”‚ A f 1285 - โ”‚ โ—‹ 36192c59f1e9 c 1286 - โ”‚ โ”‚ A c 1287 - โ—‹ โ”‚ e259f02633ca e 1288 - โ”‚ โ”‚ A e 1289 - โ—‹ โ”‚ 92943f1c8204 d 1290 - โ”œโ”€โ•ฏ D b 1291 - โ”‚ A d 1292 - โ—‹ 59aac8514774 a b 1293 - โ”‚ A a 1294 - โ”‚ A b 1295 - โ—† 000000000000 (empty) 1296 - [EOF] 1297 - "); 1298 - let output = work_dir.run_jj(["file", "list", "-r=b"]); 1299 - insta::assert_snapshot!(output, @r" 1300 - a 1301 - b 1302 - [EOF] 1303 - "); 1304 - let output = work_dir.run_jj(["file", "list", "-r=c"]); 1305 - insta::assert_snapshot!(output, @r" 1306 - a 1307 - b 1308 - c 1309 - [EOF] 1310 - "); 1311 - let output = work_dir.run_jj(["file", "list", "-r=d"]); 1312 - insta::assert_snapshot!(output, @r" 1313 - a 1314 - d 1315 - [EOF] 1316 - "); 1317 - 1318 - // same test, but with --keep-emptied 1319 - work_dir 1320 - .run_jj(["operation", "restore", &beginning]) 1321 - .success(); 1322 - let output = work_dir.run_jj([ 1323 - "squash", 1324 - "--from=b", 1325 - "--into=a", 1326 - "--keep-emptied", 1327 - "--restore-descendants", 1328 - ]); 1329 - insta::assert_snapshot!(output, @r" 1330 - ------- stderr ------- 1331 - Rebased 5 descendant commits (while preserving their content) 1332 - Working copy (@) now at: kpqxywon 6eadede0 f | (no description set) 1333 - Parent commit (@-) : yostqsxw 97233b50 e | (no description set) 1334 - Parent commit (@-) : mzvwutvl 5b2d6858 c | (no description set) 1335 - [EOF] 1336 - "); 1337 - // BUG! b should now be empty! 1338 - insta::assert_snapshot!(run_log(), @r" 1339 - @ 6eadede086b1 f 1340 - โ”œโ”€โ•ฎ A b 1341 - โ”‚ โ”‚ A f 1342 - โ”‚ โ—‹ 5b2d685868b7 c 1343 - โ”‚ โ”‚ A b 1344 - โ”‚ โ”‚ A c 1345 - โ”‚ โ—‹ 904dac9cd09e b 1346 - โ”‚ โ”‚ D b 1347 - โ—‹ โ”‚ 97233b506c11 e 1348 - โ”‚ โ”‚ A e 1349 - โ—‹ โ”‚ 8cbe1a629aed d 1350 - โ”œโ”€โ•ฏ D b 1351 - โ”‚ A d 1352 - โ—‹ c1fbbbe74a28 a 1353 - โ”‚ A a 1354 - โ”‚ A b 1355 - โ—† 000000000000 (empty) 1356 - [EOF] 1357 - "); 1358 - let output = work_dir.run_jj(["file", "list", "-r=b"]); 1359 - insta::assert_snapshot!(output, @r" 1360 - a 1361 - [EOF] 1362 - "); 1363 - let output = work_dir.run_jj(["file", "list", "-r=c"]); 1364 - insta::assert_snapshot!(output, @r" 1365 - a 1366 - b 1367 - c 1368 - [EOF] 1369 - "); 1370 - let output = work_dir.run_jj(["file", "list", "-r=d"]); 1371 - insta::assert_snapshot!(output, @r" 1372 - a 1373 - d 1374 - [EOF] 1375 - "); 1376 - 1377 - // ========== Part 3 ========= 1378 - // Reminder of the setup 1379 - test_env.advance_test_rng_seed_to_multiple_of(200_000); 1380 - work_dir 1381 - .run_jj(["operation", "restore", &beginning]) 1382 - .success(); 1383 - insta::assert_snapshot!(run_log(), @r" 1384 - @ 42acd0537c88 f 1385 - โ”œโ”€โ•ฎ A f 1386 - โ”‚ โ—‹ 4fb9706b0f47 c 1387 - โ”‚ โ”‚ A c 1388 - โ”‚ โ—‹ b1e1eea2f666 b 1389 - โ”‚ โ”‚ A b 1390 - โ—‹ โ”‚ b4e3197108ba e 1391 - โ”‚ โ”‚ A e 1392 - โ—‹ โ”‚ d707102f499f d 1393 - โ”œโ”€โ•ฏ A d 1394 - โ—‹ 7468364c89fc a 1395 - โ”‚ A a 1396 - โ—† 000000000000 (empty) 1397 - [EOF] 1398 - "); 1399 - let output = work_dir.run_jj(["file", "list", "-r=d"]); 1400 - insta::assert_snapshot!(output, @r" 1401 - a 1402 - d 1403 - [EOF] 1404 - "); 1405 - let output = work_dir.run_jj(["file", "list", "-r=f"]); 1406 - insta::assert_snapshot!(output, @r" 1407 - a 1408 - b 1409 - c 1410 - d 1411 - e 1412 - f 1413 - [EOF] 1414 - "); 1415 - 1416 - // --restore-descendants works when squashing from grandchild to grandparent 1417 - work_dir 1418 - .run_jj(["operation", "restore", &beginning]) 1419 - .success(); 1420 - let output = work_dir.run_jj(["squash", "--from=e", "--into=a", "--restore-descendants"]); 1421 - insta::assert_snapshot!(output, @r" 1422 - ------- stderr ------- 1423 - Rebased 4 descendant commits (while preserving their content) 1424 - Working copy (@) now at: kpqxywon 6d14c928 f | (no description set) 1425 - Parent commit (@-) : yqosqzyt ab775412 d e | (no description set) 1426 - Parent commit (@-) : mzvwutvl 175aa1f2 c | (no description set) 1427 - [EOF] 1428 - "); 1429 - insta::assert_snapshot!(run_log(), @r" 1430 - @ 6d14c928f32e f 1431 - โ”œโ”€โ•ฎ A e 1432 - โ”‚ โ”‚ A f 1433 - โ”‚ โ—‹ 175aa1f28a05 c 1434 - โ”‚ โ”‚ A c 1435 - โ”‚ โ—‹ d1076aeca3e6 b 1436 - โ”‚ โ”‚ A b 1437 - โ”‚ โ”‚ D e 1438 - โ—‹ โ”‚ ab7754126332 d e 1439 - โ”œโ”€โ•ฏ A d 1440 - โ”‚ D e 1441 - โ—‹ 4644e0c16443 a 1442 - โ”‚ A a 1443 - โ”‚ A e 1444 - โ—† 000000000000 (empty) 1445 - [EOF] 1446 - "); 1447 - let output = work_dir.run_jj(["file", "list", "-r=b"]); 1448 - insta::assert_snapshot!(output, @r" 1449 - a 1450 - b 1451 - [EOF] 1452 - "); 1453 - let output = work_dir.run_jj(["file", "list", "-r=d"]); 1454 - insta::assert_snapshot!(output, @r" 1455 - a 1456 - d 1457 - [EOF] 1458 - "); 1459 - let output = work_dir.run_jj(["file", "list", "-r=f"]); 1460 - insta::assert_snapshot!(output, @r" 1461 - a 1462 - b 1463 - c 1464 - d 1465 - e 1466 - f 1467 - [EOF] 1468 - "); 1469 - 1470 - // --restore-descendants works when squashing from grandparent to grandchild 1471 - work_dir 1472 - .run_jj(["operation", "restore", &beginning]) 1473 - .success(); 1474 - let output = work_dir.run_jj(["squash", "--from=a", "--into=e", "--restore-descendants"]); 1475 - insta::assert_snapshot!(output, @r" 1476 - ------- stderr ------- 1477 - Rebased 1 descendant commits (while preserving their content) 1478 - Working copy (@) now at: kpqxywon 94ad7042 f | (no description set) 1479 - Parent commit (@-) : yostqsxw 582d640e e | (no description set) 1480 - Parent commit (@-) : mzvwutvl 2214436c c | (no description set) 1481 - [EOF] 1482 - "); 1483 - insta::assert_snapshot!(run_log(), @r" 1484 - @ 94ad70428c4a f 1485 - โ”œโ”€โ•ฎ A f 1486 - โ”‚ โ—‹ 2214436c3fa7 c 1487 - โ”‚ โ”‚ A c 1488 - โ”‚ โ—‹ a469c893f362 b 1489 - โ”‚ โ”‚ A a 1490 - โ”‚ โ”‚ A b 1491 - โ—‹ โ”‚ 582d640e331f e 1492 - โ”‚ โ”‚ A e 1493 - โ—‹ โ”‚ 93671eb30330 d 1494 - โ”œโ”€โ•ฏ A a 1495 - โ”‚ A d 1496 - โ—† 000000000000 a (empty) 1497 - [EOF] 1498 - "); 1499 - let output = work_dir.run_jj(["file", "list", "-r=b"]); 1500 - insta::assert_snapshot!(output, @r" 1501 - a 1502 - b 1503 - [EOF] 1504 - "); 1505 - let output = work_dir.run_jj(["file", "list", "-r=d"]); 1506 - insta::assert_snapshot!(output, @r" 1507 - a 1508 - d 1509 - [EOF] 1510 - "); 1511 - let output = work_dir.run_jj(["file", "list", "-r=f"]); 1512 - insta::assert_snapshot!(output, @r" 1513 - a 1514 - b 1515 - c 1516 - d 1517 - e 1518 - f 1519 - [EOF] 1520 - "); 1521 - } 1522 - 1523 - #[test] 1524 fn test_squash_from_multiple() { 1525 let test_env = TestEnvironment::default(); 1526 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
··· 746 } 747 748 #[test] 749 fn test_squash_from_multiple() { 750 let test_env = TestEnvironment::default(); 751 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
+8 -3
docs/releasing.md
··· 13 to run something like this: 14 15 ```shell 16 - root=$(jj log --no-graph -r 'heads(tags(glob:"v*.*.*") & ::trunk())' -T 'commit_id') 17 - gh api "/repos/jj-vcs/jj/compare/$root...main" --paginate \ 18 - | jq -r '.commits[] | select(.author.login | endswith("[bot]") | not) | "* " + .commit.author.name + " (@" + .author.login + ")"' | sort -fu 19 ``` 20 21 https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#compare-two-commits
··· 13 to run something like this: 14 15 ```shell 16 + root=$(jj log --no-graph -r 'heads(tags(glob:"v*.*.*") & ::trunk())' -T commit_id) 17 + filter=' 18 + map(.commits[] | select(.author.login | endswith("[bot]") | not)) 19 + | unique_by(.author.login) 20 + | map("* \(.commit.author.name) (@\(.author.login))")) 21 + | .[] 22 + ' 23 + gh api "/repos/jj-vcs/jj/compare/$root...main" --paginate | jq -sr "$filter" | sort -f 24 ``` 25 26 https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#compare-two-commits
+28 -9
lib/src/config_resolver.rs
··· 27 use crate::config::ConfigGetError; 28 use crate::config::ConfigLayer; 29 use crate::config::ConfigNamePathBuf; 30 use crate::config::ConfigUpdateError; 31 use crate::config::ConfigValue; 32 use crate::config::StackedConfig; ··· 367 pub fn migrate( 368 config: &mut StackedConfig, 369 rules: &[ConfigMigrationRule], 370 - ) -> Result<Vec<String>, ConfigMigrateError> { 371 let mut descriptions = Vec::new(); 372 for layer in config.layers_mut() { 373 migrate_layer(layer, rules, &mut descriptions) ··· 379 fn migrate_layer( 380 layer: &mut Arc<ConfigLayer>, 381 rules: &[ConfigMigrationRule], 382 - descriptions: &mut Vec<String>, 383 ) -> Result<(), ConfigMigrateLayerError> { 384 let rules_to_apply = rules 385 .iter() ··· 391 let layer_mut = Arc::make_mut(layer); 392 for rule in rules_to_apply { 393 let desc = rule.apply(layer_mut)?; 394 - descriptions.push(desc); 395 } 396 Ok(()) 397 } ··· 862 let descriptions = migrate(&mut config, &rules).unwrap(); 863 insta::assert_debug_snapshot!(descriptions, @r#" 864 [ 865 - "foo.old is renamed to foo.new", 866 - "bar.old is deleted (superseded by baz.new)", 867 - "bar.old is renamed to baz.new", 868 ] 869 "#); 870 insta::assert_snapshot!(config.layers()[0].data, @r" ··· 913 let descriptions = migrate(&mut config, &rules).unwrap(); 914 insta::assert_debug_snapshot!(descriptions, @r#" 915 [ 916 - "foo.old is updated to foo.new = ['foo.old #0']", 917 - "bar.old is deleted (superseded by baz.new)", 918 - "bar.old is updated to baz.new = \"bar.old #1 updated\"", 919 ] 920 "#); 921 insta::assert_snapshot!(config.layers()[0].data, @r"
··· 27 use crate::config::ConfigGetError; 28 use crate::config::ConfigLayer; 29 use crate::config::ConfigNamePathBuf; 30 + use crate::config::ConfigSource; 31 use crate::config::ConfigUpdateError; 32 use crate::config::ConfigValue; 33 use crate::config::StackedConfig; ··· 368 pub fn migrate( 369 config: &mut StackedConfig, 370 rules: &[ConfigMigrationRule], 371 + ) -> Result<Vec<(ConfigSource, String)>, ConfigMigrateError> { 372 let mut descriptions = Vec::new(); 373 for layer in config.layers_mut() { 374 migrate_layer(layer, rules, &mut descriptions) ··· 380 fn migrate_layer( 381 layer: &mut Arc<ConfigLayer>, 382 rules: &[ConfigMigrationRule], 383 + descriptions: &mut Vec<(ConfigSource, String)>, 384 ) -> Result<(), ConfigMigrateLayerError> { 385 let rules_to_apply = rules 386 .iter() ··· 392 let layer_mut = Arc::make_mut(layer); 393 for rule in rules_to_apply { 394 let desc = rule.apply(layer_mut)?; 395 + descriptions.push((layer_mut.source, desc)); 396 } 397 Ok(()) 398 } ··· 863 let descriptions = migrate(&mut config, &rules).unwrap(); 864 insta::assert_debug_snapshot!(descriptions, @r#" 865 [ 866 + ( 867 + User, 868 + "foo.old is renamed to foo.new", 869 + ), 870 + ( 871 + User, 872 + "bar.old is deleted (superseded by baz.new)", 873 + ), 874 + ( 875 + User, 876 + "bar.old is renamed to baz.new", 877 + ), 878 ] 879 "#); 880 insta::assert_snapshot!(config.layers()[0].data, @r" ··· 923 let descriptions = migrate(&mut config, &rules).unwrap(); 924 insta::assert_debug_snapshot!(descriptions, @r#" 925 [ 926 + ( 927 + User, 928 + "foo.old is updated to foo.new = ['foo.old #0']", 929 + ), 930 + ( 931 + User, 932 + "bar.old is deleted (superseded by baz.new)", 933 + ), 934 + ( 935 + User, 936 + "bar.old is updated to baz.new = \"bar.old #1 updated\"", 937 + ), 938 ] 939 "#); 940 insta::assert_snapshot!(config.layers()[0].data, @r"
+40 -26
lib/src/default_index/store.rs
··· 15 #![allow(missing_docs)] 16 17 use std::any::Any; 18 - use std::collections::HashSet; 19 use std::fs; 20 use std::io; 21 use std::io::Write as _; 22 use std::path::Path; 23 use std::path::PathBuf; 24 use std::sync::Arc; 25 26 use itertools::Itertools as _; ··· 49 use crate::object_id::ObjectId as _; 50 use crate::op_store::OpStoreError; 51 use crate::op_store::OperationId; 52 use crate::operation::Operation; 53 use crate::store::Store; 54 ··· 185 operation: &Operation, 186 store: &Arc<Store>, 187 ) -> Result<Arc<ReadonlyIndexSegment>, DefaultIndexStoreError> { 188 - let view = operation.view()?; 189 let operations_dir = self.operations_dir(); 190 let commit_id_length = store.commit_id_length(); 191 let change_id_length = store.change_id_length(); 192 - let mut visited_heads: HashSet<CommitId> = 193 - view.all_referenced_commit_ids().cloned().collect(); 194 - let mut historical_heads: Vec<(CommitId, OperationId)> = visited_heads 195 .iter() 196 - .map(|commit_id| (commit_id.clone(), operation.id().clone())) 197 - .collect(); 198 - let mut parent_op_id: Option<OperationId> = None; 199 - for op in dag_walk::dfs_ok( 200 - [Ok(operation.clone())], 201 - |op: &Operation| op.id().clone(), 202 - |op: &Operation| op.parents().collect_vec(), 203 - ) { 204 - let op = op?; 205 - // Pick the latest existing ancestor operation as the parent 206 - // segment. Perhaps, breadth-first search is more appropriate here, 207 - // but that wouldn't matter in practice as the operation log is 208 - // mostly linear. 209 - if parent_op_id.is_none() && operations_dir.join(op.id().hex()).is_file() { 210 - parent_op_id = Some(op.id().clone()); 211 } 212 - // TODO: no need to walk ancestors of the parent_op_id operation 213 for commit_id in op.view()?.all_referenced_commit_ids() { 214 - if visited_heads.insert(commit_id.clone()) { 215 - historical_heads.push((commit_id.clone(), op.id().clone())); 216 } 217 } 218 } 219 let maybe_parent_file; 220 let mut mutable_index; 221 - match parent_op_id { 222 None => { 223 maybe_parent_file = None; 224 mutable_index = DefaultMutableIndex::full(commit_id_length, change_id_length); 225 } 226 - Some(parent_op_id) => { 227 let parent_file = self.load_index_segments_at_operation( 228 - &parent_op_id, 229 commit_id_length, 230 change_id_length, 231 )?;
··· 15 #![allow(missing_docs)] 16 17 use std::any::Any; 18 + use std::collections::HashMap; 19 use std::fs; 20 use std::io; 21 use std::io::Write as _; 22 use std::path::Path; 23 use std::path::PathBuf; 24 + use std::slice; 25 use std::sync::Arc; 26 27 use itertools::Itertools as _; ··· 50 use crate::object_id::ObjectId as _; 51 use crate::op_store::OpStoreError; 52 use crate::op_store::OperationId; 53 + use crate::op_walk; 54 use crate::operation::Operation; 55 use crate::store::Store; 56 ··· 187 operation: &Operation, 188 store: &Arc<Store>, 189 ) -> Result<Arc<ReadonlyIndexSegment>, DefaultIndexStoreError> { 190 + tracing::info!("scanning operations to index"); 191 let operations_dir = self.operations_dir(); 192 let commit_id_length = store.commit_id_length(); 193 let change_id_length = store.change_id_length(); 194 + let ops_to_visit: Vec<_> = 195 + op_walk::walk_ancestors(slice::from_ref(operation)).try_collect()?; 196 + // Pick the latest existing ancestor operation as the parent segment. 197 + let parent_op = ops_to_visit 198 .iter() 199 + .find(|op| operations_dir.join(op.id().hex()).is_file()) 200 + .cloned(); 201 + // Remove ancestors of the latest existing operation, which should have 202 + // been indexed in the parent segment. This could be optimized for 203 + // linear history, but parent_op is often None. 204 + let ops_to_visit = if let Some(op) = &parent_op { 205 + let mut wanted_ops: HashMap<&OperationId, &Operation> = 206 + ops_to_visit.iter().map(|op| (op.id(), op)).collect(); 207 + let mut work = vec![op.id()]; 208 + while let Some(id) = work.pop() { 209 + if let Some(op) = wanted_ops.remove(id) { 210 + work.extend(op.parent_ids()); 211 + } 212 } 213 + ops_to_visit 214 + .iter() 215 + .filter(|op| wanted_ops.contains_key(op.id())) 216 + .cloned() 217 + .collect() 218 + } else { 219 + ops_to_visit 220 + }; 221 + tracing::info!( 222 + ops_count = ops_to_visit.len(), 223 + "collecting head commits to index" 224 + ); 225 + let mut historical_heads: HashMap<CommitId, OperationId> = HashMap::new(); 226 + for op in &ops_to_visit { 227 for commit_id in op.view()?.all_referenced_commit_ids() { 228 + if !historical_heads.contains_key(commit_id) { 229 + historical_heads.insert(commit_id.clone(), op.id().clone()); 230 } 231 } 232 } 233 let maybe_parent_file; 234 let mut mutable_index; 235 + match &parent_op { 236 None => { 237 maybe_parent_file = None; 238 mutable_index = DefaultMutableIndex::full(commit_id_length, change_id_length); 239 } 240 + Some(op) => { 241 let parent_file = self.load_index_segments_at_operation( 242 + op.id(), 243 commit_id_length, 244 change_id_length, 245 )?;
+4 -19
lib/src/repo.rs
··· 1406 /// The content of those descendants will remain untouched. 1407 /// Returns the number of reparented descendants. 1408 pub fn reparent_descendants(&mut self) -> BackendResult<usize> { 1409 let mut num_reparented = 0; 1410 - self.reparent_descendants_with_progress(|_, _| { 1411 - num_reparented += 1; 1412 - })?; 1413 - Ok(num_reparented) 1414 - } 1415 - 1416 - /// Reparent descendants, and call the provided function for each moved 1417 - /// commit 1418 - /// 1419 - /// The function takes the old commit and the reparented commit. 1420 - pub fn reparent_descendants_with_progress( 1421 - &mut self, 1422 - mut progress: impl FnMut(Commit, Commit), 1423 - ) -> BackendResult<()> { 1424 - let roots = self.parent_mapping.keys().cloned().collect_vec(); 1425 self.transform_descendants(roots, |rewriter| { 1426 if rewriter.parents_changed() { 1427 - let old_commit = rewriter.old_commit().clone(); 1428 let builder = rewriter.reparent(); 1429 - let reparented_commit = builder.write()?; 1430 - progress(old_commit, reparented_commit); 1431 } 1432 Ok(()) 1433 })?; 1434 self.parent_mapping.clear(); 1435 - Ok(()) 1436 } 1437 1438 pub fn set_wc_commit(
··· 1406 /// The content of those descendants will remain untouched. 1407 /// Returns the number of reparented descendants. 1408 pub fn reparent_descendants(&mut self) -> BackendResult<usize> { 1409 + let roots = self.parent_mapping.keys().cloned().collect_vec(); 1410 let mut num_reparented = 0; 1411 self.transform_descendants(roots, |rewriter| { 1412 if rewriter.parents_changed() { 1413 let builder = rewriter.reparent(); 1414 + builder.write()?; 1415 + num_reparented += 1; 1416 } 1417 Ok(()) 1418 })?; 1419 self.parent_mapping.clear(); 1420 + Ok(num_reparented) 1421 } 1422 1423 pub fn set_wc_commit(
+11 -29
lib/src/rewrite.rs
··· 1109 pub abandoned_commits: Vec<Commit>, 1110 } 1111 1112 - #[derive(Clone, Debug)] 1113 - pub struct SquashOptions { 1114 - pub keep_emptied: bool, 1115 - pub restore_descendants: bool, 1116 - } 1117 - 1118 /// Squash `sources` into `destination` and return a [`SquashedCommit`] for the 1119 /// resulting commit. Caller is responsible for setting the description and 1120 /// finishing the commit. ··· 1122 repo: &'repo mut MutableRepo, 1123 sources: &[CommitWithSelection], 1124 destination: &Commit, 1125 - SquashOptions { 1126 - keep_emptied, 1127 - restore_descendants, 1128 - }: SquashOptions, 1129 ) -> BackendResult<Option<SquashedCommit<'repo>>> { 1130 struct SourceCommit<'a> { 1131 commit: &'a CommitWithSelection, ··· 1178 // rewritten sources. Otherwise it will likely already have the content 1179 // changes we're moving, so applying them will have no effect and the 1180 // changes will disappear. 1181 - if restore_descendants { 1182 - repo.reparent_descendants_with_progress(|old_commit, rebased_commit| { 1183 - if old_commit.id() != destination.id() { 1184 - return; 1185 - } 1186 - rewritten_destination = rebased_commit; 1187 - })?; 1188 - } else { 1189 - let options = RebaseOptions::default(); 1190 - repo.rebase_descendants_with_options(&options, |old_commit, rebased_commit| { 1191 - if old_commit.id() != destination.id() { 1192 - return; 1193 - } 1194 - rewritten_destination = match rebased_commit { 1195 - RebasedCommit::Rewritten(commit) => commit, 1196 - RebasedCommit::Abandoned { .. } => panic!("all commits should be kept"), 1197 - }; 1198 - })?; 1199 - } 1200 } 1201 // Apply the selected changes onto the destination 1202 let mut destination_tree = rewritten_destination.tree()?;
··· 1109 pub abandoned_commits: Vec<Commit>, 1110 } 1111 1112 /// Squash `sources` into `destination` and return a [`SquashedCommit`] for the 1113 /// resulting commit. Caller is responsible for setting the description and 1114 /// finishing the commit. ··· 1116 repo: &'repo mut MutableRepo, 1117 sources: &[CommitWithSelection], 1118 destination: &Commit, 1119 + keep_emptied: bool, 1120 ) -> BackendResult<Option<SquashedCommit<'repo>>> { 1121 struct SourceCommit<'a> { 1122 commit: &'a CommitWithSelection, ··· 1169 // rewritten sources. Otherwise it will likely already have the content 1170 // changes we're moving, so applying them will have no effect and the 1171 // changes will disappear. 1172 + let options = RebaseOptions::default(); 1173 + repo.rebase_descendants_with_options(&options, |old_commit, rebased_commit| { 1174 + if old_commit.id() != destination.id() { 1175 + return; 1176 + } 1177 + rewritten_destination = match rebased_commit { 1178 + RebasedCommit::Rewritten(commit) => commit, 1179 + RebasedCommit::Abandoned { .. } => panic!("all commits should be kept"), 1180 + }; 1181 + })?; 1182 } 1183 // Apply the selected changes onto the destination 1184 let mut destination_tree = rewritten_destination.tree()?;