The following pre-hook makes it possible to block large files, but with the ability to exclude certain files with a regex.
The only wish I have is to make field mandatory or when checked, it should contain a value.
import com.atlassian.bitbucket.commit.Commit
import com.atlassian.bitbucket.content.Change
import com.atlassian.bitbucket.content.ContentService
import com.atlassian.bitbucket.hook.repository.RepositoryHookResult
import com.atlassian.bitbucket.repository.Repository
import com.atlassian.sal.api.component.ComponentLocator
import com.onresolve.scriptrunner.canned.bitbucket.util.LfsContentIdentifier
import com.onresolve.scriptrunner.parameters.annotation.ShortTextInput
import com.onresolve.scriptrunner.parameters.annotation.NumberInput
import com.onresolve.scriptrunner.parameters.annotation.Checkbox
import java.util.regex.Pattern
@NumberInput(label = 'Max file size for files', description = 'The maximum allowed file size.')
Integer maxFileSize
@Checkbox(label = 'Limit LFS file size', description = 'Limit the file size of LFS files.')
Boolean limitLFS
@NumberInput(label = 'Max file size for LFS files', description = 'The maximum allowed file size.')
Integer maxFileSizeLFS
@Checkbox(label = 'Exclude files with regex', description = 'Enable the option to exclude files from size limits.')
Boolean excludeFiles
@ShortTextInput(label = 'Regex', description = 'Regular expression used to exclude files from size limits. Please use <a href="https://regexr.com/">regexr.com</a> (javascript engine) for example to test your regular expression.')
String regexValue
def pattern = Pattern.compile(regexValue)
def contentService = ComponentLocator.getComponent(ContentService)
def rejectedFiles = []
def String outputMessage
def String lfsMessage
if (limitLFS) {
lfsMessage= "LFS files are limited to " + maxFileSizeLFS + " bytes."
} else {
lfsMessage= "No file size limit for LFS files."
}
// Function isInLfs() returns an error when file isn't stored in LFS
// Exception: com.atlassian.bitbucket.scm.CommandFailedException: '■■■ cat-file -p d2ebg338626f60egef98c424c6088e0a0e45a60' exited with code -1,001
def testIfLFS (com.atlassian.bitbucket.content.Change change, com.atlassian.bitbucket.repository.Repository repository) {
try {
return change.isInLfs(repository)
} catch (Exception ex) {
return false
}
}
try {
refChanges.any { refChange ->
refChange.getChangesets(repository).each { changeSet ->
changeSet.changes.values.each { change ->
//log.warn("Full path to file: " + change.path.toString())
// We do not check for delete operations.
if (change.type.toString() != 'DELETE') {
if (excludeFiles && pattern.matcher(change.path.toString())) {
//log.warn("File is excluded from file size limits")
} else {
def boolean fileIsLFS = testIfLFS(change, repository)
def boolean fileToLarge = false
def Integer currentFileSize = 0
if (fileIsLFS) {
if (limitLFS) {
currentFileSize = (LfsContentIdentifier.catFileAndReturnLfsContentCallback(change.contentId, repository)).getLfsFileSize()
if (currentFileSize > maxFileSizeLFS) {
fileToLarge = true
}
}
} else {
currentFileSize = ((contentService.getSize(repository, changeSet.toCommit.id, change.path.toString())).getAsLong()).toInteger()
if (currentFileSize > maxFileSize) {
fileToLarge = true
}
}
if (fileToLarge) {
//log.warn("Illegal large file \"" + change.path.toString() + "\" with a size of " + currentFileSize)
rejectedFiles.add(changeSet.toCommit.displayId + " contains large file " +
change.path.toString() +
" (" +
change.type.toString() +
") " +
"by " +
changeSet.toCommit.author.name +
" with a size of " +
currentFileSize +
" bytes.")
}
}
}
}
}
}
if (rejectedFiles.size() > 0) {
outputMessage = "The Push is blocked because of the following files being larger then ${maxFileSize} bytes. " + lfsMessage
rejectedFiles.each { line ->
outputMessage = "${outputMessage}\n* ${line}"
}
//log.warn("New block message:\n" + outputMessage)
return RepositoryHookResult.rejected("Push blocked", outputMessage)
}
return RepositoryHookResult.accepted()
}
catch (Exception ex) {
log.error("Exception: ${ex}")
return RepositoryHookResult.rejected("Push blocked", "Exception in pre-hook script for max file size")
}