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