Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
29 changes: 24 additions & 5 deletions src/sentry/api/endpoints/organization_release_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
from sentry.api.exceptions import ResourceDoesNotExist
from sentry.api.serializers import serialize
from sentry.api.serializers.rest_framework import (
CommitSerializer, ListField, ReleaseHeadCommitSerializer
CommitSerializer, ListField, ReleaseHeadCommitSerializerDeprecated,
ReleaseHeadCommitSerializer,
)
from sentry.models import Activity, Group, Release, ReleaseFile
from sentry.utils.apidocs import scenario, attach_scenarios
Expand Down Expand Up @@ -47,7 +48,8 @@ class ReleaseSerializer(serializers.Serializer):
dateStarted = serializers.DateTimeField(required=False)
dateReleased = serializers.DateTimeField(required=False)
commits = ListField(child=CommitSerializer(), required=False)
headCommits = ListField(child=ReleaseHeadCommitSerializer(), required=False)
headCommits = ListField(child=ReleaseHeadCommitSerializerDeprecated(), required=False)
refs = ListField(child=ReleaseHeadCommitSerializer(), required=False)


class OrganizationReleaseDetailsEndpoint(OrganizationReleasesBaseEndpoint):
Expand Down Expand Up @@ -101,6 +103,17 @@ def put(self, request, organization, version):
:param datetime dateReleased: an optional date that indicates when
the release went live. If not provided
the current time is assumed.
:param array commits: an optional list of commit data to be associated
with the release. Commits must include parameters
``id`` (the sha of the commit), and can optionally
include ``repository``, ``message``, ``author_name``,
``author_email``, and ``timestamp``.
:param array refs: an optional way to indicate the start and end commits
for each repository included in a release. Head commits
must include parameters ``repository`` and ``commit``
(the HEAD sha). They can optionally include ``previousCommit``
(the sha of the HEAD of the previous release), which should
be specified if this is the first time you've sent commit data.
:auth: required
"""
try:
Expand Down Expand Up @@ -141,10 +154,16 @@ def put(self, request, organization, version):
# TODO(dcramer): handle errors with release payloads
release.set_commits(commit_list)

head_commits = result.get('headCommits')
if head_commits:
refs = result.get('refs')
if not refs:
refs = [{
'repository': r['repository'],
'previousCommit': r.get('previousId'),
'commit': r['currentId'],
} for r in result.get('headCommits', [])]
if refs:
fetch_commits = request.user.is_authenticated() and not commit_list
release.set_head_commits(head_commits, request.user, fetch_commits=fetch_commits)
release.set_refs(refs, request.user, fetch_commits=fetch_commits)

if (not was_released and release.date_released):
for project in release.projects.all():
Expand Down
31 changes: 20 additions & 11 deletions src/sentry/api/endpoints/organization_releases.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
from sentry.api.bases.organization import OrganizationReleasesBaseEndpoint
from sentry.api.paginator import OffsetPaginator
from sentry.api.serializers import serialize
from sentry.api.serializers.rest_framework import ReleaseHeadCommitSerializer, ListField
from sentry.api.serializers.rest_framework import (
ReleaseHeadCommitSerializer, ReleaseHeadCommitSerializerDeprecated, ListField
)
from sentry.models import Activity, Release
from sentry.utils.apidocs import scenario, attach_scenarios

Expand Down Expand Up @@ -37,7 +39,8 @@ def list_org_releases_scenario(runner):

class ReleaseSerializerWithProjects(ReleaseSerializer):
projects = ListField()
headCommits = ListField(child=ReleaseHeadCommitSerializer(), required=False)
headCommits = ListField(child=ReleaseHeadCommitSerializerDeprecated(), required=False)
refs = ListField(child=ReleaseHeadCommitSerializer(), required=False)


class OrganizationReleasesEndpoint(OrganizationReleasesBaseEndpoint):
Expand Down Expand Up @@ -111,12 +114,12 @@ def post(self, request, organization):
``id`` (the sha of the commit), and can optionally
include ``repository``, ``message``, ``author_name``,
``author_email``, and ``timestamp``.
:param array headCommits: an optional way to indicate the start and end commits
for each repository included in a release. Head commits
must include parameters ``repository`` and ``currentId``
(the HEAD sha). They can optionally include ``previousId``
(the sha of the HEAD of the previous release), which should
be specified if this is the first time you've sent commit data.
:param array refs: an optional way to indicate the start and end commits
for each repository included in a release. Head commits
must include parameters ``repository`` and ``commit``
(the HEAD sha). They can optionally include ``previousCommit``
(the sha of the HEAD of the previous release), which should
be specified if this is the first time you've sent commit data.
:auth: required
"""
serializer = ReleaseSerializerWithProjects(data=request.DATA)
Expand Down Expand Up @@ -173,10 +176,16 @@ def post(self, request, organization):
if commit_list:
release.set_commits(commit_list)

