Skip to content

fix: replace git branch -a regex with rev-parse in MergeBaseValidator - #451

Merged
shenxianpeng merged 1 commit into
mainfrom
bugfix/find-target-branch-use-rev-parse
Jul 2, 2026
Merged

fix: replace git branch -a regex with rev-parse in MergeBaseValidator#451
shenxianpeng merged 1 commit into
mainfrom
bugfix/find-target-branch-use-rev-parse

Conversation

@shenxianpeng

@shenxianpeng shenxianpeng commented Jul 2, 2026

Copy link
Copy Markdown
Member

Problem

MergeBaseValidator._find_target_branch used git branch -a + re.match() to locate the target branch. This approach is fragile because a loose regex like main could match main-old, main-staging, origin/main, etc. — whichever appeared first in git branch -a output (which is non-deterministic). This class of bug makes merge_base validation unreliable in CI.

Solution

Replace the regex-based branch list scan with precise ref resolution using git rev-parse --verify:

  1. Strip common regex anchors (^, $) from the pattern to get a clean branch name
  2. Try git rev-parse --verify refs/heads/<branch> (local branch, no ambiguity with tags)
  3. Fall back to git rev-parse --verify refs/remotes/origin/<branch> (remote tracking branch)

Using the full refs/heads/ and refs/remotes/origin/ paths eliminates any possibility of resolving to a tag or other non-branch ref — matching the old behavior which only scanned git branch -a (branches only).

Regression Analysis

All edge cases verified against old behavior:

Scenario Old behavior New behavior Regression?
regex = "^main$", branch main exists Matches main rev-parse refs/heads/main → found ✅ Match
regex = "main", branches: main, main-old May match main-old first (non-deterministic) Only refs/heads/main matches ✅ Fix
regex = "develop", only remote origin/develop Stripped from remotes/origin/develop rev-parse refs/remotes/origin/develop → found ✅ Match
Branch does not exist Returns None Returns None ✅ Match
Tag main exists, no branch main git branch -a → not found → None refs/heads/main → not found → None ✅ Match (was a risk without refs/heads/)
Complex regex like ^(main|develop)$ Regex match Would fail, but not a documented/configurable use case ⚠️ Acceptable
Remote not origin (e.g. upstream) Old code also only handled origin/ Same ✅ Consistent

Test Plan

  • All existing tests pass (366 tests)
  • Added 5 new unit tests covering:
    • Local branch found (^main$ → resolves to main via refs/heads/main)
    • Local missing, remote tracking found (develop → resolved via refs/remotes/origin/develop)
    • Neither local nor remote exists (returns None)
    • Empty/anchor-only pattern (returns None without calling subprocess)
    • Plain branch name without regex anchors works correctly

@shenxianpeng
shenxianpeng requested a review from a team as a code owner July 2, 2026 19:39
@netlify

netlify Bot commented Jul 2, 2026

Copy link
Copy Markdown

Deploy Preview for commit-check ready!

Name Link
🔨 Latest commit be6a2c3
🔍 Latest deploy log https://app.netlify.com/projects/commit-check/deploys/6a46c4780c7cb1000800b606
😎 Deploy Preview https://deploy-preview-451--commit-check.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions github-actions Bot added bug Something isn't working tests Add test related changes labels Jul 2, 2026
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@shenxianpeng, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 34 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4711a9fa-541f-48f0-9f67-ac50ae1e1b5a

📥 Commits

Reviewing files that changed from the base of the PR and between 4735bee and be6a2c3.

📒 Files selected for processing (2)
  • commit_check/engine.py
  • tests/engine_test.py
📝 Walkthrough

Walkthrough

The _find_target_branch method in MergeBaseValidator is refactored to resolve target branches using git rev-parse --verify against normalized branch names, checking local refs first and then origin/<branch> remote refs, replacing prior git branch -a regex parsing. Corresponding unit tests are added.

Changes

Target branch resolution refactor

Layer / File(s) Summary
Pattern normalization and rev-parse verification
commit_check/engine.py
Strips ^/$ anchors from the rule pattern, returns None on empty result, and verifies local then origin/<branch> refs via git rev-parse --verify instead of scanning git branch -a output.
Unit tests for branch resolution
tests/engine_test.py
Adds subprocess import and tests mocking subprocess.run to cover local match, remote fallback, no match, empty pattern (no subprocess call), and expected rev-parse --verify arguments.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant Validator as MergeBaseValidator
  participant Git as subprocess (git rev-parse --verify)

  Caller->>Validator: _find_target_branch(pattern)
  Validator->>Validator: strip ^/$ anchors
  alt branch_name empty
    Validator-->>Caller: None
  else branch_name present
    Validator->>Git: rev-parse --verify branch_name
    alt local verify succeeds
      Git-->>Validator: success
      Validator-->>Caller: branch_name
    else local verify fails
      Validator->>Git: rev-parse --verify origin/branch_name
      alt remote verify succeeds
        Git-->>Validator: success
        Validator-->>Caller: branch_name
      else remote verify fails
        Git-->>Validator: failure
        Validator-->>Caller: None
      end
    end
  end
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: replacing regex-based git branch lookup with rev-parse in MergeBaseValidator.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bugfix/find-target-branch-use-rev-parse

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.89%. Comparing base (ac1e9a9) to head (be6a2c3).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #451      +/-   ##
==========================================
+ Coverage   96.60%   96.89%   +0.28%     
==========================================
  Files          10       10              
  Lines        1090     1094       +4     
==========================================
+ Hits         1053     1060       +7     
+ Misses         37       34       -3     

☔ 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
commit_check/engine.py (2)

