Compare commits
1 Commits
bead/nixos
...
bead/nixos
| Author | SHA1 | Date | |
|---|---|---|---|
| f52b1c1d27 |
244
home/roles/development/skills/gitea_pr_review.md
Normal file
244
home/roles/development/skills/gitea_pr_review.md
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
---
|
||||||
|
description: Manage and respond to Gitea/Forgejo PR review comments
|
||||||
|
---
|
||||||
|
|
||||||
|
# Gitea PR Review Comments
|
||||||
|
|
||||||
|
This skill enables reading PR review comments and posting inline thread replies on Gitea/Forgejo instances.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- `tea` CLI configured with a Gitea/Forgejo instance
|
||||||
|
- Access token from tea config: `~/.config/tea/config.yml`
|
||||||
|
- Repository must be a Gitea/Forgejo remote (not GitHub)
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Get the Gitea instance URL and token from tea config:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Get the default login URL and token
|
||||||
|
yq -r '.logins[] | select(.name == "default") | .url' ~/.config/tea/config.yml
|
||||||
|
yq -r '.logins[] | select(.name == "default") | .token' ~/.config/tea/config.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
Or if you have a specific login name:
|
||||||
|
```bash
|
||||||
|
yq -r '.logins[] | select(.name == "YOUR_LOGIN") | .url' ~/.config/tea/config.yml
|
||||||
|
yq -r '.logins[] | select(.name == "YOUR_LOGIN") | .token' ~/.config/tea/config.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
### 1. List PR Review Comments
|
||||||
|
|
||||||
|
Fetch all reviews and their comments for a PR:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Set environment variables
|
||||||
|
GITEA_URL="https://git.johnogle.info"
|
||||||
|
TOKEN="<your-token>"
|
||||||
|
OWNER="<repo-owner>"
|
||||||
|
REPO="<repo-name>"
|
||||||
|
PR_NUMBER="<pr-number>"
|
||||||
|
|
||||||
|
# Get all reviews for the PR
|
||||||
|
curl -s -H "Authorization: token $TOKEN" \
|
||||||
|
"$GITEA_URL/api/v1/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews" | jq
|
||||||
|
|
||||||
|
# Get comments for a specific review
|
||||||
|
REVIEW_ID="<review-id>"
|
||||||
|
curl -s -H "Authorization: token $TOKEN" \
|
||||||
|
"$GITEA_URL/api/v1/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews/$REVIEW_ID/comments" | jq
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. View All Review Comments (Combined)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Get all reviews and their comments in one view
|
||||||
|
curl -s -H "Authorization: token $TOKEN" \
|
||||||
|
"$GITEA_URL/api/v1/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews" | \
|
||||||
|
jq -r '.[] | "Review \(.id) by \(.user.login): \(.state)\n Body: \(.body)"'
|
||||||
|
|
||||||
|
# For each review, show inline comments
|
||||||
|
for REVIEW_ID in $(curl -s -H "Authorization: token $TOKEN" \
|
||||||
|
"$GITEA_URL/api/v1/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews" | jq -r '.[].id'); do
|
||||||
|
echo "=== Review $REVIEW_ID comments ==="
|
||||||
|
curl -s -H "Authorization: token $TOKEN" \
|
||||||
|
"$GITEA_URL/api/v1/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews/$REVIEW_ID/comments" | \
|
||||||
|
jq -r '.[] | "[\(.path):\(.line)] \(.body)"'
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Reply to Review Comments (Web Endpoint Method)
|
||||||
|
|
||||||
|
The Gitea REST API does not support replying to review comment threads. The web UI uses a different endpoint:
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /{owner}/{repo}/pulls/{pr_number}/files/reviews/comments
|
||||||
|
Content-Type: multipart/form-data
|
||||||
|
```
|
||||||
|
|
||||||
|
**Required form fields:**
|
||||||
|
- `reply`: Review ID to reply to
|
||||||
|
- `content`: The reply message
|
||||||
|
- `path`: File path
|
||||||
|
- `line`: Line number
|
||||||
|
- `side`: `proposed` or `original`
|
||||||
|
- `single_review`: `true`
|
||||||
|
- `origin`: `timeline`
|
||||||
|
- `_csrf`: CSRF token (required for web endpoint)
|
||||||
|
|
||||||
|
**Authentication Challenge:**
|
||||||
|
This endpoint requires session-based authentication, not API tokens. Options:
|
||||||
|
|
||||||
|
#### Option A: Use Browser Session (Recommended)
|
||||||
|
|
||||||
|
1. Log in to Gitea in your browser
|
||||||
|
2. Open browser developer tools and copy cookies
|
||||||
|
3. Use the session cookies with curl
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# First, get CSRF token from the PR page
|
||||||
|
CSRF=$(curl -s -c cookies.txt -b cookies.txt \
|
||||||
|
"$GITEA_URL/$OWNER/$REPO/pulls/$PR_NUMBER/files" | \
|
||||||
|
grep -oP 'name="_csrf" value="\K[^"]+')
|
||||||
|
|
||||||
|
# Post the reply
|
||||||
|
curl -s -b cookies.txt \
|
||||||
|
-F "reply=$REVIEW_ID" \
|
||||||
|
-F "content=Your reply message here" \
|
||||||
|
-F "path=$FILE_PATH" \
|
||||||
|
-F "line=$LINE_NUMBER" \
|
||||||
|
-F "side=proposed" \
|
||||||
|
-F "single_review=true" \
|
||||||
|
-F "origin=timeline" \
|
||||||
|
-F "_csrf=$CSRF" \
|
||||||
|
"$GITEA_URL/$OWNER/$REPO/pulls/$PR_NUMBER/files/reviews/comments"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Option B: Create Top-Level Comment (Fallback)
|
||||||
|
|
||||||
|
If thread replies are not critical, use the API to create a top-level comment:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create a top-level comment mentioning the review context
|
||||||
|
curl -s -X POST \
|
||||||
|
-H "Authorization: token $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"body\": \"Re: @reviewer's comment on $FILE_PATH:$LINE_NUMBER\n\nYour reply here\"}" \
|
||||||
|
"$GITEA_URL/api/v1/repos/$OWNER/$REPO/issues/$PR_NUMBER/comments"
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use tea CLI:
|
||||||
|
```bash
|
||||||
|
tea comment $PR_NUMBER "Re: @reviewer's comment on $FILE_PATH:$LINE_NUMBER
|
||||||
|
|
||||||
|
Your reply here"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Submit a New Review
|
||||||
|
|
||||||
|
Create a new review with inline comments:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -X POST \
|
||||||
|
-H "Authorization: token $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"body": "Overall review comments",
|
||||||
|
"event": "COMMENT",
|
||||||
|
"comments": [
|
||||||
|
{
|
||||||
|
"path": "path/to/file.py",
|
||||||
|
"body": "Comment on this line",
|
||||||
|
"new_position": 10
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}' \
|
||||||
|
"$GITEA_URL/api/v1/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews"
|
||||||
|
```
|
||||||
|
|
||||||
|
Event types: `COMMENT`, `APPROVE`, `REQUEST_CHANGES`
|
||||||
|
|
||||||
|
## Workflow Example
|
||||||
|
|
||||||
|
### Reading and Responding to Reviews
|
||||||
|
|
||||||
|
1. **Set up environment**:
|
||||||
|
```bash
|
||||||
|
export GITEA_URL=$(yq -r '.logins[] | select(.name == "default") | .url' ~/.config/tea/config.yml)
|
||||||
|
export TOKEN=$(yq -r '.logins[] | select(.name == "default") | .token' ~/.config/tea/config.yml)
|
||||||
|
export OWNER="johno"
|
||||||
|
export REPO="nixos-configs"
|
||||||
|
export PR_NUMBER="5"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **List all pending review comments**:
|
||||||
|
```bash
|
||||||
|
# Get reviews
|
||||||
|
curl -s -H "Authorization: token $TOKEN" \
|
||||||
|
"$GITEA_URL/api/v1/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews" | \
|
||||||
|
jq -r '.[] | select(.state == "REQUEST_CHANGES" or .state == "COMMENT") |
|
||||||
|
"Review \(.id) by \(.user.login) (\(.state)):\n\(.body)\n"'
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Get detailed comments for a review**:
|
||||||
|
```bash
|
||||||
|
REVIEW_ID="2"
|
||||||
|
curl -s -H "Authorization: token $TOKEN" \
|
||||||
|
"$GITEA_URL/api/v1/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews/$REVIEW_ID/comments" | \
|
||||||
|
jq -r '.[] | "File: \(.path):\(.line)\nComment: \(.body)\nID: \(.id)\n---"'
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Respond using top-level comment** (most reliable):
|
||||||
|
```bash
|
||||||
|
tea comment $PR_NUMBER "Addressing review feedback:
|
||||||
|
|
||||||
|
- File \`path/to/file.py\` line 10: Fixed the issue by...
|
||||||
|
- File \`other/file.py\` line 25: Updated as suggested..."
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### Endpoints
|
||||||
|
|
||||||
|
| Action | Method | Endpoint |
|
||||||
|
|--------|--------|----------|
|
||||||
|
| List reviews | GET | `/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews` |
|
||||||
|
| Get review | GET | `/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}` |
|
||||||
|
| Get review comments | GET | `/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}/comments` |
|
||||||
|
| Create review | POST | `/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews` |
|
||||||
|
| Submit review | POST | `/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}` |
|
||||||
|
| Delete review | DELETE | `/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}` |
|
||||||
|
| Create issue comment | POST | `/api/v1/repos/{owner}/{repo}/issues/{index}/comments` |
|
||||||
|
|
||||||
|
### Review States
|
||||||
|
|
||||||
|
- `PENDING` - Draft review not yet submitted
|
||||||
|
- `COMMENT` - General comment without approval/rejection
|
||||||
|
- `APPROVE` - Approving the changes
|
||||||
|
- `REQUEST_CHANGES` - Requesting changes before merge
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
1. **Thread replies**: The Gitea REST API does not support replying directly to review comment threads. This is a known limitation. Workarounds:
|
||||||
|
- Use top-level comments with context
|
||||||
|
- Use the web UI manually for thread replies
|
||||||
|
- Implement session-based authentication to use the web endpoint
|
||||||
|
|
||||||
|
2. **CSRF tokens**: The web endpoint for thread replies requires CSRF tokens, which expire and need to be fetched from the page.
|
||||||
|
|
||||||
|
3. **Session auth**: API tokens work for REST API but not for web endpoints that require session cookies.
|
||||||
|
|
||||||
|
## Tips
|
||||||
|
|
||||||
|
- Always quote file paths and line numbers when responding via top-level comments
|
||||||
|
- Use `tea pr view $PR_NUMBER --comments` to see all comments
|
||||||
|
- Use `tea open pulls/$PR_NUMBER` to open the PR in browser for manual thread replies
|
||||||
|
- Consider using `tea pr approve $PR_NUMBER` after addressing all comments
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- Gitea API Documentation: https://docs.gitea.com/api/1.20/
|
||||||
|
- `tea` CLI: https://gitea.com/gitea/tea
|
||||||
@@ -8,21 +8,6 @@ in
|
|||||||
{
|
{
|
||||||
options.roles.nfs-mounts = {
|
options.roles.nfs-mounts = {
|
||||||
enable = mkEnableOption "Enable default NFS mounts";
|
enable = mkEnableOption "Enable default NFS mounts";
|
||||||
server = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "10.0.0.43";
|
|
||||||
description = "IP address or hostname of the NFS server";
|
|
||||||
};
|
|
||||||
remotePath = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "/media";
|
|
||||||
description = "Remote path to mount from the NFS server";
|
|
||||||
};
|
|
||||||
mountPoint = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "/media";
|
|
||||||
description = "Local mount point for the NFS share";
|
|
||||||
};
|
|
||||||
# TODO: implement requireMount
|
# TODO: implement requireMount
|
||||||
requireMount = mkOption {
|
requireMount = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
@@ -33,8 +18,8 @@ in
|
|||||||
|
|
||||||
config = mkIf cfg.enable
|
config = mkIf cfg.enable
|
||||||
{
|
{
|
||||||
fileSystems.${cfg.mountPoint} = {
|
fileSystems."/media" = {
|
||||||
device = "${cfg.server}:${cfg.remotePath}";
|
device = "10.0.0.43:/media";
|
||||||
fsType = "nfs";
|
fsType = "nfs";
|
||||||
options = [
|
options = [
|
||||||
"defaults"
|
"defaults"
|
||||||
|
|||||||
@@ -8,21 +8,6 @@ in
|
|||||||
{
|
{
|
||||||
options.roles.printing = {
|
options.roles.printing = {
|
||||||
enable = mkEnableOption "Enable default printing setup";
|
enable = mkEnableOption "Enable default printing setup";
|
||||||
printerName = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "MFC-L8900CDW_series";
|
|
||||||
description = "Name for the default printer";
|
|
||||||
};
|
|
||||||
printerUri = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "ipp://brother.oglehome/ipp/print";
|
|
||||||
description = "Device URI for the default printer (e.g., ipp://hostname/ipp/print)";
|
|
||||||
};
|
|
||||||
printerModel = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "everywhere";
|
|
||||||
description = "PPD model for the printer (use 'everywhere' for driverless IPP)";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf cfg.enable
|
config = mkIf cfg.enable
|
||||||
@@ -36,11 +21,11 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
hardware.printers.ensurePrinters = [{
|
hardware.printers.ensurePrinters = [{
|
||||||
name = cfg.printerName;
|
name = "MFC-L8900CDW_series";
|
||||||
deviceUri = cfg.printerUri;
|
deviceUri = "ipp://brother.oglehome/ipp/print";
|
||||||
model = cfg.printerModel;
|
model = "everywhere";
|
||||||
}];
|
}];
|
||||||
hardware.printers.ensureDefaultPrinter = cfg.printerName;
|
hardware.printers.ensureDefaultPrinter = "MFC-L8900CDW_series";
|
||||||
|
|
||||||
# Fix ensure-printers service to wait for network availability
|
# Fix ensure-printers service to wait for network availability
|
||||||
systemd.services.ensure-printers = {
|
systemd.services.ensure-printers = {
|
||||||
|
|||||||
@@ -8,11 +8,6 @@ in
|
|||||||
{
|
{
|
||||||
options.roles.virtualisation = {
|
options.roles.virtualisation = {
|
||||||
enable = mkEnableOption "Enable virtualisation";
|
enable = mkEnableOption "Enable virtualisation";
|
||||||
dockerUsers = mkOption {
|
|
||||||
type = types.listOf types.str;
|
|
||||||
default = [ "johno" ];
|
|
||||||
description = "List of users to add to the docker group";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf cfg.enable
|
config = mkIf cfg.enable
|
||||||
@@ -20,6 +15,6 @@ in
|
|||||||
virtualisation.libvirtd.enable = true;
|
virtualisation.libvirtd.enable = true;
|
||||||
programs.virt-manager.enable = true;
|
programs.virt-manager.enable = true;
|
||||||
virtualisation.docker.enable = true;
|
virtualisation.docker.enable = true;
|
||||||
users.extraGroups.docker.members = cfg.dockerUsers;
|
users.extraGroups.docker.members = [ "johno" ];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user