Skip to content
Open
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
31 changes: 31 additions & 0 deletions github_activity/github_activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ def get_activity(
"""

org, repo = _parse_target(target)

if repo:
# We have org/repo
search_query = f"repo:{org}/{repo}"
Expand Down Expand Up @@ -192,6 +193,10 @@ def get_activity(
"via the GitHub CLI (`gh auth login`)."
)

# Validate repository exists early if a specific repo was provided
if repo:
_validate_repository_exists(org, repo, auth)

# Figure out dates for our query
since_dt, since_is_git_ref = _get_datetime_and_type(org, repo, since, auth)
until_dt, until_is_git_ref = _get_datetime_and_type(org, repo, until, auth)
Expand Down Expand Up @@ -867,6 +872,32 @@ def _parse_target(target):
return org, repo


def _validate_repository_exists(org, repo, token):
"""Validate that a repository exists on GitHub.

Parameters
----------
org : str
The organization or user name
repo : str
The repository name
token : str
GitHub authentication token

Raises
------
ValueError
If the repository does not exist or is not accessible
"""
auth = TokenAuth(token)
repo_url = f"https://api.github.com/repos/{org}/{repo}"
response = requests.head(repo_url, auth=auth)
if response.status_code == 404:
raise ValueError(
f"Repository '{org}/{repo}' not found. Please check the repository name."
)


def _get_datetime_and_type(org, repo, datetime_or_git_ref, auth):
"""Return a datetime object and bool indicating if it is a git reference or
not."""
Expand Down
34 changes: 30 additions & 4 deletions github_activity/graphql.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,16 +195,42 @@ def request(self, n_pages=100, n_per_page=50):
auth=self.auth,
)
if ii_request.status_code != 200:
# Check for common error cases and provide helpful messages
if ii_request.status_code == 403:
try:
error_data = ii_request.json()
error_message = error_data.get("message", "")
if "rate limit" in error_message.lower():
raise Exception(
f"GitHub API rate limit exceeded. {error_message}\n"
"Please wait before making more requests, or use an authentication token with higher rate limits."
)
else:
# Generic 403 - likely a permissions issue
raise Exception(
f"GitHub API access forbidden. {error_message}\n"
"This usually means your authentication token doesn't have the required permissions.\n"
"Please check that your token has the necessary scopes for this repository."
)
except (ValueError, KeyError):
pass
raise Exception(
"Query failed to run by returning code of {}. {}".format(
ii_request.status_code, ii_gql_query
)
)
if "errors" in ii_request.json().keys():
errors = ii_request.json().get("errors")
if errors:
# Check for rate limit errors in GraphQL response
for error in errors:
if error.get("type") == "RATE_LIMITED":
error_message = error.get("message", "Rate limit exceeded")
raise Exception(
f"GitHub API rate limit exceeded. {error_message}\n"
"Please wait before making more requests, or use an authentication token with higher rate limits."
)
raise Exception(
"Query failed to run with error {}. {}".format(
ii_request.json()["errors"], ii_gql_query
)
"Query failed to run with error {}. {}".format(errors, ii_gql_query)
)
self.last_request = ii_request

Expand Down
23 changes: 23 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,26 @@ def test_changelog_features(file_regression):
assert "@dependabot" not in md_full.lower(), (
"Bot user dependabot should not appear in output"
)


def test_invalid_repository_error():
"""Test that invalid repository names produce clear error messages."""
from github_activity.github_activity import get_activity
import pytest

# Test with an invalid repository name
with pytest.raises(ValueError) as exc_info:
get_activity(
target="invalid-org/nonexistent-repo-12345",
since="2021-01-01",
until="2021-01-15",
)

# Verify the error message mentions the repository
error_message = str(exc_info.value)
assert "repository" in error_message.lower(), (
f"Error should mention repository, got: {error_message}"
)
assert "invalid-org/nonexistent-repo-12345" in error_message, (
f"Error should include the repo name, got: {error_message}"
)