feat(forge): pr comment --attach puts pictures in the comment #254

Merged
HumanBean merged 1 commit from tonesi/Longshot:feat/forge-comment-attach into main 2026-11-13 18:09:27 +00:00
Contributor

Why

Test results need screenshots (the testing skill asks for them in its report), but forge.py pr comment can post text
only, so every tester puts their pictures up some other way. With --attach, the pictures go into the comment from the
same command.

What changed

  • forge: pr comment N [TEXT | --body TEXT | --body-file F] [--attach FILE ...] in scripts/forge.py.
    post_with_pictures() posts the comment, puts each file up as an attachment of it (upload_picture() and
    api_upload(): POST /repos/{owner}/{repo}/issues/comments/{id}/assets, multipart field attachment, the real
    name in ?name=), then edits the comment (PATCH /repos/{owner}/{repo}/issues/comments/{id}) so the pictures show.
  • forge: in the text, a link target attach:NAME (![main at 21:9](attach:main-21x9.png); NAME as given or
    percent-encoded) becomes that file's browser_download_url; a file the text doesn't show goes at the end as
    ![NAME](url) (placeholder_body(), show_pictures()). An attach: naming no --attach file is refused.
  • forge: check_picture() and read_pictures(): only PNG, JPEG, GIF and WebP, by extension and by magic bytes (they
    must agree), 25 MiB at most each (ATTACH_MAX), all checked before anything is posted. A refusal lists every bad file
    and points to CONTRIBUTING.md's rule that no game files or anything made from them leave the PC (screenshots are
    fine). Each file is read once, so what is checked is what goes up.
  • forge: an upload that fails stops there, with no retry. The comment's link is printed first, then the error lists
    which pictures went up, which failed (with the server's answer) and which weren't tried. The comment is edited to show
    the ones that went up, and the rest keep their attach:NAME. Without --attach, pr comment runs exactly the line it
    ran before.
  • tests: tests/test_forge_pr.py: the stand-in Forgejo gets comment ids, the comment edit and the attachment upload.
    Six new tests: no --attach posts the text as before (an attach: in it untouched); placeholders replaced and unshown
    pictures appended, in the order post, uploads, edit; odd names (UTF-8, brackets, spaces) in ?name=, in the
    placeholder and percent-encoded in a reference; refusals before posting (missing file, folder, .nso, .bin with a
    PNG's bytes, .png with ELF bytes, .png with JPEG bytes, empty file, two files of one name, too big, an attach:
    with no file); a failed upload (the link, what went up, failed and wasn't tried, no retry, the partial edit; a first
    upload failing; the edit failing); and the upload request itself (URL, headers and the multipart form read back with
    email.parser, a short answer, HTTP 413, no connection).
  • docs: docs/forge.md: the pr comment usage lines and a short Pictures in a comment section.
    .claude/skills/testing/SKILL.md, section 5: one line saying screenshots go up with --attach.

Flags and settings

Flag What it does Default now Recommendation Helps on Doesn't help on Untested on
forge.py pr comment --attach FILE ... Puts pictures (PNG, JPEG, GIF, WebP, 25 MiB at most each) in the comment as its attachments Not used unless given A command option: use it when a comment needs pictures Test results and reviews with screenshots Anything not a picture: refused on purpose Windows; a real post with this code (the same API calls posted comment 13030)

Measurements

A change to a script: measured by its game-free checks.

main: fbd42024 (2026-11-13)
this PR: feat/forge-comment-attach @ 79475c05
Hardware: i7-12700K, 15 GB RAM, Pop!_OS 24.04 (Linux 7.2), Python 3.12.3; no GPU involved
Scene: none (game-free: a script and its checks)
Method: tests/run_all.py --jobs 8 through this PC's test gate (idle class, one run at a time, fresh HOME), two runs
on each side, interleaved (this PR, main, this PR, main); this PR's tests/test_forge_pr.py also run against main's
forge.py. The API calls themselves were used for real before this PR, by a standalone script with the same calls:
#95 (comment) has 8 PNG attachments, which Forgejo
accepted, and the edit that shows them.
How to reproduce: python -I -B tests/run_all.py; python -I -B tests/test_forge_pr.py

main @ fbd42024 this PR @ 79475c05
tests/run_all.py, median of 2 (both runs alike) 500 passed, 0 failed, 29 skipped 500 passed, 0 failed, 29 skipped
tests/test_forge_pr.py (each side's own) 25 tests, OK 31 tests, OK
this PR's tests/test_forge_pr.py against the side's forge.py 26 pass, 5 errors (the attach tests: no --attach on main) 31 pass

The 29 skips are the same on both sides: Windows-only checks, tools this PC lacks (spirv-val, glslang, dav1d), a
Vulkan device, and the game's dump. scripts/forge.py and tests/test_forge_pr.py are the same on main at 2e1e2e8c
and fbd42024, so the rows against main's forge.py (run at 2e1e2e8c) hold at fbd42024. Before the last rebase, two
pairs at main @ 2e1e2e8c and this PR @ 138d8a32 (the same change with a 10 MiB limit) gave 500 passed, 0 failed, 29
skipped on each side, and one pair before that (main @ 7315957c, this PR @ 9601b665) 499 passed on each side.

this PR @ 79475c05, run 1: 500 passed, 0 failed, 29 skipped in 116 s (linux, 8 at once)
main @ fbd42024, run 1:    500 passed, 0 failed, 29 skipped in 116 s (linux, 8 at once)
this PR @ 79475c05, run 2: 500 passed, 0 failed, 29 skipped in 115 s (linux, 8 at once)
main @ fbd42024, run 2:    500 passed, 0 failed, 29 skipped in 116 s (linux, 8 at once)

this PR @ 79475c05, tests/test_forge_pr.py:
Ran 31 tests in 0.260s
OK

main @ 2e1e2e8c's forge.py with this PR's tests/test_forge_pr.py:
ERROR: test_comment_attach_names (__main__.PR.test_comment_attach_names)
ERROR: test_comment_attach_refuses_before_posting (__main__.PR.test_comment_attach_refuses_before_posting)
ERROR: test_comment_attach_shows_the_pictures (__main__.PR.test_comment_attach_shows_the_pictures)
ERROR: test_comment_attach_upload_fails (__main__.PR.test_comment_attach_upload_fails)
ERROR: test_upload_sends_the_form (__main__.PR.test_upload_sends_the_form)
Ran 31 tests in 0.380s
FAILED (errors=5)

main @ 2e1e2e8c: python -I scripts/forge.py pr comment 5 "x" --attach a.png
forge.py: error: unrecognized arguments: --attach a.png

Testing

Tested: on this PC (Linux), game-free:

  • the whole suite, twice on each side, as in Measurements;
  • tests/test_forge_pr.py: the 25 earlier tests unchanged and the 6 new ones above, all passing;
  • the new tests against main's forge.py (a scratch copy where the stand-in's api_upload patch is made with
    create=True, so the file loads): the 5 attach tests fail and the no---attach test passes;
  • four deliberate breakages, each caught by a test: no stop after a failed upload, no first-bytes check, no appending
    of unshown pictures, no refusal of an attach: with no file;
  • forge.py pr comment --help.

Not tested:

  • In the game: nothing the game builds or runs changes (a script, its test and docs), so there is no run to make.
  • A real post with this code: it hasn't been run against git.magicbean.dev. The same three calls (post, upload as the
    comment's attachment, edit) posted #95 (comment) from a
    standalone script; this PR writes them in forge.py's own style. The first real use (a test result with
    screenshots) should print the comment's link, then "N pictures in the comment", with the pictures visible.
  • JPEG, GIF and WebP on the server: only PNGs have gone up for real. The server's [attachment] ALLOWED_TYPES decides
    (Forgejo's default list has all four); a refusal would show as a failed upload with the server's answer.
  • Windows: no Windows PC here. The code is the standard library only, the paths go through open() and os.path, and
    the names are percent-encoded UTF-8 in ?name=.

Needs other testers:

  • Windows: python -I -B tests/test_forge_pr.py, and one real pr comment <n> --body-file F --attach shot.png
    from a folder with a non-ASCII name, expecting 31 tests OK and the picture in the comment (T-XZWG)

Screenshots

Nothing on screen: a script. What the same API calls produce on a pull request:
#95 (comment)

Risk and rollback

  • Only pr comment with --attach changes; without it the command runs the same line as before. Reverting the commit
    removes it.
  • A failed upload leaves a posted comment that shows attach:NAME for the pictures that didn't go up. The error says
    so, with the comment's link and what to do (add the rest by hand or in a new comment); there is no retry and no
    second post.
  • The guard is by file type and size, not by what a picture shows: a texture dumped from the game as a PNG would pass.
    That stays the poster's responsibility (CONTRIBUTING.md).
  • 25 MiB a picture: game frames on this PC take 1.75 to 3 bytes a pixel as PNG (a 1920x1752 RGBA shot is 5.6 MiB), so
    a 4K PNG is about 14 to 24 MiB and fits. Pictures can then stay lossless, which a comment needs when it says two
    shots are pixel-identical. Larger ones are refused with "crop it, or save it as JPEG or WebP". ATTACH_MAX is one
    constant: the server's [attachment] MAX_SIZE is 2048 MB, and Cloudflare cuts an upload at 100 MB
    (docs/releases.md).
  • --attach takes several files, so a TEXT written after it is read as a file (and refused as missing): TEXT goes
    before --attach, or in --body (the help and docs/forge.md say so).
  • On Windows, printing a file name outside the console's code page (an emoji, CJK) into a pipe can fail until
    #120 makes forge.py's output UTF-8; the post, the uploads and
    the edit all happen before any file name is printed.

Reviewer notes

  • Where to look: post_with_pictures() and check_picture() in scripts/forge.py.
  • Choices: the comment is posted first with the attach:NAME placeholders and edited once the pictures are up (the
    attachments belong to the comment, so it has to exist first; a notification e-mail carries that first version). An
    upload that fails stops the rest rather than trying each, since a failure is most likely the connection or the
    sign-in. The form's file name is ASCII only; the real name goes in ?name=, which Forgejo takes first.
  • Left for later on purpose: issue comment --attach (the same functions would serve it) and pictures in
    pr update --comment.
  • #120 touches main() and tests/test_forge_pr.py too, not
    the same lines: whichever merges second rebases.

Issues

None.

Checklist

For everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description).
  • Rebased on current main.
  • python -I -B tests/run_all.py run and passing (any failure that also fails on main is named above).
  • Tested in the game, or what wasn't is named under Testing.
  • Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests).
  • Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported.
  • No game files, dumps, build output or generated files.
  • third_party/ untouched (a new folder with a PROVENANCE.md instead).
  • The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled.

