Skip to content

Conversation

@solababs
Copy link
Contributor

@solababs solababs commented Oct 27, 2025

This PR updates the git sync to check the new configuration import_sync_branch_names and if it is present it matches the branch name with the names in the config and skips adding it as a remote branch if it does not match with exception to allow

  • branch name that == default branch
  • branch with sync_with_git == True
  • branch that is_default == True

Summary by CodeRabbit

  • New Features

    • Added branch filtering for remote sync: repository sync now respects configurable import-sync patterns, so only matching branches are considered while others are skipped during synchronization.
  • Tests

    • Added tests and fixtures covering various branch-filtering scenarios to ensure correct behavior with and without import-sync patterns.

This PR adds `sync_branch_names` parameter to git settings so that users can add branches that they want to be synced with git
instead of syncing all branches like we currently do.
This parameter can also be a regex so that we can match branch names when syncing
This PR updates the git sync to check the new configuration `sync_branch_names` and if it is present
it matches the branch name with the names in the config skips adding it as a remote branch if it does not match.
@solababs solababs requested a review from a team as a code owner October 27, 2025 23:08
@github-actions github-actions bot added the group/backend Issue related to the backend (API Server, Git Agent) label Oct 27, 2025
@codspeed-hq
Copy link

codspeed-hq bot commented Oct 27, 2025

CodSpeed Performance Report

Merging #7486 will not alter performance

Comparing sb-20251027-update-git-sync-ifc-1984 (0f28898) with develop (7989c6c)

Summary

✅ 10 untouched

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 27, 2025

Walkthrough

A new async method get_filtered_remote_branches() was added to InfrahubRepositoryBase to retrieve and filter remote branches using configured import sync patterns. A utility function branch_name_in_import_sync_branches() was introduced to match branch short names against exact or regex filters from settings. Tests and fixtures were added to set config.SETTINGS.git.import_sync_branch_names and to create branch data; three async tests validate filtering behavior across different repository setups. A changelog entry documents the added branch-filtering capability.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: updating git sync logic to consider a new configuration (import_sync_branch_names). It directly relates to the primary modification in the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch sb-20251027-update-git-sync-ifc-1984

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0f4559d and 0f28898.

📒 Files selected for processing (1)
  • changelog/+66125521.added.md (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • changelog/+66125521.added.md
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: json-schema
  • GitHub Check: validate-generated-documentation
  • GitHub Check: backend-docker-integration
  • GitHub Check: backend-tests-functional
  • GitHub Check: documentation
  • GitHub Check: backend-tests-unit
  • GitHub Check: backend-tests-integration
  • GitHub Check: Cloudflare Pages

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 and usage tips.

@solababs solababs force-pushed the sb-20251027-update-git-sync-ifc-1984 branch from 6394fff to ecb1481 Compare October 28, 2025 00:01
@solababs solababs force-pushed the sb-20251027-update-git-sync-ifc-1984 branch from ecb1481 to cd7db02 Compare October 28, 2025 00:06
):
raise ValidationError(
f"Branch name '{obj.name}' does not match import_sync_branch_names: {config.SETTINGS.git.import_sync_branch_names}"
)
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we should restrict the creation of these branches within Infrahub itself. I.e. the setting should only restrict which branches are automatically created due to imports from Git, not control the naming of branches that users can create within Infrahub.

for branch_filter in config.SETTINGS.git.import_sync_branch_names:
if re.fullmatch(branch_filter, branch_short_name) or branch_filter == branch_short_name:
return True
return False
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we want to avoid adding more stuff within the infrahub.core namespace so I'd instead move this to infrahub.git.

One thought would be if we saved the compiled regex entries directly within the config object in a private attribute we could access them all and just do a .match for all entries within the list as we'd already have them compiled.

Also I'd suggest that we combine this function with the logic we have in git/base.py:

if config.SETTINGS.git.import_sync_branch_names and not branch_name_in_sync_branches

