summaryrefslogtreecommitdiff
path: root/round-robin-bisect.sh
blob: 8f8832585acb6a13145eb0e68ccc32cbe4207d66 (plain)
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
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
#!/bin/bash

set -ef -o pipefail

scripts=$(dirname $0)
# shellcheck source=jenkins-helpers.sh
. $scripts/jenkins-helpers.sh

declare -A rr

# Process bisect-only args
convert_args_to_variables "$@"
shift "$SHIFT_CONVERTED_ARGS"

obligatory_variables bad_git build_script current_project
declare bad_git build_script current_project

# Relative artifacts are used for generation of manifests and reproduction
# instructions.
rel_artifacts="${rel_artifacts-artifacts}"
artifacts=$(pwd)/$rel_artifacts

fresh_dir $artifacts \
	  "$artifacts/manifest.sh" \
	  "$artifacts/build-parameters/manifest.sh" \
	  "$artifacts/jenkins/*"

BUILD_URL="${BUILD_URL:-$(pwd)}"
replay_log="${replay_log-}"
reproduce_bisect="${reproduce_bisect:-false}"

# Process build args and record them in build-parameters.sh
convert_args_to_variables ^^ $reproduce_bisect %%build_parameters $artifacts/build-parameters "$@"
$reproduce_bisect || manifest_pop
# Account for "^^ false %%foo bar" options
SHIFT_CONVERTED_ARGS=$(($SHIFT_CONVERTED_ARGS-4))
shift "$SHIFT_CONVERTED_ARGS"

obligatory_variables build_parameters rr[ci_project] rr[ci_config]
declare build_parameters

# Process build args and record them in build-parameters.sh
convert_args_to_variables ^^ $reproduce_bisect %%baseline_parameters $artifacts/baseline-parameters "$@"
$reproduce_bisect || manifest_pop
# Account for "^^ false %%foo bar" options
SHIFT_CONVERTED_ARGS=$(($SHIFT_CONVERTED_ARGS-4))
shift "$SHIFT_CONVERTED_ARGS"

obligatory_variables baseline_parameters
declare baseline_parameters

verbose="${verbose-true}"

set -u

if $verbose; then set -x; fi

mkdir -p $artifacts/jenkins
touch $artifacts/jenkins/build-name

trap print_traceback EXIT

# Exit with success
exit_0 () {
    # Cleanup bisect/ directory, which has a full [unneeded] copy of the build.
    chmod -R +rwx bisect/
    rm -rf bisect/
    trap "" EXIT
    exit 0
}

current_project_dir=$(get_component_dir $current_project)

bad_url="${bad_git%#*}"
bad_branch="${bad_git#*#}"

rebase_workaround=false
rebase_workaround_opts=()

