Skip to content

BERTE-612: detect cross-branch contamination before merge - #279

Merged
matthiasL-scality merged 4 commits into
mainfrom
BERTE-612/check-source-branch-lineage
Aug 26, 2026
Merged

BERTE-612: detect cross-branch contamination before merge#279
matthiasL-scality merged 4 commits into
mainfrom
BERTE-612/check-source-branch-lineage

Conversation

@matthiasL-scality

Copy link
Copy Markdown
Contributor

Summary

  • Add check_source_branch_lineage() in gitwaterflow/__init__.py, called before check_integration_branches in _handle_pull_request()
  • Raises new ForeignCommitsInSourceBranch exception (code 137) when a source branch carries commits from a higher release line that are absent from the target branch
  • Add message template foreign_commits_in_source_branch.md to notify the author to rebase on the target branch directly

Context

A silent git fast-forward on development/4.3 introduced 635 commits from development/4 via ARTESCA-17922. The root cause: the feature branch had been rebased on a bert-e integration commit (w/4) instead of directly on development/4.3. From git's perspective the fast-forward was legal — no conflicts, no merge commit, no warning.

How the check works

For each development branch in the cascade that is higher than dst, compute git merge-base(src, higher). If that commit is not an ancestor of dst, the source branch shares history with a release line that dst doesn't know about → block the merge.

Cost: 2 git commands per higher branch in the cascade — negligible.

Test plan

  • test_no_higher_branches — single branch in cascade, no error
  • test_higher_branch_but_merge_base_in_dst — common ancestor already in dst, no error
  • test_merge_base_command_raises — git command failure is silently skipped
  • test_raises_when_merge_base_not_in_dst — contamination detected, exception raised
  • test_error_contains_branch_names — exception kwargs carry src, dst, foreign branch names
  • flake8 clean

🤖 Generated with Claude Code

@matthiasL-scality
matthiasL-scality requested a review from a team as a code owner August 24, 2026 11:28
Comment thread bert_e/workflow/gitwaterflow/__init__.py Outdated
@claude

claude Bot commented Aug 24, 2026

Copy link
Copy Markdown
  • Silent except Exception: continue in check_source_branch_lineage() swallows errors without logging, making failures invisible to operators
    • Add LOG.debug("merge-base(%s, %s) failed, skipping", src.name, higher.name, exc_info=True) before continue

      Review by Claude Code

@claude

claude Bot commented Aug 24, 2026

Copy link
Copy Markdown

LGTM

Review by Claude Code

@codecov

codecov Bot commented Aug 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.56332% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 90.21%. Comparing base (a6e95a1) to head (97be606).

Files with missing lines Patch % Lines
bert_e/workflow/gitwaterflow/__init__.py 97.82% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #279      +/-   ##
==========================================
+ Coverage   90.02%   90.21%   +0.19%     
==========================================
  Files          81       82       +1     
  Lines       11064    11293     +229     
