From 35a71b0d79ecf18616e5d33b3d71b6380eb90f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20K=C4=99pie=C5=84?= Date: Thu, 21 May 2026 11:13:30 +0200 Subject: [PATCH 1/6] Rewrite cherry-pick references during autorebases Use a custom rebasing script instead of "git rebase" to enable rewriting cherry-pick references during autorebases. (cherry picked from commit 98c3f339bf9271b3ef7d79aef30d25a6a26e3c92) --- .gitlab-ci.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 885ce93ee2..ba93f5c664 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2723,9 +2723,11 @@ merged-metadata: 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}" + - git remote add base-project "${CI_SERVER_URL}/${BASE_PROJECT}.git" + - git fetch --depth="${GIT_DEPTH}" base-project "${BASE_COMMIT}" + - *git_clone_bind9-qa + - > + "$CI_PROJECT_DIR"/bind9-qa/releng/rebase.py ${REWRITE_CHERRY_PICKS_FROM:+--rewrite-cherry-picks-from ${REWRITE_CHERRY_PICKS_FROM}} --base-project "${BASE_PROJECT}" "${BASE_COMMIT}" - autoreconf -fi - *configure - make -j${BUILD_PARALLEL_JOBS:-1} V=1 @@ -2773,6 +2775,7 @@ autorebase-trigger-security: REBASE_ONLY: 1 BASE_PROJECT: isc-projects/bind9 BASE_COMMIT: "${CI_COMMIT_SHA}" + REWRITE_CHERRY_PICKS_FROM: security-main trigger: project: isc-private/bind9 branch: "security-${CI_COMMIT_BRANCH}" From ae089a86f1a9910a44d172b8f46a73b663c9a8ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20K=C4=99pie=C5=84?= Date: Thu, 21 May 2026 11:13:30 +0200 Subject: [PATCH 2/6] Fix autorebase error reporting The logic used for detecting the commit breaking an autorebase does not work correctly if the offending commit is not the first one applied during the "reverse rebase". Fix by using REBASE_HEAD instead of processing the output of "git status" in a convoluted way. Furthermore, the approach used for identifying the first offending merge request in the case of a successful autorebase followed by a failed build only works correctly if the base branch is not autorebased itself. Since a solution that would work correctly for a branch autorebased on top of a branch that only moves forward does not work correctly for a branch autorebased on top of another autorebased branch and vice versa, accurately identifying the most likely culprit after a successful autorebase is a very complicated and brittle task. Since reporting no details at all is arguably better than reporting false details, only produce a minimal error notification if the build fails after a successful autorebase. (cherry picked from commit 4c0e93108e936569c0a2e92bdfd65edff0f82c65) --- .gitlab-ci.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ba93f5c664..f3d502240c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2735,17 +2735,20 @@ merged-metadata: - if ! git push --force-with-lease origin "HEAD:${CI_COMMIT_REF_NAME}"; then touch .git-push-failed; exit 1; fi 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) - | + REASON_DETAILS="" 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)" + CONFLICT_COMMIT="$(git rev-parse REBASE_HEAD)" + CONFLICT_COMMIT_AUTHOR="$(git log --max-count=1 --pretty="@%al" "${CONFLICT_COMMIT}")" + CONFLICT_COMMIT_MERGE="$(git log --reverse --merges --pretty="%H" "${CONFLICT_COMMIT}..${BASE_COMMIT}" | head -1)" + read -r CONFLICT_COMMIT_MERGE_REQUEST_PROJECT CONFLICT_COMMIT_MERGE_REQUEST_ID < <(git log --max-count=1 "${CONFLICT_COMMIT_MERGE}" | sed -n -E 's|^\s*See merge request ([a-z-]+/bind9)!([0-9]+).*|\1 \2|p' | head -1) REASON="merge conflict introduced by a change in the base branch" + REASON_DETAILS="${REASON_DETAILS}\n**First bad commit**: [${CONFLICT_COMMIT}](https://gitlab.isc.org/${CONFLICT_COMMIT_MERGE_REQUEST_PROJECT}/-/commit/${CONFLICT_COMMIT}) (authored by ${CONFLICT_COMMIT_AUTHOR})" + REASON_DETAILS="${REASON_DETAILS}\n**First bad merge request**: https://gitlab.isc.org/${CONFLICT_COMMIT_MERGE_REQUEST_PROJECT}/-/merge_requests/${CONFLICT_COMMIT_MERGE_REQUEST_ID}" 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 if [ -f ".git-push-failed" ]; then REASON="branch was updated during rebase" @@ -2756,12 +2759,10 @@ merged-metadata: 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}" + MSG="${MSG}${REASON_DETAILS}" - | curl -s -o /dev/null -X POST -H content-type:application/json -d '{"channel":"bind-9-team", "text": "'"${MSG}"'" }' "${MATTERMOST_WEBHOOK_URL}" From 432408b72cd6ad15d0e8ff389b8c137f129bbb4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20K=C4=99pie=C5=84?= Date: Thu, 21 May 2026 11:13:30 +0200 Subject: [PATCH 3/6] Limit post-push pipelines for autorebased branches Current CI job triggering rules cause a full pipeline to be started after every push to security-* branches. In this context, "push" means "branch update", which covers both "git push" invocations and merging a merge request. Meanwhile, running a test pipeline is only desired after a rebase; if a branch is fast-forwarded, it means that a merge request has been merged into it and a pipeline should have already been run for that merge request itself. Limit resource use by only triggering pipelines for security-* branches when they are pushed to with a "magic" CI variable that is only set in autorebase jobs. Leave all the other triggering rules (for scheduled/manual pipelines) intact. (cherry picked from commit 5cd870053ef2df8f45e9f3fd7b203dbd1af2daad) --- .gitlab-ci.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f3d502240c..69e242df24 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -340,8 +340,8 @@ 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"' +.rule_branch_after_autorebase: &rule_branch_after_autorebase + - if: '$CI_PIPELINE_SOURCE == "push" && $AUTOREBASED == "1"' .api-pipelines-schedules-tags-triggers-web-triggering-rules: &api_pipelines_schedules_tags_triggers_web_triggering_rules rules: @@ -351,7 +351,7 @@ stages: .default-triggering-rules_list: &default_triggering_rules_list - *rule_tag - *rule_source_all - - *rule_private_security_branch + - *rule_branch_after_autorebase .default-triggering-rules: &default_triggering_rules rules: @@ -363,7 +363,7 @@ stages: - *rule_mr_manual - *rule_tag - *rule_source_other_than_mr - - *rule_private_security_branch + - *rule_branch_after_autorebase .shell-triggering-rules: &shell_triggering_rules rules: @@ -371,7 +371,7 @@ stages: - *rule_mr_manual - *rule_tag - *rule_source_other_than_mr - - *rule_private_security_branch + - *rule_branch_after_autorebase .python-triggering-rules: &python_triggering_rules rules: @@ -379,7 +379,7 @@ stages: - *rule_mr_manual - *rule_tag - *rule_source_other_than_mr - - *rule_private_security_branch + - *rule_branch_after_autorebase .extra-system-tests-triggering-rules: &extra_system_tests_triggering_rules rules: @@ -789,7 +789,7 @@ clang-format: - *rule_mr_manual - *rule_tag - *rule_source_other_than_mr - - *rule_private_security_branch + - *rule_branch_after_autorebase script: - if [ -r .clang-format ]; then "${CLANG_FORMAT}" -i -style=file $(git ls-files '*.c' '*.h'); fi - git diff > clang-format.patch @@ -931,7 +931,7 @@ coccinelle: - *rule_mr_manual - *rule_tag - *rule_source_other_than_mr - - *rule_private_security_branch + - *rule_branch_after_autorebase script: - util/check-cocci.sh - if test "$(git status --porcelain | grep -Ev '\?\?' | wc -l)" -gt "0"; then git status --short; exit 1; fi @@ -2598,7 +2598,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 + - *rule_branch_after_autorebase trigger: include: - artifact: stress-test-configs.yml @@ -2732,7 +2732,7 @@ merged-metadata: - *configure - make -j${BUILD_PARALLEL_JOBS:-1} V=1 - git range-diff --color=always "${BASE_COMMIT}" "${CI_COMMIT_SHA}" HEAD - - if ! git push --force-with-lease origin "HEAD:${CI_COMMIT_REF_NAME}"; then touch .git-push-failed; exit 1; fi + - if ! git push --force-with-lease -o ci.variable="AUTOREBASED=1" origin "HEAD:${CI_COMMIT_REF_NAME}"; then touch .git-push-failed; exit 1; fi after_script: - if [ "${CI_JOB_STATUS}" = "success" ]; then exit 0; fi - | From 3265127e6dfe95a19b82e589b7e302541b7272f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20K=C4=99pie=C5=84?= Date: Thu, 21 May 2026 11:13:30 +0200 Subject: [PATCH 4/6] Only autorebase when there is anything to rebase In an optimistic future, security-* branches will become empty, at least intermittently. When that happens, there will be nothing left to rebase on those branches, so when something gets merged into their base branches, an autorebase will effectively be a fast-forward. While the existing autorebase logic would handle such a case perfectly fine, it is prudent to avoid creating a test pipeline after pushing such a fast-forward update as the code revision getting pushed will have already been tested by other pipelines. However, the push should still happen as non-empty downstream autorebased branches may exist and those will still need to be rebased. Achieve both of these objectives by checking early whether there is anything to rebase and pushing the fast-forwarded version of the branch without setting the AUTOREBASE CI variable if there is not. (cherry picked from commit 497f771ae32c9183d02b66d3eafd53839799d016) --- .gitlab-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 69e242df24..d64a1323fa 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2725,6 +2725,9 @@ merged-metadata: - 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 "${CI_SERVER_URL}/${BASE_PROJECT}.git" - git fetch --depth="${GIT_DEPTH}" base-project "${BASE_COMMIT}" + # If this branch has no changes compared to its base branch yet, there is nothing to rebase; + # push the new revision anyway to trigger any potential downstream autorebases. + - if [ -z "$(git log --max-count=1 "${BASE_COMMIT}..")" ]; then git push origin "${BASE_COMMIT}:${CI_COMMIT_REF_NAME}"; exit 0; fi - *git_clone_bind9-qa - > "$CI_PROJECT_DIR"/bind9-qa/releng/rebase.py ${REWRITE_CHERRY_PICKS_FROM:+--rewrite-cherry-picks-from ${REWRITE_CHERRY_PICKS_FROM}} --base-project "${BASE_PROJECT}" "${BASE_COMMIT}" From ab2aa2f4cc6bffe6824b93f31194b10301608558 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20K=C4=99pie=C5=84?= Date: Thu, 21 May 2026 11:13:30 +0200 Subject: [PATCH 5/6] Conflate missing commit reference notifications Instead of creating a separate (potentially lengthy) Danger notification for every missing commit reference in a backport, produce a single notification with a list of all unreferenced commit hashes. This makes Danger output more concise while retaining all the relevant feedback for the developer. (cherry picked from commit 086780dcf08ce1279dee6d30c380e6c0446b17af) --- dangerfile.py | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/dangerfile.py b/dangerfile.py index b9dea11273..837e169b64 100644 --- a/dangerfile.py +++ b/dangerfile.py @@ -281,25 +281,28 @@ if is_backport: else: # check for commit IDs once original MR is merged original_mr_commits = list(original_mr.commits(all=True)) backport_mr_commits = list(mr.commits(all=True)) - for orig_commit in original_mr_commits: - for backport_commit in backport_mr_commits: - if orig_commit.id in backport_commit.message: - break + missing_commits = [] + for orig_id in (o.id for o in original_mr_commits): + if not any(b for b in backport_mr_commits if orig_id in b.message): + missing_commits.append(orig_id) + if missing_commits: + msg = ( + f"The following commits from original MR !{original_mr_id} " + "are not referenced in any of the backport commits:" + ) + msg += "
    " + msg += "".join(f"
  • {orig_id}
  • " for orig_id in missing_commits) + msg += "