case "${rr[ci_project]}/${rr[ci_config]}:$current_project" in
    tcwg_kernel/*-next-*:linux)
	# Workaround linux-next/master rebasing on top of linux-next/stable.
	# Search for regressions between linux-next:stable and
	# linux-next:master.
	echo "Rebase workaround: forcing linux baseline to linux-next:stable"
	rebase_workaround=true
	rebase_workaround_opts+=("==rr[linux_git]"
				 "$bad_url#stable")
	;;
esac

# Build baseline that we are going to re-use to speed-up bisection.
# (This also confirms that infrastructure is OK.)
echo "Testing baseline revision (expecting success)"
$build_script \
    ^^ $reproduce_bisect \
    %%rr[top_artifacts] "$rel_artifacts/build-baseline" \
    @@ $build_parameters/manifest.sh \
    @@ $baseline_parameters/manifest.sh \
    ==rr[mode] build \
    ==rr[update_baseline] force \
    --verbose "$verbose" \
    "${rebase_workaround_opts[@]}"

# Establish results in build-baseline as the baseline to compare test builds
# against.
$scripts/round-robin-baseline.sh \
    @@rr[top_artifacts] "$rel_artifacts/build-baseline" \
    __base_artifacts base-artifacts

baseline_rev="${baseline_rev-$(git -C $current_project_dir rev-parse HEAD)}"
cat <<EOF | manifest_out
declare -g baseline_rev=$baseline_rev
EOF

ln -f -s "build-baseline" "$artifacts/build-$baseline_rev"

mkdir -p ./bisect

# Save baseline build state, which we restore before individual bisect tests.
# This ensures that "bad" bisect tests won't affect "good" ones and vice versa.
baseline_exclude=(
    --exclude /bisect/ --exclude "/$rel_artifacts/" --exclude "/$current_project_dir/"
)
rsync -a --del --delete-excluded "${baseline_exclude[@]}" ./ ./bisect/baseline/

mkdir $artifacts/git-logs

# Make sure the sources are clean before bisecting
git -C $current_project_dir reset -q --hard

if [ -f "$replay_log" ]; then
    cp "$replay_log" $artifacts/git-logs/bisect_replay.sh
    git -C $current_project_dir bisect replay $artifacts/git-logs/bisect_replay.sh
else
    git -C $current_project_dir bisect start
fi

# Hard-link BISECT_LOG inside $artifacts to guarantee its upload to jenkins.
ln -f "$(pwd)"/$current_project_dir/.git/BISECT_LOG $artifacts/git-logs/bisect_log

if ! git -C $current_project_dir bisect log \
	| grep "^git bisect .* $baseline_rev\$" >/dev/null; then
    git -C $current_project_dir bisect good $baseline_rev
fi

# Bisect script.
#
# With this script we find the first commit that has regressed compared
# to baseline, but not, necessarily, the commit that caused regression in
# $bad_rev.  Consider the scenario:
# - rev_10 produced good result "2000" -- this is current baseline
# - rev_20 completely broke the build (say, result "10")
# - rev_22 fixed the build
# - rev_30 regressed the build to result "1000" -- this is the regression we
#   detected vs "2000" baseline.
#
# The script will identify rev_20 as the first failing commit, which will
# cause the baseline to be reset to rev_20 with metric "10".  When we then
# rebuild master (at rev_30) we will see a /progression/ from "10" to "1000",
# thus missing the regression of "2000" to "1000".
#
# To catch the "2000" to "1000" regression someone would need to manually
# trigger bisect between rev_22 and rev_30.
#
# We reduce the impact of the above by assigning negative values to result
# metric for builds that look very broken, and do not bisect regressions into
# the "negative" side.
cat > $artifacts/test.sh <<EOF
#!/bin/bash

set -euf -o pipefail

current_project_dir=\$1

rev=\$(git -C $current_project_dir rev-parse HEAD)

if git -C $current_project_dir bisect log | grep "^git bisect bad \$rev\\\$" >/dev/null; then
  exit 1
elif git -C $current_project_dir bisect log | grep "^git bisect skip \$rev\\\$" >/dev/null; then
  exit 125
elif git -C $current_project_dir bisect log | grep "^git bisect good \$rev\\\$" >/dev/null; then
  exit 0
fi

# Restore known-good baseline state.
rsync -a --del ${baseline_exclude[@]} ./bisect/baseline/ ./

$build_script \
  ^^ $reproduce_bisect \
  %%rr[top_artifacts] $rel_artifacts/build-\$rev \
  @@ $rel_artifacts/build-parameters/manifest.sh \
  ==rr[mode] bisect \
  ==rr[update_baseline] ignore \
  ==rr[${current_project}_git] "$bad_url#\$rev" \
  --verbose "$verbose" &
res=0 && wait \$! || res=\$?

git -C $current_project_dir reset -q --hard

if [ x"\$res" != x"0" ]; then
  if [ -f $rel_artifacts/build-\$rev/trigger-build-$current_project ]; then
    exit 1
  else
    # The build failed due to an uninteresting problem -- a prerequisite
    # failed to build or benchmarking harness went down.  We mark such
    # revisions "skipped", but up to a point.  If we skip more revisions
    # in a row, than half the number of tests necessary to finish the bisect,
    # then we mark such "skipped" revision as "bad".

    # Number of "git bisect skip" in a row
    n_skips=\$(git -C $current_project_dir bisect log | awk '
BEGIN { n_skips=0 }
/git bisect skip/ { n_skips++; next }
/git bisect/ { n_skips=0 }
END { print n_skips }
')
    revs_left=\$(git -C $current_project_dir bisect view --pretty=%H | wc -l)
    # Half the number of steps to finish the bisect
    n_steps_2=\$(echo "n_steps=l(\$revs_left)/l(2); scale=0; n_steps/2" | bc -l)
    if [ \$n_steps_2 -lt 2 ]; then
      # Avoid skipping revisions at the end of the bisect.
      n_steps_2=2
    fi
    if [ \$n_skips -le \$n_steps_2 ]; then
      exit 125
    else
      # We had several skips in a row and still have many revisions to bisect.
      # Mark this one "bad" to progress the bisect.
      exit 1
    fi
  fi
else
  exit 0
fi
EOF
chmod +x $artifacts/test.sh

# Fetch $bad_branch/$bad_rev from $bad_url
prev_rev=$(git -C $current_project_dir rev-parse HEAD)
# Note: avoid using clone_or_update_repo(), which, potentially, can delete
#       and re-clone the repo.  Deleting the repo would be bad, since we would
#       lose bisect state initialized by the above "git bisect" commands.
# Note: avoid using clone_or_update_repo(), which calls git_checkout(), which
#       does a more thorough job in cleaning up the repo directory than
#       "git reset -q --hard" in ./test.sh.  The logic here is that we want
#       the "bad" build to run in the same environment as the "test" builds.
git -C "$current_project_dir" fetch "$bad_url" "$bad_branch"
git -C "$current_project_dir" checkout --detach FETCH_HEAD
bad_rev="${bad_rev-$(git -C "$current_project_dir" rev-parse HEAD)}"
cat <<EOF | manifest_out
declare -g bad_rev=$bad_rev
EOF

if [ x"$baseline_rev" = x"$bad_rev" ]; then
    echo "WARNING: Detected regression with no change in sources of $current_project"
    sed -i -e "s/\$/-no_change/" $artifacts/jenkins/build-name
    res=0
else
    # Confirm regression in $bad_rev vs $baseline_rev.
    echo "Testing bad revision (expecting failure)"
    git -C $current_project_dir checkout --detach $bad_rev
    $artifacts/test.sh $current_project_dir &
    res=0 && wait $! || res=$?
fi

# Restore revision previously checked out.  Otherwise "git bisect run"
# below will not use replay info.
git -C $current_project_dir checkout --detach $prev_rev

if [ x"$res" = x"0" ]; then
    if $rebase_workaround; then
	echo "Rebase workaround: no regression between $baseline_rev and $bad_rev"
	sed -i -e "s/\$/-bad_rev-good/" $artifacts/jenkins/build-name
	cat >> $artifacts/trigger-build-rebase <<EOF
linux_git=$bad_url#$baseline_rev
update_baseline=force
EOF
    else
	# Build for $bad_rev is successful, which can be due to a number
	# of things:
	# - Regressions in speed benchmarking are not 100% stable,
	# - Something has changed in the build environment,
	# - Underlying hardware has changed,
	# - Something entirely different.
	# In all these cases we recover by rebuilding from baseline sources
	# and updating the baseline.
	# If baseline reset->build->bisect->reset happens again and again,
	# then this is a scripting or infrastructure problem, and we detect
	# it by unusually high ratio of forced builds in CI dashboard.
	echo "WARNING: build for bad_rev $bad_rev succeeded"
	sed -i -e "s/\$/-spurious/" $artifacts/jenkins/build-name
	cat > $artifacts/trigger-build-reset <<EOF
update_baseline=force
EOF
    fi
    exit_0
elif [ x"$res" = x"125" ]; then
    # We have confirmed a regression, but not what we have been triggered
    # to bisect.
    echo "WARNING: build for bad_rev $bad_rev showed uninteresting regression"
    sed -i -e "s/\$/-uninteresting/" $artifacts/jenkins/build-name
    exit_0
fi

if ! git -C $current_project_dir bisect log \
	| grep "^git bisect .* $bad_rev\$" >/dev/null; then
    git -C $current_project_dir bisect bad $bad_rev
    ln -f -s "build-$bad_rev" "$artifacts/build-bad"
fi

# Print first_bad revision (if detected)
get_first_bad ()
{
    (
    # Allow pipefail to handle error exit codes from git bisect log and grep.
    # Note that child shell inherits settings from parent shell, so we need
    # excplicitly set "+o pipefail".
    set -euf +o pipefail

    git -C $current_project_dir bisect log | tail -n1 \
	| grep "^# first bad commit:" \
	| sed -e "s/^# first bad commit: \[\([0-9a-f]*\)\].*/\1/"
    )
}

