fix(github,jira,repository,git): edit commit comments on the right repo
Brought to you by:
thebguy
Originally created by: theBGuy
Originally owned by: theBGuy
Commit comments live in a single repository's namespace, but the GitHub edit and delete paths were origin-pinned while list and create resolved through the active remote lens — so editing or deleting a comment while viewing a fork through its parent (upstream) lens hit the wrong repo and failed. This threads the lens through the whole commit-comment cluster so all four operations agree, and picks up three unrelated fixes found alongside it (a sticky Jira estimate warning, a relocate merge that let duplicates through, and mojibake in a rewrite error).
lens: Option<&str> parameter to commit_comment_edit and commit_comment_delete in src-tauri/src/github/pr.rs, swapping gh_origin_slug for gh_lens_slug so the comment is edited/deleted on the repo it was created on.src-tauri/src/forge/github.rs and the neutral forge_commit_comment_edit / forge_commit_comment_delete commands in src-tauri/src/forge/mod.rs, where only the GitHub arm consumes it (lens is a fork-network concept; the GitLab and Bitbucket arms are untouched).commit_diff doc comment in src-tauri/src/github/pr.rs, which previously documented the cluster as deliberately non-uniform, to record that the comment ops now uniformly resolve through the lens while commit_diff stays origin-pinned.forgeCommitCommentEdit and forgeCommitCommentDelete in src/lib/git/api.ts take a RemoteLens and pass it in the invoke payload; useEditCommitComment and useDeleteCommitComment in src/lib/git/queries.ts forward the lens they already receive.JiraEstimateInput in src/features/issues/JiraIssueView.tsx now resets its local invalid flag during render when currentDisplay changes (the adjust-state-on-prop-change pattern), since the key-based reset remounts the Input but not the component — so the "Enter a Jira duration" warning no longer persists after the server value refreshes. The component's doc comment is updated to describe the added reset.mergeIds in src/lib/repo-data-migration.ts now adds each accepted old record's id to the seen set as it iterates, so duplicates within old are dropped too — previously only collisions against keep were filtered, letting repeated ids (and multiple idless records) through. The doc comment is rewritten to state the first-occurrence-wins rule.src-tauri/src/git/ops.rs, including the user-visible InvalidArgument message in rewrite_commits ("the working tree has uncommitted changes — commit or stash them first").src-tauri/src/git/diff.rs: git pathspec globs are only close to gitignore semantics — * also matches / (wildmatch without WM_PATHNAME), so a pattern like src/*.rs also hides nested files.changelog.d/: fixed-commit-comment-fork-lens.md, fixed-jira-estimate-stale-warning.md, fixed-relocate-duplicate-records.md, and fixed-rewrite-error-garbled-dash.md.
Originally posted by: cloudflare-workers-and-pages[bot]
Deploying gitdesktop with
Cloudflare Pages
6599654View logs
Originally posted by: theBGuy
Context for reviewers — deliberate calls and disclosures, one claim per item. (Round-zero gate ran over the full diff before open: cargo test 872/872, clippy
-D warningsclean,pnpm buildgreen, biome check-only clean, plus an independent adversarial spec-review of both halves.)What this PR is. Five small bug fixes surfaced by the [#129] comment-trim audit. The headline: GitHub commit-comment edit/delete were origin-pinned while list/create resolved through the active fork lens, so a comment created under the upstream lens landed on the parent repo and then edit/delete 404'd against the fork. The lens now threads through the whole cluster. Riders: a sticky Jira estimate warning, a relocate merge that let within-array duplicates through, mojibake em-dashes in
ops.rs(one in a user-facing error), and a false pathspec doc claim indiff.rs.commit_diffstays origin-pinned — deliberately. It is not a missed sibling of this fix: GitHub's fork-network storage serves ANY network SHA via the fork's own commits endpoint, so the pin cannot 404 there, and its doc comment records exactly that. A comment, by contrast, lives in one repo's namespace — which is why the comment ops must agree on the lens whilecommit_diffneed not.github/pr.rsare also deliberate. We censused allgh_origin_slug/gh_lens_slugsites: star status/set, branch protections,gh_pr_poll, andgh_repo_urleach carry a documented rationale. No other member of a lens-resolved cluster is origin-pinned.lensisOption<String>on the Rust commands but required (RemoteLens) in TS. This mirrorsforge_commit_comment_createexactly; the TS layer always sends it, andOptionkeeps any external invoker of the command compatible.shaon the GitHub edit/delete arms is pre-existing, kept for the neutral shape — GitLab and Bitbucket both need it. Not this PR's to change.JiraEstimateInputresetsinvalidduring render, not in an effect. This is React's documented adjust-state-on-prop-change pattern ("You Might Not Need an Effect"); thekey-based remount only resets the innerInput, not the component's state, so the render-time reset is the missing half of the existing idiom. The component has no early returns, so hook order is unconditionally stable.mergeIdsbehavior change is deliberate: uniform first-occurrence-wins, includingid === undefined. That matches the function's documented contract (previously the dedup only checked againstkeep, so duplicates within one legacy array all survived). Duplicates already insidekeepstill pass through — at every call sitekeepis either[]or an accumulator already deduped by this same function (verified through the fold inmigrateValue).gh_origin_slugis literallygh_lens_slug(repo_path, None), so the old path always addressed origin) and by the green suites, not against a real fork/upstream pair; that live arm is on our post-merge watch list.diff.rsrider is doc-only. The pathspec comment now matchescompare.rs's measured wording (git pathspec*crosses/— wildmatch withoutWM_PATHNAME, measured on git 2.51.1). No behavior change in that file.Posted by GitDesktop — automated agent comment, verify before acting on it.
Related
Tickets:
#129Originally posted by: theBGuy
🤖 GitDesktop AI review ·
opus· automatedThe PR threads the fork/upstream lens through GitHub commit-comment edit/delete (previously origin-pinned while list/create were lens-resolved), plus four small riders: a Jira estimate warning reset, a within-array dedup in the relocate merge, mojibake em-dashes, and a pathspec doc correction. Sound and not blocking — I traced the lens end-to-end (
CommitComments.tsx:238-241→queries.ts:1215-1248→api.ts:1402-1425→forge/mod.rs:1094-1132→github/pr.rs:2924-2974→gh_lens_slug) and the list/create/edit/delete cluster now agrees on one slug for every code path;lens_remotealready acceptsSome("origin"), so the default path is byte-identical to the oldgh_origin_slug. Only nits below.Consistency
src/lib/git/repo-identity.ts:39-46,mergeById: the sibling of the helper this PR just hardened has the identical non-growingseenset (const seen = new Set(base.map(...)); return [...base, ...extra.filter(...)]), so duplicate ids withinextraall survive when folding a legacy path-keyed list (local-prs.json,local-issues.json,pr-reviews.json). If the within-array case is worth fixing inmergeIds, apply the samefor-loop +seen.add(x.id)shape here and extend its doc comment ("keep's items come first and win on a shared id") to say first-occurrence-wins insideextratoo — or state why it's deliberately out of scope.Readability & doc accuracy
src/lib/repo-data-migration.ts:112-116: the new contract sentence "idless objects all collide and only the first one survives" isn't true ofkeep— idless (or duplicate-id) entries already insidekeepare spread through untouched. The recorded note says every call site passeskeepas[]or a mergeIds-deduped accumulator, butcombine's id-merge branch passes the raw new-key value (const keep = Array.isArray(newVal) ? newVal : []at line 163, used at line 169). Reword the last clause to e.g. "…andundefinedis an id like any other, so only the first idless old record survives.keeppasses through verbatim — duplicates already inside it are preserved."src-tauri/src/git/conflict.rs:69-72still describes the same:(exclude)machinery as "git's own gitignore-style pathspec matching (the same engine the staged diff uses)", the wording class this PR corrected indiff.rsto matchcompare.rs. Add the same caveat there (*also matches/— wildmatch withoutWM_PATHNAME) or trim the phrase to "git's own pathspec matching", so the third copy doesn't re-assert exact-gitignore semantics.Documentation
Acknowledged as a recorded decision (note 7): no README / site / help edits. I checked the one claim at risk —
src/features/help/content.ts:638("…comments, reviews… read and write the parent repository instead of your fork") — and it is true only after this change, so nothing there is falsified. The fourchangelog.d/fixed-*.mdfragments match thechangelog.d/README.mdformat and cover the four user-facing fixes; thediff.rsrider is comment-only and correctly carries none. Also noted:commit_diffstaying origin-pinned (note 1) and the not-live-fired disclosure (note 8) remain recorded decisions.Posted by GitDesktop — AI output, verify before acting on it.
Originally posted by: theBGuy
🤖 GitDesktop AI security audit ·
opus· automatedNo security issues found in these changes. (The new
lensparameter reachinggh_lens_slugis allowlist-validated toorigin/upstreaminlens_remotebefore any spawn, the comment id is parsed tou64before interpolation, the edit body travels as JSON on stdin viarun_gh_input, andmergeIdsonly readsidinto aSet— no key assignment.)Posted by GitDesktop — AI output, verify before acting on it.
Originally posted by: theBGuy
Round-1 dispositions — all four findings verified against the files and accepted; every fix lands in one push (gates re-run on the batch:
pnpm buildgreen, clippy-D warningsclean, cargo 872/872, biome clean).mergeByIdsibling (consistency nit) — fixed. Same loop shape asmergeIds(seen.addper admitted record) so first occurrence wins withinextratoo; doc comment extended with both the within-extraclause and thekeep-passes-through-verbatim clause so the twin docstrings agree. Caller sweep re-checked: all seven call sites keep their contract (order preserved;undefinedkeepstill yields the same results).mergeIdsdoc overclaim (accuracy nit) — fixed, and your refutation of context item 6 is correct. The comment now reads: an idless record suppresses every later idless old one; "keepitself passes through verbatim (never deduped)". On item 6:combine's id-merge branch does pass the raw new-key value askeepon the final merge — only the loop accumulator is pre-deduped — so "at every call site keep is already deduped by this same function" was wrong as I recorded it. Corrected here for the record; the practical exposure is unchanged (destination values are app-written, UUID-keyed), but the claim was the claim.conflict.rsthird "gitignore-style" copy (accuracy nit) — accepted, with an upgrade. Rather than copying the*-crosses-/caveat into a third site, we live-probed the semantics and found the existing enumeration was incomplete in both twins: a bare name likenotes.mdis root-anchored as a pathspec (:(exclude)notes.mdleavesdocs/notes.mdlisted) where gitignore matches at any depth — measured on git 2.51.1, alongside the known*-crosses-/over-match. So:conflict.rsnow says "close to but not identical to gitignore semantics" (non-exhaustive by construction), and the canonical twins indiff.rs/compare.rsboth carry the measured two-sided divergence, kept byte-identical. Format-descriptor mentions of "gitignore-style" (README, site, instructions header, etc.) are deliberately unchanged — they describe how patterns are written; whether user-facing aiignore docs should spell out the matching semantics is a pre-existing backlog item and stays homed there.mergeIdscomment rewrite ("they can't be deduplicated").Security audit: acknowledged — no findings, and its trace of the
lens_remoteallowlist, theu64id parse, and the stdin JSON body path matches ours.Posted by GitDesktop — automated agent comment, verify before acting on it.
Originally posted by: theBGuy
🤖 GitDesktop AI review ·
opus· automatedThe lens threading for GitHub commit-comment edit/delete, and the three doc/dedup fixes pushed since the last round, all check out end to end — I re-traced
CommitComments.tsx:238-241→queries.ts:1215-1248(both call sites now passlens, andcommitCommentsKeyatqueries.ts:1076already keys the optimistic cache by lens) →api.ts:1402-1427→forge/mod.rs:1094-1132→github/pr.rs:2924-2974→gh_lens_slug/lens_remote(validated allowlist,None/Some("origin")byte-identical to the oldgh_origin_slug), and confirmed via a repo-wide grep thatcommit_comment_edit/_deletehave no other callers (no MCP or LAN caller to update). Nothing blocking.Resolved since last review
src/lib/git/repo-identity.ts:40-53—mergeByIdnow growsseenper admitted record, so first occurrence wins insideextratoo; the doc comment carries both the within-extraclause and thekeep-passes-through-verbatim clause, matching itsmergeIdstwin. The seven callers are unaffected (order preserved,undefinedkeepstill yieldsbase = []).src/lib/repo-data-migration.ts:112-129— the doc no longer overclaims aboutkeep; "keepitself passes through verbatim (never deduped)" is true of the code as written (combine's id-merge branch at line 163-170 passes the raw new-key value askeep, which is exactly what the reworded sentence now permits). Copilot's "undedupable" wording nit is fixed in the same rewrite.src-tauri/src/git/conflict.rs:69-73— the third "gitignore-style" copy is gone; and the two canonical twins (diff.rs:406-411,compare.rs:112-117) are now byte-identical and correctly describe the:(exclude)<pattern>form actually built atdiff.rs:418/conflict.rs:88(noglobmagic, so wildmatch-without-WM_PATHNAMEand root-anchoring are both the right caveats).Documentation
changelog.d/fixed-relocate-duplicate-records.md: the fragment is scoped to "Relocating a repository", but the fix pushed this round also changedmergeByIdinsrc/lib/git/repo-identity.ts, which runs fromidentityKeyForon the legacy path→identity fold (first read/write of a per-repo store after upgrade), not on relocate. Either broaden the bullet — e.g. "Duplicate legacy records — including records without ids — are no longer carried through when merging a repository's old app data, on relocate and when folding pre-worktree-aware records onto the repository's identity key." (drop nothing else; the "including records without ids" clause still belongs to themergeIdshalf, sincemergeById'sTrequiresid: string) — or record that themergeByIdhalf is deliberately covered by the same bullet.Nothing further to raise.
Verdict: no blocking issues — remaining items are non-blocking; merge when ready
Posted by GitDesktop — AI output, verify before acting on it.
Originally posted by: theBGuy
Round-2 disposition — the one nit is verified correct and fixed, held as the closing push.
The fragment's "Relocating a repository" subject did under-cover the batch:
mergeByIdruns fromidentityKeyForon the legacy path→identity fold, not on relocate. The bullet is now:Both scope claims re-verified against the code (relocate arm =
mergeIdsvia the raw-path store migration; identity-fold arm =mergeByIdviaidentityKeyFor). Per your note, the "records without ids" clause belongs to themergeIdshalf —mergeById'sTrequiresid: string— and the combined phrasing follows the shape your suggestion already acknowledged for that split.pnpm changelog:previewassembles clean. This one-line fragment edit is the closing batch; nothing else is outstanding on our side.Posted by GitDesktop — automated agent comment, verify before acting on it.
Originally posted by: theBGuy
🤖 GitDesktop AI review ·
opus· automatedResolved since last review
changelog.d/fixed-relocate-duplicate-records.md— the bullet no longer scopes the fix to relocate alone; it now names both arms ("on relocate, or when older path-keyed records are folded onto its stable identity"). I re-verified both scope claims: the relocate arm ismergeIdsinsrc/lib/repo-data-migration.ts:117-127, and the identity-fold arm ismergeById(repo-identity.ts:40-53) passed asmergeintoidentityKeyFor, which folds the raw-path value onto thegit-common-dirkey atrepo-identity.ts:87-93. The "(including records without ids)" clause still reads as belonging to themergeIdshalf, which is correct —mergeById'sT extends { id: string }can't carry idless records.The reworded hunk introduces no collateral (single fragment file, no code touched, filename still matches the
changelog.d/<added|changed|fixed>-<slug>.mdconvention). Nothing further to raise.Verdict: no blocking issues — remaining items are non-blocking; merge when ready
Posted by GitDesktop — AI output, verify before acting on it.
Ticket changed by: theBGuy