#!/usr/bin/perl use strict; use warnings; use v5.32; use Data::Dumper; use Git; use JSON qw( from_json ); use Mojolicious::Lite -signatures; use Mojo::IOLoop; use Mojo::Exception qw(check raise); use Mojo::JSON qw(encode_json decode_json); use Time::HiRes qw( time ); my $VERSION = 'v0.0.1'; my $work_dir = "/var/lib/pr-status"; my $repo_dir = "${work_dir}/nixpkgs"; $ENV{"GIT_CONFIG_SYSTEM"} = ""; # Ignore insteadOf rules $ENV{"HOME"} = "/tmp"; # Ignore ~/.netrc Git::command_noisy( 'clone', 'https://github.com/nixos/nixpkgs', $repo_dir ) if !-e $repo_dir; my $repo = Git->repository( Directory => $repo_dir ); my $lock = 0; my $refresh = 1 * 60 * 60; my $gc_refresh = 24 * 60 * 60; my $searches = []; Mojo::IOLoop->recurring( $refresh => sub ($loop) { $lock = 1; $repo->command('fetch'); $lock = 0; } ); Mojo::IOLoop->recurring( $gc_refresh => sub ($loop) { $lock = 1; $repo->command('gc'); $lock = 0; } ); sub save_search { my $r = shift; push( @$searches, { pull_request => $r->{pull_request}, title => $r->{title}, epoch => time } ) unless grep { $r->{pull_request} == $_->{pull_request} } @$searches; write_searches(); } sub write_searches { my @sorted = reverse sort { $a->{epoch} <=> $b->{epoch} } @$searches; $searches = \@sorted; open my $fh, ">", "${work_dir}/searches.json" or die $!; print $fh encode_json($searches); close $fh; } sub load_searches { if ( -e "${work_dir}/searches.json" ) { open my $fh, "<", "${work_dir}/searches.json" or die $!; $searches = decode_json <$fh>; close $fh; } my @sorted = reverse sort { $a->{epoch} <=> $b->{epoch} } @$searches; $searches = \@sorted; } sub get_commit { my $pr = shift; $repo->command( 'fetch', '-f', 'origin', "pull/${pr}/head:pr-status-${pr}" ); $repo->command( 'checkout', '-f', "pr-status-$pr" ); my $commit = $repo->command( 'rev-parse', 'HEAD' ); my $log = $repo->command( 'log', '-n', '1', '--pretty=format:%s' ); $repo->command( 'checkout', 'master' ); chomp $commit; chomp $log; return ( $log, $commit ); } sub check_nixpkg_branches { my $commit = shift; my $list = []; return $list if $commit eq ""; my $branches = $repo->command( 'branch', '-r', '--contains', $commit ); foreach my $b ( split( '\n', $branches ) ) { $b =~ s/^\s+origin\///g; push( @$list, $b ) if $b =~ m/^nixos|^nixpkgs|^staging|^master|^release/; } return $list; } sub figure_status { my $list = shift; my $status = { state => "open", info => {}, }; my $release = "stable"; my @unstable = qw/ master staging staging-next nixpkgs-unstable nixos-unstable-small nixos-unstable /; my @stable = ( 'staging-\d\d\.\d\d', 'staging-next-\d\d\.\d\d', 'nixos-\d\d\.\d\d-small', 'nixos-\d\d\.\d\d', 'release-\d\d\.\d\d' ); if ( grep /^master$/, @{$list} ) { $release = "unstable"; foreach my $s (@unstable) { $status->{info}->{$s} = JSON::false; $status->{info}->{$s} = JSON::true if grep /^$s$/, @{$list}; } } else { $release = "stable"; foreach my $s (@stable) { # handle this stuff with a regex so we don't have to specify "22.11" kinda stuff my @b = grep /$s/, @{$list}; my $ns = $b[0]; if ( defined $ns ) { $status->{info}->{$ns} = JSON::false; $status->{info}->{$ns} = JSON::true if grep /^$s$/, @{$list}; } } } my $dataLen = scalar keys %{ $status->{info} }; my $trueCount = 0; foreach my $s ( keys %{ $status->{info} } ) { $trueCount += 1 if $status->{info}->{$s} == JSON::true; } $status->{state} = "complete" if ( $dataLen == $trueCount && @{$list} >= @stable ); $status->{state} = "open" if @{$list} == 0; return ( $release, $status ); } get '/gc' => sub ($c) { my $start = time; $repo->command('gc'); my $end = time; $c->render( json => { updateTime => sprintf( "%2f", $end - $start ) + 0.0, action => 'gc' } ); }; get '/update' => sub ($c) { my $start = time; $repo->command('fetch'); my $end = time; $c->render( json => { updateTime => sprintf( "%2f", $end - $start ) + 0.0, action => 'update' } ); }; get '/' => sub ($c) { $c->render( template => 'index' ); }; get '/searches' => sub ($c) { $c->render( json => $searches ); }; del '/searches/:pr' => sub ($c) { my $pr = $c->param('pr'); return if $lock == 1; return unless $pr =~ m/^\d+$/; @$searches = grep { $_->{pull_request} != $pr } @$searches; write_searches(); $c->render( json => $searches ); }; get '/:pr' => sub ($c) { my $pr = $c->param('pr'); if ( $lock == 1 ) { return; } return unless $pr =~ m/^\d+$/; my $title = ""; my $commit = ""; my $error = ""; eval { ( $title, $commit ) = get_commit($pr); }; check $@ => [ qr/command returned error/ => sub { say "Git command failed: $_"; $error = $_; } ]; my $start = time; my $list = check_nixpkg_branches $commit; my $end = time; my ( $release, $status ) = figure_status($list); my @sorted = reverse sort { $a->{epoch} <=> $b->{epoch} } @$searches; $searches = \@sorted; my $result = { title => $title, branches => $list, searches => $searches, pull_request => $pr + 0, status => $status->{state}, release => $release, status_info => $status->{info}, queryTime => sprintf( "%2f", $end - $start ) + 0.0, error => $error }; save_search($result); $c->render( json => $result ); }; load_searches(); app->start; __DATA__ @@ index.html.ep Main