github.com/containers/podman/v5@v5.1.0-rc1/hack/buildah-vendor-treadmill (about) 1 #!/usr/bin/perl 2 # 3 # buildah-vendor-treadmill - daily vendor of latest-buildah onto latest-podman 4 # 5 package Podman::BuildahVendorTreadmill; 6 7 use v5.14; 8 use utf8; 9 use open qw( :encoding(UTF-8) :std ); 10 11 use strict; 12 use warnings; 13 14 use File::Temp qw(tempfile); 15 use JSON; 16 use LWP::UserAgent; 17 use POSIX qw(strftime); 18 19 (our $ME = $0) =~ s|.*/||; 20 our $VERSION = '0.3'; 21 22 # For debugging, show data structures using DumpTree($var) 23 #use Data::TreeDumper; $Data::TreeDumper::Displayaddress = 0; 24 25 ############################################################################### 26 # BEGIN user-customizable section 27 28 # Page describing this process in much more detail 29 our $Docs_URL = 30 'https://github.com/containers/podman/wiki/Buildah-Vendor-Treadmill'; 31 32 # github path to buildah 33 our $Buildah = 'github.com/containers/buildah'; 34 35 # FIXME FIXME FIXME: add 'main'? I hope we never need this script for branches. 36 our $Treadmill_PR_Title = 'DO NOT MERGE: buildah vendor treadmill'; 37 38 # Github API; this is where we query to find out the active treadmill PR 39 our $API_URL = 'https://api.github.com/graphql'; 40 41 # Use colors if available and if stdout is a tty 42 our $C_Highlight = ''; 43 our $C_Warning = ''; 44 our $C_Reset = ''; 45 eval ' 46 use Term::ANSIColor; 47 if (-t 1) { 48 $C_Highlight = color("green"); 49 $C_Warning = color("bold red"); 50 $C_Reset = color("reset"); 51 52 } 53 $SIG{__WARN__} = sub { print STDERR $C_Warning, "@_", $C_Reset; }; 54 55 '; 56 57 # END user-customizable section 58 ############################################################################### 59 60 ############################################################################### 61 # BEGIN boilerplate args checking, usage messages 62 63 sub usage { 64 print <<"END_USAGE"; 65 Usage: $ME [OPTIONS] [--sync | --pick [PR] | --reset ] 66 67 $ME is (2022-04-20) **EXPERIMENTAL** 68 69 $ME is intended to solve the problem of vendoring 70 buildah into podman. 71 72 Call me with one of three options: 73 74 --sync The usual case. Mostly used by Ed. Called from a 75 development branch, this just updates everything so 76 we vendor in latest-buildah (main) on top of 77 latest-podman (main). With a few sanity checks. 78 79 --pick Used for really-truly vendoring in a new buildah; will 80 cherry-pick a commit on your buildah-vendor working branch. 81 Optional PR arg is the ID of the treadmill PR on github. 82 83 --reset Used after vendoring buildah into main, when there 84 really aren't any buildah patches to keep rolling. 85 86 For latest documentation and best practices, please see: 87 88 $Docs_URL 89 90 OPTIONS: 91 92 --help display this message 93 --version display program name and version 94 END_USAGE 95 96 exit; 97 } 98 99 # Command-line options. Note that this operates directly on @ARGV ! 100 our %action; 101 our $debug = 0; 102 our $force_old_main = 0; # in --pick, proceeds even if main is old 103 our $force_retry = 0; # in --sync, continue despite saved checkpoint 104 our $force_testing = 0; # in --sync, test even no podman/buildah changes 105 our $verbose = 0; 106 our $NOT = ''; # print "blahing the blah$NOT\n" if $debug 107 sub handle_opts { 108 use Getopt::Long; 109 GetOptions( 110 'sync' => sub { $action{sync}++ }, 111 'pick' => sub { $action{pick}++ }, 112 'reset' => sub { $action{reset}++ }, 113 114 'force-old-main' => \$force_old_main, 115 'force-retry' => \$force_retry, 116 'force-testing' => \$force_testing, 117 118 'debug!' => \$debug, 119 'dry-run|n!' => sub { $NOT = ' [NOT]' }, 120 'verbose|v' => \$verbose, 121 122 help => \&usage, 123 version => sub { print "$ME version $VERSION\n"; exit 0 }, 124 ) or die "Try `$ME --help' for help\n"; 125 } 126 127 # END boilerplate args checking, usage messages 128 ############################################################################### 129 130 ############################## CODE BEGINS HERE ############################### 131 132 # The term is "modulino". 133 __PACKAGE__->main() unless caller(); 134 135 # Main code. 136 sub main { 137 # Note that we operate directly on @ARGV, not on function parameters. 138 # This is deliberate: it's because Getopt::Long only operates on @ARGV 139 # and there's no clean way to make it use @_. 140 handle_opts(); # will set package globals 141 142 my @action = keys(%action); 143 die "$ME: Please invoke me with one of --sync or --pick\n" 144 if ! @action; 145 die "$ME: Please invoke me with ONLY one of --sync or --pick\n" 146 if @action > 1; 147 148 my $handler = __PACKAGE__->can("do_@action") 149 or die "$ME: No handler available for --@action\n"; 150 151 # We've validated the command-line args. Before running action, check 152 # that repo is clean. None of our actions can be run on a dirty repo. 153 assert_clean_repo(); 154 155 $handler->(@ARGV); 156 } 157 158 ############################################################################### 159 # BEGIN sync and its helpers 160 161 sub do_sync { 162 die "$ME: --sync takes no arguments; try $ME --help\n" if @_; 163 164 # Preserve current branch name, so we can come back after switching to main 165 my $current_branch = git_current_branch(); 166 167 # Branch HEAD must be the treadmill commit. 168 my $commit_message = git('log', '-1', '--format=%s', 'HEAD'); 169 print "[$commit_message]\n" if $verbose; 170 $commit_message =~ /buildah.*treadmill/ 171 or die "$ME: HEAD must be a 'buildah treadmill' commit.\n"; 172 173 # ...and previous commit must be a scratch buildah vendor 174 $commit_message = git('log', '-1', '--format=%B', 'HEAD^'); 175 $commit_message =~ /DO NOT MERGE.* vendor in buildah.*JUNK COMMIT/s 176 or die "$ME: HEAD^ must be a DO NOT MERGE / JUNK COMMIT commit\n"; 177 assert_buildah_vendor_commit('HEAD^'); 178 179 # Looks good so far. 180 my $buildah_old = vendored_buildah(); 181 print "-> buildah old = $buildah_old\n"; 182 183 # Pull main, and pivot back to this branch 184 pull_main(); 185 git('checkout', '-q', $current_branch); 186 187 # Make a temporary copy of this branch 188 my $temp_branch = strftime("__buildah-treadmill-checkpoint/%Y%m%d-%H%M%S", localtime); 189 git('branch', $temp_branch, $current_branch); 190 progress("Current branch preserved as $temp_branch"); 191 192 # Get the hash of the top (treadmill) commit, to cherry-pick later 193 my $treadmill_commit = git('rev-parse', 'HEAD'); 194 195 # 196 # Danger Will Robinson! This is where it gets scary: a failure here 197 # can leave us in a state where we could lose the treadmill patches. 198 # Proceed with extreme caution. 199 # 200 local $SIG{__DIE__} = sub { 201 print STDERR $C_Warning, "@_", <<"END_FAIL_INSTRUCTIONS"; 202 203 This is not something I can recover from. Your human judgment is needed. 204 205 You will need to recover from this manually. Your best option is to 206 look at the source code for this script. 207 208 Treadmill branch copy is preserved in $temp_branch 209 210 To restore state to where you were before this sync: 211 \$ git checkout main 212 \$ git branch -f $current_branch $treadmill_commit 213 END_FAIL_INSTRUCTIONS 214 215 exit 1; 216 }; 217 218 my $forkpoint = git_forkpoint(); 219 my $rebased; 220 221 # Unlikely to fail 222 git('reset', '--hard', 'HEAD^^'); 223 224 # Rebase branch. Also unlikely to fail 225 my $main_commit = git('rev-parse', 'main'); 226 if ($forkpoint eq $main_commit) { 227 progress("[Already rebased on podman main]"); 228 } 229 else { 230 progress("Rebasing on podman main..."); 231 git('rebase', '--empty=keep', 'main'); 232 $rebased = 1; 233 } 234 235 # This does have a high possibility of failing. 236 progress("Vendoring in buildah..."); 237 system('go', 'mod', 'edit', '--require' => "${Buildah}\@main") == 0 238 or die "$ME: go mod edit failed"; 239 system('make', 'vendor') == 0 240 or die "$ME: make vendor failed"; 241 my $buildah_new = vendored_buildah(); 242 print "-> buildah new = $buildah_new\n"; 243 244 # Tweak .cirrus.yml so we run bud tests first in CI (to fail fast). 245 tweak_cirrus_test_order(); 246 247 # 'make vendor' seems to git-add files under buildah itself, but not 248 # under other changed modules. Add those now, otherwise we fail 249 # the dirty-tree test in CI. 250 if (my @v = git('status', '--porcelain', '--untracked=all', 'vendor')) { 251 if (my @untracked = grep { /^\?\?\s/ } @v) { 252 my %repos = map { 253 s!^.*?vendor/[^/]+/([^/]+/[^/]+)/.*$!$1!; $_ => 1; 254 } @untracked; 255 my $repos = join(', ', sort keys %repos); 256 progress("Adding untracked files under $repos"); 257 git('add', 'vendor'); 258 } 259 } 260 261 # Commit everything. 262 git_commit_buildah($buildah_new); 263 264 # And, finally, this has the highest possibility of failing 265 local $SIG{__DIE__} = sub { 266 print STDERR $C_Warning, "@_", <<"END_FAIL_INSTRUCTIONS"; 267 268 This is not something I can recover from. Your human judgment is needed. 269 270 Chances are, you might be able to run 'git status', look for 271 merge conflicts, manually resolve those, 'git add', then 272 'git cherry-pick --continue'. If that works, run this script 273 again (you will probably need the --force-retry option). 274 275 If that DOES NOT work, your only option is to look at the source code 276 for this script. Sorry. There's only so much that can be done automatically. 277 278 Treadmill branch copy is preserved in $temp_branch 279 280 To restore state to where you were before this sync: 281 \$ git checkout main 282 \$ git branch -f $current_branch $treadmill_commit 283 END_FAIL_INSTRUCTIONS 284 285 exit 1; 286 }; 287 progress('Reapplying treadmill patches'); 288 git('cherry-pick', '--allow-empty', $treadmill_commit); 289 290 # It worked! Clean up: remove our local die() handler and the saved branch 291 undef $SIG{__DIE__}; 292 git('branch', '-D', $temp_branch); 293 294 # if buildah is unchanged, and we did not pull main, exit cleanly 295 my $change_message = ''; 296 if ($buildah_new eq $buildah_old) { 297 if (! $rebased) { 298 $change_message = "Nothing has changed (same buildah, same podman)."; 299 if ($force_testing) { 300 $change_message .= " Testing anyway due to --force-testing."; 301 } 302 else { 303 progress($change_message); 304 progress("Not much point to testing this, but use --force-testing to continue."); 305 exit 0; 306 } 307 } 308 else { 309 $change_message = "Podman has bumped, but Buildah is unchanged. There's probably not much point to testing this."; 310 } 311 } 312 else { 313 my $samenew = ($rebased ? 'new' : 'same'); 314 $change_message = "New buildah, $samenew podman. Good candidate for pushing."; 315 } 316 progress($change_message); 317 318 build_and_check_podman(); 319 320 progress("All OK. It's now up to you to 'git push --force'"); 321 progress(" --- Reminder: $change_message"); 322 323 # Kind of kludgy. If user had to retry a prior failed attempt, and 324 # things are now successful, remind them to delete old checkpoints. 325 # ($force_retry is a 'git branch -D' command string at this point.) 326 if ($force_retry) { 327 progress(" --- Retry worked! You may now $force_retry"); 328 } 329 } 330 331 ############### 332 # pull_main # Switch to main, and pull latest from github 333 ############### 334 sub pull_main { 335 progress("Pulling podman main..."); 336 git('checkout', '-q', 'main'); 337 git('pull', '-r', git_upstream(), 'main'); 338 } 339 340 ############################# 341 # tweak_cirrus_test_order # Run bud tests first, to fail fast & early 342 ############################# 343 sub tweak_cirrus_test_order { 344 my $cirrus_yml = '.cirrus.yml'; 345 my $tmpfile = "$cirrus_yml.tmp.$$"; 346 unlink $tmpfile; 347 348 progress("Tweaking test order in $cirrus_yml to run bud tests early"); 349 open my $in, '<', $cirrus_yml 350 or do { 351 warn "$ME: Cannot read $cirrus_yml: $!\n"; 352 warn "$ME: Will continue anyway\n"; 353 return; 354 }; 355 open my $out, '>'. $tmpfile 356 or die "$ME: Cannot create $tmpfile: $!\n"; 357 my $current_task = ''; 358 my $in_depend; 359 while (my $line = <$in>) { 360 chomp $line; 361 if ($line =~ /^(\S+)_task:$/) { 362 $current_task = $1; 363 undef $in_depend; 364 } 365 elsif ($line =~ /^(\s+)depends_on:$/) { 366 $in_depend = $1; 367 } 368 elsif ($in_depend && $line =~ /^($in_depend\s+-\s+)(\S+)/) { 369 my $indent = $1; 370 371 # Run the buildah-bud tests early: that's the entire point 372 # of the treadmill PR. Here we switch Cirrus task dependencies 373 # such that bud tests run as early as possible. 374 if ($current_task =~ /buildah_bud_test/) { 375 # Buildah bud now depends only on validate... 376 $line = "${indent}validate"; 377 } 378 elsif ($2 eq 'validate' && $current_task ne 'success') { 379 # ...and all other tests that relied on validate now rely on 380 # bud tests instead. The point of the treadmill PR is to 381 # run the bud tests and only then, if everything passes, 382 # run normal tests. (Reason: bud tests are the only ones 383 # likely to fail on a buildah revendor, and we want to see 384 # failures early). 385 $line = "${indent}buildah_bud_test"; 386 } 387 } 388 else { 389 undef $in_depend; 390 391 # FIXME THIS IS HORRIBLE! 392 # Add rootless jobs to the buildah bud test matrix. 393 # This is incredibly fragile; it relies on the fact 394 # (true as of 2023-12-07) that the "matrix" yaml lines 395 # are formatted just so and are followed immediately 396 # by a "gce_instance" line. 397 # 398 # Since Ed is the only one who ever runs this script, 399 # he is expected to notice if this ever changes, and 400 # to fix it. 401 if ($current_task eq 'buildah_bud_test') { 402 if ($line =~ /^(\s+)gce_instance:/) { 403 print { $out } <<'END_ROOTLESS_BUD'; 404 - env: 405 PODBIN_NAME: podman 406 PRIV_NAME: rootless 407 - env: 408 PODBIN_NAME: remote 409 PRIV_NAME: rootless 410 END_ROOTLESS_BUD 411 } 412 } 413 } 414 415 print { $out } $line, "\n"; 416 } 417 close $in; 418 close $out 419 or die "$ME: Error writing $tmpfile: $!\n"; 420 chmod 0644 => $tmpfile; 421 rename $tmpfile => $cirrus_yml 422 or die "$ME: Could not rename $tmpfile: $!\n"; 423 } 424 425 ############################ 426 # build_and_check_podman # Run quick (local) sanity checks before pushing 427 ############################ 428 sub build_and_check_podman { 429 my $errs = 0; 430 431 # Confirm that we can still build podman 432 progress("Running 'make' to confirm that podman builds cleanly..."); 433 system('make') == 0 434 or die "$ME: 'make' failed with new buildah. Cannot continue.\n"; 435 436 # See if any new options need man pages. (C_Warning will highlight errs) 437 progress('Cross-checking man pages...'); 438 print $C_Warning; 439 $errs += system('hack/xref-helpmsgs-manpages'); 440 print $C_Reset; 441 442 # Confirm that buildah-bud patches still apply. This requires knowing 443 # the name of the directory created by the bud-tests script. 444 progress("Confirming that buildah-bud-tests patches still apply..."); 445 system('rm -rf test-buildah-*'); 446 if (system('test/buildah-bud/run-buildah-bud-tests', '--no-test')) { 447 # Error 448 ++$errs; 449 warn "$ME: Leaving test-buildah- directory for you to investigate\n"; 450 } 451 else { 452 # Patches apply cleanly. Clean up 453 system('rm -rf test-buildah-*'); 454 } 455 456 return if !$errs; 457 warn <<"END_WARN"; 458 $ME: Errors found. I have to stop now for you to fix them. 459 Your best bet now is: 460 1) Find and fix whatever needs to be fixed; then 461 2) git commit -am'fixme-fixme'; then 462 3) git rebase -i main: 463 a) you are now in an editor window 464 b) move the new fixme-fixme commit up a line, to between the 465 'buildah vendor treadmill' and 'vendor in buildah @ ...' lines 466 c) change 'pick' to 'squash' (or just 's') 467 d) save & quit to continue the rebase 468 e) back to a new editor window 469 f) change the commit message: remove fixme-fixme, add a description 470 of what you actually fixed. If possible, reference the PR (buildah 471 or podman) that introduced the failure 472 g) save & quit to continue the rebase 473 474 Now, for good measure, rerun this script. 475 476 For full documentation, refer to 477 478 $Docs_URL 479 END_WARN 480 exit 1; 481 } 482 483 # END sync and its helpers 484 ############################################################################### 485 # BEGIN pick and its helpers 486 # 487 # This is what gets used on a real vendor-new-buildah PR 488 489 sub do_pick { 490 my $current_branch = git_current_branch(); 491 492 # Confirm that current branch is a buildah-vendor one 493 assert_buildah_vendor_commit('HEAD'); 494 progress("HEAD is a buildah vendor commit. Good."); 495 496 # Identify and pull the treadmill PR. 497 my $treadmill_pr = shift || treadmill_pr(); 498 499 my $treadmill_branch = "$ME/pr$treadmill_pr/tmp$$"; 500 progress("Fetching treadmill PR $treadmill_pr into $treadmill_branch"); 501 git('fetch', '-q', git_upstream(), "pull/$treadmill_pr/head:$treadmill_branch"); 502 503 # Compare merge bases of our branch and the treadmill one 504 progress("Checking merge bases"); 505 check_merge_bases($treadmill_pr, $treadmill_branch); 506 507 # read buildah go.mod from it, and from current tree, and compare 508 my $buildah_on_treadmill = vendored_buildah($treadmill_branch); 509 my $buildah_here = vendored_buildah(); 510 if ($buildah_on_treadmill ne $buildah_here) { 511 warn "$ME: Warning: buildah version mismatch:\n"; 512 warn "$ME: on treadmill: $buildah_on_treadmill\n"; 513 warn "$ME: on this branch: $buildah_here\n"; 514 # FIXME: should this require --force? A yes/no prompt? 515 # FIXME: I think not, because usual case will be a true tagged version 516 warn "$ME: Continuing anyway\n"; 517 } 518 519 cherry_pick($treadmill_pr, $treadmill_branch); 520 521 # Clean up 522 git('branch', '-D', $treadmill_branch); 523 524 build_and_check_podman(); 525 526 progress("Looks good! Please 'git commit --amend' and edit commit message before pushing."); 527 } 528 529 ################## 530 # treadmill_pr # Returns ID of open podman PR with the desired subject 531 ################## 532 sub treadmill_pr { 533 # Github API (or maybe just the search endpoint???) is restricted. 534 my $token = $ENV{GITHUB_TOKEN} 535 or do { 536 warn <<"END_NEED_PR"; 537 $ME: Cannot proceed without PR ID. 538 539 If you have a github API token, please: export GITHUB_TOKEN=....... 540 and re-run me. 541 542 If you do not have a github API token, please go here: 543 544 https://github.com/containers/podman/pulls?q=is%3Apr+is%3Aopen+%22buildah+vendor+treadmill%22 545 546 ...then reinvoke me, adding that PR ID to the command line args. 547 548 As of 2022-09-12 the treadmill PR is 13808, but that may change over time. 549 END_NEED_PR 550 exit 1; 551 }; 552 553 my $query = <<'END_QUERY'; 554 { 555 search( 556 query: "buildah vendor treadmill repo:containers/podman", 557 type: ISSUE, 558 first: 10 559 ) { 560 edges { node { ... on PullRequest { number state title } } } 561 } 562 } 563 END_QUERY 564 565 my $ua = LWP::UserAgent->new; 566 $ua->agent("$ME " . $ua->agent); # Identify ourself 567 568 my %headers = ( 569 'Authorization' => "bearer $token", 570 'Accept' => "application/vnd.github.antiope-preview+json", 571 'Content-Type' => "application/json", 572 ); 573 $ua->default_header($_ => $headers{$_}) for keys %headers; 574 575 # Massage the query: escape quotes, put it all in one line, collapse spaces 576 $query =~ s/\"/\\"/g; 577 $query =~ s/\n/\\n/g; 578 $query =~ s/\s+/ /g; 579 # ...and now one more massage 580 my $postquery = qq/{ "query": \"$query\" }/; 581 582 print $postquery, "\n" if $debug; 583 my $res = $ua->post($API_URL, Content => $postquery); 584 if ((my $code = $res->code) != 200) { 585 warn "$ME: GraphQL request failed on $API_URL:\n"; 586 print STDERR " ", $code, " ", $res->message, "\n"; 587 warn "Cannot continue.\n"; 588 exit 1; 589 } 590 591 # Got something. Confirm that it has all our required fields 592 my $content = decode_json($res->content); 593 use Data::Dump; dd $content if $debug; 594 exists $content->{data} 595 or die "$ME: No '{data}' section in response\n"; 596 exists $content->{data}{search} 597 or die "$ME: No '{data}{search}' section in response\n"; 598 exists $content->{data}{search}{edges} 599 or die "$ME: No '{data}{search}{edges}' section in response\n"; 600 601 # Confirm that there is exactly one such PR 602 my @prs = @{ $content->{data}{search}{edges} }; 603 @prs > 0 604 or die "$ME: WEIRD! No 'buildah vendor treadmill' PRs found!\n"; 605 @prs = grep { $_->{node}{title} eq $Treadmill_PR_Title } @prs 606 or die "$ME: No PRs found with title '$Treadmill_PR_Title'\n"; 607 @prs = grep { $_->{node}{state} eq 'OPEN' } @prs 608 or die "$ME: Found '$Treadmill_PR_Title' PRs, but none are OPEN\n"; 609 @prs == 1 610 or die "$ME: Multiple OPEN '$Treadmill_PR_Title' PRs found!\n"; 611 612 # Yay. Found exactly one. 613 return $prs[0]{node}{number}; 614 } 615 616 ####################### 617 # check_merge_bases # It's OK if our branch is newer than treadmill 618 ####################### 619 sub check_merge_bases { 620 my $treadmill_pr = shift; # e.g., 12345 621 my $treadmill_branch = shift; # e.g., b-v-p/pr12345/tmpNNN 622 623 # Fetch latest main, for accurate comparison 624 git('fetch', '-q', git_upstream(), 'main'); 625 626 my $forkpoint_cur = git_forkpoint(); 627 my $forkpoint_treadmill = git_forkpoint($treadmill_branch); 628 629 print "fork cur: $forkpoint_cur\nfork tm: $forkpoint_treadmill\n" 630 if $debug; 631 if ($forkpoint_cur eq $forkpoint_treadmill) { 632 progress("Nice. This branch is up-to-date wrt treadmill PR $treadmill_pr"); 633 return; 634 } 635 636 # They differ. 637 if (git_is_ancestor($forkpoint_cur, $forkpoint_treadmill)) { 638 warn <<"END_WARN"; 639 $ME: treadmill PR $treadmill_pr is based on 640 a newer main than this branch. This means it might have 641 more up-to-date patches. 642 643 END_WARN 644 645 if ($force_old_main) { 646 warn "$ME: Proceeding due to --force-old-main\n"; 647 return; 648 } 649 650 # Cannot continue. Clean up side branch, and bail. 651 git('branch', '-D', $treadmill_branch); 652 warn "$ME: You might want to consider rebasing on latest main.\n"; 653 warn "$ME: Aborting. Use --force-old-main to continue without rebasing.\n"; 654 exit 1; 655 } 656 else { 657 progress("Your branch is based on a newer main than treadmill PR $treadmill_pr. This is usually OK."); 658 } 659 } 660 661 ################# 662 # cherry_pick # cherry-pick a commit, updating its commit message 663 ################# 664 sub cherry_pick { 665 my $treadmill_pr = shift; # e.g., 12345 666 my $treadmill_branch = shift; # e.g., b-v-p/pr12345/tmpNNN 667 668 progress("Cherry-picking from $treadmill_pr"); 669 670 # Create a temp script. Do so in /var/tmp because sometimes $TMPDIR 671 # (e.g. /tmp) has noexec. 672 my ($fh, $editor) = tempfile( "$ME.edit-commit-message.XXXXXXXX", DIR => "/var/tmp" ); 673 printf { $fh } <<'END_EDIT_SCRIPT', $ME, $VERSION, $treadmill_pr; 674 #!/bin/bash 675 676 if [[ -z "$1" ]]; then 677 echo "FATAL: Did not get called with an arg" >&2 678 exit 1 679 fi 680 681 msgfile=$1 682 if [[ ! -e $msgfile ]]; then 683 echo "FATAL: git-commit file does not exist: $msgfile" >&2 684 exit 1 685 fi 686 687 tmpfile=$msgfile.tmp 688 rm -f $tmpfile 689 690 cat >$tmpfile <<EOF 691 WIP: Fixes for vendoring Buildah 692 693 This commit was automatically cherry-picked 694 by %s v%s 695 from the buildah vendor treadmill PR, #%s 696 697 /vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv 698 > The git commit message from that PR is below. Please review it, 699 > edit as necessary, then remove this comment block. 700 \^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 701 702 EOF 703 704 # Strip the "DO NOT MERGE" header from the treadmill PR, print only 705 # the "Changes since YYYY-MM-DD" and subsequent lines 706 sed -ne '/^Changes since /,$ p' <$msgfile >>$tmpfile 707 mv $tmpfile $msgfile 708 709 END_EDIT_SCRIPT 710 close $fh 711 or die "$ME: Error writing $editor: $!\n"; 712 chmod 0755 => $editor; 713 local $ENV{EDITOR} = $editor; 714 git('cherry-pick', '--allow-empty', '--edit', $treadmill_branch); 715 unlink $editor; 716 } 717 718 # END pick and its helpers 719 ############################################################################### 720 # BEGIN reset and its helpers 721 722 sub do_reset { 723 die "$ME: --sync takes no arguments; try $ME --help\n" if @_; 724 725 my $current_branch = git_current_branch(); 726 727 # Make sure side branch == main (i.e., there are no commits on the branch) 728 if (git('rev-parse', $current_branch) ne git('rev-parse', 'main')) { 729 die "$ME: for --reset, $current_branch must == main\n"; 730 } 731 732 # Pull main, and pivot back to this branch 733 pull_main(); 734 git('checkout', '-q', $current_branch); 735 736 git('rebase', '--empty=keep', 'main'); 737 git_commit_buildah('[none]'); 738 739 my $ymd = strftime("%Y-%m-%d", localtime); 740 git('commit', '--allow-empty', '-s', '-m' => <<"END_COMMIT_MESSAGE"); 741 $Treadmill_PR_Title 742 743 As you run --sync, please update this commit message with your 744 actual changes. 745 746 Changes since $ymd: 747 END_COMMIT_MESSAGE 748 749 progress("Done. You may now run --sync.\n"); 750 } 751 752 # END reset and its helpers 753 ############################################################################### 754 # BEGIN general-purpose helpers 755 756 ############## 757 # progress # Progris riport Dr Strauss says I shud rite down what I think 758 ############## 759 sub progress { 760 print $C_Highlight, "|\n+---> @_\n", $C_Reset; 761 } 762 763 ####################### 764 # assert_clean_repo # Don't even think of running with local changes 765 ####################### 766 sub assert_clean_repo { 767 # During --sync we create a temporary copy of the treadmill branch, 768 # in case something goes wrong. The branch is deleted on success. 769 # If one exists, it means we may have lost work. 770 my @relics = grep { 771 m!^__buildah-treadmill-checkpoint/\d+-\d+$! 772 } git('branch', '--list', '--format=%(refname:lstrip=2)'); 773 if (@relics) { 774 if ($force_retry) { 775 warn <<"END_WARN"; 776 $ME: WARNING: leftover checkpoint(s): @relics 777 778 ...continuing due to --force-retry. 779 780 If things work out, you can 'git branch -D @relics' 781 END_WARN 782 783 # OK, ugly override of a binary flag, but it's OK because 784 # it helps with user-friendliness: offer a reminder upon 785 # successful completion of the script. 786 $force_retry = "git branch -D @relics"; 787 } 788 else { 789 warn <<"END_WARN"; 790 $ME: FATAL: leftover checkpoint: @relics 791 792 This means that something went very wrong during an earlier sync run. 793 Your git branch may be in an inconsistent state. Your work to date 794 may be lost. This branch may be your only hope of recovering it. 795 796 This is not something a script can resolve. You need to look at this 797 branch, compare to your git HEAD, and manually reconcile any differences. 798 799 If you really know what you're doing, i.e., if you've reconciled 800 merge conflicts and have a pretty secure branch structure, try 801 rerunning me with --force-retry. Or, if that checkpoint is a 802 remnant from a past run, and you're ultra-certain that you don't 803 need it, you can git branch -D @relics 804 END_WARN 805 exit 1; 806 } 807 } 808 809 # OK so far. Now check for modified files. 810 if (my @changed = git('status', '--porcelain', '--untracked=no')) { 811 warn "$ME: Modified files in repo:\n"; 812 warn " $_\n" for @changed; 813 exit 1; 814 } 815 816 # ...and for untracked files under vendor/ 817 if (my @v = git('status', '--porcelain', '--untracked=all', 'vendor')) { 818 warn "$ME: Untracked vendor files:\n"; 819 warn " $_\n" for @v; 820 exit 1; 821 } 822 } 823 824 ######################## 825 # git_current_branch # e.g., 'vendor_buildah' 826 ######################## 827 sub git_current_branch() { 828 my $b = git('rev-parse', '--abbrev-ref=strict', 'HEAD'); 829 830 # There is no circumstance in which we can ever be called from main 831 die "$ME: must run from side branch, not main\n" if $b eq 'main'; 832 return $b; 833 } 834 835 ################### 836 # git_forkpoint # Hash at which branch (default: cur) branched from main 837 ################### 838 sub git_forkpoint { 839 # '--fork-point vendor-branch' fails silently on Paul's git tree, 840 # but plain merge-base works fine. My head hurts from trying to 841 # understand the docs, so I give up. Just try fork-point first, 842 # and if it fails, try without. #cargocult #gitishard 843 my $forkpoint = eval { git('merge-base', '--fork-point', 'main', @_) }; 844 if ($@) { 845 $forkpoint = git('merge-base', 'main', @_); 846 } 847 return $forkpoint; 848 } 849 850 ##################### 851 # git_is_ancestor # Is hash1 an ancestor of hash2? 852 ##################### 853 sub git_is_ancestor { 854 # Use system(), not git(), because we don't want to abort on exit status 855 my $rc = system('git', 'merge-base', '--is-ancestor', @_); 856 die "$ME: Cannot continue\n" if $? > 256; # e.g., Not a valid object 857 858 # Translate shell 0/256 status to logical 1/0 859 return !$rc; 860 } 861 862 ################## 863 # git_upstream # Name of true github upstream 864 ################## 865 sub git_upstream { 866 for my $line (git('remote', '-v')) { 867 my ($remote, $url, $type) = split(' ', $line); 868 if ($url =~ m!github\.com.*containers/(podman|libpod)!) { 869 if ($type =~ /fetch/) { 870 return $remote; 871 } 872 } 873 } 874 875 die "$ME: did not find a remote with 'github.com/containers/podman'\n"; 876 } 877 878 ######################## 879 # git_commit_buildah # Do the buildah commit 880 ######################## 881 sub git_commit_buildah { 882 my $buildah_version = shift; 883 884 # When called by --reset, this can be empty 885 git('commit', '-as', '--allow-empty', '-m', <<"END_COMMIT_MESSAGE"); 886 DO NOT MERGE: vendor in buildah \@ $buildah_version 887 888 This is a JUNK COMMIT from $ME v$VERSION. 889 890 DO NOT MERGE! This is just a way to keep the buildah-podman 891 vendoring in sync. Refer to: 892 893 $Docs_URL 894 END_COMMIT_MESSAGE 895 } 896 897 ######### 898 # git # Run a git command 899 ######### 900 sub git { 901 my @cmd = ('git', @_); 902 print "\$ @cmd\n" if $verbose || $debug; 903 open my $fh, '-|', @cmd 904 or die "$ME: Cannot fork: $!\n"; 905 my @results; 906 while (my $line = <$fh>) { 907 chomp $line; 908 push @results, $line; 909 } 910 close $fh 911 or die "$ME: command failed: @cmd\n"; 912 913 return wantarray ? @results : join("\n", @results); 914 } 915 916 ################################## 917 # assert_buildah_vendor_commit # Fails if input arg is not a buildah vendor 918 ################################## 919 sub assert_buildah_vendor_commit { 920 my $ref = shift; # in: probably HEAD or HEAD^ 921 922 my @deltas = git('diff', '--name-only', "$ref^", $ref); 923 924 # It's OK if there are no deltas, e.g. immediately after a buildah vendor PR 925 return if !@deltas; 926 927 # It's OK if there are more modified files than just these. 928 # It's not OK if any of these are missing. 929 my @expect = qw(go.mod go.sum vendor/modules.txt); 930 my @missing; 931 for my $expect (@expect) { 932 if (! grep { $_ eq $expect } @deltas) { 933 push @missing, "$expect is unchanged"; 934 } 935 } 936 937 if (! grep { m!^vendor/\Q$Buildah\E/! } @deltas) { 938 push @missing, "no changes under $Buildah"; 939 } 940 941 return if !@missing; 942 943 warn "$ME: $ref does not look like a buildah vendor commit:\n"; 944 warn "$ME: - $_\n" for @missing; 945 die "$ME: Cannot continue\n"; 946 } 947 948 ###################### 949 # vendored_buildah # Returns currently-vendored buildah 950 ###################### 951 sub vendored_buildah { 952 my $gomod_file = 'go.mod'; 953 my @gomod; 954 if (@_) { 955 # Called with a branch argument; fetch that version of go.mod 956 $gomod_file = "@_:$gomod_file"; 957 @gomod = git('show', $gomod_file); 958 } 959 else { 960 # No branch argument, read file 961 open my $fh, '<', $gomod_file 962 or die "$ME: Cannot read $gomod_file: $!\n"; 963 while (my $line = <$fh>) { 964 chomp $line; 965 push @gomod, $line; 966 } 967 close $fh; 968 } 969 970 for my $line (@gomod) { 971 if ($line =~ m!^\s+\Q$Buildah\E\s+(\S+)!) { 972 return $1; 973 } 974 } 975 976 die "$ME: Could not find buildah in $gomod_file!\n"; 977 } 978 979 # END general-purpose helpers 980 ###############################################################################