Skip to content
This repository was archived by the owner on Jun 13, 2025. It is now read-only.
Open
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
118 changes: 85 additions & 33 deletions webhook_handlers/views/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,10 @@
def unhandled_webhook_event(self, request, *args, **kwargs):
return Response(data=WebhookHandlerErrorMessages.UNSUPPORTED_EVENT)

def _get_repo(self, request):
def _get_repo(self):
"""
Attempts to fetch the repo first via the index on o(wnerid, service_id),
then naively on service, service_id if that fails.
Attempts to fetch the repo first via the index on (ownerid, service_id),
then naively on (service, service_id) if that fails.
"""
repo_data = self.request.data.get("repository", {})
repo_service_id = repo_data.get("id")
Expand All @@ -119,67 +119,74 @@
)
except Owner.DoesNotExist:
log.info(
f"Error fetching owner with service_id {owner_service_id}, "
f"using repository service id to get repo",
f"Error fetching owner with service_id {owner_service_id}, using repository service id to get repo",
extra=dict(repo_service_id=repo_service_id, repo_slug=repo_slug),
)
try:
log.info(
"Unable to find repository owner, fetching repo with service, service_id",
extra=dict(repo_service_id=repo_service_id, repo_slug=repo_slug),
)
return Repository.objects.get(
author__service=self.service_name, service_id=repo_service_id
)
except Repository.DoesNotExist:
log.info(
"Received event for non-existent repository",
"Received event for non-existent owner and repository",
extra=dict(repo_service_id=repo_service_id, repo_slug=repo_slug),
)
self._inc_err("repo_not_found")
self._inc_err("owner_and_repo_not_found")
raise NotFound("Repository does not exist")
else:
try:
log.debug(
"Found repository owner, fetching repo with ownerid, service_id",
extra=dict(repo_service_id=repo_service_id, repo_slug=repo_slug),
)
return Repository.objects.get(
author__ownerid=owner.ownerid, service_id=repo_service_id
)
except Repository.DoesNotExist:
log.info(
"Received event for non-existent repository",
extra=dict(repo_service_id=repo_service_id, repo_slug=repo_slug),
)
default_ghapp_installation = owner.github_app_installations.filter(
name=GITHUB_APP_INSTALLATION_DEFAULT_NAME
).first()
if default_ghapp_installation or owner.integration_id:
log.info(
"Repository no found but owner is using integration, creating repository"
"Repository not found but owner is using integration, creating repository"
)
return Repository.objects.get_or_create_from_git_repo(
repo_data, owner
)[0]
log.info(
"Received event for non-existent repository",
extra=dict(repo_service_id=repo_service_id, repo_slug=repo_slug),
)
self._inc_err("repo_not_found")
raise NotFound("Repository does not exist")

def ping(self, request, *args, **kwargs):
return Response(data="pong")

def repository(self, request, *args, **kwargs):
action, repo = self.request.data.get("action"), self._get_repo(request)
action = self.request.data.get("action")

# Repo created event, separate from other actions because we know
# that the repo will not exist in our DB yet
if action == "created":
repo_data = self.request.data.get("repository", {})
repo_service_id = repo_data.get("id")
owner_service_id = repo_data.get("owner", {}).get("id")
owner, _ = Owner.objects.get_or_create(

Check warning on line 171 in webhook_handlers/views/github.py

View check run for this annotation

Codecov Notifications / codecov/patch

webhook_handlers/views/github.py#L168-L171

Added lines #L168 - L171 were not covered by tests
service=self.service_name, service_id=owner_service_id
)
repo, _ = Repository.objects.get_or_create(

Check warning on line 174 in webhook_handlers/views/github.py

View check run for this annotation

Codecov Notifications / codecov/patch

webhook_handlers/views/github.py#L174

Added line #L174 was not covered by tests
author__ownerid=owner.ownerid, service_id=repo_service_id
)
return Response()

Check warning on line 177 in webhook_handlers/views/github.py

View check run for this annotation

Codecov Notifications / codecov/patch

webhook_handlers/views/github.py#L177

Added line #L177 was not covered by tests
Comment on lines +167 to +177
Copy link
Contributor

Choose a reason for hiding this comment

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

does the webhook give us its public/private status? if we can't know whether the repo is meant to be private, i don't think we can create it here

for public repos this would make them show up without having to trigger a sync, but for private repos you still have to sync to populate owner.permission


