Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Default revision #5610

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

edmundmiller
Copy link
Contributor

Closes #4427

Need to clean up this PR a bit, and test it out in a real-world, not just in tests.

Copy link

netlify bot commented Dec 13, 2024

Deploy Preview for nextflow-docs-staging failed. Why did it fail? →

Name Link
🔨 Latest commit d1ebdcc
🔍 Latest deploy log https://app.netlify.com/sites/nextflow-docs-staging/deploys/6762d01fa509ae00089d8654

edmundmiller and others added 10 commits December 13, 2024 10:59
- Implemented `getDefaultRevision()` method in `Manifest.groovy` to retrieve the default revision.
- Updated `AssetManager.groovy` to compare the current revision against the default revision instead of the default branch during checkout.
…efaultRevision scenarios

- Added tests to verify behavior of AssetManager when handling defaultBranch and defaultRevision configurations.
- Implemented scenarios for defaultBranch set to 'master', defaultRevision specified, and version as defaultRevision.
- Included tests for cases with no defaultBranch and fallback mechanisms to ensure correct defaults are applied.
- Ensured that commit hashes can be used as defaultRevision and validated the handling of version tags in absence of defaultBranch.
- Marked several tests as PendingFeature for future implementation.

refactor: Remove PendingFeature annotations from AssetManagerTest

- Removed @PendingFeature annotations from multiple test cases in AssetManagerTest.groovy.
- This change indicates that the tests are now fully implemented and ready for execution.
- The tests cover scenarios for defaultBranch and defaultRevision configurations, ensuring comprehensive validation of AssetManager behavior.
- Removed @PendingFeature annotations from multiple test cases, indicating they are fully implemented.
- Added comprehensive tests for AssetManager's handling of defaultBranch and defaultRevision configurations.
- Implemented scenarios for various configurations, including defaultBranch set to 'master', specific defaultRevision, and version as defaultRevision.
- Validated behavior for cases with no defaultBranch and ensured correct fallback mechanisms.
- Enhanced tests for resolving script names and managing repository providers across different platforms.
…onsolidating configurations

