Add opt-in GPG-signed commits via GitHub API#53
Add opt-in GPG-signed commits via GitHub API#53dragid10 wants to merge 1 commit intosanitizers:masterfrom
Conversation
| ) -> None: | ||
| """Returns a branch with backported PR pushed to GitHub. | ||
| *, signed_commits: bool = False, | ||
| ) -> tuple: |
There was a problem hiding this comment.
This is a bad type. It's full of typing.Any and contradicts the docstring claim.
There was a problem hiding this comment.
updated to tuple[str, str] | None
There was a problem hiding this comment.
Still a bad API. I didn't get to record all the feedback earlier. If you want to return a bunch of related things, have an object for that. A tuple with arbitrary strings isn't really usable. Although, I'm yet to check the other places for why you thought it's needed.
What's the justification for having this optional? Why do we need a feature toggle? |
| # Create a signed commit via the GitHub API and point a new | ||
| # branch at it. Commits created through the Git Data API are | ||
| # automatically signed by GitHub's web-flow GPG key and show | ||
| # as "Verified". |
There was a problem hiding this comment.
Not sure if this bunch of logic probably belongs on this abstraction layer.
| # branch at it. Commits created through the Git Data API are | ||
| # automatically signed by GitHub's web-flow GPG key and show | ||
| # as "Verified". | ||
| tree_sha, commit_message = sync_result |
There was a problem hiding this comment.
An API with hopefully-in-sync-number-of-tuple-items isn't exactly nice to work with. This should be an object. We probably need to move Git REST API interaction into a dedicated class, just like with the comments, the statuses and similar things.
I imagine most wouldn't care if this was the default setting. but since going through the GitHub web signing changes what the committer actually looks like (changes to GitHub), if they were not aware of this change, they might think it odd. And I've also learned over the years that some people just want the choice regardless of if they exercise it or not 😅 |
| # automatically signed by GitHub's web-flow GPG key and show | ||
| # as "Verified". | ||
| tree_sha, commit_message = sync_result | ||
| try: |
There was a problem hiding this comment.
try-blocks must have one single instruction. Otherwise, this causes multiple poorly-visible error handling branches that can originate on multiple lines that are difficult to guess just by looking at them.
| pr_number, target_branch, | ||
| ) | ||
| await pr_reporter.finish_reporting( | ||
| subtitle='💔 cherry-picking failed — could not push', |
There was a problem hiding this comment.
push? this error message is identical to another place, making it difficult to distinguish between them in the logs and the comments. We should make sure to include the context unique to each situation.
| logger.info('Git objects uploaded successfully') | ||
| finally: | ||
| # Always clean up the temporary ref | ||
| try: |
There was a problem hiding this comment.
Avoid nested try-in-finally. This is difficult to reason about. Additionally, such cases could be abstracted away by a CM.
| else: | ||
| logger.info('Git objects uploaded successfully') | ||
| finally: | ||
| # Always clean up the temporary ref |
There was a problem hiding this comment.
Code should speak for itself. If you feel like the code comment is needed, most of the time it's a sign of abstraction layer leaks or problematic variable naming.
| # trees) to GitHub. The ref is deleted immediately after — | ||
| # we only need the objects to exist on GitHub so the API | ||
| # commit can reference the tree SHA. | ||
| temp_ref = f'refs/patchback-tmp/{secrets.token_hex(16)}' |
There was a problem hiding this comment.
This should probably be at least prefixed the same way as the final branch name. Projects with branch protection rulesets might allow certain patterns but not others. Perhaps, just use the final branch name and append /{a_random_thing} at the end — this will likely avoid most of such cases.
| raise PermissionError( | ||
| 'Current GitHub App installation does not grant ' | ||
| 'sufficient privileges for pushing to ' | ||
| f'{repo_remote}. Lacking ' |
There was a problem hiding this comment.
This should mention the ref name.
| 'sufficient privileges for pushing to ' | ||
| f'{repo_remote}. Lacking ' | ||
| '`Contents: write` or `Workflows: write` ' | ||
| 'permissions are known to cause this.\n\n' |
There was a problem hiding this comment.
We may also want to hint the users to check their rulesets for signs of blocking the pushes.
| # Always clean up the temporary ref | ||
| try: | ||
| spawn_proc( | ||
| *git_cmd, 'push', 'origin', f':{temp_ref}', |
There was a problem hiding this comment.
That's a retro-style command. Today, it's git push -d ...
| objects via a temporary ref for signed commit creation. | ||
|
|
||
| When ``signed_commits`` is enabled, returns a | ||
| ``(tree_sha, commit_message)`` tuple. Otherwise returns ``None``. |
There was a problem hiding this comment.
This is probably not needed in the first place. We shouldn't have parts of commit manipulation in disconnected places, anyway.
Let's just get rid of this until somebody asks. I don't want additional complexity that potentially nobody even wants in the first place that I'd be left with to maintain. Less is more. Also, the committer thing is fine — the author is still the GH App which is what's most important: https://api.github.com/repos/dragid10/patchback-test-repo/commits/9b66d01f772a1fd2ba1c010524c5f24872da1895 |
I'm wondering if it'd be a good idea to provide this as a part of the utils in Still, it'd be good to have a well-isolated relocatable piece of logic in a helper for better structuring. The amount of logic in the Note: it'd be good to have a helper capable of going through a chain of commits and signing each of those one by one. This is a little out of the scope but keep it in mind when working on other stuff. |
|
I'm done w/ the review round for now. Since you're already looking into this, I'm open to getting a |
Create backport commits through GitHub's Git Data REST API so they are automatically signed by GitHub's web-flow GPG key and show as "Verified". The cherry-pick still runs locally via git subprocess. Extract git subprocess operations into git_cli.py and GitHub API calls into git_api.py, following the existing one-module-per-API pattern (checks_api.py, comments_api.py, locking_api.py). Ref: sanitizers#1 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4066988 to
b98f3ba
Compare
|
@webknjaz I pushed some new commits to address your feedback! |
|
Yeah, I liked some parts of the previous state more. Now, I'll have to find enough time to compose a full review. No ETA for that. FWIW, it's best to have smaller PRs as atomic patches are easier to review and merge-in faster + “no-go” things that may happen to be in the same PR wouldn't be blocking. One huge commit is going to take time to polish. |
| bad_req_err.status_code != http.client.FORBIDDEN or | ||
| str(bad_req_err) != 'Resource not accessible by integration' | ||
| ): | ||
| raise |
There was a problem hiding this comment.
Does this actually work? What if it's called outside of the exception handling context? Seems rather fragile.
okay would you prefer I go with this direction now? |
|
I think it's a good idea to identify the smallest improvement you can distill and work on that first. In a standalone PR. We may need to pre-agree on what that would be. This could be moving some helpers into a separate model. This could be agreeing to push the low-level sync operations closer to the subprocess invocations and making more logic async-native. |
|
FTR, I've also raised the question of having GH App-bound signing keys that would not require the API dance with some GH folks in private spaces. So long-term, this can end up being simplified if they take on the idea I presented. But probably not in months. |
Apparently, I forgot to click "Submit" yesterday, and this has been sitting unsent in my browser: |
Summary
Backport commits are now created through GitHub's Git Data REST API, so they are automatically signed by GitHub's web-flow GPG key and show as "Verified". No GPG key management needed — no new permissions required.
Git subprocess operations and GitHub API calls have been extracted into dedicated modules, following the existing one-module-per-API pattern (
checks_api.py,comments_api.py,locking_api.py):git_cli.py— git subprocess operations (clone, fetch, cherry-pick, temp ref upload)git_api.py— GitHub Git Data REST API (GitAPIclass withget_branch_head_sha,create_commit,create_branch)event_handlers.py— simplified to orchestration; git and API logic moved outHow it works
--mainlinefor merge commits)POST /repos/{owner}/{repo}/git/commitsPOST /repos/{owner}/{repo}/git/refsRef: #1
Test results
Tested end-to-end with a real GitHub App on dragid10/patchback-test-repo:
d2cbe04: committerGitHub, verified: trueTest plan
GitAPImethods and error handling (23 tests, all passing)pyflakes,vulture)