Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion migrations_lockfile.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ replays: 0007_organizationmember_replay_access

seer: 0005_delete_seerorganizationsettings

sentry: 1057_drop_legacy_alert_rule_tables
sentry: 1058_change_code_mapping_unique_constraint

social_auth: 0003_social_auth_json_field

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,15 @@ def organization(self):

def validate(self, attrs):
query = RepositoryProjectPathConfig.objects.filter(
project_id=attrs.get("project_id"), stack_root=attrs.get("stack_root")
project_id=attrs.get("project_id"),
stack_root=attrs.get("stack_root"),
source_root=attrs.get("source_root"),
)
if self.instance:
query = query.exclude(id=self.instance.id)
if query.exists():
raise serializers.ValidationError(
"Code path config already exists with this project and stack trace root"
"Code path config already exists with this project, stack trace root, and source root"
)
return attrs

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,11 +256,9 @@ def post(self, request: Request, organization: Organization) -> Response:
config = RepositoryProjectPathConfig.objects.select_for_update().get(
project=project,
stack_root=mapping["stack_root"],
source_root=mapping["source_root"],
)
for key, value in {
**defaults,
"source_root": mapping["source_root"],
}.items():
for key, value in defaults.items():
setattr(config, key, value)
created = False
except RepositoryProjectPathConfig.DoesNotExist:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class RepositoryProjectPathConfig(DefaultFieldsModelExisting):
class Meta:
app_label = "sentry"
db_table = "sentry_repositoryprojectpathconfig"
unique_together = (("project", "stack_root"),)
unique_together = (("project", "stack_root", "source_root"),)
Comment thread
romtsn marked this conversation as resolved.
Comment thread
cursor[bot] marked this conversation as resolved.

def __repr__(self) -> str:
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Generated by Django 5.2.12 on 2026-03-27 11:40

from django.db import migrations

from sentry.new_migrations.migrations import CheckedMigration


class Migration(CheckedMigration):
# This flag is used to mark that a migration shouldn't be automatically run in production.
# This should only be used for operations where it's safe to run the migration after your
# code has deployed. So this should not be used for most operations that alter the schema
# of a table.
# Here are some things that make sense to mark as post deployment:
# - Large data migrations. Typically we want these to be run manually so that they can be
# monitored and not block the deploy for a long period of time while they run.
# - Adding indexes to large tables. Since this can take a long time, we'd generally prefer to
# run this outside deployments so that we don't block them. Note that while adding an index
# is a schema change, it's completely safe to run the operation after the code has deployed.
# Once deployed, run these manually via: https://develop.sentry.dev/database-migrations/#migration-deployment

is_post_deployment = False

dependencies = [
("sentry", "1057_drop_legacy_alert_rule_tables"),
]

operations = [
migrations.AlterUniqueTogether(
name="repositoryprojectpathconfig",
unique_together={("project", "stack_root", "source_root")},
),
]
Comment thread
romtsn marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ def test_validate_path_conflict(self) -> None:
assert response.status_code == 400
assert response.data == {
"nonFieldErrors": [
"Code path config already exists with this project and stack trace root"
"Code path config already exists with this project, stack trace root, and source root"
]
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ def test_update_existing_mapping(self) -> None:
project=self.project1,
repo=self.repo1,
stack_root="com/example/maps",
source_root="old/source/root",
source_root="modules/maps/src/main/java/com/example/maps",
default_branch="old-branch",
)

response = self.make_post(
Expand All @@ -114,24 +115,26 @@ def test_update_existing_mapping(self) -> None:
assert response.data["updated"] == 1

config = RepositoryProjectPathConfig.objects.get(
project=self.project1, stack_root="com/example/maps"
project=self.project1,
stack_root="com/example/maps",
source_root="modules/maps/src/main/java/com/example/maps",
)
assert config.source_root == "modules/maps/src/main/java/com/example/maps"
assert config.default_branch == "main"

def test_mixed_create_and_update(self) -> None:
self.create_code_mapping(
project=self.project1,
repo=self.repo1,
stack_root="com/example/existing",
source_root="old/path",
source_root="existing/path",
)

response = self.make_post(
{
"mappings": [
{
"stackRoot": "com/example/existing",
"sourceRoot": "new/path",
"sourceRoot": "existing/path",
},
{
"stackRoot": "com/example/new",
Expand Down Expand Up @@ -440,7 +443,7 @@ def test_repo_from_other_org_returns_404(self) -> None:
response = self.make_post({"repository": "other-org/other-repo"})
assert response.status_code == 404

def test_duplicate_stack_roots_in_request_last_wins(self) -> None:
def test_same_stack_root_different_source_roots_creates_both(self) -> None:
response = self.make_post(
{
"mappings": [
Expand All @@ -456,13 +459,17 @@ def test_duplicate_stack_roots_in_request_last_wins(self) -> None:
}
)
assert response.status_code == 200, response.content
assert response.data["created"] == 1
assert response.data["updated"] == 1
assert response.data["created"] == 2
assert response.data["updated"] == 0

config = RepositoryProjectPathConfig.objects.get(
configs = RepositoryProjectPathConfig.objects.filter(
project=self.project1, stack_root="com/example/maps"
)
assert config.source_root == "second/source/root"
assert configs.count() == 2
assert set(configs.values_list("source_root", flat=True)) == {
"first/source/root",
"second/source/root",
}

def test_multiple_repos_same_name_returns_409(self) -> None:
# Intentionally use Repository.objects.create since create_repo uses
Expand Down
Loading