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
问题原因
- 标签的本质限制:标签只是指向某个提交的引用,不属于"提交变更"范畴。
refChanges.getCommits(repository)仅适用于分支的提交变更场景,无法从标签的引用变更中提取提交列表。 - 错误的提交获取逻辑:创建标签时的
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
相关产品推荐
相关产品推荐