Currently the branch_name_in_sync_branches() function might behave in an unexpected way if used in isolation and config.SETTINGS.git.import_sync_branch_names didn't contain any entries. If we instead had a function that would return True if we should skip the import, if users hadn't configured this option it would always return True, otherwise it would return True if the branch name didn't contain a match for the regex filters.

branch_short_name=short_name
):
skipped_branch_names.append(short_name)
continue
Copy link
Contributor

Choose a reason for hiding this comment

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

Here we must also account for the default branch, any branch in Infrahub marked as "sync with git", and potentially any "default branch" configured on the repo itself (this is typically always "main" but it can differ).

assert isinstance(remote_branches, dict)
assert sorted(remote_branches.keys()) == ["branch01", "branch02"]

config.SETTINGS.git.import_sync_branch_names = []
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of changing the config like this the test and resetting it back to another value afterwards I'd suggest that we use a fixture similar to this one:

@pytest.fixture
def query_limit_of_one() -> Generator[None, None, None]:
    original_query_size_limit = config.SETTINGS.database.query_size_limit
    config.SETTINGS.database.query_size_limit = 1
    yield
    config.SETTINGS.database.query_size_limit = original_query_size_limit

assert sorted(remote_branches.keys()) == ["branch01", "branch02", "clean-branch", "main"]


async def test_get_branches_from_remote_in_import_sync_branch_names(git_repo_01: InfrahubRepository):
Copy link
Contributor

Choose a reason for hiding this comment

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

Minor detail here, I just merged a PR to develop that went through and added return values to all of the tests that didn't have them. There's still some work to be done with other functions before it's enforced but please add a ) -> None: as the return value here.

Base automatically changed from sb-20251027-config-for-selective-git-sync-ifc-1983 to develop November 4, 2025 11:51
@solababs solababs requested review from a team as code owners November 4, 2025 11:51
@solababs solababs requested a review from ogenstad November 4, 2025 11:55
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
backend/tests/unit/git/conftest.py (1)

1364-1455: Consider consolidating duplicated mock fixtures.

mock_get_branch_git_repo_01 and mock_get_branch_git_repo_03 share significant structural duplication. Both define similar GraphQL callbacks with overlapping logic for "main" and "branch01" cases.

Consider parameterizing a single fixture or extracting common callback logic to reduce duplication:

def _create_branch_response(branch_name: str, is_default: bool = False, sync_with_git: bool = False):
    """Helper to create branch response data."""
    return {
        "data": {
            "Branch": [{
                "id": "eca306cf-662e-4e03-8180-2b788b191d3c",
                "name": branch_name,
                "sync_with_git": sync_with_git,
                "is_default": is_default,
                "origin_branch": None,
                "branched_from": "2023-02-17T09:30:17.811719Z",
                "is_isolated": False,
                "has_schema_changes": False,
            }]
        }
    }

Then use this helper in both fixtures to reduce code duplication.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0ef0946 and b6081da.

📒 Files selected for processing (4)
  • backend/infrahub/git/base.py (5 hunks)
  • backend/infrahub/git/utils.py (3 hunks)
  • backend/tests/unit/git/conftest.py (2 hunks)
  • backend/tests/unit/git/test_git_repository.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
backend/**/*

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Run backend tests with pytest or via invoke tasks

Files:

  • backend/infrahub/git/utils.py
  • backend/infrahub/git/base.py
  • backend/tests/unit/git/conftest.py
  • backend/tests/unit/git/test_git_repository.py
**/*.py

📄 CodeRabbit inference engine (.github/instructions/python-docstring.instructions.md)

**/*.py: Use triple double quotes (""") for all Python docstrings
Write docstrings in Google-style format
Include a brief one-line description at the top of each docstring
Add a detailed description section when additional context is needed
Document function/method parameters under an Args/Parameters section without typing information
Include a Returns section describing the return value
Include a Raises section listing possible exceptions
Provide an Examples section demonstrating usage when helpful

Use ruff and mypy to validate and lint Python files

**/*.py: Use type hints for all Python function parameters and return values
Prefer asynchronous code in Python when feasible
Define asynchronous Python functions with async def
Use await for asynchronous calls in Python
Use Pydantic models instead of standard dataclasses
Use ruff and mypy for linting and type checking

**/*.py: Use type hints for all Python function parameters and return values
Use async/await whenever possible in Python code
Define asynchronous functions with async def
Await asynchronous calls with await
Use Pydantic models instead of standard dataclasses for data modeling
Use triple quotes (""") for all Python docstrings
Write docstrings in Google-style format
Include docstring sections when applicable: one-line summary, optional details, Args (without types), Returns, Raises, Examples
Validate and lint Python with ruff and mypy

