From d2422d3953187facc9a151808891bbe5fff0ddce 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 --- delete.php | 2 +- editcounts.php | 2 +- includes.php | 16 +++- index.php | 7 +- new/applypatch.sh | 3 + sql/patchdemo.sql | 6 ++ update.php | 190 ++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 217 insertions(+), 9 deletions(-) create mode 100644 update.php diff --git a/delete.php b/delete.php index 0a2e3f1f..c14d0177 100644 --- a/delete.php +++ b/delete.php @@ -28,7 +28,7 @@ '' . substr( $wiki, 0, 10 ) . '' . '' . $patches . '' . '' . $linkedTasks . '' . - '' . date( 'Y-m-d H:i:s', $wikiData[ 'created' ] ) . '' . + '' . date( 'Y-m-d H:i:s', $wikiData[ 'updated' ] ) . '' . ( $useOAuth ? '' . ( $creator ? user_link( $creator ) : '?' ) . '' : '' ) . '' . ''; diff --git a/editcounts.php b/editcounts.php index 519bf3dd..0c429c6e 100644 --- a/editcounts.php +++ b/editcounts.php @@ -47,7 +47,7 @@ ], ]; -$results = $mysqli->query( 'SELECT wiki FROM wikis WHERE !deleted ORDER BY created DESC' ); +$results = $mysqli->query( 'SELECT wiki FROM wikis WHERE !deleted ORDER BY updated DESC' ); if ( !$results ) { die( $mysqli->error ); } diff --git a/includes.php b/includes.php index 9ef3eba3..0d368ec3 100644 --- a/includes.php +++ b/includes.php @@ -36,13 +36,21 @@ function insert_wiki_data( string $wiki, string $creator, int $created, string $ global $mysqli; $stmt = $mysqli->prepare( ' INSERT INTO wikis - (wiki, creator, created, branch) - VALUES(?, ?, FROM_UNIXTIME(?), ?) + (wiki, creator, created, updated, branch) + VALUES(?, ?, FROM_UNIXTIME(?), FROM_UNIXTIME(?), ?) ' ); if ( !$stmt ) { echo $mysqli->error; } - $stmt->bind_param( 'ssis', $wiki, $creator, $created, $branch ); + $stmt->bind_param( 'ssis', $wiki, $creator, $created, $created, $branch ); + $stmt->execute(); + $stmt->close(); +} + +function wiki_update_timestamp( string $wiki ) { + global $mysqli; + $stmt = $mysqli->prepare( 'UPDATE wikis SET updated = NOW() WHERE wiki = ?' ); + $stmt->bind_param( 's', $wiki ); $stmt->execute(); $stmt->close(); } @@ -77,7 +85,7 @@ function get_wiki_data( string $wiki ): array { global $mysqli; $stmt = $mysqli->prepare( ' - SELECT wiki, creator, UNIX_TIMESTAMP( created ) created, patches, branch, announcedTasks, timeToCreate, deleted + SELECT wiki, creator, UNIX_TIMESTAMP( created ) created, UNIX_TIMESTAMP( updated ) updated, patches, branch, announcedTasks, timeToCreate, deleted FROM wikis WHERE wiki = ? ' ); if ( !$stmt ) { diff --git a/index.php b/index.php index fca03f31..cde7b0c3 100644 --- a/index.php +++ b/index.php @@ -281,10 +281,10 @@ $username = $user ? $user->username : null; $stmt = $mysqli->prepare( ' - SELECT wiki, creator, UNIX_TIMESTAMP( created ) created, patches, branch, announcedTasks, timeToCreate, deleted + SELECT wiki, creator, UNIX_TIMESTAMP( updated ) updated, patches, branch, announcedTasks, timeToCreate, deleted FROM wikis WHERE !deleted - ORDER BY IF( creator = ?, 1, 0 ) DESC, created DESC + ORDER BY IF( creator = ?, 1, 0 ) DESC, updated DESC ' ); if ( !$stmt ) { die( $mysqli->error ); @@ -330,6 +330,7 @@ $actions = []; if ( $canDelete ) { + $actions[] = 'Update'; $actions[] = 'Delete'; } if ( $canCreate ) { @@ -346,7 +347,7 @@ '' . '' . $patches . '' . '' . $linkedTasks . '' . - '' . date( 'Y-m-d H:i:s', $wikiData[ 'created' ] ) . '' . + '' . date( 'Y-m-d H:i:s', $wikiData[ 'updated' ] ) . '' . ( $useOAuth ? '' . ( $creator ? user_link( $creator ) : '?' ) . '' : '' ) . ( $canAdmin ? '' . ( $wikiData['timeToCreate'] ? $wikiData['timeToCreate'] . 's' : '' ) . '' : '' ) . ( count( $actions ) ? 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/sql/patchdemo.sql b/sql/patchdemo.sql index c78762b9..df63ae66 100644 --- a/sql/patchdemo.sql +++ b/sql/patchdemo.sql @@ -42,3 +42,9 @@ ALTER TABLE `tasks` ALTER TABLE `wikis` ADD COLUMN IF NOT EXISTS `branch` VARCHAR(64) NOT NULL AFTER `patches`; + +ALTER TABLE `wikis` + ADD COLUMN `updated` DATETIME NOT NULL AFTER `created`, + ADD INDEX `updated` (`updated`); + +UPDATE wikis SET updated = created; diff --git a/update.php b/update.php new file mode 100644 index 00000000..7a4ce6f6 --- /dev/null +++ b/update.php @@ -0,0 +1,190 @@ +You are not allowed to update this wiki.

' ); +} + +function abandon( string $errHtml ) { + die( $errHtml ); +} + +echo '

Updating wiki ' . $wiki . '.

'; + +echo '
'; + +$patchesApplied = []; +$patchesToUpdate = []; +$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" ); + } + } + + $patch = $data[0]['_number'] . ',' . $data[0]['revisions'][$revision]['_number']; + $patchesApplied[] = $patch; + + $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; + } + + $patchesToUpdate[] = $patch; + + $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 {$patchesToUpdate[$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 ( $patchesToUpdate 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 _all_ patches applied (include those which weren't updated) +wiki_add_patches( $wiki, $patchesApplied ); +wiki_update_timestamp( $wiki ); + +echo "Done!"; + +echo '
';