repo = self._get_repo()
if action == "publicized":
repo.private, repo.activated = False, False
repo.save()
repo.save(update_fields=["private", "activated"])
log.info(
"Repository publicized",
extra=dict(repoid=repo.repoid, github_webhook_event=self.event),
)
elif action == "privatized":
repo.private = True
repo.save()
repo.save(update_fields=["private"])
log.info(
"Repository privatized",
extra=dict(repoid=repo.repoid, github_webhook_event=self.event),
Expand All @@ -195,33 +202,78 @@
"Repository soft-deleted",
extra=dict(repoid=repo.repoid, github_webhook_event=self.event),
)
elif action == "edited":
default_branch = (

Check warning on line 206 in webhook_handlers/views/github.py

View check run for this annotation

Codecov Notifications / codecov/patch

webhook_handlers/views/github.py#L205-L206

Added lines #L205 - L206 were not covered by tests
self.request.data.get("changes", {})
.get("default_branch", {})
.get("from")
)
if default_branch and default_branch != repo.branch:
repo.branch = default_branch
repo.save(update_fields=["branch"])
log.info(

Check warning on line 214 in webhook_handlers/views/github.py

View check run for this annotation

Codecov Notifications / codecov/patch

webhook_handlers/views/github.py#L211-L214

Added lines #L211 - L214 were not covered by tests
"Repository default branch updated",
extra=dict(repoid=repo.repoid, github_webhook_event=self.event),
)
elif action == "renamed":
repo.name = self.request.data.get("repository", {}).get("name")
repo.save(update_fields=["name"])
log.info(

Check warning on line 221 in webhook_handlers/views/github.py

View check run for this annotation

Codecov Notifications / codecov/patch

webhook_handlers/views/github.py#L218-L221

Added lines #L218 - L221 were not covered by tests
"Repository renamed",
extra=dict(repoid=repo.repoid, github_webhook_event=self.event),
)
elif action == "transferred":
repo.author = Owner.objects.get(

Check warning on line 226 in webhook_handlers/views/github.py

View check run for this annotation

Codecov Notifications / codecov/patch

webhook_handlers/views/github.py#L225-L226

Added lines #L225 - L226 were not covered by tests
service=self.service_name,
service_id=self.request.data.get("repository", {})
.get("owner", {})
.get("id"),
)
Comment on lines +226 to +231
Copy link
Contributor

Choose a reason for hiding this comment

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

probably get_or_create here too

repo.save(update_fields=["author"])
log.info(

Check warning on line 233 in webhook_handlers/views/github.py

View check run for this annotation

Codecov Notifications / codecov/patch

webhook_handlers/views/github.py#L232-L233

Added lines #L232 - L233 were not covered by tests
"Repository transferred",
extra=dict(repoid=repo.repoid, github_webhook_event=self.event),
)
elif action == "archived":
repo.activated = False
repo.save(update_fields=["activated"])
log.info(

Check warning on line 240 in webhook_handlers/views/github.py

View check run for this annotation

Codecov Notifications / codecov/patch

webhook_handlers/views/github.py#L237-L240

Added lines #L237 - L240 were not covered by tests
"Repository archived",
extra=dict(repoid=repo.repoid, github_webhook_event=self.event),
)
elif action == "unarchived":
repo.activated = True
repo.save(update_fields=["activated"])
log.info(

Check warning on line 247 in webhook_handlers/views/github.py

View check run for this annotation

Codecov Notifications / codecov/patch

webhook_handlers/views/github.py#L244-L247

Added lines #L244 - L247 were not covered by tests
"Repository unarchived",
extra=dict(repoid=repo.repoid, github_webhook_event=self.event),
)
Comment on lines +237 to +250
Copy link
Contributor

Choose a reason for hiding this comment

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

is this actually what "activated" means in our system? i figured it had plan implications or something

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think so, at least it seemed to make sense according to https://www.notion.so/sentry/Database-Field-Terminology-2c501a0844bc410aa35f1ecfd3c3eb61

else:
log.warning(
f"Unknown repository action: {action}", extra=dict(repoid=repo.repoid)
"Unknown repository action",
extra=dict(action=action, repoid=repo.repoid),
)
return Response()

def delete(self, request, *args, **kwargs):
ref_type = request.data.get("ref_type", "")
repo = self._get_repo(request)
repo = self._get_repo()
if ref_type != "branch":
log.info(
f"Unsupported ref type: {ref_type}, exiting",
extra=dict(repoid=repo.repoid, github_webhook_event=self.event),
)
return Response("Unsupported ref type")
branch_name = self.request.data.get("ref")[11:]
Branch.objects.filter(
repository=self._get_repo(request), name=branch_name
).delete()
Branch.objects.filter(repository=repo, name=branch_name).delete()
log.info(
f"Branch '{branch_name}' deleted",
extra=dict(repoid=repo.repoid, github_webhook_event=self.event),
)
return Response()

def public(self, request, *args, **kwargs):
repo = self._get_repo(request)
repo = self._get_repo()
repo.private, repo.activated = False, False
repo.save()
log.info(
Expand All @@ -232,7 +284,7 @@

def push(self, request, *args, **kwargs):
ref_type = "branch" if request.data.get("ref", "")[5:10] == "heads" else "tag"
repo = self._get_repo(request)
repo = self._get_repo()
if ref_type != "branch":
log.debug(
"Ref is tag, not branch, ignoring push event",
Expand Down Expand Up @@ -320,7 +372,7 @@
return Response()

def status(self, request, *args, **kwargs):
repo = self._get_repo(request)
repo = self._get_repo()
commitid = request.data.get("sha")

if not repo.active:
Expand Down Expand Up @@ -365,7 +417,7 @@
return Response()

def pull_request(self, request, *args, **kwargs):
repo = self._get_repo(request)
repo = self._get_repo()

if not repo.active:
log.info(
Expand Down Expand Up @@ -703,7 +755,7 @@
def member(self, request, *args, **kwargs):
action = request.data["action"]
if action == "removed":
repo = self._get_repo(request)
repo = self._get_repo()
log.info(
"Request to remove read permissions for user",
extra=dict(repoid=repo.repoid, github_webhook_event=self.event),
Expand Down
Loading