Files:

  • backend/infrahub/git/utils.py
  • backend/infrahub/git/base.py
  • backend/tests/unit/git/conftest.py
  • backend/tests/unit/git/test_git_repository.py
backend/tests/**/*

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Place backend tests in backend/tests/

Files:

  • backend/tests/unit/git/conftest.py
  • backend/tests/unit/git/test_git_repository.py
🧠 Learnings (1)
📚 Learning: 2025-08-15T08:57:37.880Z
Learnt from: BaptisteGi
Repo: opsmill/infrahub PR: 7044
File: docs/docs/guides/change-approval-workflow.mdx:93-107
Timestamp: 2025-08-15T08:57:37.880Z
Learning: In Infrahub's change approval workflow documentation, the "Restricted Access" role intentionally includes `global:edit_default_branch:allow_all` permission despite being described as allowing edits only to branches other than default. This configuration is expected behavior according to the maintainers.

Applied to files:

  • backend/infrahub/git/base.py
🧬 Code graph analysis (4)
backend/infrahub/git/utils.py (2)
backend/infrahub/config.py (1)
  • git (893-894)
backend/tests/unit/git/conftest.py (1)
  • import_sync_branch_names (1357-1361)
backend/infrahub/git/base.py (3)
backend/infrahub/config.py (1)
  • git (893-894)
backend/infrahub/git/utils.py (1)
  • branch_name_in_sync_branches (175-179)
backend/tests/unit/git/conftest.py (1)
  • import_sync_branch_names (1357-1361)
backend/tests/unit/git/conftest.py (2)
backend/infrahub/config.py (1)
  • git (893-894)
backend/tests/conftest.py (1)
  • branch (1256-1260)
backend/tests/unit/git/test_git_repository.py (2)
backend/tests/unit/git/conftest.py (5)
  • git_repo_01_w_client (169-173)
  • mock_get_branch_git_repo_01 (1365-1415)
  • import_sync_branch_names (1357-1361)
  • mock_get_branch_git_repo_03 (1419-1455)
  • git_repo_01 (135-147)
backend/infrahub/git/base.py (1)
  • get_filtered_remote_branches (777-779)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: validate-generated-documentation
  • GitHub Check: graphql-schema
  • GitHub Check: backend-tests-functional
  • GitHub Check: backend-tests-integration
  • GitHub Check: backend-tests-unit
🔇 Additional comments (2)
backend/tests/unit/git/conftest.py (1)

1356-1361: LGTM! Fixture pattern for config override.

The fixture correctly saves, overrides, and restores config.SETTINGS.git.import_sync_branch_names, following the recommended pattern from past reviews.

backend/tests/unit/git/test_git_repository.py (1)

1087-1106: LGTM! Comprehensive test coverage.

The three test functions provide good coverage of the filtering logic:

  1. When all relevant branches match the sync filter
  2. When only some branches are found in GraphQL
  3. When no import_sync_branch_names are configured (all branches returned)

The tests correctly include return type annotations as requested in past reviews.

Comment on lines 500 to 503
try:
branch = await self.client.branch.get(branch_name=short_name)
except BranchNotFoundError:
...
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Use self.sdk instead of self.client and clarify exception handling.

Line 501 accesses self.client which may be None. The ellipsis (...) as an exception handler on line 503 is valid but unconventional.

         try:
-            branch = await self.client.branch.get(branch_name=short_name)
+            branch = await self.sdk.branch.get(branch_name=short_name)
         except BranchNotFoundError:
-            ...
+            pass  # Branch doesn't exist in Infrahub, will check sync filters below

The self.sdk property automatically initializes the client if needed (line 180-184), making it safer than direct self.client access.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
branch = await self.client.branch.get(branch_name=short_name)
except BranchNotFoundError:
...
try:
branch = await self.sdk.branch.get(branch_name=short_name)
except BranchNotFoundError:
pass # Branch doesn't exist in Infrahub, will check sync filters below
🤖 Prompt for AI Agents
In backend/infrahub/git/base.py around lines 500-503, replace direct use of
self.client with self.sdk (which lazily initializes the client) and change the
bare ellipsis in the BranchNotFoundError handler to an explicit action: await
branch = await self.sdk.branch.get(branch_name=short_name) and in the except
BranchNotFoundError block either return None (or raise a clearer custom
exception) and/or log the missing-branch condition with context so the behavior
is explicit and not silently ignored.

Comment on lines 777 to 779
async def get_filtered_remote_branches(self) -> dict[str, BranchInRemote]:
remote_branches = self.get_branches_from_remote()
return await self.filter_remote_branches(branches=remote_branches)
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add docstring to public method.

The get_filtered_remote_branches method is missing a docstring as required by coding guidelines.

As per coding guidelines:

 async def get_filtered_remote_branches(self) -> dict[str, BranchInRemote]:
+    """Retrieve remote branches and apply sync filtering.
+
+    Returns:
+        Dictionary of filtered remote branches that should be synced.
+    """
     remote_branches = self.get_branches_from_remote()
     return await self.filter_remote_branches(branches=remote_branches)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async def get_filtered_remote_branches(self) -> dict[str, BranchInRemote]:
remote_branches = self.get_branches_from_remote()
return await self.filter_remote_branches(branches=remote_branches)
async def get_filtered_remote_branches(self) -> dict[str, BranchInRemote]:
"""Retrieve remote branches and apply sync filtering.
Returns:
Dictionary of filtered remote branches that should be synced.
"""
remote_branches = self.get_branches_from_remote()
return await self.filter_remote_branches(branches=remote_branches)
🤖 Prompt for AI Agents
In backend/infrahub/git/base.py around lines 777 to 779, add a concise docstring
to the public async method get_filtered_remote_branches describing its purpose,
parameters (none), return type (dict[str, BranchInRemote]) and behavior (calls
get_branches_from_remote then filters via filter_remote_branches), following
project docstring style (one-line summary plus short elaboration if needed) and
include any raised exceptions or side effects if applicable.

Comment on lines 175 to 179
def branch_name_in_sync_branches(branch_short_name: str) -> bool:
for branch_filter in config.SETTINGS.git.import_sync_branch_names:
if re.fullmatch(branch_filter, branch_short_name) or branch_filter == branch_short_name:
return True
return False
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add docstring and clarify matching logic.

The function is missing a docstring, which is required by the coding guidelines. Additionally, the dual matching logic (regex + exact match) could be simplified or explained.

As per coding guidelines, add a Google-style docstring:

 def branch_name_in_sync_branches(branch_short_name: str) -> bool:
+    """Check if a branch name matches any configured sync filters.
+
+    Filters can be exact strings or regular expressions. The function attempts
+    both regex matching (via re.fullmatch) and exact string matching.
+
+    Args:
+        branch_short_name: The short name of the branch to check.
+
+    Returns:
+        True if the branch name matches any filter, False otherwise.
+    """
     for branch_filter in config.SETTINGS.git.import_sync_branch_names:
         if re.fullmatch(branch_filter, branch_short_name) or branch_filter == branch_short_name:
             return True
     return False

Note: The exact match check is redundant when branch_filter is a plain string (regex fullmatch will handle it). Consider whether this fallback is intentional for edge cases or if re.fullmatch() alone is sufficient.