" + if not is_full_backport: + message(msg) else: - msg = ( - f"Commit {orig_commit.id} from original MR !{original_mr_id} " - "is not referenced in any of the backport commits." + msg += ( + "Please use `-x` when cherry-picking to include " + "the full original commit ID. Alternatively, use the " + "`Backport::Partial` label if not all original " + "commits are meant to be backported." ) - if not is_full_backport: - message(msg) - else: - msg += ( - " Please use `-x` when cherry-picking to include " - "the full original commit ID. Alternately, use the " - "`Backport::Partial` label if not all original " - "commits are meant to be backported." - ) - fail(msg) + fail(msg) else: if not version_labels: fail( From 0b63ecd2bb4f1d4e01b746af02ba3b62823a7519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20K=C4=99pie=C5=84?= Date: Thu, 21 May 2026 11:13:30 +0200 Subject: [PATCH 6/6] Support autorebasing backported security MRs Autorebasing a backported security fix enables convenient refreshing of cherry-pick references, which makes it trivial for developers to satisfy Danger rules just before the merge request is merged. Add a manual CI job that is only created for backported merge requests targeting security-* branches. (cherry picked from commit dd723d93cbebff4e10d8837645229fb9497fb197) --- .gitlab-ci.yml | 16 +++++++++++++++- dangerfile.py | 2 ++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d64a1323fa..fc246850d9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2737,7 +2737,7 @@ merged-metadata: - git range-diff --color=always "${BASE_COMMIT}" "${CI_COMMIT_SHA}" HEAD - if ! git push --force-with-lease -o ci.variable="AUTOREBASED=1" origin "HEAD:${CI_COMMIT_REF_NAME}"; then touch .git-push-failed; exit 1; fi after_script: - - if [ "${CI_JOB_STATUS}" = "success" ]; then exit 0; fi + - if [ "${CI_JOB_STATUS}" = "success" ] || [ "${CI_PIPELINE_SOURCE}" = "merge_request_event" ]; then exit 0; fi - | REASON_DETAILS="" if git rebase --abort; then @@ -2769,6 +2769,20 @@ merged-metadata: - | curl -s -o /dev/null -X POST -H content-type:application/json -d '{"channel":"bind-9-team", "text": "'"${MSG}"'" }' "${MATTERMOST_WEBHOOK_URL}" +autorebase-merge-request: + <<: *autorebase + stage: quick-checks + resource_group: null + before_script: + - git fetch --depth="${GIT_DEPTH}" origin "${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME}" "${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}" + - export BASE_PROJECT="isc-private/bind9" + - export BASE_COMMIT="$(git rev-parse "origin/${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}")" + - export REWRITE_CHERRY_PICKS_FROM="security-main" + rules: + - if: '$CI_PROJECT_NAMESPACE == "isc-private" && $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME =~ /^security-(bind-9\.[0-9]+)$/' + when: manual + allow_failure: true + autorebase-trigger-security: <<: *autorebase_common rules: diff --git a/dangerfile.py b/dangerfile.py index 837e169b64..2f0feed27f 100644 --- a/dangerfile.py +++ b/dangerfile.py @@ -296,6 +296,8 @@ if is_backport: if not is_full_backport: message(msg) else: + if target_branch.startswith("security-"): + msg += ":bulb: Try running the `autorebase-merge-request` job. " msg += ( "Please use `-x` when cherry-picking to include " "the full original commit ID. Alternatively, use the "