From: Michał Kępień Date: Thu, 30 Apr 2026 09:58:55 +0000 (+0200) Subject: Set up automatic rebasing for security-* branches X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=be6fc76cc206efa5df32654d24d15f93b2583e7c;p=thirdparty%2Fbind9.git Set up automatic rebasing for security-* branches Introduce a set of private branches containing only security fixes that are automatically rebased onto the corresponding open source branches whenever new changes are merged. Each rebase triggers a basic build, failing the CI job if the build breaks. When a security-* branch is rebased, create a CI pipeline for its new revision and rebase its corresponding bind-9.x-sub branch (if it exists) on top of it, creating a rebase chain. Report any failures in the process via Mattermost. These changes enable treating security fixes similarly to other code changes, without deferring merges all the way until release prep. (cherry picked from commit af7d5e566ee0bf1d6c761a4301f7b3072d5080bd) --- diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index db971d63a10..1f953e51d25 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -99,6 +99,16 @@ default: when: - runner_system_failure +workflow: + # Prevent jobs marked with "interruptible: false" that have not yet been + # started from getting canceled when a newer pipeline is created for the same + # branch. This setting was added to enable "iterative autorebases" to work + # correctly with `resource_group`. Without this setting, pending rebase jobs + # waiting for a resource group would get canceled by the "git push" operation + # at the end of a prior rebase job belonging to the same resource group. + auto_cancel: + on_new_commit: interruptible + stages: - autoconf - quick-checks @@ -291,6 +301,9 @@ stages: .rule_source_all: &rule_source_all - if: '$CI_PIPELINE_SOURCE =~ /^(api|merge_request_event|pipeline|schedule|trigger|web)$/ && $REBASE_ONLY != "1"' +.rule_private_security_branch: &rule_private_security_branch + - if: '$CI_COMMIT_BRANCH =~ /^security-(main|bind-9\.[1-9][0-9])$/ && $CI_PROJECT_PATH == "isc-private/bind9" && $REBASE_ONLY != "1"' + .api-pipelines-schedules-tags-triggers-web-triggering-rules: &api_pipelines_schedules_tags_triggers_web_triggering_rules rules: - *rule_tag @@ -299,6 +312,7 @@ stages: .default-triggering-rules_list: &default_triggering_rules_list - *rule_tag - *rule_source_all + - *rule_private_security_branch .default-triggering-rules: &default_triggering_rules rules: @@ -310,6 +324,7 @@ stages: - *rule_mr_manual - *rule_tag - *rule_source_other_than_mr + - *rule_private_security_branch .shell-triggering-rules: &shell_triggering_rules rules: @@ -317,6 +332,7 @@ stages: - *rule_mr_manual - *rule_tag - *rule_source_other_than_mr + - *rule_private_security_branch .python-triggering-rules: &python_triggering_rules rules: @@ -324,6 +340,7 @@ stages: - *rule_mr_manual - *rule_tag - *rule_source_other_than_mr + - *rule_private_security_branch .extra-system-tests-triggering-rules: &extra_system_tests_triggering_rules rules: @@ -712,6 +729,7 @@ clang-format: - *rule_mr_manual - *rule_tag - *rule_source_other_than_mr + - *rule_private_security_branch script: - if [ -r .clang-format ]; then "${CLANG_FORMAT}" -i -style=file $(git ls-files '*.c' '*.h'); fi - git diff > clang-format.patch @@ -851,6 +869,7 @@ coccinelle: - *rule_mr_manual - *rule_tag - *rule_source_other_than_mr + - *rule_private_security_branch script: - util/check-cocci.sh - if test "$(git status --porcelain | grep -Ev '\?\?' | wc -l)" -gt "0"; then git status --short; exit 1; fi @@ -2310,6 +2329,7 @@ stress-test-child-pipeline: allow_failure: true - *rule_tag - if: '$CI_PIPELINE_SOURCE =~ /^(api|pipeline|schedule|trigger|web)$/ && $REBASE_ONLY != "1"' + - *rule_private_security_branch trigger: include: - artifact: stress-test-configs.yml @@ -2415,16 +2435,93 @@ merged-metadata: - > "$CI_PROJECT_DIR"/bind9-qa/releng/after_merge.py "$CI_PROJECT_ID" "$MERGE_REQUEST_ID" -auto-rebase-trigger: +.autorebase-common: &autorebase_common stage: postmerge - rules: - - if: '$CI_PROJECT_NAMESPACE == "isc-projects" && $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_REF_NAME =~ /^bind-9.[0-9]+$/' needs: [] - interruptible: true + interruptible: false + +.autorebase: &autorebase + <<: *autorebase_common + <<: *base_image + # ensure autorebases for each distinct private branch are serialized when the + # upstream branch is pushed to multiple times in quick succession + resource_group: "${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}" + tags: + - smalljob + variables: + # avoid leftover branches from previous jobs + GIT_STRATEGY: clone + GIT_DEPTH: 1000 + script: + # CI job token is not sufficient for push operations + - git remote get-url origin | sed -e "s/gitlab-ci-token:${CI_JOB_TOKEN}/oauth2:${BIND_TEAM_WRITE_TOKEN}/" | xargs git remote set-url --push origin + - git remote add base-project "https://oauth2:${BIND_TEAM_API_TOKEN}@gitlab.isc.org/${BASE_PROJECT}.git" + - git fetch --depth=1000 base-project "${BASE_COMMIT}" + - git rebase --rebase-merges "${BASE_COMMIT}" + - autoreconf -fi + - *configure + - make -j${BUILD_PARALLEL_JOBS:-1} V=1 + - git range-diff --color=always "${BASE_COMMIT}" "${CI_COMMIT_SHA}" HEAD + - git push -f origin "HEAD:${CI_COMMIT_REF_NAME}" + after_script: + - if [ "${CI_JOB_STATUS}" = "success" ]; then exit 0; fi + - OLDEST_MERGE_COMMIT="$(git log --reverse --merges --pretty=%H "${CI_COMMIT_SHA}..${BASE_COMMIT}" | head -1)" + - read -r OLDEST_MERGE_REQUEST_PROJECT OLDEST_MERGE_REQUEST_ID < <(git log --max-count=1 "${OLDEST_MERGE_COMMIT}" | sed -nE 's|^\s*See merge request ([a-z-]+/bind9)!([0-9]+).*|\1 \2|p' | head -1) + - | + if git rebase --abort; then + # Rebase failed; try applying recent commits from the base branch on top of the branch being rebased to determine which one introduces conflicts + git rebase --rebase-merges "${CI_COMMIT_SHA}" "${BASE_COMMIT}" || true + CONFLICT_COMMIT="$(git status | sed -nE 's/^\s*(pick|merge -C) ([0-9a-f]+).*/\2/p' | head -1 | git rev-list -n 1 --stdin)" + REASON="merge conflict introduced by a change in the base branch" + else + # Rebase did not fail; most likely, this is a build failure, or the job was canceled + CONFLICT_COMMIT="${OLDEST_MERGE_COMMIT}" + if [ "${CI_JOB_STATUS}" = "failed" ]; then + REASON="build failure after a successful rebase" + else + REASON="job was canceled" + fi + fi + CONFLICT_COMMIT_AUTHOR="$(git log --max-count=1 --pretty="@%al" "${CONFLICT_COMMIT}")" + MSG="#### :rotating_light: Autorebase error for branch \`${CI_COMMIT_REF_NAME}\` :rotating_light:" + MSG="${MSG}\n**Job**: ${CI_JOB_URL}" + MSG="${MSG}\n**Reason**: ${REASON}" + MSG="${MSG}\n**First bad commit**: [${CONFLICT_COMMIT}](https://gitlab.isc.org/${OLDEST_MERGE_REQUEST_PROJECT}/-/commit/${CONFLICT_COMMIT}) (authored by ${CONFLICT_COMMIT_AUTHOR})" + MSG="${MSG}\n**First bad merge request**: https://gitlab.isc.org/${OLDEST_MERGE_REQUEST_PROJECT}/-/merge_requests/${OLDEST_MERGE_REQUEST_ID}" + - | + curl -s -o /dev/null -X POST -H content-type:application/json -d '{"channel":"bind-9-team", "text": "'"${MSG}"'" }' "${MATTERMOST_WEBHOOK_URL}" + +autorebase-trigger-security: + <<: *autorebase_common + rules: + - if: '$CI_PROJECT_NAMESPACE == "isc-projects" && $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_REF_NAME =~ /^(main|bind-9\.[0-9]+)$/' inherit: variables: false variables: REBASE_ONLY: 1 + BASE_PROJECT: isc-projects/bind9 + BASE_COMMIT: "${CI_COMMIT_SHA}" trigger: project: isc-private/bind9 - branch: "${CI_COMMIT_BRANCH}-sub" + branch: "security-${CI_COMMIT_BRANCH}" + +autorebase-security: + <<: *autorebase + rules: + - if: '$CI_PROJECT_NAMESPACE == "isc-private" && $CI_PIPELINE_SOURCE == "pipeline" && $CI_COMMIT_REF_NAME =~ /^security-(main|bind-9\.[0-9]+)$/ && $REBASE_ONLY == "1" && $CI_COMMIT_REF_NAME =~ $AUTOREBASED_BRANCHES' + +autorebase-trigger-sub: + <<: *autorebase_common + <<: *base_image + rules: + - if: '$CI_PROJECT_NAMESPACE == "isc-private" && $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_REF_NAME =~ /^security-bind-9\.[0-9]+$/' + tags: + - smalljob + script: + - > + curl -X POST --fail --header "Content-Type: application/json" --data '{ "token": "'"${CI_JOB_TOKEN}"'", "ref": "'"${CI_COMMIT_BRANCH#security-}"'-sub", "variables": { "REBASE_ONLY": "1", "BASE_PROJECT": "isc-private/bind9", "BASE_COMMIT": "'"${CI_COMMIT_SHA}"'" } }' https://gitlab.isc.org/api/v4/projects/9/trigger/pipeline + +autorebase-sub: + <<: *autorebase + rules: + - if: '$CI_PROJECT_NAMESPACE == "isc-private" && $CI_PIPELINE_SOURCE == "pipeline" && $CI_COMMIT_REF_NAME =~ /^bind-9\.[0-9]+-sub$/ && $REBASE_ONLY == "1" && $CI_COMMIT_REF_NAME =~ $AUTOREBASED_BRANCHES'