463-484: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Deduplicate ref verification and broaden exception handling.

The local and remote checks are identical except for the ref string, and both only catch subprocess.CalledProcessError, so a missing git executable (FileNotFoundError/OSError) would propagate unhandled instead of falling through to return None like the rest of this method does.

♻️ Proposed refactor to consolidate the duplicated verification logic
-        # Try local branch first
-        try:
-            subprocess.run(
-                ["git", "rev-parse", "--verify", branch_name],
-                stdout=subprocess.DEVNULL,
-                stderr=subprocess.DEVNULL,
-                check=True,
-            )
-            return branch_name
-        except subprocess.CalledProcessError:
-            pass
-
-        # Try remote tracking branch under origin/
-        try:
-            subprocess.run(
-                ["git", "rev-parse", "--verify", f"origin/{branch_name}"],
-                stdout=subprocess.DEVNULL,
-                stderr=subprocess.DEVNULL,
-                check=True,
-            )
-            return branch_name
-        except subprocess.CalledProcessError:
-            pass
-
-        return None
+        def _ref_exists(ref: str) -> bool:
+            try:
+                subprocess.run(
+                    ["git", "rev-parse", "--verify", ref],
+                    stdout=subprocess.DEVNULL,
+                    stderr=subprocess.DEVNULL,
+                    check=True,
+                )
+                return True
+            except (subprocess.CalledProcessError, OSError):
+                return False
+
+        if _ref_exists(branch_name) or _ref_exists(f"origin/{branch_name}"):
+            return branch_name
+
+        return None
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@commit_check/engine.py` around lines 463 - 484, The ref verification in the
branch lookup method is duplicated and only handles
subprocess.CalledProcessError, so missing git or other OS-level failures can
escape instead of falling through cleanly. Refactor the local and origin/ checks
in the branch-resolution logic to share a single verification path keyed by the
ref string, and broaden the exception handling around subprocess.run in this
method to also catch FileNotFoundError/OSError so it still returns None on any
verification failure.

455-456: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Move subprocess/re imports to module top-level.

Local import subprocess / import re inside the method is non-idiomatic; both are cheap, stdlib, and unconditionally needed, so hoist them to the top of the file for consistency with the rest of the module.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@commit_check/engine.py` around lines 455 - 456, Move the local subprocess and
re imports out of the method and into the module-level import block in
engine.py; these stdlib imports are used unconditionally, so update the
top-of-file imports and remove the in-method import statements from the code
path around the affected function.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@commit_check/engine.py`:
- Around line 463-484: The ref verification in the branch lookup method is
duplicated and only handles subprocess.CalledProcessError, so missing git or
other OS-level failures can escape instead of falling through cleanly. Refactor
the local and origin/ checks in the branch-resolution logic to share a single
verification path keyed by the ref string, and broaden the exception handling
around subprocess.run in this method to also catch FileNotFoundError/OSError so
it still returns None on any verification failure.
- Around line 455-456: Move the local subprocess and re imports out of the
method and into the module-level import block in engine.py; these stdlib imports
are used unconditionally, so update the top-of-file imports and remove the
in-method import statements from the code path around the affected function.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 29e5e2c2-0bbd-47b2-8b8c-439c762ea853

📥 Commits

Reviewing files that changed from the base of the PR and between ac1e9a9 and 4735bee.

📒 Files selected for processing (2)
  • commit_check/engine.py
  • tests/engine_test.py

@codspeed-hq

codspeed-hq Bot commented Jul 2, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 312 untouched benchmarks
⏩ 109 skipped benchmarks1


Comparing bugfix/find-target-branch-use-rev-parse (be6a2c3) with main (ac1e9a9)

Open in CodSpeed

Footnotes

  1. 109 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@shenxianpeng shenxianpeng removed the tests Add test related changes label Jul 2, 2026
@shenxianpeng
shenxianpeng force-pushed the bugfix/find-target-branch-use-rev-parse branch from 4735bee to 1b08d21 Compare July 2, 2026 20:01
@github-actions github-actions Bot added the tests Add test related changes label Jul 2, 2026
@shenxianpeng
shenxianpeng force-pushed the bugfix/find-target-branch-use-rev-parse branch from 1b08d21 to a13dc66 Compare July 2, 2026 20:03
The old _find_target_branch method scanned all branches via git branch -a
and used a loose regex match, which could cause false positives: a
pattern like 'main' could match 'main-old', 'main-staging', etc.
depending on the order git branch -a outputs branches.

The new approach:
1. Strips common regex anchors (^, $) from the pattern to get a clean name
2. Uses git rev-parse --verify <name> for exact local ref resolution
3. Falls back to git rev-parse --verify origin/<name> for remote tracking

This makes require_rebase_target safe to recommend to users.
@shenxianpeng
shenxianpeng force-pushed the bugfix/find-target-branch-use-rev-parse branch from a13dc66 to be6a2c3 Compare July 2, 2026 20:05
@sonarqubecloud

sonarqubecloud Bot commented Jul 2, 2026

Copy link
Copy Markdown

Quality Gate Passed Quality Gate passed

Issues
0 New issues
1 Accepted issue

Measures
0 Security Hotspots
No data about Coverage
0.0% Duplication on New Code

See analysis details on SonarQube Cloud

@shenxianpeng shenxianpeng removed the tests Add test related changes label Jul 2, 2026
@shenxianpeng
shenxianpeng merged commit 72ab7d3 into main Jul 2, 2026
33 checks passed
@shenxianpeng
shenxianpeng deleted the bugfix/find-target-branch-use-rev-parse branch July 2, 2026 20:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant