[MNG-5180] Versioning's snapshot version list is not included in metadata merge

Co-authored-by: Konrad Windszus <kwin@apache.org>

This closes #681
This commit is contained in:
Tomi Pakarinen 2020-08-28 22:35:15 +03:00 committed by Michael Osipov
parent e327be3d85
commit 1e33a57264
3 changed files with 339 additions and 5 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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 ---*/
}