just playing with tangled

Compare changes

Choose any two refs to compare.

+8 -7
CHANGELOG.md
··· 67 67 * Templates can now do arithmetic on integers with the `+`, `-`, `*`, `/`, and `%` 68 68 infix operators. 69 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 + 70 75 * Evolution history is now stored in the operation log. `jj evolog` can show 71 76 associated operations for commits created by new jj. 72 77 ··· 78 83 79 84 * `jj parallelize` can now parallelize groups of changes that _start_ with an 80 85 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 86 85 87 ### Packaging changes 86 88 ··· 118 120 `template-aliases.default_commit_description`. Please also consider using 119 121 [`templates.draft_commit_description`](docs/config.md#default-description), 120 122 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 123 126 124 ### New features 127 125 ··· 326 324 327 325 * The 'how to resolve conflicts' hint that is shown when conflicts appear can 328 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. 329 330 330 331 * `jj op diff` and `jj op log --op-diff` now show changes to which commits 331 332 correspond to working copies.
+6 -2
cli/build.rs
··· 61 61 "log", 62 62 "--no-graph", 63 63 "-r=@-", 64 - "-T=commit_id", 64 + "-T=commit_id ++ '-'", 65 65 ]) 66 66 .output() 67 67 { 68 68 if output.status.success() { 69 - return Some(String::from_utf8(output.stdout).unwrap()); 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); 70 74 } 71 75 } 72 76
+3 -13
cli/src/cli_util.rs
··· 3858 3858 "Did you update to a commit where the directory doesn't exist?", 3859 3859 ) 3860 3860 })?; 3861 - let mut config_env = ConfigEnv::from_environment(ui); 3861 + let mut config_env = ConfigEnv::from_environment(); 3862 3862 let mut last_config_migration_descriptions = Vec::new(); 3863 3863 let mut migrate_config = |config: &mut StackedConfig| -> Result<(), CommandError> { 3864 3864 last_config_migration_descriptions = ··· 3937 3937 ui.reset(&config)?; 3938 3938 3939 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 - )?; 3940 + for desc in &last_config_migration_descriptions { 3941 + writeln!(ui.warning_default(), "Deprecated config: {desc}")?; 3952 3942 } 3953 3943 3954 3944 if args.global_args.repository.is_some() {
+6 -1
cli/src/commands/commit.rs
··· 12 12 // See the License for the specific language governing permissions and 13 13 // limitations under the License. 14 14 15 + use clap_complete::ArgValueCandidates; 15 16 use clap_complete::ArgValueCompleter; 16 17 use indoc::writedoc; 17 18 use jj_lib::backend::Signature; ··· 37 38 #[arg(short, long)] 38 39 interactive: bool, 39 40 /// Specify diff editor to be used (implies --interactive) 40 - #[arg(long, value_name = "NAME")] 41 + #[arg( 42 + long, 43 + value_name = "NAME", 44 + add = ArgValueCandidates::new(complete::diff_editors), 45 + )] 41 46 tool: Option<String>, 42 47 /// The change description to use (don't open editor) 43 48 #[arg(long = "message", short, value_name = "MESSAGE")]
+6 -1
cli/src/commands/diffedit.rs
··· 14 14 15 15 use std::io::Write as _; 16 16 17 + use clap_complete::ArgValueCandidates; 17 18 use clap_complete::ArgValueCompleter; 18 19 use itertools::Itertools as _; 19 20 use jj_lib::matchers::EverythingMatcher; ··· 77 78 )] 78 79 to: Option<RevisionArg>, 79 80 /// Specify diff editor to be used 80 - #[arg(long, value_name = "NAME")] 81 + #[arg( 82 + long, 83 + value_name = "NAME", 84 + add = ArgValueCandidates::new(complete::diff_editors), 85 + )] 81 86 tool: Option<String>, 82 87 /// Preserve the content (not the diff) when rebasing descendants 83 88 ///
+60 -9
cli/src/commands/evolog.rs
··· 19 19 use clap_complete::ArgValueCompleter; 20 20 use itertools::Itertools as _; 21 21 use jj_lib::commit::Commit; 22 + use jj_lib::copies::CopyRecords; 22 23 use jj_lib::evolution::walk_predecessors; 23 24 use jj_lib::graph::reverse_graph; 24 25 use jj_lib::graph::GraphEdge; 25 26 use jj_lib::matchers::EverythingMatcher; 27 + use jj_lib::repo::Repo as _; 28 + use jj_lib::rewrite::merge_commit_trees; 26 29 use tracing::instrument; 27 30 28 31 use super::log::get_node_template; ··· 32 35 use crate::cli_util::RevisionArg; 33 36 use crate::command_error::CommandError; 34 37 use crate::complete; 38 + use crate::diff_util::get_copy_records; 35 39 use crate::diff_util::DiffFormatArgs; 36 40 use crate::graphlog::get_graphlog; 37 41 use crate::graphlog::GraphStyle; ··· 85 89 /// contaminated by unrelated changes. 86 90 #[arg(long, short = 'p')] 87 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, 88 113 #[command(flatten)] 89 114 diff_format: DiffFormatArgs, 90 115 } ··· 99 124 100 125 let start_commit = workspace_command.resolve_single_rev(ui, &args.revision)?; 101 126 102 - let diff_renderer = workspace_command.diff_renderer_for_log(&args.diff_format, args.patch)?; 127 + let diff_renderer = workspace_command 128 + .diff_renderer_for_log(&args.diff_format, args.patch || args.diff_snapshots)?; 103 129 let graph_style = GraphStyle::from_settings(workspace_command.settings())?; 104 130 let with_content_format = LogContentFormat::new(ui, workspace_command.settings())?; 105 131 ··· 173 199 if let Some(renderer) = &diff_renderer { 174 200 let predecessors: Vec<_> = entry.predecessors().try_collect()?; 175 201 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 - )?; 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 + } 184 234 } 185 235 let node_symbol = format_template(ui, &Some(entry.commit.clone()), &node_template); 186 236 graph.add_node( ··· 212 262 if let Some(renderer) = &diff_renderer { 213 263 let predecessors: Vec<_> = entry.predecessors().try_collect()?; 214 264 let width = ui.term_width(); 265 + // TODO 215 266 renderer.show_inter_diff( 216 267 ui, 217 268 formatter,
+10 -4
cli/src/commands/git/fetch.rs
··· 189 189 branches: &[StringPattern], 190 190 remotes: &[&RemoteName], 191 191 ) -> Result<(), CommandError> { 192 + let mut missing_branches = vec![]; 192 193 for branch in branches { 193 194 let matches = remotes.iter().any(|&remote| { 194 195 let remote = StringPattern::exact(remote); ··· 205 206 .is_some() 206 207 }); 207 208 if !matches { 208 - writeln!( 209 - ui.warning_default(), 210 - "No branch matching `{branch}` found on any specified/configured remote", 211 - )?; 209 + missing_branches.push(branch); 212 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 + )?; 213 219 } 214 220 215 221 Ok(())
+7 -1
cli/src/commands/resolve.rs
··· 12 12 // See the License for the specific language governing permissions and 13 13 // limitations under the License. 14 14 15 + use clap_complete::ArgValueCandidates; 15 16 use clap_complete::ArgValueCompleter; 16 17 use itertools::Itertools as _; 17 18 use jj_lib::object_id::ObjectId as _; ··· 61 62 /// 62 63 /// The built-in merge tools `:ours` and `:theirs` can be used to choose 63 64 /// side #1 and side #2 of the conflict respectively. 64 - #[arg(long, conflicts_with = "list", value_name = "NAME")] 65 + #[arg( 66 + long, 67 + conflicts_with = "list", 68 + value_name = "NAME", 69 + add = ArgValueCandidates::new(complete::merge_tools), 70 + )] 65 71 tool: Option<String>, 66 72 /// Only resolve conflicts in these paths. You can use the `--list` argument 67 73 /// to find paths to use here.
+6 -1
cli/src/commands/restore.rs
··· 14 14 15 15 use std::io::Write as _; 16 16 17 + use clap_complete::ArgValueCandidates; 17 18 use clap_complete::ArgValueCompleter; 18 19 use indoc::formatdoc; 19 20 use itertools::Itertools as _; ··· 96 97 #[arg(long, short)] 97 98 interactive: bool, 98 99 /// Specify diff editor to be used (implies --interactive) 99 - #[arg(long, value_name = "NAME")] 100 + #[arg( 101 + long, 102 + value_name = "NAME", 103 + add = ArgValueCandidates::new(complete::diff_editors), 104 + )] 100 105 tool: Option<String>, 101 106 /// Preserve the content (not the diff) when rebasing descendants 102 107 #[arg(long)]
+15 -11
cli/src/commands/split.rs
··· 14 14 use std::collections::HashMap; 15 15 use std::io::Write as _; 16 16 17 + use clap_complete::ArgValueCandidates; 17 18 use clap_complete::ArgValueCompleter; 18 19 use jj_lib::backend::CommitId; 19 20 use jj_lib::commit::Commit; ··· 49 50 /// 50 51 /// Starts a [diff editor] on the changes in the revision. Edit the right side 51 52 /// 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. 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. 54 56 /// 55 57 /// [diff editor]: 56 58 /// https://jj-vcs.github.io/jj/latest/config/#editing-diffs ··· 70 72 #[arg(long, short)] 71 73 interactive: bool, 72 74 /// Specify diff editor to be used (implies --interactive) 73 - #[arg(long, value_name = "NAME")] 75 + #[arg( 76 + long, 77 + value_name = "NAME", 78 + add = ArgValueCandidates::new(complete::diff_editors), 79 + )] 74 80 tool: Option<String>, 75 81 /// The revision to split 76 82 #[arg( ··· 219 225 // Prompt the user to select the changes they want for the first commit. 220 226 let target = select_diff(ui, &tx, &target_commit, &matcher, &diff_selector)?; 221 227 228 + let legacy_bookmark_behavior = 229 + !use_move_flags && tx.settings().get_bool("split.legacy-bookmark-behavior")?; 230 + 222 231 // Create the first commit, which includes the changes selected by the user. 223 232 let first_commit = { 224 233 let mut commit_builder = tx.repo_mut().rewrite_commit(&target.commit).detach(); 225 234 commit_builder.set_tree_id(target.selected_tree.id()); 226 - if use_move_flags { 235 + if !legacy_bookmark_behavior { 227 236 commit_builder 228 237 // Generate a new change id so that the commit being split doesn't 229 238 // become divergent. ··· 270 279 commit_builder 271 280 .set_parents(parents) 272 281 .set_tree_id(new_tree.id()); 273 - if !use_move_flags { 282 + if legacy_bookmark_behavior { 274 283 commit_builder 275 284 // Generate a new change id so that the commit being split doesn't 276 285 // become divergent. ··· 407 416 tx.repo_mut() 408 417 .transform_descendants(vec![target.commit.id().clone()], |mut rewriter| { 409 418 num_rebased += 1; 410 - if parallel && legacy_bookmark_behavior { 411 - // The old_parent is the second commit due to the rewrite above. 419 + if parallel { 412 420 rewriter 413 421 .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 422 } 419 423 rewriter.rebase()?.write()?; 420 424 Ok(())
+162 -3
cli/src/commands/squash.rs
··· 12 12 // See the License for the specific language governing permissions and 13 13 // limitations under the License. 14 14 15 + use clap_complete::ArgValueCandidates; 15 16 use clap_complete::ArgValueCompleter; 16 17 use indoc::formatdoc; 17 18 use itertools::Itertools as _; ··· 22 23 use jj_lib::repo::Repo as _; 23 24 use jj_lib::rewrite; 24 25 use jj_lib::rewrite::CommitWithSelection; 26 + use jj_lib::rewrite::SquashOptions; 25 27 use tracing::instrument; 26 28 27 29 use crate::cli_util::CommandHelper; ··· 99 101 #[arg(long, short)] 100 102 interactive: bool, 101 103 /// Specify diff editor to be used (implies --interactive) 102 - #[arg(long, value_name = "NAME")] 104 + #[arg( 105 + long, 106 + value_name = "NAME", 107 + add = ArgValueCandidates::new(complete::diff_editors), 108 + )] 103 109 tool: Option<String>, 104 110 /// Move only changes to these paths (instead of all paths) 105 111 #[arg( ··· 112 118 /// The source revision will not be abandoned 113 119 #[arg(long, short)] 114 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, 115 139 } 116 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 + 117 249 #[instrument(skip_all)] 118 250 pub(crate) fn cmd_squash( 119 251 ui: &mut Ui, ··· 166 298 .check_rewritable(sources.iter().chain(std::iter::once(&destination)).ids())?; 167 299 168 300 let mut tx = workspace_command.start_transaction(); 169 - let tx_description = format!("squash commits into {}", destination.id().hex()); 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 + ); 170 310 let source_commits = select_diff(&tx, &sources, &destination, &matcher, &diff_selector)?; 171 311 if let Some(squashed) = rewrite::squash_commits( 172 312 tx.repo_mut(), 173 313 &source_commits, 174 314 &destination, 175 - args.keep_emptied, 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 + }, 176 321 )? { 177 322 let mut commit_builder = squashed.commit_builder.detach(); 178 323 let new_description = match description { ··· 220 365 }; 221 366 commit_builder.set_description(new_description); 222 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 + } 223 380 } else { 224 381 if diff_selector.is_interactive() { 225 382 return Err(user_error("No changes selected")); ··· 241 398 } 242 399 } 243 400 } 401 + // TODO: Show the "Rebase NNN descendant commits message", add " (while 402 + // preserving their content)" in the --restore-descendants mode 244 403 tx.finish(ui, tx_description)?; 245 404 Ok(()) 246 405 }
+49 -1
cli/src/complete.rs
··· 34 34 use crate::config::ConfigArgKind; 35 35 use crate::config::ConfigEnv; 36 36 use crate::config::CONFIG_SCHEMA; 37 + use crate::merge_tools::configured_merge_tools; 38 + use crate::merge_tools::MergeEditor; 37 39 use crate::revset_util::load_revset_aliases; 38 40 use crate::ui::Ui; 39 41 ··· 474 476 .collect()) 475 477 }) 476 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 + } 477 525 478 526 fn config_keys_rec( 479 527 prefix: ConfigNamePathBuf, ··· 895 943 .and_then(dunce::canonicalize) 896 944 .map_err(user_error)?; 897 945 // No config migration for completion. Simply ignore deprecated variables. 898 - let mut config_env = ConfigEnv::from_environment(&ui); 946 + let mut config_env = ConfigEnv::from_environment(); 899 947 let maybe_cwd_workspace_loader = DefaultWorkspaceLoaderFactory.create(find_workspace_dir(&cwd)); 900 948 let _ = config_env.reload_user_config(&mut raw_config); 901 949 if let Ok(loader) = &maybe_cwd_workspace_loader {
+1 -1
cli/src/config/misc.toml
··· 54 54 # The behavior when this flag is set to false is experimental and may be changed 55 55 # in the future. 56 56 [split] 57 - legacy-bookmark-behavior = true 57 + legacy-bookmark-behavior = false
+5 -34
cli/src/config.rs
··· 43 43 use crate::command_error::config_error_with_message; 44 44 use crate::command_error::CommandError; 45 45 use crate::text_util; 46 - use crate::ui::Ui; 47 46 48 47 // TODO(#879): Consider generating entire schema dynamically vs. static file. 49 48 pub const CONFIG_SCHEMA: &str = include_str!("config-schema.json"); ··· 193 192 fn as_path(&self) -> &Path { 194 193 &self.path 195 194 } 195 + 196 196 fn exists(&self) -> bool { 197 197 match self.state { 198 198 ConfigPathState::Exists => true, ··· 218 218 #[derive(Clone, Default, Debug)] 219 219 struct UnresolvedConfigEnv { 220 220 config_dir: Option<PathBuf>, 221 - // TODO: remove after jj 0.35 222 221 macos_legacy_config_dir: Option<PathBuf>, 223 222 home_dir: Option<PathBuf>, 224 223 jj_config: Option<String>, 225 224 } 226 225 227 226 impl UnresolvedConfigEnv { 228 - fn resolve(self, ui: &Ui) -> Vec<ConfigPath> { 227 + fn resolve(self) -> Vec<ConfigPath> { 229 228 if let Some(paths) = self.jj_config { 230 229 return split_paths(&paths) 231 230 .filter(|path| !path.as_os_str().is_empty()) ··· 284 283 285 284 if let Some(path) = legacy_platform_config_path { 286 285 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 286 paths.push(path); 294 287 } 295 288 } 296 289 if let Some(path) = legacy_platform_config_dir { 297 290 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 291 paths.push(path); 305 292 } 306 293 } 307 294 308 295 paths 309 296 } 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 297 } 323 298 324 299 #[derive(Clone, Debug)] ··· 332 307 333 308 impl ConfigEnv { 334 309 /// Initializes configuration loader based on environment variables. 335 - pub fn from_environment(ui: &Ui) -> Self { 310 + pub fn from_environment() -> Self { 336 311 let config_dir = etcetera::choose_base_strategy() 337 312 .ok() 338 313 .map(|s| s.config_dir()); ··· 347 322 // Library/Preferences is supposed to be exclusively plists 348 323 s.data_dir() 349 324 }) 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 325 } else { 355 326 None 356 327 }; ··· 370 341 ConfigEnv { 371 342 home_dir, 372 343 repo_path: None, 373 - user_config_paths: env.resolve(ui), 344 + user_config_paths: env.resolve(), 374 345 repo_config_path: None, 375 346 command: None, 376 347 } ··· 1757 1728 ConfigEnv { 1758 1729 home_dir, 1759 1730 repo_path: None, 1760 - user_config_paths: env.resolve(&Ui::null()), 1731 + user_config_paths: env.resolve(), 1761 1732 repo_config_path: None, 1762 1733 command: None, 1763 1734 }
+19 -3
cli/src/diff_util.rs
··· 23 23 24 24 use bstr::BStr; 25 25 use bstr::BString; 26 + use clap_complete::ArgValueCandidates; 26 27 use futures::executor::block_on_stream; 27 28 use futures::stream::BoxStream; 28 29 use futures::StreamExt as _; ··· 124 125 /// 125 126 /// A builtin format can also be specified as `:<name>`. For example, 126 127 /// `--tool=:git` is equivalent to `--git`. 127 - #[arg(long)] 128 + #[arg( 129 + long, 130 + add = ArgValueCandidates::new(crate::complete::diff_tools), 131 + )] 128 132 pub tool: Option<String>, 129 133 /// Number of lines of context to show 130 134 #[arg(long)] ··· 152 156 } 153 157 154 158 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 155 - enum BuiltinFormatKind { 159 + pub enum BuiltinFormatKind { 156 160 Summary, 157 161 Stat, 158 162 Types, ··· 162 166 } 163 167 164 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 + 165 181 fn from_name(name: &str) -> Result<Self, String> { 166 182 match name { 167 183 "summary" => Ok(Self::Summary), ··· 205 221 } 206 222 } 207 223 208 - fn to_arg_name(self) -> &'static str { 224 + pub fn to_arg_name(self) -> &'static str { 209 225 match self { 210 226 Self::Summary => "summary", 211 227 Self::Stat => "stat",
+22 -6
cli/src/merge_tools/mod.rs
··· 207 207 } 208 208 } 209 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 + 210 215 /// Loads external diff/merge tool options from `[merge-tools.<name>]`. 211 216 pub fn get_external_tool_config( 212 217 settings: &UserSettings, ··· 378 383 let tool = MergeTool::get_tool_config(settings, name)? 379 384 .unwrap_or_else(|| MergeTool::external(ExternalMergeTool::with_program(name))); 380 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 + ) 381 402 } 382 403 383 404 /// Loads the default 3-way merge editor from the settings. ··· 765 786 let get = |name, config_text| { 766 787 let config = config_from_string(config_text); 767 788 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) 789 + MergeEditor::dummy_with_name(name, &settings).map(|editor| editor.tool) 774 790 }; 775 791 776 792 insta::assert_debug_snapshot!(get(":builtin", "").unwrap(), @"Builtin");
+15 -1
cli/tests/cli-reference@.md.snap
··· 906 906 * `-p`, `--patch` โ€” Show patch compared to the previous version of this change 907 907 908 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. 909 918 * `-s`, `--summary` โ€” For each path, show only whether it was modified, added, or deleted 910 919 * `--stat` โ€” Show a histogram of the changes 911 920 * `--types` โ€” For each path, show only its type before and after ··· 2461 2470 2462 2471 Split a revision in two 2463 2472 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. 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. 2465 2474 2466 2475 [diff editor]: https://jj-vcs.github.io/jj/latest/config/#editing-diffs 2467 2476 ··· 2524 2533 * `-i`, `--interactive` โ€” Interactively choose which parts to squash 2525 2534 * `--tool <NAME>` โ€” Specify diff editor to be used (implies --interactive) 2526 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. 2527 2541 2528 2542 2529 2543
+1 -1
cli/tests/test_commit_command.rs
··· 298 298 โ—† 000000000000 299 299 [EOF] 300 300 ------- stderr ------- 301 - Warning: Deprecated user-level config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 301 + Warning: Deprecated config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 302 302 [EOF] 303 303 "#); 304 304 insta::assert_snapshot!(
+61
cli/tests/test_completion.rs
··· 1089 1089 "); 1090 1090 } 1091 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 + 1092 1153 fn create_commit( 1093 1154 work_dir: &TestWorkDir, 1094 1155 name: &str,
-20
cli/tests/test_config_command.rs
··· 971 971 insta::assert_snapshot!(output, @r" 972 972 Make sure I can pick this up 973 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 974 "); 995 975 996 976 // if you set JJ_CONFIG, you shouldn't get a warning
+2 -2
cli/tests/test_describe_command.rs
··· 618 618 let output = work_dir.run_jj(["describe"]); 619 619 insta::assert_snapshot!(output, @r#" 620 620 ------- stderr ------- 621 - Warning: Deprecated user-level config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 621 + Warning: Deprecated config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 622 622 Working copy (@) now at: qpvuntsm 7276dfff TESTED=TODO 623 623 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set) 624 624 [EOF] ··· 639 639 let output = work_dir.run_jj(["describe", "--no-edit", "--reset-author"]); 640 640 insta::assert_snapshot!(output, @r#" 641 641 ------- stderr ------- 642 - Warning: Deprecated user-level config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 642 + Warning: Deprecated config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 643 643 Working copy (@) now at: kkmpptxz 7118bcb8 (empty) (no description set) 644 644 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set) 645 645 [EOF]
+3 -3
cli/tests/test_diff_command.rs
··· 253 253 -4 254 254 [EOF] 255 255 ------- stderr ------- 256 - Warning: Deprecated CLI-provided config: ui.diff.format is updated to ui.diff-formatter = ":git" 256 + Warning: Deprecated config: ui.diff.format is updated to ui.diff-formatter = ":git" 257 257 [EOF] 258 258 "#); 259 259 ··· 3138 3138 file3 3139 3139 [EOF] 3140 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) 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 3143 [EOF] 3144 3144 "); 3145 3145
+46
cli/tests/test_evolog_command.rs
··· 171 171 -- operation e0f8e58b3800 (2001-02-03 08:05:08) new empty commit 172 172 [EOF] 173 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 + "); 174 220 } 175 221 176 222 #[test]
+1 -2
cli/tests/test_git_fetch.rs
··· 1038 1038 ]); 1039 1039 insta::assert_snapshot!(output, @r" 1040 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 1041 + Warning: No branch matching `noexist1`, `noexist2` found on any specified/configured remote 1043 1042 Nothing changed. 1044 1043 [EOF] 1045 1044 ");
+140 -140
cli/tests/test_split_command.rs
··· 73 73 let output = work_dir.run_jj(["split", "file2"]); 74 74 insta::assert_snapshot!(output, @r" 75 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) 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 80 [EOF] 81 81 "); 82 82 insta::assert_snapshot!( ··· 92 92 assert!(!test_env.env_root().join("editor1").exists()); 93 93 94 94 insta::assert_snapshot!(get_log_output(&work_dir), @r" 95 - @ zsuskulnrvyr false 96 - โ—‹ qpvuntsmwlqt false 95 + @ qpvuntsmwlqt false 96 + โ—‹ zsuskulnrvyr false 97 97 โ—† zzzzzzzzzzzz true 98 98 [EOF] 99 99 "); ··· 128 128 ------- stderr ------- 129 129 Warning: All changes have been selected, so the original revision will become empty 130 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) 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 135 [EOF] 136 136 "); 137 137 138 138 insta::assert_snapshot!(get_log_output(&work_dir), @r" 139 - @ zsuskulnrvyr false 140 - โ—‹ znkkpsqqskkl true 141 - โ—‹ qpvuntsmwlqt false 139 + @ qpvuntsmwlqt false 140 + โ—‹ zsuskulnrvyr true 141 + โ—‹ znkkpsqqskkl false 142 142 โ—† zzzzzzzzzzzz true 143 143 [EOF] 144 144 "); ··· 159 159 ------- stderr ------- 160 160 Warning: No changes have been selected, so the new revision will be empty 161 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) 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 166 [EOF] 167 167 "); 168 168 169 169 insta::assert_snapshot!(get_log_output(&work_dir), @r" 170 - @ zsuskulnrvyr false 171 - โ—‹ lylxulplsnyw false 172 - โ—‹ qpvuntsmwlqt true 170 + @ qpvuntsmwlqt false 171 + โ—‹ znkkpsqqskkl false 172 + โ—‹ lylxulplsnyw true 173 173 โ—† zzzzzzzzzzzz true 174 174 [EOF] 175 175 "); ··· 207 207 let output = work_dir.run_jj(["split", "file1"]); 208 208 insta::assert_snapshot!(output, @r#" 209 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 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 215 [EOF] 216 216 "#); 217 217 ··· 236 236 JJ: Lines starting with "JJ:" (like this one) will be removed. 237 237 "#); 238 238 insta::assert_snapshot!(get_log_output(&work_dir), @r#" 239 - @ kkmpptxzrspx false part 2 240 - โ—‹ qpvuntsmwlqt false part 1 239 + @ qpvuntsmwlqt false part 2 240 + โ—‹ kkmpptxzrspx false part 1 241 241 โ—† zzzzzzzzzzzz true 242 242 [EOF] 243 243 ------- stderr ------- 244 - Warning: Deprecated user-level config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 244 + Warning: Deprecated config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 245 245 [EOF] 246 246 "#); 247 247 } ··· 265 265 let output = work_dir.run_jj(["split", "file1"]); 266 266 insta::assert_snapshot!(output, @r#" 267 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 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 273 [EOF] 274 274 "#); 275 275 ··· 291 291 "#); 292 292 assert!(!test_env.env_root().join("editor2").exists()); 293 293 insta::assert_snapshot!(get_log_output(&work_dir), @r#" 294 - @ rlvkpnrzqnoo false 295 - โ—‹ qpvuntsmwlqt false TESTED=TODO 294 + @ qpvuntsmwlqt false 295 + โ—‹ rlvkpnrzqnoo false TESTED=TODO 296 296 โ—† zzzzzzzzzzzz true 297 297 [EOF] 298 298 ------- stderr ------- 299 - Warning: Deprecated user-level config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 299 + Warning: Deprecated config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 300 300 [EOF] 301 301 "#); 302 302 } ··· 346 346 insta::assert_snapshot!(output, @r" 347 347 ------- stderr ------- 348 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 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 353 [EOF] 354 354 "); 355 - insta::assert_snapshot!(get_log_output(&work_dir), @r###" 355 + insta::assert_snapshot!(get_log_output(&work_dir), @r" 356 356 @ kkmpptxzrspx false Add file4 357 357 โ—‹ rlvkpnrzqnoo false Add file3 358 - โ—‹ royxmykxtrkr false Add file2 359 - โ—‹ qpvuntsmwlqt false Add file1 358 + โ—‹ qpvuntsmwlqt false Add file2 359 + โ—‹ royxmykxtrkr false Add file1 360 360 โ—† zzzzzzzzzzzz true 361 361 [EOF] 362 - "###); 362 + "); 363 363 364 364 // The commit we're splitting has a description, so the user will be 365 365 // prompted to enter a description for each of the commits. ··· 388 388 // - The initial empty commit. 389 389 // - The rewritten commit from the snapshot after the files were added. 390 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"]); 391 + // - The rewritten commit after the split with a new change ID. 392 + let evolog_1 = work_dir.run_jj(["evolog", "-r", "royxm"]); 393 393 insta::assert_snapshot!(evolog_1, @r" 394 - โ—‹ qpvuntsm test.user@example.com 2001-02-03 08:05:12 74306e35 394 + โ—‹ royxmykx test.user@example.com 2001-02-03 08:05:12 e13e94b9 395 395 โ”‚ Add file1 396 - โ”‚ -- operation 994b490f285d (2001-02-03 08:05:12) split commit 1d2499e72cefc8a2b87ebb47569140857b96189f 396 + โ”‚ -- operation a8006fdd66fd (2001-02-03 08:05:12) split commit 1d2499e72cefc8a2b87ebb47569140857b96189f 397 397 โ—‹ qpvuntsm hidden test.user@example.com 2001-02-03 08:05:08 1d2499e7 398 398 โ”‚ Add file1 & file2 399 399 โ”‚ -- operation adf4f33386c9 (2001-02-03 08:05:08) commit f5700f8ef89e290e4e90ae6adc0908707e0d8c85 ··· 407 407 "); 408 408 409 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"]); 410 + // doesn't change after the split. 411 + let evolog_2 = work_dir.run_jj(["evolog", "-r", "qpvun"]); 412 412 insta::assert_snapshot!(evolog_2, @r" 413 - โ—‹ royxmykx test.user@example.com 2001-02-03 08:05:12 0a37745e 413 + โ—‹ qpvuntsm test.user@example.com 2001-02-03 08:05:12 cf8ebbab 414 414 โ”‚ Add file2 415 - โ”‚ -- operation 994b490f285d (2001-02-03 08:05:12) split commit 1d2499e72cefc8a2b87ebb47569140857b96189f 415 + โ”‚ -- operation a8006fdd66fd (2001-02-03 08:05:12) split commit 1d2499e72cefc8a2b87ebb47569140857b96189f 416 416 โ—‹ qpvuntsm hidden test.user@example.com 2001-02-03 08:05:08 1d2499e7 417 417 โ”‚ Add file1 & file2 418 418 โ”‚ -- operation adf4f33386c9 (2001-02-03 08:05:08) commit f5700f8ef89e290e4e90ae6adc0908707e0d8c85 ··· 461 461 insta::assert_snapshot!(output, @r" 462 462 ------- stderr ------- 463 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 464 + Selected changes : royxmykx ad21dad2 Add file1 465 + Remaining changes: kkmpptxz 0922bd25 Add file2 466 + Working copy (@) now at: zsuskuln f59cd990 (empty) 2 467 467 Parent commit (@-) : qpvuntsm 884fe9b9 (empty) 1 468 - Parent commit (@-) : royxmykx e488409f Add file2 468 + Parent commit (@-) : kkmpptxz 0922bd25 Add file2 469 469 [EOF] 470 470 "); 471 471 insta::assert_snapshot!(get_log_output(&work_dir), @r" 472 472 @ zsuskulnrvyr true 2 473 473 โ”œโ”€โ•ฎ 474 - โ”‚ โ—‹ royxmykxtrkr false Add file2 475 - โ”‚ โ—‹ kkmpptxzrspx false Add file1 474 + โ”‚ โ—‹ kkmpptxzrspx false Add file2 475 + โ”‚ โ—‹ royxmykxtrkr false Add file1 476 476 โ—‹ โ”‚ qpvuntsmwlqt true 1 477 477 โ”œโ”€โ•ฏ 478 478 โ—† zzzzzzzzzzzz true ··· 498 498 โ—† zzzzzzzzzzzz true 499 499 [EOF] 500 500 ------- stderr ------- 501 - Warning: Deprecated user-level config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 501 + Warning: Deprecated config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 502 502 [EOF] 503 503 "#); 504 504 ··· 510 510 let output = work_dir.run_jj(["split", "--parallel", "file1"]); 511 511 insta::assert_snapshot!(output, @r#" 512 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) 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 517 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set) 518 518 Added 0 files, modified 0 files, removed 1 files 519 519 [EOF] 520 520 "#); 521 521 insta::assert_snapshot!(get_log_output(&work_dir), @r#" 522 - @ kkmpptxzrspx false 523 - โ”‚ โ—‹ qpvuntsmwlqt false TESTED=TODO 522 + @ qpvuntsmwlqt false 523 + โ”‚ โ—‹ kkmpptxzrspx false TESTED=TODO 524 524 โ”œโ”€โ•ฏ 525 525 โ—† zzzzzzzzzzzz true 526 526 [EOF] 527 527 ------- stderr ------- 528 - Warning: Deprecated user-level config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 528 + Warning: Deprecated config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 529 529 [EOF] 530 530 "#); 531 531 ··· 550 550 // Check the evolog for the first commit. It shows three entries: 551 551 // - The initial empty commit. 552 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"]); 553 + // - The rewritten commit after the split with a new change ID. 554 + let evolog_1 = work_dir.run_jj(["evolog", "-r", "kkmpp"]); 555 555 insta::assert_snapshot!(evolog_1, @r#" 556 - โ—‹ qpvuntsm test.user@example.com 2001-02-03 08:05:09 7bcd474c 556 + โ—‹ kkmpptxz test.user@example.com 2001-02-03 08:05:09 bd9b3db1 557 557 โ”‚ TESTED=TODO 558 - โ”‚ -- operation 2b21c33e1596 (2001-02-03 08:05:09) split commit f5700f8ef89e290e4e90ae6adc0908707e0d8c85 558 + โ”‚ -- operation 372a3799b434 (2001-02-03 08:05:09) split commit f5700f8ef89e290e4e90ae6adc0908707e0d8c85 559 559 โ—‹ qpvuntsm hidden test.user@example.com 2001-02-03 08:05:08 f5700f8e 560 560 โ”‚ (no description set) 561 561 โ”‚ -- operation 1663cd1cc445 (2001-02-03 08:05:08) snapshot working copy ··· 564 564 -- operation 8f47435a3990 (2001-02-03 08:05:07) add workspace 'default' 565 565 [EOF] 566 566 ------- stderr ------- 567 - Warning: Deprecated user-level config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 567 + Warning: Deprecated config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 568 568 [EOF] 569 569 "#); 570 570 571 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"]); 572 + // doesn't change after the split. 573 + let evolog_2 = work_dir.run_jj(["evolog", "-r", "qpvun"]); 574 574 insta::assert_snapshot!(evolog_2, @r#" 575 - @ kkmpptxz test.user@example.com 2001-02-03 08:05:09 431886f6 575 + @ qpvuntsm test.user@example.com 2001-02-03 08:05:09 5597b805 576 576 โ”‚ (no description set) 577 - โ”‚ -- operation 2b21c33e1596 (2001-02-03 08:05:09) split commit f5700f8ef89e290e4e90ae6adc0908707e0d8c85 577 + โ”‚ -- operation 372a3799b434 (2001-02-03 08:05:09) split commit f5700f8ef89e290e4e90ae6adc0908707e0d8c85 578 578 โ—‹ qpvuntsm hidden test.user@example.com 2001-02-03 08:05:08 f5700f8e 579 579 โ”‚ (no description set) 580 580 โ”‚ -- operation 1663cd1cc445 (2001-02-03 08:05:08) snapshot working copy ··· 583 583 -- operation 8f47435a3990 (2001-02-03 08:05:07) add workspace 'default' 584 584 [EOF] 585 585 ------- stderr ------- 586 - Warning: Deprecated user-level config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 586 + Warning: Deprecated config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 587 587 [EOF] 588 588 "#); 589 589 } ··· 637 637 insta::assert_snapshot!(output, @r" 638 638 ------- stderr ------- 639 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 640 + Selected changes : vruxwmqv 3f0980cb Add file1 641 + Remaining changes: qpvuntsm dff79d19 Add file2 642 + Working copy (@) now at: qpvuntsm dff79d19 Add file2 643 643 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set) 644 644 Added 0 files, modified 0 files, removed 1 files 645 645 [EOF] ··· 648 648 โ—‹ kkmpptxzrspx false Add file4 649 649 โ—‹ rlvkpnrzqnoo false Add file3 650 650 โ”œโ”€โ•ฎ 651 - โ”‚ @ vruxwmqvtpmx false Add file2 652 - โ—‹ โ”‚ qpvuntsmwlqt false Add file1 651 + โ”‚ @ qpvuntsmwlqt false Add file2 652 + โ—‹ โ”‚ vruxwmqvtpmx false Add file1 653 653 โ”œโ”€โ•ฏ 654 654 โ—† zzzzzzzzzzzz true 655 655 [EOF] ··· 714 714 insta::assert_snapshot!(output, @r" 715 715 ------- stderr ------- 716 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 717 + Selected changes : royxmykx ad21dad2 Add file1 718 + Remaining changes: kkmpptxz 23a2daac Add file2 719 + Working copy (@) now at: zsuskuln f1fcb7a6 (empty) 2 720 720 Parent commit (@-) : qpvuntsm 884fe9b9 (empty) 1 721 - Parent commit (@-) : kkmpptxz cc199567 Add file1 722 - Parent commit (@-) : royxmykx 82a5c527 Add file2 721 + Parent commit (@-) : royxmykx ad21dad2 Add file1 722 + Parent commit (@-) : kkmpptxz 23a2daac Add file2 723 723 [EOF] 724 724 "); 725 725 insta::assert_snapshot!(get_log_output(&work_dir), @r" 726 726 @ zsuskulnrvyr true 2 727 727 โ”œโ”€โ”ฌโ”€โ•ฎ 728 - โ”‚ โ”‚ โ—‹ royxmykxtrkr false Add file2 729 - โ”‚ โ—‹ โ”‚ kkmpptxzrspx false Add file1 728 + โ”‚ โ”‚ โ—‹ kkmpptxzrspx false Add file2 729 + โ”‚ โ—‹ โ”‚ royxmykxtrkr false Add file1 730 730 โ”‚ โ”œโ”€โ•ฏ 731 731 โ—‹ โ”‚ qpvuntsmwlqt true 1 732 732 โ”œโ”€โ•ฏ ··· 793 793 let output = work_dir.run_jj(["split"]); 794 794 insta::assert_snapshot!(output, @r" 795 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) 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 800 [EOF] 801 801 "); 802 802 ··· 824 824 825 825 let output = work_dir.run_jj(["log", "--summary"]); 826 826 insta::assert_snapshot!(output, @r" 827 - @ rlvkpnrz test.user@example.com 2001-02-03 08:05:08 7e5d65b1 827 + @ qpvuntsm test.user@example.com 2001-02-03 08:05:08 429f292f 828 828 โ”‚ (no description set) 829 829 โ”‚ A file2 830 - โ—‹ qpvuntsm test.user@example.com 2001-02-03 08:05:08 c664a51b 830 + โ—‹ rlvkpnrz test.user@example.com 2001-02-03 08:05:08 1ff7a783 831 831 โ”‚ (no description set) 832 832 โ”‚ A file1 833 833 โ—† zzzzzzzz root() 00000000 ··· 869 869 let output = work_dir.run_jj(["split", "-i", "file1", "file2"]); 870 870 insta::assert_snapshot!(output, @r" 871 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) 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 876 [EOF] 877 877 "); 878 878 ··· 889 889 890 890 let output = work_dir.run_jj(["log", "--summary"]); 891 891 insta::assert_snapshot!(output, @r" 892 - @ kkmpptxz test.user@example.com 2001-02-03 08:05:09 7255f070 892 + @ rlvkpnrz test.user@example.com 2001-02-03 08:05:09 7326e6fd 893 893 โ”‚ (no description set) 894 894 โ”‚ M file2 895 895 โ”‚ M file3 896 - โ—‹ rlvkpnrz test.user@example.com 2001-02-03 08:05:09 cdc9960a 896 + โ—‹ kkmpptxz test.user@example.com 2001-02-03 08:05:09 0a5bea34 897 897 โ”‚ (no description set) 898 898 โ”‚ A file1 899 899 โ—‹ qpvuntsm test.user@example.com 2001-02-03 08:05:08 ff687a2f ··· 947 947 main_dir.run_jj(["split", "file2"]).success(); 948 948 // The working copy for both workspaces will be the second split commit. 949 949 insta::assert_snapshot!(get_workspace_log_output(&main_dir), @r" 950 - @ royxmykxtrkr default@ second@ second-commit 951 - โ—‹ qpvuntsmwlqt first-commit 950 + @ qpvuntsmwlqt default@ second@ second-commit 951 + โ—‹ royxmykxtrkr first-commit 952 952 โ—† zzzzzzzzzzzz 953 953 [EOF] 954 954 "); ··· 962 962 .unwrap(); 963 963 main_dir.run_jj(["split", "file2", "--parallel"]).success(); 964 964 insta::assert_snapshot!(get_workspace_log_output(&main_dir), @r" 965 - @ yostqsxwqrlt default@ second@ second-commit 966 - โ”‚ โ—‹ qpvuntsmwlqt first-commit 965 + @ qpvuntsmwlqt default@ second@ second-commit 966 + โ”‚ โ—‹ yostqsxwqrlt first-commit 967 967 โ”œโ”€โ•ฏ 968 968 โ—† zzzzzzzzzzzz 969 969 [EOF] ··· 1006 1006 main_dir.run_jj(["split", "file2"]).success(); 1007 1007 // Only the working copy commit for the default workspace changes. 1008 1008 insta::assert_snapshot!(get_workspace_log_output(&main_dir), @r" 1009 - @ mzvwutvlkqwt default@ second-commit 1010 - โ—‹ qpvuntsmwlqt first-commit 1009 + @ qpvuntsmwlqt default@ second-commit 1010 + โ—‹ mzvwutvlkqwt first-commit 1011 1011 โ”‚ โ—‹ pmmvwywvzvvn second@ 1012 1012 โ”œโ”€โ•ฏ 1013 1013 โ—† zzzzzzzzzzzz ··· 1023 1023 .unwrap(); 1024 1024 main_dir.run_jj(["split", "file2", "--parallel"]).success(); 1025 1025 insta::assert_snapshot!(get_workspace_log_output(&main_dir), @r" 1026 - @ vruxwmqvtpmx default@ second-commit 1027 - โ”‚ โ—‹ qpvuntsmwlqt first-commit 1026 + @ qpvuntsmwlqt default@ second-commit 1027 + โ”‚ โ—‹ vruxwmqvtpmx first-commit 1028 1028 โ”œโ”€โ•ฏ 1029 1029 โ”‚ โ—‹ pmmvwywvzvvn second@ 1030 1030 โ”œโ”€โ•ฏ ··· 1064 1064 let output = work_dir.run_jj(["split", "file1"]); 1065 1065 insta::assert_snapshot!(output, @r#" 1066 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 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 1072 [EOF] 1073 1073 "#); 1074 1074 ··· 1097 1097 JJ: Lines starting with "JJ:" (like this one) will be removed. 1098 1098 "#); 1099 1099 insta::assert_snapshot!(get_log_output(&work_dir), @r#" 1100 - @ kkmpptxzrspx false part 2 1101 - โ—‹ qpvuntsmwlqt false part 1 1100 + @ qpvuntsmwlqt false part 2 1101 + โ—‹ kkmpptxzrspx false part 1 1102 1102 โ—† zzzzzzzzzzzz true 1103 1103 [EOF] 1104 1104 ------- stderr ------- 1105 - Warning: Deprecated user-level config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 1105 + Warning: Deprecated config: ui.default-description is updated to template-aliases.default_commit_description = '"\n\nTESTED=TODO\n"' 1106 1106 [EOF] 1107 1107 "#); 1108 1108 } ··· 1120 1120 let output = work_dir.run_jj(["split", "-m", "fix in file1", "file1"]); 1121 1121 insta::assert_snapshot!(output, @r" 1122 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 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 1127 [EOF] 1128 1128 "); 1129 1129 1130 1130 insta::assert_snapshot!(get_log_output(&work_dir), @r" 1131 - @ kkmpptxzrspx false my feature 1132 - โ—‹ qpvuntsmwlqt false fix in file1 1131 + @ qpvuntsmwlqt false my feature 1132 + โ—‹ kkmpptxzrspx false fix in file1 1133 1133 โ—† zzzzzzzzzzzz true 1134 1134 [EOF] 1135 1135 "); ··· 1146 1146 ]); 1147 1147 insta::assert_snapshot!(output, @r" 1148 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 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 1153 [EOF] 1154 1154 "); 1155 1155 1156 1156 insta::assert_snapshot!(get_log_output(&work_dir), @r" 1157 - @ royxmykxtrkr false my feature 1158 - โ—‹ qpvuntsmwlqt false fix in file1 1157 + @ qpvuntsmwlqt false my feature 1158 + โ—‹ royxmykxtrkr false fix in file1 1159 1159 โ”‚ 1160 1160 โ”‚ CC: test.user@example.com 1161 1161 โ—† zzzzzzzzzzzz true ··· 1470 1470 .unwrap(); 1471 1471 let output = main_dir.run_jj(["split", "file2"]); 1472 1472 match bookmark_behavior { 1473 - BookmarkBehavior::LeaveBookmarkWithTarget => { 1473 + BookmarkBehavior::Default | BookmarkBehavior::LeaveBookmarkWithTarget => { 1474 1474 insta::allow_duplicates! { 1475 1475 insta::assert_snapshot!(output, @r#" 1476 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 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 1481 [EOF] 1482 1482 "#); 1483 1483 } 1484 1484 insta::allow_duplicates! { 1485 1485 insta::assert_snapshot!(get_log_output(&main_dir), @r#" 1486 - @ mzvwutvlkqwt false second-commit 1487 - โ—‹ qpvuntsmwlqt false "*le-signet*" first-commit 1486 + @ qpvuntsmwlqt false "*le-signet*" second-commit 1487 + โ—‹ mzvwutvlkqwt false first-commit 1488 1488 โ—† zzzzzzzzzzzz true 1489 1489 [EOF] 1490 1490 "#); 1491 1491 } 1492 1492 } 1493 - BookmarkBehavior::Default | BookmarkBehavior::MoveBookmarkToChild => { 1493 + BookmarkBehavior::MoveBookmarkToChild => { 1494 1494 insta::allow_duplicates! { 1495 1495 insta::assert_snapshot!(output, @r#" 1496 1496 ------- stderr ------- ··· 1521 1521 .unwrap(); 1522 1522 main_dir.run_jj(["split", "file2", "--parallel"]).success(); 1523 1523 match bookmark_behavior { 1524 - BookmarkBehavior::LeaveBookmarkWithTarget => { 1524 + BookmarkBehavior::Default | BookmarkBehavior::LeaveBookmarkWithTarget => { 1525 1525 insta::allow_duplicates! { 1526 1526 insta::assert_snapshot!(get_log_output(&main_dir), @r#" 1527 - @ vruxwmqvtpmx false second-commit 1528 - โ”‚ โ—‹ qpvuntsmwlqt false "*le-signet*" first-commit 1527 + @ qpvuntsmwlqt false "*le-signet*" second-commit 1528 + โ”‚ โ—‹ vruxwmqvtpmx false first-commit 1529 1529 โ”œโ”€โ•ฏ 1530 1530 โ—† zzzzzzzzzzzz true 1531 1531 [EOF] 1532 1532 "#); 1533 1533 } 1534 1534 } 1535 - BookmarkBehavior::Default | BookmarkBehavior::MoveBookmarkToChild => { 1535 + BookmarkBehavior::MoveBookmarkToChild => { 1536 1536 insta::allow_duplicates! { 1537 1537 insta::assert_snapshot!(get_log_output(&main_dir), @r#" 1538 1538 @ vruxwmqvtpmx false "*le-signet*" second-commit
+775
cli/tests/test_squash_command.rs
··· 746 746 } 747 747 748 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] 749 1524 fn test_squash_from_multiple() { 750 1525 let test_env = TestEnvironment::default(); 751 1526 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
+3 -8
docs/releasing.md
··· 13 13 to run something like this: 14 14 15 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 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 24 19 ``` 25 20 26 21 https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#compare-two-commits
+9 -28
lib/src/config_resolver.rs
··· 27 27 use crate::config::ConfigGetError; 28 28 use crate::config::ConfigLayer; 29 29 use crate::config::ConfigNamePathBuf; 30 - use crate::config::ConfigSource; 31 30 use crate::config::ConfigUpdateError; 32 31 use crate::config::ConfigValue; 33 32 use crate::config::StackedConfig; ··· 368 367 pub fn migrate( 369 368 config: &mut StackedConfig, 370 369 rules: &[ConfigMigrationRule], 371 - ) -> Result<Vec<(ConfigSource, String)>, ConfigMigrateError> { 370 + ) -> Result<Vec<String>, ConfigMigrateError> { 372 371 let mut descriptions = Vec::new(); 373 372 for layer in config.layers_mut() { 374 373 migrate_layer(layer, rules, &mut descriptions) ··· 380 379 fn migrate_layer( 381 380 layer: &mut Arc<ConfigLayer>, 382 381 rules: &[ConfigMigrationRule], 383 - descriptions: &mut Vec<(ConfigSource, String)>, 382 + descriptions: &mut Vec<String>, 384 383 ) -> Result<(), ConfigMigrateLayerError> { 385 384 let rules_to_apply = rules 386 385 .iter() ··· 392 391 let layer_mut = Arc::make_mut(layer); 393 392 for rule in rules_to_apply { 394 393 let desc = rule.apply(layer_mut)?; 395 - descriptions.push((layer_mut.source, desc)); 394 + descriptions.push(desc); 396 395 } 397 396 Ok(()) 398 397 } ··· 863 862 let descriptions = migrate(&mut config, &rules).unwrap(); 864 863 insta::assert_debug_snapshot!(descriptions, @r#" 865 864 [ 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 - ), 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", 878 868 ] 879 869 "#); 880 870 insta::assert_snapshot!(config.layers()[0].data, @r" ··· 923 913 let descriptions = migrate(&mut config, &rules).unwrap(); 924 914 insta::assert_debug_snapshot!(descriptions, @r#" 925 915 [ 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 - ), 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\"", 938 919 ] 939 920 "#); 940 921 insta::assert_snapshot!(config.layers()[0].data, @r"
+26 -40
lib/src/default_index/store.rs
··· 15 15 #![allow(missing_docs)] 16 16 17 17 use std::any::Any; 18 - use std::collections::HashMap; 18 + use std::collections::HashSet; 19 19 use std::fs; 20 20 use std::io; 21 21 use std::io::Write as _; 22 22 use std::path::Path; 23 23 use std::path::PathBuf; 24 - use std::slice; 25 24 use std::sync::Arc; 26 25 27 26 use itertools::Itertools as _; ··· 50 49 use crate::object_id::ObjectId as _; 51 50 use crate::op_store::OpStoreError; 52 51 use crate::op_store::OperationId; 53 - use crate::op_walk; 54 52 use crate::operation::Operation; 55 53 use crate::store::Store; 56 54 ··· 187 185 operation: &Operation, 188 186 store: &Arc<Store>, 189 187 ) -> Result<Arc<ReadonlyIndexSegment>, DefaultIndexStoreError> { 190 - tracing::info!("scanning operations to index"); 188 + let view = operation.view()?; 191 189 let operations_dir = self.operations_dir(); 192 190 let commit_id_length = store.commit_id_length(); 193 191 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 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 198 195 .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 - } 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()); 212 211 } 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 { 212 + // TODO: no need to walk ancestors of the parent_op_id operation 227 213 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()); 214 + if visited_heads.insert(commit_id.clone()) { 215 + historical_heads.push((commit_id.clone(), op.id().clone())); 230 216 } 231 217 } 232 218 } 233 219 let maybe_parent_file; 234 220 let mut mutable_index; 235 - match &parent_op { 221 + match parent_op_id { 236 222 None => { 237 223 maybe_parent_file = None; 238 224 mutable_index = DefaultMutableIndex::full(commit_id_length, change_id_length); 239 225 } 240 - Some(op) => { 226 + Some(parent_op_id) => { 241 227 let parent_file = self.load_index_segments_at_operation( 242 - op.id(), 228 + &parent_op_id, 243 229 commit_id_length, 244 230 change_id_length, 245 231 )?;
+19 -4
lib/src/repo.rs
··· 1406 1406 /// The content of those descendants will remain untouched. 1407 1407 /// Returns the number of reparented descendants. 1408 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<()> { 1409 1424 let roots = self.parent_mapping.keys().cloned().collect_vec(); 1410 - let mut num_reparented = 0; 1411 1425 self.transform_descendants(roots, |rewriter| { 1412 1426 if rewriter.parents_changed() { 1427 + let old_commit = rewriter.old_commit().clone(); 1413 1428 let builder = rewriter.reparent(); 1414 - builder.write()?; 1415 - num_reparented += 1; 1429 + let reparented_commit = builder.write()?; 1430 + progress(old_commit, reparented_commit); 1416 1431 } 1417 1432 Ok(()) 1418 1433 })?; 1419 1434 self.parent_mapping.clear(); 1420 - Ok(num_reparented) 1435 + Ok(()) 1421 1436 } 1422 1437 1423 1438 pub fn set_wc_commit(
+29 -11
lib/src/rewrite.rs
··· 1109 1109 pub abandoned_commits: Vec<Commit>, 1110 1110 } 1111 1111 1112 + #[derive(Clone, Debug)] 1113 + pub struct SquashOptions { 1114 + pub keep_emptied: bool, 1115 + pub restore_descendants: bool, 1116 + } 1117 + 1112 1118 /// Squash `sources` into `destination` and return a [`SquashedCommit`] for the 1113 1119 /// resulting commit. Caller is responsible for setting the description and 1114 1120 /// finishing the commit. ··· 1116 1122 repo: &'repo mut MutableRepo, 1117 1123 sources: &[CommitWithSelection], 1118 1124 destination: &Commit, 1119 - keep_emptied: bool, 1125 + SquashOptions { 1126 + keep_emptied, 1127 + restore_descendants, 1128 + }: SquashOptions, 1120 1129 ) -> BackendResult<Option<SquashedCommit<'repo>>> { 1121 1130 struct SourceCommit<'a> { 1122 1131 commit: &'a CommitWithSelection, ··· 1169 1178 // rewritten sources. Otherwise it will likely already have the content 1170 1179 // changes we're moving, so applying them will have no effect and the 1171 1180 // 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 - })?; 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 + } 1182 1200 } 1183 1201 // Apply the selected changes onto the destination 1184 1202 let mut destination_tree = rewritten_destination.tree()?;