🤖 Prompt for AI Agents
In backend/infrahub/git/utils.py around lines 175 to 179, add a Google-style
docstring that explains the function purpose, arguments, return value, and
examples; then clarify the matching logic by either (a) removing the redundant
exact-equality fallback and relying solely on re.fullmatch(branch_filter,
branch_short_name) (since a plain string pattern will still full-match), or (b)
if the exact-match branch is intentional for an edge case, document that
behaviour in the docstring and add a brief code comment explaining why the
fallback is kept. Ensure the docstring follows project style and update
tests/comments if you change behavior.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
backend/infrahub/git/base.py (2)

777-779: Document get_filtered_remote_branches

This new public async helper also needs a Google-style docstring per our Python standards. Please add a short summary plus Returns section.

     async def get_filtered_remote_branches(self) -> dict[str, BranchInRemote]:
+        """Retrieve remote branches and apply the sync filter.
+
+        Returns:
+            Mapping of remote branches that passed filter_remote_branches.
+        """
         remote_branches = self.get_branches_from_remote()
         return await self.filter_remote_branches(branches=remote_branches)

490-526: Add the required docstring for filter_remote_branches

Project Python guidelines call for Google-style docstrings on every function. Please document the new method with Args/Returns so future readers understand the filter contract.

     async def filter_remote_branches(self, branches: dict[str, BranchInRemote]) -> dict[str, BranchInRemote]:
+        """Filter remote branches according to import_sync_branch_names.
+
+        Args:
+            branches: Mapping of branch names to remote branch metadata.
+
+        Returns:
+            Filtered mapping containing only branches that satisfy sync rules or exemptions.
+        """
         if not config.SETTINGS.git.import_sync_branch_names or not self.client:
             return branches
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b6081da and 6a123f6.

