Skip to content

Commit aa2ec75

Browse files
committed
Always run full Package.latest* calculation when a version changes.
1 parent 2e6a651 commit aa2ec75

File tree

8 files changed

+115
-132
lines changed

8 files changed

+115
-132
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ Important changes to data models, configuration, and migrations between each
22
AppEngine version, listed here to ease deployment and troubleshooting.
33

44
## Next Release (replace with git tag when deployed)
5+
* Note: `Package.latest*` fields are recalculated with a full list of `PackageVersion` entities.
56

67
## `20240821t093600-all`
78
* Bumped runtimeVersion to `2024.08.20`.

app/lib/admin/actions/moderate_package_versions.dart

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,15 @@ Set the moderated flag on a package version (updating the flag and the timestamp
8888

8989
// Update references to latest versions.
9090
final pkg = await tx.lookupValue<Package>(p.key);
91-
if (pkg.mayAffectLatestVersions(v.semanticVersion)) {
92-
final versions =
93-
await tx.query<PackageVersion>(pkg.key).run().toList();
94-
pkg.updateLatestVersionReferences(
95-
versions,
96-
dartSdkVersion: currentDartSdk.semanticVersion,
97-
flutterSdkVersion: currentFlutterSdk.semanticVersion,
98-
replaced: v,
99-
);
91+
final versions = await tx.query<PackageVersion>(pkg.key).run().toList();
92+
pkg.updateVersions(
93+
versions,
94+
dartSdkVersion: currentDartSdk.semanticVersion,
95+
flutterSdkVersion: currentFlutterSdk.semanticVersion,
96+
replaced: v,
97+
);
98+
if (pkg.latestVersionKey == null) {
99+
throw InvalidInputException('xx');
100100
}
101101
pkg.updated = clock.now().toUtc();
102102
tx.insert(pkg);

app/lib/admin/backend.dart

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import 'package:convert/convert.dart';
1414
import 'package:gcloud/service_scope.dart' as ss;
1515
import 'package:logging/logging.dart';
1616
import 'package:pool/pool.dart';
17-
import 'package:pub_semver/pub_semver.dart';
1817

1918
import '../account/backend.dart';
2019
import '../account/consent_backend.dart';
@@ -444,7 +443,6 @@ class AdminBackend {
444443
final versionNames = versions.map((v) => v.version).toList();
445444
if (versionNames.contains(version)) {
446445
tx.delete(packageKey.append(PackageVersion, id: version));
447-
package.versionCount--;
448446
package.updated = clock.now().toUtc();
449447
} else {
450448
print('Package $packageName does not have a version $version.');
@@ -455,14 +453,11 @@ class AdminBackend {
455453
'Last version detected. Use full package removal without the version qualifier.');
456454
}
457455

458-
if (package.mayAffectLatestVersions(Version.parse(version))) {
459-
package.updateLatestVersionReferences(
460-
versions.where((v) => v.version != version).toList(),
461-
dartSdkVersion: currentDartSdk.semanticVersion,
462-
flutterSdkVersion: currentFlutterSdk.semanticVersion,
463-
);
464-
}
465-
456+
package.updateVersions(
457+
versions.where((v) => v.version != version).toList(),
458+
dartSdkVersion: currentDartSdk.semanticVersion,
459+
flutterSdkVersion: currentFlutterSdk.semanticVersion,
460+
);
466461
package.deletedVersions ??= <String>[];
467462
if (!package.deletedVersions!.contains(version)) {
468463
package.deletedVersions!.add(version);

app/lib/package/backend.dart

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ class PackageBackend {
360360
throw NotFoundException.resource('package "$package"');
361361
}
362362

363-
final changed = p.updateLatestVersionReferences(
363+
final changed = p.updateVersions(
364364
versions,
365365
dartSdkVersion: dartSdkVersion!,
366366
flutterSdkVersion: flutterSdkVersion!,
@@ -633,21 +633,18 @@ class PackageBackend {
633633
pv.isRetracted = isRetracted;
634634
pv.retracted = isRetracted ? clock.now() : null;
635635

636-
// Update references to latest versions if the retracted version was
637-
// the latest version or the restored version is newer than the latest.
638-
if (p.mayAffectLatestVersions(pv.semanticVersion)) {
639-
final versions = await tx.query<PackageVersion>(p.key).run().toList();
640-
final currentDartSdk = await getCachedDartSdkVersion(
641-
lastKnownStable: toolStableDartSdkVersion);
642-
final currentFlutterSdk = await getCachedFlutterSdkVersion(
643-
lastKnownStable: toolStableFlutterSdkVersion);
644-
p.updateLatestVersionReferences(
645-
versions,
646-
dartSdkVersion: currentDartSdk.semanticVersion,
647-
flutterSdkVersion: currentFlutterSdk.semanticVersion,
648-
replaced: pv,
649-
);
650-
}
636+
// Update references to latest versions.
637+
final versions = await tx.query<PackageVersion>(p.key).run().toList();
638+
final currentDartSdk = await getCachedDartSdkVersion(
639+
lastKnownStable: toolStableDartSdkVersion);
640+
final currentFlutterSdk = await getCachedFlutterSdkVersion(
641+
lastKnownStable: toolStableFlutterSdkVersion);
642+
p.updateVersions(
643+
versions,
644+
dartSdkVersion: currentDartSdk.semanticVersion,
645+
flutterSdkVersion: currentFlutterSdk.semanticVersion,
646+
replaced: pv,
647+
);
651648

652649
_logger.info(
653650
'Updating ${p.name} ${pv.version} options: isRetracted: $isRetracted');
@@ -1064,12 +1061,6 @@ class PackageBackend {
10641061
lastKnownStable: toolStableFlutterSdkVersion);
10651062
final existingPackage = await lookupPackage(newVersion.package);
10661063
final isNew = existingPackage == null;
1067-
final existingLatestVersion = isNew
1068-
? null
1069-
: await lookupPackageVersion(
1070-
existingPackage.name!, existingPackage.latestVersion!);
1071-
final existingLatestIsRetracted =
1072-
existingLatestVersion?.isRetracted ?? false;
10731064

10741065
// check authorizations before the transaction
10751066
await _requireUploadAuthorization(
@@ -1111,6 +1102,10 @@ class PackageBackend {
11111102
final outgoingEmail = emailBackend.prepareEntity(email);
11121103

11131104
Package? package;
1105+
final existingVersions = await db
1106+
.query<PackageVersion>(ancestorKey: newVersion.packageKey!)
1107+
.run()
1108+
.toList();
11141109

11151110
// Add the new package to the repository by storing the tarball and
11161111
// inserting metadata to datastore (which happens atomically).
@@ -1159,14 +1154,12 @@ class PackageBackend {
11591154
newVersion.publisherId = package!.publisherId;
11601155

11611156
// Keep the latest version in the package object up-to-date.
1162-
package!.updateVersion(
1163-
newVersion,
1157+
package!.updateVersions(
1158+
[...existingVersions, newVersion],
11641159
dartSdkVersion: currentDartSdk.semanticVersion,
11651160
flutterSdkVersion: currentFlutterSdk.semanticVersion,
1166-
existingLatestIsRetracted: existingLatestIsRetracted,
11671161
);
11681162
package!.updated = clock.now().toUtc();
1169-
package!.versionCount++;
11701163

11711164
// update automated publisher identifiers if this is the first time they have been used
11721165
_updatePackageAutomatedPublishingLock(package!, agent);

app/lib/package/models.dart

Lines changed: 69 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -276,122 +276,106 @@ class Package extends db.ExpandoModel<String> {
276276
}
277277

278278
/// Updates the latest* version fields using all the available versions
279-
/// and the current Dart SDK.
279+
/// and the current Dart and Flutter SDK version.
280280
///
281281
/// If the update was triggered because of a single version changing, the
282282
/// [replaced] parameter can be used to replace the corresponding entry
283-
/// from the [allVersions] parameter, which may have been loaded before the
283+
/// from the [versions] parameter, which may have been loaded before the
284284
/// transaction started.
285-
bool updateLatestVersionReferences(
286-
Iterable<PackageVersion> allVersions, {
285+
///
286+
/// Returns whether the internal state has changed.
287+
bool updateVersions(
288+
List<PackageVersion> versions, {
287289
required Version dartSdkVersion,
288290
required Version flutterSdkVersion,
289291
PackageVersion? replaced,
290292
}) {
291-
final versions = allVersions
293+
final oldStableVersion = latestVersionKey;
294+
final oldPrereleaseVersion = latestPrereleaseVersionKey;
295+
final oldPreviewVersion = latestPreviewVersionKey;
296+
final oldLastVersionPublished = lastVersionPublished;
297+
final oldVersionCount = versionCount;
298+
299+
versions = versions
292300
.map((v) => v.version == replaced?.version ? replaced! : v)
293-
.where((v) => !v.isModerated)
294301
.toList();
295-
if (versions.isEmpty) {
302+
303+
final isAllRetracted = versions.every((v) => v.isRetracted);
304+
final isAllModerated = versions.every((v) => v.isModerated);
305+
if (isAllModerated) {
296306
throw NotAcceptableException('No visible versions left.');
297307
}
298308

299-
final oldStableVersion = latestSemanticVersion;
300-
final oldPrereleaseVersion = latestPrereleaseSemanticVersion;
301-
final oldPreviewVersion = latestPreviewSemanticVersion;
302-
303309
// reset field values
304310
latestVersionKey = null;
305-
latestPrereleaseVersionKey = null;
311+
latestPublished = null;
306312
latestPreviewVersionKey = null;
313+
latestPreviewPublished = null;
314+
latestPrereleaseVersionKey = null;
315+
latestPrereleasePublished = null;
316+
lastVersionPublished = null;
317+
versionCount = 0;
318+
319+
for (final pv in versions) {
320+
// Skip all moderated versions.
321+
if (pv.isModerated) {
322+
continue;
323+
}
307324

308-
for (final v in versions.where((v) => !v.isRetracted)) {
309-
updateVersion(
310-
v,
325+
versionCount++;
326+
327+
// `lastVersionPublished` is updated regardless of its retracted status.
328+
if (lastVersionPublished == null ||
329+
lastVersionPublished!.isBefore(pv.created!)) {
330+
lastVersionPublished = pv.created;
331+
}
332+
333+
// Skip retracted versions if there is a non-retracted version,
334+
// otherwise process all of the retracted ones.
335+
if (pv.isRetracted && !isAllRetracted) {
336+
continue;
337+
}
338+
339+
final newVersion = pv.semanticVersion;
340+
final isOnPreviewSdk = pv.pubspec!.isPreviewForCurrentSdk(
311341
dartSdkVersion: dartSdkVersion,
312342
flutterSdkVersion: flutterSdkVersion,
313343
);
314-
}
344+
final isOnStableSdk = !isOnPreviewSdk;
345+
346+
if (latestVersionKey == null ||
347+
(isNewer(latestSemanticVersion, newVersion, pubSorted: true) &&
348+
(latestSemanticVersion.isPreRelease || isOnStableSdk))) {
349+
latestVersionKey = pv.key;
350+
latestPublished = pv.created;
351+
}
352+
353+
if (latestPreviewVersionKey == null ||
354+
isNewer(latestPreviewSemanticVersion!, newVersion, pubSorted: true)) {
355+
latestPreviewVersionKey = pv.key;
356+
latestPreviewPublished = pv.created;
357+
}
315358

316-
if (latestVersionKey == null) {
317-
// All versions are retracted, we use the latest regardless of retracted status.
318-
for (final v in versions) {
319-
updateVersion(
320-
v,
321-
dartSdkVersion: dartSdkVersion,
322-
flutterSdkVersion: flutterSdkVersion,
323-
);
359+
if (latestPrereleaseVersionKey == null ||
360+
isNewer(latestPrereleaseSemanticVersion!, newVersion,
361+
pubSorted: false)) {
362+
latestPrereleaseVersionKey = pv.key;
363+
latestPrereleasePublished = pv.created;
324364
}
325365
}
326366

327-
final unchanged = oldStableVersion == latestSemanticVersion &&
328-
oldPrereleaseVersion == latestPrereleaseSemanticVersion &&
329-
oldPreviewVersion == latestPreviewSemanticVersion;
367+
final unchanged = oldStableVersion == latestVersionKey &&
368+
oldPrereleaseVersion == latestPrereleaseVersionKey &&
369+
oldPreviewVersion == latestPreviewVersionKey &&
370+
oldLastVersionPublished == lastVersionPublished &&
371+
oldVersionCount == versionCount;
330372
if (unchanged) {
331373
return false;
332374
}
333375
updated = clock.now().toUtc();
334376
return true;
335377
}
336378

337-
/// Updates latest stable, prerelease and preview versions and published
338-
/// timestamp with the new version.
339-
void updateVersion(
340-
PackageVersion pv, {
341-
required Version dartSdkVersion,
342-
required Version flutterSdkVersion,
343-
bool existingLatestIsRetracted = false,
344-
}) {
345-
final newVersion = pv.semanticVersion;
346-
final isOnStableSdk = !pv.pubspec!.isPreviewForCurrentSdk(
347-
dartSdkVersion: dartSdkVersion,
348-
flutterSdkVersion: flutterSdkVersion,
349-
);
350-
351-
if (existingLatestIsRetracted ||
352-
latestVersionKey == null ||
353-
(isNewer(latestSemanticVersion, newVersion, pubSorted: true) &&
354-
(latestSemanticVersion.isPreRelease || isOnStableSdk))) {
355-
latestVersionKey = pv.key;
356-
latestPublished = pv.created;
357-
}
358-
359-
if (existingLatestIsRetracted ||
360-
latestPreviewVersionKey == null ||
361-
isNewer(latestPreviewSemanticVersion!, newVersion, pubSorted: true)) {
362-
latestPreviewVersionKey = pv.key;
363-
latestPreviewPublished = pv.created;
364-
}
365-
366-
if (existingLatestIsRetracted ||
367-
latestPrereleaseVersionKey == null ||
368-
isNewer(latestPrereleaseSemanticVersion!, newVersion,
369-
pubSorted: false)) {
370-
latestPrereleaseVersionKey = pv.key;
371-
latestPrereleasePublished = pv.created;
372-
}
373-
374-
if (lastVersionPublished == null ||
375-
lastVersionPublished!.isBefore(pv.created!)) {
376-
lastVersionPublished = pv.created;
377-
}
378-
}
379-
380-
/// Checks if a change in a version's status may affect
381-
/// the latest versions: is it one of them or is it newer
382-
/// than one of the latests.
383-
bool mayAffectLatestVersions(Version version) {
384-
return latestVersion == version.toString() ||
385-
latestPrereleaseVersion == version.toString() ||
386-
latestPreviewVersion == version.toString() ||
387-
isNewer(latestSemanticVersion, version) ||
388-
(latestPrereleaseSemanticVersion != null &&
389-
isNewer(latestPrereleaseSemanticVersion!, version,
390-
pubSorted: false)) ||
391-
(latestPreviewSemanticVersion != null &&
392-
isNewer(latestPreviewSemanticVersion!, version, pubSorted: false));
393-
}
394-
395379
bool isNewPackage() => created!.difference(clock.now()).abs().inDays <= 30;
396380

397381
/// List of tags from the flags on the current [Package] entity.

app/lib/shared/integrity.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,10 @@ class IntegrityChecker {
365365
// to prevent false alarms that could happing if a new version is being published
366366
// while the integrity check is running.
367367
if (!pv.created!.isAfter(p.lastVersionPublished!)) {
368-
versionCountUntilLastPublished++;
368+
// Moderated versions are not counted.
369+
if (!pv.isModerated) {
370+
versionCountUntilLastPublished++;
371+
}
369372
}
370373
}
371374
if (p.versionCount != versionCountUntilLastPublished) {

app/test/package/models_test.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,8 @@ class _PublishSequence {
327327
..name = 'pkg'
328328
..created = DateTime(2021, 01, 29);
329329

330+
final _versions = <PackageVersion>[];
331+
330332
void publish(String version, {int sdk = 0}) {
331333
final minSdk = sdk > 0 ? _futureSdk : (sdk < 0 ? _pastSdk : _currentSdk);
332334
final pv = PackageVersion.init()
@@ -341,7 +343,8 @@ class _PublishSequence {
341343
'sdk': '>=$minSdk <3.0.0',
342344
},
343345
});
344-
_p.updateVersion(pv,
346+
_versions.add(pv);
347+
_p.updateVersions(_versions,
345348
dartSdkVersion: _currentSdk,
346349
flutterSdkVersion: Version.parse('3.20.0'));
347350
}

app/test/package/retracted_test.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,9 @@ void main() {
206206
final origLastVersionPublished = pkg.lastVersionPublished;
207207
final origLatestPublished = pkg.latestPublished;
208208
final origLatestPrereleasePublished = pkg.latestPrereleasePublished;
209+
final pv200devPublished =
210+
(await packageBackend.lookupPackageVersion('oxygen', '2.0.0-dev'))!
211+
.created;
209212

210213
final client =
211214
await createFakeAuthPubApiClient(email: adminAtPubDevEmail);
@@ -249,6 +252,7 @@ void main() {
249252
expect(pkg3.latestPrereleaseVersion, '2.0.0-dev');
250253
expect(
251254
pkg3.latestPrereleasePublished, isNot(origLatestPrereleasePublished));
255+
expect(pkg3.latestPrereleasePublished, pv200devPublished);
252256
expect(pkg3.lastVersionPublished, origLastVersionPublished);
253257

254258
// Retract all dev

0 commit comments

Comments
 (0)