jj-cli 0.41.0

Jujutsu - an experimental version control system
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
// Copyright 2022 The Jujutsu Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use testutils::TestResult;

use crate::common::CommandOutput;
use crate::common::TestEnvironment;
use crate::common::TestWorkDir;
use crate::common::create_commit;
use crate::common::fake_bisector_path;

#[test]
fn test_bisect_run_missing_command() {
    let test_env = TestEnvironment::default();
    test_env.run_jj_in(".", ["git", "init", "repo"]).success();
    let work_dir = test_env.work_dir("repo");

    insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=.."]), @"
    ------- stderr -------
    Error: Command argument is required
    [EOF]
    [exit status: 2]
    ");
}

#[test]
fn test_bisect_run_empty_revset() -> TestResult {
    let mut test_env = TestEnvironment::default();
    let bisector_path = fake_bisector_path();
    let bisection_script = test_env.set_up_fake_bisector();
    test_env.run_jj_in(".", ["git", "init", "repo"]).success();
    let work_dir = test_env.work_dir("repo");

    std::fs::write(&bisection_script, ["fail"].join("\0"))?;
    insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=none()", &bisector_path]), @"
    Search complete. To discard any revisions created during search, run:
      jj op restore 90267f31f904
    [EOF]
    ------- stderr -------
    Error: Could not find the first bad revision. Was the input range empty?
    [EOF]
    [exit status: 1]
    ");
    Ok(())
}

