def validate_type(self, attrs, source): value = attrs[source] if not CommitFileChange.is_valid_type(value): raise serializers.ValidationError('Commit patch_set type %s is not supported.' % value) return attrs
def validate_type(self, value): if not CommitFileChange.is_valid_type(value): raise serializers.ValidationError("Commit patch_set type %s is not supported." % value) return value
def set_commits(self, commit_list): """ Bind a list of commits to this release. This will clear any existing commit log and replace it with the given commits. """ # Sort commit list in reverse order commit_list.sort(key=lambda commit: commit.get("timestamp", 0), reverse=True) # TODO(dcramer): this function could use some cleanup/refactoring as it's a bit unwieldy from sentry.models import ( Commit, CommitAuthor, Group, GroupLink, GroupResolution, GroupStatus, PullRequest, ReleaseCommit, ReleaseHeadCommit, Repository, ) from sentry.plugins.providers.repository import RepositoryProvider from sentry.tasks.integrations import kick_off_status_syncs # todo(meredith): implement for IntegrationRepositoryProvider commit_list = [ c for c in commit_list if not RepositoryProvider.should_ignore_commit(c.get("message", "")) ] lock_key = type(self).get_lock_key(self.organization_id, self.id) lock = locks.get(lock_key, duration=10) if lock.locked(): # Signal failure to the consumer rapidly. This aims to prevent the number # of timeouts and prevent web worker exhaustion when customers create # the same release rapidly for different projects. raise ReleaseCommitError with TimedRetryPolicy(10)(lock.acquire): start = time() with atomic_transaction(using=( router.db_for_write(type(self)), router.db_for_write(ReleaseCommit), router.db_for_write(Repository), router.db_for_write(CommitAuthor), router.db_for_write(Commit), )): # TODO(dcramer): would be good to optimize the logic to avoid these # deletes but not overly important ReleaseCommit.objects.filter(release=self).delete() authors = {} repos = {} commit_author_by_commit = {} head_commit_by_repo = {} latest_commit = None for idx, data in enumerate(commit_list): repo_name = data.get( "repository") or f"organization-{self.organization_id}" if repo_name not in repos: repos[ repo_name] = repo = Repository.objects.get_or_create( organization_id=self.organization_id, name=repo_name)[0] else: repo = repos[repo_name] author_email = data.get("author_email") if author_email is None and data.get("author_name"): author_email = (re.sub(r"[^a-zA-Z0-9\-_\.]*", "", data["author_name"]).lower() + "@localhost") author_email = truncatechars(author_email, 75) if not author_email: author = None elif author_email not in authors: author_data = {"name": data.get("author_name")} author, created = CommitAuthor.objects.get_or_create( organization_id=self.organization_id, email=author_email, defaults=author_data, ) if author.name != author_data["name"]: author.update(name=author_data["name"]) authors[author_email] = author else: author = authors[author_email] commit_data = {} # Update/set message and author if they are provided. if author is not None: commit_data["author"] = author if "message" in data: commit_data["message"] = data["message"] if "timestamp" in data: commit_data["date_added"] = data["timestamp"] commit, created = Commit.objects.get_or_create( organization_id=self.organization_id, repository_id=repo.id, key=data["id"], defaults=commit_data, ) if not created: commit_data = { key: value for key, value in commit_data.items() if getattr(commit, key) != value } if commit_data: commit.update(**commit_data) if author is None: author = commit.author commit_author_by_commit[commit.id] = author # Guard against patch_set being None patch_set = data.get("patch_set") or [] if patch_set: CommitFileChange.objects.bulk_create( [ CommitFileChange( organization_id=self.organization.id, commit=commit, filename=patched_file["path"], type=patched_file["type"], ) for patched_file in patch_set ], ignore_conflicts=True, ) try: with atomic_transaction( using=router.db_for_write(ReleaseCommit)): ReleaseCommit.objects.create( organization_id=self.organization_id, release=self, commit=commit, order=idx, ) except IntegrityError: pass if latest_commit is None: latest_commit = commit head_commit_by_repo.setdefault(repo.id, commit.id) self.update( commit_count=len(commit_list), authors=[ str(a_id) for a_id in ReleaseCommit.objects.filter( release=self, commit__author_id__isnull=False). values_list("commit__author_id", flat=True).distinct() ], last_commit_id=latest_commit.id if latest_commit else None, ) metrics.timing("release.set_commits.duration", time() - start) # fill any missing ReleaseHeadCommit entries for repo_id, commit_id in head_commit_by_repo.items(): try: with atomic_transaction( using=router.db_for_write(ReleaseHeadCommit)): ReleaseHeadCommit.objects.create( organization_id=self.organization_id, release_id=self.id, repository_id=repo_id, commit_id=commit_id, ) except IntegrityError: pass release_commits = list( ReleaseCommit.objects.filter( release=self).select_related("commit").values( "commit_id", "commit__key")) commit_resolutions = list( GroupLink.objects.filter( linked_type=GroupLink.LinkedType.commit, linked_id__in=[rc["commit_id"] for rc in release_commits], ).values_list("group_id", "linked_id")) commit_group_authors = [ (cr[0], commit_author_by_commit.get(cr[1])) for cr in commit_resolutions # group_id ] pr_ids_by_merge_commit = list( PullRequest.objects.filter( merge_commit_sha__in=[ rc["commit__key"] for rc in release_commits ], organization_id=self.organization_id, ).values_list("id", flat=True)) pull_request_resolutions = list( GroupLink.objects.filter( relationship=GroupLink.Relationship.resolves, linked_type=GroupLink.LinkedType.pull_request, linked_id__in=pr_ids_by_merge_commit, ).values_list("group_id", "linked_id")) pr_authors = list( PullRequest.objects.filter( id__in=[prr[1] for prr in pull_request_resolutions ]).select_related("author")) pr_authors_dict = {pra.id: pra.author for pra in pr_authors} pull_request_group_authors = [(prr[0], pr_authors_dict.get(prr[1])) for prr in pull_request_resolutions] user_by_author = {None: None} commits_and_prs = list( itertools.chain(commit_group_authors, pull_request_group_authors)) group_project_lookup = dict( Group.objects.filter( id__in=[group_id for group_id, _ in commits_and_prs]).values_list( "id", "project_id")) for group_id, author in commits_and_prs: if author not in user_by_author: try: user_by_author[author] = author.find_users()[0] except IndexError: user_by_author[author] = None actor = user_by_author[author] with atomic_transaction(using=( router.db_for_write(GroupResolution), router.db_for_write(Group), # inside the remove_group_from_inbox router.db_for_write(GroupInbox), router.db_for_write(Activity), )): GroupResolution.objects.create_or_update( group_id=group_id, values={ "release": self, "type": GroupResolution.Type.in_release, "status": GroupResolution.Status.resolved, "actor_id": actor.id if actor else None, }, ) group = Group.objects.get(id=group_id) group.update(status=GroupStatus.RESOLVED) remove_group_from_inbox(group, action=GroupInboxRemoveAction.RESOLVED, user=actor) record_group_history(group, GroupHistoryStatus.RESOLVED, actor=actor) metrics.incr("group.resolved", instance="in_commit", skip_internal=True) issue_resolved.send_robust( organization_id=self.organization_id, user=actor, group=group, project=group.project, resolution_type="with_commit", sender=type(self), ) kick_off_status_syncs.apply_async( kwargs={ "project_id": group_project_lookup[group_id], "group_id": group_id })
def validate_type(self, attrs, source): value = attrs[source] if not CommitFileChange.is_valid_type(value): raise serializers.ValidationError( 'Commit patch_set type %s is not supported.' % value) return attrs