head_commits = result.get('headCommits')
if head_commits:
refs = result.get('refs')
if not refs:
refs = [{
'repository': r['repository'],
'previousCommit': r.get('previousId'),
'commit': r['currentId'],
} for r in result.get('headCommits', [])]
if refs:
fetch_commits = request.user.is_authenticated() and not commit_list
release.set_head_commits(head_commits, request.user, fetch_commits=fetch_commits)
release.set_refs(refs, request.user, fetch_commits=fetch_commits)

if not created and not new_projects:
# This is the closest status code that makes sense, and we want
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@
from rest_framework import serializers


class ReleaseHeadCommitSerializer(serializers.Serializer):
class ReleaseHeadCommitSerializerDeprecated(serializers.Serializer):
currentId = serializers.CharField(max_length=64)
repository = serializers.CharField(max_length=64)
previousId = serializers.CharField(max_length=64, required=False)


class ReleaseHeadCommitSerializer(serializers.Serializer):
commit = serializers.CharField(max_length=64)
repository = serializers.CharField(max_length=64)
previousCommit = serializers.CharField(max_length=64, required=False)
12 changes: 6 additions & 6 deletions src/sentry/models/release.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ def add_project(self, project):
else:
return True

def set_head_commits(self, head_commits, user, fetch_commits=False):
def set_refs(self, refs, user, fetch_commits=False):
from sentry.models import Commit, ReleaseHeadCommit, Repository
from sentry.plugins import bindings

Expand All @@ -230,19 +230,19 @@ def set_head_commits(self, head_commits, user, fetch_commits=False):

commit_list = []

for head_commit in head_commits:
for ref in refs:
try:
repo = Repository.objects.get(
organization_id=self.organization_id,
name=head_commit['repository'],
name=ref['repository'],
)
except Repository.DoesNotExist:
continue