# Print revs tested during bisect.
# $1 -- Revision kind -- good/bad/skip.
#       Print all revisions if no $1 given.
print_tested_revs ()
{
    (
    # Allow pipefail to handle error exit codes from git bisect log and grep.
    # Note that child shell inherits settings from parent shell, so we need
    # excplicitly set "+o pipefail".
    set -euf +o pipefail

    local kind="${1-[a-z]*}"
    git -C $current_project_dir bisect log | grep "^git bisect $kind " \
	| sed -e "s/^git bisect $kind //"
    )
}

# Print ratio at which commit splits current bisection range, or "-1" if
# the commit is outside of bisection range.  The ideal split is 50/50 --
# right in the middle.
# $1: Commit SHA1
print_sha1_split ()
{
    local sha1="$1"

    local line
    line=$(grep -n "^$sha1\$" $commits_to_test | cut -d":" -f 1)
    if [ x"$line" = x"" ]; then
	echo "-1"
	return
    fi

    # Skip revisions that were already tested.  Good revisions are filtered
    # out in the above $commits_to_test check, and here we filter out
    # "bad" and "skip" revisions.
    if git -C $current_project_dir bisect log \
	    | grep "^git bisect .* $sha1\$" >/dev/null; then
	echo "-1"
	return
    fi

    line=$((100 * $line / $(cat $commits_to_test | wc -l)))
    if [ $line -gt 50 ]; then
	line=$((100 - $line))
    fi
    echo "$line"
}

