From d0a598adac48bd45fb07336ac3d33bd793f6546f Mon Sep 17 00:00:00 2001 From: Ed Sanders Date: Sat, 6 Mar 2021 17:03:43 +0000 Subject: [PATCH] Allow patches to be updated Fixes #54 --- index.php | 1 + new/applypatch.sh | 3 + update.php | 183 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 187 insertions(+) create mode 100644 update.php diff --git a/index.php b/index.php index fca03f31..acd00236 100644 --- a/index.php +++ b/index.php @@ -330,6 +330,7 @@ $actions = []; if ( $canDelete ) { + $actions[] = 'Update'; $actions[] = 'Delete'; } if ( $canCreate ) { diff --git a/new/applypatch.sh b/new/applypatch.sh index c8101cb1..1d56b8f6 100755 --- a/new/applypatch.sh +++ b/new/applypatch.sh @@ -3,6 +3,9 @@ set -ex cd $PATCHDEMO/wikis/$NAME/$REPO +# Required when updating an existing wiki +git reset --hard origin/master + git fetch origin $REF # Apply $HASH and its parent commits up to $BASE on top of current HEAD. diff --git a/update.php b/update.php new file mode 100644 index 00000000..a7d5bd9e --- /dev/null +++ b/update.php @@ -0,0 +1,183 @@ +You are not allowed to update this wiki.

' ); +} + +function abandon( string $errHtml ) { + die( $errHtml ); +} + +echo '
'; + +$patchesApplied = []; +$linkedTasks = []; +$commands = []; +$usedRepos = []; + +foreach ( $wikiData['patchList'] as $patch => $patchData ) { + $r = $patchData['r']; + $data = gerrit_query( "changes/?q=change:$r&o=LABELS&o=CURRENT_REVISION", true ); + + // get the info + $repo = $data[0]['project']; + $base = 'origin/' . $data[0]['branch']; + $revision = $data[0]['current_revision']; + $ref = $data[0]['revisions'][$revision]['ref']; + $id = $data[0]['id']; + + $repos = get_repo_data(); + if ( !isset( $repos[ $repo ] ) ) { + $repo = htmlentities( $repo ); + abandon( "Repository $repo not supported" ); + } + $path = $repos[ $repo ]; + $usedRepos[] = $repo; + + if ( + $config[ 'requireVerified' ] && + ( $data[0]['labels']['Verified']['approved']['_account_id'] ?? null ) !== 75 + ) { + // The patch doesn't have V+2, check if the uploader is trusted + $uploaderId = $data[0]['revisions'][$revision]['uploader']['_account_id']; + $uploader = gerrit_query( 'accounts/' . $uploaderId, true ); + if ( !is_trusted_user( $uploader['email'] ) ) { + abandon( "Patch must be approved (Verified+2) by jenkins-bot, or uploaded by a trusted user" ); + } + } + + $r = $patchData['r']; + $pOld = (int)$patchData['p']; + $pNew = $data[0]['revisions'][$revision]['_number']; + if ( $pNew > $pOld ) { + echo "Updating change $r from patchset $pOld to $pNew."; + } else { + echo "Change $r is already using the latest patchset ($pOld)."; + continue; + } + + $patchesApplied[] = $data[0]['_number'] . ',' . $data[0]['revisions'][$revision]['_number']; + + $commands[] = [ + [ + 'REPO' => $path, + 'REF' => $ref, + 'BASE' => $base, + 'HASH' => $revision, + ], + __DIR__ . '/new/applypatch.sh' + ]; + + $relatedChanges = []; + $relatedChanges[] = [ $data[0]['_number'], $data[0]['revisions'][$revision]['_number'] ]; + + // Look at all commits in this patch's tree for cross-repo dependencies to add + $data = gerrit_query( "changes/$id/revisions/$revision/related", true ); + // Ancestor commits only, not descendants + $foundCurr = false; + foreach ( $data['changes'] as $change ) { + if ( $foundCurr ) { + // Querying by change number is allegedly deprecated, but the /related API doesn't return the 'id' + $relatedChanges[] = [ $change['_change_number'], $change['_revision_number'] ]; + } + $foundCurr = $foundCurr || $change['commit']['commit'] === $revision; + } + + foreach ( $relatedChanges as [ $c, $r ] ) { + $data = gerrit_query( "changes/$c/revisions/$r/commit", true ); + + preg_match_all( '/^Depends-On: (.+)$/m', $data['message'], $m ); + foreach ( $m[1] as $changeid ) { + if ( !in_array( $changeid, $patches, true ) ) { + // The entry we add here will be processed by the topmost foreach + $patches[] = $changeid; + } + } + } +} +$usedRepos = array_unique( $usedRepos ); + +$baseEnv = [ + 'PATCHDEMO' => __DIR__, + 'NAME' => $wiki, +]; + +if ( !count( $commands ) ) { + abandon( 'No patches to update.' ); +} + +$error = shell_echo( __DIR__ . '/new/unlink.sh', $baseEnv ); +if ( $error ) { + abandon( "Could not un-duplicate wiki." ); +} + +foreach ( $commands as $i => $command ) { + $error = shell_echo( $command[1], $baseEnv + $command[0] ); + if ( $error ) { + abandon( "Could not apply patch {$patchesApplied[$i]}" ); + } +} + +$composerInstallRepos = Yaml::parse( file_get_contents( __DIR__ . '/repository-lists/composerinstall.yaml' ) ); +foreach ( $usedRepos as $repo ) { + if ( in_array( $repo, $composerInstallRepos, true ) ) { + $error = shell_echo( __DIR__ . '/new/composerinstall.sh', + $baseEnv + [ + // Variable used by composer itself, not our script + 'COMPOSER_HOME' => __DIR__ . '/composer', + 'REPO_TARGET' => $repos[$repo], + ] + ); + if ( $error ) { + abandon( "Could not fetch dependencies for $repo" ); + } + } +} + +$mainPage = "\n\nThis wiki was updated on ~~~~~ with the following newer patches:"; +foreach ( $patchesApplied as $patch ) { + preg_match( '`([0-9]+),([0-9]+)`', $patch, $matches ); + list( $t, $r, $p ) = $matches; + + $data = gerrit_query( "changes/$r/revisions/$p/commit", true ); + if ( $data ) { + $t = $t . ': ' . $data[ 'subject' ]; + get_linked_tasks( $data['message'], $linkedTasks ); + } + + $t = htmlentities( $t ); + + $mainPage .= "\n:* [{$config['gerritUrl']}/r/c/$r/$p $t]"; +} + +$error = shell_echo( __DIR__ . '/new/postupdate.sh', + $baseEnv + [ + 'MAINPAGE' => $mainPage, + ] +); +if ( $error ) { + abandon( "Could not update wiki content" ); +} + +Update DB record with patches applied +wiki_add_patches( $wiki, $patchesApplied ); + +echo "Done!"; + +echo '
';