#[test]
fn test_bisect_run() -> TestResult {
    let mut test_env = TestEnvironment::default();
    let bisector_path = fake_bisector_path();
    let bisection_script = test_env.set_up_fake_bisector();
    test_env.run_jj_in(".", ["git", "init", "repo"]).success();
    let work_dir = test_env.work_dir("repo");

    create_commit(&work_dir, "a", &[]);
    create_commit(&work_dir, "b", &["a"]);
    create_commit(&work_dir, "c", &["b"]);
    create_commit(&work_dir, "d", &["c"]);
    create_commit(&work_dir, "e", &["d"]);
    create_commit(&work_dir, "f", &["e"]);

    std::fs::write(&bisection_script, ["fail"].join("\0"))?;
    insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=..", &bisector_path]), @"
    Bisecting: 5 revisions left to test after this (roughly 3 steps)
    Now evaluating: royxmykx dffaa0d4 c | c
    fake-bisector testing commit dffaa0d4daccf6cee70bac3498fae3b3fd5d6b5b
    The revision is bad.

    Bisecting: 2 revisions left to test after this (roughly 2 steps)
    Now evaluating: rlvkpnrz 7d980be7 a | a
    fake-bisector testing commit 7d980be7a1d499e4d316ab4c01242885032f7eaf
    The revision is bad.

    Search complete. To discard any revisions created during search, run:
      jj op restore 6f4b9c7057b1
    The first bad revision is: rlvkpnrz 7d980be7 a | a
    [EOF]
    ------- stderr -------
    Working copy  (@) now at: lylxulpl 68b3a16f (empty) (no description set)
    Parent commit (@-)      : royxmykx dffaa0d4 c | c
    Added 0 files, modified 0 files, removed 3 files
    Working copy  (@) now at: rsllmpnm 5f328bc5 (empty) (no description set)
    Parent commit (@-)      : rlvkpnrz 7d980be7 a | a
    Added 0 files, modified 0 files, removed 2 files
    [EOF]
    ");
    insta::assert_snapshot!(get_log_output(&work_dir), @"
    @  rsllmpnmslon 5f328bc5fde0 '' files:
    │ ○  kmkuslswpqwq 8b67af288466 'f' files: f
    │ ○  znkkpsqqskkl 62d30ded0e8f 'e' files: e
    │ ○  vruxwmqvtpmx 86be7a223919 'd' files: d
    │ ○  royxmykxtrkr dffaa0d4dacc 'c' files: c
    │ ○  zsuskulnrvyr 123b4d91f6e5 'b' files: b
    ├─╯
    ○  rlvkpnrzqnoo 7d980be7a1d4 'a' files: a
    ◆  zzzzzzzzzzzz 000000000000 '' files:
    [EOF]
    ");

    // Try with legacy command argument
    std::fs::write(&bisection_script, ["fail"].join("\0"))?;
    // Testing only stderr to avoid a variable op id in the stdout.
    insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=..", "--command", &bisector_path]).success().stderr, @"
    Warning: `--command` is deprecated; use positional arguments instead: `jj bisect run --range=... -- $FAKE_BISECTOR_PATH`
    Working copy  (@) now at: nkmrtpmo 1601f7b4 (empty) (no description set)
    Parent commit (@-)      : royxmykx dffaa0d4 c | c
    Added 2 files, modified 0 files, removed 0 files
    Working copy  (@) now at: ruktrxxu fb9e625c (empty) (no description set)
    Parent commit (@-)      : rlvkpnrz 7d980be7 a | a
    Added 0 files, modified 0 files, removed 2 files
    [EOF]
    ");
    insta::assert_snapshot!(get_log_output(&work_dir), @"
    @  ruktrxxusqqp fb9e625c1023 '' files:
    │ ○  kmkuslswpqwq 8b67af288466 'f' files: f
    │ ○  znkkpsqqskkl 62d30ded0e8f 'e' files: e
    │ ○  vruxwmqvtpmx 86be7a223919 'd' files: d
    │ ○  royxmykxtrkr dffaa0d4dacc 'c' files: c
    │ ○  zsuskulnrvyr 123b4d91f6e5 'b' files: b
    ├─╯
    ○  rlvkpnrzqnoo 7d980be7a1d4 'a' files: a
    ◆  zzzzzzzzzzzz 000000000000 '' files:
    [EOF]
    ");
    Ok(())
}

#[test]
fn test_bisect_run_find_first_good() {
    let mut test_env = TestEnvironment::default();
    let bisector_path = fake_bisector_path();
    test_env.set_up_fake_bisector();
    test_env.run_jj_in(".", ["git", "init", "repo"]).success();
    let work_dir = test_env.work_dir("repo");

    create_commit(&work_dir, "a", &[]);
    create_commit(&work_dir, "b", &["a"]);
    create_commit(&work_dir, "c", &["b"]);
    create_commit(&work_dir, "d", &["c"]);
    create_commit(&work_dir, "e", &["d"]);
    create_commit(&work_dir, "f", &["e"]);

    insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=..", "--find-good", &bisector_path]), @"
    Bisecting: 5 revisions left to test after this (roughly 3 steps)
    Now evaluating: royxmykx dffaa0d4 c | c
    fake-bisector testing commit dffaa0d4daccf6cee70bac3498fae3b3fd5d6b5b
    The revision is good.

    Bisecting: 2 revisions left to test after this (roughly 2 steps)
    Now evaluating: rlvkpnrz 7d980be7 a | a
    fake-bisector testing commit 7d980be7a1d499e4d316ab4c01242885032f7eaf
    The revision is good.

    Search complete. To discard any revisions created during search, run:
      jj op restore 6f4b9c7057b1
    The first good revision is: rlvkpnrz 7d980be7 a | a
    [EOF]
    ------- stderr -------
    Working copy  (@) now at: lylxulpl 68b3a16f (empty) (no description set)
    Parent commit (@-)      : royxmykx dffaa0d4 c | c
    Added 0 files, modified 0 files, removed 3 files
    Working copy  (@) now at: rsllmpnm 5f328bc5 (empty) (no description set)
    Parent commit (@-)      : rlvkpnrz 7d980be7 a | a
    Added 0 files, modified 0 files, removed 2 files
    [EOF]
    ");
    insta::assert_snapshot!(get_log_output(&work_dir), @"
    @  rsllmpnmslon 5f328bc5fde0 '' files:
    │ ○  kmkuslswpqwq 8b67af288466 'f' files: f
    │ ○  znkkpsqqskkl 62d30ded0e8f 'e' files: e
    │ ○  vruxwmqvtpmx 86be7a223919 'd' files: d
    │ ○  royxmykxtrkr dffaa0d4dacc 'c' files: c
    │ ○  zsuskulnrvyr 123b4d91f6e5 'b' files: b
    ├─╯
    ○  rlvkpnrzqnoo 7d980be7a1d4 'a' files: a
    ◆  zzzzzzzzzzzz 000000000000 '' files:
    [EOF]
    ");
}

#[test]
fn test_bisect_run_missing_bisector() {
    let test_env = TestEnvironment::default();
    test_env.run_jj_in(".", ["git", "init", "repo"]).success();
    let work_dir = test_env.work_dir("repo");

    create_commit(&work_dir, "a", &[]);
    create_commit(&work_dir, "b", &["a"]);
    create_commit(&work_dir, "c", &["b"]);
    create_commit(&work_dir, "d", &["c"]);
    create_commit(&work_dir, "e", &["d"]);
    create_commit(&work_dir, "f", &["e"]);

    let output = work_dir.run_jj(["bisect", "run", "--range=..", "nonexistent"]);
    if cfg!(unix) {
        insta::assert_snapshot!(output, @r"
        Bisecting: 5 revisions left to test after this (roughly 3 steps)
        Now evaluating: royxmykx dffaa0d4 c | c
        [EOF]
        ------- stderr -------
        Working copy  (@) now at: lylxulpl 68b3a16f (empty) (no description set)
        Parent commit (@-)      : royxmykx dffaa0d4 c | c
        Added 0 files, modified 0 files, removed 3 files
        Error: Failed to run evaluation command
        Caused by: No such file or directory (os error 2)
        [EOF]
        [exit status: 1]
        ");
    } else if cfg!(windows) {
        insta::assert_snapshot!(output, @"
        Bisecting: 5 revisions left to test after this (roughly 3 steps)
        Now evaluating: royxmykx dffaa0d4 c | c
        [EOF]
        ------- stderr -------
        Working copy  (@) now at: lylxulpl 68b3a16f (empty) (no description set)
        Parent commit (@-)      : royxmykx dffaa0d4 c | c
        Added 0 files, modified 0 files, removed 3 files
        Error: Failed to run evaluation command
        Caused by: program not found
        [EOF]
        [exit status: 1]
        ");
    }
}

#[test]
fn test_bisect_run_with_args() {
    let mut test_env = TestEnvironment::default();
    let bisector_path = fake_bisector_path();
    test_env.set_up_fake_bisector();
    test_env.run_jj_in(".", ["git", "init", "repo"]).success();
    let work_dir = test_env.work_dir("repo");

    create_commit(&work_dir, "a", &[]);
    create_commit(&work_dir, "b", &["a"]);
    create_commit(&work_dir, "c", &["b"]);
    create_commit(&work_dir, "d", &["c"]);
    create_commit(&work_dir, "e", &["d"]);
    create_commit(&work_dir, "f", &["e"]);

    insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=..", "--find-good", "--", &bisector_path, "--require-file=c"]), @"
    Bisecting: 5 revisions left to test after this (roughly 3 steps)
    Now evaluating: royxmykx dffaa0d4 c | c
    fake-bisector testing commit dffaa0d4daccf6cee70bac3498fae3b3fd5d6b5b
    The revision is good.

    Bisecting: 2 revisions left to test after this (roughly 2 steps)
    Now evaluating: rlvkpnrz 7d980be7 a | a
    fake-bisector testing commit 7d980be7a1d499e4d316ab4c01242885032f7eaf
    The revision is bad.

    Bisecting: 1 revisions left to test after this (roughly 1 steps)
    Now evaluating: zsuskuln 123b4d91 b | b
    fake-bisector testing commit 123b4d91f6e5e39bfed39bae3bacf9380dc79078
    The revision is bad.

    Search complete. To discard any revisions created during search, run:
      jj op restore 6f4b9c7057b1
    The first good revision is: royxmykx dffaa0d4 c | c
    [EOF]
    ------- stderr -------
    Working copy  (@) now at: lylxulpl 68b3a16f (empty) (no description set)
    Parent commit (@-)      : royxmykx dffaa0d4 c | c
    Added 0 files, modified 0 files, removed 3 files
    Working copy  (@) now at: rsllmpnm 5f328bc5 (empty) (no description set)
    Parent commit (@-)      : rlvkpnrz 7d980be7 a | a
    Added 0 files, modified 0 files, removed 2 files
    Working copy  (@) now at: zqsquwqt 042badd2 (empty) (no description set)
    Parent commit (@-)      : zsuskuln 123b4d91 b | b
    Added 1 files, modified 0 files, removed 0 files
    [EOF]
    ");
    insta::assert_snapshot!(get_log_output(&work_dir), @"
    @  zqsquwqtrvts 042badd28c1d '' files:
    │ ○  kmkuslswpqwq 8b67af288466 'f' files: f
    │ ○  znkkpsqqskkl 62d30ded0e8f 'e' files: e
    │ ○  vruxwmqvtpmx 86be7a223919 'd' files: d
    │ ○  royxmykxtrkr dffaa0d4dacc 'c' files: c
    ├─╯
    ○  zsuskulnrvyr 123b4d91f6e5 'b' files: b
    ○  rlvkpnrzqnoo 7d980be7a1d4 'a' files: a
    ◆  zzzzzzzzzzzz 000000000000 '' files:
    [EOF]
    ");
}

#[test]
fn test_bisect_run_crash() -> TestResult {
    let mut test_env = TestEnvironment::default();
    let bisector_path = fake_bisector_path();
    let bisection_script = test_env.set_up_fake_bisector();
    test_env.run_jj_in(".", ["git", "init", "repo"]).success();
    let work_dir = test_env.work_dir("repo");

    create_commit(&work_dir, "a", &[]);
    create_commit(&work_dir, "b", &["a"]);
    create_commit(&work_dir, "c", &["b"]);
    create_commit(&work_dir, "d", &["c"]);
    create_commit(&work_dir, "e", &["d"]);
    create_commit(&work_dir, "f", &["e"]);

    // bisector crash is equivalent to a failure
    std::fs::write(&bisection_script, ["crash"].join("\0"))?;
    insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=..", &bisector_path]), @"
    Bisecting: 5 revisions left to test after this (roughly 3 steps)
    Now evaluating: royxmykx dffaa0d4 c | c
    fake-bisector testing commit dffaa0d4daccf6cee70bac3498fae3b3fd5d6b5b
    The revision is bad.

    Bisecting: 2 revisions left to test after this (roughly 2 steps)
    Now evaluating: rlvkpnrz 7d980be7 a | a
    fake-bisector testing commit 7d980be7a1d499e4d316ab4c01242885032f7eaf
    The revision is bad.

    Search complete. To discard any revisions created during search, run:
      jj op restore 6f4b9c7057b1
    The first bad revision is: rlvkpnrz 7d980be7 a | a
    [EOF]
    ------- stderr -------
    Working copy  (@) now at: lylxulpl 68b3a16f (empty) (no description set)
    Parent commit (@-)      : royxmykx dffaa0d4 c | c
    Added 0 files, modified 0 files, removed 3 files
    Working copy  (@) now at: rsllmpnm 5f328bc5 (empty) (no description set)
    Parent commit (@-)      : rlvkpnrz 7d980be7 a | a
    Added 0 files, modified 0 files, removed 2 files
    [EOF]
    ");
    Ok(())
}

#[test]
fn test_bisect_run_abort() -> TestResult {
    let mut test_env = TestEnvironment::default();
    let bisector_path = fake_bisector_path();
    let bisection_script = test_env.set_up_fake_bisector();
    test_env.run_jj_in(".", ["git", "init", "repo"]).success();
    let work_dir = test_env.work_dir("repo");

    create_commit(&work_dir, "a", &[]);
    create_commit(&work_dir, "b", &["a"]);
    create_commit(&work_dir, "c", &["b"]);

    // stop immediately on failure
    std::fs::write(&bisection_script, ["abort"].join("\0"))?;
    insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=..", &bisector_path]), @"
    Bisecting: 2 revisions left to test after this (roughly 2 steps)
    Now evaluating: rlvkpnrz 7d980be7 a | a
    fake-bisector testing commit 7d980be7a1d499e4d316ab4c01242885032f7eaf
    Evaluation command returned 127 (command not found) - aborting bisection.

    Search complete. To discard any revisions created during search, run:
      jj op restore e229f77ca8de
    [EOF]
    ------- stderr -------
    Working copy  (@) now at: vruxwmqv 538d9e7f (empty) (no description set)
    Parent commit (@-)      : rlvkpnrz 7d980be7 a | a
    Added 0 files, modified 0 files, removed 2 files
    Error: Bisection aborted
    [EOF]
    [exit status: 1]
    ");
    Ok(())
}

#[test]
fn test_bisect_run_skip() -> TestResult {
    let mut test_env = TestEnvironment::default();
    let bisector_path = fake_bisector_path();
    let bisection_script = test_env.set_up_fake_bisector();
    test_env.run_jj_in(".", ["git", "init", "repo"]).success();
    let work_dir = test_env.work_dir("repo");

    // head (b) is assumed to be bad, even though all revisions are skipped
    create_commit(&work_dir, "a", &[]);
    create_commit(&work_dir, "b", &["a"]);

    std::fs::write(&bisection_script, ["skip"].join("\0"))?;
    insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=..", &bisector_path]), @"
    Bisecting: 1 revisions left to test after this (roughly 1 steps)
    Now evaluating: rlvkpnrz 7d980be7 a | a
    fake-bisector testing commit 7d980be7a1d499e4d316ab4c01242885032f7eaf
    It could not be determined if the revision is good or bad.

    Search complete. To discard any revisions created during search, run:
      jj op restore 07d6c9360663
    The first bad revision is: zsuskuln 123b4d91 b | b
    [EOF]
    ------- stderr -------
    Working copy  (@) now at: royxmykx 2144134b (empty) (no description set)
    Parent commit (@-)      : rlvkpnrz 7d980be7 a | a
    Added 0 files, modified 0 files, removed 1 files
    [EOF]
    ");
    Ok(())
}

#[test]
fn test_bisect_run_multiple_results() {
    let mut test_env = TestEnvironment::default();
    let bisector_path = fake_bisector_path();
    test_env.set_up_fake_bisector();
    test_env.run_jj_in(".", ["git", "init", "repo"]).success();
    let work_dir = test_env.work_dir("repo");

    // heads (d and b) are assumed to be bad
    create_commit(&work_dir, "a", &[]);
    create_commit(&work_dir, "b", &["a"]);
    create_commit(&work_dir, "c", &["a"]);
    create_commit(&work_dir, "d", &["c"]);

    insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=a|b|c|d", &bisector_path]), @"
    Bisecting: 2 revisions left to test after this (roughly 2 steps)
    Now evaluating: rlvkpnrz 7d980be7 a | a
    fake-bisector testing commit 7d980be7a1d499e4d316ab4c01242885032f7eaf
    The revision is good.

    Bisecting: 1 revisions left to test after this (roughly 1 steps)
    Now evaluating: royxmykx 991a7501 c | c
    fake-bisector testing commit 991a7501d660abb6a80e8b00f77c651d76d845d7
    The revision is good.

    Search complete. To discard any revisions created during search, run:
      jj op restore 854a7d496ee7
    The first bad revisions are:
    vruxwmqv a2dbb1aa d | d
    zsuskuln 123b4d91 b | b
    [EOF]
    ------- stderr -------
    Working copy  (@) now at: znkkpsqq 1b117fe7 (empty) (no description set)
    Parent commit (@-)      : rlvkpnrz 7d980be7 a | a
    Added 0 files, modified 0 files, removed 2 files
    Working copy  (@) now at: uuzqqzqu 6bf5f5e7 (empty) (no description set)
    Parent commit (@-)      : royxmykx 991a7501 c | c
    Added 1 files, modified 0 files, removed 0 files
    [EOF]
    ");
}

#[test]
fn test_bisect_run_write_file() -> TestResult {
    let mut test_env = TestEnvironment::default();
    let bisector_path = fake_bisector_path();
    let bisection_script = test_env.set_up_fake_bisector();
    test_env.run_jj_in(".", ["git", "init", "repo"]).success();
    let work_dir = test_env.work_dir("repo");

    create_commit(&work_dir, "a", &[]);
    create_commit(&work_dir, "b", &["a"]);
    create_commit(&work_dir, "c", &["b"]);
    create_commit(&work_dir, "d", &["c"]);
    create_commit(&work_dir, "e", &["d"]);

    std::fs::write(
        &bisection_script,
        ["write new-file\nsome contents", "fail"].join("\0"),
    )?;
    insta::assert_snapshot!(work_dir.run_jj(["bisect", "run", "--range=..", &bisector_path]), @"
    Bisecting: 4 revisions left to test after this (roughly 3 steps)
    Now evaluating: zsuskuln 123b4d91 b | b
    fake-bisector testing commit 123b4d91f6e5e39bfed39bae3bacf9380dc79078
    The revision is bad.

    Bisecting: 1 revisions left to test after this (roughly 1 steps)
    Now evaluating: rlvkpnrz 7d980be7 a | a
    fake-bisector testing commit 7d980be7a1d499e4d316ab4c01242885032f7eaf
    The revision is bad.

    Search complete. To discard any revisions created during search, run:
      jj op restore 621116e08872
    The first bad revision is: rlvkpnrz 7d980be7 a | a
    [EOF]
    ------- stderr -------
    Working copy  (@) now at: kmkuslsw 17e2a972 (empty) (no description set)
    Parent commit (@-)      : zsuskuln 123b4d91 b | b
    Added 0 files, modified 0 files, removed 3 files
    Working copy  (@) now at: msksykpx 2f6e298d (empty) (no description set)
    Parent commit (@-)      : rlvkpnrz 7d980be7 a | a
    Added 0 files, modified 0 files, removed 2 files
    [EOF]
    ");
    insta::assert_snapshot!(get_log_output(&work_dir), @"
    @  msksykpxotkr 891aeb03b623 '' files: new-file
    │ ○  kmkuslswpqwq 2bae881dc1bc '' files: new-file
    │ │ ○  znkkpsqqskkl 62d30ded0e8f 'e' files: e
    │ │ ○  vruxwmqvtpmx 86be7a223919 'd' files: d
    │ │ ○  royxmykxtrkr dffaa0d4dacc 'c' files: c
    │ ├─╯
    │ ○  zsuskulnrvyr 123b4d91f6e5 'b' files: b
    ├─╯
    ○  rlvkpnrzqnoo 7d980be7a1d4 'a' files: a
    ◆  zzzzzzzzzzzz 000000000000 '' files:
    [EOF]
    ");

    // No concurrent operations
    let output = work_dir.run_jj(["op", "log", "-n=5", "-T=description"]);
    insta::assert_snapshot!(output, @"
    @  snapshot working copy
    ○  Updated to revision 7d980be7a1d499e4d316ab4c01242885032f7eaf for bisection
    ○  snapshot working copy
    ○  Updated to revision 123b4d91f6e5e39bfed39bae3bacf9380dc79078 for bisection
    ○  create bookmark e pointing to commit 62d30ded0e8fdf8cf87012e6223898b97977fc8e
    [EOF]
    ");
    Ok(())
}

#[test]
fn test_bisect_run_jj_command() -> TestResult {
    let mut test_env = TestEnvironment::default();
    let bisector_path = fake_bisector_path();
    let bisection_script = test_env.set_up_fake_bisector();
    test_env.run_jj_in(".", ["git", "init", "repo"]).success();
    let work_dir = test_env.work_dir("repo");

    create_commit(&work_dir, "a", &[]);
    create_commit(&work_dir, "b", &["a"]);
    create_commit(&work_dir, "c", &["b"]);
    create_commit(&work_dir, "d", &["c"]);
    create_commit(&work_dir, "e", &["d"]);

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

    Bisecting: 1 revisions left to test after this (roughly 1 steps)
    Now evaluating: rlvkpnrz 7d980be7 a | a
    fake-bisector testing commit 7d980be7a1d499e4d316ab4c01242885032f7eaf
    The revision is bad.

    Search complete. To discard any revisions created during search, run:
      jj op restore 621116e08872
    The first bad revision is: rlvkpnrz 7d980be7 a | a
    [EOF]
    ------- stderr -------
    Working copy  (@) now at: kmkuslsw 17e2a972 (empty) (no description set)
    Parent commit (@-)      : zsuskuln 123b4d91 b | b
    Added 0 files, modified 0 files, removed 3 files
    Working copy  (@) now at: kmkuslsw/0 55b3b4a8 (divergent) (empty) testing
    Parent commit (@-)      : kmkuslsw/1 17e2a972 (divergent) (empty) (no description set)
    Working copy  (@) now at: msksykpx 2f6e298d (empty) (no description set)
    Parent commit (@-)      : rlvkpnrz 7d980be7 a | a
    Added 0 files, modified 0 files, removed 1 files
    Working copy  (@) now at: kmkuslsw/0 2f80658c (divergent) (empty) testing
    Parent commit (@-)      : msksykpx 2f6e298d (empty) (no description set)
    [EOF]
    ");
    insta::assert_snapshot!(get_log_output(&work_dir), @"
    @  kmkuslswpqwq 2f80658c4d26 'testing' files:
    ○  msksykpxotkr 2f6e298d59bd '' files:
    │ ○  kmkuslswpqwq 55b3b4a8b253 'testing' files:
    │ ○  kmkuslswpqwq 17e2a9721f61 '' files:
    │ │ ○  znkkpsqqskkl 62d30ded0e8f 'e' files: e
    │ │ ○  vruxwmqvtpmx 86be7a223919 'd' files: d
    │ │ ○  royxmykxtrkr dffaa0d4dacc 'c' files: c
    │ ├─╯
    │ ○  zsuskulnrvyr 123b4d91f6e5 'b' files: b
    ├─╯
    ○  rlvkpnrzqnoo 7d980be7a1d4 'a' files: a
    ◆  zzzzzzzzzzzz 000000000000 '' files:
    [EOF]
    ");

    // No concurrent operations
    let output = work_dir.run_jj(["op", "log", "-n=5", "-T=description"]);
    insta::assert_snapshot!(output, @"
    @  new empty commit
    ○  Updated to revision 7d980be7a1d499e4d316ab4c01242885032f7eaf for bisection
    ○  new empty commit
    ○  Updated to revision 123b4d91f6e5e39bfed39bae3bacf9380dc79078 for bisection
    ○  create bookmark e pointing to commit 62d30ded0e8fdf8cf87012e6223898b97977fc8e
    [EOF]
    ");
    Ok(())
}

#[must_use]
fn get_log_output(work_dir: &TestWorkDir) -> CommandOutput {
    let template = r#"separate(" ",
    change_id.short(),
    commit_id.short(),
    "'" ++  description.first_line() ++ "'",
    "files: " ++ diff.files().map(|e| e.path())
)"#;
    work_dir.run_jj(["log", "-T", template])
}