Skip to content

Commit 2f29240

Browse files
committed
bisect: --trust-endpoints
This commit holds the rest of the implementation for --trust-endpoints: the new argument, the new documentation, the new tests.
1 parent 07081ae commit 2f29240

6 files changed

Lines changed: 231 additions & 17 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
1515
* `jj git import` in non-colocated repositories no longer imports commits from a
1616
detached Git HEAD branch.
1717

18+
* `jj bisect run` now runs some consistency checks before proceeding to bisect,
19+
making sure that the provided command can indeed tell good and bad commits apart.
20+
Use the new flag `--trust-endpoints` to disable these checks.
21+
1822
### Deprecations
1923

2024
### New features

cli/src/commands/bisect/run.rs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,12 @@ pub(crate) struct BisectRunArgs {
8787
/// will abort the bisection, and any other non-zero exit status means the
8888
/// revision is "bad".
8989
///
90+
/// In order for bisection to be meaningful, `COMMAND` must succeed for
91+
/// every revision in `heads(REVSETS)`, and it must fail for every revision
92+
/// in `parents(roots(REVSETS))`; if you are using `--find-good`, these
93+
/// checks are reversed. (Note that `parents(roots(REVSETS))` contains
94+
/// revisions that are not in `REVSETS`.)
95+
///
9096
/// The target's commit ID is available to the command in the
9197
/// `$JJ_BISECT_TARGET` environment variable.
9298
#[arg(value_name = "COMMAND")]
@@ -106,6 +112,15 @@ pub(crate) struct BisectRunArgs {
106112
/// good.
107113
#[arg(long, value_name = "TARGET", default_value_t = false)]
108114
find_good: bool,
115+
116+
/// Skip the pre-bisection checks
117+
///
118+
/// By default, `COMMAND` will be run on every revision `jj bisect run`
119+
/// assumes to be good or bad before bisection actually begins, as detailed
120+
/// under the documentation for `COMMAND`. This flag disables these
121+
/// checks.
122+
#[arg(long)]
123+
trust_endpoints: bool,
109124
}
110125

111126
#[instrument(skip_all)]
@@ -132,7 +147,9 @@ pub(crate) async fn cmd_bisect_run(
132147

133148
let initial_repo = workspace_command.repo().clone();
134149

135-
let mut bisector = Bisector::new(initial_repo.as_ref(), input_range, false).await?;
150+
let mut bisector =
151+
Bisector::new(initial_repo.as_ref(), input_range, !args.trust_endpoints).await?;
152+
136153
let bisection_result = loop {
137154
match bisector.next_step().await? {
138155
jj_lib::bisect::NextStep::Verify {
@@ -146,7 +163,7 @@ pub(crate) async fn cmd_bisect_run(
146163
let mut formatter = ui.stdout_formatter();
147164
writeln!(
148165
formatter,
149-
"Pre-bisection check: ensuring this commit is {expected:#?}:"
166+
"Pre-bisection check: ensuring this commit is {expected}:"
150167
)?;
151168
let commit_template = workspace_command.commit_summary_template();
152169
commit_template.format(&commit, formatter.as_mut())?;
@@ -163,8 +180,8 @@ pub(crate) async fn cmd_bisect_run(
163180
let mut formatter = ui.stdout_formatter();
164181
writeln!(
165182
formatter,
166-
"Cannot bisect: this commit was expected to be {expected:#?}, but was \
167-
found to be {actual:#?} (exit status: {exit_code}) instead."
183+
"Cannot bisect: this commit was expected to be {expected}, but was found \
184+
to be {actual} (exit status: {exit_code}) instead."
168185
)?;
169186
break BisectionResult::VerificationFailed;
170187
}
@@ -240,7 +257,11 @@ pub(crate) async fn cmd_bisect_run(
240257
short_operation_hash(initial_repo.op_id())
241258
)?;
242259

243-
let target = if args.find_good { "good" } else { "bad" };
260+
let target = if args.find_good {
261+
Evaluation::Good
262+
} else {
263+
Evaluation::Bad
264+
};
244265
match bisection_result {
245266
BisectionResult::VerificationFailed => {
246267
return Err(user_error("Bisection preconditions failed"));

cli/tests/cli-reference@.md.snap

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,8 @@ cargo test"
362362

363363
Each revision being checked will be directly edited (will become the current working copy) before running this command. The exit status of the command will be used to mark revisions as "good" or "bad": status 0 means "good", 125 means to skip the revision, 127 (command not found) will abort the bisection, and any other non-zero exit status means the revision is "bad".
364364

365+
In order for bisection to be meaningful, `COMMAND` must succeed for every revision in `heads(REVSETS)`, and it must fail for every revision in `parents(roots(REVSETS))`; if you are using `--find-good`, these checks are reversed. (Note that `parents(roots(REVSETS))` contains revisions that are not in `REVSETS`.)
366+
365367
The target's commit ID is available to the command in the `$JJ_BISECT_TARGET` environment variable.
366368
* `<ARGS>` — Arguments to pass to the command
367369

@@ -379,6 +381,9 @@ cargo test"
379381
The interpretation of exit statuses will be inverted (excluding special exit statuses), so status 0 means bad and other non-zero statuses mean good.
380382

381383
Default value: `false`
384+
* `--trust-endpoints` — Skip the pre-bisection checks
385+
386+
By default, `COMMAND` will be run on every revision `jj bisect run` assumes to be good or bad before bisection actually begins, as detailed under the documentation for `COMMAND`. This flag disables these checks.
382387

383388

384389

cli/tests/test_bisect_command.rs

Lines changed: 180 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ fn test_bisect_run() -> TestResult {
7171
create_commit(&work_dir, "f", &["e"]);
7272

7373
std::fs::write(&bisection_script, ["fail"].join("\0"))?;
74-
insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=..", &bisector_path]), @"
74+
insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=..", "--trust-endpoints", &bisector_path]), @"
7575
Bisecting: 5 revisions left to test after this (roughly 3 steps)
7676
Now evaluating: royxmykx dffaa0d4 c | c
7777
fake-bisector testing commit dffaa0d4daccf6cee70bac3498fae3b3fd5d6b5b
@@ -111,7 +111,7 @@ fn test_bisect_run() -> TestResult {
111111
// Try with legacy command argument
112112
std::fs::write(&bisection_script, ["fail"].join("\0"))?;
113113
// Testing only stderr to avoid a variable op id in the stdout.
114-
insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=..", "--command", &bisector_path]).success().stderr, @"
114+
insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=..", "--trust-endpoints", "--command", &bisector_path]).success().stderr, @"
115115
Warning: `--command` is deprecated; use positional arguments instead: `jj bisect run --range=... -- $FAKE_BISECTOR_PATH`
116116
Working copy (@) now at: nkmrtpmo 1601f7b4 (empty) (no description set)
117117
Parent commit (@-) : royxmykx dffaa0d4 c | c
@@ -136,6 +136,169 @@ fn test_bisect_run() -> TestResult {
136136
Ok(())
137137
}
138138

139+
#[test]
140+
// like test_bisect::test_bisect_nonlinear
141+
// bisecting over commits 0,1,2,3,4,5,6 with 5 being the first bad commit
142+
// this fails because 6 should also be bad!
143+
fn test_bisect_run_nonlinear_no_merge() -> TestResult {
144+
let mut test_env = TestEnvironment::default();
145+
let bisector_path = fake_bisector_path();
146+
test_env.set_up_fake_bisector();
147+
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
148+
let work_dir = test_env.work_dir("repo");
149+
150+
// h
151+
// |\
152+
// f g
153+
// | |
154+
// d e
155+
// | |
156+
// b c
157+
// |/
158+
// a
159+
160+
create_commit(&work_dir, "a", &[]);
161+
create_commit(&work_dir, "b", &["a"]);
162+
create_commit(&work_dir, "c", &["a"]);
163+
create_commit(&work_dir, "d", &["b"]);
164+
create_commit(&work_dir, "e", &["c"]);
165+
create_commit(&work_dir, "f", &["d"]);
166+
create_commit(&work_dir, "g", &["e"]);
167+
create_commit(&work_dir, "h", &["f", "g"]);
168+
169+
// rather than complicating fake_bisector, we try and --find-good
170+
insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=ancestors(parents(h))", "--find-good", "--", &bisector_path, "--require-file=f"]), @"
171+
Pre-bisection check: ensuring this commit is good:
172+
kmkuslsw 3548fddf f | f
173+
fake-bisector testing commit 3548fddfc5b69ba4c5b78a19c1cc6d39e52e5a22
174+
Pre-bisection check: ensuring this commit is good:
175+
lylxulpl 97949f0f g | g
176+
fake-bisector testing commit 97949f0f52afedfc72ef44d63d21266bbfc42774
177+
Cannot bisect: this commit was expected to be good, but was found to be bad (exit status: 1) instead.
178+
Search complete. To discard any revisions created during search, run:
179+
jj op restore 7d0937f0feb3
180+
[EOF]
181+
------- stderr -------
182+
Working copy (@) now at: xznxytkn 08be40de (empty) (no description set)
183+
Parent commit (@-) : kmkuslsw 3548fddf f | f
184+
Added 0 files, modified 0 files, removed 4 files
185+
Working copy (@) now at: smwtzssm fc3a931c (empty) (no description set)
186+
Parent commit (@-) : lylxulpl 97949f0f g | g
187+
Added 3 files, modified 0 files, removed 3 files
188+
Error: Bisection preconditions failed
189+
[EOF]
190+
[exit status: 1]
191+
");
192+
insta::assert_snapshot!(get_log_output(&work_dir), @"
193+
@ smwtzssmuxzk fc3a931c8da8 '' files:
194+
│ ○ nkmrtpmomlro b47137c13576 'h' files: h
195+
╭─┤
196+
○ │ lylxulplsnyw 97949f0f52af 'g' files: g
197+
○ │ znkkpsqqskkl efcb3d331401 'e' files: e
198+
○ │ royxmykxtrkr 991a7501d660 'c' files: c
199+
│ ○ kmkuslswpqwq 3548fddfc5b6 'f' files: f
200+
│ ○ vruxwmqvtpmx 01a5f35e756c 'd' files: d
201+
│ ○ zsuskulnrvyr 123b4d91f6e5 'b' files: b
202+
├─╯
203+
○ rlvkpnrzqnoo 7d980be7a1d4 'a' files: a
204+
◆ zzzzzzzzzzzz 000000000000 '' files:
205+
[EOF]
206+
");
207+
Ok(())
208+
}
209+
// like test_bisect_run_nonlinear_with_merge, but we include the merge commit.
210+
// that makes it okay for commit f to be the first bad one.
211+
#[test]
212+
fn test_bisect_run_nonlinear_with_merge() -> TestResult {
213+
let mut test_env = TestEnvironment::default();
214+
let bisector_path = fake_bisector_path();
215+
test_env.set_up_fake_bisector();
216+
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
217+
let work_dir = test_env.work_dir("repo");
218+
219+
// h
220+
// |\
221+
// f g
222+
// | |
223+
// d e
224+
// | |
225+
// b c
226+
// |/
227+
// a
228+
229+
create_commit(&work_dir, "a", &[]);
230+
create_commit(&work_dir, "b", &["a"]);
231+
create_commit(&work_dir, "c", &["a"]);
232+
create_commit(&work_dir, "d", &["b"]);
233+
create_commit(&work_dir, "e", &["c"]);
234+
create_commit(&work_dir, "f", &["d"]);
235+
create_commit(&work_dir, "g", &["e"]);
236+
create_commit(&work_dir, "h", &["f", "g"]);
237+
238+
insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=..", "--find-good", "--", &bisector_path, "--require-file=f"]), @"
239+
Pre-bisection check: ensuring this commit is good:
240+
nkmrtpmo b47137c1 h | h
241+
fake-bisector testing commit b47137c135761c932a7d0c1619d6121868eb7df9
242+
Bisecting: 8 revisions left to test after this (roughly 4 steps)
243+
Now evaluating: royxmykx 991a7501 c | c
244+
fake-bisector testing commit 991a7501d660abb6a80e8b00f77c651d76d845d7
245+
The revision is bad.
246+
247+
Bisecting: 5 revisions left to test after this (roughly 3 steps)
248+
Now evaluating: znkkpsqq efcb3d33 e | e
249+
fake-bisector testing commit efcb3d33140196e8aa5157e4c865e9a4c513f8bb
250+
The revision is bad.
251+
252+
Bisecting: 4 revisions left to test after this (roughly 3 steps)
253+
Now evaluating: vruxwmqv 01a5f35e d | d
254+
fake-bisector testing commit 01a5f35e756cec7f8543120aeedcce0d086a2504
255+
The revision is bad.
256+
257+
Bisecting: 2 revisions left to test after this (roughly 2 steps)
258+
Now evaluating: kmkuslsw 3548fddf f | f
259+
fake-bisector testing commit 3548fddfc5b69ba4c5b78a19c1cc6d39e52e5a22
260+
The revision is good.
261+
262+
Search complete. To discard any revisions created during search, run:
263+
jj op restore 7d0937f0feb3
264+
The first good revision is: kmkuslsw 3548fddf f | f
265+
[EOF]
266+
------- stderr -------
267+
Working copy (@) now at: xznxytkn 8d14f10f (empty) (no description set)
268+
Parent commit (@-) : nkmrtpmo b47137c1 h | h
269+
Working copy (@) now at: smwtzssm 38d94e5b (empty) (no description set)
270+
Parent commit (@-) : royxmykx 991a7501 c | c
271+
Added 0 files, modified 0 files, removed 6 files
272+
Working copy (@) now at: zlusorwl 00dc6326 (empty) (no description set)
273+
Parent commit (@-) : znkkpsqq efcb3d33 e | e
274+
Added 1 files, modified 0 files, removed 0 files
275+
Working copy (@) now at: ukzzzykq 6e876421 (empty) (no description set)
276+
Parent commit (@-) : vruxwmqv 01a5f35e d | d
277+
Added 2 files, modified 0 files, removed 2 files
278+
Working copy (@) now at: lqoxyrsk 4967f850 (empty) (no description set)
279+
Parent commit (@-) : kmkuslsw 3548fddf f | f
280+
Added 1 files, modified 0 files, removed 0 files
281+
[EOF]
282+
");
283+
284+
insta::assert_snapshot!(get_log_output(&work_dir), @"
285+
@ lqoxyrskwpry 4967f85032d5 '' files:
286+
│ ○ nkmrtpmomlro b47137c13576 'h' files: h
287+
╭─┤
288+
│ ○ lylxulplsnyw 97949f0f52af 'g' files: g
289+
│ ○ znkkpsqqskkl efcb3d331401 'e' files: e
290+
│ ○ royxmykxtrkr 991a7501d660 'c' files: c
291+
○ │ kmkuslswpqwq 3548fddfc5b6 'f' files: f
292+
○ │ vruxwmqvtpmx 01a5f35e756c 'd' files: d
293+
○ │ zsuskulnrvyr 123b4d91f6e5 'b' files: b
294+
├─╯
295+
○ rlvkpnrzqnoo 7d980be7a1d4 'a' files: a
296+
◆ zzzzzzzzzzzz 000000000000 '' files:
297+
[EOF]
298+
");
299+
Ok(())
300+
}
301+
139302
#[test]
140303
fn test_bisect_run_find_first_good() {
141304
let mut test_env = TestEnvironment::default();
@@ -151,7 +314,7 @@ fn test_bisect_run_find_first_good() {
151314
create_commit(&work_dir, "e", &["d"]);
152315
create_commit(&work_dir, "f", &["e"]);
153316

154-
insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=..", "--find-good", &bisector_path]), @"
317+
insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=..", "--find-good", "--trust-endpoints", &bisector_path]), @"
155318
Bisecting: 5 revisions left to test after this (roughly 3 steps)
156319
Now evaluating: royxmykx dffaa0d4 c | c
157320
fake-bisector testing commit dffaa0d4daccf6cee70bac3498fae3b3fd5d6b5b
@@ -202,7 +365,13 @@ fn test_bisect_run_missing_bisector() {
202365
create_commit(&work_dir, "e", &["d"]);
203366
create_commit(&work_dir, "f", &["e"]);
204367

205-
let output = work_dir.run_jj(["bisect", "run", "--range=..", "nonexistent"]);
368+
let output = work_dir.run_jj([
369+
"bisect",
370+
"run",
371+
"--range=..",
372+
"--trust-endpoints",
373+
"nonexistent",
374+
]);
206375
if cfg!(unix) {
207376
insta::assert_snapshot!(output, @r"
208377
Bisecting: 5 revisions left to test after this (roughly 3 steps)
@@ -249,7 +418,7 @@ fn test_bisect_run_with_args() {
249418
create_commit(&work_dir, "e", &["d"]);
250419
create_commit(&work_dir, "f", &["e"]);
251420

252-
insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=..", "--find-good", "--", &bisector_path, "--require-file=c"]), @"
421+
insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=..", "--find-good", "--trust-endpoints", "--", &bisector_path, "--require-file=c"]), @"
253422
Bisecting: 5 revisions left to test after this (roughly 3 steps)
254423
Now evaluating: royxmykx dffaa0d4 c | c
255424
fake-bisector testing commit dffaa0d4daccf6cee70bac3498fae3b3fd5d6b5b
@@ -312,7 +481,7 @@ fn test_bisect_run_crash() -> TestResult {
312481

313482
// bisector crash is equivalent to a failure
314483
std::fs::write(&bisection_script, ["crash"].join("\0"))?;
315-
insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=..", &bisector_path]), @"
484+
insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=..", "--trust-endpoints", &bisector_path]), @"
316485
Bisecting: 5 revisions left to test after this (roughly 3 steps)
317486
Now evaluating: royxmykx dffaa0d4 c | c
318487
fake-bisector testing commit dffaa0d4daccf6cee70bac3498fae3b3fd5d6b5b
@@ -353,7 +522,7 @@ fn test_bisect_run_abort() -> TestResult {
353522

354523
// stop immediately on failure
355524
std::fs::write(&bisection_script, ["abort"].join("\0"))?;
356-
insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=..", &bisector_path]), @"
525+
insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=..", "--trust-endpoints", &bisector_path]), @"
357526
Bisecting: 2 revisions left to test after this (roughly 2 steps)
358527
Now evaluating: rlvkpnrz 7d980be7 a | a
359528
fake-bisector testing commit 7d980be7a1d499e4d316ab4c01242885032f7eaf
@@ -386,7 +555,7 @@ fn test_bisect_run_skip() -> TestResult {
386555
create_commit(&work_dir, "b", &["a"]);
387556

388557
std::fs::write(&bisection_script, ["skip"].join("\0"))?;
389-
insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=..", &bisector_path]), @"
558+
insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=..", "--trust-endpoints", &bisector_path]), @"
390559
Bisecting: 1 revisions left to test after this (roughly 1 steps)
391560
Now evaluating: rlvkpnrz 7d980be7 a | a
392561
fake-bisector testing commit 7d980be7a1d499e4d316ab4c01242885032f7eaf
@@ -422,7 +591,7 @@ fn test_bisect_run_multiple_results() {
422591
create_commit(&work_dir, "c", &["a"]);
423592
create_commit(&work_dir, "d", &["c"]);
424593

425-
insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=a|b|c|d", &bisector_path]), @"
594+
insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=a|b|c|d", "--trust-endpoints", &bisector_path]), @"
426595
Bisecting: 2 revisions left to test after this (roughly 2 steps)
427596
Now evaluating: rlvkpnrz 7d980be7 a | a
428597
fake-bisector testing commit 7d980be7a1d499e4d316ab4c01242885032f7eaf
@@ -468,7 +637,7 @@ fn test_bisect_run_write_file() -> TestResult {
468637
&bisection_script,
469638
["write new-file\nsome contents", "fail"].join("\0"),
470639
)?;
471-
insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=..", &bisector_path]), @"
640+
insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=..", "--trust-endpoints", &bisector_path]), @"
472641
Bisecting: 4 revisions left to test after this (roughly 3 steps)
473642
Now evaluating: zsuskuln 123b4d91 b | b
474643
fake-bisector testing commit 123b4d91f6e5e39bfed39bae3bacf9380dc79078
@@ -534,7 +703,7 @@ fn test_bisect_run_jj_command() -> TestResult {
534703
create_commit(&work_dir, "e", &["d"]);
535704

536705
std::fs::write(&bisection_script, ["jj new -mtesting", "fail"].join("\0"))?;
537-
insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=..", &bisector_path]), @"
706+
insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=..", "--trust-endpoints", &bisector_path]), @"
538707
Bisecting: 4 revisions left to test after this (roughly 3 steps)
539708
Now evaluating: zsuskuln 123b4d91 b | b
540709
fake-bisector testing commit 123b4d91f6e5e39bfed39bae3bacf9380dc79078

0 commit comments

Comments
 (0)