# Try to reduce bisection range by testing regressions (and their parents)
# identified in other configurations.
print_interesting_commit ()
{
    (
    set -euf -o pipefail

    # Generate list of commits inside the bisection range.
    git -C $current_project_dir bisect view --pretty=%H > $commits_to_test

    # Bisecting linux-next.git regressions is difficult enough due to how
    # the tree is constructed, so we prefer to not use interesting-commits
    # when $rebase_workaround is true.  This makes linux-next bisects as
    # natural as they can be.
    if $rebase_workaround; then
	return
    fi

    # Clone interesting-commits.git repo, which contains a list of SHA1s
    # that might cut down bisection time.  Mostly, these are first_bad and
    # last_good commits.
    local icommits="bisect/interesting-commits"
    clone_or_update_repo $icommits master \
			 https://git-us.linaro.org/toolchain/ci/interesting-commits.git \
			 auto master >/dev/null 2>&1

    local project_dir
    project_dir=$icommits/$(interesting_subdir $current_project)

    if ! [ -d "$project_dir" ]; then
	return
    fi

    # Below loop can generate lots of console noise.
    set +x

    # Find an interesting commit that splits bisection range best.
    local sha1 prev_sha1 best_split=-1 best_sha1=""
    while read sha1; do
	while read prev_sha1; do
	    split=$(print_sha1_split "$prev_sha1")
	    if [ $split -gt $best_split ]; then
		best_split=$split
		best_sha1=$prev_sha1
	    fi
	done < <(echo "$sha1"
		 cd "$project_dir/$sha1"
		 find -name last_good -print0 | xargs -0 cat | sort -u)
    done < <(cd "$project_dir"; ls)

    if $verbose; then set -x; fi

    if [ "$best_sha1" = "" ]; then
	# We didn't find an interesting sha1, so use a stock recommendation by
	# git.  Note that we want to remain in the "try-interesting-commits"
	# loop (rather than switching to "git bisect run") in the hopes that
	# some other job will add a new entry to interesting-commits while
	# we are testing the stock revisions.
	best_sha1=$(git -C $current_project_dir bisect next | tail -n1 \
			| sed -e "s/^\[\([0-9a-f]\+\)\].*\$/\1/")
	# Ensure that best_sha1 is indeed a sha1.  I could not figure out
	# how to tell "git bisect next" to print only sha1 without extra
	# annotations, so have to parse the string with "sed".
	if ! echo "$best_sha1" | grep '^[0-9a-f]\+$' &>/dev/null; then
	    best_sha1=""
	fi
    fi
    echo "$best_split $best_sha1"
    )
}