==========================================
+ Hits         9960    10188     +228     
- Misses       1104     1105       +1     
Flag Coverage Δ
integration 87.68% <71.69%> (-0.10%) ⬇️
tests 87.64% <71.69%> (-0.10%) ⬇️
tests-BuildFailedTest 25.71% <13.20%> (-0.08%) ⬇️
tests-QuickTest 33.02% <13.20%> (-0.13%) ⬇️
tests-RepositoryTests 25.39% <13.20%> (-0.08%) ⬇️
tests-TaskQueueTests 49.60% <69.81%> (+0.12%) ⬆️
tests-TestBertE 66.97% <71.69%> (+0.02%) ⬆️
tests-TestQueueing 51.69% <67.92%> (+0.09%) ⬆️
tests-api-mock 14.44% <1.74%> (-0.27%) ⬇️
tests-noqueue 78.38% <71.69%> (-0.05%) ⬇️
tests-noqueue-BuildFailedTest 25.71% <13.20%> (-0.08%) ⬇️
tests-noqueue-QuickTest 33.02% <13.20%> (-0.13%) ⬇️
tests-noqueue-RepositoryTests 25.39% <13.20%> (-0.08%) ⬇️
tests-noqueue-TaskQueueTests 49.60% <69.81%> (+0.12%) ⬆️
tests-noqueue-TestBertE 63.55% <71.69%> (+0.04%) ⬆️
tests-noqueue-TestQueueing 25.41% <13.20%> (-0.08%) ⬇️
tests-server 26.72% <3.05%> (-0.50%) ⬇️
unittests 43.41% <98.68%> (+1.14%) ⬆️
utests 29.09% <98.68%> (+1.44%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread bert_e/workflow/gitwaterflow/__init__.py Outdated
@claude

claude Bot commented Aug 24, 2026

Copy link
Copy Markdown
  • Bare except Exception at line 424 of bert_e/workflow/gitwaterflow/__init__.py silently swallows all errors including programming bugs. Should catch CommandError specifically, matching the pattern used by includes_commit in bert_e/lib/git.py.
    - Import CommandError from bert_e.lib.simplecmd and narrow the except clause

    The algorithm, placement in the workflow, backport detection, and test coverage all look solid.

    Review by Claude Code

Add check_source_branch_lineage(), called before jira_checks in
_handle_pull_request, that detects when a source branch shares history
with a higher release line that has not yet been cascaded into the target
branch.

The root cause this addresses: if a feature branch is rebased on a
Bert-E integration commit from a higher release line (e.g. w/4) rather
than directly on the target branch (e.g. development/4.3), git will
silently fast-forward the target into the higher line on merge.

Detection mechanism: for each branch in cascade.dst_branches that is
higher than the target, compute git merge-base(src, higher). If that
common ancestor is not already in the target branch's history, the source
branch carries foreign commits and ForeignCommitsInSourceBranch (code 137)
is raised with a clear user message.

Backport guard: if src's current tip is already an ancestor of a higher
branch (legitimate backport), that branch is skipped to avoid false
positives.

Known limitations documented in the function docstring:
- No-op for hotfix PR targets (cascade.dst_branches contains only the
  single hotfix branch, so the loop always exits immediately)
- Extended-backport false positive: if a branch was previously merged
  into a higher release and then extended with new commits, the backport
  guard misses it and a false ForeignCommitsInSourceBranch may be raised

Robustness:
- src.get_latest_commit() failure → skip entire check (fail open)
- higher.get_latest_commit() failure → skip that branch (fail open)
- git merge-base failure → skip that branch (fail open)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@matthiasL-scality
matthiasL-scality force-pushed the BERTE-612/check-source-branch-lineage branch from 3b7160a to 8a19a9c Compare August 24, 2026 16:08
@claude

claude Bot commented Aug 24, 2026

Copy link
Copy Markdown

LGTM

Review by Claude Code

@ezekiel-alexrod ezekiel-alexrod left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since I'm not so familiar with the repo, here are my two cents.

Comment thread bert_e/workflow/gitwaterflow/__init__.py Outdated
Comment thread bert_e/tests/unit/conftest.py
Comment thread bert_e/workflow/gitwaterflow/__init__.py Outdated
Comment thread bert_e/workflow/gitwaterflow/__init__.py
- Add `bypass_source_branch_lineage` privileged option (utils.py, commands.py,
  settings.py BYPASS_LIST) so administrators can unblock false positives without
  manual intervention; mention it in the error template
- Collect all contaminated branches before raising (single PR comment listing
  every foreign branch instead of one per bert-e run)
- Pass pre-resolved SHAs to git merge-base for consistency with the backport
  guard snapshot
- Fix backport guard false negative: add `src_sha != higher_tip` pre-condition
  so a branch created directly from a higher release line is not silently
  let through (git --is-ancestor is reflexive)
- Add `has_cascade_higher` / `had_higher` flags for accurate no-op diagnostics
  (hotfix target vs all branches errored)
- Move `had_higher = True` after get_latest_commit() so transient failures on
  all higher branches trigger the 'check was not performed' log path
- Add defensive `if not merge_base: continue` guard; add explicit comment on
  the merge_base == higher_tip optimisation and its dependency on the prior guard
- Upgrade USER_DOC.md: options table, check description with bypass note, error
  code 137
- README: add 'Contributing a new check' checklist making bypass a hard
  requirement for any new blocking check
- Tests: update _make_job with settings/author_bypass stubs; update all
  merge_base_map keys from branch names to SHA values (implementation now passes
  SHAs); add tests for bypass, both-branches-contaminated, and the
  src_sha == higher_tip case; remove empty untracked test_compare_branches.py

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@claude

claude Bot commented Aug 25, 2026

Copy link
Copy Markdown

LGTM

The new check is well-structured: bypass option follows the existing pattern (settings, commands, utils), the algorithm correctly detects contamination via merge-base while handling edge cases (backport guard, src==higher_tip, transient git failures), the template is clear and actionable, and the test suite is comprehensive (clean, contaminated, backport, bypass, and known-limitation scenarios all covered). No security, correctness, or compatibility issues found.

Review by Claude Code

…ERTE-612)

Without --all, git picks a single best common ancestor. In a criss-cross merge
graph two incomparable ancestors can exist; git might return the one already in
dst, silently passing a contaminated PR. --all returns every merge-base, and the
check now flags contamination if any of them falls outside dst.

Also updates openwiki/architecture/gitwaterflow.md with the new pipeline step
and adds two unit tests for the multi-merge-base code path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@claude

claude Bot commented Aug 25, 2026

Copy link
Copy Markdown

LGTM

The cross-branch contamination check is well implemented. The algorithm correctly uses git merge-base --all to handle criss-cross merges, the backport guard (src_sha != higher_tip) properly avoids false positives on legitimate backports, and the known limitation (extended backports) is documented and tested. The bypass follows the established pattern, command injection is prevented by shlex.quote in repo.cmd, and test coverage is thorough (clean, contaminated, backport, multiple merge-bases, bypass, and error-path scenarios).

Review by Claude Code

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@claude

claude Bot commented Aug 25, 2026

Copy link
Copy Markdown

LGTM

Review by Claude Code

@matthiasL-scality
matthiasL-scality merged commit cfe0bc2 into main Aug 26, 2026
19 checks passed
@matthiasL-scality
matthiasL-scality deleted the BERTE-612/check-source-branch-lineage branch August 26, 2026 13:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants