438 lines
13 KiB
Groovy
438 lines
13 KiB
Groovy
defaults = [
|
||
action_type: ['print_branches'],
|
||
action_help: '',
|
||
version: '0.0.0',
|
||
protect_branch: true
|
||
]
|
||
|
||
if (env.BRANCH_NAME == 'master') {
|
||
defaults.action_type.add('start_hotfix')
|
||
defaults.branch_type = 'hotfix'
|
||
}
|
||
if (env.BRANCH_NAME == 'develop') {
|
||
defaults.action_type.add('start_release')
|
||
defaults.branch_type = 'release'
|
||
}
|
||
if (env.BRANCH_NAME ==~ /^(hotfix)\/.+/) {
|
||
defaults.action_type.addAll(['merge_hotfix', 'finish_hotfix', 'rename_hotfix', 'delete_hotfix', 'protect_hotfix', 'unprotect_hotfix'])
|
||
defaults.branch_type = 'hotfix'
|
||
}
|
||
if (env.BRANCH_NAME ==~ /^(release)\/.+/) {
|
||
defaults.action_type.addAll(['merge_release', 'finish_release', 'rename_release', 'delete_release', 'protect_release', 'unprotect_release'])
|
||
defaults.branch_type = 'release'
|
||
}
|
||
|
||
pipeline {
|
||
agent {
|
||
label 'branch_manager'
|
||
}
|
||
environment {
|
||
GIT_SERVER = 'git.onlyoffice.com'
|
||
GITEA_TOKEN = credentials('gitea-token')
|
||
GITHUB_TOKEN = credentials('github-token')
|
||
TELEGRAM_TOKEN = credentials('telegram-bot-token')
|
||
}
|
||
options {
|
||
disableConcurrentBuilds()
|
||
}
|
||
parameters {
|
||
booleanParam (
|
||
name: 'wipe',
|
||
description: 'Wipe out current workspace',
|
||
defaultValue: false
|
||
)
|
||
choice (
|
||
name: 'action_type',
|
||
description: "Action type",
|
||
choices: defaults.action_type
|
||
)
|
||
string (
|
||
name: 'version',
|
||
description: 'Release version (for start only)',
|
||
defaultValue: defaults.version
|
||
)
|
||
booleanParam (
|
||
name: 'protect_branch',
|
||
description: 'Protect branch (for start only)',
|
||
defaultValue: defaults.protect_branch
|
||
)
|
||
string (
|
||
name: 'extra_branch',
|
||
description: 'Extra branch (for finish only)',
|
||
defaultValue: ''
|
||
)
|
||
booleanParam (
|
||
name: 'notify',
|
||
description: 'Telegram notification',
|
||
defaultValue: true
|
||
)
|
||
}
|
||
stages {
|
||
stage('Branch Manager') {
|
||
steps {
|
||
script {
|
||
currentBuild.displayName += ' - ' + params.action_type
|
||
if (params.action_type in ['start_hotfix', 'start_release'])
|
||
currentBuild.displayName += ' ' + params.version
|
||
|
||
if (params.wipe) {
|
||
deleteDir()
|
||
checkout scm
|
||
}
|
||
|
||
stats = [repos: [:]]
|
||
String branch = env.BRANCH_NAME
|
||
ArrayList baseBranches = []
|
||
getRepos().each {
|
||
stats.repos.put(it, [:])
|
||
}
|
||
Boolean pAction
|
||
Boolean sAction
|
||
|
||
if (params.action_type == 'print_branches') {
|
||
|
||
stats.repos.each { repo, status ->
|
||
pAction = printBranches(repo)
|
||
status.primary = (pAction) ? 'success' : 'failure'
|
||
status.secondary = 'none'
|
||
}
|
||
|
||
} else if (params.action_type.startsWith('start')) {
|
||
|
||
branch = defaults.branch_type + '/v' + params.version
|
||
baseBranches = [env.BRANCH_NAME]
|
||
|
||
stats.repos.each { repo, status ->
|
||
if (checkRemoteBranch(repo, branch)) {
|
||
echo "${repo}: Branch already ${branch} exists."
|
||
status.primary = 'skip'
|
||
} else {
|
||
dir ('repos/' + repo) {
|
||
checkoutRepo(repo)
|
||
if (!checkRemoteBranch(repo, 'develop')
|
||
&& !createBranch(repo, 'develop', 'master'))
|
||
error("Can't create develop branch.")
|
||
|
||
pAction = createBranch(repo, branch, baseBranches[0])
|
||
status.primary = (pAction) ? 'success' : 'failure'
|
||
}
|
||
}
|
||
|
||
if (params.protect_branch && !repo.contains('documents-pipeline')) {
|
||
sAction = protectBranch(repo, branch)
|
||
status.secondary = (sAction) ? 'lock' : ''
|
||
}
|
||
}
|
||
|
||
} else if (params.action_type.startsWith('merge')) {
|
||
|
||
baseBranches = ['master']
|
||
|
||
stats.repos.each { repo, status ->
|
||
if (!checkRemoteBranch(repo, branch)) {
|
||
echo "${repo}: Branch doesn't ${branch} exist."
|
||
status.primary = 'skip'
|
||
} else {
|
||
dir ('repos/' + repo) {
|
||
checkoutRepo(repo)
|
||
pAction = mergeBranch(repo, branch, baseBranches)
|
||
status.primary = (pAction) ? 'success' : 'failure'
|
||
}
|
||
}
|
||
}
|
||
|
||
} else if (params.action_type.startsWith('finish')) {
|
||
|
||
baseBranches = ['master', 'develop']
|
||
if (!params.extra_branch.isEmpty())
|
||
baseBranches.add(params.extra_branch)
|
||
|
||
stats.repos.each { repo, status ->
|
||
if (!checkRemoteBranch(repo, branch)) {
|
||
echo "${repo}: Branch doesn't ${branch} exist."
|
||
status.primary = 'skip'
|
||
} else {
|
||
dir ('repos/' + repo) {
|
||
checkoutRepo(repo)
|
||
unprotectBranch(repo, branch)
|
||
pAction = mergeBranch(repo, branch, baseBranches)
|
||
status.primary = (pAction) ? 'success' : 'failure'
|
||
if (pAction && !repo.contains('documents-pipeline')) {
|
||
sAction = deleteBranch(repo, branch)
|
||
status.secondary = (sAction) ? 'delete' : ''
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
} else if (params.action_type.startsWith('rename')) {
|
||
|
||
branch = defaults.branch_type + '/v' + params.version
|
||
baseBranches = [env.BRANCH_NAME]
|
||
|
||
stats.repos.each { repo, status ->
|
||
if (checkRemoteBranch(repo, branch)) {
|
||
echo "${repo}: Branch already ${branch} exists."
|
||
status.primary = 'skip'
|
||
} else {
|
||
dir ('repos/' + repo) {
|
||
checkoutRepo(repo, env.BRANCH_NAME)
|
||
pAction = createBranch(repo, branch, env.BRANCH_NAME)
|
||
status.primary = (pAction) ? 'success' : 'failure'
|
||
if (pAction) {
|
||
unprotectBranch(repo, env.BRANCH_NAME)
|
||
deleteBranch(repo, env.BRANCH_NAME)
|
||
if (params.protect_branch && !repo.contains('documents-pipeline')) {
|
||
sAction = protectBranch(repo, branch)
|
||
status.secondary = (sAction) ? 'lock' : ''
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
} else if (params.action_type.startsWith('delete')) {
|
||
|
||
stats.repos.each { repo, status ->
|
||
if (!checkRemoteBranch(repo, branch)) {
|
||
echo "${repo}: Branch doesn't ${branch} exist."
|
||
status.primary = 'skip'
|
||
} else {
|
||
dir ('repos/' + repo) {
|
||
checkoutRepo(repo, branch)
|
||
unprotectBranch(repo, branch)
|
||
if (!repo.contains('documents-pipeline')) {
|
||
pAction = deleteBranch(repo, branch)
|
||
status.primary = (pAction) ? 'success' : 'failure'
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
} else if (params.action_type.startsWith('protect')) {
|
||
|
||
stats.repos.each { repo, status ->
|
||
pAction = protectBranch(repo, branch)
|
||
status.primary = (pAction) ? 'success' : 'failure'
|
||
status.secondary = 'none'
|
||
}
|
||
|
||
} else if (params.action_type.startsWith('unprotect')) {
|
||
|
||
stats.repos.each { repo, status ->
|
||
pAction = unprotectBranch(repo, branch)
|
||
status.primary = (pAction) ? 'success' : 'failure'
|
||
status.secondary = 'none'
|
||
}
|
||
|
||
}
|
||
|
||
stats.putAll([
|
||
branch: branch,
|
||
baseBranches: baseBranches,
|
||
success: stats.repos.findAll { repo, status ->
|
||
status.primary in ['skip', 'success']
|
||
}.size(),
|
||
total: stats.repos.size()
|
||
])
|
||
println stats
|
||
|
||
}
|
||
}
|
||
}
|
||
}
|
||
post {
|
||
success {
|
||
script {
|
||
if (stats.success == 0)
|
||
currentBuild.result = 'FAILURE'
|
||
else if (stats.success != stats.total)
|
||
currentBuild.result = 'UNSTABLE'
|
||
else if (stats.success == stats.total)
|
||
currentBuild.result = 'SUCCESS'
|
||
|
||
sendNotification()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
def getRepos() {
|
||
return ['heatray/foo']
|
||
}
|
||
|
||
def checkoutRepo(String repo, String branch = 'master') {
|
||
sh (
|
||
label: "${repo}: checkout",
|
||
script: """
|
||
if [ "\$(GIT_DIR=.git git rev-parse --is-inside-work-tree)" = 'true' ]; then
|
||
git fetch --all --prune
|
||
git switch -f ${branch}
|
||
git reset --hard origin/${branch}
|
||
git clean -df
|
||
else
|
||
rm -rfv ./*
|
||
git clone -b ${branch} git@\$GIT_SERVER:${repo}.git .
|
||
fi
|
||
git branch -vv
|
||
"""
|
||
)
|
||
}
|
||
|
||
def checkRemoteBranch(String repo, String branch = 'master') {
|
||
return sh (
|
||
label: "${repo}: check branch ${branch}",
|
||
script: "git ls-remote --exit-code git@\$GIT_SERVER:${repo}.git ${branch}",
|
||
returnStatus: true
|
||
) == 0
|
||
}
|
||
|
||
def createBranch(String repo, String branch, String baseBranch) {
|
||
return sh (
|
||
label: "${repo}: start ${branch}",
|
||
script: """
|
||
git switch ${baseBranch}
|
||
git reset --hard origin/${baseBranch}
|
||
git checkout -B ${branch}
|
||
git push origin ${branch}
|
||
git branch -vv
|
||
echo "Branch created."
|
||
""",
|
||
returnStatus: true
|
||
) == 0
|
||
}
|
||
|
||
def mergeBranch(String repo, String branch, ArrayList baseBranches) {
|
||
return sh (
|
||
label: "${repo}: merge ${branch} into ${baseBranches.join(' ')}",
|
||
script: """#!/bin/bash -xe
|
||
git switch ${branch}
|
||
git reset --hard origin/${branch}
|
||
base_branches=(${baseBranches.join(' ')})
|
||
merged=0
|
||
rev_branch=\$(git rev-parse @)
|
||
for base in "\${base_branches[@]}"; do
|
||
git switch \$base || (((++merged)) && continue)
|
||
git reset --hard origin/\$base
|
||
rev_base=\$(git rev-parse @)
|
||
if [[ \$rev_branch == \$rev_base ]]; then
|
||
((++merged))
|
||
echo "No new commits."
|
||
continue
|
||
fi
|
||
# gh pr create --repo ${repo} --base \$base --head ${branch} \
|
||
# --title "Merge branch ${branch} into \$base" --fill || \
|
||
# true
|
||
if ! git merge ${branch} --no-edit --no-ff \
|
||
-m "Merge branch ${branch} into \$base"; then
|
||
git merge --abort
|
||
continue
|
||
fi
|
||
git push origin \$base
|
||
((++merged))
|
||
done
|
||
git branch -vv
|
||
if [[ \$merged -ne \${#base_branches[@]} ]]; then
|
||
echo "Not fully merged."
|
||
exit 2
|
||
fi
|
||
echo "Branch merged."
|
||
""",
|
||
returnStatus: true
|
||
) == 0
|
||
}
|
||
|
||
def deleteBranch(String repo, String branch) {
|
||
return sh (
|
||
label: "${repo}: delete ${branch}",
|
||
script: """
|
||
git switch -f master
|
||
git branch -D ${branch}
|
||
git push --delete origin ${branch}
|
||
echo "Branch deleted."
|
||
""",
|
||
returnStatus: true
|
||
) == 0
|
||
}
|
||
|
||
def printBranches(String repo) {
|
||
return sh (
|
||
label: "${repo}: branches list",
|
||
script: """
|
||
HTTP_CODE=\$(curl -s -X 'GET' \
|
||
'https://'"\$GIT_SERVER"'/api/v1/repos/${repo}/branches' \
|
||
-H 'Authorization: '"\$GITEA_TOKEN" \
|
||
-w '%{http_code}' \
|
||
-o output.json)
|
||
test \$HTTP_CODE -eq 200
|
||
jq -r '.[] | [.name, .protected] | @tsv' output.json
|
||
""",
|
||
returnStatus: true
|
||
) == 0
|
||
}
|
||
|
||
def protectBranch(String repo, String branch) {
|
||
return sh (
|
||
label: "${repo}: protect ${branch}",
|
||
script: """
|
||
echo '{
|
||
"branch_name": "${branch}"
|
||
}' | \
|
||
curl -s -X 'POST' \
|
||
'https://'"\$GIT_SERVER"'/api/v1/repos/${repo}/branch_protections' \
|
||
-H 'Authorization: token '"\$GITEA_TOKEN" \
|
||
-d @-
|
||
""",
|
||
returnStatus: true
|
||
) == 0
|
||
}
|
||
|
||
def unprotectBranch(String repo, String branch) {
|
||
String branchUrl = URLEncoder.encode(branch)
|
||
return sh (
|
||
label: "${repo}: unprotect ${branch}",
|
||
script: """
|
||
curl -s -X 'DELETE' \
|
||
'https://'"\$GIT_SERVER"'/api/v1/repos/${repo}/branch_protections/${branchUrl}' \
|
||
-H 'Authorization: token '"\$GITEA_TOKEN"
|
||
""",
|
||
returnStatus: true
|
||
) == 0
|
||
}
|
||
|
||
def sendNotification() {
|
||
String text = ''
|
||
switch(params.action_type) {
|
||
case ['start_hotfix', 'start_release']:
|
||
text = "Branch `${stats.branch}` created from `${stats.baseBranches[0]}`"
|
||
break
|
||
case ['merge_hotfix', 'merge_release']:
|
||
text = "Branch `${stats.branch}` merged into `${stats.baseBranches[0]}`"
|
||
break
|
||
case ['finish_hotfix', 'finish_release']:
|
||
text = "Branch `${stats.branch}` merged into "
|
||
text += stats.baseBranches.collect({"`$it`"}).join(', ')
|
||
break
|
||
default: text = 'Stats'
|
||
}
|
||
text += " \\[${stats.success}/${stats.total}]"
|
||
stats.repos.each { repo, status ->
|
||
text += '\n'
|
||
switch(status.primary) {
|
||
case 'skip': text += '🔘'; break
|
||
case 'success': text += '☑️'; break
|
||
case 'failure': text += '🚫'; break
|
||
default: text += '➖'
|
||
}
|
||
switch(status.secondary) {
|
||
case 'lock': text += '🔒'; break
|
||
case 'delete': text += '♻️'; break
|
||
case 'none': text += ''; break
|
||
default: text += '➖'
|
||
}
|
||
text += " [${repo}](https://${env.GIT_SERVER}/${repo})"
|
||
}
|
||
|
||
echo text
|
||
}
|