commits_to_test=$artifacts/git-logs/commits_to_test

IFS=" " read -r split sha1 <<< "$(print_interesting_commit)"

# Record commits in the initial bisect range.  These are commits_to_test plus
# commits that have been tested.
commits_in_range=$artifacts/git-logs/commits_in_range
cp $commits_to_test $commits_in_range
print_tested_revs >> $commits_in_range

while [ x"$sha1" != x"" ] \
	  && [ x"$(get_first_bad </dev/null)" = x"" ]; do
    if [ "$split" = "-1" ]; then
	echo "Trying $sha1 stock recommendation"
    else
	echo "Trying $sha1 interesting commit, which splits bisections range" \
	     "at ${split}%"
    fi

    git -C $current_project_dir checkout --detach $sha1
    $artifacts/test.sh $current_project_dir &
    res=0 && wait $! || res=$?
    if [ x"$res" = x"0" ]; then
	git -C $current_project_dir bisect good || break
    elif [ x"$res" = x"125" ]; then
	# It may happen that we get to the point where only skipped commits
	# are left to test, and any new "git bisect skip" will return an error.
	# In this case break from this loop, and let "git bisect run" below
	# handle this case.  [We also add "|| break" for good and bad cases
	# for symmetry.]
	git -C $current_project_dir bisect skip || break
    else
	git -C $current_project_dir bisect bad || break
    fi

    IFS=" " read -r split sha1 <<< "$(print_interesting_commit)"
done

if [ x"$(get_first_bad)" = x"" ]; then
    # Run stock "git bisect run" for corner cases like $rebase_workaround.
    # Most of the time we have bisected the failure down to first_bad above.

    git -C $current_project_dir bisect run $artifacts/test.sh . &
    res=0 && wait $! || res=$?

    if [ x"$res" = x"0" ]; then
	assert_with_msg "Didn't find first bad commit!" [ x"$(get_first_bad)" != x"" ]
    fi
fi

first_bad=$(get_first_bad)

if [ x"$first_bad" != x"" ] \
       && ! [ -f $artifacts/build-$first_bad/trigger-build-$current_project ]; then
    # First_bad is not a real or "interesting" regression.  Perhaps it was
    # a "skipped" commit marked as "bad.
    first_bad=""
fi

