Skip to content

Commit

Permalink
Identify default git branch when downloading pipelines (#3593)
Browse files Browse the repository at this point in the history
Signed-off-by: Tom Sellman <[email protected]>
  • Loading branch information
tom-seqera committed Oct 8, 2024
1 parent 64bb5a9 commit 1227f8e
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package nextflow.config

import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import static nextflow.Const.DEFAULT_BRANCH
import static nextflow.Const.DEFAULT_MAIN_FILE_NAME
/**
* Models the nextflow config manifest settings
Expand Down Expand Up @@ -52,7 +51,7 @@ class Manifest {


String getDefaultBranch() {
target.defaultBranch ?: DEFAULT_BRANCH
target.defaultBranch
}

String getDescription() {
Expand Down
42 changes: 35 additions & 7 deletions modules/nextflow/src/main/groovy/nextflow/scm/AssetManager.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ import org.eclipse.jgit.merge.MergeStrategy
@Slf4j
@CompileStatic
class AssetManager {
private static final String REMOTE_REFS_ROOT = "refs/remotes/origin/"
private static final String REMOTE_DEFAULT_HEAD = REMOTE_REFS_ROOT + "HEAD"

/**
* The folder all pipelines scripts are installed
Expand Down Expand Up @@ -422,7 +424,14 @@ class AssetManager {
}

String getDefaultBranch() {
getManifest().getDefaultBranch()
// if specified in manifest, that takes priority
String branch = getManifest().getDefaultBranch()
if( !branch ) {
// otherwise look for a symbolic ref (refs/remotes/origin/HEAD)
Ref remoteHead = git.getRepository().findRef(REMOTE_DEFAULT_HEAD)
branch = remoteHead?.getTarget()?.getName()?.substring(REMOTE_REFS_ROOT.length())
}
return branch ?: DEFAULT_BRANCH
}

@Memoized
Expand Down Expand Up @@ -582,7 +591,7 @@ class AssetManager {
final cloneURL = getGitRepositoryUrl()
log.debug "Pulling $project -- Using remote clone url: ${cloneURL}"

// clone it
// clone it, but don't specify a revision - jgit will checkout the default branch
def clone = Git.cloneRepository()
if( provider.hasCredentials() )
clone.setCredentialsProvider( provider.getGitCredentials() )
Expand All @@ -595,9 +604,25 @@ class AssetManager {
clone.setDepth(deep)
clone.call()

// git cli would automatically create a 'refs/remotes/origin/HEAD' symbolic ref pointing at the remote's
// default branch. jgit doesn't do this, but since it automatically checked out the default branch on clone
// we can create the symbolic ref ourselves using the current head
def head = git.getRepository().findRef(Constants.HEAD)
if( head ) {
def headName = head.isSymbolic()
? Repository.shortenRefName(head.getTarget().getName())
: head.getName()

git.repository.getRefDatabase()
.newUpdate(REMOTE_DEFAULT_HEAD, true)
.link(REMOTE_REFS_ROOT + headName)
} else {
log.debug "Unable to determine default branch of repo ${cloneURL}, symbolic ref not created"
}

// now the default branch is recorded in the repo, explicitly checkout the revision (if specified).
// this also allows 'revision' to be a SHA commit id, which isn't supported by the clone command
if( revision ) {
// use an explicit checkout command *after* the clone instead of cloning a specific branch
// because the clone command does not allow the use of SHA commit id (only branch and tag names)
try { git.checkout() .setName(revision) .call() }
catch ( RefNotFoundException e ) { checkoutRemoteBranch(revision) }
}
Expand Down Expand Up @@ -729,6 +754,9 @@ class AssetManager {
}
}

static boolean isRemoteBranch(Ref ref) {
return ref.name.startsWith(REMOTE_REFS_ROOT) && ref.name != REMOTE_DEFAULT_HEAD
}

/**
* @return A list of existing branches and tags names. For example
Expand All @@ -750,7 +778,7 @@ class AssetManager {
def master = getDefaultBranch()

List<String> branches = getBranchList()
.findAll { it.name.startsWith('refs/heads/') || it.name.startsWith('refs/remotes/origin/') }
.findAll { it.name.startsWith('refs/heads/') || isRemoteBranch(it) }
.unique { shortenRefName(it.name) }
.collect { Ref it -> refToString(it,current,master,false,level) }

Expand Down Expand Up @@ -785,7 +813,7 @@ class AssetManager {

Map<String, Ref> remote = checkForUpdates ? git.lsRemote().callAsMap() : null
getBranchList()
.findAll { it.name.startsWith('refs/heads/') || it.name.startsWith('refs/remotes/origin/') }
.findAll { it.name.startsWith('refs/heads/') || isRemoteBranch(it) }
.unique { shortenRefName(it.name) }
.each { Ref it -> branches << refToMap(it,remote) }

Expand Down Expand Up @@ -882,7 +910,7 @@ class AssetManager {

def remote = git.lsRemote().callAsMap()
List<String> branches = getBranchList()
.findAll { it.name.startsWith('refs/heads/') || it.name.startsWith('refs/remotes/origin/') }
.findAll { it.name.startsWith('refs/heads/') || isRemoteBranch(it) }
.unique { shortenRefName(it.name) }
.findAll { Ref ref -> hasRemoteChange(ref,remote) }
.collect { Ref ref -> formatUpdate(remote.get(ref.name),level) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class CmdInfoTest extends Specification {
screen.contains(" main script : main.nf")
screen.contains(" revisions : ")
screen.contains(" * master (default)")
!screen.contains(" HEAD")
}

def 'should print json info' () {
Expand All @@ -89,11 +90,12 @@ class CmdInfoTest extends Specification {
json.repository == "https://github.com/nextflow-io/hello"
json.localPath == "$tempDir/nextflow-io/hello"
json.manifest.mainScript == 'main.nf'
json.manifest.defaultBranch == 'master'
json.manifest.defaultBranch == null
json.revisions.current == 'master'
json.revisions.master == 'master'
json.revisions.branches.size()>1
json.revisions.branches.any { it.name == 'master' }
!json.revisions.branches.any { it.name == 'HEAD' }
json.revisions.tags.size()>1
json.revisions.tags.any { it.name == 'v1.1' }

Expand All @@ -115,11 +117,12 @@ class CmdInfoTest extends Specification {
json.repository == "https://github.com/nextflow-io/hello"
json.localPath == "$tempDir/nextflow-io/hello"
json.manifest.mainScript == 'main.nf'
json.manifest.defaultBranch == 'master'
json.manifest.defaultBranch == null
json.revisions.current == 'master'
json.revisions.master == 'master'
json.revisions.branches.size()>1
json.revisions.branches.any { it.name == 'master' }
!json.revisions.branches.any { it.name == 'HEAD' }
json.revisions.tags.size()>1
json.revisions.tags.any { it.name == 'v1.1' }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ class AssetManagerTest extends Specification {
then:
holder.getMainScriptName() == 'main.nf'
holder.getHomePage() == 'https://github.com/foo/bar'
holder.manifest.getDefaultBranch() == 'master'
holder.manifest.getDefaultBranch() == null
holder.manifest.getDescription() == null

}
Expand Down Expand Up @@ -601,4 +601,54 @@ class AssetManagerTest extends Specification {
noExceptionThrown()
}

@Requires({System.getenv('NXF_GITHUB_ACCESS_TOKEN')})
def 'should identify default branch when downloading repo'() {

given:
def folder = tempDir.getRoot()
def token = System.getenv('NXF_GITHUB_ACCESS_TOKEN')
def manager = new AssetManager().build('nextflow-io/socks', [providers: [github: [auth: token]]])

when:
// simulate calling `nextflow run nextflow-io/socks` without specifying a revision
manager.download()
manager.checkout(null)
then:
folder.resolve('nextflow-io/socks/.git').isDirectory()
manager.getCurrentRevision() == 'main'

when:
manager.download()
then:
noExceptionThrown()
}

@Requires({System.getenv('NXF_GITHUB_ACCESS_TOKEN')})
def 'can filter remote branches'() {
given:
def folder = tempDir.getRoot()
def token = System.getenv('NXF_GITHUB_ACCESS_TOKEN')
def manager = new AssetManager().build('nextflow-io/hello', [providers: [github: [auth: token]]])
manager.download()
def branches = manager.getBranchList()

when:
def remote_head = branches.find { it.name == 'refs/remotes/origin/HEAD' }
then:
remote_head != null
!AssetManager.isRemoteBranch(remote_head)

when:
def remote_master = branches.find { it.name == 'refs/remotes/origin/master' }
then:
remote_master != null
AssetManager.isRemoteBranch(remote_master)

when:
def local_master = branches.find { it.name == 'refs/heads/master' }
then:
local_master != null
!AssetManager.isRemoteBranch(local_master)
}

}
Empty file modified tests/checks/run.sh
100644 → 100755
Empty file.

0 comments on commit 1227f8e

Please sign in to comment.