Skip to content
This repository was archived by the owner on Jun 13, 2025. It is now read-only.

Commit cd1ef89

Browse files
fix: Improve branch search speed (#909)
1 parent e30e3ed commit cd1ef89

File tree

2 files changed

+44
-8
lines changed

2 files changed

+44
-8
lines changed

core/commands/branch/interactors/fetch_branches.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
from django.db.models import OuterRef, Q, Subquery
1+
from typing import Any
2+
3+
from django.db.models import OuterRef, Q, QuerySet, Subquery
4+
from shared.django_apps.core.models import Repository
25

36
from codecov.commands.base import BaseInteractor
47
from codecov.db import sync_to_async
@@ -7,13 +10,17 @@
710

811
class FetchRepoBranchesInteractor(BaseInteractor):
912
@sync_to_async
10-
def execute(self, repository, filters):
13+
def execute(self, repository: Repository, filters: dict[str, Any]) -> QuerySet:
1114
queryset = repository.branches.all()
1215

1316
filters = filters or {}
1417
search_value = filters.get("search_value")
1518
if search_value:
16-
queryset = queryset.filter(name__icontains=search_value)
19+
# force use of ILIKE to optimize search; django icontains doesn't work
20+
# see https://github.com/codecov/engineering-team/issues/2537
21+
queryset = queryset.extra(
22+
where=['"branches"."branch" ILIKE %s'], params=[f"%{search_value}%"]
23+
)
1724

1825
merged = filters.get("merged_branches", False)
1926
if not merged:

core/commands/branch/interactors/tests/test_fetch_branches.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Any
2+
13
from asgiref.sync import async_to_sync
24
from django.test import TransactionTestCase
35
from shared.django_apps.core.tests.factories import (
@@ -11,7 +13,7 @@
1113

1214

1315
class FetchRepoBranchesInteractorTest(TransactionTestCase):
14-
def setUp(self):
16+
def setUp(self) -> None:
1517
self.org = OwnerFactory(username="codecov")
1618
self.repo = RepositoryFactory(
1719
author=self.org, name="gazebo", private=False, branch="main"
@@ -23,20 +25,20 @@ def setUp(self):
2325
BranchFactory(repository=self.repo, head=self.head.commitid, name="test2"),
2426
]
2527

26-
def execute(self, owner, repository, filters):
28+
def execute(self, owner, repository, filters) -> FetchRepoBranchesInteractor:
2729
service = owner.service if owner else "github"
2830
return FetchRepoBranchesInteractor(owner, service).execute(repository, filters)
2931

30-
def test_fetch_branches(self):
32+
def test_fetch_branches(self) -> None:
3133
repository = self.repo
32-
filters = {}
34+
filters: dict[str, Any] = {}
3335
branches = async_to_sync(self.execute)(None, repository, filters)
3436
assert any(branch.name == "main" for branch in branches)
3537
assert any(branch.name == "test1" for branch in branches)
3638
assert any(branch.name == "test2" for branch in branches)
3739
assert len(branches) == 3
3840

39-
def test_fetch_branches_unmerged(self):
41+
def test_fetch_branches_unmerged(self) -> None:
4042
merged = CommitFactory(repository=self.repo, merged=True)
4143
BranchFactory(repository=self.repo, head=merged.commitid, name="merged")
4244
branches = [
@@ -50,3 +52,30 @@ def test_fetch_branches_unmerged(self):
5052
)
5153
]
5254
assert "merged" in branches
55+
56+
def test_fetch_branches_filtered_by_name(self) -> None:
57+
repository = self.repo
58+
filters = {"search_value": "tESt", "merged_branches": True}
59+
branches = async_to_sync(self.execute)(None, repository, filters)
60+
assert not any(branch.name == "main" for branch in branches)
61+
assert any(branch.name == "test1" for branch in branches)
62+
assert any(branch.name == "test2" for branch in branches)
63+
assert len(branches) == 2
64+
65+
def test_fetch_branches_filtered_by_name_no_sql_injection(self) -> None:
66+
repository = self.repo
67+
malicious_filters = {
68+
"search_value": "'; DROP TABLE branches; --",
69+
"merged_branches": True,
70+
}
71+
find_branches_sql_injection_attempt = async_to_sync(self.execute)(
72+
None, repository, malicious_filters
73+
)
74+
assert (
75+
# assert no branches found with that branch name
76+
len(find_branches_sql_injection_attempt) == 0
77+
)
78+
79+
# confirm data is unaltered after sql injection attempt
80+
find_branches = async_to_sync(self.execute)(None, repository, {})
81+
assert len(find_branches) == 3

0 commit comments

Comments
 (0)