- Removed several test cases that were redundant or overly complex, focusing on essential scenarios for AssetManager's behavior.
- Consolidated Git configuration tests into static final strings for better readability and maintainability.
- Streamlined the setup process for tests, ensuring clarity in the initialization of AssetManager instances.
- Enhanced the clarity of the remaining tests by improving comments and structure, making it easier to understand the purpose of each test case.
@@ -55,11 +55,23 @@ class Manifest {


String getDefaultBranch() {
target.defaultBranch
target.defaultBranch ?: 'master'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A git repository has its own concept of a 'default' branch, which is usually master or main - but can be absolutely anything. If the manifest doesn't specify a default branch/revision, we want to use the default branch set in git.

So, rather than assuming 'master', this method needs to return null if no value is specified in the manifest file. This allows the logic in AssetManager.getDefaultBranch() to interrogate git's default branch.

* @return The revision to use
*/
String getDefaultRevision() {
target.defaultRevision ?: getVersion() ?: getDefaultBranch()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think 'version' should be in this list at all.

'defaultRevision'/'defaultBranch' are pointers to a (potentially) different branch/revision from the one checked out.

Version, if set, would usually represent the version of the checked out branch/revision, so I think including it in the list effectively means that whichever branch is checked out will become the default. It seems like this could result in surprising behaviour. Probably better only to rely on manifest fields which are explicitly related to defaults.

Comment on lines +213 to +308

/**
* Checks if the current version is a development version
* @return true if version ends with 'dev' or '-dev', false otherwise
*/
boolean isDevelopmentVersion() {
def version = getVersion()
if (!version) return false
return version.endsWith('dev') || version.endsWith('-dev')
}

/**
* Checks if the current version is a release candidate
* @return true if version contains '-RC', false otherwise
*/
boolean isReleaseCandidate() {
def version = getVersion()
if (!version) return false
return version.contains('-RC')
}

/**
* Checks if the current version is a hotfix
* @return true if version contains '-hotfix', false otherwise
*/
boolean isHotfix() {
def version = getVersion()
if (!version) return false
return version.contains('-hotfix')
}

/**
* Validates if a version string is a valid semantic version
* @param version The version string to validate
* @return true if valid, false otherwise
*/
boolean isValidVersion(String version) {
if (!version) return false
// Matches standard versions like 1.0.0, release candidates like 1.0.0-RC1, development versions like 1.0.0dev, and hotfixes like 1.0.0-hotfix
version ==~ /^\d+\.\d+\.\d+(-RC\d+|-dev|dev|-hotfix)?$/
}

/**
* Compares two version strings
* @return -1 if v1 < v2, 0 if v1 == v2, 1 if v1 > v2
*/
private int compareVersions(String v1, String v2) {
def v1Parts = v1.tokenize('.')
def v2Parts = v2.tokenize('.')

for (int i = 0; i < Math.min(v1Parts.size(), v2Parts.size()); i++) {
def v1Part = v1Parts[i].replaceAll(/(-RC\d+|-dev|dev|-hotfix)$/, '') as int
def v2Part = v2Parts[i].replaceAll(/(-RC\d+|-dev|dev|-hotfix)$/, '') as int
if (v1Part != v2Part) {
return v1Part <=> v2Part
}
}

v1Parts.size() <=> v2Parts.size()
}

/**
* Checks if the current version is greater than the provided version
* @param otherVersion The version to compare against
* @return true if current version is greater, false otherwise
*/
boolean isVersionGreaterThan(String otherVersion) {
def version = getVersion()
if (!version || !otherVersion) return false

// Strip any suffixes for comparison
def v1 = version.replaceAll(/(-RC\d+|-dev|dev|-hotfix)$/, '')
def v2 = otherVersion.replaceAll(/(-RC\d+|-dev|dev|-hotfix)$/, '')

compareVersions(v1, v2) > 0
}

/**
* Checks if the current version is compatible with the provided version
* @param otherVersion The version to check compatibility with
* @return true if compatible, false otherwise
*/
boolean isVersionCompatibleWith(String otherVersion) {
def version = getVersion()
if (!version || !otherVersion) return false

// Development versions are always compatible
if (isDevelopmentVersion()) return true

// For release candidates and hotfixes, compare base versions
def v1 = version.replaceAll(/(-RC\d+|-dev|dev|-hotfix)$/, '')
def v2 = otherVersion.replaceAll(/(-RC\d+|-dev|dev|-hotfix)$/, '')

// Version should be greater than or equal to the other version
compareVersions(v1, v2) >= 0
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of these version-related methods seem to be assuming/enforcing a very specific versioning scheme on pipelines. I don't think Nextflow can make these kind of assumptions/expectations since any organisation could be writing their own Nextflow pipelines and might use a different versioning scheme which is completely incompatible.

It doesn't look like any of these methods are used in this PR though, so I think they can just be removed.

@@ -937,9 +937,18 @@ class AssetManager {
assert localPath

def current = getCurrentRevision()
if( current != defaultBranch ) {
def defaultRev = getManifest().getDefaultRevision()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be asking the manifest directly; it needs to use the defaultBranch logic in AssetManager to handle cases where no value is specified in the manifest.

Comment on lines +944 to +949
Ref head = git.getRepository().findRef(Constants.HEAD);

// try to resolve the the current object id to a tag name
Map<ObjectId, String> names = git.nameRev().addPrefix( "refs/tags/" ).add(head.objectId).call()
def tag = names.get( head.objectId ) ?: head.objectId.name()
if( current != tag ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not quite sure I understand this change.

It looks like the new logic boils down to "don't throw the error if the current revision is a tag (any tag)". Is that the intention?

@@ -602,7 +547,7 @@ class AssetManagerTest extends Specification {
}

@Requires({System.getenv('NXF_GITHUB_ACCESS_TOKEN')})
def 'should identify default branch when downloading repo'() {
def "should use a default tag"() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test name ('should identify default branch when downloading repo') was correct and shouldn't be changed. It tests that if the repo manifest doesn't explicitly specify a default branch (or revision) then Nextflow can correctly identify the default branch configured at the git level. In the case of the 'socks' repo, this is 'main'.

holder.manifest.getDefaultBranch() == 'master'
}

def "should use version as defaultRevision when available"() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned, I'm not sure 'version' should be involved in determining the default revision/branch.

@@ -623,32 +568,470 @@ class AssetManagerTest extends Specification {
noExceptionThrown()
}

@Requires({System.getenv('NXF_GITHUB_ACCESS_TOKEN')})
def 'can filter remote branches'() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test ('can filter remote branches') should not be removed.

def testList() {

given:
def "should list available pipeline assets"() {
Copy link
Contributor

@tom-seqera tom-seqera Jan 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I agree with efforts to improve the naming and clarity of the tests, doing so alongside changes to behaviour introduces a lot of noise. I'd suggest splitting such changes into a separate PR which improves the tests without modifying any behaviour.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a great idea. Let me do that first. This PR got convoluted because I wanted to make sure any changes I made were going to be backwards compatible 😄

@edmundmiller edmundmiller mentioned this pull request Jan 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Set a default revision
3 participants