Skip to content

Commit 46ea598

Browse files
authored
fix: pass in token to fetch GH default branch (#475)
You can't fetch the default branch for a private repo without a token.
1 parent 66fa857 commit 46ea598

File tree

2 files changed

+54
-11
lines changed

2 files changed

+54
-11
lines changed

src/nitpick/style/fetchers/github.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,21 +34,30 @@ class GitHubURL:
3434
def default_branch(self) -> str:
3535
"""Default GitHub branch."""
3636
# get_default_branch() is memoized
37-
return get_default_branch(self.api_url.url)
37+
return get_default_branch(self.api_url.url, token=self.token)
3838

3939
@property
40-
def credentials(self) -> tuple[str, str] | tuple[()]:
41-
"""Credentials encoded in this URL.
40+
def token(self) -> str | None:
41+
"""Token encoded in this URL.
4242
43-
A tuple of ``(api_token, '')`` if present, or empty tuple otherwise. If
44-
the value of ``api_token`` begins with ``$``, it will be replaced with
45-
the value of the environment corresponding to the remaining part of the
43+
If present and it starts with a ``$``, it will be replaced with the
44+
value of the environment corresponding to the remaining part of the
4645
string.
46+
4747
"""
4848
token = self.auth_token
4949
if token is not None and token.startswith("$"):
5050
token = os.getenv(token[1:])
51+
return token
5152

53+
@property
54+
def credentials(self) -> tuple[str, str] | tuple[()]:
55+
"""Credentials encoded in this URL.
56+
57+
A tuple of ``(api_token, '')`` if present, or empty tuple otherwise.
58+
59+
"""
60+
token = self.token
5261
return (token, "") if token else ()
5362

5463
@property
@@ -128,18 +137,18 @@ def _build_url(self, scheme: str) -> furl:
128137

129138

130139
@lru_cache()
131-
def get_default_branch(api_url: str) -> str:
140+
def get_default_branch(api_url: str, *, token: str | None = None) -> str:
132141
"""Get the default branch from the GitHub repo using the API.
133142
134-
For now, the request is not authenticated on GitHub, so it might hit a rate limit with:
143+
For now, for URLs without an authorization token embedded, the request is
144+
not authenticated on GitHub, so it might hit a rate limit with:
135145
``requests.exceptions.HTTPError: 403 Client Error: rate limit exceeded for url``
136146
137147
This function is using ``lru_cache()`` as a simple memoizer, trying to avoid this rate limit error.
138148
139-
Another option for the future: perform an authenticated request to GitHub.
140-
That would require some user credentials.
141149
"""
142-
response = API_SESSION.get(api_url)
150+
headers = {"Authorization": f"token {token}"} if token else None
151+
response = API_SESSION.get(api_url, headers=headers)
143152
response.raise_for_status()
144153

145154
return response.json()["default_branch"]

tests/test_style.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,40 @@ def test_fetch_private_github_urls(tmp_path):
431431
project.flake8(offline=True).assert_no_errors()
432432

433433

434+
@responses.activate
435+
def test_fetch_private_github_urls_no_branch(tmp_path):
436+
"""Fetch private GitHub URLs with a token on the query string."""
437+
file_token = "query-string-token-generated-by-github-for-private-files"
438+
gh_url = f"gh://{file_token}@user/private_repo/path/to/nitpick-style"
439+
api_url = "https://api.github.com/repos/user/private_repo"
440+
api_response = '{"default_branch": "branch"}'
441+
full_raw_url = f"https://raw.githubusercontent.com/user/private_repo/branch/path/to/nitpick-style{TOML_EXTENSION}"
442+
body = """
443+
["pyproject.toml".tool.black]
444+
missing = "thing"
445+
"""
446+
responses.add(responses.GET, api_url, api_response, status=200)
447+
responses.add(responses.GET, full_raw_url, dedent(body), status=200)
448+
449+
project = ProjectMock(tmp_path).pyproject_toml(
450+
f"""
451+
[tool.nitpick]
452+
style = "{gh_url}"
453+
"""
454+
)
455+
project.flake8(offline=False).assert_single_error(
456+
f"""
457+
NIP318 File pyproject.toml has missing values:{SUGGESTION_BEGIN}
458+
[tool.black]
459+
missing = "thing"{SUGGESTION_END}
460+
"""
461+
)
462+
assert responses.calls[0].request.headers["Authorization"] == f"token {file_token}"
463+
token_on_basic_auth = b64encode(f"{file_token}:".encode()).decode().strip()
464+
assert responses.calls[1].request.headers["Authorization"] == f"Basic {token_on_basic_auth}"
465+
project.flake8(offline=True).assert_no_errors()
466+
467+
434468
@pytest.mark.parametrize(
435469
"style_url",
436470
[

0 commit comments

Comments
 (0)