You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Bitbucket Server预接收钩子创建标签时无法获取提交内容问题排查

问题:Bitbucket预接收钩子创建Tag时无法获取提交内容(commits为空)

使用Scriptrunner for Bitbucket Server编写预接收钩子,目的是在创建Tag时检查仓库中conanfile.py文件的依赖版本格式:

  • 若仓库中不存在conanfile.py,允许创建标签;
  • 若文件存在,通过正则校验依赖版本格式,不符合则阻止标签创建。

但钩子运行时commits始终为空,导致检查逻辑失效,相关代码及日志如下:

log.warn('Starting pre-receive hook')
if (refChanges.any { it.ref.id.startsWith("refs/tags/") && it.type == RefChangeType.ADD && it.fromHash == "0000000000000000000000000000000000000000"}) {

    log.warn('Ref change detected')

    @ShortTextInput(label = 'regex', description = 'Regex expression used to verify file content')
    String regexValue
    def pattern = Pattern.compile(".*requires\\(\"[^\\d_\\-](\\S)+/(\\[\\d{1,3}(\\.\\d{1,3})*\\]|\\d(\\.\\d)*)@(eca|exail)")
    def commitService = ComponentLocator.getComponent(CommitService)
    def contentService = ComponentLocator.getComponent(ContentService)
    def commits = refChanges.getCommits(repository) as List<Commit>

    log.warn("Number of commits: ${commits.size()}")

    def filePaths = [:] as Map<String, String>
    commits.reverseEach { commit ->
        def changesRequest = new ChangesRequest.Builder(repository, commit.id).build()
        commitService.streamChanges(changesRequest) { Change change ->
            if (change.path.toString().endsWith("conanfile.py")) {
                filePaths[change.path.toString()] = commit.id
            }
            true
        }
    }

    log.warn("Number of files: ${filePaths.size()}")

    def result = true
    def lineCount = 0
    def matchCount = 0

    filePaths.keySet().each { filePath ->
        def fileStream = new ByteArrayOutputStream()
        contentService.streamFile(repository, filePaths[filePath], filePath) {
            fileStream
        }
        fileStream.toString().split("\n").each { line ->
            if (line.startsWith("        self.requires")) {
                lineCount++
                log.warn("Line count: ${lineCount}")
                def match = pattern.matcher(line)
                if (match.find()) {
                    matchCount++
                    log.warn("Match count: ${matchCount}")
                }
            }
        }
    }

    if (lineCount != matchCount) {
        result = false
    } else {
        result = true
    }

    if (result) {
        log.warn('Pre-receive hook succeeded')
        return RepositoryHookResult.accepted()
    } else {
        log.warn('Pre-receive hook failed')
        return RepositoryHookResult.rejected('Tag non crée ', 'Vous devez figer vos versions de dependance que vous utilisez quand vous créez un tag')
    }

} else {
    log.warn('No relevant ref change detected')
    return RepositoryHookResult.accepted()
}

运行日志:

2023-02-22 13:19:29,551 WARN [c.o.s.r.ScriptBindingsManager]: Starting pre-receive hook
2023-02-22 13:19:29,559 WARN [c.o.s.r.ScriptBindingsManager]: Ref change detected
2023-02-22 13:19:29,608 WARN [c.o.s.r.ScriptBindingsManager]: Number of commits: 0
2023-02-22 13:19:29,609 WARN [c.o.s.r.ScriptBindingsManager]: Number of files: 0
2023-02-22 13:19:29,610 WARN [c.o.s.r.ScriptBindingsManager]: Pre-receive hook succeeded

问题原因

  1. 标签的本质限制:标签只是指向某个提交的引用,不属于"提交变更"范畴。refChanges.getCommits(repository)仅适用于分支的提交变更场景,无法从标签的引用变更中提取提交列表。
  2. 错误的提交获取逻辑:创建标签时的refChange仅包含标签自身的引用信息,不包含其指向的目标提交,因此调用getCommits()会返回空列表。

修复方案

直接通过标签的toHash属性获取其指向的目标提交ID,再根据该ID获取提交对象并检查文件内容:

完整修复后的代码

log.warn('Starting pre-receive hook')
def tagRefChange = refChanges.find { it.ref.id.startsWith("refs/tags/") && it.type == RefChangeType.ADD && it.fromHash == "0000000000000000000000000000000000000000" }

if (tagRefChange) {
    log.warn('Ref change detected')

    @ShortTextInput(label = 'regex', description = 'Regex expression used to verify file content')
    String regexValue
    def pattern = Pattern.compile(".*requires\\(\"[^\\d_\\-](\\S)+/(\\[\\d{1,3}(\\.\\d{1,3})*\\]|\\d(\\.\\d)*)@(eca|exail)")
    def commitService = ComponentLocator.getComponent(CommitService)
    def contentService = ComponentLocator.getComponent(ContentService)
    def commitId = tagRefChange.toHash
    def commit = commitService.getCommit(repository, commitId)

    log.warn("Target commit ID: ${commitId}")

    def filePaths = [:] as Map<String, String>
    def changesRequest = new ChangesRequest.Builder(repository, commit.id).build()
    commitService.streamChanges(changesRequest) { Change change ->
        if (change.path.toString().endsWith("conanfile.py")) {
            filePaths[change.path.toString()] = commit.id
        }
        true
    }

    log.warn("Number of files: ${filePaths.size()}")

    def result = true
    def lineCount = 0
    def matchCount = 0

    filePaths.keySet().each { filePath ->
        def fileStream = new ByteArrayOutputStream()
        contentService.streamFile(repository, filePaths[filePath], filePath) {
            fileStream
        }
        fileStream.toString().split("\n").each { line ->
            if (line.startsWith("        self.requires")) {
                lineCount++
                log.warn("Line count: ${lineCount}")
                def match = pattern.matcher(line)
                if (match.find()) {
                    matchCount++
                    log.warn("Match count: ${matchCount}")
                }
            }
        }
    }

    if (lineCount != matchCount) {
        result = false
    } else {
        result = true
    }

    if (result) {
        log.warn('Pre-receive hook succeeded')
        return RepositoryHookResult.accepted()
    } else {
        log.warn('Pre-receive hook failed')
        return RepositoryHookResult.rejected('Tag non crée ', 'Vous devez figer vos versions de dependance que vous utilisez quand vous créez un tag')
    }

} else {
    log.warn('No relevant ref change detected')
    return RepositoryHookResult.accepted()
}

内容的提问来源于stack exchange,提问作者Vince

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 18:09:21