podman

Форк
0
/
buildah-vendor-treadmill 
980 строк · 32.0 Кб
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
###############################################################################
981

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.