This is an automated email from the ASF dual-hosted git repository. michaelo pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/maven.git
The following commit(s) were added to refs/heads/master by this push: new 1e33a57 [MNG-5180] Versioning's snapshot version list is not included in metadata merge 1e33a57 is described below commit 1e33a572643628e7742eda2d469e4bd04b880523 Author: Tomi Pakarinen <tomi.pakari...@resys.io> AuthorDate: Fri Aug 28 22:35:15 2020 +0300 [MNG-5180] Versioning's snapshot version list is not included in metadata merge Co-authored-by: Konrad Windszus <k...@apache.org> This closes #681 --- maven-repository-metadata/pom.xml | 5 + .../src/main/mdo/metadata.mdo | 50 +++- .../artifact/repository/metadata/MetadataTest.java | 289 +++++++++++++++++++++ 3 files changed, 339 insertions(+), 5 deletions(-) diff --git a/maven-repository-metadata/pom.xml b/maven-repository-metadata/pom.xml index 28fbbf8..d989e0b 100644 --- a/maven-repository-metadata/pom.xml +++ b/maven-repository-metadata/pom.xml @@ -38,6 +38,11 @@ under the License. <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-utils</artifactId> </dependency> + <dependency> + <groupId>org.apache.maven.resolver</groupId> + <artifactId>maven-resolver-api</artifactId> + <scope>test</scope> + </dependency> </dependencies> <build> diff --git a/maven-repository-metadata/src/main/mdo/metadata.mdo b/maven-repository-metadata/src/main/mdo/metadata.mdo index ddaeb0a..6ce381d 100644 --- a/maven-repository-metadata/src/main/mdo/metadata.mdo +++ b/maven-repository-metadata/src/main/mdo/metadata.mdo @@ -88,6 +88,11 @@ under the License. <codeSegment> <version>1.0.0+</version> <code><![CDATA[ + private String getSnapshotVersionKey( SnapshotVersion sv ) + { + return sv.getClassifier() + ":" + sv.getExtension(); + } + public boolean merge( Metadata sourceMetadata ) { boolean changed = false; @@ -178,11 +183,13 @@ under the License. Snapshot snapshot = versioning.getSnapshot(); if ( snapshot != null ) { + boolean updateSnapshotVersions = false; if ( s == null ) { s = new Snapshot(); v.setSnapshot( s ); changed = true; + updateSnapshotVersions = true; } // overwrite @@ -191,6 +198,7 @@ under the License. { s.setTimestamp( snapshot.getTimestamp() ); changed = true; + updateSnapshotVersions = true; } if ( s.getBuildNumber() != snapshot.getBuildNumber() ) { @@ -202,6 +210,34 @@ under the License. s.setLocalCopy( snapshot.isLocalCopy() ); changed = true; } + if ( updateSnapshotVersions ) + { + java.util.Map<String, SnapshotVersion> versions = new java.util.LinkedHashMap<>(); + // never convert from legacy to new format if either source or target is legacy format + if ( !v.getSnapshotVersions().isEmpty() ) + { + for ( SnapshotVersion sv : versioning.getSnapshotVersions() ) + { + String key = getSnapshotVersionKey( sv ); + versions.put( key, sv ); + } + // never convert from legacy format + if ( !versions.isEmpty() ) + { + for ( SnapshotVersion sv : v.getSnapshotVersions() ) + { + String key = getSnapshotVersionKey( sv ); + if ( !versions.containsKey( key ) ) + { + versions.put( key, sv ); + } + } + } + v.setSnapshotVersions( new java.util.ArrayList<SnapshotVersion>( versions.values() ) ); + } + + changed = true; + } } } } @@ -241,7 +277,7 @@ under the License. <name>lastUpdated</name> <version>1.0.0+</version> <type>String</type> - <description>When the metadata was last updated (both "groupId/artifactId" and "groupId/artifactId/version" directories)</description> + <description>When the metadata was last updated (both "groupId/artifactId" and "groupId/artifactId/version" directories). The timestamp is expressed using UTC in the format yyyyMMddHHmmss.</description> </field> <field xdoc.separator="blank"> <name>snapshot</name> @@ -254,7 +290,7 @@ under the License. <field> <name>snapshotVersions</name> <version>1.1.0+</version> - <description>Information for each sub-artifact available in this artifact snapshot.</description> + <description>Information for each sub-artifact available in this artifact snapshot. This is only the most recent SNAPSHOT for each unique extension/classifier combination.</description> <association> <type>SnapshotVersion</type> <multiplicity>*</multiplicity> @@ -289,7 +325,7 @@ under the License. <field> <name>timestamp</name> <version>1.0.0+</version> - <description>The time it was deployed</description> + <description>The timestamp when this version was deployed. The timestamp is expressed using UTC in the format yyyyMMdd.HHmmss.</description> <type>String</type> </field> <field> @@ -316,26 +352,30 @@ under the License. <name>classifier</name> <version>1.1.0+</version> <type>String</type> - <description>The classifier of the sub-artifact.</description> + <description>The classifier of the sub-artifact. Each classifier and extension pair may only appear once.</description> <defaultValue></defaultValue> + <identifier>true</identifier> </field> <field> <name>extension</name> <version>1.1.0+</version> <type>String</type> - <description>The file extension of the sub-artifact.</description> + <description>The file extension of the sub-artifact. Each classifier and extension pair may only appear once.</description> + <identifier>true</identifier> </field> <field xml.tagName="value"> <name>version</name> <version>1.1.0+</version> <type>String</type> <description>The resolved snapshot version of the sub-artifact.</description> + <identifier>true</identifier> </field> <field> <name>updated</name> <version>1.1.0+</version> <type>String</type> <description>The timestamp when this version information was last updated. The timestamp is expressed using UTC in the format yyyyMMddHHmmss.</description> + <identifier>true</identifier> </field> </fields> </class> diff --git a/maven-repository-metadata/src/test/java/org/apache/maven/artifact/repository/metadata/MetadataTest.java b/maven-repository-metadata/src/test/java/org/apache/maven/artifact/repository/metadata/MetadataTest.java new file mode 100644 index 0000000..ccdadbf --- /dev/null +++ b/maven-repository-metadata/src/test/java/org/apache/maven/artifact/repository/metadata/MetadataTest.java @@ -0,0 +1,289 @@ +package org.apache.maven.artifact.repository.metadata; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class MetadataTest +{ + + Artifact artifact; + + Metadata target; + + @BeforeEach + void before() + { + artifact = new DefaultArtifact( "myGroup:myArtifact:1.0-SNAPSHOT" ); + target = createMetadataFromArtifact( artifact ); + } + + /*--- START test common metadata ---*/ + @Test + void mergeEmptyMetadata() + throws Exception + { + Metadata metadata = new Metadata(); + assertFalse( metadata.merge( new Metadata() ) ); + } + + @Test + void mergeDifferentGAV() + throws Exception + { + // merge implicitly assumes that merge is only called on the same GAV and does not perform any validation here! + Metadata source = new Metadata(); + source.setArtifactId( "source-artifact" ); + source.setGroupId( "source-group" ); + source.setVersion( "2.0" ); + assertFalse( target.merge( source ) ); + assertEquals( "myArtifact", target.getArtifactId() ); + assertEquals( "myGroup", target.getGroupId() ); + assertEquals( "1.0-SNAPSHOT", target.getVersion() ); + } + /*--- END test common metadata ---*/ + + /*--- START test "groupId/artifactId/version" metadata ---*/ + @Test + void mergeSnapshotWithEmptyList() + throws Exception + { + Snapshot snapshot = new Snapshot(); + snapshot.setBuildNumber( 3 ); + snapshot.setTimestamp( "20200710.072412" ); + target.getVersioning().setSnapshot( snapshot ); + target.getVersioning().setLastUpdated( "20200921071745" ); + SnapshotVersion sv = new SnapshotVersion(); + sv.setClassifier( "sources" ); + sv.setExtension( "jar" ); + sv.setUpdated( "20200710072412" ); + target.getVersioning().addSnapshotVersion( sv ); + + Metadata source = createMetadataFromArtifact( artifact ); + // nothing should be actually changed, but still merge returns true + assertTrue( target.merge( source ) ); + + // NOTE! Merge updates last updated to source + assertEquals( "20200921071745", source.getVersioning().getLastUpdated() ); + + assertEquals( "myArtifact", target.getArtifactId() ); + assertEquals( "myGroup", target.getGroupId() ); + + assertEquals( 3, target.getVersioning().getSnapshot().getBuildNumber() ); + assertEquals( "20200710.072412", target.getVersioning().getSnapshot().getTimestamp() ); + + assertEquals( 1, target.getVersioning().getSnapshotVersions().size() ); + assertEquals( "sources", target.getVersioning().getSnapshotVersions().get( 0 ).getClassifier() ); + assertEquals( "jar", target.getVersioning().getSnapshotVersions().get( 0 ).getExtension() ); + assertEquals( "20200710072412", target.getVersioning().getSnapshotVersions().get( 0 ).getUpdated() ); + } + + @Test + void mergeWithSameSnapshotWithDifferentVersionsAndNewerLastUpdated() + { + Metadata source = createMetadataFromArtifact( artifact ); + Date before = new Date( System.currentTimeMillis() - 5000 ); + Date after = new Date( System.currentTimeMillis() ); + addSnapshotVersion( target.getVersioning(), "jar", before, "1", 1 ); + SnapshotVersion sv2 = + addSnapshotVersion( source.getVersioning(), "jar", after, "1.0-" + formatDate( after, true ) + "-2", 2 ); + SnapshotVersion sv3 = + addSnapshotVersion( source.getVersioning(), "pom", after, "1.0-" + formatDate( after, true ) + "-2", 2 ); + assertTrue( target.merge( source ) ); + Versioning actualVersioning = target.getVersioning(); + assertEquals( 2, actualVersioning.getSnapshotVersions().size() ); + assertEquals( sv2, actualVersioning.getSnapshotVersions().get( 0 ) ); + assertEquals( sv3, actualVersioning.getSnapshotVersions().get( 1 ) ); + assertEquals( formatDate( after, false ), actualVersioning.getLastUpdated() ); + assertEquals( formatDate( after, true ), actualVersioning.getSnapshot().getTimestamp() ); + assertEquals( 2, actualVersioning.getSnapshot().getBuildNumber() ); + } + + @Test + void mergeWithSameSnapshotWithDifferentVersionsAndOlderLastUpdated() + { + Metadata source = createMetadataFromArtifact( artifact ); + Date before = new Date( System.currentTimeMillis() - 5000 ); + Date after = new Date( System.currentTimeMillis() ); + SnapshotVersion sv1 = addSnapshotVersion( target.getVersioning(), after, artifact ); + addSnapshotVersion( source.getVersioning(), before, artifact ); + // nothing should be updated, as the target was already updated at a later date than source + assertFalse( target.merge( source ) ); + assertEquals( 1, target.getVersioning().getSnapshotVersions().size() ); + assertEquals( sv1, target.getVersioning().getSnapshotVersions().get( 0 ) ); + assertEquals( formatDate( after, false ), target.getVersioning().getLastUpdated() ); + assertEquals( formatDate( after, true ), target.getVersioning().getSnapshot().getTimestamp() ); + } + + @Test + void mergeWithSameSnapshotWithSameVersionAndTimestamp() + { + Metadata source = createMetadataFromArtifact( artifact ); + Date date = new Date(); + addSnapshotVersion( target.getVersioning(), date, artifact ); + SnapshotVersion sv1 = addSnapshotVersion( source.getVersioning(), date, artifact ); + // although nothing has changed merge returns true, as the last modified date is equal + // TODO: improve merge here? + assertTrue( target.merge( source ) ); + assertEquals( 1, target.getVersioning().getSnapshotVersions().size() ); + assertEquals( sv1, target.getVersioning().getSnapshotVersions().get( 0 ) ); + assertEquals( formatDate( date, false ), target.getVersioning().getLastUpdated() ); + assertEquals( formatDate( date, true ), target.getVersioning().getSnapshot().getTimestamp() ); + } + + @Test + void mergeLegacyWithSnapshotLegacy() + { + Metadata source = createMetadataFromArtifact( artifact ); + Date before = new Date( System.currentTimeMillis() - 5000 ); + Date after = new Date( System.currentTimeMillis() ); + // legacy metadata did not have "versioning.snapshotVersions" + addSnapshotVersionLegacy( target.getVersioning(), before, 1 ); + addSnapshotVersionLegacy( source.getVersioning(), after, 2 ); + // although nothing has changed merge returns true, as the last modified date is equal + // TODO: improve merge here? + assertTrue( target.merge( source ) ); + assertEquals( 0, target.getVersioning().getSnapshotVersions().size() ); + assertEquals( formatDate( after, false ), target.getVersioning().getLastUpdated() ); + assertEquals( formatDate( after, true ), target.getVersioning().getSnapshot().getTimestamp() ); + } + + @Test + void mergeLegacyWithSnapshot() + { + Metadata source = createMetadataFromArtifact( artifact ); + Date before = new Date( System.currentTimeMillis() - 5000 ); + Date after = new Date( System.currentTimeMillis() ); + // legacy metadata did not have "versioning.snapshotVersions" + addSnapshotVersionLegacy( target.getVersioning(), before, 1 ); + addSnapshotVersion( source.getVersioning(), after, artifact ); + // although nothing has changed merge returns true, as the last modified date is equal + // TODO: improve merge here? + assertTrue( target.merge( source ) ); + // never convert from legacy format to v1.1 format + assertEquals( 0, target.getVersioning().getSnapshotVersions().size() ); + assertEquals( formatDate( after, false ), target.getVersioning().getLastUpdated() ); + assertEquals( formatDate( after, true ), target.getVersioning().getSnapshot().getTimestamp() ); + } + + @Test + void mergeWithSnapshotLegacy() + { + Metadata source = createMetadataFromArtifact( artifact ); + Date before = new Date( System.currentTimeMillis() - 5000 ); + Date after = new Date( System.currentTimeMillis() ); + addSnapshotVersion( target.getVersioning(), before, artifact ); + // legacy metadata did not have "versioning.snapshotVersions" + addSnapshotVersionLegacy( source.getVersioning(), after, 2 ); + // although nothing has changed merge returns true, as the last modified date is equal + // TODO: improve merge here? + assertTrue( target.merge( source ) ); + // the result must be legacy format as well + assertEquals( 0, target.getVersioning().getSnapshotVersions().size() ); + assertEquals( formatDate( after, false ), target.getVersioning().getLastUpdated() ); + assertEquals( formatDate( after, true ), target.getVersioning().getSnapshot().getTimestamp() ); + assertEquals( 2, target.getVersioning().getSnapshot().getBuildNumber() ); + } + /*-- END test "groupId/artifactId/version" metadata ---*/ + + /*-- START helper methods to populate metadata objects ---*/ + private static final String SNAPSHOT = "SNAPSHOT"; + + private static final String DEFAULT_SNAPSHOT_TIMESTAMP_FORMAT = "yyyyMMdd.HHmmss"; + + private static final String DEFAULT_DATE_FORMAT = "yyyyMMddHHmmss"; + + private static String formatDate( Date date, boolean forSnapshotTimestamp ) + { + // logic from metadata.mdo, class "Versioning" + TimeZone timezone = TimeZone.getTimeZone( "UTC" ); + DateFormat fmt = + new SimpleDateFormat( forSnapshotTimestamp ? DEFAULT_SNAPSHOT_TIMESTAMP_FORMAT : DEFAULT_DATE_FORMAT ); + fmt.setCalendar( new GregorianCalendar() ); + fmt.setTimeZone( timezone ); + return fmt.format( date ); + } + + private static Metadata createMetadataFromArtifact( Artifact artifact ) + { + Metadata metadata = new Metadata(); + metadata.setArtifactId( artifact.getArtifactId() ); + metadata.setGroupId( artifact.getGroupId() ); + metadata.setVersion( artifact.getVersion() ); + metadata.setVersioning( new Versioning() ); + return metadata; + } + + private static SnapshotVersion addSnapshotVersion( Versioning versioning, Date timestamp, Artifact artifact ) + { + int buildNumber = 1; + // this generates timestamped versions like maven-resolver-provider: + // https://github.com/apache/maven/blob/03df5f7c639db744a3597c7175c92c8e2a27767b/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/RemoteSnapshotMetadata.java#L79 + String version = artifact.getVersion(); + String qualifier = formatDate( timestamp, true ) + '-' + buildNumber; + version = version.substring( 0, version.length() - SNAPSHOT.length() ) + qualifier; + return addSnapshotVersion( versioning, artifact.getExtension(), timestamp, version, buildNumber ); + } + + private static SnapshotVersion addSnapshotVersion( Versioning versioning, String extension, Date timestamp, + String version, int buildNumber ) + { + Snapshot snapshot = new Snapshot(); + snapshot.setBuildNumber( buildNumber ); + snapshot.setTimestamp( formatDate( timestamp, true ) ); + + SnapshotVersion sv = new SnapshotVersion(); + sv.setExtension( extension ); + sv.setVersion( version ); + sv.setUpdated( formatDate( timestamp, false ) ); + versioning.addSnapshotVersion( sv ); + + // make the new snapshot the current one + versioning.setSnapshot( snapshot ); + versioning.setLastUpdatedTimestamp( timestamp ); + return sv; + } + + // the format written by Maven 2 + // (https://maven.apache.org/ref/2.2.1/maven-repository-metadata/repository-metadata.html) + private static void addSnapshotVersionLegacy( Versioning versioning, Date timestamp, int buildNumber ) + { + Snapshot snapshot = new Snapshot(); + snapshot.setBuildNumber( buildNumber ); + snapshot.setTimestamp( formatDate( timestamp, true ) ); + + versioning.setSnapshot( snapshot ); + versioning.setLastUpdatedTimestamp( timestamp ); + } + /*-- END helper methods to populate metadata objects ---*/ +} \ No newline at end of file