commit = Commit.objects.get_or_create(
organization_id=self.organization_id,
repository_id=repo.id,
key=head_commit['currentId'],
key=ref['commit'],
)[0]
# update head commit for repo/release if exists
ReleaseHeadCommit.objects.create_or_update(
Expand All @@ -261,8 +261,8 @@ def set_head_commits(self, head_commits, user, fetch_commits=False):

# if previous commit isn't provided, try to get from
# previous release otherwise, give up
if head_commit.get('previousId'):
start_sha = head_commit['previousId']
if ref.get('previousCommit'):
start_sha = ref['previousCommit']
elif prev_release:
try:
start_sha = Commit.objects.filter(
Expand Down
84 changes: 84 additions & 0 deletions tests/sentry/api/endpoints/test_organization_release_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,90 @@ def test_simple(self):

self.login_as(user=user)

url = reverse('sentry-api-0-organization-release-details', kwargs={
'organization_slug': org.slug,
'version': release.version,
})
response = self.client.put(url, {
'ref': 'master',
'refs': [
{'commit': 'a' * 40, 'repository': repo.name},
{'commit': 'b' * 40, 'repository': repo2.name},
],
})

assert response.status_code == 200, response.content
assert response.data['version'] == release.version
assert ReleaseCommit.objects.filter(
commit__repository_id=repo.id,
commit__key='62de626b7c7cfb8e77efb4273b1a3df4123e6216',
release__version=response.data['version'],
).exists()
assert ReleaseCommit.objects.filter(
commit__repository_id=repo.id,
commit__key='58de626b7c7cfb8e77efb4273b1a3df4123e6345',
release__version=response.data['version'],
).exists()
assert ReleaseCommit.objects.filter(
commit__repository_id=repo2.id,
commit__key='62de626b7c7cfb8e77efb4273b1a3df4123e6216',
release__version=response.data['version'],
).exists()
assert ReleaseCommit.objects.filter(
commit__repository_id=repo2.id,
commit__key='58de626b7c7cfb8e77efb4273b1a3df4123e6345',
release__version=response.data['version'],
).exists()

release = Release.objects.get(id=release.id)
assert release.ref == 'master'

# no access
url = reverse('sentry-api-0-organization-release-details', kwargs={
'organization_slug': org.slug,
'version': release2.version,
})
response = self.client.put(url, {'ref': 'master'})
assert response.status_code == 403

def test_deprecated_head_commits(self):
user = self.create_user(is_staff=False, is_superuser=False)
org = self.organization
org.flags.allow_joinleave = False
org.save()

repo = Repository.objects.create(
organization_id=org.id,
name='example/example',
provider='dummy',
)
repo2 = Repository.objects.create(
organization_id=org.id,
name='example/example2',
provider='dummy',
)

team1 = self.create_team(organization=org)
team2 = self.create_team(organization=org)

project = self.create_project(team=team1, organization=org)
project2 = self.create_project(team=team2, organization=org)

release = Release.objects.create(
organization_id=org.id,
version='abcabcabc',
)
release2 = Release.objects.create(
organization_id=org.id,
version='12345678',
)
release.add_project(project)
release2.add_project(project2)

self.create_member(teams=[team1], user=user, organization=org)

self.login_as(user=user)

url = reverse('sentry-api-0-organization-release-details', kwargs={
'organization_slug': org.slug,
'version': release.version,
Expand Down
63 changes: 62 additions & 1 deletion tests/sentry/api/endpoints/test_organization_releases.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,67 @@ def test_commits_from_provider(self):
self.create_member(teams=[team], user=user, organization=org)
self.login_as(user=user)

url = reverse('sentry-api-0-organization-releases', kwargs={
'organization_slug': org.slug
})
response = self.client.post(url, data={
'version': '1.2.1',
'refs': [
{'commit': 'a' * 40, 'repository': repo.name},
{'commit': 'b' * 40, 'repository': repo2.name},
],
'projects': [project.slug]
})
assert response.status_code == 201
# check fake commits from dummy repo provider were created
assert ReleaseCommit.objects.filter(
commit__repository_id=repo.id,
commit__key='62de626b7c7cfb8e77efb4273b1a3df4123e6216',
release__version=response.data['version'],
).exists()
assert ReleaseCommit.objects.filter(
commit__repository_id=repo.id,
commit__key='58de626b7c7cfb8e77efb4273b1a3df4123e6345',
release__version=response.data['version'],
).exists()
assert ReleaseCommit.objects.filter(
commit__repository_id=repo2.id,
commit__key='62de626b7c7cfb8e77efb4273b1a3df4123e6216',
release__version=response.data['version'],
).exists()
assert ReleaseCommit.objects.filter(
commit__repository_id=repo2.id,
commit__key='58de626b7c7cfb8e77efb4273b1a3df4123e6345',
release__version=response.data['version'],
).exists()

def test_commits_from_provider_deprecated_head_commits(self):
user = self.create_user(is_staff=False, is_superuser=False)
org = self.create_organization()
org.flags.allow_joinleave = False
org.save()

repo = Repository.objects.create(
organization_id=org.id,
name='example/example',
provider='dummy',
)
repo2 = Repository.objects.create(
organization_id=org.id,
name='example/example2',
provider='dummy',
)

team = self.create_team(organization=org)
project = self.create_project(
name='foo',
organization=org,
team=team
)

self.create_member(teams=[team], user=user, organization=org)
self.login_as(user=user)

url = reverse('sentry-api-0-organization-releases', kwargs={
'organization_slug': org.slug
})
Expand Down Expand Up @@ -674,7 +735,7 @@ def test_api_token(self):
response = self.client.post(url, data={
'version': '1.2.1',
'headCommits': [
{'currentId': 'a' * 40, 'repository': repo.name},
{'currentId': 'a' * 40, 'repository': repo.name, 'previousId': 'c' * 40},
{'currentId': 'b' * 40, 'repository': repo2.name},
],
'projects': [project1.slug]
Expand Down