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
117 changes: 115 additions & 2 deletions modules/nextflow/src/main/groovy/nextflow/config/Manifest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -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.

}

/**
* Gets the default revision to use
* Priority order:
* 1. defaultRevision if set
* 2. version if set
* 3. defaultBranch
* @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.

}

String getDescription() {
target.description
target.description
}

String getAuthor() {
Expand Down Expand Up @@ -104,6 +116,10 @@ class Manifest {
target.nextflowVersion
}

/**
* Gets the version
* @return The version string or null if not set
*/
String getVersion() {
target.version
}
Expand Down Expand Up @@ -139,6 +155,7 @@ class Manifest {
.map(c -> c.toMap())
.collect(Collectors.toList())
result.defaultBranch = getDefaultBranch()
result.defaultRevision = getDefaultRevision()
result.description = getDescription()
result.homePage = homePage
result.gitmodules = getGitmodules()
Expand Down Expand Up @@ -193,4 +210,100 @@ class Manifest {
MAINTAINER,
CONTRIBUTOR
}

/**
* 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
}
Comment on lines +213 to +308
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.

}
13 changes: 11 additions & 2 deletions modules/nextflow/src/main/groovy/nextflow/scm/AssetManager.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -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.

if( current != defaultRev ) {
// NOTE This is the issue
if( !revision ) {
throw new AbortOperationException("Project `$project` is currently stuck on revision: $current -- you need to explicitly specify a revision with the option `-r` in order to use it")
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 ) {
Comment on lines +944 to +949
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?

throw new AbortOperationException("Project `$project` is currently stuck on revision: $current -- you need to explicitly specify a revision with the option `-r` in order to use it")
}
}
}
if( !revision || revision == current ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ class CmdRunTest extends Specification {
}

@Unroll
def 'should guss is repo' () {
def 'should guess is repo' () {
expect:
CmdRun.guessIsRepo(PATH) == EXPECTED

Expand Down
Loading
Loading