if [ x"$first_bad" != x"" ]; then
    # "git bisect run" succeeded.  Check whether this is an actual regression
    # or bisection artifact.
    last_good=""
    bad_last_good=""
    for sha1 in $(git -C $current_project_dir rev-parse $first_bad^@); do
	# It seems that git-bisect assumes parent commit as "good" on
	# the basis of one of its children being "good".  Therefore we
	# can have a situation when we have parent P with children C1 and C2,
	# and child C1 has a child of its own CB.  Git-bisect tests C2 as
	# "good", and CB as "bad".  From C2 being good it assumes P as "good",
	# and it knows CB is "bad", so git-bisect returns C1 as the first bad
	# commit.
	# To simplify investigations we explicitly test parent of $first_bad.

	# Ignore commits outside of bisection range, but make an exception
	# for commits that weren't tested due to their child tested good.
	# This happens in projects that actively use merges, e.g., linux.
	if ! grep -q "^$sha1\$" $commits_in_range; then
	    child_tested_good=false
	    for tested_good in $(print_tested_revs good); do
		if git -C $current_project_dir merge-base --is-ancestor $sha1 $tested_good; then
		    child_tested_good=true
		    break
		fi
	    done
	    if ! $child_tested_good; then
		continue
	    fi
	fi

	echo "Testing first_bad's parent $sha1 (hoping for success)"
	git -C $current_project_dir checkout --detach "$sha1"
	$artifacts/test.sh $current_project_dir &
	res=0 && wait $! || res=$?
	if [ x"$res" = x"0" ]; then
	    last_good=$sha1
	    break
	fi
	bad_last_good=$sha1
    done

    if [ x"$last_good" = x"" ]; then
	assert_with_msg "Broken bisection range" [ x"$bad_last_good" != x"" ]
	# All parents of $first_bad tested bad.  This is, essencially,
	# same situation as "git bisect" failing, which we handle below.
	first_bad=""
    fi
fi

reset_rev=""
if [ x"$first_bad" != x"" ]; then
    reset_rev="$first_bad"
else
    # "Git bisect" didn't find the first_bad commit or this commit points to
    # an uninteresting regression.
    # Possible reasons include:
    # - We have marked the regressing commit as "skip", which can happen
    #   to tcwg_bmk* projects when benchmarking infra has a problem.
    #
    # - Merge-base of $baseline_rev and $bad_rev is worse than $baseline_rev.
    #   We want to reset baseline to HEAD in this case, so that we catch most
    #   of the commits that introduced change in the result metric.
    #
    # - We have marked a skipped commit as bad to advance bisect, even though
    #   the failure was uninteresting.
    #
    # So, to make at least some progress on narrowing down the regression ...
    # - look for the last commit that tested "good".  If it's not $baseline_rev,
    #   then we have narrowed down the bisection range somewhat.  Therefore,
    #   trigger two builds:
    #   - one to ADVANCE baseline to the last GOOD commit, and
    #   - another to expose the regression again.
    #
    # - If $baseline_rev is the only good commit, then we have got ourselves
    #   into a tricky situation: we can't find the regression and can't make
    #   a good build, which would advance the baseline.  To resolve this,
    #   trigger two builds:
    #   - one to RESET baseline to the last BAD commit, and
    #   - another to advance the baseline and expose another regression.
    #   Note that this approach correctly handles case when merge-base of
    #   $baseline_rev and $bad_rev tests "bad".

    last_good=$(print_tested_revs good | tail -n1)
    assert_with_msg "We should have at least baseline_rev as a good commit" \
		    [ x"$last_good" != x"" ]

    if [ x"$last_good" != x"$baseline_rev" ]; then
	sed -i -e "s/\$/-advance-baseline/" $artifacts/jenkins/build-name
    else
	# Reset baseline to the earliest bad revision with a true regression.
	# For this we look through tested-bad revisions in reverse order.
	for reset_rev in $(print_tested_revs bad | tac); do
	    if [ -f $artifacts/build-$reset_rev/trigger-build-$current_project ]; then
		break
	    fi
	done
	sed -i -e "s/\$/-reset-baseline/" $artifacts/jenkins/build-name
    fi
