+8
-7
CHANGELOG.md
+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
+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
+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
+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
+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
+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
+
©_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
+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
+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
+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
+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
+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
+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
+1
-1
cli/src/config/misc.toml
+5
-34
cli/src/config.rs
+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
+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
+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
+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
+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
+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
-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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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()?;