If applicable

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • A game-free test added for the fix, failing on main and passing here.
  • A check under tests/ that reads changed source updated in this PR, not weakened.
  • Docs updated.

🤖 Generated with Claude Code

## Why Test results need screenshots (the testing skill asks for them in its report), but `forge.py pr comment` can post text only, so every tester puts their pictures up some other way. With `--attach`, the pictures go into the comment from the same command. ## What changed - forge: `pr comment N [TEXT | --body TEXT | --body-file F] [--attach FILE ...]` in `scripts/forge.py`. `post_with_pictures()` posts the comment, puts each file up as an attachment of it (`upload_picture()` and `api_upload()`: `POST /repos/{owner}/{repo}/issues/comments/{id}/assets`, multipart field `attachment`, the real name in `?name=`), then edits the comment (`PATCH /repos/{owner}/{repo}/issues/comments/{id}`) so the pictures show. - forge: in the text, a link target `attach:NAME` (`![main at 21:9](attach:main-21x9.png)`; NAME as given or percent-encoded) becomes that file's `browser_download_url`; a file the text doesn't show goes at the end as `![NAME](url)` (`placeholder_body()`, `show_pictures()`). An `attach:` naming no `--attach` file is refused. - forge: `check_picture()` and `read_pictures()`: only PNG, JPEG, GIF and WebP, by extension and by magic bytes (they must agree), 25 MiB at most each (`ATTACH_MAX`), all checked before anything is posted. A refusal lists every bad file and points to CONTRIBUTING.md's rule that no game files or anything made from them leave the PC (screenshots are fine). Each file is read once, so what is checked is what goes up. - forge: an upload that fails stops there, with no retry. The comment's link is printed first, then the error lists which pictures went up, which failed (with the server's answer) and which weren't tried. The comment is edited to show the ones that went up, and the rest keep their `attach:NAME`. Without `--attach`, `pr comment` runs exactly the line it ran before. - tests: `tests/test_forge_pr.py`: the stand-in Forgejo gets comment ids, the comment edit and the attachment upload. Six new tests: no `--attach` posts the text as before (an `attach:` in it untouched); placeholders replaced and unshown pictures appended, in the order post, uploads, edit; odd names (UTF-8, brackets, spaces) in `?name=`, in the placeholder and percent-encoded in a reference; refusals before posting (missing file, folder, `.nso`, `.bin` with a PNG's bytes, `.png` with ELF bytes, `.png` with JPEG bytes, empty file, two files of one name, too big, an `attach:` with no file); a failed upload (the link, what went up, failed and wasn't tried, no retry, the partial edit; a first upload failing; the edit failing); and the upload request itself (URL, headers and the multipart form read back with `email.parser`, a short answer, HTTP 413, no connection). - docs: `docs/forge.md`: the `pr comment` usage lines and a short *Pictures in a comment* section. `.claude/skills/testing/SKILL.md`, section 5: one line saying screenshots go up with `--attach`. ## Flags and settings | Flag | What it does | Default now | Recommendation | Helps on | Doesn't help on | Untested on | |---|---|---|---|---|---|---| | `forge.py pr comment --attach FILE ...` | Puts pictures (PNG, JPEG, GIF, WebP, 25 MiB at most each) in the comment as its attachments | Not used unless given | A command option: use it when a comment needs pictures | Test results and reviews with screenshots | Anything not a picture: refused on purpose | Windows; a real post with this code (the same API calls posted comment 13030) | ## Measurements A change to a script: measured by its game-free checks. main: fbd42024 (2026-11-13) this PR: feat/forge-comment-attach @ 79475c05 Hardware: i7-12700K, 15 GB RAM, Pop!_OS 24.04 (Linux 7.2), Python 3.12.3; no GPU involved Scene: none (game-free: a script and its checks) Method: `tests/run_all.py --jobs 8` through this PC's test gate (idle class, one run at a time, fresh HOME), two runs on each side, interleaved (this PR, main, this PR, main); this PR's `tests/test_forge_pr.py` also run against main's `forge.py`. The API calls themselves were used for real before this PR, by a standalone script with the same calls: https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/95#issuecomment-13030 has 8 PNG attachments, which Forgejo accepted, and the edit that shows them. How to reproduce: `python -I -B tests/run_all.py`; `python -I -B tests/test_forge_pr.py` | | main @ fbd42024 | this PR @ 79475c05 | |---|---|---| | `tests/run_all.py`, median of 2 (both runs alike) | 500 passed, 0 failed, 29 skipped | 500 passed, 0 failed, 29 skipped | | `tests/test_forge_pr.py` (each side's own) | 25 tests, OK | 31 tests, OK | | this PR's `tests/test_forge_pr.py` against the side's `forge.py` | 26 pass, 5 errors (the attach tests: no `--attach` on main) | 31 pass | The 29 skips are the same on both sides: Windows-only checks, tools this PC lacks (spirv-val, glslang, dav1d), a Vulkan device, and the game's dump. `scripts/forge.py` and `tests/test_forge_pr.py` are the same on main at 2e1e2e8c and fbd42024, so the rows against main's `forge.py` (run at 2e1e2e8c) hold at fbd42024. Before the last rebase, two pairs at main @ 2e1e2e8c and this PR @ 138d8a32 (the same change with a 10 MiB limit) gave 500 passed, 0 failed, 29 skipped on each side, and one pair before that (main @ 7315957c, this PR @ 9601b665) 499 passed on each side. ``` this PR @ 79475c05, run 1: 500 passed, 0 failed, 29 skipped in 116 s (linux, 8 at once) main @ fbd42024, run 1: 500 passed, 0 failed, 29 skipped in 116 s (linux, 8 at once) this PR @ 79475c05, run 2: 500 passed, 0 failed, 29 skipped in 115 s (linux, 8 at once) main @ fbd42024, run 2: 500 passed, 0 failed, 29 skipped in 116 s (linux, 8 at once) this PR @ 79475c05, tests/test_forge_pr.py: Ran 31 tests in 0.260s OK main @ 2e1e2e8c's forge.py with this PR's tests/test_forge_pr.py: ERROR: test_comment_attach_names (__main__.PR.test_comment_attach_names) ERROR: test_comment_attach_refuses_before_posting (__main__.PR.test_comment_attach_refuses_before_posting) ERROR: test_comment_attach_shows_the_pictures (__main__.PR.test_comment_attach_shows_the_pictures) ERROR: test_comment_attach_upload_fails (__main__.PR.test_comment_attach_upload_fails) ERROR: test_upload_sends_the_form (__main__.PR.test_upload_sends_the_form) Ran 31 tests in 0.380s FAILED (errors=5) main @ 2e1e2e8c: python -I scripts/forge.py pr comment 5 "x" --attach a.png forge.py: error: unrecognized arguments: --attach a.png ``` ## Testing **Tested:** on this PC (Linux), game-free: - the whole suite, twice on each side, as in Measurements; - `tests/test_forge_pr.py`: the 25 earlier tests unchanged and the 6 new ones above, all passing; - the new tests against main's `forge.py` (a scratch copy where the stand-in's `api_upload` patch is made with `create=True`, so the file loads): the 5 attach tests fail and the no-`--attach` test passes; - four deliberate breakages, each caught by a test: no stop after a failed upload, no first-bytes check, no appending of unshown pictures, no refusal of an `attach:` with no file; - `forge.py pr comment --help`. **Not tested:** - In the game: nothing the game builds or runs changes (a script, its test and docs), so there is no run to make. - A real post with this code: it hasn't been run against git.magicbean.dev. The same three calls (post, upload as the comment's attachment, edit) posted https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/95#issuecomment-13030 from a standalone script; this PR writes them in `forge.py`'s own style. The first real use (a test result with screenshots) should print the comment's link, then "N pictures in the comment", with the pictures visible. - JPEG, GIF and WebP on the server: only PNGs have gone up for real. The server's `[attachment] ALLOWED_TYPES` decides (Forgejo's default list has all four); a refusal would show as a failed upload with the server's answer. - Windows: no Windows PC here. The code is the standard library only, the paths go through `open()` and `os.path`, and the names are percent-encoded UTF-8 in `?name=`. **Needs other testers:** - Windows: `python -I -B tests/test_forge_pr.py`, and one real `pr comment <n> --body-file F --attach shot.png` from a folder with a non-ASCII name, expecting 31 tests OK and the picture in the comment (T-XZWG) ## Screenshots Nothing on screen: a script. What the same API calls produce on a pull request: https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/95#issuecomment-13030 ## Risk and rollback - Only `pr comment` with `--attach` changes; without it the command runs the same line as before. Reverting the commit removes it. - A failed upload leaves a posted comment that shows `attach:NAME` for the pictures that didn't go up. The error says so, with the comment's link and what to do (add the rest by hand or in a new comment); there is no retry and no second post. - The guard is by file type and size, not by what a picture shows: a texture dumped from the game as a PNG would pass. That stays the poster's responsibility (CONTRIBUTING.md). - 25 MiB a picture: game frames on this PC take 1.75 to 3 bytes a pixel as PNG (a 1920x1752 RGBA shot is 5.6 MiB), so a 4K PNG is about 14 to 24 MiB and fits. Pictures can then stay lossless, which a comment needs when it says two shots are pixel-identical. Larger ones are refused with "crop it, or save it as JPEG or WebP". `ATTACH_MAX` is one constant: the server's `[attachment] MAX_SIZE` is 2048 MB, and Cloudflare cuts an upload at 100 MB (docs/releases.md). - `--attach` takes several files, so a TEXT written after it is read as a file (and refused as missing): TEXT goes before `--attach`, or in `--body` (the help and docs/forge.md say so). - On Windows, printing a file name outside the console's code page (an emoji, CJK) into a pipe can fail until https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/120 makes forge.py's output UTF-8; the post, the uploads and the edit all happen before any file name is printed. ## Reviewer notes - Where to look: `post_with_pictures()` and `check_picture()` in `scripts/forge.py`. - Choices: the comment is posted first with the `attach:NAME` placeholders and edited once the pictures are up (the attachments belong to the comment, so it has to exist first; a notification e-mail carries that first version). An upload that fails stops the rest rather than trying each, since a failure is most likely the connection or the sign-in. The form's file name is ASCII only; the real name goes in `?name=`, which Forgejo takes first. - Left for later on purpose: `issue comment --attach` (the same functions would serve it) and pictures in `pr update --comment`. - https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/120 touches `main()` and `tests/test_forge_pr.py` too, not the same lines: whichever merges second rebases. ## Issues None. ## Checklist ### For everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`). - [x] Rebased on current `main`. - [x] `python -I -B tests/run_all.py` run and passing (any failure that also fails on `main` is named above). - [x] Tested in the game, or what wasn't is named under Testing. - [x] Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests). - [x] Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported. - [x] No game files, dumps, build output or generated files. - [x] `third_party/` untouched (a new folder with a `PROVENANCE.md` instead). - [x] The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled. ### If applicable - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] A game-free test added for the fix, failing on `main` and passing here. - [x] A check under `tests/` that reads changed source updated in this PR, not weakened. - [x] Docs updated. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(forge): pr comment --attach puts pictures in the comment
Some checks are pending
CI / Conventional Commit title (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Blocked by required conditions
Third-party parts / The same as upstream (pull_request) Blocked by required conditions
Third-party parts / As their notes say (pull_request) Blocked by required conditions
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Blocked by required conditions
CI / Workflow lint (actionlint) (pull_request) Blocked by required conditions
PR description / Description follows the template (pull_request_target) Successful in 3s
79475c0521
pr comment could post text only, but test results ask for screenshots
(.claude/skills/testing/SKILL.md, section 5), so every tester worked
around it. --attach FILE ... posts the comment, uploads each file as an
attachment of it (POST issues/comments/{id}/assets) and edits the
comment so the pictures show: attach:NAME in the text becomes the
file's link, and a file the text doesn't show goes at the end.

Only PNG, JPEG, GIF and WebP of 25 MiB at most, by extension and by
first bytes, checked before anything is posted (no game files leave the
PC: CONTRIBUTING.md), as is an attach: with no file. A failed upload
stops there, with no retry, and names the comment, what went up and
what didn't. Without --attach, pr comment is unchanged.

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Author
Contributor

Rebased onto main fbd42024 and raised the picture limit from 10 to 25 MiB, so a 4K PNG goes up as it is (lossless, for pixel comparisons). Measured again at 79475c05 against main fbd42024: two interleaved pairs, 500 passed, 0 failed, 29 skipped on each side. Windows test request: T-XZWG.

Rebased onto main fbd42024 and raised the picture limit from 10 to 25 MiB, so a 4K PNG goes up as it is (lossless, for pixel comparisons). Measured again at 79475c05 against main fbd42024: two interleaved pairs, 500 passed, 0 failed, 29 skipped on each side. Windows test request: T-XZWG.
tonesi changed title from WIP: feat(forge): pr comment --attach puts pictures in the comment to feat(forge): pr comment --attach puts pictures in the comment 2026-11-13 17:51:09 +00:00
HumanBean added this to the v3.0.0 milestone 2026-11-13 18:07:04 +00:00
HumanBean approved these changes 2026-11-13 18:09:19 +00:00
HumanBean left a comment

Approve. I read the code and did not run it. This is a careful change, and it can merge as it is.

What's good:

  • The upload uses Forgejo's comment-assets endpoint correctly (POST /repos/{owner}/{repo}/issues/comments/{id}/assets, field attachment). The real name goes in ?name=, which Forgejo takes before the form's file name, so the ASCII-only form filename is a sensible way to keep the multipart header simple. The multipart body is built with the stdlib only, and its boundary is random.
  • Sign-in: api_upload() gets its token through token() → auth.forge_session(), like api() and api_text(). A renewal therefore goes through the session lock and saves the new refresh token. The upload adds no second refresh path.
  • The guard holds. The name and the first bytes must agree on PNG, JPEG, GIF or WebP, so a .bin with PNG bytes or a .png with ELF bytes is refused. Each file is read once in binary and capped at 25 MiB, and the bytes that were checked are the bytes that go up. Every problem is refused before anything is posted, and the refusal points to the no-game-files rule. The module docstring, --help, docs/forge.md and the testing skill all say that screenshots are fine and that game files are not.
  • Errors: a failed upload stops with no retry, prints the comment's link and lists what went up, what failed and what wasn't tried. The partial edit still shows the pictures that did go up. The 120 s timeout is fine for 25 MiB.
  • Tests are added, not weakened. The stand-in now also blocks urlopen, and the real api_upload() form is parsed back with email.parser. main has not changed forge.py, docs/forge.md or the test since the fork point (fbd42024), so the "Rebased" box holds.

Should change (nit):

  • In the text, a reference stops at ). A file whose name has a parenthesis, such as shot (2).png, must be written percent-encoded, just like a name with a space. If it isn't, it is refused before posting, so nothing goes wrong, but the docs mention only spaces. See the inline comment.
Approve. I read the code and did not run it. This is a careful change, and it can merge as it is. What's good: - The upload uses Forgejo's comment-assets endpoint correctly (`POST /repos/{owner}/{repo}/issues/comments/{id}/assets`, field `attachment`). The real name goes in `?name=`, which Forgejo takes before the form's file name, so the ASCII-only form filename is a sensible way to keep the multipart header simple. The multipart body is built with the stdlib only, and its boundary is random. - Sign-in: `api_upload()` gets its token through `token()` → `auth.forge_session()`, like `api()` and `api_text()`. A renewal therefore goes through the session lock and saves the new refresh token. The upload adds no second refresh path. - The guard holds. The name and the first bytes must agree on PNG, JPEG, GIF or WebP, so a `.bin` with PNG bytes or a `.png` with ELF bytes is refused. Each file is read once in binary and capped at 25 MiB, and the bytes that were checked are the bytes that go up. Every problem is refused before anything is posted, and the refusal points to the no-game-files rule. The module docstring, `--help`, docs/forge.md and the testing skill all say that screenshots are fine and that game files are not. - Errors: a failed upload stops with no retry, prints the comment's link and lists what went up, what failed and what wasn't tried. The partial edit still shows the pictures that did go up. The 120 s timeout is fine for 25 MiB. - Tests are added, not weakened. The stand-in now also blocks `urlopen`, and the real `api_upload()` form is parsed back with `email.parser`. main has not changed `forge.py`, `docs/forge.md` or the test since the fork point (fbd42024), so the "Rebased" box holds. Should change (nit): - In the text, a reference stops at `)`. A file whose name has a parenthesis, such as `shot (2).png`, must be written percent-encoded, just like a name with a space. If it isn't, it is refused before posting, so nothing goes wrong, but the docs mention only spaces. See the inline comment.
@ -133,0 +134,4 @@
## Pictures in a comment
A test result or a review often needs screenshots: `pr comment --attach` puts them in the comment. In the text,
`![main @ 7afe6ae1](attach:main.png)` shows the file `main.png` there (a name with a space is written percent-encoded,
Owner

Nit: ATTACH_REF stops at ) as well as at whitespace, so a name with a parenthesis (shot (2).png) also has to be written percent-encoded (attach:shot%20%282%29.png). If it isn't, it's refused before posting, so nothing is lost. Maybe say "a name with a space or a parenthesis" here and in the docstring.

Nit: `ATTACH_REF` stops at `)` as well as at whitespace, so a name with a parenthesis (`shot (2).png`) also has to be written percent-encoded (`attach:shot%20%282%29.png`). If it isn't, it's refused before posting, so nothing is lost. Maybe say "a name with a space or a parenthesis" here and in the docstring.
HumanBean deleted branch feat/forge-comment-attach 2026-11-13 18:09:28 +00:00
Some checks are pending
CI / Conventional Commit title (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Blocked by required conditions
Third-party parts / The same as upstream (pull_request) Blocked by required conditions
Third-party parts / As their notes say (pull_request) Blocked by required conditions
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Blocked by required conditions
CI / Workflow lint (actionlint) (pull_request) Blocked by required conditions
PR description / Description follows the template (pull_request_target) Successful in 3s
Some workflows are waiting to be reviewed.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MagicBeanDev/Longshot!254
No description provided.