fi

# Bisect is officially over.

# Create trigger-build-* files for subsequent builds.

# Advance the baseline to $last_good revision.  Don't trigger unnecessary
# build for $baseline_rev itself.
if [ x"$last_good" != x"$baseline_rev" ]; then
    assert_with_msg "last_good should not be empty" [ x"$last_good" != x"" ]
    cat > $artifacts/trigger-build-1-last-good <<EOF
${current_project}_git=$bad_url#$last_good
update_baseline=force
EOF
    # Note that even though $last_good build should succeed, we are forcing
    # it to update the baseline.  Otherwise, should the last-good build fail,
    # it will start a bisect job, which will trigger some more builds.
    # The problem is that, as soon as our first-bad build resets the baseline,
    # all those builds triggered by the 2nd bisect will operate "in the past".
    # Therefore, we either need to handle builds "from the past", or, easier,
    # just force the last-good build to always succeed.
fi

if ! [ -f $artifacts/build-$bad_rev/trigger-build-$current_project ]; then
    # This can happen *only* when replaying bisects.
    # Otherwise $bad_rev is always tested second to $baseline_rev.
    bad_build=$(find $artifacts -path "$artifacts/build-*/trigger-build-$current_project" | tail -n1)
    assert_with_msg "No bad build during this bisect" [ x"$bad_build" != x"" ]
    mkdir -p $artifacts/build-$bad_rev
    cp $bad_build $artifacts/build-$bad_rev/trigger-build-$current_project
    sed -i -e "s/^\(${current_project}_git=.*\)#.*\$/\1#$bad_rev/" \
	$artifacts/build-$bad_rev/trigger-build-$current_project
fi

if [ x"$reset_rev" != x"" ]; then \
    if ! [ -f $artifacts/build-$reset_rev/trigger-build-$current_project ]; then
	# This is rare, but can happen that $reset_rev hasn't been tested.
	# Workaround by constructing trigger-build file from that of $bad_rev's.
	mkdir -p $artifacts/build-$reset_rev
	cp $artifacts/build-$bad_rev/trigger-build-$current_project \
	   $artifacts/build-$reset_rev/trigger-build-$current_project
	sed -i -e "s/^\(${current_project}_git=.*\)#$bad_rev\$/\1#$reset_rev/" \
	    $artifacts/build-$reset_rev/trigger-build-$current_project
    fi

    # Reset baseline to the regressed commit so that we will catch subsequent
    # regressions (worse than $bad_rev).
    cp $artifacts/build-$reset_rev/trigger-build-$current_project \
       $artifacts/trigger-build-2-reset
    echo "update_baseline=force" >> $artifacts/trigger-build-2-reset

    if [ "$reset_rev" = "$first_bad" ]; then
	# We have identified a single-commit regression, so notify developers
	# about it.
	echo "notify=onregression" >> $artifacts/trigger-build-2-reset
    fi
fi

# Trigger master build now instead of waiting for next timed SCM trigger.
# Make sure git specification is as it was passed to the bisect
# (i.e., master branch, not a specific SHA1).
cp $artifacts/build-$bad_rev/trigger-build-$current_project \
   $artifacts/trigger-build-3-default
sed -i -e "s%^\(${current_project}_git\)=.*\$%\1=$bad_git%" \
    $artifacts/trigger-build-3-default

# Save BISECT_* logs
find "$current_project_dir" -path "$current_project_dir/.git/BISECT_*" -print0 \
    | xargs -0 -I@ mv @ $artifacts/git-logs/

if [ x"$first_bad" != x"" ]; then
    sed -i -e "s/\$/-$first_bad/" $artifacts/jenkins/build-name

    ln -f -s "build-$first_bad" "$artifacts/build-first_bad"
    ln -f -s "build-$last_good" "$artifacts/build-last_good"
fi

exit_0