📒 Files selected for processing (2)
  • backend/infrahub/git/base.py (5 hunks)
  • python_sdk (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • python_sdk
🧰 Additional context used
📓 Path-based instructions (2)
backend/**/*

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Run backend tests with pytest or via invoke tasks

Files:

  • backend/infrahub/git/base.py
**/*.py

📄 CodeRabbit inference engine (.github/instructions/python-docstring.instructions.md)

**/*.py: Use triple double quotes (""") for all Python docstrings
Write docstrings in Google-style format
Include a brief one-line description at the top of each docstring
Add a detailed description section when additional context is needed
Document function/method parameters under an Args/Parameters section without typing information
Include a Returns section describing the return value
Include a Raises section listing possible exceptions
Provide an Examples section demonstrating usage when helpful

Use ruff and mypy to validate and lint Python files

**/*.py: Use type hints for all Python function parameters and return values
Prefer asynchronous code in Python when feasible
Define asynchronous Python functions with async def
Use await for asynchronous calls in Python
Use Pydantic models instead of standard dataclasses
Use ruff and mypy for linting and type checking

**/*.py: Use type hints for all Python function parameters and return values
Use async/await whenever possible in Python code
Define asynchronous functions with async def
Await asynchronous calls with await
Use Pydantic models instead of standard dataclasses for data modeling
Use triple quotes (""") for all Python docstrings
Write docstrings in Google-style format
Include docstring sections when applicable: one-line summary, optional details, Args (without types), Returns, Raises, Examples
Validate and lint Python with ruff and mypy

Files:

  • backend/infrahub/git/base.py
🧠 Learnings (1)
📚 Learning: 2025-08-15T08:57:37.880Z
Learnt from: BaptisteGi
Repo: opsmill/infrahub PR: 7044
File: docs/docs/guides/change-approval-workflow.mdx:93-107
Timestamp: 2025-08-15T08:57:37.880Z
Learning: In Infrahub's change approval workflow documentation, the "Restricted Access" role intentionally includes `global:edit_default_branch:allow_all` permission despite being described as allowing edits only to branches other than default. This configuration is expected behavior according to the maintainers.

Applied to files:

  • backend/infrahub/git/base.py
🧬 Code graph analysis (1)
backend/infrahub/git/base.py (4)
backend/infrahub/exceptions.py (1)
  • BranchNotFoundError (194-200)
backend/infrahub/config.py (2)
  • logging (929-930)
  • git (893-894)
backend/infrahub/core/branch/models.py (1)
  • Branch (28-536)
backend/infrahub/git/utils.py (1)
  • branch_name_in_sync_branches (175-179)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
backend/infrahub/git/base.py (2)

777-779: Add docstring to public method.

This method is missing a docstring. Per coding guidelines, document the purpose, return value, and behavior.

Apply this diff:

 async def get_filtered_remote_branches(self) -> dict[str, BranchInRemote]:
+    """Retrieve remote branches and apply sync filtering.
+
+    Fetches all branches from the remote repository and filters them based on
+    import_sync_branch_names configuration.
+
+    Returns:
+        Dictionary of filtered remote branches that should be synced.
+    """
     remote_branches = self.get_branches_from_remote()

As per coding guidelines.


503-503: Replace ellipsis with explicit pass.

Using ellipsis (...) as an exception handler is unconventional. Use pass with a comment explaining why the exception is caught.

Apply this diff:

         except BranchNotFoundError:
-            ...
+            pass  # Branch doesn't exist in Infrahub, will check import_sync filters below
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6a123f6 and fb7694b.

📒 Files selected for processing (2)
  • backend/infrahub/git/base.py (5 hunks)
  • backend/infrahub/git/utils.py (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/infrahub/git/utils.py
🧰 Additional context used
📓 Path-based instructions (2)
backend/**/*

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Run backend tests with pytest or via invoke tasks

Files:

  • backend/infrahub/git/base.py
**/*.py

📄 CodeRabbit inference engine (.github/instructions/python-docstring.instructions.md)

**/*.py: Use triple double quotes (""") for all Python docstrings
Write docstrings in Google-style format
Include a brief one-line description at the top of each docstring
Add a detailed description section when additional context is needed
Document function/method parameters under an Args/Parameters section without typing information
Include a Returns section describing the return value
Include a Raises section listing possible exceptions
Provide an Examples section demonstrating usage when helpful

Use ruff and mypy to validate and lint Python files

**/*.py: Use type hints for all Python function parameters and return values
Prefer asynchronous code in Python when feasible
Define asynchronous Python functions with async def
Use await for asynchronous calls in Python
Use Pydantic models instead of standard dataclasses
Use ruff and mypy for linting and type checking

**/*.py: Use type hints for all Python function parameters and return values
Use async/await whenever possible in Python code
Define asynchronous functions with async def
Await asynchronous calls with await
Use Pydantic models instead of standard dataclasses for data modeling
Use triple quotes (""") for all Python docstrings
Write docstrings in Google-style format
Include docstring sections when applicable: one-line summary, optional details, Args (without types), Returns, Raises, Examples
Validate and lint Python with ruff and mypy

Files:

  • backend/infrahub/git/base.py
🧠 Learnings (1)
📚 Learning: 2025-08-15T08:57:37.880Z
Learnt from: BaptisteGi
Repo: opsmill/infrahub PR: 7044
File: docs/docs/guides/change-approval-workflow.mdx:93-107
Timestamp: 2025-08-15T08:57:37.880Z
Learning: In Infrahub's change approval workflow documentation, the "Restricted Access" role intentionally includes `global:edit_default_branch:allow_all` permission despite being described as allowing edits only to branches other than default. This configuration is expected behavior according to the maintainers.

Applied to files:

  • backend/infrahub/git/base.py
🧬 Code graph analysis (1)
backend/infrahub/git/base.py (5)
backend/infrahub/exceptions.py (1)
  • BranchNotFoundError (194-200)
backend/infrahub/config.py (2)
  • logging (929-930)
  • git (893-894)
backend/infrahub/core/branch/models.py (1)
  • Branch (28-536)
backend/infrahub/git/utils.py (1)
  • branch_name_in_import_sync_branches (175-179)
backend/tests/unit/git/conftest.py (1)
  • import_sync_branch_names (1357-1361)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: validate-generated-documentation
🔇 Additional comments (2)
backend/infrahub/git/base.py (2)

14-14: LGTM: Imports are appropriate.

The new imports support the branch filtering functionality and are all used correctly in the implementation.

Also applies to: 20-20, 22-22, 36-36


793-793: LGTM: Filtering correctly integrated.

The change correctly applies branch filtering to the comparison flow by using the new get_filtered_remote_branches() method with proper await.

Comment on lines 490 to 526
async def filter_remote_branches(self, branches: dict[str, BranchInRemote]) -> dict[str, BranchInRemote]:
if not config.SETTINGS.git.import_sync_branch_names:
return branches

filtered_branches = {}
skipped_branch_names = []

for short_name, branch_data in branches.items():
branch = None

try:
branch = await self.sdk.branch.get(branch_name=short_name)
except BranchNotFoundError:
...

branch_exists_import_sync_condition = branch and (
branch.name not in {GLOBAL_BRANCH_NAME, registry.default_branch}
and not branch.is_default
and not branch.sync_with_git
and not branch_name_in_import_sync_branches(branch_short_name=short_name)
)
branch_does_not_exist_import_sync_condition = not branch and not branch_name_in_import_sync_branches(
branch_short_name=short_name
)

if branch_exists_import_sync_condition or branch_does_not_exist_import_sync_condition:
skipped_branch_names.append(short_name)
continue

filtered_branches[short_name] = branch_data

if skipped_branch_names:
log.debug(
f"Skipped the following branches {skipped_branch_names} "
f"because no match was found in import_sync_branch_names {config.SETTINGS.git.import_sync_branch_names}"
)
return filtered_branches
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add docstring to public method.

This method is missing a docstring. Per coding guidelines, all functions should have docstrings with a brief description, Args, Returns, and any Raises sections.

Apply this diff to add a docstring:

 async def filter_remote_branches(self, branches: dict[str, BranchInRemote]) -> dict[str, BranchInRemote]:
+    """Filter remote branches based on sync configuration.
+
+    Applies import_sync_branch_names filtering to remote branches. Branches are kept if they match
+    the configured patterns or meet special conditions (default, global, sync_with_git).
+
+    Args:
+        branches: Dictionary of remote branches to filter.
+
+    Returns:
+        Filtered dictionary containing only branches that should be synced.
+    """
     if not config.SETTINGS.git.import_sync_branch_names:

As per coding guidelines.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async def filter_remote_branches(self, branches: dict[str, BranchInRemote]) -> dict[str, BranchInRemote]:
if not config.SETTINGS.git.import_sync_branch_names:
return branches
filtered_branches = {}
skipped_branch_names = []
for short_name, branch_data in branches.items():
branch = None
try:
branch = await self.sdk.branch.get(branch_name=short_name)
except BranchNotFoundError:
...
branch_exists_import_sync_condition = branch and (
branch.name not in {GLOBAL_BRANCH_NAME, registry.default_branch}
and not branch.is_default
and not branch.sync_with_git
and not branch_name_in_import_sync_branches(branch_short_name=short_name)
)
branch_does_not_exist_import_sync_condition = not branch and not branch_name_in_import_sync_branches(
branch_short_name=short_name
)
if branch_exists_import_sync_condition or branch_does_not_exist_import_sync_condition:
skipped_branch_names.append(short_name)
continue
filtered_branches[short_name] = branch_data
if skipped_branch_names:
log.debug(
f"Skipped the following branches {skipped_branch_names} "
f"because no match was found in import_sync_branch_names {config.SETTINGS.git.import_sync_branch_names}"
)
return filtered_branches
async def filter_remote_branches(self, branches: dict[str, BranchInRemote]) -> dict[str, BranchInRemote]:
"""Filter remote branches based on sync configuration.
Applies import_sync_branch_names filtering to remote branches. Branches are kept if they match
the configured patterns or meet special conditions (default, global, sync_with_git).
Args:
branches: Dictionary of remote branches to filter.
Returns:
Filtered dictionary containing only branches that should be synced.
"""
if not config.SETTINGS.git.import_sync_branch_names:
return branches
filtered_branches = {}
skipped_branch_names = []
for short_name, branch_data in branches.items():
branch = None
try:
branch = await self.sdk.branch.get(branch_name=short_name)
except BranchNotFoundError:
...
branch_exists_import_sync_condition = branch and (
branch.name not in {GLOBAL_BRANCH_NAME, registry.default_branch}
and not branch.is_default
and not branch.sync_with_git
and not branch_name_in_import_sync_branches(branch_short_name=short_name)
)
branch_does_not_exist_import_sync_condition = not branch and not branch_name_in_import_sync_branches(
branch_short_name=short_name
)
if branch_exists_import_sync_condition or branch_does_not_exist_import_sync_condition:
skipped_branch_names.append(short_name)
continue
filtered_branches[short_name] = branch_data
if skipped_branch_names:
log.debug(
f"Skipped the following branches {skipped_branch_names} "
f"because no match was found in import_sync_branch_names {config.SETTINGS.git.import_sync_branch_names}"
)
return filtered_branches
🤖 Prompt for AI Agents
In backend/infrahub/git/base.py around lines 490 to 526, the public async method
filter_remote_branches is missing a docstring; add a comprehensive docstring
immediately above the method that briefly describes its purpose (filters a
mapping of remote branch short names to BranchInRemote according to
import_sync_branch_names and existing local branch properties), documents Args
(branches: dict[str, BranchInRemote] — mapping of short branch names to remote
branch data), Returns (dict[str, BranchInRemote] — filtered mapping of branches
that should be processed), and Raises (document that BranchNotFoundError may be
encountered/handled within the method or note that exceptions from
self.sdk.branch.get may be raised), and mention any important side-effects or
config usage (uses config.SETTINGS.git.import_sync_branch_names and logs skipped
branch names); keep the wording concise and follow existing project docstring
style (brief description, Args, Returns, Raises).


async def get_filtered_remote_branches(self) -> dict[str, BranchInRemote]:
remote_branches = self.get_branches_from_remote()
return await self.filter_remote_branches(branches=remote_branches)
Copy link
Contributor

Choose a reason for hiding this comment

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

As we don't call filter_remote_branches in other locations and always call .get_filtered_remote_branches() I wonder if it's clearer if we remove the filter_remote_branches() method and just have that code inline within get_filtered_remote_branches()?

branch = None

try:
branch = await self.sdk.branch.get(branch_name=short_name)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this could potentially be problematic if the imported repository has a very high number of branches as we'd always try to get the branch name from the API using the SDK. The branches should always be loaded in the registry. so if we instead have something like this we'd just look in a local dictionary and avoid the delay.

branch = registry.get_branch_from_registry(branch=short_name)

Alternatively we could add a method in the registry that would return all registry branches (i.e. just return the self.branch dictionary)

...

branch_exists_import_sync_condition = branch and (
branch.name not in {GLOBAL_BRANCH_NAME, registry.default_branch}
Copy link
Contributor

Choose a reason for hiding this comment

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

The GLOBAL_BRANCH_NAME is -global- which is an invalid branch name in Git and as such should never come from a git remote. However the registry itself could have a default branch (self.default_branch) in most cases it will be exactly the same but it doesn't have to be, for that reason we should also add the default branch of the repository to this entry.


branch_exists_import_sync_condition = branch and (
branch.name not in {GLOBAL_BRANCH_NAME, registry.default_branch}
and not branch.is_default
Copy link
Contributor

Choose a reason for hiding this comment

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

As we already check for registry.default_branch perhaps this "not branch.is_default` is redundant?

@solababs solababs requested a review from ogenstad November 5, 2025 13:51
Copy link
Contributor

@ogenstad ogenstad left a comment

Choose a reason for hiding this comment

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

Can you also please add a towncrier newsfragment of the "added" kind that adds this as a new feature?

@solababs solababs requested a review from ogenstad November 6, 2025 14:46
@solababs solababs merged commit 13f0e64 into develop Nov 6, 2025
67 of 68 checks passed
@solababs solababs deleted the sb-20251027-update-git-sync-ifc-1984 branch November 6, 2025 17:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

group/backend Issue related to the backend (API Server, Git Agent)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants